~timo-jyrinki/ubuntu/trusty/pitivi/backport_utopic_fixes

« back to all changes in this revision

Viewing changes to pitivi/utils.py

  • Committer: Package Import Robot
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 15:28:16 UTC
  • mfrom: (6.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20140405152816-6lijoax4cngiz5j5
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
 
#       utils.py
4
 
#
5
 
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
6
 
# Copyright (c) 2009, Alessandro Decina <alessandro.d@gmail.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
 
# set of utility functions
24
 
 
25
 
import sys
26
 
import gio
27
 
import gobject
28
 
import gst
29
 
import gtk
30
 
import bisect
31
 
import os
32
 
import struct
33
 
import time
34
 
 
35
 
from pitivi.configure import APPMANUALURL_OFFLINE, APPMANUALURL_ONLINE
36
 
from pitivi.signalinterface import Signallable
37
 
import pitivi.log.log as log
38
 
from gettext import ngettext
39
 
try:
40
 
    import cProfile
41
 
except ImportError:
42
 
    pass
43
 
 
44
 
 
45
 
UNKNOWN_DURATION = 2 ** 63 - 1
46
 
 
47
 
native_endianness = struct.pack('=I', 0x34333231)
48
 
 
49
 
big_to_cairo_alpha_mask = struct.unpack('=i', '\xFF\x00\x00\x00')[0]
50
 
big_to_cairo_red_mask = struct.unpack('=i', '\x00\xFF\x00\x00')[0]
51
 
big_to_cairo_green_mask = struct.unpack('=i', '\x00\x00\xFF\x00')[0]
52
 
big_to_cairo_blue_mask = struct.unpack('=i', '\x00\x00\x00\xFF')[0]
53
 
 
54
 
 
55
 
def between(a, b, c):
56
 
    return (a <= b) and (b <= c)
57
 
 
58
 
 
59
 
def time_to_string(value):
60
 
    """
61
 
    Converts the given time in nanoseconds to a human readable string
62
 
 
63
 
    Format HH:MM:SS.XXX
64
 
    """
65
 
    if value == gst.CLOCK_TIME_NONE:
66
 
        return "--:--:--.---"
67
 
    ms = value / gst.MSECOND
68
 
    sec = ms / 1000
69
 
    ms = ms % 1000
70
 
    mins = sec / 60
71
 
    sec = sec % 60
72
 
    hours = mins / 60
73
 
    mins = mins % 60
74
 
    return "%01d:%02d:%02d.%03d" % (hours, mins, sec, ms)
75
 
 
76
 
 
77
 
def beautify_length(length):
78
 
    """
79
 
    Converts the given time in nanoseconds to a human readable string
80
 
    """
81
 
    sec = length / gst.SECOND
82
 
    mins = sec / 60
83
 
    sec = sec % 60
84
 
    hours = mins / 60
85
 
    mins = mins % 60
86
 
 
87
 
    parts = []
88
 
    if hours:
89
 
        parts.append(ngettext("%d hour", "%d hours", hours) % hours)
90
 
 
91
 
    if mins:
92
 
        parts.append(ngettext("%d minute", "%d minutes", mins) % mins)
93
 
 
94
 
    if not hours and sec:
95
 
        parts.append(ngettext("%d second", "%d seconds", sec) % sec)
96
 
 
97
 
    return ", ".join(parts)
98
 
 
99
 
 
100
 
def beautify_ETA(length):
101
 
    """
102
 
    Converts the given time in nanoseconds to a fuzzy estimate,
103
 
    intended for progress ETAs, not to indicate a clip's duration.
104
 
    """
105
 
    sec = length / gst.SECOND
106
 
    mins = sec / 60
107
 
    sec = sec % 60
108
 
    hours = mins / 60
109
 
    mins = mins % 60
110
 
 
111
 
    parts = []
112
 
    if hours:
113
 
        parts.append(ngettext("%d hour", "%d hours", hours) % hours)
114
 
 
115
 
    if mins:
116
 
        parts.append(ngettext("%d minute", "%d minutes", mins) % mins)
117
 
 
118
 
    if not hours and mins < 2 and sec:
119
 
        parts.append(ngettext("%d second", "%d seconds", sec) % sec)
120
 
 
121
 
    return ", ".join(parts)
122
 
 
123
 
 
124
 
def call_false(function, *args, **kwargs):
125
 
    """ Helper function for calling an arbitrary function once in the gobject
126
 
        mainloop.  Any positional or keyword arguments after the function will
127
 
        be provided to the function.
128
 
 
129
 
    @param function: the function to call
130
 
    @type function: callable({any args})
131
 
    @returns: False
132
 
    @rtype: bool
133
 
    """
134
 
    function(*args, **kwargs)
135
 
    return False
136
 
 
137
 
 
138
 
def bin_contains(bin, element):
139
 
    """ Returns True if the bin contains the given element, the search is recursive """
140
 
    if not isinstance(bin, gst.Bin):
141
 
        return False
142
 
    if not isinstance(element, gst.Element):
143
 
        return False
144
 
    for elt in bin:
145
 
        if element is elt:
146
 
            return True
147
 
        if isinstance(elt, gst.Bin) and bin_contains(elt, element):
148
 
            return True
149
 
    return False
150
 
 
151
 
# Python re-implementation of binary search algorithm found here:
152
 
# http://en.wikipedia.org/wiki/Binary_search
153
 
#
154
 
# This is the iterative version without the early termination branch, which
155
 
# also tells us the element of A that are nearest to Value, if the element we
156
 
# want is not found. This is useful for implementing edge snaping in the UI,
157
 
# where we repeatedly search through a list of control points for the one
158
 
# closes to the cursor. Because we don't care whether the cursor position
159
 
# matches the list, this function returns the index of the lement closest to
160
 
# value in the array.
161
 
 
162
 
 
163
 
def binary_search(col, value):
164
 
    low = 0
165
 
    high = len(col)
166
 
    while (low < high):
167
 
        mid = (low + high) / 2
168
 
        if (col[mid] < value):
169
 
            low = mid + 1
170
 
        else:
171
 
            #can't be high = mid-1: here col[mid] >= value,
172
 
            #so high can't be < mid if col[mid] == value
173
 
            high = mid
174
 
    return low
175
 
 
176
 
 
177
 
# Returns the element of seq nearest to item, and the difference between them
178
 
def closest_item(seq, item, lo=0):
179
 
    index = bisect.bisect(seq, item, lo)
180
 
    if index >= len(seq):
181
 
        index = len(seq) - 1
182
 
    res = seq[index]
183
 
    diff = abs(res - item)
184
 
 
185
 
    # binary_search returns largest element closest to item.
186
 
    # if there is a smaller element...
187
 
    if index - 1 >= 0:
188
 
        res_a = seq[index - 1]
189
 
        # ...and it is closer to the pointer...
190
 
        diff_a = abs(res_a - item)
191
 
        if diff_a < diff:
192
 
            # ...use it instead.
193
 
            res = res_a
194
 
            diff = diff_a
195
 
            index = index - 1
196
 
 
197
 
    return res, diff, index
198
 
 
199
 
 
200
 
def argmax(func, seq):
201
 
    """return the element of seq that gives max(map(func, seq))"""
202
 
    def compare(a1, b1):
203
 
        if a1[0] > b1[0]:
204
 
            return a1
205
 
        return b1
206
 
    # using a generator expression here should save memory
207
 
    objs = ((func(val), val) for val in seq)
208
 
    return reduce(compare, objs)[1]
209
 
 
210
 
 
211
 
def same(seq):
212
 
    i = iter(seq)
213
 
    first = i.next()
214
 
    for item in i:
215
 
        if first != item:
216
 
            return None
217
 
    return first
218
 
 
219
 
 
220
 
def data_probe(pad, data, section=""):
221
 
    """Callback to use for gst.Pad.add_*_probe.
222
 
 
223
 
    The extra argument will be used to prefix the debug messages
224
 
    """
225
 
    if section == "":
226
 
        section = "%s:%s" % (pad.get_parent().get_name(), pad.get_name())
227
 
    if isinstance(data, gst.Buffer):
228
 
        log.debug("probe", "%s BUFFER timestamp:%s , duration:%s , size:%d , offset:%d , offset_end:%d",
229
 
                  section, gst.TIME_ARGS(data.timestamp), gst.TIME_ARGS(data.duration),
230
 
                  data.size, data.offset, data.offset_end)
231
 
        if data.flags & gst.BUFFER_FLAG_DELTA_UNIT:
232
 
            log.debug("probe", "%s DELTA_UNIT", section)
233
 
        if data.flags & gst.BUFFER_FLAG_DISCONT:
234
 
            log.debug("probe", "%s DISCONT", section)
235
 
        if data.flags & gst.BUFFER_FLAG_GAP:
236
 
            log.debug("probe", "%s GAP", section)
237
 
        log.debug("probe", "%s flags:%r", section, data.flags)
238
 
    else:
239
 
        log.debug("probe", "%s EVENT %s", section, data.type)
240
 
        if data.type == gst.EVENT_NEWSEGMENT:
241
 
            upd, rat, fmt, start, stop, pos = data.parse_new_segment()
242
 
            log.debug("probe", "%s Update:%r rate:%f fmt:%s, start:%s, stop:%s, pos:%s",
243
 
                      section, upd, rat, fmt, gst.TIME_ARGS(start),
244
 
                      gst.TIME_ARGS(stop), gst.TIME_ARGS(pos))
245
 
    return True
246
 
 
247
 
 
248
 
def linkDynamic(element, target):
249
 
 
250
 
    def pad_added(bin, pad, target):
251
 
        compatpad = target.get_compatible_pad(pad)
252
 
        if compatpad:
253
 
            pad.link_full(compatpad, gst.PAD_LINK_CHECK_NOTHING)
254
 
    element.connect("pad-added", pad_added, target)
255
 
 
256
 
 
257
 
def element_make_many(*args):
258
 
    return tuple((gst.element_factory_make(arg) for arg in args))
259
 
 
260
 
 
261
 
def pipeline(graph):
262
 
    E = graph.iteritems()
263
 
    V = graph.iterkeys()
264
 
    p = gst.Pipeline()
265
 
    p.add(*V)
266
 
    for u, v in E:
267
 
        if v:
268
 
            try:
269
 
                u.link(v)
270
 
            except gst.LinkError:
271
 
                linkDynamic(u, v)
272
 
    return p
273
 
 
274
 
 
275
 
def filter_(caps):
276
 
    f = gst.element_factory_make("capsfilter")
277
 
    f.props.caps = gst.caps_from_string(caps)
278
 
    return f
279
 
 
280
 
 
281
 
## URI functions
282
 
def isWritable(path):
283
 
    """Check if the file/path is writable"""
284
 
    try:
285
 
        # Needs to be "rw", not "w", otherwise you'll corrupt files
286
 
        f = open(path, "rw")
287
 
    except:
288
 
        return False
289
 
    f.close()
290
 
    return True
291
 
 
292
 
 
293
 
def uri_is_valid(uri):
294
 
    """Checks if the given uri is a valid uri (of type file://)
295
 
 
296
 
    Will also check if the size is valid (> 0).
297
 
 
298
 
    @param uri: The location to check
299
 
    @type uri: C{URI}
300
 
    """
301
 
    return (gst.uri_is_valid(uri) and
302
 
            gst.uri_get_protocol(uri) == "file" and
303
 
            len(os.path.basename(gst.uri_get_location(uri))) > 0)
304
 
 
305
 
 
306
 
def uri_is_reachable(uri):
307
 
    """ Check whether the given uri is reachable and we can read/write
308
 
    to it.
309
 
 
310
 
    @param uri: The location to check
311
 
    @type uri: C{URI}
312
 
    @return: C{True} if the uri is reachable.
313
 
    @rtype: C{bool}
314
 
    """
315
 
    if not uri_is_valid(uri):
316
 
        raise NotImplementedError(
317
 
            # Translators: "non local" means the project is not stored
318
 
            # on a local filesystem
319
 
            _("%s doesn't yet handle non-local projects") % APPNAME)
320
 
    return os.path.isfile(gst.uri_get_location(uri))
321
 
 
322
 
 
323
 
class PropertyChangeTracker(Signallable):
324
 
 
325
 
    __signals__ = {}
326
 
 
327
 
    def __init__(self):
328
 
        self.properties = {}
329
 
        self.obj = None
330
 
 
331
 
    def connectToObject(self, obj):
332
 
        self.obj = obj
333
 
        self.properties = self._takeCurrentSnapshot(obj)
334
 
        for property_name in self.property_names:
335
 
            signal_name = property_name + '-changed'
336
 
            self.__signals__[signal_name] = []
337
 
            obj.connect(signal_name,
338
 
                    self._propertyChangedCb, property_name)
339
 
 
340
 
    def _takeCurrentSnapshot(self, obj):
341
 
        properties = {}
342
 
        for property_name in self.property_names:
343
 
            properties[property_name] = \
344
 
                    getattr(obj, property_name.replace("-", "_"))
345
 
 
346
 
        return properties
347
 
 
348
 
    def disconnectFromObject(self, obj):
349
 
        self.obj = None
350
 
        obj.disconnect_by_func(self._propertyChangedCb)
351
 
 
352
 
    def _propertyChangedCb(self, object, value, property_name):
353
 
        old_value = self.properties[property_name]
354
 
        self.properties[property_name] = value
355
 
 
356
 
        self.emit(property_name + '-changed', object, old_value, value)
357
 
 
358
 
 
359
 
class Seeker(Signallable):
360
 
    __signals__ = {'seek': ['position', 'format']}
361
 
 
362
 
    def __init__(self, timeout):
363
 
        self.timeout = timeout
364
 
        self.pending_seek_id = None
365
 
        self.position = None
366
 
        self.format = None
367
 
 
368
 
    def seek(self, position, format=gst.FORMAT_TIME, on_idle=False):
369
 
        self.position = position
370
 
        self.format = format
371
 
 
372
 
        if self.pending_seek_id is None:
373
 
            if on_idle:
374
 
                gobject.idle_add(self._seekTimeoutCb)
375
 
            else:
376
 
                self._seekTimeoutCb()
377
 
            self.pending_seek_id = self._scheduleSeek(self.timeout,
378
 
                    self._seekTimeoutCb)
379
 
 
380
 
    def _scheduleSeek(self, timeout, callback):
381
 
        return gobject.timeout_add(timeout, callback)
382
 
 
383
 
    def _seekTimeoutCb(self):
384
 
        self.pending_seek_id = None
385
 
        if self.position != None and self.format != None:
386
 
            position, self.position = self.position, None
387
 
            format, self.format = self.format, None
388
 
            try:
389
 
                self.emit('seek', position, format)
390
 
            except:
391
 
                log.doLog(log.ERROR, None, "seeker", "Error while seeking to position:%s format:%r",
392
 
                          (gst.TIME_ARGS(position), format))
393
 
                # if an exception happened while seeking, properly
394
 
                # reset ourselves
395
 
                return False
396
 
        return False
397
 
 
398
 
 
399
 
def get_filesystem_encoding():
400
 
    return sys.getfilesystemencoding() or "utf-8"
401
 
 
402
 
 
403
 
def get_controllable_properties(element):
404
 
    """
405
 
    Returns a list of controllable properties for the given
406
 
    element (and child if it's a container).
407
 
 
408
 
    The list is made of tuples containing:
409
 
    * The GstObject
410
 
    * The GParamspec
411
 
    """
412
 
    log.debug("utils", "element %r, %d", element, isinstance(element, gst.Bin))
413
 
    res = []
414
 
    if isinstance(element, gst.Bin):
415
 
        for child in element.elements():
416
 
            res.extend(get_controllable_properties(child))
417
 
    else:
418
 
        for prop in gobject.list_properties(element):
419
 
            if prop.flags & gst.PARAM_CONTROLLABLE:
420
 
                log.debug("utils", "adding property %r", prop)
421
 
                res.append((element, prop))
422
 
    return res
423
 
 
424
 
 
425
 
def start_insort_left(a, x, lo=0, hi=None):
426
 
    if hi is None:
427
 
        hi = len(a)
428
 
    while lo < hi:
429
 
        mid = (lo + hi) // 2
430
 
        if a[mid].start < x.start:
431
 
            lo = mid + 1
432
 
        else:
433
 
            hi = mid
434
 
    a.insert(lo, x)
435
 
 
436
 
 
437
 
def start_insort_right(a, x, lo=0, hi=None):
438
 
    if hi is None:
439
 
        hi = len(a)
440
 
    while lo < hi:
441
 
        mid = (lo + hi) // 2
442
 
        if x.start < a[mid].start:
443
 
            hi = mid
444
 
        else:
445
 
            lo = mid + 1
446
 
    a.insert(lo, x)
447
 
 
448
 
 
449
 
def start_bisect_left(a, x, lo=0, hi=None):
450
 
    if hi is None:
451
 
        hi = len(a)
452
 
    while lo < hi:
453
 
        mid = (lo + hi) // 2
454
 
        if a[mid].start < x.start:
455
 
            lo = mid + 1
456
 
        else:
457
 
            hi = mid
458
 
    return lo
459
 
 
460
 
 
461
 
class Infinity(object):
462
 
    def __cmp__(self, other):
463
 
        if isinstance(other, Infinity):
464
 
            return 0
465
 
 
466
 
        return 1
467
 
 
468
 
infinity = Infinity()
469
 
 
470
 
 
471
 
def findObject(obj, objects):
472
 
    low = 0
473
 
    high = len(objects)
474
 
    while low < high:
475
 
        low = start_bisect_left(objects, obj, lo=low)
476
 
        if low == high:
477
 
            break
478
 
 
479
 
        if objects[low] is obj:
480
 
            return low
481
 
        else:
482
 
            low = low + 1
483
 
 
484
 
    return low
485
 
 
486
 
 
487
 
def getPreviousObject(obj, objects, priority=-1, skip=None):
488
 
    if priority == -1:
489
 
        priority = obj.priority
490
 
 
491
 
    obj_index = findObject(obj, objects)
492
 
    if obj_index is None:
493
 
        raise Exception("woot this should never happen")
494
 
    # check if there are same-start objects
495
 
    prev_obj_index = obj_index + 1
496
 
    while prev_obj_index < len(objects):
497
 
        prev_obj = objects[prev_obj_index]
498
 
        prev_obj_index += 1
499
 
        if skip is not None and skip(prev_obj):
500
 
            continue
501
 
 
502
 
        if prev_obj.start != obj.start:
503
 
            break
504
 
 
505
 
        if priority is None or prev_obj.priority == priority:
506
 
            return prev_obj
507
 
 
508
 
    # check if there are objects with start < obj.start
509
 
    prev_obj_index = obj_index - 1
510
 
    while prev_obj_index >= 0:
511
 
        prev_obj = objects[prev_obj_index]
512
 
        if (priority is None or prev_obj.priority == priority) \
513
 
                and (skip is None or not skip(prev_obj)):
514
 
            return prev_obj
515
 
 
516
 
        prev_obj_index -= 1
517
 
 
518
 
    return None
519
 
 
520
 
 
521
 
def getNextObject(obj, objects, priority=-1, skip=None):
522
 
    if priority == -1:
523
 
        priority = obj.priority
524
 
 
525
 
    obj_index = findObject(obj, objects)
526
 
    next_obj_index = obj_index + 1
527
 
    objs_len = len(objects)
528
 
    while next_obj_index < objs_len:
529
 
        next_obj = objects[next_obj_index]
530
 
        if (priority is None or next_obj.priority == priority) and \
531
 
                (skip is None or not skip(next_obj)):
532
 
            return next_obj
533
 
 
534
 
        next_obj_index += 1
535
 
 
536
 
    return None
537
 
 
538
 
 
539
 
class CachedFactoryList(object):
540
 
    def __init__(self, factoryFilter=None):
541
 
        self._factoryFilter = factoryFilter
542
 
        self._factories = None
543
 
        self._registry = gst.registry_get_default()
544
 
        self._registry.connect("feature-added", self._registryFeatureAddedCb)
545
 
 
546
 
    def get(self):
547
 
        if self._factories is None:
548
 
            self._buildFactories()
549
 
 
550
 
        return self._factories
551
 
 
552
 
    def _buildFactories(self):
553
 
        # build the cache
554
 
        log.debug("utils", "Getting factories list")
555
 
        factories = self._registry.get_feature_list(gst.ElementFactory)
556
 
        if self._factoryFilter is not None:
557
 
            log.debug("utils", "filtering")
558
 
            factories = filter(self._factoryFilter, factories)
559
 
 
560
 
        log.debug("utils", "Sorting by rank")
561
 
        factories.sort(key=lambda factory: factory.get_rank(), reverse=True)
562
 
        self._factories = factories
563
 
        log.debug("utils", "Cached factories is now %r", self._factories)
564
 
 
565
 
    def _registryFeatureAddedCb(self, registry, feature):
566
 
        # invalidate the cache
567
 
        log.warning("utils", "New feature added, invalidating cached factories")
568
 
        self._factories = None
569
 
 
570
 
 
571
 
def profile(func, profiler_filename="result.prof"):
572
 
    import os.path
573
 
    counter = 1
574
 
    output_filename = profiler_filename
575
 
    while os.path.exists(output_filename):
576
 
        output_filename = profiler_filename + str(counter)
577
 
        counter += 1
578
 
 
579
 
    def _wrapper(*args, **kwargs):
580
 
        local_func = func
581
 
        cProfile.runctx("result = local_func(*args, **kwargs)", globals(), locals(),
582
 
                        filename=output_filename)
583
 
        return locals()["result"]
584
 
 
585
 
    return _wrapper
586
 
 
587
 
 
588
 
def formatPercent(value):
589
 
    return "%3d%%" % (value * 100)
590
 
 
591
 
 
592
 
def quantize(input, interval):
593
 
    return (input // interval) * interval
594
 
 
595
 
 
596
 
def show_user_manual():
597
 
    time_now = int(time.time())
598
 
    for uri in (APPMANUALURL_OFFLINE, APPMANUALURL_ONLINE):
599
 
        try:
600
 
            gtk.show_uri(None, uri, time_now)
601
 
            return
602
 
        except Exception, e:
603
 
            log.debug("utils", "Failed loading URI %s: %s", uri, e)
604
 
            continue
605
 
    log.warning("utils", "Failed loading URIs")
606
 
    # TODO: Show an error message to the user.