#!/usr/bin/python

#Audio Tools, a module and set of tools for manipulating audio data
#Copyright (C) 2007-2012  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 sys
import os
import tempfile
from itertools import izip
import audiotools
import audiotools.ui
import audiotools.text as _
import termios


def limit(l, indexes):
    """given a list and set of indexes (starting from 1)
    returns a new list with only those items"""

    return [item for (index, item) in enumerate(l)
            if ((index + 1) in indexes)]


if (__name__ == '__main__'):
    parser = audiotools.OptionParser(
        usage=_.USAGE_DVDA2TRACK,
        version="Python Audio Tools %s" % (audiotools.VERSION))

    parser.add_option(
        '-I', '--interactive',
        action='store_true',
        default=False,
        dest='interactive',
        help=_.OPT_INTERACTIVE_OPTIONS)

    parser.add_option(
        '-c', '--cdrom', action='store',
        type='string', dest='cdrom',
        default=audiotools.DEFAULT_CDROM)

    parser.add_option('-A', '--audio-ts', action='store', default=None,
                      type='string', dest='audio_ts', metavar='DIR',
                      help=_.OPT_AUDIO_TS)

    parser.add_option('--title', action='store', default=1,
                      type='int', dest='title',
                      help=_.OPT_DVDA_TITLE)

    parser.add_option(
        '-V', '--verbose',
        action='store',
        dest='verbosity',
        choices=audiotools.VERBOSITY_LEVELS,
        default=audiotools.DEFAULT_VERBOSITY,
        help=_.OPT_VERBOSE)

    conversion = audiotools.OptionGroup(parser, _.OPT_CAT_EXTRACTION)

    conversion.add_option(
        '-t', '--type',
        action='store',
        dest='type',
        choices=sorted(audiotools.TYPE_MAP.keys() + ['help']),
        default=audiotools.DEFAULT_TYPE,
        help=_.OPT_TYPE)

    conversion.add_option(
        '-q', '--quality',
        action='store',
        type='string',
        dest='quality',
        help=_.OPT_QUALITY)

    conversion.add_option(
        '-d', '--dir',
        action='store',
        type='string',
        dest='dir',
        default='.',
        help=_.OPT_DIR)

    conversion.add_option(
        '--format',
        action='store',
        type='string',
        default=audiotools.FILENAME_FORMAT,
        dest='format',
        help=_.OPT_FORMAT)

    parser.add_option_group(conversion)

    lookup = audiotools.OptionGroup(parser, _.OPT_CAT_DVDA_LOOKUP)

    lookup.add_option(
        '--musicbrainz-server', action='store',
        type='string', dest='musicbrainz_server',
        default=audiotools.MUSICBRAINZ_SERVER,
        metavar='HOSTNAME')
    lookup.add_option(
        '--musicbrainz-port', action='store',
        type='int', dest='musicbrainz_port',
        default=audiotools.MUSICBRAINZ_PORT,
        metavar='PORT')
    lookup.add_option(
        '--no-musicbrainz', action='store_false',
        dest='use_musicbrainz',
        default=audiotools.MUSICBRAINZ_SERVICE,
        help=_.OPT_NO_MUSICBRAINZ)

    lookup.add_option(
        '--freedb-server', action='store',
        type='string', dest='freedb_server',
        default=audiotools.FREEDB_SERVER,
        metavar='HOSTNAME')
    lookup.add_option(
        '--freedb-port', action='store',
        type='int', dest='freedb_port',
        default=audiotools.FREEDB_PORT,
        metavar='PORT')
    lookup.add_option(
        '--no-freedb', action='store_false',
        dest='use_freedb',
        default=audiotools.FREEDB_SERVICE,
        help=_.OPT_NO_FREEDB)

    lookup.add_option(
        '-D', '--default',
        dest='use_default', action='store_true', default=False,
        help=_.OPT_DEFAULT)

    parser.add_option_group(lookup)

    metadata = audiotools.OptionGroup(parser, _.OPT_CAT_METADATA)

    metadata.add_option(
        '--track-start',
        action='store',
        type='int',
        dest='track_start',
        default=None,
        help=_.OPT_TRACK_START)

    metadata.add_option(
        '--track-total',
        action='store',
        type='int',
        dest='track_total',
        default=None,
        help=_.OPT_TRACK_TOTAL)

    metadata.add_option(
        '--album-number',
        dest='album_number',
        action='store',
        type='int',
        default=None,
        help=_.OPT_ALBUM_NUMBER)

    metadata.add_option(
        '--album-total',
        dest='album_total',
        action='store',
        type='int',
        default=None,
        help=_.OPT_ALBUM_TOTAL)

    #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=_.OPT_REPLAY_GAIN)

    metadata.add_option(
        '--no-replay-gain',
        action='store_false',
        dest='add_replay_gain',
        help=_.OPT_NO_REPLAY_GAIN)

    parser.add_option_group(metadata)

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

    #ensure interactive mode is available, if selected
    if (options.interactive and (not audiotools.ui.AVAILABLE)):
        audiotools.ui.not_available_message(msg)
        sys.exit(1)

    #get the AudioFile class we are converted to
    if (options.type == 'help'):
        audiotools.ui.show_available_formats(msg)
        sys.exit(0)
    else:
        AudioType = audiotools.TYPE_MAP[options.type]

    #ensure the selected compression is compatible with that class
    if (options.quality == 'help'):
        audiotools.ui.show_available_qualities(msg, AudioType)
        sys.exit(0)
    elif (options.quality is None):
        options.quality = audiotools.__default_quality__(AudioType.NAME)
    elif (options.quality not in AudioType.COMPRESSION_MODES):
        msg.error(_.ERR_UNSUPPORTED_COMPRESSION_MODE %
                  {"quality": options.quality,
                   "type": AudioType.NAME})
        sys.exit(1)

    quality = options.quality
    base_directory = options.dir

    if (options.audio_ts is None):
        msg.error(_.ERR_NO_AUDIO_TS)
        sys.exit(1)

    #get main DVDAudio object
    try:
        dvda = audiotools.DVDAudio(options.audio_ts, options.cdrom)
    except audiotools.InvalidDVDA, err:
        msg.error(unicode(err))
        sys.exit(1)
    except OSError, err:
        msg.os_error(err)
        sys.exit(1)

    #get selected DVDATitle from DVDAudio object
    if (options.title < 1):
        msg.error(ERR_INVALID_TITLE_NUMBER)
        sys.exit(1)
    title = dvda[0][options.title - 1]

    #use DVDATtitle object to query metadata services for metadata choices
    metadata_choices = title.metadata_lookup(
        musicbrainz_server=options.musicbrainz_server,
        musicbrainz_port=options.musicbrainz_port,
        freedb_server=options.freedb_server,
        freedb_port=options.freedb_port,
        use_musicbrainz=options.use_musicbrainz,
        use_freedb=options.use_freedb)

    #update MetaData with command-line
    #track_start, track_total, album_number, album_total - if given
    for c in metadata_choices:
        for m in c:
            if (((options.track_start is not None) and
                 (m.track_number is not None))):
                #track_number and track_start both start at 1
                #so minus 1 to take care of the offset
                m.track_number += (options.track_start - 1)
            if (options.track_total is not None):
                m.track_total = options.track_total
            if (options.album_number is not None):
                m.album_number = options.album_number
            if (options.album_total is not None):
                m.album_total = options.album_total

    #determine which tracks to extract from the DVDATitle
    if (len(args) == 0):
        to_rip = dict([(track.track, track) for track in title])
    else:
        to_rip = {}
        for arg in args:
            try:
                to_rip[int(arg)] = title[int(arg)]
            except IndexError:
                continue
            except ValueError:
                continue

    #decide which metadata and output options use when extracting tracks
    if (options.interactive):
        #pick choice using interactive widget
        output_widget = audiotools.ui.OutputFiller(
            track_labels=[_.LAB_TRACK_X_OF_Y % (i, len(title))
                          for i in sorted(to_rip.keys())],
            metadata_choices=[limit(c, to_rip.keys())
                              for c in metadata_choices],
            input_filenames=[
                audiotools.Filename("track-%d-%2.2d.dvda.wav" %
                                    (options.title, i))
                for i in sorted(to_rip.keys())],
            output_directory=options.dir,
            format_string=options.format,
            output_class=AudioType,
            quality=options.quality,
            completion_label=_.LAB_DVDA2TRACK_APPLY)

        loop = audiotools.ui.urwid.MainLoop(
            output_widget,
            audiotools.ui.style(),
            unhandled_input=output_widget.handle_text,
            pop_ups=True)
        try:
            loop.run()
            msg.ansi_clearscreen()
        except (termios.error, IOError):
            msg.error(_.ERR_TERMIOS_ERROR)
            msg.info(_.ERR_TERMIOS_SUGGESTION)
            msg.info(audiotools.ui.xargs_suggestion(sys.argv))
            sys.exit(1)

        if (not output_widget.cancelled()):
            output_tracks = list(output_widget.output_tracks())
        else:
            sys.exit(0)
    else:
        #pick choice without using GUI
        try:
            output_tracks = list(
                audiotools.ui.process_output_options(
                    metadata_choices=[limit(c, to_rip.keys())
                                      for c in metadata_choices],
                    input_filenames=[
                        audiotools.Filename("track-%d-%2.2d.dvda.wav" %
                                            (options.title, i))
                        for i in sorted(to_rip.keys())],
                    output_directory=options.dir,
                    format_string=options.format,
                    output_class=AudioType,
                    quality=options.quality,
                    msg=msg,
                    use_default=options.use_default))
        except audiotools.UnsupportedTracknameField, err:
            err.error_msg(msg)
            sys.exit(1)
        except (audiotools.InvalidFilenameFormat,
                audiotools.OutputFileIsInput,
                audiotools.DuplicateOutputFile), err:
            msg.error(unicode(err))
            sys.exit(1)

    #perform actual track extraction
    encoded = []
    pcmreader = title.to_pcm()
    for (i, pts_ticks) in enumerate([t.pts_length for t in title]):
        pcmreader.next_track(pts_ticks)
        pcm_frames = pcmreader.pcm_frames

        if ((i + 1) in to_rip):
            (output_class,
             output_filename,
             output_quality,
             output_metadata) = output_tracks.pop(0)

            try:
                audiotools.make_dirs(str(output_filename))
            except OSError, err:
                msg.os_error(err)
                sys.exit(1)

            progress = audiotools.SingleProgressDisplay(
                msg, unicode(output_filename))

            try:
                encoded.append(
                    output_class.from_pcm(
                        str(output_filename),
                        audiotools.PCMReaderProgress(pcmreader,
                                                     pcm_frames,
                                                     progress.update),
                        output_quality))
            except audiotools.EncodingError, err:
                msg.error(_.ERR_ENCODING_ERROR % (output_filename,))
                sys.exit(1)

            encoded[-1].set_metadata(output_metadata)
            progress.clear()

            msg.info(
                audiotools.output_progress(
                    _.LAB_ENCODE % {
                        "source": _.LAB_DVDA_TRACK %
                        {"title_number": options.title,
                         "track_number": i + 1},
                        "destination": output_filename},
                    len(to_rip) - len(output_tracks),
                    len(to_rip)))
        else:
            #bypass skipped tracks by decoding them to null
            audiotools.transfer_framelist_data(pcmreader, lambda f: f)

    #add ReplayGain to ripped tracks, if necessary
    if ((audiotools.ADD_REPLAYGAIN and
         (options.add_replay_gain if (options.add_replay_gain is not None)
          else output_class.lossless_replay_gain()) and
         output_class.can_add_replay_gain(encoded))):
        rg_progress = audiotools.ReplayGainProgressDisplay(
            msg, output_class.lossless_replay_gain())
        rg_progress.initial_message()
        try:
            #all audio files must belong to the same album, by definition
            output_class.add_replay_gain([f.filename for f in encoded],
                                         rg_progress.update)
        except ValueError, err:
            rg_progress.clear()
            msg.error(unicode(err))
            sys.exit(1)
        rg_progress.final_message()
