1
# PiTiVi , Non-linear video editor
5
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
6
# Copyright (c) 2009, Alessandro Decina <alessandro.d@gmail.com>
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.
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.
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.
23
# set of utility functions
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
45
UNKNOWN_DURATION = 2 ** 63 - 1
47
native_endianness = struct.pack('=I', 0x34333231)
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]
56
return (a <= b) and (b <= c)
59
def time_to_string(value):
61
Converts the given time in nanoseconds to a human readable string
65
if value == gst.CLOCK_TIME_NONE:
67
ms = value / gst.MSECOND
74
return "%01d:%02d:%02d.%03d" % (hours, mins, sec, ms)
77
def beautify_length(length):
79
Converts the given time in nanoseconds to a human readable string
81
sec = length / gst.SECOND
89
parts.append(ngettext("%d hour", "%d hours", hours) % hours)
92
parts.append(ngettext("%d minute", "%d minutes", mins) % mins)
95
parts.append(ngettext("%d second", "%d seconds", sec) % sec)
97
return ", ".join(parts)
100
def beautify_ETA(length):
102
Converts the given time in nanoseconds to a fuzzy estimate,
103
intended for progress ETAs, not to indicate a clip's duration.
105
sec = length / gst.SECOND
113
parts.append(ngettext("%d hour", "%d hours", hours) % hours)
116
parts.append(ngettext("%d minute", "%d minutes", mins) % mins)
118
if not hours and mins < 2 and sec:
119
parts.append(ngettext("%d second", "%d seconds", sec) % sec)
121
return ", ".join(parts)
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.
129
@param function: the function to call
130
@type function: callable({any args})
134
function(*args, **kwargs)
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):
142
if not isinstance(element, gst.Element):
147
if isinstance(elt, gst.Bin) and bin_contains(elt, element):
151
# Python re-implementation of binary search algorithm found here:
152
# http://en.wikipedia.org/wiki/Binary_search
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.
163
def binary_search(col, value):
167
mid = (low + high) / 2
168
if (col[mid] < value):
171
#can't be high = mid-1: here col[mid] >= value,
172
#so high can't be < mid if col[mid] == value
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):
183
diff = abs(res - item)
185
# binary_search returns largest element closest to item.
186
# if there is a smaller element...
188
res_a = seq[index - 1]
189
# ...and it is closer to the pointer...
190
diff_a = abs(res_a - item)
197
return res, diff, index
200
def argmax(func, seq):
201
"""return the element of seq that gives max(map(func, seq))"""
206
# using a generator expression here should save memory
207
objs = ((func(val), val) for val in seq)
208
return reduce(compare, objs)[1]
220
def data_probe(pad, data, section=""):
221
"""Callback to use for gst.Pad.add_*_probe.
223
The extra argument will be used to prefix the debug messages
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)
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))
248
def linkDynamic(element, target):
250
def pad_added(bin, pad, target):
251
compatpad = target.get_compatible_pad(pad)
253
pad.link_full(compatpad, gst.PAD_LINK_CHECK_NOTHING)
254
element.connect("pad-added", pad_added, target)
257
def element_make_many(*args):
258
return tuple((gst.element_factory_make(arg) for arg in args))
262
E = graph.iteritems()
270
except gst.LinkError:
276
f = gst.element_factory_make("capsfilter")
277
f.props.caps = gst.caps_from_string(caps)
282
def isWritable(path):
283
"""Check if the file/path is writable"""
285
# Needs to be "rw", not "w", otherwise you'll corrupt files
293
def uri_is_valid(uri):
294
"""Checks if the given uri is a valid uri (of type file://)
296
Will also check if the size is valid (> 0).
298
@param uri: The location to check
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)
306
def uri_is_reachable(uri):
307
""" Check whether the given uri is reachable and we can read/write
310
@param uri: The location to check
312
@return: C{True} if the uri is reachable.
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))
323
class PropertyChangeTracker(Signallable):
331
def connectToObject(self, 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)
340
def _takeCurrentSnapshot(self, obj):
342
for property_name in self.property_names:
343
properties[property_name] = \
344
getattr(obj, property_name.replace("-", "_"))
348
def disconnectFromObject(self, obj):
350
obj.disconnect_by_func(self._propertyChangedCb)
352
def _propertyChangedCb(self, object, value, property_name):
353
old_value = self.properties[property_name]
354
self.properties[property_name] = value
356
self.emit(property_name + '-changed', object, old_value, value)
359
class Seeker(Signallable):
360
__signals__ = {'seek': ['position', 'format']}
362
def __init__(self, timeout):
363
self.timeout = timeout
364
self.pending_seek_id = None
368
def seek(self, position, format=gst.FORMAT_TIME, on_idle=False):
369
self.position = position
372
if self.pending_seek_id is None:
374
gobject.idle_add(self._seekTimeoutCb)
376
self._seekTimeoutCb()
377
self.pending_seek_id = self._scheduleSeek(self.timeout,
380
def _scheduleSeek(self, timeout, callback):
381
return gobject.timeout_add(timeout, callback)
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
389
self.emit('seek', position, format)
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
399
def get_filesystem_encoding():
400
return sys.getfilesystemencoding() or "utf-8"
403
def get_controllable_properties(element):
405
Returns a list of controllable properties for the given
406
element (and child if it's a container).
408
The list is made of tuples containing:
412
log.debug("utils", "element %r, %d", element, isinstance(element, gst.Bin))
414
if isinstance(element, gst.Bin):
415
for child in element.elements():
416
res.extend(get_controllable_properties(child))
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))
425
def start_insort_left(a, x, lo=0, hi=None):
430
if a[mid].start < x.start:
437
def start_insort_right(a, x, lo=0, hi=None):
442
if x.start < a[mid].start:
449
def start_bisect_left(a, x, lo=0, hi=None):
454
if a[mid].start < x.start:
461
class Infinity(object):
462
def __cmp__(self, other):
463
if isinstance(other, Infinity):
468
infinity = Infinity()
471
def findObject(obj, objects):
475
low = start_bisect_left(objects, obj, lo=low)
479
if objects[low] is obj:
487
def getPreviousObject(obj, objects, priority=-1, skip=None):
489
priority = obj.priority
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]
499
if skip is not None and skip(prev_obj):
502
if prev_obj.start != obj.start:
505
if priority is None or prev_obj.priority == priority:
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)):
521
def getNextObject(obj, objects, priority=-1, skip=None):
523
priority = obj.priority
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)):
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)
547
if self._factories is None:
548
self._buildFactories()
550
return self._factories
552
def _buildFactories(self):
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)
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)
565
def _registryFeatureAddedCb(self, registry, feature):
566
# invalidate the cache
567
log.warning("utils", "New feature added, invalidating cached factories")
568
self._factories = None
571
def profile(func, profiler_filename="result.prof"):
574
output_filename = profiler_filename
575
while os.path.exists(output_filename):
576
output_filename = profiler_filename + str(counter)
579
def _wrapper(*args, **kwargs):
581
cProfile.runctx("result = local_func(*args, **kwargs)", globals(), locals(),
582
filename=output_filename)
583
return locals()["result"]
588
def formatPercent(value):
589
return "%3d%%" % (value * 100)
592
def quantize(input, interval):
593
return (input // interval) * interval
596
def show_user_manual():
597
time_now = int(time.time())
598
for uri in (APPMANUALURL_OFFLINE, APPMANUALURL_ONLINE):
600
gtk.show_uri(None, uri, time_now)
603
log.debug("utils", "Failed loading URI %s: %s", uri, e)
605
log.warning("utils", "Failed loading URIs")
606
# TODO: Show an error message to the user.