Merge lp:~jderose/novacut/renderer into lp:novacut
- renderer
- Merge into trunk
Proposed by
Jason Gerard DeRose
Status: | Merged |
---|---|
Merged at revision: | 73 |
Proposed branch: | lp:~jderose/novacut/renderer |
Merge into: | lp:novacut |
Diff against target: |
926 lines (+672/-32) 14 files modified
create-demo-project.py (+73/-0) debian/control (+1/-0) demo.json (+157/-0) novacut-cli (+6/-0) novacut-renderer (+50/-0) novacut-service (+39/-9) novacut/schema.py (+52/-2) novacut2/builder.py (+2/-2) novacut2/extractor.py (+186/-0) novacut2/renderer.py (+8/-7) novacut2/tests/test_renderer.py (+9/-8) novacut2/worker.py (+82/-0) setup.py (+6/-0) setup2.py (+1/-4) |
To merge this branch: | bzr merge lp:~jderose/novacut/renderer |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Novacut Dev | Pending | ||
Review via email: mp+89452@code.launchpad.net |
Commit message
Description of the change
Yup, too tired to go into details!
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'create-demo-project.py' |
2 | --- create-demo-project.py 1970-01-01 00:00:00 +0000 |
3 | +++ create-demo-project.py 2012-01-20 15:32:24 +0000 |
4 | @@ -0,0 +1,73 @@ |
5 | +#!/usr/bin/python3 |
6 | + |
7 | +import json |
8 | +import time |
9 | +from microfiber import dmedia_env, Database, Conflict |
10 | +from novacut import schema |
11 | +from collections import OrderedDict |
12 | + |
13 | +docs = json.load(open('demo.json', 'r')) |
14 | +for doc in docs: |
15 | + doc['ver'] = 0 |
16 | + doc['time'] = time.time() |
17 | + |
18 | + |
19 | +p = { |
20 | + '_id': '5A236DNAC6NMS5XIBUMHTY2A', |
21 | + 'ver': 0, |
22 | + 'title': '2 Clip Demo', |
23 | + 'db_name': 'novacut-0-5a236dnac6nms5xibumhty2a', |
24 | + 'time': 1327022245.45872, |
25 | + 'atime': 1327022245.45872, |
26 | + 'type': 'novacut/project', |
27 | +} |
28 | + |
29 | +env = dmedia_env() |
30 | +db = Database('novacut-0', env) |
31 | +db.ensure() |
32 | +project = Database(p['db_name'], env) |
33 | +if project.ensure(): |
34 | + project.post(p) |
35 | + project.post({'docs': docs}, '_bulk_docs') |
36 | +try: |
37 | + db.save(p) |
38 | +except Conflict: |
39 | + pass |
40 | +db.post({'docs': docs}, '_bulk_docs') |
41 | + |
42 | +root = schema.save_to_intrinsic('AUABDULVRZIBH727GQP2HXSA', project, db) |
43 | +print('root:', root) |
44 | + |
45 | +node = { |
46 | + 'muxer': {'name': 'oggmux'}, |
47 | + 'video': { |
48 | + 'encoder': { |
49 | + 'name': 'theoraenc', |
50 | + 'props': { |
51 | + 'quality': 52, |
52 | + }, |
53 | + }, |
54 | + 'filter': { |
55 | + 'mime': 'video/x-raw-yuv', |
56 | + 'caps': { |
57 | + 'width': 960, |
58 | + 'height': 540, |
59 | + }, |
60 | + }, |
61 | + }, |
62 | +} |
63 | +doc = schema.create_settings(node) |
64 | +try: |
65 | + db.save(doc) |
66 | +except Conflict: |
67 | + pass |
68 | +print('settings:', doc['_id']) |
69 | + |
70 | +doc = schema.create_job(root, doc['_id']) |
71 | +try: |
72 | + db.save(doc) |
73 | +except Conflict: |
74 | + pass |
75 | +print('job:', doc['_id']) |
76 | + |
77 | + |
78 | |
79 | === modified file 'debian/control' |
80 | --- debian/control 2012-01-17 07:29:22 +0000 |
81 | +++ debian/control 2012-01-20 15:32:24 +0000 |
82 | @@ -18,6 +18,7 @@ |
83 | python-gst0.10, |
84 | python-dbus, |
85 | dmedia (>= 12.01), |
86 | + dc3 (>= 12.01), |
87 | python3-microfiber (>= 12.01), |
88 | python3-userwebkit (>= 12.01), |
89 | python3-gi | python3-gobject, |
90 | |
91 | === added file 'demo.json' |
92 | --- demo.json 1970-01-01 00:00:00 +0000 |
93 | +++ demo.json 2012-01-20 15:32:24 +0000 |
94 | @@ -0,0 +1,157 @@ |
95 | +[ |
96 | + { |
97 | + "_id": "AUABDULVRZIBH727GQP2HXSA", |
98 | + "node": { |
99 | + "src": [ |
100 | + "XBIMKG7RJRSCNBWLI24BMIVS", |
101 | + "THYLBLNPTA2BBFSBGPUDSE3J", |
102 | + "TNSLSNXN7UCRC6XECSZ46LHC", |
103 | + "7FR4UH4JQPO4ISOPOWFDJ3FO", |
104 | + "SZ6A53C2YTYQMUMLY3SXWHKX", |
105 | + "5B6QDHAFTHJUU3TWX6JWE5TV", |
106 | + "QD3FLQVR5TQIQDQHXAZ5EPXM", |
107 | + "WXW4VPQJDG4K5XVIHX5ZTVBJ" |
108 | + ], |
109 | + "type": "sequence" |
110 | + }, |
111 | + "type": "novacut/node" |
112 | + }, |
113 | + { |
114 | + "_id": "WXW4VPQJDG4K5XVIHX5ZTVBJ", |
115 | + "node": { |
116 | + "src": "VQIXPULW3G77W4XLGROMEDGFAH2XJBN4SAVFUGOZRFSIVU7N", |
117 | + "start": { |
118 | + "frame": 421 |
119 | + }, |
120 | + "stop": { |
121 | + "frame": 436 |
122 | + }, |
123 | + "stream": "video", |
124 | + "type": "slice" |
125 | + }, |
126 | + "type": "novacut/node" |
127 | + }, |
128 | + { |
129 | + "_id": "QD3FLQVR5TQIQDQHXAZ5EPXM", |
130 | + "node": { |
131 | + "src": "W62OZLFQUSKE4K6SLJWJ4EHFDUTRLD7JKQXUQMDJSSUG6TAQ", |
132 | + "start": { |
133 | + "frame": 294 |
134 | + }, |
135 | + "stop": { |
136 | + "frame": 309 |
137 | + }, |
138 | + "stream": "video", |
139 | + "type": "slice" |
140 | + }, |
141 | + "type": "novacut/node" |
142 | + }, |
143 | + { |
144 | + "_id": "5B6QDHAFTHJUU3TWX6JWE5TV", |
145 | + "node": { |
146 | + "src": "VQIXPULW3G77W4XLGROMEDGFAH2XJBN4SAVFUGOZRFSIVU7N", |
147 | + "start": { |
148 | + "frame": 381 |
149 | + }, |
150 | + "stop": { |
151 | + "frame": 406 |
152 | + }, |
153 | + "stream": "video", |
154 | + "type": "slice" |
155 | + }, |
156 | + "type": "novacut/node" |
157 | + }, |
158 | + { |
159 | + "_id": "SZ6A53C2YTYQMUMLY3SXWHKX", |
160 | + "node": { |
161 | + "src": "W62OZLFQUSKE4K6SLJWJ4EHFDUTRLD7JKQXUQMDJSSUG6TAQ", |
162 | + "start": { |
163 | + "frame": 244 |
164 | + }, |
165 | + "stop": { |
166 | + "frame": 269 |
167 | + }, |
168 | + "stream": "video", |
169 | + "type": "slice" |
170 | + }, |
171 | + "type": "novacut/node" |
172 | + }, |
173 | + { |
174 | + "_id": "7FR4UH4JQPO4ISOPOWFDJ3FO", |
175 | + "node": { |
176 | + "src": "VQIXPULW3G77W4XLGROMEDGFAH2XJBN4SAVFUGOZRFSIVU7N", |
177 | + "start": { |
178 | + "frame": 318 |
179 | + }, |
180 | + "stop": { |
181 | + "frame": 356 |
182 | + }, |
183 | + "stream": "video", |
184 | + "type": "slice" |
185 | + }, |
186 | + "type": "novacut/node" |
187 | + }, |
188 | + { |
189 | + "_id": "TNSLSNXN7UCRC6XECSZ46LHC", |
190 | + "node": { |
191 | + "src": "W62OZLFQUSKE4K6SLJWJ4EHFDUTRLD7JKQXUQMDJSSUG6TAQ", |
192 | + "start": { |
193 | + "frame": 168 |
194 | + }, |
195 | + "stop": { |
196 | + "frame": 206 |
197 | + }, |
198 | + "stream": "video", |
199 | + "type": "slice" |
200 | + }, |
201 | + "type": "novacut/node" |
202 | + }, |
203 | + { |
204 | + "_id": "THYLBLNPTA2BBFSBGPUDSE3J", |
205 | + "node": { |
206 | + "src": "VQIXPULW3G77W4XLGROMEDGFAH2XJBN4SAVFUGOZRFSIVU7N", |
207 | + "start": { |
208 | + "frame": 230 |
209 | + }, |
210 | + "stop": { |
211 | + "frame": 280 |
212 | + }, |
213 | + "stream": "video", |
214 | + "type": "slice" |
215 | + }, |
216 | + "type": "novacut/node" |
217 | + }, |
218 | + { |
219 | + "_id": "XBIMKG7RJRSCNBWLI24BMIVS", |
220 | + "node": { |
221 | + "src": "W62OZLFQUSKE4K6SLJWJ4EHFDUTRLD7JKQXUQMDJSSUG6TAQ", |
222 | + "start": { |
223 | + "frame": 68 |
224 | + }, |
225 | + "stop": { |
226 | + "frame": 118 |
227 | + }, |
228 | + "stream": "video", |
229 | + "type": "slice" |
230 | + }, |
231 | + "type": "novacut/node" |
232 | + }, |
233 | + { |
234 | + "_id": "W62OZLFQUSKE4K6SLJWJ4EHFDUTRLD7JKQXUQMDJSSUG6TAQ", |
235 | + "framerate": { |
236 | + "denom": 1, |
237 | + "num": 25 |
238 | + }, |
239 | + "samplerate": 48000, |
240 | + "type": "dmedia/file" |
241 | + }, |
242 | + { |
243 | + "_id": "VQIXPULW3G77W4XLGROMEDGFAH2XJBN4SAVFUGOZRFSIVU7N", |
244 | + "framerate": { |
245 | + "denom": 1, |
246 | + "num": 25 |
247 | + }, |
248 | + "samplerate": 48000, |
249 | + "type": "dmedia/file" |
250 | + } |
251 | +] |
252 | |
253 | === modified file 'novacut-cli' |
254 | --- novacut-cli 2012-01-17 08:37:54 +0000 |
255 | +++ novacut-cli 2012-01-20 15:32:24 +0000 |
256 | @@ -105,6 +105,12 @@ |
257 | return '{} was running for {}'.format(self.bus, minsec(seconds)) |
258 | |
259 | |
260 | +class Render(_Method): |
261 | + 'Render an edit' |
262 | + |
263 | + args = ['job_id'] |
264 | + |
265 | + |
266 | |
267 | parser = optparse.OptionParser( |
268 | usage='%prog METHOD [ARGS...]', |
269 | |
270 | === added file 'novacut-renderer' |
271 | --- novacut-renderer 1970-01-01 00:00:00 +0000 |
272 | +++ novacut-renderer 2012-01-20 15:32:24 +0000 |
273 | @@ -0,0 +1,50 @@ |
274 | +#!/usr/bin/python |
275 | + |
276 | +# novacut: the collaborative video editor |
277 | +# Copyright (C) 2011 Novacut Inc |
278 | +# |
279 | +# This file is part of `novacut`. |
280 | +# |
281 | +# `novacut` is free software: you can redistribute it and/or modify it under |
282 | +# the terms of the GNU Affero General Public License as published by the Free |
283 | +# Software Foundation, either version 3 of the License, or (at your option) |
284 | +# any later version. |
285 | +# |
286 | +# `novacut` is distributed in the hope that it will be useful, but WITHOUT ANY |
287 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
288 | +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for |
289 | +# more details. |
290 | +# |
291 | +# You should have received a copy of the GNU Affero General Public License |
292 | +# along with `novacut`. If not, see <http://www.gnu.org/licenses/>. |
293 | +# |
294 | +# Authors: |
295 | +# Jason Gerard DeRose <jderose@novacut.com> |
296 | + |
297 | +""" |
298 | +Script to fire-off a render. |
299 | +""" |
300 | + |
301 | +import optparse |
302 | +import novacut2 |
303 | + |
304 | +import gobject |
305 | +import dbus |
306 | +from dbus.mainloop.glib import DBusGMainLoop |
307 | + |
308 | + |
309 | +gobject.threads_init() |
310 | +DBusGMainLoop(set_as_default=True) |
311 | +session = dbus.SessionBus() |
312 | + |
313 | + |
314 | +parser = optparse.OptionParser( |
315 | + version=novacut2.__version__, |
316 | +) |
317 | +(options, args) = parser.parse_args() |
318 | +job_id = args[0] |
319 | + |
320 | +from novacut2.worker import Worker |
321 | +worker = Worker() |
322 | +_id = worker.run(job_id) |
323 | +print(_id) |
324 | |
325 | === modified file 'novacut-service' |
326 | --- novacut-service 2012-01-17 05:42:15 +0000 |
327 | +++ novacut-service 2012-01-20 15:32:24 +0000 |
328 | @@ -1,4 +1,4 @@ |
329 | -#!/usr/bin/python |
330 | +#!/usr/bin/python3 |
331 | |
332 | # novacut: the collaborative video editor |
333 | # Copyright (C) 2011 Novacut Inc |
334 | @@ -31,31 +31,42 @@ |
335 | import optparse |
336 | import json |
337 | from os import path |
338 | +from threading import Thread |
339 | +import subprocess |
340 | |
341 | import dbus |
342 | import dbus.service |
343 | from dbus.mainloop.glib import DBusGMainLoop |
344 | -import gobject |
345 | +from gi.repository import GObject |
346 | |
347 | -import novacut2 |
348 | +import novacut |
349 | |
350 | |
351 | BUS = 'com.novacut.Renderer' |
352 | IFACE = BUS |
353 | |
354 | -gobject.threads_init() |
355 | +GObject.threads_init() |
356 | DBusGMainLoop(set_as_default=True) |
357 | session = dbus.SessionBus() |
358 | |
359 | |
360 | -def dumps(obj): |
361 | - return json.dumps(obj, sort_keys=True, separators=(',', ': '), indent=4) |
362 | +renderer = path.join(path.dirname(__file__), 'novacut-renderer') |
363 | +assert path.isfile(renderer) |
364 | + |
365 | + |
366 | +def _start_thread(target, *args): |
367 | + thread = Thread(target=target, args=args) |
368 | + thread.daemon = True |
369 | + thread.start() |
370 | + return thread |
371 | |
372 | |
373 | class Service(dbus.service.Object): |
374 | + workers = {} |
375 | + |
376 | def __init__(self, bus): |
377 | self.bus = bus |
378 | - self.mainloop = gobject.MainLoop() |
379 | + self.mainloop = GObject.MainLoop() |
380 | super(Service, self).__init__(session, object_path='/') |
381 | self.busname = dbus.service.BusName(bus, session) |
382 | |
383 | @@ -65,12 +76,17 @@ |
384 | def kill(self): |
385 | self.mainloop.quit() |
386 | |
387 | + def render(self, job_id): |
388 | + cmd = [renderer, job_id] |
389 | + file_id = subprocess.check_output(cmd).decode('utf-8') |
390 | + self.RenderFinished(job_id, file_id) |
391 | + |
392 | @dbus.service.method(IFACE, in_signature='', out_signature='s') |
393 | def Version(self): |
394 | """ |
395 | Return Novacut version. |
396 | """ |
397 | - return novacut2.__version__ |
398 | + return novacut.__version__ |
399 | |
400 | @dbus.service.method(IFACE, in_signature='', out_signature='i') |
401 | def Kill(self): |
402 | @@ -80,9 +96,23 @@ |
403 | self.kill() |
404 | return int(time.time() - start_time) |
405 | |
406 | + @dbus.service.method(IFACE, in_signature='s', out_signature='b') |
407 | + def Render(self, job_id): |
408 | + """ |
409 | + Render an edit. |
410 | + """ |
411 | + if job_id in self.workers: |
412 | + return False |
413 | + self.workers[job_id] = _start_thread(self.render, job_id) |
414 | + return True |
415 | + |
416 | + @dbus.service.signal(IFACE, signature='ss') |
417 | + def RenderFinished(self, job_id, file_id): |
418 | + del self.workers[job_id] |
419 | + |
420 | |
421 | parser = optparse.OptionParser( |
422 | - version=novacut2.__version__, |
423 | + version=novacut.__version__, |
424 | ) |
425 | parser.add_option('--bus', |
426 | help='DBus bus name; default is {!r}'.format(BUS), |
427 | |
428 | === modified file 'novacut/schema.py' |
429 | --- novacut/schema.py 2012-01-17 12:15:44 +0000 |
430 | +++ novacut/schema.py 2012-01-20 15:32:24 +0000 |
431 | @@ -132,7 +132,7 @@ |
432 | from collections import namedtuple |
433 | |
434 | from skein import skein512 |
435 | -from microfiber import random_id, RANDOM_B32LEN |
436 | +from microfiber import random_id, RANDOM_B32LEN, Conflict |
437 | from dmedia.schema import ( |
438 | _label, |
439 | _value, |
440 | @@ -267,6 +267,17 @@ |
441 | return inode.id |
442 | |
443 | |
444 | +def save_to_intrinsic(root, src, dst): |
445 | + results = {} |
446 | + iroot = intrinsic_graph(root, src.get, results) |
447 | + for inode in results.values(): |
448 | + doc = create_inode(inode) |
449 | + try: |
450 | + dst.save(doc) |
451 | + except Conflict: |
452 | + pass |
453 | + return iroot |
454 | + |
455 | |
456 | def check_novacut(doc): |
457 | """ |
458 | @@ -411,7 +422,46 @@ |
459 | } |
460 | }, |
461 | 'ver': VER, |
462 | - 'type': 'novacut/node', |
463 | + 'type': 'novacut/inode', |
464 | + 'time': time.time(), |
465 | + 'node': inode.node, |
466 | + 'renders': {}, |
467 | + } |
468 | + |
469 | + |
470 | +def create_settings(node): |
471 | + inode = intrinsic_node(node) |
472 | + return { |
473 | + '_id': inode.id, |
474 | + '_attachments': { |
475 | + 'node': { |
476 | + 'data': b64encode(inode.data).decode('utf-8'), |
477 | + 'content_type': 'application/json', |
478 | + } |
479 | + }, |
480 | + 'ver': VER, |
481 | + 'type': 'novacut/settings', |
482 | + 'time': time.time(), |
483 | + 'node': inode.node, |
484 | + } |
485 | + |
486 | + |
487 | +def create_job(root, settings): |
488 | + node = { |
489 | + 'root': root, |
490 | + 'settings': settings, |
491 | + } |
492 | + inode = intrinsic_node(node) |
493 | + return { |
494 | + '_id': inode.id, |
495 | + '_attachments': { |
496 | + 'node': { |
497 | + 'data': b64encode(inode.data).decode('utf-8'), |
498 | + 'content_type': 'application/json', |
499 | + } |
500 | + }, |
501 | + 'ver': VER, |
502 | + 'type': 'novacut/job', |
503 | 'time': time.time(), |
504 | 'node': inode.node, |
505 | 'renders': {}, |
506 | |
507 | === modified file 'novacut2/builder.py' |
508 | --- novacut2/builder.py 2012-01-18 01:02:46 +0000 |
509 | +++ novacut2/builder.py 2012-01-20 15:32:24 +0000 |
510 | @@ -61,11 +61,11 @@ |
511 | |
512 | |
513 | class LiveBuilder(Builder): |
514 | - def __init__(self, project_id): |
515 | + def __init__(self): |
516 | self.Dmedia = session.get_object('org.freedesktop.Dmedia', '/') |
517 | env = json.loads(self.Dmedia.GetEnv()) |
518 | env['url'] = env['url'].encode('utf-8') |
519 | - self.db = Database(project_db_name(project_id), env) |
520 | + self.db = Database('novacut-0', env) |
521 | self._cache = {} |
522 | |
523 | def resolve_file(self, _id): |
524 | |
525 | === added file 'novacut2/extractor.py' |
526 | --- novacut2/extractor.py 1970-01-01 00:00:00 +0000 |
527 | +++ novacut2/extractor.py 2012-01-20 15:32:24 +0000 |
528 | @@ -0,0 +1,186 @@ |
529 | +# novacut: the distributed video editor |
530 | +# Copyright (C) 2011 Novacut Inc |
531 | +# |
532 | +# This file is part of `novacut`. |
533 | +# |
534 | +# `novacut` is free software: you can redistribute it and/or modify it under |
535 | +# the terms of the GNU Affero General Public License as published by the Free |
536 | +# Software Foundation, either version 3 of the License, or (at your option) |
537 | +# any later version. |
538 | +# |
539 | +# `novacut` is distributed in the hope that it will be useful, but WITHOUT ANY |
540 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
541 | +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for |
542 | +# more details. |
543 | +# |
544 | +# You should have received a copy of the GNU Affero General Public License |
545 | +# along with `novacut`. If not, see <http://www.gnu.org/licenses/>. |
546 | +# |
547 | +# Authors: |
548 | +# Jason Gerard DeRose <jderose@novacut.com> |
549 | + |
550 | +import sys |
551 | +import gst |
552 | +import gobject |
553 | + |
554 | + |
555 | +gobject.threads_init() |
556 | + |
557 | + |
558 | +class FakeBin(gst.Bin): |
559 | + def __init__(self, doc, rate): |
560 | + super(FakeBin, self).__init__() |
561 | + self._doc = doc |
562 | + self._q = gst.element_factory_make('queue') |
563 | + self._rate = gst.element_factory_make(rate) |
564 | + self._sink = gst.element_factory_make('fakesink') |
565 | + self.add(self._q, self._rate, self._sink) |
566 | + self._q.link(self._rate) |
567 | + self._rate.link(self._sink) |
568 | + |
569 | + # Ghost Pads |
570 | + pad = self._q.get_pad('sink') |
571 | + self.add_pad( |
572 | + gst.GhostPad('sink', pad) |
573 | + ) |
574 | + pad.connect('notify::caps', self._on_notify_caps) |
575 | + |
576 | + def _get_doc(self): |
577 | + self._finalize() |
578 | + return self._doc |
579 | + |
580 | + def _query_duration(self, pad): |
581 | + q = gst.query_new_duration(gst.FORMAT_TIME) |
582 | + if pad.get_peer().query(q): |
583 | + (format, duration) = q.parse_duration() |
584 | + if format == gst.FORMAT_TIME: |
585 | + return duration |
586 | + |
587 | + |
588 | +class VideoBin(FakeBin): |
589 | + def __init__(self, doc): |
590 | + super(VideoBin, self).__init__(doc, 'videorate') |
591 | + |
592 | + def _on_notify_caps(self, pad, args): |
593 | + caps = pad.get_negotiated_caps() |
594 | + if not caps: |
595 | + return |
596 | + d = caps[0] |
597 | + num = d['framerate'].num |
598 | + denom = d['framerate'].denom |
599 | + self._doc['framerate'] = { |
600 | + 'num': num, |
601 | + 'denom': denom, |
602 | + } |
603 | + self._doc['width'] = d['width'] |
604 | + self._doc['height'] = d['height'] |
605 | + ns = self._query_duration(pad) |
606 | + if ns: |
607 | + self._doc['video_ns'] = ns |
608 | + self._doc['frames'] = ns * num / denom / gst.SECOND |
609 | + |
610 | + def _finalize(self): |
611 | + self._doc['frames2'] = self._rate.get_property('in') |
612 | + |
613 | + |
614 | +class AudioBin(FakeBin): |
615 | + def __init__(self, doc): |
616 | + super(AudioBin, self).__init__(doc, 'audiorate') |
617 | + |
618 | + def _on_notify_caps(self, pad, args): |
619 | + caps = pad.get_negotiated_caps() |
620 | + if not caps: |
621 | + return |
622 | + d = caps[0] |
623 | + self._doc['samplerate'] = d['rate'] |
624 | + self._doc['channels'] = d['channels'] |
625 | + ns = self._query_duration(pad) |
626 | + if ns: |
627 | + self._doc['audio_ns'] = ns |
628 | + self._doc['samples'] = d['rate'] * ns / gst.SECOND |
629 | + |
630 | + def _finalize(self): |
631 | + # FIXME: why is this so worthless? |
632 | + if 'samples' not in self._doc: |
633 | + self._doc['samples'] = self._rate.get_property('in') |
634 | + |
635 | + |
636 | +class Extractor(object): |
637 | + def __init__(self, filename): |
638 | + self.doc = {'video_ns': 0, 'audio_ns': 0} |
639 | + self.mainloop = gobject.MainLoop() |
640 | + self.pipeline = gst.Pipeline() |
641 | + |
642 | + # Create bus and connect several handlers |
643 | + self.bus = self.pipeline.get_bus() |
644 | + self.bus.add_signal_watch() |
645 | + self.bus.connect('message::eos', self.on_eos) |
646 | + self.bus.connect('message::error', self.on_error) |
647 | + |
648 | + # Create elements |
649 | + self.src = gst.element_factory_make('filesrc') |
650 | + self.dec = gst.element_factory_make('decodebin2') |
651 | + |
652 | + # Set properties |
653 | + self.src.set_property('location', filename) |
654 | + |
655 | + # Connect handler for 'new-decoded-pad' signal |
656 | + self.dec.connect('pad-added', self.on_pad_added) |
657 | + self.dec.connect('no-more-pads', self.on_no_more_pads) |
658 | + self.typefind = self.dec.get_by_name('typefind') |
659 | + self.typefind.connect('have-type', self.on_have_type) |
660 | + |
661 | + # Add elements to pipeline |
662 | + self.pipeline.add(self.src, self.dec) |
663 | + |
664 | + # Link elements |
665 | + self.src.link(self.dec) |
666 | + |
667 | + self.audio = None |
668 | + self.video = None |
669 | + |
670 | + def run(self): |
671 | + self.pipeline.set_state(gst.STATE_PLAYING) |
672 | + self.mainloop.run() |
673 | + |
674 | + def kill(self): |
675 | + ns = max(self.doc['video_ns'], self.doc['audio_ns']) |
676 | + self.doc['seconds'] = float(ns) / gst.SECOND |
677 | + self.pipeline.set_state(gst.STATE_NULL) |
678 | + self.pipeline.get_state() |
679 | + self.mainloop.quit() |
680 | + |
681 | + def link_pad(self, pad, name): |
682 | + cls = {'audio': AudioBin, 'video': VideoBin}[name] |
683 | + fakebin = cls(self.doc) |
684 | + self.pipeline.add(fakebin) |
685 | + pad.link(fakebin.get_pad('sink')) |
686 | + fakebin.set_state(gst.STATE_PLAYING) |
687 | + return fakebin |
688 | + |
689 | + def on_pad_added(self, element, pad): |
690 | + name = pad.get_caps()[0].get_name() |
691 | + if name.startswith('audio/'): |
692 | + assert self.audio is None |
693 | + self.audio = self.link_pad(pad, 'audio') |
694 | + elif name.startswith('video/'): |
695 | + assert self.video is None |
696 | + self.video = self.link_pad(pad, 'video') |
697 | + |
698 | + def on_no_more_pads(self, element): |
699 | + print('no more decoded pads') |
700 | + |
701 | + def on_have_type(self, element, prop, caps): |
702 | + self.doc['content_type'] = caps.to_string() |
703 | + |
704 | + def on_eos(self, bus, msg): |
705 | + if self.video: |
706 | + self.video._finalize() |
707 | + self.kill() |
708 | + |
709 | + def on_error(self, bus, msg): |
710 | + error = msg.parse_error()[1] |
711 | + self.kill() |
712 | + print(error) |
713 | + sys.exit(2) |
714 | + |
715 | |
716 | === modified file 'novacut2/renderer.py' |
717 | --- novacut2/renderer.py 2012-01-17 05:15:04 +0000 |
718 | +++ novacut2/renderer.py 2012-01-20 15:32:24 +0000 |
719 | @@ -251,14 +251,15 @@ |
720 | |
721 | |
722 | class Renderer(object): |
723 | - def __init__(self, job, builder, dst): |
724 | + def __init__(self, root, settings, builder, dst): |
725 | """ |
726 | Initialize. |
727 | |
728 | :param job: a ``dict`` describing the transcode to perform. |
729 | :param fs: a `FileStore` instance in which to store transcoded file |
730 | """ |
731 | - self.job = job |
732 | + self.root = root |
733 | + self.settings = settings |
734 | self.builder = builder |
735 | self.mainloop = gobject.MainLoop() |
736 | self.pipeline = gst.Pipeline() |
737 | @@ -270,8 +271,8 @@ |
738 | self.bus.connect('message::error', self.on_error) |
739 | |
740 | # Create elements |
741 | - self.src = builder.build(job['src']) |
742 | - self.mux = make_element(job['muxer']) |
743 | + self.src = builder.build(root) |
744 | + self.mux = make_element(settings['muxer']) |
745 | self.sink = gst.element_factory_make('filesink') |
746 | |
747 | # Add elements to pipeline |
748 | @@ -301,15 +302,15 @@ |
749 | |
750 | def link_pad(self, pad, name, key): |
751 | log.info('link_pad: %r, %r, %r', pad, name, key) |
752 | - if key in self.job: |
753 | + if key in self.settings: |
754 | klass = {'audio': AudioEncoder, 'video': VideoEncoder}[key] |
755 | - el = klass(self.job[key]) |
756 | + el = klass(self.settings[key]) |
757 | else: |
758 | el = gst.element_factory_make('fakesink') |
759 | self.pipeline.add(el) |
760 | log.info('Linking pad %r with %r', name, el) |
761 | pad.link(el.get_compatible_pad(pad, pad.get_caps())) |
762 | - if key in self.job: |
763 | + if key in self.settings: |
764 | el.link(self.mux) |
765 | el.set_state(gst.STATE_PLAYING) |
766 | return el |
767 | |
768 | === modified file 'novacut2/tests/test_renderer.py' |
769 | --- novacut2/tests/test_renderer.py 2012-01-17 05:15:04 +0000 |
770 | +++ novacut2/tests/test_renderer.py 2012-01-20 15:32:24 +0000 |
771 | @@ -535,26 +535,27 @@ |
772 | tmp = TempDir() |
773 | builder = DummyBuilder(docs) |
774 | |
775 | - job = { |
776 | - 'src': sequence1, |
777 | + root = sequence1 |
778 | + settings = { |
779 | 'muxer': {'name': 'oggmux'}, |
780 | } |
781 | dst = tmp.join('out1.ogv') |
782 | - inst = renderer.Renderer(job, builder, dst) |
783 | + inst = renderer.Renderer(root, settings, builder, dst) |
784 | |
785 | - self.assertTrue(inst.job is job) |
786 | - self.assertTrue(inst.builder is builder) |
787 | + self.assertIs(inst.root, root) |
788 | + self.assertIs(inst.settings, settings) |
789 | + self.assertIs(inst.builder, builder) |
790 | |
791 | self.assertIsInstance(inst.src, gst.Element) |
792 | - self.assertTrue(inst.src.get_parent() is inst.pipeline) |
793 | + self.assertIs(inst.src.get_parent(), inst.pipeline) |
794 | self.assertEqual(inst.src.get_factory().get_name(), 'gnlcomposition') |
795 | |
796 | self.assertIsInstance(inst.mux, gst.Element) |
797 | - self.assertTrue(inst.mux.get_parent() is inst.pipeline) |
798 | + self.assertIs(inst.mux.get_parent(), inst.pipeline) |
799 | self.assertEqual(inst.mux.get_factory().get_name(), 'oggmux') |
800 | |
801 | self.assertIsInstance(inst.sink, gst.Element) |
802 | - self.assertTrue(inst.sink.get_parent() is inst.pipeline) |
803 | + self.assertIs(inst.sink.get_parent(), inst.pipeline) |
804 | self.assertEqual(inst.sink.get_factory().get_name(), 'filesink') |
805 | self.assertEqual(inst.sink.get_property('location'), dst) |
806 | |
807 | |
808 | === added file 'novacut2/worker.py' |
809 | --- novacut2/worker.py 1970-01-01 00:00:00 +0000 |
810 | +++ novacut2/worker.py 2012-01-20 15:32:24 +0000 |
811 | @@ -0,0 +1,82 @@ |
812 | +# novacut: the distributed video editor |
813 | +# Copyright (C) 2011 Novacut Inc |
814 | +# |
815 | +# This file is part of `novacut`. |
816 | +# |
817 | +# `novacut` is free software: you can redistribute it and/or modify it under |
818 | +# the terms of the GNU Affero General Public License as published by the Free |
819 | +# Software Foundation, either version 3 of the License, or (at your option) |
820 | +# any later version. |
821 | +# |
822 | +# `novacut` is distributed in the hope that it will be useful, but WITHOUT ANY |
823 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
824 | +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for |
825 | +# more details. |
826 | +# |
827 | +# You should have received a copy of the GNU Affero General Public License |
828 | +# along with `novacut`. If not, see <http://www.gnu.org/licenses/>. |
829 | +# |
830 | +# Authors: |
831 | +# Jason Gerard DeRose <jderose@novacut.com> |
832 | + |
833 | +import json |
834 | + |
835 | +import dbus |
836 | +from dbus.mainloop.glib import DBusGMainLoop |
837 | +import gobject |
838 | +from dc3lib.microfiber import Database |
839 | + |
840 | +from renderer import Builder, Renderer |
841 | + |
842 | + |
843 | +gobject.threads_init() |
844 | +DBusGMainLoop(set_as_default=True) |
845 | +session = dbus.SessionBus() |
846 | + |
847 | + |
848 | +class LiveBuilder(Builder): |
849 | + def __init__(self, Dmedia, db): |
850 | + self.Dmedia = Dmedia |
851 | + self.db = db |
852 | + self._cache = {} |
853 | + |
854 | + def resolve_file(self, _id): |
855 | + return self.Dmedia.Resolve(_id) |
856 | + |
857 | + def get_doc(self, _id): |
858 | + try: |
859 | + return self._cache[_id] |
860 | + except KeyError: |
861 | + doc = self.db.get(_id) |
862 | + self._cache[_id] = doc |
863 | + return doc |
864 | + |
865 | + |
866 | +class Worker(object): |
867 | + def __init__(self): |
868 | + self.Dmedia = session.get_object('org.freedesktop.Dmedia', '/') |
869 | + env = json.loads(self.Dmedia.GetEnv()) |
870 | + env['url'] = env['url'].encode('utf-8') |
871 | + self.novacut = Database('novacut-0', env) |
872 | + self.dmedia = Database('dmedia-0', env) |
873 | + |
874 | + def run(self, job_id): |
875 | + job = self.novacut.get(job_id) |
876 | + root = job['node']['root'] |
877 | + settings = self.novacut.get(job['node']['settings']) |
878 | + builder = LiveBuilder(self.Dmedia, self.novacut) |
879 | + dst = self.Dmedia.AllocateTmp() |
880 | + renderer = Renderer(root, settings['node'], builder, dst) |
881 | + renderer.run() |
882 | + _id = self.Dmedia.HashAndMove(dst, 'render') |
883 | + doc = self.dmedia.get(_id) |
884 | + doc['render_of'] = job_id |
885 | + self.dmedia.save(doc) |
886 | + job['renders'][_id] = { |
887 | + 'bytes': doc['bytes'], |
888 | + 'time': doc['time'], |
889 | + } |
890 | + self.novacut.save(job) |
891 | + return _id |
892 | + |
893 | + |
894 | |
895 | === modified file 'setup.py' |
896 | --- setup.py 2012-01-17 08:37:54 +0000 |
897 | +++ setup.py 2012-01-20 15:32:24 +0000 |
898 | @@ -147,6 +147,12 @@ |
899 | ('share/icons/hicolor/48x48/apps', |
900 | ['data/novacut.svg'] |
901 | ), |
902 | + ('lib/novacut', |
903 | + ['novacut-service'], |
904 | + ), |
905 | + ('share/dbus-1/services/', |
906 | + ['data/com.novacut.Renderer.service'] |
907 | + ), |
908 | ], |
909 | cmdclass={'test': Test}, |
910 | ) |
911 | |
912 | === modified file 'setup2.py' |
913 | --- setup2.py 2012-01-17 08:26:59 +0000 |
914 | +++ setup2.py 2012-01-20 15:32:24 +0000 |
915 | @@ -134,10 +134,7 @@ |
916 | packages=['novacut2'], |
917 | data_files=[ |
918 | ('lib/novacut', |
919 | - ['novacut-service'], |
920 | - ), |
921 | - ('share/dbus-1/services/', |
922 | - ['data/com.novacut.Renderer.service'] |
923 | + ['novacut-renderer'], |
924 | ), |
925 | ], |
926 | ) |