#!/usr/bin/perl -w

my $mp3diagversion = "0.3";

# mp3diag.pl
# A little perl script to edit ID3v1/2 tags from mp3 files
# Author: Alberto Garcia <agarcia.at.igalia.dot.com>
# Based on mp3diag by Thorvald Natvig

# This script requires MP3::Tag from Thomas Geffert
# See http://tagged.sourceforge.net/ for details

###################
## Configuration ##
###################
my $id3version = 1; # Default ID3 version to edit (1 or 2)
my @dialogPrograms = qw(whiptail dialog Xdialog); # In order of preference
###################

# ChangeLog:
#
# 0.3
# $Date: 2005-12-29 00:50:18 +0100 (Qui, 29 Dez 2005) $
# mp3diag.pl will choose the first available dialog program from a list
# New command-line options to view, render and delete tags in batch mode
# Some internal changes and optimizations

# 0.2
# Date: 2005-11-30 17:02:39 +0100 (Qua, 30 Nov 2005)
# Fixed bug when switching tag versions.
# Minor optimizations.
# Config. variable to choose dialog flavour (whiptail, Xdialog, ...)
#
# 0.1
# Date: 2005-11-30 02:51:22 +0100 (Qua, 30 Nov 2005)
# Initial release

# 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; version 2 dated June, 1991.
#
# 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.
#
# http://www.gnu.org/licenses/gpl.txt

use strict;
use Getopt::Std;
use MP3::Tag;

# Some globals
my $mp3file;
my $id3;
my @tagsToEdit = (1, 1); # Edit both tags by default in batch mode
my %opt;
my $dialogProgram = "";

# Tag names in the same order used in $id3->all (see MP3::Tag::ID3v1)
my @id3v1names = qw(Title Artist Album Year Comment Track Genre);
my @id3v2names = qw(TIT2  TPE1   TALB  TYER COMM    TRCK  TCON);
my @renderopts = qw(s     a      A     y    c       t     g);
my @genres = @{MP3::Tag->genres};

####################################
## Command-line argument checking ##
####################################
if ($#ARGV < 0) {
    printUsage();
}

usageError() if (!getopts('12dvLs:a:A:y:g:t:c:',\%opt));

# ID3 version to use
if ($opt{'1'} && $opt{'2'}) {
    usageError("You cannot use both -1 and -2 options");
}
if ($opt{'1'}) {
    $id3version = 1;
    @tagsToEdit = (1, 0);
}
if ($opt{'2'}) {
    $id3version = 2;
    @tagsToEdit = (0, 1);
}

# List genres
if ($opt{'L'}) {
    for (my $i = 0; $i <= $#genres; $i++) {
        printf "%3d: %s\n", $i, $genres[$i];
    }
    exit 0;
}

# File checking
if ($#ARGV < 0) {
    usageError("No mp3 file selected");
} elsif ($#ARGV > 0) {
    usageError("Please select just one mp3 file");
}
my $filename = $ARGV[0];
if (!-f $filename) {
    print STDERR "Can't open file '$filename'\n";
    exit 66;
}

########################
## Tag initialization ##
########################
$mp3file = MP3::Tag->new($filename);
$mp3file->get_tags;

#################
## ID3 viewing ##
#################
if ($opt{'v'}) {
    foreach my $v (1, 2) {
        if ($tagsToEdit[$v-1]) {
            $id3version = $v;
            viewId3Tag();
        }
    }
    exit 0;
}

##################
## ID3 deleting ##
##################
if ($opt{'d'}) {
    foreach my $v (1, 2) {
        if ($tagsToEdit[$v-1]) {
            $id3version = $v;
            $id3->remove_tag() if (readId3Tag() == 0);
        }
    }
    exit 0;
}

###################
## ID3 rendering ##
###################
my $render = 0;
foreach my $o (@renderopts) {
    $render = 1 if (defined($opt{$o}));
}
if ($render) {
    foreach my $v (1, 2) {
        if ($tagsToEdit[$v-1]) {
            $id3version = $v;
            readId3Tag();
            for (my $i = 0; $i <= $#renderopts; $i++) {
                my $value = $opt{$renderopts[$i]};
                if (defined($value)) {
                    setId3($id3v1names[$i],$value);
                }
            }
            $id3->write_tag;
        }
    }
    exit 0;
}

######################
## Interactive mode ##
######################
while (!$dialogProgram && @dialogPrograms) {
    my $prog = shift(@dialogPrograms);
    $prog = `which $prog`;
    chomp($prog);
    $dialogProgram = $prog if (-x $prog);
}
if (!$dialogProgram) {
    print STDERR "No dialog program found.\n";
    exit 78;
}

my $noCancelOption = "--no-cancel";
$noCancelOption = "--nocancel" if ($dialogProgram =~ /whiptail/);

readId3Tag();

my $basefile = $filename;
$basefile =~ s,.*/,,;
my $versionName = "ID3v".$id3version;
my $versionName2 = "ID3v".(3-$id3version);

################################
## Interactive mode main loop ##
################################
while (1) {

    my $option = execDialog(
         $noCancelOption, "--menu","$basefile ($versionName)",
         "20","75","10", (map { $_ , getId3($_) }
          qw(Title Artist Album Year Genre Track Comment)),
         "Copy","Copy $versionName into $versionName2",
         "Switch","Edit $versionName2",
         "Exit","DONE"
    );

    my $newValue = undef;

    if ($option eq "Genre") {
        $newValue = chooseGenre(getId3($option));
    } elsif ($option eq "Copy") {
        copyId3Tag();
    } elsif ($option eq "Switch") {
        $id3version = 3-$id3version;
        ($versionName,$versionName2) = ($versionName2,$versionName);
        readId3Tag();
    } elsif (grep {$option eq $_} @id3v1names) {
        $newValue = execDialog("--inputbox",$option,"8","75",
                               getId3($option));
    } else {
        exit 0;
    }

    if (defined($newValue)) {
        setId3($option,$newValue);
        $id3->write_tag;
    }
}

exit 0;

###############
# Subroutines #
###############
sub usageError {
    my $err = shift;
    print STDERR "$err\n" if defined($err);
    print STDERR "Write 'mp3diag.pl' with no options for more info.\n";
    exit 64;
}

sub printUsage {
    print <<__EOT__;
mp3diag.pl v$mp3diagversion - A little dialog-based ID3 tag editor
(C) 2005 Alberto Garcia <agarcia.at.igalia.dot.com>

* Interactive mode: mp3diag.pl [-1|-2] <mp3 file>

  Use -1 or -2 to select the ID3 tag version to edit.
  Defaults to ID3v$id3version in interactive mode, and both versions in
  batch modes.

* Deleting a tag in batch mode: mp3diag.pl [-1|-2] -d <mp3 file>

* Viewing a tag in batch mode: mp3diag.pl [-1|-2] -v <mp3 file>

* Rendering a tag in batch mode: mp3diag.pl [-1|-2] [options] <mp3 file>

  -s NAME:  Song name
  -a NAME:  Artist name
  -A NAME:  Album name
  -y NUM:   Year
  -g GENRE: Genre (number or name: use mp3diag.pl -L to list genres)
  -t NUM:   Track number
  -c TEXT:  Comment

mp3diag.pl comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under the terms of the GNU GPL.
See the source code for details.
__EOT__
    exit 0;
}

sub viewId3Tag {
    my $name = "ID3v" . $id3version;
    if (readId3Tag()) {
        print "--$name not found--\n";
        return;
    }
    print "--$name--\n";
    foreach my $n (@id3v1names) {
        printf "%7s: %s\n", $n, getId3($n);
    }
    print "\n";
}

sub execDialog {
    my @args = @_;
    pipe (NEWIN,NEWOUT);
    my $pid = fork();
    if (!$pid) {
        open(STDERR,">&NEWOUT");
        exec($dialogProgram,@args);
    }
    close(NEWOUT);
    waitpid($pid,0);
    my $exitcode = $? >> 8;
    my $output = <NEWIN>;
    close (NEWIN);
    return undef if ($exitcode);
    return "" if (!defined($output));
    $output =~ s/^\s*(.*?)\s*$/$1/;
    return $output;
}

sub chooseGenre {
    my $genre = shift;
    my $i = 0;
    my @opts = map { $i++, $_, ($genre eq $_) ? 'on' : 'off' } @genres;
    my $newValue = execDialog("--radiolist","Genre","22","35","15",@opts);
    return $newValue;
}

sub readId3Tag {
    my $name = "ID3v" . $id3version;
    $id3 = $mp3file->{$name};
    return 0 if (defined($id3));
    $id3 = $mp3file->new_tag($name);
    return 1;
}

sub copyId3Tag {
    my %values = map { $_ => getId3($_) } @id3v1names;
    $id3version = 3-$id3version;
    readId3Tag();
    foreach (keys(%values)) {
        setId3($_,$values{$_});
    }
    $id3->write_tag;
    $id3version = 3-$id3version;
    readId3Tag();
}

sub setId3 {
    my $tagName = shift;
    my $value = shift;
    my $tagNumber = getTagNumber($tagName);
    if ($id3version == 1) {
        my @id3data = $id3->all;
        $id3data[$tagNumber] = $value;
        $id3->all(@id3data);
    } else {
        $id3->remove_frame($id3v2names[$tagNumber]);
        if ($value ne "") {
            if (($tagName eq "Genre") && ($value =~ /^\d+$/)) {
                $value = "($value)";
            }
            my @id3args = ($value);
            if ($tagName eq "Comment") {
                unshift(@id3args,'eng','');
            }
            $id3->add_frame($id3v2names[$tagNumber],@id3args);
        }
    }
}

sub getId3 {
    my $tagName = shift;
    my $tagNumber = getTagNumber($tagName);
    my $result;
    if ($id3version == 1) {
        my @id3data = $id3->all;
        $result = $id3data[$tagNumber];
        if (($tagName eq "Track") && ($result eq "0")) {
            $result = "";
        }
    } else {
        $result = $id3->get_frame($id3v2names[$tagNumber]);
        if ($tagName eq "Comment") {
            $result = $result->{'Text'};
        }
    }
    $result = "" if (!defined($result));
    return $result;
}

# getTagNumber returns the position of the tag name within @id3v1names
sub getTagNumber {
    my $tagName = shift;
    for (my $i = 0; $i <= $#id3v1names; $i++) {
        if ($id3v1names[$i] eq $tagName) {
            return $i;
        }
    }
    print STDERR "Tag $tagName unknown!\n";
    exit 70;
}
