#!/usr/bin/perl eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}' if 0; # not running under some shell # @(#)$Header: /home/mythtv/mythtvrep/scripts/tv_grab_oztivo,v 1.36 2010/06/22 09:35:41 mythtv Exp $ =pod =head1 NAME tv_grab_oztivo - Grab TV listings for Australia from oztivo =head1 SYNOPSIS tv_grab_oztivo --help tv_grab_oztivo --configure [--config-file FILE] tv_grab_oztivo [--config-file FILE] [--output FILE] [--days N] [--offset N] [--quiet] [--download yes|no] [--build yes|no] tv_grab_oztivo --list-channels [--config-file FILE] tv_grab_oztivo --add-channel channel xmltvid display-name move-time [--config-file FILE] tv_grab_oztivo --delete-channel [--config-file FILE] tv_grab_oztivo --check [--config-file FILE] =head1 DESCRIPTION Output TV and radio listings in XMLTV format for stations available from oztivo (Australia). =head1 OPTIONS tv_grab_oztivo [--gui OPTION] [--quiet] [--verbose] [--configure] [--download yes|no] [--mythfilldatabase sourceid] [--build yes|no] [--remove yes|no] [--config-file FILE] [--offset days] [--days DaysToCollect] [--old OldAge] [--password password] [--delete-channel channel] [--add-channel channel xmltvid display-name move-time] [--list-channels] [--check] [--output file] [--version] [--description] [--capabilities] [--preferredmethod] [--checkforupdate] [Channel ...] --quiet No progress information. --verbose Lots of progress information. --configure Create new configuration file. --download Download files yes or no . Default Yes. --build Build xmltv file yes or no. Default Yes. --mythfilldatabase sourceid Run mythfilldatabase on sourceid. Default No --remove Remove old files yes or no. Default Yes. --config-file configuration file. Default $HOME/.xmltv/tv_grab_oztivo.conf. --offset days. Default start from today. --days Number of days to collect. Default from ConfigFile. --old Old Age in days of old station files to remove. Default from ConfigFile. --password oztivo password. Encodes oztivo passwd and adds it to the ConfigFile. --delete-channel channel. Delete channel from ConfigFile. --add-channel channel xmltvid display-name move-time. Add channel to ConfigFile. --list-channels List channels. --check Compare configured channels to MythTV db channels and oztivo channels. --output Output file. --checkforupdate Check for updated version of tv_grab_oztivo. (no download/build of xmltv file) Channels to build. Default from ConfigFile. =head1 USAGE First you must run B --configure to select the stations to receive, Then running B with no arguments will get about 7 days of listings for the channels you chose. =head1 REQUIRES libwww-perl http://search.cpan.org/dist/libwww-perl/ (Fedora rpm perl-libwww-perl) XMLTV http://membled.com/work/apps/xmltv/ (Fedora rpm perl-XMLTV) XML-Simple http://search.cpan.org/dist/XML-Simple/ (Fedora rpm perl-XML-Simple) Config-General http://search.cpan.org/dist/Config-General/ (Fedora rpm perl-Config-General) DateTime http://search.cpan.org/dist/DateTime/ (Fedora rpm perl-DateTime) DateTime-Format-Strptime http://search.cpan.org/dist/DateTime-Format-Strptime/ (Fedora rpm perl-DateTime-Format-Strptime) MythTV http://atrpms.net/ (atrpms perl-MythTV) Email-MIME-Encodings http://search.cpan.org/dist/Email-MIME-Encodings/ (Fedora rpm perl-Email-MIME-Encodings) =head1 AUTHOR Auric =head1 BUGS =head1 HISTORY B<2008-02-07 Auric> - Initial =cut use strict; use strict 'subs'; use XMLTV::Version '$Id: tv_grab_oztivo,v 1.36 2010/06/22 09:35:41 mythtv Exp $'; our $version = 'tv_grab_oztivo/$Revision: 1.36 $'; use XMLTV::Description 'Australia oztivo'; use XMLTV::Capabilities qw/baseline manualconfig share cache preferredmethod/; use XMLTV::PreferredMethod 'allatonce'; use Getopt::Long; use Config::General; use Data::Dumper; use HTTP::Cookies; require HTTP::Request; use LWP::UserAgent; use LWP::ConnCache; use XMLTV; use XMLTV::Ask; use XMLTV::ProgressBar; use DB_File; use XML::Simple; # Can't use Date::Manip for timezones in Australia as it gets it wrong. use DateTime; use DateTime::TimeZone; use Time::Local; use File::stat; use List::Util 'shuffle'; use File::Copy; use File::Path; use File::Basename; use File::Temp qw/mktemp/; use Scalar::Util qw(openhandle); use Term::ANSIColor qw(:constants); our $Strptime = 0; eval { require DateTime::Format::Strptime }; $Strptime = 1 unless $@; our $EncodingsOK = 0; eval { require Email::MIME::Encodings }; $EncodingsOK = 1 unless $@; our $MythTVOK = 0; eval { require MythTV }; $MythTVOK = 1 unless $@; use XMLTV::Usage <{$hdc} = 'hd' } $conf->save_file($configfile, \%config); print STDERR "Added stationquality setting to $configfile\n" unless $opt_quiet; } unless ($config{'global'}->{'updatecheckurl'}) { $config{'global'}->{'updatecheckurl'} = 'http://web.aanet.com.au/auric/tv_grab_oztivo-version.txt'; $conf->save_file($configfile, \%config); print STDERR "Added updatecheckurl setting to $configfile\n" unless $opt_quiet; } unless (defined($config{'global'}->{'updatecheckdays'})) { $config{'global'}->{'updatecheckdays'} = 0; $conf->save_file($configfile, \%config); print STDERR "Added updatecheckdays setting to $configfile\n" unless $opt_quiet; } unless ($config{'global'}->{'appendlive2title'}) { $config{'global'}->{'appendlive2title'} = "no"; $conf->save_file($configfile, \%config); print STDERR "Added appendlive2title setting to $configfile\n" unless $opt_quiet; } # remove minnie if (grep(/minnie.tuhs.org/, @{$config{'global'}->{'channelbaseurl'}})) { my $wwwoztivonet = 0; my @tmp = @{$config{'global'}->{'channelbaseurl'}}; delete $config{'global'}->{'channelbaseurl'}; foreach my $u (@tmp) { $u eq "http://minnie.tuhs.org" and next; $u eq "http://www.oztivo.net" and $wwwoztivonet = 1; push(@{$config{'global'}->{'channelbaseurl'}}, $u); } push(@{$config{'global'}->{'channelbaseurl'}}, "http://www.oztivo.net") unless $wwwoztivonet; $conf->save_file($configfile, \%config); print STDERR "Removed channelbaseurl http://minnie.tuhs.org setting from $configfile\n" unless $opt_quiet; } } sub cleandie { my $mesg = shift @_; (%urlmodifystore) and untie %urlmodifystore; my @tf = glob("${sharedir}/.tvgo${$}*tmp*"); unlink(@tf); die "$mesg"; } sub is_terminal { (-t *STDOUT) and return 1 or return 0; } sub get_xmltvurl { my ($URL) = shift @_; my ($dirfile) = shift @_; my ($dir, $file, $ext) = fileparse($dirfile, qr/\.[^.]*/); unless ($ua) { $cookies = HTTP::Cookies->new(ignore_discard => 1, file => "$ENV{HOME}".'/.cookie_jar', autosave => 1); $cache = LWP::ConnCache->new; $ua = LWP::UserAgent->new; $ua->cookie_jar($cookies); $ua->conn_cache($cache); $ua->agent($version); $ua->default_header( 'Accept' => 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5', 'Accept-Language' => 'en-us,en;q=0.5', 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'Accept-Encoding' => 'gzip', 'Keep-Alive' => '300', 'Connect' => 'keep-alive' ); ($config{'global'}->{'proxy'}) and $ua->proxy("http", $config{'global'}->{'proxy'}); } (%urlmodifystore) or tie %urlmodifystore, "DB_File", "${sharedir}/urlmodifystore" or cleandie("Cannot open ${sharedir}/urlmodifystore $!\n"); print STDERR "Requesting $URL [$dirfile] " if $opt_verbose; my $response; my $cstat; stat($dirfile) and $cstat = stat($dirfile)->size or $cstat = 0; if($cstat > 0) { $response = $ua->get($URL, 'If-Modified-Since' => $urlmodifystore{"${URL}-Last-Modified"}, 'If-None-Match' => $urlmodifystore{"${URL}-ETag"}); } else { $response = $ua->get($URL); } if ($response->code == 304) { $stats{'localvalid'}++; print STDERR "Local copy still valid\n" if $opt_verbose; return 1; } elsif ($response->is_success) { my $tmpfile = mktemp("${sharedir}/.tvgo${$}dlxmltmpXXXX"); open(C, ">$tmpfile") or return 0; my $trans = $response->decoded_content; ($ext eq '.xml') and $trans =~ s/\'/\'/g; print C $trans; close(C); if (isxmltv($tmpfile)) { unlink($dirfile); unless (move($tmpfile, $dirfile)) { unlink($tmpfile); return 0; } unlink($tmpfile); $stats{'downloaded'}++; print STDERR "Downloaded " if $opt_verbose; $urlmodifystore{"${URL}-Last-Modified"} = $response->header('Last-Modified'); $urlmodifystore{"${URL}-ETag"} = $response->header('ETag'); print STDERR "\n" if $opt_verbose; return 1; } else { $stats{'errored'}++; print STDERR "errored\n" if $opt_verbose; unlink($tmpfile); return 0; } } else { $stats{'errored'}++; print STDERR $response->status_line, "\n" unless $opt_quiet; return 0; } } # Have to validate this way as oztivo realm breaks ua->credentials due to commas in realm sub LWP::UserAgent::get_basic_credentials { my $uahash = shift @_; my $realm = shift @_; my $uri = shift @_; return ($config{'global'}->{'oztivo_uname'}, decode($config{'global'}->{'oztivo_password'})); } sub isxmltv { my $file = shift @_; open(FH, "<$file") or return 0; for(my $c = 5; $c > 0; $c--) { =~ /{'channelsurl'}); $file =~ s/(.*)\.gz/$1/; my $dirfile = "${sharedir}/${file}"; my @urls; if (ref($config{'global'}->{'channelbaseurl'}) eq "ARRAY") { @urls = shuffleminnielast(@{$config{'global'}->{'channelbaseurl'}}); } else { push(@urls, $config{'global'}->{'channelbaseurl'}); } foreach my $url (@urls) { get_xmltvurl("${url}/$config{'global'}->{'channelsurl'}", $dirfile) and return $dirfile; } unlink($dirfile); cleandie("Could not download channels file\n"); } sub getdatalistfile { my $file = basename($config{'global'}->{'datalisturl'}); $file =~ s/(.*)\.gz/$1/; my $dirfile = "${sharedir}/${file}"; my @urls; if (ref($config{'global'}->{'channelbaseurl'}) eq "ARRAY") { @urls = shuffleminnielast(@{$config{'global'}->{'channelbaseurl'}}); } else { push(@urls, $config{'global'}->{'channelbaseurl'}); } foreach my $url (@urls) { get_xmltvurl("${url}/$config{'global'}->{'datalisturl'}", $dirfile) and return $dirfile; } unlink($dirfile); cleandie("Could not download datalist file\n"); } sub listchannels { my $chanfile = getchannelfile; my @channels; my $channelsxml = XMLin($chanfile); foreach my $key (keys(%{$channelsxml->{'channel'}})) { push(@channels, "$key,$channelsxml->{'channel'}->{$key}->{'display-name'}->{'content'}"); } return sort(@channels); } sub createemptyconfigfile { my $configfile = shift @_; open(CF, ">$configfile") or cleandie("Could no open config file $configfile $!\n"); print CF $emptyconfigfile; close(CF); } sub createconfig { my $conf = shift @_; my $configfile = shift @_; my $answer = ask("oztivo username? "); chomp($answer); $config{'global'}->{'oztivo_uname'} = $answer; $answer = ask_password("oztivo password? "); chomp($answer); $config{'global'}->{'oztivo_password'} = encode($answer); $answer = ask("Enter FULL path of directory to store downloaded oztivo files in? [$config{'global'}->{'sharedir'}] "); chomp($answer); ($answer) and $config{'global'}->{'sharedir'} = $answer; $sharedir = $config{'global'}->{'sharedir'}; eval { mkpath($sharedir) }; ($@) and cleandie("Could not create $sharedir $@"); my ($mythobj, $sth); ($MythTVOK) and $mythobj = MythTV->new(); delete $config{'stations'}->{'dontinclude'}; delete $config{'stations'}->{'include'}; my $secondrun = 0; while (1) { print "\nEnter a xmltvid to include the station. Nothing to not include the station.\n"; if ($mythobj) { print BOLD, RED, "READ: ", RESET; print "{xyz.au} are POSSIBLE xmltvid's found in the MythTV db, NOT DEFAULTS.\n"; $sth = $mythobj->{'dbh'}->prepare("select xmltvid from channel where lcase(callsign) like lcase(?) or lcase(name) like lcase(?) or lcase(xmltvid) like lcase(?);"); } if ($secondrun) { print BOLD, RED, "READ: ", RESET; print "[xyz.au] are the xmltvid's entered in previous loop and WILL BE INCLUDED.\n"; print " They can be replaced with a new entry or enter the word none to remove\n"; delete $config{'stations'}->{'dontinclude'}; delete $config{'stations'}->{'include'}; } foreach my $channel (listchannels) { my ($tvguide, $disname) = split(',',$channel); my $possible = ""; my $default = ""; my $prompt = ""; if ($secondrun) { $default = $config{'xmltvidmap'}->{$tvguide}; ($default) and $prompt = "[$default] "; } if (!$prompt && $mythobj) { $sth->bind_param(1, "%$tvguide%"); $sth->bind_param(2, "%$tvguide%"); $sth->bind_param(3, "%$tvguide%"); $sth->execute; while (my $row = $sth->fetchrow_array) { ($possible) and $possible = "$possible $row" or $possible = "$row"; } ($possible) and $prompt = "{$possible} "; } my $answer = ask("xmltvid for $channel? $prompt"); chomp($answer); if ($answer && $answer ne "none") { push(@{$config{'stations'}->{'include'}}, $tvguide); $config{'xmltvidmap'}->{$tvguide} = $answer; } elsif (!$answer && $default) { push(@{$config{'stations'}->{'include'}}, $tvguide); $config{'xmltvidmap'}->{$tvguide} = $default; } else { push(@{$config{'stations'}->{'dontinclude'}}, $tvguide); delete $config{'xmltvidmap'}->{$tvguide}; } $config{'displaynamemap'}->{$tvguide} = $disname; } $secondrun = 1; $^ = 'UPDATECONF_TOP'; $~ = 'UPDATECONF'; $= = 1000; if ($mythobj) { $sth = $mythobj->{'dbh'}->prepare("select xmltvid from channel where xmltvid = ?;"); } print "Added the following channels\n"; foreach $tvguide (sort(keys(%{$config{'xmltvidmap'}}))) { if ($mythobj) { $sth->bind_param(1, $config{'xmltvidmap'}->{$tvguide}); $sth->execute; ($sth->rows < 1) and $indb = "Missing" or $indb = "Yes"; } else { $indb = "NA"; } write; } my $answer; until ($answer eq "OK" || $answer eq "again") { $answer = ask("OK or start again? [OK|again] "); chomp($answer); } $answer eq "OK" and last; } $conf->save_file($configfile); if ($mythobj) { $sth->finish; $mythobj->{'dbh'}->disconnect; } print "The following oztivo stations in the config file are marked as HD.\n"; foreach my $hdc (@defaultHDchannels) { print "$hdc\n"; } print "If a channel is marked as hd then the xmltv quality elemement will be set to HDTV.\n"; print "This is done on the assumption oztivo only includes HD programms on the HD channels.\n"; print "Only mythfilldatabase 0.21 does anything with this.\n"; } format UPDATECONF_TOP= oztivo Name XMLTVID Display Name In Myth db ---------------------------------------------------------------------------------- . format UPDATECONF= @<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<< $tvguide,$config{'xmltvidmap'}->{$tvguide},$config{'displaynamemap'}->{$tvguide},$indb . sub updateconfigfromtvgrabau { my $conf = shift @_; my $configfile = shift @_; my $tv_grab_au = shift @_; my $grabxml = XMLin($tv_grab_au); print "Setting oztivo user to $grabxml->{'login'}->{'user'}\n"; $config{'global'}->{'oztivo_uname'} = $grabxml->{'login'}->{'user'}; print "Setting oztivo password to $grabxml->{'login'}->{'password'}\n"; $config{'global'}->{'oztivo_password'} = encode($grabxml->{'login'}->{'password'}); my $answer = ask("Enter FULL path of directory to store downloaded oztivo files in? [$config{'global'}->{'sharedir'}] "); chomp($answer); ($answer) and $config{'global'}->{'sharedir'} = $answer; $sharedir = $config{'global'}->{'sharedir'}; delete $config{'stations'}->{'dontinclude'}; delete $config{'stations'}->{'include'}; $^ = 'UPDATECONF_TOP'; $~ = 'UPDATECONF'; $= = 1000; my ($mythobj, $sth); ($MythTVOK) and $mythobj = MythTV->new(); if ($mythobj) { $sth = $mythobj->{'dbh'}->prepare("select xmltvid from channel where xmltvid = ?;"); } print "Added the following channels\n"; foreach my $channel (@{$grabxml->{'channel'}}) { $tvguide = "$channel->{'tvguide'}"; $config{'xmltvidmap'}->{$tvguide} = $channel->{'local'}; $config{'displaynamemap'}->{$tvguide} = $channel->{'display'}; push(@{$config{'stations'}->{'include'}}, $tvguide); if ($mythobj) { $sth->bind_param(1, $config{'xmltvidmap'}->{$tvguide}); $sth->execute; ($sth->rows < 1) and $indb = "Missing" or $indb = "Yes"; } else { $indb = "NA"; } write; } $conf->save_file($configfile); if ($mythobj) { $sth->finish; $mythobj->{'dbh'}->disconnect; } } sub deletechannel { my $conf = shift @_; my $configfile = shift @_; my $delchan = shift @_; my @tmp; delete $config{'stations'}->{'dontinclude'}; my $notfound = 1; foreach my $channel (@{$config{'stations'}->{'include'}}) { if ($delchan eq $channel) { delete $config{'xmltvidmap'}->{$delchan}; $notfound = 0; print "Deleted $delchan $config{'xmltvidmap'}->{$delchan}\n"; } else { push(@tmp, $channel); } } if ($notfound) { print "Already deleted $delchan\n"; } else { @{$config{'stations'}->{'include'}} = @tmp; } $conf->save_file($configfile); } sub addchannel { my $conf = shift @_; my $configfile = shift @_; my $addchan = shift @_; my $addxmltvid = shift @_; my $adddisplay = shift @_; my $addmovetime = shift @_; my $notfound = 1; delete $config{'stations'}->{'dontinclude'}; foreach my $channel (@{$config{'stations'}->{'include'}}) { if ($addchan eq $channel) { print "$addchan Already there\n"; $notfound = 0; } } if ($notfound) { $config{'xmltvidmap'}->{$addchan} = $addxmltvid; ($adddisplay) and $config{'displaynamemap'}->{$addchan} = $adddisplay; ($addmovetime) and $config{'movetimemap'}->{$addchan} = $addmovetime; push(@{$config{'stations'}->{'include'}}, $addchan); print "Added $addchan $addxmltvid\n"; } $conf->save_file($configfile); } sub checkchannels { $^ = 'CHECK_TOP'; $~ = 'CHECK'; $= = 1000; my ($mythobj, $sth, %store, @warn); ($MythTVOK) and $mythobj = MythTV->new(); if ($mythobj) { $sth = $mythobj->{'dbh'}->prepare("select callsign,name,xmltvid from channel where xmltvid = ? and visible = 1;"); } foreach $tvguide (sort(@{$config{'stations'}->{'include'}})) { $xmltvid = $config{'xmltvidmap'}->{$tvguide}; if ($mythobj) { $sth->bind_param(1, $config{'xmltvidmap'}->{$tvguide}); $sth->execute; if ($sth->rows < 1) { @row = ("", "", ""); push(@warn, $tvguide); write; } else { @row = $sth->fetchrow_array; $store{$row[2]} = 1; write; while (@row = $sth->fetchrow_array) { write }; } } else { write; } } if ($mythobj) { $sth = $mythobj->{'dbh'}->prepare("select callsign,name,xmltvid from channel where xmltvid != '' and visible = 1;"); $sth->execute; while (@row = $sth->fetchrow_array) { $xmltvid = $row[2]; ($store{$row[2]}) or write; } $sth->finish; $mythobj->{'dbh'}->disconnect; foreach my $w (@warn) { print RED, "- WARNING $w NOT IN MythTV db or xmltvid does not align\n", RESET; } } my @channels = listchannels; foreach $tvguide (sort(@{$config{'stations'}->{'include'}})) { my $found = grep { my ($channel) = split(',',$_); $tvguide eq $channel } @channels; ($found) or print RED, "- WARNING $tvguide NOT IN oztivo\n", RESET; } } format CHECK_TOP= oztivo Name db Name XMLTVID oztivo Display Name db Display Name -------------------------------------------------------------------------------------------------------------- . format CHECK= @<<<<<<<<<< @<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<< $tvguide,$row[0],$xmltvid,$config{'displaynamemap'}->{$tvguide},$row[1] . our $pw2 = "Rubbish"; sub encode { my $pwin = shift @_; my $tmp = $pwin ^ $pw2; return "X:".Email::MIME::Encodings::encode(base64 => $tmp); } sub decode { my $pwin = shift @_; $pwin =~ s/^X:(.*)/$1/ or return $pwin; my $tmp = Email::MIME::Encodings::decode(base64 => $pwin); return $tmp ^ $pw2; } sub encoding_cb($) { my $encoding = shift; } sub credits_cb($) { my $credits = shift; } sub channel_cb($) { my $channel = shift; foreach my $station (@stations) { if ($channel->{'id'} eq $station) { ($xmltvidmap{$station}) and $channel->{'id'} = $xmltvidmap{$station}; ($displaynamemap{$station}) and $channel->{'display-name'}->[0]->[0] = $displaynamemap{$station}; $xmltv->write_channel($channel); } } } sub programme_cb($) { my $program = shift; if ($movetimemap{$program->{'channel'}}) { # DATATZ to support OLD format file. if ($movetimemap{$program->{'channel'}} eq 'DATATZ' || $movetimemap{$program->{'channel'}} eq 'LOCALTZ') { print STDERR "$program->{'channel'} - Changing data timezone to $localtz\n" if $opt_verbose; my($time, $datatz) = split(' ',$program->{'start'}); $program->{'start'} = "$time $localtz"; ($time, $datatz) = split(' ',$program->{'stop'}); $program->{'stop'} = "$time $localtz"; } else { my $movetimedt = DateTime::Duration->new(minutes => $movetimemap{$program->{'channel'}}); print STDERR "$program->{'channel'} - Moving time by ", $movetimedt->delta_minutes, " mins\n" if $opt_verbose; my($time, $tz) = split(' ',$program->{'start'}); my $strp = new DateTime::Format::Strptime(pattern => "%Y%m%d%H%M%S"); my $dttime = $strp->parse_datetime($time); my $tmpdt = $dttime->add_duration($movetimedt); $program->{'start'} = $tmpdt->strftime("%Y%m%d%H%M%S") . " $tz"; ($time, $tz) = split(' ',$program->{'stop'}); $strp = new DateTime::Format::Strptime(pattern => "%Y%m%d%H%M%S"); $dttime = $strp->parse_datetime($time); $tmpdt = $dttime->add_duration($movetimedt); $program->{'stop'} = $tmpdt->strftime("%Y%m%d%H%M%S") . " $tz"; } } # mythfilldatabase treats movie, series, sports specially. # If there is a episode-num element then mythfilldatabase auto adds series category. for (my $c = 0; $c <= $#{$program->{'category'}}; $c++) { ($program->{'category'}->[$c]->[0] =~ /Movie/) and $program->{'category'}->[$c]->[0] = "movie" and last; ($program->{'category'}->[$c]->[0] =~ /Sports Group/) and $program->{'category'}->[$c]->[0] = "sports" and last; } # oztivo only include HD programs on HD channels, so can mark all as HD. # Will assume it is also colour, 16:9 and dolby digital. if ($config{'stationquality'}->{$program->{'channel'}} eq 'hd') { $program->{'video'}->{'present'} = 1; $program->{'video'}->{'quality'} = "HDTV"; $program->{'video'}->{'aspect'} = "16:9"; $program->{'video'}->{'colour'} = 1; $program->{'audio'}->{'present'} = 1; $program->{'audio'}->{'stereo'} = "dolby digital"; } # Notify of update by changing "Station Close" title. if ($updateavailable && $program->{'title'}->[0]->[0] eq "Close" && defined($program->{'category'}->[0]->[0]) && $program->{'category'}->[0]->[0] eq "Station Close") { $program->{'title'}->[0]->[0] = "Close - New version of tv_grab_oztivo available"; $program->{'desc'}->[0]->[0] = "Station Close - If you don't want to run a check for updates. Change updatecheckdays to 0 in the global section of the config-file and rm ${sharedir}/updatecheck.stamp"; } # Add Live to title if category Live if ($config{'global'}->{'appendlive2title'} eq 'yes') { for (my $c = 0; $c <= $#{$program->{'category'}}; $c++) { ($program->{'category'}->[$c]->[0] =~ /Live/) and $program->{'title'}->[0]->[0] .= " (Live)" and last; } } if ($xmltvidmap{$program->{'channel'}}) { $program->{'channel'} = $xmltvidmap{$program->{'channel'}}; } $xmltv->write_programme(\%{$program}); } ####################################### Main ########################################## my $xmltvvererr="You need a more recent perl XMLTV version. Try http://sourceforge.net/projects/xmltv\n"; my ($main, $ver, $rel) = split('\.', $XMLTV::VERSION); while(1) { ($main > 0) and last; ($main < 0) and cleandie("$xmltvvererr"); ($ver > 5) and last; ($ver < 5) and cleandie("$xmltvvererr"); ($rel > 44) and last; cleandie("$xmltvvererr"); } if ($MythTVOK) { $mythversion = 20; # Would be nice to use $MythTV::VERSION but it is not exported. ($MythTV::PROTO_VERSION >= 40) and $mythversion = 21; ($MythTV::PROTO_VERSION >= 72) and $mythversion = 25; } unless (is_terminal) { $ENV{'ANSI_COLORS_DISABLED'} = "YES"; } GetOptions ( "quiet" => \$opt_quiet, "verbose" => \$opt_verbose, "cache" => \$opt_cache, "gui=s" => \$opt_gui, "share=s" => \$opt_share, "download=s" => \$opt_d, "build=s" => \$opt_b, "mythfilldatabase=i" => \$opt_m, "remove=s" => \$opt_r, "config-file=s" => \$opt_cf, "offset=i" => \$opt_offset, "days=i" => \$opt_days, "old=i" => \$opt_o, "help" => \$opt_h, "example-config-file" => \$opt_ecf, "password=s" => \$opt_pw, "delete-channel=s" => \$opt_del, "add-channel=s" => \$opt_add, "output=s" => \$opt_of, "configure" => \$opt_configure, "list-channels" => \$opt_list, "check" => \$opt_check, "checkforupdate" => \$opt_cfu ) or &HELP_MESSAGE; ($opt_h) and &HELP_MESSAGE; if ($opt_ecf) { print "$emptyconfigfile\n"; exit; } XMLTV::Ask::init($opt_gui); my $configfile; if ($opt_cf) { $configfile = $opt_cf; } else { my $home = $ENV{'HOME'}; $home = '.' if not defined $home; my $conf_dir = "$home/.xmltv"; (-d $conf_dir) or mkdir($conf_dir, 0777) or cleandie("cannot mkdir $conf_dir $!\n"); $configfile = "$conf_dir/" . basename($0) . ".conf"; } if ($opt_configure) { my $answer = ask("Do you have a tv_grab_au or tv_grab_au_reg xmltv config file?\nIf so enter FULL path to where it is ? "); chomp($answer); if ($answer) { stat($answer) or cleandie("Could no open config file $answer $!\n"); copy($answer,"${answer}.pre_tv_grab_oztivo"); createemptyconfigfile($configfile); my $conf = new Config::General($configfile) or cleandie("Could not open $configfile $!\n"); %config = $conf->getall; updateconfigfromtvgrabau($conf, $configfile, "${answer}.pre_tv_grab_oztivo"); } else { createemptyconfigfile($configfile); my $conf = new Config::General($configfile) or cleandie("Could not open $configfile $!\n"); %config = $conf->getall; createconfig($conf, $configfile) } exit; } stat($configfile) or cleandie("Could not open config file $configfile $!\n"); my $conf = new Config::General($configfile) or cleandie("Could not open $configfile $!\n"); %config = $conf->getall; %xmltvidmap = %{$config{'xmltvidmap'}}; %displaynamemap = %{$config{'displaynamemap'}}; %movetimemap = %{$config{'movetimemap'}}; updateconfig($conf, $configfile); (keys(%movetimemap) > 0 && !$Strptime) and cleandie("Must have perl module DateTime::Format::Strptime for movetimemap function\n"); ($opt_o) and $oldage = $opt_o or $oldage = $config{'global'}->{'oldage'}; ($opt_share) and $sharedir = $opt_share or $sharedir = $config{'global'}->{'sharedir'}; ($opt_of) and $outputfile = $opt_of; if ($opt_m) { ($MythTVOK) or cleandie("Must have perl MythTV module for --mythfilldatabase option\n"); ($outputfile) or $outputfile = mktemp("${sharedir}/.tvgo${$}mfdbtmpXXXX"); } if ($opt_pw) { ($EncodingsOK) or cleandie "Must have perl module Email::MIME::Encodings for --password option\n"; $config{'global'}->{'oztivo_password'} = encode($opt_pw); $conf->save_file($configfile); print "Updated\n"; exit; } if ($opt_del) { deletechannel($conf, $configfile, $opt_del); exit; } if ($opt_add) { (scalar(@ARGV) < 1) and &HELP_MESSAGE; addchannel($conf, $configfile, $opt_add, $ARGV[0], $ARGV[1], $ARGV[2]); exit; } if ($opt_list) { foreach my $channel (listchannels) { print "$channel\n"; } exit; } if ($opt_check) { checkchannels; exit; } (scalar(@ARGV) > 0) and @stations = @ARGV or @stations = @{$config{'stations'}->{'include'}}; # Calculate days to get my $localtimezone = 'local'; ($config{'global'}->{'localtimezone'}) and $localtimezone = $config{'global'}->{'localtimezone'}; my $dt; eval { $dt = DateTime->now(time_zone => $localtimezone) }; if ($@) { my $atz; foreach my $z (DateTime::TimeZone->names_in_category("Australia")) { $atz .= "Australia/$z\n"; } cleandie("DateTime failed. This is probably because it could not determine the local timezone See man page for DateTime::TimeZone for how it tries to find the local timezone. If it still fails, set localtimezone in the global section of the config file. e.g. localtimezone Australia/Melbourne ... ... localtimezone should match to a dir/file under /usr/share/zoneinfo (This directory my be different on some linux distros). It Appears your system has the following Australian timezones $atz Your current config file is $configfile "); } if ($opt_offset) { $dt->add(days => $opt_offset); } my @tmp; push(@collectdates, $dt->ymd); my $daystocollect; ($opt_days) and $daystocollect = $opt_days or $daystocollect = $config{'global'}->{'daystocollect'}; while (--$daystocollect) { $dt->add(days => 1); push(@collectdates, $dt->ymd); } # local tz +-HHMM my $tmphrs = abs($dt->offset / (60 * 60)); my $tmpmin = abs(($dt->offset / 60) % 60); $localtz = sprintf("%02d%02d",$tmphrs, $tmpmin); ($dt->offset > 0) and $localtz = '+' . $localtz or $localtz = '-' . $localtz; print STDERR "Local timezone offset: " . $localtz . "\n" if $opt_verbose; # Create dirs if ($outputfile) { my $opdir = dirname($outputfile); eval { mkpath($opdir) }; ($@) and cleandie("Could not create $opdir $@"); } eval { mkpath($sharedir) }; ($@) and cleandie("Could not create $sharedir $@"); if ($config{'global'}->{'updatecheckdays'} > 6 && lc($opt_d) ne "no" || $opt_cfu) { my $updatechecksecs = 86400 * $config{'global'}->{'updatecheckdays'}; my $upstat = stat("${sharedir}/updatecheck.stamp"); # Check for updates and put latest version in ${sharedir}/updatecheck.stamp if ($opt_cfu || !defined($upstat) || time > ($upstat->mtime + $updatechecksecs)) { my $ua = LWP::UserAgent->new; my $response = $ua->get($config{'global'}->{'updatecheckurl'}); my $tmp = $response->decoded_content; chomp($tmp); my ($lver, $lrel) = split('\.', $tmp); if ($lver =~ /\d\d*/ && $lrel =~ /\d\d*/) { # --checkforupdate will also reset updatecheck.stamp. unlink("${sharedir}/updatecheck.stamp"); open(UCS, ">${sharedir}/updatecheck.stamp"); print UCS "${lver}.${lrel}"; close(UCS); } } # Compare current version to one in ${sharedir}/updatecheck.stamp open(UCS, "<${sharedir}/updatecheck.stamp"); my $tmp = ; chomp($tmp); close(UCS); my ($lver, $lrel) = split('\.', $tmp); (my $rub, $tmp) = split(' ', $version); my ($tver, $trel) = split('\.', $tmp); if ($lver =~ /\d\d*/ && $tver =~ /\d\d*/ && $lrel =~ /\d\d*/ && $trel =~ /\d\d*/) { if ($lver > $tver || ($lver == $tver && $lrel > $trel)) { $updateavailable = 1; print STDERR BOLD, "There is a updated version of tv_grab_oztivo available at ".dirname($config{'global'}->{'updatecheckurl'})."\n", RESET; print STDERR BOLD, "This version: ${tver}.${trel}\nLatest version: ${lver}.${lrel}\n", RESET; print STDERR "If you don't want to run a check for updates\n" unless $opt_cfu; print STDERR "Change updatecheckdays to 0 in the global section of $configfile\n" unless $opt_cfu; print STDERR "and rm ${sharedir}/updatecheck.stamp\n" unless $opt_cfu; print STDERR "This message also put into any \"Station Close\" program titles\n" unless $opt_cfu; ($opt_cfu) and exit 1; } else { print STDERR "No update of tv_grab_oztivo available\n" if $opt_cfu; } } ($opt_cfu) and exit; } if ((!defined($opt_quiet)) && (lc($opt_d) ne "no" || lc($opt_b) ne "no")) { print STDERR "Channels "; foreach my $station (@stations) { print STDERR "$station "; } print STDERR "\n"; print STDERR "Days "; foreach my $date (@collectdates) { print STDERR "$date "; } print STDERR "\n"; } ################################## Download files ##################################### if (lc($opt_d) ne "no" && ($opt_cache != 1)) { my $chanfile = getchannelfile; # Build station url's hash my %stationsbaseurl; my $channelsxml = XMLin($chanfile); foreach my $station (@stations) { my @urls; if (ref($channelsxml->{'channel'}->{$station}->{'base-url'}) eq "ARRAY") { @urls = shuffleminnielast(@{$channelsxml->{'channel'}->{$station}->{'base-url'}}); } else { push(@urls, $channelsxml->{'channel'}->{$station}->{'base-url'}); } $stationsbaseurl{$station} = [@urls]; } # Build datalist my $datalistfile = getdatalistfile; my $datalistxml = XMLin($datalistfile); # Get Stations my $bar = new XMLTV::ProgressBar({name => "Downloading", count => (scalar(keys(%stationsbaseurl)) * scalar(@collectdates))}) unless ($opt_quiet||$opt_verbose); foreach my $date (@collectdates) { foreach my $key (keys(%stationsbaseurl)) { update $bar if $bar; my $datafor = 0; # Are there any datafor if (defined($datalistxml->{'channel'}->{$key}->{'datafor'})) { if (ref($datalistxml->{'channel'}->{$key}->{'datafor'}) eq "ARRAY") { # Multiple datafor foreach my $df (@{$datalistxml->{'channel'}->{$key}->{'datafor'}}) { if ($date eq $df->{'content'}) { $datafor = 1; last; } } } else { # Single datafor ($date eq $datalistxml->{'channel'}->{$key}->{'datafor'}->{'content'}) and $datafor = 1; } } if ($datafor) { my $file = $key . "_" . $date . ".xml"; my $dirfile = "${sharedir}/${file}"; foreach my $url (@{$stationsbaseurl{$key}}) { get_xmltvurl("${url}${file}.gz", $dirfile) and last; } } else { print STDERR "No datafile for $key $date\n" if $opt_verbose; next; } sleep(2); # Wait bewteen gets } } $bar->finish() if $bar; untie %urlmodifystore; } ################################ Build xmltv file ##################################### if (lc($opt_b) ne "no") { my %w_args; $w_args{'encoding'} = 'ISO-8859-1'; my $output; if ($outputfile) { $output = new IO::File(">$outputfile"); $w_args{'OUTPUT'} = $output; } $xmltv = new XMLTV::Writer(%w_args); $xmltv->start({'generator-info-name' => $version, 'source-info-url' => "oztivo", 'source-data-url' => "oztivo"}); ##### Channels ####### my $file = basename($config{'global'}->{'channelsurl'}); $file =~ s/(.*)\.gz/$1/; my $dirfile = "${sharedir}/${file}"; my $tmp = mktemp("${sharedir}/.tvgo${$}chantmpXXXX"); if (stat($dirfile)) { open(I, "<$dirfile") or cleandie("Cannot open $dirfile $!\n"); open(O, ">$tmp") or cleandie("Cannot open $tmp $!\n"); while () { /base-url/ or print O; } close(O); close(I); print STDERR "$dirfile Processing\n" if $opt_verbose; XMLTV::parsefiles_callback(\&encoding_cb, \&credits_cb, \&channel_cb, \&programme_cb, $tmp); unlink($tmp); } else { cleandie("$dirfile Not found\n"); } ##### Programs ####### my $bar = new XMLTV::ProgressBar({name => "Building", count => (scalar(@stations) * scalar(@collectdates))}) unless ($opt_quiet||$opt_verbose); foreach my $date (@collectdates) { foreach my $station (@stations) { $stats{'total'}++; update $bar if $bar; my $file = $station . "_" . $date . ".xml"; my $dirfile = "${sharedir}/${file}"; if (stat($dirfile)) { if (isxmltv($dirfile)) { $stats{'good'}++; print STDERR "$dirfile Processing\n" if $opt_verbose; XMLTV::parsefiles_callback(\&encoding_cb, \&credits_cb, \&channel_cb, \&programme_cb, $dirfile); } else { $stats{'bad'}++; print STDERR "$dirfile not a xmltv file\n" if $opt_verbose; } } else { $stats{'notfound'}++; print STDERR "$dirfile Not found\n" if $opt_verbose; } } } $bar->finish() if $bar; $xmltv->end(); } if (lc($opt_r) ne "no") { print STDERR "Removing files older than $oldage days " if $opt_verbose; my $dt = DateTime->now(time_zone => $localtimezone); $dt->subtract(days => $oldage); for (1 .. 100) { $dt->subtract(days => 1); my $tmp = "${sharedir}/*" . $dt->ymd . ".xml"; my @rf = glob("$tmp"); if (@rf) { foreach my $r (@rf) { $stats{'removed'}++; print STDERR basename($r)," " if $opt_verbose; } unlink(@rf); } } print STDERR "Done\n" if $opt_verbose; } my $updated; if ($opt_m) { my $mythfilldatabase; my $mythobj = MythTV->new(); $mythfilldatabase = $mythobj->backend_setting('MythFillDatabasePath'); my $mfdc; if ($mythversion >= 25) { $mfdc = "$mythfilldatabase --verbose general --update --file --sourceid $opt_m --xmlfile $outputfile"; } elsif ($mythversion >= 21) { $mfdc = "$mythfilldatabase --verbose general --update --file $opt_m $outputfile"; } else { $mfdc = "$mythfilldatabase --update --file $opt_m -1 $outputfile"; } print STDERR "$mfdc\n" unless $opt_quiet; open(MFDB, "$mfdc 2>&1 |") or cleandie("Could not execute $mythfilldatabase $!\n"); while() { next if $opt_quiet; print if $opt_verbose; if (/Updated programs/) { chomp; $_ =~ /.*(Updated programs.*)/; $updated = $1; } } close(MFDB); $outputfile =~ /\.tvgo.*mfdbtmp/ and unlink($outputfile); } unless ($opt_quiet) { if (lc($opt_d) ne "no" && ($opt_cache != 1)) { print STDERR BOLD, "Download Stage: ", RESET; print STDERR "Downloaded $stats{'downloaded'} Local Still Valid $stats{'localvalid'} Errored $stats{'errored'}\n"; } if (lc($opt_b) ne "no") { print STDERR BOLD, "Build Stage: ", RESET; print STDERR "Good $stats{'good'} Bad Format $stats{'bad'} Missing $stats{'notfound'}\n"; } if (lc($opt_r) ne "no") { print STDERR BOLD, "Remove Stage: ", RESET; print STDERR "Removed $stats{'removed'}\n"; } if ($opt_m) { print STDERR BOLD, "Mythfilldatabase Stage: ", RESET; print STDERR "$updated\n"; } } (%urlmodifystore) and untie %urlmodifystore; my @tf = glob("${sharedir}/.tvgo${$}*tmp*"); unlink(@tf); exit;