gremlin/gremlin.in: Fix things for new GI-based GStreamer etc. bindings.
authorMark Wooding <mdw@distorted.org.uk>
Tue, 17 Apr 2018 21:34:31 +0000 (22:34 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 20 Apr 2018 12:18:25 +0000 (13:18 +0100)
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.

debian/control
gremlin/gremlin.in

index 458b77f..f59ecff 100644 (file)
@@ -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
index 0a76641..f3a870a 100644 (file)
@@ -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():