Having been massively disappointed in the O'Reilly TiVo[1] book due to the misleading copy that neglected to mention you shouldn't bother to buy it if you have a Series 2 TiVo, I decide to make the best of it and use the Perl module on their developer site to get the foolish beast talking to my Linux server.
While I'm a SunOS guy at heart, my Linux box is about 100 times as fast[1.5] as my aging SPARC 20, so I figured that serving images and MP3s would be more practical off of the latter.
So step one was to get and install the Perl module. That was simple enough, merely downloading TiVo.pm and deciding where to plop it down. I decided on /usr/lib/perl5/site_perl because that seemed appropriate - definitely specific to the site.
Next I had to install the TiVoConnect CGI script in the right place and set up Apache to actually pay attention to it. The CGI script needs a location for a filesystem cache (/tmp seems like an obvious choice), and real locations for the /Photos and /Music partitions from which it will serve pictures and music. No big deal.
First, the TiVo docs say in a sideways manner that all requests will begin with the prefix /TiVoConnect, followed by the parameters, for example:
GET /TiVoConnect?Command=QueryContainer&Container=/Music
# Trying to set up TiVo for Home Media Option.
#
Alias /TiVoConnect "/var/www/cgi-bin/TiVoConnect.cgi"
Secondly, we need to figure out what port the TiVo is going to use.
Now this is rather poorly documented, but if you root around long
enough in the troubleshooting part of the TiVo web site, you can find
a section that talks about firewalls. Ignoring the retarded advice to
turn off your firewall if you have trouble connecting, you can find
the port numbers under the section for Mac users. The section for
Windoze users doesn't give the actual ports.
"TiVo Desktop tries to reach the network on port 8101 first, then tries 8102, then 8200."Okay, we can do that. We need to add a Listen directive in Apache, and when we kill -HUP the server, we can now telnet to 8101 and fetch documents via the /TiVoConnect URL.[2] But what the hell, we just get raw Perl, not any form of XML like we're supposed to.
Turns out that the comments in the httpd.conf file don't exactly tell the truth:
# If you want to use server side includes, or CGI outside
# ScriptAliased directories, uncomment the following lines.
#
# To use CGI scripts:
#
#AddHandler cgi-script .cgi
Well, it turns out that for my installation, you need to enable the
cgi-script handler even if you DO put the CGI in the ScriptAliased
directory. Uncommenting that line, and we're good to go. Now we get
HTML. Of course, since the Music and Photos directories are empty,
it's not particularly exciting, but we get XML out of it now, like
we're supposed to.
|
...and set up the IP on the TiVo, but immediately get a "No servers were found at 192.168.1.172" error. I tell it to leave the server there anyways and see what happens - nothing. Now my list is: |
|
Music on TiVo Online Photos on TiVo Online Manually add a Server |
Well, it so happens that there's a magic beacon that the server is supposed to send out that tells the TiVo the server is out there. But thankfully, after an annoying (but relatively short, about 22-25 seconds) delay, it seems to pick it up anyways, and the music and photos options show up in my TiVo menu:
|
My Personal Photos Music on TiVo Online Photos on TiVo Online Manually add a Server |
It was now that I began Adventures with Perl. The TiVo.pm module needs a number of modules to be installed, and will silently fail if they're not. CPAN is your friend, and ImageMagick is a pain in the ass. :-)
Well, first I found and installed ImageMagick, which includes PerlMagick, which is cool, but it wasn't immediately apparent that the usual configure/make/make install deal does NOT install the Perl module. It took a 'find' in the /usr/lib/perl5 hierarchy to prove this to myself. So I figured I'd use CPAN to install it. Hah.
Well, to be fair, the usual:
% perl -MCPAN -e shell
cpan shell -- CPAN exploration and modules installation (v1.76)
ReadLine support enabled
cpan>
worked for everything BUT ImageMagick - it fetched me Storable, and
MP3::Info, and a number of other dependencies without issue, but
barfed big-time on the Image::Magick module. Manually going into the
CPAN temp directory, I was able to munge the makefile and convince it
to compile, but then I got symbol errors at runtime. Not helpful.
The standalone executables that came with ImageMagick worked fine, so
it was specific to the Perl stuff, and I had a hunch it was related to
the fact that CPAN was trying to install an older version of
Image::Magick than I had installed of ImageMagick, which I noticed when
I saw the version of ImageMagick in the CPAN temp dir.
But in any case, I now had the magic key - a second setup/make/install sequence was necessary to get the Perl module installed, and this second build sequence was located in the PerlMagick subdirectory of the ImageMagick build. With some convincing, I got it to use /usr/lib/perl5 even though ImageMagick itself was installed in /usr/local, and I had Image::Magick installed.
|
So, finally, I deleted the cache in /tmp again, and this time the
images showed up. And the MP3s that I copied over from my friend's band. Good deal. |
|
+---------+
| | my_picture.jpg
| () * | Taken: unknown
| * *| Imported: unknown
|--\__/\__| Modified: unknown
+---------+
(View photo )
(Rotate 90d Clockwise)
(Play slide show )
(Don't do anything )
|
In any case, this meant back to the old printf() debugging. [3] Except that this being CGI, using print statements just wrecks the returned XML. So instead I created a tivo log file and dumped everything to that. Worked fairly well, except that eventually I ended up with print statements all over the place. But it makes it easy to tell what's going on, and I can turn them all off by setting debug to 0, so not that big of a deal. Except that I've gone back in time to 1990.
So anyways, I can see WAV as a type in TiVo.pm ... but it won't load them. More on that later. MP3s are fine, and who knows what the hell M3Us and PLSs are .. but those don't load either. Oh well. Guess it's time to figure out that stupid rotate thing after all.
Well, without the scary details, I did manage to get rotate working, but it took some trickiness with the cache to make it stick and make it not munge the damn images. Initially, I'd hit rotate, and it would rotate the little preview image just fine, but when I went to "View Image", the full-size image was unchanged. There was a bunch of other intermediary fun and games as well. The original Perl code to load an image was like this (I've omitted some junk):
##
## TiVo::Item::JPG->send( )
##
## TiVo::Item send extension supporting image transforms
##
sub send {
#########################################
# initialization code skipped
#########################################
# We will check cache if a transform has been requested
if( defined( $params->_Width ) ||
defined( $params->_Height ) ||
defined( $params->_PixelShape ) ||
( uc($suffix) ne uc($source_suffix) ) ) {
# Use MD5 to hash the filename
require Digest::MD5;
my $cache_name = Digest::MD5::md5_hex($path);
my $string = "";
$string .= "_H" . $params->_Height if defined($params->_Height);
$string .= "_W" . $params->_Width if defined($params->_Width);
$string .= "_S" . $params->_PixelShape if defined($params->_PixelShape);
# Append transform data to filename
$cache_name .= $string;
my $cache_dir = $server->_ImageDir || '/tmp';
my $cache_path = "$cache_dir/$cache_name.$suffix";
# Send cached image directly, if it exists
if( -f $cache_path ) {
$path = $cache_path;
# Otherwise, use Image::Magick to perform requested transforms
} else {
require Image::Magick;
my $width = $self->_SourceWidth;
my $height = $self->_SourceHeight;
my $ratio = 1;
#########################################
# Calculation of scaling skipped
#########################################
my $img = new Image::Magick;
$img->Read($self->_Path);
if( $ratio ) {
$img->Scale(width => $width * $ratio,
height => $height * $ratio);
}
$img->Write(filename => $cache_path);
$path = $cache_path;
}
}
#########################################
# Return of binary data skipped
#########################################
}
Basically we check the request parameters for the desired height,
width and pixel aspect ratio, and scale the image. Then we store the
transformed image in the cache with a special name so we can fetch
directly from the cache without a transform next time. Pretty cool.
So, we can rotate easily enough, right? Well, yes and no. We have the rotation in the request, but it turns out that there's no stateful storage of it, so the rotation parameter is always 90. It's easy to get the small image to rotate by 90 degrees, but impossible to get it to rotate more or un-rotate except by going out and back in. Ugh.
In a nutshell, what I ended up doing was two-fold. First, when a rotation is requested, the main image is rotated and stored in the cache. From then on, the cached main image is considered as the source image, rather than the original image on disk.
Then any scaled images (which is all of them, an untransformed image is never displayed directly by the TiVo) are created or pulled from the cache as necessary - and the date of the cached thumbnails are always compared to that of the source image (be it in the cache or out on disk). That way, if the image is rotated, all thumbnails are also equivalently rotated - and the rotation is persistant, so that a second rotation of 90 degrees nets a 180 degree rotation.
Took a number of iterations to get it just right, but it is now pretty cool. Though I had to figure out some of the Storable stuff as well, because it turns out that it stores a descriptor of the image in the cache, the aspect ratio got all hosed up if the cached descriptor wasn't updated as well.
Cool. So now I can look at all the images from our digital cameras on the TiVo. Not a bad deal. Next, figuring out how to create MP3s from CDs.[4]
Anyways, I have a ton of CDs, and it turns out that ripping MP3s is amazingly easy. My RedHat 7.3 system comes with cdparanoia, which happily pulls a track from a disk and saves it as a huge .wav file, and it took me minutes to download and build LAME, which merrily converts said .wav files into MP3s. Easy enough. Except that this deleting the /tmp cache every time I put a new file in is getting old.
Next I had an aborted attempt to support .wav files. CPAN loaded the Audio::Wav module quickly enough, and I updated the class hierarchy to support WAV as well as MP3. Then I loaded one and ... well ...
You ever try dumping a binary to the audio device on your machine? If so, then you know what it sounded like. Clearly either the TiVo doesn't *really* support WAV files (no docs on it, but it WAS in the TiVo.pm module), or I loaded it wrong .. and since I was reading it just fine with Audio::Wav, oh well.
Then it crashed the TiVo. Hell, I didn't even know the TiVo COULD crash - "HOLY CRAP", I'm thinking, "I broke the TiVo." Well, it recovered quickly, and I hope to never see the GSOD (Gray Screen of Death) again (It's interesting - I've seen this referred to a number of times as the "Green Screen of Death", but it sure as hell looks gray to me. Or then again, maybe I'm not seeing the Green Screen of Death, I'm seeing the Gray Screen of Boot. Dunno).
The WAV support was quickly removed.
So, next ... fixing the cache.
This took a little bit of trickiness. I'm no Perl guru, so a couple of things that I tried that didn't work mystified me, but in the end I got a working system for that too. The cache is measured in two ways - first, if the cached file is older than the source file, the item is expired and reloaded. Second, if the item in the cache is older than an absolute limit (I set it to 48 hours for no particular reason) the item will be refreshed. Now it picks up new files when they're dropped in the directory, because the directory itself has its date updated, and the entry is recreated.
There's still two outstanding things I will tackle one of these days. The first is that fool beacon so that I don't have to wait 25 seconds every time I go to the Music & Photos page. I downloaded a silly PHP shell script that was supposed to do it, but it didn't, and I have even less patience with PHP than I do with Perl. It's some wacky UDP thing on port 2190 and haven't yet been motivated enough to write it from scratch. One of these days I'll install the fool Windoze version and snoop the network to see what the real packet is.
Secondly, I'd like to put a limit on the cache size. I figure I can quickly calculate the disk usage of the cache directory (it's a flat cache, it doesn't mirror the source structure at all), and then while it's greater than the max allowed size, delete the oldest item there. But I'll do that another day.
For those of you interested, here's the original TiVo.pm module, and my modified version of the same.
% ls -l TiVo* -r--r--r-- 1 toolset xruler 37319 Mar 20 12:55 TiVo-orig.pm -r--r--r-- 1 toolset xruler 46996 Sep 26 01:21 TiVo.pm
| 03.09.26 | GBS | |
| 03.11.06 | P.S. | Any comments, complaints or suggestions, feel free to email me at bonehead@acm.org. You can also take a look at my home page, though frankly it's still kind of a cobweb site from the 90s - there's not a lot there of much merit except for this and the article on setting up DNS. |
| [1] | No, I'm not going to give the URL for the book here. I'm STILL pissed about it, and I got it over a month ago. I bought a second TiVo specifically so that I could mess with it without screwing up the main one in the house, so it's not a matter of being out the twenty-five dollars for the book, it's a matter of being out the five-hundred and thirty-five dollars it cost for the TiVo, service, and a network adapter. |
| [1.5] | Actually, it's 66.3 times as fast, according to ubench. The SPARC 20 clocked in with CPU=1,356 and MEM=3,423 while the Linux box got CPU=89,918 and MEM=108,519. Of course, we are comparing a system I bought used in '96 with a system I bought new in '02. Furthermore, the dual-CPU SS20 I just built clocks in with a massive 4,158 CPU and 10,875 MEM - over 3 times as fast as my workhorse machine. Now I just have to upgrade it from Solaris 2.4. |
| [2] | An interesting note about the Listen directive: There is a
confusing interaction between the Apache Listen and Port
directives. My first iteration had the following:
#
# Listen: Allows you to bind Apache to specific IP addresses and/or
# ports, in addition to the default. See also the
Mostly because I configure Apache so rarely, I never remember what
goes where in the config. Well, I did the kill -HUP to
reconfigure it, and it was listening on port 8101 as expected.
Cool. Until I stopped and started Apache and it wouldn't start:
[Sun Sep 21 15:55:08 2003] [crit] (98)Address already in use:
make_sock: could not bind to port 80
What the hell? It's been working for days! Well anyways, it
pretty quickly became apparent that the Listen 80 directive was
not only redundant, but unaccepable. Heh.
|
| [3] | But first, an aside: I find Perl excruciating to debug. I'm a full-time Java developer, and have gotten spoiled by how easy it is to write and debug Java programs, never mind how well-written the compilers are these days. In Perl, the OO stuff is kind of grafted on, thus just making things more complex. Yeah, I know, if I'd spent the time writing Perl that I've spent writing Java, I'd be just as good at that ... |
| [4] |
<soapbox> I know I'm old-fashioned, but I'm not particularly keen on the whole Napster thing. I figure, if you want it, buy it, don't freakin' steal it. I have a brother-in-law who's a doctor and yet seems to take personal pride in downloading bootleg copies of movies and burning DVDs. What the hell, would it kill him to pay $15 for a movie? [5] </soapbox> |
| [5] |
And anyone who knows me will immediately say "But you got
Two Towers off of a bootleg site! You're a freakin' hypocrite! :-) A reasonable point. But I did get the stupid-ass theatrical release the day it came out, and I'll be getting the fool "extended release" when that comes out as well. So they're not losing a dime from me, I wasn't going to go to the theature and pay $8 a just to see Gandalf fight the Balrog a third time. Nope. |