[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