diff options
Diffstat (limited to 'obsolete-patch/series.pl')
-rwxr-xr-x | obsolete-patch/series.pl | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/obsolete-patch/series.pl b/obsolete-patch/series.pl new file mode 100755 index 0000000..8423da9 --- /dev/null +++ b/obsolete-patch/series.pl @@ -0,0 +1,543 @@ +#!/usr/bin/perl +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# Or, point your browser to http://www.gnu.org/copyleft/gpl.html +# +# Parts of this code are taken from VDRAdmin-AM +# (C) 2005 - 2008 by Andreas Mair <mail@andreas.vdr-developer.org> + + + +use Socket; +use POSIX; + +my (%CONFIG); +$CONFIG{VDR_HOST} = "localhost"; # Name or IP address of VDR server +$CONFIG{VDR_PORT} = 6419; # SVDRP port on VDR server (use 2001 pre - 1.7.15) +$CONFIG{SERIES_TIMEOUT} = 30; # Expiry time of a series (days) +$CONFIG{START_PADDING} = 1; # Padding when creating new timers without VPS +$CONFIG{STOP_PADDING} = 3; +$CONFIG{PRIORITY} = 99; # Recording priority and lifetime +$CONFIG{LIFETIME} = 99; +$CONFIG{LINKSDIR} = "/video/video"; # Directory holding links file +$CONFIG{VPS} = 1; # 1 = control timer from EIT running status + +if ($CONFIG{VPS}) { + $CONFIG{START_PADDING} = 0; + $CONFIG{VPS} = 1; +} + +my (@timers, @chans, @epg); +my %links = {}; +my $updates = 0; + +open_SVDRP(); +get_channels(); +get_epg(); +get_timers(); +get_links(); +if (scalar (@timers)) { + $updates = check_timers(); +} +$updates += check_links(); +if (scalar (@timers)) { + $updates += check_split_recordings(); + if ($updates) { + put_links(); + @timers = (); + get_timers(); + } +#show_links(); + check_timer_clashes(); + check_changed_events(); +} +close_SVDRP(); + +# Examine each timer and update the links file if necessary (manually-added timers) + +sub check_timers { + + my $count = 0; + foreach my $timer (@timers) { + my $channelid = $chans[$timer->{chan}] -> {id}; + my $start_t = $timer->{tstart}; + my $stop_t = $timer->{tstop}; + foreach my $prog (@epg) { + my $scrid = $prog->{scrid}; + my $icrid = $prog->{icrid}; + next if $scrid eq 'NULL'; + if (($prog->{sid} eq $channelid) && ($start_t <= $prog->{st}) && ($stop_t >= $prog->{et})) { + if (exists $links{$scrid}) { # Existing series + if ($links{$scrid} !~ /$icrid/) { + my ($last_t,$val) = split(';', $links{$scrid}); + $links{$scrid} = "$start_t;$val:$icrid"; # New episode already added + $count++; + } + } + else { # New series added to timer + $links{$scrid} = "$start_t;$icrid"; + $count++; + } + } + } + } + return $count; +} + +# Check for new EPG entries for each series in the links file and create timers. +# FIXME This algorithm fails if an item is part of two series and both are being +# recorded (how can that happen?). + +sub check_links { + my $count = 0; + foreach my $prog (@epg) { + my $scrid = $prog->{scrid}; + my $icrid = $prog->{icrid}; + next if $scrid eq 'NULL'; + if (exists $links{$scrid}) { + if ($links{$scrid} !~ /$icrid/) { +# Have we already recorded this programme on a diferent series? + my $title = get_title($prog->{sid},$prog->{st}); + my $done = 0; + for (values %links) { + if (/$icrid/) { + print STDOUT "Item $icrid ($title) already recorded!\n"; + $done = 1; + } + } + if (!$done) { + my $fstart = strftime("%Y-%m-%d:%H%M", localtime($prog->{st}-$CONFIG{START_PADDING}*60)); + my $fend = strftime("%H%M", localtime($prog->{et}+$CONFIG{STOP_PADDING}*60)); + my $flag = 1 + 4*$CONFIG{VPS}; + print STDOUT "New timer set for $scrid, \"$title\" at $fstart\n"; + set_timer ("$flag:$prog->{sid}:$fstart:$fend:$CONFIG{PRIORITY}:$CONFIG{LIFETIME}:$title:"); + my ($last_t,$val) = split(';', $links{$scrid}); + $links{$scrid} = "$prog->{st};$val:$icrid"; + $count++; + } + } + } + } + return $count; +} + +# Review the timer list for clashes. FIXME: What happens when there are multiple cards? + +sub check_timer_clashes { + my ($ii, $jj); + my (@tstart, @tstop); + print STDOUT "Checking for timer clashes\n"; + for ($ii = 0 ; $ii < @timers ; $ii++) { + push @tstart, $timers[$ii]->{tstart}; + push @tstop, $timers[$ii]->{tstop}; + + for ($jj = 0 ; $jj < $ii ; $jj++) { + if (is_clash($ii, $jj)) { + # What to do?? For now just report the collision + my $start1 = $tstart[$ii]+$CONFIG{START_PADDING}*60; + my $start2 = $tstart[$jj]+$CONFIG{START_PADDING}*60; + my $ttl1 = get_title($chans[$timers[$ii]->{chan}]->{id}, $start1); + my $ttl2 = get_title($chans[$timers[$jj]->{chan}]->{id}, $start2); + my $crid1 = get_crid_for_timer($chans[$timers[$ii]->{chan}]->{id}, $start1); + my $crid2 = get_crid_for_timer($chans[$timers[$jj]->{chan}]->{id}, $start2); + my $when = localtime ($timers[$ii]->{tstart}); + print STDOUT "Collision! $when\n$ttl1 ($crid1) <-> $ttl2 ($crid2)\n"; + list_alternatives($crid1, $ttl1, $start1); + list_alternatives($crid2, $ttl2, $start2); + } + } + } + + sub is_clash { + my ($i, $j) = @_; + if (($tstart[$i] >= $tstart[$j] && $tstart[$i] < $tstop[$j]) + || ($tstart[$j] >= $tstart[$i] && $tstart[$j] < $tstop[$i])) { + # Timers collide in time. Check if the + # Timers are on the same transponder + my $t1 = $chans[$timers[$i]->{chan}] -> {transponder}; + my $t2 = $chans[$timers[$j]->{chan}] -> {transponder}; + if ($t1 eq $t2) { +# print STDOUT "Multiple recordings on same transponder - OK\n"; + return 0; + } + else { + return 1; + } + } + return 0; + } + + sub list_alternatives { + my ($crid, $ttl, $start) = @_; + + if (my @prog = events_for_crid($crid)) { + print STDOUT "Alternatives for $ttl:\n"; + foreach my $evt (@prog) { + if ($evt->{st} != $start) { + my $w = localtime($evt->{st}); + my ($c) = channel_name_from_id($evt->{sid}) =~ /(.*?);/; + print STDOUT "\t$c - $w\n"; + } + } + } + } +} + +# Look for split events (eg a film with a news summary in the middle), and ensure +# that all parts of the event are being recorded. +# A split event has a # in the item CRID, for example /19778232#1 +# FIXME this will likely fail if a split event is repeated or is part of a series. + +sub check_split_recordings { + + my $count = 0; + my @splits; + print STDOUT "Checking for split recordings\n"; + foreach my $timer (@timers) { + my $channelid = $chans[$timer->{chan}] -> {id}; + my $start_t = $timer->{tstart}; + my $stop_t = $timer->{tstop}; + foreach my $prog (@epg) { + if (($prog->{sid} eq $channelid) && ($start_t <= $prog->{st}) && ($stop_t >= $prog->{et})) { + if ($prog->{icrid} =~ /\#/) { + push @splits, "$prog->{icrid}:$prog->{st}:$channelid"; + } + } + } + } + foreach $split (@splits) { + my ($tcrid, $tstart, $tchan) = split(':', $split); + foreach my $prog (@epg) { + if (($prog->{icrid} eq $tcrid) && ($tchan eq $prog->{sid}) && ($prog->{st} != $tstart)) { + my $got = 0; + foreach $split2 (@splits) { + my ($tcrid2, $tstart2, $tchan2) = split(':', $split2); + if (($prog->{icrid} eq $tcrid2) && ($tchan2 eq $prog->{sid}) && ($prog->{st} == $tstart2)) { + $got = 1; + } + } + if (!$got) { + print STDOUT "Unset timer for split event $tcrid found!\n"; + my $fstart = strftime("%Y-%m-%d:%H%M", localtime($prog->{st}-$CONFIG{START_PADDING}*60)); + my $fend = strftime("%H%M", localtime($prog->{et}+$CONFIG{STOP_PADDING}*60)); + my $title = get_title($prog->{sid},$prog->{st}); + my $flag = 1 + 4*$CONFIG{VPS}; + print STDOUT "New timer set for \"$title\" at $fstart\n"; + set_timer ("$flag:$prog->{sid}:$fstart:$fend:$CONFIG{PRIORITY}:$CONFIG{LIFETIME}:$title:"); + $count++; + } + } + } + } + return $count; +} + +# Scan the timers list for events which have changed since the timer was +# set. For now we just report the error. TODO: if the event has moved maybe +# we should try to find the new one? + +sub check_changed_events { + + print STDOUT "Checking for changed timer events\n"; + foreach my $timer (@timers) { + my $channelid = $chans[$timer->{chan}] -> {id}; + my $start_t = $timer->{tstart}; + my $timer_title = $timer ->{title}; + my $event_title = get_title ($channelid, $start_t); + if ($timer_title ne $event_title) { + print STDOUT "Event: $event_title <=> Timer: $timer_title\n"; + } + } +} + +# Get a list of all occurrences of an event CRID. Return an array of EPG objects. + +sub events_for_crid { + + my ($crid) = @_; + my @elist; + + foreach my $prog (@epg) { + if ($prog->{icrid} eq $crid) { + push (@elist, $prog); + } + } + return @elist; +} + +# Get the name of a channel given its ID. + +sub channel_name_from_id { + + my ($id) = @_; + foreach my $chan (@chans) { + if ($chan->{id} eq $id) { + return $chan->{name}; + } + } + return ('Unknown'); +} + +# Read the timers from VDR + +sub get_timers { + + Send("LSTT"); + while (<SOCK>) { + chomp; + if (/^\d*([- ])\d* (.*)/) { + my ($flag,$chan,$day,$start,$stop,$prio,$life,$title) = split(':', $2); + my ($yy,$mm,$dd) = split('-', $day); + my $starth = $start / 100; + my $startm = $start % 100; + my $stoph = $stop / 100; + my $stopm = $stop % 100; + my $tstart = mktime(0, $startm, $starth, $dd, $mm-1, $yy-1900, 0, 0, -1); + if ($stoph < $starth) { # prog over midnight + $dd++; + } + my $tstop = mktime(0, $stopm, $stoph, $dd, $mm-1, $yy-1900, 0, 0, -1); + push (@timers, { + flag => $flag, + chan => $chan, + tstart => $tstart, + tstop => $tstop, + title => $title + }); + } + last if $1 ne "-"; + } + print STDOUT "Read ",scalar(@timers)," Timers\n"; +} + +# Read the EPG from VDR (TVAnytime events only) + +sub get_epg { + + my ($sid,$id,$st,$et,$d); + my $icrid = "NULL"; + my $scrid = "NULL"; + Send("LSTE"); + while (<SOCK>) { + chomp; + if (my ($flag,$type,$data) = /^215(.)(.) *(.*)$/) { + if ($type eq 'C') { + ($sid) = ($data =~ /^(.*?) /); + } + elsif ($type eq 'Y') { + $icrid = $data; + } + elsif ($type eq 'Z') { + $scrid = $data; + } + elsif ($type eq 'E') { + ($id,$st,$d) = split(" ",$data); + $et = $st + $d; + } + elsif ($type eq 'e') { + if ($icrid ne "NULL") { + push (@epg, { + sid => $sid, + st => $st, + et => $et, + id => $id, + icrid => $icrid, + scrid => $scrid + }); + $icrid = "NULL"; + } + $scrid = "NULL"; + } + last if $flag ne "-"; +} + } + print STDOUT "Read ",scalar(@epg)," EPG lines\n"; +} + +# Read the channels list from VDR. Channel numbers are 1-based not 0-based... + +sub get_channels { + + push (@chans, { + id => '0-0-0-0', + transponder => '0-0', + name => 'Dummy' + }); + Send("LSTC"); + while (<SOCK>) { + chomp; + if (/^\d*([- ])\d* (.*)/) { + my ($name,$f,$p,$t,$d4,$d5,$d6,$d7,$d8,$id1,$id2,$id3) = split(':', $2); + push (@chans, { + id => join('-', $t, $id2, $id3, $id1), + transponder => join('-', $t, $f), + name => $name + }); + } + last if $1 ne "-"; + } + print STDOUT "Read ",scalar(@chans)," Channels\n"; +} + +# Read the links file. + +sub get_links { + + if (open (LINKS,'<',"$CONFIG{LINKSDIR}/links.data")) { + while (<LINKS>) { + chomp; + my ($scrid,$icrids) = split(','); + $links{$scrid} = $icrids; + } + close (LINKS); + print STDOUT "Read ",scalar(keys(%links))," Links\n"; + } + else { + print STDOUT "No links file found\n"; + } +} + +# Save the links file + +sub put_links { + + print STDOUT "Rewriting Links file\n"; + open (LINKS,'>',"$CONFIG{LINKSDIR}/links.data.new") or die "Cannot open new links file\n"; + while (my($link,$val) = each %links){ + if ($val ne '') { + my ($last_t,$entries) = split(';',$val); + if (($last_t + ($CONFIG{SERIES_TIMEOUT} * 86640)) > time()) { + print LINKS $link,',',$val,"\n"; + } + else { + print STDOUT "Expiring series $link\n"; + } + } + } + close (LINKS); + if (-e "$CONFIG{LINKSDIR}/links.data.old") { + unlink "$CONFIG{LINKSDIR}/links.data.old"; + } + if (-e "$CONFIG{LINKSDIR}/links.data") { + rename "$CONFIG{LINKSDIR}/links.data", "$CONFIG{LINKSDIR}/links.data.old"; + } + rename "$CONFIG{LINKSDIR}/links.data.new", "$CONFIG{LINKSDIR}/links.data"; + print STDOUT "Wrote ",scalar(keys(%links))," Links\n"; +} + +# Display the links + +sub show_links { + + while (my($link,$val) = each %links){ + if ($val ne '') { + my ($last_t,$entries) = split(';',$val); + my $last = strftime("%Y-%m-%d:%H%M", localtime($last_t)); + print STDOUT "$link\t$last\n"; + } + } +} + +# Get the program title from EPG, given channel and start time. + +sub get_title { + + my $title = "TITLE"; + my ($chan,$time) = @_; + Send ("LSTE $chan at $time"); + while (<SOCK>) { + chomp; + if (/^215-T (.*)/) { + $title = $1; + } + last if substr($_, 3, 1) ne "-"; + } + $title =~ s/:/|/g; + return ($title); +} + +# Get the item CRID for a timer entry. + +sub get_crid_for_timer { + + my $crid = "CRID"; + my ($chan,$time) = @_; + Send ("LSTE $chan at $time"); + while (<SOCK>) { + chomp; + if (/^215-Y (.*)/) { + $crid = $1; + } + last if substr($_, 3, 1) ne "-"; + } + $crid =~ s/:/|/g; + return ($crid); +} + + +# Set a new timer + +sub set_timer { + + my $string = shift; + Send ("NEWT $string"); + Receive(); +} + +# SVDRP handling + +sub open_SVDRP { + + $SIG{ALRM} = sub { Error("timeout"); }; + alarm($Timeout); + + $iaddr = inet_aton($CONFIG{VDR_HOST}) || Error("no host: $Dest"); + $paddr = sockaddr_in($CONFIG{VDR_PORT}, $iaddr); + + $proto = getprotobyname('tcp'); + socket(SOCK, PF_INET, SOCK_STREAM, $proto) || Error("socket: $!"); + connect(SOCK, $paddr) || Error("connect: $!"); + select(SOCK); $| = 1; + Receive(); +} + +sub close_SVDRP { + print STDOUT "Closing connection\n"; + Send("quit"); + Receive(); + close(SOCK) || Error("close: $!"); +} + +sub Send +{ + my $cmd = shift || Error("no command to send"); + print SOCK "$cmd\r\n"; +} + +sub Receive +{ + while (<SOCK>) { +# print STDOUT $_; + last if substr($_, 3, 1) ne "-"; + } +} + +sub Error +{ + print STDERR "@_\n"; + close(SOCK); + exit 0; +} + |