Merge lp:~jderose/novacut/renderer into lp:novacut

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
Reviewer Review Type Date Requested Status
Novacut Dev Pending
Review via email: mp+89452@code.launchpad.net

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 )

Subscribers

People subscribed via source and target branches

to status/vote changes: