From 6c2f3cc3786dfc1c14e7c1c713c961ed9d92ce50 Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sat, 21 May 2011 17:01:19 +0000 Subject: [PATCH] * morituri/common/checksum.py: * morituri/image/image.py: * morituri/test/test_common_checksum.py: * morituri/common/gstreamer.py (added): Factor out GstException and GstPipelineTask. --- ChangeLog | 8 +++ morituri/common/checksum.py | 80 +-------------------- morituri/common/gstreamer.py | 100 ++++++++++++++++++++++++++ morituri/image/image.py | 42 +++++++++++ morituri/test/test_common_checksum.py | 6 +- 5 files changed, 156 insertions(+), 80 deletions(-) create mode 100644 morituri/common/gstreamer.py diff --git a/ChangeLog b/ChangeLog index f1fdad1..a4405e6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2011-05-21 Thomas Vander Stichele + + * morituri/common/checksum.py: + * morituri/image/image.py: + * morituri/test/test_common_checksum.py: + * morituri/common/gstreamer.py (added): + Factor out GstException and GstPipelineTask. + 2011-05-21 Thomas Vander Stichele * misc/morituri-uninstalled: diff --git a/morituri/common/checksum.py b/morituri/common/checksum.py index 14b61ef..9163547 100644 --- a/morituri/common/checksum.py +++ b/morituri/common/checksum.py @@ -26,86 +26,12 @@ import zlib import gst -from morituri.common import common, task +from morituri.common import common, task, gstreamer # checksums are not CRC's. a CRC is a specific type of checksum. -# FIXME: probably this should move higher up the module hierarchy and -# be used wider -class GstException(Exception): - def __init__(self, gerror, debug): - self.args = (gerror, debug, ) - self.gerror = gerror - self.debug = debug -# FIXME: this should move up too; other tasks might have use for it. -class GstPipelineTask(task.Task): - """ - I am a base class for tasks that use a GStreamer pipeline. - - I handle errors and raise them appropriately. - """ - def start(self, runner): - task.Task.start(self, runner) - desc = self.getPipelineDesc() - - self.debug('creating pipeline %r', desc) - self.pipeline = gst.parse_launch(desc) - - self._bus = self.pipeline.get_bus() - gst.debug('got bus %r' % self._bus) - - # a signal watch calls callbacks from an idle loop - # self._bus.add_signal_watch() - - # sync emission triggers sync-message signals which calls callbacks - # from the thread that signals, but happens immediately - self._bus.enable_sync_message_emission() - self._bus.connect('sync-message::eos', self.bus_eos_cb) - self._bus.connect('sync-message::tag', self.bus_tag_cb) - self._bus.connect('sync-message::error', self.bus_error_cb) - - self.parsed() - - self.debug('pausing pipeline') - self.pipeline.set_state(gst.STATE_PAUSED) - self.pipeline.get_state() - self.debug('paused pipeline') - - if not self.exception: - self.paused() - else: - raise self.exception - - def getPipelineDesc(self): - raise NotImplementedError - - def parsed(self): - """ - Called after parsing the pipeline but before setting it to paused. - """ - pass - - def paused(self): - """ - Called after pipeline is paused - """ - pass - - def bus_eos_cb(self, bus, message): - pass - - def bus_tag_cb(self, bus, message): - pass - - def bus_error_cb(self, bus, message): - exc = GstException(*message.parse_error()) - self.setAndRaiseException(exc) - gst.debug('error, scheduling stop') - #self.runner.schedule(0, self.stop) - - -class ChecksumTask(GstPipelineTask): +class ChecksumTask(gstreamer.GstPipelineTask): """ I am a task that calculates a checksum of the decoded audio data. @@ -341,7 +267,7 @@ class AccurateRipChecksumTask(ChecksumTask): return checksum -class TRMTask(GstPipelineTask): +class TRMTask(gstreamer.GstPipelineTask): """ I calculate a MusicBrainz TRM fingerprint. diff --git a/morituri/common/gstreamer.py b/morituri/common/gstreamer.py new file mode 100644 index 0000000..bdac99f --- /dev/null +++ b/morituri/common/gstreamer.py @@ -0,0 +1,100 @@ +# -*- Mode: Python; test-case-name: morituri.test.test_common_gstreamer -*- +# vi:si:et:sw=4:sts=4:ts=4 + +# Morituri - for those about to RIP + +# Copyright (C) 2009 Thomas Vander Stichele + +# This file is part of morituri. +# +# morituri 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 3 of the License, or +# (at your option) any later version. +# +# morituri 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 morituri. If not, see . + +import os +import struct +import zlib + +import gst + +from morituri.common import common, task + +class GstException(Exception): + def __init__(self, gerror, debug): + self.args = (gerror, debug, ) + self.gerror = gerror + self.debug = debug + +class GstPipelineTask(task.Task): + """ + I am a base class for tasks that use a GStreamer pipeline. + + I handle errors and raise them appropriately. + """ + def start(self, runner): + task.Task.start(self, runner) + desc = self.getPipelineDesc() + + self.debug('creating pipeline %r', desc) + self.pipeline = gst.parse_launch(desc) + + self._bus = self.pipeline.get_bus() + gst.debug('got bus %r' % self._bus) + + # a signal watch calls callbacks from an idle loop + # self._bus.add_signal_watch() + + # sync emission triggers sync-message signals which calls callbacks + # from the thread that signals, but happens immediately + self._bus.enable_sync_message_emission() + self._bus.connect('sync-message::eos', self.bus_eos_cb) + self._bus.connect('sync-message::tag', self.bus_tag_cb) + self._bus.connect('sync-message::error', self.bus_error_cb) + + self.parsed() + + self.debug('pausing pipeline') + self.pipeline.set_state(gst.STATE_PAUSED) + self.pipeline.get_state() + self.debug('paused pipeline') + + if not self.exception: + self.paused() + else: + raise self.exception + + def getPipelineDesc(self): + raise NotImplementedError + + def parsed(self): + """ + Called after parsing the pipeline but before setting it to paused. + """ + pass + + def paused(self): + """ + Called after pipeline is paused + """ + pass + + def bus_eos_cb(self, bus, message): + pass + + def bus_tag_cb(self, bus, message): + pass + + def bus_error_cb(self, bus, message): + exc = GstException(*message.parse_error()) + self.setAndRaiseException(exc) + gst.debug('error, scheduling stop') + #self.runner.schedule(0, self.stop) diff --git a/morituri/image/image.py b/morituri/image/image.py index c42388b..294695f 100644 --- a/morituri/image/image.py +++ b/morituri/image/image.py @@ -294,3 +294,45 @@ class ImageEncodeTask(task.MultiSeparateTask): self.debug('encoding track %d', trackIndex + 1) index = track.indexes[1] add(index) + +class ImageJoinTask(task.Task): + """ + I concatenate the decoded audio from all tracks into a single .wav + I can optionally offset the joint audio to compensate for a write offset. + """ + + description = "Joining tracks" + + def __init__(self, image, outfile): + + self._image = image + cue = image.cue + self._tasks = [] + self.lengths = {} + + def add(index): + # here to avoid import gst eating our options + from morituri.common import encode + + path = image.getRealPath(index.path) + assert type(path) is unicode, "%r is not unicode" % path + self.debug('schedule encode of %r', path) + root, ext = os.path.splitext(os.path.basename(path)) + outpath = os.path.join(outdir, root + '.' + profile.extension) + self.debug('schedule encode to %r', outpath) + taskk = encode.EncodeTask(path, os.path.join(outdir, + root + '.' + profile.extension), profile) + self.addTask(taskk) + + try: + htoa = cue.table.tracks[0].indexes[0] + self.debug('encoding htoa track') + add(htoa) + except (KeyError, IndexError): + self.debug('no htoa track') + pass + + for trackIndex, track in enumerate(cue.table.tracks): + self.debug('encoding track %d', trackIndex + 1) + index = track.indexes[1] + add(index) diff --git a/morituri/test/test_common_checksum.py b/morituri/test/test_common_checksum.py index 09fbf90..24b40f6 100644 --- a/morituri/test/test_common_checksum.py +++ b/morituri/test/test_common_checksum.py @@ -7,7 +7,7 @@ import tempfile import gobject gobject.threads_init() -from morituri.common import task, checksum, log +from morituri.common import task, checksum, log, gstreamer from morituri.test import common as tcommon @@ -23,7 +23,7 @@ class EmptyTestCase(tcommon.TestCase): # FIXME: do we want a specific error for this ? e = self.assertRaises(task.TaskException, self.runner.run, checksumtask, verbose=False) - self.failUnless(isinstance(e.exception, checksum.GstException)) + self.failUnless(isinstance(e.exception, gstreamer.GstException)) os.unlink(path) class PathTestCase(tcommon.TestCase): @@ -33,7 +33,7 @@ class PathTestCase(tcommon.TestCase): checksumtask = checksum.ChecksumTask(path) e = self.assertRaises(task.TaskException, self.runner.run, checksumtask, verbose=False) - self.failUnless(isinstance(e.exception, checksum.GstException)) + self.failUnless(isinstance(e.exception, gstreamer.GstException)) os.unlink(path) class UnicodePathTestCase(PathTestCase, tcommon.UnicodeTestMixin):