gremlin/gremlin.in: Add function for linking a sequence of elements.
[autoys] / gremlin / gremlin.in
old mode 100755 (executable)
new mode 100644 (file)
index 8d2cac0..79c0ea9
@@ -7,18 +7,20 @@
 
 ###----- Licensing notice ---------------------------------------------------
 ###
-### This program is free software; you can redistribute it and/or modify
+### This file is part of the `autoys' audio tools collection.
+###
+### `autoys' is free software; you can redistribute it and/or modify
 ### it under the terms of the GNU General Public License as published by
 ### the Free Software Foundation; either version 2 of the License, or
 ### (at your option) any later version.
 ###
-### This program is distributed in the hope that it will be useful,
+### `autoys' is distributed in the hope that it will be useful,
 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ### GNU General Public License for more details.
 ###
 ### You should have received a copy of the GNU General Public License
-### along with this program; if not, write to the Free Software Foundation,
+### along with `autoys'; if not, write to the Free Software Foundation,
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 ###--------------------------------------------------------------------------
@@ -39,11 +41,11 @@ 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
+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
@@ -103,7 +105,6 @@ def charwidth(s):
     else: w += 1
 
   ## Done.
-  #print ';; %r -> %d' % (s, w)
   return w
 
 class StatusLine (object):
@@ -136,8 +137,6 @@ class StatusLine (object):
 
     ## Eyecandy update.
     if me.eyecandyp:
-      #print
-      #print ';; new status %r' % line
 
       ## If the old line was longer, we need to clobber its tail, so work out
       ## what that involves.
@@ -159,7 +158,6 @@ class StatusLine (object):
       ## Actually do the output, all in one syscall.
       b = charwidth(me._last[i:])
       SYS.stdout.write(pre + '\b'*b + line[i:])
-      #print ';; => %r' % (pre + '\b'*b + line[i:])
       SYS.stdout.flush()
 
     ## Update our idea of what's gone on.
@@ -249,7 +247,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 = '???'
 
@@ -301,7 +299,6 @@ String = P.QuotedString('"', '\\')
 ## Handy abbreviations for constructed parser elements.
 def K(k): return P.Keyword(k).suppress()
 def D(d): return P.Literal(d).suppress()
-##R = P.ZeroOrMore
 def R(p): return P.ZeroOrMore(p).setParseAction(lambda s, l, t: [t])
 O = P.Optional
 
@@ -414,6 +411,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 = {}
 
@@ -715,6 +714,10 @@ def make_element(factory, name = None, **props):
   elt.set_properties(**props)
   return elt
 
+def link_elements(elts):
+  "Link the elements ELTS together, in order."
+  GS.element_link_many(*elts)
+
 class GStreamerProgressEyecandy (ProgressEyecandy):
   """
   Provide amusement while GStreamer is busy doing something.
@@ -828,7 +831,7 @@ class AudioIdentifier (object):
         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)
+    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
@@ -992,7 +995,7 @@ class AudioFormat (BaseFormat):
     elts = me.encoder_chain()
     bin = GS.Bin()
     bin.add(*elts)
-    GS.element_link_many(*elts)
+    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')))
     return bin
@@ -1029,8 +1032,8 @@ class AudioFormat (BaseFormat):
     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)
+    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
@@ -1094,7 +1097,7 @@ class AudioFormat (BaseFormat):
 class OggVorbisFormat (AudioFormat):
   "AudioFormat object for Ogg Vorbis."
 
-  ## From http://en.wikipedia.org/wiki/Vorbis
+  ## From https://en.wikipedia.org/wiki/Vorbis
   QMAP = [(-1,  45), ( 0,  64), ( 1,  80), ( 2,  96),
           ( 3, 112), ( 4, 128), ( 5, 160), ( 6, 192),
           ( 7, 224), ( 8, 256), ( 9, 320), (10, 500)]
@@ -1105,13 +1108,15 @@ class OggVorbisFormat (AudioFormat):
   EXT = 'ogg'
 
   def encoder_chain(me):
-    for q, br in me.QMAP:
-      if br >= me.bitrate:
-        break
-    else:
-      raise ValueError, 'no suitable quality setting found'
-    return [make_element('vorbisenc',
-                         quality = q/10.0),
+    encprops = {}
+    if me.bitrate is not None:
+      for q, br in me.QMAP:
+        if br >= me.bitrate:
+          break
+      else:
+        raise ValueError, 'no suitable quality setting found'
+      encprops['quality'] = q/10.0
+    return [make_element('vorbisenc', **encprops),
             make_element('oggmux')]
 
 defformat('ogg-vorbis', OggVorbisFormat)
@@ -1124,9 +1129,9 @@ class MP3Format (AudioFormat):
   EXT = 'mp3'
 
   def encoder_chain(me):
-    return [make_element('lame',
-                         vbr_mean_bitrate = me.bitrate,
-                         vbr = 4),
+    encprops = {}
+    if me.bitrate is not None: encprops['vbr_mean_bitrate'] = me.bitrate
+    return [make_element('lame', vbr = 4, **encprops),
             make_element('xingmux'),
             make_element('id3v2mux')]
 
@@ -1137,13 +1142,16 @@ class MP3Format (AudioFormat):
     GStreamer produces ID3v2 tags, but not ID3v1.  This seems unnecessarily
     unkind to stupid players.
     """
-    tag = E3.Tag()
-    tag.link(path)
-    tag.setTextEncoding(E3.UTF_8_ENCODING)
-    try:
-      tag.update(E3.ID3_V1_1)
-    except (UnicodeEncodeError, E3.tag.GenreException):
-      pass
+    f = E3.load(path)
+    if f is None: return
+    t = f.tag
+    if t is None: return
+    for v in [E3.id3.ID3_V2_3, E3.id3.ID3_V1]:
+      try: f.tag.save(version = v)
+      except (UnicodeEncodeError,
+              E3.id3.GenreException,
+              E3.id3.TagException):
+        pass
 
 defformat('mp3', MP3Format)
 
@@ -1241,7 +1249,7 @@ class JPEGFormat (ImageFormat):
   optimize
           If present, take a second pass to select optimal encoder settings.
 
-  progression
+  progressive
           If present, make a progressive file.
 
   quality Integer from 1--100 (worst to best); default is 75.