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

Switch to unified view

a b/trunk/tubetutor/tubetutor.py
1
import os
2
import tkinter
3
import ffmpeg
4
import atexit
5
import keyboard
6
import shutil
7
import glob
8
9
path="videos"
10
#resolution="3840:2160"
11
resolution="1920:1080"
12
13
def id(filename):
14
    id = os.path.basename(filename).split(".")[0]
15
    return id
16
17
def text_changed(event):
18
    global path
19
    filename=str(event.widget).split(".")[-1] + ".txt"
20
    content=event.widget.get("1.0", tkinter.END)
21
    f = open(os.path.join(path, "working", filename), "w")
22
    if f:
23
        f.write(content)
24
        f.close()
25
26
class TubeTutorWin:
27
    files=[]
28
    frames=[]
29
    buttons={}
30
    labels={}
31
    images={}
32
    canvases={}
33
    textfields={}
34
    lasttextfield=""
35
    master=None
36
    frame=None
37
    lastid=0
38
    keyboardid=None
39
    hotkey_stop = False
40
    
41
    def __init__(self, master, path):
42
        self.master = master
43
        self.path = path
44
        master.title("TubeTutor - Create easy video tutorials")
45
        self.refresh_files()
46
        self.refresh_display()
47
        self.textfields[self.lasttextfield].focus_set()
48
        self.master.after(1000, self.poll)
49
#       keyboard.add_hotkey('ctrl+<', self.hotkey)
50
        keyboard.add_hotkey('ctrl+alt+r', self.hotkey)
51
52
    def poll(self):
53
        # change color of record button, if ffmpeg is running
54
        try:
55
            if not self.orig_buttoncolor:
56
                self.orig_buttoncolor = self.buttons["Record"].cget("background")
57
        except:
58
            pass            
59
60
        if ffmpeg.is_recording():
61
            try:
62
                self.buttons["Record"].configure(bg="red")
63
            except:
64
                pass
65
        else:
66
            try:
67
                self.buttons["Record"].configure(bg=self.orig_buttoncolor)
68
            except:
69
                pass
70
        # process record hotkey
71
        if self.hotkey_stop:
72
            print("hotkey polled")
73
            self.hotkey_stop = False
74
            self.refresh_files()
75
            self.refresh_display()
76
            self.textfields[self.lasttextfield].focus_force()
77
        self.master.after(1000, self.poll)
78
79
    def hotkey(self):
80
        global resolution
81
        print("Hotkey pressed")
82
        if ffmpeg.is_recording():
83
            ffmpeg.stop()
84
            self.hotkey_stop = True
85
        else:
86
            ffmpeg.record(self.get_filename())
87
88
    def get_filename(self, extension="mkv"):
89
        global path
90
        self.lastid += 10
91
        basename = "%04d.%s" % (self.lastid, extension)
92
        return os.path.join(path, "working", basename)
93
94
    def set_filename(self, filename):
95
        base = filename.split(".")[0]
96
        self.lastid = int(base)
97
98
    def get_title(self):
99
        title=self.textfields["title"].get("1.0", tkinter.END)
100
        return title
101
102
103
    def refresh_files(self):
104
        self.files = []
105
        for root, dirs, files in os.walk(os.path.join(self.path, "working")):
106
            for file in files:
107
                if file.endswith(".mkv") or file.endswith(".txt"):
108
                    self.files.append(os.path.join(root, file))
109
110
    # Output the main window
111
    def refresh_display(self):
112
        # remove all widgets
113
        for w in self.buttons:
114
            self.buttons[w].pack_forget()
115
        self.buttons = {}
116
117
        for w in self.frames:
118
            w.pack_forget()
119
        self.frames = []
120
121
        for w in self.labels:
122
            self.labels[w].pack_forget()
123
        self.labels = {}
124
125
        for w in self.textfields:
126
            self.textfields[w].pack_forget()
127
        self.textfields = {}
128
129
        for w in self.canvases:
130
            self.canvases[w].pack_forget()
131
        self.images = {}
132
        self.canvases = {}
133
134
        self.lastid=0
135
136
137
        # re-add all widgets
138
        self.add_label("description", text="Press <Ctrl+Alt+R> to start or end recording.")
139
140
        # Title of the video
141
        self.start_frame()
142
        self.add_label("title", "Title:", side=tkinter.LEFT)
143
        self.add_text("title", "Video Tutorial", width=30, height=1, autosave=False, side=tkinter.LEFT)
144
        self.add_button("Render", self.render, side=tkinter.LEFT)
145
        self.add_button("Stash", self.stash, side=tkinter.LEFT)
146
        self.end_frame()
147
148
        # Recording control panel
149
        self.start_frame()
150
        self.add_button("Record", self.record, side=tkinter.LEFT)
151
        self.add_button("Stop", self.stop, side=tkinter.LEFT)
152
        self.end_frame()
153
154
        file = self.get_filename("txt")
155
        content=""
156
        try:
157
            f = open(file, "r")
158
            if f:
159
                content = f.read()
160
                f.close()
161
        except:
162
            pass
163
        self.add_text(id(file), content);
164
        for file in self.files:
165
            if os.path.basename(file).startswith("tmp_"):
166
                continue
167
            if file.endswith(".mkv"):
168
                self.start_frame()
169
                moviefile=file
170
                self.add_label(id(file) + "_image", image=os.path.join("media", "movie.png"), side=tkinter.LEFT)
171
                self.add_label(id(file), text=file, side=tkinter.LEFT)
172
                self.add_button("del", lambda: self.delete(moviefile), side=tkinter.LEFT)
173
                self.end_frame()
174
                self.set_filename(os.path.basename(file))
175
176
                file = self.get_filename("txt")
177
                content=""
178
                try:
179
                    f = open(file, "r")
180
                    if f:
181
                        content = f.read()
182
                        f.close()
183
                except:
184
                    pass
185
                self.add_text(id(file), content);
186
        
187
188
    def add_image(self, name, filename, width=32, height=32, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
189
        self.images[name] = tkinter.PhotoImage(master=self.root(), file=filename)
190
191
        self.canvases[name] = tkinter.Canvas(self.root(), width=width, height=height)
192
        self.canvases[name].create_image(0, 0, image=self.images[name])
193
        self.canvases[name].pack(side=side, fill=fill, expand=expand)
194
195
    def add_label(self, name, text="", image="", width=120, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
196
        if image != "":
197
            self.images[name] = tkinter.PhotoImage(master=self.root(), file=image)
198
            self.labels[name] = tkinter.Label(self.root(), width=width, text=text, image=self.images[name])
199
        else:
200
            self.labels[name] = tkinter.Label(self.root(), text=text)
201
        self.labels[name].pack(side=side, fill=fill, expand=expand)
202
203
    def add_text(self, name, content="", autosave=True, width=50, height=2, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
204
        self.textfields[name] = tkinter.Text(self.root(), name=name, width=width, height=height)
205
        if content != "":
206
            self.textfields[name].insert(tkinter.INSERT, content)
207
        self.textfields[name].pack(side=side, fill=fill, expand=expand)
208
        if autosave:
209
            self.textfields[name].bind("<KeyRelease>", text_changed)
210
        self.lasttextfield = name
211
212
    def add_button(self, name, fn, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
213
        self.buttons[name] = tkinter.Button(self.root(), text=name, command=fn)
214
        self.buttons[name].pack(side=side, fill=fill, expand=expand)
215
216
    def start_frame(self, side=tkinter.TOP, fill=tkinter.NONE, expand=False):
217
        self.frame = tkinter.Frame(self.master)
218
        self.frame.pack(side=side, fill=fill, expand=expand)
219
        self.frames.append(self.frame)
220
221
    def end_frame(self):
222
        self.frame = None
223
224
    def root(self):
225
        if self.frame:
226
            return self.frame
227
        else:
228
            return self.master
229
230
    def record(self):
231
        ffmpeg.record(self.get_filename())
232
233
    def stop(self):
234
        ffmpeg.stop()
235
        self.refresh_files()
236
        self.refresh_display()
237
238
    def render(self):
239
        global resolution
240
        self.refresh_files()
241
        videos=[]
242
        description=""
243
        
244
        # intro
245
        r = ffmpeg.renderer()
246
        r.resolution(resolution)
247
        r.filename(os.path.join("media", "intro.mkv"))
248
        params = " -scale %s!" % resolution.replace(":", "x")
249
        params += " -font %s -weight 500 -pointsize 100" % ffmpeg.font
250
        params += " -draw \"gravity northwest fill white text 100,775 '%s'\"" % self.get_title()
251
        params += " -scale %s!" % resolution.replace(":", "x")
252
        ffmpeg.convert_image(os.path.join("media", "intro.jpg"), os.path.join(path, "tmp", "intro.jpg"), params)
253
        r.add_image(os.path.join(path, "tmp", "intro.jpg"), fadein=False)
254
        r.process(os.path.join(path, "tmp", "intro-scale.mkv"), "-vf scale=" + resolution)
255
        r.concat(os.path.join(path, "tmp", "intro-concat.mkv"))
256
257
        # mux intro
258
        r = ffmpeg.renderer()
259
        r.resolution(resolution)
260
        r.filename(os.path.join(path, "tmp", "intro-concat.mkv"))
261
        r.mux(os.path.join(path, "tmp", "intro.mkv"), os.path.join("media", "intro.mp3"))
262
        videos.append(r.lastfilename)
263
264
        # main
265
        r = ffmpeg.renderer()
266
        r.resolution(resolution)
267
        for file in self.files:
268
            if os.path.basename(file).startswith("tmp_"):
269
                continue
270
            if file.endswith(".txt"):
271
                description += "- %s" % r.add_text(file)
272
            if file.endswith(".mkv"):
273
                r.add_video(file)
274
        r.concat(os.path.join(path, "tmp", "concat.mkv"))
275
        r.speedup(os.path.join(path, "tmp", "speedup.mkv"), 0.7)
276
        r.mux(os.path.join(path, "tmp", "main.mkv"), os.path.join("media", "back.mp3"))
277
        videos.append(r.lastfilename)
278
279
        # outro
280
        r = ffmpeg.renderer()
281
        r.resolution(resolution)
282
        r.filename(os.path.join("media", "outro.mkv"))
283
        r.process(os.path.join(path, "tmp", "outro.mkv"), "-vf scale=" + resolution)
284
        videos.append(r.lastfilename)
285
286
        # mastering
287
        outputfilename=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "") + ".mkv"
288
        r = ffmpeg.renderer()
289
        r.resolution(resolution)
290
        for file in videos:
291
            r.add_video(file)
292
        r.concat(os.path.join(path, outputfilename), audio=True)
293
294
        # write description
295
        outputfilename=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "") + ".txt"
296
        f = open(os.path.join("media", "legal.txt"), "r")
297
        if f:
298
            description += f.read()
299
            f.close()
300
        
301
        f = open(os.path.join(path, outputfilename), "w")
302
        if f:
303
            f.write(description)
304
            f.close()
305
306
        
307
    def delete(self, filename):
308
        os.remove(filename)
309
        self.refresh_files()
310
        self.refresh_display()
311
312
    def stash(self):
313
        dirname=self.get_title().replace(" ", "-").replace(".", "_").replace("\n", "")
314
        if not os.path.exists(os.path.join(path, dirname)):
315
            os.makedirs(os.path.join(path, dirname))
316
        for filename in glob.glob(os.path.join(path, "working", "*")):
317
            shutil.move(filename, os.path.join(path, dirname))
318
        self.refresh_files()
319
        self.refresh_display()
320
321
    def quit(self, event):
322
        ffmpeg.stop()
323
324
325
root = tkinter.Tk()
326
327
win = TubeTutorWin(root, path)
328
root.bind("<Destroy>", win.quit)
329
root.mainloop()