~timo-jyrinki/ubuntu/trusty/pitivi/merge_debian_0.93-3

« back to all changes in this revision

Viewing changes to pitivi/formatters/etree.py

  • Committer: Timo Jyrinki
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 13:28:16 UTC
  • mfrom: (1.5.8)
  • Revision ID: timo.jyrinki@canonical.com-20140405132816-wmv1rhbtmlxmx3ag
Tags: 0.93-3
* debian/control:
  + Depend on python-gi (>= 3.10), older versions do not work
    with pitivi (Closes: #732813).
  + Add missing dependency on gir1.2-clutter-gst-2.0 (Closes: #743692).
  + Add suggests on gir1.2-notify-0.7 and gir1.2-gnomedesktop-3.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# PiTiVi , Non-linear video editor
2
 
#
3
 
#       etree.py
4
 
#
5
 
# Copyright (c) 2009, Alessandro Decina <alessandrod.@gmail.com>
6
 
# Copyright (c) 2009, Edward Hervey <bilboed@bilboed.com>
7
 
#
8
 
# This program is free software; you can redistribute it and/or
9
 
# modify it under the terms of the GNU Lesser General Public
10
 
# License as published by the Free Software Foundation; either
11
 
# version 2.1 of the License, or (at your option) any later version.
12
 
#
13
 
# This program is distributed in the hope that it will be useful,
14
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 
# Lesser General Public License for more details.
17
 
#
18
 
# You should have received a copy of the GNU Lesser General Public
19
 
# License along with this program; if not, write to the
20
 
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21
 
# Boston, MA 02110-1301, USA.
22
 
 
23
 
from gettext import gettext as _
24
 
import gobject
25
 
import gst
26
 
 
27
 
from xml.etree.ElementTree import Element, SubElement, tostring, parse
28
 
 
29
 
from pitivi.reflect import qual, namedAny
30
 
from pitivi.factories.base import SourceFactory
31
 
from pitivi.factories.file import FileSourceFactory
32
 
from pitivi.factories.operation import EffectFactory
33
 
from pitivi.timeline.track import Track, TrackEffect
34
 
from pitivi.timeline.timeline import TimelineObject
35
 
from pitivi.formatters.base import Formatter, FormatterError
36
 
from pitivi.utils import get_filesystem_encoding
37
 
from pitivi.settings import ExportSettings
38
 
from pitivi.stream import match_stream_groups_map
39
 
 
40
 
version = "0.1"
41
 
 
42
 
 
43
 
def indent(elem, level=0):
44
 
    i = "\n" + level * "  "
45
 
    if len(elem):
46
 
        if not elem.text or not elem.text.strip():
47
 
            elem.text = i + "  "
48
 
        if not elem.tail or not elem.tail.strip():
49
 
            elem.tail = i
50
 
        for elem in elem:
51
 
            indent(elem, level + 1)
52
 
        if not elem.tail or not elem.tail.strip():
53
 
            elem.tail = i
54
 
    else:
55
 
        if level and (not elem.tail or not elem.tail.strip()):
56
 
            elem.tail = i
57
 
 
58
 
 
59
 
class ElementTreeFormatterContext(object):
60
 
    def __init__(self):
61
 
        self.streams = {}
62
 
        self.factories = {}
63
 
        self.track_objects = {}
64
 
        self.rootelement = None
65
 
 
66
 
 
67
 
class ElementTreeFormatterSaveContext(ElementTreeFormatterContext):
68
 
    pass
69
 
 
70
 
 
71
 
class ElementTreeFormatterLoadContext(ElementTreeFormatterContext):
72
 
    pass
73
 
 
74
 
 
75
 
class ElementTreeFormatter(Formatter):
76
 
    _element_id = 0
77
 
    _our_properties = ["id", "type"]
78
 
 
79
 
    def __init__(self, avalaible_effects, *args, **kwargs):
80
 
        Formatter.__init__(self, avalaible_effects, *args, **kwargs)
81
 
        # An Element representing the <factories> element.
82
 
        self.factoriesnode = None
83
 
        # An Element representing the <timeline> element.
84
 
        self.timelinenode = None
85
 
        # An Element representing the <export-settings> element.
86
 
        self._settingsnode = None
87
 
        # An Element representing the <metadata> element.
88
 
        self._metadatanode = None
89
 
 
90
 
        # A list of SourceFactory objects.
91
 
        self._sources = None
92
 
        self._context = ElementTreeFormatterContext()
93
 
 
94
 
    def _new_element_id(self):
95
 
        element_id = self._element_id
96
 
        self._element_id += 1
97
 
 
98
 
        return str(element_id)
99
 
 
100
 
    def _filterElementProperties(self, element):
101
 
        for name, value in element.attrib.iteritems():
102
 
            if name in self._our_properties:
103
 
                continue
104
 
 
105
 
            yield name, value
106
 
 
107
 
    def _parsePropertyValue(self, value):
108
 
        if value == "":
109
 
            return value
110
 
        # We treat the GEnum values differently because they are actually ints.
111
 
        if "(GEnum)" in value:
112
 
            return int(value.split("(GEnum)")[1])
113
 
        # We treat the guint64 values differently because when we serialize
114
 
        # a long integer, the serialization is, for example, "(guint64) 4L",
115
 
        # and gst.Caps() fails to parse it, because it has "L" at the end.
116
 
        if "(guint64)" in value:
117
 
            value = value.rstrip('lL')
118
 
        try:
119
 
            # TODO: Use what gst.Caps() uses to parse a property value.
120
 
            caps = gst.Caps("structure1, property1=%s;" % value)
121
 
        except TypeError, exception:
122
 
            # Report the value which caused the exception.
123
 
            raise Exception(exception, value)
124
 
        structure = caps[0]
125
 
        return structure["property1"]
126
 
 
127
 
    def _saveStream(self, stream):
128
 
        element = Element("stream")
129
 
        element.attrib["id"] = self._new_element_id()
130
 
        element.attrib["type"] = qual(stream.__class__)
131
 
        element.attrib["caps"] = str(stream.caps)
132
 
        element.attrib["name"] = str(stream.pad_name)
133
 
 
134
 
        self._context.streams[stream] = element
135
 
 
136
 
        return element
137
 
 
138
 
    def _loadStream(self, element):
139
 
        id_ = element.attrib["id"]
140
 
        klass = namedAny(element.attrib["type"])
141
 
        caps = gst.Caps(element.attrib["caps"])
142
 
 
143
 
        stream = klass(caps, element.attrib.get("name", None))
144
 
 
145
 
        self._context.streams[id_] = stream
146
 
 
147
 
        return stream
148
 
 
149
 
    def _saveStreamRef(self, stream):
150
 
        stream_element = self._context.streams[stream]
151
 
        element = Element("stream-ref")
152
 
        element.attrib["id"] = stream_element.attrib["id"]
153
 
 
154
 
        return element
155
 
 
156
 
    def _loadStreamRef(self, element):
157
 
        return self._context.streams[element.attrib["id"]]
158
 
 
159
 
    def _saveSource(self, source):
160
 
        element = self._saveObjectFactory(source)
161
 
        if isinstance(source, FileSourceFactory):
162
 
            return self._saveFileSourceFactory(element, source)
163
 
 
164
 
        return element
165
 
 
166
 
    def _loadFactory(self, element):
167
 
        klass = namedAny(element.attrib["type"])
168
 
        assert issubclass(klass, SourceFactory)
169
 
        factory = self._loadObjectFactory(klass, element)
170
 
        self._context.factories[element.attrib["id"]] = factory
171
 
        return factory
172
 
 
173
 
    def _saveObjectFactory(self, factory):
174
 
        element = Element("source")
175
 
        element.attrib["id"] = self._new_element_id()
176
 
        element.attrib["type"] = qual(factory.__class__)
177
 
        element.attrib["default_duration"] = str(factory.default_duration)
178
 
        element.attrib["duration"] = str(factory.duration)
179
 
 
180
 
        input_streams_element = SubElement(element, "input-streams")
181
 
        input_streams = factory.getInputStreams()
182
 
        for stream in input_streams:
183
 
            stream_element = self._saveStream(stream)
184
 
            input_streams_element.append(stream_element)
185
 
 
186
 
        output_streams_element = SubElement(element, "output-streams")
187
 
        output_streams = factory.getOutputStreams()
188
 
        for stream in output_streams:
189
 
            stream_element = self._saveStream(stream)
190
 
            output_streams_element.append(stream_element)
191
 
 
192
 
        self._context.factories[factory] = element
193
 
 
194
 
        return element
195
 
 
196
 
    def _loadObjectFactory(self, klass, element):
197
 
        """Instantiate the specified class and set its attributes.
198
 
 
199
 
        @param klass: An ObjectFactory subclass.
200
 
        @param element: The Element representing the object to be created.
201
 
        @return: An instance of the specified klass.
202
 
        """
203
 
        self.debug("klass:%r, element:%r", klass, element)
204
 
        # Instantiate the class.
205
 
        args = []
206
 
        if issubclass(klass, FileSourceFactory):
207
 
            filename = element.attrib.get("filename")
208
 
            if isinstance(filename, unicode):
209
 
                filename = filename.encode("utf-8")
210
 
            args.append(filename)
211
 
        factory = klass(*args)
212
 
 
213
 
        # Set the attributes of the instance.
214
 
        factory.duration = long(element.attrib["duration"])
215
 
        factory.default_duration = long(element.attrib["default_duration"])
216
 
 
217
 
        input_streams = element.find("input-streams")
218
 
        if input_streams is not None:
219
 
            for stream_element in input_streams:
220
 
                stream = self._loadStream(stream_element)
221
 
                factory.addInputStream(stream)
222
 
 
223
 
        output_streams = element.find("output-streams")
224
 
        if output_streams is not None:
225
 
            for stream_element in output_streams:
226
 
                stream = self._loadStream(stream_element)
227
 
                factory.addOutputStream(stream)
228
 
 
229
 
        if issubclass(klass, FileSourceFactory):
230
 
            filename1 = self.validateSourceURI(filename, factory)
231
 
            if filename != filename1:
232
 
                # the file was moved
233
 
                factory.uri = filename1
234
 
                factory.filename = filename1
235
 
 
236
 
        return factory
237
 
 
238
 
    def _saveFileSourceFactory(self, element, source):
239
 
        # FIXME: we should probably have a rule that we only deal with unicode
240
 
        # strings in pitivi
241
 
        if not isinstance(source.filename, unicode):
242
 
            fs_encoding = get_filesystem_encoding()
243
 
            filename = source.filename.decode(fs_encoding)
244
 
        else:
245
 
            filename = source.filename
246
 
        element.attrib["filename"] = filename
247
 
 
248
 
        return element
249
 
 
250
 
    def _saveFactoryRef(self, factory):
251
 
        element = Element("factory-ref")
252
 
        element.attrib["id"] = self._context.factories[factory].attrib["id"]
253
 
 
254
 
        return element
255
 
 
256
 
    def _loadFactoryRef(self, element):
257
 
        return self._context.factories[element.attrib["id"]]
258
 
 
259
 
    def _saveFactories(self, factories):
260
 
        element = Element("factories")
261
 
        sources = SubElement(element, "sources")
262
 
        for factory in factories:
263
 
            if isinstance(factory, SourceFactory):
264
 
                source_element = self._saveSource(factory)
265
 
                sources.append(source_element)
266
 
 
267
 
        return element
268
 
 
269
 
    def _loadSources(self):
270
 
        """Deserialize the sources.
271
 
 
272
 
        @return: A list of SourceFactory objects.
273
 
        """
274
 
        sources = self.factoriesnode.find("sources")
275
 
        res = []
276
 
        # For each <source> element in the <sources> element, create the
277
 
        # object represented by it.
278
 
        for src in sources:
279
 
            res.append(self._loadFactory(src))
280
 
        return res
281
 
 
282
 
    def _serializeDict(self, element, values_dict):
283
 
        """Serialize the specified dict into the specified Element instance."""
284
 
        for key, value in values_dict.iteritems():
285
 
            if isinstance(value, str):
286
 
                # TODO: If this starts with "(<type>)", or if it contains ";",
287
 
                # the deserialization might fail.
288
 
                serialized_value = value
289
 
            elif isinstance(value, bool):
290
 
                serialized_value = "(boolean) %r" % value
291
 
            elif isinstance(value, float):
292
 
                serialized_value = "(float) %r" % value
293
 
            else:
294
 
                serialized_value = "(guint64) %r" % value
295
 
            element.attrib[key] = serialized_value
296
 
 
297
 
    def _deserializeDict(self, element):
298
 
        """Get the specified Element as a deserialized dict."""
299
 
        values_dict = {}
300
 
        for name, value_string in element.attrib.iteritems():
301
 
            values_dict[name] = self._parsePropertyValue(value_string)
302
 
        return values_dict
303
 
 
304
 
    def _saveProjectSettings(self, settings):
305
 
        element = Element('export-settings')
306
 
        element.attrib["videowidth"] = str(int(settings.videowidth))
307
 
        element.attrib["videoheight"] = str(int(settings.videoheight))
308
 
        element.attrib["render-scale"] = str(int(settings.render_scale))
309
 
        element.attrib["videorate-num"] = str(int(settings.videorate.num))
310
 
        element.attrib["videorate-denom"] = str(int(settings.videorate.denom))
311
 
        element.attrib["videopar-num"] = str(int(settings.videopar.num))
312
 
        element.attrib["videopar-denom"] = str(int(settings.videopar.denom))
313
 
        element.attrib["audiochannels"] = str(int(settings.audiochannels))
314
 
        element.attrib["audiorate"] = str(int(settings.audiorate))
315
 
        element.attrib["audiodepth"] = str(int(settings.audiodepth))
316
 
        element.attrib["vencoder"] = settings.vencoder or ""
317
 
        element.attrib["aencoder"] = settings.aencoder or ""
318
 
        element.attrib["muxer"] = settings.muxer
319
 
 
320
 
        # container/encoder settings
321
 
        if settings.containersettings != {}:
322
 
            ss = SubElement(element, "container-settings")
323
 
            self._serializeDict(ss, settings.containersettings)
324
 
        if settings.vcodecsettings != {}:
325
 
            ss = SubElement(element, "vcodec-settings")
326
 
            self._serializeDict(ss, settings.vcodecsettings)
327
 
        if settings.acodecsettings != {}:
328
 
            ss = SubElement(element, "acodec-settings")
329
 
            self._serializeDict(ss, settings.acodecsettings)
330
 
        return element
331
 
 
332
 
    def _saveProjectMetadata(self, project):
333
 
        element = Element('metadata')
334
 
        element.attrib["author"] = project.author
335
 
        element.attrib["name"] = project.name
336
 
        element.attrib["year"] = project.year
337
 
        return element
338
 
 
339
 
    def _loadProjectSettings(self, element):
340
 
        self.debug("element:%r", element)
341
 
        settings = ExportSettings()
342
 
        settings.videowidth = int(element.attrib["videowidth"])
343
 
        settings.videoheight = int(element.attrib["videoheight"])
344
 
        if "render-scale" in element.attrib:
345
 
            settings.render_scale = int(element.attrib["render-scale"])
346
 
        settings.videorate = gst.Fraction(int(element.attrib["videorate-num"]),
347
 
                                         int(element.attrib["videorate-denom"]))
348
 
        settings.videopar = gst.Fraction(int(element.attrib["videopar-num"]),
349
 
                                         int(element.attrib["videopar-denom"]))
350
 
        settings.audiochannels = int(element.attrib["audiochannels"])
351
 
        settings.audiorate = int(element.attrib["audiorate"])
352
 
        settings.audiodepth = int(element.attrib["audiodepth"])
353
 
        settings.aencoder = element.attrib["aencoder"]
354
 
        settings.vencoder = element.attrib["vencoder"]
355
 
        settings.muxer = element.attrib["muxer"]
356
 
 
357
 
        sett = element.find("container-settings")
358
 
        if sett != None:
359
 
            settings.containersettings = self._deserializeDict(sett)
360
 
        sett = element.find("vcodec-settings")
361
 
        if sett != None:
362
 
            settings.vcodecsettings = self._deserializeDict(sett)
363
 
        sett = element.find("acodec-settings")
364
 
        if sett != None:
365
 
            settings.acodecsettings = self._deserializeDict(sett)
366
 
 
367
 
        return settings
368
 
 
369
 
    def _loadProjectMetadata(self, element, project):
370
 
        project.name = element.attrib["name"]
371
 
        project.author = element.attrib["author"]
372
 
        project.year = element.attrib["year"]
373
 
 
374
 
    def _saveTrackObject(self, track_object):
375
 
        element = Element("track-object")
376
 
        element.attrib["id"] = self._new_element_id()
377
 
        element.attrib["type"] = qual(track_object.__class__)
378
 
        for attribute in ("start", "duration",
379
 
                "in_point", "media_duration"):
380
 
            element.attrib[attribute] = \
381
 
                    str("(gint64)%s" % getattr(track_object, attribute))
382
 
 
383
 
        element.attrib["priority"] = "(int)%s" % track_object.priority
384
 
        element.attrib["active"] = "(bool)%s" % track_object.active
385
 
 
386
 
        if not isinstance(track_object.factory, EffectFactory):
387
 
            self._saveSourceTrackObject(track_object, element)
388
 
        else:
389
 
            self._saveTrackEffect(track_object, element)
390
 
 
391
 
        self._context.track_objects[track_object] = element
392
 
 
393
 
        return element
394
 
 
395
 
    def _saveTrackEffect(self, track_object, element):
396
 
        effect_element = Element("effect")
397
 
        element.append(effect_element)
398
 
 
399
 
        factory_element = Element("factory")
400
 
        factory_element.attrib["name"] = track_object.factory.name
401
 
        effect_element.append(factory_element)
402
 
 
403
 
        self._saveEffectProperties(track_object, effect_element)
404
 
 
405
 
    def _saveEffectProperties(self, track_object, effect_element):
406
 
        effect_properties = Element("gst-element-properties")
407
 
        effect = track_object.getElement()
408
 
        properties = gobject.list_properties(effect)
409
 
        for prop in properties:
410
 
            if prop.flags & gobject.PARAM_READABLE:
411
 
                type_name = str(gobject.type_name(prop.value_type.fundamental))
412
 
                #FIXME we just take the int equivalent to the GEnum, how should it be handled?
413
 
                if type_name == "GEnum":
414
 
                    value = str(effect.get_property(prop.name).__int__())
415
 
                else:
416
 
                    value = str(effect.get_property(prop.name))
417
 
                effect_properties.attrib[prop.name] = '(' + type_name + ')' + value
418
 
        effect_element.append(effect_properties)
419
 
 
420
 
    def _saveSourceTrackObject(self, track_object, element):
421
 
        factory_ref = self._saveFactoryRef(track_object.factory)
422
 
        stream_ref = self._saveStreamRef(track_object.stream)
423
 
        element.append(factory_ref)
424
 
        element.append(stream_ref)
425
 
        interpolators = track_object.getInterpolators()
426
 
        curves = Element("curves")
427
 
        for property, interpolator in interpolators.itervalues():
428
 
            curves.append(self._saveInterpolator(interpolator, property))
429
 
        element.append(curves)
430
 
 
431
 
    def _loadTrackObject(self, track, element):
432
 
        self.debug("%r", element)
433
 
        klass = namedAny(element.attrib["type"])
434
 
        if klass is TrackEffect:
435
 
            track_object = self._loadEffectTrackObject(element, klass, track)
436
 
        else:
437
 
            track_object = self._loadSourceTrackObject(element, klass, track)
438
 
        return track_object
439
 
 
440
 
    def _loadEffectTrackObject(self, element, klass, track):
441
 
        effect_element = element.find('effect')
442
 
        factory_name = effect_element.find('factory').attrib['name']
443
 
        properties_elem = effect_element.find('gst-element-properties')
444
 
        try:
445
 
            factory = self.avalaible_effects.getFactoryFromName(factory_name)
446
 
        except KeyError:
447
 
            # TODO: Find a way to install the missing effect.
448
 
            raise FormatterError(_("The project contains effects which are not "
449
 
                                   "available on the system."))
450
 
 
451
 
        input_stream = factory.getInputStreams()
452
 
        if not input_stream:
453
 
            raise FormatterError("cant find effect factory input stream")
454
 
        input_stream = input_stream[0]
455
 
        track_object = klass(factory, input_stream)
456
 
        track.addTrackObject(track_object)
457
 
 
458
 
        for name, value_string in self._filterElementProperties(element):
459
 
            value = self._parsePropertyValue(value_string)
460
 
            setattr(track_object, name, value)
461
 
 
462
 
        effect_gst_element = track_object.getElement()
463
 
        for name, value in properties_elem.attrib.iteritems():
464
 
            value = self._parsePropertyValue(value)
465
 
            effect_gst_element.set_property(name, value)
466
 
 
467
 
        self._context.track_objects[element.attrib["id"]] = track_object
468
 
        return track_object
469
 
 
470
 
    def _loadSourceTrackObject(self, element, klass, track):
471
 
        factory_ref = element.find("factory-ref")
472
 
        factory = self._loadFactoryRef(factory_ref)
473
 
 
474
 
        stream_ref = element.find("stream-ref")
475
 
        stream = self._loadStreamRef(stream_ref)
476
 
 
477
 
        track_object = klass(factory, stream)
478
 
        for name, value_string in self._filterElementProperties(element):
479
 
            value = self._parsePropertyValue(value_string)
480
 
            setattr(track_object, name, value)
481
 
        track.addTrackObject(track_object)
482
 
        curves_element = element.find("curves")
483
 
        if curves_element is not None:
484
 
            for curve in curves_element.getchildren():
485
 
                self._loadInterpolator(curve, track_object)
486
 
 
487
 
        self._context.track_objects[element.attrib["id"]] = track_object
488
 
        return track_object
489
 
 
490
 
    def _saveInterpolator(self, interpolator, prop):
491
 
        typename = prop.value_type.name
492
 
        element = Element("curve", property=prop.name, type=typename,
493
 
            version="1")
494
 
 
495
 
        start = self._saveKeyframe(interpolator.start, typename, False)
496
 
        start.tag = "start"
497
 
        element.append(start)
498
 
 
499
 
        for kf in interpolator.getInteriorKeyframes():
500
 
            kfel = self._saveKeyframe(kf, typename)
501
 
            element.append(kfel)
502
 
 
503
 
        end = self._saveKeyframe(interpolator.end, typename, False)
504
 
        end.tag = "end"
505
 
        element.append(end)
506
 
        return element
507
 
 
508
 
    def _saveKeyframe(self, keyframe, typename, time=True):
509
 
        element = Element("keyframe")
510
 
        element.attrib["value"] = "(%s)%r" % (typename, keyframe.value)
511
 
        element.attrib["mode"] = str(keyframe.mode)
512
 
        if not time:
513
 
            return element
514
 
        element.attrib["time"] = str(keyframe.time)
515
 
        return element
516
 
 
517
 
    def _loadInterpolator(self, element, trackobject):
518
 
        interpolator = trackobject.getInterpolator(element.attrib["property"])
519
 
        start = element.find("start")
520
 
        interpolator.start.value = self._parsePropertyValue(
521
 
            start.attrib["value"])
522
 
        interpolator.start.mode = int(start.attrib["mode"])
523
 
 
524
 
        for kf in element.getiterator("keyframe"):
525
 
            interpolator.newKeyframe(long(kf.attrib["time"]),
526
 
                value=self._parsePropertyValue(kf.attrib["value"]),
527
 
                mode=int(kf.attrib["mode"]))
528
 
        end = element.find("end")
529
 
        interpolator.end.value = self._parsePropertyValue(end.attrib["value"])
530
 
        interpolator.end.mode = int(end.attrib["mode"])
531
 
 
532
 
        # if we are using old-style keyframe curves where start, end point
533
 
        # represent start of file, convert to the newer representation to
534
 
        # preserve the existing shape of the curve
535
 
        if not ("version" in element.attrib):
536
 
            # move start, end keyframes to start of file
537
 
            interpolator.updateMediaStart(0)
538
 
            interpolator.updateMediaStop(trackobject.factory.duration)
539
 
 
540
 
            # get the value of the curve at true start/end points
541
 
            startval = interpolator.valueAt(trackobject.in_point)
542
 
            endval = interpolator.valueAt(trackobject.out_point)
543
 
            interpolator.start.value = startval
544
 
            interpolator.end.value = endval
545
 
 
546
 
            # move start, end keyframes back to proper position
547
 
            interpolator.updateMediaStart(trackobject.in_point)
548
 
            interpolator.updateMediaStop(trackobject.out_point)
549
 
 
550
 
    def _saveTrackObjectRef(self, track_object):
551
 
        element = Element("track-object-ref")
552
 
        element.attrib["id"] = self._context.track_objects[track_object].attrib["id"]
553
 
 
554
 
        return element
555
 
 
556
 
    def _loadTrackObjectRef(self, element):
557
 
        self.debug("%r", element)
558
 
        return self._context.track_objects[element.attrib["id"]]
559
 
 
560
 
    def _saveTrackObjectRefs(self, track_objects):
561
 
        element = Element("track-object-refs")
562
 
 
563
 
        for track_object in track_objects:
564
 
            track_object_ref = self._saveTrackObjectRef(track_object)
565
 
            element.append(track_object_ref)
566
 
 
567
 
        return element
568
 
 
569
 
    def _loadTrackObjectRefs(self, element):
570
 
        self.debug("%r", element)
571
 
        track_objects = []
572
 
        for track_object_element in element:
573
 
            track_object = self._loadTrackObjectRef(track_object_element)
574
 
            track_objects.append(track_object)
575
 
 
576
 
        return track_objects
577
 
 
578
 
    def _saveTrack(self, track):
579
 
        element = Element("track")
580
 
        stream_element = self._saveStream(track.stream)
581
 
        element.append(stream_element)
582
 
        track_objects = SubElement(element, "track-objects")
583
 
 
584
 
        for track_object in track.track_objects:
585
 
 
586
 
            track_object_element = self._saveTrackObject(track_object)
587
 
            track_objects.append(track_object_element)
588
 
 
589
 
        return element
590
 
 
591
 
    def _loadTrack(self, element):
592
 
        self.debug("%r", element)
593
 
        stream_element = element.find("stream")
594
 
        stream = self._loadStream(stream_element)
595
 
 
596
 
        track = Track(stream)
597
 
 
598
 
        track_objects_element = element.find("track-objects")
599
 
        for track_object_element in track_objects_element:
600
 
            self._loadTrackObject(track, track_object_element)
601
 
 
602
 
        return track
603
 
 
604
 
    def _saveTracks(self, tracks):
605
 
        element = Element("tracks")
606
 
        for track in tracks:
607
 
            track_element = self._saveTrack(track)
608
 
            element.append(track_element)
609
 
 
610
 
        return element
611
 
 
612
 
    def _loadTracks(self, element):
613
 
        self.debug("element:%r", element)
614
 
        tracks = []
615
 
        for track_element in element:
616
 
            track = self._loadTrack(track_element)
617
 
            tracks.append(track)
618
 
 
619
 
        return tracks
620
 
 
621
 
    ## TimelineObjects
622
 
 
623
 
    def _saveTimelineObject(self, timeline_object):
624
 
        element = Element("timeline-object")
625
 
        factory_ref = self._saveFactoryRef(timeline_object.factory)
626
 
        element.append(factory_ref)
627
 
        track_object_refs = \
628
 
                self._saveTrackObjectRefs(timeline_object.track_objects)
629
 
        element.append(track_object_refs)
630
 
 
631
 
        return element
632
 
 
633
 
    def _loadTimelineObject(self, element):
634
 
        factory_ref = element.find("factory-ref")
635
 
        factory = self._loadFactoryRef(factory_ref)
636
 
 
637
 
        timeline_object = TimelineObject(factory)
638
 
        track_object_refs_element = element.find("track-object-refs")
639
 
        track_objects = \
640
 
                self._loadTrackObjectRefs(track_object_refs_element)
641
 
 
642
 
        for track_object in track_objects:
643
 
            timeline_object.addTrackObject(track_object)
644
 
 
645
 
        return timeline_object
646
 
 
647
 
    def _saveTimelineObjects(self, timeline_objects):
648
 
        element = Element("timeline-objects")
649
 
        for timeline_object in timeline_objects:
650
 
            timeline_object_element = self._saveTimelineObject(timeline_object)
651
 
            element.append(timeline_object_element)
652
 
 
653
 
        return element
654
 
 
655
 
    def _loadTimelineObjects(self, element):
656
 
        timeline_objects = []
657
 
        for timeline_object_element in element:
658
 
            timeline_object = \
659
 
                    self._loadTimelineObject(timeline_object_element)
660
 
            timeline_objects.append(timeline_object)
661
 
 
662
 
        return timeline_objects
663
 
 
664
 
    ## Timeline
665
 
 
666
 
    def _saveTimeline(self, timeline):
667
 
        element = Element("timeline")
668
 
 
669
 
        tracks = self._saveTracks(timeline.tracks)
670
 
        element.append(tracks)
671
 
 
672
 
        timeline_objects = \
673
 
                self._saveTimelineObjects(timeline.timeline_objects)
674
 
        element.append(timeline_objects)
675
 
 
676
 
        return element
677
 
 
678
 
    def _loadTimeline(self, element):
679
 
        self.debug("element:%r", element)
680
 
 
681
 
        timeline = self.project.timeline
682
 
 
683
 
        # Tracks
684
 
        tracks_element = element.find("tracks")
685
 
        tracks = self._loadTracks(tracks_element)
686
 
 
687
 
        # Timeline Object
688
 
        timeline_objects_element = element.find("timeline-objects")
689
 
        timeline_objects = \
690
 
                self._loadTimelineObjects(timeline_objects_element)
691
 
 
692
 
        for track in tracks:
693
 
            timeline.addTrack(track)
694
 
 
695
 
        # add the timeline objects
696
 
        for timeline_object in timeline_objects:
697
 
            # NOTE: this is a low-level routine that simply appends the
698
 
            # timeline object to the timeline list. It doesn't ensure all the
699
 
            # child track objects have been added to their respective tracks.
700
 
            timeline.addTimelineObject(timeline_object)
701
 
 
702
 
        return timeline
703
 
 
704
 
    ## Main methods
705
 
 
706
 
    def _saveMainTag(self):
707
 
        element = Element("pitivi")
708
 
        element.attrib["formatter"] = "etree"
709
 
        element.attrib["version"] = version
710
 
 
711
 
        return element
712
 
 
713
 
    def _serializeProject(self, project):
714
 
        root = self._saveMainTag()
715
 
 
716
 
        # settings
717
 
        if project.settings:
718
 
            root.append(self._saveProjectSettings(project.settings))
719
 
 
720
 
        # metadata
721
 
        root.append(self._saveProjectMetadata(project))
722
 
 
723
 
        # sources
724
 
        root.append(self._saveFactories(project.sources.getSources()))
725
 
 
726
 
        # timeline
727
 
        root.append(self._saveTimeline(project.timeline))
728
 
        return root
729
 
 
730
 
    ## Formatter method implementations
731
 
 
732
 
    def _saveProject(self, project, location):
733
 
        root = self._serializeProject(project)
734
 
        f = file(location.split('file://')[1], "w")
735
 
        indent(root)
736
 
        f.write(tostring(root))
737
 
        f.close()
738
 
 
739
 
        return True
740
 
 
741
 
    def _loadProject(self, project_uri, project):
742
 
        self.debug("project_uri:%s, project:%r", project_uri, project)
743
 
        # open the given location
744
 
        self._context.rootelement = parse(project_uri.split('://', 1)[1])
745
 
        self.factoriesnode = self._context.rootelement.find("factories")
746
 
        self.timelinenode = self._context.rootelement.find("timeline")
747
 
        self._settingsnode = self._context.rootelement.find("export-settings")
748
 
        self._metadatanode = self._context.rootelement.find("metadata")
749
 
 
750
 
        if self._settingsnode != None:
751
 
            project.setSettings(self._loadProjectSettings(self._settingsnode))
752
 
 
753
 
        if self._metadatanode != None:
754
 
            self._loadProjectMetadata(self._metadatanode, project)
755
 
        # rediscover the factories
756
 
        closure = {"rediscovered": 0}
757
 
        try:
758
 
            sources = self._getSources()
759
 
        except FormatterError, e:
760
 
            self.emit("new-project-failed", project_uri, e)
761
 
            return
762
 
 
763
 
        uris = [source.uri for source in sources]
764
 
        project.sources.nb_file_to_import = len(uris)
765
 
        discoverer = project.sources.discoverer
766
 
        discoverer.connect("discovery-done", self._discovererDiscoveryDoneCb,
767
 
                project, sources, uris, closure)
768
 
        discoverer.connect("discovery-error", self._discovererDiscoveryErrorCb,
769
 
                project, sources, uris, closure, project_uri)
770
 
 
771
 
        if not sources:
772
 
            self._finishLoadingProject(project)
773
 
            return
774
 
        # start the rediscovering from the first source
775
 
        source = sources[0]
776
 
        discoverer.addUri(source.uri)
777
 
 
778
 
    def _findFactoryContextKey(self, old_factory):
779
 
        key = None
780
 
        for k, old_factory1 in self._context.factories.iteritems():
781
 
            if old_factory is old_factory1:
782
 
                key = k
783
 
                break
784
 
 
785
 
        return key
786
 
 
787
 
    def _matchFactoryStreams(self, factory, old_factory):
788
 
        old_streams = old_factory.getOutputStreams()
789
 
        streams = factory.getOutputStreams()
790
 
        self.debug("matching factory streams old (%s) %s new (%s) %s",
791
 
                len(old_streams), old_streams, len(streams), streams)
792
 
        if len(old_streams) != len(streams):
793
 
            raise FormatterError("cant find all streams")
794
 
 
795
 
        stream_map = match_stream_groups_map(old_streams, streams)
796
 
        self.debug("stream map (%s) %s", len(stream_map), stream_map)
797
 
        if len(stream_map) != len(old_streams):
798
 
            raise FormatterError("streams don't match")
799
 
 
800
 
        return stream_map
801
 
 
802
 
    def _replaceOldFactoryStreams(self, factory, old_factory):
803
 
        old_stream_to_new_stream = self._matchFactoryStreams(factory,
804
 
                old_factory)
805
 
 
806
 
        new_streams = {}
807
 
        for stream_id, old_stream in self._context.streams.iteritems():
808
 
            try:
809
 
                new_stream = old_stream_to_new_stream[old_stream]
810
 
            except KeyError:
811
 
                new_stream = old_stream
812
 
            new_streams[stream_id] = new_stream
813
 
 
814
 
        self._context.streams = new_streams
815
 
 
816
 
    def _replaceMatchingOldFactory(self, factory, old_factories):
817
 
        old_factory = None
818
 
        old_factory_index = None
819
 
        for index, old_factory1 in enumerate(old_factories):
820
 
            if old_factory1.uri == factory.uri:
821
 
                old_factory = old_factory1
822
 
                old_factory_index = index
823
 
                break
824
 
 
825
 
        # this should never happen
826
 
        assert old_factory is not None
827
 
 
828
 
        # replace the old factory with the new rediscovered one
829
 
        old_factories[old_factory_index] = factory
830
 
 
831
 
        # make self._context.factories[key] point to the new factory
832
 
        context_key = self._findFactoryContextKey(old_factory)
833
 
        self._context.factories[context_key] = factory
834
 
 
835
 
        self._replaceOldFactoryStreams(factory, old_factory)
836
 
 
837
 
    def _discovererDiscoveryDoneCb(self, discoverer, uri, factory,
838
 
            project, old_factories, uris, closure):
839
 
        if factory.uri not in uris:
840
 
            # someone else is using discoverer, this signal isn't for us
841
 
            return
842
 
 
843
 
        self._replaceMatchingOldFactory(factory, old_factories)
844
 
        project.sources.addFactory(factory)
845
 
 
846
 
        closure["rediscovered"] += 1
847
 
        if closure["rediscovered"] == len(old_factories):
848
 
            self._finishLoadingProject(project)
849
 
            return
850
 
 
851
 
        # schedule the next source
852
 
        next = old_factories[closure["rediscovered"]]
853
 
        discoverer.addUri(next.uri)
854
 
 
855
 
    def _discovererDiscoveryErrorCb(self, discoverer, uri, error, detail,
856
 
            project, sources, uris, closure, project_uri):
857
 
        if uri not in uris:
858
 
            # someone else is using discoverer, this signal isn't for us
859
 
            return
860
 
 
861
 
        message = _("Failed loading %(uri)s.") % {"uri": uri}
862
 
        message += "\n\n%s" % error
863
 
        if detail:
864
 
            message += "\n\n%s" % detail
865
 
        formatter_error = FormatterError(message)
866
 
        self.emit("new-project-failed", project_uri, formatter_error)
867
 
 
868
 
    def newProject(self):
869
 
        project = Formatter.newProject(self)
870
 
        # add the settings
871
 
        if self._settingsnode != None:
872
 
            project.setSettings(self._loadProjectSettings(self._settingsnode))
873
 
 
874
 
        # add metadata
875
 
        if self._metadatanode != None:
876
 
            self._loadProjectMetadata(self._metadatanode, project)
877
 
 
878
 
        return project
879
 
 
880
 
    def _getSources(self):
881
 
        self.debug("%r", self)
882
 
        if self._sources is None:
883
 
            self._sources = self._loadSources()
884
 
        return self._sources
885
 
 
886
 
    def _fillTimeline(self):
887
 
        # fill up self.project
888
 
        self._loadTimeline(self.timelinenode)
889
 
 
890
 
    @classmethod
891
 
    def canHandle(cls, uri):
892
 
        return uri.endswith(".xptv")