#!/usr/local/bin/perl
#
# titlefix.pl
#
# Author: resident alian
#
# Date created:  06 Aug 2004
#
# Date modified: 16 Oct 2006
#
# Description: titlefix renames files, correcting titles with standard English
#		title case -- all middle words in an artist or title are
#		capitalized except: articles, prepositions, and conjunctions
#		less than 5 letters long in the artist or title name
#	When called as titleid3, it uses the formats described below to tag
#		mp3s with ID3V2 and ID3V1 tag info, but does no renaming (for
#		when case is to be preserved).
#	When called as titlefixid3, it does both renaming and tagging.
#	When called as single[fix][id3], it does the same as its title*
#		counterpart without looking for a track number.
#
# License: GPL
#
# Homepage: http://titlefix.sourceforge.net/
#
# Revision history:
# 16 Oct 2006: Version 0.5.3: fixed '$' in filename when tagging; fixed help;
#	fixed processing of files with extensions with lengths other than 3;
#	added processing for flac (same as mp3)
# 20 Sep 2006: Version 0.5.2: added protection for "single"-style files (i.e.
#	no 2-digit track number prefix in the filename) to be treated as
#	"single" files even when title* is called; added bitrate/"alt" suffix
#	removal for title tag (filename left unchanged); added "o'" to list;
#	fixed renaming for directories and regular file with no extension
# 07 Sep 2006: Version 0.5.1: fixed singlefix renaming; added bitrate/"alt"
# 16 Aug 2006: Version 0.5: changed progname checking to use regexp (in case of
#	.pl suffix); added singlefix functionality (no NN prefix); fixed
#	tagging without renaming and help message -- d'oh!!; added test
#	in Makefile (call by "make test" in source folder)
# 15 Aug 2006: Version 0.4: used symbolic links for specific functionality;
#	disabled renaming for id3-only variant; made help message the default
#	action with no arguments; created manpage
# 29 Jun 2006: used Perl builtin rename instead of shell command mv; made strict
# 16 Nov 2005: Version 0.3: removed .pl extension (duh on my part), changed
#	example file name
# 04 Jun 2005: removed "but" from list
# 24 Feb 2005: fixed case where no artist is given (doesn't add fabricated
#	artist)
# 11 Feb 2005: Version 0.2: fixed help text, fixed matching all middle words
#	(i.e. all words not next to another word or number (except first word
#	in artist name))
# 02 Feb 2005: added "As" to list
# 01 Feb 2005: replaces two single quotes in filename with double quote in tag
# 27 Jan 2005: expanded (so far) to allow three-song title (2 dashes in title)
# 25 Jan 2005: removed "yet" from list, assumed .mp3 extension, used -h for
#	help and modularized (with subroutines)
# 21 Jan 2005: added "De", "Du", "En", "Por", "Y" to list
# 19 Jan 2005: removed "so" from list
#

use strict;

my $debug = 0;
my $do_stuff = 1;

my @lc_words = qw/a an the and for nor or as at by for from in o' of on to with de du en por y/;
my @uc_words = qw/A An The And For Nor Or As At By For From In O' Of On To With De Du En Por Y/;

my $fn_ex = "12 Front 242 - One - With The Fire.mp3";
my $fn_ex_mod = "12 Front 242 - One - With the Fire.mp3";

my $fn;
my @files;

my $progname = $0;

my $do_rename = 0;
my $do_tag = 0;
my $use_tn = 1;
my $file_to_process = "file_to_rename";

$progname =~ s/^.*\///g;

## customize functionality script's called filename ##
if (($progname =~ /^titlefixid3/) or ($progname =~ /^singlefixid3/))
{
  $do_rename = 1;
  $do_tag = 1;
  $file_to_process = "mp3_file_to_rename_and_tag";
}
elsif (($progname =~ /^titleid3/) or ($progname =~ /^singleid3/))
{
  $do_rename = 0;
  $do_tag = 1;
  $file_to_process = "mp3_file_to_tag";
}
else ## assume titlefix ##
{
  $do_rename = 1;
  $do_tag = 0;
  $file_to_process = "file_to_rename";
}

if ($progname =~ /^single/)
{
  ## assume no track number prefix NN in filename ##
  $use_tn = 0;
  $fn_ex =~ s/^...//g;
  $fn_ex_mod =~ s/^...//g;
}

### fixit start ###
sub fixit
{
  my ($fn) = @_;
  my $art;
  my $tt;
  my $tn;
  my $fn_mod;
  my $cmd;
  my $i;
  my $use_art = 1;
  my $use_tn_this_file = $use_tn;

  ## position of (first) " - ":
  my $dashpos = index($fn, " - ");

  ## position of (last) ".":
  my $dotpos = rindex($fn, ".");

  my $ext;
  my $ttendpos;

  if ($dotpos == -1)
  {
    $ext = "";
    $ttendpos = -1;
  }
  else
  {
    $ext = substr($fn, $dotpos + 1);
    $ttendpos = -1 - length($ext);
    $ext = ".$ext";
  }

  ## track number:
  ## protect accidental usage of "title" variants where "single" variant
  ##   is more appropriate (i.e. no 2-digit track number prefix found):
  if (!($fn =~ /^[0-9][0-9] /))
  {
    $use_tn_this_file = 0;
  }
  if ($use_tn_this_file)
  {
    $tn = substr($fn, 0, 2);
  }
  else
  {
    $tn = "";
  }

  ## artist and track title:
  if ($dashpos eq "-1")
  {
    $use_art = 0;
    $art = "";
    if ($use_tn_this_file)
    {
      if ($ext eq "")
      {
        $tt = substr($fn, 3);
      }
      else
      {
        $tt = substr($fn, 3, $ttendpos);
      }
    }
    else
    {
      if ($ext eq "")
      {
        $tt = $fn;
      }
      else
      {
        $tt = substr($fn, 0, $ttendpos);
      }
    }
  }
  else
  {
    if ($use_tn_this_file)
    {
      $art = substr($fn, 3, $dashpos - 3);
    }
    else
    {
      $art = substr($fn, 0, $dashpos);
    }

    if ($ext eq "")
    {
      $tt = substr($fn, $dashpos + 3);
    }
    else
    {
      $tt = substr($fn, $dashpos + 3, $ttendpos);
    }
  }

  ## renaming code ##

  if ($do_rename)
  {
    ## do word replacement
    for ($i = 0; $i <= $#lc_words; ++$i)
    {
      my $uw;
      my $lw;

      $uw = @uc_words[$i];
      $lw = @lc_words[$i];
      #if ($debug)
      #{
        #print("*** replacing \"$uw\" with \"$lw\"\n");
      #}
      $art =~ s/(\w )$uw( \w)/\1$lw\2/g;
      $tt =~ s/(\w )$uw( \w)/\1$lw\2/g;
    }

    ## check if no artist
    if ($art eq "")
    {
      $tt =~ s/^ //g; ## don't remember why this is here!

      if ($use_tn_this_file)
      {
        $art = substr($fn, 3, $dashpos - 3);
        $fn_mod = "$tn $tt$ext";
      }
      else
      {
        $art = substr($fn, 0, $dashpos - 3);
        $fn_mod = "$tt$ext";
      }
    }
    else
    {
      if ($use_tn_this_file)
      {
        $fn_mod = "$tn $art - $tt$ext";
      }
      else
      {
        $fn_mod = "$art - $tt$ext";
      }
    }

    if ($fn eq $fn_mod)
    {
      print("(file \"$fn\" OK)\n");
    }
    else
    {
      if ((-e $fn_mod) and (lc($fn) ne lc($fn_mod))) ## unlikely ##
      {
        print("*** destination $fn_mod exists!\n");
      }
      else
      {
        if ($do_stuff)
        {
          rename $fn, $fn_mod;
        }
        else
        {
          print("renaming \"$fn\" to \"$fn_mod\"\n");
        }
      }
    }
  }

  ## '' => " conversion
  $art =~ s/\'\'/\\\"/g;
  $tt =~ s/\'\'/\\\"/g;

  if ($debug)
  {
    print(STDERR "DEBUG: dashpos: $dashpos
DEBUG: tn: $tn
DEBUG: art: $art
DEBUG: tt: $tt
");
  }

  ## tagging code ##

  ## protect non-mp3s and non-flacs from tagging attempts ##
  if (!(($fn =~ /\.[Mm][Pp]3$/) or ($fn =~ /\.[Ff][Ll][Aa][Cc]$/)))
  {
    $do_tag = 0;
  }

  if ($do_tag)
  {
    my $tag_art = "";
    my $tag_tn = "";
    my $tag_tt = $tt;

    if (!$do_rename)
    {
      $fn_mod = $fn;
    }

    if ($use_art)
    {
      $tag_art = " -a \"$art\"";
    }

    if ($use_tn_this_file)
    {
      $tag_tn = " -T \"$tn\"";
    }
    else
    {
      ## don't tag bitrates specified in files ##
      ## just to be sure, only do this for "single" mp3s ##
      $tag_tt =~ s/ (112|128|160|192|224|256|320|vbr|alt)$//g;
    }

    $cmd = "id3v2$tag_tn$tag_art -t \"$tag_tt\" \"$fn_mod\"";

    ## escape any dollar signs (otherwise interpreted as variables) ##
    $cmd =~ s/\$/\\\$/g;

    if ($do_stuff)
    {
      print("$cmd\n");
      system($cmd);
    }
    else
    {
      print("$cmd\n");
    }
  }
} ### fixit ###

### main start ###
## check for test option ##
if (($ARGV[0] =~ /^-t/) or ($ARGV[0] eq "--test"))
{
  $do_stuff = 0;
  if (($ARGV[0] eq "--test") or ($ARGV[0] eq "-t"))
  {
    shift @ARGV;
  }
  if ($ARGV[0] =~ /^-t/)
  {
    $ARGV[0] =~ s/^-t/-/g;
  }
}

## check for help option or no arguments ##
if (($ARGV[0] =~ /^-h/) or ($ARGV[0] eq "--help") or ($ARGV[0] eq ""))
{
  my $tnplaceholder = "NN ";

  if (!$use_tn)
  {
    $tnplaceholder = "";
  }

  print("
usage: $progname [-t] [$file_to_process | -h | -l | -a] ...

for example:

\t$progname \"$fn_ex\"

results in this file name:

\t\"$fn_ex_mod\"
");

  if ($do_tag)
  {
    print("
note: this format must be used for each file:

\t${tnplaceholder}Artist - Title.mp3

special note: if this format is used, no artist will be tagged:

\t${tnplaceholder}Title.mp3

");
  }

  print("
switches:

\t-t,
\t--test\tprints processing commands but doesn't execute them

\t-a,
\t--all\tprocesses *.mp3 in the current directory

\t-f,
\t--all-flac\tprocesses *.flac in the current directory

\t-h,
\t--help\tdisplays help and exits (ignores remaining arguments)

\t-l,
\t--list\tprints list of words to make lowercase and exits (ignores remaining arguments)

also see manpage:

\tman $progname
");

  exit(0);
}

## check for list option ##
if (($ARGV[0] =~ /^-l/) or ($ARGV[0] eq "--list"))
{
  print("
words to make lowercase:

@lc_words

");

  exit(0);
}

## check for all-mp3 option ##
if (($ARGV[0] eq "-a") or ($ARGV[0] eq "--all"))
{
  @files = glob("*.[Mm][Pp]3");
  foreach $fn (@files)
  {
    &fixit($fn);
  }
}
elsif (($ARGV[0] eq "-f") or ($ARGV[0] eq "--all-flac"))
{
  @files = glob("*.[Ff][Ll][Aa][Cc]");
  foreach $fn (@files)
  {
    &fixit($fn);
  }
}
else
{
  ## file argument(s) => just do that/those file(s) ##
  foreach $fn (@ARGV)
  {
    &fixit($fn);
  }
}
### main end ###
