#!/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 sys
import os
import os.path
import gettext

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

MAX_CPUS = audiotools.MAX_JOBS

def convert_file(destination_class, destination_filename,
                 source_audiofile, metadata, compression,
                 messenger, thumbnail_images=False):
    messenger.info(_(u"%(source)s -> %(destination)s") % \
                      {"source":messenger.filename(source_audiofile.filename),
                       "destination":messenger.filename(destination_filename)})

    try:
        if (isinstance(source_audiofile,audiotools.WaveAudio)):
            #if we're converting from a WAVE file, use from_wave()
            destination_audiofile = destination_class.from_wave(
                destination_filename,
                source_audiofile.filename,
                compression)
        elif (destination_class == audiotools.WaveAudio):
            #if we're converting to a WAVE file, use to_wave()
            source_audiofile.to_wave(destination_filename)
            destination_audiofile = audiotools.WaveAudio(destination_filename)

        elif ((source_audiofile.has_foreign_riff_chunks()) and
              (destination_class.supports_foreign_riff_chunks())):
            #if the source contains foreign chunks
            #and the target format supports foreign chunks
            #route the data through a temporary WAVE file
            import tempfile

            temp_wave = tempfile.NamedTemporaryFile(suffix='.wav')
            try:
                source_audiofile.to_wave(temp_wave.name)
                destination_audiofile = destination_class.from_wave(
                    destination_filename,
                    temp_wave.name,
                    compression)
            finally:
                temp_wave.close()

        else:
            #otherwise, route the data through PCM
            destination_audiofile = destination_class.from_pcm(
                destination_filename,
                source_audiofile.to_pcm(),
                compression)

        if (metadata is not None):
            if (thumbnail_images):
                for img in metadata.images():
                    metadata.delete_image(img)
                    metadata.add_image(img.thumbnail(
                            audiotools.THUMBNAIL_SIZE,
                            audiotools.THUMBNAIL_SIZE,
                            audiotools.THUMBNAIL_FORMAT))

            destination_audiofile.set_metadata(metadata)
        else:
            destination_audiofile.set_metadata(audiotools.MetaData(
                    track_number=source_audiofile.track_number(),
                    album_number=source_audiofile.album_number()))

        existing_cuesheet = destination_audiofile.get_cuesheet()
        if (existing_cuesheet is None):
            destination_audiofile.set_cuesheet(source_audiofile.get_cuesheet())

    except audiotools.UnsupportedChannelMask:
        messenger.error(_(u"Unable to write \"%(target_filename)s\" with channel assignment from \"%(source_filename)s\"") % \
                            {"target_filename":messenger.filename(destination_filename),
                             "source_filename":messenger.filename(source_audiofile.filename)})
        sys.exit(1)
    except audiotools.UnsupportedChannelCount:
        messenger.error(_(u"Unable to write \"%(target_filename)s\" with %(channels)d channel input" % \
                            {"target_filename":messenger.filename(destination_filename),
                             "channels":source_audiofile.channels()}))
        sys.exit(1)
    except audiotools.UnsupportedBitsPerSample:
        messenger.error(_(u"Unable to write \"%(target_filename)s\" with %(bps)d bits per sample") % \
                            {"target_filename":messenger.filename(destination_filename),
                             "bps":source_audiofile.bits_per_sample()})
        sys.exit(1)
    except audiotools.EncodingError:
        messenger.error(_(u"Unable to write \"%s\"") % \
                            (messenger.filename(destination_filename)))
        sys.exit(1)
    except audiotools.InvalidFormat,msg:
        messenger.error(unicode(msg))
        sys.exit(1)


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

    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('-o','--output',
                      action='store',
                      dest='output',
                      help=_(u'output filename to use, overriding default and -d'))


    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'))

    metadata.add_option('-T','--thumbnail',
                        action='store_true',
                        default=False,
                        dest='thumbnail',
                        help=_(u'convert embedded images to smaller thumbnails during conversion'))


    #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 created tracks'))

    parser.add_option_group(metadata)

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

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

    #if one specifies incompatible output options,
    #complain about it right away
    if (options.output is not None):
        if (options.dir != "."):
            msg.error(_(u"-o and -d options are not compatible"))
            msg.info(_(u"Please specify either -o or -d but not both"))
            sys.exit(1)

        if (options.format is not None):
            msg.warning(_(u"--format has no effect when used with -o"))

    #get the AudioFile class we are converted to
    if (options.output is None):
        if (options.type is not None):
            AudioType = audiotools.TYPE_MAP[options.type]
        else:
            AudioType = audiotools.TYPE_MAP['wav']
    else:
        if (options.type is not None):
            AudioType = audiotools.TYPE_MAP[options.type]
        else:
            try:
                AudioType = audiotools.filename_to_type(options.output)
            except audiotools.UnknownAudioType,exp:
                exp.error_msg(msg)
                sys.exit(1)

    #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)

    #grab the list of AudioFile objects we are converting from
    audiofiles = audiotools.open_files(args,messenger=msg)
    if (len(audiofiles) < 1):
        msg.error(_(u"You must specify at least 1 supported 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)

    if ((options.output is not None) and
        (len(audiofiles) != 1)):
        msg.error(_(u'You may specify only 1 input file for use with -o'))
        sys.exit(1)

    #determine whether to add ReplayGain by default
    if (options.add_replay_gain is None):
        options.add_replay_gain = (AudioType.lossless_replay_gain() and
                                   audiotools.applicable_replay_gain(audiofiles))

    #if we're using an XMCD/XML file, use that file for 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: #if we're not using an XMCD file, use the AudioFile objects' data
        xmcd = None

    if (options.thumbnail):
        if (not audiotools.can_thumbnail()):
            msg.error(_(u"Unable to generate thumbnails"))
            msg.info(_(u"Please install the Python Imaging Library"))
            msg.info(_(u"available at http://www.pythonware.com/products/pil/"))
            msg.info(_(u"to enable image resizing"))
            sys.exit(1)

        if (audiotools.THUMBNAIL_FORMAT.upper() not in audiotools.thumbnail_formats()):
            msg.error(_(u"Unsupported thumbnail format \"%s\"") % \
                          (audiotools.THUMBNAIL_FORMAT))
            msg.info(_(u"Available formats are: %s") % \
                         (", ".join(audiotools.thumbnail_formats())))
            sys.exit(1)

    quality = options.quality
    max_processes = options.max_processes


    if (options.output is None):
        #the default encoding method, without an output file

        base_directory = options.dir

        queue = audiotools.ExecQueue()
        encoded_filenames = []

        for audiofile in audiofiles:
            try:
                track_number = audiofile.track_number()

                if (xmcd is not None):
                    track_metadata = audiofile.get_metadata()
                    if (track_metadata is not None):
                        xmcd_metadata = track_metadata.__class__.converted(
                            xmcd.get(track_number,None))
                        xmcd_metadata.merge(track_metadata)
                        metadata = xmcd_metadata
                    else:
                        metadata = xmcd.get(track_number,None)

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

                    try:
                        audiotools.make_dirs(filename)
                    except OSError:
                        msg.error(_(u"Unable to write \"%s\"") % \
                                      (filename))
                        sys.exit(1)

                    queue.execute(convert_file,
                                  (AudioType,
                                   filename,
                                   audiofile,
                                   metadata,
                                   quality,
                                   msg))
                    encoded_filenames.append(filename)
                else:
                    metadata = audiofile.get_metadata()

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

                    try:
                        audiotools.make_dirs(filename)
                    except OSError:
                        msg.error(_(u"Unable to write \"%s\"") % \
                                      (filename))
                        sys.exit(1)

                    queue.execute(convert_file,
                                  (AudioType,
                                   filename,
                                   audiofile,
                                   metadata,
                                   quality,
                                   msg,
                                   options.thumbnail))
                encoded_filenames.append(filename)
            except audiotools.UnsupportedTracknameField,err:
                err.error_msg(msg)
                sys.exit(1)
            except KeyError:
                continue

        queue.run(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:
                #separate encoded files by album_name and album_number
                for album in audiotools.group_tracks(
                    audiotools.open_files(encoded_filenames,
                                          messenger=msg)):
                    #add ReplayGain to groups of files
                    #belonging to the same album
                    AudioType.add_replay_gain([a.filename for a in album])
            except ValueError,err:
                msg.error(unicode(err))
                sys.exit(1)
    else:
        #encoding only a single file
        audiofile = audiofiles[0]

        if (xmcd is not None):
            track_metadata = audiofile.get_metadata()
            if (track_metadata is not None):
                xmcd_metadata = track_metadata.__class__.converted(
                    xmcd.get(audiofile.track_number(),None))
                xmcd_metadata.merge(track_metadata)
                metadata = xmcd_metadata
            else:
                metadata = xmcd.get(audiofile.track_number(),None)
        else:
            metadata = audiofile.get_metadata()

        convert_file(AudioType,
                     options.output,
                     audiofile,
                     metadata,
                     quality,
                     audiotools.SilentMessenger("track2track"),
                     options.thumbnail)

