X-Git-Url: https://git.distorted.org.uk/~mdw/autoys/blobdiff_plain/608b936e9f7b5ffe05988bde66af1fb1c6acf239..e9bf7f71307405c89e34d7e337f11ff370848cbc:/gremlin/gremlin.in diff --git a/gremlin/gremlin.in b/gremlin/gremlin.in index 84429b7..985cb6d 100644 --- a/gremlin/gremlin.in +++ b/gremlin/gremlin.in @@ -41,20 +41,18 @@ import shutil as SH import optparse as OP import threading as TH import shlex as L -from math import sqrt +from math import sqrt, ceil 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 @@ -247,7 +245,7 @@ class ProgressEyecandy (object): ## Work out -- well, guess -- the time remaining. if cur: t = T.time() - eta = me._fmt_time((t - me._start)*(max - cur)/cur) + eta = me._fmt_time(ceil((t - me._start)*(max - cur)/cur)) else: eta = '???' @@ -411,6 +409,8 @@ class FormatParser (P.ParserElement): named format and its superclasses. """ + name = 'format-spec' + ## We cache the parser elements we generate to avoid enormous consing. CACHE = {} @@ -708,10 +708,28 @@ 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." + 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." + 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): """ Provide amusement while GStreamer is busy doing something. @@ -752,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." @@ -811,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 @@ -821,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) - me._pipe.add(source, decoder, sink) - GS.element_link_many(source, decoder) + 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 @@ -853,7 +872,7 @@ class AudioIdentifier (object): ## things from being too awful.) me.cap = None me.dcap = None - for e in decoder.elements(): + for e in bin_children(decoder): if e.get_factory().get_name() == 'typefind': tfelt = e break @@ -862,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. @@ -875,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. @@ -896,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): @@ -917,28 +938,31 @@ 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) - with GStreamerProgressEyecandy(filestatus(file, 'measure bitrate') % - me._pipe, - silentp = True): + 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. @@ -988,10 +1012,10 @@ class AudioFormat (BaseFormat): """ elts = me.encoder_chain() bin = GS.Bin() - bin.add(*elts) - GS.element_link_many(*elts) - bin.add_pad(GS.GhostPad('sink', elts[0].get_pad('sink'))) - bin.add_pad(GS.GhostPad('src', elts[-1].get_pad('src'))) + for i in elts: bin.add(i) + link_elements(elts) + 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): @@ -1004,7 +1028,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 @@ -1025,9 +1048,9 @@ class AudioFormat (BaseFormat): convert = make_element('audioconvert', 'convert') encoder = me.encoder() sink = make_element('filesink', 'sink', location = new) - pipe.add(source, decoder, convert, encoder, sink) - GS.element_link_many(source, decoder) - GS.element_link_many(convert, encoder, sink) + for i in [source, decoder, convert, encoder, sink]: pipe.add(i) + link_elements([source, decoder]) + link_elements([convert, encoder, sink]) ## Some decoders (e.g., the AC3 decoder) include channel-position ## indicators in their output caps. The Vorbis encoder interferes with @@ -1055,7 +1078,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: @@ -1065,20 +1088,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] @@ -1107,8 +1133,8 @@ class OggVorbisFormat (AudioFormat): for q, br in me.QMAP: if br >= me.bitrate: break - else: - raise ValueError, 'no suitable quality setting found' + else: + raise ValueError, 'no suitable quality setting found' encprops['quality'] = q/10.0 return [make_element('vorbisenc', **encprops), make_element('oggmux')] @@ -1124,8 +1150,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')] @@ -1503,8 +1534,9 @@ 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: - gf = GIO.File(masterfile) - mime = gf.query_info('standard::content-type').get_content_type() + mime = GIO.file_new_for_path(masterfile) \ + .query_info('standard::content-type', 0) \ + .get_content_type() cats = [] for cat in pmap.iterkeys(): id = cat.identify(masterfile, mime)