gremlin/gremlin.in: Use `locale.getpreferredencoding'.
[autoys] / gremlin / gremlin.in
index 985cb6d..522ca9b 100644 (file)
 from __future__ import with_statement
 
 ## Standard Python libraries.
-import sys as SYS
-import os as OS
 import errno as E
-import time as T
-import unicodedata as UD
 import fnmatch as FN
+import locale as LC
+import optparse as OP
+import os as OS
 import re as RX
+import sys as SYS
+import time as T
+import shlex as L
 import shutil as SH
-import optparse as OP
 import threading as TH
-import shlex as L
+import unicodedata as UD
 from math import sqrt, ceil
 from contextlib import contextmanager
 
@@ -71,6 +72,8 @@ G.threads_init()
 ###--------------------------------------------------------------------------
 ### Eyecandy progress reports.
 
+DEFAULT_ENCODING = None
+
 def charwidth(s):
   """
   Return the width of S, in characters.
@@ -83,12 +86,14 @@ def charwidth(s):
   None of this handles tab characters in any kind of useful way.  Sorry.
   """
 
-  ## If there's no encoding for stdout then we're doing something stupid.
-  if SYS.stdout.encoding is None: return len(s)
+  global DEFAULT_ENCODING
+
+  ## Figure out the default encoding.
+  if DEFAULT_ENCODING is None: DEFAULT_ENCODING = LC.getpreferredencoding()
 
   ## Turn the string into Unicode so we can hack on it properly.  Maybe that
   ## won't work out, in which case fall back to being stupid.
-  try: u = s.decode(SYS.stdout.encoding)
+  try: u = s.decode(DEFAULT_ENCODING)
   except UnicodeError: return len(s)
 
   ## Our main problem is combining characters, but we should also try to
@@ -783,7 +788,7 @@ class GStreamerProgressEyecandy (ProgressEyecandy):
       return
 
     ## Update regularly.  The pipeline runs asynchronously.
-    me._id = G.timeout_add(200, me._update)
+    me._id = G.timeout_add(100, me._update)
 
   def __exit__(me, ty, val, tb):
     "Leave context: remove display and report completion or failure."
@@ -819,29 +824,36 @@ class AudioIdentifier (object):
           demand.
   """
 
-  def __init__(me, file, mime):
-    "Initialize the object suitably for identifying FILE."
-
-    ## Make some initial GStreamer objects.  We'll want the pipeline later if
-    ## we need to analyse a poorly tagged MP3 stream, so save it away.
-    me._pipe = GS.Pipeline()
-    me._file = file
-    bus = me._pipe.get_bus()
-    loop = G.MainLoop()
+  def _prepare_pipeline(me):
+    pipe = GS.Pipeline()
+    bus = pipe.get_bus()
 
     ## The basic recognition kit is based around `decodebin'.  We must keep
     ## it happy by giving it sinks for the streams it's found, which it
     ## announces asynchronously.
-    source = make_element('filesrc', 'file', location = file)
+    source = make_element('filesrc', 'file', location = me._file)
     decoder = make_element('decodebin', 'decode')
     sink = make_element('fakesink')
     def decoder_pad_arrived(elt, pad):
       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)
+    decoder.connect('pad-added', decoder_pad_arrived)
+    for i in [source, decoder, sink]: pipe.add(i)
     link_elements([source, decoder])
 
+    ## Done.
+    return pipe, bus, decoder, sink
+
+  def __init__(me, file, mime):
+    "Initialize the object suitably for identifying FILE."
+
+    me._file = file
+    pipe, bus, decoder, sink = me._prepare_pipeline()
+
+    ## Make some initial GStreamer objects.  We'll want the pipeline later if
+    ## we need to analyse a poorly tagged MP3 stream, so save it away.
+    loop = G.MainLoop()
+
     ## Arrange to collect tags from the pipeline's bus as they're reported.
     tags = {}
     fail = []
@@ -852,7 +864,7 @@ class AudioIdentifier (object):
         loop.quit()
       elif ty == GS.MessageType.STATE_CHANGED:
         if s['new-state'] == GS.State.PAUSED and \
-               msg.src == me._pipe:
+               msg.src == pipe:
           loop.quit()
       elif ty == GS.MessageType.TAG:
         tt = s['taglist']
@@ -882,13 +894,12 @@ 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.
     bus.add_signal_watch()
-    me._pipe.set_state(GS.State.PAUSED)
+    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)
+      pipe.set_state(GS.State.NULL)
       raise fail[0], fail[1], fail[2]
 
     ## Store the collected tags.
@@ -907,17 +918,11 @@ class AudioIdentifier (object):
     elif 'bitrate' in tags and tags['bitrate'] >= 80000:
       me._bitrate = tags['bitrate']/1000
     else:
-      me._bitrate = None
-
-    ## The bitrate computation wants the file size.  Ideally we'd want the
-    ## total size of the frames' contents, but that seems hard to dredge
-    ## out.  If the framing overhead is small, this should be close enough
-    ## for our purposes.
-    me._bytes = OS.stat(file).st_size
-
-  def __del__(me):
-    "Close the pipeline down so we don't leak file descriptors."
-    me._pipe.set_state(GS.State.NULL)
+      ok, n = pipe.query_duration(GS.Format.BYTES)
+      if ok: ok, t = pipe.query_duration(GS.Format.TIME)
+      if ok: me._bitrate = int((8e6*n)/t)
+      else: me._bitrate = None
+    pipe.set_state(GS.State.NULL)
 
   @property
   def bitrate(me):
@@ -931,7 +936,8 @@ class AudioIdentifier (object):
     if me._bitrate is not None:
       return me._bitrate
 
-    ## Make up a new main loop.
+    ## Make up a new pipeline and main loop.
+    pipe, bus, _, _ = me._prepare_pipeline()
     loop = G.MainLoop()
 
     ## Watch for bus messages.  We'll stop when we reach the end of the
@@ -944,26 +950,34 @@ class AudioIdentifier (object):
         loop.quit()
       elif ty == GS.MessageType.EOS:
         loop.quit()
-    bus = me._pipe.get_bus()
+    bus = pipe.get_bus()
     bmid = bus.connect('message', bus_message)
 
     ## Get everything moving, and keep the user amused while we work.
     bus.add_signal_watch()
-    me._pipe.set_state(GS.State.PLAYING)
-    with GStreamerProgressEyecandy(filestatus(file, 'measure bitrate'),
-                                   me._pipe, silentp = True):
+    pipe.set_state(GS.State.PLAYING)
+    with GStreamerProgressEyecandy(filestatus(me._file, 'measure bitrate'),
+                                   pipe, silentp = True):
       loop.run()
     bus.remove_signal_watch()
     bus.disconnect(bmid)
     if fail:
-      me._pipe.set_state(GS.State.NULL)
+      pipe.set_state(GS.State.NULL)
       raise fail[0], fail[1], fail[2]
+    STATUS.clear()
+
+    ## The bitrate computation wants the file size.  Ideally we'd want the
+    ## total size of the frames' contents, but that seems hard to dredge
+    ## out.  If the framing overhead is small, this should be close enough
+    ## for our purposes.
+    bytes = OS.stat(me._file).st_size
 
     ## Now we should be able to find out our position accurately and work out
     ## a bitrate.  Cache it in case anybody asks again.
     ok, t = pipe.query_position(GS.Format.TIME)
     assert ok, 'failed to discover bitrate'
-    me._bitrate = int(8*me._bytes*1e6/t)
+    me._bitrate = int(8*bytes*1e6/t)
+    pipe.set_state(GS.State.NULL)
 
     ## Done.
     return me._bitrate