diff --git a/README.md b/README.md
index 98fb4de3..fddac3ec 100644
--- a/README.md
+++ b/README.md
@@ -69,10 +69,6 @@ Whipper relies on the following packages in order to run correctly and provide a
- [cdparanoia](https://www.xiph.org/paranoia/), for the actual ripping
- [cdrdao](http://cdrdao.sourceforge.net/), for session, TOC, pre-gap, and ISRC extraction
-- [GStreamer](https://gstreamer.freedesktop.org/) and its python bindings, for encoding (it's going to be removed soon™)
- - `gstreamer0.10-base-plugins` (or `gstreamer0.10-plugins-base` depending on Linux distro) >= **0.10.22** for appsink
- - `gstreamer0.10-good-plugins` (or `gstreamer0.10-plugins-good`) for wav encoding (it depends on the Linux distro used)
- - `python-gst0.10` (GStreamer Python bindings)
- [python-musicbrainzngs](https://github.com/alastair/python-musicbrainzngs), for metadata lookup
- [python-setuptools](https://pypi.python.org/pypi/setuptools), for installation, plugins support
- [python-cddb](http://cddb-py.sourceforge.net/), for showing but not using metadata if disc not available in the MusicBrainz DB
diff --git a/morituri/command/cd.py b/morituri/command/cd.py
index dbfcced3..38e03e05 100644
--- a/morituri/command/cd.py
+++ b/morituri/command/cd.py
@@ -32,7 +32,7 @@
from morituri.command.basecommand import BaseCommand
from morituri.common import (
- accurip, common, config, drive, gstreamer, program, task
+ accurip, common, config, drive, program, task
)
from morituri.program import cdrdao, cdparanoia, utils
from morituri.result import result
@@ -317,17 +317,6 @@ def handle_arguments(self):
def doCommand(self):
- # here to avoid import gst eating our options
- from morituri.common import encode
- profile = encode.PROFILES['flac']()
- self.program.result.profileName = profile.name
- self.program.result.profilePipeline = profile.pipeline
- elementFactory = profile.pipeline.split(' ')[0]
- self.program.result.gstreamerVersion = gstreamer.gstreamerVersion()
- self.program.result.gstPythonVersion = gstreamer.gstPythonVersion()
- self.program.result.encoderVersion = gstreamer.elementFactoryVersion(
- elementFactory)
-
self.program.setWorkingDirectory(self.options.working_directory)
self.program.outdir = self.options.output_directory.decode('utf-8')
self.program.result.offset = int(self.options.offset)
@@ -339,7 +328,7 @@ def doCommand(self):
while True:
discName = self.program.getPath(self.program.outdir,
self.options.disc_template, self.mbdiscid, 0,
- profile=profile, disambiguate=disambiguate)
+ disambiguate=disambiguate)
dirname = os.path.dirname(discName)
if os.path.exists(dirname):
sys.stdout.write("Output directory %s already exists\n" %
@@ -382,8 +371,8 @@ def ripIfNotRipped(number):
path = self.program.getPath(self.program.outdir,
self.options.track_template,
self.mbdiscid, number,
- profile=profile, disambiguate=disambiguate) \
- + '.' + profile.extension
+ disambiguate=disambiguate) \
+ + '.' + 'flac'
logger.debug('ripIfNotRipped: path %r' % path)
trackResult.number = number
@@ -429,7 +418,6 @@ def ripIfNotRipped(number):
self.program.ripTrack(self.runner, trackResult,
offset=int(self.options.offset),
device=self.device,
- profile=profile,
taglist=self.program.getTagList(number),
overread=self.options.overread,
what='track %d of %d%s' % (
@@ -509,7 +497,7 @@ def ripIfNotRipped(number):
### write disc files
discName = self.program.getPath(self.program.outdir,
self.options.disc_template, self.mbdiscid, 0,
- profile=profile, disambiguate=disambiguate)
+ disambiguate=disambiguate)
dirname = os.path.dirname(discName)
if not os.path.exists(dirname):
os.makedirs(dirname)
@@ -521,7 +509,8 @@ def ripIfNotRipped(number):
logger.debug('writing m3u file for %r', discName)
m3uPath = u'%s.m3u' % discName
handle = open(m3uPath, 'w')
- handle.write(u'#EXTM3U\n')
+ u = u'#EXTM3U\n'
+ handle.write(u.encode('utf-8'))
def writeFile(handle, path, length):
targetPath = common.getRelativePath(path, m3uPath)
@@ -541,8 +530,7 @@ def writeFile(handle, path, length):
path = self.program.getPath(self.program.outdir,
self.options.track_template, self.mbdiscid, i + 1,
- profile=profile,
- disambiguate=disambiguate) + '.' + profile.extension
+ disambiguate=disambiguate) + '.' + 'flac'
writeFile(handle, path,
self.itable.getTrackLength(i + 1) / common.FRAMES_PER_SECOND)
diff --git a/morituri/command/debug.py b/morituri/command/debug.py
index 8271486c..c6310db3 100644
--- a/morituri/command/debug.py
+++ b/morituri/command/debug.py
@@ -153,14 +153,6 @@ def add_arguments(self):
# here to avoid import gst eating our options
from morituri.common import encode
- default = 'flac'
- # slated for deletion as flac will be the only encoder
- self.parser.add_argument('--profile',
- action="store",
- dest="profile",
- help="profile for encoding (default '%s', choices '%s')" % (
- default, "', '".join(encode.ALL_PROFILES.keys())),
- default=default)
self.parser.add_argument('input', action='store',
help="audio file to encode")
self.parser.add_argument('output', nargs='?', action='store',
@@ -168,7 +160,6 @@ def add_arguments(self):
def do(self):
from morituri.common import encode
- profile = encode.ALL_PROFILES[self.options.profile]()
try:
fromPath = unicode(self.options.input)
@@ -180,7 +171,7 @@ def do(self):
try:
toPath = unicode(self.options.output)
except IndexError:
- toPath = fromPath + '.' + profile.extension
+ toPath = fromPath + '.flac'
runner = task.SyncRunner()
@@ -191,33 +182,14 @@ def do(self):
runner.run(encodetask)
- sys.stdout.write('Peak level: %r\n' % encodetask.peak)
- sys.stdout.write('Encoded to %s\n' % toPath.encode('utf-8'))
-
-
-class MaxSample(BaseCommand):
- summary = "run a max sample task"
- description = summary
-
- def add_arguments(self):
- self.parser.add_argument('files', nargs='+', action='store',
- help="audio files to sample")
-
- def do(self):
- runner = task.SyncRunner()
- # here to avoid import gst eating our options
- from morituri.common import checksum
-
- for arg in self.options.files:
- fromPath = unicode(arg.decode('utf-8'))
+ # I think we want this to be
+ # fromPath, not toPath, since the sox peak task, afaik, works on wave
+ # files
+ peaktask = encode.SoxPeakTask(fromPath)
+ runner.run(peaktask)
- checksumtask = checksum.MaxSampleTask(fromPath)
-
- runner.run(checksumtask)
-
- sys.stdout.write('%s\n' % arg)
- sys.stdout.write('Biggest absolute sample: %04x\n' %
- checksumtask.checksum)
+ sys.stdout.write('Peak level: %r\n' % peaktask.peak)
+ sys.stdout.write('Encoded to %s\n' % toPath.encode('utf-8'))
class Tag(BaseCommand):
@@ -325,7 +297,6 @@ class Debug(BaseCommand):
subcommands = {
'checksum': Checksum,
'encode': Encode,
- 'maxsample': MaxSample,
'tag': Tag,
'musicbrainzngs': MusicBrainzNGS,
'resultcache': ResultCache,
diff --git a/morituri/command/image.py b/morituri/command/image.py
index 52a22d3b..606ed813 100644
--- a/morituri/command/image.py
+++ b/morituri/command/image.py
@@ -25,6 +25,7 @@
from morituri.command.basecommand import BaseCommand
from morituri.common import accurip, config, program
+from morituri.common import encode
from morituri.extern.task import task
from morituri.image import image
from morituri.result import result
@@ -59,8 +60,6 @@ def add_arguments(self):
)
def do(self):
- # here to avoid import gst eating our options
- from morituri.common import encode
prog = program.Program(config.Config(), stdout=sys.stdout)
runner = task.SyncRunner()
diff --git a/morituri/command/offset.py b/morituri/command/offset.py
index b8a86b2b..065f2ea0 100644
--- a/morituri/command/offset.py
+++ b/morituri/command/offset.py
@@ -32,6 +32,7 @@
from morituri.common import accurip, common, config, drive, program
from morituri.common import task as ctask
from morituri.program import cdrdao, cdparanoia, utils
+from morituri.common import checksum
from morituri.extern.task import task
@@ -80,7 +81,6 @@ def handle_arguments(self):
logger.debug('Trying with offsets %r', self._offsets)
def do(self):
- prog = program.Program(config.Config())
runner = ctask.SyncRunner()
device = self.options.device
@@ -209,8 +209,6 @@ def _arcs(self, runner, table, track, offset):
track, offset)
runner.run(t)
- # here to avoid import gst eating our options
- from morituri.common import checksum
# TODO MW: Update this to also use the v2 checksum(s)
t = checksum.FastAccurateRipChecksumTask(path, trackNumber=track,
diff --git a/morituri/common/checksum.py b/morituri/common/checksum.py
index 0e89dabc..9ed12a80 100644
--- a/morituri/common/checksum.py
+++ b/morituri/common/checksum.py
@@ -20,18 +20,10 @@
# 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 binascii
import wave
-import gst
-from morituri.common import common, task
-from morituri.common import gstreamer as cgstreamer
-
-from morituri.extern.task import gstreamer
from morituri.extern.task import task as etask
from morituri.program.arc import accuraterip_checksum
@@ -42,238 +34,6 @@
# checksums are not CRC's. a CRC is a specific type of checksum.
-class ChecksumTask(gstreamer.GstPipelineTask):
- """
- I am a task that calculates a checksum of the decoded audio data.
-
- @ivar checksum: the resulting checksum
- """
-
- logCategory = 'ChecksumTask'
-
- # this object needs a main loop to stop
- description = 'Calculating checksum'
-
- def __init__(self, path, sampleStart=0, sampleLength=-1):
- """
- A sample is considered a set of samples for each channel;
- ie 16 bit stereo is 4 bytes per sample.
- If sampleLength < 0 it is treated as 'unknown' and calculated.
-
- @type path: unicode
- @type sampleStart: int
- @param sampleStart: the sample to start at
- """
-
- # sampleLength can be e.g. -588 when it is -1 * SAMPLES_PER_FRAME
-
- assert type(path) is unicode, "%r is not unicode" % path
-
- self.logName = "ChecksumTask 0x%x" % id(self)
-
- # use repr/%r because path can be unicode
- if sampleLength < 0:
- logger.debug(
- 'Creating checksum task on %r from sample %d until the end',
- path, sampleStart)
- else:
- logger.debug(
- 'Creating checksum task on %r from sample %d for %d samples',
- path, sampleStart, sampleLength)
-
- if not os.path.exists(path):
- raise IndexError('%r does not exist' % path)
-
- self._path = path
- self._sampleStart = sampleStart
- self._sampleLength = sampleLength
- self._sampleEnd = None
- self._checksum = 0
- self._bytes = 0 # number of bytes received
- self._first = None
- self._last = None
- self._adapter = gst.Adapter()
-
- self.checksum = None # result
-
- cgstreamer.removeAudioParsers()
-
- ### gstreamer.GstPipelineTask implementations
-
- def getPipelineDesc(self):
- return '''
- filesrc location="%s" !
- decodebin name=decode ! audio/x-raw-int !
- appsink name=sink sync=False emit-signals=True
- ''' % gstreamer.quoteParse(self._path).encode('utf-8')
-
- def _getSampleLength(self):
- # get length in samples of file
- sink = self.pipeline.get_by_name('sink')
-
- logger.debug('query duration')
- try:
- length, qformat = sink.query_duration(gst.FORMAT_DEFAULT)
- except gst.QueryError, e:
- self.setException(e)
- return None
-
- # wavparse 0.10.14 returns in bytes
- if qformat == gst.FORMAT_BYTES:
- logger.debug('query returned in BYTES format')
- length /= 4
- logger.debug('total sample length of file: %r', length)
-
- return length
-
-
- def paused(self):
- sink = self.pipeline.get_by_name('sink')
-
- length = self._getSampleLength()
- if length is None:
- return
-
- if self._sampleLength < 0:
- self._sampleLength = length - self._sampleStart
- logger.debug('sampleLength is queried as %d samples',
- self._sampleLength)
- else:
- logger.debug('sampleLength is known, and is %d samples' %
- self._sampleLength)
-
- self._sampleEnd = self._sampleStart + self._sampleLength - 1
- logger.debug('sampleEnd is sample %d' % self._sampleEnd)
-
- logger.debug('event')
-
-
- if self._sampleStart == 0 and self._sampleEnd + 1 == length:
- logger.debug('No need to seek, crcing full file')
- else:
- # the segment end only is respected since -good 0.10.14.1
- event = gst.event_new_seek(1.0, gst.FORMAT_DEFAULT,
- gst.SEEK_FLAG_FLUSH,
- gst.SEEK_TYPE_SET, self._sampleStart,
- gst.SEEK_TYPE_SET, self._sampleEnd + 1) # half-inclusive
- logger.debug('CRCing %r from frame %d to frame %d (excluded)' % (
- self._path,
- self._sampleStart / common.SAMPLES_PER_FRAME,
- (self._sampleEnd + 1) / common.SAMPLES_PER_FRAME))
- # FIXME: sending it with sampleEnd set screws up the seek, we
- # don't get # everything for flac; fixed in recent -good
- result = sink.send_event(event)
- logger.debug('event sent, result %r', result)
- if not result:
- msg = 'Failed to select samples with GStreamer seek event'
- logger.critical(msg)
- raise Exception(msg)
- sink.connect('new-buffer', self._new_buffer_cb)
- sink.connect('eos', self._eos_cb)
-
- logger.debug('scheduling setting to play')
- # since set_state returns non-False, adding it as timeout_add
- # will repeatedly call it, and block the main loop; so
- # gobject.timeout_add(0L, self.pipeline.set_state, gst.STATE_PLAYING)
- # would not work.
-
- def play():
- self.pipeline.set_state(gst.STATE_PLAYING)
- return False
- self.schedule(0, play)
-
- #self.pipeline.set_state(gst.STATE_PLAYING)
- logger.debug('scheduled setting to play')
-
- def stopped(self):
- logger.debug('stopped')
- if not self._last:
- # see http://bugzilla.gnome.org/show_bug.cgi?id=578612
- logger.debug(
- 'not a single buffer gotten, setting exception EmptyError')
- self.setException(common.EmptyError('not a single buffer gotten'))
- return
- else:
- self._checksum = self._checksum % 2 ** 32
- logger.debug("last buffer's sample offset %r", self._last.offset)
- logger.debug("last buffer's sample size %r", len(self._last) / 4)
- last = self._last.offset + len(self._last) / 4 - 1
- logger.debug("last sample offset in buffer: %r", last)
- logger.debug("requested sample end: %r", self._sampleEnd)
- logger.debug("requested sample length: %r", self._sampleLength)
- logger.debug("checksum: %08X", self._checksum)
- logger.debug("bytes: %d", self._bytes)
- if self._sampleEnd != last:
- msg = 'did not get all samples, %d of %d missing' % (
- self._sampleEnd - last, self._sampleEnd)
- logger.warning(msg)
- self.setExceptionAndTraceback(common.MissingFrames(msg))
- return
-
- self.checksum = self._checksum
-
- ### subclass methods
-
- def do_checksum_buffer(self, buf, checksum):
- """
- Subclasses should implement this.
-
- @param buf: a byte buffer containing two 16-bit samples per
- channel.
- @type buf: C{str}
- @param checksum: the checksum so far, as returned by the
- previous call.
- @type checksum: C{int}
- """
- raise NotImplementedError
-
- ### private methods
-
- def _new_buffer_cb(self, sink):
- buf = sink.emit('pull-buffer')
- gst.log('received new buffer at offset %r with length %r' % (
- buf.offset, buf.size))
- if self._first is None:
- self._first = buf.offset
- logger.debug('first sample is sample offset %r', self._first)
- self._last = buf
-
- assert len(buf) % 4 == 0, "buffer is not a multiple of 4 bytes"
-
- # FIXME: gst-python 0.10.14.1 doesn't have adapter_peek/_take wrapped
- # see http://bugzilla.gnome.org/show_bug.cgi?id=576505
- self._adapter.push(buf)
-
- while self._adapter.available() >= common.BYTES_PER_FRAME:
- # FIXME: in 0.10.14.1, take_buffer leaks a ref
- buf = self._adapter.take_buffer(common.BYTES_PER_FRAME)
-
- self._checksum = self.do_checksum_buffer(buf, self._checksum)
- self._bytes += len(buf)
-
- # update progress
- sample = self._first + self._bytes / 4
- samplesDone = sample - self._sampleStart
- progress = float(samplesDone) / float((self._sampleLength))
- # marshal to the main thread
- self.schedule(0, self.setProgress, progress)
-
- def _eos_cb(self, sink):
- # get the last one; FIXME: why does this not get to us before ?
- #self._new_buffer_cb(sink)
- logger.debug('eos, scheduling stop')
- self.schedule(0, self.stop)
-
-class CRC32TaskOld(ChecksumTask):
- """
- I do a simple CRC32 check.
- """
-
- description = 'Calculating CRC'
-
- def do_checksum_buffer(self, buf, checksum):
- return zlib.crc32(buf, checksum)
-
class CRC32Task(etask.Task):
# TODO: Support sampleStart, sampleLength later on (should be trivial, just
# add change the read part in _crc32 to skip some samples and/or not
@@ -314,143 +74,3 @@ def _arc(self):
self.checksum = arc
self.stop()
-
-
-class AccurateRipChecksumTask(ChecksumTask):
- """
- I implement the AccurateRip checksum.
-
- See http://www.accuraterip.com/
- """
-
- description = 'Calculating AccurateRip checksum'
-
- def __init__(self, path, trackNumber, trackCount, sampleStart=0,
- sampleLength=-1):
- ChecksumTask.__init__(self, path, sampleStart, sampleLength)
- self._trackNumber = trackNumber
- self._trackCount = trackCount
- self._discFrameCounter = 0 # 1-based
-
- def __repr__(self):
- return "" % (
- self._trackNumber, self._path)
-
- def do_checksum_buffer(self, buf, checksum):
- self._discFrameCounter += 1
-
- # on first track ...
- if self._trackNumber == 1:
- # ... skip first 4 CD frames
- if self._discFrameCounter <= 4:
- gst.debug('skipping frame %d' % self._discFrameCounter)
- return checksum
- # ... on 5th frame, only use last value
- elif self._discFrameCounter == 5:
- values = struct.unpack(" discFrameLength - 5:
- logger.debug('skipping frame %d', self._discFrameCounter)
- return checksum
-
- # self._bytes is updated after do_checksum_buffer
- factor = self._bytes / 4 + 1
- values = struct.unpack("<%dI" % (len(buf) / 4), buf)
- for value in values:
- checksum += factor * value
- factor += 1
- # offset = self._bytes / 4 + i + 1
- # if offset % common.SAMPLES_PER_FRAME == 0:
- # print 'frame %d, ends before %d, last value %08x, CRC %08x' % (
- # offset / common.SAMPLES_PER_FRAME, offset, value, sum)
-
- checksum &= 0xFFFFFFFF
- return checksum
-
-
-class TRMTask(task.GstPipelineTask):
- """
- I calculate a MusicBrainz TRM fingerprint.
-
- @ivar trm: the resulting trm
- """
-
- trm = None
- description = 'Calculating fingerprint'
-
- def __init__(self, path):
- if not os.path.exists(path):
- raise IndexError('%s does not exist' % path)
-
- self.path = path
- self._trm = None
- self._bus = None
-
- def getPipelineDesc(self):
- return '''
- filesrc location="%s" !
- decodebin ! audioconvert ! audio/x-raw-int !
- trm name=trm !
- appsink name=sink sync=False emit-signals=True''' % self.path
-
- def parsed(self):
- sink = self.pipeline.get_by_name('sink')
- sink.connect('new-buffer', self._new_buffer_cb)
-
- def paused(self):
- gst.debug('query duration')
-
- self._length, qformat = self.pipeline.query_duration(gst.FORMAT_TIME)
- gst.debug('total length: %r' % self._length)
- gst.debug('scheduling setting to play')
- # since set_state returns non-False, adding it as timeout_add
- # will repeatedly call it, and block the main loop; so
- # gobject.timeout_add(0L, self.pipeline.set_state, gst.STATE_PLAYING)
- # would not work.
-
-
- # FIXME: can't move this to base class because it triggers too soon
- # in the case of checksum
-
- def bus_eos_cb(self, bus, message):
- gst.debug('eos, scheduling stop')
- self.schedule(0, self.stop)
-
- def bus_tag_cb(self, bus, message):
- taglist = message.parse_tag()
- if 'musicbrainz-trmid' in taglist.keys():
- self._trm = taglist['musicbrainz-trmid']
-
- def _new_buffer_cb(self, sink):
- # this is just for counting progress
- buf = sink.emit('pull-buffer')
- position = buf.timestamp
- if buf.duration != gst.CLOCK_TIME_NONE:
- position += buf.duration
- self.setProgress(float(position) / self._length)
-
- def stopped(self):
- self.trm = self._trm
-
-class MaxSampleTask(ChecksumTask):
- """
- I check for the biggest sample value.
- """
-
- description = 'Finding highest sample value'
-
- def do_checksum_buffer(self, buf, checksum):
- values = struct.unpack("<%dh" % (len(buf) / 2), buf)
- absvalues = [abs(v) for v in values]
- m = max(absvalues)
- if checksum < m:
- checksum = m
-
- return checksum
-
diff --git a/morituri/common/common.py b/morituri/common/common.py
index d13488c1..641720f1 100644
--- a/morituri/common/common.py
+++ b/morituri/common/common.py
@@ -135,47 +135,6 @@ def formatTime(seconds, fractional=3):
return " ".join(chunks)
-
-def tagListToDict(tl):
- """
- Converts gst.TagList to dict.
- Also strips it of tags that are not writable.
- """
- import gst
-
- d = {}
- for key in tl.keys():
- if key == gst.TAG_DATE:
- date = tl[key]
- d[key] = "%4d-%2d-%2d" % (date.year, date.month, date.day)
- elif key in [
- gst.TAG_AUDIO_CODEC,
- gst.TAG_VIDEO_CODEC,
- gst.TAG_MINIMUM_BITRATE,
- gst.TAG_BITRATE,
- gst.TAG_MAXIMUM_BITRATE,
- ]:
- pass
- else:
- d[key] = tl[key]
- return d
-
-
-def tagListEquals(tl1, tl2):
- d1 = tagListToDict(tl1)
- d2 = tagListToDict(tl2)
-
- return d1 == d2
-
-
-def tagListDifference(tl1, tl2):
- d1 = tagListToDict(tl1)
- d2 = tagListToDict(tl2)
- return set(d1.keys()) - set(d2.keys())
-
- return d1 == d2
-
-
class MissingDependencyException(Exception):
dependency = None
diff --git a/morituri/common/encode.py b/morituri/common/encode.py
index 9bf2e02f..4d3b69ad 100644
--- a/morituri/common/encode.py
+++ b/morituri/common/encode.py
@@ -20,156 +20,17 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see .
-import math
-import os
-import shutil
-import tempfile
from mutagen.flac import FLAC
-from morituri.common import common
-from morituri.common import gstreamer as cgstreamer
-from morituri.common import task as ctask
+from morituri.extern.task import task
-from morituri.extern.task import task, gstreamer
from morituri.program import sox
from morituri.program import flac
import logging
logger = logging.getLogger(__name__)
-class Profile:
-
- name = None
- extension = None
- pipeline = None
- losless = None
-
- def test(self):
- """
- Test if this profile will work.
- Can check for elements, ...
- """
- pass
-
-
-class FlacProfile(Profile):
- name = 'flac'
- extension = 'flac'
- pipeline = 'flacenc name=tagger quality=8'
- lossless = True
-
- # FIXME: we should do something better than just printing ERRORS
-
- def test(self):
-
- # here to avoid import gst eating our options
- import gst
-
- plugin = gst.registry_get_default().find_plugin('flac')
- if not plugin:
- print 'ERROR: cannot find flac plugin'
- return False
-
- versionTuple = tuple([int(x) for x in plugin.get_version().split('.')])
- if len(versionTuple) < 4:
- versionTuple = versionTuple + (0, )
- if versionTuple > (0, 10, 9, 0) and versionTuple <= (0, 10, 15, 0):
- print 'ERROR: flacenc between 0.10.9 and 0.10.15 has a bug'
- return False
-
- return True
-
-# FIXME: ffenc_alac does not have merge_tags
-
-
-class AlacProfile(Profile):
- name = 'alac'
- extension = 'alac'
- pipeline = 'ffenc_alac'
- lossless = True
-
-# FIXME: wavenc does not have merge_tags
-
-
-class WavProfile(Profile):
- name = 'wav'
- extension = 'wav'
- pipeline = 'wavenc'
- lossless = True
-
-
-class WavpackProfile(Profile):
- name = 'wavpack'
- extension = 'wv'
- pipeline = 'wavpackenc bitrate=0 name=tagger'
- lossless = True
-
-
-class _LameProfile(Profile):
- extension = 'mp3'
- lossless = False
-
- def test(self):
- version = cgstreamer.elementFactoryVersion('lamemp3enc')
- logger.debug('lamemp3enc version: %r', version)
- if version:
- t = tuple([int(s) for s in version.split('.')])
- if t >= (0, 10, 19):
- self.pipeline = self._lamemp3enc_pipeline
- return True
-
- version = cgstreamer.elementFactoryVersion('lame')
- logger.debug('lame version: %r', version)
- if version:
- self.pipeline = self._lame_pipeline
- return True
-
- return False
-
-
-class MP3Profile(_LameProfile):
- name = 'mp3'
-
- _lame_pipeline = 'lame name=tagger quality=0 ! id3v2mux'
- _lamemp3enc_pipeline = \
- 'lamemp3enc name=tagger target=bitrate cbr=true bitrate=320 ! ' \
- 'xingmux ! id3v2mux'
-
-
-class MP3VBRProfile(_LameProfile):
- name = 'mp3vbr'
-
- _lame_pipeline = 'lame name=tagger ' \
- 'vbr-quality=0 vbr=new vbr-mean-bitrate=192 ! ' \
- 'id3v2mux'
- _lamemp3enc_pipeline = 'lamemp3enc name=tagger quality=0 ' \
- '! xingmux ! id3v2mux'
-
-
-class VorbisProfile(Profile):
- name = 'vorbis'
- extension = 'oga'
- pipeline = 'audioconvert ! vorbisenc name=tagger ! oggmux'
- lossless = False
-
-
-PROFILES = {
- 'wav': WavProfile,
- 'flac': FlacProfile,
- 'alac': AlacProfile,
- 'wavpack': WavpackProfile,
-}
-
-LOSSY_PROFILES = {
- 'mp3': MP3Profile,
- 'mp3vbr': MP3VBRProfile,
- 'vorbis': VorbisProfile,
-}
-
-ALL_PROFILES = PROFILES.copy()
-ALL_PROFILES.update(LOSSY_PROFILES)
-
class SoxPeakTask(task.Task):
description = 'Calculating peak level'
@@ -226,380 +87,3 @@ def _tag(self):
w.save()
self.stop()
-
-class EncodeTask(ctask.GstPipelineTask):
- """
- I am a task that encodes a .wav file.
- I set tags too.
- I also calculate the peak level of the track.
-
- @param peak: the peak volume, from 0.0 to 1.0. This is the sqrt of the
- peak power.
- @type peak: float
- """
-
- logCategory = 'EncodeTask'
-
- description = 'Encoding'
- peak = None
-
- def __init__(self, inpath, outpath, profile, taglist=None, what="track"):
- """
- @param profile: encoding profile
- @type profile: L{Profile}
- """
- assert type(inpath) is unicode, "inpath %r is not unicode" % inpath
- assert type(outpath) is unicode, \
- "outpath %r is not unicode" % outpath
-
- self._inpath = inpath
- self._outpath = outpath
- self._taglist = taglist
- self._length = 0 # in samples
-
- self._level = None
- self._peakdB = None
- self._profile = profile
-
- self.description = "Encoding %s" % what
- self._profile.test()
-
- cgstreamer.removeAudioParsers()
-
- def getPipelineDesc(self):
- # start with an emit interval of one frame, because we end up setting
- # the final interval after paused and after processing some samples
- # already, which is too late
- interval = int(self.gst.SECOND / 75.0)
- return '''
- filesrc location="%s" !
- decodebin name=decoder !
- audio/x-raw-int,width=16,depth=16,channels=2 !
- level name=level interval=%d !
- %s ! identity name=identity !
- filesink location="%s" name=sink''' % (
- gstreamer.quoteParse(self._inpath).encode('utf-8'),
- interval,
- self._profile.pipeline,
- gstreamer.quoteParse(self._outpath).encode('utf-8'))
-
- def parsed(self):
- tagger = self.pipeline.get_by_name('tagger')
-
- # set tags
- if tagger and self._taglist:
- # FIXME: under which conditions do we not have merge_tags ?
- # See for example comment saying wavenc did not have it.
- try:
- tagger.merge_tags(self._taglist, self.gst.TAG_MERGE_APPEND)
- except AttributeError, e:
- logger.warning('Could not merge tags: %r', str(e))
-
- def paused(self):
- # get length
- identity = self.pipeline.get_by_name('identity')
- logger.debug('query duration')
- try:
- length, qformat = identity.query_duration(self.gst.FORMAT_DEFAULT)
- except self.gst.QueryError, e:
- self.setException(e)
- self.stop()
- return
-
-
- # wavparse 0.10.14 returns in bytes
- if qformat == self.gst.FORMAT_BYTES:
- logger.debug('query returned in BYTES format')
- length /= 4
- logger.debug('total length: %r', length)
- self._length = length
-
- duration = None
- try:
- duration, qformat = identity.query_duration(self.gst.FORMAT_TIME)
- except self.gst.QueryError, e:
- logger.debug('Could not query duration')
- self._duration = duration
-
- # set up level callbacks
- # FIXME: publicize bus and reuse it instead of regetting and adding ?
- bus = self.pipeline.get_bus()
- bus.add_signal_watch()
-
- bus.connect('message::element', self._message_element_cb)
- self._level = self.pipeline.get_by_name('level')
-
- # set an interval that is smaller than the duration
- # FIXME: check level and make sure it emits level up to the last
- # sample, even if input is small
- interval = self.gst.SECOND
- if interval > duration:
- interval = duration / 2
- logger.debug('Setting level interval to %s, duration %s',
- self.gst.TIME_ARGS(interval), self.gst.TIME_ARGS(duration))
- self._level.set_property('interval', interval)
- # add a probe so we can track progress
- # we connect to level because this gives us offset in samples
- srcpad = self._level.get_static_pad('src')
- self.gst.debug('adding srcpad buffer probe to %r' % srcpad)
- ret = srcpad.add_buffer_probe(self._probe_handler)
- self.gst.debug('added srcpad buffer probe to %r: %r' % (srcpad, ret))
-
- def _probe_handler(self, pad, buffer):
- # update progress based on buffer offset (expected to be in samples)
- # versus length in samples
- # marshal to main thread
- self.schedule(0, self.setProgress,
- float(buffer.offset) / self._length)
-
- # don't drop the buffer
- return True
-
- def bus_eos_cb(self, bus, message):
- logger.debug('eos, scheduling stop')
- self.schedule(0, self.stop)
-
- def _message_element_cb(self, bus, message):
- if message.src != self._level:
- return
-
- s = message.structure
- if s.get_name() != 'level':
- return
-
-
- if self._peakdB is None:
- self._peakdB = s['peak'][0]
-
- for p in s['peak']:
- if self._peakdB < p:
- logger.debug('higher peakdB found, now %r', self._peakdB)
- self._peakdB = p
-
- # FIXME: works around a bug on F-15 where buffer probes don't seem
- # to get triggered to update progress
- if self._duration is not None:
- self.schedule(0, self.setProgress,
- float(s['stream-time'] + s['duration']) / self._duration)
-
- def stopped(self):
- if self._peakdB is not None:
- logger.debug('peakdB %r', self._peakdB)
- self.peak = math.sqrt(math.pow(10, self._peakdB / 10.0))
- return
-
- logger.warning('No peak found.')
-
- self.peak = 0.0
-
- if self._duration:
- logger.warning('GStreamer level element did not send messages.')
- # workaround for when the file is too short to have volume ?
- if self._length == common.SAMPLES_PER_FRAME:
- logger.warning('only one frame of audio, setting peak to 0.0')
- self.peak = 0.0
-
-class TagReadTask(ctask.GstPipelineTask):
- """
- I am a task that reads tags.
-
- @ivar taglist: the tag list read from the file.
- @type taglist: L{gst.TagList}
- """
-
- logCategory = 'TagReadTask'
-
- description = 'Reading tags'
-
- taglist = None
-
- def __init__(self, path):
- """
- """
- assert type(path) is unicode, "path %r is not unicode" % path
-
- self._path = path
-
- def getPipelineDesc(self):
- return '''
- filesrc location="%s" !
- decodebin name=decoder !
- fakesink''' % (
- gstreamer.quoteParse(self._path).encode('utf-8'))
-
- def bus_eos_cb(self, bus, message):
- logger.debug('eos, scheduling stop')
- self.schedule(0, self.stop)
-
- def bus_tag_cb(self, bus, message):
- taglist = message.parse_tag()
- logger.debug('tag_cb, %d tags' % len(taglist.keys()))
- if not self.taglist:
- self.taglist = taglist
- else:
- import gst
- self.taglist = self.taglist.merge(taglist, gst.TAG_MERGE_REPLACE)
-
-
-class TagWriteTask(ctask.LoggableTask):
- """
- I am a task that retags an encoded file.
- """
-
- logCategory = 'TagWriteTask'
-
- description = 'Writing tags'
-
- def __init__(self, inpath, outpath, taglist=None):
- """
- """
- assert type(inpath) is unicode, "inpath %r is not unicode" % inpath
- assert type(outpath) is unicode, "outpath %r is not unicode" % outpath
-
- self._inpath = inpath
- self._outpath = outpath
- self._taglist = taglist
-
- def start(self, runner):
- task.Task.start(self, runner)
-
- # here to avoid import gst eating our options
- import gst
-
- # FIXME: this hardcodes flac; we should be using the correct
- # tag element instead
- self._pipeline = gst.parse_launch('''
- filesrc location="%s" !
- flactag name=tagger !
- filesink location="%s"''' % (
- gstreamer.quoteParse(self._inpath).encode('utf-8'),
- gstreamer.quoteParse(self._outpath).encode('utf-8')))
-
- # set tags
- tagger = self._pipeline.get_by_name('tagger')
- if self._taglist:
- tagger.merge_tags(self._taglist, gst.TAG_MERGE_APPEND)
-
- logger.debug('pausing pipeline')
- self._pipeline.set_state(gst.STATE_PAUSED)
- self._pipeline.get_state()
- logger.debug('paused pipeline')
-
- # add eos handling
- bus = self._pipeline.get_bus()
- bus.add_signal_watch()
- bus.connect('message::eos', self._message_eos_cb)
-
- logger.debug('scheduling setting to play')
- # since set_state returns non-False, adding it as timeout_add
- # will repeatedly call it, and block the main loop; so
- # gobject.timeout_add(0L, self._pipeline.set_state,
- # gst.STATE_PLAYING)
- # would not work.
-
- def play():
- self._pipeline.set_state(gst.STATE_PLAYING)
- return False
- self.schedule(0, play)
-
- #self._pipeline.set_state(gst.STATE_PLAYING)
- logger.debug('scheduled setting to play')
-
- def _message_eos_cb(self, bus, message):
- logger.debug('eos, scheduling stop')
- self.schedule(0, self.stop)
-
- def stop(self):
- # here to avoid import gst eating our options
- import gst
-
- logger.debug('stopping')
- logger.debug('setting state to NULL')
- self._pipeline.set_state(gst.STATE_NULL)
- logger.debug('set state to NULL')
- task.Task.stop(self)
-
-
-class SafeRetagTask(ctask.LoggableMultiSeparateTask):
- """
- I am a task that retags an encoded file safely in place.
- First of all, if the new tags are the same as the old ones, it doesn't
- do anything.
- If the tags are not the same, then the file gets retagged, but only
- if the decodes of the original and retagged file checksum the same.
-
- @ivar changed: True if the tags have changed (and hence an output file is
- generated)
- """
-
- logCategory = 'SafeRetagTask'
-
- description = 'Retagging'
-
- changed = False
-
- def __init__(self, path, taglist=None):
- """
- """
- assert type(path) is unicode, "path %r is not unicode" % path
-
- task.MultiSeparateTask.__init__(self)
-
- self._path = path
- self._taglist = taglist.copy()
-
- self.tasks = [TagReadTask(path), ]
-
- def stopped(self, taskk):
- from morituri.common import checksum
-
- if not taskk.exception:
- # Check if the tags are different or not
- if taskk == self.tasks[0]:
- taglist = taskk.taglist.copy()
- if common.tagListEquals(taglist, self._taglist):
- logger.debug('tags are already fine: %r',
- common.tagListToDict(taglist))
- else:
- # need to retag
- logger.debug('tags need to be rewritten')
- logger.debug('Current tags: %r, new tags: %r',
- common.tagListToDict(taglist),
- common.tagListToDict(self._taglist))
- assert common.tagListToDict(taglist) \
- != common.tagListToDict(self._taglist)
- self.tasks.append(checksum.CRC32Task(self._path))
- self._fd, self._tmppath = tempfile.mkstemp(
- dir=os.path.dirname(self._path), suffix=u'.morituri')
- self.tasks.append(TagWriteTask(self._path,
- self._tmppath, self._taglist))
- self.tasks.append(checksum.CRC32Task(self._tmppath))
- self.tasks.append(TagReadTask(self._tmppath))
- elif len(self.tasks) > 1 and taskk == self.tasks[4]:
- if common.tagListEquals(self.tasks[4].taglist, self._taglist):
- logger.debug('tags written successfully')
- c1 = self.tasks[1].checksum
- c2 = self.tasks[3].checksum
- logger.debug('comparing checksums %08x and %08x' % (c1, c2))
- if c1 == c2:
- # data is fine, so we can now move
- # but first, copy original mode to our temporary file
- shutil.copymode(self._path, self._tmppath)
- logger.debug('moving temporary file to %r' % self._path)
- os.rename(self._tmppath, self._path)
- self.changed = True
- else:
- # FIXME: don't raise TypeError
- e = TypeError("Checksums failed")
- self.setAndRaiseException(e)
- else:
- logger.debug('failed to update tags, only have %r',
- common.tagListToDict(self.tasks[4].taglist))
- logger.debug('difference: %r',
- common.tagListDifference(self.tasks[4].taglist,
- self._taglist))
- os.unlink(self._tmppath)
- e = TypeError("Tags not written")
- self.setAndRaiseException(e)
-
- task.MultiSeparateTask.stopped(self, taskk)
diff --git a/morituri/common/gstreamer.py b/morituri/common/gstreamer.py
deleted file mode 100644
index ed1e5c35..00000000
--- a/morituri/common/gstreamer.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# -*- 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 re
-import commands
-
-import logging
-logger = logging.getLogger(__name__)
-
-# workaround for issue #64
-
-
-def removeAudioParsers():
- logger.debug('Removing buggy audioparsers plugin if needed')
-
- import gst
- registry = gst.registry_get_default()
-
- plugin = registry.find_plugin("audioparsersbad")
- if plugin:
- # always remove from bad
- logger.debug('removing audioparsersbad plugin from registry')
- registry.remove_plugin(plugin)
-
- plugin = registry.find_plugin("audioparsers")
- if plugin:
- logger.debug('removing audioparsers plugin from %s %s',
- plugin.get_source(), plugin.get_version())
-
- # the query bug was fixed after 0.10.30 and before 0.10.31
- # the seek bug is still there though
- # if plugin.get_source() == 'gst-plugins-good' \
- # and plugin.get_version() > '0.10.30.1':
- # return
-
- registry.remove_plugin(plugin)
-
-def gstreamerVersion():
- import gst
- return _versionify(gst.version())
-
-def gstPythonVersion():
- import gst
- return _versionify(gst.pygst_version)
-
-_VERSION_RE = re.compile(
- "Version:\s*(?P[\d.]+)")
-
-def elementFactoryVersion(name):
- # surprisingly, there is no python way to get from an element factory
- # to its plugin and its version directly; you can only compare
- # with required versions
- # Let's use gst-inspect-0.10 and wave hands and assume it points to the
- # same version that python uses
- output = commands.getoutput('gst-inspect-0.10 %s | grep Version' % name)
- m = _VERSION_RE.search(output)
- if not m:
- return None
- return m.group('version')
-
-
-def _versionify(tup):
- l = list(tup)
- if len(l) == 4 and l[3] == 0:
- l = l[:3]
- v = [str(n) for n in l]
- return ".".join(v)
diff --git a/morituri/common/program.py b/morituri/common/program.py
index 27295073..d1285aab 100644
--- a/morituri/common/program.py
+++ b/morituri/common/program.py
@@ -30,9 +30,10 @@
import time
from morituri.common import common, mbngs, cache, path
+from morituri.common import checksum
from morituri.program import cdrdao, cdparanoia
from morituri.image import image
-from morituri.extern.task import task, gstreamer
+from morituri.extern.task import task
import logging
logger = logging.getLogger(__name__)
@@ -172,8 +173,7 @@ def getRipResult(self, cddbdiscid):
def saveRipResult(self):
self._presult.persist()
- def getPath(self, outdir, template, mbdiscid, i, profile=None,
- disambiguate=False):
+ def getPath(self, outdir, template, mbdiscid, i, disambiguate=False):
"""
Based on the template, get a complete path for the given track,
minus extension.
@@ -185,7 +185,6 @@ def getPath(self, outdir, template, mbdiscid, i, profile=None,
@type template: unicode
@param i: track number (0 for HTOA, or for disc)
@type i: int
- @type profile: L{morituri.common.encode.Profile}
@rtype: unicode
"""
@@ -208,7 +207,7 @@ def getPath(self, outdir, template, mbdiscid, i, profile=None,
v['R'] = 'Unknown'
v['B'] = '' # barcode
v['C'] = '' # catalog number
- v['x'] = profile and profile.extension or 'unknown'
+ v['x'] = 'flac'
v['X'] = v['x'].upper()
v['y'] = '0000'
@@ -416,12 +415,12 @@ def getMusicBrainz(self, ittoc, mbdiscid, release=None, country=None, prompt=Fal
def getTagList(self, number):
"""
- Based on the metadata, get a gst.TagList for the given track.
+ Based on the metadata, get a dict of tags for the given track.
@param number: track number (0 for HTOA)
@type number: int
- @rtype: L{gst.TagList}
+ @rtype: dict
"""
trackArtist = u'Unknown Artist'
albumArtist = u'Unknown Artist'
@@ -491,8 +490,6 @@ def getHTOA(self):
return (start, stop)
def verifyTrack(self, runner, trackResult):
- # here to avoid import gst eating our options
- from morituri.common import checksum
t = checksum.CRC32Task(trackResult.filename)
@@ -502,9 +499,6 @@ def verifyTrack(self, runner, trackResult):
if isinstance(e.exception, common.MissingFrames):
logger.warning('missing frames for %r' % trackResult.filename)
return False
- elif isinstance(e.exception, gstreamer.GstException):
- logger.warning('GstException %r' % (e.exception, ))
- return False
else:
raise
@@ -513,7 +507,7 @@ def verifyTrack(self, runner, trackResult):
trackResult.testcrc, t.checksum, ret)
return ret
- def ripTrack(self, runner, trackResult, offset, device, profile, taglist,
+ def ripTrack(self, runner, trackResult, offset, device, taglist,
overread, what=None):
"""
Ripping the track may change the track's filename as stored in
@@ -521,8 +515,6 @@ def ripTrack(self, runner, trackResult, offset, device, profile, taglist,
@param trackResult: the object to store information in.
@type trackResult: L{result.TrackResult}
- @param number: track number (1-based)
- @type number: int
"""
if trackResult.number == 0:
start, stop = self.getHTOA()
@@ -541,7 +533,6 @@ def ripTrack(self, runner, trackResult, offset, device, profile, taglist,
self.result.table, start, stop, overread,
offset=offset,
device=device,
- profile=profile,
taglist=taglist,
what=what)
diff --git a/morituri/common/task.py b/morituri/common/task.py
index f43cf172..24197042 100644
--- a/morituri/common/task.py
+++ b/morituri/common/task.py
@@ -6,7 +6,7 @@
import subprocess
from morituri.extern import asyncsub
-from morituri.extern.task import task, gstreamer
+from morituri.extern.task import task
import logging
logger = logging.getLogger(__name__)
@@ -24,10 +24,6 @@ class LoggableMultiSeparateTask(task.MultiSeparateTask):
pass
-class GstPipelineTask(gstreamer.GstPipelineTask):
- pass
-
-
class PopenTask(task.Task):
"""
I am a task that runs a command using Popen.
diff --git a/morituri/extern/task/gstreamer.py b/morituri/extern/task/gstreamer.py
deleted file mode 100644
index 6cd7f024..00000000
--- a/morituri/extern/task/gstreamer.py
+++ /dev/null
@@ -1,272 +0,0 @@
-# -*- Mode: Python; test-case-name: test_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 task
-
-def quoteParse(path):
- """
- Quote a path for use in gst.parse_launch.
- """
- # Make sure double quotes and backslashes are escaped. See
- # morituri.test.test_common_checksum.NormalPathTestCase
-
- return path.replace('\\', '\\\\').replace('"', '\\"')
-
-
-class GstException(Exception):
- def __init__(self, gerror, debug):
- self.args = (gerror, debug, )
- self.gerror = gerror
- self.debug = debug
-
- def __repr__(self):
- return '' % (
- self.gerror.message, self.debug)
-
-class GstPipelineTask(task.Task):
- """
- I am a base class for tasks that use a GStreamer pipeline.
-
- I handle errors and raise them appropriately.
-
- @cvar gst: the GStreamer module, so code does not have to import gst
- as a module in code everywhere to avoid option stealing.
- @cvar playing: whether the pipeline should be set to playing after
- paused. Some pipelines don't need to play for a task
- to be done (for example, querying length)
- @type playing: bool
- @type pipeline: L{gst.Pipeline}
- @type bus: L{gst.Bus}
- """
-
- gst = None
- playing = True
- pipeline = None
- bus = None
-
- ### task.Task implementations
- def start(self, runner):
- import gst
- self.gst = gst
-
- task.Task.start(self, runner)
-
- self.getPipeline()
-
- self.bus = self.pipeline.get_bus()
- # FIXME: remove this
- self._bus = self.bus
- self.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('setting pipeline to PAUSED')
- self.pipeline.set_state(gst.STATE_PAUSED)
- self.debug('set pipeline to PAUSED')
- # FIXME: this can block
- ret = self.pipeline.get_state()
- self.debug('got pipeline to PAUSED: %r', ret)
-
- # GStreamer tasks could already be done in paused, and not
- # need playing.
- if self.exception:
- raise self.exception
-
- done = self.paused()
-
- if done:
- self.debug('paused() is done')
- else:
- self.debug('paused() wants more')
- self.play()
-
- def play(self):
- # since set_state returns non-False, adding it as timeout_add
- # will repeatedly call it, and block the main loop; so
- # gobject.timeout_add(0L, self._pipeline.set_state,
- # gst.STATE_PLAYING)
- # would not work.
- def playLater():
- if self.exception:
- self.debug('playLater: exception was raised, not playing')
- self.stop()
- return False
-
- self.debug('setting pipeline to PLAYING')
- self.pipeline.set_state(self.gst.STATE_PLAYING)
- self.debug('set pipeline to PLAYING')
- return False
-
- if self.playing:
- self.debug('schedule playLater()')
- self.schedule(0, playLater)
-
- def stop(self):
- self.debug('stopping')
-
-
- # FIXME: in theory this should help clean up properly,
- # but in practice we can still get
- # python: /builddir/build/BUILD/Python-2.7/Python/pystate.c:595: PyGILState_Ensure: Assertion `autoInterpreterState' failed.
-
- self.pipeline.set_state(self.gst.STATE_READY)
- self.debug('set pipeline to READY')
- # FIXME: this can block
- ret = self.pipeline.get_state()
- self.debug('got pipeline to READY: %r', ret)
-
- self.debug('setting state to NULL')
- self.pipeline.set_state(self.gst.STATE_NULL)
- self.debug('set state to NULL')
- self.stopped()
- task.Task.stop(self)
-
- ### subclass optional implementations
- def getPipeline(self):
- desc = self.getPipelineDesc()
-
- self.debug('creating pipeline %r', desc)
- self.pipeline = self.gst.parse_launch(desc)
-
- def getPipelineDesc(self):
- """
- subclasses should implement this to provide a pipeline description.
-
- @rtype: str
- """
- raise NotImplementedError
-
- def parsed(self):
- """
- Called after parsing/getting the pipeline but before setting it to
- paused.
- """
- pass
-
- def paused(self):
- """
- Called after pipeline is paused.
-
- If this returns True, the task is done and
- should not continue going to PLAYING.
- """
- pass
-
- def stopped(self):
- """
- Called after pipeline is set back to NULL but before chaining up to
- stop()
- """
- pass
-
- def bus_eos_cb(self, bus, message):
- """
- Called synchronously (ie from messaging thread) on eos message.
-
- Override me to handle eos
- """
- pass
-
- def bus_tag_cb(self, bus, message):
- """
- Called synchronously (ie from messaging thread) on tag message.
-
- Override me to handle tags.
- """
- pass
-
- def bus_error_cb(self, bus, message):
- """
- Called synchronously (ie from messaging thread) on error message.
- """
- self.debug('bus_error_cb: bus %r, message %r' % (bus, message))
- if self.exception:
- self.debug('bus_error_cb: already got an exception, ignoring')
- return
-
- exc = GstException(*message.parse_error())
- self.setAndRaiseException(exc)
- self.debug('error, scheduling stop')
- self.schedule(0, self.stop)
-
- def query_length(self, element):
- """
- Query the length of the pipeline in samples, for progress updates.
- To be called from paused()
- """
- # get duration
- self.debug('query duration')
- try:
- duration, qformat = element.query_duration(self.gst.FORMAT_DEFAULT)
- except self.gst.QueryError, e:
- # Fall back to time; for example, oggdemux/vorbisdec only supports
- # TIME
- try:
- duration, qformat = element.query_duration(self.gst.FORMAT_TIME)
- except self.gst.QueryError, e:
- self.setException(e)
- # schedule it, otherwise runner can get set to None before
- # we're done starting
- self.schedule(0, self.stop)
- return
-
- # wavparse 0.10.14 returns in bytes
- if qformat == self.gst.FORMAT_BYTES:
- self.debug('query returned in BYTES format')
- duration /= 4
-
- if qformat == self.gst.FORMAT_TIME:
- rate = None
- self.debug('query returned in TIME format')
- # we need sample rate
- pads = list(element.pads())
- sink = element.get_by_name('sink')
- pads += list(sink.pads())
-
- for pad in pads:
- caps = pad.get_negotiated_caps()
- print caps[0].keys()
- if 'rate' in caps[0].keys():
- rate = caps[0]['rate']
- self.debug('Sample rate: %d Hz', rate)
-
- if not rate:
- raise KeyError(
- 'Cannot find sample rate, cannot convert to samples')
-
- duration = int(float(rate) * (float(duration) / self.gst.SECOND))
-
- self.debug('total duration: %r', duration)
-
- return duration
-
-
diff --git a/morituri/image/image.py b/morituri/image/image.py
index 42c692c9..d2f2b92c 100644
--- a/morituri/image/image.py
+++ b/morituri/image/image.py
@@ -26,7 +26,9 @@
import os
+from morituri.common import encode
from morituri.common import common
+from morituri.common import checksum
from morituri.image import cue, table
from morituri.extern.task import task
from morituri.program.soxi import AudioLengthTask
@@ -135,8 +137,6 @@ def __init__(self, image):
path = image.getRealPath(index.path)
- # here to avoid import gst eating our options
- from morituri.common import checksum
checksumTask = checksum.FastAccurateRipChecksumTask(path,
trackNumber=trackIndex + 1, trackCount=len(cue.table.tracks),
@@ -221,27 +221,24 @@ class ImageEncodeTask(task.MultiSeparateTask):
description = "Encoding tracks"
- def __init__(self, image, profile, outdir):
+ def __init__(self, image, outdir):
task.MultiSeparateTask.__init__(self)
self._image = image
- self._profile = profile
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
logger.debug('schedule encode of %r', path)
root, ext = os.path.splitext(os.path.basename(path))
- outpath = os.path.join(outdir, root + '.' + profile.extension)
+ outpath = os.path.join(outdir, root + '.' + 'flac')
logger.debug('schedule encode to %r', outpath)
- taskk = encode.EncodeTaskFlac(path, os.path.join(outdir,
- root + '.' + profile.extension))
+ taskk = encode.FlacEncodeTask(path, os.path.join(outdir,
+ root + '.' + 'flac'))
self.addTask(taskk)
try:
diff --git a/morituri/program/cdparanoia.py b/morituri/program/cdparanoia.py
index 6daa672f..b2321ade 100644
--- a/morituri/program/cdparanoia.py
+++ b/morituri/program/cdparanoia.py
@@ -430,7 +430,7 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
_tmppath = None
def __init__(self, path, table, start, stop, overread, offset=0,
- device=None, profile=None, taglist=None, what="track"):
+ device=None, taglist=None, what="track"):
"""
@param path: where to store the ripped track
@type path: str
@@ -444,10 +444,8 @@ def __init__(self, path, table, start, stop, overread, offset=0,
@type offset: int
@param device: the device to rip from
@type device: str
- @param profile: the encoding profile
- @type profile: L{encode.Profile}
- @param taglist: a list of tags
- @param taglist: L{gst.TagList}
+ @param taglist: a dict of tags
+ @type taglist: dict
"""
task.MultiSeparateTask.__init__(self)
@@ -461,7 +459,6 @@ def __init__(self, path, table, start, stop, overread, offset=0,
os.close(fd)
self._tmpwavpath = tmppath
- # here to avoid import gst eating our options
from morituri.common import checksum
self.tasks = []
@@ -487,7 +484,6 @@ def __init__(self, path, table, start, stop, overread, offset=0,
self._tmppath = tmpoutpath
self.path = path
- # here to avoid import gst eating our options
from morituri.common import encode
self.tasks.append(encode.FlacEncodeTask(tmppath, tmpoutpath))
diff --git a/morituri/result/logger.py b/morituri/result/logger.py
index ce89a0af..fbd3505b 100644
--- a/morituri/result/logger.py
+++ b/morituri/result/logger.py
@@ -56,17 +56,6 @@ def logRip(self, ripResult, epoch):
lines.append(" Gap detection: cdrdao %s" % ripResult.cdrdaoVersion)
lines.append("")
- # Rip encoding settings
- lines.append("Encoding phase information:")
- lines.append(" Used output format: %s" % ripResult.profileName)
- lines.append(" GStreamer:")
- lines.append(" Pipeline: %s" % ripResult.profilePipeline)
- lines.append(" Version: %s" % ripResult.gstreamerVersion)
- lines.append(" Python version: %s" % ripResult.gstPythonVersion)
- lines.append(" Encoder plugin version: %s" %
- ripResult.encoderVersion)
- lines.append("")
-
# CD metadata
lines.append("CD metadata:")
lines.append(" Album: %s - %s" % (ripResult.artist, ripResult.title))
diff --git a/morituri/result/result.py b/morituri/result/result.py
index 26f72adf..52e5f47a 100644
--- a/morituri/result/result.py
+++ b/morituri/result/result.py
@@ -108,13 +108,6 @@ class RipResult:
cdparanoiaVersion = None
cdparanoiaDefeatsCache = None
- gstreamerVersion = None
- gstPythonVersion = None
- encoderVersion = None
-
- profileName = None
- profilePipeline = None
-
classVersion = 3
def __init__(self):
diff --git a/morituri/test/test_common_checksum.py b/morituri/test/test_common_checksum.py
deleted file mode 100644
index 32639755..00000000
--- a/morituri/test/test_common_checksum.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- Mode: Python; test-case-name: morituri.test.test_common_checksum -*-
-# vi:si:et:sw=4:sts=4:ts=4
-
-import os
-import tempfile
-
-import gobject
-gobject.threads_init()
-
-from morituri.common import checksum, task as ctask
-
-from morituri.extern.task import task, gstreamer
-
-from morituri.test import common as tcommon
-
-
-def h(i):
- return "0x%08x" % i
-
-
-class EmptyTestCase(tcommon.TestCase):
-
- def testEmpty(self):
- # this test makes sure that checksumming empty files doesn't hang
- self.runner = ctask.SyncRunner(verbose=False)
- fd, path = tempfile.mkstemp(suffix=u'morituri.test.empty')
- checksumtask = checksum.ChecksumTask(path)
- # 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, gstreamer.GstException))
- os.unlink(path)
-
-
-class PathTestCase(tcommon.TestCase):
-
- def _testSuffix(self, suffix):
- self.runner = ctask.SyncRunner(verbose=False)
- fd, path = tempfile.mkstemp(suffix=suffix)
- checksumtask = checksum.ChecksumTask(path)
- e = self.assertRaises(task.TaskException, self.runner.run,
- checksumtask, verbose=False)
- self.failUnless(isinstance(e.exception, gstreamer.GstException))
- os.unlink(path)
-
-
-class UnicodePathTestCase(PathTestCase, tcommon.UnicodeTestMixin):
-
- def testUnicodePath(self):
- # this test makes sure we can checksum a unicode path
- self._testSuffix(u'morituri.test.B\xeate Noire.empty')
-
-
-class NormalPathTestCase(PathTestCase):
-
- def testSingleQuote(self):
- self._testSuffix(u"morituri.test.Guns 'N Roses")
-
- def testDoubleQuote(self):
- # This test makes sure we can checksum files with double quote in
- # their name
- self._testSuffix(u'morituri.test.12" edit')
-
- def testBackSlash(self):
- # This test makes sure we can checksum files with a backslash in
- # their name
- self._testSuffix(u'morituri.test.40 Years Back\\Come')
diff --git a/morituri/test/test_common_encode.py b/morituri/test/test_common_encode.py
deleted file mode 100644
index 38a8b9ab..00000000
--- a/morituri/test/test_common_encode.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# -*- Mode: Python; test-case-name: morituri.test.test_common_encode -*-
-# vi:si:et:sw=4:sts=4:ts=4
-
-import os
-import tempfile
-
-import gobject
-gobject.threads_init()
-
-import gst
-
-from morituri.common import encode
-
-from morituri.extern.task import task, gstreamer
-
-from morituri.test import common
-
-
-class PathTestCase(common.TestCase):
-
- def _testSuffix(self, suffix):
- # because of https://bugzilla.gnome.org/show_bug.cgi?id=688625
- # we first create the file with a 'normal' filename, then rename
- self.runner = task.SyncRunner(verbose=False)
- fd, path = tempfile.mkstemp()
-
- cmd = "gst-launch " \
- "audiotestsrc num-buffers=100 samplesperbuffer=1024 ! " \
- "audioconvert ! audio/x-raw-int,width=16,depth=16,channels =2 ! " \
- "wavenc ! " \
- "filesink location=\"%s\" > /dev/null 2>&1" % (
- gstreamer.quoteParse(path).encode('utf-8'), )
- self.debug('Running cmd %r' % cmd)
- os.system(cmd)
- self.failUnless(os.path.exists(path))
- os.close(fd)
-
- fd, newpath = tempfile.mkstemp(suffix=suffix)
- os.rename(path, newpath)
-
- encodetask = encode.EncodeTask(newpath, newpath + '.out',
- encode.WavProfile())
- self.runner.run(encodetask, verbose=False)
- os.close(fd)
- os.unlink(newpath)
- os.unlink(newpath + '.out')
-
-
-# class UnicodePathTestCase(PathTestCase, common.UnicodeTestMixin):
-
-# def testUnicodePath(self):
-# # this test makes sure we can checksum a unicode path
-# self._testSuffix(u'.morituri.test_encode.B\xeate Noire')
-
-
-# class NormalPathTestCase(PathTestCase):
-
-# def testSingleQuote(self):
-# self._testSuffix(u".morituri.test_encode.Guns 'N Roses")
-
-# def testDoubleQuote(self):
-# self._testSuffix(u'.morituri.test_encode.12" edit')
-
-
-class TagReadTestCase(common.TestCase):
-
- def testRead(self):
- path = os.path.join(os.path.dirname(__file__), u'track.flac')
- self.runner = task.SyncRunner(verbose=False)
- t = encode.TagReadTask(path)
- self.runner.run(t)
- self.failUnless(t.taglist)
- self.assertEquals(t.taglist['audio-codec'], 'FLAC')
- self.assertEquals(t.taglist['description'], 'audiotest wave')
-
-
-# class TagWriteTestCase(common.TestCase):
-
-# def testWrite(self):
-# fd, inpath = tempfile.mkstemp(suffix=u'.morituri.tagwrite.flac')
-
-# # wave is pink-noise because a pure sine is encoded too efficiently
-# # by flacenc and triggers not enough frames in parsing
-# # FIXME: file a bug for this in GStreamer
-# os.system('gst-launch '
-# 'audiotestsrc '
-# 'wave=pink-noise num-buffers=10 samplesperbuffer=588 ! '
-# 'audioconvert ! '
-# 'audio/x-raw-int,channels=2,width=16,height=16,rate=44100 ! '
-# 'flacenc ! filesink location=%s > /dev/null 2>&1' % inpath)
-# os.close(fd)
-
-# fd, outpath = tempfile.mkstemp(suffix=u'.morituri.tagwrite.flac')
-# self.runner = task.SyncRunner(verbose=False)
-# taglist = gst.TagList()
-# taglist[gst.TAG_ARTIST] = 'Artist'
-# taglist[gst.TAG_TITLE] = 'Title'
-
-# t = encode.TagWriteTask(inpath, outpath, taglist)
-# self.runner.run(t)
-
-# t = encode.TagReadTask(outpath)
-# self.runner.run(t)
-# self.failUnless(t.taglist)
-# self.assertEquals(t.taglist['audio-codec'], 'FLAC')
-# self.assertEquals(t.taglist['description'], 'audiotest wave')
-# self.assertEquals(t.taglist[gst.TAG_ARTIST], 'Artist')
-# self.assertEquals(t.taglist[gst.TAG_TITLE], 'Title')
-
-# os.unlink(inpath)
-# os.unlink(outpath)
-
-
-class SafeRetagTestCase(common.TestCase):
-
- def setUp(self):
- self._fd, self._path = tempfile.mkstemp(suffix=u'.morituri.retag.flac')
-
- os.system('gst-launch '
- 'audiotestsrc '
- 'num-buffers=40 samplesperbuffer=588 wave=pink-noise ! '
- 'audioconvert ! '
- 'audio/x-raw-int,channels=2,width=16,height=16,rate=44100 ! '
- 'flacenc ! filesink location=%s > /dev/null 2>&1' % self._path)
- os.close(self._fd)
- self.runner = task.SyncRunner(verbose=False)
-
- def tearDown(self):
- os.unlink(self._path)
-
- # def testNoChange(self):
- # taglist = gst.TagList()
- # taglist[gst.TAG_DESCRIPTION] = 'audiotest wave'
- # taglist[gst.TAG_AUDIO_CODEC] = 'FLAC'
-
- # t = encode.SafeRetagTask(self._path, taglist)
- # self.runner.run(t)
-
- # def testChange(self):
- # taglist = gst.TagList()
- # taglist[gst.TAG_DESCRIPTION] = 'audiotest retagged'
- # taglist[gst.TAG_AUDIO_CODEC] = 'FLAC'
- # taglist[gst.TAG_ARTIST] = 'Artist'
-
- # t = encode.SafeRetagTask(self._path, taglist)
- # self.runner.run(t)
diff --git a/morituri/test/test_common_gstreamer.py b/morituri/test/test_common_gstreamer.py
deleted file mode 100644
index 94de5c41..00000000
--- a/morituri/test/test_common_gstreamer.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- Mode: Python -*-
-# vi:si:et:sw=4:sts=4:ts=4
-
-from morituri.common import gstreamer
-
-from morituri.test import common
-
-
-class VersionTestCase(common.TestCase):
-
- def testGStreamer(self):
- version = gstreamer.gstreamerVersion()
- self.failUnless(version.startswith('0.'))
-
- def testGSTPython(self):
- version = gstreamer.gstPythonVersion()
- self.failUnless(version.startswith('0.'))
-
- def testFlacEnc(self):
- version = gstreamer.elementFactoryVersion('flacenc')
- self.failUnless(version.startswith('0.'))
diff --git a/morituri/test/test_image_image.py b/morituri/test/test_image_image.py
deleted file mode 100644
index c0221c88..00000000
--- a/morituri/test/test_image_image.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- Mode: Python; test-case-name: morituri.test.test_image_image -*-
-# vi:si:et:sw=4:sts=4:ts=4
-
-import os
-import tempfile
-
-import gobject
-gobject.threads_init()
-
-import gst
-
-from morituri.image import image
-from morituri.common import common
-
-from morituri.extern.task import task, gstreamer
-
-from morituri.test import common as tcommon
-
-
-def h(i):
- return "0x%08x" % i
-
-
-class TrackSingleTestCase(tcommon.TestCase):
-
- def setUp(self):
- self.image = image.Image(os.path.join(os.path.dirname(__file__),
- u'track-single.cue'))
- self.runner = task.SyncRunner(verbose=False)
- self.image.setup(self.runner)
-
- def testAccurateRipChecksum(self):
- checksumtask = image.AccurateRipChecksumTask(self.image)
- self.runner.run(checksumtask, verbose=False)
-
- self.assertEquals(len(checksumtask.checksums), 4)
-# self.assertEquals(h(checksumtask.checksums[0]), '0x00000000')
-# self.assertEquals(h(checksumtask.checksums[1]), '0x793fa868')
-# self.assertEquals(h(checksumtask.checksums[2]), '0x8dd37c26')
-# self.assertEquals(h(checksumtask.checksums[3]), '0x00000000')
-
- def testLength(self):
- self.assertEquals(self.image.table.getTrackLength(1), 2)
- self.assertEquals(self.image.table.getTrackLength(2), 2)
- self.assertEquals(self.image.table.getTrackLength(3), 2)
- self.assertEquals(self.image.table.getTrackLength(4), 4)
-
- def testCDDB(self):
- self.assertEquals(self.image.table.getCDDBDiscId(), "08000004")
-
- def testAccurateRip(self):
- self.assertEquals(self.image.table.getAccurateRipIds(), (
- "00000016", "0000005b"))
-
-
-class TrackSeparateTestCase(tcommon.TestCase):
-
- def setUp(self):
- self.image = image.Image(os.path.join(os.path.dirname(__file__),
- u'track-separate.cue'))
- self.runner = task.SyncRunner(verbose=False)
- self.image.setup(self.runner)
-
- def testAccurateRipChecksum(self):
- checksumtask = image.AccurateRipChecksumTask(self.image)
- self.runner.run(checksumtask, verbose=False)
-
- self.assertEquals(len(checksumtask.checksums), 4)
- self.assertEquals(h(checksumtask.checksums[0]), '0xd60e55e1')
- self.assertEquals(h(checksumtask.checksums[1]), '0xd63dc2d2')
- self.assertEquals(h(checksumtask.checksums[2]), '0xd63dc2d2')
- self.assertEquals(h(checksumtask.checksums[3]), '0x7271db39')
-
- def testLength(self):
- self.assertEquals(self.image.table.getTrackLength(1), 10)
- self.assertEquals(self.image.table.getTrackLength(2), 10)
- self.assertEquals(self.image.table.getTrackLength(3), 10)
- self.assertEquals(self.image.table.getTrackLength(4), 10)
-
- def testCDDB(self):
- self.assertEquals(self.image.table.getCDDBDiscId(), "08000004")
-
- def testAccurateRip(self):
- self.assertEquals(self.image.table.getAccurateRipIds(), (
- "00000064", "00000191"))