From f735f00f86a871245f72329d20a7381a55529079 Mon Sep 17 00:00:00 2001 From: "Jeison Yehuda Amihud (Blender Dumbass)" Date: Wed, 23 Dec 2020 13:56:18 +0000 Subject: [PATCH] Upload files to 'studio' --- studio/bpy_get_render_settings.py | 22 + studio/studio_dialogs.py | 32 ++ studio/studio_gtk.py | 17 +- studio/studio_renderLayer.py | 727 ++++++++++++++++++++++++++++++ studio/studio_scriptLayer.py | 9 +- studio/studio_storyLayer.py | 25 +- 6 files changed, 828 insertions(+), 4 deletions(-) create mode 100644 studio/bpy_get_render_settings.py create mode 100644 studio/studio_renderLayer.py diff --git a/studio/bpy_get_render_settings.py b/studio/bpy_get_render_settings.py new file mode 100644 index 0000000..4c5ac86 --- /dev/null +++ b/studio/bpy_get_render_settings.py @@ -0,0 +1,22 @@ +# THIS FILE IS A PART OF VCStudio +# PYTHON 3 + +############################################################################# + +# This file is going to output render setting from the blend file to our +# renderer. NOTE: This file is using BPY module that is running inside +# blender and is not going to run on standard python3. + +############################################################################# + +import bpy + +# The following print() commands are going to be piped into VCStudio. +# You can extend the data by adding more lines here. Keep in mind that +# you have to also write support for the lines you add into the render layer. +# See: +# studio/studio_renderLayer.py + +print("Start_frame :", bpy.context.scene.frame_start) +print("End_frame :", bpy.context.scene.frame_end) + diff --git a/studio/studio_dialogs.py b/studio/studio_dialogs.py index 9fdbbe6..279bdfa 100644 --- a/studio/studio_dialogs.py +++ b/studio/studio_dialogs.py @@ -74,6 +74,7 @@ from studio import studio_file_selectLayer from studio import studio_asset_selectLayer from studio import studio_shot_linkLayer from studio import studio_asset_configureLayer +from studio import studio_renderLayer ################# @@ -240,6 +241,37 @@ def asset_configure(win, name, call, asset, force=False): } + # Wiping the history of the assets. See studio/studio_asset_selectLayer.py + win.assets = {} + + # Let's clear the LMB just in case + win.previous["LMB"] = False + +def render(win, name, call, filename="", force=False): + + # This function going to launch a window that shows all current renders and + # confuge them. + + print("Test 2") + + if name not in win.current["calls"]: + win.current["calls"][name] = { + "var" :None, # This is the variable that we are waiting for + "call":call, # This is what it's going to run when it's done + "url" :"render", + "back":win.url,# This is where it's going to come back when it's done + "draw":studio_renderLayer.layer + } + + # let's prepare the data for this operation + + if force or "renders_window" not in win.current\ + or win.current["renders_window"]["filename"] != filename: + win.current["renders_window"] = { + "filename":filename + } + + # Wiping the history of the assets. See studio/studio_asset_selectLayer.py win.assets = {} diff --git a/studio/studio_gtk.py b/studio/studio_gtk.py index 99eb929..fb0ac22 100644 --- a/studio/studio_gtk.py +++ b/studio/studio_gtk.py @@ -32,6 +32,9 @@ from UI import UI_testing from UI import UI_color from UI import UI_elements +# Network +from network import network_renders + def previous(win): win.previous = {} for i in win.current: @@ -94,6 +97,7 @@ def run(project, win): win.layercashe = {} # Here I gonna store layers that are inactive to speed up stuff win.checklists = {} win.blink = False # Cursor blinking thing. + win.renders = {} # List of current active renders. if pm_project.is_legacy(project): win.story = story.get_legacy(project) @@ -290,7 +294,7 @@ def pmdrawing(pmdrawing, main_layer, win): del win.current["calls"][call] except: - pass + raise() # WHILE DEVELOPING DIALOGS SET THIS TO raise() Layers.append([UI_testing.layer(win)]) Layers.append([win.tooltip_surface]) @@ -323,6 +327,12 @@ def pmdrawing(pmdrawing, main_layer, win): win.current["tool"] = "selection" win.current["keys"] = [] win.textactive = "" + + # For the rendering I will need to read render info at each frame and parse + # it in various windows. + + network_renders.read_renders(win) + # There is a but in the Gnome I guess. That autopresses the Shift and Ctrl # keys when you scroll to different windows. So here is a little fix. @@ -389,6 +399,11 @@ def key_release(widget, event, win): except: win.current["keys"] = [] + # I also want to clean the key letter. Because other wise in the + # script writer I had weird key presses all the time. + + win.current["key_letter"] = "" + def scrolling(widget, event, win): e, x, y = event.get_scroll_deltas() win.current["scroll"] = [x,y] diff --git a/studio/studio_renderLayer.py b/studio/studio_renderLayer.py new file mode 100644 index 0000000..58cc0b0 --- /dev/null +++ b/studio/studio_renderLayer.py @@ -0,0 +1,727 @@ +# THIS FILE IS A PART OF VCStudio +# PYTHON 3 + +import os +import datetime +import json +from subprocess import * + +# GTK module ( Graphical interface +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import Gdk +import cairo + +# Own modules +from settings import settings +from settings import talk +from settings import fileformats +from settings import oscalls +from project_manager import pm_project + +#UI modules +from UI import UI_elements +from UI import UI_color +from UI import UI_math + +# Studio +from studio import studio_dialogs +from studio import analytics +from studio import story + +# Network / Rendering +from network import network_renders + +def save_settings(win, filename): + + ############################################################################ + + # This function will save the render settings file. + + ############################################################################ + + folder = filename[:filename.rfind("/")]+"/extra" + savefile = folder+filename[filename.rfind("/"):]+".json" + + # First let's make sure that the extra folder exists. + + try: + os.makedirs(win.project+folder) + except: + pass + + # Then let's write the file in there. + + with open(win.project+savefile, 'w') as fp: + json.dump(win.renders[filename], fp, sort_keys=True, indent=4) + + +def layer(win, call): + + ########################################################################## + + # This file will setup and manage rendering of shots. It's a bit complex + # in function. I have 2 network scripts at the moment. And it might grow + # beyond that. + + # See: + # network/during_render.py + # network/network_renders.py + + # This file is the UI part of the process. ( The maing UI ) Some beats and + # peaces will exists in various windows through out the program. + + ########################################################################## + + + # Making the layer + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, win.current['w'], + win.current['h']) + layer = cairo.Context(surface) + + + #text setting + layer.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) + + UI_color.set(layer, win, "dark_overdrop") + layer.rectangle( + 0, + 0, + win.current["w"], + win.current["h"], + ) + layer.fill() + + + + UI_color.set(layer, win, "node_background") + UI_elements.roundrect(layer, win, + win.current["w"]/2-250, + 100, + 500, + win.current["h"]-200, + 10) + + + + is_rendering = False # This will determen whether + + for render in win.renders: + if win.renders[render]["rendering"]: + is_rendering = True + + + if not is_rendering: + # Render button + def do(): + + # This here will launch a script that will be on it's on from now + # on. See: + # network/during_render.py + + Popen(["python3", "network/during_render.py", win.project, oscalls.get_current_blender(win)]) + + + UI_elements.roundrect(layer, win, + win.current["w"]/2-20, + win.current["h"]-140, + 40, + 40, + 10, + button=do, + icon="right") + else: + # Stop Render button + + def do(): + + network_renders.stop_render(win) + + + UI_elements.roundrect(layer, win, + win.current["w"]/2-20, + win.current["h"]-140, + 40, + 40, + 10, + button=do, + icon="stop") + + # Exit button + def do(): + win.current["calls"][call]["var"] = False + + + UI_elements.roundrect(layer, win, + win.current["w"]/2+210, + win.current["h"]-140, + 40, + 40, + 10, + button=do, + icon="cancel", + tip=talk.text("cancel")) + + + x = win.current["w"]/2-250 + 10 + y = 100 + 10 + width = 500 - 20 + height = win.current["h"]-200 - 20 + + UI_elements.roundrect(layer, win, + x, + y, + width, + height-60, + 10, + fill=False) + layer.clip() + + clip = [ + x, + y, + width, + height-60] + + + + + # Let's get the filename of the current file that we want to setup. + filename = win.current["renders_window"]["filename"] + + # So this window could be accessed from both main window and from the script. + # One is to see where each render is. And ther other is to configure and add + # renders to the list. + + if filename: + + # Basically if any file is inputted. It means that it's to configure. + # but sometimes. ( I'm tyring to see myself using it ) the user will + # click on the render button to just access the render. And to hell with + # it. Let's make the filename variable the selection of the file. + + if filename not in win.renders: + + # So on the initialization we want to do a couple of things. + # First of which will be to get render settings data from the + # blend file. + + # I think a quick bpy script could do. + # See: + # studio/bpy_get_render_settings.py + + blenderpath = oscalls.get_current_blender(win) + blend = win.project+filename + + checkframes = Popen([blenderpath, "-b", blend , "-P", + os.getcwd()+"/studio/bpy_get_render_settings.py"],stdout=PIPE, universal_newlines=True) + checkframes.wait() + checkstring = checkframes.stdout.read() + + # We are going to need the following options. + + start_frame = 0 + end_frame = 250 + image_format = "PNG" + save_folder = "storyboard" + + + for line in checkstring.split("\n"): + + if line.startswith("Start_frame"): + try: + start_frame = int(line[line.find(":")+1:]) + except: + pass + + if line.startswith("End_frame"): + try: + end_frame = int(line[line.find(":")+1:]) + except: + pass + + # Now since we've got the data. Let's write it to the dictionary first. + + win.renders[filename] = { + "start_frame" :start_frame , # The frame we want to start on + "end_frame" :end_frame , # The frame we want to end on + "image_format" :image_format, # What format to save the images + "save_folder" :save_folder , # Into what folder to save images + "clean_folder" :False , # Whether to delete current frames before rendering + "current_frame":0 , # What is current frame rendering + "rendering" :False , # Whether it's currently rendering + "analytics" :{} # Times of each frame + } + + # Now in order not to loose the data immediatly. We are going to need + # to add the filename into a special file. + + s = open(win.project+"/set/active_renders.data", "a") + s.write(filename+"\n") + s.close() + + # Also we want to make a little json file in the extra folder of + # the shot. This will contain our settings. And the file will be + # read by the renderer script while it's running. It has to be on + # it's own. So it's good to have extendable files. + + save_settings(win, filename) + + # Now let's get to the actuall UI of the stuff. Basically I want it to + # always give us a list of the currently set renders. And one of them + # might be selected and expendet to see they settings / current data. + + # Setting up the scroll + if "render" not in win.scroll: + win.scroll["render"] = 0 + + current_Y = 0 + + # So let's do this. + + is_rendering = False # This will determen whether + + # Before we dive into settings and graphs. Let's make a deletion + if 65535 in win.current["keys"] and not win.renders[win.current["renders_window"]["filename"]]["rendering"]: + try: + del win.renders[win.current["renders_window"]["filename"]] + + active_renders = open(win.project+"/set/active_renders.data") + active_renders = active_renders.read() + active_renders = active_renders.split("\n") + + s = open(win.project+"/set/active_renders.data", "w") + + for i in active_renders: + if i != win.current["renders_window"]["filename"] and i: + s.write(i+"\n") + + s.close() + + except: + pass + win.current["renders_window"]["filename"] = "" + win.current["keys"] = [] + + for render in win.renders: + + # Let's get a shot name for each render. + + shot = render[:render.rfind("/")].replace("/rnd", "") + blend = render[render.rfind("/"):] + + tip = (shot+blend).replace("/", "", 1).replace("/", " | ") + + if win.renders[render]["rendering"]: + tip = win.renders[render]["rendering"] + + def do(): + win.current["renders_window"]["filename"] = render + + UI_elements.roundrect(layer, win, + x, + y+current_Y+win.scroll["render"], + width, + 80, + 10, + button=do, + tip=tip, + clip=clip) + + # I think the render logo could be cool. + + UI_elements.image(layer, win, + "settings/themes/"+win.settings["Theme"]+"/icons/render.png", + x+5, y+current_Y+win.scroll["render"]+5, 40, 40) + + # And the name of the shot + + UI_color.set(layer, win, "text_normal") + layer.set_font_size(20) + layer.move_to(x+60, + y+current_Y + win.scroll["render"]+30) + layer.show_text((shot+blend).replace("/", "", 1).replace("/", " | ")) + + # And let's draw the fraction + + fraction = (win.renders[render]["current_frame"] - win.renders[render]["start_frame"])\ + / (win.renders[render]["end_frame"] - win.renders[render]["start_frame"]) + + fraction = min(1, fraction) + + UI_color.set(layer, win, "progress_background") + UI_elements.roundrect(layer, win, + x+20, + y+current_Y + win.scroll["render"]+55, + width-40, + 0, + 5) + + UI_color.set(layer, win, "progress_active") + UI_elements.roundrect(layer, win, + x+20, + y+current_Y + win.scroll["render"]+55, + (width-40)*fraction, + 0, + 5) + + # Now selection. When you click on one you expend it. And you can see + # what settings are inside. + + if win.current["renders_window"]["filename"] == render: + + UI_color.set(layer, win, "progress_background") + UI_elements.roundrect(layer, win, + x, + y+current_Y+win.scroll["render"], + width, + 80, + 10, + fill=False) + layer.stroke() + + current_Y = current_Y + 90 + + + + # We are going to have 2 differnt UI's for those who are rendering + # and those who are not rendering. + + if not win.renders[render]["rendering"]: + + # We need to choose the folder of where the given render will be done. + # For the user tho I don't think nessesary to understand that it's a + # folder nessesary. They will see 4 circles. 4 render qualities. And + # will decide stuff based on that. + + fouricons = [ "storyboard", "opengl", "test_rnd", "rendered"] + + for num, f in enumerate(fouricons): + if f == win.renders[render]["save_folder"]: + + UI_color.set(layer, win, "progress_time") + UI_elements.roundrect(layer, win, + x+20+(40*num), + y+current_Y + win.scroll["render"], + 40, + 40, + 10) + + def do(): + win.renders[render]["save_folder"] = f + save_settings(win, render) + + UI_elements.roundrect(layer, win, + x+20+(40*num), + y+current_Y + win.scroll["render"], + 40, + 40, + 10, + button=do, + icon=f, + tip=f) + + # Here also I want to have a little clean icon. This will make sure + # to clean the current frames that are currently inside the folder. + # I know it's slightly counter intuitive compared to the rest of + # the program in that it's better not to give the user the ability + # to delete any actuall files. But in this case it makes a bit + # sense. Since for the user rendering again is like replacing the + # previous files with new ones. And the algorythm always renders + # from the last frame in the folder. So it never deletes anything + # by defaul. So I guess a button to clean frames could be a good + # thing. + + if win.renders[render]["clean_folder"]: + + UI_color.set(layer, win, "progress_time") + UI_elements.roundrect(layer, win, + x+width-40, + y+current_Y + win.scroll["render"], + 40, + 40, + 10) + + def do(): + win.renders[render]["clean_folder"] = not win.renders[render]["clean_folder"] + save_settings(win, render) + + UI_elements.roundrect(layer, win, + x+width-40, + y+current_Y + win.scroll["render"], + 40, + 40, + 10, + button=do, + icon="clean", + tip=talk.text("clean_render_folder")) + + + current_Y = current_Y + 50 + + # I will give 4 formats to save the renders too. The user will have + # to select 1 of those 4. + + # PNG: The good old PNG. Which is an open format and is widly used + # in GNU / Linux operating system. Also it has lossless compre- + # ssion and an alha channel to store transparent parts. + + # JPEG: A standard JPEG image type. Widly used to store photos. It + # content aware compression. Which is lossy, but more effective + # then PNG. Tho JPEG lacks alpha channel. + + # EXR: In comparison to PNG or JPEG EXR format stores way broader + # range of values for each channel. Not simply from black to + # white. But also values that can be less then black and more + # then white. Giving you a High Dynamic Range look. And a + # possibility to store data such as Z depth. + + # HDR: Similar to EXR. But more industry standard. And has bit more + # effective compression. + + # I will not add any video files since the algoryhm will require + # actuall frames to be stored as separate files. This will insure + # that ALL frames were rendered. And in case of crash the algorythm + # will pick up from a previous frame in a folder. With video is not + # only impossible. But in case of a crash with video all the previous + # frames will be lost. + + formats = ["JPEG", "PNG", "EXR", "HDR"] + + for num, f in enumerate(formats): + + if f == win.renders[render]["image_format"]: + + UI_color.set(layer, win, "progress_time") + UI_elements.roundrect(layer, win, + x+20+(80*num), + y+current_Y + win.scroll["render"], + 70, + 40, + 10) + + def do(): + win.renders[render]["image_format"] = f + save_settings(win, render) + + UI_elements.roundrect(layer, win, + x+20+(80*num), + y+current_Y + win.scroll["render"], + 70, + 40, + 10, + button=do) + + UI_color.set(layer, win, "text_normal") + layer.set_font_size(20) + layer.move_to(x+30+(80*num), + y+current_Y + win.scroll["render"]+30) + layer.show_text(f) + + current_Y = current_Y + 50 + + # Now let's put the settings them selves. + # First thing is we need start and end frames. And we also need it + # somehow readable for the user. I think a schedule logo would + # go fine here. + + UI_elements.image(layer, win, + "settings/themes/"+win.settings["Theme"]+"/icons/schedule.png", + x+5, y+current_Y+win.scroll["render"], 40, 40) + + # And the stuff it self + + # I will probably need this. + def is_number(string): + try: + int(string) + return True + except: + return False + + # START FRAME + + UI_elements.text(layer, win, render+"start_frame", + x+60, + y+current_Y+win.scroll["render"], + 200, + 40, + set_text=str(win.renders[render]["start_frame"])) + + if win.text[render+"start_frame"]["text"] != str(win.renders[render]["start_frame"])\ + and is_number(win.text[render+"start_frame"]["text"]): + def do(): + win.renders[render]["start_frame"] = int(win.text[render+"start_frame"]["text"]) + save_settings(win, render) + + + UI_elements.roundrect(layer, win, + x+215, + y+current_Y+win.scroll["render"], + 40, + 40, + 10, + button=do, + icon="ok", + tip=talk.text("checked")) + + # END FRAME + UI_elements.text(layer, win, render+"end_frame", + x+60+210, + y+current_Y+win.scroll["render"], + 200, + 40, + set_text=str(win.renders[render]["end_frame"])) + + if win.text[render+"end_frame"]["text"] != str(win.renders[render]["end_frame"])\ + and is_number(win.text[render+"end_frame"]["text"]): + def do(): + win.renders[render]["end_frame"] = int(win.text[render+"end_frame"]["text"]) + save_settings(win, render) + + UI_elements.roundrect(layer, win, + x+215+210, + y+current_Y+win.scroll["render"], + 40, + 40, + 10, + button=do, + icon="ok", + tip=talk.text("checked")) + + + current_Y = current_Y + 50 + + + else: + + # And here comes the UI of when it's during RENDERING + + # First thing will be to draw a little graph. This will show current + # frame and analytics data about render times. + + UI_color.set(layer, win, "dark_overdrop") + layer.rectangle( + x+5, + y+current_Y+win.scroll["render"], + width-10, + 100) + layer.fill() + + for frame in range(win.renders[render]["start_frame"], win.renders[render]["end_frame"]+1): + + numofis = win.renders[render]["end_frame"] - win.renders[render]["start_frame"] + + if frame == win.renders[render]["current_frame"]: + + UI_color.set(layer, win, "progress_time") + layer.rectangle( + x+5+(width-10)/numofis*(frame-1), + y+current_Y+win.scroll["render"], + (width-10)/numofis, + 100) + layer.fill() + + # Now I want to be able to interact with the graph. For this I want to + # add a little mouse sensitive region. That will give me data about the + # current frame. In a tooltip? + + if int(win.current["mx"]) in range(int(x+5+(width-10)/numofis*frame),int(x+5+(width-10)/numofis*frame+(width-10)/numofis))\ + and int(win.current["my"]) in range(int(y+current_Y+win.scroll["render"]), int(y+current_Y+win.scroll["render"]+100)): + + UI_color.set(layer, win, "progress_background") + layer.move_to(x+5+(width-10)/numofis*(frame-0.5), + y+current_Y+win.scroll["render"] + ) + layer.line_to( + x+5+(width-10)/numofis*(frame-0.5), + y+current_Y+win.scroll["render"]+100 + ) + layer.stroke() + if win.renders[render]["save_folder"] in win.renders[render]["analytics"]: + if str(frame) in win.renders[render]["analytics"][win.renders[render]["save_folder"]]: + + value = win.renders[render]["analytics"][win.renders[render]["save_folder"]][str(frame)] + + UI_elements.tooltip(win, UI_math.timestring(value/1000000)) + + # Now let's draw a graph. I love graphs. They are cool AF. First of all tho + # we need to know the maximum value. Because who know how long was the render + + mx = 0 + allvalues = [] + if win.renders[render]["save_folder"] in win.renders[render]["analytics"]: + for v in win.renders[render]["analytics"][win.renders[render]["save_folder"]]: + mx = max(win.renders[render]["analytics"][win.renders[render]["save_folder"]][v], mx) + allvalues.append(win.renders[render]["analytics"][win.renders[render]["save_folder"]][v]) + + UI_color.set(layer, win, "progress_background") + layer.move_to(x+5, y+current_Y+win.scroll["render"]+100) + for frame in range(win.renders[render]["start_frame"], win.renders[render]["end_frame"]+1): + numofis = win.renders[render]["end_frame"] - win.renders[render]["start_frame"] + if win.renders[render]["save_folder"] in win.renders[render]["analytics"]: + if str(frame) in win.renders[render]["analytics"][win.renders[render]["save_folder"]]: + + value = win.renders[render]["analytics"][win.renders[render]["save_folder"]][str(frame)] + + layer.line_to( + x+5+(width-10)/numofis*(frame-0.5), + (y+current_Y+win.scroll["render"]+100)-(100/mx*value) + ) + layer.stroke() + + current_Y = current_Y + 110 + + # Now let's put out some data in the text format. For the user to + # know stuff he or she might want to know. + + # AVARAGE TIME + + avarage = 0 + try: + avarage = sum(allvalues) / len(allvalues) + except: + pass + + UI_color.set(layer, win, "text_normal") + layer.set_font_size(20) + layer.move_to(x+10, + y+current_Y + win.scroll["render"]+30) + layer.show_text(talk.text("render_avarage_time")+" "+UI_math.timestring(avarage/1000000)) + + current_Y = current_Y + 40 + + # REMAINING TIME + + remaining = avarage * (win.renders[render]["end_frame"] - win.renders[render]["current_frame"]) + + UI_color.set(layer, win, "text_normal") + layer.set_font_size(20) + layer.move_to(x+10, + y+current_Y + win.scroll["render"]+30) + layer.show_text(talk.text("render_remaining_time")+" "+UI_math.timestring(remaining/1000000)) + + current_Y = current_Y + 40 + + + else: + + current_Y = current_Y + 85 + + ########################### + + UI_elements.scroll_area(layer, win, "render", + x, + y, + width, + height-60, + current_Y, + bar=True, + mmb=True, + url="render" + ) + + + return surface diff --git a/studio/studio_scriptLayer.py b/studio/studio_scriptLayer.py index fe78929..40f8df0 100644 --- a/studio/studio_scriptLayer.py +++ b/studio/studio_scriptLayer.py @@ -2263,7 +2263,9 @@ def layer(win): 40, 20, button=do, - icon=icon) + icon=icon, + tip=icon) + current_Y_shots = current_Y_shots + 50 @@ -2615,7 +2617,10 @@ def layer(win): # Render def do(): - print("Render") + def after(win, var): + pass + + studio_dialogs.render(win, "render_setup", after, "/rnd"+win.cur+"/"+blend) UI_elements.roundrect(layer, win, x+tileX+65, diff --git a/studio/studio_storyLayer.py b/studio/studio_storyLayer.py index 2569b03..857017d 100644 --- a/studio/studio_storyLayer.py +++ b/studio/studio_storyLayer.py @@ -493,7 +493,10 @@ def layer(win): # Renders def do(): - print("Renders") + def after(win, var): + pass + + studio_dialogs.render(win, "current_renders", after) UI_elements.roundrect(layer, win, 5, @@ -506,6 +509,26 @@ def layer(win): talk.text("render_lists_tooltip"), url="story_editor") + # Let's draw on top of this button a little indicator of how many renders + # are currently setup. + + if win.renders: + count = str(len(win.renders)) + + UI_color.set(layer, win, "node_background") + UI_elements.roundrect(layer, win, + 30, + 405, + len(count)*12+6, + 25, + 5) + layer.fill() + UI_color.set(layer, win, "text_normal") + layer.set_font_size(20) + layer.move_to(33,425) + layer.show_text(count) + + # Edit Video def do(): print("Edit Video")