From: Mark Wooding Date: Tue, 17 Apr 2018 21:34:31 +0000 (+0100) Subject: gremlin/gremlin.in: Fix things for new GI-based GStreamer etc. bindings. X-Git-Url: https://git.distorted.org.uk/~mdw/autoys/commitdiff_plain/3bf73acf248b6c874719bdbaf26e0ccea2a8665f gremlin/gremlin.in: Fix things for new GI-based GStreamer etc. bindings. Quite a lot of mostly petty details need changing, because stable interfaces are for losers who can't cope with the world changing every five minutes. --- diff --git a/debian/control b/debian/control index 458b77f..f59ecff 100644 --- a/debian/control +++ b/debian/control @@ -27,9 +27,8 @@ Package: gremlin Architecture: all Section: sound Depends: ${python:Depends}, - python-pyparsing, - python-gst0.10, python-gobject-2, - python-eyed3, python-imaging + python-pyparsing, python-gst-1.0, python-eyed3, python-imaging +Suggests: gstreamer1.0-plugins-ugly Description: Maintain converted trees of audio files. The `gremlin' program converts audio files in an input `master' directory tree, which presumably contains high-quality (ideally lossless) encodings of diff --git a/gremlin/gremlin.in b/gremlin/gremlin.in index 0a76641..f3a870a 100644 --- a/gremlin/gremlin.in +++ b/gremlin/gremlin.in @@ -47,14 +47,12 @@ from contextlib import contextmanager ## eyeD3 tag fettling. import eyed3 as E3 -## Gstreamer. It picks up command-line arguments -- most notably `--help' -- -## and processes them itself. Of course, its help is completely wrong. This -## kludge is due to Jonas Wagner. -_argv, SYS.argv = SYS.argv, [] -import gobject as G -import gio as GIO -import gst as GS -SYS.argv = _argv +## Gstreamer. +import gi +gi.require_version('GLib', '2.0'); from gi.repository import GLib as G +gi.require_version('Gio', '2.0'); from gi.repository import Gio as GIO +gi.require_version('Gst', '1.0'); from gi.repository import Gst as GS +GS.init([]) ## Python Imaging. from PIL import Image as I @@ -710,17 +708,27 @@ Policy << (And | Or | Accept | Convert) def make_element(factory, name = None, **props): "Return a new element from the FACTORY with the given NAME and PROPS." - elt = GS.element_factory_make(factory, name) + elt = GS.ElementFactory.make(factory, name) + if elt is None: raise ValueError, 'failed to make `%s\' element' % factory elt.set_properties(**props) return elt def link_elements(elts): "Link the elements ELTS together, in order." - GS.element_link_many(*elts) + e0 = None + for e1 in elts: + if e0 is not None: e0.link(e1) + e0 = e1 def bin_children(bin): "Iterate over the (direct) children of a BIN." - for elt in bin.elements(): yield elt + iter = bin.iterate_elements() + while True: + rc, elt = iter.next() + if rc == GS.IteratorResult.DONE: break + elif rc != GS.IteratorResult.OK: + raise ValueError, 'iteration failed (%s)' % rc + else: yield elt class GStreamerProgressEyecandy (ProgressEyecandy): """ @@ -762,12 +770,10 @@ class GStreamerProgressEyecandy (ProgressEyecandy): ## time, because (particularly with VBR-encoded MP3 inputs) the estimated ## duration can change as we progress. Hopefully it settles down fairly ## soon. - try: - t, hunoz = me._elt.query_position(GS.FORMAT_TIME) - end, hukairz = me._elt.query_duration(GS.FORMAT_TIME) - return t, end - except GS.QueryError: - return None, None + ok, t = me._elt.query_position(GS.Format.TIME) + if ok: ok, end = me._elt.query_duration(GS.Format.TIME) + if ok: return t, end + else: return None, None def __enter__(me): "Enter context: attach progress meter display." @@ -821,7 +827,6 @@ class AudioIdentifier (object): me._pipe = GS.Pipeline() me._file = file bus = me._pipe.get_bus() - bus.add_signal_watch() loop = G.MainLoop() ## The basic recognition kit is based around `decodebin'. We must keep @@ -831,27 +836,31 @@ class AudioIdentifier (object): decoder = make_element('decodebin', 'decode') sink = make_element('fakesink') def decoder_pad_arrived(elt, pad): - if pad.get_caps()[0].get_name().startswith('audio/'): + if pad.get_current_caps()[0].get_name().startswith('audio/'): elt.link_pads(pad.get_name(), sink, 'sink') dpaid = decoder.connect('pad-added', decoder_pad_arrived) for i in [source, decoder, sink]: me._pipe.add(i) link_elements([source, decoder]) ## Arrange to collect tags from the pipeline's bus as they're reported. - ## If we reuse the pipeline later, we'll want different bus-message - ## handling, so make sure we can take the signal handler away. tags = {} fail = [] def bus_message(bus, msg): - if msg.type == GS.MESSAGE_ERROR: - fail[:] = (ValueError, msg.structure['debug'], None) + ty, s = msg.type, msg.get_structure() + if ty == GS.MessageType.ERROR: + fail[:] = (ValueError, s['debug'], None) loop.quit() - elif msg.type == GS.MESSAGE_STATE_CHANGED: - if msg.structure['new-state'] == GS.STATE_PAUSED and \ + elif ty == GS.MessageType.STATE_CHANGED: + if s['new-state'] == GS.State.PAUSED and \ msg.src == me._pipe: loop.quit() - elif msg.type == GS.MESSAGE_TAG: - tags.update(msg.structure) + elif ty == GS.MessageType.TAG: + tt = s['taglist'] + for i in xrange(tt.n_tags()): + t = tt.nth_tag_name(i) + if tt.get_tag_size(t) != 1: continue + v = tt.get_value_index(t, 0) + tags[t] = v bmid = bus.connect('message', bus_message) ## We want to identify the kind of stream this is. (Hmm. The MIME type @@ -872,12 +881,14 @@ class AudioIdentifier (object): ## Crank up most of the heavy machinery. The message handler will stop ## the loop when things seem to be sufficiently well underway. - me._pipe.set_state(GS.STATE_PAUSED) + bus.add_signal_watch() + me._pipe.set_state(GS.State.PAUSED) loop.run() bus.disconnect(bmid) decoder.disconnect(dpaid) + bus.remove_signal_watch() if fail: - me._pipe.set_state(GS.STATE_NULL) + me._pipe.set_state(GS.State.NULL) raise fail[0], fail[1], fail[2] ## Store the collected tags. @@ -885,9 +896,9 @@ class AudioIdentifier (object): ## Gather the capabilities. The `typefind' element knows the input data ## type. The 'decodebin' knows the raw data type. - me.cap = tfelt.get_pad('src').get_negotiated_caps()[0] + me.cap = tfelt.get_static_pad('src').get_allowed_caps()[0] me.mime = set([mime, me.cap.get_name()]) - me.dcap = sink.get_pad('sink').get_negotiated_caps()[0] + me.dcap = sink.get_static_pad('sink').get_allowed_caps()[0] ## If we found a plausible bitrate then stash it. Otherwise note that we ## failed. If anybody asks then we'll work it out then. @@ -906,7 +917,7 @@ class AudioIdentifier (object): def __del__(me): "Close the pipeline down so we don't leak file descriptors." - me._pipe.set_state(GS.STATE_NULL) + me._pipe.set_state(GS.State.NULL) @property def bitrate(me): @@ -927,28 +938,32 @@ class AudioIdentifier (object): ## stream: then we'll have a clear idea of how long the track was. fail = [] def bus_message(bus, msg): - if msg.type == GS.MESSAGE_ERROR: - fail[:] = (ValueError, msg.structure['debug'], None) + ty, s = msg.type, msg.get_structure() + if ty == GS.MessageType.ERROR: + fail[:] = (ValueError, s['debug'], None) loop.quit() - elif msg.type == GS.MESSAGE_EOS: + elif ty == GS.MessageType.EOS: loop.quit() bus = me._pipe.get_bus() bmid = bus.connect('message', bus_message) ## Get everything moving, and keep the user amused while we work. - me._pipe.set_state(GS.STATE_PLAYING) + bus.add_signal_watch() + me._pipe.set_state(GS.State.PLAYING) with GStreamerProgressEyecandy(filestatus(file, 'measure bitrate') % me._pipe, silentp = True): loop.run() + bus.remove_signal_watch() bus.disconnect(bmid) if fail: - me._pipe.set_state(GS.STATE_NULL) + me._pipe.set_state(GS.State.NULL) raise fail[0], fail[1], fail[2] ## Now we should be able to find out our position accurately and work out ## a bitrate. Cache it in case anybody asks again. - t, hukairz = me._pipe.query_position(GS.FORMAT_TIME) + ok, t = pipe.query_position(GS.Format.TIME) + assert ok, 'failed to discover bitrate' me._bitrate = int(8*me._bytes*1e6/t) ## Done. @@ -1000,8 +1015,8 @@ class AudioFormat (BaseFormat): bin = GS.Bin() for i in elts: bin.add(i) link_elements(elts) - bin.add_pad(GS.GhostPad('sink', elts[0].get_pad('sink'))) - bin.add_pad(GS.GhostPad('src', elts[-1].get_pad('src'))) + bin.add_pad(GS.GhostPad('sink', elts[0].get_static_pad('sink'))) + bin.add_pad(GS.GhostPad('src', elts[-1].get_static_pad('src'))) return bin def convert(me, master, id, target): @@ -1014,7 +1029,6 @@ class AudioFormat (BaseFormat): ## Construct the necessary equipment. pipe = GS.Pipeline() bus = pipe.get_bus() - bus.add_signal_watch() loop = G.MainLoop() ## Make sure that there isn't anything in the way of our output. We're @@ -1065,7 +1079,7 @@ class AudioFormat (BaseFormat): ## our encoding chain. For now, we'll hope that there's only one audio ## stream in there, and just throw everything else away. def decoder_pad_arrived(elt, pad): - if pad.get_caps()[0].get_name().startswith('audio/'): + if pad.get_current_caps()[0].get_name().startswith('audio/'): if dcap: elt.link_pads_filtered(pad.get_name(), convert, 'sink', dcap) else: @@ -1075,20 +1089,23 @@ class AudioFormat (BaseFormat): ## Watch the bus for completion messages. fail = [] def bus_message(bus, msg): - if msg.type == GS.MESSAGE_ERROR: - fail[:] = (ValueError, msg.structure['debug'], None) + if msg.type == GS.MessageType.ERROR: + fail[:] = (ValueError, msg.get_structure()['debug'], None) loop.quit() - elif msg.type == GS.MESSAGE_EOS: + elif msg.type == GS.MessageType.EOS: loop.quit() bmid = bus.connect('message', bus_message) ## Get everything ready and let it go. - pipe.set_state(GS.STATE_PLAYING) + bus.add_signal_watch() + pipe.set_state(GS.State.PLAYING) with GStreamerProgressEyecandy(filestatus(master, 'convert to %s' % me.NAME), pipe): loop.run() - pipe.set_state(GS.STATE_NULL) + pipe.set_state(GS.State.NULL) + bus.remove_signal_watch() + bus.disconnect(bmid) if fail: raise fail[0], fail[1], fail[2] @@ -1134,8 +1151,13 @@ class MP3Format (AudioFormat): def encoder_chain(me): encprops = {} - if me.bitrate is not None: encprops['vbr_mean_bitrate'] = me.bitrate - return [make_element('lame', vbr = 4, **encprops), + if me.bitrate is not None: + encprops['bitrate'] = me.bitrate + encprops['target'] = 'bitrate' + else: + encprops['quality'] = 4 + encprops['target'] = 'quality' + return [make_element('lamemp3enc', quality = 4, **encprops), make_element('xingmux'), make_element('id3v2mux')] @@ -1513,8 +1535,8 @@ def grobble(master, targets, noact = False): ## the appropriate categories. Later, we'll apply policy to the ## files, by category, and work out what to do with them all. else: - mime = GIO.File(masterfile) \ - .query_info('standard::content-type') \ + mime = GIO.file_new_for_path(masterfile) \ + .query_info('standard::content-type', 0) \ .get_content_type() cats = [] for cat in pmap.iterkeys():