I tried out TiVo Desktop on my Mac and it was kind of cool. It lets you play music on your TiVo that’s streamed from your computer. But my mac already streams music from my Linux server and TiVo desktop wanted to serve up real mp3s and not re-broadcast streams–so none of my music would play. I found some articles and discovered the TiVo Desktop is just a http daemon plus some mdns stuff. Sounds easy enough!
I installed mdns-scan (thank you Debian) which reports:
+ David Caldwell's Photos on black._tivo-photos._tcp.local + David Caldwell's Music on black._tivo-music._tcp.local
I copied /etc/avahi/services/ssh.service to /etc/avahi/services/tivo.service. I then changed the name to “Lovely Music”, “_ssh._tcp” to “_tivo-music._tcp” and picked a port out of the air: 8000. I reloaded the avahi daemon and wham, the “Lovely Music” shows up on the TiVo. Yay!
The rest is just http on port 8000 that spews XML. That should be fairly easy to do…
…. Time Passes ….
And 8 hours later I say, “Fairly easy?”. Actually it turned out to be pretty easy. The main problem I had was that the %$#@! TiVo had died on me and wouldn’t play any music, not even from the official TiVo Desktop. I had to reset it to make it work again. Of course, I kept thinking it was something I was doing wrong and so I kept implementing more and more of the protocol. For 4 hours. Grrr….
Avahi
Turns out mdns-scan doesn’t give enough info to really get it working. I used avahi-discover which dumped the txt records that were needed. In the end my avahi services file looked like this:
<?xml version="1.0" standalone='no'?><!--*-nxml-*--> <!DOCTYPE service-group SYSTEM "avahi-service.dtd"> <!-- See avahi.service(5) for more information about this configuration file --> <service-group> <name>Lovely Music</name> <service> <type>_tivo-music._tcp</type> <port>8001</port> <txt-record>protocol=http</txt-record> <txt-record>path=/tivo.cgi/music</txt-record> </service> </service-group>
The path, <port> and <name> can be anything you want. I’m not sure about protocol.
Here’s the corresponding one for the photo sharing:
<?xml version="1.0" standalone='no'?><!--*-nxml-*--> <!DOCTYPE service-group SYSTEM "avahi-service.dtd"> <!-- See avahi.service(5) for more information about this configuration file --> <service-group> <name>Beautiful Photos</name> <service> <type>_tivo-photos._tcp</type> <port>8001</port> <txt-record>protocol=http</txt-record> <txt-record>path=/tivo.cgi/photos</txt-record> </service> </service-group>
http server
I set up port 8001 on apache and then wrote tivo.cgi. I wrote it in the least general way possible and so I don’t plan on publishing it–it just wouldn’t be of much use without specific perl data files that I already had lying around. They actually are for the Philips Streamium server that I heavily modified. And so I wanted to share them instead of putting a whole bunch of work into reinventing the interface…
I could publish my simple test server, if anyone is interested…
Photos
I haven’t looked too much at the photo sharing yet, but it looks exactly like the music sharing at first glance. In fact, you can see in the avahi configs I pointed photos and music to the same tivo.cgi. Only I don’t really check for /music or /photo yet and so a couple times I accidentally selected the photo option from the TiVo menu and it came up with a list of my music folders in the thumbnails (with the correct names and everything). So I think it really is the same.
Protocol
The protocol is pretty simple.
The tivo will send a request like this (each CGI parameter is on it’s own line for clarity and horizontal space conservation):
/tivo.cgi/music?Recurse=No &Filter=x-container%2Ffolder,x-container%2Fplaylist,audio%2F* &SortOrder=Type,Title &ItemCount=0 &Details=Basic &Format=text%2Fxml
The server should respond with something like this:
<?xml version="1.0" encoding="UTF-8" ?> <TiVoContainer> <Details> <Title>Top</Title> <ContentType>x-container/tivo-music</ContentType> <SourceFormat>x-container/tivo-music</SourceFormat> <TotalItems>2</TotalItems> </Details> <ItemStart>0</ItemStart> <ItemCount>0</ItemCount> </TiVoContainer>
The TiVo will then repeat the request with a higher ItemCount:
/tivo.cgi/music?Recurse=No &Filter=x-container%2Ffolder,x-container%2Fplaylist,audio%2F* &SortOrder=Type,Title &ItemCount=2 &Details=Basic &Format=text%2Fxml
This time the server should give some <Item>s:
<?xml version="1.0" encoding="UTF-8" ?> <TiVoContainer> <Details> <Title>Top</Title> <ContentType>x-container/tivo-music</ContentType> <SourceFormat>x-container/tivo-music</SourceFormat> <TotalItems>2</TotalItems> </Details> <Item> <Details> <Title>Flac</Title> <ContentType>x-container/folder</ContentType> <SourceFormat>x-container/folder</SourceFormat> </Details> <Links> <Content> <Url>/tivo.cgi/25</Url> </Content> </Links> </Item> <Item> <Details> <Title>dsongs</Title> <ContentType>x-container/folder</ContentType> <SourceFormat>x-container/folder</SourceFormat> </Details> <Links> <Content> <Url>/tivo.cgi/26</Url> </Content> </Links> </Item> <ItemStart>0</ItemStart> <ItemCount>2</ItemCount> </TiVoContainer>
The TiVo will now show a menu with 2 items (“Flac”, and “dsongs”). If you select “Flac” it will send a new query the same as the the first but to /tivo.cgi/25?Recurse=No&... etc.
So far I’ve only shown <Item>s that were folders. Here’s what a song looks like:
<Item> <Details> <Title>Way Out -></Title> <SongTitle>Way Out -></SongTitle> <SourceBitRate>320</SourceBitRate> <AlbumTitle>The Middle Of Nowhere</AlbumTitle> <ArtistName>Orbital</ArtistName> <AlbumYear>1999</AlbumYear> <ContentType>audio/mpeg</ContentType> <SourceFormat>audio/mpeg</SourceFormat> </Details> <Links> <Content> <Url>http://10.0.0.1:8080/db/730f0108/01-iTunes-320.mp3</Url> </Content> </Links> </Item>
Note that the URL can point anywhere–to another port on my server in this case (running Apache::MP3 plus some transcoding extensions I wrote–that’s why the directory is labeled “Flac” but we’re pulling mp3s out of it).
I don’t show it in this case, but there are also <Duration> and <MusicGenre> tags. I don’t know if they are order dependent or not, but I copied the TiVoDesktop server which puts them after <SongTitle> and <AlbumYear> respectively.
The next thing that can happen is if you scroll through a list that has more than 8 items in it. When it needs to get the next set of items it issues something like this:
/tivo.cgi/22?Recurse=No &Filter=x-container%2Ffolder,x-container%2Fplaylist,audio%2F* &SortOrder=Type,Title &AnchorItem=%2Ftivo.cgi%2F3 &AnchorOffset=-1 &ItemCount=1 &Details=Basic &Format=text%2Fxml
So they added 2 new parameters: AnchorItem and AnchorOffset.
<rant>Whoever made up this part of the protocol was drunk. Notice that AnchorItem is given as a URL instead of an offset. This means if you have a list of <Item>s you have to search through them to find the URL of the AnchorItem. You could use a hash table to efficiently look for it but then you still need the original list so you can get the next 7 items. So for no good reason (that I can tell) this part of the protocol complicates your life. </rant>
Anyway, once you’ve found which <Item> the AnchorItem is referring to, you need to iterate AnchorItem by AnchorOffset + 1. Yes, +1. That’s why they almost always pass -1 as AnchorOffset. Lovely.
One other “interesting” thing: ItemCount can be negative. If you scroll up through a list it’ll start sending you negative counts. I deal with this like this:
if ($count < 0) { $start += $count; $count = -$count; }
I also then make sure to clip $start and $count so they are within the list of items on that container.
When you select a track to play the TiVo will ask for details on just that item like this:
/tivo.cgi/4?Command=QueryItem &Url=http%3A%2F%2F10.0.0.1%3A8080%2Fdb%2F730f0108%2F01-iTunes-320.mp3
The response looks like this:
<?xml version="1.0" encoding="UTF-8" ?> <TiVoItem> <Item> <Details> <Title>Way Out -></Title> <SongTitle>Way Out -></SongTitle> <SourceBitRate>320</SourceBitRate> <AlbumTitle>The Middle Of Nowhere</AlbumTitle> <ArtistName>Orbital</ArtistName> <AlbumYear>1999</AlbumYear> <ContentType>audio/mpeg</ContentType> <SourceFormat>audio/mpeg</SourceFormat> </Details> <Links> <Content> <Url>http://10.0.0.1:8080/db/730f0108/01-iTunes-320.mp3</Url> </Content> </Links> </Item> </TiVoItem>
That’s just a standard <Item> wrapped in a <TiVoItem> tag.
That is enough to set up a hierarchy, have the TiVo be able to maneuver around and play music. I wanted shuffle though so I enabled it in the TiVo expecting it to just jump around in the folders randomly. Nope. It sends this:
/tivo.cgi/1?Recurse=Yes &Filter=audio%2F* &SortOrder=Random &RandomSeed=1172006919 &RandomStart=http%3A%2F%2F10.0.0.1%3A8080%2Fdb%2F0511ce12%2F02-iTunes-320.mp3 &AnchorItem=http%3A%2F%2F10.0.0.1%3A8080%2Fdb%2F5f103b07%2F04-iTunes-320.mp3&ItemCount=1 &Details=Optimal &Format=text%2Fxml
RandomSeed and RandomStart are constant once you’ve started some music playing with shuffle enabled on the TiVo. Essentially It sends you the last song it played in AnchorItem and expects you to return a new song to play in <TiVoItem> form. For now I just key off of the SortOrder parameter and give a random item from the folder I’m in. This isn’t a good shuffle on small playlists since it will repeat songs while ignoring others, but it was simple.
In fact, the implementation I made only does the bare minimum to work. I ignore most of the parameters passed in (like Details and Filter) but it works just fine (for now).
Debugging
What helped me was pointing towards the TiVo Desktop server on my Mac and submitting test queries. I’d also always had the apache access.log open in my Emacs so I could see what the TiVo was trying to ask me. And if I didn’t understand I’d recreate the query on the Mac to see what it would do. I’ll try to post more later, especially if there is some interest.
Conclusion
I’m happy I did this. It isn’t complete but it good enough for me for the time being. It’s been working all day (I’m listening to music out of my substandard TV speakers as I write this).