#!/scratchbox/tools/bin/python # Pretend to be /usr/bin/id3v2 from id3lib, sort of. # Copyright 2005 Joe Wreschnig # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # $Id: mid3v2 4275 2008-06-01 06:32:37Z piman $ import os import sys import locale from optparse import OptionParser, SUPPRESS_HELP VERSION = (1, 1) class ID3OptionParser(OptionParser): def __init__(self): mutagen_version = ".".join(map(str, mutagen.version)) my_version = ".".join(map(str, VERSION)) version = "mid3v2 %s\nUses Mutagen %s" % (my_version, mutagen_version) return OptionParser.__init__( self, version=version, usage="%prog [OPTION] [FILE]...", description="Mutagen-based replacement for id3lib's id3v2.") def format_help(self, *args, **kwargs): text = OptionParser.format_help(self, *args, **kwargs) return text + """\ You can set the value for any ID3v2 frame by using '--' and then a frame ID. For example: mid3v2 --TIT3 "Monkey!" file.mp3 would set the "Subtitle/Description" frame to "Monkey!". Any editing operation will cause the ID3 tag to be upgraded to ID3v2.4. """ def list_frames(option, opt, value, parser): items = mutagen.id3.Frames.items() items.sort() for name, frame in items: print " --%s %s" % (name, frame.__doc__.split("\n")[0]) raise SystemExit def list_frames_2_2(option, opt, value, parser): items = mutagen.id3.Frames_2_2.items() items.sort() for name, frame in items: print " --%s %s" % (name, frame.__doc__.split("\n")[0]) raise SystemExit def list_genres(option, opt, value, parser): for i, genre in enumerate(mutagen.id3.TCON.GENRES): print "%3d: %s" % (i, genre) raise SystemExit def delete_tags(filenames, v1, v2): for filename in filenames: print "deleting ID3 tag info in %s" % filename mutagen.id3.delete(filename, v1, v2) def delete_frames(deletes, filenames): frames = deletes.split(",") for filename in filenames: print "deleting %s from %s" % (deletes, filename) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: print "No ID3 header found; skipping." continue except Exception, err: print str(err) continue else: map(id3.delall, frames) id3.save() def write_files(edits, filenames): enc = locale.getpreferredencoding() edits = [(frame[2:], value.decode(enc)) for (frame, value) in edits] for filename in filenames: print "Writing", filename try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: print "No ID3 header found; creating a new tag" id3 = mutagen.id3.ID3() except Exception, err: print str(err) continue for frame, value in edits: if frame == "COMM": values = value.split(":") if len(values) == 1: value, desc, lang = values[0], "", "eng" elif len(values) == 2: desc, value, lang = values[0], values[1], "eng" else: value = ":".join(values[1:-1]) desc, lang = values[0], values[-1] frame = mutagen.id3.COMM( encoding=3, text=value, lang=lang, desc=desc) elif issubclass(mutagen.id3.Frames[frame], mutagen.id3.UrlFrame): frame = mutagen.id3.Frames[frame](encoding=3, url=value) else: frame = mutagen.id3.Frames[frame](encoding=3, text=value) id3.add(frame) id3.save(filename) def list_tags(filenames): enc = locale.getpreferredencoding() for filename in filenames: print "IDv2 tag info for %s:" % filename try: id3 = mutagen.id3.ID3(filename, translate=False) except Exception, err: print str(err) else: print id3.pprint().encode(enc, "replace") def list_tags_raw(filenames): enc = locale.getpreferredencoding() for filename in filenames: print "Raw IDv2 tag info for %s:" % filename try: id3 = mutagen.id3.ID3(filename, translate=False) except Exception, err: print str(err) else: for frame in id3.values(): print repr(frame) def main(argv): parser = ID3OptionParser() parser.add_option( "-f", "--list-frames", action="callback", callback=list_frames, help="Display all possible frames for ID3v2.3 / ID3v2.4") parser.add_option( "--list-frames-v2.2", action="callback", callback=list_frames_2_2, help="Display all possible frames for ID3v2.2") parser.add_option( "-L", "--list-genres", action="callback", callback=list_genres, help="Lists all ID3v1 genres") parser.add_option( "-l", "--list", action="store_const", dest="action", const="list", help="Lists the tag(s) on the file(s)") parser.add_option( "--list-raw", action="store_const", dest="action", const="list-raw", help="Lists the tag(s) on the file(s) in Python format") parser.add_option( "-d", "--delete-v2", action="store_const", dest="action", const="delete-v2", help="Deletes ID3v2 tags") parser.add_option( "-s", "--delete-v1", action="store_const", dest="action", const="delete-v1", help="Deletes ID3v1 tags") parser.add_option( "-D", "--delete-all", action="store_const", dest="action", const="delete-v1-v2", help="Deletes ID3v1 and ID3v2 tags") parser.add_option( '--delete-frames', metavar='FID1,FID2,...', action='store', dest='deletes', default='', help="Delete the given frames") parser.add_option( "-C", "--convert", action="store_const", dest="action", const="convert", help="Convert tags to ID3v2.4 (any editing will do this)") parser.add_option( "-a", "--artist", metavar='"ARTIST"', action="callback", help="Set the artist information", type="string", callback=lambda *args: args[3].edits.append(("--TPE1", args[2]))) parser.add_option( "-A", "--album", metavar='"ALBUM"', action="callback", help="Set the album title information", type="string", callback=lambda *args: args[3].edits.append(("--TALB", args[2]))) parser.add_option( "-t", "--song", metavar='"SONG"', action="callback", help="Set the song title information", type="string", callback=lambda *args: args[3].edits.append(("--TIT2", args[2]))) parser.add_option( "-c", "--comment", metavar='"DESCRIPTION":"COMMENT":"LANGUAGE"', action="callback", help="Set the comment information", type="string", callback=lambda *args: args[3].edits.append(("--COMM", args[2]))) parser.add_option( "-g", "--genre", metavar='"GENRE"', action="callback", help="Set the genre or genre number", type="string", callback=lambda *args: args[3].edits.append(("--TCON", args[2]))) parser.add_option( "-y", "--year", "--date", metavar='YYYY[-MM-DD]', action="callback", help="Set the year/date", type="string", callback=lambda *args: args[3].edits.append(("--TDRC", args[2]))) parser.add_option( "-T", "--track", metavar='"num/num"', action="callback", help="Set the track number/(optional) total tracks", type="string", callback=lambda *args: args[3].edits.append(("--TRCK", args[2]))) for frame in mutagen.id3.Frames: if issubclass(mutagen.id3.Frames[frame], mutagen.id3.TextFrame) \ or issubclass(mutagen.id3.Frames[frame], mutagen.id3.UrlFrame): parser.add_option( "--" + frame, action="callback", help=SUPPRESS_HELP, type='string', metavar="value", # optparse blows up with this callback=lambda *args: args[3].edits.append(args[1:3])) parser.edits = [] (options, args) = parser.parse_args(argv[1:]) if args: if parser.edits: write_files(parser.edits, args) elif options.deletes: delete_frames(options.deletes, args) elif options.action in [None, 'list']: list_tags(args) elif options.action == "list-raw": list_tags_raw(args) elif options.action == "convert": write_files([], args) elif options.action.startswith("delete"): delete_tags(args, "v1" in options.action, "v2" in options.action) else: parser.print_help() else: parser.print_help() if __name__ == "__main__": try: import mutagen, mutagen.id3 except ImportError: # Run as ./mid3v2 out of tools/ sys.path.append(os.path.abspath("../")) import mutagen, mutagen.id3 main(sys.argv)