#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# This application is released under the GNU General Public License 
# v3 (or, at your option, any later version). You can find the full 
# text of the license under http://www.gnu.org/licenses/gpl.txt
# By using, editing and/or distributing this software you agree to 
# the terms and conditions of this license.
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
#!v0.3~mickyz(c)2012

import os, sys

from mutagen.flac import FLAC
from mutagen.easyid3 import EasyID3
from mutagen.mp4 import MP4
from mutagen.oggvorbis import OggVorbis

from optparse import OptionParser, SUPPRESS_HELP

# supported audio formats with mutagen modules
AUDIO_FORMATS = {
'.flac':'FLAC',
'.mp3': 'EasyID3',
'.mp4': 'MP4',
'.oga': 'OggVorbis',
'.ogg': 'OggVorbis'
}
AUDIO_CMDKEYS = {
'-a': 'artist',
'-b': 'album',
'-t': 'title',
'-n': 'tracknumber',
'-y': 'date',
}
AUDIO_MP4KEYS = {
u'\xa9ART': 'artist',
u'\xa9alb': 'album',
u'\xa9nam': 'title',
u'trkn': 'tracknumber',
u'\xa9day': 'date',
}
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

def __decode_str(value):
    return value.decode('utf-8', 'replace')

def __map_module(ext):
    return eval(AUDIO_FORMATS[ext])

def __reverse_mapping(dictionary):
    return dict(( (v,k) for k,v in dictionary.iteritems() ))

def __printer(filename, msg):
    print '$ %s "%s"' % (msg, filename)

def __wait(time_lapse):
    import time

    time_start = time.time()
    time_end = (time_start + time_lapse)

    while time_end > time.time(): pass
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

def delete_tag(filenames):
    for filename in filenames:
        try: ext = filename[filename.rindex('.')+0:].lower()
        except: continue

        if ext not in AUDIO_FORMATS: continue

        if ext == '.mp3': # strip art before delete
            picname = filename[:filename.rindex('.')+0]
            if not os.path.exists('%s.jpg' % picname):
                __saveto_pic(filename, picname)

        try:
            audio = __map_module(ext)(filename)
            audio.delete()
            __strip_ape(filename)
            __printer(filename, '--delete')
        except:
            pass

def __strip_ape(filename):
    from mutagen.apev2 import APEv2

    try:
        # APEv2 tag is most commonly used with Musepack files,
        # but some MP3s also have it, this can cause problems.
        audio = APEv2(filename)
        audio.delete()
    except:
        pass
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

def read_tag(filenames):
    for filename in filenames:
        try: ext = filename[filename.rindex('.')+0:].lower()
        except: continue

        if ext not in AUDIO_FORMATS: continue

        try:
            audio = __map_module(ext)(filename)
            keys = audio.pprint().split('\n')

            if keys != ['']: __printer(filename, '--list')

            for k in sorted(keys):
                value, name = k.split('=')
                if ext == '.mp4':
                    print '  %s=%s' % (AUDIO_MP4KEYS[value], name)
                else:
                    print '  %s=%s' % (value, name)            
        except:
            continue
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

def write_tag(edits, filenames):
    for filename in filenames:
        try: ext = filename[filename.rindex('.')+0:].lower()
        except: continue

        if edits[0][0] == 'pic':
            if ext == '.mp3': embed_pic(edits[0][1], filename)
            continue

        if ext not in AUDIO_FORMATS: continue
        __printer(filename, '--write')

        try: audio = __map_module(ext)(filename)
        except: audio = __map_module(ext)()

        for value in edits:
            try:
                if ext == '.mp4':
                    keys = __reverse_mapping(AUDIO_MP4KEYS)
                    audio[keys[value[0]].encode('latin1')] = __decode_str(value[1])
                else:
                    audio[value[0]] = __decode_str(value[1])
            except:
                continue

        try: audio.save()
        except: audio.save(filename)
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

def rename_file(filenames):
    for filename in filenames:
        try: ext = filename[filename.rindex('.')+0:].lower()
        except: continue

        if ext not in AUDIO_FORMATS: continue
        try:
            audio = __map_module(ext)(filename)
            keys = audio.pprint().split('\n')
        except:
            keys = []
        for k in keys:
            try:
                value, name = k.split('=')
                if ext == '.mp4':  
                    if AUDIO_MP4KEYS[value] == 'title': title = name
                    if AUDIO_MP4KEYS[value] == 'artist': artist = name
                else:
                    if value == 'title': title = name
                    if value == 'artist': artist = name
            except:
                continue

        try: destname = '%s - %s' % (artist, title)
        except: destname = filename[:filename.rindex('.')].replace('_', ' ')

        for x in destname.split(' '):
            if x.startswith('('): z = '('+x[1:].capitalize()
            elif '-' in x: z = x[:x.rfind('-')].title()+x[x.rfind('-'):].title()
            elif '.' in x: z = x[:x.rfind('.')].title()+x[x.rfind('.'):].title()
            else: z = x.capitalize()
            destname = destname.replace(x, z)
        destname = destname +ext

        if os.path.exists(filename) and not os.path.exists(destname):
            os.rename(filename, destname)
            __printer(destname, '--rename')
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

def embed_pic(sourcepic, filename):
    from mutagen.id3 import ID3, APIC

    pic = __resize_pic(sourcepic)
    if not pic: return
    __printer(filename, '--write')

    try:
        try: audio = ID3(filename)
        except: audio = ID3() # add id3 header

        frame = APIC(
            encoding = 3, # 3 is for utf-8
            mime = 'image/jpeg', # image/jpeg or image/png
            type = 3, # 3 is for cover image
            desc = u'', # comment string, must be empty
            data = pic) # image raw data
        audio.add(frame)

        try: audio.save()
        except: audio.save(filename)
    except:
        pass

def __read_pic(filename):
    from mutagen.mp3 import MP3

    try:
        audio = MP3(filename)
        data = audio.tags.getall('APIC')
        pic = [x.data for x in data][0]
        #f = open('...', 'wb')
        #f.write(pic)
        #f.close()
        return pic
    except:
        return None

def __resize_pic(sourcepic):
    import gtk

    try:
        pbuf = gtk.gdk.pixbuf_new_from_file_at_size(sourcepic, 200, 200)
        pbuf.save('/tmp/image', 'jpeg', {'quality':'100'})
        #__wait(1)
        return open('/tmp/image', 'rb').read()
    except:
        return None

def __saveto_pic(filename, picname):
    import gtk

    try:
        pic = __read_pic(filename)
        if not pic: raise
        pbl = gtk.gdk.PixbufLoader()
        pbl.set_size(200, 200)
        pbl.write(pic)
        pbuf = pbl.get_pixbuf()
        pbl.close()
        pbufpath = '%s.jpg' % picname
        pbuf.save(pbufpath, 'jpeg', {'quality':'100'})
    except:
        pass
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

class tagOptionParser(OptionParser):

    def __init__(self):
        mutagen_version = '.'.join(map(str, mutagen.version))
        return OptionParser.__init__(self, version=None,
            usage='%prog [OPTION] [FILE]...')#, description='...')

    def format_help(self, *args, **kwargs):
        text = OptionParser.format_help(self, *args, **kwargs)
        return '%s\nSupports:\nFLAC, MP3, MP4, OggVorbis\n\n' % text
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

def main(argv):
    parser = tagOptionParser()

    parser.add_option("-l", "--list", action="store_const",
        dest="action", const="list", help="list tags information")

    parser.add_option("-d", "--delete", action="store_const",
        dest="action", const="delete", help="delete tags")

    parser.add_option("-r", "--rename", action="store_const",
        dest="action", const="rename", help="rename files")

    parser.add_option("-a", metavar='"artist"', action="callback",
        help="set artist name", type="string",
        callback=lambda *args: args[3].edits.append(("artist", args[2])))

    parser.add_option("-b", metavar='"album"', action="callback",
        help="set album name", type="string",
        callback=lambda *args: args[3].edits.append(("album", args[2])))

    parser.add_option("-p", metavar='"jpg/png"', action="callback",
        help="set art picture", type="string",
        callback=lambda *args: args[3].edits.append(("pic", args[2])))

    parser.add_option("-t", metavar='"title"', action="callback",
        help="set title name", type="string",
        callback=lambda *args: args[3].edits.append(("title", args[2])))

    parser.add_option("-n", metavar='"1"', action="callback",
        help="set track number", type="string",
        callback=lambda *args: args[3].edits.append(("tracknumber", args[2])))

    parser.add_option("-y", metavar='"2012"', action="callback",
        help="set release year", type="string",
        callback=lambda *args: args[3].edits.append(("date", args[2])))

    parser.edits = []

    (options, args) = parser.parse_args(argv[1:])

    if args:
        if parser.edits: write_tag(parser.edits, args)
        elif options.action in [None, 'list']: read_tag(args)
        elif options.action in ['delete']: delete_tag(args)
        elif options.action in ['rename']: rename_file(args)

        else: parser.print_help()
    else: parser.print_help()
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

if __name__ == '__main__':
    try: import mutagen
    except ImportError, error:
        print 'Error: %s (python-mutagen)' % error 
    main(sys.argv)
