Sep 15 2005

Happy not to own a TiVo anymore ..

After reading this: pvrblog: TiVo 7.2 OS adds content protection, blocks transfers, and auto-deletes some shows, I’m certainly happy that all TiVo units have been disconnected in this household.

At the moment we are using the Dishplayer 942 (which does HD and SD) and an MS Mediacenter PC (which does SD only, but it’s good enough as a backup).


Apr 15 2003

Oh my – now the TV is speaking to me ..

A few days ago (see: TiVo + Home Media Option + EMail) we went through a little exercise to have the TiVo (with Home Media Option installed) display “images” for all current emails on a POP3 server. This worked fine and was a nice little hack.

Now let’s take that concept to another level.

Besides images, the Home Media Option also allows to send MP3 files from your PC to the TiVo unit – right?

Well, I changed the script from the message referenced above to use the Microsoft Speech Object Library (version 5.0; which was installed on my Windows XP Professional system) to “render” the first few lines (PREVIEW_LINES) of each email to a temporary WAV file. After the email has been “recorded” in the WAV file I use a command-line WAV to MP3 converter to turn the file into a file the TiVo unit can understand. If we create the file successully, we moved it into an area that can be “seen” by the TiVo Media Desktop (D:\MP3\TiVo).

Besides perl (see: http://www.activestate.com/Products/ActivePerl/), you will need the Microsoft Spech Object Library on your system and you will need access to a command line MP3 encoder. I’ve picked “bladeenc” for this purpose. You will need to google it or use a different mp3 encoder. The line were the encoder is called, is marked in italic below.

And here is the script that does it all:

#!c:\perl\bin\perl.exe

use strict; use Cwd; use File::Copy; use File::Path; use File::Spec; use Mail::POP3Client; use Mail::Internet; use LWP::Simple; use Win32; use Win32::OLE; use Win32::OLE::Const qq{Microsoft Speech Object Library};
use constant DESTINATION => q{D:\MP3\TiVo}; use constant PREVIEW_LINES => 30;
my @accounts = ( { DESC => q{thoellri@foobar.com}, USER => "thoellri", AUTH_MODE => "PASS", PASSWORD => "password", HOST => "pop3.foobar.com" }, { DESC => q{tobias@somewhere.com}, USER => "tobias", AUTH_MODE => "PASS", PASSWORD => "password", HOST => "mail.somewhere.com" }, );
for my $account (@accounts) { # erase existing messages rmtree([ File::Spec->catfile(DESTINATION, qq{Email}, $account->{DESC}) ], 0, 0); my $pop = new Mail::POP3Client (%$account); unless ($pop) { warn "Couldn't connect\n"; next; } my $count = $pop->Count; if ($count <0) { warn "Authorization failed"; next; } next if($count == 0); # no new messages # create new directory for messages mkpath([ File::Spec->catfile(DESTINATION, qq{Email}, $account->{DESC}) ], 0, 0711); for my $num (1..$count) { my @preview=$pop->HeadAndBody($num,100); my $mail=Mail::Internet->new(\@preview); my $mp3file=mail2mp3($mail); next unless defined($mp3file); my $out=File::Spec->catfile(DESTINATION, qq{Email}, $account->{DESC}, qq{message-}.sprintf("%02d",$num).qq{.mp3}); copy($mp3file,$out); unlink($mp3file); } $pop->Close; }
sub mail2mp3 { my($m)=@_; my $header=$m->head();
my $type=Win32::OLE->new('SAPI.SpAudioFormat'); $type->{Type}=SAFT32kHz16BitMono; my $stream=Win32::OLE->new('SAPI.SpFileStream'); $stream->{Format}=$type; $stream->Open("output$$.wav",SSFMCreateForWrite,undef); my $speech=Win32::OLE->new('SAPI.SpVoice'); $speech->{AudioOutputStream}=$stream; $speech->Speak(qq{From: }.$header->get('From'),SVSFDefault); $speech->Speak(qq{Subject: }.$header->get('Subject'),SVSFDefault); my($lines); foreach my $line (@{$m->body()}) { chomp($line); $speech->Speak($line." ",SVSFDefault); last if($lines++ >= PREVIEW_LINES); } $speech->WaitUntilDone(-1); $stream->Close(); return undef unless (-f "output$$.wav");
# here we call out to the command line mp3 encode system(qq{bladeenc -quiet -nocfg -quit output$$.wav output$$.mp3});
unlink(qq{output$$.wav}); return undef unless (-f "output$$.mp3"); return File::Spec->catfile(getcwd,qq{output$$.mp3}); }

Again, let me know if you have questions about this stuff …


Apr 14 2003

Endless Source of live streams for TiVo

I started playing around with MP3 live streams from the Internet and found an interesting thread at http://www.tivocommunity.com/tivo-vb/showthread.php?s=&threadid=110606, which detailed the format of the .m3u files to have the TiVo unit recognize and play live streams.
There seems to be a requirement that URLs for live-streams must not have a path, but just consist of a host:port portion.
It was posted in the same thread how to get to those type of links, which involves a lot of trial-and-error.

Decided to use perl again as for the other TiVo projects (Beacon Watcher and Email Renderer). This time we ask www.shoutcast.com to help us out with the generation of .m3u files for the TiVo unit.

Let’s say I share out the path c:\tivo\mp3\ with my HMO “Music” option on the TiVo. Let’s also say that I want the top 15 Punk (just picked that for no particular reason) stations from shoutcast served through the location above. I would type the following on the PC that hosts the TiVo Media Server:

perl m3ugen.pl Punk 15 c:\tivo\mp3\Punk.m3u

This will go to www.shoucast.com, look up the category “Punk”, extract the top 15 playlists for Punk-stations, download each individual playlists, find TiVo-compatible server-entries and generate a Punk.m3u file in c:\tivo\mp3.
After doing this you would be able to go to the “Music and Photos” section of your HMO-enabled TiVo and find a “Punk.m3u” section under Music, which will show you up to 15 stations (I say “up to”, because some may not offer servers with TiVo-compatible format).

Here’s the list that was generated a few seconds ago (trimmed the length of entries):

#EXTM3U
#EXTINF:,(#1 - 15/100) idobi Radio: Music that doesn't suck!
http://66.227.96.189:5046
#EXTINF:,(#1 - 35/70) RADIO ELECTRACKS .: best tunes only
http://64.202.98.33:6390
#EXTINF:,(#1 - 20/100) Radio That Doesn't Suck
http://209.142.3.9:8100
#EXTINF:,(#1 - 20/50) #Punkrockers Oi! Oi! Oi!: serAphim onAir
http://80.67.228.169:8003
#EXTINF:,(#1 - 14/128) radio.punkmac.com -=- we dont play crap
http://66.227.110.18
#EXTINF:,(#1 - 11/20) Dr. Yo - the perfect prescription
http://64.202.98.33:6020
#EXTINF:,(#1 - 6/64) Dashnine Radio: Apathy is a cool emotion
http://38.210.43.154:8000
#EXTINF:,(#1 - 4/25) ReDDeR2k RaDiO
http://216.127.90.185:9860
#EXTINF:,(#1 - 5/300) All Ages Show -- High Bitrate
http://205.188.234.35:8078
#EXTINF:,(#1 - 2/32) k-storm radio
http://80.0.115.56:8000
#EXTINF:,(#1 - 1/100) (( SoCal P.I.S.S. )) (( where every
http://207.182.239.62:8000
#EXTINF:,(#1 - 4/50) BEYOND THE BEAT GENERATION
http://64.202.98.38:2720
#EXTINF:,(#1 - 3/25) Electrobleep.com Radio --)
http://66.227.96.189:5388
#EXTINF:,(#1 - 2/12) Butthole Radio
http://67.124.196.20:8002
#EXTINF:,(#1 - 2/10) SonicBlaze: Indie, Punk, Emo, and Ska
http://66.250.32.196:8006

Cool – isn’t it? You can request up to 25 results (and I do that to limit the traffic generated on shoutcast.com) and request quite a bunch of genres. To see the complete list, go to http://www.shoutcast.com and select “–Choose a genre –” on the right hand side. After selecting one, the page will refresh and you will see a URL that says http://www.shoutcast.com/directory/?sgenre= – the word that appears after the equal-sign is the same one the perl-script will expect on the command line.

And here’s the script in case you’re interested. As before, all missing modules can be downloaded via ppm. Just copy & paste the code below into a file called m3ugen.pl. You must have a direct internet connection on the system where you run this script. If you have a proxy-server in your network, you will need to set the HTTP_PROXY environment variable to point to your proxy-server.
Have fun and let me know if it works for you as well.

#!c:\perl\bin\perl.exe
use strict;
use HTML::LinkExtor;
use LWP::Simple;
use URI::URL;
use constant PROVIDER  => qq{http://www.shoutcast.com/};
use constant DIRECTORY => PROVIDER.qq{directory/};
my $genre=$ARGV[0];
my $results=$ARGV[1];
my $outfile=$ARGV[2];
unless (defined($genre) &&
	defined($results) && $results && $results <= 25 &&
        defined($outfile)) {
    die qq{Usage:\t$0 [genre] [numresults] [outfile]\n}.
      qq{\tgenre=TopTen,House,Blues,Punk,...\n}.
      qq{\tnumresults=1..25\n}.
      qq{\toutfile=m3u output file\n};
}
my @playlists=getPlaylists(DIRECTORY.qq{?sgenre=$genre&numresult=$results},PROVIDER);
unless (scalar(@playlists)) {
    die "No results found - unable to create playlist\n";
    exit(0);
}
@playlists=mapForTiVo(@playlists);
open(OUT,">".$outfile) or die "Unable to create output file - $!";
print OUT qq{#EXTM3U\n};
foreach my $entry (@playlists) {
    my($url,$title)=%$entry;
    print OUT qq{#EXTINF:,$title\n$url\n};
}
close(OUT);
sub getPlaylists {
    my($url,$base)=@_;
    my(@results);
    my $content=get($url);
    unless (defined($content) && length($content)) {
	warn qq{Unable to fetch "$url"\n};
	return @results;
    }
    my $parser=HTML::LinkExtor->new(sub {my($t,%a)=@_;
					 return if $t ne 'a';
					 push(@results,$a{href})
					   if($a{href}=~/filename\.pls$/i);});
    $parser->parse($content);
    @results = map {$_=url($_,$base)->abs;} @results;
    return @results;
}
sub mapForTiVo {
    my(@list)=@_;
    my(@results);
    foreach my $url (@list) {
	my $content=get($url);
	next unless(defined($content) && length($content));
	my($file);
	foreach my $line (split(/[\n\r]/,$content)) {
	    if ($line =~ /^File\d+=(.*)$/i) {
		my $u=URI::URL->new($1);
		$u->path(""),$file=$u->abs if($u->path eq '/' || $u->path eq '');
	    } elsif ($line =~ /^Title\d+\s*=(.*)$/i  && defined($file)) {
		push(@results,{$file => $1});
		last;
	    }
	}
    }
    return @results;
}

Apr 9 2003

Beacon watcher

If you ever want to watch the beacons being sent by your TiVo Unit or the TiVo Media Server on your PC, you can use the following piece of perl code. This script will listen for UDP messages on port 2190 and will print them as they arrive. If you try to run this on the same PC where you have the TiVo Desktop installed, you will get an error message, because we can’t bind to port 2190 – it has been allocated by the TiVo Beacon service. You can “stop” the service via net stop TivoBeacon, run the script and later on restart the service via net start TivoBeacon.

Here’s what the script will print when both the TiVo unit and the Desktop Server are active:

[Wed Apr  9 18:46:16 2003] santafe:2035 sent beacon packet
[Wed Apr  9 18:46:16 2003]      TiVoConnect=1
[Wed Apr  9 18:46:16 2003]      Machine=SANTAFE
[Wed Apr  9 18:46:16 2003]      Identity={239F9D3F-31E0-410E-8991-19DF88661779}
[Wed Apr  9 18:46:16 2003]      Method=Broadcast
[Wed Apr  9 18:46:16 2003]      Platform=pc/WinNT:5.1.2600
[Wed Apr  9 18:46:16 2003]      Services=TiVoMediaServer:8080/http
[Wed Apr  9 18:47:08 2003] 172.25.36.113:2190 sent beacon packet
[Wed Apr  9 18:47:08 2003]      tivoconnect=1
[Wed Apr  9 18:47:08 2003]      swversion=4.0-01-2-140
[Wed Apr  9 18:47:08 2003]      method=broadcast
[Wed Apr  9 18:47:08 2003]      identity=140000080205DD9
[Wed Apr  9 18:47:08 2003]      machine=Living Room
[Wed Apr  9 18:47:08 2003]      platform=tcd/Series2
[Wed Apr  9 18:47:08 2003]      services=TiVo-ServeTcdVideo-1:2191/tvbus_v3

And here is the code for the script:

#!c:\perl\bin\perl.exe
use strict;
use IO::Socket;
use constant BEACON_PORT => 2190;
use constant MAXLEN      => 1024;
my $socket=IO::Socket::INET->new(LocalPort=>BEACON_PORT,Proto => 'udp')
  or die "unable to create socket - $!";
while ($socket->recv(my $msg,MAXLEN)) {
    my($port, $ipaddr) = sockaddr_in($socket->peername);
    my $sender=gethostbyaddr($ipaddr, AF_INET);
    $sender=inet_ntoa($ipaddr) unless(defined($sender));
    print "[".scalar(localtime())."] $sender:$port sent beacon packet\n";
    foreach (split(/[\n\r]/,$msg)) {
        s/^/\t/;
        print "[".scalar(localtime())."] $_\n";
    }
}

Apr 9 2003

TiVo + Home Media Option + Email

Oh, this is pretty funny. Just a few days ago when the home media option from TiVo (http://www.tivo.com/) came out, of course I had to be among the first ones to install it. Now we have the TiVo unit in the living room connected to the home network (using a wireless USB network adapter) and I can share pictures/music from my desktop system with the TiVo unit allowing me to do slideshows on the TV-set and stream radio stations over to the living room.

This is really pretty cool.

So, besides sharing images/mp3s – what else can you do? Well, I went over to http://www.tivo.com/developer/ and downloaded the protocol specifications for the Home Media Option. It shows how TiVo’s beacon works (how different units find each other and find out what services they have to offer) and how the http-based Media Server Protocol works.

I went ahead and modified the existing example of a Media Server to serve previews of my email through the TiVo. This worked nice, however did not coexist with the TiVo Media Server, because I did not find a way how I could convince the TiVoBeacon service on my system to advertise my custom service besides the TiVoServer services. So, I scratched that idea for the moment and went a different route.

Using perl (from http://www.activestate.com/Products/ActivePerl/) and some addon modules (GD, Mail::Internet, Mail::POP3Client, File::Path, File::Spec) which were easily installed via the Perl Package Manager (ppm) I created a script which would “render” email messages into PNG files. I shared a new folder (D:\DigitalPhotos\Tivo\EMail in this case) through the TiVo Desktop Server and used the script below (which is invoked on a regular basis through “Scheduled Tasks”) to get my email from a number of POP3 servers and create a PNG file for each message.

This allows me to preview messages on my TV when I’m not at my computer. This does not mean that I’m soo addicted to email that I have to watch it on the TV screen and it does also not mean that I’m really that often in front of the tube (fact is, that we spend less time in front of the TV since we got the TiVo unit).

The output looks something like this:
message-06.png

And here is the script that fetches and renders email messages. It’s a non-desctructive fetch, which means that messages stay on the server.

#!c:\perl\bin\perl.exe
use strict;
use File::Path;
use File::Spec;
use GD;
use Mail::POP3Client;
use Mail::Internet;
use constant DESTINATION   => q{d:\DigitalPhotos\Tivo\EMail};
use constant PREVIEW_LINES => 100;
use constant WIDTH         => 640;
use constant HEIGHT        => 480;
use constant HEADER_FONT   => gdMediumBoldFont;
use constant BODY_FONT     => gdLargeFont;
my @accounts = (
    {
     DESC      => q{thoellri@foobar.com},
     USER      => "thoellri",
     AUTH_MODE => "PASS",
     PASSWORD  => "password",
     HOST      => "pop3.foobar.com"
    },
    {
     DESC      => q{tobias@somewhere.com},
     USER      => "tobias",
     AUTH_MODE => "PASS",
     PASSWORD  => "password",
     HOST      => "mail.somewhere.com"
    },
);
for my $account (@accounts) {
    # erase existing messages
    rmtree([ File::Spec->catfile(DESTINATION, $account->{DESC}) ], 0, 0);
    my $pop = new Mail::POP3Client (%$account);
    unless ($pop) { warn "Couldn't connect\n"; next; }
    my $count = $pop->Count;
    if ($count <0) { warn "Authorization failed"; next; }
    next if($count == 0); # no new messages
    # create new directory for messages
    mkpath([ File::Spec->catfile(DESTINATION, $account->{DESC}) ], 0, 0711);
    for my $num (1..$count) {
	my @preview=$pop->HeadAndBody($num,100);
	my $mail=Mail::Internet->new(\@preview);
	my $header=$mail->head;
	my $image=render($mail);
	my $out=File::Spec->catfile(DESTINATION, $account->{DESC},qq{message-}.
                      sprintf("%02d",$num).qq{.png});
	open(OUT, qq{>$out});
	binmode OUT;
	print OUT $image->png;
	close(OUT);
    }
    $pop->Close;
}
sub render {
    my($m)=@_;
    my $header=$m->head();
    my $im = new GD::Image(WIDTH, HEIGHT);
    # allocate some colors
    my $white = $im->colorAllocate(255,255,255);
    my $black = $im->colorAllocate(0,0,0);
    my $gray = $im->colorAllocate(20,20,20);
    my $red = $im->colorAllocate(255,0,0);
    my $blue = $im->colorAllocate(0,0,255);
    my $y=2;
    $im->string(HEADER_FONT, 5,$y, "Date:    ".$header->get('Date'), $black);$y+=10;
    $im->string(HEADER_FONT, 5,$y, "From:    ".$header->get('From'), $black);$y+=10;
    $im->string(HEADER_FONT, 5,$y, "To:      ".$header->get('To'), $black);$y+=10;
    $im->string(HEADER_FONT, 5,$y, "Subject: ".$header->get('Subject'), $blue);$y+=10;
    $im->string(HEADER_FONT, 5,$y, "-" x 80, $black);$y+=8;
    foreach my $line (@{$m->body()}) {
	chomp($line);
	$im->string(BODY_FONT, 5, $y, $line, $gray);
	$y+=13; last if($y>=HEIGHT);
    }
    return $im;
}

Of course those usernames/hostnames/passwords are just samples and need to be customized for your environment.
And depending on the size of your TV-set you may have to tweak those values for HEADER_FONT and BODY_FONT.

Let me know, if it works for you as well ….