lørdag 14. november 2009

rTorrent + XBMC TV Library

In my never ending quest of creating the best home theater solution, i have written a bash script that converts downloaded media into a tv-series library structure. Read the description field of the script for more detailed information.

So in short, this script will turn the following folder structure:
/series/
       tv-show.s09.e10.xxxxx
       tv-show-0609.xxxxx
       tv-show_09x10.xxxxx

into the following:

/series/tv-show/season 06/
                         tv-show-0609.xxxxx
/series/tv-show/season 09/
                         tv-show.s09.e10.xxxxx
                         tv-show_09x10.xxxxx

And here is the script that does all the dirty work.

#!/bin/bash
# Filename:  mv-series
# Author:  Kim Eik
# License:  GPLv3
# Description:  This file is used in conjunction with rtorrent, it takes a
# a single input argument ex. "mythbusters.s01.e09.avi" and
# an optional prefix argument ex "/home/user". With this data
# the script creates the directory:
# "/home/user/Mythbusters/Season 01"
# And also creates the files: 
# "/home/user/Mythbusters/tvshow.nfo"
# and
# "/home/user/Mythbusters/Season 01/mythbusters.s01.e09.nfo"
# which XBMC can read to lookup tv-show information through
# scrapers. In addition the script prints out 
# "/home/user/Mythbuster/Season 01" without new line feed
# at the end. This output is for use with rtorrent, to set the
# new download directory (d.set_directory)

shopt -s nocasematch
filename=$1
seriesRegex=(
                "(.{3,})s([0-9]+).{0,1}?e([0-9]+{0,1}){0,1}"
                "(.{3,})([0-9]+)x([0-9]+)"
  "(.{3,})([0-9]{2})([0-9]{2})"
  "(.{3,})([0-9]{1})([0-9]{2})"
            )

trim()
{
    trimmed=$1
    trimmed=${trimmed%% }
    trimmed=${trimmed## }
    echo $trimmed
}

caseChange() {
 output=''
 for arg in $*
 do
  firstChar=${arg:0:1}
  rest=${arg:1}
  output="${output} `echo "${firstChar}" | tr '[a-z]' '[A-Z]'``echo "${rest}" | tr '[A-Z]' '[a-z]'`"
 done
 echo $output;
}

setSeriesTitle () {
        seriesTitle=$(trim "`echo $1 | tr '[_. ]' ' '`")
 seriesTitle=$(caseChange $seriesTitle)
}

setSeriesSeason() {
        seriesSeason=$1
}

setSeriesEpisode() {
 seriesEpisode=$1
}

createSeriesNFO() {
 file="${1}tvshow.nfo"
 if [[ ! -f $file ]]; then
 echo "
  <tvshow>
   <title>${2}</title>
  </tvshow>
      " > "${file}"
 fi
}

createEpisodeNFO() {
 file="${1}.nfo"
 if [[ ! -f $file ]]; then
 echo "
  <episodedetails>
    <season>${2}</season>
    <episode>${3}</episode>
  </episodedetails>
      " > "${file}"
 fi
}

if [[ $# -gt 2 ]]; then
        echo "Usage: ${0} \"filename\" [\"prefix\"]"
        exit 1
fi

for (( x=0; x<${#seriesRegex[@]}; x++ ))
do
        regex=${seriesRegex[$x]}
        if [[ $filename =~ $regex ]]; then
                case $x in
                        * )
                        setSeriesTitle "${BASH_REMATCH[1]}"
                        setSeriesSeason "${BASH_REMATCH[2]}"
   setSeriesEpisode "${BASH_REMATCH[3]}"
                        ;;
                esac
  if [ $# -eq 2 ]; then
   if [ "${2:$((${#2}-${#filename}))}" = "$filename" ]; then
    directory="${2:0:$((${#2}-${#filename}))}"
   else
    directory="$2/"
   fi
  fi
  seriesDir="${directory}${seriesTitle}/"
                seriesSeasonDir="${seriesDir}Season ${seriesSeason}/"
  mkdir -p "$seriesSeasonDir"
  
  #create nfo files
  #createSeriesNFO "$seriesDir" "$seriesTitle"
  
  if [[ "$filename" =~ (.*)\..{3} ]]; then
   filename=${BASH_REMATCH[1]}
   #createEpisodeNFO "${seriesSeasonDir}${filename}" "$seriesSeason" "$seriesEpisode"
   output="${seriesSeasonDir}"
  else
   #createEpisodeNFO "${seriesSeasonDir}${filename}" "$seriesSeason" "$seriesEpisode"
   output="${seriesSeasonDir}${filename}"
  fi

  #Output for rtorrent
  echo -n "${output}"
                exit 0
        fi
done

exit 1


in .rtorrent.rc:
schedule = watch_directory_1,5,5,"load_start=~/watch/series/*.torrent,d.set_directory=~/series/,d.set_custom3=series"
system.method.set_key = event.download.inserted_new,move_series,"branch=d.get_custom3=,\"d.set_directory=\\\"$execute_capture=/usr/local/bin/mv-series,$d.get_name=,$d.get_directory=\\\"\""

for all torrents in the watch folder "series" i set the custom3 variable to series, and in the case where custom3 variable is not empty rtorrent will execute the mv-series script which will return the new download directory.

And thats about it, according to the xbmc documentation, my htpc should now be showing my tv series in library mode.

A note about the nfo creation. It seems that xbmc omits looking up series information from scrapers when a nfo is present, where i originally thought it used nfo files as a supplement to the scrapers. Because of this, i have commented out the lines that actually creates the nfo files.

If you put this script too use, and you make any changes to it, please share your changes with the masses, so other can benefit from it.

Reference:
http://xbmc.org/wiki/?title=TV_Shows_%28Video_Library%29
http://xbmc.org/wiki/?title=Import_-_Export_Library#Video_nfo_Files

søndag 8. november 2009

Using the XBMC event server with rTorrent.

So i stumbled upon the xbmc's event server and its capabilities when doing a clean install of xbmc on my home theater pc solution. Basically what the event server does, is that it allows you to turn any device with network capability into a xbmc remote. anything that you can do with a mouse or a keyboard you can pass along to the event server. However a feature that isn't very well described is that the event server also supports notifications.

I've always been frustrated that i don't always have full overview of whats going on with my rTorrent server. I either have to use a ssh shell and connect to a screen running rtorrent to check my current downloads or use my own developed program nTorrent on a client machine before watching any given downloaded media. Now however, i can create a c++/java/python/c# (or whatever) notification program, that i can hook into with rtorrent's onstart and onfinished events to actually display the state of my current downloads right on my television set! sweet eyh!?

So i started having some fun with a little c++ code, and after an hour or so (yes i am not very driven in c++)  i ended up with this piece of code:

//============================================================================
// Name        : SimpleXBMCNotification.cpp
// Author      : Kim Eik
// Version     : 0.1
// Copyright   : GPLv3
// Description : Sends a notification to an xbmc event server
//============================================================================

#include <stdio.h>
#include <xbmc/xbmcclient.h>
#include <sys/socket.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>



int main(int argc, char *argv[]) {

 if(argc <= 3){
  std::cout << "Invalid arguments.\n" << argv[0] << "<title> <message> <address> [<port>]\n";
  return EXIT_FAILURE;
 }

 const char *title = argv[1];
 const char *message = argv[2];
 const char *address = argv[3];
 int port;

 if(argc < 5){
  port = STD_PORT;
 }else{
  port = atoi(argv[4]);
 }

 CAddress addressObj = CAddress(address,port);
 int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

 if (sockfd < 0){
  std::cout << "Error creating socket\n";
  return EXIT_FAILURE;
 }

 addressObj.Bind(sockfd);

 CPacketHELO HeloPackage("Notification", ICON_NONE);
 HeloPackage.Send(sockfd, addressObj);

 CPacketNOTIFICATION packet(title,message,ICON_NONE);
 packet.Send(sockfd, addressObj);

 CPacketBYE bye;
 bye.Send(sockfd, addressObj);

 return EXIT_SUCCESS;
}

And voila! it works!

Now that i can send notifications to my xbmc i can start configuring rtorrent to call the SimpleXBMCNotification application i just created.

I set about reading the rtorrent documentation for available events and came up with the following additions to the rtorrent.rc config file (most of the lines are commented out as you can see).

#SimpleXBMCNotification
system.method.set_key = event.download.finished,notify_finished,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent finished\",$d.get_name=,10.0.0.12"
system.method.set_key = event.download.erased,notify_erased,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent erased\",$d.get_name=,10.0.0.12"
system.method.set_key = event.download.inserted_new,notify_inserted_new,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent inserted (new)\",$d.get_name=,10.0.0.12"

#Other available events.
#system.method.set_key = event.download.inserted_session,notify_inserted_session,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent inserted (session)\",$d.get_name=,10.0.0.12"
#system.method.set_key = event.download.inserted,notify_inserted,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent inserted\",$d.get_name=,10.0.0.12"
#system.method.set_key = event.download.opened,notify_opened,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent opened\",$d.get_name=,10.0.0.12"
#system.method.set_key = event.download.closed,notify_closed,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent closed\",$d.get_name=,10.0.0.12"
#system.method.set_key = event.download.resumed,notify_resumed,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent started\",$d.get_name=,10.0.0.12"
#system.method.set_key = event.download.paused,notify_stop,"execute=/usr/local/bin/SimpleXBMCNotification,\"Torrent stopped\",$d.get_name=,10.0.0.12"
#system.method.set_key = event.download.hash_queued,notify_hash_queued,"execute=/usr/local/bin/SimpleXBMCNotification,\"Hash check queued\",$d.get_name=,10.0.0.12"
#system.method.set_key = event.download.hash_removed,notify_hash_removed,"execute=/usr/local/bin/SimpleXBMCNotification,\"Hash check removed\",$d.get_name=,10.0.0.12"
#system.method.set_key = event.download.hash_done,notify_hash_done,"execute=/usr/local/bin/SimpleXBMCNotification,\"Hash check completed\",$d.get_name=,10.0.0.12"

So after i added theese lines to my rtorrent.rc config file, notifications started to pop up on my television set! Here is some proof!




So that's that, please feel free to copy the code, and make your own changes too it. An obvious change would be to add your own icons to the notification application. I skipped all the details as i just wanted to see if it really was possible. Anyways happy hacking.