From bf302580e9e60b263b0dcdc82b4a324484991465 Mon Sep 17 00:00:00 2001 From: Dave Date: Mon, 16 Jan 2012 12:55:53 +0000 Subject: Reorganised to stress 'patch' version is obsolete. Added collection of 'Suggested CRIDS. Added notification of split events. --- HISTORY | 36 ++ Makefile | 111 ++++ README | 108 ++++ README - vps | 35 ++ TODO | 13 + obsolete-patch/HISTORY | 42 ++ obsolete-patch/README | 33 ++ obsolete-patch/README - series | 24 + obsolete-patch/README - vps | 18 + obsolete-patch/TODO | 9 + obsolete-patch/series.pl | 543 +++++++++++++++++ obsolete-patch/vdrtva-1.6.0-2.diff | 534 +++++++++++++++++ obsolete-patch/vdrtva-1.7.18.diff | 482 +++++++++++++++ obsolete-patch/vdrtva-1.7.20.diff | 361 ++++++++++++ patch/HISTORY | 42 -- patch/README | 33 -- patch/README - series | 24 - patch/README - vps | 18 - patch/TODO | 9 - patch/series.pl | 543 ----------------- patch/vdrtva-1.6.0-2.diff | 534 ----------------- patch/vdrtva-1.7.18.diff | 482 --------------- patch/vdrtva-1.7.20.diff | 361 ------------ plugin/HISTORY | 31 - plugin/Makefile | 111 ---- plugin/README | 99 ---- plugin/README - vps | 33 -- plugin/TODO | 10 - plugin/vdrtva.c | 1042 -------------------------------- plugin/vdrtva.h | 128 ---- plugin/vdrvps-1.7.22.diff | 61 -- vdrtva.c | 1143 ++++++++++++++++++++++++++++++++++++ vdrtva.h | 154 +++++ vdrvps-1.7.22.diff | 61 ++ 34 files changed, 3707 insertions(+), 3561 deletions(-) create mode 100644 HISTORY create mode 100644 Makefile create mode 100644 README create mode 100644 README - vps create mode 100644 TODO create mode 100644 obsolete-patch/HISTORY create mode 100644 obsolete-patch/README create mode 100644 obsolete-patch/README - series create mode 100644 obsolete-patch/README - vps create mode 100644 obsolete-patch/TODO create mode 100755 obsolete-patch/series.pl create mode 100644 obsolete-patch/vdrtva-1.6.0-2.diff create mode 100644 obsolete-patch/vdrtva-1.7.18.diff create mode 100644 obsolete-patch/vdrtva-1.7.20.diff delete mode 100644 patch/HISTORY delete mode 100644 patch/README delete mode 100644 patch/README - series delete mode 100644 patch/README - vps delete mode 100644 patch/TODO delete mode 100755 patch/series.pl delete mode 100644 patch/vdrtva-1.6.0-2.diff delete mode 100644 patch/vdrtva-1.7.18.diff delete mode 100644 patch/vdrtva-1.7.20.diff delete mode 100644 plugin/HISTORY delete mode 100644 plugin/Makefile delete mode 100644 plugin/README delete mode 100644 plugin/README - vps delete mode 100644 plugin/TODO delete mode 100644 plugin/vdrtva.c delete mode 100644 plugin/vdrtva.h delete mode 100644 plugin/vdrvps-1.7.22.diff create mode 100644 vdrtva.c create mode 100644 vdrtva.h create mode 100644 vdrvps-1.7.22.diff diff --git a/HISTORY b/HISTORY new file mode 100644 index 0000000..fa65d43 --- /dev/null +++ b/HISTORY @@ -0,0 +1,36 @@ +VDR Plugin 'vdrtva' Revision History +------------------------------------- + +2007-10-07: Version 0.0.1 + +- Initial experimental plugin. + +2011-06-18: Version 0.0.2 + +- First functional proof-of-concept. + +2011-07-17: Version 0.0.3 + +- Handling of Links file moved from Perl script into plugin. +- Implemented config settings. + +2011-08-18: Version 0.0.4 + +- Major functions of Perl script built into plugin. +- Auto-execution of series link updates. +- Simplified internal data structures. + +2011-09-02: Version 0.0.5 + +- Added check for series links whenever a new timer is added. +- Added setup menu (not fully tested). + +2012-01-05: Version 0.0.6 +- Configuration of update time changed. +- Fixed bug in processing of updates. +- Patch version is now deprecated. + +2012-01-15: Version 0.0.7 +- Added collection of 'Suggested' CRIDs and SVDRP command to display them. +- Added notification of split events. +- Fixed (again) expiration of links. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..421a70e --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id$ + +# The official name of this plugin. +# This name will be used in the '-P...' option of VDR to load the plugin. +# By default the main source file also carries this name. +# IMPORTANT: the presence of this macro is important for the Make.config +# file. So it must be defined, even if it is not used here! +# +PLUGIN = vdrtva + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +CXXFLAGS ?= -g -O3 -Wall -Woverloaded-virtual -Wno-parentheses + +### The directory environment: + +VDRDIR = ../../.. +LIBDIR = ../../lib +TMPDIR = /tmp + +### Make sure that necessary options are included: + +include $(VDRDIR)/Make.global + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config + +### The version number of VDR's plugin API (taken from VDR's "config.h"): + +APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +### The object files (add further files here): + +OBJS = $(PLUGIN).o + +### Implicit rules: + +%.o: %.c + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +### Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ + +-include $(DEPFILE) + +### Internationalization (I18N): + +PODIR = po +LOCALEDIR = $(VDRDIR)/locale +I18Npo = $(wildcard $(PODIR)/*.po) +I18Ndirs = $(notdir $(foreach file, $(I18Npo), $(basename $(file)))) +I18Npot = $(PODIR)/$(PLUGIN).pot + +%.mo: %.po + msgfmt -c -o $@ $< + +$(I18Npot): $(wildcard *.c) + xgettext -C -cTRANSLATORS --no-wrap -F -k -ktr -ktrNOOP --msgid-bugs-address='' -o $@ $(wildcard *.c) + +$(I18Npo): $(I18Npot) + msgmerge -U --no-wrap -F --backup=none -q $@ $< + +i18n: $(I18Nmo) + @mkdir -p $(LOCALEDIR) + for i in $(I18Ndirs); do\ + mkdir -p $(LOCALEDIR)/$$i/LC_MESSAGES;\ + cp $(PODIR)/$$i.mo $(LOCALEDIR)/$$i/LC_MESSAGES/$(PLUGIN).mo;\ + done + +### Targets: + +all: libvdr-$(PLUGIN).so i18n + +libvdr-$(PLUGIN).so: $(OBJS) + $(CXX) $(CXXFLAGS) -shared $(OBJS) -o $@ + @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) + +dist: clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot diff --git a/README b/README new file mode 100644 index 0000000..fc9d9e2 --- /dev/null +++ b/README @@ -0,0 +1,108 @@ +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. See the file COPYING for more information. + +Description: + +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. The main +standard is ETSI TS 102 323. + +In the UK a subset of the TV-Anytime specification is broadcast on the DTV +service under the trade name "FreeView Plus". This plugin is written for the UK +version but should work with the full specification (untested). + +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:/// + +in which is a registered internet domain name (RFC 1034) and +is a free-format string. The section relates to the content provider +(TV channel or company), and the section to the programme. + +CRIDs are transmitted in the EIT as Content Identifier Descriptors, with +descriptor ID 0x76. To save bandwith only the section is sent, the part is taken from the Default Authority Descriptor in the SDT, and the +crid:// is assumed. + +A programme may have up to three CRIDs in its EPG entry. One identifies the +specific item of content which is being broadcast, while another identifies a +series of programmes which this item belongs to. The third type includes the +CRIDs of 'suggested' events which the viewer might be interested in. In FreeView +Plus these CRIDs have crid_type values 0x31 0x32 and 0x33 respectively +(TV-Anytime uses values 0x01 - 0x03). + +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. + +Operation: + +The use of the 'Accurate Recording' feature is described in README-vps. + +The plugin runs every 24 hours at a time set by the '-u' parameter (default +03:00). It captures CRID data for a time (10 minutes) then: + +- Checks for new manually-created timers and adds series links for them. + +- Checks each series link to see if any new events have been added to the EPG in + the same series. If so then timers are added for them. + +- Checks for timer clashes and suggests possible alternative recording times. + +- Checks that the event being recorded by each timer is the same as when the + timer was set (ie that the EPG has not changed in the meantime) + +- Flags any split events (eg a long programme with a news sumary in the middle). + At present a manual check is needed that all perts of the programme are set to + be recorded. + +The plugin logs its activity through the VDR syslog. + +The plugin has an SVDRP interface which is mainly used for debugging, but could +be used to interface with other applications. The commands are: + +LSTL Print the series links list + +LSTS Print the 'suggested' events list + +LSTY Print the CRIDs for each event + +LSTZ Print the Default Authority data for each channel + +STOP Start and stop CRID data capture +STRT + +UPDT Trigger an update of the series links. + +Points to remember: + +- Not all channels on UK Freeview have CRIDs in the EPG. Some radio channels + have item CRIDs but none have series CRIDs. + +- 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. + +- This plugin has not been tested with multiple tuner cards or with mixed DVB-T + and DVB-S setups. + +- The 'suggested' events list may contain duplicates and may have CRIDs which + do not appear in the events list. + +Although I use this software on my VDR installation, this is Alpha-quality code +- USE AT YOUR OWN RISK!! diff --git a/README - vps b/README - vps new file mode 100644 index 0000000..68aff42 --- /dev/null +++ b/README - vps @@ -0,0 +1,35 @@ +One of the important features of a video recorder is the ability to deal with +programme schedule changes and to ensure that the wanted programme is always +recorded. TVAnytime has its own set of data descriptors to handle late-running +or cancelled programmes but these are not used by Freeview Plus. Instead the +Running Status from the now/next section of the EIT is used to start and stop +the recording. + +VDR can control timers from the running status by using 'VPS' functions in +conjunction with this patch. Note however that the normal VPS operation will no +longer work. + +To use VPS for accurate recording, some conditions must be met: + +- VDR must be compiled with the 'VPS Fallback' patch (included with this + plugin). Note that some Linux distributions (eg. Mandriva) include this + patch in their binaries. +- Parameters 'UseVps' and 'VpsFallback' in setup.conf must both be set to 1 +- Timers must have the 'use VPS' flag set +- The start time of a timer must be set to exactly the scheduled start time of + the programme (ie no padding). + +With these conditions met, the sequence of events is: + +- Shortly before the scheduled time of the recording (set by 'VpsMargin' in + VDR's setup.conf) VDR switches to the multiplex containing the channel to be + recorded +- VDR monitors the running status of the programme to be recorded +- When the running status changes to 'running', VDR starts recording +- When the running status changes to 'not running', VDR stops recording and + deletes the timer. +- If the programme has not started by the time it was scheduled to finish, + monitoring is stopped and the timer is deleted. + +CAUTION - VPS recordings seem to have a lower priority than live viewing. VDR +will not interrupt live viewing to start looking for the start of a recording. diff --git a/TODO b/TODO new file mode 100644 index 0000000..97193b7 --- /dev/null +++ b/TODO @@ -0,0 +1,13 @@ +Mail a log file after the update. + +Check split recordings should create timers if necessary. + +Timer clash check code needs to cope with multiple tuner cards. + +Test GUI configuration and Config file. + +Some events have a series CRID but no item CRID - how to handle these? + +Bugs: + 'Suggestions' list contains duplicates. + Rare crash 'pure virtual method called' in plugin. diff --git a/obsolete-patch/HISTORY b/obsolete-patch/HISTORY new file mode 100644 index 0000000..9af5ce2 --- /dev/null +++ b/obsolete-patch/HISTORY @@ -0,0 +1,42 @@ +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. + +2009/11/22 - Version 0.0.6 (vdr versions 1.6.0-2 and 1.7.10) +- timers.c - Apply an old VPS patch by Anssi Hannula to use Running Status for VPS. +- series.pl - Auto-created timers use Running Status to set start/stop times. +- README-vps New file. + +2010/1/6 - Version 0.0.7 (vdr versions 1.6.0-2 and 1.7.11) +- config.c +- menu.c +- timers.c - Modify VPS patch to use a new config parameter 'VpsFallback' to determine whether to use Running Status for accurate recording. Config changes based on the Mandriva source for vdr. +- epg.c - Change the ID letters used for the CRIDs in epg.data to X and Y, because vdr now uses R for Parental Rating. Note that this will cause error messages in the log when viewing old recordings but these are harmless. +- vdr.5 - Updated man page with new CRID IDs. + +2010/5/9 - Version 0.0.8 (vdr versions 1.6.0-2 and 1.7.14) +series.pl - Ensure that all parts of split events (eg a long film with a news summary in the middle) are recorded. Warn if the title of an event in the EPG has changed since a timer was set - maybe it is no longer the same programme... + +2010/6/6- Version 0.0.9 (vdr versions 1.6.0-2 and 1.7.15) +series.pl - Don't try to process timers if there aren't any. Change default SVRDP port to 6419 in line with VDR 1.7.15. diff --git a/obsolete-patch/README b/obsolete-patch/README new file mode 100644 index 0000000..af7b373 --- /dev/null +++ b/obsolete-patch/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). The main standard is ETSI TS 102 323. + +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 (untested). + +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:/// + +in which is a registered internet domain name (RFC 1034) and is +a free-format string. The section relates to the content provider (TV +channel or company), and the section to the programme. + +CRIDs are transmitted in the EIT as Content Identifier Descriptors, with descriptor +ID 0x76. To save bandwith only the section is sent, the 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/obsolete-patch/README - series b/obsolete-patch/README - series new file mode 100644 index 0000000..6e0956b --- /dev/null +++ b/obsolete-patch/README - series @@ -0,0 +1,24 @@ +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). + +If you set the 'VPS' config parameter to '1', and also set 'UseVps = 1' and 'VpsFallack=1' in VDR's setup.conf, new timers will be created to use the EIT Running Status to set start and end times. An accurate Running Status is a requirement of the Freeview Plus specification. However if the programme starts earlier than (scheduled time - VpsMargin) the beginning will be missed. + +The script detects split events, eg a film with a news summary in the middle, and ensures that if a timer is set for one part, all parts of the programme are recorded (as separate timers). The script also checks for timer clashes, though it doesn't try to resolve the clash, and also warns if the title of an event in the EPG has changed since the timer was set (so perhaps a different programme is being broadcast). + +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 and Virgin 1). Some radio channels have item CRIDs but none have series CRIDs. + +- 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/obsolete-patch/README - vps b/obsolete-patch/README - vps new file mode 100644 index 0000000..5f4f38a --- /dev/null +++ b/obsolete-patch/README - vps @@ -0,0 +1,18 @@ +One of the important features of a video recorder is the ability to deal with programme schedule changes and to ensure that the wanted programme is always recorded. TVAnytime has its own set of data descriptors to handle late-running or cancelled programmes but these are not used by Freeview Plus. Instead the Running Status from the now/next section of the EIT is used to start and stop the recording. + +VDR can control timers from the running status by using 'VPS' functions in conjunction with this patch. Note however that the normal VPS operation will no longer work. + +To use VPS for accurate recording, some conditions must be met: + +- Parameters 'UseVps' and 'VpsFallback' in VDR's setup.conf must both be set to 1 +- Timers must have the 'use VPS' flag set in timers.conf +- The start time of a timer must be set to exactly the scheduled start time of the programme (ie no padding). + +With these conditions met, the sequence of events is: + +- Shortly before the scheduled time of the recording (set by 'VpsMargin' in VDR's setup.conf) VDR switches to the multiplex containing the channel to be recorded +- VDR monitors the running status of the programme to be recorded +- When the running status changes to 'running', VDR starts recording +- When the running status changes to 'not running', VDR stops recording and deletes the timer. + +CAUTION - VPS recordings seem to have a lower priority than live viewing. VDR will not interrupt live viewing to start looking for the start of a recording. \ No newline at end of file diff --git a/obsolete-patch/TODO b/obsolete-patch/TODO new file mode 100644 index 0000000..ac4653d --- /dev/null +++ b/obsolete-patch/TODO @@ -0,0 +1,9 @@ +- 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. + +- series.pl should be smarter; it should choose the 'best' broadcast of an event, should workaround schedule clashes, and only record a series if the user requests it. + +- When using the accurate recording function, the event will not be recorded if it starts before (scheduled_start_time - VpsMargin). \ No newline at end of file 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 + + + +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 () { + 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 () { + 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 () { + 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 () { + 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 () { + 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 () { + 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 () { +# print STDOUT $_; + last if substr($_, 3, 1) ne "-"; + } +} + +sub Error +{ + print STDERR "@_\n"; + close(SOCK); + exit 0; +} + diff --git a/obsolete-patch/vdrtva-1.6.0-2.diff b/obsolete-patch/vdrtva-1.6.0-2.diff new file mode 100644 index 0000000..a8032f4 --- /dev/null +++ b/obsolete-patch/vdrtva-1.6.0-2.diff @@ -0,0 +1,534 @@ +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, ¶mbuf, &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, ¶mbuf, &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/config.c vdr-1.5/config.c +--- vdr-1.6.0/config.c 2008-02-17 13:39:00.000000000 +0000 ++++ vdr-1.5/config.c 2009-12-31 15:11:46.000000000 +0000 +@@ -255,6 +255,7 @@ + UseSubtitle = 1; + UseVps = 0; + VpsMargin = 120; ++ VpsFallback = 0; + RecordingDirs = 1; + VideoDisplayFormat = 1; + VideoFormat = 0; +@@ -428,6 +429,7 @@ + else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); + else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); + else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); ++ else if (!strcasecmp(Name, "VpsFallback")) VpsFallback = atoi(Value); + else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); + else if (!strcasecmp(Name, "VideoDisplayFormat")) VideoDisplayFormat = atoi(Value); + else if (!strcasecmp(Name, "VideoFormat")) VideoFormat = atoi(Value); +@@ -511,6 +513,7 @@ + Store("UseSubtitle", UseSubtitle); + Store("UseVps", UseVps); + Store("VpsMargin", VpsMargin); ++ Store("VpsFallback", VpsFallback); + Store("RecordingDirs", RecordingDirs); + Store("VideoDisplayFormat", VideoDisplayFormat); + Store("VideoFormat", VideoFormat); +diff -ur vdr-1.6.0/config.h vdr-1.5/config.h +--- vdr-1.6.0/config.h 2010-01-03 19:44:47.000000000 +0000 ++++ vdr-1.5/config.h 2009-12-31 15:09:37.000000000 +0000 +@@ -237,6 +237,7 @@ + int UseSubtitle; + int UseVps; + int VpsMargin; ++ int VpsFallback; + int RecordingDirs; + int VideoDisplayFormat; + int VideoFormat; +diff -ur vdr-1.6.0/dvbdevice.h vdr-1.5/dvbdevice.h +--- vdr-1.6.0/dvbdevice.h 2008-02-08 13:48:31.000000000 +0000 ++++ vdr-1.5/dvbdevice.h 2009-05-31 16:28:58.000000000 +0100 +@@ -15,8 +15,8 @@ + #include "device.h" + #include "dvbspu.h" + +-#if DVB_API_VERSION != 3 +-#error VDR requires Linux DVB driver API version 3! ++#if (DVB_API_VERSION != 3) && (DVB_API_VERSION != 5) ++#error VDR requires Linux DVB driver API version 3 or 5! + #endif + + #define MAXDVBDEVICES 8 +diff -ur vdr-1.6.0/eit.c vdr-1.5/eit.c +--- vdr-1.6.0/eit.c 2010-01-03 19:44:41.000000000 +0000 ++++ vdr-1.5/eit.c 2008-10-14 20:10:48.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,27 @@ + } + else if (!HasExternalData) + pEvent->SetDescription(NULL); ++ if (itemCrid && (itemCrid->getCridLocation() == 0)) { ++ char buffer[Utf8BufSize(256)]; ++ char buff2[Utf8BufSize(256)]; ++ strcpy (buffer, channel->DefaultAuthority()); ++ itemCrid->entry.getText(buff2, sizeof(buff2)); ++ strcat(buffer, buff2); ++ pEvent->SetItemCRID(buffer); + } ++ if (seriesCrid && (seriesCrid->getCridLocation() == 0)) { ++ char buffer[Utf8BufSize(256)]; ++ char buff2[Utf8BufSize(256)]; ++ strcpy (buffer, channel->DefaultAuthority()); ++ seriesCrid->entry.getText(buff2, sizeof(buff2)); ++ strcat(buffer, buff2); ++ 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(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/menu.c vdr-1.5/menu.c +--- vdr-1.6.0/menu.c 2008-03-16 11:15:28.000000000 +0000 ++++ vdr-1.5/menu.c 2009-12-31 15:14:34.000000000 +0000 +@@ -2753,6 +2753,7 @@ + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); + Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Use running status as VPS fallback"), &data.VpsFallback)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); + Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); + Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); +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/timers.c vdr-1.5/timers.c +--- vdr-1.6.0/timers.c 2010-01-03 19:44:41.000000000 +0000 ++++ vdr-1.5/timers.c 2009-12-31 15:16:18.000000000 +0000 +@@ -413,7 +413,7 @@ + } + + if (HasFlags(tfActive)) { +- if (HasFlags(tfVps) && event && event->Vps()) { ++ if (HasFlags(tfVps) && event && (Setup.VpsFallback || event->Vps())) { + if (Margin || !Directly) { + startTime = event->StartTime(); + stopTime = event->EndTime(); +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@ + \fBX\fR@ + \fBV\fR@ ++\fBI\fR@ ++\fBR\fR@ + \fBe\fR@ + \fBc\fR@ + .TE +@@ -653,6 +658,8 @@ + @is the three letter language code (optionally two codes, separated by '+') + @is the description of this stream component + @is the Video Programming Service time of this event ++ @is the CRID of this event (TVAnytime) ++ @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/obsolete-patch/vdrtva-1.7.18.diff b/obsolete-patch/vdrtva-1.7.18.diff new file mode 100644 index 0000000..775ebaa --- /dev/null +++ b/obsolete-patch/vdrtva-1.7.18.diff @@ -0,0 +1,482 @@ +diff -ur vdr-1.7.18/channels.c vdr-1.7/channels.c +--- vdr-1.7.18/channels.c 2010-11-07 12:24:59.000000000 +0000 ++++ vdr-1.7/channels.c 2011-04-17 16:04:26.000000000 +0100 +@@ -61,6 +61,7 @@ + shortName = strdup(""); + provider = strdup(""); + portalName = strdup(""); ++ defaultAuthority = strdup(""); + memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__); + parameters = ""; + modification = CHANNELMOD_NONE; +@@ -75,6 +76,7 @@ + shortName = NULL; + provider = NULL; + portalName = NULL; ++ defaultAuthority = NULL; + schedule = NULL; + linkChannels = NULL; + refChannel = NULL; +@@ -103,6 +105,7 @@ + free(shortName); + free(provider); + free(portalName); ++ free(defaultAuthority); + } + + cChannel& cChannel::operator= (const cChannel &Channel) +@@ -111,6 +114,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__); + parameters = Channel.parameters; + return *this; +@@ -255,6 +259,13 @@ + } + } + ++void cChannel::SetDefaultAuthority(const char *DefaultAuthority) ++{ ++ if (!isempty(DefaultAuthority) && strcmp(defaultAuthority, DefaultAuthority) != 0) { ++ defaultAuthority = strcpyrealloc(defaultAuthority, DefaultAuthority); ++ } ++} ++ + #define STRDIFF 0x01 + #define VALDIFF 0x02 + +@@ -506,11 +517,11 @@ + q += IntArrayToString(q, Channel->dpids, 10, Channel->dlangs, Channel->dtypes); + } + *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->parameters, *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->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, Channel->tpid, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid, Channel->defaultAuthority); + } + return buffer; + } +@@ -545,13 +556,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, ¶mbuf, &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, ¶mbuf, &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; +@@ -677,12 +691,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.18/channels.h vdr-1.7/channels.h +--- vdr-1.7.18/channels.h 2010-06-05 14:12:54.000000000 +0100 ++++ vdr-1.7/channels.h 2011-04-17 16:04:26.000000000 +0100 +@@ -96,6 +96,7 @@ + char *shortName; + char *provider; + char *portalName; ++ char *defaultAuthority; + int __BeginData__; + int frequency; // MHz + int source; +@@ -141,6 +142,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 +@@ -189,6 +191,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, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, 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.18/config.c vdr-1.7/config.c +--- vdr-1.7.18/config.c 2010-06-06 11:06:43.000000000 +0100 ++++ vdr-1.7/config.c 2011-04-17 16:04:26.000000000 +0100 +@@ -353,6 +353,7 @@ + UseSubtitle = 1; + UseVps = 0; + VpsMargin = 120; ++ VpsFallback = 0; + RecordingDirs = 1; + FoldersInTimerMenu = 1; + NumberKeysForChars = 1; +@@ -544,6 +545,7 @@ + else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); + else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); + else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); ++ else if (!strcasecmp(Name, "VpsFallback")) VpsFallback = atoi(Value); + else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); + else if (!strcasecmp(Name, "FoldersInTimerMenu")) FoldersInTimerMenu = atoi(Value); + else if (!strcasecmp(Name, "NumberKeysForChars")) NumberKeysForChars = atoi(Value); +@@ -640,6 +642,7 @@ + Store("UseSubtitle", UseSubtitle); + Store("UseVps", UseVps); + Store("VpsMargin", VpsMargin); ++ Store("VpsFallback", VpsFallback); + Store("RecordingDirs", RecordingDirs); + Store("FoldersInTimerMenu", FoldersInTimerMenu); + Store("NumberKeysForChars", NumberKeysForChars); +diff -ur vdr-1.7.18/config.h vdr-1.7/config.h +--- vdr-1.7.18/config.h 2011-03-13 12:02:31.000000000 +0000 ++++ vdr-1.7/config.h 2011-04-17 16:04:26.000000000 +0100 +@@ -252,6 +252,7 @@ + int UseSubtitle; + int UseVps; + int VpsMargin; ++ int VpsFallback; + int RecordingDirs; + int FoldersInTimerMenu; + int NumberKeysForChars; +diff -ur vdr-1.7.18/eit.c vdr-1.7/eit.c +--- vdr-1.7.18/eit.c 2010-05-14 15:08:35.000000000 +0100 ++++ vdr-1.7/eit.c 2011-04-17 16:04:26.000000000 +0100 +@@ -265,6 +265,27 @@ + } + } + break; ++ case SI::ContentIdentifierDescriptorTag: { ++ SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d; ++ SI::ContentIdentifierDescriptor::Identifier cde; ++ for (SI::Loop::Iterator ite; (cd->identifierLoop.getNext(cde,ite)); ) { ++ if (cde.getCridLocation() == 0) { ++ char buffer[Utf8BufSize(256)]; ++ strcpy (buffer, channel->DefaultAuthority()); ++ strcat(buffer, cde.identifier.getText()); ++ switch (cde.getCridType()) { ++ case 0x01: ++ case 0x31: ++ pEvent->SetItemCRID(buffer); ++ break; ++ case 0x02: ++ case 0x32: ++ pEvent->SetSeriesCRID(buffer); ++ break; ++ } ++ } ++ } ++ } + default: ; + } + delete d; +diff -ur vdr-1.7.18/epg.c vdr-1.7/epg.c +--- vdr-1.7.18/epg.c 2011-02-25 15:16:05.000000000 +0000 ++++ vdr-1.7/epg.c 2011-04-17 16:04:26.000000000 +0100 +@@ -126,6 +126,8 @@ + startTime = 0; + duration = 0; + vps = 0; ++ itemCRID = NULL; ++ seriesCRID = NULL; + SetSeen(); + } + +@@ -134,6 +136,8 @@ + free(title); + free(shortText); + free(description); ++ free(itemCRID); ++ free(seriesCRID); + delete components; + } + +@@ -229,6 +233,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); +@@ -457,6 +471,10 @@ + } + if (vps) + fprintf(f, "%sV %ld\n", Prefix, vps); ++ if (!isempty(itemCRID)) ++ fprintf(f, "%sY %s\n", Prefix, itemCRID); ++ if (!isempty(seriesCRID)) ++ fprintf(f, "%sZ %s\n", Prefix, seriesCRID); + if (!InfoOnly) + fprintf(f, "%se\n", Prefix); + } +@@ -495,6 +513,10 @@ + break; + case 'V': SetVps(atoi(t)); + break; ++ case 'Y': SetItemCRID(t); ++ break; ++ case 'Z': SetSeriesCRID(t); ++ break; + default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s); + return false; + } +diff -ur vdr-1.7.18/epg.h vdr-1.7/epg.h +--- vdr-1.7.18/epg.h 2011-02-25 14:14:38.000000000 +0000 ++++ vdr-1.7/epg.h 2011-04-17 16:04:26.000000000 +0100 +@@ -86,6 +86,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(); +@@ -106,6 +108,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; +@@ -129,6 +133,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.18/libsi/descriptor.c vdr-1.7/libsi/descriptor.c +--- vdr-1.7.18/libsi/descriptor.c 2010-11-01 15:24:31.000000000 +0000 ++++ vdr-1.7/libsi/descriptor.c 2011-04-17 16:04:26.000000000 +0100 +@@ -643,6 +643,33 @@ + textualServiceIdentifier.setData(data+sizeof(descr_service_identifier), getLength()-sizeof(descr_service_identifier)); + } + ++void ContentIdentifierDescriptor::Parse() { ++ identifierLoop.setData(data+sizeof(descr_content_identifier), getLength()-sizeof(descr_content_identifier)); ++} ++ ++void ContentIdentifierDescriptor::Identifier::Parse() { ++ int offset=0; ++ data.setPointerAndOffset(s, offset); ++ if (s->crid_location == 0) { ++ identifier.setData(data+(offset-1), s->crid_length); ++ } ++ else { ++ identifier.setData(data+(offset-1), 2); ++ } ++} ++ ++int ContentIdentifierDescriptor::Identifier::getCridType() const { ++ return s->crid_type; ++} ++ ++int ContentIdentifierDescriptor::Identifier::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.18/libsi/descriptor.h vdr-1.7/libsi/descriptor.h +--- vdr-1.7.18/libsi/descriptor.h 2010-11-01 15:24:32.000000000 +0000 ++++ vdr-1.7/libsi/descriptor.h 2011-04-17 16:04:26.000000000 +0100 +@@ -361,6 +361,31 @@ + virtual void Parse(); + }; + ++class ContentIdentifierDescriptor : public Descriptor { ++public: ++ class Identifier : public LoopElement { ++ public: ++ String identifier; ++ int getCridType() const; ++ int getCridLocation() const; ++ virtual int getLength() { return sizeof(content_identifier_entry)+identifier.getLength(); } ++ protected: ++ virtual void Parse(); ++ private: ++ const content_identifier_entry *s; ++ }; ++ StructureLoop identifierLoop; ++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.18/libsi/headers.h vdr-1.7/libsi/headers.h +--- vdr-1.7.18/libsi/headers.h 2010-11-01 15:24:32.000000000 +0000 ++++ vdr-1.7/libsi/headers.h 2011-04-17 16:04:26.000000000 +0100 +@@ -1680,6 +1680,24 @@ + u_char descriptor_length :8; + }; + ++struct content_identifier_entry { ++#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) */ + + struct descr_time_slice_fec_identifier { +diff -ur vdr-1.7.18/libsi/si.c vdr-1.7/libsi/si.c +--- vdr-1.7.18/libsi/si.c 2010-11-01 15:24:32.000000000 +0000 ++++ vdr-1.7/libsi/si.c 2011-04-17 16:04:26.000000000 +0100 +@@ -609,6 +609,12 @@ + case RegistrationDescriptorTag: + d=new RegistrationDescriptor(); + 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. +@@ -650,10 +656,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.18/menu.c vdr-1.7/menu.c +--- vdr-1.7.18/menu.c 2011-02-27 12:37:48.000000000 +0000 ++++ vdr-1.7/menu.c 2011-04-17 16:04:26.000000000 +0100 +@@ -3057,6 +3057,7 @@ + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); + Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Use running status as VPS fallback"), &data.VpsFallback)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); + Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); + Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); +diff -ur vdr-1.7.18/sdt.c vdr-1.7/sdt.c +--- vdr-1.7.18/sdt.c 2010-05-16 15:23:21.000000000 +0100 ++++ vdr-1.7/sdt.c 2011-04-17 16:04:26.000000000 +0100 +@@ -128,6 +128,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.18/timers.c vdr-1.7/timers.c +--- vdr-1.7.18/timers.c 2010-01-16 11:18:53.000000000 +0000 ++++ vdr-1.7/timers.c 2011-04-17 16:04:26.000000000 +0100 +@@ -423,7 +423,7 @@ + } + + if (HasFlags(tfActive)) { +- if (HasFlags(tfVps) && event && event->Vps()) { ++ if (HasFlags(tfVps) && event && (Setup.VpsFallback || event->Vps())) { + if (Margin || !Directly) { + startTime = event->StartTime(); + stopTime = event->EndTime(); +diff -ur vdr-1.7.18/vdr.5 vdr-1.7/vdr.5 +--- vdr-1.7.18/vdr.5 2011-04-03 11:21:36.000000000 +0100 ++++ vdr-1.7/vdr.5 2011-04-17 16:04:26.000000000 +0100 +@@ -247,6 +247,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: +@@ -778,6 +781,8 @@ + \fBR\fR@ + \fBX\fR@ + \fBV\fR@ ++\fBY\fR@ ++\fBZ\fR@ + \fBe\fR@ + \fBc\fR@ + .TE +@@ -812,6 +817,8 @@ + @is the three letter language code (optionally two codes, separated by '+') + @is the description of this stream component + @is the Video Programming Service time of this event ++ @is the CRID of this event (TVAnytime) ++ @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/obsolete-patch/vdrtva-1.7.20.diff b/obsolete-patch/vdrtva-1.7.20.diff new file mode 100644 index 0000000..438d104 --- /dev/null +++ b/obsolete-patch/vdrtva-1.7.20.diff @@ -0,0 +1,361 @@ +diff -ur vdr-1.7.20/channels.c vdr-1.7.20.new/channels.c +--- vdr-1.7.20/channels.c 2010-11-07 12:24:59.000000000 +0000 ++++ vdr-1.7.20.new/channels.c 2011-08-16 18:08:26.000000000 +0100 +@@ -61,6 +61,7 @@ + shortName = strdup(""); + provider = strdup(""); + portalName = strdup(""); ++ defaultAuthority = strdup(""); + memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__); + parameters = ""; + modification = CHANNELMOD_NONE; +@@ -75,6 +76,7 @@ + shortName = NULL; + provider = NULL; + portalName = NULL; ++ defaultAuthority = NULL; + schedule = NULL; + linkChannels = NULL; + refChannel = NULL; +@@ -103,6 +105,7 @@ + free(shortName); + free(provider); + free(portalName); ++ free(defaultAuthority); + } + + cChannel& cChannel::operator= (const cChannel &Channel) +@@ -111,6 +114,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__); + parameters = Channel.parameters; + return *this; +@@ -255,6 +259,13 @@ + } + } + ++void cChannel::SetDefaultAuthority(const char *DefaultAuthority) ++{ ++ if (!isempty(DefaultAuthority) && strcmp(defaultAuthority, DefaultAuthority) != 0) { ++ defaultAuthority = strcpyrealloc(defaultAuthority, DefaultAuthority); ++ } ++} ++ + #define STRDIFF 0x01 + #define VALDIFF 0x02 + +@@ -506,11 +517,11 @@ + q += IntArrayToString(q, Channel->dpids, 10, Channel->dlangs, Channel->dtypes); + } + *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->parameters, *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->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, Channel->tpid, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid, Channel->defaultAuthority); + } + return buffer; + } +@@ -545,13 +556,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, ¶mbuf, &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, ¶mbuf, &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; +@@ -677,12 +691,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.20/channels.h vdr-1.7.20.new/channels.h +--- vdr-1.7.20/channels.h 2011-08-06 10:56:13.000000000 +0100 ++++ vdr-1.7.20.new/channels.h 2011-08-16 18:08:26.000000000 +0100 +@@ -96,6 +96,7 @@ + char *shortName; + char *provider; + char *portalName; ++ char *defaultAuthority; + int __BeginData__; + int frequency; // MHz + int source; +@@ -141,6 +142,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 +@@ -189,6 +191,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, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, 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.20/config.c vdr-1.7.20.new/config.c +--- vdr-1.7.20/config.c 2011-06-13 15:41:01.000000000 +0100 ++++ vdr-1.7.20.new/config.c 2011-08-16 18:08:26.000000000 +0100 +@@ -353,6 +353,7 @@ + UseSubtitle = 1; + UseVps = 0; + VpsMargin = 120; ++ VpsFallback = 0; + RecordingDirs = 1; + FoldersInTimerMenu = 1; + NumberKeysForChars = 1; +@@ -545,6 +546,7 @@ + else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); + else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); + else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); ++ else if (!strcasecmp(Name, "VpsFallback")) VpsFallback = atoi(Value); + else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); + else if (!strcasecmp(Name, "FoldersInTimerMenu")) FoldersInTimerMenu = atoi(Value); + else if (!strcasecmp(Name, "NumberKeysForChars")) NumberKeysForChars = atoi(Value); +@@ -641,6 +643,7 @@ + Store("UseSubtitle", UseSubtitle); + Store("UseVps", UseVps); + Store("VpsMargin", VpsMargin); ++ Store("VpsFallback", VpsFallback); + Store("RecordingDirs", RecordingDirs); + Store("FoldersInTimerMenu", FoldersInTimerMenu); + Store("NumberKeysForChars", NumberKeysForChars); +diff -ur vdr-1.7.20/config.h vdr-1.7.20.new/config.h +--- vdr-1.7.20/config.h 2011-06-21 22:43:01.000000000 +0100 ++++ vdr-1.7.20.new/config.h 2011-08-16 18:08:26.000000000 +0100 +@@ -252,6 +252,7 @@ + int UseSubtitle; + int UseVps; + int VpsMargin; ++ int VpsFallback; + int RecordingDirs; + int FoldersInTimerMenu; + int NumberKeysForChars; +diff -ur vdr-1.7.20/eit.c vdr-1.7.20.new/eit.c +--- vdr-1.7.20/eit.c 2010-05-14 15:08:35.000000000 +0100 ++++ vdr-1.7.20.new/eit.c 2011-08-16 18:08:26.000000000 +0100 +@@ -265,6 +265,33 @@ + } + } + break; ++ case SI::ContentIdentifierDescriptorTag: { ++ SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d; ++ SI::ContentIdentifierDescriptor::Identifier cde; ++ for (SI::Loop::Iterator ite; (cd->identifierLoop.getNext(cde,ite)); ) { ++ if (cde.getCridLocation() == 0) { ++ char buffer[Utf8BufSize(256)]; ++ strcpy (buffer, channel->DefaultAuthority()); ++ int i = strlen(buffer); ++ if (i) { ++ cde.identifier.getText(buffer+i, Utf8BufSize(256)-i); ++ switch (cde.getCridType()) { ++ case 0x01: // ETSI 102 363 code ++ case 0x31: // UK Freeview private code ++ pEvent->SetItemCRID(buffer); ++ break; ++ case 0x02: // ETSI 102 363 code ++ case 0x32: // UK Freeview private code ++ pEvent->SetSeriesCRID(buffer); ++ break; ++ // ETSI 102 323 defines CRID type 0x03, which describes 'related' or 'suggested' events. ++ // Freeview broadcasts these as CRID type 0x33. ++ // There can be more than one type 0x33 descriptor per event (each with one CRID). ++ } ++ } ++ } ++ } ++ } + default: ; + } + delete d; +diff -ur vdr-1.7.20/epg.c vdr-1.7.20.new/epg.c +--- vdr-1.7.20/epg.c 2011-02-25 15:16:05.000000000 +0000 ++++ vdr-1.7.20.new/epg.c 2011-08-16 18:08:26.000000000 +0100 +@@ -126,6 +126,8 @@ + startTime = 0; + duration = 0; + vps = 0; ++ itemCRID = NULL; ++ seriesCRID = NULL; + SetSeen(); + } + +@@ -134,6 +136,8 @@ + free(title); + free(shortText); + free(description); ++ free(itemCRID); ++ free(seriesCRID); + delete components; + } + +@@ -229,6 +233,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); +@@ -457,6 +471,10 @@ + } + if (vps) + fprintf(f, "%sV %ld\n", Prefix, vps); ++ if (!isempty(itemCRID)) ++ fprintf(f, "%sY %s\n", Prefix, itemCRID); ++ if (!isempty(seriesCRID)) ++ fprintf(f, "%sZ %s\n", Prefix, seriesCRID); + if (!InfoOnly) + fprintf(f, "%se\n", Prefix); + } +@@ -495,6 +513,10 @@ + break; + case 'V': SetVps(atoi(t)); + break; ++ case 'Y': SetItemCRID(t); ++ break; ++ case 'Z': SetSeriesCRID(t); ++ break; + default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s); + return false; + } +diff -ur vdr-1.7.20/epg.h vdr-1.7.20.new/epg.h +--- vdr-1.7.20/epg.h 2011-02-25 14:14:38.000000000 +0000 ++++ vdr-1.7.20.new/epg.h 2011-08-16 18:08:26.000000000 +0100 +@@ -86,6 +86,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(); +@@ -106,6 +108,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; +@@ -129,6 +133,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.20/menu.c vdr-1.7.20.new/menu.c +--- vdr-1.7.20/menu.c 2011-08-06 14:13:34.000000000 +0100 ++++ vdr-1.7.20.new/menu.c 2011-08-16 18:08:26.000000000 +0100 +@@ -3058,6 +3058,7 @@ + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); + Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Use running status as VPS fallback"), &data.VpsFallback)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); + Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); + Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); +diff -ur vdr-1.7.20/sdt.c vdr-1.7.20.new/sdt.c +--- vdr-1.7.20/sdt.c 2010-05-16 15:23:21.000000000 +0100 ++++ vdr-1.7.20.new/sdt.c 2011-08-16 18:08:26.000000000 +0100 +@@ -128,6 +128,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.20/timers.c vdr-1.7.20.new/timers.c +--- vdr-1.7.20/timers.c 2011-08-06 14:13:54.000000000 +0100 ++++ vdr-1.7.20.new/timers.c 2011-08-16 18:08:26.000000000 +0100 +@@ -430,7 +430,7 @@ + deferred = 0; + + if (HasFlags(tfActive)) { +- if (HasFlags(tfVps) && event && event->Vps()) { ++ if (HasFlags(tfVps) && event && (Setup.VpsFallback || event->Vps())) { + if (Margin || !Directly) { + startTime = event->StartTime(); + stopTime = event->EndTime(); +diff -ur vdr-1.7.20/vdr.5 vdr-1.7.20.new/vdr.5 +--- vdr-1.7.20/vdr.5 2011-04-03 11:21:36.000000000 +0100 ++++ vdr-1.7.20.new/vdr.5 2011-08-16 18:08:26.000000000 +0100 +@@ -247,6 +247,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: +@@ -778,6 +781,8 @@ + \fBR\fR@ + \fBX\fR@ + \fBV\fR@ ++\fBY\fR@ ++\fBZ\fR@ + \fBe\fR@ + \fBc\fR@ + .TE +@@ -812,6 +817,8 @@ + @is the three letter language code (optionally two codes, separated by '+') + @is the description of this stream component + @is the Video Programming Service time of this event ++ @is the CRID of this event (TVAnytime) ++ @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/patch/HISTORY b/patch/HISTORY deleted file mode 100644 index 9af5ce2..0000000 --- a/patch/HISTORY +++ /dev/null @@ -1,42 +0,0 @@ -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. - -2009/11/22 - Version 0.0.6 (vdr versions 1.6.0-2 and 1.7.10) -- timers.c - Apply an old VPS patch by Anssi Hannula to use Running Status for VPS. -- series.pl - Auto-created timers use Running Status to set start/stop times. -- README-vps New file. - -2010/1/6 - Version 0.0.7 (vdr versions 1.6.0-2 and 1.7.11) -- config.c -- menu.c -- timers.c - Modify VPS patch to use a new config parameter 'VpsFallback' to determine whether to use Running Status for accurate recording. Config changes based on the Mandriva source for vdr. -- epg.c - Change the ID letters used for the CRIDs in epg.data to X and Y, because vdr now uses R for Parental Rating. Note that this will cause error messages in the log when viewing old recordings but these are harmless. -- vdr.5 - Updated man page with new CRID IDs. - -2010/5/9 - Version 0.0.8 (vdr versions 1.6.0-2 and 1.7.14) -series.pl - Ensure that all parts of split events (eg a long film with a news summary in the middle) are recorded. Warn if the title of an event in the EPG has changed since a timer was set - maybe it is no longer the same programme... - -2010/6/6- Version 0.0.9 (vdr versions 1.6.0-2 and 1.7.15) -series.pl - Don't try to process timers if there aren't any. Change default SVRDP port to 6419 in line with VDR 1.7.15. diff --git a/patch/README b/patch/README deleted file mode 100644 index af7b373..0000000 --- a/patch/README +++ /dev/null @@ -1,33 +0,0 @@ -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). The main standard is ETSI TS 102 323. - -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 (untested). - -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:/// - -in which is a registered internet domain name (RFC 1034) and is -a free-format string. The section relates to the content provider (TV -channel or company), and the section to the programme. - -CRIDs are transmitted in the EIT as Content Identifier Descriptors, with descriptor -ID 0x76. To save bandwith only the section is sent, the 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/patch/README - series b/patch/README - series deleted file mode 100644 index 6e0956b..0000000 --- a/patch/README - series +++ /dev/null @@ -1,24 +0,0 @@ -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). - -If you set the 'VPS' config parameter to '1', and also set 'UseVps = 1' and 'VpsFallack=1' in VDR's setup.conf, new timers will be created to use the EIT Running Status to set start and end times. An accurate Running Status is a requirement of the Freeview Plus specification. However if the programme starts earlier than (scheduled time - VpsMargin) the beginning will be missed. - -The script detects split events, eg a film with a news summary in the middle, and ensures that if a timer is set for one part, all parts of the programme are recorded (as separate timers). The script also checks for timer clashes, though it doesn't try to resolve the clash, and also warns if the title of an event in the EPG has changed since the timer was set (so perhaps a different programme is being broadcast). - -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 and Virgin 1). Some radio channels have item CRIDs but none have series CRIDs. - -- 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/patch/README - vps b/patch/README - vps deleted file mode 100644 index 5f4f38a..0000000 --- a/patch/README - vps +++ /dev/null @@ -1,18 +0,0 @@ -One of the important features of a video recorder is the ability to deal with programme schedule changes and to ensure that the wanted programme is always recorded. TVAnytime has its own set of data descriptors to handle late-running or cancelled programmes but these are not used by Freeview Plus. Instead the Running Status from the now/next section of the EIT is used to start and stop the recording. - -VDR can control timers from the running status by using 'VPS' functions in conjunction with this patch. Note however that the normal VPS operation will no longer work. - -To use VPS for accurate recording, some conditions must be met: - -- Parameters 'UseVps' and 'VpsFallback' in VDR's setup.conf must both be set to 1 -- Timers must have the 'use VPS' flag set in timers.conf -- The start time of a timer must be set to exactly the scheduled start time of the programme (ie no padding). - -With these conditions met, the sequence of events is: - -- Shortly before the scheduled time of the recording (set by 'VpsMargin' in VDR's setup.conf) VDR switches to the multiplex containing the channel to be recorded -- VDR monitors the running status of the programme to be recorded -- When the running status changes to 'running', VDR starts recording -- When the running status changes to 'not running', VDR stops recording and deletes the timer. - -CAUTION - VPS recordings seem to have a lower priority than live viewing. VDR will not interrupt live viewing to start looking for the start of a recording. \ No newline at end of file diff --git a/patch/TODO b/patch/TODO deleted file mode 100644 index ac4653d..0000000 --- a/patch/TODO +++ /dev/null @@ -1,9 +0,0 @@ -- 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. - -- series.pl should be smarter; it should choose the 'best' broadcast of an event, should workaround schedule clashes, and only record a series if the user requests it. - -- When using the accurate recording function, the event will not be recorded if it starts before (scheduled_start_time - VpsMargin). \ No newline at end of file diff --git a/patch/series.pl b/patch/series.pl deleted file mode 100755 index 8423da9..0000000 --- a/patch/series.pl +++ /dev/null @@ -1,543 +0,0 @@ -#!/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 - - - -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 () { - 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 () { - 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 () { - 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 () { - 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 () { - 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 () { - 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 () { -# print STDOUT $_; - last if substr($_, 3, 1) ne "-"; - } -} - -sub Error -{ - print STDERR "@_\n"; - close(SOCK); - exit 0; -} - diff --git a/patch/vdrtva-1.6.0-2.diff b/patch/vdrtva-1.6.0-2.diff deleted file mode 100644 index a8032f4..0000000 --- a/patch/vdrtva-1.6.0-2.diff +++ /dev/null @@ -1,534 +0,0 @@ -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, ¶mbuf, &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, ¶mbuf, &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/config.c vdr-1.5/config.c ---- vdr-1.6.0/config.c 2008-02-17 13:39:00.000000000 +0000 -+++ vdr-1.5/config.c 2009-12-31 15:11:46.000000000 +0000 -@@ -255,6 +255,7 @@ - UseSubtitle = 1; - UseVps = 0; - VpsMargin = 120; -+ VpsFallback = 0; - RecordingDirs = 1; - VideoDisplayFormat = 1; - VideoFormat = 0; -@@ -428,6 +429,7 @@ - else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); - else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); - else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); -+ else if (!strcasecmp(Name, "VpsFallback")) VpsFallback = atoi(Value); - else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); - else if (!strcasecmp(Name, "VideoDisplayFormat")) VideoDisplayFormat = atoi(Value); - else if (!strcasecmp(Name, "VideoFormat")) VideoFormat = atoi(Value); -@@ -511,6 +513,7 @@ - Store("UseSubtitle", UseSubtitle); - Store("UseVps", UseVps); - Store("VpsMargin", VpsMargin); -+ Store("VpsFallback", VpsFallback); - Store("RecordingDirs", RecordingDirs); - Store("VideoDisplayFormat", VideoDisplayFormat); - Store("VideoFormat", VideoFormat); -diff -ur vdr-1.6.0/config.h vdr-1.5/config.h ---- vdr-1.6.0/config.h 2010-01-03 19:44:47.000000000 +0000 -+++ vdr-1.5/config.h 2009-12-31 15:09:37.000000000 +0000 -@@ -237,6 +237,7 @@ - int UseSubtitle; - int UseVps; - int VpsMargin; -+ int VpsFallback; - int RecordingDirs; - int VideoDisplayFormat; - int VideoFormat; -diff -ur vdr-1.6.0/dvbdevice.h vdr-1.5/dvbdevice.h ---- vdr-1.6.0/dvbdevice.h 2008-02-08 13:48:31.000000000 +0000 -+++ vdr-1.5/dvbdevice.h 2009-05-31 16:28:58.000000000 +0100 -@@ -15,8 +15,8 @@ - #include "device.h" - #include "dvbspu.h" - --#if DVB_API_VERSION != 3 --#error VDR requires Linux DVB driver API version 3! -+#if (DVB_API_VERSION != 3) && (DVB_API_VERSION != 5) -+#error VDR requires Linux DVB driver API version 3 or 5! - #endif - - #define MAXDVBDEVICES 8 -diff -ur vdr-1.6.0/eit.c vdr-1.5/eit.c ---- vdr-1.6.0/eit.c 2010-01-03 19:44:41.000000000 +0000 -+++ vdr-1.5/eit.c 2008-10-14 20:10:48.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,27 @@ - } - else if (!HasExternalData) - pEvent->SetDescription(NULL); -+ if (itemCrid && (itemCrid->getCridLocation() == 0)) { -+ char buffer[Utf8BufSize(256)]; -+ char buff2[Utf8BufSize(256)]; -+ strcpy (buffer, channel->DefaultAuthority()); -+ itemCrid->entry.getText(buff2, sizeof(buff2)); -+ strcat(buffer, buff2); -+ pEvent->SetItemCRID(buffer); - } -+ if (seriesCrid && (seriesCrid->getCridLocation() == 0)) { -+ char buffer[Utf8BufSize(256)]; -+ char buff2[Utf8BufSize(256)]; -+ strcpy (buffer, channel->DefaultAuthority()); -+ seriesCrid->entry.getText(buff2, sizeof(buff2)); -+ strcat(buffer, buff2); -+ 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(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/menu.c vdr-1.5/menu.c ---- vdr-1.6.0/menu.c 2008-03-16 11:15:28.000000000 +0000 -+++ vdr-1.5/menu.c 2009-12-31 15:14:34.000000000 +0000 -@@ -2753,6 +2753,7 @@ - Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); - Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); - Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); -+ Add(new cMenuEditBoolItem(tr("Setup.Recording$Use running status as VPS fallback"), &data.VpsFallback)); - Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); - Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); - Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); -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/timers.c vdr-1.5/timers.c ---- vdr-1.6.0/timers.c 2010-01-03 19:44:41.000000000 +0000 -+++ vdr-1.5/timers.c 2009-12-31 15:16:18.000000000 +0000 -@@ -413,7 +413,7 @@ - } - - if (HasFlags(tfActive)) { -- if (HasFlags(tfVps) && event && event->Vps()) { -+ if (HasFlags(tfVps) && event && (Setup.VpsFallback || event->Vps())) { - if (Margin || !Directly) { - startTime = event->StartTime(); - stopTime = event->EndTime(); -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@ - \fBX\fR@ - \fBV\fR@ -+\fBI\fR@ -+\fBR\fR@ - \fBe\fR@ - \fBc\fR@ - .TE -@@ -653,6 +658,8 @@ - @is the three letter language code (optionally two codes, separated by '+') - @is the description of this stream component - @is the Video Programming Service time of this event -+ @is the CRID of this event (TVAnytime) -+ @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/patch/vdrtva-1.7.18.diff b/patch/vdrtva-1.7.18.diff deleted file mode 100644 index 775ebaa..0000000 --- a/patch/vdrtva-1.7.18.diff +++ /dev/null @@ -1,482 +0,0 @@ -diff -ur vdr-1.7.18/channels.c vdr-1.7/channels.c ---- vdr-1.7.18/channels.c 2010-11-07 12:24:59.000000000 +0000 -+++ vdr-1.7/channels.c 2011-04-17 16:04:26.000000000 +0100 -@@ -61,6 +61,7 @@ - shortName = strdup(""); - provider = strdup(""); - portalName = strdup(""); -+ defaultAuthority = strdup(""); - memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__); - parameters = ""; - modification = CHANNELMOD_NONE; -@@ -75,6 +76,7 @@ - shortName = NULL; - provider = NULL; - portalName = NULL; -+ defaultAuthority = NULL; - schedule = NULL; - linkChannels = NULL; - refChannel = NULL; -@@ -103,6 +105,7 @@ - free(shortName); - free(provider); - free(portalName); -+ free(defaultAuthority); - } - - cChannel& cChannel::operator= (const cChannel &Channel) -@@ -111,6 +114,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__); - parameters = Channel.parameters; - return *this; -@@ -255,6 +259,13 @@ - } - } - -+void cChannel::SetDefaultAuthority(const char *DefaultAuthority) -+{ -+ if (!isempty(DefaultAuthority) && strcmp(defaultAuthority, DefaultAuthority) != 0) { -+ defaultAuthority = strcpyrealloc(defaultAuthority, DefaultAuthority); -+ } -+} -+ - #define STRDIFF 0x01 - #define VALDIFF 0x02 - -@@ -506,11 +517,11 @@ - q += IntArrayToString(q, Channel->dpids, 10, Channel->dlangs, Channel->dtypes); - } - *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->parameters, *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->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, Channel->tpid, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid, Channel->defaultAuthority); - } - return buffer; - } -@@ -545,13 +556,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, ¶mbuf, &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, ¶mbuf, &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; -@@ -677,12 +691,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.18/channels.h vdr-1.7/channels.h ---- vdr-1.7.18/channels.h 2010-06-05 14:12:54.000000000 +0100 -+++ vdr-1.7/channels.h 2011-04-17 16:04:26.000000000 +0100 -@@ -96,6 +96,7 @@ - char *shortName; - char *provider; - char *portalName; -+ char *defaultAuthority; - int __BeginData__; - int frequency; // MHz - int source; -@@ -141,6 +142,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 -@@ -189,6 +191,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, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, 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.18/config.c vdr-1.7/config.c ---- vdr-1.7.18/config.c 2010-06-06 11:06:43.000000000 +0100 -+++ vdr-1.7/config.c 2011-04-17 16:04:26.000000000 +0100 -@@ -353,6 +353,7 @@ - UseSubtitle = 1; - UseVps = 0; - VpsMargin = 120; -+ VpsFallback = 0; - RecordingDirs = 1; - FoldersInTimerMenu = 1; - NumberKeysForChars = 1; -@@ -544,6 +545,7 @@ - else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); - else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); - else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); -+ else if (!strcasecmp(Name, "VpsFallback")) VpsFallback = atoi(Value); - else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); - else if (!strcasecmp(Name, "FoldersInTimerMenu")) FoldersInTimerMenu = atoi(Value); - else if (!strcasecmp(Name, "NumberKeysForChars")) NumberKeysForChars = atoi(Value); -@@ -640,6 +642,7 @@ - Store("UseSubtitle", UseSubtitle); - Store("UseVps", UseVps); - Store("VpsMargin", VpsMargin); -+ Store("VpsFallback", VpsFallback); - Store("RecordingDirs", RecordingDirs); - Store("FoldersInTimerMenu", FoldersInTimerMenu); - Store("NumberKeysForChars", NumberKeysForChars); -diff -ur vdr-1.7.18/config.h vdr-1.7/config.h ---- vdr-1.7.18/config.h 2011-03-13 12:02:31.000000000 +0000 -+++ vdr-1.7/config.h 2011-04-17 16:04:26.000000000 +0100 -@@ -252,6 +252,7 @@ - int UseSubtitle; - int UseVps; - int VpsMargin; -+ int VpsFallback; - int RecordingDirs; - int FoldersInTimerMenu; - int NumberKeysForChars; -diff -ur vdr-1.7.18/eit.c vdr-1.7/eit.c ---- vdr-1.7.18/eit.c 2010-05-14 15:08:35.000000000 +0100 -+++ vdr-1.7/eit.c 2011-04-17 16:04:26.000000000 +0100 -@@ -265,6 +265,27 @@ - } - } - break; -+ case SI::ContentIdentifierDescriptorTag: { -+ SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d; -+ SI::ContentIdentifierDescriptor::Identifier cde; -+ for (SI::Loop::Iterator ite; (cd->identifierLoop.getNext(cde,ite)); ) { -+ if (cde.getCridLocation() == 0) { -+ char buffer[Utf8BufSize(256)]; -+ strcpy (buffer, channel->DefaultAuthority()); -+ strcat(buffer, cde.identifier.getText()); -+ switch (cde.getCridType()) { -+ case 0x01: -+ case 0x31: -+ pEvent->SetItemCRID(buffer); -+ break; -+ case 0x02: -+ case 0x32: -+ pEvent->SetSeriesCRID(buffer); -+ break; -+ } -+ } -+ } -+ } - default: ; - } - delete d; -diff -ur vdr-1.7.18/epg.c vdr-1.7/epg.c ---- vdr-1.7.18/epg.c 2011-02-25 15:16:05.000000000 +0000 -+++ vdr-1.7/epg.c 2011-04-17 16:04:26.000000000 +0100 -@@ -126,6 +126,8 @@ - startTime = 0; - duration = 0; - vps = 0; -+ itemCRID = NULL; -+ seriesCRID = NULL; - SetSeen(); - } - -@@ -134,6 +136,8 @@ - free(title); - free(shortText); - free(description); -+ free(itemCRID); -+ free(seriesCRID); - delete components; - } - -@@ -229,6 +233,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); -@@ -457,6 +471,10 @@ - } - if (vps) - fprintf(f, "%sV %ld\n", Prefix, vps); -+ if (!isempty(itemCRID)) -+ fprintf(f, "%sY %s\n", Prefix, itemCRID); -+ if (!isempty(seriesCRID)) -+ fprintf(f, "%sZ %s\n", Prefix, seriesCRID); - if (!InfoOnly) - fprintf(f, "%se\n", Prefix); - } -@@ -495,6 +513,10 @@ - break; - case 'V': SetVps(atoi(t)); - break; -+ case 'Y': SetItemCRID(t); -+ break; -+ case 'Z': SetSeriesCRID(t); -+ break; - default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s); - return false; - } -diff -ur vdr-1.7.18/epg.h vdr-1.7/epg.h ---- vdr-1.7.18/epg.h 2011-02-25 14:14:38.000000000 +0000 -+++ vdr-1.7/epg.h 2011-04-17 16:04:26.000000000 +0100 -@@ -86,6 +86,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(); -@@ -106,6 +108,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; -@@ -129,6 +133,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.18/libsi/descriptor.c vdr-1.7/libsi/descriptor.c ---- vdr-1.7.18/libsi/descriptor.c 2010-11-01 15:24:31.000000000 +0000 -+++ vdr-1.7/libsi/descriptor.c 2011-04-17 16:04:26.000000000 +0100 -@@ -643,6 +643,33 @@ - textualServiceIdentifier.setData(data+sizeof(descr_service_identifier), getLength()-sizeof(descr_service_identifier)); - } - -+void ContentIdentifierDescriptor::Parse() { -+ identifierLoop.setData(data+sizeof(descr_content_identifier), getLength()-sizeof(descr_content_identifier)); -+} -+ -+void ContentIdentifierDescriptor::Identifier::Parse() { -+ int offset=0; -+ data.setPointerAndOffset(s, offset); -+ if (s->crid_location == 0) { -+ identifier.setData(data+(offset-1), s->crid_length); -+ } -+ else { -+ identifier.setData(data+(offset-1), 2); -+ } -+} -+ -+int ContentIdentifierDescriptor::Identifier::getCridType() const { -+ return s->crid_type; -+} -+ -+int ContentIdentifierDescriptor::Identifier::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.18/libsi/descriptor.h vdr-1.7/libsi/descriptor.h ---- vdr-1.7.18/libsi/descriptor.h 2010-11-01 15:24:32.000000000 +0000 -+++ vdr-1.7/libsi/descriptor.h 2011-04-17 16:04:26.000000000 +0100 -@@ -361,6 +361,31 @@ - virtual void Parse(); - }; - -+class ContentIdentifierDescriptor : public Descriptor { -+public: -+ class Identifier : public LoopElement { -+ public: -+ String identifier; -+ int getCridType() const; -+ int getCridLocation() const; -+ virtual int getLength() { return sizeof(content_identifier_entry)+identifier.getLength(); } -+ protected: -+ virtual void Parse(); -+ private: -+ const content_identifier_entry *s; -+ }; -+ StructureLoop identifierLoop; -+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.18/libsi/headers.h vdr-1.7/libsi/headers.h ---- vdr-1.7.18/libsi/headers.h 2010-11-01 15:24:32.000000000 +0000 -+++ vdr-1.7/libsi/headers.h 2011-04-17 16:04:26.000000000 +0100 -@@ -1680,6 +1680,24 @@ - u_char descriptor_length :8; - }; - -+struct content_identifier_entry { -+#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) */ - - struct descr_time_slice_fec_identifier { -diff -ur vdr-1.7.18/libsi/si.c vdr-1.7/libsi/si.c ---- vdr-1.7.18/libsi/si.c 2010-11-01 15:24:32.000000000 +0000 -+++ vdr-1.7/libsi/si.c 2011-04-17 16:04:26.000000000 +0100 -@@ -609,6 +609,12 @@ - case RegistrationDescriptorTag: - d=new RegistrationDescriptor(); - 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. -@@ -650,10 +656,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.18/menu.c vdr-1.7/menu.c ---- vdr-1.7.18/menu.c 2011-02-27 12:37:48.000000000 +0000 -+++ vdr-1.7/menu.c 2011-04-17 16:04:26.000000000 +0100 -@@ -3057,6 +3057,7 @@ - Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); - Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); - Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); -+ Add(new cMenuEditBoolItem(tr("Setup.Recording$Use running status as VPS fallback"), &data.VpsFallback)); - Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); - Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); - Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); -diff -ur vdr-1.7.18/sdt.c vdr-1.7/sdt.c ---- vdr-1.7.18/sdt.c 2010-05-16 15:23:21.000000000 +0100 -+++ vdr-1.7/sdt.c 2011-04-17 16:04:26.000000000 +0100 -@@ -128,6 +128,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.18/timers.c vdr-1.7/timers.c ---- vdr-1.7.18/timers.c 2010-01-16 11:18:53.000000000 +0000 -+++ vdr-1.7/timers.c 2011-04-17 16:04:26.000000000 +0100 -@@ -423,7 +423,7 @@ - } - - if (HasFlags(tfActive)) { -- if (HasFlags(tfVps) && event && event->Vps()) { -+ if (HasFlags(tfVps) && event && (Setup.VpsFallback || event->Vps())) { - if (Margin || !Directly) { - startTime = event->StartTime(); - stopTime = event->EndTime(); -diff -ur vdr-1.7.18/vdr.5 vdr-1.7/vdr.5 ---- vdr-1.7.18/vdr.5 2011-04-03 11:21:36.000000000 +0100 -+++ vdr-1.7/vdr.5 2011-04-17 16:04:26.000000000 +0100 -@@ -247,6 +247,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: -@@ -778,6 +781,8 @@ - \fBR\fR@ - \fBX\fR@ - \fBV\fR@ -+\fBY\fR@ -+\fBZ\fR@ - \fBe\fR@ - \fBc\fR@ - .TE -@@ -812,6 +817,8 @@ - @is the three letter language code (optionally two codes, separated by '+') - @is the description of this stream component - @is the Video Programming Service time of this event -+ @is the CRID of this event (TVAnytime) -+ @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/patch/vdrtva-1.7.20.diff b/patch/vdrtva-1.7.20.diff deleted file mode 100644 index 438d104..0000000 --- a/patch/vdrtva-1.7.20.diff +++ /dev/null @@ -1,361 +0,0 @@ -diff -ur vdr-1.7.20/channels.c vdr-1.7.20.new/channels.c ---- vdr-1.7.20/channels.c 2010-11-07 12:24:59.000000000 +0000 -+++ vdr-1.7.20.new/channels.c 2011-08-16 18:08:26.000000000 +0100 -@@ -61,6 +61,7 @@ - shortName = strdup(""); - provider = strdup(""); - portalName = strdup(""); -+ defaultAuthority = strdup(""); - memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__); - parameters = ""; - modification = CHANNELMOD_NONE; -@@ -75,6 +76,7 @@ - shortName = NULL; - provider = NULL; - portalName = NULL; -+ defaultAuthority = NULL; - schedule = NULL; - linkChannels = NULL; - refChannel = NULL; -@@ -103,6 +105,7 @@ - free(shortName); - free(provider); - free(portalName); -+ free(defaultAuthority); - } - - cChannel& cChannel::operator= (const cChannel &Channel) -@@ -111,6 +114,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__); - parameters = Channel.parameters; - return *this; -@@ -255,6 +259,13 @@ - } - } - -+void cChannel::SetDefaultAuthority(const char *DefaultAuthority) -+{ -+ if (!isempty(DefaultAuthority) && strcmp(defaultAuthority, DefaultAuthority) != 0) { -+ defaultAuthority = strcpyrealloc(defaultAuthority, DefaultAuthority); -+ } -+} -+ - #define STRDIFF 0x01 - #define VALDIFF 0x02 - -@@ -506,11 +517,11 @@ - q += IntArrayToString(q, Channel->dpids, 10, Channel->dlangs, Channel->dtypes); - } - *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->parameters, *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->parameters, *cSource::ToString(Channel->source), Channel->srate, vpidbuf, apidbuf, Channel->tpid, caidbuf, Channel->sid, Channel->nid, Channel->tid, Channel->rid, Channel->defaultAuthority); - } - return buffer; - } -@@ -545,13 +556,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, ¶mbuf, &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, ¶mbuf, &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; -@@ -677,12 +691,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.20/channels.h vdr-1.7.20.new/channels.h ---- vdr-1.7.20/channels.h 2011-08-06 10:56:13.000000000 +0100 -+++ vdr-1.7.20.new/channels.h 2011-08-16 18:08:26.000000000 +0100 -@@ -96,6 +96,7 @@ - char *shortName; - char *provider; - char *portalName; -+ char *defaultAuthority; - int __BeginData__; - int frequency; // MHz - int source; -@@ -141,6 +142,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 -@@ -189,6 +191,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, int *Atypes, char ALangs[][MAXLANGCODE2], int *Dpids, int *Dtypes, 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.20/config.c vdr-1.7.20.new/config.c ---- vdr-1.7.20/config.c 2011-06-13 15:41:01.000000000 +0100 -+++ vdr-1.7.20.new/config.c 2011-08-16 18:08:26.000000000 +0100 -@@ -353,6 +353,7 @@ - UseSubtitle = 1; - UseVps = 0; - VpsMargin = 120; -+ VpsFallback = 0; - RecordingDirs = 1; - FoldersInTimerMenu = 1; - NumberKeysForChars = 1; -@@ -545,6 +546,7 @@ - else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); - else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); - else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); -+ else if (!strcasecmp(Name, "VpsFallback")) VpsFallback = atoi(Value); - else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); - else if (!strcasecmp(Name, "FoldersInTimerMenu")) FoldersInTimerMenu = atoi(Value); - else if (!strcasecmp(Name, "NumberKeysForChars")) NumberKeysForChars = atoi(Value); -@@ -641,6 +643,7 @@ - Store("UseSubtitle", UseSubtitle); - Store("UseVps", UseVps); - Store("VpsMargin", VpsMargin); -+ Store("VpsFallback", VpsFallback); - Store("RecordingDirs", RecordingDirs); - Store("FoldersInTimerMenu", FoldersInTimerMenu); - Store("NumberKeysForChars", NumberKeysForChars); -diff -ur vdr-1.7.20/config.h vdr-1.7.20.new/config.h ---- vdr-1.7.20/config.h 2011-06-21 22:43:01.000000000 +0100 -+++ vdr-1.7.20.new/config.h 2011-08-16 18:08:26.000000000 +0100 -@@ -252,6 +252,7 @@ - int UseSubtitle; - int UseVps; - int VpsMargin; -+ int VpsFallback; - int RecordingDirs; - int FoldersInTimerMenu; - int NumberKeysForChars; -diff -ur vdr-1.7.20/eit.c vdr-1.7.20.new/eit.c ---- vdr-1.7.20/eit.c 2010-05-14 15:08:35.000000000 +0100 -+++ vdr-1.7.20.new/eit.c 2011-08-16 18:08:26.000000000 +0100 -@@ -265,6 +265,33 @@ - } - } - break; -+ case SI::ContentIdentifierDescriptorTag: { -+ SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d; -+ SI::ContentIdentifierDescriptor::Identifier cde; -+ for (SI::Loop::Iterator ite; (cd->identifierLoop.getNext(cde,ite)); ) { -+ if (cde.getCridLocation() == 0) { -+ char buffer[Utf8BufSize(256)]; -+ strcpy (buffer, channel->DefaultAuthority()); -+ int i = strlen(buffer); -+ if (i) { -+ cde.identifier.getText(buffer+i, Utf8BufSize(256)-i); -+ switch (cde.getCridType()) { -+ case 0x01: // ETSI 102 363 code -+ case 0x31: // UK Freeview private code -+ pEvent->SetItemCRID(buffer); -+ break; -+ case 0x02: // ETSI 102 363 code -+ case 0x32: // UK Freeview private code -+ pEvent->SetSeriesCRID(buffer); -+ break; -+ // ETSI 102 323 defines CRID type 0x03, which describes 'related' or 'suggested' events. -+ // Freeview broadcasts these as CRID type 0x33. -+ // There can be more than one type 0x33 descriptor per event (each with one CRID). -+ } -+ } -+ } -+ } -+ } - default: ; - } - delete d; -diff -ur vdr-1.7.20/epg.c vdr-1.7.20.new/epg.c ---- vdr-1.7.20/epg.c 2011-02-25 15:16:05.000000000 +0000 -+++ vdr-1.7.20.new/epg.c 2011-08-16 18:08:26.000000000 +0100 -@@ -126,6 +126,8 @@ - startTime = 0; - duration = 0; - vps = 0; -+ itemCRID = NULL; -+ seriesCRID = NULL; - SetSeen(); - } - -@@ -134,6 +136,8 @@ - free(title); - free(shortText); - free(description); -+ free(itemCRID); -+ free(seriesCRID); - delete components; - } - -@@ -229,6 +233,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); -@@ -457,6 +471,10 @@ - } - if (vps) - fprintf(f, "%sV %ld\n", Prefix, vps); -+ if (!isempty(itemCRID)) -+ fprintf(f, "%sY %s\n", Prefix, itemCRID); -+ if (!isempty(seriesCRID)) -+ fprintf(f, "%sZ %s\n", Prefix, seriesCRID); - if (!InfoOnly) - fprintf(f, "%se\n", Prefix); - } -@@ -495,6 +513,10 @@ - break; - case 'V': SetVps(atoi(t)); - break; -+ case 'Y': SetItemCRID(t); -+ break; -+ case 'Z': SetSeriesCRID(t); -+ break; - default: esyslog("ERROR: unexpected tag while reading EPG data: %s", s); - return false; - } -diff -ur vdr-1.7.20/epg.h vdr-1.7.20.new/epg.h ---- vdr-1.7.20/epg.h 2011-02-25 14:14:38.000000000 +0000 -+++ vdr-1.7.20.new/epg.h 2011-08-16 18:08:26.000000000 +0100 -@@ -86,6 +86,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(); -@@ -106,6 +108,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; -@@ -129,6 +133,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.20/menu.c vdr-1.7.20.new/menu.c ---- vdr-1.7.20/menu.c 2011-08-06 14:13:34.000000000 +0100 -+++ vdr-1.7.20.new/menu.c 2011-08-16 18:08:26.000000000 +0100 -@@ -3058,6 +3058,7 @@ - Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); - Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); - Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); -+ Add(new cMenuEditBoolItem(tr("Setup.Recording$Use running status as VPS fallback"), &data.VpsFallback)); - Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); - Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); - Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); -diff -ur vdr-1.7.20/sdt.c vdr-1.7.20.new/sdt.c ---- vdr-1.7.20/sdt.c 2010-05-16 15:23:21.000000000 +0100 -+++ vdr-1.7.20.new/sdt.c 2011-08-16 18:08:26.000000000 +0100 -@@ -128,6 +128,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.20/timers.c vdr-1.7.20.new/timers.c ---- vdr-1.7.20/timers.c 2011-08-06 14:13:54.000000000 +0100 -+++ vdr-1.7.20.new/timers.c 2011-08-16 18:08:26.000000000 +0100 -@@ -430,7 +430,7 @@ - deferred = 0; - - if (HasFlags(tfActive)) { -- if (HasFlags(tfVps) && event && event->Vps()) { -+ if (HasFlags(tfVps) && event && (Setup.VpsFallback || event->Vps())) { - if (Margin || !Directly) { - startTime = event->StartTime(); - stopTime = event->EndTime(); -diff -ur vdr-1.7.20/vdr.5 vdr-1.7.20.new/vdr.5 ---- vdr-1.7.20/vdr.5 2011-04-03 11:21:36.000000000 +0100 -+++ vdr-1.7.20.new/vdr.5 2011-08-16 18:08:26.000000000 +0100 -@@ -247,6 +247,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: -@@ -778,6 +781,8 @@ - \fBR\fR@ - \fBX\fR@ - \fBV\fR@ -+\fBY\fR@ -+\fBZ\fR@ - \fBe\fR@ - \fBc\fR@ - .TE -@@ -812,6 +817,8 @@ - @is the three letter language code (optionally two codes, separated by '+') - @is the description of this stream component - @is the Video Programming Service time of this event -+ @is the CRID of this event (TVAnytime) -+ @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/plugin/HISTORY b/plugin/HISTORY deleted file mode 100644 index 93a7782..0000000 --- a/plugin/HISTORY +++ /dev/null @@ -1,31 +0,0 @@ -VDR Plugin 'vdrtva' Revision History -------------------------------------- - -2007-10-07: Version 0.0.1 - -- Initial experimental plugin. - -2011-06-18: Version 0.0.2 - -- First functional proof-of-concept. - -2011-07-17: Version 0.0.3 - -- Handling of Links file moved from Perl script into plugin. -- Implemented config settings. - -2011-08-18: Version 0.0.4 - -- Major functions of Perl script built into plugin. -- Auto-execution of series link updates. -- Simplified internal data structures. - -2011-09-02: Version 0.0.5 - -- Added check for series links whenever a new timer is added. -- Added setup menu (not fully tested). - -2012-01-05 Version 0.0.6 -- Configuration of update time changed. -- Fixed bug in processing of updates. -- Patch version is now deprecated. diff --git a/plugin/Makefile b/plugin/Makefile deleted file mode 100644 index 421a70e..0000000 --- a/plugin/Makefile +++ /dev/null @@ -1,111 +0,0 @@ -# -# Makefile for a Video Disk Recorder plugin -# -# $Id$ - -# The official name of this plugin. -# This name will be used in the '-P...' option of VDR to load the plugin. -# By default the main source file also carries this name. -# IMPORTANT: the presence of this macro is important for the Make.config -# file. So it must be defined, even if it is not used here! -# -PLUGIN = vdrtva - -### The version number of this plugin (taken from the main source file): - -VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g') - -### The C++ compiler and options: - -CXX ?= g++ -CXXFLAGS ?= -g -O3 -Wall -Woverloaded-virtual -Wno-parentheses - -### The directory environment: - -VDRDIR = ../../.. -LIBDIR = ../../lib -TMPDIR = /tmp - -### Make sure that necessary options are included: - -include $(VDRDIR)/Make.global - -### Allow user defined options to overwrite defaults: - --include $(VDRDIR)/Make.config - -### The version number of VDR's plugin API (taken from VDR's "config.h"): - -APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) - -### The name of the distribution archive: - -ARCHIVE = $(PLUGIN)-$(VERSION) -PACKAGE = vdr-$(ARCHIVE) - -### Includes and Defines (add further entries here): - -INCLUDES += -I$(VDRDIR)/include - -DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' - -### The object files (add further files here): - -OBJS = $(PLUGIN).o - -### Implicit rules: - -%.o: %.c - $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< - -### Dependencies: - -MAKEDEP = $(CXX) -MM -MG -DEPFILE = .dependencies -$(DEPFILE): Makefile - @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ - --include $(DEPFILE) - -### Internationalization (I18N): - -PODIR = po -LOCALEDIR = $(VDRDIR)/locale -I18Npo = $(wildcard $(PODIR)/*.po) -I18Ndirs = $(notdir $(foreach file, $(I18Npo), $(basename $(file)))) -I18Npot = $(PODIR)/$(PLUGIN).pot - -%.mo: %.po - msgfmt -c -o $@ $< - -$(I18Npot): $(wildcard *.c) - xgettext -C -cTRANSLATORS --no-wrap -F -k -ktr -ktrNOOP --msgid-bugs-address='' -o $@ $(wildcard *.c) - -$(I18Npo): $(I18Npot) - msgmerge -U --no-wrap -F --backup=none -q $@ $< - -i18n: $(I18Nmo) - @mkdir -p $(LOCALEDIR) - for i in $(I18Ndirs); do\ - mkdir -p $(LOCALEDIR)/$$i/LC_MESSAGES;\ - cp $(PODIR)/$$i.mo $(LOCALEDIR)/$$i/LC_MESSAGES/$(PLUGIN).mo;\ - done - -### Targets: - -all: libvdr-$(PLUGIN).so i18n - -libvdr-$(PLUGIN).so: $(OBJS) - $(CXX) $(CXXFLAGS) -shared $(OBJS) -o $@ - @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) - -dist: clean - @-rm -rf $(TMPDIR)/$(ARCHIVE) - @mkdir $(TMPDIR)/$(ARCHIVE) - @cp -a * $(TMPDIR)/$(ARCHIVE) - @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) - @-rm -rf $(TMPDIR)/$(ARCHIVE) - @echo Distribution package created as $(PACKAGE).tgz - -clean: - @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot diff --git a/plugin/README b/plugin/README deleted file mode 100644 index 7a9b012..0000000 --- a/plugin/README +++ /dev/null @@ -1,99 +0,0 @@ -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. See the file COPYING for more information. - -Description: - -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. The main -standard is ETSI TS 102 323. - -In the UK a subset of the TV-Anytime specification is broadcast on the DTV -service under the trade name "FreeView Plus". This plugin is written for the UK -version but should work with the full specification (untested). - -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:/// - -in which is a registered internet domain name (RFC 1034) and -is a free-format string. The section relates to the content provider -(TV channel or company), and the section to the programme. - -CRIDs are transmitted in the EIT as Content Identifier Descriptors, with -descriptor ID 0x76. To save bandwith only the section is sent, the 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. - -Operation: - -The use of the 'Accurate Recording' feature is described in README-vps. - -The plugin runs every 24 hours at a time set by the '-u' parameter (default -03:00). It captures CRID data for a time (10 minutes) then: - -- Checks for new manually-created timers and adds series links for them. - -- Checks each series link to see if any new events have been added to the EPG in - the same series. If so then timers are added for them. - -- Checks for timer clashes and suggests possible alternative recording times. - -- Checks that the event being recorded by each timer is the same as when the - timer was set (ie that the EPG has not changed in the meantime) - -The plugin logs its activity through the VDR syslog. - -The plugin has an SVDRP interface which is mainly used for debugging, but could -be used to interface with other applications. The commands are: - -LSTL Print the series links list - -LSTY Print the CRIDs for each event - -LSTZ Print the Default Authority data for each channel - -STOP Start and stop CRID data capture -STRT - -UPDT Trigger an update of the series links. - -Points to remember: - -- Not all channels on UK Freeview have CRIDs in the EPG Some radio channels - have item CRIDs but none have series CRIDs. - -- 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. - -- This script has not been tested with multiple tuner cards or with mixed DVB-T - and DVB-S setups. - - -Although I use this software on my VDR installation, this is Alpha-quality code -- USE AT YOUR OWN RISK!! diff --git a/plugin/README - vps b/plugin/README - vps deleted file mode 100644 index 8d172c2..0000000 --- a/plugin/README - vps +++ /dev/null @@ -1,33 +0,0 @@ -One of the important features of a video recorder is the ability to deal with -programme schedule changes and to ensure that the wanted programme is always -recorded. TVAnytime has its own set of data descriptors to handle late-running -or cancelled programmes but these are not used by Freeview Plus. Instead the -Running Status from the now/next section of the EIT is used to start and stop -the recording. - -VDR can control timers from the running status by using 'VPS' functions in -conjunction with this patch. Note however that the normal VPS operation will no -longer work. - -To use VPS for accurate recording, some conditions must be met: - -- VDR must be compiled with the 'VPS Fallback' patch (included with this - plugin). Note that some Linux distributions (esp. Mandriva) include this - patch in their binaries. -- Parameters 'UseVps' and 'VpsFallback' in setup.conf must both be set to 1 -- Timers must have the 'use VPS' flag set -- The start time of a timer must be set to exactly the scheduled start time of - the programme (ie no padding). - -With these conditions met, the sequence of events is: - -- Shortly before the scheduled time of the recording (set by 'VpsMargin' in - VDR's setup.conf) VDR switches to the multiplex containing the channel to be - recorded -- VDR monitors the running status of the programme to be recorded -- When the running status changes to 'running', VDR starts recording -- When the running status changes to 'not running', VDR stops recording and - deletes the timer. - -CAUTION - VPS recordings seem to have a lower priority than live viewing. VDR -will not interrupt live viewing to start looking for the start of a recording. \ No newline at end of file diff --git a/plugin/TODO b/plugin/TODO deleted file mode 100644 index 9b9a913..0000000 --- a/plugin/TODO +++ /dev/null @@ -1,10 +0,0 @@ -Handle CRID 'suggestions' via new SVDRP command. - -Mail a log file after the update. - -Missing functions from Perl script: - Check split recordings - -Timer clash check code needs to cope with multiple tuner cards. - -Config file diff --git a/plugin/vdrtva.c b/plugin/vdrtva.c deleted file mode 100644 index bfc45cf..0000000 --- a/plugin/vdrtva.c +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * vdrtva.c: A plugin for the Video Disk Recorder - * - * See the README file for copyright information and how to reach the author. - * - * $Id$ - */ - -#include -#include -#include -#include -#include - -#include "vdrtva.h" - -#define SVDRPOSD_BUFSIZE KILOBYTE(4) -#define SECONDSPERDAY (86400) - -cChanDAs *ChanDAs; -cEventCRIDs *EventCRIDs; -cLinks *Links; - -static const char *VERSION = "0.0.6"; -static const char *DESCRIPTION = "TV-Anytime plugin"; -static const char *MAINMENUENTRY = "vdrTva"; - -int collectionperiod; // Time to collect all CRID data (default 10 minutes) -int lifetime; // Lifetime of series link recordings (default 99) -int priority; // Priority of series link recordings (default 99) -int seriesLifetime; // Expiry time of a series link (default 30 days) -int updatetime; // Time to carry out the series link update HHMM (default 03:00) - - - -class cPluginvdrTva : public cPlugin { -private: - // Add any member variables or functions you may need here. - int length; - int size; - int flags; - int state; - time_t nextactiontime; - char *buffer; - char* configDir; - cTvaFilter *Filter; - cTvaStatusMonitor *statusMonitor; - bool Append(const char *Fmt, ...); - bool AppendItems(const char* Option); - const char* Reply(); - bool AddSeriesLink(const char *scrid, int modtime, const char *icrid); - void LoadLinksFile(void); - bool SaveLinksFile(void); - bool UpdateLinksFromTimers(void); - bool AddNewEventsToSeries(void); - void CheckChangedEvents(void); - void CheckTimerClashes(void); - void FindAlternatives(const cEvent *event); - void StartDataCapture(void); - void StopDataCapture(void); - void Update(void); - void Check(void); -// void Reset(); -public: - cPluginvdrTva(void); - virtual ~cPluginvdrTva(); - virtual const char *Version(void) { return VERSION; } - virtual const char *Description(void) { return tr(DESCRIPTION); } - virtual const char *CommandLineHelp(void); - virtual bool ProcessArgs(int argc, char *argv[]); - virtual bool Initialize(void); - virtual bool Start(void); - virtual void Stop(void); - virtual void Housekeeping(void); - virtual void MainThreadHook(void); - virtual cString Active(void); - virtual time_t WakeupTime(void); - virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; } - virtual cOsdObject *MainMenuAction(void); - virtual cMenuSetupPage *SetupMenu(void); - virtual bool SetupParse(const char *Name, const char *Value); - virtual bool Service(const char *Id, void *Data = NULL); - virtual const char **SVDRPHelpPages(void); - virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); - }; - -cPluginvdrTva::cPluginvdrTva(void) -{ - // Initialize any member variables here. - // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL - // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! - buffer = NULL; - configDir = NULL; - Filter = NULL; - ChanDAs = NULL; - EventCRIDs = NULL; - Links = NULL; - seriesLifetime = 30 * SECONDSPERDAY; - priority = 99; - lifetime = 99; - flags = 5; - state = 0; - collectionperiod = 10 * 60; - updatetime = 300; -} - -cPluginvdrTva::~cPluginvdrTva() -{ - // Clean up after yourself! - delete statusMonitor; -} - -const char *cPluginvdrTva::CommandLineHelp(void) -{ - // Return a string that describes all known command line options. - return " -l n --lifetime=n Lifetime of new timers (default 99)\n" - " -p n --priority=n Priority of new timers (default 99)\n" - " -s n --serieslifetime=n Days to remember a series after the last event (default 30)\n" - " -u HH:MM --updatetime=HH:MM Time to update series links (default 03:00)\n"; -} - -bool cPluginvdrTva::ProcessArgs(int argc, char *argv[]) -{ - // Implement command line argument processing here if applicable. - static struct option long_options[] = { - { "serieslifetime", required_argument, NULL, 's' }, - { "priority", required_argument, NULL, 'p' }, - { "lifetime", required_argument, NULL, 'l' }, - { "updatetime", required_argument, NULL, 'u' }, - { NULL } - }; - - int c, opt; - char *hours, *mins, *strtok_next; - char buf[32]; - while ((c = getopt_long(argc, argv, "l:p:s:u:", long_options, NULL)) != -1) { - switch (c) { - case 'l': - opt = atoi(optarg); - if (opt > 0) lifetime = opt; - break; - case 'p': - opt = atoi(optarg); - if (opt > 0) priority = opt; - break; - case 's': - opt = atoi(optarg); - if (opt > 0) seriesLifetime = opt * SECONDSPERDAY; - break; - case 'u': - strncpy(buf, optarg,sizeof(buf)); - hours = strtok_r(buf, ":", &strtok_next); - mins = strtok_r(NULL, "!", &strtok_next); - updatetime = atoi(hours)*100 + atoi(mins); - break; - default: - return false; - } - } - return true; -} - -bool cPluginvdrTva::Initialize(void) -{ - // Initialize any background activities the plugin shall perform. - return true; -} - -bool cPluginvdrTva::Start(void) -{ - // Start any background activities the plugin shall perform. - configDir = strcpyrealloc(configDir, cPlugin::ConfigDirectory("vdrtva")); - LoadLinksFile(); - statusMonitor = new cTvaStatusMonitor; - struct tm tm_r; - char buff[32]; - time_t now = time(NULL); - localtime_r(&now, &tm_r); - tm_r.tm_sec = 0; - tm_r.tm_hour = updatetime / 100; - tm_r.tm_min = updatetime % 100; - nextactiontime = mktime(&tm_r); - if (nextactiontime < now) nextactiontime += SECONDSPERDAY; - ctime_r(&nextactiontime, buff); - isyslog("vdrtva: next update due at %s", buff); - return true; -} - -void cPluginvdrTva::Stop(void) -{ - // Stop any background activities the plugin is performing. - if (Filter) { - delete Filter; - Filter = NULL; - } -} - -void cPluginvdrTva::Housekeeping(void) -{ - // Perform any cleanup or other regular tasks. - if (nextactiontime < time(NULL)) { - statusMonitor->ClearTimerAdded(); // Ignore any timer changes while update is in progress - switch (state) { - case 0: - StartDataCapture(); - nextactiontime += collectionperiod; - state++; - break; - case 1: - StopDataCapture(); - state++; - break; - case 2: - Update(); - state++; - break; - case 3: - Check(); - nextactiontime += (SECONDSPERDAY - collectionperiod); - state = 0; - break; - } - } - else if (EventCRIDs && statusMonitor->GetTimerAddedDelta() > 60) { - Update(); // Wait 1 minute for VDR to enter the event data into the new timer. - Check(); - statusMonitor->ClearTimerAdded(); - } -} - -void cPluginvdrTva::MainThreadHook(void) -{ - // Perform actions in the context of the main program thread. - // WARNING: Use with great care - see PLUGINS.html! -} - -cString cPluginvdrTva::Active(void) -{ - // Return a message string if shutdown should be postponed - return NULL; -} - -time_t cPluginvdrTva::WakeupTime(void) -{ - // Return custom wakeup time for shutdown script - return 0; -} - -cOsdObject *cPluginvdrTva::MainMenuAction(void) -{ - // Perform the action when selected from the main VDR menu. - return NULL; -} - -cMenuSetupPage *cPluginvdrTva::SetupMenu(void) -{ - // Return a setup menu in case the plugin supports one. - return new cTvaMenuSetup; -} - -bool cPluginvdrTva::SetupParse(const char *Name, const char *Value) -{ - // Parse your own setup parameters and store their values. - if (!strcasecmp(Name, "CollectionPeriod")) collectionperiod = atoi(Value); - else if (!strcasecmp(Name, "SeriesLifetime")) seriesLifetime = atoi(Value); - else if (!strcasecmp(Name, "TimerLifetime")) lifetime = atoi(Value); - else if (!strcasecmp(Name, "TimerPriority")) priority = atoi(Value); - else if (!strcasecmp(Name, "UpdateTime")) updatetime = atoi(Value); - else return false; - return true; -} - -bool cPluginvdrTva::Service(const char *Id, void *Data) -{ - // Handle custom service requests from other plugins - return false; -} - -const char **cPluginvdrTva::SVDRPHelpPages(void) -{ - // Return help text for SVDRP commands this plugin implements - static const char *HelpPages[] = { - "LSTL\n" - " Print the Links list.", - "LSTY\n" - " Print the Event list including CRIDs.", - "LSTZ\n" - " Print the channel list with Default Authority.", - "STOP\n" - " Stop Event data capture (retaining data).", - "STRT\n" - " Start Event data capture (erasing any existing data)", - "UPDT\n" - " Update timers and links (series link functionality)", - NULL - }; - return HelpPages; -} - -cString cPluginvdrTva::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) -{ - // Process SVDRP commands this plugin implements - isyslog ("vdrtva: processing command %s", Command); - if (strcasecmp(Command, "LSTL") == 0) { - if (Links && (Links->MaxNumber() >=1)) { - ReplyCode = 250; - for (cLinkItem *linkItem = Links->First(); linkItem; linkItem = Links->Next(linkItem)) { - Append("%s,%d;%s\n", linkItem->sCRID(), linkItem->ModTime(), linkItem->iCRIDs()); - } - } - if (buffer && length > 0) return cString(Reply(), true); - else return cString::sprintf("Nothing in the buffer!"); - } - else if (strcasecmp(Command, "LSTY") == 0) { - if (EventCRIDs && (EventCRIDs->MaxNumber() >= 1)) { - ReplyCode = 250; - for (cEventCRID *eventCRID = EventCRIDs->First(); eventCRID; eventCRID = EventCRIDs->Next(eventCRID)) { - cChanDA *chanDA = ChanDAs->GetByChannelID(eventCRID->Cid()); - if(chanDA) { - Append("%d %d %s%s %s%s\n", chanDA->Cid(), eventCRID->Eid(), chanDA->DA(), eventCRID->iCRID(), chanDA->DA(), eventCRID->sCRID()); - } - } - if (buffer && length > 0) return cString(Reply(), true); - else return cString::sprintf("Nothing in the buffer!"); - } - else - return cString::sprintf("No events defined"); - } - else if (strcasecmp(Command, "LSTZ") == 0) { - if (ChanDAs && (ChanDAs->MaxNumber() >= 1)) { - ReplyCode = 250; - for (cChanDA *chanDA = ChanDAs->First(); chanDA; chanDA = ChanDAs->Next(chanDA)) { - Append("%d %s\n", chanDA->Cid(), chanDA->DA()); - } - if (buffer && length > 0) return cString(Reply(), true); - else return cString::sprintf("Nothing in the buffer!"); - } - else - return cString::sprintf("No channels defined"); - } - else if (strcasecmp(Command, "STRT") == 0) { - if (!Filter) { - StartDataCapture(); - return cString::sprintf("Data capture started"); - } - else { - ReplyCode = 999; - return cString::sprintf("Double start attempted"); - } - } - else if (strcasecmp(Command, "STOP") == 0) { - if (Filter) { - StopDataCapture(); - return cString::sprintf("Data capture stopped"); - } - else { - ReplyCode = 999; - return cString::sprintf("Double stop attempted"); - } - } - else if (strcasecmp(Command, "UPDT") == 0) { - Update(); - Check(); - return cString::sprintf("Update completed"); - } - return NULL; -} - -bool cPluginvdrTva::Append(const char *Fmt, ...) -{ - va_list ap; - - if (!buffer) { - length = 0; - size = SVDRPOSD_BUFSIZE; - buffer = (char *) malloc(sizeof(char) * size); - } - while (buffer) { - va_start(ap, Fmt); - int n = vsnprintf(buffer + length, size - length, Fmt, ap); - va_end(ap); - - if (n < size - length) { - length += n; - return true; - } - // overflow: realloc and try again - size += SVDRPOSD_BUFSIZE; - char *tmp = (char *) realloc(buffer, sizeof(char) * size); - if (!tmp) - free(buffer); - buffer = tmp; - } - return false; -} - -const char* cPluginvdrTva::Reply() -{ - char *tmp = buffer; - buffer = NULL; - return tmp; -} - -void cPluginvdrTva::StartDataCapture() -{ - if (!Filter) { - if (EventCRIDs) delete EventCRIDs; - if (ChanDAs) delete ChanDAs; - EventCRIDs = new cEventCRIDs(); - ChanDAs = new cChanDAs(); - Filter = new cTvaFilter(); - cDevice::ActualDevice()->AttachFilter(Filter); - isyslog("vdrtva: Data capture started"); - } -} - -void cPluginvdrTva::StopDataCapture() -{ - if (Filter) { - delete Filter; - Filter = NULL; - isyslog("vdrtva: Data capture stopped"); - } -} - -void cPluginvdrTva::Update() -{ - bool status = UpdateLinksFromTimers(); - status |= AddNewEventsToSeries(); - if(status) SaveLinksFile(); - isyslog("vdrtva: Updates complete"); -} - -void cPluginvdrTva::Check() -{ - CheckChangedEvents(); - CheckTimerClashes(); - isyslog("vdrtva: Checks complete"); -} - -// add a new event to the Links table, either as an addition to an existing series or as a new series. -// return false = nothing done, true = new event for old series, or new series. - -bool cPluginvdrTva::AddSeriesLink(const char *scrid, int modtime, const char *icrid) -{ - if (Links && (Links->MaxNumber() >=1)) { - for (cLinkItem *Item = Links->First(); Item; Item = Links->Next(Item)) { - if (strcasecmp(Item->sCRID(), scrid) == 0) { - if (strstr(Item->iCRIDs(), icrid) == NULL) { - cString icrids = cString::sprintf("%s:%s", Item->iCRIDs(), icrid); - modtime = max(Item->ModTime(), modtime); - Item->Set(Item->sCRID(), modtime, icrids); - isyslog("vdrtva: Adding new event %s to series %s\n", icrid, scrid); - return true; - } - return false; - } - } - } - Links->NewLinkItem(scrid, modtime, icrid); - isyslog("vdrtva: Creating new series %s for event %s\n", scrid, icrid); - return true; -} - -void cPluginvdrTva::LoadLinksFile() -{ - Links = new cLinks(); - cString curlinks = AddDirectory(configDir, "links.data"); - FILE *f = fopen(curlinks, "r"); - if (f) { - char *s; - char *strtok_next; - cReadLine ReadLine; - cLinkItem *LinkItem; - int modtime; - while ((s = ReadLine.Read(f)) != NULL) { - char *scrid = strtok_r(s, ",", &strtok_next); - char *mtime = strtok_r(NULL, ";", &strtok_next); - char *icrids = strtok_r(NULL, "!", &strtok_next); - modtime = atoi(mtime); - LinkItem = Links->NewLinkItem(scrid, modtime, icrids); - } - fclose (f); - isyslog("vdrtva: loaded %d series links\n", Links->MaxNumber()); - } - else isyslog("vdrtva: series links file not found\n"); -} - -bool cPluginvdrTva::SaveLinksFile() -{ - cString curlinks = AddDirectory(configDir, "links.data"); - cString newlinks = AddDirectory(configDir, "links.new"); - cString oldlinks = AddDirectory(configDir, "links.old"); - FILE *f = fopen(newlinks, "w"); - if (f) { - for (cLinkItem *Item = Links->First(); Item; Item = Links->Next(Item)) { - if ((Item->ModTime() + seriesLifetime) > time(NULL)) { - fprintf(f, "%s,%d;%s\n", Item->sCRID(), Item->ModTime(), Item->iCRIDs()); - } - else { - isyslog ("vdrtva: Expiring series %s\n", Item->sCRID()); - cLinkItem *tmp = Links->Prev(Item); - if (!tmp) tmp = Links->First(); - Links->Del(Item); - Item = tmp; - } - } - fclose(f); - unlink (oldlinks); // Allow to fail if the save file does not exist - rename (curlinks, oldlinks); - rename (newlinks, curlinks); - } - return true; -} - -// Check that all timers are part of series links and update the links. - -bool cPluginvdrTva::UpdateLinksFromTimers() -{ - if ((Timers.Count() == 0) || (!EventCRIDs)) return false; - bool status = false; - for (int i = 0; i < Timers.Count(); i++) { - cTimer *timer = Timers.Get(i); - if (timer) { -// find the event for this timer - const cEvent *event = timer->Event(); - if (event) { - cChannel *channel = Channels.GetByChannelID(event->ChannelID()); -// find the sCRID and iCRID for the event - cChanDA *chanda = ChanDAs->GetByChannelID(channel->Number()); - cEventCRID *eventcrid = EventCRIDs->GetByID(channel->Number(), event->EventID()); - if (eventcrid && chanda) { - cString scrid = cString::sprintf("%s%s", chanda->DA(),eventcrid->sCRID()); - cString icrid = cString::sprintf("%s%s", chanda->DA(),eventcrid->iCRID()); -// scan the links table for the sCRID -// if found, check if the iCRID is present, if not add it -// else create a new links entry - status |= AddSeriesLink(scrid, event->StartTime(), icrid); - } - } - } - } - return status; -} - -// Find new events for series links and create timers for them. -// It would be simpler to create the timer directly from the event, but it would not then be possible to -// control the VPS parameters. - -bool cPluginvdrTva::AddNewEventsToSeries() -{ - bool saveNeeded = false; - if (!Links || (Links->MaxNumber() < 1)) return false; -// Foreach CRID - for (cEventCRID *eventCRID = EventCRIDs->First(); eventCRID; eventCRID = EventCRIDs->Next(eventCRID)) { - cChanDA *chanDA = ChanDAs->GetByChannelID(eventCRID->Cid()); - if (chanDA) { -// Check for an entry in the Links table with the same sCRID - cString scrid = cString::sprintf("%s%s", chanDA->DA(),eventCRID->sCRID()); - for (cLinkItem *Item = Links->First(); Item; Item = Links->Next(Item)) { - if (strcasecmp(Item->sCRID(), scrid) == 0) { -// if found, look for the event's icrid in ALL series - cString icrid = cString::sprintf("%s%s", chanDA->DA(),eventCRID->iCRID()); - bool done = false; - for (cLinkItem *Item2 = Links->First(); Item2; Item2 = Links->Next(Item2)) { - if (strstr(Item2->iCRIDs(), icrid) != NULL) { - done = true; - } - } -// if not found, add a new timer for the event and update the series. - if (!done) { - cChannel *channel = Channels.GetByNumber(eventCRID->Cid()); - cSchedulesLock SchedulesLock; - const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); - if (Schedules) { - const cSchedule *schedule = Schedules->GetSchedule(channel); - if (schedule) { - const cEvent *event = schedule->GetEvent(eventCRID->Eid()); - struct tm tm_r; - char startbuff[64], endbuff[64], etitle[256]; - time_t starttime = event->StartTime(); - time_t endtime = event->EndTime(); - if (!Setup.UseVps) { - starttime -= Setup.MarginStart; - endtime += Setup.MarginStop; - } - localtime_r(&starttime, &tm_r); - strftime(startbuff, sizeof(startbuff), "%Y-%m-%d:%H%M", &tm_r); - localtime_r(&endtime, &tm_r); - strftime(endbuff, sizeof(endbuff), "%H%M", &tm_r); - strn0cpy (etitle, event->Title(), sizeof(etitle)); - strreplace(etitle, ':', '|'); - cString timercmd = cString::sprintf("%u:%d:%s:%s:%d:%d:%s:\n", flags, channel->Number(), startbuff, endbuff, priority, lifetime, etitle); - cTimer *timer = new cTimer; - if (timer->Parse(timercmd)) { - cTimer *t = Timers.GetTimer(timer); - if (!t) { - Timers.Add(timer); - Timers.SetModified(); - isyslog("vdrtva: timer %s added on %s", *timer->ToDescr(), *DateString(timer->StartTime())); - AddSeriesLink(scrid, event->StartTime(), icrid); - saveNeeded = true; - } - else isyslog("vdrtva: Duplicate timer creation attempted for %s on %s", *timer->ToDescr(), *DateString(timer->StartTime())); - } - } - } - } - } - } - } - } - return saveNeeded; -} - -// Check timers to see if the event they were set to record is still in the EPG. -// This won't work if the start time is padded. - -void cPluginvdrTva::CheckChangedEvents() -{ - if (Timers.Count() == 0) return; - for (int i = 0; i < Timers.Count(); i++) { - cTimer *timer = Timers.Get(i); - if (timer) { - const cChannel *channel = timer->Channel(); - cSchedulesLock SchedulesLock; - const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); - if (Schedules) { - const cSchedule *schedule = Schedules->GetSchedule(channel); - if (schedule) { - const cEvent *event = schedule->GetEvent(NULL, timer->StartTime()); - if (!event) isyslog("Event for timer '%s' at %s seems to no longer exist", timer->File(), *DayDateTime(timer->StartTime())); - else if (strcmp(timer->File(), event->Title())) { - isyslog("vdrtva: Changed timer event at %s: %s <=> %s", *DayDateTime(timer->StartTime()), timer->File(), event->Title()); - } - } - } - } - } -} - -// Check for timer clashes - overlapping timers which are not on the same transponder. -// FIXME How to deal with multiple input devices?? - -void cPluginvdrTva::CheckTimerClashes(void) -{ - if (Timers.Count() < 2) return; - for (int i = 1; i < Timers.Count(); i++) { - cTimer *timer1 = Timers.Get(i); - if (timer1) { - for (int j = 0; j < i; j++) { - cTimer *timer2 = Timers.Get(j); - if (timer2) { - if((timer1->StartTime() >= timer2->StartTime() && timer1->StartTime() < timer2->StopTime()) - ||(timer2->StartTime() >= timer1->StartTime() && timer2->StartTime() < timer1->StopTime())) { - const cChannel *channel1 = timer1->Channel(); - const cChannel *channel2 = timer2->Channel(); - if (channel1->Transponder() != channel2->Transponder()) { - isyslog("vdrtva: Collision at %s. %s <=> %s", *DayDateTime(timer1->StartTime()), timer1->File(), timer2->File()); - FindAlternatives(timer1->Event()); - FindAlternatives(timer2->Event()); - } - } - } - } - } - } -} - -void cPluginvdrTva::FindAlternatives(const cEvent *event) -{ - if (!event) return; - cChannel *channel = Channels.GetByChannelID(event->ChannelID()); - cChanDA *chanda = ChanDAs->GetByChannelID(channel->Number()); - cEventCRID *eventcrid = EventCRIDs->GetByID(channel->Number(), event->EventID()); - if (!eventcrid || !chanda) return; - - bool found = false; - for (cEventCRID *eventcrid2 = EventCRIDs->First(); eventcrid2; eventcrid2 = EventCRIDs->Next(eventcrid2)) { - if ((strcmp(eventcrid->iCRID(), eventcrid2->iCRID()) == 0) && (event->EventID() != eventcrid2->Eid())) { - cChanDA *chanda2 = ChanDAs->GetByChannelID(eventcrid2->Cid()); - if (strcmp(chanda->DA(), chanda2->DA()) == 0) { - cChannel *channel2 = Channels.GetByNumber(eventcrid2->Cid()); - cSchedulesLock SchedulesLock; - const cSchedules *schedules = cSchedules::Schedules(SchedulesLock); - if (schedules) { - const cSchedule *schedule = schedules->GetSchedule(channel2); - if (schedule) { - const cEvent *event2 = schedule->GetEvent(eventcrid2->Eid(), 0); - if (!found) { - isyslog("vdrtva: Alternatives for '%s':", event->Title()); - found = true; - } - isyslog("vdrtva: %s %s", channel2->Name(), *DayDateTime(event2->StartTime())); - } - } - } - } - } - if (!found) isyslog("vdrtva: No alternatives for '%s':", event->Title()); -} - - -/* - cTvaStatusMonitor - callback for timer changes. -*/ - -cTvaStatusMonitor::cTvaStatusMonitor(void) -{ - timeradded = NULL; -} - -void cTvaStatusMonitor::TimerChange(const cTimer *Timer, eTimerChange Change) -{ - if (Change == tcAdd) timeradded = time(NULL); -} - -int cTvaStatusMonitor::GetTimerAddedDelta(void) -{ - if (timeradded) { - return (time(NULL) - timeradded); - } - return 0; -} - -void cTvaStatusMonitor::ClearTimerAdded(void) -{ - timeradded = NULL; - return; -} - - -/* - cTvaMenuSetup - setup menu function. -*/ - -cTvaMenuSetup::cTvaMenuSetup(void) -{ - newcollectionperiod = collectionperiod; - newlifetime = lifetime; - newpriority = priority; - newseriesLifetime = seriesLifetime; - newupdatetime = updatetime; - Add(new cMenuEditIntItem(tr("Collection period (min)"), &newcollectionperiod)); - Add(new cMenuEditIntItem(tr("Series link lifetime (days)"), &newseriesLifetime)); - Add(new cMenuEditIntItem(tr("New timer lifetime"), &newlifetime)); - Add(new cMenuEditIntItem(tr("New timer priority"), &newpriority)); - Add(new cMenuEditIntItem(tr("Update Time (HHMM)"), &newupdatetime)); -} - -void cTvaMenuSetup::Store(void) -{ - SetupStore("CollectionPeriod", newcollectionperiod); - SetupStore("SeriesLifetime", newseriesLifetime); - SetupStore("TimerLifetime", newlifetime); - SetupStore("TimerPriority", newpriority); - SetupStore("UpdateTime", newupdatetime); -} - - - -/* - cTvaFilter - capture the CRID data from EIT. -*/ - -cTvaFilter::cTvaFilter(void) -{ - Set(0x11, 0x42); // SDT (Actual) - Set(0x11, 0x46); // SDT (Other) - Set(0x12, 0x40, 0xC0); // event info, actual(0x4E)/other(0x4F) TS, present/following - // event info, actual TS, schedule(0x50)/schedule for future days(0x5X) - // event info, other TS, schedule(0x60)/schedule for future days(0x6X) -} - -void cTvaFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) -{ - // do something with the data here - switch (Pid) { - case 0x11: { - sectionSyncer.Reset(); - SI::SDT sdt(Data, false); - if (!sdt.CheckCRCAndParse()) { - dsyslog ("vdrtva: SDT Parse / CRC error\n"); - return; - } - if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber())) { - dsyslog ("vdrtva: SDT Syncer error\n"); - return; - } - SI::SDT::Service SiSdtService; - for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) { - cChannel *chan = Channels.GetByChannelID(tChannelID(Source(),sdt.getOriginalNetworkId(),sdt.getTransportStreamId(),SiSdtService.getServiceId())); - if (chan) { - cChanDA *chanDA = ChanDAs->GetByChannelID(chan->Number()); - if (!chanDA) { - SI::Descriptor *d; - for (SI::Loop::Iterator it2; (d = SiSdtService.serviceDescriptors.getNext(it2)); ) { - switch (d->getDescriptorTag()) { - case SI::DefaultAuthorityDescriptorTag: { - SI::DefaultAuthorityDescriptor *da = (SI::DefaultAuthorityDescriptor *)d; - char DaBuf[Utf8BufSize(1024)]; - da->DefaultAuthority.getText(DaBuf, sizeof(DaBuf)); - chanDA = ChanDAs->NewChanDA(chan->Number()); - chanDA->SetDA(DaBuf); - } - break; - default: ; - } - delete d; - } - } - } - } - } - case 0x12: { - if (Tid >= 0x4E && Tid <= 0x6F) { -// sectionSyncer.Reset(); - SI::EIT eit(Data, false); - if (!eit.CheckCRCAndParse()) { - dsyslog ("vdrtva: EIT Parse / CRC error\n"); - return; - } - - cChannel *chan = Channels.GetByChannelID(tChannelID(Source(),eit.getOriginalNetworkId(),eit.getTransportStreamId(),eit.getServiceId())); - if (!chan) { - return; - } - SI::EIT::Event SiEitEvent; - for (SI::Loop::Iterator it; eit.eventLoop.getNext(SiEitEvent, it); ) { - cEventCRID *eventCRID = EventCRIDs->GetByID(chan->Number(), SiEitEvent.getEventId()); - if (!eventCRID) { - SI::Descriptor *d; - char iCRIDBuf[Utf8BufSize(1024)] = {'\0'}, sCRIDBuf[Utf8BufSize(1024)] = {'\0'}; - for (SI::Loop::Iterator it2; (d = SiEitEvent.eventDescriptors.getNext(it2)); ) { - switch (d->getDescriptorTag()) { - case SI::ContentIdentifierDescriptorTag: { - SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d; - SI::ContentIdentifierDescriptor::Identifier cde; - for (SI::Loop::Iterator ite; (cd->identifierLoop.getNext(cde,ite)); ) { - if (cde.getCridLocation() == 0) { - switch (cde.getCridType()) { - case 0x01: // ETSI 102 363 code - case 0x31: // UK Freeview private code - cde.identifier.getText(iCRIDBuf, sizeof(iCRIDBuf)); - break; - case 0x02: // ETSI 102 363 code - case 0x32: // UK Freeview private code - cde.identifier.getText(sCRIDBuf, sizeof(sCRIDBuf)); - // ETSI 102 323 defines CRID type 0x03, which describes 'related' or 'suggested' events. - // Freeview broadcasts these as CRID type 0x33. - // There can be more than one type 0x33 descriptor per event (each with one CRID). - } - } - else { - dsyslog ("vdrtva: Incorrect CRID Loc %x\n", cde.getCridLocation()); - } - } - } - break; - default: ; - } - delete d; - } - if (iCRIDBuf[0] && sCRIDBuf[0]) { // Only log events which are part of a series. - eventCRID = EventCRIDs->NewEventCRID(chan->Number(), SiEitEvent.getEventId()); - eventCRID->SetCRIDs(iCRIDBuf, sCRIDBuf); - } - } - } - } - } - break; - } -} - - -/* - cChanDA - Default Authority for a channel. -*/ - -cChanDA::cChanDA(void) -{ - defaultAuthority = NULL; -} - -cChanDA::~cChanDA(void) -{ - free(defaultAuthority); -} - - -void cChanDA::Set(int Cid) { - cid = Cid; -} - -void cChanDA::SetDA(char *DA) { - defaultAuthority = strcpyrealloc(defaultAuthority, DA); -} - -/* - cChanDAs - in-memory list of channels and Default Authorities. -*/ - -cChanDAs::cChanDAs(void) -{ - maxNumber = 0; -} - -cChanDAs::~cChanDAs(void) -{ - chanDAHash.Clear(); -} - -cChanDA *cChanDAs::GetByChannelID(int cid) -{ - cList *list = chanDAHash.GetList(cid); - if (list) { - for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cChanDA *chanDA = (cChanDA *)hobj->Object(); - if (chanDA->Cid() == cid) - return chanDA; - } - } - return NULL; -} - -cChanDA *cChanDAs::NewChanDA(int Cid) -{ - cChanDA *NewChanDA = new cChanDA; - NewChanDA->Set(Cid); - Add(NewChanDA); - chanDAHash.Add(NewChanDA, Cid); - ChanDAs->SetMaxNumber(ChanDAs->MaxNumber()+1); - return NewChanDA; -} - - -/* - cEventCRID - CRIDs for an event. -*/ - -cEventCRID::cEventCRID(void) -{ - iCrid = sCrid = NULL; -} - -cEventCRID::~cEventCRID(void) -{ - free (iCrid); - free (sCrid); -} - -void cEventCRID::Set(int Cid, tEventID Eid) { - eid = Eid; - cid = Cid; -} - -void cEventCRID::SetCRIDs(char *iCRID, char *sCRID) { - iCrid = strcpyrealloc(iCrid, iCRID); - sCrid = strcpyrealloc(sCrid, sCRID); -} - - -/* - cEventCRIDs - in-memory list of events and CRIDs. -*/ - -cEventCRIDs::cEventCRIDs(void) -{ - maxNumber = 0; -} - -cEventCRIDs::~cEventCRIDs(void) -{ - EventCRIDHash.Clear(); -} - -cEventCRID *cEventCRIDs::GetByID(int Cid, tEventID Eid) -{ - cList *list = EventCRIDHash.GetList(Cid*33000 + Eid); - if (list) { - for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { - cEventCRID *EventCRID = (cEventCRID *)hobj->Object(); - if ((EventCRID->Eid() == Eid) && (EventCRID->Cid() == Cid)) - return EventCRID; - } - } - return NULL; -} - -cEventCRID *cEventCRIDs::NewEventCRID(int Cid, tEventID Eid) -{ - cEventCRID *NewEventCRID = new cEventCRID; - NewEventCRID->Set(Cid, Eid); - Add(NewEventCRID); - EventCRIDHash.Add(NewEventCRID, Eid + Cid*33000); - EventCRIDs->SetMaxNumber(EventCRIDs->MaxNumber()+1); - return NewEventCRID; -} - - -/* - cLinkItem - Entry from the links file -*/ - -cLinkItem::cLinkItem(void) -{ - sCrid = iCrids = NULL; -} - -cLinkItem::~cLinkItem(void) -{ - free(sCrid); - free(iCrids); -} - -void cLinkItem::Set(const char *sCRID, int ModTime, const char *iCRIDs) -{ - sCrid = strcpyrealloc(sCrid, sCRID); - modtime = ModTime; - iCrids = strcpyrealloc(iCrids, iCRIDs); -} - -/* - cLinks - list of cLinkItem entities -*/ - -cLinks::cLinks(void) -{ - maxNumber = 0; -} - -cLinkItem *cLinks::NewLinkItem(const char *sCRID, int ModTime, const char *iCRIDs) -{ - cLinkItem *NewLinkItem = new cLinkItem; - NewLinkItem->Set(sCRID, ModTime, iCRIDs); - Add(NewLinkItem); - Links->SetMaxNumber(Links->MaxNumber()+1); - return NewLinkItem; -} - -VDRPLUGINCREATOR(cPluginvdrTva); // Don't touch this! diff --git a/plugin/vdrtva.h b/plugin/vdrtva.h deleted file mode 100644 index 4149b0c..0000000 --- a/plugin/vdrtva.h +++ /dev/null @@ -1,128 +0,0 @@ -#include -#include -#include - -class cTvaFilter : public cFilter { -private: - cSectionSyncer sectionSyncer; - cPatFilter *patFilter; -protected: - virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); -public: - cTvaFilter(void); -}; - -class cTvaStatusMonitor : public cStatus { - private: - time_t timeradded; - protected: - virtual void TimerChange(const cTimer *Timer, eTimerChange Change); - // Indicates a change in the timer settings. - // If Change is tcAdd or tcDel, Timer points to the timer that has - // been added or will be deleted, respectively. In case of tcMod, - // Timer is NULL; this indicates that some timer has been changed. - // Note that tcAdd and tcDel are always also followed by a tcMod. - public: - cTvaStatusMonitor(void); - int GetTimerAddedDelta(void); - void ClearTimerAdded(void); -}; - - -class cTvaMenuSetup : public cMenuSetupPage { -private: - int newcollectionperiod; - int newlifetime; - int newpriority; - int newseriesLifetime; - int newupdatehours; - int newupdatemins; -protected: - virtual void Store(void); -public: - cTvaMenuSetup(void); -}; - - -class cChanDA : public cListObject { - private: - int cid; - char *defaultAuthority; - public: - cChanDA(void); - ~cChanDA(void); - int Cid(void) { return cid; } - void Set(int Cid); - char * DA(void) { return defaultAuthority; } - void SetDA(char *DA); -}; - -class cChanDAs : public cRwLock, public cConfig { - private: - int maxNumber; - cHash chanDAHash; - public: - cChanDAs(void); - ~cChanDAs(void); - int MaxNumber(void) { return maxNumber; } - void SetMaxNumber(int number) { maxNumber = number; } - cChanDA *GetByChannelID(int cid); - cChanDA *NewChanDA(int Cid); -}; - - -class cEventCRID : public cListObject { - private: - tEventID eid; - int cid; - char *iCrid; - char *sCrid; - public: - cEventCRID(void); - ~cEventCRID(void); - tEventID Eid(void) { return eid; } - void Set(int Cid, tEventID Eid); - char * iCRID(void) { return iCrid; } - char * sCRID(void) { return sCrid; } - void SetCRIDs(char *iCRID, char *sCRID); - int Cid(void) { return cid; } -}; - -class cEventCRIDs : public cRwLock, public cConfig { - private: - int maxNumber; - cHash EventCRIDHash; - public: - cEventCRIDs(void); - ~cEventCRIDs(void); - int MaxNumber(void) { return maxNumber; } - void SetMaxNumber(int number) { maxNumber = number; } - cEventCRID *GetByID(int Cid, tEventID Eid); - cEventCRID *NewEventCRID(int Cid, tEventID Eid); -}; - - -class cLinkItem : public cListObject { - private: - char *sCrid; - int modtime; - char *iCrids; - public: - cLinkItem(void); - ~cLinkItem(void); - void Set(const char *sCRID, int ModTime, const char *iCRIDs); - char * iCRIDs(void) { return iCrids; } - char * sCRID(void) { return sCrid; } - int ModTime(void) { return modtime; } -}; - -class cLinks : public cRwLock, public cConfig { - private: - int maxNumber; - public: - cLinks(void); -// ~cLinks(void); - int MaxNumber(void) { return maxNumber; } - void SetMaxNumber(int number) { maxNumber = number; } - cLinkItem *NewLinkItem(const char *sCRID, int ModTime, const char *iCRIDs); -}; diff --git a/plugin/vdrvps-1.7.22.diff b/plugin/vdrvps-1.7.22.diff deleted file mode 100644 index c851995..0000000 --- a/plugin/vdrvps-1.7.22.diff +++ /dev/null @@ -1,61 +0,0 @@ -diff -u vdr-1.7.22/config.c vdr-1.7/config.c ---- vdr-1.7.22/config.c 2011-12-03 15:21:30.000000000 +0000 -+++ vdr-1.7/config.c 2011-12-14 15:19:34.000000000 +0000 -@@ -416,6 +416,7 @@ - UseSubtitle = 1; - UseVps = 0; - VpsMargin = 120; -+ VpsFallback = 0; - RecordingDirs = 1; - FoldersInTimerMenu = 1; - NumberKeysForChars = 1; -@@ -610,6 +611,7 @@ - else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); - else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); - else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); -+ else if (!strcasecmp(Name, "VpsFallback")) VpsFallback = atoi(Value); - else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); - else if (!strcasecmp(Name, "FoldersInTimerMenu")) FoldersInTimerMenu = atoi(Value); - else if (!strcasecmp(Name, "NumberKeysForChars")) NumberKeysForChars = atoi(Value); -@@ -707,6 +709,7 @@ - Store("UseSubtitle", UseSubtitle); - Store("UseVps", UseVps); - Store("VpsMargin", VpsMargin); -+ Store("VpsFallback", VpsFallback); - Store("RecordingDirs", RecordingDirs); - Store("FoldersInTimerMenu", FoldersInTimerMenu); - Store("NumberKeysForChars", NumberKeysForChars); -diff -u vdr-1.7.22/config.h vdr-1.7/config.h ---- vdr-1.7.22/config.h 2011-12-03 14:19:52.000000000 +0000 -+++ vdr-1.7/config.h 2011-12-14 15:19:34.000000000 +0000 -@@ -271,6 +271,7 @@ - int UseSubtitle; - int UseVps; - int VpsMargin; -+ int VpsFallback; - int RecordingDirs; - int FoldersInTimerMenu; - int NumberKeysForChars; -diff -u vdr-1.7.22/menu.c vdr-1.7/menu.c ---- vdr-1.7.22/menu.c 2011-12-04 14:52:38.000000000 +0000 -+++ vdr-1.7/menu.c 2011-12-14 15:19:34.000000000 +0000 -@@ -3092,6 +3092,7 @@ - Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); - Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); - Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); -+ Add(new cMenuEditBoolItem(tr("Setup.Recording$Use running status as VPS fallback"), &data.VpsFallback)); - Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); - Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); - Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); -diff -u vdr-1.7.22/timers.c vdr-1.7/timers.c ---- vdr-1.7.22/timers.c 2011-08-06 14:13:54.000000000 +0100 -+++ vdr-1.7/timers.c 2011-12-14 15:19:34.000000000 +0000 -@@ -430,7 +430,7 @@ - deferred = 0; - - if (HasFlags(tfActive)) { -- if (HasFlags(tfVps) && event && event->Vps()) { -+ if (HasFlags(tfVps) && event && (Setup.VpsFallback || event->Vps())) { - if (Margin || !Directly) { - startTime = event->StartTime(); - stopTime = event->EndTime(); diff --git a/vdrtva.c b/vdrtva.c new file mode 100644 index 0000000..ce39114 --- /dev/null +++ b/vdrtva.c @@ -0,0 +1,1143 @@ +/* + * vdrtva.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#include +#include +#include +#include +#include + +#include "vdrtva.h" + +#define SVDRPOSD_BUFSIZE KILOBYTE(4) +#define SECONDSPERDAY (86400) + +cChanDAs *ChanDAs; +cEventCRIDs *EventCRIDs; +cSuggestCRIDs *SuggestCRIDs; +cLinks *Links; + +static const char *VERSION = "0.0.6"; +static const char *DESCRIPTION = "TV-Anytime plugin"; +static const char *MAINMENUENTRY = "vdrTva"; + +int collectionperiod; // Time to collect all CRID data (default 10 minutes) +int lifetime; // Lifetime of series link recordings (default 99) +int priority; // Priority of series link recordings (default 99) +int seriesLifetime; // Expiry time of a series link (default 30 days) +int updatetime; // Time to carry out the series link update HHMM (default 03:00) + + + +class cPluginvdrTva : public cPlugin { +private: + // Add any member variables or functions you may need here. + int length; + int size; + int flags; + int state; + time_t nextactiontime; + char *buffer; + char* configDir; + cTvaFilter *Filter; + cTvaStatusMonitor *statusMonitor; + bool Append(const char *Fmt, ...); + bool AppendItems(const char* Option); + const char* Reply(); + bool AddSeriesLink(const char *scrid, int modtime, const char *icrid); + void LoadLinksFile(void); + bool SaveLinksFile(void); + bool UpdateLinksFromTimers(void); + bool AddNewEventsToSeries(void); + bool CheckSplitTimers(void); + void CheckChangedEvents(void); + void CheckTimerClashes(void); + void FindAlternatives(const cEvent *event); + void StartDataCapture(void); + void StopDataCapture(void); + void Update(void); + void Check(void); +// void Reset(); +public: + cPluginvdrTva(void); + virtual ~cPluginvdrTva(); + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void) { return tr(DESCRIPTION); } + virtual const char *CommandLineHelp(void); + virtual bool ProcessArgs(int argc, char *argv[]); + virtual bool Initialize(void); + virtual bool Start(void); + virtual void Stop(void); + virtual void Housekeeping(void); + virtual void MainThreadHook(void); + virtual cString Active(void); + virtual time_t WakeupTime(void); + virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; } + virtual cOsdObject *MainMenuAction(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); + virtual bool Service(const char *Id, void *Data = NULL); + virtual const char **SVDRPHelpPages(void); + virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); + }; + +cPluginvdrTva::cPluginvdrTva(void) +{ + // Initialize any member variables here. + // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL + // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! + buffer = NULL; + configDir = NULL; + Filter = NULL; + ChanDAs = NULL; + EventCRIDs = NULL; + SuggestCRIDs = NULL; + Links = NULL; + seriesLifetime = 30 * SECONDSPERDAY; + priority = 99; + lifetime = 99; + flags = 5; + state = 0; + collectionperiod = 10 * 60; + updatetime = 300; +} + +cPluginvdrTva::~cPluginvdrTva() +{ + // Clean up after yourself! + delete statusMonitor; +} + +const char *cPluginvdrTva::CommandLineHelp(void) +{ + // Return a string that describes all known command line options. + return " -l n --lifetime=n Lifetime of new timers (default 99)\n" + " -p n --priority=n Priority of new timers (default 99)\n" + " -s n --serieslifetime=n Days to remember a series after the last event (default 30)\n" + " -u HH:MM --updatetime=HH:MM Time to update series links (default 03:00)\n"; +} + +bool cPluginvdrTva::ProcessArgs(int argc, char *argv[]) +{ + // Implement command line argument processing here if applicable. + static struct option long_options[] = { + { "serieslifetime", required_argument, NULL, 's' }, + { "priority", required_argument, NULL, 'p' }, + { "lifetime", required_argument, NULL, 'l' }, + { "updatetime", required_argument, NULL, 'u' }, + { NULL } + }; + + int c, opt; + char *hours, *mins, *strtok_next; + char buf[32]; + while ((c = getopt_long(argc, argv, "l:p:s:u:", long_options, NULL)) != -1) { + switch (c) { + case 'l': + opt = atoi(optarg); + if (opt > 0) lifetime = opt; + break; + case 'p': + opt = atoi(optarg); + if (opt > 0) priority = opt; + break; + case 's': + opt = atoi(optarg); + if (opt > 0) seriesLifetime = opt * SECONDSPERDAY; + break; + case 'u': + strncpy(buf, optarg,sizeof(buf)); + hours = strtok_r(buf, ":", &strtok_next); + mins = strtok_r(NULL, "!", &strtok_next); + updatetime = atoi(hours)*100 + atoi(mins); + break; + default: + return false; + } + } + return true; +} + +bool cPluginvdrTva::Initialize(void) +{ + // Initialize any background activities the plugin shall perform. + return true; +} + +bool cPluginvdrTva::Start(void) +{ + // Start any background activities the plugin shall perform. + configDir = strcpyrealloc(configDir, cPlugin::ConfigDirectory("vdrtva")); + LoadLinksFile(); + statusMonitor = new cTvaStatusMonitor; + struct tm tm_r; + char buff[32]; + time_t now = time(NULL); + localtime_r(&now, &tm_r); + tm_r.tm_sec = 0; + tm_r.tm_hour = updatetime / 100; + tm_r.tm_min = updatetime % 100; + nextactiontime = mktime(&tm_r); + if (nextactiontime < now) nextactiontime += SECONDSPERDAY; + ctime_r(&nextactiontime, buff); + isyslog("vdrtva: next update due at %s", buff); + return true; +} + +void cPluginvdrTva::Stop(void) +{ + // Stop any background activities the plugin is performing. + if (Filter) { + delete Filter; + Filter = NULL; + } +} + +void cPluginvdrTva::Housekeeping(void) +{ + // Perform any cleanup or other regular tasks. + if (nextactiontime < time(NULL)) { + statusMonitor->ClearTimerAdded(); // Ignore any timer changes while update is in progress + switch (state) { + case 0: + StartDataCapture(); + nextactiontime += collectionperiod; + state++; + break; + case 1: + StopDataCapture(); + state++; + break; + case 2: + Update(); + state++; + break; + case 3: + Check(); + nextactiontime += (SECONDSPERDAY - collectionperiod); + state = 0; + break; + } + } + else if (EventCRIDs && statusMonitor->GetTimerAddedDelta() > 60) { + Update(); // Wait 1 minute for VDR to enter the event data into the new timer. + Check(); + statusMonitor->ClearTimerAdded(); + } +} + +void cPluginvdrTva::MainThreadHook(void) +{ + // Perform actions in the context of the main program thread. + // WARNING: Use with great care - see PLUGINS.html! +} + +cString cPluginvdrTva::Active(void) +{ + // Return a message string if shutdown should be postponed + return NULL; +} + +time_t cPluginvdrTva::WakeupTime(void) +{ + // Return custom wakeup time for shutdown script + return 0; +} + +cOsdObject *cPluginvdrTva::MainMenuAction(void) +{ + // Perform the action when selected from the main VDR menu. + return NULL; +} + +cMenuSetupPage *cPluginvdrTva::SetupMenu(void) +{ + // Return a setup menu in case the plugin supports one. + return new cTvaMenuSetup; +} + +bool cPluginvdrTva::SetupParse(const char *Name, const char *Value) +{ + // Parse your own setup parameters and store their values. + if (!strcasecmp(Name, "CollectionPeriod")) collectionperiod = atoi(Value); + else if (!strcasecmp(Name, "SeriesLifetime")) seriesLifetime = atoi(Value); + else if (!strcasecmp(Name, "TimerLifetime")) lifetime = atoi(Value); + else if (!strcasecmp(Name, "TimerPriority")) priority = atoi(Value); + else if (!strcasecmp(Name, "UpdateTime")) updatetime = atoi(Value); + else return false; + return true; +} + +bool cPluginvdrTva::Service(const char *Id, void *Data) +{ + // Handle custom service requests from other plugins + return false; +} + +const char **cPluginvdrTva::SVDRPHelpPages(void) +{ + // Return help text for SVDRP commands this plugin implements + static const char *HelpPages[] = { + "LSTL\n" + " Print the Links list.", + "LSTS\n" + " Print the suggested events list", + "LSTY\n" + " Print the Event list including CRIDs.", + "LSTZ\n" + " Print the channel list with Default Authority.", + "STOP\n" + " Stop Event data capture (retaining data).", + "STRT\n" + " Start Event data capture (erasing any existing data)", + "UPDT\n" + " Update timers and links (series link functionality)", + NULL + }; + return HelpPages; +} + +cString cPluginvdrTva::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) +{ + // Process SVDRP commands this plugin implements + isyslog ("vdrtva: processing command %s", Command); + if (strcasecmp(Command, "LSTL") == 0) { + if (Links && (Links->MaxNumber() >=1)) { + ReplyCode = 250; + for (cLinkItem *linkItem = Links->First(); linkItem; linkItem = Links->Next(linkItem)) { + Append("%s,%d;%s\n", linkItem->sCRID(), linkItem->ModTime(), linkItem->iCRIDs()); + } + } + if (buffer && length > 0) return cString(Reply(), true); + else return cString::sprintf("Nothing in the buffer!"); + } + else if (strcasecmp(Command, "LSTS") == 0) { + if (SuggestCRIDs && (SuggestCRIDs->MaxNumber() >= 1)) { + ReplyCode = 250; + for (cSuggestCRID *suggestCRID = SuggestCRIDs->First(); suggestCRID; suggestCRID = SuggestCRIDs->Next(suggestCRID)) { + cChanDA *chanDA = ChanDAs->GetByChannelID(suggestCRID->Cid()); + if(chanDA) { + Append("%s%s %s%s\n", chanDA->DA(), suggestCRID->iCRID(), chanDA->DA(), suggestCRID->gCRID()); + } + } + if (buffer && length > 0) return cString(Reply(), true); + else return cString::sprintf("Nothing in the buffer!"); + } + else + return cString::sprintf("No suggested events defined"); + } + else if (strcasecmp(Command, "LSTY") == 0) { + if (EventCRIDs && (EventCRIDs->MaxNumber() >= 1)) { + ReplyCode = 250; + for (cEventCRID *eventCRID = EventCRIDs->First(); eventCRID; eventCRID = EventCRIDs->Next(eventCRID)) { + cChanDA *chanDA = ChanDAs->GetByChannelID(eventCRID->Cid()); + if(chanDA) { + Append("%d %d %s%s %s%s\n", chanDA->Cid(), eventCRID->Eid(), chanDA->DA(), eventCRID->iCRID(), chanDA->DA(), eventCRID->sCRID()); + } + } + if (buffer && length > 0) return cString(Reply(), true); + else return cString::sprintf("Nothing in the buffer!"); + } + else + return cString::sprintf("No events defined"); + } + else if (strcasecmp(Command, "LSTZ") == 0) { + if (ChanDAs && (ChanDAs->MaxNumber() >= 1)) { + ReplyCode = 250; + for (cChanDA *chanDA = ChanDAs->First(); chanDA; chanDA = ChanDAs->Next(chanDA)) { + Append("%d %s\n", chanDA->Cid(), chanDA->DA()); + } + if (buffer && length > 0) return cString(Reply(), true); + else return cString::sprintf("Nothing in the buffer!"); + } + else + return cString::sprintf("No channels defined"); + } + else if (strcasecmp(Command, "STRT") == 0) { + if (!Filter) { + StartDataCapture(); + return cString::sprintf("Data capture started"); + } + else { + ReplyCode = 999; + return cString::sprintf("Double start attempted"); + } + } + else if (strcasecmp(Command, "STOP") == 0) { + if (Filter) { + StopDataCapture(); + return cString::sprintf("Data capture stopped"); + } + else { + ReplyCode = 999; + return cString::sprintf("Double stop attempted"); + } + } + else if (strcasecmp(Command, "UPDT") == 0) { + Update(); + Check(); + return cString::sprintf("Update completed"); + } + return NULL; +} + +bool cPluginvdrTva::Append(const char *Fmt, ...) +{ + va_list ap; + + if (!buffer) { + length = 0; + size = SVDRPOSD_BUFSIZE; + buffer = (char *) malloc(sizeof(char) * size); + } + while (buffer) { + va_start(ap, Fmt); + int n = vsnprintf(buffer + length, size - length, Fmt, ap); + va_end(ap); + + if (n < size - length) { + length += n; + return true; + } + // overflow: realloc and try again + size += SVDRPOSD_BUFSIZE; + char *tmp = (char *) realloc(buffer, sizeof(char) * size); + if (!tmp) + free(buffer); + buffer = tmp; + } + return false; +} + +const char* cPluginvdrTva::Reply() +{ + char *tmp = buffer; + buffer = NULL; + return tmp; +} + +void cPluginvdrTva::StartDataCapture() +{ + if (!Filter) { + if (EventCRIDs) delete EventCRIDs; + if (ChanDAs) delete ChanDAs; + if (SuggestCRIDs) delete SuggestCRIDs; + EventCRIDs = new cEventCRIDs(); + SuggestCRIDs = new cSuggestCRIDs; + ChanDAs = new cChanDAs(); + Filter = new cTvaFilter(); + cDevice::ActualDevice()->AttachFilter(Filter); + isyslog("vdrtva: Data capture started"); + } +} + +void cPluginvdrTva::StopDataCapture() +{ + if (Filter) { + delete Filter; + Filter = NULL; + isyslog("vdrtva: Data capture stopped"); + } +} + +void cPluginvdrTva::Update() +{ + bool status = UpdateLinksFromTimers(); + status |= AddNewEventsToSeries(); + if(status) SaveLinksFile(); + isyslog("vdrtva: Updates complete"); +} + +void cPluginvdrTva::Check() +{ + CheckChangedEvents(); + CheckTimerClashes(); + CheckSplitTimers(); + isyslog("vdrtva: Checks complete"); +} + +// add a new event to the Links table, either as an addition to an existing series or as a new series. +// return false = nothing done, true = new event for old series, or new series. + +bool cPluginvdrTva::AddSeriesLink(const char *scrid, int modtime, const char *icrid) +{ + if (Links && (Links->MaxNumber() >=1)) { + for (cLinkItem *Item = Links->First(); Item; Item = Links->Next(Item)) { + if (strcasecmp(Item->sCRID(), scrid) == 0) { + if (strstr(Item->iCRIDs(), icrid) == NULL) { + cString icrids = cString::sprintf("%s:%s", Item->iCRIDs(), icrid); + modtime = max(Item->ModTime(), modtime); + Item->Set(Item->sCRID(), modtime, icrids); + isyslog("vdrtva: Adding new event %s to series %s\n", icrid, scrid); + return true; + } + return false; + } + } + } + Links->NewLinkItem(scrid, modtime, icrid); + isyslog("vdrtva: Creating new series %s for event %s\n", scrid, icrid); + return true; +} + +void cPluginvdrTva::LoadLinksFile() +{ + Links = new cLinks(); + cString curlinks = AddDirectory(configDir, "links.data"); + FILE *f = fopen(curlinks, "r"); + if (f) { + char *s; + char *strtok_next; + cReadLine ReadLine; + cLinkItem *LinkItem; + int modtime; + while ((s = ReadLine.Read(f)) != NULL) { + char *scrid = strtok_r(s, ",", &strtok_next); + char *mtime = strtok_r(NULL, ";", &strtok_next); + char *icrids = strtok_r(NULL, "!", &strtok_next); + modtime = atoi(mtime); + LinkItem = Links->NewLinkItem(scrid, modtime, icrids); + } + fclose (f); + isyslog("vdrtva: loaded %d series links\n", Links->MaxNumber()); + } + else isyslog("vdrtva: series links file not found\n"); +} + +bool cPluginvdrTva::SaveLinksFile() +{ + cString curlinks = AddDirectory(configDir, "links.data"); + cString newlinks = AddDirectory(configDir, "links.new"); + cString oldlinks = AddDirectory(configDir, "links.old"); + FILE *f = fopen(newlinks, "w"); + if (f) { + cLinkItem *Item = Links->First(); + while (Item) { + cLinkItem *next = Links->Next(Item); + if ((Item->ModTime() + seriesLifetime) > time(NULL)) { + fprintf(f, "%s,%d;%s\n", Item->sCRID(), Item->ModTime(), Item->iCRIDs()); + } + else { + isyslog ("vdrtva: Expiring series %s\n", Item->sCRID()); + Links->Del(Item); + } + Item = next; + } + fclose(f); + unlink (oldlinks); // Allow to fail if the save file does not exist + rename (curlinks, oldlinks); + rename (newlinks, curlinks); + } + return true; +} + +// Check that all timers are part of series links and update the links. + +bool cPluginvdrTva::UpdateLinksFromTimers() +{ + if ((Timers.Count() == 0) || (!EventCRIDs)) return false; + bool status = false; + for (int i = 0; i < Timers.Count(); i++) { + cTimer *timer = Timers.Get(i); + if (timer) { +// find the event for this timer + const cEvent *event = timer->Event(); + if (event) { + cChannel *channel = Channels.GetByChannelID(event->ChannelID()); +// find the sCRID and iCRID for the event + cChanDA *chanda = ChanDAs->GetByChannelID(channel->Number()); + cEventCRID *eventcrid = EventCRIDs->GetByID(channel->Number(), event->EventID()); + if (eventcrid && chanda) { + cString scrid = cString::sprintf("%s%s", chanda->DA(),eventcrid->sCRID()); + cString icrid = cString::sprintf("%s%s", chanda->DA(),eventcrid->iCRID()); +// scan the links table for the sCRID +// if found, check if the iCRID is present, if not add it +// else create a new links entry + status |= AddSeriesLink(scrid, event->StartTime(), icrid); + } + } + } + } + return status; +} + +// Find new events for series links and create timers for them. +// It would be simpler to create the timer directly from the event, but it would not then be possible to +// control the VPS parameters. + +bool cPluginvdrTva::AddNewEventsToSeries() +{ + bool saveNeeded = false; + if (!Links || (Links->MaxNumber() < 1)) return false; +// Foreach CRID + for (cEventCRID *eventCRID = EventCRIDs->First(); eventCRID; eventCRID = EventCRIDs->Next(eventCRID)) { + cChanDA *chanDA = ChanDAs->GetByChannelID(eventCRID->Cid()); + if (chanDA) { +// Check for an entry in the Links table with the same sCRID + cString scrid = cString::sprintf("%s%s", chanDA->DA(),eventCRID->sCRID()); + for (cLinkItem *Item = Links->First(); Item; Item = Links->Next(Item)) { + if (strcasecmp(Item->sCRID(), scrid) == 0) { +// if found, look for the event's icrid in ALL series + cString icrid = cString::sprintf("%s%s", chanDA->DA(),eventCRID->iCRID()); + bool done = false; + for (cLinkItem *Item2 = Links->First(); Item2; Item2 = Links->Next(Item2)) { + if (strstr(Item2->iCRIDs(), icrid) != NULL) { + done = true; + } + } +// if not found, add a new timer for the event and update the series. + if (!done) { + cChannel *channel = Channels.GetByNumber(eventCRID->Cid()); + cSchedulesLock SchedulesLock; + const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); + if (Schedules) { + const cSchedule *schedule = Schedules->GetSchedule(channel); + if (schedule) { + const cEvent *event = schedule->GetEvent(eventCRID->Eid()); + struct tm tm_r; + char startbuff[64], endbuff[64], etitle[256]; + time_t starttime = event->StartTime(); + time_t endtime = event->EndTime(); + if (!Setup.UseVps) { + starttime -= Setup.MarginStart; + endtime += Setup.MarginStop; + } + localtime_r(&starttime, &tm_r); + strftime(startbuff, sizeof(startbuff), "%Y-%m-%d:%H%M", &tm_r); + localtime_r(&endtime, &tm_r); + strftime(endbuff, sizeof(endbuff), "%H%M", &tm_r); + strn0cpy (etitle, event->Title(), sizeof(etitle)); + strreplace(etitle, ':', '|'); + cString timercmd = cString::sprintf("%u:%d:%s:%s:%d:%d:%s:\n", flags, channel->Number(), startbuff, endbuff, priority, lifetime, etitle); + cTimer *timer = new cTimer; + if (timer->Parse(timercmd)) { + cTimer *t = Timers.GetTimer(timer); + if (!t) { + Timers.Add(timer); + Timers.SetModified(); + isyslog("vdrtva: timer %s added on %s", *timer->ToDescr(), *DateString(timer->StartTime())); + AddSeriesLink(scrid, event->StartTime(), icrid); + saveNeeded = true; + } + else isyslog("vdrtva: Duplicate timer creation attempted for %s on %s", *timer->ToDescr(), *DateString(timer->StartTime())); + } + } + } + } + } + } + } + } + return saveNeeded; +} + +// Check timers to see if the event they were set to record is still in the EPG. +// This won't work if the start time is padded. + +void cPluginvdrTva::CheckChangedEvents() +{ + if (Timers.Count() == 0) return; + for (int i = 0; i < Timers.Count(); i++) { + cTimer *timer = Timers.Get(i); + if (timer) { + const cChannel *channel = timer->Channel(); + cSchedulesLock SchedulesLock; + const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); + if (Schedules) { + const cSchedule *schedule = Schedules->GetSchedule(channel); + if (schedule) { + const cEvent *event = schedule->GetEvent(NULL, timer->StartTime()); + if (!event) isyslog("Event for timer '%s' at %s seems to no longer exist", timer->File(), *DayDateTime(timer->StartTime())); + else if (strcmp(timer->File(), event->Title())) { + isyslog("vdrtva: Changed timer event at %s: %s <=> %s", *DayDateTime(timer->StartTime()), timer->File(), event->Title()); + } + } + } + } + } +} + +// Check for timer clashes - overlapping timers which are not on the same transponder. +// FIXME How to deal with multiple input devices?? + +void cPluginvdrTva::CheckTimerClashes(void) +{ + if (Timers.Count() < 2) return; + for (int i = 1; i < Timers.Count(); i++) { + cTimer *timer1 = Timers.Get(i); + if (timer1) { + for (int j = 0; j < i; j++) { + cTimer *timer2 = Timers.Get(j); + if (timer2) { + if((timer1->StartTime() >= timer2->StartTime() && timer1->StartTime() < timer2->StopTime()) + ||(timer2->StartTime() >= timer1->StartTime() && timer2->StartTime() < timer1->StopTime())) { + const cChannel *channel1 = timer1->Channel(); + const cChannel *channel2 = timer2->Channel(); + if (channel1->Transponder() != channel2->Transponder()) { + isyslog("vdrtva: Collision at %s. %s <=> %s", *DayDateTime(timer1->StartTime()), timer1->File(), timer2->File()); + FindAlternatives(timer1->Event()); + FindAlternatives(timer2->Event()); + } + } + } + } + } + } +} + +void cPluginvdrTva::FindAlternatives(const cEvent *event) +{ + if (!event) return; + cChannel *channel = Channels.GetByChannelID(event->ChannelID()); + cChanDA *chanda = ChanDAs->GetByChannelID(channel->Number()); + cEventCRID *eventcrid = EventCRIDs->GetByID(channel->Number(), event->EventID()); + if (!eventcrid || !chanda) return; + + bool found = false; + for (cEventCRID *eventcrid2 = EventCRIDs->First(); eventcrid2; eventcrid2 = EventCRIDs->Next(eventcrid2)) { + if ((strcmp(eventcrid->iCRID(), eventcrid2->iCRID()) == 0) && (event->EventID() != eventcrid2->Eid())) { + cChanDA *chanda2 = ChanDAs->GetByChannelID(eventcrid2->Cid()); + if (strcmp(chanda->DA(), chanda2->DA()) == 0) { + cChannel *channel2 = Channels.GetByNumber(eventcrid2->Cid()); + cSchedulesLock SchedulesLock; + const cSchedules *schedules = cSchedules::Schedules(SchedulesLock); + if (schedules) { + const cSchedule *schedule = schedules->GetSchedule(channel2); + if (schedule) { + const cEvent *event2 = schedule->GetEvent(eventcrid2->Eid(), 0); + if (!found) { + isyslog("vdrtva: Alternatives for '%s':", event->Title()); + found = true; + } + isyslog("vdrtva: %s %s", channel2->Name(), *DayDateTime(event2->StartTime())); + } + } + } + } + } + if (!found) isyslog("vdrtva: No alternatives for '%s':", event->Title()); +} + +// Check that, if any split events (eg a long programme with a news break in the middle) +// are being recorded, that timers are set for all of the parts. +// FIXME This may not work if the programme is being repeated. Inefficient algorithm. + +bool cPluginvdrTva::CheckSplitTimers(void) +{ + if (Timers.Count() == 0) return false; + for (int i = 0; i < Timers.Count(); i++) { + cTimer *timer = Timers.Get(i); + if (timer) { + const cEvent *event = timer->Event(); + if (event) { + cChannel *channel = Channels.GetByChannelID(event->ChannelID()); + cChanDA *chanda = ChanDAs->GetByChannelID(channel->Number()); + cEventCRID *eventcrid = EventCRIDs->GetByID(channel->Number(), event->EventID()); + if (eventcrid && chanda && strchr(eventcrid->iCRID(), '#')) { +// char crid[Utf8BufSize(256)], *next; +// strcpy(crid, eventcrid->iCRID()); +// char *prefix = strtok_r(crid, "#", &next); +// char *suffix = strtok_r(NULL, "#", &next); + isyslog("Timer for split event '%s' found - check all parts are being recorded!", event->Title()); + } + } + } + } + return false; +} + + +/* + cTvaStatusMonitor - callback for timer changes. +*/ + +cTvaStatusMonitor::cTvaStatusMonitor(void) +{ + timeradded = NULL; +} + +void cTvaStatusMonitor::TimerChange(const cTimer *Timer, eTimerChange Change) +{ + if (Change == tcAdd) timeradded = time(NULL); +} + +int cTvaStatusMonitor::GetTimerAddedDelta(void) +{ + if (timeradded) { + return (time(NULL) - timeradded); + } + return 0; +} + +void cTvaStatusMonitor::ClearTimerAdded(void) +{ + timeradded = NULL; + return; +} + + +/* + cTvaMenuSetup - setup menu function. +*/ + +cTvaMenuSetup::cTvaMenuSetup(void) +{ + newcollectionperiod = collectionperiod; + newlifetime = lifetime; + newpriority = priority; + newseriesLifetime = seriesLifetime; + newupdatetime = updatetime; + Add(new cMenuEditIntItem(tr("Collection period (min)"), &newcollectionperiod)); + Add(new cMenuEditIntItem(tr("Series link lifetime (days)"), &newseriesLifetime)); + Add(new cMenuEditIntItem(tr("New timer lifetime"), &newlifetime)); + Add(new cMenuEditIntItem(tr("New timer priority"), &newpriority)); + Add(new cMenuEditIntItem(tr("Update Time (HHMM)"), &newupdatetime)); +} + +void cTvaMenuSetup::Store(void) +{ + SetupStore("CollectionPeriod", newcollectionperiod); + SetupStore("SeriesLifetime", newseriesLifetime); + SetupStore("TimerLifetime", newlifetime); + SetupStore("TimerPriority", newpriority); + SetupStore("UpdateTime", newupdatetime); +} + + + +/* + cTvaFilter - capture the CRID data from EIT. +*/ + +cTvaFilter::cTvaFilter(void) +{ + Set(0x11, 0x42); // SDT (Actual) + Set(0x11, 0x46); // SDT (Other) + Set(0x12, 0x40, 0xC0); // event info, actual(0x4E)/other(0x4F) TS, present/following + // event info, actual TS, schedule(0x50)/schedule for future days(0x5X) + // event info, other TS, schedule(0x60)/schedule for future days(0x6X) +} + +void cTvaFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) +{ + // do something with the data here + switch (Pid) { + case 0x11: { + sectionSyncer.Reset(); + SI::SDT sdt(Data, false); + if (!sdt.CheckCRCAndParse()) { + dsyslog ("vdrtva: SDT Parse / CRC error\n"); + return; + } + if (!sectionSyncer.Sync(sdt.getVersionNumber(), sdt.getSectionNumber(), sdt.getLastSectionNumber())) { + dsyslog ("vdrtva: SDT Syncer error\n"); + return; + } + SI::SDT::Service SiSdtService; + for (SI::Loop::Iterator it; sdt.serviceLoop.getNext(SiSdtService, it); ) { + cChannel *chan = Channels.GetByChannelID(tChannelID(Source(),sdt.getOriginalNetworkId(),sdt.getTransportStreamId(),SiSdtService.getServiceId())); + if (chan) { + cChanDA *chanDA = ChanDAs->GetByChannelID(chan->Number()); + if (!chanDA) { + SI::Descriptor *d; + for (SI::Loop::Iterator it2; (d = SiSdtService.serviceDescriptors.getNext(it2)); ) { + switch (d->getDescriptorTag()) { + case SI::DefaultAuthorityDescriptorTag: { + SI::DefaultAuthorityDescriptor *da = (SI::DefaultAuthorityDescriptor *)d; + char DaBuf[Utf8BufSize(1024)]; + da->DefaultAuthority.getText(DaBuf, sizeof(DaBuf)); + chanDA = ChanDAs->NewChanDA(chan->Number()); + chanDA->SetDA(DaBuf); + } + break; + default: ; + } + delete d; + } + } + } + } + } + case 0x12: { + if (Tid >= 0x4E && Tid <= 0x6F) { +// sectionSyncer.Reset(); + SI::EIT eit(Data, false); + if (!eit.CheckCRCAndParse()) { + dsyslog ("vdrtva: EIT Parse / CRC error\n"); + return; + } + + cChannel *chan = Channels.GetByChannelID(tChannelID(Source(),eit.getOriginalNetworkId(),eit.getTransportStreamId(),eit.getServiceId())); + if (!chan) { + return; + } + SI::EIT::Event SiEitEvent; + for (SI::Loop::Iterator it; eit.eventLoop.getNext(SiEitEvent, it); ) { + cEventCRID *eventCRID = EventCRIDs->GetByID(chan->Number(), SiEitEvent.getEventId()); + if (!eventCRID) { + SI::Descriptor *d; + char iCRIDBuf[Utf8BufSize(256)] = {'\0'}, sCRIDBuf[Utf8BufSize(256)] = {'\0'}, gCRIDBuf[Utf8BufSize(256)] = {'\0'}; + for (SI::Loop::Iterator it2; (d = SiEitEvent.eventDescriptors.getNext(it2)); ) { + switch (d->getDescriptorTag()) { + case SI::ContentIdentifierDescriptorTag: { + SI::ContentIdentifierDescriptor *cd = (SI::ContentIdentifierDescriptor *)d; + SI::ContentIdentifierDescriptor::Identifier cde; + for (SI::Loop::Iterator ite; (cd->identifierLoop.getNext(cde,ite)); ) { + if (cde.getCridLocation() == 0) { + switch (cde.getCridType()) { + case 0x01: // ETSI 102 323 code + case 0x31: // UK Freeview private code + cde.identifier.getText(iCRIDBuf, sizeof(iCRIDBuf)); + break; + case 0x02: // ETSI 102 323 code + case 0x32: // UK Freeview private code + cde.identifier.getText(sCRIDBuf, sizeof(sCRIDBuf)); + break; + // ETSI 102 323 defines CRID type 0x03, which describes 'related' or 'suggested' events. + // Freeview broadcasts these as CRID type 0x33. + // There can be more than one type 0x33 descriptor per event (each with one CRID). + case 0x03: + case 0x33: + cde.identifier.getText(gCRIDBuf, sizeof(gCRIDBuf)); // FIXME Rashly assuming that a 0x31 CRID will always precede a 0x33 CRID. + if (iCRIDBuf[0]) SuggestCRIDs->NewSuggestCRID(chan->Number(), iCRIDBuf, gCRIDBuf); + } + } + else { + dsyslog ("vdrtva: Incorrect CRID Loc %x\n", cde.getCridLocation()); + } + } + } + break; + default: ; + } + delete d; + } + if (iCRIDBuf[0] && sCRIDBuf[0]) { // Only log events which are part of a series. + eventCRID = EventCRIDs->NewEventCRID(chan->Number(), SiEitEvent.getEventId()); + eventCRID->SetCRIDs(iCRIDBuf, sCRIDBuf); + } + } + } + } + } + break; + } +} + + +/* + cChanDA - Default Authority for a channel. +*/ + +cChanDA::cChanDA(void) +{ + defaultAuthority = NULL; +} + +cChanDA::~cChanDA(void) +{ + free(defaultAuthority); +} + + +void cChanDA::Set(int Cid) { + cid = Cid; +} + +void cChanDA::SetDA(char *DA) { + defaultAuthority = strcpyrealloc(defaultAuthority, DA); +} + +/* + cChanDAs - in-memory list of channels and Default Authorities. +*/ + +cChanDAs::cChanDAs(void) +{ + maxNumber = 0; +} + +cChanDAs::~cChanDAs(void) +{ + chanDAHash.Clear(); +} + +cChanDA *cChanDAs::GetByChannelID(int cid) +{ + cList *list = chanDAHash.GetList(cid); + if (list) { + for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { + cChanDA *chanDA = (cChanDA *)hobj->Object(); + if (chanDA->Cid() == cid) + return chanDA; + } + } + return NULL; +} + +cChanDA *cChanDAs::NewChanDA(int Cid) +{ + cChanDA *NewChanDA = new cChanDA; + NewChanDA->Set(Cid); + Add(NewChanDA); + chanDAHash.Add(NewChanDA, Cid); + ChanDAs->SetMaxNumber(ChanDAs->MaxNumber()+1); + return NewChanDA; +} + + +/* + cEventCRID - CRIDs for an event. +*/ + +cEventCRID::cEventCRID(void) +{ + iCrid = sCrid = NULL; +} + +cEventCRID::~cEventCRID(void) +{ + free (iCrid); + free (sCrid); +} + +void cEventCRID::Set(int Cid, tEventID Eid) { + eid = Eid; + cid = Cid; +} + +void cEventCRID::SetCRIDs(char *iCRID, char *sCRID) { + iCrid = strcpyrealloc(iCrid, iCRID); + sCrid = strcpyrealloc(sCrid, sCRID); +} + + +/* + cEventCRIDs - in-memory list of events and CRIDs. +*/ + +cEventCRIDs::cEventCRIDs(void) +{ + maxNumber = 0; +} + +cEventCRIDs::~cEventCRIDs(void) +{ + EventCRIDHash.Clear(); +} + +cEventCRID *cEventCRIDs::GetByID(int Cid, tEventID Eid) +{ + cList *list = EventCRIDHash.GetList(Cid*33000 + Eid); + if (list) { + for (cHashObject *hobj = list->First(); hobj; hobj = list->Next(hobj)) { + cEventCRID *EventCRID = (cEventCRID *)hobj->Object(); + if ((EventCRID->Eid() == Eid) && (EventCRID->Cid() == Cid)) + return EventCRID; + } + } + return NULL; +} + +cEventCRID *cEventCRIDs::NewEventCRID(int Cid, tEventID Eid) +{ + cEventCRID *NewEventCRID = new cEventCRID; + NewEventCRID->Set(Cid, Eid); + Add(NewEventCRID); + EventCRIDHash.Add(NewEventCRID, Eid + Cid*33000); + EventCRIDs->SetMaxNumber(EventCRIDs->MaxNumber()+1); + return NewEventCRID; +} + + +/* + cSuggestCRID - CRIDs of suggested items for an event. +*/ + +cSuggestCRID::cSuggestCRID(void) +{ + iCrid = gCrid = NULL; +} + +cSuggestCRID::~cSuggestCRID(void) +{ + free (iCrid); + free (gCrid); +} + +void cSuggestCRID::Set(int Cid, char *iCRID, char *gCRID) { + iCrid = strcpyrealloc(iCrid, iCRID); + gCrid = strcpyrealloc(gCrid, gCRID); + cid = Cid; +} + + +/* + cSuggestCRIDs - in-memory list of suggested events +*/ + +cSuggestCRIDs::cSuggestCRIDs(void) +{ + maxNumber = 0; +} + +cSuggestCRIDs::~cSuggestCRIDs(void) +{ +} + +cSuggestCRID *cSuggestCRIDs::NewSuggestCRID(int cid, char *icrid, char *gcrid) +{ + cSuggestCRID *NewSuggestCRID = new cSuggestCRID; + NewSuggestCRID->Set(cid, icrid, gcrid); + Add(NewSuggestCRID); + SuggestCRIDs->SetMaxNumber(SuggestCRIDs->MaxNumber()+1); + return NewSuggestCRID; +} + + +/* + cLinkItem - Entry from the links file +*/ + +cLinkItem::cLinkItem(void) +{ + sCrid = iCrids = NULL; +} + +cLinkItem::~cLinkItem(void) +{ + free(sCrid); + free(iCrids); +} + +void cLinkItem::Set(const char *sCRID, int ModTime, const char *iCRIDs) +{ + sCrid = strcpyrealloc(sCrid, sCRID); + modtime = ModTime; + iCrids = strcpyrealloc(iCrids, iCRIDs); +} + +/* + cLinks - list of cLinkItem entities +*/ + +cLinks::cLinks(void) +{ + maxNumber = 0; +} + +cLinkItem *cLinks::NewLinkItem(const char *sCRID, int ModTime, const char *iCRIDs) +{ + cLinkItem *NewLinkItem = new cLinkItem; + NewLinkItem->Set(sCRID, ModTime, iCRIDs); + Add(NewLinkItem); + Links->SetMaxNumber(Links->MaxNumber()+1); + return NewLinkItem; +} + +VDRPLUGINCREATOR(cPluginvdrTva); // Don't touch this! diff --git a/vdrtva.h b/vdrtva.h new file mode 100644 index 0000000..85cbe17 --- /dev/null +++ b/vdrtva.h @@ -0,0 +1,154 @@ +#include +#include +#include + +class cTvaFilter : public cFilter { +private: + cSectionSyncer sectionSyncer; + cPatFilter *patFilter; +protected: + virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); +public: + cTvaFilter(void); +}; + +class cTvaStatusMonitor : public cStatus { + private: + time_t timeradded; + protected: + virtual void TimerChange(const cTimer *Timer, eTimerChange Change); + // Indicates a change in the timer settings. + // If Change is tcAdd or tcDel, Timer points to the timer that has + // been added or will be deleted, respectively. In case of tcMod, + // Timer is NULL; this indicates that some timer has been changed. + // Note that tcAdd and tcDel are always also followed by a tcMod. + public: + cTvaStatusMonitor(void); + int GetTimerAddedDelta(void); + void ClearTimerAdded(void); +}; + + +class cTvaMenuSetup : public cMenuSetupPage { +private: + int newcollectionperiod; + int newlifetime; + int newpriority; + int newseriesLifetime; + int newupdatetime; +protected: + virtual void Store(void); +public: + cTvaMenuSetup(void); +}; + + +class cChanDA : public cListObject { + private: + int cid; + char *defaultAuthority; + public: + cChanDA(void); + ~cChanDA(void); + int Cid(void) { return cid; } + void Set(int Cid); + char * DA(void) { return defaultAuthority; } + void SetDA(char *DA); +}; + +class cChanDAs : public cRwLock, public cConfig { + private: + int maxNumber; + cHash chanDAHash; + public: + cChanDAs(void); + ~cChanDAs(void); + int MaxNumber(void) { return maxNumber; } + void SetMaxNumber(int number) { maxNumber = number; } + cChanDA *GetByChannelID(int cid); + cChanDA *NewChanDA(int Cid); +}; + + +class cEventCRID : public cListObject { + private: + tEventID eid; + int cid; + char *iCrid; + char *sCrid; + public: + cEventCRID(void); + ~cEventCRID(void); + tEventID Eid(void) { return eid; } + void Set(int Cid, tEventID Eid); + char * iCRID(void) { return iCrid; } + char * sCRID(void) { return sCrid; } + void SetCRIDs(char *iCRID, char *sCRID); + int Cid(void) { return cid; } +}; + +class cEventCRIDs : public cRwLock, public cConfig { + private: + int maxNumber; + cHash EventCRIDHash; + public: + cEventCRIDs(void); + ~cEventCRIDs(void); + int MaxNumber(void) { return maxNumber; } + void SetMaxNumber(int number) { maxNumber = number; } + cEventCRID *GetByID(int Cid, tEventID Eid); + cEventCRID *NewEventCRID(int Cid, tEventID Eid); +}; + + +class cSuggestCRID : public cListObject { + private: + char *iCrid; + char *gCrid; + int cid; + public: + cSuggestCRID(void); + ~cSuggestCRID(void); + char * iCRID(void) { return iCrid; } + char * gCRID(void) { return gCrid; } + int Cid(void) { return cid; } + void Set(int Cid, char *iCRID, char *gCRID); +}; + + +class cSuggestCRIDs : public cRwLock, public cConfig { + private: + int maxNumber; + public: + cSuggestCRIDs(void); + ~cSuggestCRIDs(void); + int MaxNumber(void) { return maxNumber; } + void SetMaxNumber(int number) { maxNumber = number; } + cSuggestCRID *NewSuggestCRID(int Cid, char *icrid, char *gcrid); +}; + + +class cLinkItem : public cListObject { + private: + char *sCrid; + int modtime; + char *iCrids; + public: + cLinkItem(void); + ~cLinkItem(void); + void Set(const char *sCRID, int ModTime, const char *iCRIDs); + char * iCRIDs(void) { return iCrids; } + char * sCRID(void) { return sCrid; } + int ModTime(void) { return modtime; } +}; + +class cLinks : public cRwLock, public cConfig { + private: + int maxNumber; + public: + cLinks(void); +// ~cLinks(void); + int MaxNumber(void) { return maxNumber; } + void SetMaxNumber(int number) { maxNumber = number; } + cLinkItem *NewLinkItem(const char *sCRID, int ModTime, const char *iCRIDs); +}; diff --git a/vdrvps-1.7.22.diff b/vdrvps-1.7.22.diff new file mode 100644 index 0000000..c851995 --- /dev/null +++ b/vdrvps-1.7.22.diff @@ -0,0 +1,61 @@ +diff -u vdr-1.7.22/config.c vdr-1.7/config.c +--- vdr-1.7.22/config.c 2011-12-03 15:21:30.000000000 +0000 ++++ vdr-1.7/config.c 2011-12-14 15:19:34.000000000 +0000 +@@ -416,6 +416,7 @@ + UseSubtitle = 1; + UseVps = 0; + VpsMargin = 120; ++ VpsFallback = 0; + RecordingDirs = 1; + FoldersInTimerMenu = 1; + NumberKeysForChars = 1; +@@ -610,6 +611,7 @@ + else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); + else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); + else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); ++ else if (!strcasecmp(Name, "VpsFallback")) VpsFallback = atoi(Value); + else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); + else if (!strcasecmp(Name, "FoldersInTimerMenu")) FoldersInTimerMenu = atoi(Value); + else if (!strcasecmp(Name, "NumberKeysForChars")) NumberKeysForChars = atoi(Value); +@@ -707,6 +709,7 @@ + Store("UseSubtitle", UseSubtitle); + Store("UseVps", UseVps); + Store("VpsMargin", VpsMargin); ++ Store("VpsFallback", VpsFallback); + Store("RecordingDirs", RecordingDirs); + Store("FoldersInTimerMenu", FoldersInTimerMenu); + Store("NumberKeysForChars", NumberKeysForChars); +diff -u vdr-1.7.22/config.h vdr-1.7/config.h +--- vdr-1.7.22/config.h 2011-12-03 14:19:52.000000000 +0000 ++++ vdr-1.7/config.h 2011-12-14 15:19:34.000000000 +0000 +@@ -271,6 +271,7 @@ + int UseSubtitle; + int UseVps; + int VpsMargin; ++ int VpsFallback; + int RecordingDirs; + int FoldersInTimerMenu; + int NumberKeysForChars; +diff -u vdr-1.7.22/menu.c vdr-1.7/menu.c +--- vdr-1.7.22/menu.c 2011-12-04 14:52:38.000000000 +0000 ++++ vdr-1.7/menu.c 2011-12-14 15:19:34.000000000 +0000 +@@ -3092,6 +3092,7 @@ + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); + Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Use running status as VPS fallback"), &data.VpsFallback)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); + Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); + Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); +diff -u vdr-1.7.22/timers.c vdr-1.7/timers.c +--- vdr-1.7.22/timers.c 2011-08-06 14:13:54.000000000 +0100 ++++ vdr-1.7/timers.c 2011-12-14 15:19:34.000000000 +0000 +@@ -430,7 +430,7 @@ + deferred = 0; + + if (HasFlags(tfActive)) { +- if (HasFlags(tfVps) && event && event->Vps()) { ++ if (HasFlags(tfVps) && event && (Setup.VpsFallback || event->Vps())) { + if (Margin || !Directly) { + startTime = event->StartTime(); + stopTime = event->EndTime(); -- cgit v1.2.3