summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormikeos <mike.osipov@gmail.com>2013-09-30 04:36:57 +0400
committermikeos <mike.osipov@gmail.com>2013-09-30 04:36:57 +0400
commit0ae5a430e4f92ee7bf9e458cf584a0a12fd5c25a (patch)
treeda2f685caf97a8c51991360b97b852466f7ca765
parent5fb3e2f167ba4a742dc84ed58ddd68c8374a1195 (diff)
refactoring
-rw-r--r--coding.py (renamed from utils.py)0
-rw-r--r--config.py2
-rw-r--r--cue.py2
-rw-r--r--cuedump.py8
-rwxr-xr-xcutter439
-rw-r--r--formats/flac.py2
-rw-r--r--formats/mp3.py2
-rw-r--r--formats/ogg.py2
-rw-r--r--splitter.py403
-rw-r--r--tools.py26
10 files changed, 449 insertions, 437 deletions
diff --git a/utils.py b/coding.py
index ffae410..ffae410 100644
--- a/utils.py
+++ b/coding.py
diff --git a/config.py b/config.py
index 1348767..c9e8b96 100644
--- a/config.py
+++ b/config.py
@@ -1,4 +1,4 @@
-from utils import is_python_v2, to_unicode
+from coding import is_python_v2, to_unicode
import os
try:
diff --git a/cue.py b/cue.py
index 7967d82..d2fb958 100644
--- a/cue.py
+++ b/cue.py
@@ -319,7 +319,7 @@ def __read_file(filename, coding = None):
return encoded
-def read_cue(filename, coding = None, on_error = None):
+def read(filename, coding = None, on_error = None):
if on_error:
def msg(fmt, *args):
err = CueParserError(fmt % args)
diff --git a/cuedump.py b/cuedump.py
index 660603e..6069e0f 100644
--- a/cuedump.py
+++ b/cuedump.py
@@ -1,7 +1,7 @@
from os.path import basename
import sys
-from cue import read_cue
+import cue
if sys.version_info.major == 2:
class Encoded:
@@ -35,7 +35,7 @@ if len(sys.argv) != 2:
sys.exit(1)
try:
- cue = read_cue(sys.argv[1], on_error = lambda err:\
+ cuesheet = cue.read(sys.argv[1], on_error = lambda err:\
sys.stderr.write("** %s:%d: %s\n" % (progname, err.line, err))
)
except Exception as err:
@@ -43,10 +43,10 @@ except Exception as err:
sys.exit(1)
printf("Cue attributes:\n")
-for k, v in cue.attrs():
+for k, v in cuesheet.attrs():
printf("\t%s = %s\n", k, quote(v))
-for file in cue.files(filter_audio=False):
+for file in cuesheet.files(filter_audio=False):
printf("File %s %s\n", quote(file.name), file.type)
for track in file.tracks(filter_audio=False):
printf("\tTrack %d\n", track.number)
diff --git a/cutter b/cutter
index d3d8890..50f64c1 100755
--- a/cutter
+++ b/cutter
@@ -1,61 +1,23 @@
#!/usr/bin/python
-from utils import to_unicode, to_bytes
-from cue import read_cue
+from coding import to_unicode, to_bytes
+from splitter import Splitter
+from tools import *
import formats
+import cue
from optparse import OptionParser, OptionGroup
-from tempfile import mkdtemp
-from itertools import chain
-import collections
-import subprocess
-import shutil
import sys
import os
-ILLEGAL_CHARACTERS_MAP = {
- u"\\": u"-",
- u":": u"-",
- u"*": u"+",
- u"?": u"_",
- u'"': u"'",
- u"<": u"(",
- u">": u")",
- u"|": u"-"
-}
-
-progname = os.path.basename(sys.argv[0])
-
-def printf(fmt, *args):
- out = fmt % args
- sys.stdout.write(out)
-
- if out[-1] != '\n':
- sys.stdout.flush()
-
-def printerr(fmt, *args):
- msg = fmt % args
- if msg[-1] != "\n":
- msg += "\n"
- sys.stderr.write("** " + progname + ": " + msg)
-
-def debug(fmt, *args):
- msg = fmt % args
- if msg[-1] != "\n":
- msg += "\n"
- sys.stderr.write("-- " + msg)
-
try:
import config
except Exception as err:
printerr("import config failed: %s", err)
sys.exit(0)
-def quote(s, ch = '"'):
- return s if " " not in s else ch + s + ch
-
def msf(ts):
m = ts / (60 * 75)
s = ts / 75 % 60
@@ -63,33 +25,6 @@ def msf(ts):
return "%d:%02d:%02d" % (m, s, f)
-class TempLink:
- def __init__(self, path, name):
- self.tmpdir = mkdtemp(prefix = "temp-")
- self.linkpath = "%s/%s" % (self.tmpdir, name)
-
- try:
- os.symlink(path, self.tmpdir + "/" + name)
- except Exception as err:
- os.rmdir(self.tmpdir)
- raise err
-
- def remove(self):
- os.unlink(self.linkpath)
- os.rmdir(self.tmpdir)
-
- def __repr__(self):
- return "TempLink('%s')" % self.linkpath
-
- def __str__(self):
- return self.linkpath
-
- def __enter__(self):
- return self
-
- def __exit__(self, *args):
- self.remove()
-
def print_cue(cue):
for k, v in cue.attrs():
printf("%s: %s\n", k.upper(), quote(v))
@@ -271,358 +206,6 @@ def process_options(opt):
return True
-def filterdir(dir, prefix):
- return sorted(filter(lambda f: f.startswith(prefix), os.listdir(dir)))
-
-def mkdir(path):
- if not os.path.exists(path):
- try:
- os.makedirs(path)
- except OSError as err:
- printerr("make dir %s failed: %s", quote(path), err)
- sys.exit(1)
-
-def convert_characters(path):
- return "".join([ILLEGAL_CHARACTERS_MAP.get(ch, ch) for ch in path])
-
-class StreamInfo:
- __mapping = {
- b"Channels:": "channels",
- b"Bits/sample:": "bits_per_sample",
- b"Samples/sec:": "sample_rate"
- }
-
- @staticmethod
- def get(name):
- info = StreamInfo()
- proc = subprocess.Popen(["shninfo", name], stdout = subprocess.PIPE)
- for line in proc.stdout.readlines():
- data = line.split()
- attr = StreamInfo.__mapping.get(data[0])
- if attr:
- setattr(info, attr, int(data[1]))
- elif line.startswith(b"Handled by:"):
- info.type = to_unicode(data[2])
-
- if proc.wait():
- return None
-
- return info
-
-class CueSplitter:
- EXT = ["ape", "flac", "wv"]
-
- class File:
- def __init__(self, fileobj, name, info):
- self.fileobj = fileobj
- self.name = name
- self.info = info
-
- def __getattr__(self, attr):
- return getattr(self.fileobj, attr)
-
- class TrackInfo:
- def __init__(self, name, tags):
- self.name = name
- self.tags = tags
-
- @staticmethod
- def format_by_tags(fmt, tags, replace=False):
- if replace:
- def conv(var):
- if isinstance(var, str):
- return var.replace("/", "-")
- return var
-
- tags = {k: conv(v) for k, v in tags.items()}
-
- try:
- return fmt.format(year=tags["date"], **tags)
- except KeyError as err:
- printerr("invalid format key: %s", err)
- sys.exit(1)
- except ValueError as err:
- printerr("invalid format option: %s", err)
- sys.exit(1)
-
- def __init__(self, cue, opt):
- self.cue = cue
- self.opt = opt
- self.tracktotal = len(list(self.all_tracks()))
-
- self.enctype = formats.handler(opt.type, logger=printf)
- self.tag_supported = self.enctype.is_tag_supported()
-
- self.tags = {
- "album": self.opt.album or self.cue.get("title"),
- "date": self.opt.date or self.cue.get("date"),
- "genre": self.opt.genre or self.cue.get("genre"),
- "comment": self.opt.comment or self.cue.get("comment"),
- "composer": self.opt.composer
- or self.cue.get("songwriter"),
- "artist": self.opt.albumartist or self.opt.artist
- or self.cue.get("performer"),
- "albumartist": self.opt.albumartist
- }
-
- tmp = self.format_by_tags(os.path.dirname(opt.fmt), self.tags, True)
-
- if opt.convert_chars:
- tmp = convert_characters(tmp)
-
- self.dest = os.path.join(opt.dir, tmp)
- track_fmt = os.path.basename(opt.fmt)
-
- tracknumber = 0
- self.track_info = {}
- for track in self.all_tracks():
- tracknumber += 1
- self.track_info[track] = self.get_track_info(
- track, tracknumber, track_fmt
- )
-
- def get_track_info(self, track, tracknumber, fmt):
- tags = dict(self.tags)
- tags.update({
- "tracknumber": tracknumber,
- "tracktotal": self.tracktotal,
-
- "title": track.get("title") or "track",
- "artist": self.opt.artist or track.get("performer")
- or self.cue.get("performer"),
- "composer": self.opt.composer or track.get("songwriter")
- or self.cue.get("songwriter")
- })
-
- name = self.format_by_tags(fmt, tags).replace("/", "-")
-
- if self.opt.convert_chars:
- name = convert_characters(name)
-
- return self.TrackInfo(name + "." + self.enctype.ext, tags)
-
- def find_realfile(self, name):
- if not name.endswith(".wav"):
- return None
-
- orig = name.rpartition(".")[0]
- for file in filterdir(self.cue.dir or ".", orig):
- head, _, ext = file.rpartition(".")
- if head == orig and ext in self.EXT:
- return file
-
- return None
-
- def open_files(self):
- lst = []
-
- for file in self.cue.files():
- if not file.has_audio_tracks():
- debug("skip file %s: no tracks", quote(file.name))
- continue
-
- name = self.cue.dir + file.name
- if not os.path.exists(name):
- real = self.find_realfile(file.name)
- if not real:
- printerr("no such file %s", quote(file.name))
- sys.exit(1)
- name = self.cue.dir + real
-
- info = StreamInfo.get(name)
- if info is None:
- printerr("%s: unknown type", quote(file.name))
- sys.exit(1)
-
- lst.append(self.File(file, name, info))
-
- return lst
-
- def shntool_args(self, tool, info):
- encode = self.enctype.encode(self.opt, info)
- return [tool, "-w", "-d", self.dest, "-o", encode]
-
- def track_name(self, track):
- return self.track_info[track].name
-
- def track_tags(self, track):
- return self.track_info[track].tags
-
- def tag(self, track, path):
- if not self.tag_supported:
- return
-
- printf("Tag [%s] : ", path)
- if not self.enctype.tag(path, self.track_tags(track)):
- printf("FAILED\n")
- sys.exit(1)
-
- printf("OK\n")
-
- def copy_file(self, file):
- noteq = lambda a, b: a and a != b
-
- if file.info.type != self.encode.name:
- return False
- if noteq(self.opt.sample_rate, file.info.sample_rate):
- return False
- if noteq(self.opt.bits_per_sample, file.info.bits_per_sample):
- return False
- if noteq(self.opt.channels, file.info.channels):
- return False
-
- track = list(file.tracks())[0]
- trackname = self.track_name(track)
- path = os.path.join(self.dest, trackname)
-
- if self.opt.dry_run:
- printf("Copy [%s] --> [%s]\n", file.name, path)
- return True
-
- printf("Copy [%s] --> [%s] : ", file.name, path)
-
- try:
- shutil.copyfile(file.name, path)
- except Exception as err:
- printf("FAILED: %s\n", err)
- sys.exit(1)
- else:
- printf("OK\n")
-
- self.tag(track, path)
-
- return True
-
- def convert_file(self, file):
- track = list(file.tracks())[0]
- trackname = self.track_name(track)
-
- args = self.shntool_args("shnconv", file.info)
-
- if self.opt.dry_run:
- name = "link to " + quote(file.name, "'")
- debug("run %s", " ".join(map(quote, args + [name])))
- return
-
- try:
- link = TempLink(os.path.abspath(file.name), trackname)
- except OSError as err:
- printerr("create symlink %s failed: %s", quote(trackname), err)
- sys.exit(1)
-
- ret = subprocess.call(args + [str(link)])
- link.remove()
-
- if ret:
- printerr("shnconv failed: exit code %d", ret);
- sys.exit(1)
-
- self.tag(track, os.path.join(self.dest, trackname))
-
- def split_file(self, file, points):
- args = self.shntool_args("shnsplit", file.info) + [file.name]
-
- if self.opt.dry_run:
- debug("run %s", " ".join(map(quote, args)))
- return
-
- proc = subprocess.Popen(args, stdin = subprocess.PIPE)
- proc.stdin.write(to_bytes("\n".join(map(str, points))))
- proc.stdin.close()
-
- if proc.wait():
- printerr("shnsplit failed: exit code %d", proc.returncode)
- sys.exit(1)
-
- splitted = filterdir(self.dest, "split-track")
- for track, filename in zip(file.tracks(), splitted):
- trackname = self.track_name(track)
- path = os.path.join(self.dest, trackname)
-
- printf("Rename [%s] --> [%s] : ", filename, trackname)
- try:
- os.rename(os.path.join(self.dest, filename), path)
- except OSError as err:
- printf("FAILED: %s\n", err)
- sys.exit(1)
- else:
- printf("OK\n")
-
- self.tag(track, path)
-
- def check_duplicates(self):
- names = [x.name for x in self.track_info.values()]
- dup = [k for k, v in collections.Counter(names).items() if v > 1]
- if dup:
- printerr("track names are duplicated: %s", " ".join(dup))
- sys.exit(1)
-
- def transfer_files(self, source, dest):
- for file in sorted(os.listdir(source)):
- path = os.path.join(source, file)
- if not os.path.isfile(path):
- debug("skip non file %s", quote(file))
- continue
-
- printf("Copy [%s] into [%s] : ", file, dest)
-
- try:
- shutil.copy(path, dest)
- except Exception as err:
- printf("FAILED: %s\n", err)
- sys.exit(1)
- else:
- printf("OK\n")
-
- def split(self):
- self.check_duplicates()
-
- files = self.open_files()
-
- self.realpath = None
- if not self.opt.dry_run:
- mkdir(self.dest)
- if self.opt.use_tempdir:
- self.realpath = self.dest
- tempdir = mkdtemp(prefix="cutter-")
- self.dest = to_unicode(tempdir)
-
- for file in files:
- points = list(file.split_points(file.info))
- if not points:
- if not self.copy_file(file):
- self.convert_file(file)
- else:
- self.split_file(file, points)
-
- if self.realpath:
- self.transfer_files(self.dest, self.realpath)
- try:
- shutil.rmtree(self.dest)
- except Exception as err:
- printerr("rm %s failed: %s\n", self.dest, err)
- sys.exit(1)
-
- def all_tracks(self):
- return chain(*[f.tracks() for f in self.cue.files()])
-
- def dump_tags(self):
- add_line = False
- for track in self.all_tracks():
- if add_line:
- printf("\n")
- add_line = True
-
- tags = self.track_tags(track)
- for k, v in sorted(tags.items()):
- if v is not "":
- printf("%s=%s\n", k.upper(), v)
-
- def dump_tracks(self):
- for track in self.all_tracks():
- trackname = self.track_name(track)
- printf("%s\n", os.path.join(self.dest, trackname))
-
def find_cuefile(path):
for file in os.listdir(path):
fullname = os.path.join(path, file)
@@ -653,7 +236,7 @@ def main():
debug("use cue file %s", quote(cuepath))
try:
- cue = read_cue(cuepath, options.coding, on_error=on_error)
+ cuesheet = cue.read(cuepath, options.coding, on_error=on_error)
except StopIteration:
return 1
except IOError as err:
@@ -669,15 +252,15 @@ def main():
return 1
- cue.dir = os.path.dirname(cuepath)
- if cue.dir:
- cue.dir += "/"
+ cuesheet.dir = os.path.dirname(cuepath)
+ if cuesheet.dir:
+ cuesheet.dir += "/"
{
"cue": lambda: print_cue(cue),
- "tags": lambda: CueSplitter(cue, options).dump_tags(),
- "tracks": lambda: CueSplitter(cue, options).dump_tracks(),
- None: lambda: CueSplitter(cue, options).split()
+ "tags": lambda: Splitter(cuesheet, options).dump_tags(),
+ "tracks": lambda: Splitter(cuesheet, options).dump_tracks(),
+ None: lambda: Splitter(cuesheet, options).split()
}[options.dump]()
return 0
diff --git a/formats/flac.py b/formats/flac.py
index 35db1e4..542aba2 100644
--- a/formats/flac.py
+++ b/formats/flac.py
@@ -1,5 +1,5 @@
from formats.__base__ import *
-from utils import to_bytes
+from coding import to_bytes
import subprocess
diff --git a/formats/mp3.py b/formats/mp3.py
index cbd912f..67019cc 100644
--- a/formats/mp3.py
+++ b/formats/mp3.py
@@ -1,5 +1,5 @@
from formats.__base__ import *
-from utils import to_bytes
+from coding import to_bytes
import subprocess
import struct
diff --git a/formats/ogg.py b/formats/ogg.py
index c7352c2..464ba8b 100644
--- a/formats/ogg.py
+++ b/formats/ogg.py
@@ -1,5 +1,5 @@
from formats.__base__ import *
-from utils import to_bytes
+from coding import to_bytes
import subprocess
diff --git a/splitter.py b/splitter.py
new file mode 100644
index 0000000..fbbafff
--- /dev/null
+++ b/splitter.py
@@ -0,0 +1,403 @@
+from coding import to_unicode, to_bytes
+from tools import *
+
+import formats
+
+from tempfile import mkdtemp
+from itertools import chain
+
+import collections
+import subprocess
+import shutil
+import sys
+import os
+
+ILLEGAL_CHARACTERS_MAP = {
+ u"\\": u"-",
+ u":": u"-",
+ u"*": u"+",
+ u"?": u"_",
+ u'"': u"'",
+ u"<": u"(",
+ u">": u")",
+ u"|": u"-"
+}
+
+def filterdir(dir, prefix):
+ return sorted(filter(lambda f: f.startswith(prefix), os.listdir(dir)))
+
+def mkdir(path):
+ if not os.path.exists(path):
+ try:
+ os.makedirs(path)
+ except OSError as err:
+ printerr("make dir %s failed: %s", quote(path), err)
+ sys.exit(1)
+
+def convert_characters(path):
+ return "".join([ILLEGAL_CHARACTERS_MAP.get(ch, ch) for ch in path])
+
+class TempLink:
+ def __init__(self, path, name):
+ self.tmpdir = mkdtemp(prefix = "temp-")
+ self.linkpath = "%s/%s" % (self.tmpdir, name)
+
+ try:
+ os.symlink(path, self.tmpdir + "/" + name)
+ except Exception as err:
+ os.rmdir(self.tmpdir)
+ raise err
+
+ def remove(self):
+ os.unlink(self.linkpath)
+ os.rmdir(self.tmpdir)
+
+ def __repr__(self):
+ return "TempLink('%s')" % self.linkpath
+
+ def __str__(self):
+ return self.linkpath
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.remove()
+
+class StreamInfo:
+ __mapping = {
+ b"Channels:": "channels",
+ b"Bits/sample:": "bits_per_sample",
+ b"Samples/sec:": "sample_rate"
+ }
+
+ @staticmethod
+ def get(name):
+ info = StreamInfo()
+ proc = subprocess.Popen(["shninfo", name], stdout = subprocess.PIPE)
+ for line in proc.stdout.readlines():
+ data = line.split()
+ attr = StreamInfo.__mapping.get(data[0])
+ if attr:
+ setattr(info, attr, int(data[1]))
+ elif line.startswith(b"Handled by:"):
+ info.type = to_unicode(data[2])
+
+ if proc.wait():
+ return None
+
+ return info
+
+class Splitter:
+ EXT = ["ape", "flac", "wv"]
+
+ class File:
+ def __init__(self, fileobj, name, info):
+ self.fileobj = fileobj
+ self.name = name
+ self.info = info
+
+ def __getattr__(self, attr):
+ return getattr(self.fileobj, attr)
+
+ class TrackInfo:
+ def __init__(self, name, tags):
+ self.name = name
+ self.tags = tags
+
+ @staticmethod
+ def format_by_tags(fmt, tags, replace=False):
+ if replace:
+ def conv(var):
+ if isinstance(var, str):
+ return var.replace("/", "-")
+ return var
+
+ tags = {k: conv(v) for k, v in tags.items()}
+
+ try:
+ return fmt.format(year=tags["date"], **tags)
+ except KeyError as err:
+ printerr("invalid format key: %s", err)
+ sys.exit(1)
+ except ValueError as err:
+ printerr("invalid format option: %s", err)
+ sys.exit(1)
+
+ def __init__(self, cue, opt):
+ self.cue = cue
+ self.opt = opt
+ self.tracktotal = len(list(self.all_tracks()))
+
+ self.enctype = formats.handler(opt.type, logger=printf)
+ self.tag_supported = self.enctype.is_tag_supported()
+
+ self.tags = {
+ "album": self.opt.album or self.cue.get("title"),
+ "date": self.opt.date or self.cue.get("date"),
+ "genre": self.opt.genre or self.cue.get("genre"),
+ "comment": self.opt.comment or self.cue.get("comment"),
+ "composer": self.opt.composer
+ or self.cue.get("songwriter"),
+ "artist": self.opt.albumartist or self.opt.artist
+ or self.cue.get("performer"),
+ "albumartist": self.opt.albumartist
+ }
+
+ tmp = self.format_by_tags(os.path.dirname(opt.fmt), self.tags, True)
+
+ if opt.convert_chars:
+ tmp = convert_characters(tmp)
+
+ self.dest = os.path.join(opt.dir, tmp)
+ track_fmt = os.path.basename(opt.fmt)
+
+ tracknumber = 0
+ self.track_info = {}
+ for track in self.all_tracks():
+ tracknumber += 1
+ self.track_info[track] = self.get_track_info(
+ track, tracknumber, track_fmt
+ )
+
+ def get_track_info(self, track, tracknumber, fmt):
+ tags = dict(self.tags)
+ tags.update({
+ "tracknumber": tracknumber,
+ "tracktotal": self.tracktotal,
+
+ "title": track.get("title") or "track",
+ "artist": self.opt.artist or track.get("performer")
+ or self.cue.get("performer"),
+ "composer": self.opt.composer or track.get("songwriter")
+ or self.cue.get("songwriter")
+ })
+
+ name = self.format_by_tags(fmt, tags).replace("/", "-")
+
+ if self.opt.convert_chars:
+ name = convert_characters(name)
+
+ return self.TrackInfo(name + "." + self.enctype.ext, tags)
+
+ def find_realfile(self, name):
+ if not name.endswith(".wav"):
+ return None
+
+ orig = name.rpartition(".")[0]
+ for file in filterdir(self.cue.dir or ".", orig):
+ head, _, ext = file.rpartition(".")
+ if head == orig and ext in self.EXT:
+ return file
+
+ return None
+
+ def open_files(self):
+ lst = []
+
+ for file in self.cue.files():
+ if not file.has_audio_tracks():
+ debug("skip file %s: no tracks", quote(file.name))
+ continue
+
+ name = self.cue.dir + file.name
+ if not os.path.exists(name):
+ real = self.find_realfile(file.name)
+ if not real:
+ printerr("no such file %s", quote(file.name))
+ sys.exit(1)
+ name = self.cue.dir + real
+
+ info = StreamInfo.get(name)
+ if info is None:
+ printerr("%s: unknown type", quote(file.name))
+ sys.exit(1)
+
+ lst.append(self.File(file, name, info))
+
+ return lst
+
+ def shntool_args(self, tool, info):
+ encode = self.enctype.encode(self.opt, info)
+ return [tool, "-w", "-d", self.dest, "-o", encode]
+
+ def track_name(self, track):
+ return self.track_info[track].name
+
+ def track_tags(self, track):
+ return self.track_info[track].tags
+
+ def tag(self, track, path):
+ if not self.tag_supported:
+ return
+
+ printf("Tag [%s] : ", path)
+ if not self.enctype.tag(path, self.track_tags(track)):
+ printf("FAILED\n")
+ sys.exit(1)
+
+ printf("OK\n")
+
+ def copy_file(self, file):
+ noteq = lambda a, b: a and a != b
+
+ if file.info.type != self.encode.name:
+ return False
+ if noteq(self.opt.sample_rate, file.info.sample_rate):
+ return False
+ if noteq(self.opt.bits_per_sample, file.info.bits_per_sample):
+ return False
+ if noteq(self.opt.channels, file.info.channels):
+ return False
+
+ track = list(file.tracks())[0]
+ trackname = self.track_name(track)
+ path = os.path.join(self.dest, trackname)
+
+ if self.opt.dry_run:
+ printf("Copy [%s] --> [%s]\n", file.name, path)
+ return True
+
+ printf("Copy [%s] --> [%s] : ", file.name, path)
+
+ try:
+ shutil.copyfile(file.name, path)
+ except Exception as err:
+ printf("FAILED: %s\n", err)
+ sys.exit(1)
+ else:
+ printf("OK\n")
+
+ self.tag(track, path)
+
+ return True
+
+ def convert_file(self, file):
+ track = list(file.tracks())[0]
+ trackname = self.track_name(track)
+
+ args = self.shntool_args("shnconv", file.info)
+
+ if self.opt.dry_run:
+ name = "link to " + quote(file.name, "'")
+ debug("run %s", " ".join(map(quote, args + [name])))
+ return
+
+ try:
+ link = TempLink(os.path.abspath(file.name), trackname)
+ except OSError as err:
+ printerr("create symlink %s failed: %s", quote(trackname), err)
+ sys.exit(1)
+
+ ret = subprocess.call(args + [str(link)])
+ link.remove()
+
+ if ret:
+ printerr("shnconv failed: exit code %d", ret);
+ sys.exit(1)
+
+ self.tag(track, os.path.join(self.dest, trackname))
+
+ def split_file(self, file, points):
+ args = self.shntool_args("shnsplit", file.info) + [file.name]
+
+ if self.opt.dry_run:
+ debug("run %s", " ".join(map(quote, args)))
+ return
+
+ proc = subprocess.Popen(args, stdin = subprocess.PIPE)
+ proc.stdin.write(to_bytes("\n".join(map(str, points))))
+ proc.stdin.close()
+
+ if proc.wait():
+ printerr("shnsplit failed: exit code %d", proc.returncode)
+ sys.exit(1)
+
+ splitted = filterdir(self.dest, "split-track")
+ for track, filename in zip(file.tracks(), splitted):
+ trackname = self.track_name(track)
+ path = os.path.join(self.dest, trackname)
+
+ printf("Rename [%s] --> [%s] : ", filename, trackname)
+ try:
+ os.rename(os.path.join(self.dest, filename), path)
+ except OSError as err:
+ printf("FAILED: %s\n", err)
+ sys.exit(1)
+ else:
+ printf("OK\n")
+
+ self.tag(track, path)
+
+ def check_duplicates(self):
+ names = [x.name for x in self.track_info.values()]
+ dup = [k for k, v in collections.Counter(names).items() if v > 1]
+ if dup:
+ printerr("track names are duplicated: %s", " ".join(dup))
+ sys.exit(1)
+
+ def transfer_files(self, source, dest):
+ for file in sorted(os.listdir(source)):
+ path = os.path.join(source, file)
+ if not os.path.isfile(path):
+ debug("skip non file %s", quote(file))
+ continue
+
+ printf("Copy [%s] into [%s] : ", file, dest)
+
+ try:
+ shutil.copy(path, dest)
+ except Exception as err:
+ printf("FAILED: %s\n", err)
+ sys.exit(1)
+ else:
+ printf("OK\n")
+
+ def split(self):
+ self.check_duplicates()
+
+ files = self.open_files()
+
+ self.realpath = None
+ if not self.opt.dry_run:
+ mkdir(self.dest)
+ if self.opt.use_tempdir:
+ self.realpath = self.dest
+ tempdir = mkdtemp(prefix="cutter-")
+ self.dest = to_unicode(tempdir)
+
+ for file in files:
+ points = list(file.split_points(file.info))
+ if not points:
+ if not self.copy_file(file):
+ self.convert_file(file)
+ else:
+ self.split_file(file, points)
+
+ if self.realpath:
+ self.transfer_files(self.dest, self.realpath)
+ try:
+ shutil.rmtree(self.dest)
+ except Exception as err:
+ printerr("rm %s failed: %s\n", self.dest, err)
+ sys.exit(1)
+
+ def all_tracks(self):
+ return chain(*[f.tracks() for f in self.cue.files()])
+
+ def dump_tags(self):
+ add_line = False
+ for track in self.all_tracks():
+ if add_line:
+ printf("\n")
+ add_line = True
+
+ tags = self.track_tags(track)
+ for k, v in sorted(tags.items()):
+ if v is not "":
+ printf("%s=%s\n", k.upper(), v)
+
+ def dump_tracks(self):
+ for track in self.all_tracks():
+ trackname = self.track_name(track)
+ printf("%s\n", os.path.join(self.dest, trackname))
diff --git a/tools.py b/tools.py
new file mode 100644
index 0000000..69d8bdb
--- /dev/null
+++ b/tools.py
@@ -0,0 +1,26 @@
+import sys
+import os
+
+progname = os.path.basename(sys.argv[0])
+
+def quote(s, ch = '"'):
+ return s if " " not in s else ch + s + ch
+
+def printf(fmt, *args):
+ out = fmt % args
+ sys.stdout.write(out)
+
+ if out[-1] != '\n':
+ sys.stdout.flush()
+
+def printerr(fmt, *args):
+ msg = fmt % args
+ if msg[-1] != "\n":
+ msg += "\n"
+ sys.stderr.write("** " + progname + ": " + msg)
+
+def debug(fmt, *args):
+ msg = fmt % args
+ if msg[-1] != "\n":
+ msg += "\n"
+ sys.stderr.write("-- " + msg)