#!/usr/bin/perl
# $Id: TrackInfo.pl,v 1.10 2010-06-13 18:09:48 steve Exp $
# TrackInfo [options] infile...
#
) {
if (/^[ \t]*$/) { } # blank line
elsif (/^[ \t]*\%.*$/) { } # comment: ignore
# Variable-setting macros:
elsif (/\\begin\{song/) { begSong($_); } # \begin{song}{title}
elsif (/\\title/) { $title = getContent($_); }
elsif (/\\subtitle/) { $subtitle = getContent($_); }
elsif (/\\key/) { $key = getContent($_); }
elsif (/\\tags/) { $tags = getContent($_); }
elsif (/\\category/) { $tags = getContent($_); }
elsif (/\\dedication/) { $dedication = getContent($_); }
elsif (/\\description/) { $description = getContent($_); }
elsif (/\\license/) { $license = getContent($_); }
elsif (/\\timing/) { $timing = getContent($_); }
elsif (/\\created/) { $created = getContent($_); }
elsif (/\\notice/) { $notice = getContent($_); }
elsif (/\\cvsid/) { $cvsid = getContent($_); }
elsif (/\\music/) { $music = getContent($_); }
elsif (/\\lyrics/) { $lyrics = getContent($_); }
elsif (/\\arranger/) { $arranger = getContent($_); }
elsif (/\\performer/) { $performer = getContent($_); }
elsif (/\\credits/) { $credits = getContent($_); }
elsif ($title) {
# everything's at the top, so we have it all now.
last;
}
}
close(IN);
return 1;
}
### getTrackInfo($filename)
# Get information for a track.
# The results are returned in global variables, which is ugly
# but works in this case.
#
sub getTrackInfo {
$filename = shift;
$shortname = $filename;
# Extract the shortname from the filename:
# a leading numeric prefix separated by hypens is ignored.
# everything after "." is ignored. This allows track numbers
# and qualifiers (foo.a, etc.)
if ($filename =~ /^(.+)\-\-/) {
$shortname = $1;
} elsif ($filename =~ /^.+\.([^.]+)(\.|\-\-)?/) {
$shortname = $1;
} elsif ($filename =~ /^([a-z]*[0-9]+\-+)?([^.]+)\.?/) {
$shortname = $2;
}
#print STDERR "filename=$filename; shortname=$shortname\n";
clearTrackInfo();
getSongFileInfo($shortname);
if (-f "./$filename.flk") {
getSongFileInfo($filename, ".");
}
#print STDERR "shortname = $shortname; title = $title\n";
$title = $shortname unless $title;
$longname = $title;
$longname =~ s/ /_/g;
$longname =~ s/[^0-9a-zA-Z_]/-/g;
# Set index_title (for sorting) from title
$index_title = "" . $title;
$index_title =~ s/^(An? |The )//;
# If lyricist specified but composer isn't, composer is the default
$music = $default_songwriter if ! $music && $lyrics;
# If lyricist isn't specified, it's the default
$lyrics = $default_songwriter unless $lyrics;
# If $credits isn't specified, construct it from $music and $lyrics
if (! $credits) {
$credits = last_name($lyrics);
$credits .= "/" . last_name($music) if $music && ($music ne $lyrics);
}
# look for a .wav file:
# o .wav file on the command line
# o Master/$filename.wav (only if format=cd (TOC))
# o ./$filename.wav
# o most recent .wav file in ../Tracks/$shortname/
$trackddir = "$trackDir/$shortname";
$trackddir = "" unless -d $trackddir;
if ($track) {
# track data (wav file) specified on command line
$track_data = $track;
$track = "";
} elsif ($filename =~ /.wav$/ && -f $filename) {
$track_data = $filename;
} elsif ($filename =~ /.iso$/ && -f $filename) {
$track_data = $filename;
} elsif (($format =~ /^cd/) && -f "Master/${filename}.wav") {
$track_data = "Master/${filename}.wav";
} elsif (-f "${filename}.wav") {
$track_data = "${filename}.wav";
} elsif ($trackddir) {
# track data from ../tracks/$shortname
# if there's more than one, it takes the last (most recent) one
$track_data = `ls -tr $trackddir | grep .wav | tail -1`;
$track_data = $trackddir . "/" . trim($track_data);
} else {
$track_data = "";
}
my $real_track_data = "Master/${filename}.wav";
if (-f $real_track_data && $want_timing) {
my $tt = `shntool len $real_track_data | tail -1`;
$tt =~ /([0-9]+)\:([0-9]+).([0-9]+)/;
$timing = "$1:$2";
if ($format eq "cd") {
$timing .= ":$3";
} elsif ($3 > 37) {
# round up to next second
$timing = "$1:" . sprintf("%02d", $2+1);
}
}
}
### mmss_to_seconds($timing)
sub mmss_to_seconds {
my ($tt) = @_;
$tt =~ /([0-9]+)\:([0-9]+)/;
return $1 * 60 + $2;
}
sub seconds_to_mmss {
my ($s) = @_;
my $m = int($s / 60);
#my $s -= $m * 60;
return sprintf("%2d:%02d", int($s/60), $s%60);}
### last_name($credits)
# used in short-form credits.
# special hacks to abbreviate "Trad." and
# eliminate a parenthesized phrase like (PD).
#
sub last_name {
my ($name) = @_;
if ($name =~ /[Tt]rad/) { $name = "Trad."; }
#if ($name =~ /[^ ]+ (*)$/) { $name = $1; }
if ($name =~ /([^ ]+)( +\([^\(]*\))?$/) { $name = $1; }
return $name;
}
sub printInfo {
$track_number = ($track_number)? $track_number + 1 : 1;
if ($format eq "cd" && $track_data =~ /.iso/) {
print "TRACK MODE1\n";
print "COPY\n";
print "CD_TEXT {\n";
print " LANGUAGE 0 {\n";
print " TITLE \"CD-ROM Data\"\n";
print " PERFORMER \"-\"\n";
print " SONGWRITER \"-\"\n";
print " COMPOSER \"-\"\n";
print " ARRANGER \"-\"\n" if $arranger;
print " }\n";
print "}\n";
print "DATAFILE \"$track_data\" 0\n";
} elsif ($format eq "cd") {
# One would think that there should be a subchannel, but that fails.
# === current cdrdao is probably screwed up somehow ===
# every track needs a composer now, so use the songwriter unless
# we already have one.
$music = $lyrics unless $music;
print "TRACK AUDIO\n";
print "COPY\n";
print "CD_TEXT {\n";
print " LANGUAGE 0 {\n";
print " TITLE \"$title\"\n";
print " PERFORMER \"$performer\"\n";
print " SONGWRITER \"$lyrics\"\n";
print " COMPOSER \"$music\"\n" if $music;
print " ARRANGER \"$arranger\"\n" if $arranger;
print " }\n";
print "}\n";
print "PREGAP 0:2:0\n";
if (! $track_data) {
$status = -1;
print STDERR "TrackInfo: No track data for $shortname ($title)\n";
print "SILENCE 0:0:1\n"; # make new cdrdao happy
} elsif ($track_data_not_padded) {
print "SILENCE 0:0:1\n"; # make new cdrdao happy
print "FILE \"$track_data\" 0\n"; # don't add $timing if not padded
} else {
print "FILE \"$track_data\" 0 $timing \n";
}
} elsif ($format =~ /files$/) {
# Just the track data file names
print "$track_data";
} elsif ($format eq "songs") {
# Just the corresponding shortnames
print "$shortname";
} elsif ($format eq "list.text") {
# the timing really needs to come off the track_data if present ===
my $c = $show_credits? "($credits)" : "";
my $t = $want_timing? " $timing " : "";
my $d = ($hex && $dec)? sprintf("(%02d) ", $track_number) : "";
if ($hex) {
print sprintf("0x%02x", $track_number) . " $d$t$title $c";
} else {
print sprintf(" %2d:", $track_number) . " $t$title $c";
}
if ($long) {
$description =~ s/\n[ \t]*/\n /gs;
print "\n $description";
}
} elsif ($format eq "list.html") {
# note: when making changes in the number of columns,
# remember to make corresponding changes in the
# following description row.
my $d = ($hex && $dec)? sprintf("(%02d) ", $track_number) : "";
print (" \n");
print (" " .
($hex? sprintf("0x%02x", $track_number) : $track_number) .
" | \n");
print (" " .
sprintf("(%02d)", $track_number) .
" | \n") if $hex && $dec;
# === should just make these columns conditional on $sound_links
if ($sound_links) {
print (" ");
if (-f "$f.ogg") {
# put the sound file first -- this is a concert after all
print " [ogg]";
}
print (" | \n");
print (" ");
if (-f "$f.mp3") {
# put the sound file first -- this is a concert after all
print " [mp3]";
}
print (" | \n");
}
print (" ");
if (-d "$songDir/$shortname") {
print "$title";
} elsif (-f "./$shortname.flk") {
# There's a local .flk file:
# check for a local lyrics page or song directory
if ( -f "./$shortname.html") {
print "$title";
} elsif ( -d "./$shortname") {
print "$title";
} else {
# === should really go on to check for (.+)_(.+) as below
print $title;
}
} elsif ($shortname =~ /(.+)_(.+)/) {
$s1 = $1; $s2 = $2;
if ($title =~ /(.+) *\/ *(.+)/) {
$t1 = $1; $t2 = $2; $ts = " / ";
} else {
$t1 = $s1; $t2 = $s2; $ts = " ";
}
if (-d "$songDir/$s1") {
print "$t1$ts";
} else {
print "$t1$ts";
}
if (-d "$songDir/$s2") {
print "$t2";
} else {
print $t2;
}
} else {
print "$title";
}
print (" ($credits)") if $show_credits;
print (" ($timing)") if $timing && ! $show_credits;
print (" | \n");
print ("
");
if ($long && $description) {
print (" \n");
print (" | \n");
print (" | \n") if $hex && $dec;
print (" | \n") if $sound_links;
print (" | \n") if $sound_links;
print (" $description\n");
print (" | \n");
print ("
");
}
} elsif ($format eq "ol.html") {
print (" ");
if (-f "$f.ogg") {
# put the sound file first -- this is a concert after all
print " [ogg]";
}
if (-d "$songDir/$shortname") {
print "$title";
} elsif (-f "$songDir/$shortname.html") {
print "$title";
} else {
print "$title";
}
print " ";
} elsif ($format eq "java") {
# Java uses hierarchical property names of the form a.b
# so we can use $shortname.property
} elsif ($format eq "ogg") {
# Output an oggenc argument list.
print "-a '$performer' " if $performer;
print "-t \"$title\" " if $title;
print "-c 'songwriter=$lyrics' " if $lyrics;
print "-c 'composer=$music' " if $music;
print "-c 'arranger=$arranger' " if $arranger;
print "-l \"$ctitle\" " if $ctitle;
# === needs license and url
print "$track_data\n";
} elsif ($format eq "mp3") {
# Output a lame argument list.
print "--ta '$performer' " if $performer;
print "--tt \"$title\" " if $title;
#print "-c 'songwriter=$lyrics' ";
#print "-c 'composer=$music' " if $music;
#print "-c 'arranger=$arranger' " if $arranger;
print "--tl \"$ctitle\" " if $ctitle;
# === needs license and url
print "-\n"; # assume stdin -- "$track_data\n";
} elsif ($format eq "shell") {
# Shell is name='value' -- need single quotes to prevent expansion
print "shortname='$shortname'\n";
print "longname='$longname'\n";
print "filename='$filename'\n";
print "title=\"$title\"\n";
print "index_title=\"$index_title\"\n";
print "track_number='$track_number'\n";
print "subtitle='$subtitle'\n" if $subtitle;
# can't (easily) have multiline items in shell format
#print "dedication='$dedication'\n" if $dedication;
#print "description='$description'\n" if $description;
# === needs license and url
$music = $lyrics unless $music;
print "lyrics='$lyrics'\n";
print "music='$music'\n" if $music;
print "arranger='$arranger'\n" if $arranger;
print "timing='$timing'\n" if $timing;
print "tags='$tags'\n" if $tags;
print "key='$key'\n" if $key;
print "created='$created'\n" if $created;
print "cvsid='$cvsid'\n" if $cvsid;
} elsif ($format eq "symlinks") {
# make symlinks in $dir for track files with long names
# the link target should either be in . or $dir.
# $dir becomes a self-contained album directory.
my $s = sprintf("%02d-$longname", $track_number);
print " $s";
for my $e ("ogg", "mp3", "flac") {
if ( -f "$dir/$shortname.$e" ) {
`cd $dir; ln -s $shortname.$e $s.$e`;
} elsif ( -f "$shortname.$e" ) {
# if the file is in ., make a symlink to it to ensure that
# the directory is self-contained.
`cd $dir; ln -s ../$shortname.$e .; ln -s $shortname.$e $s.$e`;
}
}
if ( -d "Master" && -f "Master/$shortname.wav" ) {
`cd Master; ln -s $shortname.wav $s.wav`;
}
} else {
# Sort of a generic java/make format suitable for a only single song
print "shortname=$shortname\n";
print "longname=$longname\n";
print "filename=$filename\n";
print "title=$title\n";
print "index_title=$index_title\n";
print "track_number=$track_number\n";
print "subtitle=$subtitle\n" if $subtitle;
print "lyrics=$lyrics\n";
print "music=$music\n" if $music;
print "arranger=$arranger\n" if $arranger;
print "performer=$performer\n" if $performer;
print "timing=$timing\n" if $timing;
print "tags=$tags\n" if $tags;
print "key=$key\n" if $key;
print "created=$created\n" if $created;
print "cvsid=$cvsid\n" if $cvsid;
#$notice $license $dedication
}
}
sub printHeading {
if ($format eq "cd" && $ctitle) {
# Print header for CDR TOC
# note that wodim insists on a performer, composer, and songwriter
# for every CD even if it's a mix. Use the default performer, and
# use the default songwriter for the composer because I'm a singer-
# songwriter. Eventually need to parametrize them.
#
# multisession disks have to be type CD_ROM_XA; single-session
# data disks should be CD_ROM, but we're not doing that yet.
#
if ($multi) { print "CD_ROM_XA"; }
elsif ($cdrom) { print "CD_ROM" ; }
else { print "CD_DA"; }
print "
CD_TEXT {
LANGUAGE_MAP {
0 : EN
}
LANGUAGE 0 {
TITLE \"$ctitle\"
PERFORMER \"$default_performer\"
COMPOSER \"$default_songwriter\"
SONGWRITER \"$default_songwriter\"
}
}\n\n";
} elsif ($format eq "tracklist" && $ctitle) {
print "Track list for $ctitle\n";
} elsif ($format eq "list.html") {
print "\n";
} elsif ($format eq "ol.html") {
print "\n";
}
$html = 1 if $format =~ /html/;
setupFormattingConstants();
}
sub printFooting {
if ($format eq "list.html") {
if ($show_ttime) {
print "total: | "
. seconds_to_mmss($total_time)
. ($untimed? " ($untimed untimed)" : "")
. " |
\n";
}
print "
\n";
} elsif ($format eq "ol.html") {
print "\n";
} elsif ($format eq 'list.text' && $show_ttime) {
print "total: "
. seconds_to_mmss($total_time)
. " in $i tracks"
. ($untimed? " ($untimed untimed)" : "")
. "\n";
}
}
########################################################################
###
### Macro handlers:
###
### Each of the following routines handles a LaTeX macro.
###
### Separate verses.
sub sepVerse {
if ($vlines) { endVerse(); }
}
### Handle a blank line.
sub blankLine {
if ($vlines) { endVerse(); }
if ($plain) {
print "\n";
$plines = 0;
}
}
### Begin a song:
### Stash the title.
sub begSong {
my ($line) = @_; # input line
$line =~ s/^.*song\}//;
$title = getContent($line);
}
########################################################################
###
### Block conversion:
###
### Each of these routines converts the start or end of a
### delimited block of lines to output format.
###
sub doHeader {
if ($html) { htmlHeader(); }
else { textHeader(); }
$header ++;
}
sub center {
# === need to handle multiple lines ===
my ($text) = @_;
$text =~ s/^[ \t]*//;
$text =~ s/[ \t]*\n$//;
$text =~ s/\\copyright/Copyright/;
my $w = $WIDTH - length($text);
for ( ; $w > 0; $w -= 2) { $text = " " . $text; }
print "$text\n";
}
sub hcenter {
my ($h, $text) = @_;
$text =~ s/^[ \t]*//;
$text =~ s/\\copyright/\©/;
$text =~ s/\n/\
/g;
$text = "$text";
print "$text\n";
}
sub textHeader {
center "$title\n";
if ($subtitle) { center "$subtitle\n"; }
if ($notice) { center "$notice\n"; }
if ($license) { center "$license\n"; }
if ($dedication) { center "$dedication\n"; }
print "\n";
}
sub htmlHeader {
hcenter 1, $title;
if ($subtitle) { hcenter 2, $subtitle; }
if ($notice) { hcenter 3, $notice; }
if ($license) { hcenter 3, $license; }
if ($dedication) { hcenter 3, $dedication; }
print "\n";
}
sub footer {
}
########################################################################
###
### Line conversion:
###
### Each of these routines converts a single line of mixed chords
### and text.
###
### Process the current line:
### Does any necessary dispatching.
sub doLine {
# Put out the header, if this is the very first line.
if (! $header) { doHeader(); }
if ($plain) {
if ($plines == 0) {
if ($html) { print "\n"; }
else { print "\n"; }
}
$_ = deTeX($_);
#if ($html) { s/\~/ /g; } else { s/\~/ /g; }
s/\\newline/$NL/g;
s/\\\///g;
indentLine($_, $indent);
$plines ++;
} else {
if ($vlines == 0) { begVerse(); }
if ($tables) { print tableLine($_); }
else { print chordLine($_); }
$vlines ++;
}
}
### Put out a plain line, possibly indented.
sub indentLine {
my ($line, $indent) = @_;
$line =~ s/^[ \t]*//;
while ($indent--) { $line = " ".$line; }
print $line;
}
### Convert an ordinary line to chords + text
# === does not insert indent yet.
sub chordLine {
my ($line) = @_; # input line
my $cline = ""; # chord line
my $dline = ""; # dest. (text) line
my ($scol, $ccol, $dcol, $inchord, $inmacro) = ($indent, 0, 0, 0, 0);
my $c = ''; # current character
my $p = 0; # current position
$line = deTeX($line);
$line =~ s/^[ \t]*//;
$line =~ s/\\sus/sus/g;
$line =~ s/\\min/m/g;
for ($p = 0; $p < length($line); $p++) {
$c = substr($line, $p, 1);
if ($c eq "\n" || $c eq "\r") { break; }
if ($c eq '[') { $inchord ++; }
elsif ($c eq ']') { $inchord --; }
elsif ($c eq ' ') { if (!$inchord) { $scol ++; } }
elsif ($c eq "\t") {
if (!$inchord) { do {$scol ++; } while ($scol % 8); } }
else {
if ($inchord) {
while ($ccol < $scol) { $cline .= ' '; $ccol ++ }
$cline .= $c;
$ccol ++;
} else {
while ($dcol < $scol) { $dline .= ' '; $dcol ++ }
$dline .= $c;
$dcol ++;
$scol++;
}
}
}
# The result has a newline appended to it.
return (($cline eq "")? $dline : $cline . "\n" . $dline);
}
### Convert a line to a table
### When using tables, each line becomes a separate table.
### This, in turn, becomes a row in a table containing the verse.
sub tableLine {
}
### Convert a line to XML
sub xmlLine {
}
### Formatting constants:
sub setupFormattingConstants {
if ($html) {
$EM = "";
$_EM = "";
$BF = "";
$_BF = "";
$TT = "";
$_TT = "";
$UL = "";
$_UL = "";
$SMALL = "";
$_SMALL = "";
$SPOKEN = "(spoken)";
$_SPOKEN = "";
$NL = "
\n";
$NP = "
\n";
$SP = " ";
$AMP = "&";
$FLKTRAN = "flktran
";
# Creative Commons copyright notice
$CCnotice = "Some rights reserved.";
} else {
$EM = "_";
$_EM = "_";
$BF = "*";
$_BF = "*";
$TT = "";
$_TT = "";
$UL = "";
$_UL = "";
$SMALL = "";
$_SMALL = "";
$SPOKEN = "(spoken)";
$_SPOKEN = "";
$NL = "\n";
$NP = "\f";
$SP = " ";
$AMP = "&";
$FLKTRAN = "flktran";
$CCnotice = "Some Rights Reserved: CC by-nc-sa/2.0/";
}
}
### Remove LaTeX constructs.
### This would be easier with a table.
sub deTeX {
my ($txt) = @_; # input line
while ($txt =~ /\%/) { # TeX comments eat the line break, too.
$txt =~ s/\%.*$//;
$txt .= ;
}
while ($txt =~ /\{\\em[ \t\n]/
|| $txt =~ /\{\\tt[ \t\n]/
|| $txt =~ /\{\\bf[ \t\n]/) {
# This will fail if there's a \bf and \em in one line in that order
if ($txt =~ /\{\\em[ \t\n]/) {
$txt =~ s/\{\\em[ \t\n]/$EM/;
while (! $txt =~ /\}/) { $txt .= ; }
$txt =~ s/\}/$_EM/;
}
if ($txt =~ /\{\\tt[ \t\n]/) {
$txt =~ s/\{\\tt[ \t\n]/$TT/;
while (! $txt =~ /\}/) { $txt .= ; }
$txt =~ s/\}/$_TT/;
}
if ($txt =~ /\{\\bf[ \t\n]/) {
$txt =~ s/\{\\bf[ \t\n]/$BF/;
while (! $txt =~ /\}/) { $txt .= ; }
$txt =~ s/\}/$_BF/;
}
if ($txt =~ /\{\\small[ \t\n]/) {
$txt =~ s/\{\\small[ \t\n]/$SMALL/;
while (! $txt =~ /\}/) { $txt .= ; }
$txt =~ s/\}/$_SMALL/;
}
}
if ($html) {
$txt =~ s/\~/ /g;
$txt =~ s/---/--/g; # no &emdash;?
} else {
$txt =~ s/\~/ /g;
$txt =~ s/---/--/g;
}
while ($txt =~ /\\link\{[^}]+\}\{[^}]+\}/s) {
if ($html) {
$txt =~ s/\\link\{([^}]+)\}\{([^}]+)\}/$2<\/a>/s;
} else {
$txt =~ s/\\link\{([^}]+)\}\{([^}]+)\}/$2/s;
}
}
$txt =~ s/\\&/$AMP/g;
$txt =~ s/\\;/$SP/g;
$txt =~ s/\\ /$SP/g;
$txt =~ s/\\ldots/.../g;
$txt =~ s/\\\\/$NL/g;
$txt =~ s/\\min/m/g;
$txt =~ s/\\capo/ capo/g;
return $txt
}
### getContent(line): get what's between macro braces.
#
sub getContent {
my ($line) = @_; # input line
# Throw away everything up to the "{"
$line =~ s/^[^{]*\{//;
$line = deTeX($line);
# Suck in more lines if we haven't seen the closing brace
# NOTE that we have to use the same file handle as getSongInfo!!
while ($line !~ /\}/) { $line .= ; $line = deTeX($line); }
# Throw away everything after the "}"
$line =~ s/\}[^}]*$//;
$line =~ s/\n$//;
return $line;
}