aboutsummaryrefslogtreecommitdiffstats
path: root/cli/ape2id3.py
diff options
context:
space:
mode:
Diffstat (limited to 'cli/ape2id3.py')
-rwxr-xr-xcli/ape2id3.py145
1 files changed, 145 insertions, 0 deletions
diff --git a/cli/ape2id3.py b/cli/ape2id3.py
new file mode 100755
index 0000000..8651a2f
--- /dev/null
+++ b/cli/ape2id3.py
@@ -0,0 +1,145 @@
+#! /usr/bin/env python
+import sys
+from optparse import OptionParser
+import mutagen
+from mutagen.apev2 import APEv2
+from mutagen.id3 import ID3, TXXX
+
+def convert_gain(gain):
+ if gain[-3:] == " dB":
+ gain = gain[:-3]
+ try:
+ gain = float(gain)
+ except ValueError:
+ raise ValueError, "invalid gain value"
+ return "%.2f dB" % gain
+def convert_peak(peak):
+ try:
+ peak = float(peak)
+ except ValueError:
+ raise ValueError, "invalid peak value"
+ return "%.6f" % peak
+
+REPLAYGAIN_TAGS = (
+ ("mp3gain_album_minmax", None),
+ ("mp3gain_minmax", None),
+ ("replaygain_album_gain", convert_gain),
+ ("replaygain_album_peak", convert_peak),
+ ("replaygain_track_gain", convert_gain),
+ ("replaygain_track_peak", convert_peak),
+)
+
+
+class Logger(object):
+ def __init__(self, log_level, prog_name):
+ self.log_level = log_level
+ self.prog_name = prog_name
+ self.filename = None
+ def prefix(self, msg):
+ if self.filename is None:
+ return msg
+ return "%s: %s" % (self.filename, msg)
+ def debug(self, msg):
+ if self.log_level >= 4:
+ print self.prefix(msg)
+ def info(self, msg):
+ if self.log_level >= 3:
+ print self.prefix(msg)
+ def warning(self, msg):
+ if self.log_level >= 2:
+ print self.prefix("WARNING: %s" % msg)
+ def error(self, msg):
+ if self.log_level >= 1:
+ sys.stderr.write("%s: %s\n" % (self.prog_name, msg))
+ def critical(self, msg, retval=1):
+ self.error(msg)
+ sys.exit(retval)
+
+class Ape2Id3(object):
+ def __init__(self, logger, force=False):
+ self.log = logger
+ self.force = force
+ def convert_tag(self, name, value):
+ pass
+ def copy_replaygain_tag(self, apev2, id3, name, converter=None):
+ self.log.debug("processing '%s' tag" % name)
+ if name not in apev2:
+ self.log.info("no APEv2 '%s' tag found, skipping tag" % name)
+ return False
+ if not self.force and ("TXXX:%s" % name) in id3:
+ self.log.info("ID3 '%s' tag already exists, skpping tag" % name)
+ return False
+ value = str(apev2[name])
+ if callable(converter):
+ self.log.debug("converting APEv2 '%s' tag from '%s'" %
+ (name, value))
+ try:
+ value = converter(value)
+ except ValueError:
+ self.log.warning("invalid value for APEv2 '%s' tag" % name)
+ return False
+ self.log.debug("converted APEv2 '%s' tag to '%s'" % (name, value))
+ id3.add(TXXX(encoding=1, desc=name, text=value))
+ self.log.info("added ID3 '%s' tag with value '%s'" % (name, value))
+ return True
+ def copy_replaygain_tags(self, filename):
+ self.log.filename = filename
+ self.log.debug("begin processing file")
+ try:
+ apev2 = APEv2(filename)
+ except mutagen.apev2.error:
+ self.log.info("no APEv2 tag found, skipping file")
+ return
+ except IOError:
+ e = sys.exc_info()
+ self.log.error("%s" % e[1])
+ return
+ try:
+ id3 = ID3(filename)
+ except mutagen.id3.error:
+ self.log.info("no ID3 tag found, creating one")
+ id3 = ID3()
+ modified = False
+ for name, converter in REPLAYGAIN_TAGS:
+ copied = self.copy_replaygain_tag(apev2, id3, name, converter)
+ if copied:
+ modified = True
+ if modified:
+ self.log.debug("saving modified ID3 tag")
+ id3.save(filename)
+ self.log.debug("done processing file")
+ self.log.filename = None
+
+def main(prog_name, options, args):
+ logger = Logger(options.log_level, prog_name)
+ ape2id3 = Ape2Id3(logger, force=options.force)
+ for filename in args:
+ ape2id3.copy_replaygain_tags(filename)
+
+if __name__ == "__main__":
+ parser = OptionParser(version="0.1", usage="%prog [OPTION]... FILE...",
+ description="Copy APEv2 ReplayGain tags on "
+ "FILE(s) to ID3v2.")
+ parser.add_option("-q", "--quiet", dest="log_level",
+ action="store_const", const=0, default=1,
+ help="do not output error messages")
+ parser.add_option("-v", "--verbose", dest="log_level",
+ action="store_const", const=3,
+ help="output warnings and informational messages")
+ parser.add_option("-d", "--debug", dest="log_level",
+ action="store_const", const=4,
+ help="output debug messages")
+ parser.add_option("-f", "--force", dest="force",
+ action="store_true", default=False,
+ help="force overwriting of existing ID3v2 "
+ "ReplayGain tags")
+ prog_name = parser.get_prog_name()
+ options, args = parser.parse_args()
+ if len(args) < 1:
+ parser.error("no files specified")
+ try:
+ main(prog_name, options, args)
+ except KeyboardInterrupt:
+ pass
+
+# vim: set expandtab shiftwidth=4 softtabstop=4 textwidth=79: