[SCM] morituri/master: * morituri/common/encode.py: * morituri/program/cdparanoia.py: Add encoding profiles, kept simple for now as a class and subclasses. Use them to encode. Calculate peak level while encoding, compared to EAC and replaygain's value. * morituri/rip/cd.py: Use the encoding profiles, ripping with the right extension. Add a --profile parameter for it.
js at users.alioth.debian.org
js at users.alioth.debian.org
Sun Oct 19 20:09:05 UTC 2014
The following commit has been merged in the master branch:
commit f33c50ceadcc2a480e1d03d425c22db8a7cc530a
Author: Thomas Vander Stichele <thomas (at) apestaart (dot) org>
Date: Sun May 31 23:04:58 2009 +0000
* morituri/common/encode.py:
* morituri/program/cdparanoia.py:
Add encoding profiles, kept simple for now as a class and
subclasses. Use them to encode. Calculate peak level while
encoding, compared to EAC and replaygain's value.
* morituri/rip/cd.py:
Use the encoding profiles, ripping with the right extension.
Add a --profile parameter for it.
diff --git a/ChangeLog b/ChangeLog
index 4f924fb..e9010ee 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2009-06-01 Thomas Vander Stichele <thomas at apestaart dot org>
+
+ * morituri/common/encode.py:
+ * morituri/program/cdparanoia.py:
+ Add encoding profiles, kept simple for now as a class and
+ subclasses. Use them to encode. Calculate peak level while
+ encoding, compared to EAC and replaygain's value.
+ * morituri/rip/cd.py:
+ Use the encoding profiles, ripping with the right extension.
+ Add a --profile parameter for it.
+
2009-05-31 Thomas Vander Stichele <thomas at apestaart dot org>
* morituri/rip/cd.py:
diff --git a/morituri/common/encode.py b/morituri/common/encode.py
index d8e0723..d2b6164 100644
--- a/morituri/common/encode.py
+++ b/morituri/common/encode.py
@@ -21,6 +21,7 @@
# along with morituri. If not, see <http://www.gnu.org/licenses/>.
import os
+import math
import struct
import zlib
@@ -31,33 +32,79 @@ from morituri.common import common, task
from morituri.common import log
log.init()
+class Profile(object):
+ name = None
+ extension = None
+ pipeline = None
+
+class FlacProfile(Profile):
+ name = 'flac'
+ extension = 'flac'
+ pipeline = 'flacenc name=muxer quality=8'
+
+class AlacProfile(Profile):
+ name = 'alac'
+ extension = 'alac'
+ pipeline = 'ffenc_alac name=muxer'
+
+class WavProfile(Profile):
+ name = 'wav'
+ extension = 'wav'
+ pipeline = 'wavenc name=muxer'
+
+class WavpackProfile(Profile):
+ name = 'wavpack'
+ extension = 'wv'
+ pipeline = 'wavpackenc bitrate=0 name=muxer'
+
+
+PROFILES = {
+ 'wav': WavProfile,
+ 'flac': FlacProfile,
+ 'alac': AlacProfile,
+ 'wavpack': WavpackProfile,
+}
+
class EncodeTask(task.Task):
"""
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 power, from 0.0 to 1.0. To get the peak volume,
+ square root this value.
+ @type peak: float
"""
description = 'Encoding'
+ peak = None
- def __init__(self, inpath, outpath, taglist=None):
+ def __init__(self, inpath, outpath, profile, taglist=None):
"""
"""
self._inpath = inpath
self._outpath = outpath
self._taglist = taglist
+ self._level = None
+ self._peakdB = None
+ self._profile = PROFILES[profile]
+
def start(self, runner):
task.Task.start(self, runner)
self._pipeline = gst.parse_launch('''
filesrc location="%s" !
- decodebin name=decoder ! audio/x-raw-int !
- flacenc name=muxer !
- filesink location="%s" name=sink''' % (self._inpath, self._outpath))
+ decodebin name=decoder !
+ audio/x-raw-int,width=16,depth=16,channels=2 !
+ level name=level !
+ %s !
+ filesink location="%s" name=sink''' % (self._inpath,
+ self._profile.pipeline, self._outpath))
+ muxer = self._pipeline.get_by_name('muxer')
# set tags
if self._taglist:
- muxer = self._pipeline.get_by_name('muxer')
muxer.merge_tags(self._taglist, gst.TAG_MERGE_APPEND)
self.debug('pausing pipeline')
@@ -79,12 +126,16 @@ class EncodeTask(task.Task):
# add a probe so we can track progress
sinkpad = muxer.get_pad('sink')
srcpad = sinkpad.get_peer()
- srcpad.add_buffer_probe(self._probe_handler, False)
+ srcpad.add_buffer_probe(self._probe_handler)
# add eos handling
bus = self._pipeline.get_bus()
bus.add_signal_watch()
- bus.connect('message::eos', self._eos_cb)
+ bus.connect('message::eos', self._message_eos_cb)
+
+ # set up level callbacks
+ bus.connect('message::element', self._message_element_cb)
+ self._level = self._pipeline.get_by_name('level')
self.debug('scheduling setting to play')
# since set_state returns non-False, adding it as timeout_add
@@ -100,19 +151,40 @@ class EncodeTask(task.Task):
#self._pipeline.set_state(gst.STATE_PLAYING)
self.debug('scheduled setting to play')
- def _probe_handler(self, pad, buffer, ret):
+ def _probe_handler(self, pad, buffer):
# marshal to main thread
self.runner.schedule(0, self.setProgress,
float(buffer.offset) / self._length)
+
+ # don't drop the buffer
return True
- def _eos_cb(self, bus, message):
+ def _message_eos_cb(self, bus, message):
self.debug('eos, scheduling stop')
self.runner.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:
+ self._peakdB = p
+
def stop(self):
self.debug('stopping')
self.debug('setting state to NULL')
self._pipeline.set_state(gst.STATE_NULL)
self.debug('set state to NULL')
task.Task.stop(self)
+
+
+ self.peak = math.pow(10, self._peakdB / 10.0)
diff --git a/morituri/program/cdparanoia.py b/morituri/program/cdparanoia.py
index 6221c8e..479a52e 100644
--- a/morituri/program/cdparanoia.py
+++ b/morituri/program/cdparanoia.py
@@ -27,7 +27,7 @@ import shutil
import subprocess
import tempfile
-from morituri.common import task, log, common, checksum
+from morituri.common import task, log, common, checksum, encode
from morituri.extern import asyncsub
class FileSizeError(Exception):
@@ -228,22 +228,25 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
@ivar checksum: the checksum of the track; set if they match.
@ivar testchecksum: the test checksum of the track.
@ivar copychecksum: the copy checksum of the track.
+ @ivar peak: the peak level of the track
"""
- def __init__(self, path, table, start, stop, offset=0, device=None):
+ def __init__(self, path, table, start, stop, offset=0, device=None, profile=None):
"""
- @param path: where to store the ripped track
- @type path: str
- @param table: table of contents of CD
- @type table: L{table.Table}
- @param start: first frame to rip
- @type start: int
- @param stop: last frame to rip (inclusive)
- @type stop: int
- @param offset: read offset, in samples
- @type offset: int
- @param device: the device to rip from
- @type device: str
+ @param path: where to store the ripped track
+ @type path: str
+ @param table: table of contents of CD
+ @type table: L{table.Table}
+ @param start: first frame to rip
+ @type start: int
+ @param stop: last frame to rip (inclusive)
+ @type stop: int
+ @param offset: read offset, in samples
+ @type offset: int
+ @param device: the device to rip from
+ @type device: str
+ @param profile: the encoding profile
+ @type profile: str
"""
task.MultiSeparateTask.__init__(self)
@@ -263,10 +266,20 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
self.tasks.append(t)
self.tasks.append(checksum.CRC32Task(tmppath))
+ # FIXME: clean this up
+ fd, tmpoutpath = tempfile.mkstemp(suffix='.morituri.flac')
+ os.close(fd)
+ self._tmppath = tmpoutpath
+ self.tasks.append(encode.EncodeTask(tmppath, tmpoutpath, profile))
+ # make sure our encoding is accurate
+ self.tasks.append(checksum.CRC32Task(tmpoutpath))
+
self.checksum = None
def stop(self):
if not self.exception:
+ self.peak = self.tasks[4].peak
+
self.testchecksum = c1 = self.tasks[1].checksum
self.copychecksum = c2 = self.tasks[3].checksum
if c1 == c2:
@@ -275,6 +288,8 @@ class ReadVerifyTrackTask(task.MultiSeparateTask):
else:
self.error('read and verify failed')
+ if self.tasks[5].checksum != self.checksum:
+ self.error('Encoding failed, checksum does not match')
try:
shutil.move(self._tmppath, self.path)
self.checksum = checksum
diff --git a/morituri/rip/cd.py b/morituri/rip/cd.py
index f6ab023..80d78ce 100644
--- a/morituri/rip/cd.py
+++ b/morituri/rip/cd.py
@@ -22,11 +22,13 @@
import os
import sys
+import math
import gobject
gobject.threads_init()
-from morituri.common import logcommand, task, checksum, common, accurip, drive
+from morituri.common import logcommand, task, checksum, common, accurip
+from morituri.common import drive, encode
from morituri.image import image, cue, table
from morituri.program import cdrdao, cdparanoia
@@ -199,6 +201,12 @@ class Rip(logcommand.LogCommand):
action="store", dest="disc_template",
help="template for disc file naming (default %s)" % default,
default=default)
+ default = 'flac'
+ self.parser.add_option('', '--profile',
+ action="store", dest="profile",
+ help="profile for encoding (default '%s', choices '%s')" % (
+ default, "', '".join(encode.PROFILES.keys())),
+ default=default)
def do(self, args):
@@ -248,6 +256,8 @@ class Rip(logcommand.LogCommand):
itable.getAccurateRipURL(), ittoc.getAccurateRipURL())
outdir = self.options.output_directory or os.getcwd()
+ profile = encode.PROFILES[self.options.profile]
+ extension = profile.extension
# check for hidden track one audio
htoapath = None
@@ -264,7 +274,7 @@ class Rip(logcommand.LogCommand):
print 'Found Hidden Track One Audio from frame %d to %d' % (start, stop)
# rip it
- htoapath = getPath(outdir, self.options.track_template, metadata, 0) + '.wav'
+ htoapath = getPath(outdir, self.options.track_template, metadata, 0) + '.' + extension
dirname = os.path.dirname(htoapath)
if not os.path.exists(dirname):
os.makedirs(dirname)
@@ -275,12 +285,17 @@ class Rip(logcommand.LogCommand):
t = cdparanoia.ReadVerifyTrackTask(htoapath, ittoc,
start, stop - 1,
offset=int(self.options.offset),
- device=self.parentCommand.options.device)
+ device=self.parentCommand.options.device,
+ profile=self.options.profile)
function(runner, t)
+
if t.checksum is not None:
print 'Checksums match for track %d' % 0
else:
print 'ERROR: checksums did not match for track %d' % 0
+ print 'Peak level: %.2f %%' % (math.sqrt(t.peak) * 100.0, )
+ if t.peak == 0.0:
+ print 'HTOA is completely silent'
# overlay this rip onto the Table
itable.setFile(1, 0, htoapath, htoalength, 0)
@@ -292,7 +307,7 @@ class Rip(logcommand.LogCommand):
track.indexes[1].relative = 0
continue
- path = getPath(outdir, self.options.track_template, metadata, i + 1) + '.wav'
+ path = getPath(outdir, self.options.track_template, metadata, i + 1) + '.' + extension
dirname = os.path.dirname(path)
if not os.path.exists(dirname):
os.makedirs(dirname)
@@ -304,13 +319,15 @@ class Rip(logcommand.LogCommand):
ittoc.getTrackStart(i + 1),
ittoc.getTrackEnd(i + 1),
offset=int(self.options.offset),
- device=self.parentCommand.options.device)
+ device=self.parentCommand.options.device,
+ profile=self.options.profile)
t.description = 'Reading Track %d' % (i + 1)
function(runner, t)
if t.checksum:
print 'Checksums match for track %d' % (i + 1)
else:
print 'ERROR: checksums did not match for track %d' % (i + 1)
+ print 'Peak level: %.2f %%' % (math.sqrt(t.peak) * 100.0, )
# overlay this rip onto the Table
itable.setFile(i + 1, 1, path, ittoc.getTrackLength(i + 1), i + 1)
@@ -340,7 +357,7 @@ class Rip(logcommand.LogCommand):
handle.write('%s\n' % os.path.basename(htoapath))
for i, track in enumerate(itable.tracks):
- path = getPath(outdir, self.options.track_template, metadata, i) + '.wav'
+ path = getPath(outdir, self.options.track_template, metadata, i) + '.' + extension
handle.write('#EXTINF:%d,%s\n' % (
itable.getTrackLength(i + 1) / common.FRAMES_PER_SECOND,
os.path.basename(path)))
--
morituri packaging
More information about the pkg-multimedia-commits
mailing list