#!/usr/bin/python

#Audio Tools, a module and set of tools for manipulating audio data
#Copyright (C) 2007-2010  Brian Langenberger

#This program is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.

#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.

#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


import audiotools
import audiotools.cue
import sys
import os
import os.path
import gettext

gettext.install("audiotools",unicode=True)

MAX_CPUS = audiotools.MAX_JOBS

def extract_part(input_filename, input_pcm, output_filename, quality, metadata,
                 messenger):
    messenger.info(_(u"%(source)s -> %(destination)s") % \
                       {"source":messenger.filename(input_filename),
                        "destination":messenger.filename(output_filename)})

    try:
        destination_audiofile = AudioType.from_pcm(
            output_filename,
            input_pcm,
            quality)

        encoded_filenames.append(filename)

        if (metadata is not None):
            destination_audiofile.set_metadata(metadata)

    except audiotools.EncodingError:
        messenger.error(_(u"Unable to write \"%s\"") % \
                            (messenger.filename(output_filename)))
        sys.exit(1)
    except audiotools.InvalidFormat,msg:
        messenger.error(u"%(filename)s: %(error)s" % \
                            {"filename":messenger.filename(output_filename),
                             "error":unicode(msg)})
        sys.exit(1)


def has_embedded_cuesheet(audiofile):
    return audiofile.get_cuesheet() is not None

if (__name__ == '__main__'):
    parser = audiotools.OptionParser(
        _(u'%prog [options] [-d directory] <track>'),
        version="Python Audio Tools %s" % (audiotools.VERSION))

    parser.add_option('-V','--verbose',
                      action='store',
                      dest='verbosity',
                      choices=["quiet","normal","debug"],
                      default="normal",
                      help=_(u'the verbosity level to execute at'))

    parser.add_option('--cue',
                      action='store',
                      type='string',
                      dest='cuesheet',
                      metavar='FILENAME',
                      help=_(u'the cuesheet to use for splitting track'))

    conversion = audiotools.OptionGroup(parser,_(u"Conversion Options"))

    conversion.add_option('-t','--type',
                          action='store',
                          dest='type',
                          choices=audiotools.TYPE_MAP.keys(),
                          help=_(u'the type of audio track to convert to'))

    conversion.add_option('-q','--quality',
                          action='store',
                          type='string',
                          dest='quality',
                          help=_(u'the quality to store audio tracks at'))

    conversion.add_option('-d','--dir',
                          action='store',
                          type='string',
                          dest='dir',
                          default='.',
                          help=_(u'the directory to store converted audio tracks'))

    conversion.add_option('--format',
                          action='store',
                          type='string',
                          default=None,
                          dest='format',
                          help=_(u'the format string for new filenames'))

    conversion.add_option('-j','--joint',
                          action='store',
                          type='int',
                          default=MAX_CPUS,
                          dest='max_processes',
                          help=_(u'the maximum number of processes to run at a time'))

    parser.add_option_group(conversion)

    metadata = audiotools.OptionGroup(parser,_(u"Metadata Options"))

    metadata.add_option('-x','--xmcd',
                          action='store',
                          type='string',
                          dest='xmcd',
                          metavar='FILENAME',
                          help=_(u'FreeDB XMCD file or MusicBrainz XML file'))

    #if adding ReplayGain is a lossless process
    #(i.e. added as tags rather than modifying track data)
    #add_replay_gain should default to True
    #if not, add_replay_gain should default to False
    #which is which depends on the track type
    metadata.add_option('--replay-gain',
                        action='store_true',
                        dest='add_replay_gain',
                        help=_(u'add ReplayGain metadata to newly created tracks'))

    metadata.add_option('--no-replay-gain',
                      action='store_false',
                      dest='add_replay_gain',
                      help=_(u'do not add ReplayGain metadata to newly extracted tracks'))

    parser.add_option_group(metadata)

    (options,args) = parser.parse_args()
    msg = audiotools.Messenger("tracksplit",options)

    #get the AudioFile class we are converted to
    if (options.type is not None):
        AudioType = audiotools.TYPE_MAP[options.type]
    else:
        AudioType = audiotools.TYPE_MAP['wav']

    #ensure the selected compression is compatible with that class
    if (options.quality == 'help'):
        if (len(AudioType.COMPRESSION_MODES) > 1):
            msg.info(_(u"Available compression types for %s:") % \
                         (AudioType.NAME))
            for mode in AudioType.COMPRESSION_MODES:
                msg.info(mode.decode('ascii'))
        else:
            msg.error(_(u"Audio type %s has no compression modes") % \
                            (AudioType.NAME))
        sys.exit(0)
    elif (options.quality is None):
        options.quality = AudioType.DEFAULT_COMPRESSION
    elif (options.quality not in AudioType.COMPRESSION_MODES):
        msg.error(_(u"\"%(quality)s\" is not a supported compression mode for type \"%(type)s\"") % \
                      {"quality":options.quality,
                       "type":AudioType.NAME})
        sys.exit(1)

    if (len(args) != 1):
        msg.error(_(u"You must specify exactly 1 supported audio file"))
        sys.exit(1)

    try:
        audiofile = audiotools.open(args[0])
    except audiotools.UnsupportedFile:
        msg.error(_(u"You must specify exactly 1 supported audio file"))
        sys.exit(1)
    except IOError:
        msg.error(_(u"Unable to open \"%s\"") % (msg.filename(args[0])))
        sys.exit(1)

    if (options.add_replay_gain is None):
        options.add_replay_gain = (AudioType.lossless_replay_gain() and
                                   audiotools.applicable_replay_gain([audiofile]))

    if ((options.cuesheet is None) and (not has_embedded_cuesheet(audiofile))):
        msg.error(_(u"You must specify a cuesheet to split audio file"))
        sys.exit(1)

    if (options.max_processes < 1):
        msg.error(_(u'You must run at least 1 process at a time'))
        sys.exit(1)

    base_directory = options.dir
    encoded_filenames = []
    queue = audiotools.ExecQueue()

    if (options.cuesheet is not None):
        #grab the cuesheet we're using to split tracks
        #(this overrides an embedded cuesheet)
        try:
            cuesheet = audiotools.read_sheet(options.cuesheet)
        except audiotools.SheetException,err:
            msg.error(unicode(err))
            sys.exit(1)
    else:
        cuesheet = audiofile.get_cuesheet()

    if (list(cuesheet.pcm_lengths(audiofile.total_frames()))[-1] <= 0):
        msg.error(_(u"Cuesheet too long for track being split"))
        sys.exit(1)


    #pull initial metadata from the base file, where appropriate
    base_metadata = audiofile.get_metadata()
    if (base_metadata is not None):
        base = audiotools.AlbumMetaData([])
        ISRCs = cuesheet.ISRCs()
        for (i,indexes) in enumerate(cuesheet.indexes()):
            track_metadata = audiotools.MetaData.converted(base_metadata)
            track_metadata.track_name = u""
            track_metadata.track_number = i + 1
            track_metadata.track_total = len(list(cuesheet.indexes()))
            track_metadata.ISRC = ISRCs.get(i + 1,u"")

            base[i + 1] = track_metadata
    else:
        base = audiotools.AlbumMetaData(
            [audiotools.MetaData(track_number=i+1)
                  for (i,indexes) in enumerate(cuesheet.indexes())])

    #if we're using an XMCD file, use that file for track MetaData
    if (options.xmcd is not None):
        try:
            xmcd = audiotools.read_metadata_file(options.xmcd)
        except audiotools.MetaDataFileException,err:
            msg.error(unicode(err))
            sys.exit(1)
    else:
        xmcd = audiotools.AlbumMetaData(
            [audiotools.MetaData(track_number=i+1)
                  for (i,indexes) in enumerate(cuesheet.indexes())])


    try:
        split_pcm = audiotools.pcm_split(
            audiofile.to_pcm(),
            cuesheet.pcm_lengths(audiofile.total_frames()))
    except audiotools.DecodingError,err:
        msg.error(unicode(err))
        sys.exit(1)

    try:
        for (i,pcmreader) in enumerate(split_pcm):
            track_number = i + 1

            metadata = xmcd.get(track_number,None)
            if (metadata is not None):
                metadata.merge(base.get(track_number,None))
            else:
                metadata = base.get(track_number,None)

            filename = os.path.join(
                base_directory,
                AudioType.track_name(file_path=audiofile.filename,
                                     track_metadata=metadata,
                                     format=options.format))

            audiotools.make_dirs(filename)
            encoded_filenames.append(filename)

            queue.execute(extract_part,
                          (audiofile.filename,
                           pcmreader,
                           filename,
                           options.quality,
                           metadata,
                           msg))
    except audiotools.UnsupportedTracknameField,err:
        err.error_msg(msg)
        sys.exit(1)

    queue.run(options.max_processes)
    if (queue.return_values != set([0])):
        sys.exit(1)

    if (options.add_replay_gain and AudioType.can_add_replay_gain()):
        if (AudioType.lossless_replay_gain()):
            msg.info(_(u"Adding ReplayGain metadata.  This may take some time."))
        else:
            msg.info(_(u"Applying ReplayGain.  This may take some time."))
        try:
            AudioType.add_replay_gain(encoded_filenames)
        except ValueError,err:
            msg.error(unicode(err))
            sys.exit(1)
