--- a +++ b/trunk/tubetutor/tubetutor.py @@ -0,0 +1,329 @@ +import os +import tkinter +import ffmpeg +import atexit +import keyboard +import shutil +import glob + +path="videos" +#resolution="3840:2160" +resolution="1920:1080" + +def id(filename): + id = os.path.basename(filename).split(".")[0] + return id + +def text_changed(event): + global path + filename=str(event.widget).split(".")[-1] + ".txt" + content=event.widget.get("1.0", tkinter.END) + f = open(os.path.join(path, "working", filename), "w") + if f: + f.write(content) + f.close() + +class TubeTutorWin: + files=[] + frames=[] + buttons={} + labels={} + images={} + canvases={} + textfields={} + lasttextfield="" + master=None + frame=None + lastid=0 + keyboardid=None + hotkey_stop = False + + def __init__(self, master, path): + self.master = master + self.path = path + master.title("TubeTutor - Create easy video tutorials") + self.refresh_files() + self.refresh_display() + self.textfields[self.lasttextfield].focus_set() + self.master.after(1000, self.poll) +# keyboard.add_hotkey('ctrl+<', self.hotkey) + keyboard.add_hotkey('ctrl+alt+r', self.hotkey) + + def poll(self): + # change color of record button, if ffmpeg is running + try: + if not self.orig_buttoncolor: + self.orig_buttoncolor = self.buttons["Record"].cget("background") + except: + pass + + if ffmpeg.is_recording(): + try: + self.buttons["Record"].configure(bg="red") + except: + pass + else: + try: + self.buttons["Record"].configure(bg=self.orig_buttoncolor) + except: + pass + # process record hotkey + if self.hotkey_stop: + print("hotkey polled") + self.hotkey_stop = False + self.refresh_files() + self.refresh_display() + self.textfields[self.lasttextfield].focus_force() + self.master.after(1000, self.poll) + + def hotkey(self): + global resolution + print("Hotkey pressed") + if ffmpeg.is_recording(): + ffmpeg.stop() + self.hotkey_stop = True + else: + ffmpeg.record(self.get_filename()) + + def get_filename(self, extension="mkv"): + global path + self.lastid += 10 + basename = "%04d.%s" % (self.lastid, extension) + return os.path.join(path, "working", basename) + + def set_filename(self, filename): + base = filename.split(".")[0] + self.lastid = int(base) + + def get_title(self): + title=self.textfields["title"].get("1.0", tkinter.END) + return title + + + def refresh_files(self): + self.files = [] + for root, dirs, files in os.walk(os.path.join(self.path, "working")): + for file in files: + if file.endswith(".mkv") or file.endswith(".txt"): + self.files.append(os.path.join(root, file)) + + # Output the main window + def refresh_display(self): + # remove all widgets + for w in self.buttons: + self.buttons[w].pack_forget() + self.buttons = {} + + for w in self.frames: + w.pack_forget() + self.frames = [] + + for w in self.labels: + self.labels[w].pack_forget() + self.labels = {} + + for w in self.textfields: + self.textfields[w].pack_forget() + self.textfields = {} + + for w in self.canvases: + self.canvases[w].pack_forget() + self.images = {} + self.canvases = {} + + self.lastid=0 + + + # re-add all widgets + self.add_label("description", text="Press <Ctrl+Alt+R> to start or end recording.") + + # Title of the video + self.start_frame() + self.add_label("title", "Title:", side=tkinter.LEFT) + self.add_text("title", "Video Tutorial", width=30, height=1, autosave=False, side=tkinter.LEFT) + self.add_button("Render", self.render, side=tkinter.LEFT) + self.add_button("Stash", self.stash, side=tkinter.LEFT) + self.end_frame() + + # Recording control panel + self.start_frame() + self.add_button("Record", self.record, side=tkinter.LEFT) + self.add_button("Stop", self.stop, side=tkinter.LEFT) + self.end_frame() + + file = self.get_filename("txt") + content="" + try: + f = open(file, "r") + if f: + content = f.read() + f.close() + except: + pass + self.add_text(id(file), content); + for file in self.files: + if os.path.basename(file).startswith("tmp_"): + continue + if file.endswith(".mkv"): + self.start_frame() + moviefile=file + self.add_label(id(file) + "_image", image=os.path.join("media", "movie.png"), side=tkinter.LEFT) + self.add_label(id(file), text=file, side=tkinter.LEFT) + self.add_button("del", lambda: self.delete(moviefile), side=tkinter.LEFT) + self.end_frame() + self.set_filename(os.path.basename(file)) + + file = self.get_filename("txt") + content="" + try: + f = open(file, "r") + if f: + content = f.read() + f.close() + except: + pass + self.add_text(id(file), content); + + + def add_image(self, name, filename, width=32, height=32, side=tkinter.TOP, fill=tkinter.NONE, expand=False): + self.images[name] = tkinter.PhotoImage(master=self.root(), file=filename) + + self.canvases[name] = tkinter.Canvas(self.root(), width=width, height=height) + self.canvases[name].create_image(0, 0, image=self.images[name]) + self.canvases[name].pack(side=side, fill=fill, expand=expand) + + def add_label(self, name, text="", image="", width=120, side=tkinter.TOP, fill=tkinter.NONE, expand=False): + if image != "": + self.images[name] = tkinter.PhotoImage(master=self.root(), file=image) + self.labels[name] = tkinter.Label(self.root(), width=width, text=text, image=self.images[name]) + else: + self.labels[name] = tkinter.Label(self.root(), text=text) + self.labels[name].pack(side=side, fill=fill, expand=expand) + + def add_text(self, name, content="", autosave=True, width=50, height=2, side=tkinter.TOP, fill=tkinter.NONE, expand=False): + self.textfields[name] = tkinter.Text(self.root(), name=name, width=width, height=height) + if content != "": + self.textfields[name].insert(tkinter.INSERT, content) + self.textfields[name].pack(side=side, fill=fill, expand=expand) + if autosave: + self.textfields[name].bind("<KeyRelease>", text_changed) + self.lasttextfield = name + + def add_button(self, name, fn, side=tkinter.TOP, fill=tkinter.NONE, expand=False): + self.buttons[name] = tkinter.Button(self.root(), text=name, command=fn) + self.buttons[name].pack(side=side, fill=fill, expand=expand) + + def start_frame(self, side=tkinter.TOP, fill=tkinter.NONE, expand=False): + self.frame = tkinter.Frame(self.master) + self.frame.pack(side=side, fill=fill, expand=expand) + self.frames.append(self.frame) + + def end_frame(self): + self.frame = None + + def root(self): + if self.frame: + return self.frame + else: + return self.master + + def record(self): + ffmpeg.record(self.get_filename()) + + def stop(self): + ffmpeg.stop() + self.refresh_files() + self.refresh_display() + + def render(self): + global resolution + self.refresh_files() + videos=[] + description="" + + # intro + r = ffmpeg.renderer() + r.resolution(resolution) + r.filename(os.path.join("media", "intro.mkv")) + params = " -scale %s!" % resolution.replace(":", "x") + params += " -font %s -weight 500 -pointsize 100" % ffmpeg.font + params += " -draw \"gravity northwest fill white text 100,775 '%s'\"" % self.get_title() + params += " -scale %s!" % resolution.replace(":", "x") + ffmpeg.convert_image(os.path.join("media", "intro.jpg"), os.path.join(path, "tmp", "intro.jpg"), params) + r.add_image(os.path.join(path, "tmp", "intro.jpg"), fadein=False) + r.process(os.path.join(path, "tmp", "intro-scale.mkv"), "-vf scale=" + resolution) + r.concat(os.path.join(path, "tmp", "intro-concat.mkv")) + + # mux intro + r = ffmpeg.renderer() + r.resolution(resolution) + r.filename(os.path.join(path, "tmp", "intro-concat.mkv")) + r.mux(os.path.join(path, "tmp", "intro.mkv"), os.path.join("media", "intro.mp3")) + videos.append(r.lastfilename) + + # main + r = ffmpeg.renderer() + r.resolution(resolution) + for file in self.files: + if os.path.basename(file).startswith("tmp_"): + continue + if file.endswith(".txt"): + description += "- %s" % r.add_text(file) + if file.endswith(".mkv"): + r.add_video(file) + r.concat(os.path.join(path, "tmp", "concat.mkv")) + r.speedup(os.path.join(path, "tmp", "speedup.mkv"), 0.7) + r.mux(os.path.join(path, "tmp", "main.mkv"), os.path.join("media", "back.mp3")) + videos.append(r.lastfilename) + + # outro + r = ffmpeg.renderer() + r.resolution(resolution) + r.filename(os.path.join("media", "outro.mkv")) + r.process(os.path.join(path, "tmp", "outro.mkv"), "-vf scale=" + resolution) + videos.append(r.lastfilename) + + # mastering + outputfilename=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "") + ".mkv" + r = ffmpeg.renderer() + r.resolution(resolution) + for file in videos: + r.add_video(file) + r.concat(os.path.join(path, outputfilename), audio=True) + + # write description + outputfilename=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "") + ".txt" + f = open(os.path.join("media", "legal.txt"), "r") + if f: + description += f.read() + f.close() + + f = open(os.path.join(path, outputfilename), "w") + if f: + f.write(description) + f.close() + + + def delete(self, filename): + os.remove(filename) + self.refresh_files() + self.refresh_display() + + def stash(self): + dirname=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "") + if not os.path.exists(os.path.join(path, dirname)): + os.makedirs(os.path.join(path, dirname)) + for filename in glob.glob(os.path.join(path, "working", "*")): + shutil.move(filename, os.path.join(path, dirname)) + self.refresh_files() + self.refresh_display() + + def quit(self, event): + ffmpeg.stop() + + +root = tkinter.Tk() + +win = TubeTutorWin(root, path) +root.bind("<Destroy>", win.quit) +root.mainloop()