Josh + Paola McFarren

Home | Music | Food | Code | Crafts | Links | Maps | Search:

Share Your EyeTV Archive


When I posted my original shell script to make a folder of links to your EyeTV recordings with human-readable names, it caught on faster then I expected. I made no real effort to promote it, but soon my referrer logs showed it had been posted on the CenterStage message board and the AV Science Forum. The only explanation is someone found my comment here. Since at least a few people found the script useful too, I was motivated to rewrite it in perl. I had been meaning to do this for a while, and I knew I should have done it with perl in the first place. Plus, I fixed a bug where two recordings of the same show on the same day would only link the latter. (Thanks Jan!)

Overview


If you have an Elgato EyeTV personal video recorder (PVR)—like a TiVo for the Mac—and you want to share the recorded programs on a local network there's a small problem. EyeTV stores your programs in a directory on your hard drive but its far from as user friendly as the system iTunes uses. I like the EyeTV interface fine but I don't like being forced to use it to access my content.

With the perl script below you can have a better way to access your content through the file system locally or on a network. This is especially helpful if you want stream it to a Home Theater system or to share it with other users on your LAN. Plus, you can easily drag and drop an mpg to copy or burn it, without exporting it first from EyeTV. The script below will create links to the files in your EyeTV Archive in another folder that you choose—with human-readable names. You can quickly grab the file from the link with out launching EyeTV to export the file.

Disclaimer


Use of this software is at your own risk. You agree not hold me liable for any unexpected results or lost data.

Installation


Download the script here. If you copy and paste the code below you need to make sure you save the file with unix line breaks. Save the script to /usr/local/bin and chmod +x it. You'll need to edit the first few lines to set up your preferences. Enter the full path of your EyeTV Archive ($archive_dir) and the full path to the folder you want to put the links ($link_dir). The latter should be an empty folder because the script will delete any files there that end in .mpg.

How to Run It


The script can either create hard links or symbolic links to the mpgs in your archive. Symbolic links are more like aliases and the finder will treat them as such. The disadvantage is they wont necessarily work for file sharing (AFP & Samba). Hard links are more like a pointer to the same location on your hard drive. As such, you can only use hard links if the file you are linking to is on the same physical drive as the link.

The script can take three switches when executed: -p will print a report; -l with make hard links; and -s will make symbolic links. If for some reason you entered both -sl it would default to symbolic links. Here's and example of how to execute the command to create symbolic links:

% eyetvlinks.pl -s

I set up a cron job to fire off the script a couple times a day so the links get updated as the content in the archive changes. I recommend Cronnix for that. In my configuration I have the location of the links on the same drive as the archive, so I use hard links. I also print the report and pipe it to logger to make an entry into the system log. The command I use in Cronnix/cron is:

/usr/local/bin/eyetvlinks.pl -pl | logger

Source Code


#!/usr/bin/perl -w

# EyeTvLinks 1.2
#
# Search recursively through the EyeTV Archive and make some
# friendly links in my shared video directory for the roommates.
#
# Copyright 2005 Joshua McFarren. Some rights reserved.
# This work is licensed under a Creative Commons License:
# http://creativecommons.org/licenses/by-sa/2.0/

my $archive_dir = "/Volumes/Seagate 200/EyeTV Archive";
my $link_dir    = "/Volumes/Seagate 200/Video/EyeTV";
my $separator   = "  ";

### SCRIPT ###

use strict;
use Getopt::Std;
my $script_name = &pluck($0);
my $version     = '1.2';
my %opts;
&print_usage() && exit unless getopts('slp', \%opts);

opendir(ARCHIVE, $archive_dir) or die "ERROR! Couldn't open $archive_dir: $!\n";
my @dirs = grep { !/^(f{16}|\.)/ } grep { -d "$archive_dir/$_" } readdir(ARCHIVE);
closedir(ARCHIVE);

my %shows = ();
foreach my $show_code (@dirs) {

    my $title = '';
    my $subdir = "$archive_dir/$show_code";

    opendir(SUBDIR, $subdir ) or die "ERROR! Couldn't open $subdir: $!\n";
    my @files = grep { s/.mpg// } grep { /.mpg/ } readdir(SUBDIR);
    closedir(SUBDIR);

    if (@files) {
        my $slurp = &slurp_file("$subdir/$show_code" . ".eyetvp");
        if ( $slurp =~ m{<key>title</key>\s+<string>([^<]+)</string>}i ) {
            $title = $1;
            $title =~ s{\s*(/|:)\s*}{ - }g;
        } else { print STDERR "ERROR! Bad XML found in $subdir/$show_code", ".eyetvp\n"; }
    } else { next; } # no mpgs recorded yet move on

    my %recordings = (); # reset list of recordings for each show
    foreach my $recording_code (@files) {
        my $slurp = &slurp_file("$subdir/$recording_code" . ".eyetvr");
        if ( $slurp =~ m{<key>start</key>\s+<date>(\d{4}-\d{2}-\d{2})[^<]*</date>}i ) {
            # for readablity, we could push $1 instead
            my $date = $1;
            # hash of recordings for active show
            $recordings{$recording_code} = $date;
        } else { next; }
    } # next recording

    # store a reference to it in the hash of shows
    $shows{$show_code} = { 'title' => $title, 'recordings' => \%recordings };

} # next show

if (! %opts) {
    &print_usage("$script_name version $version");
} else {
    if ( $opts{'l'} || $opts{'s'} ) {
        &clear_dir();
        &make_links();
    }
    if ( $opts{'p'} ) { &print_info(); }
}

### SUBS ###

sub clear_dir {
    opendir(LINKDIR, $link_dir) or die "ERROR! Couldn't open $link_dir: $!\n";
    my @links = map { "$link_dir/$_" } grep { /.mpg/ } readdir(LINKDIR);
    closedir(LINKDIR);
    foreach my $link (@links) {
        unlink ($link) or warn "ERROR! Couldn't unlink file: $!\n$link\n";
    }
}

sub make_links {
    foreach my $show_code (keys %shows) {
        # pull out the reference we stored earlier
        my $showref = $shows{$show_code}->{'recordings'};
        foreach my $recording_code (keys %$showref){
            my $source = "$archive_dir/$show_code/$recording_code" . ".mpg";
            my $target  = "$link_dir/" . $shows{$show_code}->{'title'} . " - ";
               $target .=  $shows{$show_code}->{'recordings'}->{$recording_code} . ".mpg";
            if ($opts{'s'}) {
                my $episode_num = 2;
                while (-e $target ) {
                    $target =~ s{(\d{4}-\d{2}-\d{2})( - \d+)?(.mpg)}{$1 - $episode_num$3}i;
                    $episode_num++;
                }
                symlink $source, $target or warn "\nERROR! $!\n$source\n$target\n";
            } else {
                my $episode_num = 2;
                while (-e $target ) {
                    $target =~ s{(\d{4}-\d{2}-\d{2})( - \d+)?(.mpg)}{$1 - $episode_num$3}i;
                    $episode_num++;
                }
                link $source, $target or warn "\nERROR! $!\n$source\n$target\n";
            }
        }
    }
}

sub print_info {
    foreach my $show_code (keys %shows) {
        print "\n";
        print $shows{$show_code}->{'title'}, "\n";
        # pull out the reference we stored earlier
        my $showref = $shows{$show_code}->{'recordings'};
        foreach my $recording_code (keys %$showref){
            print $separator, $shows{$show_code}->{'recordings'}->{$recording_code};
            print $separator, "$show_code/$recording_code", ".mpg\n";
        }
    }
}

sub slurp_file {
    my $path = shift;
    my $contents = '';
    open (INFILE, "< $path") or die "ERROR! Can't open $path: $!\n";
    while (<INFILE>) { $contents .= $_; }
    close INFILE;
    return $contents;
}

sub pluck {
    my @path = split ('/', "$_[0]");
    return pop @path;
}

sub print_usage {
if ($_[0]){ print STDERR "$_[0]\n"; }
print STDERR <<EOF;
Usage: $script_name [OPTIONS]
    options:
    -s  create symbolic links
    -l  create hard links
    -p  print information about recordings
EOF
}

__END__

HISTORY:

1.2   April 22, 2005 - Ported script to perl. No more backticks, no more
      sed or awk. Huge performance increase. Rewritten from the ground up
      with nested hash data structure. Fixed bug where two recordings of
      the same program on the same day replace each other. (Thanks Jan!)
      Supports command line switches. Better reporting with -p switch.

1.1b  February 15, 2005 - Added support for multiple recordings of same
      program on different days by adding date to the link name. Removed
      useless uses of echo and cat.

1.0b  May 5, 2004 - Rough cut.

TO DO:

   *  Put description in the finders get info field?

   *  Add support for locating the EyeTV archive automatically?
      Chris Nandor did this, but you need to install modules for it to
      work. I would like to keep this simple to install and run for
      people who have little cli experience. Perhaps just check for the
      prefs in the obvious places.

   *  Add support for specifying the link_dir with a switch

DISTRIBUTION:

This script and it’s documentation can be found here:
http://www.mcfarren.org/archives/000030.html

This work is licensed under a Creative Commons License:
http://creativecommons.org/licenses/by-sa/2.0/

Old Shell Script


#!/bin/sh
#
# EyeTVLinks 1.1b
#
# Search recursively through the EyeTV Archive and make some friendly
# links in my shared video directory for the roommates
#
# Written by Joshua McFarren 2/15/05
# This work is licensed under a Creative Commons License.
# http://creativecommons.org/licenses/by-sa/2.0/
#
# To do: rewrite this in perl instead of this kluge i hacked together ;)
#

# create hard links or symbolic links?
# set to no your symbolic links might not work over samba or afp
# set to yes the directories below must be on the same drive
hard_link="yes"

# declare constants for the working paths
my_dir="/Volumes/Caviar 120/Video/EyeTV"           # location of aliases
archive_dir="/Volumes/Caviar 120/EyeTV Archive/"   # location of archive

###############################################################################
#                                                                             #
#    DO NOT EDIT BELOW HERE UNLESS YOU CAN FIX MY USELESS USE OF BACKTICKS    #
#                                                                             #
###############################################################################

d=0            # counter for dir array
f=0            # counter for file array
name=""        # name of file
my_path=""     # path to file in archive
show_date=""   # date for multiple instances of same show
vid_dirs=()    # array of directories that may contain a file
vid_files=()   # array of files in the active directory

# fix the directory list to populate array without color
unset CLICOLOR

# turn this on for logger
echo -n "Refreshing EyeTV links "; date

# clear the old links
rm "$my_dir/"*.mpg 2>&1

# get an array of video directories and number
vid_dirs=(`ls -F1 "$archive_dir" | grep '/' | sed "s'/''"`)

# while there are directories left to traverse
while [ "$d" -lt `expr ${#vid_dirs[*]} - 1` ]
do
  # get the path for position of counter
  my_path="$archive_dir${vid_dirs[$d]}/"
 
  # get the name of the file in that dir
  name=`egrep string "$archive_dir${vid_dirs[$d]}/${vid_dirs[$d]}.eyetvp"\
  | sed -e "s|<string>||g" -e "s|</string>||" -e "s/ /_/g" | tail -1\
  | awk '{print $1}' | sed "s|:||"`
 
  # get an array of all files in the active dir and a total num of files
  vid_files=(`ls -1 "$archive_dir${vid_dirs[$d]}/"|grep .mpg|sed "s/.mpg//"`)
 
  # if there is a video file in the directory
  if [ "${#vid_files[*]}" -gt "0" ]; then
 
    # reset the file counter for each directory
    f=0
   
    # turn this on for error checking or logger
    echo "$my_path $name"  | sed "s'$archive_dir''"
   
    # for each video file print a result
    while [ "${#vid_files[*]}" -gt "$f" ]
    do
      show_date=`egrep date \
      "$archive_dir${vid_dirs[$d]}/${vid_files[$f]}.eyetvr" \
      | sed -e "s|<date>||g" -e "s|</date>||" -e "s/ /_/g" \
      -e "s/2005-//" -e "s/2004-//" -e "s/T.*//"| \
      awk '{print $1}' | tail -1`
     
      # make the link
      if [ $hard_link = "yes" ]; then
        ln "$my_path${vid_files[$f]}.mpg" "$my_dir/$name"_"$show_date.mpg"
      else
        ln -s "$my_path${vid_files[$f]}.mpg" "$my_dir/$name"_"$show_date.mpg"
      fi
     
      # increment the file counter
      f=`expr $f + 1`
    done
    # no more video files
   
  fi
 
  # increment the directory counter


CategoryCode
Page was generated in 0.3075 seconds