Index: contrib/mythlink.pl
===================================================================
--- contrib/mythlink.pl	(revision 7175)
+++ contrib/mythlink.pl	(working copy)
@@ -26,14 +26,43 @@
     our ($db_host, $db_user, $db_name, $db_pass, $video_dir);
     our ($hostname, $dbh, $sh);
 
+# Default filename format
+    $dformat = '%T - %Y-%m-%d, %g-%i %A - %S';
+# Default separator character
+    $dseparator_char = '-';
+# Default replacement character
+    $dreplacement_char = '-';
+# Default space character
+    $dspace_char = ' ';
+
+# Provide default values for GetOptions
+    $format = $dformat;
+    $separator_char = $dseparator_char;
+    $replacement_char = $dreplacement_char;
+    $space_char = $dspace_char;
+
 # Load the destination directory, if one was specified
-    GetOptions('dest|destination|path=s' => \$dest,
-               'format=s'                => \$format,
-               'usage|help|h'            => \$usage,
+    GetOptions('dest|destination|path=s'    => \$dest,
+               'format=s'                   => \$format,
+               'sepchar|separator-char=s'   => \$separator_char,
+               'repchar|replacement-char=s' => \$replacement_char,
+               'space|space-char=s'         => \$space_char,
+               'usage|help|h'               => \$usage,
               );
 
-# Default filename format
-    $format ||= '%T - %Y-%m-%d, %g-%i %A - %S';
+# Check the separator, replacement, and space characters for illegal characters
+    if ($separator_char =~ /(?:[\/\\:*?<>|"])/) {
+      print "Illegal separator character specified.  Using default.\n";
+      $separator_char = $dseparator_char;
+    }
+    if ($replacement_char =~ /(?:[\/\\:*?<>|"])/) {
+      print "Illegal replacement character specified.  Using default.\n";
+      $replacement_char = $dreplacement_char;
+    }
+    if ($space_char =~ /(?:[\/\\:*?<>|"])/) {
+      print "Illegal space character specified.  Using default.\n";
+      $space_char = $dspace_char;
+    }
 
 # Print usage
     if ($usage) {
@@ -51,11 +80,14 @@
 
 --format
 
-    default:  $format
+    default:  $dformat
 
     \%T = title    (aka show name)
     \%S = subtitle (aka episode name)
     \%R = description
+    \%C = category (as reported by grabber)
+    \%U = recording group
+    \%p = separator character
     \%y = year, 2 digits
     \%Y = year, 4 digits
     \%n = month
@@ -71,9 +103,38 @@
     \%a = am/pm
     \%A = AM/PM
 
-    * Illegal filename characters will be replaced with dashes.
+    * For end time, prepend an "e" to the appropriate time/date format code
+      above; i.e. "\%eG" gives the 24-hour hour for the end time.
+
+    * For original airdate, prepend an "o" to the year, month, or day format
+      codes above; i.e. "\%oY" gives the year in which the episode was first
+      aired.
+
     * A suffix of .mpg or .nuv will be added where appropriate.
 
+--separator-char
+
+    The character used to separate sections of the link name.  Specifying the
+    separator character allows trailing separators to be removed from the link
+    name and multiple separators caused by missing data to be consolidated.
+    Indicate the separator character in the format string using either a
+    literal character or the '%p' specifier.
+
+    default:  '$dseparator_char'
+
+--replacement-char
+
+    Characters in the link name which are not legal on some filesystems will
+    be replaced with the given character
+
+    default:  '$dreplacement_char'
+
+--space-char
+
+    Use the specified character instead of space in the link name
+
+    default:  '$dspace_char'
+
 --help
 
     Show this help text.
@@ -165,7 +226,7 @@
     }
 
 # Prepare a database query
-    $sh = $dbh->prepare('SELECT title, subtitle, description FROM recorded WHERE chanid=? AND starttime=? AND endtime=?');
+    $sh = $dbh->prepare('SELECT title, subtitle, description, recgroup, category, originalairdate FROM recorded WHERE chanid=? AND starttime=? AND endtime=?');
 
 # Create symlinks for the files on this machine
     foreach my $file (<$video_dir/*.nuv>) {
@@ -182,8 +243,9 @@
     # Query the desired info about this recording
         $sh->execute($channel, "$syear$smonth$sday$shour$sminute$ssecond", "$eyear$emonth$eday$ehour$eminute$esecond")
             or die "Could not execute ($q):  $!\n\n";
-        my ($title, $subtitle, $description) = $sh->fetchrow_array;
+        my ($title, $subtitle, $description, $recgroup, $category, $oad) = $sh->fetchrow_array;
     # Format some fields we may be parsing below
+        # Start time
         my $meridian = ($shour > 12) ? 'AM' : 'PM';
         my $hour = ($shour > 12) ? $shour - 12 : $shour;
         if ($hour < 10) {
@@ -192,11 +254,28 @@
         elsif ($hour < 1) {
             $hour = 12;
         }
+        # End time
+        my $emeridian = ($ehour > 12) ? 'AM' : 'PM';
+        my $ethour = ($ehour > 12) ? $ehour - 12 : $ehour;
+        if ($ethour < 10) {
+            $ethour = "0$ethour";
+        }
+        elsif ($ethour < 1) {
+            $ethour = 12;
+        }
+        # Original airdate
+        # Handle NULL values for original airdate.
+        $oad ||= '0000-00-00';
+        my ($oyear, $omonth, $oday) = split(/\-/, $oad, 3);
     # Build a list of name format options
         my %fields;
         ($fields{'T'} = ($title       or '')) =~ s/%/%%/g;
         ($fields{'S'} = ($subtitle    or '')) =~ s/%/%%/g;
         ($fields{'R'} = ($description or '')) =~ s/%/%%/g;
+        ($fields{'C'} = ($category    or '')) =~ s/%/%%/g;
+        ($fields{'U'} = ($recgroup    or '')) =~ s/%/%%/g;
+        $fields{'p'} = $separator_char;     # separator character
+        # Start time
         $fields{'y'} = substr($syear, 2);   # year, 2 digits
         $fields{'Y'} = $syear;              # year, 4 digits
         $fields{'n'} = int($smonth);        # month
@@ -211,18 +290,40 @@
         $fields{'s'} = $ssecond;            # seconds
         $fields{'a'} = lc($meridian);       # am/pm
         $fields{'A'} = $meridian;           # AM/PM
+        # End time
+        $fields{'ey'} = substr($eyear, 2);  # year, 2 digits
+        $fields{'eY'} = $eyear;             # year, 4 digits
+        $fields{'en'} = int($emonth);       # month
+        $fields{'em'} = $emonth;            # month, leading zero
+        $fields{'ej'} = int($eday);         # day of month
+        $fields{'ed'} = $eday;              # day of month, leading zero
+        $fields{'eg'} = int($ethour);       # 12-hour hour
+        $fields{'eG'} = int($ehour);        # 24-hour hour
+        $fields{'eh'} = $ethour;            # 12-hour hour, with leading zero
+        $fields{'eH'} = $ehour;             # 24-hour hour, with leading zero
+        $fields{'ei'} = $eminute;           # minutes
+        $fields{'es'} = $esecond;           # seconds
+        $fields{'ea'} = lc($emeridian);     # am/pm
+        $fields{'eA'} = $emeridian;         # AM/PM
+        # Original Airdate
+        $fields{'oy'} = substr($oyear, 2);  # year, 2 digits
+        $fields{'oY'} = $oyear;             # year, 4 digits
+        $fields{'on'} = int($omonth);       # month
+        $fields{'om'} = $omonth;            # month, leading zero
+        $fields{'oj'} = int($oday);         # day of month
+        $fields{'od'} = $oday;              # day of month, leading zero
     # Make the substitution
         my $keys = join('', sort keys %fields);
         my $name = $format;
-        $name =~ s/(?<!%)(?:%([$keys]))/$fields{$1}/g;
+        $name =~ s/(?<!%)(?:%([eo]?)([$keys]))/$fields{$1.$2}/g;
         $name =~ s/%%/%/g;
     # Some basic cleanup for illegal (windows) filename characters, etc.
-        $name =~ tr/\ \t\r\n/ /s;
+        $name =~ s/(?:[\/\\\:\*\?\<\>\|$replacement_char])+/$replacement_char/sg;
         $name =~ tr/"/'/s;
-        $name =~ s/(?:[\-\/\\:*?<>|]+\s*)+(?=[^\d\s])/- /sg;
-        $name =~ tr/\/\\:*?<>|/-/;
-        $name =~ s/^[\-\ ]+//s;
-        $name =~ s/[\-\ ]+$//s;
+        $name =~ s/[\s$space_char]+/$space_char/sg;
+        $name =~ s/($space_char*)(?:$separator_char+($space_char*))+/$1$separator_char$2/sg;
+        $name =~ s/^$space_char+//s;
+        $name =~ s/$space_char*$separator_char+$space_char*$//s;
     # Get a shell-safe version of the filename (yes, I know it's not needed in this case, but I'm anal about such things)
         my $safe_file = $file;
         $safe_file =~ s/'/'\\''/sg;
Index: contrib/mythlink.sh
===================================================================
--- contrib/mythlink.sh	(revision 7175)
+++ contrib/mythlink.sh	(working copy)
@@ -6,128 +6,65 @@
 #
 # mythlink.sh
 #
-# Creates readable symlinks referring to MythTV recordings
-#
-# Based on the script mythlink.sh by Dale Gass
-#
-# Modified by Mike Dean
-#  - Provides multiple easily-configurable "views"
-#  - System specific information is specified as environment variables
-#  - Uses '-' to separate portions of the filename (instead of '_' since '_'
-#    is used to replace special characters (such as the space character))
-#  - Shows recording year
-#  - Shows end time
-#  - Separates date and time info
-#  - Removes trailing separator (i.e. for movies, which have no subtitle)
-#  - Adds an optional extension (for specifying filetype to Windows)
+# Creates readable symlinks referring to MythTV recordings using the
+# mythlink.pl script
 
 
 ### Modify these values for your installation ###
-# The location of the MythTV Recordings (with "ugly" filenames)
-MYTH_RECORDINGS_PATH=/var/storage/mythtv
 # The path in which the views ("pretty" links) will be created
-VIEWS_PATH=/var/storage/mythtv/views
-# The extension added to the end of the links, including the period.
-# For no extension, specify '' for the value.
-EXTENSION='.mpg'
-# Enables output for debugging (set to 0 for no output)
-DEBUG=0
+VIEWS_PATH=/var/video/views
 
+# The location of the mythlink.pl script
+# The default value assumes mythlink.pl is in the user's PATH
+COMMAND=mythlink.pl
+
+
 ### The following directory names and formats may be customized ###
 
-# Formats may consist of any combination of
-# ${title}, ${subtitle}
-# ${date}, ${starttime}, ${endtime}, ${originalairdate}
-# ${category}, ${recgroup}
+# Formats may be constructed using the format specifiers listed in the
+# output of "mythlink.pl --help"
 
 # Files will be sorted "alphabetically" so the appropriate fields on which you
 # would like to sort should be placed at the beginning of the format
 
-# Formats of the individual fields were chosen to minimize confusion caused by
-# use of different sorting algorithms by clients (i.e. alphabetically versus
-# alphanumerically).
-
 # To add a custom format, simply include a directory name and format in the
-# environment variables below.  (Whitespace separates the values.)
+# environment variables below using the syntax shown below.
 
 # The names of the directories containing the views
-DIRECTORIES='time
-             title
-	     group
-	     category
-	     original_airdate'
+DIRECTORIES=(
+             'time'
+             'title'
+             'group'
+             'category'
+             'original_airdate'
+            )
 # The formats used for the respective directories specified above
-FORMATS='${date}-${starttime}-${endtime}-${title}-${subtitle}
-         ${title}-${date}-${starttime}-${endtime}-${subtitle}
-         ${recgroup}-${title}-${date}-${starttime}-${endtime}-${subtitle}
-         ${category}-${title}-${date}-${starttime}-${endtime}-${subtitle}
-         ${title}-${originalairdate}-${subtitle}'
+FORMATS=(
+         '%Y%m%d-%H%i-%eH%ei-%T-%S'
+         '%T-%Y%m%d-%H%i-%eH%ei-%S'
+         '%U-%T-%Y%m%d-%H%i-%eH%ei-%S'
+         '%C-%T-%Y%m%d-%H%i-%eH%ei-%S'
+         '%T-%oY%om%od-%S'
+        )
 
+# The character used to separate sections of the link name
+SEPARATOR_CHAR='-'
 
-### These values most likely do not need modification ###
-# The name of the MythTV database
-MYTH_DB=mythconverg
-# The database username and password
-MYTH_DB_USER=mythtv
-MYTH_DB_PASSWD=mythtv
+# The character used to replace illegal characters in the filename
+REPLACEMENT_CHAR='_'
 
+# The character used to replace space characters in the filename
+# (Use ' ' to allow spaces in filenames)
+SPACE_CHAR='_'
 
-export MYTH_RECORDINGS_PATH VIEWS_PATH EXTENSION DEBUG DIRECTORIES FORMATS
 
-
-for dir in ${DIRECTORIES}
-  do
-  rm -rf ${VIEWS_PATH}/${dir}/*
+# Make the links by calling mythlink.pl with the appropriate arguments
+last_index=$(( ${#DIRECTORIES[@]} - 1 ))
+for directory in `seq 0 $last_index`; do
+  ${COMMAND} --separator-char $SEPARATOR_CHAR \
+             --replacement-char $REPLACEMENT_CHAR \
+             --space-char $SPACE_CHAR \
+             --dest ${VIEWS_PATH}/${DIRECTORIES[$directory]} \
+             --format ${FORMATS[$directory]}
 done
-mysql -u${MYTH_DB_USER} -p${MYTH_DB_PASSWD} ${MYTH_DB} -B --exec "select chanid,starttime,endtime,title,subtitle,recgroup,category,originalairdate from recorded;" >/tmp/mythlink.$$
-perl -w -e '
-  my $mythpath=$ENV{"MYTH_RECORDINGS_PATH"};
-  my $viewspath=$ENV{"VIEWS_PATH"};
-  my $extension=$ENV{"EXTENSION"};
-  my $debug=$ENV{"DEBUG"};
-  my $dirs=$ENV{"DIRECTORIES"};
-  my $fmts=$ENV{"FORMATS"};
-  my @directories=split(/\s+/,$dirs);
-  my @formats=split(/\s+/,$fmts);
-  if (!-d ${viewspath}) {
-    mkdir ${viewspath} or die "Failed to make directory: ${viewspath}\n";
-  }
-  foreach (@directories) {
-    if (!-d "${viewspath}/$_") {
-      mkdir "${viewspath}/$_" or die "Failed to make directory: ${viewspath}/$_\n";
-    }
-  }
-  <>;
-  while (<>) {
-    chomp;
-    my ($chanid,$start,$end,$title,$subtitle,$recgroup,$category,$originalairdate) = split /\t/;
-    $start =~ s/[^0-9]//g;
-    $end =~ s/[^0-9]//g;
-    $subtitle = "" if(!defined $subtitle);
-    my $filename = "${chanid}_${start}_${end}.nuv";
-    do { print "Skipping ${mythpath}/${filename}\n"; next } unless -e "${mythpath}/${filename}";
-    $end =~ /^........(....)/;
-    my $endtime = $1;
-    $start =~ /^(........)(....)/;
-    my $date = $1;
-    my $starttime = $2;
-    $originalairdate =~ s/[^0-9]//g;
-    $originalairdate = "00000000" if(($originalairdate eq ""));
-    for ($i = 0; $i < @directories; $i++) {
-      my $directory = $directories[$i];
-      my $link=$formats[$i];
-      $link =~ s/(\${\w+})/$1/gee;
-      $link =~ s/-$//;
-      $link =~ s/ /_/g;
-      $link =~ s/&/+/g;
-      $link =~ s/[^+0-9a-zA-Z_-]+/_/g;
-      $link = $link . $extension;
-      print "Creating $link\n" if ($debug);
-      unlink "${viewspath}/${directory}/${link}" if(-e "${viewspath}/${directory}/${link}");
-      symlink "${mythpath}/${filename}", "${viewspath}/${directory}/${link}" or die "Failed to create symlink ${viewspath}/${directory}/${link}: $!";
-    }
-  }
-' /tmp/mythlink.$$
-rm /tmp/mythlink.$$
 
-
