summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave <vdr@pickles.me.uk>2011-08-21 09:22:34 +0100
committerDave <vdr@pickles.me.uk>2011-08-21 09:22:34 +0100
commitbf74a992bb85b026802eae35f884c949706c3d1f (patch)
tree613875c88ca8eaac6187b6b98d98d56dbdf55cb7
parent7a02673d36a45482a111687df03c652560a07468 (diff)
downloadvdrtva-bf74a992bb85b026802eae35f884c949706c3d1f.tar.gz
vdrtva-bf74a992bb85b026802eae35f884c949706c3d1f.tar.bz2
Initial plugin release.
-rw-r--r--plugin/HISTORY21
-rw-r--r--plugin/Makefile111
-rw-r--r--plugin/README81
-rw-r--r--plugin/README - series48
-rw-r--r--plugin/README - vps33
-rw-r--r--plugin/TODO13
-rw-r--r--plugin/vdrtva.c964
-rw-r--r--plugin/vdrtva.h95
-rw-r--r--plugin/vdrvps-1.7.20.diff61
9 files changed, 1427 insertions, 0 deletions
diff --git a/plugin/HISTORY b/plugin/HISTORY
new file mode 100644
index 0000000..b6f70dc
--- /dev/null
+++ b/plugin/HISTORY
@@ -0,0 +1,21 @@
+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.
diff --git a/plugin/Makefile b/plugin/Makefile
new file mode 100644
index 0000000..421a70e
--- /dev/null
+++ b/plugin/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='<see README>' -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
new file mode 100644
index 0000000..37cd204
--- /dev/null
+++ b/plugin/README
@@ -0,0 +1,81 @@
+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 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://<DNS name>/<data>
+
+in which <DNS name> is a registered internet domain name (RFC 1034) and <data>
+is a free-format string. The <DNS Name> section relates to the content provider
+(TV channel or company), and the <data> section to the programme.
+
+CRIDs are transmitted in the EIT as Content Identifier Descriptors, with
+descriptor ID 0x76. To save bandwith only the <data> section is sent, the <DNS
+Name> part is taken from the Default Authority Descriptor in the SDT, and the
+crid:// is assumed.
+
+A programme may have up to two CRIDs in its EPG entry. One identifies the
+specific item of content which is being broadcast, while the other identifies a
+series of programmes which this item belongs to. In FreeView Plus these CRIDs
+have crid_type values 0x31 and 0x32 respectively (TV-Anytime uses values 0x01
+and 0x02).
+
+To give an example, the programme "Torchwood" broadcast on channel BBC2 at 21:00
+on 2008-01-16 had item CRID '54BXLC' and series CRID 'KCJ00C'. When the same
+programme was repeated the following day on channel BBC3, the item CRID remained
+the same but the series CRID was 'KCJ12C'. Meanwhile the episode broadcast on
+BBC2 one week later on 2008-01-24 had CRID '54BXLD' but the same series as the
+previous week. Hence it is possible for a PVR to record an entire series by
+using the series CRID, or to find an alternative broadcast for an individual
+item if there is a clash with another recording.
+
+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 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 new manually-created timers and adds series links 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.
+
+
+This is Alpha-quality code - USE AT YOUR OWN RISK!!
diff --git a/plugin/README - series b/plugin/README - series
new file mode 100644
index 0000000..1450945
--- /dev/null
+++ b/plugin/README - series
@@ -0,0 +1,48 @@
+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/plugin/README - vps b/plugin/README - vps
new file mode 100644
index 0000000..8d172c2
--- /dev/null
+++ b/plugin/README - vps
@@ -0,0 +1,33 @@
+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
new file mode 100644
index 0000000..8e78452
--- /dev/null
+++ b/plugin/TODO
@@ -0,0 +1,13 @@
+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.
+
+Maybe add a timer callback so that a series can be registered immediately
+instead of waiting for an overnight update.
+
+Config file
diff --git a/plugin/vdrtva.c b/plugin/vdrtva.c
new file mode 100644
index 0000000..9c7dee6
--- /dev/null
+++ b/plugin/vdrtva.c
@@ -0,0 +1,964 @@
+/*
+ * vdrtva.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <vdr/plugin.h>
+#include <libsi/section.h>
+#include <libsi/descriptor.h>
+#include <stdarg.h>
+#include <getopt.h>
+
+#include "vdrtva.h"
+
+#define SVDRPOSD_BUFSIZE KILOBYTE(4)
+#define SECONDSPERDAY (86400)
+
+cChanDAs *ChanDAs;
+cEventCRIDs *EventCRIDs;
+cLinks *Links;
+
+static const char *VERSION = "0.0.4";
+static const char *DESCRIPTION = "TV-Anytime plugin";
+static const char *MAINMENUENTRY = "vdrTva";
+
+class cPluginvdrTva : public cPlugin {
+private:
+ // Add any member variables or functions you may need here.
+ int length;
+ int size;
+ int seriesLifetime;
+ int priority;
+ int lifetime;
+ int flags;
+ int collectionperiod;
+ int state;
+ time_t nextactiontime;
+ int updatehours;
+ int updatemins;
+ char *buffer;
+ char* configDir;
+ cTvaFilter *Filter;
+ 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;
+ updatehours = 3;
+ updatemins = 0;
+}
+
+cPluginvdrTva::~cPluginvdrTva()
+{
+ // Clean up after yourself!
+}
+
+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);
+ updatehours = atoi(hours);
+ updatemins = 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();
+
+ 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 = updatehours;
+ tm_r.tm_min = updatemins;
+ 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)) {
+ 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;
+ }
+ }
+}
+
+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 NULL;
+}
+
+bool cPluginvdrTva::SetupParse(const char *Name, const char *Value)
+{
+ // Parse your own setup parameters and store their values.
+ return false;
+}
+
+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()
+{
+ if(
+ UpdateLinksFromTimers() ||
+ AddNewEventsToSeries()
+ ) 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());
+ Links->Del(Item);
+ }
+ }
+ 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];
+ 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);
+ cString timercmd = cString::sprintf("%u:%d:%s:%s:%d:%d:%s:\n", flags, channel->Number(), startbuff, endbuff, priority, lifetime, event->Title());
+ 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", *timer->ToDescr());
+ AddSeriesLink(scrid, event->StartTime(), icrid);
+ saveNeeded = true;
+ }
+ else isyslog("vdrtva: Duplicate timer creation attempted for %s", *timer->ToDescr());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ 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' seems to no longer exist", timer->File());
+ else if (strcmp(timer->File(), event->Title())) {
+ isyslog("vdrtva: Changed timer event: %s <=> %s", timer->File(), event->Title());
+ }
+ }
+ }
+ }
+ }
+}
+
+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());
+}
+
+
+/*
+ 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) {
+ return;
+ }
+ 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<cHashObject> *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<cHashObject> *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
new file mode 100644
index 0000000..785f846
--- /dev/null
+++ b/plugin/vdrtva.h
@@ -0,0 +1,95 @@
+#include <vdr/filter.h>
+#include <vdr/device.h>
+
+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 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<cChanDA> {
+ private:
+ int maxNumber;
+ cHash<cChanDA> 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<cEventCRID> {
+ private:
+ int maxNumber;
+ cHash<cEventCRID> 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<cLinkItem> {
+ 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.20.diff b/plugin/vdrvps-1.7.20.diff
new file mode 100644
index 0000000..8f93683
--- /dev/null
+++ b/plugin/vdrvps-1.7.20.diff
@@ -0,0 +1,61 @@
+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/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/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();