diff options
| author | mikeos <mike.osipov@gmail.com> | 2013-09-28 16:38:17 +0400 |
|---|---|---|
| committer | mikeos <mike.osipov@gmail.com> | 2013-09-28 16:38:17 +0400 |
| commit | 07aa2154f13a2c90ac3f98b676cddd4e2b1a82b8 (patch) | |
| tree | 15e69b7874d1e95570a536bbfe69a5a78e85f058 | |
| parent | 1bb90cf78b14c4cc228826e4d5443f4de047ed24 (diff) | |
support of different output encoding formats
| -rw-r--r-- | config.py | 2 | ||||
| -rwxr-xr-x | cutter | 90 | ||||
| -rw-r--r-- | formats/__base__.py | 25 | ||||
| -rw-r--r-- | formats/__init__.py | 24 | ||||
| -rw-r--r-- | formats/flac.py | 39 | ||||
| -rw-r--r-- | formats/mp3.py | 49 |
6 files changed, 187 insertions, 42 deletions
@@ -44,8 +44,10 @@ cfg = CfgParser() cfg.read(os.path.expanduser("~/.cutter.cfg")) DIR = cfg.get("encoding", "dir", ".") +TYPE = cfg.get("encoding", "type") USE_TEMPDIR = cfg.getbool("encoding", "use_tempdir") COMPRESSION = cfg.getint("encoding", "compression") +BITRATE = cfg.getint("encoding", "bitrate") SAMPLE_RATE = cfg.getint("output", "sample_rate") CHANNELS = cfg.getint("output", "channels") @@ -3,12 +3,14 @@ from utils import to_unicode, to_bytes from cue import read_cue +import formats + from optparse import OptionParser, OptionGroup -from subprocess import Popen, PIPE from tempfile import mkdtemp from itertools import chain import collections +import subprocess import shutil import sys import os @@ -27,7 +29,11 @@ ILLEGAL_CHARACTERS_MAP = { progname = os.path.basename(sys.argv[0]) def printf(fmt, *args): - sys.stdout.write(fmt % args) + out = fmt % args + sys.stdout.write(out) + + if out[-1] != '\n': + sys.stdout.flush() def printerr(fmt, *args): msg = fmt % args @@ -135,6 +141,10 @@ def parse_args(): enc = OptionGroup(parser, "Encoding options") + enc.add_option("-t", "--type", dest="type", + choices = formats.supported(), + help="output file format") + enc.add_option("--coding", dest="coding", help="encoding of original text") @@ -151,7 +161,12 @@ def parse_args(): enc.add_option("-C", "--compression", type="int", dest="compression", default=config.COMPRESSION, - help="compression factor for output format") + metavar="FACTOR", + help="compression factor for output format (used for flac)") + + enc.add_option("--bitrate", type="int", + dest="bitrate", default=config.BITRATE, + help="audio bitrate (used for mp3)") parser.add_option_group(enc) @@ -199,9 +214,24 @@ def parse_args(): return parser.parse_args() def process_options(opt): - if opt.compression is not None and opt.compression < 0 or opt.compression > 8: + if opt.compression is not None and (opt.compression < 0 or opt.compression > 8): printerr("invalid compression value %d, must be in range 0 .. 8", opt.compression) - sys.exit(1) + return False + + if opt.bitrate is not None and (opt.bitrate < 32 or opt.bitrate > 320): + printerr("invalid bitrate value %d, must be in range 32 .. 320", opt.bitrate) + return False + + if opt.type is None and config.TYPE: + if not formats.issupported(config.TYPE): + printerr("invalid configuration: type '%s' is not supported", config.TYPE) + return False + + opt.type = config.TYPE + + if not opt.dump and opt.type is None: + printerr("--type option is missed") + return False if not opt.dir: opt.dir = u"." @@ -211,7 +241,7 @@ def process_options(opt): opt.fmt = to_unicode(opt.fmt) if not os.path.basename(opt.fmt): printerr("invalid format option \"%s\"", opt.fmt) - sys.exit(1) + return False else: opt.fmt = os.path.normpath(opt.fmt) if opt.fmt.startswith("/"): @@ -222,17 +252,7 @@ def process_options(opt): if opt.use_tempdir is None: opt.use_tempdir = config.USE_TEMPDIR -def get_encoder_formart(opt, info): - cmd = "flac sox - " - if opt.compression is not None: - cmd += "-C %d " % opt.compression - if opt.sample_rate and opt.sample_rate != info.sample_rate: - cmd += "-r %d " % opt.sample_rate - if opt.bits_per_sample and opt.bits_per_sample != info.bits_per_sample: - cmd += "-b %d " % opt.bits_per_sample - if opt.channels and opt.channels != info.channels: - cmd += "-c %d " % opt.channels - return cmd + "%f" + return True def filterdir(dir, prefix): return sorted(filter(lambda f: f.startswith(prefix), os.listdir(dir))) @@ -258,7 +278,7 @@ class StreamInfo: @staticmethod def get(name): info = StreamInfo() - proc = Popen(["shninfo", name], stdout = PIPE) + proc = subprocess.Popen(["shninfo", name], stdout = subprocess.PIPE) for line in proc.stdout.readlines(): data = line.split() attr = StreamInfo.__mapping.get(data[0]) @@ -274,7 +294,6 @@ class StreamInfo: class CueSplitter: EXT = ["ape", "flac", "wv"] - TRACK_SUFFIX = "flac" class File: def __init__(self, fileobj, name, info): @@ -314,6 +333,8 @@ class CueSplitter: self.opt = opt self.tracktotal = len(list(self.all_tracks())) + self.enctype = formats.handler(opt.type, logger=printf) + self.tags = { "album": self.opt.album or self.cue.get("title"), "date": self.opt.date or self.cue.get("date"), @@ -360,7 +381,7 @@ class CueSplitter: if self.opt.convert_chars: name = convert_characters(name) - return self.TrackInfo(name + "." + self.TRACK_SUFFIX, tags) + return self.TrackInfo(name + "." + self.enctype.ext, tags) def find_realfile(self, name): if not name.endswith(".wav"): @@ -400,7 +421,7 @@ class CueSplitter: return lst def shntool_args(self, tool, info): - encode = get_encoder_formart(self.opt, info) + encode = self.enctype.encode(self.opt, info) return [tool, "-w", "-d", self.dest, "-o", encode] def track_name(self, track): @@ -410,27 +431,13 @@ class CueSplitter: return self.track_info[track].tags def tag(self, track, path): - tags = self.track_tags(track) - args = ["metaflac", "--remove-all-tags", "--import-tags-from=-", path] - printf("Tag [%s] : ", path) - sys.stdout.flush() - - proc = Popen(args, stdin = PIPE) - for k, v in tags.items(): - if v is not "": - proc.stdin.write(to_bytes("%s=%s\n" % (k.upper(), v))) - proc.stdin.close() - - if proc.wait(): - printf("FAILED\n") + if not self.enctype.tag(path, self.track_tags(track)): sys.exit(1) - printf("OK\n") - def copy_file(self, file): noteq = lambda a, b: a and a != b - if file.info.type != "flac": + if file.info.type != self.encode.name: return False if noteq(self.opt.sample_rate, file.info.sample_rate): return False @@ -448,7 +455,6 @@ class CueSplitter: return True printf("Copy [%s] --> [%s] : ", file.name, path) - sys.stdout.flush() try: shutil.copyfile(file.name, path) @@ -479,7 +485,7 @@ class CueSplitter: printerr("create symlink %s failed: %s", quote(trackname), err) sys.exit(1) - ret = Popen(args + [str(link)]).wait() + ret = subprocess.call(args + [str(link)]) link.remove() if ret: @@ -495,7 +501,7 @@ class CueSplitter: debug("run %s", " ".join(map(quote, args))) return - proc = Popen(args, stdin = PIPE) + proc = subprocess.Popen(args, stdin = subprocess.PIPE) proc.stdin.write(to_bytes("\n".join(map(str, points)))) proc.stdin.close() @@ -534,7 +540,6 @@ class CueSplitter: continue printf("Copy [%s] into [%s] : ", file, dest) - sys.stdout.flush() try: shutil.copy(path, dest) @@ -604,7 +609,8 @@ def find_cuefile(path): def main(): options, args = parse_args() - process_options(options) + if not process_options(options): + sys.exit(1) if len(args) != 1: printf("Usage: %s [options] cuefile\n", progname) diff --git a/formats/__base__.py b/formats/__base__.py new file mode 100644 index 0000000..4bb6598 --- /dev/null +++ b/formats/__base__.py @@ -0,0 +1,25 @@ +class BaseHandler: + def __init__(self, logger = None): + self.logger = logger + self.buf = [] + + def log(self, fmt, *args): + if self.logger is not None: + self.logger(fmt, *args) + + def add(self, *args): + self.buf.extend(args) + + def build(self, join=True): + data = " ".join(self.buf) if join else self.buf + self.buf = [] + + return data + + def add_sox_args(self, opt, info): + if opt.sample_rate and opt.sample_rate != info.sample_rate: + self.add("-r %d" % opt.sample_rate) + if opt.bits_per_sample and opt.bits_per_sample != info.bits_per_sample: + self.add("-b %d" % opt.bits_per_sample) + if opt.channels and opt.channels != info.channels: + self.add("-c %d" % opt.channels) diff --git a/formats/__init__.py b/formats/__init__.py new file mode 100644 index 0000000..7ebd9fd --- /dev/null +++ b/formats/__init__.py @@ -0,0 +1,24 @@ +import importlib +import os + +path = os.path.dirname(__file__) or "." + +__formats = {} + +for entry in sorted(os.listdir(path)): + if not entry.endswith(".py") or entry.startswith("_"): + continue + + modname = entry.replace(".py", "") + mod = __import__(modname, globals(), locals(), ["init"], 1) + fmt = mod.init() + __formats[fmt.name] = fmt + +def supported(): + return sorted(__formats.keys()) + +def issupported(name): + return name in __formats + +def handler(name, logger = None): + return __formats.get(name)(logger) diff --git a/formats/flac.py b/formats/flac.py new file mode 100644 index 0000000..258b122 --- /dev/null +++ b/formats/flac.py @@ -0,0 +1,39 @@ +from formats.__base__ import * +from utils import to_bytes + +import subprocess + +class FlacHandler(BaseHandler): + name = "flac" + ext = "flac" + + def encode(self, opt, info): + self.add("flac sox -") + + if opt.compression is not None: + self.add("-C %d" % opt.compression) + + self.add_sox_args(opt, info) + self.add("%f") + + return self.build() + + def tag(self, path, tags): + args = ["metaflac", "--remove-all-tags", "--import-tags-from=-", path] + self.log("Tag [%s] : ", path) + + proc = subprocess.Popen(args, stdin = subprocess.PIPE) + for k, v in tags.items(): + if v is not "": + proc.stdin.write(to_bytes("%s=%s\n" % (k.upper(), v))) + proc.stdin.close() + + if proc.wait(): + self.log("FAILED\n") + return False + + self.log("OK\n") + return True + +def init(): + return FlacHandler diff --git a/formats/mp3.py b/formats/mp3.py new file mode 100644 index 0000000..0c91911 --- /dev/null +++ b/formats/mp3.py @@ -0,0 +1,49 @@ +from formats.__base__ import * +from utils import to_bytes + +import subprocess + +class Mp3Handler(BaseHandler): + name = "mp3" + ext = "mp3" + + __tag_opts = { + "album": "-a", + "artist": "-A", + "date": "-y", + "title": "-t" + } + + def encode(self, opt, info): + self.add("cust ext=%s sox -" % self.ext) + + if opt.bitrate is not None: + self.add("-C %d" % opt.bitrate) + + self.add_sox_args(opt, info) + self.add("%f") + + return self.build() + + def tag(self, path, tags): + self.add("id3v2", "--id3v1-only") + + for k, v in tags.items(): + if k in self.__tag_opts and v: + self.add(self.__tag_opts[k]) + self.add(v) + + self.add("-T", "%d/%d" % (tags["tracknumber"], tags["tracktotal"])) + self.add(path) + + self.log("Tag [%s] : ", path) + + if subprocess.call(self.build(False)): + self.log("FAILED\n") + return False + + self.log("OK\n") + return True + +def init(): + return Mp3Handler |
