home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / python2.4 / site-packages / serpentine / audio.py < prev    next >
Encoding:
Python Source  |  2006-08-23  |  18.5 KB  |  576 lines

  1. # Copyright (C) 2004 Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  2. #
  3. # This library is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU Library General Public
  5. # License as published by the Free Software Foundation; either
  6. # version 2 of the License, or (at your option) any later version.
  7. #
  8. # This library is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11. # Library General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Library General Public
  14. # License along with this library; if not, write to the
  15. # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  16. # Boston, MA 02111-1307, USA.
  17. #
  18. # Authors: Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  19. #          Alessandro Decina <alessandro@nnva.org>
  20.  
  21. """
  22. This module contains operations to convert sound files to WAV and to
  23. retrieve a their metadata.
  24. """
  25. if __name__ == '__main__':
  26.     try:
  27.         import pygst
  28.         pygst.require("0.10")
  29.     except ImportError:
  30.         pass
  31.  
  32. import threading
  33. import gst
  34. import gobject
  35. import operations
  36.  
  37. GVFS_SRC = "gnomevfssrc"
  38. FILE_SRC = "filesrc"
  39.  
  40. class ElementNotFoundError(KeyError):
  41.     """This error is thrown when an element is not found"""
  42.  
  43. # Versions below 0.9 do not raise an exception when the element is not found
  44. if gst.gst_version < (0, 9):
  45.     def safe_element_factory_make(*args, **kwargs):
  46.         element = gst.element_factory_make(*args, **kwargs)
  47.         if element is None:
  48.             raise ElementNotFoundError(args)
  49.         return element
  50. # New versions >= 0.9 already raise an exception, so there's not a big problem
  51. else:
  52.     safe_element_factory_make = gst.element_factory_make
  53.     
  54. class GstPlayingFailledError(StandardError):
  55.     """This error is thrown when we can't set the state to PLAYING"""
  56.     
  57. class GstOperationListener (operations.OperationListener):
  58.     def on_tag (self, event, tag):
  59.         """Called when a tag is found"""
  60.  
  61.     def on_eos (self, event):
  62.         """Called when the pipeline reaches EOS."""
  63.  
  64.     def on_error (self, event, error):
  65.         """Called when an error occurs"""
  66.    
  67. class GstPipelineOperation (operations.MeasurableOperation):
  68.     """GStreamer pipeline operation"""
  69.  
  70.     can_start = property (lambda self: self.__can_start)
  71.     running = property (lambda self: self.__running)
  72.     bin = property (lambda self: self.__bin)
  73.     query_element = None
  74.     progress = property (lambda self: self._get_progress ())
  75.     
  76.     def __init__ (self, query_element, pipeline):
  77.         super (GstPipelineOperation, self).__init__ ()
  78.  
  79.         self.query_element = query_element
  80.         self.__bin = pipeline
  81.         self.__progress = 0.0
  82.         self.__can_start = True
  83.         self.__running = False
  84.         self.__duration = 0
  85.  
  86.     def start (self):
  87.         assert self.can_start
  88.         if self.bin.set_state (gst.STATE_PLAYING):
  89.             self.__can_start = False
  90.             self.__running = True
  91.         else:
  92.             raise GstPlayingFailledError()
  93.  
  94.     def stop (self):
  95.         self._finalize (operations.ABORTED)
  96.  
  97.     def query_duration (self, format = gst.FORMAT_BYTES):
  98.         """Return the total duration"""
  99.         return 0.0
  100.  
  101.     def query_position (self, format = gst.FORMAT_BYTES):
  102.         """Return the current position"""
  103.         return 0
  104.     
  105.     # implementation methods
  106.     def _finalize (self, event_id, error = None):
  107.         # set state to NULL and notify the listeners
  108.         if self.__running:
  109.             self.bin.set_state (gst.STATE_NULL)
  110.             self.__running = False
  111.             self._send_finished_event (event_id, error)
  112.         # Notice that GstOperations are run-once.
  113.  
  114.     def _get_progress (self):
  115.         # get the current progress
  116.         if self.query_element and self.__progress < 1: 
  117.             if not self.__duration:
  118.                 self.__duration = self.query_duration ()
  119.             if self.__duration == 0:
  120.                 progress = 0
  121.             else:
  122.                 position = self.query_position ()
  123.                 progress = self.query_position () / self.__duration
  124.  
  125.             self.__progress = max (self.__progress, progress)
  126.             assert 0 <= self.__progress <= 1, self.__progress
  127.             
  128.         return self.__progress
  129.  
  130.     def _on_eos (self):
  131.         event = operations.Event (self)
  132.         self._notify ("on_eos", event)
  133.         self._finalize (operations.SUCCESSFUL)
  134.  
  135.     def _on_error (self, error):
  136.         event = operations.Event (self)
  137.         self._notify ("on_error", event, error)
  138.         self._finalize (operations.ERROR, error)
  139.  
  140.     def _on_tag (self, taglist):
  141.         event = operations.Event (self)
  142.         self._notify ("on_tag", event, taglist)
  143.  
  144. class Gst08Operation (GstPipelineOperation):
  145.     """Implement GstPipelineOperation with gstreamer 0.8 API"""
  146.     def __init__ (self, query_element = None, pipeline = None,
  147.                 use_threads = False):
  148.         
  149.         if pipeline is None:
  150.             pipeline = use_threads and gst.Thread () or gst.Pipeline ()
  151.         
  152.         super (Gst08Operation, self).__init__ (query_element, pipeline)
  153.         
  154.         self.__use_threads = use_threads
  155.         pipeline.connect ("found-tag", self._on_tag)
  156.         pipeline.connect ("error", self._on_error)
  157.         pipeline.connect ("eos", self._on_eos)
  158.         self.__source = None
  159.  
  160.     def start (self):
  161.         super (Gst08Operation, self).start ()
  162.         if self.running and not self.__use_threads:
  163.             self.__source = gobject.idle_add (self.bin.iterate)
  164.  
  165.     def stop (self):
  166.         if self.__source is not None:
  167.             gobject.source_remove (self.__source)
  168.             self.__source = None
  169.         super (Gst08Operation, self).stop ()
  170.  
  171.     def query_duration (self, format = gst.FORMAT_TIME):
  172.         return float (self.query_element.query(gst.QUERY_TOTAL, format))
  173.  
  174.     def query_position (self, format = gst.FORMAT_TIME):
  175.         return self.query_element.query (gst.QUERY_POSITION, format)
  176.  
  177.     def _on_error (self, pipeline, element, error, user_data = None):
  178.         super (Gst08Operation, self)._on_error (error)
  179.     
  180.     def _on_tag (self, pipeline, element, taglist):
  181.         super (Gst08Operation, self)._on_tag (taglist)
  182.  
  183.     def _on_eos (self, pipeline):
  184.         super (Gst08Operation, self)._on_eos ()
  185.  
  186.     def _finalize (self, event_id, error = None):
  187.         super (Gst08Operation, self)._finalize (event_id, error)
  188.         if self.__source is not None:
  189.             gobject.source_remove (self.__source)
  190.             self.__source = None
  191.  
  192.  
  193. class Gst09Operation (GstPipelineOperation):
  194.     """Implement GstPipelineOperation with gstreamer 0.9/0.10 API"""
  195.     running = property (lambda self: self.__running)
  196.  
  197.     def __init__ (self, query_element = None, pipeline = None):
  198.         if pipeline is None:
  199.             pipeline = gst.Pipeline ()
  200.         
  201.         super (Gst09Operation, self).__init__ (query_element, pipeline)
  202.  
  203.         self.bus = pipeline.get_bus ()
  204.         self.bus.add_watch (self._dispatch_bus_message)
  205.         self.__running = False
  206.         
  207.         # the operation can be started/stopped in the main thread, when start()
  208.         # or stop() are called inside serpentine, and in the stream thread,
  209.         # when .stop() is called in on_handoff. start() and _finalize() are thus
  210.         # synchronized with self.lock
  211.         self.lock = threading.RLock ()
  212.  
  213.     def query_duration (self, format = gst.FORMAT_TIME):
  214.         try:
  215.             total, format = self.query_element.query_duration (format)
  216.             return float (total)
  217.         except gst.QueryError, err:
  218.             return 0.0
  219.  
  220.     def query_position (self, format = gst.FORMAT_TIME):
  221.         try:
  222.             pos, format = self.query_element.query_position (format)
  223.             return pos
  224.         except gst.QueryError:
  225.             return 0
  226.     
  227.     def start (self):
  228.         self.lock.acquire ()
  229.         try:
  230.             if not self.__running:
  231.                 self.__running = True
  232.                 super (Gst09Operation, self).start ()
  233.         finally:
  234.             self.lock.release ()
  235.  
  236.     def _dispatch_bus_message (self, bus, message):
  237.         handler = getattr (self, "_on_" + message.type.first_value_nick, None)
  238.         if handler:
  239.             handler (bus, message)
  240.  
  241.         return True
  242.    
  243.     def _finalize (self, event_id, error = None):
  244.         self.lock.acquire ()
  245.         try:
  246.             if self.running:
  247.                 self.__running = False
  248.                 # finalize the pipeline in the main thread to avoid deadlocks
  249.                 def wrapper ():
  250.                     super (Gst09Operation, self)._finalize (event_id, error)
  251.                     return False
  252.  
  253.                 gobject.idle_add (wrapper)
  254.         finally:
  255.             self.lock.release ()
  256.  
  257.     def _on_eos (self, bus, message):
  258.         super (Gst09Operation, self)._on_eos ()
  259.  
  260.     def _on_error (self, bus, message):
  261.         super (Gst09Operation, self)._on_error (message.parse_error ()) 
  262.  
  263.     def _on_tag (self, bus, message):
  264.         super (Gst09Operation, self)._on_tag (message.parse_tag ())
  265.  
  266. if gst.gst_version[0] == 0 and gst.gst_version[1] >= 9:
  267.     # use Gst09Operation with gstreamer >= 0.9
  268.     NEW_PAD_SIGNAL = "pad-added"
  269.     GstOperation = Gst09Operation
  270. else:
  271.     # use Gst08Operation with gstreamer < 0.9
  272.     NEW_PAD_SIGNAL = "new-pad"
  273.     GstOperation = Gst08Operation
  274.  
  275. def create_source(source, location, src_prop="location"):
  276.     src = safe_element_factory_make(source)
  277.     src.set_property(src_prop, location)
  278.     return src
  279.  
  280. ################################################################################
  281. class AudioMetadataListener (operations.OperationListener):
  282.     """
  283.     The on_metadata event is called before the FinishedEvent, if the metadata
  284.     retriavel is successful.
  285.     """
  286.     def on_metadata (self, event, metadata):
  287.         pass
  288.  
  289. class AudioMetadataEvent (operations.Event):
  290.     "Event that holds the audio metadata."
  291.     
  292.     def __init__ (self, source, id, metadata):
  293.         operations.Event.__init__ (source, id)
  294.         self.__metadata = metadata
  295.         
  296.     metadata = property (lambda self: self.__metadata)
  297.  
  298. class AudioMetadata (operations.Operation, GstOperationListener):
  299.     """Returns the metadata associated with the source element.
  300.     
  301.     To retrieve the metadata associated with a certain media file on gst-launch -t:
  302.     source ! decodebin ! fakesink
  303.     """
  304.  
  305.     can_start = property (lambda self: self.__oper.can_start)
  306.     running = property (lambda self: self.__oper.running)
  307.  
  308.     def __init__ (self, source):
  309.         super (AudioMetadata, self).__init__ ()
  310.         
  311.         # setup the metadata extracting pipeline
  312.         bin = gst.parse_launch ("decodebin name=am_decodebin ! \
  313.                                 fakesink name=am_fakesink")
  314.         self.__oper = GstOperation(pipeline = bin)
  315.         # link source with decodebin
  316.         bin.add (source)
  317.         source.link (bin.get_by_name ("am_decodebin"))
  318.         # set fakesink as the query_element
  319.         self._fakesink = bin.get_by_name ("am_fakesink")
  320.         self._fakesink.set_property ("signal-handoffs", True)
  321.         self._fakesink.connect ("handoff", self.on_handoff)
  322.         self.__oper.query_element = self._fakesink
  323.         self.__oper.listeners.append (self) 
  324.         self.__metadata = {}
  325.         self.__element = None
  326.  
  327.     def start (self):
  328.         self.__oper.start ()
  329.     
  330.     def stop (self):
  331.         self._check_duration ()
  332.         self.__oper.stop ()
  333.  
  334.     def on_eos (self, event):
  335.         self._check_duration ()
  336.  
  337.     def on_error (self, event, message):
  338.         # try to get the size even when there is an error
  339.         self._check_duration ()
  340.  
  341.     def on_handoff (self, *ignored):
  342.         self._fakesink.set_property ("signal-handoffs", False)
  343.         self.stop ()
  344.  
  345.     def on_tag (self, event, taglist):
  346.         self.__metadata.update (taglist)
  347.  
  348.     def on_finished (self, event):
  349.         if event.id == operations.ERROR:
  350.             self._propagate(event)
  351.             return
  352.             
  353.         try:
  354.             duration = int (self.__metadata["duration"]) / gst.SECOND
  355.         except KeyError:
  356.             duration = 0
  357.         
  358.         if duration == -1 or duration == 0:
  359.             self._send_finished_event(operations.ERROR)
  360.             return
  361.             
  362.         self.__metadata["duration"] = duration
  363.         
  364.         evt = operations.Event (self)
  365.         self._notify ("on_metadata", evt, self.__metadata)
  366.         
  367.         self.__metadata = None
  368.         self.__element = None
  369.         self._send_finished_event (operations.SUCCESSFUL)
  370.  
  371.     def _check_duration (self):
  372.         if not self.__metadata.has_key ("duration"):
  373.             self.__metadata["duration"] = \
  374.                 self.__oper.query_duration (gst.FORMAT_TIME)
  375.  
  376. def get_metadata(source, location):
  377.     return AudioMetadata(create_source(source, location))
  378.  
  379.  
  380. ################################################################################
  381. WavPcmStruct = {
  382.     'rate'      : 44100,
  383.     'signed'    : True,
  384.     'channels'  : 2,
  385.     'width'     : 16,
  386.     'depth'     : 16,
  387.     'endianness': 1234
  388. }
  389.  
  390. _WAV_PCM_PARSE = ("audio/x-raw-int, endianness=(int)1234, width=(int)16, "
  391.                "depth=(int)16, signed=(boolean)true, rate=(int)44100, "
  392.                "channels=(int)2")
  393.  
  394. def is_caps_wav_pcm (caps):
  395.     struct = caps[0]
  396.     if not struct.get_name () == "audio/x-raw-int":
  397.         return False
  398.     
  399.     for key, value in WavPcmStruct.iteritems ():
  400.         if not struct.has_field (key) or struct[key] != value:
  401.             return False
  402.     
  403.     return True
  404.  
  405.  
  406. class IsWavPcm (operations.Operation, GstOperationListener):
  407.     """
  408.     Tests if a certain WAV is in the PCM format.
  409.     """
  410.  
  411.     can_start = property (lambda self: self.oper.can_start)
  412.     running = property (lambda self: self.oper.running)
  413.  
  414.     def __init__ (self, source):
  415.         
  416.         super (IsWavPcm, self).__init__ ()
  417.         
  418.         self.is_wav_pcm = False
  419.         bin = gst.parse_launch (
  420.             "typefind name=iwp_typefind ! \
  421.             wavparse name=iwp_wavparse ! "
  422.             + _WAV_PCM_PARSE +
  423.             " ! fakesink name=iwp_fakesink"
  424.         )
  425.         
  426.         self.oper = GstOperation(pipeline = bin)
  427.         self.oper.listeners.append (self)
  428.  
  429.         decoder = bin.get_by_name ("iwp_typefind")
  430.         
  431.         sink = bin.get_by_name ("iwp_fakesink")
  432.         self.oper.query_element = sink
  433.         sink.set_property ("signal-handoffs", True)
  434.         sink.connect ("handoff", self.on_handoff)
  435.         
  436.         self.waveparse = bin.get_by_name ("iwp_wavparse")
  437.         self.waveparse.connect (NEW_PAD_SIGNAL, self.on_new_pad)
  438.         
  439.         self.oper.bin.add (source)
  440.         source.link (decoder)
  441.         
  442.         self.is_wav_pcm = False
  443.  
  444.     def on_handoff (self, *args):
  445.         self.oper.stop ()
  446.     
  447.     def on_new_pad (self, src, pad):
  448.         caps = pad.get_caps()
  449.         self.is_wav_pcm = is_caps_wav_pcm (caps)
  450.     
  451.     def on_finished (self, event):
  452.         if event.id != operations.ERROR and self.is_wav_pcm:
  453.             assert event.id == operations.SUCCESSFUL or \
  454.                    event.id == operations.ABORTED
  455.             
  456.             self._send_finished_event (operations.SUCCESSFUL)
  457.         else:
  458.             if event.id == operations.SUCCESSFUL:
  459.                 eid = operations.ERROR
  460.                 err = StandardError ("Not a valid WAV PCM")
  461.             else:
  462.                 eid = event.id
  463.                 err = event.error
  464.                 
  465.             self._send_finished_event (eid, err)
  466.     
  467.     def start (self):
  468.         self.oper.start ()
  469.         self.__can_start = False
  470.     
  471.     def stop (self):
  472.         self.oper.stop ()
  473.         
  474.     can_start = property (lambda self: self.__can_start)
  475.     
  476.     running = property (lambda self: self.__oper != None)
  477.  
  478. def is_wav_pcm(source, location):
  479.     return IsWavPcm(create_source(source, location))
  480.  
  481. is_wav_pcm = operations.operation_factory(is_wav_pcm)
  482. is_wav_pcm = operations.async(is_wav_pcm)
  483.  
  484. ################################################################################
  485. def source_to_wav(source, sink):
  486.     """
  487.     Converts a given source element to wav format and sends it to sink element.
  488.     
  489.     To convert a media file to a wav using gst-launch:
  490.     source ! decodebin ! audioconvert ! audioscale !$_WAV_PCM_PARSE ! wavenc
  491.     """
  492.  
  493.     bin = gst.parse_launch (
  494.         "decodebin name=stw_decodebin !"
  495.         "audioconvert ! "
  496.         + _WAV_PCM_PARSE +
  497.         " ! wavenc name=stw_wavenc"
  498.     )
  499.     oper = GstOperation(sink, bin)
  500.       
  501.     decoder = bin.get_by_name ("stw_decodebin")
  502.     encoder = bin.get_by_name ("stw_wavenc")
  503.       
  504.     oper.bin.add (source)
  505.     oper.bin.add (sink)
  506.     source.link (decoder)
  507.     encoder.link (sink)
  508.     
  509.     return oper
  510.  
  511. source_to_wav = operations.operation_factory(source_to_wav)
  512.  
  513. def convert_to_wav(source, source_location, sink_location):
  514.     """
  515.     Utility function that given a source filename it converts it to a wav
  516.     with sink_filename.
  517.     """
  518.     sink = safe_element_factory_make("filesink")
  519.     sink.set_property("location", sink_location)
  520.     return source_to_wav(create_source(source, source_location), sink)
  521.  
  522. convert_to_wav = operations.operation_factory(convert_to_wav)
  523. convert_to_wav = operations.async(convert_to_wav)
  524.  
  525. commands = {
  526.     "convert": convert_to_wav,
  527.     "is_wav": is_wav_pcm,
  528.     "get_metadata": get_metadata
  529. }
  530.  
  531. def parse_command(operation, source, source_location, *args):
  532.     return commands[operation](source, source_location, *args)
  533.     
  534. ################################################################################
  535.  
  536. ################################################################################
  537. if __name__ == '__main__':
  538.     import sys
  539.     import gst
  540.  
  541.     mainloop = gobject.MainLoop ()
  542.     class Listener (GstOperationListener):
  543.         def __init__(self, oper):
  544.             self.oper = oper
  545.             
  546.         def on_metadata (self, event, metadata):
  547.             print >> sys.stderr, metadata
  548.             
  549.         def on_finished (self, event):
  550.             self.success = operations.SUCCESSFUL == event.id
  551.             mainloop.quit ()
  552.  
  553.         def on_progress (self):
  554.             print self.oper.progress
  555.             return True
  556.     
  557.     #f = convert_to_wav (FILE_SRC, sys.argv[1], sys.argv[2])
  558.     f = parse_command(sys.argv[1], FILE_SRC, sys.argv[2], *sys.argv[3:])
  559.     #f = is_wav_pcm (FILE_SRC, sys.argv[1])
  560.     #print operations.syncOperation (f).id == operations.SUCCESSFUL
  561.     #raise SystemError
  562.     #f = get_metadata("filesrc", sys.argv[1])
  563.     #f = extractAudioTrackFile ("/dev/cdrom", int(sys.argv[1]) + 1, sys.argv[2])
  564.     l = Listener(f)
  565.     if isinstance(f, operations.MeasurableOperation):
  566.         gobject.timeout_add (200, l.on_progress)
  567.     f.listeners.append (l)
  568.     f.start()
  569.     l.finished = False
  570.  
  571.     mainloop.run ()
  572.     if not l.success:
  573.         import sys
  574.         sys.exit(1)
  575.     
  576.