#!/usr/bin/perl -w
# downloads streams from bbc iplayer and if required inserts data into mythtv
# See --help for instructions.
#
# @date      11/03/2008
# @version   0.01
# @author    Jerome Hettich
# @license   GPL

use strict;
use Getopt::Long;
use LWP::Simple;
use LWP::UserAgent;
use HTTP::Cookies; 
use HTTP::Request;
use Time::Local;
use DBI;
use Cwd 'abs_path';

my $usage;                    # print help
my $video_dir;                # save file in this directory
my $img_dir;                  # save any image in this directory
my $myth_video = 0;           # update mythvideo
my $file;                     # override the output filename
my $dbhost = "localhost";     # database to connect to if updating myth
my $database = "mythconverg"; # myth database
my $user = "mythtv";          # myth database user
my $pass = "mythtv";          # myth database password
my $debug = 0;                # if true only download 0.2% of file

# Load the cli options
GetOptions(
    'usage|help|h'     => \$usage,
    'filename|f:s'     => \$file,
    'videodir|d:s'     => \$video_dir,
    'imagedir|i:s'     => \$img_dir,
    'mythvideo|v'      => \$myth_video,
    'database=s'       => \$database,
    'dbhost=s'         => \$dbhost,
    'user=s'           => \$user,
    'pass=s'           => \$pass,
    'debug'            => \$debug
);

my $argc=@ARGV;

# no arguments so print help
if ($argc == 0) {$usage = 1;}

# Last argument must always be bbc iplayer url or programme id
my $page=$ARGV[$#ARGV];

if ($usage) {
    print qq{usage: bbc_iplayer [options] iplayer_url

This script downloads the mp4 stream from the bbc iPlayer site 
(over http) as well as determining the associated metadata. The 
default behaviour (if no option are specified) are to save the 
file in current working directory.  Unless specified the file will 
be named "pid - heading1 - heading2.mov" and there will be no
updates to the myth database.

[options]:
--help		- Displays this message
--filename	- Create the file with this name rather than
		  the default name of "pid - heading1 - heading2.mov"
--videodir	- Save the file in this directory. If this parameter is set
		  then the downloaded mp4 stream will be saved in this 
		  directory.
--mythvideo	- If this parameter is set an entry with the appropriate 
		  metadata for this programme will be created in the myth 
		  database for Myth Video (please make sure database options 
		  are provided if defaults are incorrect).
		  Additionally the image for this programme will be downloaded.
		  If the imagedir option is not specified this will be saved 
		  in the same location as the video (as the filename+.jpg). 
		  If the imagedir paramater is NOT specified it is assumed that
		  the image file will end up in the Myth Video directory. 
		  Therefore an entry will be created in the myth video database 
		  for the image file to prevent this from being visible.
--imagedir	- If downloading an image file (when --mythvideo is used)
		  if this option is used the image will be saved in this 
		  directory. 
--debug		- If this paramater is set only 0.2% of the mp4 stream will be
		  downloaded and additional information will be printed. 
--dbhost        - hostname or IP address of the mysql server
                  (default: \"$dbhost\")
--user          - DBUSERNAME (default: \"$user\")
--pass          - DBPASSWORD (default: \"$pass\")
--database      - DATABASENAME (default: \"$database\")

Examples:

./bbc_iplayer b00936vz
Downloads program with id b00936vz to the current directory named
"pid - heading1 - heading2.mov"

./bbc_iplayer --videodir /video/films/bbc b00936vz
Downloads program with id b00936vz to the directory /video/films/bbc
"pid - heading1 - heading2.mov"

./bbc_iplayer --videodir /tmp --filename test.mov b00936vz
Downloads program with id b00936vz to the directory /tmp named test.mov

./bbc_iplayer --mythvideo --videodir /video/films/bbc 
                 --imagedir /home/mythtv/.mythtv/MythVideo b00936vz
Downloads program with id b00936vz to the directory /video/films/bbc named
"pid - heading1 - heading2.mov", saves the BBCs jpg image to 
/home/mythtv/.mythtv/MythVideo and creates a record with the metadata for 
myth video in the myth database 

    };
    exit(0);
}


my $iplayer_base = 'http://www.bbc.co.uk/iplayer/page/item/';
my $stream_base = 'http://www.bbc.co.uk'.
                  '/mediaselector/3/auth/iplayer_streaming_http_mp4/';

# Validate last arguement contains a programme in correct format 
if ($page =~ m/^[a-z0-9]{8}$/) {
    print "Using page: $page\n";
}
elsif ($page =~ m/([a-z0-9]{8}).shtml/) {
    $page = $1;
    print "Using page: $page\n";
} else {
    print "Unexpected format: $page expected format xnnnnnxx eg b00936vz\n";
    exit(0);
}


# Download iPlayer page
my $iplay_page = $iplayer_base.$page.'.shtml';
print "Finding details from url: $iplay_page\n";
my $content = get($iplay_page);

# Find pid and other useful info from iplayer page
my $pid;
my $title="";
my $img_url;
my $start;
my $end;
my $heading;
my $heading2;
my $duration;
my $synopsis;
my $channel;

if ($content =~ m/pid       : '([a-z0-9]{8})'/){
    $pid = $1;
}
else {
    print "No pid found - Exiting\n";
    exit(0);
}

#Find title from iplayer page
if ($content =~ m/iplayer.prog = "\s*(.*)"/){
    $title = $1;
}

#Find image url from iplayer page
if ($content =~ m/src   : '(.*)\.jpg'/){
    $img_url = $1.'.jpg';
}

#Find available dates for mp4 stream from iplayer page
if ($content =~ m/iplayer_streaming_http_mp4.*\n.*\((.*)\).*\n.*\((.*)\)/){
    # this script should only be running in the uk 
    # as iPlayer streams only available in uk so ok to use local time
    $start = $1;
    $end = $2;
    if ($start=~ m/([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+)/){
        $start = timelocal($6,$5,$4,$3,$2,$1);
    }
    if ($end =~ m/([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+)/){
        $end = timelocal($6,$5,$4,$3,$2,$1);
    }
}
# check that mp4 stream is currently available
my $current_time = time();
if (not defined $start or not defined $end or 
    $start > $current_time or $current_time > $end) {
    die "MP4 stream not currently available\n";
}

if ($content =~ m/<p class="heading">(.*)<\/p>/){
    $heading = $1;
}

if ($content =~ m/<h2>(.*)<\/h2>/){
    $heading2 = $1;
}

if ($content =~ m/<h3>Duration: ([0-9]+) minutes<\/h3>/){
    $duration = $1;
}

if ($content =~ m/<p id="synopsis">(.*)<\/p>/){
    $synopsis = $1;
}

if ($content =~ m/<div id="mip-channel-brand"><img alt="([^"]*)"/){
    $channel = $1;
}

my $pretty_start = scalar(localtime($start));
my $pretty_end = scalar(localtime($end));

print qq{	pid:		$pid
	title:		$title
	channel:	$channel
	img url:	$img_url
	mp4 available:	FROM ($pretty_start) TO ($pretty_end)
	heading:	$heading
	heading2:	$heading2
	duration:	$duration
	synopsis:	$synopsis
};

# Save mp4 stream to disk
my $ua = LWP::UserAgent->new;
my $cookies = new HTTP::Cookies();
$ua->cookie_jar($cookies); 
$ua->requests_redirectable   (['GET', 'HEAD', 'POST']);
my $mp4_file;
my $mp4_dir="";
if(defined $video_dir){
    $mp4_dir = abs_path($video_dir);
    if (not $mp4_dir =~ m/\/$/) {
        $mp4_dir = $mp4_dir.'/';
    }
    #TODO check this directory is accessible
}
	
if(not defined $file) {
    $file = $pid.' - '.$heading.' - '.$heading2.'.mov';
}
$mp4_file = $mp4_dir.$file;
open(FILE, ">$mp4_file") || die "Can't open $mp4_file: $!\n";
binmode FILE;
my $url = $stream_base.$pid;
$| = 1;  # autoflush
print qq{Saving to file $mp4_file from $url\n0% Downloaded};

my $length;            # total number of bytes to download
my $size = 0;          # number of bytes received
my $percent = 0;       # percent downloaded
my $last_print = 0;    # last percent printed
my $freq = 0.05;       # print every X%
my $formatted_current; # current percentage complete
my $formatted_length;  # length in Mbytes

my $res = $ua->request(HTTP::Request->new(GET => $url),
    sub{
        my $res = $_[1];
        if (not defined $res->content_length) {
            print "\nCould not download $url\n";
            exit (0);
        }
        if (not defined $length) {
            $length = $res->content_length;
            $formatted_length = int($length/(10.24*1024))/100
        }
        my $current = $size / $length * 100;

        # if debuging only download 0.2%
        if ($debug and $current > 0.2) {die;}

        if ($current > $last_print + $freq) {
            $last_print = $current;
            my $formatted_current;
            $formatted_current = int($current*100)/100;
            print "\r".$formatted_current."% of ".
                  $formatted_length." MBytes downloaded";
        }
        print FILE $_[0] or die "Can't write to $mp4_file: $!\n";
        $size += length($_[0]);
    }
);
print "\nDownload Complete\n";
close (FILE);

if ($myth_video) {
    # download the programe's image for the video cover file
    my $img = get($img_url);
    my $img_file;
    if (defined $img_dir) {
        $img_dir = abs_path($img_dir);
        if (not $img_dir =~ m/\/$/) {
            $img_dir = $img_dir.'/';
        }
        #TODO check this directory is accessible
        $img_file = $img_dir.$file.'.jpg';
    } else {
        $img_file = $mp4_file.'.jpg';
    }
    print qq{Downloading cover image to $img_file from $img_url\n};
    open (IFILE, ">$img_file") || die "Can't open $img_file: $!\n";
    print IFILE $img or die "Can't write to $img_file: $!\n";
    close (IFILE);

    # update the myth database
    print "Inserting video details into $database database\n";
    my $dbh = DBI->connect("dbi:mysql:database=$database:host=$dbhost",
        "$user","$pass") or die "Cannot connect to database ($!)\n";

    my $title = "$heading - $heading2";
    # create a videometadata record for video with details from bbc
    # or update the relevant record in videometadata if it exists
    my $q = "SELECT * FROM videometadata WHERE filename = ?";
    my $sth = $dbh->prepare($q);
    $sth->execute ($mp4_file) or die "Could not execute ($q)\n";
    if (my @row = $sth->fetchrow_array) {
        print "Video metadata already exists for video file - Updating\n";
        $q = "UPDATE videometadata ".
             "SET title = ?, plot = ?, coverfile = ?, ".
             "length = ?, browse = 1, showlevel = 1 ".
             "WHERE filename = ?";
        $sth = $dbh->prepare($q);
        $sth->execute ($title, $synopsis, $img_file, $duration, $mp4_file) 
            or die "Could not execute ($q)\n";
    } else {
        $q = "INSERT into ".
             "videometadata (title, plot, filename, ".
                             "coverfile, length, browse, showlevel) ".
             "VALUES (?, ?, ?, ?, ?, 1, 1)";
        $sth = $dbh->prepare($q);
        $sth->execute ($title, $synopsis, $mp4_file, $img_file, $duration)
            or die "Could not execute ($q)\n";
    }

    # if imagedir option not provided and mythvideo is set then the jpg image
    # will be saved to the same directory as the mp4 file. Therefore create a 
    # videometadata record for the jpg file so it is not visible in myth video
    if (not defined $img_dir) {
        $q = "SELECT * from videometadata WHERE filename = ?";
        $sth = $dbh->prepare($q);
        $sth->execute ($img_file) or die "Could not execute ($q)\n";
        my $cf_title = "CoverFile for $title";
        if (my @row = $sth->fetchrow_array) {
            print "Video metadata already exists for image file - Updating\n";
            $q = "UPDATE videometadata ".
                 "SET title = ?, browse = 0, showlevel = 0 ".
                 "WHERE filename = ?";
            $sth = $dbh->prepare($q);
            $sth->execute ($cf_title, $img_file) 
                or die "Could not execute ($q)\n";
        } else {
            $q = "INSERT into ".
                 "videometadata (title, filename, browse, showlevel) ".
                 "VALUES (?, ?, 0, 0)";
            $sth = $dbh->prepare($q);
            $sth->execute ($cf_title, $img_file) 
                or die "Could not execute ($q)\n";
        }
    }
}
exit(1);
