summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--HISTORY24
-rw-r--r--README33
-rw-r--r--README - series21
-rw-r--r--TODO6
-rwxr-xr-xseries.pl380
-rw-r--r--vdrtva-1.6.0.diff455
-rw-r--r--vdrtva-1.7.9.diff455
7 files changed, 1374 insertions, 0 deletions
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..e2f7bba
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,24 @@
+2008/2/2 - Version 0.0.1 (vdr version 1.5.13)
+
+- Initial preview.
+
+2008/3/30 - Version 0.0.2 (vdr version 1.6.0)
+
+- sdt.c - minor code tidy.
+- eit.c - don't populate CRIDs if DefaultAuthority hasn't been set yet.
+ - change tags in epg.data to match vdr standard.
+ - check CRID is not indirectly referenced via a CIT.
+- vdr.5 - document changes made to files.
+
+2008/9/30 - Version 0.0.3 (vdr version 1.6.0-2)
+
+- Patch against latest VDR version
+- Sample Perl program included to implement Series Link.
+- Freeview Playback is now Freeview Plus.
+
+2008/12/01 - Version 0.0.4 (vdr version 1.6.0-2)
+- eit.c - fixed memory leak (thanks Dominic Morris).
+- series.pl - links file could be corrupted if a new timer was manually added to a series. Timer clash detection added. General clean-up.
+
+2009/8/23 - Version 0.0.5 (vdr versions 1.6.0-2 and 1.7.9)
+- New patch for development version of vdr.
diff --git a/README b/README
new file mode 100644
index 0000000..bc4ac21
--- /dev/null
+++ b/README
@@ -0,0 +1,33 @@
+TV-Anytime is the name given to a set of technologies which aim to simplify the
+process of recording and replaying broadcast content. The standards are published
+by ETSI and are available without cost from www.etsi.org (registration required).
+
+In the UK a subset of the TV-Anytime specification is broadcast on the DTV service
+under the trade name "FreeView Plus". This patch is written for the UK version but
+should work with the full specification.
+
+TV-Anytime data is contained in Content Reference Identifiers (CRIDs). The syntax
+of a CRID is described in RFC 4078; it is a URI-compliant string of the form:
+
+ crid://<DNS name>/<data>
+
+in which <DNS name> is a registered internet domain name (RFC 1034) and <data> is
+a free-format string. The <DNS Name> section relates to the content provider (TV
+channel or company), and the <data> section to the programme.
+
+CRIDs are transmitted in the EIT as Content Identifier Descriptors, with descriptor
+ID 0x76. To save bandwith only the <data> section is sent, the <DNS Name> part is
+taken from the Default Authority Descriptor in the SDT, and the crid:// is assumed.
+
+A programme may have up to two CRIDs in its EPG entry. One identifies the specific
+item of content which is being broadcast, while the other identifies a series of
+programmes which this item belongs to. In FreeView Plus these CRIDs have crid_type
+values 0x31 and 0x32 respectively (TV-Anytime uses values 0x01 and 0x02).
+
+To give an example, the programme "Torchwood" broadcast on channel BBC2 at 21:00 on
+2008-01-16 had item CRID '54BXLC' and series CRID 'KCJ00C'. When the same programme
+was repeated the following day on channel BBC3, the item CRID remained the same but
+the series CRID was 'KCJ12C'. Meanwhile the episode broadcast on BBC2 one week
+later on 2008-01-24 had CRID '54BXLD' but the same series as the previous week. Hence it is possible for a PVR to record an entire series by using the series CRID, or to find an alternative broadcast for an individual item if there is a clash with
+another recording.
+
diff --git a/README - series b/README - series
new file mode 100644
index 0000000..069d063
--- /dev/null
+++ b/README - series
@@ -0,0 +1,21 @@
+This is a very simple script to demonstrate the 'series link' concept. Run it every day as a cron job and never miss your favourite series again! Just create a timer for the first programme in a series and the script will automatically record the rest for you.
+
+Configuration parameters at the start of the file must be set to match your vdr
+settings. The 'padding' values must match those used when you manually set timers (eg when using vdradmin).
+
+The script creates a file "links.data" in the vdr directory when run. This file contains series CRIDs of all of the timers which have been set, and the item CRIDs of the individual programmes which have had recordings scheduled. A timestamp against each entry gives the date of the last timer set, so that old series can be automatically purged.
+
+Points to remember:
+
+- Not all channels on UK Freeview have CRIDs in the EPG (at present just the BBC, ITV, C4 and C5 stables plus Sky 3).
+
+- Different programme providers have different ideas of what constitutes a 'series'.
+
+- The timer creation process is very simplistic; it doesn't check for timer clashes, and selects the first physical entry in the EPG (which may not be the prime broadcast of the programme).
+
+- A series link is created for every timer whether you want one or not.
+
+- If you run this script overnight, a timer set one day which fires on the same day will not create a series link (because the timer no longer exists).
+
+- This script has not been tested with multiple tuner cards or with mixed DVB-T and
+DVB-S setups.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..9cad1ff
--- /dev/null
+++ b/TODO
@@ -0,0 +1,6 @@
+- We should allow for Content Identifier Descriptors to include more than one CRID, as allowed in ETSI 102 323. Freeview has each CRID in a separate descriptor.
+
+- The Authority portion of the CRID is always taken from the SDT. ETSI 102 323 provides several other locations for this information which should be searched before the SDT.
+
+- ETSI 102 323 allows CRID data to be referenced indirectly via a Content Identifier Table (though this use is deprecated). Indirection is not supported as Freeview does not broadcast CITs.
+
diff --git a/series.pl b/series.pl
new file mode 100755
index 0000000..f58d8c7
--- /dev/null
+++ b/series.pl
@@ -0,0 +1,380 @@
+#!/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} = 2001; # SVDRP port on VDR server
+$CONFIG{SERIES_TIMEOUT} = 30; # Expiry time of a series (days)
+$CONFIG{START_PADDING} = 1; # Padding when creating new timers
+$CONFIG{STOP_PADDING} = 3;
+$CONFIG{PRIORITY} = 99; # Recording priority and lifetime
+$CONFIG{LIFETIME} = 99;
+$CONFIG{LINKSDIR} = "/video/video"; # Directory holding links file
+
+my (@timers, @chans, @epg);
+my %links = {};
+
+open_SVDRP();
+get_channels();
+get_epg();
+get_timers();
+get_links();
+my $updates = check_timers();
+$updates += check_links();
+if ($updates) {
+ put_links();
+ @timers = ();
+ get_timers();
+}
+#show_links();
+check_timer_clashes();
+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 ($flag,$chan,$day,$start,$stop) = split(':', $timer);
+ my $channelid = $chans[$timer->{chan}-1] -> {id};
+ my ($yy,$mm,$dd) = split('-', $timer->{day});
+ my $starth = $timer->{start} / 100;
+ my $startm = $timer->{start} % 100;
+ my $stoph = $timer->{stop} / 100;
+ my $stopm = $timer->{stop} % 100;
+ my $start_t = mktime(0, $startm, $starth, $dd, $mm-1, $yy-1900, 0, 0, -1);
+ if ($stoph < $starth) { # prog over midnight
+ $dd++;
+ }
+ my $stop_t = mktime(0, $stopm, $stoph, $dd, $mm-1, $yy-1900, 0, 0, -1);
+ foreach my $prog (@epg) {
+ my ($sid, $st, $et, $id, $icrid, $scrid) = split(',', $prog);
+ if (($sid eq $channelid) && ($start_t <= $st) && ($stop_t >= $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 ($sid, $st, $et, $id, $icrid, $scrid) = split(',', $prog);
+ if (exists $links{$scrid}) {
+ if ($links{$scrid} !~ /$icrid/) {
+# Have we already recorded this programme on a diferent series?
+ my $done = 0;
+ for (values %links) {
+ if (/$icrid/) {
+ print STDOUT "Item $icrid already recorded!\n";
+ $done = 1;
+ }
+ }
+ if (!$done) {
+ my $fstart = strftime("%Y-%m-%d:%H%M", localtime($st-$CONFIG{START_PADDING}*60));
+ my $fend = strftime("%H%M", localtime($et+$CONFIG{STOP_PADDING}*60));
+ my $title = get_title($sid,$st);
+ print STDOUT "New timer set for $scrid, \"$title\" at $fstart\n";
+ set_timer ("1:$sid:$fstart:$fend:$CONFIG{PRIORITY}:$CONFIG{LIFETIME}:$title:");
+ my ($last_t,$val) = split(';', $links{$scrid});
+ $links{$scrid} = "$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++) {
+ my ($yy,$mm,$dd) = split('-', $timers[$ii]->{day});
+ my $starth = $timers[$ii]->{start} / 100;
+ my $startm = $timers[$ii]->{start} % 100;
+ my $stoph = $timers[$ii]->{stop} / 100;
+ my $stopm = $timers[$ii]->{stop} % 100;
+ push @tstart, mktime(0, $startm, $starth, $dd, $mm-1, $yy-1900, 0, 0, -1);
+ if ($stoph < $starth) { # prog over midnight
+ $dd++;
+ }
+ push @tstop, mktime(0, $stopm, $stoph, $dd, $mm-1, $yy-1900, 0, 0, -1);
+
+ for ($jj = 0 ; $jj < $ii ; $jj++) {
+ if (($tstart[$ii] >= $tstart[$jj] && $tstart[$ii] < $tstop[$jj])
+ || ($tstart[$jj] >= $tstart[$ii] && $tstart[$jj] < $tstop[$ii]))
+ {
+ # Timers collide in time. Check if the
+ # Timers are on the same transponder
+ my $t1 = $chans[$timers[$ii]->{chan}-1] -> {transponder};
+ my $t2 = $chans[$timers[$jj]->{chan}-1] -> {transponder};
+ if ($t1 eq $t2) {
+# print STDOUT "Multiple recordings on same transponder - OK\n";
+ }
+ else {
+ # What to do?? For now just report the collision
+ my $ttl1 = get_title($chans[$timers[$ii]->{chan}-1]->{id}, $tstart[$ii]+$CONFIG{START_PADDING}*60);
+ my $ttl2 = get_title($chans[$timers[$jj]->{chan}-1]->{id}, $tstart[$jj]+$CONFIG{START_PADDING}*60);
+ print STDOUT "Collision! $timers[$ii]->{day} $timers[$ii]->{start}\n$ttl1 <-> $ttl2\n";
+ }
+ }
+ }
+ }
+
+ sub is_clash {
+ return 1;
+ }
+}
+
+
+# Read the timers from VDR
+
+sub get_timers {
+
+ Send("LSTT");
+ while (<SOCK>) {
+ chomp;
+ /^\d*([- ])\d* (.*)/;
+ my ($flag,$chan,$day,$start,$stop) = split(':', $2);
+ push (@timers, {
+ flag => $flag,
+ chan => $chan,
+ day => $day,
+ start => $start,
+ stop => $stop
+ });
+# last if substr($_, 3, 1) ne "-";
+ 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;
+ my ($type,$data) = /^215.(.) *(.*)$/;
+ if ($type eq 'C') {
+ ($sid) = ($data =~ /^(.*?) /);
+ }
+ elsif ($type eq 'I') {
+ $icrid = $data;
+ }
+ elsif ($type eq 'R') {
+ $scrid = $data;
+ }
+ elsif ($type eq 'E') {
+ ($id,$st,$d) = split(" ",$data);
+ $et = $st + $d;
+ }
+ elsif ($type eq 'e') {
+ if (($icrid ne "NULL") && ($scrid ne "NULL")) {
+ push @epg, join(',', $sid, $st, $et, $id, $icrid, $scrid);
+ }
+ $icrid = "NULL";
+ $scrid = "NULL";
+ }
+ last if substr($_, 3, 1) ne "-";
+ }
+ print STDOUT "Read ",scalar(@epg)," EPG lines\n";
+}
+
+# Read the channels list from VDR
+
+sub get_channels {
+
+ Send("LSTC");
+ while (<SOCK>) {
+ chomp;
+ /^\d*([- ])\d* (.*)/;
+#print STDOUT $2;
+ 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";
+}
+
+# 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);
+}
+
+# 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;
+}
+
diff --git a/vdrtva-1.6.0.diff b/vdrtva-1.6.0.diff
new file mode 100644
index 0000000..0df555b
--- /dev/null
+++ b/vdrtva-1.6.0.diff
@@ -0,0 +1,455 @@
+diff -ur vdr-1.6.0/channels.c vdr-1.5/channels.c
+--- vdr-1.6.0/channels.c 2008-03-05 16:42:50.000000000 +0000
++++ vdr-1.5/channels.c 2008-03-16 12:55:17.000000000 +0000
+@@ -166,6 +166,7 @@
+ shortName = strdup("");
+ provider = strdup("");
+ portalName = strdup("");
++ defaultAuthority = strdup("");
+ memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__);
+ inversion = INVERSION_AUTO;
+ bandwidth = BANDWIDTH_AUTO;
+@@ -187,6 +188,7 @@
+ shortName = NULL;
+ provider = NULL;
+ portalName = NULL;
++ defaultAuthority = NULL;
+ schedule = NULL;
+ linkChannels = NULL;
+ refChannel = NULL;
+@@ -215,6 +217,7 @@
+ free(shortName);
+ free(provider);
+ free(portalName);
++ free(defaultAuthority);
+ }
+
+ cChannel& cChannel::operator= (const cChannel &Channel)
+@@ -223,6 +226,7 @@
+ shortName = strcpyrealloc(shortName, Channel.shortName);
+ provider = strcpyrealloc(provider, Channel.provider);
+ portalName = strcpyrealloc(portalName, Channel.portalName);
++ defaultAuthority = strcpyrealloc(defaultAuthority, Channel.defaultAuthority);
+ memcpy(&__BeginData__, &Channel.__BeginData__, (char *)&Channel.__EndData__ - (char *)&Channel.__BeginData__);
+ return *this;
+ }
+@@ -407,6 +411,13 @@
+ }
+ }
+
++void cChannel::SetDefaultAuthority(const char *DefaultAuthority)
++{
++ if (!isempty(DefaultAuthority) && strcmp(defaultAuthority, DefaultAuthority) != 0) {
++ defaultAuthority = strcpyrealloc(defaultAuthority, DefaultAuthority);
++ }
++}
++
+ #define STRDIFF 0x01
+ #define VALDIFF 0x02
+
+@@ -681,11 +692,11 @@
+ q += IntArrayToString(q, Channel->dpids, 10, Channel->dlangs);
+ }
+ *q = 0;
+- char caidbuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia
++ char caidbuf[MAXCAIDS * 5 + 10 + 256]; // 5: 4 digits plus delimiting ',', 10 + max DNS domain length: paranoia
+ q = caidbuf;
+ q += IntArrayToString(q, Channel->caids, 16);
+ *q = 0;
+- buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%d:%s:%d:%d:%d:%d\n", FullName, Channel->frequency, *Channel->ParametersToString(), *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, Channel->tpid, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid);
++ buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%d:%s:%d:%d:%d:%d:%s\n", FullName, Channel->frequency, *Channel->ParametersToString(), *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, Channel->tpid, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid, Channel->defaultAuthority);
+ }
+ return buffer;
+ }
+@@ -720,13 +731,16 @@
+ char *vpidbuf = NULL;
+ char *apidbuf = NULL;
+ char *caidbuf = NULL;
+- int fields = sscanf(s, "%a[^:]:%d :%a[^:]:%a[^:] :%d :%a[^:]:%a[^:]:%d :%a[^:]:%d :%d :%d :%d ", &namebuf, &frequency, &parambuf, &sourcebuf, &srate, &vpidbuf, &apidbuf, &tpid, &caidbuf, &sid, &nid, &tid, &rid);
++ char *dabuf = NULL;
++ int fields = sscanf(s, "%a[^:]:%d :%a[^:]:%a[^:] :%d :%a[^:]:%a[^:]:%d :%a[^:]:%d :%d :%d :%d :%a[^:]", &namebuf, &frequency, &parambuf, &sourcebuf, &srate, &vpidbuf, &apidbuf, &tpid, &caidbuf, &sid, &nid, &tid, &rid, &dabuf);
+ if (fields >= 9) {
+ if (fields == 9) {
+ // allow reading of old format
+ sid = atoi(caidbuf);
+ delete caidbuf;
+ caidbuf = NULL;
++ delete dabuf;
++ dabuf = NULL;
+ caids[0] = tpid;
+ caids[1] = 0;
+ tpid = 0;
+@@ -828,12 +842,17 @@
+ }
+ name = strcpyrealloc(name, namebuf);
+
++ if (dabuf) {
++ defaultAuthority = strcpyrealloc(defaultAuthority, dabuf);
++ }
++
+ free(parambuf);
+ free(sourcebuf);
+ free(vpidbuf);
+ free(apidbuf);
+ free(caidbuf);
+ free(namebuf);
++ free(dabuf);
+ if (!GetChannelID().Valid()) {
+ esyslog("ERROR: channel data results in invalid ID!");
+ return false;
+diff -ur vdr-1.6.0/channels.h vdr-1.5/channels.h
+--- vdr-1.6.0/channels.h 2008-02-08 13:48:31.000000000 +0000
++++ vdr-1.5/channels.h 2008-02-17 15:13:46.000000000 +0000
+@@ -114,6 +114,7 @@
+ char *shortName;
+ char *provider;
+ char *portalName;
++ char *defaultAuthority;
+ int __BeginData__;
+ int frequency; // MHz
+ int source;
+@@ -162,6 +163,7 @@
+ const char *ShortName(bool OrName = false) const { return (OrName && isempty(shortName)) ? name : shortName; }
+ const char *Provider(void) const { return provider; }
+ const char *PortalName(void) const { return portalName; }
++ const char *DefaultAuthority(void) const { return defaultAuthority; }
+ int Frequency(void) const { return frequency; } ///< Returns the actual frequency, as given in 'channels.conf'
+ int Transponder(void) const; ///< Returns the transponder frequency in MHz, plus the polarization in case of sat
+ static int Transponder(int Frequency, char Polarization); ///< builds the transponder from the given Frequency and Polarization
+@@ -212,6 +214,7 @@
+ void SetId(int Nid, int Tid, int Sid, int Rid = 0);
+ void SetName(const char *Name, const char *ShortName, const char *Provider);
+ void SetPortalName(const char *PortalName);
++ void SetDefaultAuthority(const char *DefaultAuthority);
+ void SetPids(int Vpid, int Ppid, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
+ void SetCaIds(const int *CaIds); // list must be zero-terminated
+ void SetCaDescriptors(int Level);
+diff -ur vdr-1.6.0/eit.c vdr-1.5/eit.c
+--- vdr-1.6.0/eit.c 2008-09-30 18:18:46.000000000 +0100
++++ vdr-1.5/eit.c 2008-09-07 11:29:49.000000000 +0100
+@@ -110,6 +110,8 @@
+ SI::Descriptor *d;
+ SI::ExtendedEventDescriptors *ExtendedEventDescriptors = NULL;
+ SI::ShortEventDescriptor *ShortEventDescriptor = NULL;
++ SI::ContentIdentifierDescriptor *itemCrid = NULL;
++ SI::ContentIdentifierDescriptor *seriesCrid = NULL;
+ cLinkChannels *LinkChannels = NULL;
+ cComponents *Components = NULL;
+ for (SI::Loop::Iterator it2; (d = SiEitEvent.eventDescriptors.getNext(it2)); ) {
+@@ -227,6 +229,23 @@
+ }
+ }
+ break;
++ case SI::ContentIdentifierDescriptorTag: {
++ SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d;
++ switch (cd->getCridType()) {
++ case 0x01:
++ case 0x31: {
++ itemCrid = cd;
++ d = NULL; // so that it is not deleted
++ break;
++ }
++ case 0x02:
++ case 0x32: {
++ seriesCrid = cd;
++ d = NULL; // so that it is not deleted
++ break;
++ }
++ }
++ }
+ default: ;
+ }
+ delete d;
+@@ -248,9 +267,23 @@
+ }
+ else if (!HasExternalData)
+ pEvent->SetDescription(NULL);
++ if (itemCrid && (itemCrid->getCridLocation() == 0)) {
++ char buffer[Utf8BufSize(256)];
++ strcpy (buffer, channel->DefaultAuthority());
++ strcat(buffer, itemCrid->entry.getText());
++ pEvent->SetItemCRID(buffer);
+ }
++ if (seriesCrid && (seriesCrid->getCridLocation() == 0)) {
++ char buffer[Utf8BufSize(256)];
++ strcpy (buffer, channel->DefaultAuthority());
++ strcat(buffer, seriesCrid->entry.getText());
++ pEvent->SetSeriesCRID(buffer);
++ }
++ }
+ delete ExtendedEventDescriptors;
+ delete ShortEventDescriptor;
++ delete itemCrid;
++ delete seriesCrid;
+
+ pEvent->SetComponents(Components);
+
+diff -ur vdr-1.6.0/epg.c vdr-1.5/epg.c
+--- vdr-1.6.0/epg.c 2008-02-16 16:09:12.000000000 +0000
++++ vdr-1.5/epg.c 2008-02-23 12:28:13.000000000 +0000
+@@ -113,6 +113,8 @@
+ startTime = 0;
+ duration = 0;
+ vps = 0;
++ itemCRID = NULL;
++ seriesCRID = NULL;
+ SetSeen();
+ }
+
+@@ -121,6 +123,8 @@
+ free(title);
+ free(shortText);
+ free(description);
++ free(itemCRID);
++ free(seriesCRID);
+ delete components;
+ }
+
+@@ -205,6 +209,16 @@
+ vps = Vps;
+ }
+
++void cEvent::SetItemCRID(const char *CRID)
++{
++ itemCRID = strcpyrealloc(itemCRID, CRID);
++}
++
++void cEvent::SetSeriesCRID(const char *CRID)
++{
++ seriesCRID = strcpyrealloc(seriesCRID, CRID);
++}
++
+ void cEvent::SetSeen(void)
+ {
+ seen = time(NULL);
+@@ -278,6 +292,10 @@
+ }
+ if (vps)
+ fprintf(f, "%sV %ld\n", Prefix, vps);
++ if (!isempty(itemCRID))
++ fprintf(f, "%sI %s\n", Prefix, itemCRID);
++ if (!isempty(seriesCRID))
++ fprintf(f, "%sR %s\n", Prefix, seriesCRID);
+ if (!InfoOnly)
+ fprintf(f, "%se\n", Prefix);
+ }
+@@ -300,6 +318,10 @@
+ break;
+ case 'V': SetVps(atoi(t));
+ break;
++ case 'I': SetItemCRID(t);
++ break;
++ case 'R': SetSeriesCRID(t);
++ break;
+ default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
+ return false;
+ }
+diff -ur vdr-1.6.0/epg.h vdr-1.5/epg.h
+--- vdr-1.6.0/epg.h 2006-10-07 14:47:19.000000000 +0100
++++ vdr-1.5/epg.h 2008-02-17 15:13:46.000000000 +0000
+@@ -66,6 +66,8 @@
+ int duration; // Duration of this event in seconds
+ time_t vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL)
+ time_t seen; // When this event was last seen in the data stream
++ char *itemCRID; // Item CRID
++ char *seriesCRID; // Series CRID
+ public:
+ cEvent(tEventID EventID);
+ ~cEvent();
+@@ -84,6 +86,8 @@
+ time_t EndTime(void) const { return startTime + duration; }
+ int Duration(void) const { return duration; }
+ time_t Vps(void) const { return vps; }
++ const char *ItemCRID(void) const { return itemCRID; }
++ const char *SeriesCRID(void) const { return seriesCRID; }
+ time_t Seen(void) const { return seen; }
+ bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; }
+ bool HasTimer(void) const;
+@@ -103,6 +107,8 @@
+ void SetStartTime(time_t StartTime);
+ void SetDuration(int Duration);
+ void SetVps(time_t Vps);
++ void SetItemCRID(const char *CRID);
++ void SetSeriesCRID(const char *CRID);
+ void SetSeen(void);
+ cString ToDescr(void) const;
+ void Dump(FILE *f, const char *Prefix = "", bool InfoOnly = false) const;
+diff -ur vdr-1.6.0/libsi/descriptor.c vdr-1.5/libsi/descriptor.c
+--- vdr-1.6.0/libsi/descriptor.c 2007-02-03 11:45:58.000000000 +0000
++++ vdr-1.5/libsi/descriptor.c 2008-02-17 15:13:46.000000000 +0000
+@@ -643,6 +643,29 @@
+ textualServiceIdentifier.setData(data+sizeof(descr_service_identifier), getLength()-sizeof(descr_service_identifier));
+ }
+
++void ContentIdentifierDescriptor::Parse() {
++ int offset=0;
++ data.setPointerAndOffset<const descr_content_identifier>(s, offset);
++ if (s->crid_location == 0) {
++ entry.setData(data+(offset-1), s->crid_length);
++ }
++ else {
++ entry.setData(data+(offset-1), 2);
++ }
++}
++
++int ContentIdentifierDescriptor::getCridType() const {
++ return s->crid_type;
++}
++
++int ContentIdentifierDescriptor::getCridLocation() const {
++ return s->crid_location;
++}
++
++void DefaultAuthorityDescriptor::Parse() {
++ DefaultAuthority.setData(data+sizeof(descr_default_authority), getLength()-sizeof(descr_default_authority));
++}
++
+ void MultilingualNameDescriptor::Parse() {
+ nameLoop.setData(data+sizeof(descr_multilingual_network_name), getLength()-sizeof(descr_multilingual_network_name));
+ }
+diff -ur vdr-1.6.0/libsi/descriptor.h vdr-1.5/libsi/descriptor.h
+--- vdr-1.6.0/libsi/descriptor.h 2007-02-03 11:45:58.000000000 +0000
++++ vdr-1.5/libsi/descriptor.h 2008-02-17 15:13:46.000000000 +0000
+@@ -361,6 +361,25 @@
+ virtual void Parse();
+ };
+
++class ContentIdentifierDescriptor : public Descriptor {
++public:
++ String entry;
++ int getCridType() const;
++ int getCridLocation() const;
++// virtual int getLength() { return sizeof(descr_content_identifier)+entry.getLength(); }
++ private:
++ const descr_content_identifier *s;
++protected:
++ virtual void Parse();
++};
++
++class DefaultAuthorityDescriptor : public Descriptor {
++public:
++ String DefaultAuthority; //ID
++protected:
++ virtual void Parse();
++};
++
+ //abstract base class
+ class MultilingualNameDescriptor : public Descriptor {
+ public:
+diff -ur vdr-1.6.0/libsi/headers.h vdr-1.5/libsi/headers.h
+--- vdr-1.6.0/libsi/headers.h 2007-02-03 11:45:58.000000000 +0000
++++ vdr-1.5/libsi/headers.h 2008-02-17 15:13:46.000000000 +0000
+@@ -1673,11 +1673,39 @@
+ u_char descriptor_length :8;
+ };
+
++struct entry_tva_id {
++ u_char tva_id_hi :8;
++ u_char tva_id_lo :8;
++#if BYTE_ORDER == BIG_ENDIAN
++ u_char reserved :5;
++ u_char running_status :3;
++#else
++ u_char running_status :3;
++ u_char reserved :5;
++#endif
++};
++
++
+ /* 0x76 content_identifier_descriptor (ETSI TS 102 323) */
+
+ struct descr_content_identifier {
+ u_char descriptor_tag :8;
+ u_char descriptor_length :8;
++#if BYTE_ORDER == BIG_ENDIAN
++ u_char crid_type :6;
++ u_char crid_location :2;
++#else
++ u_char crid_location :2;
++ u_char crid_type :6;
++#endif
++ union {
++ u_char crid_length :8;
++ u_char crid_ref_hi :8;
++ };
++ union {
++ u_char crid_byte :8;
++ u_char crid_ref_lo :8;
++ };
+ };
+
+ /* 0x77 time_slice_fec_identifier_descriptor (ETSI EN 301 192) */
+diff -ur vdr-1.6.0/libsi/si.c vdr-1.5/libsi/si.c
+--- vdr-1.6.0/libsi/si.c 2008-03-05 17:00:55.000000000 +0000
++++ vdr-1.5/libsi/si.c 2008-03-16 12:55:17.000000000 +0000
+@@ -605,6 +605,12 @@
+ case ExtensionDescriptorTag:
+ d=new ExtensionDescriptor();
+ break;
++ case ContentIdentifierDescriptorTag:
++ d=new ContentIdentifierDescriptor();
++ break;
++ case DefaultAuthorityDescriptorTag:
++ d=new DefaultAuthorityDescriptor();
++ break;
+
+ //note that it is no problem to implement one
+ //of the unimplemented descriptors.
+@@ -647,10 +653,8 @@
+ case TransportStreamDescriptorTag:
+
+ //defined in ETSI EN 300 468 v 1.7.1
+- case DefaultAuthorityDescriptorTag:
+ case RelatedContentDescriptorTag:
+ case TVAIdDescriptorTag:
+- case ContentIdentifierDescriptorTag:
+ case TimeSliceFecIdentifierDescriptorTag:
+ case ECMRepetitionRateDescriptorTag:
+ case EnhancedAC3DescriptorTag:
+diff -ur vdr-1.6.0/sdt.c vdr-1.5/sdt.c
+--- vdr-1.6.0/sdt.c 2008-02-08 13:48:31.000000000 +0000
++++ vdr-1.5/sdt.c 2008-02-17 15:13:46.000000000 +0000
+@@ -123,6 +123,12 @@
+ }
+ }
+ break;
++ case SI::DefaultAuthorityDescriptorTag: {
++ SI::DefaultAuthorityDescriptor *da = (SI::DefaultAuthorityDescriptor *)d;
++ char DaBuf[Utf8BufSize(1024)];
++ da->DefaultAuthority.getText(DaBuf, sizeof(DaBuf));
++ channel->SetDefaultAuthority(DaBuf);
++ }
+ default: ;
+ }
+ delete d;
+diff -ur vdr-1.6.0/vdr.5 vdr-1.5/vdr.5
+--- vdr-1.6.0/vdr.5 2008-03-09 15:46:57.000000000 +0000
++++ vdr-1.5/vdr.5 2008-03-16 12:55:17.000000000 +0000
+@@ -177,6 +177,9 @@
+ .B RID
+ The Radio ID of this channel (typically 0, may be used to distinguish channels where
+ NID, TID and SID are all equal).
++.TP
++.B Default Authority
++The Default Authority for CRIDs on this channel (TVAnytime).
+ .PP
+ A particular channel can be uniquely identified by its \fBchannel\ ID\fR,
+ which is a string that looks like this:
+@@ -620,6 +623,8 @@
+ \fBD\fR@<description>
+ \fBX\fR@<stream> <type> <language> <descr>
+ \fBV\fR@<vps time>
++\fBI\fR@<item CRID>
++\fBR\fR@<series CRID>
+ \fBe\fR@
+ \fBc\fR@
+ .TE
+@@ -653,6 +658,8 @@
+ <language> @is the three letter language code (optionally two codes, separated by '+')
+ <descr> @is the description of this stream component
+ <vps time> @is the Video Programming Service time of this event
++<item CRID> @is the CRID of this event (TVAnytime)
++<series CRID> @is the CRID of the series which this event is part of (TVAnytime)
+ .TE
+
+ This file will be read at program startup in order to restore the results of
diff --git a/vdrtva-1.7.9.diff b/vdrtva-1.7.9.diff
new file mode 100644
index 0000000..d410ca9
--- /dev/null
+++ b/vdrtva-1.7.9.diff
@@ -0,0 +1,455 @@
+diff -ur vdr-1.7.9/channels.c vdr-1.7/channels.c
+--- vdr-1.7.9/channels.c 2009-08-16 16:08:49.000000000 +0100
++++ vdr-1.7/channels.c 2009-08-23 15:15:16.000000000 +0100
+@@ -188,6 +188,7 @@
+ shortName = strdup("");
+ provider = strdup("");
+ portalName = strdup("");
++ defaultAuthority = strdup("");
+ memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__);
+ inversion = INVERSION_AUTO;
+ bandwidth = 8000000;
+@@ -211,6 +212,7 @@
+ shortName = NULL;
+ provider = NULL;
+ portalName = NULL;
++ defaultAuthority = NULL;
+ schedule = NULL;
+ linkChannels = NULL;
+ refChannel = NULL;
+@@ -239,6 +241,7 @@
+ free(shortName);
+ free(provider);
+ free(portalName);
++ free(defaultAuthority);
+ }
+
+ cChannel& cChannel::operator= (const cChannel &Channel)
+@@ -247,6 +250,7 @@
+ shortName = strcpyrealloc(shortName, Channel.shortName);
+ provider = strcpyrealloc(provider, Channel.provider);
+ portalName = strcpyrealloc(portalName, Channel.portalName);
++ defaultAuthority = strcpyrealloc(defaultAuthority, Channel.defaultAuthority);
+ memcpy(&__BeginData__, &Channel.__BeginData__, (char *)&Channel.__EndData__ - (char *)&Channel.__BeginData__);
+ return *this;
+ }
+@@ -438,6 +442,13 @@
+ }
+ }
+
++void cChannel::SetDefaultAuthority(const char *DefaultAuthority)
++{
++ if (!isempty(DefaultAuthority) && strcmp(defaultAuthority, DefaultAuthority) != 0) {
++ defaultAuthority = strcpyrealloc(defaultAuthority, DefaultAuthority);
++ }
++}
++
+ #define STRDIFF 0x01
+ #define VALDIFF 0x02
+
+@@ -752,11 +763,11 @@
+ q += IntArrayToString(q, Channel->dpids, 10, Channel->dlangs);
+ }
+ *q = 0;
+- char caidbuf[MAXCAIDS * 5 + 10]; // 5: 4 digits plus delimiting ',', 10: paranoia
++ char caidbuf[MAXCAIDS * 5 + 10 + 256]; // 5: 4 digits plus delimiting ',', 10 + max DNS domain length: paranoia
+ q = caidbuf;
+ q += IntArrayToString(q, Channel->caids, 16);
+ *q = 0;
+- buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%d:%s:%d:%d:%d:%d\n", FullName, Channel->frequency, *Channel->ParametersToString(), *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, Channel->tpid, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid);
++ buffer = cString::sprintf("%s:%d:%s:%s:%d:%s:%s:%d:%s:%d:%d:%d:%d:%s\n", FullName, Channel->frequency, *Channel->ParametersToString(), *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, Channel->tpid, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid, Channel->defaultAuthority);
+ }
+ return buffer;
+ }
+@@ -791,13 +802,16 @@
+ char *vpidbuf = NULL;
+ char *apidbuf = NULL;
+ char *caidbuf = NULL;
+- int fields = sscanf(s, "%a[^:]:%d :%a[^:]:%a[^:] :%d :%a[^:]:%a[^:]:%d :%a[^:]:%d :%d :%d :%d ", &namebuf, &frequency, &parambuf, &sourcebuf, &srate, &vpidbuf, &apidbuf, &tpid, &caidbuf, &sid, &nid, &tid, &rid);
++ char *dabuf = NULL;
++ int fields = sscanf(s, "%a[^:]:%d :%a[^:]:%a[^:] :%d :%a[^:]:%a[^:]:%d :%a[^:]:%d :%d :%d :%d :%a[^:]", &namebuf, &frequency, &parambuf, &sourcebuf, &srate, &vpidbuf, &apidbuf, &tpid, &caidbuf, &sid, &nid, &tid, &rid, &dabuf);
+ if (fields >= 9) {
+ if (fields == 9) {
+ // allow reading of old format
+ sid = atoi(caidbuf);
+ delete caidbuf;
+ caidbuf = NULL;
++ delete dabuf;
++ dabuf = NULL;
+ caids[0] = tpid;
+ caids[1] = 0;
+ tpid = 0;
+@@ -904,12 +918,17 @@
+ }
+ name = strcpyrealloc(name, namebuf);
+
++ if (dabuf) {
++ defaultAuthority = strcpyrealloc(defaultAuthority, dabuf);
++ }
++
+ free(parambuf);
+ free(sourcebuf);
+ free(vpidbuf);
+ free(apidbuf);
+ free(caidbuf);
+ free(namebuf);
++ free(dabuf);
+ if (!GetChannelID().Valid()) {
+ esyslog("ERROR: channel data results in invalid ID!");
+ return false;
+diff -ur vdr-1.7.9/channels.h vdr-1.7/channels.h
+--- vdr-1.7.9/channels.h 2009-08-16 15:58:26.000000000 +0100
++++ vdr-1.7/channels.h 2009-08-23 15:15:16.000000000 +0100
+@@ -116,6 +116,7 @@
+ char *shortName;
+ char *provider;
+ char *portalName;
++ char *defaultAuthority;
+ int __BeginData__;
+ int frequency; // MHz
+ int source;
+@@ -171,6 +172,7 @@
+ const char *ShortName(bool OrName = false) const { return (OrName && isempty(shortName)) ? name : shortName; }
+ const char *Provider(void) const { return provider; }
+ const char *PortalName(void) const { return portalName; }
++ const char *DefaultAuthority(void) const { return defaultAuthority; }
+ int Frequency(void) const { return frequency; } ///< Returns the actual frequency, as given in 'channels.conf'
+ int Transponder(void) const; ///< Returns the transponder frequency in MHz, plus the polarization in case of sat
+ static int Transponder(int Frequency, char Polarization); ///< builds the transponder from the given Frequency and Polarization
+@@ -227,6 +229,7 @@
+ void SetId(int Nid, int Tid, int Sid, int Rid = 0);
+ void SetName(const char *Name, const char *ShortName, const char *Provider);
+ void SetPortalName(const char *PortalName);
++ void SetDefaultAuthority(const char *DefaultAuthority);
+ void SetPids(int Vpid, int Ppid, int Vtype, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid);
+ void SetCaIds(const int *CaIds); // list must be zero-terminated
+ void SetCaDescriptors(int Level);
+diff -ur vdr-1.7.9/eit.c vdr-1.7/eit.c
+--- vdr-1.7.9/eit.c 2009-06-21 14:46:20.000000000 +0100
++++ vdr-1.7/eit.c 2009-08-23 15:15:16.000000000 +0100
+@@ -121,6 +121,8 @@
+ SI::Descriptor *d;
+ SI::ExtendedEventDescriptors *ExtendedEventDescriptors = NULL;
+ SI::ShortEventDescriptor *ShortEventDescriptor = NULL;
++ SI::ContentIdentifierDescriptor *itemCrid = NULL;
++ SI::ContentIdentifierDescriptor *seriesCrid = NULL;
+ cLinkChannels *LinkChannels = NULL;
+ cComponents *Components = NULL;
+ for (SI::Loop::Iterator it2; (d = SiEitEvent.eventDescriptors.getNext(it2)); ) {
+@@ -234,6 +236,23 @@
+ }
+ }
+ break;
++ case SI::ContentIdentifierDescriptorTag: {
++ SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d;
++ switch (cd->getCridType()) {
++ case 0x01:
++ case 0x31: {
++ itemCrid = cd;
++ d = NULL; // so that it is not deleted
++ break;
++ }
++ case 0x02:
++ case 0x32: {
++ seriesCrid = cd;
++ d = NULL; // so that it is not deleted
++ break;
++ }
++ }
++ }
+ default: ;
+ }
+ delete d;
+@@ -255,9 +274,23 @@
+ }
+ else if (!HasExternalData)
+ pEvent->SetDescription(NULL);
++ if (itemCrid && (itemCrid->getCridLocation() == 0)) {
++ char buffer[Utf8BufSize(256)];
++ strcpy (buffer, channel->DefaultAuthority());
++ strcat(buffer, itemCrid->entry.getText());
++ pEvent->SetItemCRID(buffer);
+ }
++ if (seriesCrid && (seriesCrid->getCridLocation() == 0)) {
++ char buffer[Utf8BufSize(256)];
++ strcpy (buffer, channel->DefaultAuthority());
++ strcat(buffer, seriesCrid->entry.getText());
++ pEvent->SetSeriesCRID(buffer);
++ }
++ }
+ delete ExtendedEventDescriptors;
+ delete ShortEventDescriptor;
++ delete itemCrid;
++ delete seriesCrid;
+
+ pEvent->SetComponents(Components);
+
+diff -ur vdr-1.7.9/epg.c vdr-1.7/epg.c
+--- vdr-1.7.9/epg.c 2008-05-01 15:53:55.000000000 +0100
++++ vdr-1.7/epg.c 2009-06-06 09:48:00.000000000 +0100
+@@ -115,6 +115,8 @@
+ startTime = 0;
+ duration = 0;
+ vps = 0;
++ itemCRID = NULL;
++ seriesCRID = NULL;
+ SetSeen();
+ }
+
+@@ -123,6 +125,8 @@
+ free(title);
+ free(shortText);
+ free(description);
++ free(itemCRID);
++ free(seriesCRID);
+ delete components;
+ }
+
+@@ -207,6 +211,16 @@
+ vps = Vps;
+ }
+
++void cEvent::SetItemCRID(const char *CRID)
++{
++ itemCRID = strcpyrealloc(itemCRID, CRID);
++}
++
++void cEvent::SetSeriesCRID(const char *CRID)
++{
++ seriesCRID = strcpyrealloc(seriesCRID, CRID);
++}
++
+ void cEvent::SetSeen(void)
+ {
+ seen = time(NULL);
+@@ -280,6 +294,10 @@
+ }
+ if (vps)
+ fprintf(f, "%sV %ld\n", Prefix, vps);
++ if (!isempty(itemCRID))
++ fprintf(f, "%sI %s\n", Prefix, itemCRID);
++ if (!isempty(seriesCRID))
++ fprintf(f, "%sR %s\n", Prefix, seriesCRID);
+ if (!InfoOnly)
+ fprintf(f, "%se\n", Prefix);
+ }
+@@ -302,6 +320,10 @@
+ break;
+ case 'V': SetVps(atoi(t));
+ break;
++ case 'I': SetItemCRID(t);
++ break;
++ case 'R': SetSeriesCRID(t);
++ break;
+ default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s);
+ return false;
+ }
+diff -ur vdr-1.7.9/epg.h vdr-1.7/epg.h
+--- vdr-1.7.9/epg.h 2006-10-07 14:47:19.000000000 +0100
++++ vdr-1.7/epg.h 2009-06-06 09:48:00.000000000 +0100
+@@ -66,6 +66,8 @@
+ int duration; // Duration of this event in seconds
+ time_t vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL)
+ time_t seen; // When this event was last seen in the data stream
++ char *itemCRID; // Item CRID
++ char *seriesCRID; // Series CRID
+ public:
+ cEvent(tEventID EventID);
+ ~cEvent();
+@@ -84,6 +86,8 @@
+ time_t EndTime(void) const { return startTime + duration; }
+ int Duration(void) const { return duration; }
+ time_t Vps(void) const { return vps; }
++ const char *ItemCRID(void) const { return itemCRID; }
++ const char *SeriesCRID(void) const { return seriesCRID; }
+ time_t Seen(void) const { return seen; }
+ bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; }
+ bool HasTimer(void) const;
+@@ -103,6 +107,8 @@
+ void SetStartTime(time_t StartTime);
+ void SetDuration(int Duration);
+ void SetVps(time_t Vps);
++ void SetItemCRID(const char *CRID);
++ void SetSeriesCRID(const char *CRID);
+ void SetSeen(void);
+ cString ToDescr(void) const;
+ void Dump(FILE *f, const char *Prefix = "", bool InfoOnly = false) const;
+diff -ur vdr-1.7.9/libsi/descriptor.c vdr-1.7/libsi/descriptor.c
+--- vdr-1.7.9/libsi/descriptor.c 2007-02-03 11:45:58.000000000 +0000
++++ vdr-1.7/libsi/descriptor.c 2009-06-06 09:48:00.000000000 +0100
+@@ -643,6 +643,29 @@
+ textualServiceIdentifier.setData(data+sizeof(descr_service_identifier), getLength()-sizeof(descr_service_identifier));
+ }
+
++void ContentIdentifierDescriptor::Parse() {
++ int offset=0;
++ data.setPointerAndOffset<const descr_content_identifier>(s, offset);
++ if (s->crid_location == 0) {
++ entry.setData(data+(offset-1), s->crid_length);
++ }
++ else {
++ entry.setData(data+(offset-1), 2);
++ }
++}
++
++int ContentIdentifierDescriptor::getCridType() const {
++ return s->crid_type;
++}
++
++int ContentIdentifierDescriptor::getCridLocation() const {
++ return s->crid_location;
++}
++
++void DefaultAuthorityDescriptor::Parse() {
++ DefaultAuthority.setData(data+sizeof(descr_default_authority), getLength()-sizeof(descr_default_authority));
++}
++
+ void MultilingualNameDescriptor::Parse() {
+ nameLoop.setData(data+sizeof(descr_multilingual_network_name), getLength()-sizeof(descr_multilingual_network_name));
+ }
+diff -ur vdr-1.7.9/libsi/descriptor.h vdr-1.7/libsi/descriptor.h
+--- vdr-1.7.9/libsi/descriptor.h 2007-02-03 11:45:58.000000000 +0000
++++ vdr-1.7/libsi/descriptor.h 2009-06-06 09:48:00.000000000 +0100
+@@ -361,6 +361,25 @@
+ virtual void Parse();
+ };
+
++class ContentIdentifierDescriptor : public Descriptor {
++public:
++ String entry;
++ int getCridType() const;
++ int getCridLocation() const;
++// virtual int getLength() { return sizeof(descr_content_identifier)+entry.getLength(); }
++ private:
++ const descr_content_identifier *s;
++protected:
++ virtual void Parse();
++};
++
++class DefaultAuthorityDescriptor : public Descriptor {
++public:
++ String DefaultAuthority; //ID
++protected:
++ virtual void Parse();
++};
++
+ //abstract base class
+ class MultilingualNameDescriptor : public Descriptor {
+ public:
+diff -ur vdr-1.7.9/libsi/headers.h vdr-1.7/libsi/headers.h
+--- vdr-1.7.9/libsi/headers.h 2007-02-03 11:45:58.000000000 +0000
++++ vdr-1.7/libsi/headers.h 2009-06-06 09:48:00.000000000 +0100
+@@ -1673,11 +1673,39 @@
+ u_char descriptor_length :8;
+ };
+
++struct entry_tva_id {
++ u_char tva_id_hi :8;
++ u_char tva_id_lo :8;
++#if BYTE_ORDER == BIG_ENDIAN
++ u_char reserved :5;
++ u_char running_status :3;
++#else
++ u_char running_status :3;
++ u_char reserved :5;
++#endif
++};
++
++
+ /* 0x76 content_identifier_descriptor (ETSI TS 102 323) */
+
+ struct descr_content_identifier {
+ u_char descriptor_tag :8;
+ u_char descriptor_length :8;
++#if BYTE_ORDER == BIG_ENDIAN
++ u_char crid_type :6;
++ u_char crid_location :2;
++#else
++ u_char crid_location :2;
++ u_char crid_type :6;
++#endif
++ union {
++ u_char crid_length :8;
++ u_char crid_ref_hi :8;
++ };
++ union {
++ u_char crid_byte :8;
++ u_char crid_ref_lo :8;
++ };
+ };
+
+ /* 0x77 time_slice_fec_identifier_descriptor (ETSI EN 301 192) */
+diff -ur vdr-1.7.9/libsi/si.c vdr-1.7/libsi/si.c
+--- vdr-1.7.9/libsi/si.c 2008-03-05 17:00:55.000000000 +0000
++++ vdr-1.7/libsi/si.c 2009-06-06 09:48:00.000000000 +0100
+@@ -605,6 +605,12 @@
+ case ExtensionDescriptorTag:
+ d=new ExtensionDescriptor();
+ break;
++ case ContentIdentifierDescriptorTag:
++ d=new ContentIdentifierDescriptor();
++ break;
++ case DefaultAuthorityDescriptorTag:
++ d=new DefaultAuthorityDescriptor();
++ break;
+
+ //note that it is no problem to implement one
+ //of the unimplemented descriptors.
+@@ -647,10 +653,8 @@
+ case TransportStreamDescriptorTag:
+
+ //defined in ETSI EN 300 468 v 1.7.1
+- case DefaultAuthorityDescriptorTag:
+ case RelatedContentDescriptorTag:
+ case TVAIdDescriptorTag:
+- case ContentIdentifierDescriptorTag:
+ case TimeSliceFecIdentifierDescriptorTag:
+ case ECMRepetitionRateDescriptorTag:
+ case EnhancedAC3DescriptorTag:
+diff -ur vdr-1.7.9/sdt.c vdr-1.7/sdt.c
+--- vdr-1.7.9/sdt.c 2008-04-12 14:33:55.000000000 +0100
++++ vdr-1.7/sdt.c 2009-06-06 09:48:00.000000000 +0100
+@@ -124,6 +124,12 @@
+ }
+ }
+ break;
++ case SI::DefaultAuthorityDescriptorTag: {
++ SI::DefaultAuthorityDescriptor *da = (SI::DefaultAuthorityDescriptor *)d;
++ char DaBuf[Utf8BufSize(1024)];
++ da->DefaultAuthority.getText(DaBuf, sizeof(DaBuf));
++ channel->SetDefaultAuthority(DaBuf);
++ }
+ default: ;
+ }
+ delete d;
+diff -ur vdr-1.7.9/vdr.5 vdr-1.7/vdr.5
+--- vdr-1.7.9/vdr.5 2009-01-06 12:37:35.000000000 +0000
++++ vdr-1.7/vdr.5 2009-06-06 09:48:00.000000000 +0100
+@@ -186,6 +186,9 @@
+ .B RID
+ The Radio ID of this channel (typically 0, may be used to distinguish channels where
+ NID, TID and SID are all equal).
++.TP
++.B Default Authority
++The Default Authority for CRIDs on this channel (TVAnytime).
+ .PP
+ A particular channel can be uniquely identified by its \fBchannel\ ID\fR,
+ which is a string that looks like this:
+@@ -643,6 +646,8 @@
+ \fBD\fR@<description>
+ \fBX\fR@<stream> <type> <language> <descr>
+ \fBV\fR@<vps time>
++\fBI\fR@<item CRID>
++\fBR\fR@<series CRID>
+ \fBe\fR@
+ \fBc\fR@
+ .TE
+@@ -674,6 +679,8 @@
+ <language> @is the three letter language code (optionally two codes, separated by '+')
+ <descr> @is the description of this stream component
+ <vps time> @is the Video Programming Service time of this event
++<item CRID> @is the CRID of this event (TVAnytime)
++<series CRID> @is the CRID of the series which this event is part of (TVAnytime)
+ .TE
+
+ This file will be read at program startup in order to restore the results of