diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 59e679e..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,6 +0,0 @@
-[submodule "morituri/extern/python-command"]
- path = morituri/extern/python-command
- url = git://github.com/thomasvs/python-command.git
-[submodule "morituri/extern/flog"]
- path = morituri/extern/flog
- url = git://github.com/Flumotion/flog
diff --git a/README.md b/README.md
index dd519a5..4ca9bce 100644
--- a/README.md
+++ b/README.md
@@ -81,9 +81,6 @@ Change to a directory where you want to put whipper source code (for example, `$
```bash
git clone -b master --single-branch https://github.com/JoeLametta/whipper.git
cd whipper
-# fetch bundled python dependencies
-git submodule init
-git submodule update
```
### Building the bundled dependencies
@@ -114,7 +111,7 @@ is correct, while
`whipper cd rip -d (device)`
-is not, because the `-d` argument applies to the whipper command.
+is not, because the `-d` argument applies to the `cd` command.
~~Check the man page (`whipper(1)`) for more information.~~ (currently not available as whipper's documentation is planned to be reworked ([Issue #73](https://github.com/JoeLametta/whipper/issues/73)).
@@ -261,15 +258,17 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Please use the [issue tracker](https://github.com/JoeLametta/whipper/issues) to report any bugs or to file feature requests.
-When filing bug reports, please run the failing command with the environment variable `RIP_DEBUG` set. For example:
+When filing bug reports, please run the failing command with the environment variable `WHIPPER_DEBUG` set. For example:
```bash
-RIP_DEBUG=5 whipper offset find > whipper.log 2>&1
+WHIPPER_DEBUG=DEBUG WHIPPER_LOGFILE=whipper.log whipper offset find
gzip whipper.log
```
And attach the gzipped log file to your bug report.
+Without `WHIPPER_LOGFILE` set, logging messages will go to stderr. `WHIPPER_DEBUG` accepts a string of the [default python logging levels](https://docs.python.org/2/library/logging.html#logging-levels).
+
### Developing
Pull requests are welcome.
diff --git a/morituri/__init__.py b/morituri/__init__.py
index e69de29..bdf7a36 100644
--- a/morituri/__init__.py
+++ b/morituri/__init__.py
@@ -0,0 +1,12 @@
+import logging
+import os
+import sys
+
+level = logging.WARNING
+if 'WHIPPER_DEBUG' in os.environ:
+ level = os.environ['WHIPPER_DEBUG'].upper()
+if 'WHIPPER_LOGFILE' in os.environ:
+ logging.basicConfig(filename=os.environ['WHIPPER_LOGFILE'],
+ filemode='w', level=level)
+else:
+ logging.basicConfig(stream=sys.stderr, level=level)
diff --git a/morituri/rip/__init__.py b/morituri/command/__init__.py
similarity index 100%
rename from morituri/rip/__init__.py
rename to morituri/command/__init__.py
diff --git a/morituri/rip/accurip.py b/morituri/command/accurip.py
similarity index 71%
rename from morituri/rip/accurip.py
rename to morituri/command/accurip.py
index bda12d0..09ae087 100644
--- a/morituri/rip/accurip.py
+++ b/morituri/command/accurip.py
@@ -20,39 +20,44 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see .
-from morituri.common import logcommand, accurip
+import sys
+from morituri.command.basecommand import BaseCommand
+from morituri.common import accurip
-class Show(logcommand.LogCommand):
+import logging
+logger = logging.getLogger(__name__)
+class Show(BaseCommand):
summary = "show accuraterip data"
+ description = """
+retrieves and display accuraterip data from the given URL
+"""
- def do(self, args):
-
- try:
- url = args[0]
- except IndexError:
- self.stdout.write('Please specify an accuraterip URL.\n')
- return 3
+ def add_arguments(self):
+ self.parser.add_argument('url', action='store',
+ help="accuraterip URL to load data from")
+ def do(self):
+ url = self.options.url
cache = accurip.AccuCache()
responses = cache.retrieve(url)
count = responses[0].trackCount
- self.stdout.write("Found %d responses for %d tracks\n\n" % (
+ sys.stdout.write("Found %d responses for %d tracks\n\n" % (
len(responses), count))
for (i, r) in enumerate(responses):
if r.trackCount != count:
- self.stdout.write(
+ sys.stdout.write(
"Warning: response %d has %d tracks instead of %d\n" % (
i, r.trackCount, count))
# checksum and confidence by track
for track in range(count):
- self.stdout.write("Track %d:\n" % (track + 1))
+ sys.stdout.write("Track %d:\n" % (track + 1))
checksums = {}
for (i, r) in enumerate(responses):
@@ -81,12 +86,17 @@ class Show(logcommand.LogCommand):
sortedChecksums.reverse()
for highest, checksum in sortedChecksums:
- self.stdout.write(" %d result(s) for checksum %s: %s\n" % (
+ sys.stdout.write(" %d result(s) for checksum %s: %s\n" % (
len(checksums[checksum]), checksum,
str(checksums[checksum])))
-class AccuRip(logcommand.LogCommand):
- description = "Handle AccurateRip information."
-
- subCommandClasses = [Show, ]
+class AccuRip(BaseCommand):
+ summary = "handle AccurateRip information"
+ description = """
+Handle AccurateRip information. Retrieves AccurateRip disc entries and
+displays diagnostic information.
+"""
+ subcommands = {
+ 'show': Show
+ }
diff --git a/morituri/command/basecommand.py b/morituri/command/basecommand.py
new file mode 100644
index 0000000..563dde0
--- /dev/null
+++ b/morituri/command/basecommand.py
@@ -0,0 +1,129 @@
+# -*- Mode: Python -*-
+# vi:si:et:sw=4:sts=4:ts=4
+
+import argparse
+import os
+import sys
+
+from morituri.common import drive
+
+import logging
+logger = logging.getLogger(__name__)
+
+# Q: What about argparse.add_subparsers(), you ask?
+# A: Unfortunately add_subparsers() does not support specifying the
+# formatter_class of subparsers, nor does it support epilogs, so
+# it does not quite fit our use case.
+
+# Q: Why not subclass ArgumentParser and extend/replace the relevant
+# methods?
+# A: If this can be done in a simpler fashion than this current
+# implementation, by all means submit a patch.
+
+# Q: Why not argparse.parse_known_args()?
+# A: The prefix matching prevents passing '-h' (and possibly other
+# options) to the child command.
+
+class BaseCommand():
+ """
+ A base command class for whipper commands.
+
+ Creates an argparse.ArgumentParser.
+ Override add_arguments() and handle_arguments() to register
+ and process arguments before & after argparse.parse_args().
+
+ Provides self.epilog() formatting command for argparse.
+
+ device_option = True adds -d / --device option to current command
+ no_add_help = True removes -h / --help option from current command
+
+ Overriding formatter_class sets the argparse formatter class.
+
+ If the 'subcommands' dictionary is set, __init__ searches the
+ arguments for subcommands.keys() and instantiates the class
+ implementing the subcommand as self.cmd, passing all non-understood
+ arguments, the current options namespace, and the full command path
+ name.
+ """
+ device_option = False
+ no_add_help = False # for rip.main.Whipper
+ formatter_class = argparse.RawDescriptionHelpFormatter
+
+ def __init__(self, argv, prog_name, opts):
+ self.opts = opts # for Rip.add_arguments()
+ self.prog_name = prog_name
+
+ self.init_parser()
+ self.add_arguments()
+
+ if hasattr(self, 'subcommands'):
+ self.parser.add_argument('remainder',
+ nargs=argparse.REMAINDER,
+ help=argparse.SUPPRESS)
+
+ if self.device_option:
+ # pick the first drive as default
+ drives = drive.getAllDevicePaths()
+ if not drives:
+ msg = 'No CD-DA drives found!'
+ logger.critical(msg)
+ # morituri exited with return code 3 here
+ raise IOError(msg)
+ self.parser.add_argument('-d', '--device',
+ action="store",
+ dest="device",
+ default=drives[0],
+ help="CD-DA device")
+
+ self.options = self.parser.parse_args(argv, namespace=opts)
+
+ if self.device_option:
+ # this can be a symlink to another device
+ self.options.device = os.path.realpath(self.options.device)
+ if not os.path.exists(self.options.device):
+ msg = 'CD-DA device %s not found!' % self.options.device
+ logger.critical(msg)
+ raise IOError(msg)
+
+ self.handle_arguments()
+
+ if hasattr(self, 'subcommands'):
+ if not self.options.remainder:
+ self.parser.print_help()
+ sys.exit(0)
+ if not self.options.remainder[0] in self.subcommands:
+ sys.stderr.write("incorrect subcommand: %s" %
+ self.options.remainder[0])
+ sys.exit(1)
+ self.cmd = self.subcommands[self.options.remainder[0]](
+ self.options.remainder[1:],
+ prog_name + " " + self.options.remainder[0],
+ self.options
+ )
+
+ def init_parser(self):
+ kw = {
+ 'prog': self.prog_name,
+ 'description': self.description,
+ 'formatter_class': self.formatter_class,
+ }
+ if hasattr(self, 'subcommands'):
+ kw['epilog'] = self.epilog()
+ if self.no_add_help:
+ kw['add_help'] = False
+ self.parser = argparse.ArgumentParser(**kw)
+
+ def add_arguments(self):
+ pass
+
+ def handle_arguments(self):
+ pass
+
+ def do(self):
+ return self.cmd.do()
+
+ def epilog(self):
+ s = "commands:\n"
+ for com in sorted(self.subcommands.keys()):
+ s += " %s %s\n" % (com.ljust(8), self.subcommands[com].summary)
+ return s
diff --git a/morituri/rip/cd.py b/morituri/command/cd.py
similarity index 69%
rename from morituri/rip/cd.py
rename to morituri/command/cd.py
index 3939a60..f280468 100644
--- a/morituri/rip/cd.py
+++ b/morituri/command/cd.py
@@ -20,29 +20,57 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see .
+import argparse
import os
-import math
import glob
import urllib2
import socket
+import sys
import gobject
gobject.threads_init()
-from morituri.common import logcommand, common, accurip, gstreamer
-from morituri.common import drive, program, task
-from morituri.result import result
+from morituri.command.basecommand import BaseCommand
+from morituri.common import (
+ accurip, common, config, drive, gstreamer, program, task
+)
from morituri.program import cdrdao, cdparanoia
-from morituri.rip import common as rcommon
+from morituri.result import result
-from morituri.extern.command import command
+import logging
+logger = logging.getLogger(__name__)
SILENT = 1e-10
MAX_TRIES = 5
+DEFAULT_TRACK_TEMPLATE = u'%r/%A - %d/%t. %a - %n'
+DEFAULT_DISC_TEMPLATE = u'%r/%A - %d/%A - %d'
-class _CD(logcommand.LogCommand):
+TEMPLATE_DESCRIPTION = '''
+Tracks are named according to the track template, filling in the variables
+and adding the file extension. Variables exclusive to the track template are:
+ - %t: track number
+ - %a: track artist
+ - %n: track title
+ - %s: track sort name
+
+Disc files (.cue, .log, .m3u) are named according to the disc template,
+filling in the variables and adding the file extension. Variables for both
+disc and track template are:
+ - %A: album artist
+ - %S: album sort name
+ - %d: disc title
+ - %y: release year
+ - %r: release type, lowercase
+ - %R: Release type, normal case
+ - %x: audio extension, lowercase
+ - %X: audio extension, uppercase
+
+'''
+
+
+class _CD(BaseCommand):
"""
@type program: L{program.Program}
@@ -51,31 +79,34 @@ class _CD(logcommand.LogCommand):
eject = True
- def addOptions(self):
+ @staticmethod
+ def add_arguments(parser):
# FIXME: have a cache of these pickles somewhere
- self.parser.add_option('-T', '--toc-pickle',
+ parser.add_argument('-T', '--toc-pickle',
action="store", dest="toc_pickle",
help="pickle to use for reading and writing the TOC")
- self.parser.add_option('-R', '--release-id',
+ parser.add_argument('-R', '--release-id',
action="store", dest="release_id",
help="MusicBrainz release id to match to (if there are multiple)")
- self.parser.add_option('-p', '--prompt',
+ parser.add_argument('-p', '--prompt',
action="store_true", dest="prompt",
help="Prompt if there are multiple matching releases")
- self.parser.add_option('-c', '--country',
+ parser.add_argument('-c', '--country',
action="store", dest="country",
help="Filter releases by country")
- def do(self, args):
- self.program = program.Program(self.getRootCommand().config,
- record=self.getRootCommand().record,
- stdout=self.stdout)
+ def do(self):
+ self.config = config.Config()
+ self.program = program.Program(self.config,
+ record=self.options.record,
+ stdout=sys.stdout)
self.runner = task.SyncRunner()
# if the device is mounted (data session), unmount it
- self.device = self.parentCommand.options.device
- self.stdout.write('Checking device %s\n' % self.device)
+ #self.device = self.parentCommand.options.device
+ self.device = self.options.device
+ sys.stdout.write('Checking device %s\n' % self.device)
self.program.loadDevice(self.device)
self.program.unmountDevice(self.device)
@@ -87,11 +118,11 @@ class _CD(logcommand.LogCommand):
# already show us some info based on this
self.program.getRipResult(self.ittoc.getCDDBDiscId())
- self.stdout.write("CDDB disc id: %s\n" % self.ittoc.getCDDBDiscId())
+ sys.stdout.write("CDDB disc id: %s\n" % self.ittoc.getCDDBDiscId())
self.mbdiscid = self.ittoc.getMusicBrainzDiscId()
- self.stdout.write("MusicBrainz disc id %s\n" % self.mbdiscid)
+ sys.stdout.write("MusicBrainz disc id %s\n" % self.mbdiscid)
- self.stdout.write("MusicBrainz lookup URL %s\n" %
+ sys.stdout.write("MusicBrainz lookup URL %s\n" %
self.ittoc.getMusicBrainzSubmitURL())
self.program.metadata = self.program.getMusicBrainz(self.ittoc,
@@ -105,7 +136,7 @@ class _CD(logcommand.LogCommand):
cddbid = self.ittoc.getCDDBValues()
cddbmd = self.program.getCDDB(cddbid)
if cddbmd:
- self.stdout.write('FreeDB identifies disc as %s\n' % cddbmd)
+ sys.stdout.write('FreeDB identifies disc as %s\n' % cddbmd)
# also used by rip cd info
if not getattr(self.options, 'unknown', False):
@@ -113,12 +144,13 @@ class _CD(logcommand.LogCommand):
self.program.ejectDevice(self.device)
return -1
+ # FIXME ?????
# Hackish fix for broken commit
offset = 0
- info = drive.getDeviceInfo(self.parentCommand.options.device)
+ info = drive.getDeviceInfo(self.device)
if info:
try:
- offset = self.getRootCommand().config.getReadOffset(*info)
+ offset = self.config.getReadOffset(*info)
except KeyError:
pass
@@ -148,13 +180,13 @@ class _CD(logcommand.LogCommand):
self.program.result.cdrdaoVersion = cdrdao.getCDRDAOVersion()
self.program.result.cdparanoiaVersion = \
cdparanoia.getCdParanoiaVersion()
- info = drive.getDeviceInfo(self.parentCommand.options.device)
+ info = drive.getDeviceInfo(self.device)
if info:
try:
self.program.result.cdparanoiaDefeatsCache = \
- self.getRootCommand().config.getDefeatsCache(*info)
+ self.config.getDefeatsCache(*info)
except KeyError, e:
- self.debug('Got key error: %r' % (e, ))
+ logger.debug('Got key error: %r' % (e, ))
self.program.result.artist = self.program.metadata \
and self.program.metadata.artist \
or 'Unknown Artist'
@@ -182,13 +214,17 @@ class _CD(logcommand.LogCommand):
class Info(_CD):
summary = "retrieve information about the currently inserted CD"
-
+ description = ("Display musicbrainz, CDDB/FreeDB, and AccurateRip"
+ "information for the currently inserted CD.")
eject = False
+ # Requires opts.device
+
+ def add_arguments(self):
+ _CD.add_arguments(self.parser)
class Rip(_CD):
summary = "rip CD"
-
# see morituri.common.program.Program.getPath for expansion
description = """
Rips a CD.
@@ -200,76 +236,66 @@ relative to the directory of the disc files.
All files will be created relative to the given output directory.
Log files will log the path to tracks relative to this directory.
-""" % rcommon.TEMPLATE_DESCRIPTION
+""" % TEMPLATE_DESCRIPTION
+ formatter_class = argparse.ArgumentDefaultsHelpFormatter
- def addOptions(self):
- _CD.addOptions(self)
+ # Requires opts.record
+ # Requires opts.device
+ def add_arguments(self):
loggers = result.getLoggers().keys()
+ default_offset = None
+ info = drive.getDeviceInfo(self.opts.device)
+ if info:
+ try:
+ default_offset = config.Config().getReadOffset(*info)
+ sys.stdout.write("Using configured read offset %d\n" %
+ default_offset)
+ except KeyError:
+ pass
- self.parser.add_option('-L', '--logger',
- action="store", dest="logger",
- default='morituri',
- help="logger to use "
- "(default '%default', choose from '" +
- "', '".join(loggers) + "')")
+ _CD.add_arguments(self.parser)
+
+ self.parser.add_argument('-L', '--logger',
+ action="store", dest="logger", default='morituri',
+ help="logger to use (choose from '" + "', '".join(loggers) + "')")
# FIXME: get from config
- self.parser.add_option('-o', '--offset',
- action="store", dest="offset",
- help="sample read offset (defaults to configured value, or 0)")
- self.parser.add_option('-x', '--force-overread',
- action="store_true", dest="overread",
+ self.parser.add_argument('-o', '--offset',
+ action="store", dest="offset", default=default_offset,
+ help="sample read offset")
+ self.parser.add_argument('-x', '--force-overread',
+ action="store_true", dest="overread", default=False,
help="Force overreading into the lead-out portion of the disc. "
"Works only if the patched cdparanoia package is installed "
- "and the drive supports this feature. "
- "The default value is: %default",
- default=False)
- self.parser.add_option('-O', '--output-directory',
+ "and the drive supports this feature. ")
+ self.parser.add_argument('-O', '--output-directory',
action="store", dest="output_directory",
- help="output directory; will be included in file paths in result "
- "files "
- "(defaults to absolute path to current directory; set to "
- "empty if you want paths to be relative instead; "
- "configured value: %default) ")
- self.parser.add_option('-W', '--working-directory',
+ default=os.path.relpath(os.getcwd()),
+ help="output directory; will be included in file paths in log")
+ self.parser.add_argument('-W', '--working-directory',
action="store", dest="working_directory",
help="working directory; morituri will change to this directory "
- "and files will be created relative to it when not absolute "
- "(configured value: %default) ")
-
- rcommon.addTemplate(self)
-
- default = 'flac'
-
- # here to avoid import gst eating our options
- from morituri.common import encode
-
- self.parser.add_option('', '--profile',
- action="store", dest="profile",
- help="profile for encoding (default '%%default', choices '%s')" % (
- "', '".join(encode.PROFILES.keys())),
- default=default)
- self.parser.add_option('-U', '--unknown',
+ "and files will be created relative to it when not absolute")
+ self.parser.add_argument('--track-template',
+ action="store", dest="track_template",
+ default=DEFAULT_TRACK_TEMPLATE,
+ help="template for track file naming (default default)")
+ self.parser.add_argument('--disc-template',
+ action="store", dest="disc_template",
+ default=DEFAULT_DISC_TEMPLATE,
+ help="template for disc file naming (default default)")
+ self.parser.add_argument('-U', '--unknown',
action="store_true", dest="unknown",
- help="whether to continue ripping if the CD is unknown (%default)",
+ help="whether to continue ripping if the CD is unknown",
default=False)
- def handleOptions(self, options):
- options.track_template = options.track_template.decode('utf-8')
- options.disc_template = options.disc_template.decode('utf-8')
+ def handle_arguments(self):
+ self.options.output_directory = os.path.expanduser(self.options.output_directory)
- if options.offset is None:
- info = drive.getDeviceInfo(self.parentCommand.options.device)
- if info:
- try:
- options.offset = self.getRootCommand(
- ).config.getReadOffset(*info)
- self.stdout.write("Using configured read offset %d\n" %
- options.offset)
- except KeyError:
- pass
+ self.options.track_template = self.options.track_template.decode('utf-8')
+ self.options.disc_template = self.options.disc_template.decode('utf-8')
- if options.offset is None:
+ if self.options.offset is None:
raise ValueError("Drive offset is unconfigured.\n"
"Please install pycdio and run 'rip offset "
"find' to detect your drive's offset or set it "
@@ -277,29 +303,23 @@ Log files will log the path to tracks relative to this directory.
"also be specified at runtime using the "
"'--offset=value' argument")
- if self.options.output_directory is None:
- self.options.output_directory = os.getcwd()
- else:
- self.options.output_directory = os.path.expanduser(self.options.output_directory)
if self.options.working_directory is not None:
self.options.working_directory = os.path.expanduser(self.options.working_directory)
if self.options.logger:
try:
- klazz = result.getLoggers()[self.options.logger]
+ self.logger = result.getLoggers()[self.options.logger]()
except KeyError:
- self.stderr.write("No logger named %s found!\n" % (
- self.options.logger))
- raise command.CommandError("No logger named %s" %
- self.options.logger)
+ msg = "No logger named %s found!" % self.options.logger
+ logger.critical(msg)
+ raise ValueError(msg)
- self.logger = klazz()
def doCommand(self):
# here to avoid import gst eating our options
from morituri.common import encode
- profile = encode.PROFILES[self.options.profile]()
+ profile = encode.PROFILES['flac']()
self.program.result.profileName = profile.name
self.program.result.profilePipeline = profile.pipeline
elementFactory = profile.pipeline.split(' ')[0]
@@ -322,11 +342,11 @@ Log files will log the path to tracks relative to this directory.
profile=profile, disambiguate=disambiguate)
dirname = os.path.dirname(discName)
if os.path.exists(dirname):
- self.stdout.write("Output directory %s already exists\n" %
+ sys.stdout.write("Output directory %s already exists\n" %
dirname.encode('utf-8'))
logs = glob.glob(os.path.join(dirname, '*.log'))
if logs:
- self.stdout.write(
+ sys.stdout.write(
"Output directory %s is a finished rip\n" %
dirname.encode('utf-8'))
if not disambiguate:
@@ -337,7 +357,7 @@ Log files will log the path to tracks relative to this directory.
break
else:
- self.stdout.write("Creating output directory %s\n" %
+ sys.stdout.write("Creating output directory %s\n" %
dirname.encode('utf-8'))
os.makedirs(dirname)
break
@@ -349,14 +369,14 @@ Log files will log the path to tracks relative to this directory.
# FIXME: turn this into a method
def ripIfNotRipped(number):
- self.debug('ripIfNotRipped for track %d' % number)
+ logger.debug('ripIfNotRipped for track %d' % number)
# we can have a previous result
trackResult = self.program.result.getTrackResult(number)
if not trackResult:
trackResult = result.TrackResult()
self.program.result.tracks.append(trackResult)
else:
- self.debug('ripIfNotRipped have trackresult, path %r' %
+ logger.debug('ripIfNotRipped have trackresult, path %r' %
trackResult.filename)
path = self.program.getPath(self.program.outdir,
@@ -364,7 +384,7 @@ Log files will log the path to tracks relative to this directory.
self.mbdiscid, number,
profile=profile, disambiguate=disambiguate) \
+ '.' + profile.extension
- self.debug('ripIfNotRipped: path %r' % path)
+ logger.debug('ripIfNotRipped: path %r' % path)
trackResult.number = number
assert type(path) is unicode, "%r is not unicode" % path
@@ -377,18 +397,18 @@ Log files will log the path to tracks relative to this directory.
if path != trackResult.filename:
# the path is different (different name/template ?)
# but we can copy it
- self.debug('previous result %r, expected %r' % (
+ logger.debug('previous result %r, expected %r' % (
trackResult.filename, path))
- self.stdout.write('Verifying track %d of %d: %s\n' % (
+ sys.stdout.write('Verifying track %d of %d: %s\n' % (
number, len(self.itable.tracks),
os.path.basename(path).encode('utf-8')))
if not self.program.verifyTrack(self.runner, trackResult):
- self.stdout.write('Verification failed, reripping...\n')
+ sys.stdout.write('Verification failed, reripping...\n')
os.unlink(path)
if not os.path.exists(path):
- self.debug('path %r does not exist, ripping...' % path)
+ logger.debug('path %r does not exist, ripping...' % path)
tries = 0
# we reset durations for test and copy here
trackResult.testduration = 0.0
@@ -398,15 +418,15 @@ Log files will log the path to tracks relative to this directory.
tries += 1
if tries > 1:
extra = " (try %d)" % tries
- self.stdout.write('Ripping track %d of %d%s: %s\n' % (
+ sys.stdout.write('Ripping track %d of %d%s: %s\n' % (
number, len(self.itable.tracks), extra,
os.path.basename(path).encode('utf-8')))
try:
- self.debug('ripIfNotRipped: track %d, try %d',
+ logger.debug('ripIfNotRipped: track %d, try %d',
number, tries)
self.program.ripTrack(self.runner, trackResult,
offset=int(self.options.offset),
- device=self.parentCommand.options.device,
+ device=self.device,
profile=profile,
taglist=self.program.getTagList(number),
overread=self.options.overread,
@@ -414,41 +434,41 @@ Log files will log the path to tracks relative to this directory.
number, len(self.itable.tracks), extra))
break
except Exception, e:
- self.debug('Got exception %r on try %d',
+ logger.debug('Got exception %r on try %d',
e, tries)
if tries == MAX_TRIES:
- self.error('Giving up on track %d after %d times' % (
+ logger.critical('Giving up on track %d after %d times' % (
number, tries))
raise RuntimeError(
"track can't be ripped. "
"Rip attempts number is equal to 'MAX_TRIES'")
if trackResult.testcrc == trackResult.copycrc:
- self.stdout.write('Checksums match for track %d\n' %
+ sys.stdout.write('Checksums match for track %d\n' %
number)
else:
- self.stdout.write(
+ sys.stdout.write(
'ERROR: checksums did not match for track %d\n' %
number)
raise
- self.stdout.write('Peak level: {:.2%} \n'.format(trackResult.peak))
+ sys.stdout.write('Peak level: {:.2%} \n'.format(trackResult.peak))
- self.stdout.write('Rip quality: {:.2%}\n'.format(trackResult.quality))
+ sys.stdout.write('Rip quality: {:.2%}\n'.format(trackResult.quality))
# overlay this rip onto the Table
if number == 0:
# HTOA goes on index 0 of track 1
# ignore silence in PREGAP
if trackResult.peak <= SILENT:
- self.debug('HTOA peak %r is below SILENT threshold, disregarding', trackResult.peak)
+ logger.debug('HTOA peak %r is below SILENT threshold, disregarding', trackResult.peak)
self.itable.setFile(1, 0, None,
self.ittoc.getTrackStart(1), number)
- self.debug('Unlinking %r', trackResult.filename)
+ logger.debug('Unlinking %r', trackResult.filename)
os.unlink(trackResult.filename)
trackResult.filename = None
- self.stdout.write('HTOA discarded, contains digital silence\n')
+ sys.stdout.write('HTOA discarded, contains digital silence\n')
else:
self.itable.setFile(1, 0, trackResult.filename,
self.ittoc.getTrackStart(1), number)
@@ -464,7 +484,7 @@ Log files will log the path to tracks relative to this directory.
htoa = self.program.getHTOA()
if htoa:
start, stop = htoa
- self.stdout.write(
+ sys.stdout.write(
'Found Hidden Track One Audio from frame %d to %d\n' % (
start, stop))
@@ -475,7 +495,7 @@ Log files will log the path to tracks relative to this directory.
for i, track in enumerate(self.itable.tracks):
# FIXME: rip data tracks differently
if not track.audio:
- self.stdout.write(
+ sys.stdout.write(
'WARNING: skipping data track %d, not implemented\n' % (
i + 1, ))
# FIXME: make it work for now
@@ -492,11 +512,11 @@ Log files will log the path to tracks relative to this directory.
if not os.path.exists(dirname):
os.makedirs(dirname)
- self.debug('writing cue file for %r', discName)
+ logger.debug('writing cue file for %r', discName)
self.program.writeCue(discName)
# write .m3u file
- self.debug('writing m3u file for %r', discName)
+ logger.debug('writing m3u file for %r', discName)
m3uPath = u'%s.m3u' % discName
handle = open(m3uPath, 'w')
handle.write(u'#EXTM3U\n')
@@ -528,7 +548,7 @@ Log files will log the path to tracks relative to this directory.
# verify using accuraterip
url = self.ittoc.getAccurateRipURL()
- self.stdout.write("AccurateRip URL %s\n" % url)
+ sys.stdout.write("AccurateRip URL %s\n" % url)
accucache = accurip.AccuCache()
try:
@@ -536,7 +556,7 @@ Log files will log the path to tracks relative to this directory.
except urllib2.URLError, e:
if isinstance(e.args[0], socket.gaierror):
if e.args[0].errno == -2:
- self.stdout.write("Warning: network error: %r\n" % (
+ sys.stdout.write("Warning: network error: %r\n" % (
e.args[0], ))
responses = None
else:
@@ -545,21 +565,21 @@ Log files will log the path to tracks relative to this directory.
raise
if not responses:
- self.stdout.write('Album not found in AccurateRip database\n')
+ sys.stdout.write('Album not found in AccurateRip database\n')
if responses:
- self.stdout.write('%d AccurateRip reponses found\n' %
+ sys.stdout.write('%d AccurateRip reponses found\n' %
len(responses))
if responses[0].cddbDiscId != self.itable.getCDDBDiscId():
- self.stdout.write(
+ sys.stdout.write(
"AccurateRip response discid different: %s\n" %
responses[0].cddbDiscId)
self.program.verifyImage(self.runner, responses)
- self.stdout.write("\n".join(
+ sys.stdout.write("\n".join(
self.program.getAccurateRipResults()) + "\n")
self.program.saveRipResult()
@@ -570,26 +590,12 @@ Log files will log the path to tracks relative to this directory.
self.program.ejectDevice(self.device)
-class CD(logcommand.LogCommand):
+class CD(BaseCommand):
+ summary = "handle CDs"
+ description = "Display and rip CD-DA and metadata."
+ device_option = True
- summary = "handle CD's"
-
- subCommandClasses = [Info, Rip, ]
-
- def addOptions(self):
- self.parser.add_option('-d', '--device',
- action="store", dest="device",
- help="CD-DA device")
-
- def handleOptions(self, options):
- if not options.device:
- drives = drive.getAllDevicePaths()
- if not drives:
- self.error('No CD-DA drives found!')
- return 3
-
- # pick the first
- self.options.device = drives[0]
-
- # this can be a symlink to another device
- self.options.device = os.path.realpath(self.options.device)
+ subcommands = {
+ 'info': Info,
+ 'rip': Rip
+ }
diff --git a/morituri/rip/debug.py b/morituri/command/debug.py
similarity index 50%
rename from morituri/rip/debug.py
rename to morituri/command/debug.py
index f6d2894..ede5079 100644
--- a/morituri/rip/debug.py
+++ b/morituri/command/debug.py
@@ -20,15 +20,19 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see .
-from morituri.common import logcommand
+import argparse
+import sys
+
+from morituri.command.basecommand import BaseCommand
+from morituri.common import cache, task
from morituri.result import result
-from morituri.common import task, cache
+import logging
+logger = logging.getLogger(__name__)
-class RCCue(logcommand.LogCommand):
-
- name = "cue"
+class RCCue(BaseCommand):
summary = "write a cue file for the cached result"
+ description = summary
def do(self, args):
self._cache = cache.ResultCache()
@@ -36,24 +40,23 @@ class RCCue(logcommand.LogCommand):
try:
discid = args[0]
except IndexError:
- self.stderr.write(
+ sys.stderr.write(
'Please specify a cddb disc id\n')
return 3
persisted = self._cache.getRipResult(discid, create=False)
if not persisted:
- self.stderr.write(
+ sys.stderr.write(
'Could not find a result for cddb disc id %s\n' % discid)
return 3
- self.stdout.write(persisted.object.table.cue().encode('utf-8'))
+ sys.stdout.write(persisted.object.table.cue().encode('utf-8'))
-class RCList(logcommand.LogCommand):
-
- name = "list"
+class RCList(BaseCommand):
summary = "list cached results"
+ description = summary
def do(self, args):
self._cache = cache.ResultCache()
@@ -71,24 +74,24 @@ class RCList(logcommand.LogCommand):
if title is None:
title = '(None)'
- self.stdout.write('%s: %s - %s\n' % (
+ sys.stdout.write('%s: %s - %s\n' % (
cddbid, artist.encode('utf-8'), title.encode('utf-8')))
-class RCLog(logcommand.LogCommand):
-
- name = "log"
+class RCLog(BaseCommand):
summary = "write a log file for the cached result"
+ description = summary
+ formatter_class = argparse.ArgumentDefaultsHelpFormatter
- def addOptions(self):
+ def add_arguments(self):
loggers = result.getLoggers().keys()
- self.parser.add_option('-L', '--logger',
+ self.parser.add_argument(
+ '-L', '--logger',
action="store", dest="logger",
default='morituri',
- help="logger to use "
- "(default '%default', choose from '" +
- "', '".join(loggers) + "')")
+ help="logger to use (choose from '" + "', '".join(loggers) + "')"
+ )
def do(self, args):
self._cache = cache.ResultCache()
@@ -96,146 +99,155 @@ class RCLog(logcommand.LogCommand):
persisted = self._cache.getRipResult(args[0], create=False)
if not persisted:
- self.stderr.write(
+ sys.stderr.write(
'Could not find a result for cddb disc id %s\n' % args[0])
return 3
try:
klazz = result.getLoggers()[self.options.logger]
except KeyError:
- self.stderr.write("No logger named %s found!\n" % (
+ sys.stderr.write("No logger named %s found!\n" % (
self.options.logger))
return 3
logger = klazz()
- self.stdout.write(logger.log(persisted.object).encode('utf-8'))
+ sys.stdout.write(logger.log(persisted.object).encode('utf-8'))
-class ResultCache(logcommand.LogCommand):
-
+class ResultCache(BaseCommand):
summary = "debug result cache"
- aliases = ['rc', ]
+ description = summary
- subCommandClasses = [RCCue, RCList, RCLog, ]
+ subcommands = {
+ 'cue': RCCue,
+ 'list': RCList,
+ 'log': RCLog,
+ }
-class Checksum(logcommand.LogCommand):
-
+class Checksum(BaseCommand):
summary = "run a checksum task"
+ description = summary
- def do(self, args):
- if not args:
- self.stdout.write('Please specify one or more input files.\n')
- return 3
+ def add_arguments(self):
+ self.parser.add_argument('files', nargs='+', action='store',
+ help="audio files to checksum")
+ def do(self):
runner = task.SyncRunner()
# here to avoid import gst eating our options
from morituri.common import checksum
- for arg in args:
- fromPath = unicode(arg)
-
+ for f in self.options.files:
+ fromPath = unicode(f)
checksumtask = checksum.CRC32Task(fromPath)
-
runner.run(checksumtask)
-
- self.stdout.write('Checksum: %08x\n' % checksumtask.checksum)
+ sys.stdout.write('Checksum: %08x\n' % checksumtask.checksum)
-class Encode(logcommand.LogCommand):
-
+class Encode(BaseCommand):
summary = "run an encode task"
+ description = summary
- def addOptions(self):
+ def add_arguments(self):
# here to avoid import gst eating our options
from morituri.common import encode
default = 'flac'
- self.parser.add_option('', '--profile',
- action="store", dest="profile",
+ # 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',
+ help="output path")
- def do(self, args):
+ def do(self):
from morituri.common import encode
profile = encode.ALL_PROFILES[self.options.profile]()
try:
- fromPath = unicode(args[0])
+ fromPath = unicode(self.options.input)
except IndexError:
- self.stdout.write('Please specify an input file.\n')
+ # unexercised after BaseCommand
+ sys.stdout.write('Please specify an input file.\n')
return 3
try:
- toPath = unicode(args[1])
+ toPath = unicode(self.options.output)
except IndexError:
toPath = fromPath + '.' + profile.extension
runner = task.SyncRunner()
- self.debug('Encoding %s to %s',
+ logger.debug('Encoding %s to %s',
fromPath.encode('utf-8'),
toPath.encode('utf-8'))
encodetask = encode.EncodeTask(fromPath, toPath, profile)
runner.run(encodetask)
- self.stdout.write('Peak level: %r\n' % encodetask.peak)
- self.stdout.write('Encoded to %s\n' % toPath.encode('utf-8'))
+ sys.stdout.write('Peak level: %r\n' % encodetask.peak)
+ sys.stdout.write('Encoded to %s\n' % toPath.encode('utf-8'))
-class MaxSample(logcommand.LogCommand):
-
+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, args):
- if not args:
- self.stdout.write('Please specify one or more input files.\n')
- return 3
-
+ def do(self):
runner = task.SyncRunner()
# here to avoid import gst eating our options
from morituri.common import checksum
- for arg in args:
+ for arg in self.options.files:
fromPath = unicode(arg.decode('utf-8'))
checksumtask = checksum.MaxSampleTask(fromPath)
runner.run(checksumtask)
- self.stdout.write('%s\n' % arg)
- self.stdout.write('Biggest absolute sample: %04x\n' %
+ sys.stdout.write('%s\n' % arg)
+ sys.stdout.write('Biggest absolute sample: %04x\n' %
checksumtask.checksum)
-class Tag(logcommand.LogCommand):
-
+class Tag(BaseCommand):
summary = "run a tag reading task"
+ description = summary
- def do(self, args):
+ def add_arguments(self):
+ self.parser.add_argument('file', action='store',
+ help="audio file to tag")
+
+ def do(self):
try:
- path = unicode(args[0])
+ path = unicode(self.options.file)
except IndexError:
- self.stdout.write('Please specify an input file.\n')
+ sys.stdout.write('Please specify an input file.\n')
return 3
runner = task.SyncRunner()
from morituri.common import encode
- self.debug('Reading tags from %s' % path.encode('utf-8'))
+ logger.debug('Reading tags from %s' % path.encode('utf-8'))
tagtask = encode.TagReadTask(path)
runner.run(tagtask)
for key in tagtask.taglist.keys():
- self.stdout.write('%s: %r\n' % (key, tagtask.taglist[key]))
+ sys.stdout.write('%s: %r\n' % (key, tagtask.taglist[key]))
-class MusicBrainzNGS(logcommand.LogCommand):
-
- usage = "[MusicBrainz disc id]"
+class MusicBrainzNGS(BaseCommand):
summary = "examine MusicBrainz NGS info"
description = """Look up a MusicBrainz disc id and output information.
@@ -243,62 +255,79 @@ You can get the MusicBrainz disc id with rip cd info.
Example disc id: KnpGsLhvH.lPrNc1PBL21lb9Bg4-"""
- def do(self, args):
+ def add_arguments(self):
+ self.parser.add_argument('mbdiscid', action='store',
+ help="MB disc id to look up")
+
+ def do(self):
try:
- discId = unicode(args[0])
+ discId = unicode(self.options.mbdiscid)
except IndexError:
- self.stdout.write('Please specify a MusicBrainz disc id.\n')
+ sys.stdout.write('Please specify a MusicBrainz disc id.\n')
return 3
from morituri.common import mbngs
- metadatas = mbngs.musicbrainz(discId,
- record=self.getRootCommand().record)
+ metadatas = mbngs.musicbrainz(discId, record=self.options.record)
- self.stdout.write('%d releases\n' % len(metadatas))
+ sys.stdout.write('%d releases\n' % len(metadatas))
for i, md in enumerate(metadatas):
- self.stdout.write('- Release %d:\n' % (i + 1, ))
- self.stdout.write(' Artist: %s\n' % md.artist.encode('utf-8'))
- self.stdout.write(' Title: %s\n' % md.title.encode('utf-8'))
- self.stdout.write(' Type: %s\n' % md.releaseType.encode('utf-8'))
- self.stdout.write(' URL: %s\n' % md.url)
- self.stdout.write(' Tracks: %d\n' % len(md.tracks))
+ sys.stdout.write('- Release %d:\n' % (i + 1, ))
+ sys.stdout.write(' Artist: %s\n' % md.artist.encode('utf-8'))
+ sys.stdout.write(' Title: %s\n' % md.title.encode('utf-8'))
+ sys.stdout.write(' Type: %s\n' % md.releaseType.encode('utf-8'))
+ sys.stdout.write(' URL: %s\n' % md.url)
+ sys.stdout.write(' Tracks: %d\n' % len(md.tracks))
if md.catalogNumber:
- self.stdout.write(' Cat no: %s\n' % md.catalogNumber)
+ sys.stdout.write(' Cat no: %s\n' % md.catalogNumber)
if md.barcode:
- self.stdout.write(' Barcode: %s\n' % md.barcode)
+ sys.stdout.write(' Barcode: %s\n' % md.barcode)
for j, track in enumerate(md.tracks):
- self.stdout.write(' Track %2d: %s - %s\n' % (
+ sys.stdout.write(' Track %2d: %s - %s\n' % (
j + 1, track.artist.encode('utf-8'),
track.title.encode('utf-8')))
-class CDParanoia(logcommand.LogCommand):
+class CDParanoia(BaseCommand):
+ summary = "show cdparanoia version"
+ description = summary
- def do(self, args):
+ def do(self):
from morituri.program import cdparanoia
version = cdparanoia.getCdParanoiaVersion()
- self.stdout.write("cdparanoia version: %s\n" % version)
+ sys.stdout.write("cdparanoia version: %s\n" % version)
-class CDRDAO(logcommand.LogCommand):
+class CDRDAO(BaseCommand):
+ summary = "show cdrdao version"
+ description = summary
- def do(self, args):
+ def do(self):
from morituri.program import cdrdao
version = cdrdao.getCDRDAOVersion()
- self.stdout.write("cdrdao version: %s\n" % version)
+ sys.stdout.write("cdrdao version: %s\n" % version)
-class Version(logcommand.LogCommand):
-
+class Version(BaseCommand):
summary = "debug version getting"
+ description = summary
- subCommandClasses = [CDParanoia, CDRDAO]
+ subcommands = {
+ 'cdparanoia': CDParanoia,
+ 'cdrdao': CDRDAO,
+ }
-class Debug(logcommand.LogCommand):
-
+class Debug(BaseCommand):
summary = "debug internals"
+ description = "debug internals"
- subCommandClasses = [Checksum, Encode, MaxSample, Tag, MusicBrainzNGS,
- ResultCache, Version]
+ subcommands = {
+ 'checksum': Checksum,
+ 'encode': Encode,
+ 'maxsample': MaxSample,
+ 'tag': Tag,
+ 'musicbrainzngs': MusicBrainzNGS,
+ 'resultcache': ResultCache,
+ 'version': Version,
+ }
diff --git a/morituri/rip/drive.py b/morituri/command/drive.py
similarity index 60%
rename from morituri/rip/drive.py
rename to morituri/command/drive.py
index 2e136d4..91b43ed 100644
--- a/morituri/rip/drive.py
+++ b/morituri/command/drive.py
@@ -20,116 +20,105 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see .
-import os
+import sys
+from morituri.command.basecommand import BaseCommand
+from morituri.common import config, drive
from morituri.extern.task import task
-
-from morituri.common import logcommand, drive
from morituri.program import cdparanoia
-class Analyze(logcommand.LogCommand):
+import logging
+logger = logging.getLogger(__name__)
+class Analyze(BaseCommand):
summary = "analyze caching behaviour of drive"
+ description = """Determine whether cdparanoia can defeat the audio cache of the drive."""
+ device_option = True
- def addOptions(self):
- self.parser.add_option('-d', '--device',
- action="store", dest="device",
- help="CD-DA device")
-
- def handleOptions(self, options):
- if not options.device:
- drives = drive.getAllDevicePaths()
- if not drives:
- self.error('No CD-DA drives found!')
- return 3
-
- # pick the first
- self.options.device = drives[0]
-
- # this can be a symlink to another device
- self.options.device = os.path.realpath(self.options.device)
-
- def do(self, args):
+ def do(self):
runner = task.SyncRunner()
t = cdparanoia.AnalyzeTask(self.options.device)
runner.run(t)
if t.defeatsCache is None:
- self.stdout.write(
+ sys.stdout.write(
'Cannot analyze the drive. Is there a CD in it?\n')
return
if not t.defeatsCache:
- self.stdout.write(
+ sys.stdout.write(
'cdparanoia cannot defeat the audio cache on this drive.\n')
else:
- self.stdout.write(
+ sys.stdout.write(
'cdparanoia can defeat the audio cache on this drive.\n')
info = drive.getDeviceInfo(self.options.device)
if not info:
- self.stdout.write('Drive caching behaviour not saved: could not get device info (requires pycdio).\n')
+ sys.stdout.write('Drive caching behaviour not saved: could not get device info (requires pycdio).\n')
return
- self.stdout.write(
+ sys.stdout.write(
'Adding drive cache behaviour to configuration file.\n')
- self.getRootCommand().config.setDefeatsCache(info[0], info[1], info[2],
+ config.Config().setDefeatsCache(info[0], info[1], info[2],
t.defeatsCache)
-class List(logcommand.LogCommand):
-
+class List(BaseCommand):
summary = "list drives"
+ description = """list available CD-DA drives"""
- def do(self, args):
+ def do(self):
paths = drive.getAllDevicePaths()
+ self.config = config.Config()
if not paths:
- self.stdout.write('No drives found.\n')
- self.stdout.write('Create /dev/cdrom if you have a CD drive, \n')
- self.stdout.write('or install pycdio for better detection.\n')
+ sys.stdout.write('No drives found.\n')
+ sys.stdout.write('Create /dev/cdrom if you have a CD drive, \n')
+ sys.stdout.write('or install pycdio for better detection.\n')
return
try:
import cdio as _
except ImportError:
- self.stdout.write(
+ sys.stdout.write(
'Install pycdio for vendor/model/release detection.\n')
return
for path in paths:
vendor, model, release = drive.getDeviceInfo(path)
- self.stdout.write(
+ sys.stdout.write(
"drive: %s, vendor: %s, model: %s, release: %s\n" % (
path, vendor, model, release))
try:
- offset = self.getRootCommand().config.getReadOffset(
+ offset = self.config.getReadOffset(
vendor, model, release)
- self.stdout.write(
+ sys.stdout.write(
" Configured read offset: %d\n" % offset)
except KeyError:
- self.stdout.write(
+ sys.stdout.write(
" No read offset found. Run 'rip offset find'\n")
try:
- defeats = self.getRootCommand().config.getDefeatsCache(
+ defeats = self.config.getDefeatsCache(
vendor, model, release)
- self.stdout.write(
+ sys.stdout.write(
" Can defeat audio cache: %s\n" % defeats)
except KeyError:
- self.stdout.write(
+ sys.stdout.write(
" Unknown whether audio cache can be defeated. "
"Run 'rip drive analyze'\n")
if not paths:
- self.stdout.write('No drives found.\n')
+ sys.stdout.write('No drives found.\n')
-class Drive(logcommand.LogCommand):
-
+class Drive(BaseCommand):
summary = "handle drives"
-
- subCommandClasses = [Analyze, List, ]
+ description = """Drive utilities."""
+ subcommands = {
+ 'analyze': Analyze,
+ 'list': List
+ }
diff --git a/morituri/rip/image.py b/morituri/command/image.py
similarity index 51%
rename from morituri/rip/image.py
rename to morituri/command/image.py
index 156ec34..52a22d3 100644
--- a/morituri/rip/image.py
+++ b/morituri/command/image.py
@@ -21,117 +21,60 @@
# along with morituri. If not, see .
import os
+import sys
-from morituri.common import logcommand, accurip, program
+from morituri.command.basecommand import BaseCommand
+from morituri.common import accurip, config, program
+from morituri.extern.task import task
from morituri.image import image
from morituri.result import result
-from morituri.extern.task import task
+import logging
+logger = logging.getLogger(__name__)
-class Encode(logcommand.LogCommand):
-
- summary = "encode image"
-
- def addOptions(self):
- # FIXME: get from config
- self.parser.add_option('-O', '--output-directory',
- action="store", dest="output_directory",
- help="output directory (defaults to current directory)")
-
- default = 'vorbis'
-
- # here to avoid import gst eating our options
- from morituri.common import encode
-
- self.parser.add_option('', '--profile',
- action="store", dest="profile",
- help="profile for encoding (default '%s', choices '%s')" % (
- default, "', '".join(encode.ALL_PROFILES.keys())),
- default=default)
-
- def do(self, args):
- prog = program.Program(self.getRootCommand().config)
- prog.outdir = (self.options.output_directory or os.getcwd())
- prog.outdir = prog.outdir.decode('utf-8')
-
- # here to avoid import gst eating our options
- from morituri.common import encode
-
- profile = encode.ALL_PROFILES[self.options.profile]()
-
- runner = task.SyncRunner()
-
- for arg in args:
- arg = arg.decode('utf-8')
- indir = os.path.dirname(arg)
- cueImage = image.Image(arg)
- cueImage.setup(runner)
- # FIXME: find a decent way to get an album-specific outdir
- root = os.path.basename(indir)
- outdir = os.path.join(prog.outdir, root)
- try:
- os.makedirs(outdir)
- except:
- # FIXME: handle other exceptions than OSError Errno 17
- pass
- # FIXME: handle this nicer
- assert outdir != indir
-
- taskk = image.ImageEncodeTask(cueImage, profile, outdir)
- runner.run(taskk)
-
- # FIXME: translate .m3u file if it exists
- root, ext = os.path.splitext(arg)
- m3upath = root + '.m3u'
- if os.path.exists(m3upath):
- self.debug('translating .m3u file')
- inm3u = open(m3upath)
- outm3u = open(os.path.join(outdir, os.path.basename(m3upath)),
- 'w')
- for line in inm3u.readlines():
- root, ext = os.path.splitext(line)
- if ext:
- # newline is swallowed by splitext here
- outm3u.write('%s.%s\n' % (root, profile.extension))
- else:
- outm3u.write('%s' % root)
- outm3u.close()
-
-
-class Retag(logcommand.LogCommand):
-
+class Retag(BaseCommand):
summary = "retag image files"
+ description = """
+Retags the image from the given .cue files with tags obtained from MusicBrainz.
+"""
- def addOptions(self):
- self.parser.add_option('-R', '--release-id',
+ def add_arguments(self):
+ self.parser.add_argument('cuefile', nargs='+', action='store',
+ help="cue file to load rip image from")
+ self.parser.add_argument(
+ '-R', '--release-id',
action="store", dest="release_id",
- help="MusicBrainz release id to match to (if there are multiple)")
- self.parser.add_option('-p', '--prompt',
+ help="MusicBrainz release id to match to (if there are multiple)"
+ )
+ self.parser.add_argument(
+ '-p', '--prompt',
action="store_true", dest="prompt",
- help="Prompt if there are multiple matching releases")
- self.parser.add_option('-c', '--country',
+ help="Prompt if there are multiple matching releases"
+ )
+ self.parser.add_argument(
+ '-c', '--country',
action="store", dest="country",
- help="Filter releases by country")
+ help="Filter releases by country"
+ )
-
- def do(self, args):
+ def do(self):
# here to avoid import gst eating our options
from morituri.common import encode
- prog = program.Program(self.getRootCommand().config, stdout=self.stdout)
+ prog = program.Program(config.Config(), stdout=sys.stdout)
runner = task.SyncRunner()
- for arg in args:
- self.stdout.write('Retagging image %r\n' % arg)
+ for arg in self.options.cuefile:
+ sys.stdout.write('Retagging image %r\n' % arg)
arg = arg.decode('utf-8')
cueImage = image.Image(arg)
cueImage.setup(runner)
mbdiscid = cueImage.table.getMusicBrainzDiscId()
- self.stdout.write('MusicBrainz disc id is %s\n' % mbdiscid)
+ sys.stdout.write('MusicBrainz disc id is %s\n' % mbdiscid)
- self.stdout.write("MusicBrainz lookup URL %s\n" %
+ sys.stdout.write("MusicBrainz lookup URL %s\n" %
cueImage.table.getMusicBrainzSubmitURL())
prog.metadata = prog.getMusicBrainz(cueImage.table, mbdiscid,
release=self.options.release_id,
@@ -151,7 +94,7 @@ class Retag(logcommand.LogCommand):
path = cueImage.getRealPath(track.indexes[1].path)
taglist = prog.getTagList(track.number)
- self.debug(
+ logger.debug(
'possibly retagging %r from cue path %r with taglist %r',
path, arg, taglist)
t = encode.SafeRetagTask(path, taglist)
@@ -164,21 +107,22 @@ class Retag(logcommand.LogCommand):
print
-class Verify(logcommand.LogCommand):
-
- usage = '[CUEFILE]...'
+class Verify(BaseCommand):
summary = "verify image"
-
- description = '''
+ description = """
Verifies the image from the given .cue files against the AccurateRip database.
-'''
+"""
- def do(self, args):
- prog = program.Program(self.getRootCommand().config)
+ def add_arguments(self):
+ self.parser.add_argument('cuefile', nargs='+', action='store',
+ help="cue file to load rip image from")
+
+ def do(self):
+ prog = program.Program(config.Config())
runner = task.SyncRunner()
cache = accurip.AccuCache()
- for arg in args:
+ for arg in self.options.cuefile:
arg = arg.decode('utf-8')
cueImage = image.Image(arg)
cueImage.setup(runner)
@@ -199,14 +143,14 @@ Verifies the image from the given .cue files against the AccurateRip database.
print "\n".join(prog.getAccurateRipResults()) + "\n"
-class Image(logcommand.LogCommand):
-
+class Image(BaseCommand):
summary = "handle images"
-
description = """
Handle disc images. Disc images are described by a .cue file.
Disc images can be encoded to another format (for example, to make a
compressed encoding), retagged and verified.
"""
-
- subCommandClasses = [Encode, Retag, Verify, ]
+ subcommands = {
+ 'verify': Verify,
+ 'retag': Retag
+ }
diff --git a/morituri/command/main.py b/morituri/command/main.py
new file mode 100644
index 0000000..0f12338
--- /dev/null
+++ b/morituri/command/main.py
@@ -0,0 +1,86 @@
+# -*- Mode: Python -*-
+# vi:si:et:sw=4:sts=4:ts=4
+
+import os
+import sys
+import pkg_resources
+import musicbrainzngs
+
+from morituri.command import cd, offset, drive, image, accurip, debug
+from morituri.command.basecommand import BaseCommand
+from morituri.common import common, directory
+from morituri.configure import configure
+from morituri.extern.task import task
+
+import logging
+logger = logging.getLogger(__name__)
+
+def main():
+ # set user agent
+ musicbrainzngs.set_useragent("whipper", configure.version,
+ "https://github.com/JoeLametta/whipper")
+ # register plugins with pkg_resources
+ distributions, _ = pkg_resources.working_set.find_plugins(
+ pkg_resources.Environment([directory.data_path('plugins')])
+ )
+ map(pkg_resources.working_set.add, distributions)
+ try:
+ ret = Whipper(sys.argv[1:], os.path.basename(sys.argv[0]), None).do()
+ except SystemError, e:
+ sys.stderr.write('whipper: error: %s\n' % e.args)
+ return 255
+ except ImportError, e:
+ raise ImportError(e)
+ except task.TaskException, e:
+ if isinstance(e.exception, ImportError):
+ raise ImportError(e.exception)
+ elif isinstance(e.exception, common.MissingDependencyException):
+ sys.stderr.write('whipper: error: missing dependency "%s"\n' %
+ e.exception.dependency)
+ return 255
+
+ if isinstance(e.exception, common.EmptyError):
+ logger.debug("EmptyError: %r", str(e.exception))
+ sys.stderr.write('whipper: error: Could not create encoded file.\n')
+ return 255
+
+ # in python3 we can instead do `raise e.exception` as that would show
+ # the exception's original context
+ sys.stderr.write(e.exceptionMessage)
+ return 255
+ return ret if ret else 0
+
+class Whipper(BaseCommand):
+ description = """whipper is a CD ripping utility focusing on accuracy over speed.
+
+whipper gives you a tree of subcommands to work with.
+You can get help on subcommands by using the -h option to the subcommand.
+"""
+ no_add_help = True
+ subcommands = {
+ 'accurip': accurip.AccuRip,
+ 'cd': cd.CD,
+ 'debug': debug.Debug,
+ 'drive': drive.Drive,
+ 'offset': offset.Offset,
+ 'image': image.Image
+ }
+
+ def add_arguments(self):
+ self.parser.add_argument('-R', '--record',
+ action='store_true', dest='record',
+ help="record API requests for playback")
+ self.parser.add_argument('-v', '--version',
+ action="store_true", dest="version",
+ help="show version information")
+ self.parser.add_argument('-h', '--help',
+ action="store_true", dest="help",
+ help="show this help message and exit")
+
+ def handle_arguments(self):
+ if self.options.help:
+ self.parser.print_help()
+ sys.exit(0)
+ if self.options.version:
+ print "whipper %s" % configure.version
+ sys.exit(0)
diff --git a/morituri/rip/offset.py b/morituri/command/offset.py
similarity index 71%
rename from morituri/rip/offset.py
rename to morituri/command/offset.py
index a9b5982..f8e9774 100644
--- a/morituri/rip/offset.py
+++ b/morituri/command/offset.py
@@ -20,18 +20,24 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see .
+import argparse
import os
+import sys
import tempfile
import gobject
gobject.threads_init()
-from morituri.common import logcommand, accurip, drive, program, common
+from morituri.command.basecommand import BaseCommand
+from morituri.common import accurip, common, config, drive, program
from morituri.common import task as ctask
from morituri.program import cdrdao, cdparanoia
from morituri.extern.task import task
+import logging
+logger = logging.getLogger(__name__)
+
# see http://www.accuraterip.com/driveoffsets.htm
# and misc/offsets.py
OFFSETS = "+6, +48, +102, +667, +12, +30, +618, +594, +738, -472, " + \
@@ -47,26 +53,23 @@ OFFSETS = "+6, +48, +102, +667, +12, +30, +618, +594, +738, -472, " + \
"+1127"
-class Find(logcommand.LogCommand):
+class Find(BaseCommand):
summary = "find drive read offset"
description = """Find drive's read offset by ripping tracks from a
CD in the AccurateRip database."""
+ formatter_class = argparse.ArgumentDefaultsHelpFormatter
+ device_option = True
- def addOptions(self):
- default = OFFSETS
- self.parser.add_option('-o', '--offsets',
- action="store", dest="offsets",
- help="list of offsets, comma-separated, "
- "colon-separated for ranges (defaults to %s)" % default,
- default=default)
- self.parser.add_option('-d', '--device',
- action="store", dest="device",
- help="CD-DA device")
+ def add_arguments(self):
+ self.parser.add_argument(
+ '-o', '--offsets',
+ action="store", dest="offsets", default=OFFSETS,
+ help="list of offsets, comma-separated, colon-separated for ranges"
+ )
- def handleOptions(self, options):
- self.options = options
+ def handle_arguments(self):
self._offsets = []
- blocks = options.offsets.split(',')
+ blocks = self.options.offsets.split(',')
for b in blocks:
if ':' in b:
a, b = b.split(':')
@@ -74,27 +77,16 @@ CD in the AccurateRip database."""
else:
self._offsets.append(int(b))
- self.debug('Trying with offsets %r', self._offsets)
+ logger.debug('Trying with offsets %r', self._offsets)
- if not options.device:
- drives = drive.getAllDevicePaths()
- if not drives:
- self.error('No CD-DA drives found!')
- return 3
-
- # pick the first
- self.options.device = drives[0]
-
- # this can be a symlink to another device
-
- def do(self, args):
- prog = program.Program(self.getRootCommand().config)
+ def do(self):
+ prog = program.Program(config.Config())
runner = ctask.SyncRunner()
device = self.options.device
# if necessary, load and unmount
- self.stdout.write('Checking device %s\n' % device)
+ sys.stdout.write('Checking device %s\n' % device)
prog.loadDevice(device)
prog.unmountDevice(device)
@@ -103,9 +95,9 @@ CD in the AccurateRip database."""
t = cdrdao.ReadTOCTask(device)
table = t.table
- self.debug("CDDB disc id: %r", table.getCDDBDiscId())
+ logger.debug("CDDB disc id: %r", table.getCDDBDiscId())
url = table.getAccurateRipURL()
- self.debug("AccurateRip URL: %s", url)
+ logger.debug("AccurateRip URL: %s", url)
# FIXME: download url as a task too
responses = []
@@ -116,17 +108,17 @@ CD in the AccurateRip database."""
responses = accurip.getAccurateRipResponses(data)
except urllib2.HTTPError, e:
if e.code == 404:
- self.stdout.write(
+ sys.stdout.write(
'Album not found in AccurateRip database.\n')
return 1
else:
raise
if responses:
- self.debug('%d AccurateRip responses found.' % len(responses))
+ logger.debug('%d AccurateRip responses found.' % len(responses))
if responses[0].cddbDiscId != table.getCDDBDiscId():
- self.warning("AccurateRip response discid different: %s",
+ logger.warning("AccurateRip response discid different: %s",
responses[0].cddbDiscId)
# now rip the first track at various offsets, calculating AccurateRip
@@ -140,7 +132,7 @@ CD in the AccurateRip database."""
return None, None
for offset in self._offsets:
- self.stdout.write('Trying read offset %d ...\n' % offset)
+ sys.stdout.write('Trying read offset %d ...\n' % offset)
try:
archecksum = self._arcs(runner, table, 1, offset)
except task.TaskException, e:
@@ -151,23 +143,23 @@ CD in the AccurateRip database."""
raise e
if isinstance(e.exception, cdparanoia.FileSizeError):
- self.stdout.write(
+ sys.stdout.write(
'WARNING: cannot rip with offset %d...\n' % offset)
continue
- self.warning("Unknown task exception for offset %d: %r" % (
+ logger.warning("Unknown task exception for offset %d: %r" % (
offset, e))
- self.stdout.write(
+ sys.stdout.write(
'WARNING: cannot rip with offset %d...\n' % offset)
continue
- self.debug('AR checksum calculated: %s' % archecksum)
+ logger.debug('AR checksum calculated: %s' % archecksum)
c, i = match(archecksum, 1, responses)
if c:
count = 1
- self.debug('MATCHED against response %d' % i)
- self.stdout.write(
+ logger.debug('MATCHED against response %d' % i)
+ sys.stdout.write(
'Offset of device is likely %d, confirming ...\n' %
offset)
@@ -178,14 +170,14 @@ CD in the AccurateRip database."""
archecksum = self._arcs(runner, table, track, offset)
except task.TaskException, e:
if isinstance(e.exception, cdparanoia.FileSizeError):
- self.stdout.write(
+ sys.stdout.write(
'WARNING: cannot rip with offset %d...\n' %
offset)
continue
c, i = match(archecksum, track, responses)
if c:
- self.debug('MATCHED track %d against response %d' % (
+ logger.debug('MATCHED track %d against response %d' % (
track, i))
count += 1
@@ -193,17 +185,17 @@ CD in the AccurateRip database."""
self._foundOffset(device, offset)
return 0
else:
- self.stdout.write(
+ sys.stdout.write(
'Only %d of %d tracks matched, continuing ...\n' % (
count, len(table.tracks)))
- self.stdout.write('No matching offset found.\n')
- self.stdout.write('Consider trying again with a different disc.\n')
+ sys.stdout.write('No matching offset found.\n')
+ sys.stdout.write('Consider trying again with a different disc.\n')
# TODO MW: Update this further for ARv2 code
def _arcs(self, runner, table, track, offset):
# rips the track with the given offset, return the arcs checksum
- self.debug('Ripping track %r with offset %d ...', track, offset)
+ logger.debug('Ripping track %r with offset %d ...', track, offset)
fd, path = tempfile.mkstemp(
suffix=u'.track%02d.offset%d.morituri.wav' % (
@@ -229,21 +221,25 @@ CD in the AccurateRip database."""
return "%08x" % t.checksum
def _foundOffset(self, device, offset):
- self.stdout.write('\nRead offset of device is: %d.\n' %
+ sys.stdout.write('\nRead offset of device is: %d.\n' %
offset)
info = drive.getDeviceInfo(device)
if not info:
- self.stdout.write('Offset not saved: could not get device info (requires pycdio).\n')
+ sys.stdout.write('Offset not saved: could not get device info (requires pycdio).\n')
return
- self.stdout.write('Adding read offset to configuration file.\n')
+ sys.stdout.write('Adding read offset to configuration file.\n')
- self.getRootCommand().config.setReadOffset(info[0], info[1], info[2],
+ config.Config().setReadOffset(info[0], info[1], info[2],
offset)
-class Offset(logcommand.LogCommand):
+class Offset(BaseCommand):
summary = "handle drive offsets"
-
- subCommandClasses = [Find, ]
+ description = """
+Drive offset detection utility.
+"""
+ subcommands = {
+ 'find': Find,
+ }
diff --git a/morituri/common/accurip.py b/morituri/common/accurip.py
index 7451803..e9fb31f 100644
--- a/morituri/common/accurip.py
+++ b/morituri/common/accurip.py
@@ -26,16 +26,19 @@ import struct
import urlparse
import urllib2
-from morituri.common import log, directory
+from morituri.common import directory
+
+import logging
+logger = logging.getLogger(__name__)
_CACHE_DIR = directory.cache_path()
-class AccuCache(log.Loggable):
+class AccuCache:
def __init__(self):
if not os.path.exists(_CACHE_DIR):
- self.debug('Creating cache directory %s', _CACHE_DIR)
+ logger.debug('Creating cache directory %s', _CACHE_DIR)
os.makedirs(_CACHE_DIR)
def _getPath(self, url):
@@ -43,18 +46,18 @@ class AccuCache(log.Loggable):
return os.path.join(_CACHE_DIR, urlparse.urlparse(url)[2][1:])
def retrieve(self, url, force=False):
- self.debug("Retrieving AccurateRip URL %s", url)
+ logger.debug("Retrieving AccurateRip URL %s", url)
path = self._getPath(url)
- self.debug("Cached path: %s", path)
+ logger.debug("Cached path: %s", path)
if force:
- self.debug("forced to download")
+ logger.debug("forced to download")
self.download(url)
elif not os.path.exists(path):
- self.debug("%s does not exist, downloading", path)
+ logger.debug("%s does not exist, downloading", path)
self.download(url)
if not os.path.exists(path):
- self.debug("%s does not exist, not in database", path)
+ logger.debug("%s does not exist, not in database", path)
return None
data = self._read(url)
@@ -81,8 +84,8 @@ class AccuCache(log.Loggable):
try:
os.makedirs(os.path.dirname(path))
except OSError, e:
- self.debug('Could not make dir %s: %r' % (
- path, log.getExceptionMessage(e)))
+ logger.debug('Could not make dir %s: %r' % (
+ path, str(e)))
if e.errno != errno.EEXIST:
raise
@@ -91,7 +94,7 @@ class AccuCache(log.Loggable):
handle.close()
def _read(self, url):
- self.debug("Reading %s from cache", url)
+ logger.debug("Reading %s from cache", url)
path = self._getPath(url)
handle = open(path, 'rb')
data = handle.read()
diff --git a/morituri/common/cache.py b/morituri/common/cache.py
index 0a2d473..260abe1 100644
--- a/morituri/common/cache.py
+++ b/morituri/common/cache.py
@@ -29,10 +29,10 @@ import shutil
from morituri.result import result
from morituri.common import directory
-from morituri.extern.log import log
+import logging
+logger = logging.getLogger(__name__)
-
-class Persister(log.Loggable):
+class Persister:
"""
I wrap an optional pickle to persist an object to disk.
@@ -88,7 +88,7 @@ class Persister(log.Loggable):
handle.close()
# do an atomic move
shutil.move(path, self._path)
- self.debug('saved persisted object to %r' % self._path)
+ logger.debug('saved persisted object to %r' % self._path)
def _unpickle(self, default=None):
self.object = default
@@ -104,7 +104,7 @@ class Persister(log.Loggable):
try:
self.object = pickle.load(handle)
- self.debug('loaded persisted object from %r' % self._path)
+ logger.debug('loaded persisted object from %r' % self._path)
except:
# can fail for various reasons; in that case, pretend we didn't
# load it
@@ -115,7 +115,7 @@ class Persister(log.Loggable):
os.unlink(self._path)
-class PersistedCache(log.Loggable):
+class PersistedCache:
"""
I wrap a directory of persisted objects.
"""
@@ -142,7 +142,7 @@ class PersistedCache(log.Loggable):
if hasattr(persister.object, 'instanceVersion'):
o = persister.object
if o.instanceVersion < o.__class__.classVersion:
- self.debug(
+ logger.debug(
'key %r persisted object version %d is outdated',
key, o.instanceVersion)
persister.object = None
@@ -152,7 +152,7 @@ class PersistedCache(log.Loggable):
return persister
-class ResultCache(log.Loggable):
+class ResultCache:
def __init__(self, path=None):
self._path = path or directory.cache_path('result')
@@ -168,16 +168,16 @@ class ResultCache(log.Loggable):
presult = self._pcache.get(cddbdiscid)
if not presult.object:
- self.debug('result for cddbdiscid %r not in cache', cddbdiscid)
+ logger.debug('result for cddbdiscid %r not in cache', cddbdiscid)
if not create:
- self.debug('returning None')
+ logger.debug('returning None')
return None
- self.debug('creating result')
+ logger.debug('creating result')
presult.object = result.RipResult()
presult.persist(presult.object)
else:
- self.debug('result for cddbdiscid %r found in cache, reusing',
+ logger.debug('result for cddbdiscid %r found in cache, reusing',
cddbdiscid)
return presult
@@ -188,7 +188,7 @@ class ResultCache(log.Loggable):
return [os.path.splitext(os.path.basename(path))[0] for path in paths]
-class TableCache(log.Loggable):
+class TableCache:
"""
I read and write entries to and from the cache of tables.
@@ -215,11 +215,11 @@ class TableCache(log.Loggable):
ptable = self._pcache.get(cddbdiscid)
if ptable.object:
if ptable.object.getMusicBrainzDiscId() != mbdiscid:
- self.debug('cached table is for different mb id %r' % (
+ logger.debug('cached table is for different mb id %r' % (
ptable.object.getMusicBrainzDiscId()))
ptable.object = None
else:
- self.debug('no valid cached table found for %r' %
+ logger.debug('no valid cached table found for %r' %
cddbdiscid)
if not ptable.object:
diff --git a/morituri/common/checksum.py b/morituri/common/checksum.py
index da85026..8786d15 100644
--- a/morituri/common/checksum.py
+++ b/morituri/common/checksum.py
@@ -26,21 +26,21 @@ import zlib
import gst
-from morituri.common import common
+from morituri.common import common, task
from morituri.common import gstreamer as cgstreamer
-from morituri.common import log
-from morituri.common import task
from morituri.extern.task import gstreamer
from morituri.extern.task import task as etask
from morituri.program.arc import accuraterip_checksum
+import logging
+logger = logging.getLogger(__name__)
# checksums are not CRC's. a CRC is a specific type of checksum.
-class ChecksumTask(log.Loggable, gstreamer.GstPipelineTask):
+class ChecksumTask(gstreamer.GstPipelineTask):
"""
I am a task that calculates a checksum of the decoded audio data.
@@ -71,11 +71,11 @@ class ChecksumTask(log.Loggable, gstreamer.GstPipelineTask):
# use repr/%r because path can be unicode
if sampleLength < 0:
- self.debug(
+ logger.debug(
'Creating checksum task on %r from sample %d until the end',
path, sampleStart)
else:
- self.debug(
+ logger.debug(
'Creating checksum task on %r from sample %d for %d samples',
path, sampleStart, sampleLength)
@@ -109,7 +109,7 @@ class ChecksumTask(log.Loggable, gstreamer.GstPipelineTask):
# get length in samples of file
sink = self.pipeline.get_by_name('sink')
- self.debug('query duration')
+ logger.debug('query duration')
try:
length, qformat = sink.query_duration(gst.FORMAT_DEFAULT)
except gst.QueryError, e:
@@ -118,9 +118,9 @@ class ChecksumTask(log.Loggable, gstreamer.GstPipelineTask):
# wavparse 0.10.14 returns in bytes
if qformat == gst.FORMAT_BYTES:
- self.debug('query returned in BYTES format')
+ logger.debug('query returned in BYTES format')
length /= 4
- self.debug('total sample length of file: %r', length)
+ logger.debug('total sample length of file: %r', length)
return length
@@ -134,40 +134,42 @@ class ChecksumTask(log.Loggable, gstreamer.GstPipelineTask):
if self._sampleLength < 0:
self._sampleLength = length - self._sampleStart
- self.debug('sampleLength is queried as %d samples',
+ logger.debug('sampleLength is queried as %d samples',
self._sampleLength)
else:
- self.debug('sampleLength is known, and is %d samples' %
+ logger.debug('sampleLength is known, and is %d samples' %
self._sampleLength)
self._sampleEnd = self._sampleStart + self._sampleLength - 1
- self.debug('sampleEnd is sample %d' % self._sampleEnd)
+ logger.debug('sampleEnd is sample %d' % self._sampleEnd)
- self.debug('event')
+ logger.debug('event')
if self._sampleStart == 0 and self._sampleEnd + 1 == length:
- self.debug('No need to seek, crcing full file')
+ 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
- self.debug('CRCing %r from frame %d to frame %d (excluded)' % (
+ 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)
- self.debug('event sent, result %r', result)
+ logger.debug('event sent, result %r', result)
if not result:
- self.error('Failed to select samples with GStreamer seek event')
+ 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)
- self.debug('scheduling setting to play')
+ 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)
@@ -179,30 +181,30 @@ class ChecksumTask(log.Loggable, gstreamer.GstPipelineTask):
self.schedule(0, play)
#self.pipeline.set_state(gst.STATE_PLAYING)
- self.debug('scheduled setting to play')
+ logger.debug('scheduled setting to play')
def stopped(self):
- self.debug('stopped')
+ logger.debug('stopped')
if not self._last:
# see http://bugzilla.gnome.org/show_bug.cgi?id=578612
- self.debug(
+ 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
- self.debug("last buffer's sample offset %r", self._last.offset)
- self.debug("last buffer's sample size %r", len(self._last) / 4)
+ 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
- self.debug("last sample offset in buffer: %r", last)
- self.debug("requested sample end: %r", self._sampleEnd)
- self.debug("requested sample length: %r", self._sampleLength)
- self.debug("checksum: %08X", self._checksum)
- self.debug("bytes: %d", self._bytes)
+ 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)
- self.warning(msg)
+ logger.warning(msg)
self.setExceptionAndTraceback(common.MissingFrames(msg))
return
@@ -231,7 +233,7 @@ class ChecksumTask(log.Loggable, gstreamer.GstPipelineTask):
buf.offset, buf.size))
if self._first is None:
self._first = buf.offset
- self.debug('first sample is sample offset %r', self._first)
+ 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"
@@ -257,7 +259,7 @@ class ChecksumTask(log.Loggable, gstreamer.GstPipelineTask):
def _eos_cb(self, sink):
# get the last one; FIXME: why does this not get to us before ?
#self._new_buffer_cb(sink)
- self.debug('eos, scheduling stop')
+ logger.debug('eos, scheduling stop')
self.schedule(0, self.stop)
@@ -335,7 +337,7 @@ class AccurateRipChecksumTask(ChecksumTask):
if self._trackNumber == self._trackCount:
discFrameLength = self._sampleLength / common.SAMPLES_PER_FRAME
if self._discFrameCounter > discFrameLength - 5:
- self.debug('skipping frame %d', self._discFrameCounter)
+ logger.debug('skipping frame %d', self._discFrameCounter)
return checksum
# self._bytes is updated after do_checksum_buffer
diff --git a/morituri/common/common.py b/morituri/common/common.py
index 76efeea..b7d7ecd 100644
--- a/morituri/common/common.py
+++ b/morituri/common/common.py
@@ -28,7 +28,9 @@ import math
import subprocess
from morituri.extern import asyncsub
-from morituri.extern.log import log
+
+import logging
+logger = logging.getLogger(__name__)
FRAMES_PER_SECOND = 75
@@ -273,22 +275,21 @@ def getRelativePath(targetPath, collectionPath):
Used to determine the path to use in .cue/.m3u files
"""
- log.debug('common', 'getRelativePath: target %r, collection %r' % (
+ logger.debug('getRelativePath: target %r, collection %r' % (
targetPath, collectionPath))
targetDir = os.path.dirname(targetPath)
collectionDir = os.path.dirname(collectionPath)
if targetDir == collectionDir:
- log.debug('common',
- 'getRelativePath: target and collection in same dir')
+ logger.debug('getRelativePath: target and collection in same dir')
return os.path.basename(targetPath)
else:
rel = os.path.relpath(
targetDir + os.path.sep,
collectionDir + os.path.sep)
- log.debug('common',
- 'getRelativePath: target and collection in different dir, %r' %
- rel)
+ logger.debug(
+ 'getRelativePath: target and collection in different dir, %r' % rel
+ )
return os.path.join(rel, os.path.basename(targetPath))
diff --git a/morituri/common/config.py b/morituri/common/config.py
index 2ff3078..d9229e6 100644
--- a/morituri/common/config.py
+++ b/morituri/common/config.py
@@ -20,17 +20,19 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see .
+import ConfigParser
+import codecs
import os.path
import shutil
-import urllib
-import codecs
import tempfile
-import ConfigParser
+import urllib
-from morituri.common import directory, log
+from morituri.common import directory
+import logging
+logger = logging.getLogger(__name__)
-class Config(log.Loggable):
+class Config:
def __init__(self, path=None):
self._path = path or directory.config_path()
@@ -45,8 +47,8 @@ class Config(log.Loggable):
with codecs.open(self._path, 'r', encoding='utf-8') as f:
self._parser.readfp(f)
- self.info('Loaded %d sections from config file' %
- len(self._parser.sections()))
+ logger.info('Loaded %d sections from config file' %
+ len(self._parser.sections()))
def write(self):
fd, path = tempfile.mkstemp(suffix=u'.moriturirc')
@@ -121,13 +123,14 @@ class Config(log.Loggable):
if not name.startswith('drive:'):
continue
- self.debug('Looking at section %r' % name)
+ logger.debug('Looking at section %r' % name)
conf = {}
for key in ['vendor', 'model', 'release']:
locals()[key] = locals()[key].strip()
conf[key] = self._parser.get(name, key)
- self.debug("%s: '%s' versus '%s'" % (
- key, locals()[key], conf[key]))
+ logger.debug("%s: '%s' versus '%s'" % (
+ key, locals()[key], conf[key]
+ ))
if vendor.strip() != conf['vendor']:
continue
if model.strip() != conf['model']:
@@ -154,5 +157,3 @@ class Config(log.Loggable):
self.write()
return self._findDriveSection(vendor, model, release)
-
-
diff --git a/morituri/common/drive.py b/morituri/common/drive.py
index a206ac0..f0e5867 100644
--- a/morituri/common/drive.py
+++ b/morituri/common/drive.py
@@ -22,8 +22,8 @@
import os
-from morituri.common import log
-
+import logging
+logger = logging.getLogger(__name__)
def _listify(listOrString):
if type(listOrString) == str:
@@ -37,7 +37,7 @@ def getAllDevicePaths():
# see https://savannah.gnu.org/bugs/index.php?38477
return [str(dev) for dev in _getAllDevicePathsPyCdio()]
except ImportError:
- log.info('drive', 'Cannot import pycdio')
+ logger.info('Cannot import pycdio')
return _getAllDevicePathsStatic()
diff --git a/morituri/common/encode.py b/morituri/common/encode.py
index 29fedc9..1203fe7 100644
--- a/morituri/common/encode.py
+++ b/morituri/common/encode.py
@@ -25,15 +25,17 @@ import os
import shutil
import tempfile
-from morituri.common import common, log
+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, gstreamer
from morituri.program import sox
+import logging
+logger = logging.getLogger(__name__)
-class Profile(log.Loggable):
+class Profile:
name = None
extension = None
@@ -107,7 +109,7 @@ class _LameProfile(Profile):
def test(self):
version = cgstreamer.elementFactoryVersion('lamemp3enc')
- self.debug('lamemp3enc version: %r', version)
+ logger.debug('lamemp3enc version: %r', version)
if version:
t = tuple([int(s) for s in version.split('.')])
if t >= (0, 10, 19):
@@ -115,7 +117,7 @@ class _LameProfile(Profile):
return True
version = cgstreamer.elementFactoryVersion('lame')
- self.debug('lame version: %r', version)
+ logger.debug('lame version: %r', version)
if version:
self.pipeline = self._lame_pipeline
return True
@@ -246,13 +248,12 @@ class EncodeTask(ctask.GstPipelineTask):
try:
tagger.merge_tags(self._taglist, self.gst.TAG_MERGE_APPEND)
except AttributeError, e:
- self.warning('Could not merge tags: %r',
- log.getExceptionMessage(e))
+ logger.warning('Could not merge tags: %r', str(e))
def paused(self):
# get length
identity = self.pipeline.get_by_name('identity')
- self.debug('query duration')
+ logger.debug('query duration')
try:
length, qformat = identity.query_duration(self.gst.FORMAT_DEFAULT)
except self.gst.QueryError, e:
@@ -263,16 +264,16 @@ class EncodeTask(ctask.GstPipelineTask):
# wavparse 0.10.14 returns in bytes
if qformat == self.gst.FORMAT_BYTES:
- self.debug('query returned in BYTES format')
+ logger.debug('query returned in BYTES format')
length /= 4
- self.debug('total length: %r', length)
+ 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:
- self.debug('Could not query duration')
+ logger.debug('Could not query duration')
self._duration = duration
# set up level callbacks
@@ -289,7 +290,7 @@ class EncodeTask(ctask.GstPipelineTask):
interval = self.gst.SECOND
if interval > duration:
interval = duration / 2
- self.debug('Setting level interval to %s, duration %s',
+ 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
@@ -310,7 +311,7 @@ class EncodeTask(ctask.GstPipelineTask):
return True
def bus_eos_cb(self, bus, message):
- self.debug('eos, scheduling stop')
+ logger.debug('eos, scheduling stop')
self.schedule(0, self.stop)
def _message_element_cb(self, bus, message):
@@ -327,7 +328,7 @@ class EncodeTask(ctask.GstPipelineTask):
for p in s['peak']:
if self._peakdB < p:
- self.log('higher peakdB found, now %r', self._peakdB)
+ 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
@@ -338,19 +339,19 @@ class EncodeTask(ctask.GstPipelineTask):
def stopped(self):
if self._peakdB is not None:
- self.debug('peakdB %r', self._peakdB)
+ logger.debug('peakdB %r', self._peakdB)
self.peak = math.sqrt(math.pow(10, self._peakdB / 10.0))
return
- self.warning('No peak found.')
+ logger.warning('No peak found.')
self.peak = 0.0
if self._duration:
- self.warning('GStreamer level element did not send messages.')
+ 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:
- self.warning('only one frame of audio, setting peak to 0.0')
+ logger.warning('only one frame of audio, setting peak to 0.0')
self.peak = 0.0
class TagReadTask(ctask.GstPipelineTask):
@@ -382,12 +383,12 @@ class TagReadTask(ctask.GstPipelineTask):
gstreamer.quoteParse(self._path).encode('utf-8'))
def bus_eos_cb(self, bus, message):
- self.debug('eos, scheduling stop')
+ logger.debug('eos, scheduling stop')
self.schedule(0, self.stop)
def bus_tag_cb(self, bus, message):
taglist = message.parse_tag()
- self.debug('tag_cb, %d tags' % len(taglist.keys()))
+ logger.debug('tag_cb, %d tags' % len(taglist.keys()))
if not self.taglist:
self.taglist = taglist
else:
@@ -434,17 +435,17 @@ class TagWriteTask(ctask.LoggableTask):
if self._taglist:
tagger.merge_tags(self._taglist, gst.TAG_MERGE_APPEND)
- self.debug('pausing pipeline')
+ logger.debug('pausing pipeline')
self._pipeline.set_state(gst.STATE_PAUSED)
self._pipeline.get_state()
- self.debug('paused pipeline')
+ logger.debug('paused pipeline')
# add eos handling
bus = self._pipeline.get_bus()
bus.add_signal_watch()
bus.connect('message::eos', self._message_eos_cb)
- self.debug('scheduling setting to play')
+ 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,
@@ -457,20 +458,20 @@ class TagWriteTask(ctask.LoggableTask):
self.schedule(0, play)
#self._pipeline.set_state(gst.STATE_PLAYING)
- self.debug('scheduled setting to play')
+ logger.debug('scheduled setting to play')
def _message_eos_cb(self, bus, message):
- self.debug('eos, scheduling stop')
+ logger.debug('eos, scheduling stop')
self.schedule(0, self.stop)
def stop(self):
# here to avoid import gst eating our options
import gst
- self.debug('stopping')
- self.debug('setting state to NULL')
+ logger.debug('stopping')
+ logger.debug('setting state to NULL')
self._pipeline.set_state(gst.STATE_NULL)
- self.debug('set state to NULL')
+ logger.debug('set state to NULL')
task.Task.stop(self)
@@ -512,12 +513,12 @@ class SafeRetagTask(ctask.LoggableMultiSeparateTask):
if taskk == self.tasks[0]:
taglist = taskk.taglist.copy()
if common.tagListEquals(taglist, self._taglist):
- self.debug('tags are already fine: %r',
+ logger.debug('tags are already fine: %r',
common.tagListToDict(taglist))
else:
# need to retag
- self.debug('tags need to be rewritten')
- self.debug('Current tags: %r, new tags: %r',
+ 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) \
@@ -531,15 +532,15 @@ class SafeRetagTask(ctask.LoggableMultiSeparateTask):
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):
- self.debug('tags written successfully')
+ logger.debug('tags written successfully')
c1 = self.tasks[1].checksum
c2 = self.tasks[3].checksum
- self.debug('comparing checksums %08x and %08x' % (c1, c2))
+ 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)
- self.debug('moving temporary file to %r' % self._path)
+ logger.debug('moving temporary file to %r' % self._path)
os.rename(self._tmppath, self._path)
self.changed = True
else:
@@ -547,9 +548,9 @@ class SafeRetagTask(ctask.LoggableMultiSeparateTask):
e = TypeError("Checksums failed")
self.setAndRaiseException(e)
else:
- self.debug('failed to update tags, only have %r',
+ logger.debug('failed to update tags, only have %r',
common.tagListToDict(self.tasks[4].taglist))
- self.debug('difference: %r',
+ logger.debug('difference: %r',
common.tagListDifference(self.tasks[4].taglist,
self._taglist))
os.unlink(self._tmppath)
diff --git a/morituri/common/gstreamer.py b/morituri/common/gstreamer.py
index fd5c38a..ed1e5c3 100644
--- a/morituri/common/gstreamer.py
+++ b/morituri/common/gstreamer.py
@@ -23,13 +23,14 @@
import re
import commands
-from morituri.common import log
+import logging
+logger = logging.getLogger(__name__)
# workaround for issue #64
def removeAudioParsers():
- log.debug('gstreamer', 'Removing buggy audioparsers plugin if needed')
+ logger.debug('Removing buggy audioparsers plugin if needed')
import gst
registry = gst.registry_get_default()
@@ -37,12 +38,12 @@ def removeAudioParsers():
plugin = registry.find_plugin("audioparsersbad")
if plugin:
# always remove from bad
- log.debug('gstreamer', 'removing audioparsersbad plugin from registry')
+ logger.debug('removing audioparsersbad plugin from registry')
registry.remove_plugin(plugin)
plugin = registry.find_plugin("audioparsers")
if plugin:
- log.debug('gstreamer', 'removing audioparsers plugin from %s %s',
+ 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
diff --git a/morituri/common/log.py b/morituri/common/log.py
deleted file mode 100644
index f0244ea..0000000
--- a/morituri/common/log.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- Mode: Python -*-
-# 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 .
-
-"""
-Logging
-"""
-
-from morituri.extern.log import log as externlog
-from morituri.extern.log.log import *
-
-
-def init():
- externlog.init('RIP_DEBUG')
- externlog.setPackageScrubList('morituri')
diff --git a/morituri/common/logcommand.py b/morituri/common/logcommand.py
deleted file mode 100644
index 32c3afc..0000000
--- a/morituri/common/logcommand.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- Mode: Python -*-
-# 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 .
-
-"""
-Logging Command.
-"""
-
-from morituri.extern.command import command
-from morituri.common import log
-
-
-class LogCommand(command.Command, log.Loggable):
-
- def __init__(self, parentCommand=None, **kwargs):
- command.Command.__init__(self, parentCommand, **kwargs)
- self.logCategory = self.name
-
- def parse(self, argv):
- cmd = self.getRootCommand()
- if hasattr(cmd, 'config'):
- config = cmd.config
- # find section name
- cmd = self
- section = []
- while cmd is not None:
- section.insert(0, cmd.name)
- cmd = cmd.parentCommand
- section = '.'.join(section)
- # get defaults from config
- defaults = {}
- for opt in self.parser.option_list:
- if opt.dest is None:
- continue
- if 'string' == opt.type:
- val = config.get(section, opt.dest)
- elif opt.action in ('store_false', 'store_true'):
- val = config.getboolean(section, opt.dest)
- else:
- val = None
- if val is not None:
- defaults[opt.dest] = val
- self.parser.set_defaults(**defaults)
- command.Command.parse(self, argv)
-
- # command.Command has a fake debug method, so choose the right one
-
- def debug(self, format, *args):
- kwargs = {}
- log.Loggable.doLog(self, log.DEBUG, -2, format, *args, **kwargs)
diff --git a/morituri/common/mbngs.py b/morituri/common/mbngs.py
index 58ec099..f29982a 100644
--- a/morituri/common/mbngs.py
+++ b/morituri/common/mbngs.py
@@ -26,7 +26,8 @@ Handles communication with the musicbrainz server using NGS.
import urllib2
-from morituri.common import log
+import logging
+logger = logging.getLogger(__name__)
VA_ID = "89ad4ac3-39f7-470e-963a-56509c546377" # Various Artists
@@ -93,7 +94,7 @@ def _record(record, which, name, what):
handle = open(filename, 'w')
handle.write(json.dumps(what))
handle.close()
- log.info('mbngs', 'Wrote %s %s to %s', which, name, filename)
+ logger.info('Wrote %s %s to %s', which, name, filename)
# credit is of the form [dict, str, dict, ... ]
# e.g. [
@@ -152,16 +153,16 @@ def _getMetadata(releaseShort, release, discid, country=None):
@rtype: L{DiscMetadata} or None
"""
- log.debug('program', 'getMetadata for release id %r',
+ logger.debug('getMetadata for release id %r',
release['id'])
if not release['id']:
- log.warning('program', 'No id for release %r', release)
+ logger.warning('No id for release %r', release)
return None
assert release['id'], 'Release does not have an id'
if 'country' in release and country and release['country'] != country:
- log.warning('program', '%r was not released in %r', release, country)
+ logger.warning('%r was not released in %r', release, country)
return None
discMD = DiscMetadata()
@@ -176,7 +177,7 @@ def _getMetadata(releaseShort, release, discid, country=None):
if len(discCredit) > 1:
- log.debug('mbngs', 'artist-credit more than 1: %r', discCredit)
+ logger.debug('artist-credit more than 1: %r', discCredit)
albumArtistName = discCredit.getName()
@@ -185,7 +186,7 @@ def _getMetadata(releaseShort, release, discid, country=None):
discMD.sortName = discCredit.getSortName()
# FIXME: is format str ?
if not 'date' in release:
- log.warning('mbngs', 'Release %r does not have date', release)
+ logger.warning('Release %r does not have date', release)
else:
discMD.release = release['date']
@@ -219,8 +220,8 @@ def _getMetadata(releaseShort, release, discid, country=None):
track = TrackMetadata()
trackCredit = _Credit(t['recording']['artist-credit'])
if len(trackCredit) > 1:
- log.debug('mbngs',
- 'artist-credit more than 1: %r', trackCredit)
+ logger.debug('artist-credit more than 1: %r',
+ trackCredit)
# FIXME: leftover comment, need an example
# various artists discs can have tracks with no artist
@@ -234,8 +235,7 @@ def _getMetadata(releaseShort, release, discid, country=None):
# FIXME: unit of duration ?
track.duration = int(t['recording'].get('length', 0))
if not track.duration:
- log.warning('getMetadata',
- 'track %r (%r) does not have duration' % (
+ logger.warning('track %r (%r) does not have duration' % (
track.title, track.mbid))
tainted = True
else:
@@ -266,7 +266,7 @@ def musicbrainz(discid, country=None, record=False):
@rtype: list of L{DiscMetadata}
"""
- log.debug('musicbrainzngs', 'looking up results for discid %r', discid)
+ logger.debug('looking up results for discid %r', discid)
import musicbrainzngs
ret = []
@@ -279,14 +279,13 @@ def musicbrainz(discid, country=None, record=False):
if e.cause.code == 404:
raise NotFoundException(e)
else:
- log.debug('musicbrainzngs',
- 'received bad response from the server')
+ logger.debug('received bad response from the server')
raise MusicBrainzException(e)
# The result can either be a "disc" or a "cdstub"
if result.get('disc'):
- log.debug('musicbrainzngs', 'found %d releases for discid %r',
+ logger.debug('found %d releases for discid %r',
len(result['disc']['release-list']), discid)
_record(record, 'releases', discid, result)
@@ -295,7 +294,7 @@ def musicbrainz(discid, country=None, record=False):
import json
for release in result['disc']['release-list']:
formatted = json.dumps(release, sort_keys=False, indent=4)
- log.debug('program', 'result %s: artist %r, title %r' % (
+ logger.debug('result %s: artist %r, title %r' % (
formatted, release['artist-credit-phrase'], release['title']))
# to get titles of recordings, we need to query the release with
@@ -307,16 +306,16 @@ def musicbrainz(discid, country=None, record=False):
_record(record, 'release', release['id'], res)
releaseDetail = res['release']
formatted = json.dumps(releaseDetail, sort_keys=False, indent=4)
- log.debug('program', 'release %s' % formatted)
+ logger.debug('release %s' % formatted)
md = _getMetadata(release, releaseDetail, discid, country)
if md:
- log.debug('program', 'duration %r', md.duration)
+ logger.debug('duration %r', md.duration)
ret.append(md)
return ret
elif result.get('cdstub'):
- log.debug('musicbrainzngs', 'query returned cdstub: ignored')
+ logger.debug('query returned cdstub: ignored')
return None
else:
return None
diff --git a/morituri/common/program.py b/morituri/common/program.py
index 3bc8400..1d40cd2 100644
--- a/morituri/common/program.py
+++ b/morituri/common/program.py
@@ -24,22 +24,24 @@
Common functionality and class for all programs using morituri.
"""
+import musicbrainzngs
import os
import sys
import time
-from morituri.common import common, log, mbngs, cache, path
+from morituri.common import common, mbngs, cache, path
from morituri.program import cdrdao, cdparanoia
from morituri.image import image
-
from morituri.extern.task import task, gstreamer
-import musicbrainzngs
+
+import logging
+logger = logging.getLogger(__name__)
# FIXME: should Program have a runner ?
-class Program(log.Loggable):
+class Program:
"""
I maintain program state and functionality.
@@ -85,7 +87,7 @@ class Program(log.Loggable):
def setWorkingDirectory(self, workingDirectory):
if workingDirectory:
- self.info('Changing to working directory %s' % workingDirectory)
+ logger.info('Changing to working directory %s' % workingDirectory)
os.chdir(workingDirectory)
def loadDevice(self, device):
@@ -108,7 +110,7 @@ class Program(log.Loggable):
If the given device is a symlink, the target will be checked.
"""
device = os.path.realpath(device)
- self.debug('possibly unmount real path %r' % device)
+ logger.debug('possibly unmount real path %r' % device)
proc = open('/proc/mounts').read()
if device in proc:
print 'Device %s is mounted, unmounting' % device
@@ -129,7 +131,7 @@ class Program(log.Loggable):
from pkg_resources import parse_version as V
version = cdrdao.getCDRDAOVersion()
if V(version) < V('1.2.3rc2'):
- self.stdout.write('Warning: cdrdao older than 1.2.3 has a '
+ sys.stdout.write('Warning: cdrdao older than 1.2.3 has a '
'pre-gap length bug.\n'
'See http://sourceforge.net/tracker/?func=detail'
'&aid=604751&group_id=2171&atid=102171\n')
@@ -158,24 +160,24 @@ class Program(log.Loggable):
itable = tdict[offset]
if not itable:
- self.debug('getTable: cddbdiscid %s, mbdiscid %s not in cache for offset %s, '
+ logger.debug('getTable: cddbdiscid %s, mbdiscid %s not in cache for offset %s, '
'reading table' % (
cddbdiscid, mbdiscid, offset))
t = cdrdao.ReadTableTask(device)
itable = t.table
tdict[offset] = itable
ptable.persist(tdict)
- self.debug('getTable: read table %r' % itable)
+ logger.debug('getTable: read table %r' % itable)
else:
- self.debug('getTable: cddbdiscid %s, mbdiscid %s in cache for offset %s' % (
+ logger.debug('getTable: cddbdiscid %s, mbdiscid %s in cache for offset %s' % (
cddbdiscid, mbdiscid, offset))
- self.debug('getTable: loaded table %r' % itable)
+ logger.debug('getTable: loaded table %r' % itable)
assert itable.hasTOC()
self.result.table = itable
- self.debug('getTable: returning table with mb id %s' %
+ logger.debug('getTable: returning table with mb id %s' %
itable.getMusicBrainzDiscId())
return itable
@@ -275,7 +277,7 @@ class Program(log.Loggable):
elif self.metadata.barcode:
templateParts[-2] += ' (%s)' % self.metadata.barcode
template = os.path.join(*templateParts)
- self.debug('Disambiguated template to %r' % template)
+ logger.debug('Disambiguated template to %r' % template)
import re
template = re.sub(r'%(\w)', r'%(\1)s', template)
@@ -296,7 +298,7 @@ class Program(log.Loggable):
import CDDB
try:
code, md = CDDB.query(cddbdiscid)
- self.debug('CDDB query result: %r, %r', code, md)
+ logger.debug('CDDB query result: %r, %r', code, md)
if code == 200:
return md['title']
@@ -317,7 +319,7 @@ class Program(log.Loggable):
self._stdout.write('Disc duration: %s, %d audio tracks\n' % (
common.formatTime(ittoc.duration() / 1000.0),
ittoc.getAudioTracks()))
- self.debug('MusicBrainz submit url: %r',
+ logger.debug('MusicBrainz submit url: %r',
ittoc.getMusicBrainzSubmitURL())
ret = None
@@ -385,7 +387,7 @@ class Program(log.Loggable):
if release:
metadatas = [m for m in metadatas if m.url.endswith(release)]
- self.debug('Asked for release %r, only kept %r',
+ logger.debug('Asked for release %r, only kept %r',
release, metadatas)
if len(metadatas) == 1:
self._stdout.write('\n')
@@ -410,11 +412,11 @@ class Program(log.Loggable):
releaseTitle = metadatas[0].releaseTitle
for i, metadata in enumerate(metadatas):
if not artist == metadata.artist:
- self.warning("artist 0: %r and artist %d: %r "
+ logger.warning("artist 0: %r and artist %d: %r "
"are not the same" % (
artist, i, metadata.artist))
if not releaseTitle == metadata.releaseTitle:
- self.warning("title 0: %r and title %d: %r "
+ logger.warning("title 0: %r and title %d: %r "
"are not the same" % (
releaseTitle, i, metadata.releaseTitle))
@@ -505,8 +507,7 @@ class Program(log.Loggable):
# Jan and 1st if MM and DD are missing
date = self.metadata.release
if date:
- log.debug('metadata',
- 'Converting release date %r to structure', date)
+ logger.debug('Converting release date %r to structure', date)
if len(date) == 4:
date += '-01'
if len(date) == 7:
@@ -554,18 +555,17 @@ class Program(log.Loggable):
runner.run(t)
except task.TaskException, e:
if isinstance(e.exception, common.MissingFrames):
- self.warning('missing frames for %r' % trackResult.filename)
+ logger.warning('missing frames for %r' % trackResult.filename)
return False
elif isinstance(e.exception, gstreamer.GstException):
- self.warning('GstException %r' % (e.exception, ))
+ logger.warning('GstException %r' % (e.exception, ))
return False
else:
raise
ret = trackResult.testcrc == t.checksum
- log.debug('program',
- 'verifyTrack: track result crc %r, file crc %r, result %r',
- trackResult.testcrc, t.checksum, ret)
+ logger.debug('verifyTrack: track result crc %r, file crc %r, result %r',
+ trackResult.testcrc, t.checksum, ret)
return ret
def ripTrack(self, runner, trackResult, offset, device, profile, taglist,
@@ -602,10 +602,10 @@ class Program(log.Loggable):
runner.run(t)
- self.debug('ripped track')
- self.debug('test speed %.3f/%.3f seconds' % (
+ logger.debug('ripped track')
+ logger.debug('test speed %.3f/%.3f seconds' % (
t.testspeed, t.testduration))
- self.debug('copy speed %.3f/%.3f seconds' % (
+ logger.debug('copy speed %.3f/%.3f seconds' % (
t.copyspeed, t.copyduration))
trackResult.testcrc = t.testchecksum
trackResult.copycrc = t.copychecksum
@@ -619,7 +619,7 @@ class Program(log.Loggable):
if trackResult.filename != t.path:
trackResult.filename = t.path
- self.info('Filename changed to %r', trackResult.filename)
+ logger.info('Filename changed to %r', trackResult.filename)
def retagImage(self, runner, taglists):
cueImage = image.Image(self.cuePath)
@@ -634,7 +634,7 @@ class Program(log.Loggable):
Will set accurip and friends on each TrackResult.
"""
- self.debug('verifying Image against %d AccurateRip responses',
+ logger.debug('verifying Image against %d AccurateRip responses',
len(responses or []))
cueImage = image.Image(self.cuePath)
@@ -653,7 +653,7 @@ class Program(log.Loggable):
if not responses:
- self.warning('No AccurateRip responses, cannot verify.')
+ logger.warning('No AccurateRip responses, cannot verify.')
return
# now loop to match responses
@@ -667,7 +667,7 @@ class Program(log.Loggable):
for j, r in enumerate(responses):
if "%08x" % csum == r.checksums[i]:
response = r
- self.debug(
+ logger.debug(
"Track %02d matched response %d of %d in "
"AccurateRip database",
i + 1, j + 1, len(responses))
@@ -679,7 +679,7 @@ class Program(log.Loggable):
trackResult.ARDBConfidence = confidence
if not trackResult.accurip:
- self.warning("Track %02d: not matched in AccurateRip database",
+ logger.warning("Track %02d: not matched in AccurateRip database",
i + 1)
# I have seen AccurateRip responses with 0 as confidence
@@ -691,11 +691,11 @@ class Program(log.Loggable):
maxConfidence = r.confidences[i]
maxResponse = r
- self.debug('Track %02d: found max confidence %d' % (
+ logger.debug('Track %02d: found max confidence %d' % (
i + 1, maxConfidence))
trackResult.ARDBMaxConfidence = maxConfidence
if not response:
- self.warning('Track %02d: none of the responses matched.',
+ logger.warning('Track %02d: none of the responses matched.',
i + 1)
trackResult.ARDBCRC = int(
maxResponse.checksums[i], 16)
@@ -743,7 +743,7 @@ class Program(log.Loggable):
def writeCue(self, discName):
assert self.result.table.canCue()
cuePath = '%s.cue' % discName
- self.debug('write .cue file to %s', cuePath)
+ logger.debug('write .cue file to %s', cuePath)
handle = open(cuePath, 'w')
# FIXME: do we always want utf-8 ?
handle.write(self.result.table.cue(cuePath).encode('utf-8'))
diff --git a/morituri/common/task.py b/morituri/common/task.py
index a40acc2..f43cf17 100644
--- a/morituri/common/task.py
+++ b/morituri/common/task.py
@@ -6,27 +6,29 @@ import signal
import subprocess
from morituri.extern import asyncsub
-from morituri.extern.log import log
from morituri.extern.task import task, gstreamer
-# log.Loggable first to get logging
+import logging
+logger = logging.getLogger(__name__)
-class SyncRunner(log.Loggable, task.SyncRunner):
+class SyncRunner(task.SyncRunner):
pass
-class LoggableTask(log.Loggable, task.Task):
- pass
-
-class LoggableMultiSeparateTask(log.Loggable, task.MultiSeparateTask):
- pass
-
-class GstPipelineTask(log.Loggable, gstreamer.GstPipelineTask):
+class LoggableTask(task.Task):
pass
-class PopenTask(log.Loggable, task.Task):
+class LoggableMultiSeparateTask(task.MultiSeparateTask):
+ pass
+
+
+class GstPipelineTask(gstreamer.GstPipelineTask):
+ pass
+
+
+class PopenTask(task.Task):
"""
I am a task that runs a command using Popen.
"""
@@ -51,7 +53,7 @@ class PopenTask(log.Loggable, task.Task):
raise
- self.debug('Started %r with pid %d', self.command,
+ logger.debug('Started %r with pid %d', self.command,
self._popen.pid)
self.schedule(1.0, self._read, runner)
@@ -63,14 +65,14 @@ class PopenTask(log.Loggable, task.Task):
ret = self._popen.recv()
if ret:
- self.log("read from stdout: %s", ret)
+ logger.debug("read from stdout: %s", ret)
self.readbytesout(ret)
read = True
ret = self._popen.recv_err()
if ret:
- self.log("read from stderr: %s", ret)
+ logger.debug("read from stderr: %s", ret)
self.readbyteserr(ret)
read = True
@@ -89,8 +91,7 @@ class PopenTask(log.Loggable, task.Task):
self._done()
except Exception, e:
- self.debug('exception during _read()')
- self.debug(log.getExceptionMessage(e))
+ logger.debug('exception during _read(): %r', str(e))
self.setException(e)
self.stop()
@@ -98,9 +99,9 @@ class PopenTask(log.Loggable, task.Task):
assert self._popen.returncode is not None, "No returncode"
if self._popen.returncode >= 0:
- self.debug('Return code was %d', self._popen.returncode)
+ logger.debug('Return code was %d', self._popen.returncode)
else:
- self.debug('Terminated with signal %d',
+ logger.debug('Terminated with signal %d',
-self._popen.returncode)
self.setProgress(1.0)
@@ -114,7 +115,7 @@ class PopenTask(log.Loggable, task.Task):
return
def abort(self):
- self.debug('Aborting, sending SIGTERM to %d', self._popen.pid)
+ logger.debug('Aborting, sending SIGTERM to %d', self._popen.pid)
os.kill(self._popen.pid, signal.SIGTERM)
# self.stop()
diff --git a/morituri/configure/configure.py b/morituri/configure/configure.py
index eac69c6..bb0f5b8 100644
--- a/morituri/configure/configure.py
+++ b/morituri/configure/configure.py
@@ -7,7 +7,7 @@ from morituri.common import common
config_dict = {
'revision': common.getRevision(),
- 'version': '0.3.0',
+ 'version': '0.4.0',
}
for key, value in config_dict.items():
diff --git a/morituri/extern/command b/morituri/extern/command
deleted file mode 120000
index 7625d61..0000000
--- a/morituri/extern/command
+++ /dev/null
@@ -1 +0,0 @@
-python-command/command
\ No newline at end of file
diff --git a/morituri/extern/flog b/morituri/extern/flog
deleted file mode 160000
index a38ebac..0000000
--- a/morituri/extern/flog
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a38ebac80eb21002a3b63e7e30c059ab028f943b
diff --git a/morituri/extern/log b/morituri/extern/log
deleted file mode 120000
index bc6368c..0000000
--- a/morituri/extern/log
+++ /dev/null
@@ -1 +0,0 @@
-flog/log
\ No newline at end of file
diff --git a/morituri/extern/python-command b/morituri/extern/python-command
deleted file mode 160000
index bea37f8..0000000
--- a/morituri/extern/python-command
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit bea37f88ecb02db5342e52d3ab0f61ec33d85b1f
diff --git a/morituri/image/cue.py b/morituri/image/cue.py
index 18502ad..c774ccf 100644
--- a/morituri/image/cue.py
+++ b/morituri/image/cue.py
@@ -29,9 +29,12 @@ See http://digitalx.org/cuesheetsyntax.php
import re
import codecs
-from morituri.common import common, log
+from morituri.common import common
from morituri.image import table
+import logging
+logger = logging.getLogger(__name__)
+
_REM_RE = re.compile("^REM\s(\w+)\s(.*)$")
_PERFORMER_RE = re.compile("^PERFORMER\s(.*)$")
_TITLE_RE = re.compile("^TITLE\s(.*)$")
@@ -57,7 +60,7 @@ _INDEX_RE = re.compile(r"""
""", re.VERBOSE)
-class CueFile(object, log.Loggable):
+class CueFile(object):
"""
I represent a .cue file as an object.
@@ -84,7 +87,7 @@ class CueFile(object, log.Loggable):
currentTrack = None
counter = 0
- self.info('Parsing .cue file %r', self._path)
+ logger.info('Parsing .cue file %r', self._path)
handle = codecs.open(self._path, 'r', 'utf-8')
for number, line in enumerate(handle.readlines()):
@@ -120,7 +123,7 @@ class CueFile(object, log.Loggable):
trackNumber = int(m.group('track'))
#trackMode = m.group('mode')
- self.debug('found track %d', trackNumber)
+ logger.debug('found track %d', trackNumber)
currentTrack = table.Track(trackNumber)
self.table.tracks.append(currentTrack)
continue
@@ -141,7 +144,7 @@ class CueFile(object, log.Loggable):
+ seconds * common.FRAMES_PER_SECOND \
+ minutes * common.FRAMES_PER_SECOND * 60
- self.debug('found index %d of track %r in %r:%d',
+ logger.debug('found index %d of track %r in %r:%d',
indexNumber, currentTrack, currentFile.path, frameOffset)
# FIXME: what do we do about File's FORMAT ?
currentTrack.index(indexNumber,
diff --git a/morituri/image/image.py b/morituri/image/image.py
index 33c166a..be2de71 100644
--- a/morituri/image/image.py
+++ b/morituri/image/image.py
@@ -26,15 +26,16 @@ Wrap on-disk CD images based on the .cue file.
import os
-from morituri.common import log, common
+from morituri.common import common
from morituri.image import cue, table
-
from morituri.extern.task import task
-
from morituri.program.soxi import AudioLengthTask
+import logging
+logger = logging.getLogger(__name__)
-class Image(object, log.Loggable):
+
+class Image(object):
"""
@ivar table: The Table of Contents for this image.
@type table: L{table.Table}
@@ -71,11 +72,11 @@ class Image(object, log.Loggable):
Do initial setup, like figuring out track lengths, and
constructing the Table of Contents.
"""
- self.debug('setup image start')
+ logger.debug('setup image start')
verify = ImageVerifyTask(self)
- self.debug('verifying image')
+ logger.debug('verifying image')
runner.run(verify)
- self.debug('verified image')
+ logger.debug('verified image')
# calculate offset and length for each track
@@ -104,10 +105,10 @@ class Image(object, log.Loggable):
self.table = table.Table(tracks)
self.table.leadout = offset
- self.debug('setup image done')
+ logger.debug('setup image done')
-class AccurateRipChecksumTask(log.Loggable, task.MultiSeparateTask):
+class AccurateRipChecksumTask(task.MultiSeparateTask):
"""
I calculate the AccurateRip checksums of all tracks.
"""
@@ -122,14 +123,14 @@ class AccurateRipChecksumTask(log.Loggable, task.MultiSeparateTask):
cue = image.cue
self.checksums = []
- self.debug('Checksumming %d tracks' % len(cue.table.tracks))
+ logger.debug('Checksumming %d tracks' % len(cue.table.tracks))
for trackIndex, track in enumerate(cue.table.tracks):
index = track.indexes[1]
length = cue.getTrackLength(track)
if length < 0:
- self.debug('track %d has unknown length' % (trackIndex + 1, ))
+ logger.debug('track %d has unknown length' % (trackIndex + 1, ))
else:
- self.debug('track %d is %d samples long' % (
+ logger.debug('track %d is %d samples long' % (
trackIndex + 1, length))
path = image.getRealPath(index.path)
@@ -148,7 +149,7 @@ class AccurateRipChecksumTask(log.Loggable, task.MultiSeparateTask):
task.MultiSeparateTask.stop(self)
-class ImageVerifyTask(log.Loggable, task.MultiSeparateTask):
+class ImageVerifyTask(task.MultiSeparateTask):
"""
I verify a disk image and get the necessary track lengths.
"""
@@ -171,32 +172,32 @@ class ImageVerifyTask(log.Loggable, task.MultiSeparateTask):
track = cue.table.tracks[0]
path = image.getRealPath(htoa.path)
assert type(path) is unicode, "%r is not unicode" % path
- self.debug('schedule scan of audio length of %r', path)
+ logger.debug('schedule scan of audio length of %r', path)
taskk = AudioLengthTask(path)
self.addTask(taskk)
self._tasks.append((0, track, taskk))
except (KeyError, IndexError):
- self.debug('no htoa track')
+ logger.debug('no htoa track')
for trackIndex, track in enumerate(cue.table.tracks):
- self.debug('verifying track %d', trackIndex + 1)
+ logger.debug('verifying track %d', trackIndex + 1)
index = track.indexes[1]
length = cue.getTrackLength(track)
if length == -1:
path = image.getRealPath(index.path)
assert type(path) is unicode, "%r is not unicode" % path
- self.debug('schedule scan of audio length of %r', path)
+ logger.debug('schedule scan of audio length of %r', path)
taskk = AudioLengthTask(path)
self.addTask(taskk)
self._tasks.append((trackIndex + 1, track, taskk))
else:
- self.debug('track %d has length %d', trackIndex + 1, length)
+ logger.debug('track %d has length %d', trackIndex + 1, length)
def stop(self):
for trackIndex, track, taskk in self._tasks:
if taskk.exception:
- self.debug('subtask %r had exception %r, shutting down' % (
+ logger.debug('subtask %r had exception %r, shutting down' % (
taskk, taskk.exception))
self.setException(taskk.exception)
break
@@ -213,7 +214,7 @@ class ImageVerifyTask(log.Loggable, task.MultiSeparateTask):
task.MultiSeparateTask.stop(self)
-class ImageEncodeTask(log.Loggable, task.MultiSeparateTask):
+class ImageEncodeTask(task.MultiSeparateTask):
"""
I encode a disk image to a different format.
"""
@@ -235,23 +236,23 @@ class ImageEncodeTask(log.Loggable, task.MultiSeparateTask):
path = image.getRealPath(index.path)
assert type(path) is unicode, "%r is not unicode" % path
- self.debug('schedule encode of %r', 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)
- self.debug('schedule encode to %r', outpath)
+ logger.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')
+ logger.debug('encoding htoa track')
add(htoa)
except (KeyError, IndexError):
- self.debug('no htoa track')
+ logger.debug('no htoa track')
pass
for trackIndex, track in enumerate(cue.table.tracks):
- self.debug('encoding track %d', trackIndex + 1)
+ logger.debug('encoding track %d', trackIndex + 1)
index = track.indexes[1]
add(index)
diff --git a/morituri/image/table.py b/morituri/image/table.py
index 590cefe..193a0c7 100644
--- a/morituri/image/table.py
+++ b/morituri/image/table.py
@@ -28,9 +28,12 @@ import copy
import urllib
import urlparse
-from morituri.common import common, log
+from morituri.common import common
from morituri.configure import configure
+import logging
+logger = logging.getLogger(__name__)
+
# FIXME: taken from libcdio, but no reference found for these
CDTEXT_FIELDS = [
@@ -156,7 +159,7 @@ class Index:
self.number, self.absolute, self.path, self.relative, self.counter)
-class Table(object, log.Loggable):
+class Table(object):
"""
I represent a table of indexes on a CD.
@@ -188,7 +191,7 @@ class Table(object, log.Loggable):
def unpickled(self):
self.logName = "Table 0x%08x v%d" % (id(self), self.instanceVersion)
- self.debug('set logName')
+ logger.debug('set logName')
def getTrackStart(self, number):
"""
@@ -294,7 +297,7 @@ class Table(object, log.Loggable):
# print 'THOMAS: disc leadout', self.leadout
last = self.tracks[-1]
leadout = self.getTrackEnd(last.number) + 1
- self.debug('leadout LBA: %d', leadout)
+ logger.debug('leadout LBA: %d', leadout)
# FIXME: we can't replace these calculations with the getFrameLength
# call because the start and leadout in the algorithm get rounded
@@ -313,9 +316,9 @@ class Table(object, log.Loggable):
result.insert(0, value)
# compare this debug line to cd-discid output
- self.debug('cddb values: %r', result)
+ logger.debug('cddb values: %r', result)
- self.debug('cddb disc id debug: %s',
+ logger.debug('cddb disc id debug: %s',
" ".join(["%08x" % value, ] + debug))
return result
@@ -338,8 +341,8 @@ class Table(object, log.Loggable):
@returns: the 28-character base64-encoded disc ID
"""
if self.mbdiscid:
- self.log('getMusicBrainzDiscId: returning cached %r'
- % self.mbdiscid)
+ logger.debug('getMusicBrainzDiscId: returning cached %r'
+ % self.mbdiscid)
return self.mbdiscid
values = self._getMusicBrainzValues()
@@ -387,7 +390,7 @@ class Table(object, log.Loggable):
assert len(result) == 28, \
"Result should be 28 characters, not %d" % len(result)
- self.log('getMusicBrainzDiscId: returning %r' % result)
+ logger.debug('getMusicBrainzDiscId: returning %r' % result)
self.mbdiscid = result
return result
@@ -419,7 +422,7 @@ class Table(object, log.Loggable):
last = self.tracks[self.getAudioTracks() - 1]
leadout = self.getTrackEnd(last.number) + 1
- self.debug('leadout LBA: %d', leadout)
+ logger.debug('leadout LBA: %d', leadout)
durationFrames = leadout - self.getTrackStart(1)
return durationFrames
@@ -475,7 +478,7 @@ class Table(object, log.Loggable):
pass
- self.log('Musicbrainz values: %r', result)
+ logger.debug('Musicbrainz values: %r', result)
return result
def getAccurateRipIds(self):
@@ -533,7 +536,7 @@ class Table(object, log.Loggable):
@rtype: C{unicode}
"""
- self.debug('generating .cue for cuePath %r', cuePath)
+ logger.debug('generating .cue for cuePath %r', cuePath)
lines = []
@@ -541,7 +544,7 @@ class Table(object, log.Loggable):
targetPath = common.getRelativePath(path, cuePath)
line = 'FILE "%s" WAVE' % targetPath
lines.append(line)
- self.debug('writeFile: %r' % line)
+ logger.debug('writeFile: %r' % line)
# header
main = ['PERFORMER', 'TITLE']
@@ -582,11 +585,11 @@ class Table(object, log.Loggable):
counter = index.counter
if index.path:
- self.debug('counter %d, writeFile' % counter)
+ logger.debug('counter %d, writeFile' % counter)
writeFile(index.path)
for i, track in enumerate(self.tracks):
- self.debug('track i %r, track %r' % (i, track))
+ logger.debug('track i %r, track %r' % (i, track))
# FIXME: skip data tracks for now
if not track.audio:
continue
@@ -598,7 +601,7 @@ class Table(object, log.Loggable):
for number in indexes:
index = track.indexes[number]
- self.debug('index %r, %r' % (number, index))
+ logger.debug('index %r, %r' % (number, index))
# any time the source counter changes to a higher value,
# write a FILE statement
@@ -606,9 +609,9 @@ class Table(object, log.Loggable):
# at counter 0 here
if index.counter > counter:
if index.path:
- self.debug('counter %d, writeFile' % counter)
+ logger.debug('counter %d, writeFile' % counter)
writeFile(index.path)
- self.debug('setting counter to index.counter %r' %
+ logger.debug('setting counter to index.counter %r' %
index.counter)
counter = index.counter
@@ -617,7 +620,7 @@ class Table(object, log.Loggable):
wroteTrack = True
line = " TRACK %02d %s" % (i + 1, 'AUDIO')
lines.append(line)
- self.debug('%r' % line)
+ logger.debug('%r' % line)
for key in CDTEXT_FIELDS:
if key in track.cdtext:
@@ -667,11 +670,11 @@ class Table(object, log.Loggable):
index = self.tracks[0].getFirstIndex()
i = index.number
- self.debug('clearing path')
+ logger.debug('clearing path')
while True:
track = self.tracks[t - 1]
index = track.getIndex(i)
- self.debug('Clearing path on track %d, index %d', t, i)
+ logger.debug('Clearing path on track %d, index %d', t, i)
index.path = None
index.relative = None
try:
@@ -690,7 +693,7 @@ class Table(object, log.Loggable):
@type track: C{int}
@type index: C{int}
"""
- self.debug('setFile: track %d, index %d, path %r, '
+ logger.debug('setFile: track %d, index %d, path %r, '
'length %r, counter %r', track, index, path, length, counter)
t = self.tracks[track - 1]
@@ -704,7 +707,7 @@ class Table(object, log.Loggable):
i.path = path
i.relative = i.absolute - start
i.counter = counter
- self.debug('Setting path %r, relative %r on '
+ logger.debug('Setting path %r, relative %r on '
'track %d, index %d, counter %r',
path, i.relative, track, index, counter)
try:
@@ -726,19 +729,19 @@ class Table(object, log.Loggable):
counter = index.counter
#for t in self.tracks: print t, t.indexes
- self.debug('absolutizing')
+ logger.debug('absolutizing')
while True:
track = self.tracks[t - 1]
index = track.getIndex(i)
assert track.number == t
assert index.number == i
if index.counter is None:
- self.debug('Track %d, index %d has no counter', t, i)
+ logger.debug('Track %d, index %d has no counter', t, i)
break
if index.counter != counter:
- self.debug('Track %d, index %d has a different counter', t, i)
+ logger.debug('Track %d, index %d has a different counter', t, i)
break
- self.debug('Setting absolute offset %d on track %d, index %d',
+ logger.debug('Setting absolute offset %d on track %d, index %d',
index.relative, t, i)
if index.absolute is not None:
if index.absolute != index.relative:
@@ -772,16 +775,16 @@ class Table(object, log.Loggable):
for i in t.indexes.values():
if i.absolute is not None:
i.absolute += self.leadout + gap
- self.debug('Fixing track %02d, index %02d, absolute %d' % (
+ logger.debug('Fixing track %02d, index %02d, absolute %d' % (
t.number, i.number, i.absolute))
if i.counter is not None:
i.counter += sourceCounter
- self.debug('Fixing track %02d, index %02d, counter %d' % (
+ logger.debug('Fixing track %02d, index %02d, counter %d' % (
t.number, i.number, i.counter))
self.tracks.append(t)
self.leadout += other.leadout + gap # FIXME
- self.debug('Fixing leadout, now %d', self.leadout)
+ logger.debug('Fixing leadout, now %d', self.leadout)
def _getSessionGap(self, session):
# From cdrecord multi-session info:
@@ -836,15 +839,15 @@ class Table(object, log.Loggable):
offsets, as well as the leadout.
"""
if not self.leadout:
- self.debug('no leadout, no TOC')
+ logger.debug('no leadout, no TOC')
return False
for t in self.tracks:
if 1 not in t.indexes.keys():
- self.debug('no index 1, no TOC')
+ logger.debug('no index 1, no TOC')
return False
if t.indexes[1].absolute is None:
- self.debug('no absolute index 1, no TOC')
+ logger.debug('no absolute index 1, no TOC')
return False
return True
@@ -854,13 +857,13 @@ class Table(object, log.Loggable):
Check if this table can be used to generate a .cue file
"""
if not self.hasTOC():
- self.debug('No TOC, cannot cue')
+ logger.debug('No TOC, cannot cue')
return False
for t in self.tracks:
for i in t.indexes.values():
if i.relative is None:
- self.debug('Track %02d, Index %02d does not have relative',
+ logger.debug('Track %02d, Index %02d does not have relative',
t.number, i.number)
return False
diff --git a/morituri/image/toc.py b/morituri/image/toc.py
index 622c262..45ae12f 100644
--- a/morituri/image/toc.py
+++ b/morituri/image/toc.py
@@ -29,9 +29,12 @@ The .toc file format is described in the man page of cdrdao
import re
import codecs
-from morituri.common import common, log
+from morituri.common import common
from morituri.image import table
+import logging
+logger = logging.getLogger(__name__)
+
# shared
_CDTEXT_CANDIDATE_RE = re.compile(r'(?P\w+) "(?P.+)"')
@@ -91,7 +94,7 @@ _INDEX_RE = re.compile(r"""
""", re.VERBOSE)
-class Sources(log.Loggable):
+class Sources:
"""
I represent the list of sources used in the .toc file.
Each SILENCE and each FILE is a source.
@@ -108,7 +111,7 @@ class Sources(log.Loggable):
@type counter: int
@param offset: the absolute disc offset where this source starts
"""
- self.debug('Appending source, counter %d, abs offset %d, source %r' % (
+ logger.debug('Appending source, counter %d, abs offset %d, source %r' % (
counter, offset, source))
self._sources.append((counter, offset, source))
@@ -133,7 +136,7 @@ class Sources(log.Loggable):
return self._sources[-1][1]
-class TocFile(object, log.Loggable):
+class TocFile(object):
def __init__(self, path):
"""
@@ -151,7 +154,7 @@ class TocFile(object, log.Loggable):
absolute = absoluteOffset + trackOffset
# this may be in a new source, so calculate relative
c, o, s = self._sources.get(absolute)
- self.debug('at abs offset %d, we are in source %r' % (
+ logger.debug('at abs offset %d, we are in source %r' % (
absolute, s))
counterStart = self._sources.getCounterStart(c)
relative = absolute - counterStart
@@ -160,7 +163,7 @@ class TocFile(object, log.Loggable):
absolute=absolute,
relative=relative,
counter=c)
- self.debug(
+ logger.debug(
'[track %02d index %02d] trackOffset %r, added %r',
currentTrack.number, i, trackOffset,
currentTrack.getIndex(i))
@@ -207,11 +210,11 @@ class TocFile(object, log.Loggable):
# is a limitation of our parser approach
if state == 'HEADER':
self.table.cdtext[key] = value
- self.debug('Found disc CD-Text %s: %r', key, value)
+ logger.debug('Found disc CD-Text %s: %r', key, value)
elif state == 'TRACK':
if key != 'ISRC' or not currentTrack \
or currentTrack.isrc is not None:
- self.debug('Found track CD-Text %s: %r',
+ logger.debug('Found track CD-Text %s: %r',
key, value)
currentTrack.cdtext[key] = value
@@ -219,7 +222,7 @@ class TocFile(object, log.Loggable):
m = _CATALOG_RE.search(line)
if m:
self.table.catalog = m.group('catalog')
- self.debug("Found catalog number %s", self.table.catalog)
+ logger.debug("Found catalog number %s", self.table.catalog)
# look for TRACK lines
m = _TRACK_RE.search(line)
@@ -244,7 +247,7 @@ class TocFile(object, log.Loggable):
totalLength += currentLength
# FIXME: track mode
- self.debug('found track %d, mode %s, at absoluteOffset %d',
+ logger.debug('found track %d, mode %s, at absoluteOffset %d',
trackNumber, trackMode, absoluteOffset)
# reset counters relative to a track
@@ -258,23 +261,23 @@ class TocFile(object, log.Loggable):
m = _PRE_EMPHASIS_RE.search(line)
if m:
currentTrack.pre_emphasis = True
- self.debug('Track has PRE_EMPHASIS')
+ logger.debug('Track has PRE_EMPHASIS')
# look for ISRC lines
m = _ISRC_RE.search(line)
if m:
isrc = m.group('isrc')
currentTrack.isrc = isrc
- self.debug('Found ISRC code %s', isrc)
+ logger.debug('Found ISRC code %s', isrc)
# look for SILENCE lines
m = _SILENCE_RE.search(line)
if m:
length = m.group('length')
- self.debug('SILENCE of %r', length)
+ logger.debug('SILENCE of %r', length)
self._sources.append(counter, absoluteOffset, None)
if currentFile is not None:
- self.debug('SILENCE after FILE, increasing counter')
+ logger.debug('SILENCE after FILE, increasing counter')
counter += 1
relativeOffset = 0
currentFile = None
@@ -284,7 +287,7 @@ class TocFile(object, log.Loggable):
m = _ZERO_RE.search(line)
if m:
if currentFile is not None:
- self.debug('ZERO after FILE, increasing counter')
+ logger.debug('ZERO after FILE, increasing counter')
counter += 1
relativeOffset = 0
currentFile = None
@@ -297,13 +300,13 @@ class TocFile(object, log.Loggable):
filePath = m.group('name')
start = m.group('start')
length = m.group('length')
- self.debug('FILE %s, start %r, length %r',
+ logger.debug('FILE %s, start %r, length %r',
filePath, common.msfToFrames(start),
common.msfToFrames(length))
if not currentFile or filePath != currentFile.path:
counter += 1
relativeOffset = 0
- self.debug('track %d, switched to new FILE, '
+ logger.debug('track %d, switched to new FILE, '
'increased counter to %d',
trackNumber, counter)
currentFile = File(filePath, common.msfToFrames(start),
@@ -319,12 +322,12 @@ class TocFile(object, log.Loggable):
filePath = m.group('name')
length = m.group('length')
# print 'THOMAS', length
- self.debug('FILE %s, length %r',
+ logger.debug('FILE %s, length %r',
filePath, common.msfToFrames(length))
if not currentFile or filePath != currentFile.path:
counter += 1
relativeOffset = 0
- self.debug('track %d, switched to new FILE, '
+ logger.debug('track %d, switched to new FILE, '
'increased counter to %d',
trackNumber, counter)
# FIXME: assume that a MODE2_FORM_MIX track always starts at 0
@@ -345,7 +348,7 @@ class TocFile(object, log.Loggable):
length = common.msfToFrames(m.group('length'))
c, o, s = self._sources.get(absoluteOffset)
- self.debug('at abs offset %d, we are in source %r' % (
+ logger.debug('at abs offset %d, we are in source %r' % (
absoluteOffset, s))
counterStart = self._sources.getCounterStart(c)
relativeOffset = absoluteOffset - counterStart
@@ -353,7 +356,7 @@ class TocFile(object, log.Loggable):
currentTrack.index(0, path=s and s.path or None,
absolute=absoluteOffset,
relative=relativeOffset, counter=c)
- self.debug('[track %02d index 00] added %r',
+ logger.debug('[track %02d index 00] added %r',
currentTrack.number, currentTrack.getIndex(0))
# store the pregapLength to add it when we index 1 for this
# track on the next iteration
@@ -377,7 +380,7 @@ class TocFile(object, log.Loggable):
# totalLength was added up to the penultimate track
self.table.leadout = totalLength + currentLength
- self.debug('parse: leadout: %r', self.table.leadout)
+ logger.debug('parse: leadout: %r', self.table.leadout)
def message(self, number, message):
"""
diff --git a/morituri/program/arc.py b/morituri/program/arc.py
index 1d9a413..1473fd2 100644
--- a/morituri/program/arc.py
+++ b/morituri/program/arc.py
@@ -1,6 +1,8 @@
-import logging
-from subprocess import Popen, PIPE
from os.path import exists
+from subprocess import Popen, PIPE
+
+import logging
+logger = logging.getLogger(__name__)
ARB = 'accuraterip-checksum'
FLAC = 'flac'
@@ -33,18 +35,18 @@ def accuraterip_checksum(f, track, tracks, wave=False, v2=False):
arc_rc = arc.returncode
if not wave and flac_rc != 0:
- logging.warning('ARC calculation failed: flac return code is non zero')
+ logger.warning('ARC calculation failed: flac return code is non zero')
return None
if arc_rc != 0:
- logging.warning('ARC calculation failed: arc return code is non zero')
+ logger.warning('ARC calculation failed: arc return code is non zero')
return None
out = out.strip()
try:
outh = int('0x%s' % out, base=16)
except ValueError:
- logging.warning('ARC output is not usable')
+ logger.warning('ARC output is not usable')
return None
return outh
diff --git a/morituri/program/cdparanoia.py b/morituri/program/cdparanoia.py
index 918b910..2bd6162 100644
--- a/morituri/program/cdparanoia.py
+++ b/morituri/program/cdparanoia.py
@@ -20,21 +20,23 @@
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see .
-import os
import errno
-import time
+import os
import re
-import stat
import shutil
+import stat
import subprocess
import tempfile
+import time
-from morituri.common import log, common
+from morituri.common import common
from morituri.common import task as ctask
-
from morituri.extern import asyncsub
from morituri.extern.task import task
+import logging
+logger = logging.getLogger(__name__)
+
class FileSizeError(Exception):
@@ -78,7 +80,7 @@ _ERROR_RE = re.compile("^scsi_read error:")
# number of single-channel samples, ie. 2 bytes (word) per unit, and absolute
-class ProgressParser(log.Loggable):
+class ProgressParser:
read = 0 # last [read] frame
wrote = 0 # last [wrote] frame
errors = 0 # count of number of scsi errors
@@ -130,12 +132,12 @@ class ProgressParser(log.Loggable):
# set nframes if not yet set
if self._nframes is None and self.read != 0:
self._nframes = frameOffset - self.read
- self.debug('set nframes to %r', self._nframes)
+ logger.debug('set nframes to %r', self._nframes)
# set firstFrames if not yet set
if self._firstFrames is None:
self._firstFrames = frameOffset - self.start
- self.debug('set firstFrames to %r', self._firstFrames)
+ logger.debug('set firstFrames to %r', self._firstFrames)
markStart = None
markEnd = None # the next unread frame (half-inclusive)
@@ -192,7 +194,7 @@ class ProgressParser(log.Loggable):
"""
frames = self.stop - self.start + 1 # + 1 since stop is inclusive
reads = self.reads
- self.debug('getTrackQuality: frames %d, reads %d' % (frames, reads))
+ logger.debug('getTrackQuality: frames %d, reads %d' % (frames, reads))
# don't go over a 100%; we know cdparanoia reads each frame at least
# twice
@@ -202,7 +204,7 @@ class ProgressParser(log.Loggable):
# FIXME: handle errors
-class ReadTrackTask(log.Loggable, task.Task):
+class ReadTrackTask(task.Task):
"""
I am a task that reads a track using cdparanoia.
@@ -271,11 +273,11 @@ class ReadTrackTask(log.Loggable, task.Task):
stopTrack = i + 1
stopOffset = self._stop - self._table.getTrackStart(i + 1)
- self.debug('Ripping from %d to %d (inclusive)',
+ logger.debug('Ripping from %d to %d (inclusive)',
self._start, self._stop)
- self.debug('Starting at track %d, offset %d',
+ logger.debug('Starting at track %d, offset %d',
startTrack, startOffset)
- self.debug('Stopping at track %d, offset %d',
+ logger.debug('Stopping at track %d, offset %d',
stopTrack, stopOffset)
bufsize = 1024
@@ -291,7 +293,7 @@ class ReadTrackTask(log.Loggable, task.Task):
startTrack, common.framesToHMSF(startOffset),
stopTrack, common.framesToHMSF(stopOffset)),
self.path])
- self.debug('Running %s' % (" ".join(argv), ))
+ logger.debug('Running %s' % (" ".join(argv), ))
try:
self._popen = asyncsub.Popen(argv,
bufsize=bufsize,
@@ -333,7 +335,7 @@ class ReadTrackTask(log.Loggable, task.Task):
# fail if too many errors
if self._parser.errors > self._MAXERROR:
- self.debug('%d errors, terminating', self._parser.errors)
+ logger.debug('%d errors, terminating', self._parser.errors)
self._popen.terminate()
num = self._parser.wrote - self._start + 1
@@ -366,13 +368,13 @@ class ReadTrackTask(log.Loggable, task.Task):
expected = offsetLength * common.BYTES_PER_FRAME + 44
if size != expected:
# FIXME: handle errors better
- self.warning('file size %d did not match expected size %d',
+ logger.warning('file size %d did not match expected size %d',
size, expected)
if (size - expected) % common.BYTES_PER_FRAME == 0:
- self.warning('%d frames difference' % (
+ logger.warning('%d frames difference' % (
(size - expected) / common.BYTES_PER_FRAME))
else:
- self.warning('non-integral amount of frames difference')
+ logger.warning('non-integral amount of frames difference')
self.setAndRaiseException(FileSizeError(self.path,
"File size %d did not match expected size %d" % (
@@ -382,7 +384,7 @@ class ReadTrackTask(log.Loggable, task.Task):
if self._errors:
print "\n".join(self._errors)
else:
- self.warning('exit code %r', self._popen.returncode)
+ logger.warning('exit code %r', self._popen.returncode)
self.exception = ReturnCodeError(self._popen.returncode)
self.quality = self._parser.getTrackQuality()
@@ -393,7 +395,7 @@ class ReadTrackTask(log.Loggable, task.Task):
return
-class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
+class ReadVerifyTrackTask(task.MultiSeparateTask):
"""
I am a task that reads and verifies a track using cdparanoia.
I also encode the track.
@@ -449,10 +451,10 @@ class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
"""
task.MultiSeparateTask.__init__(self)
- self.debug('Creating read and verify task on %r', path)
+ logger.debug('Creating read and verify task on %r', path)
if taglist:
- self.debug('read and verify with taglist %r', taglist)
+ logger.debug('read and verify with taglist %r', taglist)
# FIXME: choose a dir on the same disk/dir as the final path
fd, tmppath = tempfile.mkstemp(suffix='.morituri.wav')
tmppath = unicode(tmppath)
@@ -504,7 +506,7 @@ class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
self.quality = max(self.tasks[0].quality,
self.tasks[2].quality)
self.peak = self.tasks[6].peak
- self.debug('peak: %r', self.peak)
+ logger.debug('peak: %r', self.peak)
self.testspeed = self.tasks[0].speed
self.copyspeed = self.tasks[2].speed
self.testduration = self.tasks[0].duration
@@ -513,11 +515,11 @@ class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
self.testchecksum = c1 = self.tasks[1].checksum
self.copychecksum = c2 = self.tasks[3].checksum
if c1 == c2:
- self.info('Checksums match, %08x' % c1)
+ logger.info('Checksums match, %08x' % c1)
self.checksum = self.testchecksum
else:
# FIXME: detect this before encoding
- self.info('Checksums do not match, %08x %08x' % (
+ logger.info('Checksums do not match, %08x %08x' % (
c1, c2))
self.exception = ChecksumException(
'read and verify failed: test checksum')
@@ -531,17 +533,17 @@ class ReadVerifyTrackTask(log.Loggable, task.MultiSeparateTask):
if not self.exception:
try:
- self.debug('Moving to final path %r', self.path)
+ logger.debug('Moving to final path %r', self.path)
os.rename(self._tmppath, self.path)
except Exception, e:
- self.debug('Exception while moving to final path %r: '
+ logger.debug('Exception while moving to final path %r: '
'%r',
- self.path, log.getExceptionMessage(e))
+ self.path, str(e))
self.exception = e
else:
os.unlink(self._tmppath)
else:
- self.debug('stop: exception %r', self.exception)
+ logger.debug('stop: exception %r', self.exception)
except Exception, e:
print 'WARNING: unhandled exception %r' % (e, )
diff --git a/morituri/program/cdrdao.py b/morituri/program/cdrdao.py
index 95ba4f6..97a7928 100644
--- a/morituri/program/cdrdao.py
+++ b/morituri/program/cdrdao.py
@@ -1,4 +1,3 @@
-import logging
import os
import re
import tempfile
@@ -6,6 +5,9 @@ from subprocess import check_call, Popen, PIPE, CalledProcessError
from morituri.image.toc import TocFile
+import logging
+logger = logging.getLogger(__name__)
+
CDRDAO = 'cdrdao'
def read_toc(device, fast_toc=False):
@@ -28,7 +30,7 @@ def read_toc(device, fast_toc=False):
try:
check_call(cmd, stdout=PIPE, stderr=PIPE)
except CalledProcessError, e:
- logging.warning('cdrdao read-toc failed: return code is non-zero: ' +
+ logger.warning('cdrdao read-toc failed: return code is non-zero: ' +
str(e.returncode))
raise e
toc = TocFile(tocfile)
@@ -43,14 +45,14 @@ def version():
cdrdao = Popen(CDRDAO, stderr=PIPE)
out, err = cdrdao.communicate()
if cdrdao.returncode != 1:
- logging.warning("cdrdao version detection failed: "
- "return code is " + str(cdrdao.returncode))
+ logger.warning("cdrdao version detection failed: "
+ "return code is " + str(cdrdao.returncode))
return None
m = re.compile(r'^Cdrdao version (?P.*) - \(C\)').search(
err.decode('utf-8'))
if not m:
- logging.warning("cdrdao version detection failed: "
- + "could not find version")
+ logger.warning("cdrdao version detection failed: "
+ "could not find version")
return None
return m.group('version')
diff --git a/morituri/program/sox.py b/morituri/program/sox.py
index 93030e3..c8efa28 100644
--- a/morituri/program/sox.py
+++ b/morituri/program/sox.py
@@ -1,7 +1,9 @@
-import logging
import os
from subprocess import Popen, PIPE
+import logging
+logger = logging.getLogger(__name__)
+
SOX = 'sox'
def peak_level(track_path):
@@ -12,12 +14,12 @@ def peak_level(track_path):
Returns None on error.
"""
if not os.path.exists(track_path):
- logging.warning("SoX peak detection failed: file not found")
+ logger.warning("SoX peak detection failed: file not found")
return None
sox = Popen([SOX, track_path, "-n", "stat"], stderr=PIPE)
out, err = sox.communicate()
if sox.returncode:
- logging.warning("SoX peak detection failed: " + str(sox.returncode))
+ logger.warning("SoX peak detection failed: " + str(sox.returncode))
return None
# relevant captured line looks like:
# Maximum amplitude: 0.123456
diff --git a/morituri/program/soxi.py b/morituri/program/soxi.py
index 4473000..36fb03a 100644
--- a/morituri/program/soxi.py
+++ b/morituri/program/soxi.py
@@ -1,11 +1,14 @@
import os
-from morituri.common import log, common
+from morituri.common import common
from morituri.common import task as ctask
+import logging
+logger = logging.getLogger(__name__)
+
SOXI = 'soxi'
-class AudioLengthTask(ctask.PopenTask, log.Loggable):
+class AudioLengthTask(ctask.PopenTask):
"""
I calculate the length of a track in audio samples.
@@ -42,5 +45,5 @@ class AudioLengthTask(ctask.PopenTask, log.Loggable):
def done(self):
if self._error:
- self.warning("soxi reported on stderr: %s", "".join(self._error))
+ logger.warning("soxi reported on stderr: %s", "".join(self._error))
self.length = int("".join(self._output))
diff --git a/morituri/rip/common.py b/morituri/rip/common.py
deleted file mode 100644
index e931646..0000000
--- a/morituri/rip/common.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- Mode: Python -*-
-# vi:si:et:sw=4:sts=4:ts=4
-
-# options and arguments shared between commands
-
-DEFAULT_TRACK_TEMPLATE = u'%r/%A - %d/%t. %a - %n'
-DEFAULT_DISC_TEMPLATE = u'%r/%A - %d/%A - %d'
-
-TEMPLATE_DESCRIPTION = '''
-Tracks are named according to the track template, filling in the variables
-and adding the file extension. Variables exclusive to the track template are:
- - %t: track number
- - %a: track artist
- - %n: track title
- - %s: track sort name
-
-Disc files (.cue, .log, .m3u) are named according to the disc template,
-filling in the variables and adding the file extension. Variables for both
-disc and track template are:
- - %A: album artist
- - %S: album sort name
- - %d: disc title
- - %y: release year
- - %r: release type, lowercase
- - %R: Release type, normal case
- - %x: audio extension, lowercase
- - %X: audio extension, uppercase
-
-'''
-
-def addTemplate(obj):
- # FIXME: get from config
- obj.parser.add_option('', '--track-template',
- action="store", dest="track_template",
- help="template for track file naming (default %default)",
- default=DEFAULT_TRACK_TEMPLATE)
- obj.parser.add_option('', '--disc-template',
- action="store", dest="disc_template",
- help="template for disc file naming (default %default)",
- default=DEFAULT_DISC_TEMPLATE)
diff --git a/morituri/rip/main.py b/morituri/rip/main.py
deleted file mode 100644
index 93ad000..0000000
--- a/morituri/rip/main.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- Mode: Python -*-
-# vi:si:et:sw=4:sts=4:ts=4
-
-import os
-import sys
-import pkg_resources
-import musicbrainzngs
-
-from morituri.common import log, logcommand, common, config, directory
-from morituri.configure import configure
-from morituri.extern.command import command
-from morituri.extern.task import task
-from morituri.rip import cd, offset, drive, image, accurip, debug
-
-
-def main():
- # set user agent
- musicbrainzngs.set_useragent("morituri", configure.version,
- 'https://thomas.apestaart.org/morituri/trac')
- # register plugins with pkg_resources
- distributions, _ = pkg_resources.working_set.find_plugins(
- pkg_resources.Environment([directory.data_path('plugins')])
- )
- map(pkg_resources.working_set.add, distributions)
- c = Rip()
- try:
- ret = c.parse(sys.argv[1:])
- except SystemError, e:
- sys.stderr.write('rip: error: %s\n' % e.args)
- return 255
- except ImportError, e:
- raise ImportError(e)
- except task.TaskException, e:
- if isinstance(e.exception, ImportError):
- raise ImportError(e.exception)
- elif isinstance(e.exception, common.MissingDependencyException):
- sys.stderr.write('rip: error: missing dependency "%s"\n' %
- e.exception.dependency)
- return 255
-
- if isinstance(e.exception, common.EmptyError):
- log.debug('main',
- "EmptyError: %r", log.getExceptionMessage(e.exception))
- sys.stderr.write(
- 'rip: error: Could not create encoded file.\n')
- return 255
-
- # in python3 we can instead do `raise e.exception` as that would show
- # the exception's original context
- sys.stderr.write(e.exceptionMessage)
- return 255
- except command.CommandError, e:
- sys.stderr.write('rip: error: %s\n' % e.output)
- return e.status
-
- if ret is None:
- return 0
-
- return ret
-
-
-class Rip(logcommand.LogCommand):
- usage = "%prog %command"
- description = """Rip rips CD's.
-
-Rip gives you a tree of subcommands to work with.
-You can get help on subcommands by using the -h option to the subcommand.
-"""
-
- subCommandClasses = [accurip.AccuRip,
- cd.CD, debug.Debug, drive.Drive, offset.Offset, image.Image, ]
-
- def addOptions(self):
- # FIXME: is this the right place ?
- log.init()
- log.debug("morituri", "This is morituri version %s (%s)",
- configure.version, configure.revision)
-
- self.parser.add_option('-R', '--record',
- action="store_true", dest="record",
- help="record API requests for playback")
- self.parser.add_option('-v', '--version',
- action="store_true", dest="version",
- help="show version information")
-
- def handleOptions(self, options):
- if options.version:
- print "rip %s" % configure.version
- sys.exit(0)
-
- self.record = options.record
-
- self.config = config.Config()
-
- def parse(self, argv):
- log.debug("morituri", "rip %s" % " ".join(argv))
- logcommand.LogCommand.parse(self, argv)
diff --git a/morituri/test/bloc.cue b/morituri/test/bloc.cue
index 5b5519c..8c2fd08 100644
--- a/morituri/test/bloc.cue
+++ b/morituri/test/bloc.cue
@@ -1,5 +1,5 @@
REM DISCID AD0BE00D
-REM COMMENT "morituri 0.3.0"
+REM COMMENT "morituri 0.4.0"
FILE "data.wav" WAVE
TRACK 01 AUDIO
PREGAP 03:22:70
diff --git a/morituri/test/breeders.cue b/morituri/test/breeders.cue
index ab4390d..aa326f1 100644
--- a/morituri/test/breeders.cue
+++ b/morituri/test/breeders.cue
@@ -1,5 +1,5 @@
REM DISCID BE08990D
-REM COMMENT "morituri 0.3.0"
+REM COMMENT "morituri 0.4.0"
CATALOG 0652637280326
PERFORMER "THE BREEDERS"
TITLE "MOUNTAIN BATTLES"
diff --git a/morituri/test/common.py b/morituri/test/common.py
index aebf004..5195ce2 100644
--- a/morituri/test/common.py
+++ b/morituri/test/common.py
@@ -8,11 +8,8 @@ import sys
# twisted's unittests have skip support, standard unittest don't
from twisted.trial import unittest
-from morituri.common import log
from morituri.configure import configure
-log.init()
-
# lifted from flumotion
@@ -44,7 +41,7 @@ def diffStrings(orig, new, desc='input'):
desc=desc)
-class TestCase(log.Loggable, unittest.TestCase):
+class TestCase(unittest.TestCase):
# unittest.TestCase.failUnlessRaises does not return the exception,
# and we'd like to check for the actual exception under TaskException,
# so override the way twisted.trial.unittest does, without failure
@@ -55,13 +52,13 @@ class TestCase(log.Loggable, unittest.TestCase):
except exception, inst:
return inst
except exception, e:
- raise self.failureException('%s raised instead of %s:\n %s'
- % (sys.exc_info()[0],
- exception.__name__,
- log.getExceptionMessage(e)))
+ raise Exception('%s raised instead of %s:\n %s' %
+ (sys.exec_info()[0], exception.__name__, str(e))
+ )
else:
- raise self.failureException('%s not raised (%r returned)'
- % (exception.__name__, result))
+ raise Exception('%s not raised (%r returned)' %
+ (exception.__name__, result)
+ )
assertRaises = failUnlessRaises
diff --git a/morituri/test/cure.cue b/morituri/test/cure.cue
index 2eab2b8..8b7ac9a 100644
--- a/morituri/test/cure.cue
+++ b/morituri/test/cure.cue
@@ -1,5 +1,5 @@
REM DISCID B90C650D
-REM COMMENT "morituri 0.3.0"
+REM COMMENT "morituri 0.4.0"
CATALOG 0602517642256
FILE "data.wav" WAVE
TRACK 01 AUDIO
diff --git a/morituri/test/test_common_program.py b/morituri/test/test_common_program.py
index d49c8bb..0cc4601 100644
--- a/morituri/test/test_common_program.py
+++ b/morituri/test/test_common_program.py
@@ -9,7 +9,7 @@ import unittest
from morituri.result import result
from morituri.common import program, accurip, mbngs, config
-from morituri.rip import common as rcommon
+from morituri.command.cd import DEFAULT_DISC_TEMPLATE
class TrackImageVerifyTestCase(unittest.TestCase):
@@ -89,7 +89,7 @@ class PathTestCase(unittest.TestCase):
def testStandardTemplateEmpty(self):
prog = program.Program(config.Config())
- path = prog.getPath(u'/tmp', rcommon.DEFAULT_DISC_TEMPLATE,
+ path = prog.getPath(u'/tmp', DEFAULT_DISC_TEMPLATE,
'mbdiscid', 0)
self.assertEquals(path,
u'/tmp/unknown/Unknown Artist - mbdiscid/Unknown Artist - mbdiscid')
@@ -101,7 +101,7 @@ class PathTestCase(unittest.TestCase):
md.title = 'Grace'
prog.metadata = md
- path = prog.getPath(u'/tmp', rcommon.DEFAULT_DISC_TEMPLATE,
+ path = prog.getPath(u'/tmp', DEFAULT_DISC_TEMPLATE,
'mbdiscid', 0)
self.assertEquals(path,
u'/tmp/unknown/Jeff Buckley - Grace/Jeff Buckley - Grace')
diff --git a/morituri/test/test_image_image.py b/morituri/test/test_image_image.py
index b9a1d02..c0221c8 100644
--- a/morituri/test/test_image_image.py
+++ b/morituri/test/test_image_image.py
@@ -10,14 +10,12 @@ gobject.threads_init()
import gst
from morituri.image import image
-from morituri.common import common, log
+from morituri.common import common
from morituri.extern.task import task, gstreamer
from morituri.test import common as tcommon
-log.init()
-
def h(i):
return "0x%08x" % i
diff --git a/setup.py b/setup.py
index 9ac84b1..a1b5c69 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ setup(
packages=find_packages(),
entry_points = {
'console_scripts': [
- 'whipper = morituri.rip.main:main'
+ 'whipper = morituri.command.main:main'
]
}
)