summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormikeos <mike.osipov@gmail.com>2013-09-28 16:38:17 +0400
committermikeos <mike.osipov@gmail.com>2013-09-28 16:38:17 +0400
commit07aa2154f13a2c90ac3f98b676cddd4e2b1a82b8 (patch)
tree15e69b7874d1e95570a536bbfe69a5a78e85f058
parent1bb90cf78b14c4cc228826e4d5443f4de047ed24 (diff)
support of different output encoding formats
-rw-r--r--config.py2
-rwxr-xr-xcutter90
-rw-r--r--formats/__base__.py25
-rw-r--r--formats/__init__.py24
-rw-r--r--formats/flac.py39
-rw-r--r--formats/mp3.py49
6 files changed, 187 insertions, 42 deletions
diff --git a/config.py b/config.py
index 7baf99a..29cd396 100644
--- a/config.py
+++ b/config.py
@@ -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")
diff --git a/cutter b/cutter
index fc53c14..8b775a1 100755
--- a/cutter
+++ b/cutter
@@ -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