The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#!/usr/bin/perl
#
use strict ;

# Local
use App::Framework ;

use Linux::DVB::DVBT::Apps::QuartzPVR::Config::Constants ;

## CPAN REQUIRED:
use Linux::DVB::DVBT ;
use Linux::DVB::DVBT::TS ;
use Linux::DVB::DVBT::Advert ;


use MP3::Tag ;
use DBI ;
use DBD::mysql ;
## CPAN REQUIRED

use Linux::DVB::DVBT::Ffmpeg ;
use Linux::DVB::DVBT::Config ;


# VERSION
our $VERSION = '5.00' ;

## Global
our $DEBUG = 0 ;
our $DBG_SQL = 0 ;
our $VERBOSE = 0 ;
our $NICE = 19 ;

## Cut from get_iplayer
our @history_format = qw/pid name episode type timeadded mode filename 
	versions duration desc channel categories thumbnail guidance web 
	episodenum seriesnum/;


## Global data
our @info_lines ;
our %dbh ;
our $progname ;
our $mailto ;

	# Create application and run it
	App::Framework->new() ;
	go() ;


#=================================================================================
# SUBROUTINES EXECUTED BY APP
#=================================================================================

#----------------------------------------------------------------------
# Main execution
#
sub app
{
	my ($app, $opts_href, $args_href) = @_ ;

	my $ok = 1 ;
	
	my $user = $opts_href->{'user'}	|| Linux::DVB::DVBT::Apps::QuartzPVR::Config::Constants::SQL_USER ;
	my $password = $opts_href->{'password'}	|| Linux::DVB::DVBT::Apps::QuartzPVR::Config::Constants::SQL_PASSWORD ;
	
	if (!$user || !$password)
	{
		print "Error: You must specify the MySQL username AND password when using this script outside the PVR suite\n" ;
		exit 1 ;
	}
	
	$DEBUG = $opts_href->{'debug'} ;
	$DBG_SQL = $opts_href->{'dbg-sql'} ;
	$VERBOSE = $opts_href->{'verbose'} ;
	
	my $rec_file = $opts_href->{'file'} ;
	my $id = $opts_href->{'id'} ; 
	my $force = $opts_href->{'force'} ; 
	my $dest = $opts_href->{'dest'} ; 
	my $type = $opts_href->{'type'} ; 
	
	$mailto = $opts_href->{'mailto'} ;
	my $log = "$opts_href->{'log_dir'}/dvbt-iplay.log" ;
	
	%dbh = (
		'host' 		=> 'localhost',
		'db' 		=> $opts_href->{'database'},
		'tbl' 		=> $opts_href->{'table'},
		'user' 		=> $user,
		'password' 	=> $password,
	) ;
	
	$progname = $app->name() ;
	

	info("===============================================================") ;
	info("$progname v$VERSION") ;
	info("") ;

    if (!$rec_file)
    {
	    die("$progname: No arguments given.")  if (@{$args_href->{'name'}} == 0) ;
	    die("$progname: No title given.")  if (@{$args_href->{'name'}} < 1) ;
    }
    
	
	my @recargs ;	
	my ($title) ;
	
	## Get recordings from file
	#
	# -t <tv|radio> -o <dest> -g <title>
	#
	if ($rec_file)
	{
		# ($to, $error_msg, $errors_aref, $log, $rec_href)
		open my $fh, "<$rec_file" or die_error_mail($mailto, "Failed to open recording list file $rec_file : $!") ;		
		my $line ;
		while (defined($line=<$fh>))
		{
			chomp $line ;
			$line =~ s/^\s+// ;
			$line =~ s/\s+$// ;
			next unless $line ;
			
			# skip comments
			next if ($line =~ /^\s*#/) ;

print STDERR "LINE: $line\n" if $DEBUG>=2;
			
			## check for extra options
			my %options ;
			while ($line =~ /\s*\-{1,2}(\w+)\s+(?:'([^']+)'|"([^"]+)"|(\S+))/g)
			{
				$options{$1} = $2|$3|$4 ;
				$title ||= $options{'get'} || $options{'g'}  ;
				
print STDERR " + OPTION: $1 = <$2> <$3> <$4>\n" if $DEBUG>=2;
			}
			
			## process
			$options{'force'} = 1 if $force ;
			process_args(\@recargs, \%options) ;
		}
		close $fh ;
	}	
	
	## Get recordings from command line
	else
	{
		my @args = @{$args_href->{'name'}} ;
		
		# process default args
		my %options = () ;
		$options{'get'} = shift @args ;
		$title = $options{'get'} ;
		$options{'type'} = $type if $type ;
		$options{'output'} = $dest if $dest ;
		$options{'id'} = $id if $id ;
		$options{'force'} = 1 if $force ;
		process_args(\@recargs, \%options) ;
print STDERR "GET: $options{'get'}\n" if $DEBUG>=2;
		
		while (scalar(@args) >= 1)
		{
			$options{'get'} = shift @args ;
			process_args(\@recargs, \%options) ;
print STDERR "GET: $options{'get'}\n" if $DEBUG>=2;
		}
		
		
	}
print STDERR "TITLE: $title\n" if $DEBUG ;

    die("$progname: No title given.")  unless $title ;

	## History
	my $history_href = read_history() ;

	## Index management
	my $index_ok = get_iplay_index() ;
	if (!$index_ok)
	{
		die_error_mail($mailto, "Error: Unable to read get_iplayer index. Are you sure get_iplayer is installed?") ;
	}
	my $index_href = get_indexes() ;

Linux::DVB::DVBT::prt_data("INDEX", $index_href) if $DEBUG >= 2 ;

print STDERR "RECORD\n" if $DEBUG ;

	## Record
	my %results ;
	my @total_errors ;
	foreach my $rec_href (@recargs)
	{
		my @lines ;
		my @warnings ;
		my @errors ;
		
		## Get program id
		my $prog_id = $rec_href->{'id'} ;
		
		$results{$prog_id} = {} ;

print STDERR "NEXT: get='$rec_href->{get}'\n" if $DEBUG ;		
		## Match file(s) in cache
		my $target = $rec_href->{'get'} ;
		my @found=() ;
		
		# 'get' list consists of any matching entries from the indexes.
		# Also, if program has already been downloaded, includes a ref to the HASH from
		# the download history
		my @get = match_iplay($index_href->{$rec_href->{'type'}}, $history_href, $target, \@found, $rec_href->{'force'}) ;

Linux::DVB::DVBT::prt_data("TARGET='$target', GET=", \@get, "Found=", \@found) if $DEBUG>=2 ;

		## Skip if nothing to do
		next unless @get ;

		## Mark as started
		sql_start_status(\%dbh, $prog_id, \@get) ;

		
		## Get file(s)
		my %recorded ;
		my $rc = get_iplay(\@get, $rec_href, \@lines, \@warnings, \@errors, \%recorded) ;

Linux::DVB::DVBT::prt_data("RECORDED=", \%recorded) if $DEBUG>=2 ;

		## Update history 
		## NOTE: 'recorded' HASH points to the entries in the 'get' ARRAY and so the
		##       'recorded' HASH will also be updated
		$history_href = read_history() ;
		update_iplay(\@get, $history_href) ;

		## Mark with recorded status
		sql_update_status(\%dbh, $prog_id, \%recorded) ;
		
		## Check status
		if ($rc != 0)
		{
			push @errors, "Getting $rec_href->{get} : status $rc" ;
		}
		
		# lines
		foreach my $line (@lines)
		{
			info("$line") ;
		}
	
		# warning lines
		foreach my $line (@warnings)
		{
			info("WARN: $line") ;
		}
	
		# error lines
		foreach my $line (@errors)
		{
			info("ERROR: $line") ;
		}
		
		push @total_errors, @errors ;
		
		## Save results
		foreach my $iplayer_pid (keys %recorded)
		{
			$results{$prog_id}{$iplayer_pid} = { %{$recorded{$iplayer_pid}} } ;
			if ($recorded{$iplayer_pid}{'info'})
			{
				if ($recorded{$iplayer_pid}{'info'}{'downloaded'})
				{
					$results{$prog_id}{$iplayer_pid} = {
						%{ $recorded{$iplayer_pid}{'info'}{'downloaded'} },
						%{ $results{$prog_id}{$iplayer_pid} },
					} ;
				}
				else
				{
					$results{$prog_id}{$iplayer_pid} = {
						%{ $recorded{$iplayer_pid}{'info'} },
						%{ $results{$prog_id}{$iplayer_pid} },
					} ;
				}
			}
		}
	}

	## Show all results
	info("") ;
	info("---------------------------------------------------------------") ;
	info("") ;
	foreach my $rec_href (@recargs)
	{
		# Get program id
		my $prog_id = $rec_href->{'id'} ;
		
		info("Title:    $rec_href->{get}") ;
		info("Type:     $rec_href->{type}") ;
		
		my $results_href = $results{$prog_id} ;
		foreach my $iplayer_pid (sort keys %$results_href)
		{
			info("") ;
			info("    IPID:     $iplayer_pid") ;
			if ($results_href->{$iplayer_pid}{'error'})
			{
				info("    ERROR:    $results_href->{$iplayer_pid}{'error'}") ;
			}
			else
			{
				info("    File:     $results_href->{$iplayer_pid}{'file'}") ;
				info("    Title:    $results_href->{$iplayer_pid}{'name'}") ;
				info("    Channel:  $results_href->{$iplayer_pid}{'channel'}") ;
				info("    Episode:  $results_href->{$iplayer_pid}{'episode'}") if $results_href->{$iplayer_pid}{'episode'} ;
				info("    Info:     $results_href->{$iplayer_pid}{'desc'}") ;
				info("    Mode:     $results_href->{$iplayer_pid}{'mode'}") if $results_href->{$iplayer_pid}{'mode'} ;
			}
		}
		
		info("") ;
	}
	
	## Handle errors
	if (@total_errors)
	{
		## End
		# ($to, $error_msg, $errors_aref, $log, $rec_href)
		die_error_mail($mailto, "Failed to complete", \@total_errors) ;
	}
	
	
	## End
	info("COMPLETE") ;

}


#=================================================================================
# SUBROUTINES
#=================================================================================

#-----------------------------------------------------------------------------
sub process_args
{
	my ($recargs_aref, $opts_href) = @_ ;
	
	$opts_href ||= {} ;

	my $out = $opts_href->{'o'} || $opts_href->{'output'} || "" ;
	my $id = $opts_href->{'id'} || 0 ;
	my $force = $opts_href->{'force'} || 0 ;
	my $rec_href = {
		'get'		=> $opts_href->{'g'} || $opts_href->{'get'}, 
		'type'		=> $opts_href->{'t'} || $opts_href->{'type'} || 'tv', 
		'id'		=> $id,
		'force'		=> $force,
	} ;
	
	$rec_href->{'output'} = $out if $out ;

	info("Title:    $rec_href->{get}") ;
	info("Type:     $rec_href->{type}") ;
	info("Dir:      $out") if $out ;
	info("ID:       $id") if $id ;
	info("Force:    True") if $force ;
	info("") ;
	
	push @$recargs_aref, $rec_href ;
	
	
}

#-----------------------------------------------------------------------------
# Run get_iplayer to update indexes
sub get_iplay_index
{
	my ($type) = @_ ;
	
	my $cmd = 'get_iplayer' ;
	$cmd .= " --list --type $type" ;
	
	# set niceness
	my $nice = "" ;
	if ($NICE)
	{
		$cmd = "nice -n $NICE  $cmd" ;
	}

	# run command
	my @lines = `$cmd 2>&1 ; echo RC=$?` ;
	
	my $ok = 1 ;
	foreach my $line (@lines)
	{
		chomp $line ;
		if ($line =~ /No such file/i)
		{
			$ok = 0 ;
		}
	}	
Linux::DVB::DVBT::prt_data("get_iplay_index() = ", \@lines) if $DEBUG >= 10 ;
	
	return $ok ;
}

#-----------------------------------------------------------------------------
# Read in the indexes
sub get_indexes
{
	my %indexes = (
		'tv'	=> read_index('tv.cache'),
		'radio'	=> read_index('radio.cache'),
	) ;
	return \%indexes ;
}

#-----------------------------------------------------------------------------
# Read in the index
sub read_index
{
	my ($index_file) = @_ ;
	
	my @index ;

	my $filename = "$ENV{'HOME'}/.get_iplayer/$index_file" ;
	if (-f $filename)
	{
		open my $fh, "<$filename" or die "Error: Unable to read get_iplayer cache $filename : $!" ;
		my $line ;
		my @fields ;
		while (defined($line=<$fh>))
		{
			chomp $line ;
			$line =~ s/^\s+// ;
			$line =~ s/\s+$// ;
			next unless $line ;
			
			# First comment line contains the list of fields
			if ($line =~ s/^#//)
			{
				next if @fields ;
				
				@fields = split(/\|/, $line) ;
			}
			else
			{
				my @data = split(/\|/, $line) ;
				
				my %data ;
				for (my $idx=0; $idx < @fields ; ++$idx)
				{
					$data{ $fields[$idx] } = $data[$idx] ;
				}
				
				# Glue together information for matching
				my @parts ;
				foreach (qw/name episode desc/)
				{
					push @parts, $data{$_} if $data{$_} ;
				}
				$data{'search'} = join ' ', @parts ;
				
				push @index, \%data ;
			}
		}
		close $fh ;
	}

	return \@index ;
}


#-----------------------------------------------------------------------------
# Read in the history file
sub read_history
{
	my %history ;

	my $filename = "$ENV{'HOME'}/.get_iplayer/download_history" ;
	if (-f $filename)
	{
		open my $fh, "<$filename" or die "Error: Unable to read get_iplayer history $filename : $!" ;
		my $line ;
		while (defined($line=<$fh>))
		{
			chomp $line ;
			$line =~ s/^\s+// ;
			$line =~ s/\s+$// ;
			next if $line =~ /^#/ ;
			next unless $line ;
			
			my @data = split(/\|/, $line) ;
			
			my %data ;
			for (my $idx=0; $idx < @history_format ; ++$idx)
			{
				$data{ @history_format[$idx] } = $data[$idx] ;
			}
			
			$history{ $data{'pid'} } = \%data ;
		}
		close $fh ;
	}

	return \%history ;
}




#-----------------------------------------------------------------------------
# Searches for target text in the latest index file and returns an ARRAY fo HASH entries, each entry
# being from the data in the appropriate get_iplayer index (tv or radio):
#
#    available => Unknown,
#    categories => Comedy,Radio,Spoof,
#    channel => BBC 7,
#    desc => Battles galore in Earth's definitive history from the National Theatre of Brent.,
#    duration => Unknown,
#    episode => Episode 6,
#    episodenum => 6,
#    guidance => 0,
#    index => 10197,
#    name => All the World's a Globe,
#    pid => b007jwv5,
#    search => All the World's a Globe Episode 6 Battles galore in Earth's definitive history from the National Theatre of Brent.,
#    seriesnum => 0,
#    thumbnail => http://www.bbc.co.uk/iplayer/images/episode/b007jwv5_150_84.jpg,
#    timeadded => 1312308008,
#    type => radio,
#    versions => default,
#    web => http://www.bbc.co.uk/programmes/b007jwv5.html,
#
# Where the program is already downloaded, also contains the data from the download history:
#
#    downloaded => 
#      { # HASH(0x90a6fd0)
#        categories => Comedy,Spoof,
#        channel => BBC Radio 4 Extra,
#        desc => Battles galore in Earth's definitive history from the National Theatre of Brent. Stars Patrick Barlow and Jim Broadbent.,
#        duration => 900,
#        episode => Episode 6,
#        episodenum => 6,
#        filename => /home/sdprice1/svn/record-multirec-devel/temp/All_the_Worlds_a_Globe_-_Episode_6_b007jwv5_default.aac,
#        guidance => 0,
#        mode => flashaacstd1,
#        name => All the World's a Globe,
#        pid => b007jwv5,
#        seriesnum => undef,
#        thumbnail => http://www.bbc.co.uk/iplayer/images/episode/b007jwv5_150_84.jpg,
#        timeadded => 1312881788,
#        type => radio,
#        versions => default,
#        web => http://www.bbc.co.uk/programmes/b007jwv5.html,
#      },
# 
#
sub match_iplay
{
	my ($index_aref, $history_href, $target, $found_aref, $force) = @_ ;

print STDERR "match_iplay($target)\n" if $DEBUG ;		

	my @get ;

	## Convert title into regexp
	my $regexp = title2regexp($target) ;
	
	for (my $i=0; $i < @$index_aref; ++$i)
	{
#print STDERR "regexp='$regexp' : search='$index_aref->[$i]{'search'}'\n" ;		
		if ($index_aref->[$i]{'search'} =~ /$regexp/i)
		{
			my $entry_href = $index_aref->[$i] ;
			
			my $id = $entry_href->{'pid'} ;
			
			# check download history
			$entry_href->{'downloaded'} = 0 ;
			if (exists($history_href->{$id}))
			{
				$entry_href->{'downloaded'} = $history_href->{$id} ;
			}
			
			# save
			push @$found_aref, $entry_href ;
print STDERR " + FOUND (download=$entry_href->{'downloaded'})\n" if $DEBUG ;		
		}
	}

Linux::DVB::DVBT::prt_data("Found list=", $found_aref) if $DEBUG>=2 ;

print STDERR "Create get list:\n" if $DEBUG ;		
	
	foreach my $href (@$found_aref)
	{
		# skip already downloaded AND not forced
		if ($href->{'downloaded'} && !$force)
		{
			next ;
		}

print STDERR " + GET $href->{'pid'} : $href->{name} : $href->{desc}\n" if $DEBUG ;		
		
		# add to list of files to get
		push @get, $href ;
	}

Linux::DVB::DVBT::prt_data("Get list=", \@get) if $DEBUG>=2 ;

	return @get ;
}

#-----------------------------------------------------------------------------
# Update the 'get' list with the latest download history
sub update_iplay
{
	my ($get_aref, $history_href) = @_ ;

print STDERR "update_iplay()\n" if $DEBUG ;		

	foreach my $entry_href (@$get_aref)
	{
		my $id = $entry_href->{'pid'} ;
		
		# check download history
		$entry_href->{'downloaded'} = 0 ;
		if (exists($history_href->{$id}))
		{
			$entry_href->{'downloaded'} = $history_href->{$id} ;
		}
	}
}


#-----------------------------------------------------------------------------
sub title2regexp
{
	my ($title) = @_ ;

	my @fields = split(/[^\w\d\']+/, $title) ;
	
	my $regexp = "" ;
	foreach my $field (@fields)
	{
		$field =~ s/\'/.{0,1}/g ;
		if (!$regexp)
		{
			$regexp = "^" ;
		}
		else
		{
			$regexp .= ".+\\b";
		}
		$regexp .= "$field\\b" ;
	}


	print STDERR "Regexp: $regexp\n" if $DEBUG>=3 ;
	
	return $regexp ;
}


#-----------------------------------------------------------------------------
# Run get_iplayer and check for errors
#
# Fills in the recorded HASH ref with details for each get_iplayer index:
#
#{ # HASH(0x8fd74a0)
#  b007jwv5 => 
#    { # HASH(0x965b018)
#      error => 0,
#      file => /home/sdprice1/svn/record-multirec-devel/temp/All_the_Worlds_a_Globe_-_Episode_6_b007jwv5_default.aac,
#      status => recorded,
#    },
#
# Also includes the key 'info' which points to the HASH ref for that entry in the 'get' ARRAY:
#
#      info => 
#        { # HASH(0x9222670)
#          available => Unknown,
#          categories => Comedy,Radio,Spoof,
#          channel => BBC 7,
#          desc => Battles galore in Earth's definitive history from the National Theatre of Brent.,
#          downloaded => 
#            { # HASH(0x90a6fd0)
#              categories => Comedy,Spoof,
#              channel => BBC Radio 4 Extra,
#              desc => Battles galore in Earth's definitive history from the National Theatre of Brent. Stars Patrick Barlow and Jim Broadbent.,
#              duration => 900,
#              episode => Episode 6,
#              episodenum => 6,
#              filename => /home/sdprice1/svn/record-multirec-devel/temp/All_the_Worlds_a_Globe_-_Episode_6_b007jwv5_default.aac,
#              guidance => 0,
#              mode => flashaacstd1,
#              name => All the World's a Globe,
#              pid => b007jwv5,
#              seriesnum => undef,
#              thumbnail => http://www.bbc.co.uk/iplayer/images/episode/b007jwv5_150_84.jpg,
#              timeadded => 1312881788,
#              type => radio,
#              versions => default,
#              web => http://www.bbc.co.uk/programmes/b007jwv5.html,
#            },
#          duration => Unknown,
#          episode => Episode 6,
#          episodenum => 6,
#          guidance => 0,
#          index => 10197,
#          name => All the World's a Globe,
#          pid => b007jwv5,
#          search => All the World's a Globe Episode 6 Battles galore in Earth's definitive history from the National Theatre of Brent.,
#          seriesnum => 0,
#          thumbnail => http://www.bbc.co.uk/iplayer/images/episode/b007jwv5_150_84.jpg,
#          timeadded => 1312308008,
#          type => radio,
#          versions => default,
#          web => http://www.bbc.co.uk/programmes/b007jwv5.html,
#        },
#
# (Note the 'downloaded' field is only populated if the program has already been downloaded)
#
sub get_iplay
{
	my ($get_aref, $rec_href, $lines_aref, $warn_aref, $error_aref, $recorded_href) = @_ ;
	
	my $cmd = 'get_iplayer' ;
	$cmd .= " --force" if $rec_href->{'force'} ;
	$cmd .= " --type $rec_href->{type}" if $rec_href->{'type'} ; 
	$cmd .= " --output '$rec_href->{output}'" if $rec_href->{'output'} ; 
	
	## Create list of indexes to get and set up the recorded file HASH
	my $get = "" ;
	foreach my $href (@$get_aref)
	{
		my $iplay_index = $href->{'index'} ;
		my $iplay_pid = $href->{'pid'} ;
		
		# add to list of files to get
		$get .= ' ' if $get ;
		$get .= $iplay_index ;
		
		## Set up recorded status
		$recorded_href->{$iplay_pid} = {
			'status'	=> 'error',
			'file'		=> '',
			'error'		=> 'Unable to find program',
			'info'		=> $href,
		} ;
	}
	$cmd .= " --get $get" ; 
	
	
	# set niceness
	my $nice = "" ;
	if ($NICE)
	{
		$cmd = "nice -n $NICE  $cmd" ;
	}
	
	## Ensure destination dir exists
	my $dir = $rec_href->{output} ;
	if (! -d $dir)
	{
		if (!mkpath([$dir], 0, 0755))
		{
			my $error = "ERROR: unable to create dir $dir : $!" ;
			info($error) ;
			push @$error_aref, $error ;
			return 1 ;
		}
	}
	
	# run command
	info("CMD: $cmd") ;
	@$lines_aref = `$cmd 2>&1 ; echo RC=$?` ;
	
	# strip newlines
	my $num_recorded = 0 ;
	foreach my $line (@$lines_aref)
	{
print STDERR "$line\n" if $DEBUG>=10 ;
		
		# Strip out the intermediate processing output (flvstreamer status lines)
		$line =~ s/^.*\r//g ;

		## Process output lines to get recorded info
		if ($line =~ /INFO:/)
		{
			if ($line =~ /(\d+) Matching Prog/i)
			{
				$num_recorded = $1 ;
			}
			
			if ($line =~ m%Recorded\s+(/\S.*_)([\w\d]+)(_default\..*)%i)
			{
				my ($file, $iplay_pid) = ("$1$2$3", $2) ;
				$recorded_href->{$iplay_pid}{'status'} = 'recorded' ;
				$recorded_href->{$iplay_pid}{'file'} = $file ;
				$recorded_href->{$iplay_pid}{'error'} = '' ;
			}
		}

		chomp $line ;
		
		# prepend with command name
		$line = "[get_iplayer] $line" ;
	}

	# Add command to start
	unshift @$lines_aref , $cmd ;
	
	# get status
	my $rc=-1 ;
	if ($lines_aref->[-1] =~ m/RC=(\d+)/)
	{
		$rc = $1 ;
	}
	
	return $rc ;
}


#=================================================================================
# MYSQL
#=================================================================================

## NOTE: For SQL table, 'pid' refers to the program id

#-----------------------------------------------------------------------------
sub sql_escape_str
{
	my ($str) = @_ ;
	$str =~ s/\'/\\'/g ;
	return $str ;
}


#-----------------------------------------------------------------------------
sub sql_connect
{
	my ($db_href) = @_ ;

	$db_href->{'dbh'} = 0 ;
	
	eval
	{
		# Connect
		my $dbh = DBI->connect("DBI:mysql:database=".$db_href->{'db'}.
					";host=".$db_href->{'host'},
					$db_href->{'user'}, $db_href->{'password'},
					{'RaiseError' => 1}) ;
					
		$db_href->{'dbh'} = $dbh ;
	};
	if ($@)
	{
		die_error_mail($mailto, "Unable to connect to database : $@") ;
	}
	
	return $db_href->{'dbh'} ;
}

#-----------------------------------------------------------------------------
sub sql_send
{
	my ($db_href, $sql) = @_ ;
	
	my $dbh = sql_connect($db_href) ;
	if ($dbh)
	{
		# Do query
		eval
		{
			print STDERR "sql_send($sql)\n" if $DBG_SQL ;			
			$dbh->do($sql) ;
		};
		if ($@)
		{
			die_error_mail($mailto, "SQL do error $@\nSql=$sql") ;
		}
	}
}

#-----------------------------------------------------------------------------
sub sql_get
{
	my ($db_href, $sql) = @_ ;
	
	my @results ;
	my $dbh = sql_connect($db_href) ;
	if ($dbh)
	{
		# Do query
		eval
		{
			print STDERR "sql_get($sql)\n" if $DBG_SQL ;			

			my $sth = $dbh->prepare($sql) ;
			$sth->execute() ;
		
			while (my $sql_href = $sth->fetchrow_hashref())
			{
				push @results, $sql_href ;
			}
		};
		if ($@)
		{
#			print STDERR "SQL do error $@\nSql=$sql" ;
			die_error_mail($mailto, "SQL do error $@\nSql=$sql") ;
		}
	}
	return @results ;
}

#-----------------------------------------------------------------------------
# Converts the returned list of results into a hash keyed by the named $key. Each entry 
# is an array ref containing 1 or more instances of that key value
sub sql_get_href
{
	my ($db_href, $sql, $key) = @_ ;
	
	my @results = sql_get($db_href, $sql) ;
Linux::DVB::DVBT::prt_data("sql_get_href($sql) key=$key results=", \@results) if $DEBUG >= 2 ;
	my %results ;
	
	foreach my $href (@results)
	{
		if (exists($href->{$key}))
		{
			my $keyval = $href->{$key} ; 
			$results{$keyval} ||= [] ;
			push @{ $results{$keyval} }, $href ;
		}
	}
	
	return %results ;
}


#-----------------------------------------------------------------------------
sub sql_start_status
{
	my ($db_href, $pid, $get_aref) = @_ ;

	print STDERR "sql_start_status(pid=$pid)\n" if $DBG_SQL ;
	return unless $pid ;
	
Linux::DVB::DVBT::prt_data("Get=", $get_aref) if $DEBUG>=2 ;

	## Get existing entries
	my $search_sql = "SELECT * FROM $db_href->{tbl} WHERE `pid`='$pid' AND `rectype`='iplay'" ;
	my %results = sql_get_href($db_href, $search_sql, 'ipid') ;
Linux::DVB::DVBT::prt_data("Existing SQL recorded entries=", \%results) if $DEBUG>=2 ;

	# need there to be at least one instance already set up
	my @keys = keys %results ;
	return unless @keys ;
	
	# keep a copy for creating new entry
	my $existing_href = $results{$keys[0]}[0] ;
	
	## need to update all existing entries OR create new ones
	foreach my $href (@$get_aref)
	{
		my $iplayer_pid = $href->{'pid'} ;

print STDERR " + Looking at get entry: iplayer pid=$iplayer_pid\n" if $DEBUG	;
		
		my $entry_id ;
		if (exists($results{$iplayer_pid})) 
		{
			## get sql row id
			my $entry_href = pop @{$results{$iplayer_pid}} ;
			$entry_id = $entry_href->{'id'} ;
print STDERR " + + Matches existing SQL id=$entry_id\n" if $DEBUG	;
		}
		elsif (exists($results{'-'}) && @{$results{'-'}} )
		{
			## use empty entry
			my $entry_href = pop @{$results{'-'}} ;
			$entry_id = $entry_href->{'id'} ;
print STDERR " + + Matches empty SQL id=$entry_id\n" if $DEBUG	;
		}
		else
		{
			## Create new entry
print STDERR " + + Create new SQL entry...\n" if $DEBUG	;
			
			# pid rid rectype channel title date start duration adapter record priority file
			my $sql = "INSERT $db_href->{tbl} SET `changed`=CURRENT_TIMESTAMP, `ipid`='-'" ;
			foreach my $field (qw/pid rid rectype channel title date start duration adapter record priority file/)
			{
				my $val = $existing_href->{$field} ;
				$val = sql_escape_str($val) ;
				$sql .= ", `$field`='$val'" ;
			}
			sql_send($db_href, $sql) ;


			## Get new id
			$sql = "SELECT `id` FROM $db_href->{tbl} ORDER BY `id` DESC LIMIT 1" ;
print STDERR "Get sql: \"$sql\"\n" if $DEBUG ;
			my @results = sql_get($db_href, $sql) ;
			$entry_id = $results[0]{'id'} ;

print STDERR " + + New SQL id=$entry_id\n" if $DEBUG	;
		}
	
		die_error_mail($mailto, "Error: Invalid entry $entry_id (ID $iplayer_pid)") unless ($entry_id > 0) ;
		
		my $text = "" ;
		$text .= "$href->{'episode'}: " if $href->{'episode'} ;
		$text .= "$href->{'desc'}" if $href->{'desc'} ;
		$text = sql_escape_str($text) ;
		
		# UPDATE tbl SET flags=TRIM(',' FROM CONCAT(flags, ',', 'flagtoadd'))
		my $sql = "UPDATE $db_href->{tbl} SET `status`='started', `ipid`='$iplayer_pid', `changed`=CURRENT_TIMESTAMP" ;
		$sql .= ", `text`='$text'" ;
		$sql .= " WHERE `id`='$entry_id'" ;
		sql_send($db_href, $sql) ;
	}


#	# UPDATE tbl SET flags=TRIM(',' FROM CONCAT(flags, ',', 'flagtoadd'))
#	my $sql = "UPDATE $db_href->{tbl} SET `status`='started', `changed`=CURRENT_TIMESTAMP" ;
#	$sql .= " WHERE `pid`='$pid' AND `rectype`='iplay'" ;
#	
#	sql_send($db_href, $sql) ;
	
#exit 0 ;	
}


#-----------------------------------------------------------------------------
sub sql_update_status
{
	my ($db_href, $pid, $recorded_href) = @_ ;

	print STDERR "sql_update_status(pid=$pid)\n" if $DBG_SQL ;
	
Linux::DVB::DVBT::prt_data("Recorded=", $recorded_href) if $DEBUG>=2 ;
	
	return unless $pid ;
	
	## Get existing entries
	my $search_sql = "SELECT * FROM $db_href->{tbl} WHERE `pid`='$pid' AND `rectype`='iplay'" ;
	my %results = sql_get_href($db_href, $search_sql, 'ipid') ;
Linux::DVB::DVBT::prt_data("Existing SQL recorded entries=", \%results) if $DEBUG>=2 ;
	
	## need to update all existing entries OR create new ones
	foreach my $iplayer_pid (keys %$recorded_href)
	{
print STDERR " + Looking at get entry: iplayer pid=$iplayer_pid\n" if $DEBUG	;
		my $href = $recorded_href->{$iplayer_pid} ;
		
		my $entry_id ;
		if (exists($results{$iplayer_pid})) 
		{
			## get sql row id
			my $entry_href = pop @{$results{$iplayer_pid}} ;
			$entry_id = $entry_href->{'id'} ;
print STDERR " + + Matches existing SQL id=$entry_id\n" if $DEBUG	;
		}
		else
		{
			die_error_mail($mailto, "Status update should already have table entry for ID $iplayer_pid") ;
		}
	
		die_error_mail($mailto, "Error: Invalid entry $entry_id (ID $iplayer_pid)") unless ($entry_id > 0) ;
		
		my $text = "" ;
		if ($href->{'downloaded'})
		{
			# Downloaded info has better description
			$text .= "$href->{'downloaded'}{'episode'}: " if $href->{'downloaded'}{'episode'} ;
			$text .= "$href->{'downloaded'}{'desc'}" if $href->{'downloaded'}{'desc'} ;
			$text = sql_escape_str($text) ;
		}
		
		my $status = $href->{'status'} ;
		my $error = "" ;
		if ($href->{'error'})
		{
			$error = sql_escape_str($href->{'error'}) ;
		}
		else
		{
			$status .= ",complete" ;
		}
		
		my $file = sql_escape_str($href->{'file'}) ;
		
		die_error_mail($mailto, "Error: Invalid entry $entry_id (ID $iplayer_pid)") unless ($entry_id > 0) ;
		
		# UPDATE tbl SET flags=TRIM(',' FROM CONCAT(flags, ',', 'flagtoadd'))
		my $sql = "UPDATE $db_href->{tbl} SET `status`=TRIM(',' FROM CONCAT(`status`, ',', '$status')), `ipid`='$iplayer_pid', `changed`=CURRENT_TIMESTAMP" ;
		$sql .= ", `text`='$text'" if $text ;
		$sql .= ", `errorText`='$error'" if $error ;
		$sql .= ", `file`='$file'" if $file ;
		
		$sql .= " WHERE `id`='$entry_id'" ;
		sql_send($db_href, $sql) ;
	}
}

##-----------------------------------------------------------------------------
#sub sql_set_stats
#{
#	my ($db_href, $pid, $stats_href, $get_aref) = @_ ;
#
#	return unless $pid ;
#	
#	my $values = "" ;
#	foreach my $var (sort keys %$stats_href)
#	{
#		$values .= ", " if $values ;
#		$values .= "`$var`='$stats_href->{$var}'" ;
#	}
#	
#	my $sql = "UPDATE $db_href->{tbl} SET $values, `changed`=CURRENT_TIMESTAMP" ;
#	$sql .= " WHERE `pid`='$pid' AND `rectype`='iplay'" ;
#	
#	sql_send($db_href, $sql) ;
#}

##-----------------------------------------------------------------------------
#sub sql_set_error
#{
#	my ($db_href, $pid, $error, $get_aref) = @_ ;
#	
#	return unless $pid ;
#	
#	sql_update_status($db_href, $pid, 'error') ;
#	
#	my $sql = "UPDATE $db_href->{tbl} SET `errorText`='$error', `changed`=CURRENT_TIMESTAMP" ;
#	$sql .= " WHERE `pid`='$pid' AND `rectype`='iplay'" ;
#	
#	sql_send($db_href, $sql) ;
#}

#=================================================================================
# UTILITIES
#=================================================================================

#-----------------------------------------------------------------------------
# Format a timestamp for the reply
sub timestamp
{
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	return sprintf "%02d:%02d:%02d %02d/%02d/%04d", $hour,$min,$sec, $mday,$mon+1,$year+1900;
}


#---------------------------------------------------------------------------------
sub prompt
{
	my $timestamp = timestamp() ;
	my $prompt = "[$progname ($$) $timestamp]" ;
	
	return $prompt ;
}

#---------------------------------------------------------------------------------
sub info
{
	my ($msg) = @_ ;

	my $prompt = prompt() ;
	$msg =~ s/\n/\n$prompt /g ;
	print STDERR "$prompt $msg\n" ;
	
	my $timestamp = timestamp() ;
	push @info_lines, "$prompt $msg" ;
}


#---------------------------------------------------------------------------------
# send error email
sub error_mail
{
	my ($to, $errors_aref) = @_ ;
	
	$errors_aref ||= [] ;
	my $prompt = prompt() ;
	
	my $data = "echo 'Unable to report details...'" ;
	
	my $tmpfile = "/tmp/dvbt-iplay.$$" ;
	if (open my $fh, ">$tmpfile")
	{
		print $fh "ERROR:\n" ;
		foreach (@$errors_aref)
		{
			print $fh "$_\n" ;
		}
		print $fh "\n\n" ;
		foreach (@info_lines)
		{
			print $fh "$_\n" ;
		}
		close $fh ;
		
		$data = "cat $tmpfile" ;	
	}
	else
	{
		$tmpfile = undef ;
	}
	
	`$data | mail -s '$prompt Error' $to` ;
	
	# clean up
	unlink $tmpfile if $tmpfile ;
}

#---------------------------------------------------------------------------------
# send error email then exit
sub die_error_mail
{
	my ($to, $error_msg, $errors_aref, $rec_href) = @_ ;
	
	## Mark as failed
	$error_msg ||= "" ;
	$errors_aref ||= [] ;
	$rec_href ||= {} ;
#	if (exists($rec_href->{'id'}))
#	{
#		my $prog_id = $rec_href->{'id'} ;
#		sql_set_error(\%dbh, $prog_id, $error_msg) ;
#	}
	
	if ($error_msg)
	{
		unshift @$errors_aref, $error_msg ;
		info($error_msg) ;
	}

	error_mail($to, $errors_aref) ;

	info("FATAL Stopping") ;
	exit 1 ;
}


#=================================================================================
# END
#=================================================================================
__END__

[SUMMARY]

Use get_iplayer to download files

[ARGS]

* name=s*		Name(s)

Specify one of more program names to get (alternatively specify the information using -file option)


[OPTIONS]

-type=s				Program type [default=tv]

Can be either tv or radio

-d|'dest'|dir=s		Destination directory

Where to record the file to

-mailto=s				Mail destination [default=$DEF_MAIL_TO]

If any errors occur, then this is where they will be mailed to

-db|'database'=s		Database [default=$DEF_DATABASE]

Specify database name

-tbl|'table'=s			Table [default=$DEF_TBL_RECORDED]

Specify database table name

-u|'user'=s		User

Specify Mysql user name

-p|'password'=s		Password

Specify Mysql user password

-dbg-sql=i	Debug sql module

-a|'adapter'=i		DVB-T adapter number [default=0]

-force=i			Force recording

By default, any file already downloaded will not be downloaded again. Setting this option forces a re-download

-log_dir=s		Log directory [default=$DEF_PVR_LOGDIR]

Log files location

-file=s				Recording specification file

Instead of specifying the recording information on the command line, use a file to store the list of files

-id=s				Program id


[DESCRIPTION]

Get one or more IPLAYER videos/audio files