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

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

if (audiotools.ui.AVAILABLE):
    urwid_present = True
    urwid = audiotools.ui.urwid
    from audiotools.ui import get_focus

    class PathAlbum(audiotools.ui.Album):
        def update_tracks(self):
            """updates metadata of Album's tracks
            returns total number of tracks updated"""

            total = 0

            for (track, metadata) in zip(self.tracks, self.get_metadata()):
                track_metadata = track.track.get_metadata()
                updated = 0
                if (track_metadata is not None):
                    #transfer metadata to track's native format
                    for (attr, field) in metadata.fields():
                        if (getattr(track_metadata, attr) != field):
                            setattr(track_metadata, attr, field)
                            updated = 1
                    if (updated == 1):
                        #but only perform update if any changes are made
                        track.track.update_metadata(track_metadata)
                else:
                    track.track.set_metadata(metadata)
                    updated = 1

                total += updated

            return total

    class PathTrack(audiotools.ui.Track):
        FIELDS = [(u"Path", "path"),
                  (u"Track Name", "track_name"),
                  (u"Track Number", "track_number"),
                  (u"Album", "album_name"),
                  (u"Artist", "artist_name"),
                  (u"Performer", "performer_name"),
                  (u"Composer", "composer_name"),
                  (u"Conductor", "conductor_name"),
                  (u"ISRC", "ISRC"),
                  (u"Copyright", "copyright"),
                  (u"Recording Date", "date"),
                  (u"Comment", "comment")]

        def __init__(self, track, metadata):
            """takes an AudioFile"""

            audiotools.ui.Track.__init__(self, metadata)
            self.track = track

            self.path = urwid.Text(
                audiotools.VerboseMessenger("").filename(self.track.filename))

    class Tracktag(audiotools.ui.FocusFrame):
        (EDITING_LIST_ONLY,
         EDITING_LIST_W_FIELDS,
         EDITING_FIELDS,
         APPLY_LIST,
         APPLY_LIST_W_FIELDS,
         UNKNOWN_STATE) = range(6)

        #the Tracktag UI states are:
        #|                  | fields closed     | fields open           |
        #|------------------+-------------------+-----------------------|
        #| cursor on list   | EDITING_LIST_ONLY | EDITING_LIST_W_FIELDS |
        #| cursor on fields | N/A               | EDITING_FIELDS        |
        #| cursor on apply  | APPLY_LIST        | APPLY_LIST_W_FIELDS   |
        #|------------------+-------------------+-----------------------|

        def __init__(self, tracks):
            def by_album(a1, a2):
                c = cmp(a1[0][1], a2[0][1])
                if (c == 0):
                    return cmp(a1[0][2], a2[0][2])
                else:
                    return c

            #group tracks by album
            #(track_total, album_name, album_number, album_total) ->
            # [(track, metadata), ...]
            grouped_tracks = {}
            for track in tracks:
                try:
                    metadata = track.get_metadata()
                    if (metadata is None):
                        metadata = audiotools.MetaData()
                except IOError:
                    continue

                grouped_tracks.setdefault((metadata.track_total,
                                           metadata.album_name,
                                           track.album_number(),
                                           metadata.album_total),
                                          []).append((track, metadata))

            #build Albums from grouped tracks
            #which are used to build AlbumList
            albums = [PathAlbum([PathTrack(*t) for t in tracks])
                      for (album, tracks) in
                      sorted(grouped_tracks.items(), by_album)]

            #attach on-change callback to Albums and Tracks
            #to activate the "<Apply>" button's styling
            for album in albums:
                for field in ["album_name",
                              "artist_name",
                              "performer_name",
                              "composer_name",
                              "conductor_name",
                              "media",
                              "catalog",
                              "copyright",
                              "publisher",
                              "year",
                              "date",
                              "comment"]:
                    urwid.connect_signal(getattr(album, field),
                                         'change',
                                         self.modified)
                for track in album.tracks:
                    for field in ["track_name",
                                  "artist_name",
                                  "performer_name",
                                  "composer_name",
                                  "conductor_name",
                                  "ISRC",
                                  "copyright",
                                  "date",
                                  "comment"]:
                        urwid.connect_signal(getattr(track, field),
                                             'change',
                                             self.modified)

            self.cancel = urwid.Button("Quit", on_press=self.exit)
            self.status = urwid.Text(u"", align='left')
            self.collapsed = urwid.Divider(div_char=u'\u2500')
            self.__modified__ = False

            self.track_selector = audiotools.ui.AlbumList(
                albums, self.select_item)
            if (len(albums) > 0):
                self.save = urwid.Button("Apply", on_press=self.save)
                controls = urwid.BoxAdapter(
                    urwid.ListBox([
                            urwid.GridFlow([self.save, self.cancel],
                                           10, 5, 1, 'center'),
                            self.status]),
                    2)
                self.work_area = audiotools.ui.FocusFrame(
                    body=urwid.Filler(self.track_selector),
                    footer=self.collapsed)
                self.work_area.set_focus_callback(self.update_focus)
                self.has_selector = True
            else:
                controls = urwid.BoxAdapter(
                    urwid.ListBox([
                            urwid.GridFlow([self.cancel],
                                           10, 5, 1, 'center'),
                            self.status]),
                    2)

                self.work_area = urwid.Frame(body=urwid.Filler(urwid.Text(
                            u"please select 1 or more tracks " +
                            u"from the command line", align='center')))
                self.has_selector = False

            audiotools.ui.FocusFrame.__init__(self,
                                              body=self.work_area,
                                              footer=controls)

            if (len(albums) > 0):
                self.set_focus_callback(self.update_focus)
            else:
                self.set_focus('footer')

        def get_state(self):
            if (self.work_area.get_footer() is not self.collapsed):
                #fields open
                if ((get_focus(self) == 'body') and
                    (get_focus(self.work_area) == 'body')):
                    #cursor on list
                    return self.EDITING_LIST_W_FIELDS
                elif ((get_focus(self) == 'body') and
                      (get_focus(self.work_area) == 'footer')):
                    #cursor on fields
                    return self.EDITING_FIELDS
                elif (get_focus(self) == 'footer'):
                    #cursor on apply/close
                    return self.APPLY_LIST_W_FIELDS
                else:
                    #unknown cursor position
                    return self.UNKNOWN_STATE
            else:
                #fields closed
                if ((get_focus(self) == 'body') and
                    (get_focus(self.work_area) == 'body')):
                    #cursor on list
                    return self.EDITING_LIST_ONLY
                elif ((get_focus(self) == 'body') and
                      (get_focus(self.work_area) == 'footer')):
                    #cursor on fields
                    return self.UNKNOWN_STATE
                elif (get_focus(self) == 'footer'):
                    #cursor on apply/close
                    return self.APPLY_LIST
                else:
                    #unknown cursor position
                    return self.UNKNOWN_STATE

        def set_state_message(self, state):
            if (state != self.UNKNOWN_STATE):
                self.set_keys([
                        #EDITING_LIST_ONLY
                        [(u"esc", u"go to Apply / Quit buttons")],

                        #EDITING_LIST_W_FIELDS
                        [(u"esc", u"close fields"),
                         (u"tab", u"return to fields")],

                        #EDITING_FIELDS
                        [(u"esc", u"close fields"),
                         (u"tab", u"return to list")],

                        #APPLY_LIST
                        [(u"esc", u"return to list")],

                        #APPLY_LIST_W_FIELDS
                        [(u"esc", u"return to list")]
                        ][state])
            else:
                self.status.set_text(u"")

        def select_item(self, checkbox, state_change, user_data=None):
            if (state_change == True):
                #select item
                self.work_area.set_footer(
                    urwid.BoxAdapter(user_data, user_data.field_count()))
                self.work_area.set_focus('footer')
            elif (state_change == False):
                #unselect item
                self.work_area.set_footer(self.collapsed)
                self.work_area.set_focus('body')

        def handle_text(self, i):
            state = self.get_state()
            if (state == self.EDITING_LIST_ONLY):
                if (i == 'esc'):
                    self.set_focus('footer')
            elif (state == self.EDITING_LIST_W_FIELDS):
                if (i == 'tab'):
                    self.work_area.set_focus('footer')
                    self.set_focus('body')
                elif (i == 'esc'):
                    for checkbox in self.track_selector.radios:
                        checkbox.set_state(False, do_callback=False)
                    self.work_area.set_footer(self.collapsed)
                    self.work_area.set_focus('body')
                    self.set_focus('body')
            elif (state == self.EDITING_FIELDS):
                if (i == 'tab'):
                    self.work_area.set_focus('body')
                    self.set_focus('body')
                elif (i == 'esc'):
                    for checkbox in self.track_selector.radios:
                        checkbox.set_state(False, do_callback=False)
                    self.work_area.set_footer(self.collapsed)
                    self.work_area.set_focus('body')
                    self.set_focus('body')
            elif (state == self.APPLY_LIST):
                if (i == 'esc'):
                    self.set_focus('body')
                    self.work_area.set_focus('body')
            elif (state == self.APPLY_LIST_W_FIELDS):
                if (i == 'esc'):
                    self.set_focus('body')
                    self.work_area.set_focus('body')

        def update_focus(self, widget, focus_part):
            """widget is a urwid.Frame subclass, focus_part is a string"""

            self.set_state_message(self.get_state())

        def set_keys(self, keys):
            """keys is a [(key, action), ...] list
            where 'key' and 'action' are both strings"""

            text = []
            for (last, (key, action)) in audiotools.iter_last(iter(keys)):
                text.append(('key', key))
                text.append(u" - " + action)
                if (not last):
                    text.append(u"  ")

            self.status.set_text(text)

        def modified(self, widget, new_value):
            if (not self.__modified__):
                self.save.set_label(('modified', u'Apply'))
                self.__modified__ = True

        def save(self, button, arg=None):
            if (self.__modified__):
                self.status.set_text(u"updating tracks")
                self.draw_screen()
                total = 0
                for album in self.track_selector.albums:
                    total += album.update_tracks()

                if (total != 1):
                    self.status.set_text(u"%d tracks updated" % (total))
                else:
                    self.status.set_text(u"%d track updated" % (total))
                self.save.set_label(u"Apply")
                self.__modified__ = False
            else:
                self.set_keys([(u"esc", u"return to list")])

        def exit(self, button, arg=None):
            raise urwid.ExitMainLoop()

else:
    urwid_present = False


#tries to return a populated Image object of the appropriate type
#raises InvalidImage if something goes wrong during opening or parsing
def get_raw_image(filename, type):
    try:
        f = open(filename, 'rb')
        data = f.read()
        f.close()

        return audiotools.Image.new(data, u'', type)
    except IOError:
        raise audiotools.InvalidImage(_(u"Unable to open file"))


def get_thumbnailed_image(filename, type):
    image = get_raw_image(filename, type)
    if ((image.width > audiotools.THUMBNAIL_SIZE) or
        (image.height > audiotools.THUMBNAIL_SIZE)):
        return image.thumbnail(audiotools.THUMBNAIL_SIZE,
                               audiotools.THUMBNAIL_SIZE,
                               audiotools.THUMBNAIL_FORMAT)
    else:
        return image


#given a comment filename
#returns the comment as a unicode string
#or exits with an error if the file cannot be read
#or is not UTF-8 text
def read_comment(filename, messenger):
    try:
        f = open(filename, 'rb')
        data = f.read().decode('utf-8', 'replace')
        f.close()

        if (((data.count(u"\uFFFD") * 100) / len(data)) >= 10):
            messenger.error(
                _(u"Comment file \"%s\" does not appear to be UTF-8 text") %
                (messenger.filename(filename)))
            sys.exit(1)
        else:
            return data
    except IOError:
        messenger.error(_(u"Unable to open comment file \"%s\"") % \
                            (messenger.filename(filename)))
        sys.exit(1)


if (__name__ == '__main__'):
    #add an enormous number of options to the parser
    #neatly categorized for convenience
    parser = audiotools.OptionParser(
        usage=_(u"%prog [options] <track 1> [track 2] ..."),
        version="Python Audio Tools %s" % (audiotools.VERSION))

    parser.add_option(
        '-I', '--interactive',
        action='store_true',
        default=False,
        dest='interactive',
        help=_(u'run in interactive mode'))

    text_group = audiotools.OptionGroup(parser, _(u"Text Options"))

    for (option, destination, helptext) in [
        ('--name', 'track_name', _(u"the name of the track")),
        ('--artist', 'artist_name', _(u'the name of the artist')),
        ('--performer', 'performer_name', _(u'the name of the performer')),
        ('--composer', 'composer_name', _(u'the name of the composer')),
        ('--conductor', 'conductor_name', _(u'the name of the conductor')),
        ('--album', 'album_name', _(u'the name of the album')),
        ('--catalog', 'catalog', _(u'the catalog number of the album')),
        ('--number', 'track_number',
         _(u"the number of the track in the album")),
        ('--track-total', 'track_total',
         _(u"the total number of tracks in the album")),
        ('--album-number', 'album_number',
         _(u'the number of the album in a set of albums')),
        ('--album-total', 'album_total',
         _(u"the total number of albums in a set of albums")),
        ('--ISRC', 'ISRC', _(u'the ISRC of the track')),
        ('--publisher', 'publisher', _(u'the publisher of the album')),
        ('--media-type', 'media_type',
         _(u'the media type of the album, such as "CD"')),
        ('--year', 'year', _(u'the year of release')),
        ('--date', 'date', _(u'the date of recording')),
        ('--copyright', 'copyright', _(u'copyright information')),
        ('--comment', 'comment', _(u'a text comment'))]:
        if (destination not in audiotools.MetaData.INTEGER_FIELDS):
            text_group.add_option(option,
                                  action='store',
                                  type='string',
                                  dest=destination,
                                  help=helptext)
        else:
            text_group.add_option(option,
                                  action='store',
                                  type='int',
                                  dest=destination,
                                  help=helptext)

    text_group.add_option(
        '--comment-file',
        action='store',
        type='string',
        dest='comment_file',
        metavar='FILENAME',
        help=_(u'a file containing comment text'))

    parser.add_option_group(text_group)

    parser.add_option(
        '-r', '--replace',
        action='store_true',
        default=False,
        dest='replace',
        help=_(u'completely replace all metadata'))

    parser.add_option(
        '--cue',
        action='store',
        type='string',
        dest='cue',
        metavar='FILENAME',
        help=_(u'a cuesheet to import or get audio metadata from'))

    img_group = audiotools.OptionGroup(parser, _(u"Image Options"))

    img_group.add_option(
        '--remove-images',
        action='store_true',
        default=False,
        dest='remove_images',
        help=_(u'remove existing images prior to adding new ones'))

    for (option, destination, helptext) in [
        ('--front-cover', 'front_cover',
         _(u'an image file of the front cover')),
        ('--back-cover', 'back_cover',
         _(u'an image file of the back cover')),
        ('--leaflet', 'leaflet', _(u'an image file of a leaflet page')),
        ('--media', 'media', _(u'an image file of the media')),
        ('--other-image', 'other_image',
         _(u'an image file related to the track'))]:
        img_group.add_option(
            option,
            action='append',
            type='string',
            dest=destination,
            metavar='FILENAME',
            help=helptext)

    img_group.add_option(
        '-T', '--thumbnail',
        action='store_true',
        default=False,
        dest='thumbnail',
        help=_(u'convert given images to smaller thumbnails ' +
               u'before adding'))

    parser.add_option_group(img_group)

    remove_group = audiotools.OptionGroup(parser, _(u"Removal Options"))

    for (option, destination, helptext) in [
        ('--remove-name', 'remove_track_name', _(u'remove track name')),
        ('--remove-artist', 'remove_artist_name', _(u'remove track artist')),
        ('--remove-performer', 'remove_performer_name',
         _(u'remove track performer')),
        ('--remove-composer', 'remove_composer_name',
         _(u'remove track composer')),
        ('--remove-conductor', 'remove_conductor_name',
         _(u'remove track conductor')),
        ('--remove-album', 'remove_album_name', _(u'remove album name')),
        ('--remove-catalog', 'remove_catalog', _(u'remove catalog number')),
        ('--remove-number', 'remove_track_number',
         _(u'remove track number')),
        ('--remove-track-total', 'remove_track_total',
         _(u'remove total number of tracks')),
        ('--remove-album-number', 'remove_album_number',
         _(u'remove album number')),
        ('--remove-album-total', 'remove_album_total',
         _(u'remove total number of albums')),
        ('--remove-ISRC', 'remove_ISRC', _(u'remove ISRC')),
        ('--remove-publisher', 'remove_publisher', _(u'remove publisher')),
        ('--remove-media-type', 'remove_media_type',
         _(u'remove album\'s media type')),
        ('--remove-year', 'remove_year', _(u'remove release year')),
        ('--remove-date', 'remove_date', _(u'remove recording date')),
        ('--remove-copyright', 'remove_copyright',
         _(u'remove copyright information')),
        ('--remove-comment', 'remove_comment', _(u'remove text comment'))]:
        remove_group.add_option(
            option,
            action='store_true',
            default=False,
            dest=destination,
            help=helptext)

    parser.add_option_group(remove_group)

    parser.add_option(
        '--replay-gain',
        action='store_true',
        default=False,
        dest='add_replay_gain',
        help=_(u'add ReplayGain metadata to track(s)'))

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

    parser.add_option(
        '-V', '--verbose',
        action='store',
        dest='verbosity',
        choices=audiotools.VERBOSITY_LEVELS,
        default=audiotools.DEFAULT_VERBOSITY,
        help=_(u'the verbosity level to execute at'))

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

    #open our set of input files for tagging
    audiofiles = audiotools.open_files(args, messenger=msg)

    #and the --cue file
    isrcs = {}
    import_cuesheet = None
    if (options.cue is not None):
        try:
            cuesheet = audiotools.read_sheet(options.cue)

            #if there's a single audio file
            #and the cuesheet is sized to fit that file
            #attempt to embed the cuesheet in the file
            if ((len(audiofiles) == 1) and
                (list(cuesheet.pcm_lengths(
                            audiofiles[0].total_frames()))[-1] > 0)):
                import_cuesheet = cuesheet
            else:
                #otherwise, treat the cuesheet is a source of ISRC data
                isrcs = dict([(k, v.decode('ascii', 'replace'))
                              for (k, v) in
                              cuesheet.ISRCs().items()])
        except audiotools.SheetException, err:
            msg.error(unicode(err))
            sys.exit(1)

    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)

        get_image = get_thumbnailed_image
    else:
        get_image = get_raw_image

    for file in audiofiles:
        track_modified = False

        #determine which MetaData to use as our base
        #depending on whether we're performing a full replacement
        if (not options.replace):
            metadata = file.get_metadata()
            if (metadata is not None):
                update_method = "update_metadata"
            else:
                metadata = audiotools.MetaData()
                update_method = "set_metadata"
        else:
            metadata = audiotools.MetaData()
            update_method = "set_metadata"

        #apply tagging options to that metadata in reverse order of precedence

        #perform the image tagging
        try:
            if (metadata.supports_images()):
                if (options.remove_images):
                    for i in metadata.images():
                        metadata.delete_image(i)
                        track_modified = True

                if (options.front_cover is not None):
                    for path in options.front_cover:
                        metadata.add_image(get_image(path, 0))
                        track_modified = True

                if (options.leaflet is not None):
                    for path in options.leaflet:
                        metadata.add_image(get_image(path, 2))
                        track_modified = True

                if (options.back_cover is not None):
                    for path in options.back_cover:
                        metadata.add_image(get_image(path, 1))
                        track_modified = True

                if (options.media is not None):
                    for path in options.media:
                        metadata.add_image(get_image(path, 3))
                        track_modified = True

                if (options.other_image is not None):
                    for path in options.other_image:
                        metadata.add_image(get_image(path, 4))
                        track_modified = True
        except audiotools.InvalidImage, err:
            msg.error(_(u"%(filename)s: %(message)s") % \
                          {"filename": msg.filename(file.filename),
                           "message": unicode(err)})
            sys.exit(1)

        #apply text field removal
        for field in ('track_name',
                      'artist_name',
                      'performer_name',
                      'composer_name',
                      'conductor_name',
                      'album_name',
                      'catalog',
                      'track_number',
                      'track_total',
                      'album_number',
                      'album_total',
                      'ISRC',
                      'publisher',
                      'media_type',
                      'year',
                      'date',
                      'copyright',
                      'comment'):
            if (getattr(options, 'remove_' + field)):
                delattr(metadata, field)
                track_modified = True

        if (options.track_number is not None):
            track_number = options.track_number
        else:
            track_number = file.track_number()

        #handle cuesheet ISRC data
        if (track_number in isrcs):
            metadata.ISRC = isrcs[track_number]
            track_modified = True

        #update fields from the command line
        for field in ('track_name',
                      'artist_name',
                      'performer_name',
                      'composer_name',
                      'conductor_name',
                      'album_name',
                      'catalog',
                      'track_number',
                      'track_total',
                      'album_number',
                      'album_total',
                      'ISRC',
                      'publisher',
                      'media_type',
                      'year',
                      'date',
                      'copyright',
                      'comment'):
            if (getattr(options, field) is not None):
                attr = getattr(options, field)
                if (isinstance(attr, str)):
                    attr = attr.decode(audiotools.IO_ENCODING, 'replace')
                setattr(metadata, field, attr)
                track_modified = True

        #add comment-file
        if (options.comment_file is not None):
            metadata.comment = read_comment(options.comment_file, msg)
            track_modified = True

        #check if there's been any modifications made
        if (track_modified or
            (import_cuesheet is not None) or
            options.replace):
            try:
                #either set or update metadata
                #depending on whether we're performing a full replacement
                getattr(file, update_method)(metadata)

                #insert embedded cuesheet file
                if (import_cuesheet is not None):
                    file.set_cuesheet(import_cuesheet)
            except IOError:
                msg.error(_(u"Unable to modify \"%s\"") % \
                              msg.filename(file.filename))
                sys.exit(1)

    #add/apply replay_gain to tracks if indicated
    if (options.add_replay_gain and
        (len(audiofiles) > 0) and
        (audiofiles[0].can_add_replay_gain())):
        #separate encoded files by album_name and album_number
        try:
            queue = audiotools.ExecProgressQueue(
                audiotools.ProgressDisplay(msg))

            for album in audiotools.group_tracks(audiofiles):
                #add ReplayGain to groups of files
                #belonging to the same album

                album_number = set([a.album_number() for a in album]).pop()
                audio_type = album[0].__class__

                if (album_number == 0):
                    if (audio_type.lossless_replay_gain()):
                        progress_text = _(u"Adding ReplayGain")
                        completion_output = _(u"ReplayGain added")
                    else:
                        progress_text = _(u"Applying ReplayGain")
                        completion_output = _(u"ReplayGain applied")
                else:
                    if (audio_type.lossless_replay_gain()):
                        progress_text = (
                            _(u"Adding ReplayGain to album %d") %
                            (album_number))
                        completion_output = (
                            _(u"ReplayGain added to album %d") %
                            (album_number))
                    else:
                        progress_text = (
                            _(u"Applying ReplayGain to album %d") %
                            (album_number))
                        completion_output = (
                            _(u"ReplayGain applied to album %d") %
                            (album_number))

                queue.execute(audio_type.add_replay_gain,
                              progress_text,
                              completion_output,
                              [a.filename for a in album])

            queue.run(options.max_processes)
        except ValueError, err:
            msg.error(unicode(err))
            sys.exit(1)

    #finally, after all the command-line arguments are processed
    #run interactive mode if indicated
    if (options.interactive):
        if (not urwid_present):
            msg.error(_(u"urwid is required for interactive mode"))
            msg.output(_(u"Please download and install urwid " +
                         u"from http://excess.org/urwid/"))
            msg.output(_(u"or your system's package manager."))
            sys.exit(1)

        tracktag = Tracktag(audiofiles)
        loop = urwid.MainLoop(tracktag,
                              [('key', 'white', 'dark blue'),
                               ('albumname', 'default,underline', 'default'),
                               ('modified', 'default,bold', 'default', '')],
                              unhandled_input=tracktag.handle_text)
        tracktag.draw_screen = loop.draw_screen
        loop.run()
