Update to Configuring Your MCE IR Remote - Getting Meta Data Fast

January 4th, 2010

Just made another quick update to the Configuring Your MCE IR Remote post. I have assigned the “Clear” button to download meta data for videos when in the video manager. This is far quicker than pressing “i” and scrolling through menus and means you can get all the cover / fan art and synopsis with one key press.

Acer Aspire Revo - A cheap HD frontend?

August 12th, 2009

ord-ace-revo-150x150Was tipped off about this fantastic article from a recent Ubuntu Uk Podcast.  The hardware looks pretty interesting an could make a great HD frontend for MythTV with VDPAU enabled. There’s a good post about trying VDPAU in MythBuntu on the forums but I haven’t had a chance to try it yet as I don’t have one of the supported GPUs.

Anyway, just thought the article was so good it was worth pointing out for anyone that might be looking for a cheap, near silent HD frontend.

Update to Configuring Your MCE IR Remote

August 11th, 2009

Just made a quick update to the Configuring Your MCE IR Remote post. I have added in Audio Delay + and - to the mplayer lirc config. This allows you to control the audio / video sync with the Blue and Yellow buttons on your MCE remote. I had quite a few films that have the audio out of sync so this can now be sorted without reaching for the keyboard :)

Normalise Movie Volume

March 27th, 2009

I know DTS is all the rage at the moment but some DTS soundtracks have VERY LOUD LOUD BITS and very quiet quiet bits. This is great for the cinema or those people lucky enough to live in a detached house, but for my 2 bed flat it’s a bit over the top. To level things out mplayer can normalise your movie’s volume in realtime so that explosions are at the same volume as conversations. To enable normalisation add the following switch to the launch command for mplayer:

-af volnorm

So for example on your frontend navigate to “Utilities / Setup” -> “Setup” -> “Media Settings” -> “Video Settings” -> “Player Settings” and set the “Default Video Player” to:

mplayer -fs -zoom -quiet -vo xv -subfont-text-scale 3 -af volnorm -cache 8912 -cache-min 4 -lavdopts threads=2:fast:skiploopfilter=all -sws 0 %s

You may find it easier to connect to the mythconverg database and set the VideoDefaultPlayer to the value above for the host machine.

Automatically Convert Radio Shows To MP3 Format

February 15th, 2009

It’s great to be able to record radio as well as TV channels using MythTV but to listen to them on an MP3 player they need to be transcoded to MP3 format. The process can be completely automated by setting up a User Job which runs after the show has recorded.

There are four main steps needed to get this going:

1. Install FFmpeg with MP3 encoding support
There’s a great guide for installing FFmpeg with MP3 encoding support here. Just follow it through making sure you use the suggested –enable-libmp3lame switch when running ./configure in step 5.

2. Install libid3-3.8.3-dev for automated ID3 tagging
This library will allow the MP3 encoding script to place ID3 meta tags into the resulting MP3.  Info like artist, song and year is set based on the show you are recording.

sudo apt-get install libid3-3.8.3-dev

3. Setup conversion script and MythTV User Job
The script below is the one that is executed by the User Job when the show finishes recording. It is based on this script from the MythTV Wiki but I’ve modified it to work with the latest version of FFmpeg and tag and name the resulting MP3 in my preferred format.

#!/bin/bash
 
###########
#
# Suggested execution format in the job settings for the backend (in general of the backend setup)
# Remember to allow the job to run in the backend general settings too :)
#
# autoaudio.sh %FILE% %STARTTIMEISO% "%TITLE%"
#
###########
 
OUTPUTDIR="/media/recordedtv/transcoded"
INPUTDIR="/media/recordedtv"
INFILE=$1
ISODATE=`date +%F`
PROGTITLE=$3
STARTDATE=${2:0:10}
 
# Split
###########
OUTFILE="$OUTPUTDIR/$PROGTITLE - $STARTDATE.mp3"
 
#transcode mp2 audio to mp3
ffmpeg -i "$INPUTDIR/$INFILE" -acodec libmp3lame -ab 192k -ar 44100 -f mp3 "$OUTFILE"
 
# Tag
##########
YEAR=`date +%Y`
id3tag --artist="$PROGTITLE" --song="$PROGTITLE - $STARTDATE" --comment="" -y$YEAR -A"Radio" "$OUTFILE"

Copy the script above and put it in a new file in your home directory called autoaudio.sh. For me this was /home/colin/autoaudio.sh. Change the “OUTPUTDIR” and “INPUTDIR” paths to match your setup. “OUTPUTDIR” will be where the MP3 files are created and “INPUTDIR” should be the path to the mpg files of your recorded shows.

Remember to make the script executable.

chmod 777 autoaudio.sh

Also, make sure the MythTV user can write to the output path.

4. Setup the user job
Go into the mythbackend setup and from the menu choose “General” and go to the last screen in this section titled “Job Queue (Job Commands)”. Enter a new job with the description of “Convert to MP3″ then enter the command as below changing the path to the location of your autoaudio.sh file. For me this is

/home/colin/autoaudio.sh %FILE% %STARTTIMEISO% "%TITLE%"

Choose “Finish” so you are back at the mythbackend main menu. Now we need to allow the job to run, so go back into “General” and scroll through to the screen titled “Job Queue (Backend-Specific)”. Tick the box for “Allow Convert to MP3 jobs”. Click “Next” and “Finish” until you are back to the main menu.

All Done
That’s it, you now have a user job which will convert shows to MP3, let’s check it works. I use MythWeb to manage things most of the time. In MythWeb, go to “Recorded Programs”, Click on a show title, then under “Queue a job” click the “Convert to MP3″ button. If all went well you should see an MP3 file created in the output directory you specified in autoaudio.sh. There may be a delay between clicking the button and an MP3 being created depending on how often your mythbackend is set to run user jobs. If nothing is created check the mythbackend logfile at /var/log/mythtv/mythbackend.log for errors.

Once it’s all working it can be activated on an existing recording schedule. In MythWeb, go to “Recording Schedules”, click on a schedule you have setup and look under “Advanced Options”. You should see a tick box for “Convert to MP3″. The show will now be converted to MP3 every time it records.

Updated imdb.pl for getting hi-res movie posters

February 12th, 2009

The current version of imdb.pl included with MythTV is not downloading hi-res poster images for movies due to a site change at IMDB’s end. I have edited imdb.pl to include a fix as detailed in https://bugs.launchpad.net/mythbuntu/+bug/256027 and included below.

To get it going, backup your old imdb.pl file in /usr/share/mythtv/mythvideo/scripts/ and replace with:

#!/usr/bin/perl -w
 
#
# This perl script is intended to perform movie data lookups based on
# the popular www.imdb.com website
#
# For more information on MythVideo's external movie lookup mechanism, see
# the README file in this directory.
#
# Author: Tim Harvey (tharvey AT alumni.calpoly DOT edu)
# Modified: Andrei Rjeousski
# v1.1
# - Added amazon.com covers and improved handling for imdb posters
# v1.2
#     - when searching amazon, try searching for main movie name and if nothing
#       is found, search for informal name
#     - better handling for amazon posters, see if movie title is a substring
#       in the search results returned by amazon
#     - fixed redirects for some movies on impawards
# v1.3
#     - fixed search for low res images (imdb changed the page layout)
#     - added cinemablend poster search
#     - added nexbase poster search
#     - removed amazon.com searching for now
 
# changes:
# 10-26-2007:
#   Added release date (in ISO 8601 form) to output
# 9-10-2006: Anduin Withers
#   Changed output to utf8
 
use LWP::Simple;      # libwww-perl providing simple HTML get actions
use HTML::Entities;
use URI::Escape;
 
eval "use DateTime::Format::Strptime"; my $has_date_format = $@ ? 0 : 1;
 
use vars qw($opt_h $opt_r $opt_d $opt_i $opt_v $opt_D $opt_M $opt_P);
use Getopt::Std;
 
$title = "IMDB Query";
$version = "v1.3.5";
$author = "Tim Harvey, Andrei Rjeousski";
 
my @countries = qw(USA UK Canada Japan);
 
binmode(STDOUT, ":utf8");
 
# display usage
sub usage {
   print "usage: $0 -hdrviMPD [parameters]\n";
   print "       -h           help\n";
   print "       -d           debug\n";
   print "       -r           dump raw query result data only\n";
   print "       -v           display version\n";
   print "       -i           display info\n";
   print "\n";
   print "       -M [options] <query>    get movie list\n";
   print "               some known options are:\n";
   print "                  type=[fuzy]         looser search\n";
   print "                  from_year=[int]     limit matches to year\n";
   print "                  to_year=[int]       limit matches to year\n";
   print "                  sort=[smart]        ??\n";
   print "                  tv=[no|both|only]   limits between tv and movies\n";
   print "               Note: multiple options must be separated by ';'\n";
   print "       -P <movieid>  get movie poster\n";
   print "       -D <movieid>  get movie data\n";
   exit(-1);
}
 
# display 1-line of info that describes the version of the program
sub version {
   print "$title ($version) by $author\n"
}
 
# display 1-line of info that can describe the type of query used
sub info {
   print "Performs queries using the www.imdb.com website.\n";
}
 
# display detailed help
sub help {
   version();
   info();
   usage();
}
 
sub trim {
   my ($str) = @_;
   $str =~ s/^\s+//;
   $str =~ s/\s+$//;
   return $str;
}
 
# returns text within 'data' between 'beg' and 'end' matching strings
sub parseBetween {
   my ($data, $beg, $end)=@_; # grab parameters
 
   my $ldata = lc($data);
   my $start = index($ldata, lc($beg)) + length($beg);
   my $finish = index($ldata, lc($end), $start);
   if ($start != (length($beg) -1) && $finish != -1) {
      my $result = substr($data, $start, $finish - $start);
      # return w/ decoded numeric character references
      # (see http://www.w3.org/TR/html4/charset.html#h-5.3.1)
      decode_entities($result);
      return $result;
   }
   return "";
}
 
# get Movie Data
sub getMovieData {
   my ($movieid)=@_; # grab movieid parameter
   if (defined $opt_d) { printf("# looking for movie id: '%s'\n", $movieid);}
 
   my $name_link_pat = qr'<a href="/name/[^"]*">([^<]*)</a>'m;
 
   # get the search results  page
   my $request = "http://www.imdb.com/title/tt" . $movieid . "/";
   if (defined $opt_d) { printf("# request: '%s'\n", $request); }
   my $response = get $request;
   if (defined $opt_r) { printf("%s", $response); }
 
   # parse title and year
   my $year = "";
   my $title = parseBetween($response, "<title>", "</title>");
   if ($title =~ m#(.+) \((\d+).*\)#) # Note some years have a /II after them?
   {
      $title = $1;
      $year = $2;
   }
   elsif ($title =~ m#(.+) \(\?\?\?\?\)#)
   {
      $title = $1;
   }
 
   # parse director
   my $data = parseBetween($response, ">Director:</h5>", "</div>");
   if (!length($data)) {
      $data = parseBetween($response, ">Directors:</h5>", "</div>");
   }
   my $director = join(",", ($data =~ m/$name_link_pat/g));
 
   # parse writer
   # (Note: this takes the 'first' writer, may want to include others)
   $data = parseBetween($response, ">Writers <a href=\"/wga\">(WGA)</a>:</h5>", "</div>");
   if (!length($data)) {
         $data = parseBetween($response, ">Writer:</h5>", "</div>");
   }
   if (!length($data)) {
         $data = parseBetween($response, ">Writers:</h5>", "</div>");
   }
   my $writer = join(",", ($data =~ m/$name_link_pat/g));
 
   # parse release date
   my $releasedate = '';
   if ($has_date_format) {
      my $dtp = new DateTime::Format::Strptime(pattern => '%d %b %Y',
         on_error => 'undef');
      my $dt = $dtp->parse_datetime(parseBetween($response,
            ">Release Date:</h5> ", "<a "));
      if (defined($dt)) {
         $releasedate = $dt->strftime("%F");
      }
   }
 
   # parse plot
   my $plot = parseBetween($response, ">Plot Outline:</h5> ", "</div>");
   if (!$plot) {
      $plot = parseBetween($response, ">Plot Summary:</h5> ", "</div>");
   }
   if (!$plot) {
      $plot = parseBetween($response, ">Plot:</h5>", "</div>");
   }
 
   if ($plot) {
      # replace name links in plot (example 0388795)
      $plot =~ s/$name_link_pat/$1/g;
 
      # replace title links
      my $title_link_pat = qr!<a href="/title/[^"]*">([^<]*)</a>!m;
      $plot =~ s/$title_link_pat/$1/g;
 
      # plot ends at first remaining link
      my $plot_end = index($plot, "<a ");
      if ($plot_end != -1) {
         $plot = substr($plot, 0, $plot_end);
      }
      $plot = trim($plot);
   }
 
   # parse user rating
   my $userrating = parseBetween($response, ">User Rating:</b>", "</b>");
   $userrating = parseBetween($userrating, "<b>", "/");
 
   # parse MPAA rating
   my $ratingcountry = "USA";
   my $movierating = trim(parseBetween($response, ">MPAA</a>:</h5>", "</div>"));
   if (!$movierating) {
       $movierating = parseBetween($response, ">Certification:</h5>", "</div>");
       $movierating = parseBetween($movierating, "certificates=$ratingcountry",
                                   "/a>");
       $movierating = parseBetween($movierating, ">", "<");
   }
 
   # parse movie length
   my $rawruntime = trim(parseBetween($response, ">Runtime:</h5>", "</div>"));
   my $runtime = trim(parseBetween($rawruntime, "", " min"));
   for my $country (@countries) {
      last if ($runtime =~ /^-?\d/);
      $runtime = trim(parseBetween($rawruntime, "$country:", " min"));
   }
 
   # parse cast
   #  Note: full cast would be from url:
   #    www.imdb.com/title/<movieid>/fullcredits
   my $cast = "";
   $data = parseBetween($response, "Cast overview, first billed only",
                               "/table>");
   if (!$data) {
       $data = parseBetween($response, "Series Cast Summary",
                               "/table>");
   }
 
   if (!$data) {
       $data = parseBetween($response, "Complete credited cast",
                               "/table>");
   }
 
   if ($data) {
      $cast = join(',', ($data =~ m/$name_link_pat/g));
      $cast = trim($cast);
   }
 
 
   # parse genres
   my $lgenres = "";
   $data = parseBetween($response, "<h5>Genre:</h5>","</div>");
   if ($data) {
      my $genre_pat = qr'/Sections/Genres/(?:[a-z ]+/)*">([^<]+)<'im;
      $lgenres = join(',', ($data =~ /$genre_pat/g));
   }
 
   # parse countries
   $data = parseBetween($response, "Country:</h5>","</div>");
   my $country_pat = qr'/Sections/Countries/[A-Z]+/">([^<]+)</a>'i;
   my $lcountries = trim(join(",", ($data =~ m/$country_pat/g)));
 
   # output fields (these field names must match what MythVideo is looking for)
   print "Title:$title\n";
   print "Year:$year\n";
   print "ReleaseDate:$releasedate\n";
   print "Director:$director\n";
   print "Plot:$plot\n";
   print "UserRating:$userrating\n";
   print "MovieRating:$movierating\n";
   print "Runtime:$runtime\n";
   print "Writers: $writer\n";
   print "Cast:$cast\n";
   print "Genres: $lgenres\n";
   print "Countries: $lcountries\n";
}
 
# dump Movie Poster
sub getMoviePoster {
   my ($movieid)=@_; # grab movieid parameter
   if (defined $opt_d) { printf("# looking for movie id: '%s'\n", $movieid);}
 
   # get the search results  page
   my $request = "http://www.imdb.com/title/tt" . $movieid . "/posters";
   if (defined $opt_d) { printf("# request: '%s'\n", $request); }
   my $response = get $request;
   if (defined $opt_r) { printf("%s", $response); }
 
   if (!defined $response) {return;}
 
   my $uri = "";
 
   # look for references to impawards.com posters - they are high quality
   my $site = "http://www.impawards.com";
   my $impsite = parseBetween($response, "<a href=\"".$site, "\">");
 
 
   if ($impsite) {
      $impsite = $site . $impsite;
 
      if (defined $opt_d) { print "# Searching for poster at: ".$impsite."\n"; }
      my $impres = get $impsite;
      if (defined $opt_d) { printf("# got %i bytes\n", length($impres)); }
      if (defined $opt_r) { printf("%s", $impres); }
 
      # making sure it isnt redirect
      $uri = parseBetween($impres, "0;URL=..", "\">");
      if ($uri ne "") {
         if (defined $opt_d) { printf("# processing redirect to %s\n",$uri); }
         # this was redirect
         $impsite = $site . $uri;
         $impres = get $impsite;
      }
 
      # do stuff normally
      $uri = parseBetween($impres, "<img SRC=\"posters/", "\" ALT");
      # uri here is relative... patch it up to make a valid uri
      if ($uri =~ /\.(jpe?g|gif|png)$/) {
          if (!($uri =~ /http:(.*)/ )) {
             my $path = substr($impsite, 0, rindex($impsite, '/') + 1);
             $uri = $path."posters/".$uri;
          }
          if (defined $opt_d) { print "# found ipmawards poster: $uri\n"; }
      }
      else {
          $uri = "";
      }
   }
 
   # try looking on MoTechPosters 
   if ($uri eq "" && $response =~ m/<a href="([^"]*)">([^"]*?)motechposters/i) {
    if ($1 ne "") {
      if (defined $opt_d) { print "# found MoTechPosters poster page: $1 \n"; }
        my $cinres = get $1;
        if (defined $opt_d) { printf("# got %i bytes\n", length($cinres)); }
          if (defined $opt_r) { printf("%s", $cinres); }
           if ($cinres =~ m/<img src="([^"]*?$movieid[^"]*?)"/i) {
             if (defined $opt_d) { print "# MoTechPosters url retreived\n"; }
               $uri = "http://posters.motechnet.com".$1;
           }
    }
  }
 
   # try looking on nexbase
   if ($uri eq "" && $response =~ m/<a href="([^"]*)">([^"]*?)nexbase/i) {
      if ($1 ne "") {
         if (defined $opt_d) { print "# found nexbase poster page: $1 \n"; }
         my $cinres = get $1;
         if (defined $cinres) {
            if (defined $opt_d) { printf("# got %i bytes\n", length($cinres)); }
            if (defined $opt_r) { printf("%s", $cinres); }
 
            if ($cinres =~ m/<a id="photo_url" href="([^"]*?)" ><\/a>/i) {
               if (defined $opt_d) { print "# nexbase url retreived\n"; }
               $uri = $1;
            }
         }
      }
   }
 
   # try looking on cinemablend
   if ($uri eq "" && $response =~ m/<a href="([^"]*)">([^"]*?)cinemablend/i) {
      if ($1 ne "") {
         if (defined $opt_d) { print "# found cinemablend poster page: $1 \n"; }
         my $cinres = get $1;
         if (defined $opt_d) { printf("# got %i bytes\n", length($cinres)); }
         if (defined $opt_r) { printf("%s", $cinres); }
        if ($cinres =~ m#<img\b[^>]+\bsrc="(/images/reviews/[^"]*?)"#i) {
            if (defined $opt_d) { print "# cinemablend url retreived\n"; }
            $uri = "http://www.cinemablend.com/".$1;
         }
      }
   }
 
   # if the impawards site attempt didn't give a filename grab it from imdb
   if ($uri eq "") {
       if (defined $opt_d) { print "# looking for imdb posters\n"; }
       my $host = "http://posters.imdb.com/posters/";
 
       $uri = parseBetween($response, $host, "\"><td><td><a href=\"");
       if ($uri ne "") {
           $uri = $host.$uri;
       } else {
          if (defined $opt_d) { print "# no poster found\n"; }
       }
   }
 
 
 
   my @movie_titles;
   my $found_low_res = 0;
   my $k = 0;
 
   # no poster found, take lowres image from imdb
   if ($uri eq "") {
      if (defined $opt_d) { print "# looking for lowres imdb posters\n"; }
      my $host = "http://www.imdb.com/title/tt" . $movieid . "/";
      $response = get $host;
 
      # Better handling for low resolution posters
      #
      if ($response =~ m/<a name="poster".*<img.*src="([^"]*).*<\/a>/ig) {
         if (defined $opt_d) { print "# found low res poster at: $1\n"; }
         $uri = $1;
         $found_low_res = 1;
      } else {
         if (defined $opt_d) { print "# no low res poster found\n"; }
         $uri = "";
      }
 
      if (defined $opt_d) { print "# starting to look for movie title\n"; }
 
      # get main title
      if (defined $opt_d) { print "# Getting possible movie titles:\n"; }
      $movie_titles[$k++] = parseBetween($response, "<title>", "<\/title>");
      if (defined $opt_d) { print "# Title: ".$movie_titles[$k-1]."\n"; }
 
      # now we get all other possible movie titles and store them in the titles array
      while($response =~ m/>([^>^\(]*)([ ]{0,1}\([^\)]*\)[^\(^\)]*[ ]{0,1}){0,1}\(informal title\)/g) {
         $movie_titles[$k++] = trim($1);
         if (defined $opt_d) { print "# Title: ".$movie_titles[$k-1]."\n"; }
      }
 
   }
 
   print "$uri\n";
}
 
# dump Movie list:  1 entry per line, each line as 'movieid:Movie Title'
sub getMovieList {
   my ($filename, $options)=@_; # grab parameters
 
   # If we wanted to inspect the file for any reason we can do that now
 
   #
   # Convert filename into a query string
   # (use same rules that Metadata::guesTitle does)
   my $query = $filename;
   $query = uri_unescape($query);  # in case it was escaped
   # Strip off the file extension
   if (rindex($query, '.') != -1) {
      $query = substr($query, 0, rindex($query, '.'));
   }
   # Strip off anything following '(' - people use this for general comments
   if (rindex($query, '(') != -1) {
      $query = substr($query, 0, rindex($query, '('));
   }
   # Strip off anything following '[' - people use this for general comments
   if (rindex($query, '[') != -1) {
      $query = substr($query, 0, rindex($query, '['));
   }
 
   # IMDB searches do better if any trailing ,The is left off
   $query =~ /(.*), The$/i;
   if ($1) { $query = $1; }
 
   # prepare the url
   $query = uri_escape($query);
   if (!$options) { $options = "" ;}
   if (defined $opt_d) {
      printf("# query: '%s', options: '%s'\n", $query, $options);
   }
 
   # get the search results  page
   #    some known IMDB options are:
   #         type=[fuzy]         looser search
   #         from_year=[int]     limit matches to year (broken at imdb)
   #         to_year=[int]       limit matches to year (broken at imdb)
   #         sort=[smart]        ??
   #         tv=[no|both|only]   limits between tv and movies (broken at imdb)
   #$options = "tt=on;nm=on;mx=20";  # not exactly clear what these options do
   my $request = "http://www.imdb.com/find?q=$query;$options";
   if (defined $opt_d) { printf("# request: '%s'\n", $request); }
   my $response = get $request;
   if (defined $opt_r) {
      print $response;
      exit(0);
   }
 
   # check to see if we got a results page or a movie page
   #    looking for 'add=<movieid>" target=' which only exists
   #    in a movie description page
   my $movienum = parseBetween($response, "add=", "\"");
   if (!$movienum) {
      $movienum = parseBetween($response, ";add=", "'");
   }
   if ($movienum) {
      if ($movienum !~ m/^[0-9]+$/) {
         if (defined $opt_d) {
            printf("# Error: IMDB movie number ($movienum), isn't.\n");
         }
         exit(0);
      }
 
      if (defined $opt_d) { printf("# redirected to movie page\n"); }
      my $movietitle = parseBetween($response, "<title>", "</title>");
      $movietitle =~ m#(.+) \((\d+)\)#;
      $movietitle = $1;
      print "$movienum:$movietitle\n";
      exit(0);
   }
 
   # extract possible matches
   #    possible matches are grouped in several catagories:
   #        exact, partial, and approximate
   my $popular_results = parseBetween($response, "<b>Popular Titles</b>",
                                              "</table>");
   my $exact_matches = parseBetween($response, "<b>Titles (Exact Matches)</b>",
                                              "</table>");
   my $partial_matches = parseBetween($response, "<b>Titles (Partial Matches)</b>",
                                              "</table>");
#   my $approx_matches = parseBetween($response, "<b>Titles (Approx Matches)</b>",
#                                               "</table>");
   # parse movie list from matches
   my $beg = "<tr>";
   my $end = "</tr>";
   my $count = 0;
   my @movies;
 
#   my $data = $exact_matches.$partial_matches;
   my $data = $popular_results.$exact_matches;
   # resort to partial matches if no exact
   if ($data eq "") { $data = $partial_matches; }
   # resort to approximate matches if no exact or partial
#   if ($data eq "") { $data = $approx_matches; }
   if ($data eq "") {
      if (defined $opt_d) { printf("# no results\n"); }
      return;
   }
   my $start = index($data, $beg);
   my $finish = index($data, $end, $start);
   my $year;
   my $type;
   my $title;
   while ($start != -1 && $start < length($data)) {
      $start += length($beg);
      my $entry = substr($data, $start, $finish - $start);
      $start = index($data, $beg, $finish + 1);
      $finish = index($data, $end, $start);
 
      my $title = "";
      my $year = "";
      my $type = "";
      my $movienum = "";
 
      # Some titles are identical, IMDB indicates this by appending /I /II to
      # the release year.
      #   e.g. "Mon meilleur ami" 2006/I vs "Mon meilleur ami" 2006/II
      if ($entry =~ m/<a href="\/title\/tt(\d+)\/.*\">(.+)<\/a> \((\d+)\/?[a-z]*\)(?: \((.+)\))?/i) {
          $movienum = $1;
          $title = $2;
          $year = $3;
          $type = $4 if ($4);
      } else {
           if (defined $opt_d) {
               print("Unrecognized entry format ($entry)\n");
           }
           next;
      }
 
      my $skip = 0;
 
      # fix broken 'tv=no' option
      if ($options =~ /tv=no/) {
         if ($type eq "TV") {
            if (defined $opt_d) {printf("# skipping TV program: %s\n", $title);}
            $skip = 1;
         }
      }
      if ($options =~ /tv=only/) {
         if ($type eq "") {
            if (defined $opt_d) {printf("# skipping Movie: %s\n", $title);}
            $skip = 1;
         }
      }
      # fix broken 'from_year=' option
      if ($options =~ /from_year=(\d+)/) {
         if ($year < $1) {
            if (defined $opt_d) {printf("# skipping b/c of yr: %s\n", $title);}
            $skip = 1;
         }
      }
      # fix broken 'to_year=' option
      if ($options =~ /to_year=(\d+)/) {
         if ($year > $1) {
            if (defined $opt_d) {printf("# skipping b/c of yr: %s\n", $title);}
            $skip = 1;
         }
      }
 
      # option to strip out videos (I think that's what '(V)' means anyway?)
      if ($options =~ /video=no/) {
         if ($type eq "V") {
            if (defined $opt_d) {
                printf("# skipping Video program: %s\n", $title);
            }
            $skip = 1;
         }
      }
 
      # (always) strip out video game's (why does IMDB give these anyway?)
      if ($type eq "VG") {
         if (defined $opt_d) {printf("# skipping videogame: %s\n", $title);}
         $skip = 1;
      }
 
      # add to array
      if (!$skip) {
          my $moviename = $title;
          if ($year ne "") {
              $moviename .= " ($year)";
          }
 
#         $movies[$count++] = $movienum . ":" . $title;
         $movies[$count++] = $movienum . ":" . $moviename;
      }
   }
 
   # display array of values
   for $movie (@movies) { print "$movie\n"; }
}
 
#
# Main Program
#
 
# parse command line arguments
getopts('ohrdivDMP');
 
# print out info
if (defined $opt_v) { version(); exit 1; }
if (defined $opt_i) { info(); exit 1; }
 
# print out usage if needed
if (defined $opt_h || $#ARGV<0) { help(); }
 
if (defined $opt_D) {
   # take movieid from cmdline arg
   $movieid = shift || die "Usage : $0 -D <movieid>\n";
   getMovieData($movieid);
}
 
elsif (defined $opt_P) {
   # take movieid from cmdline arg
   $movieid = shift || die "Usage : $0 -P <movieid>\n";
   getMoviePoster($movieid);
}
 
elsif (defined $opt_M) {
   # take query from cmdline arg
   $options = shift || die "Usage : $0 -M [options] <query>\n";
   $query = shift;
   if (!$query) {
      $query = $options;
      $options = "";
   }
   getMovieList($query, $options);
}
# vim: set expandtab ts=3 sw=3 :

Quickly Sort Channels

February 11th, 2009

Something that’s not quite perfect in MythTV is sorting channels. You can move channels around one by one in the backend setup screens but this is slow and boring. A quicker way is to update the channel table in the mythconverg database on your backed. I had a search around and found this useful post on the Ubuntu forms but it was still a pain to have to adjust the SQL strings as they were.  My solution is to put the SQL into to a spreadsheet (attached below):

Channel Order Helper Spreadsheet

Obviously your channel listing is going do be different to mine so I suggest you do the following:

  1. Download the spreadsheet
  2. Get your existing channel names by running:
    SELECT DISTINCT name FROM channel ORDER BY LPAD(channum, 3, 0) ASC;
  3. Paste them into column D in the spread sheet
  4. Re-arrange as required
  5. Select all the cells and copy into gedit or similar (removing any tabs)
  6. Run all statements agains your mythconverg database.

NB. This will update the channel numbers for all sources (you could always change the sql to include a “WHERE sourceid = 1″ clause or simialr. Also, make sure that your frontend is set to sort by “Channel Number” in “Utilities/Setup” -> “Setup” -> “TV” -> “Settings” -> “General”

Play 720p HD Content Over Wireless & Make The Subtitle Size Sensible

February 9th, 2009

This one drove me nuts for a while, watching 720p content over 54Mbit/s wi-fi should work fine but with the “out of the box” settings HD moves were stuttering terribly for me.

As an additional niggle, embedded subtitles were appearing but they were enormous.

By default MythTV uses mplayer for mkv, avi and mpeg files. To resolve the stutter and subtitle issue open the MythTV frontend and go to “Utilities / Setup” -> “Setup” -> “Media Settings” -> “Video Settings” -> “Player Settings” and set the “Default Video Player” to:

mplayer -fs -zoom -quiet -vo xv -subfont-text-scale 3 -cache 8912 -cache-min 4 -lavdopts threads=2:fast:skiploopfilter=all -sws 0 %s

You may find it easier to connect to the mythconverg database and set the VideoDefaultPlayer to the value above for the host machine on wireless.

Configuring Your MCE IR Remote

February 9th, 2009

You can use the keyboard to control absouletly everything in MythTV but it’s not that practical once the lights are out and you’re mid movie. So one of the first things I was looking to do is get my Microsoft MCE remote working just the way I wanted.

For some background info you might want to check out the MCE Remote section of the MythTV wiki here.

MythTV does a pretty good job of setting up some defaults for the buttons but, if like me you’ve come from a Windows MCE system you may fine the lirc config files and associated script below useful.

These configs do four things above the functionality MythTV provides out of the box:

  1. Start / Restart mythfrontend when “the big green button” is pressed (handy if the frontend ever misbehaves)
  2. Cycle through subtitles when watching TV or movies in mplayer
  3. Make the Red button behave as expected for interactive TV. Green, Yellow, Blue and Teletext also act as they should.
  4. Make other buttons behave as a Windows MCE user would expect.

Here’s how all the buttons will function once everything is set up:

MCE Remote Mappings

MCE Remote Mappings


Girlfriend proof it

A small tip which helps with the gf factor is removing the exit and shutdown menu when the back button is pressed from the main menu. To do this, in the frontend go to “Utilities / Setup” -> “Setup” -> “General”. Go to the fourth screen with the title of  “General” and change the “System Exit Key” to “No exit key”. You can always exit the frontend with “alt F4″ if you need to.

Bash script to start / restart the frontend

The first thing to do is setup the bash script which will start or restart the MythTV frontend application when the big green button on your remote is pressed. Copy the script below and put it in a new file in your home directory.  I put mine in ~/mythtv/runmyth

#!/bin/sh
RUNNING=0;
 
for x in `ps -C mythfrontend.re | grep -v PID` end; do
    test $x != 'mythfrontend.re' &amp;&amp; continue
    RUNNING=1;
done
 
if [ $RUNNING = 1 ]; then
  #kill the process
   kill -9 `ps -aef | grep 'mythfrontend.re' | grep -v grep | awk '{print $2}'`
  #start it up again
  `mythfrontend &amp;`
else
  `mythfrontend &amp;`
fi

Don’t forget to make it executable

chmod 777 runmyth

lirc config files
Now that’s out the way here follows the lirc files for MythTV and mplayer. They should go into ~/.lirc/mythtv and ~/.lirc/mplayer respectively. Make sure the runmyth script path is defined correctly on line four of the mythtv lirc file.

mythtv

begin
    prog = irexec
    button = Home
    config = /home/colin/mythtv/runmyth &amp;;
end
 
begin
    remote = mceusb
    prog = mythtv
    button = LiveTV
    config = %
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = RecTV
    config = ;
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = DVD
    config = m
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Red
    config = F2
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Green
    config = F3
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Yellow
    config = F4
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Blue
    config = F5
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Teletext
    config = F6
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Clear
    config = w
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Star
    config = T
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Record
    config = R
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Seven
    config = 7
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Right
    config = Right
    repeat = 3
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Mute
    config = |
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Skip
    config = Right
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = One
    config = 1
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Down
    config = Down
    repeat = 3
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Zero
    config = 0
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Replay
    config = Left
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Pause
    config = P
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Six
    config = 6
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Two
    config = 2
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = ChanDown
    config = Down
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = ChanUp
    config = Up
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Rewind
    config = &lt;
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Forward
    config = &gt;
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Play
    config = P
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = VolDown
    config = [
    repeat = 1
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Stop
    config = Escape
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Back
    config = Escape
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = VolUp
    config = ]
    repeat = 1
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Five
    config = 5
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = More
    config = I
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Four
    config = 4
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = OK
    config = Return
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Up
    config = Up
    repeat = 3
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Nine
    config = 9
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Three
    config = 3
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Enter
    config = Enter
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Eight
    config = 8
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Guide
    config = S
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mythtv
    button = Left
    config = Left
    repeat = 3
    delay = 0
end

mplayer

begin
    remote = mceusb
    prog = mplayer
    button = Play
    config = pause
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Star
    config = sub_select
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Blue
    config = audio_delay 0.1
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Yellow
    config = audio_delay -0.1
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = ChanDown
    config = panscan -0.1
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = ChanUp
    config = panscan +0.1
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Pause
    config = pause
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = OK
    config = pause
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Power
    config = quit
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Mute
    config = mute
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = VolDown
    config = volume -1
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Skip
    config = seek +15 0
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Enter
    config = pause
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Stop
    config = quit
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Up
    config = seek +60 0
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = VolUp
    config = volume +1
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Down
    config = seek -60 0
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Replay
    config = seek -15 0
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Right
    config = seek +6 0
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Rewind
    config = seek -30 0
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Forward
    config = seek +30 0
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Home
    config = vo_fullscreen
    repeat = 0
    delay = 0
end
 
begin
    remote = mceusb
    prog = mplayer
    button = Left
    config = seek -6 0
    repeat = 0
    delay = 0
end

Time to get public

February 9th, 2009

After many months of tweaking, hacking and generally abusing MythTv to behave the way I’d like (and be girlfriend friendly) I though it was about time to publish some of the things that have helped me.

Who knows how often I’ll update this blog but the current plan is just to add things as and when I discover them in the hope that it helps you too!

If you are new to MythTv check out the About page for general info and links.

Let the tips commence…