#!/usr/local/bin/python2.5
# 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 4002 2007-04-15 18:49:32Z 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)
