Diff of /trunk/tubetutor/tubetutor.py [000000] .. [r1]  Maximize  Restore

Switch to side-by-side view

--- 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()