diff --git a/.gitignore b/.gitignore index e5e9b86..d736688 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ /2ea854ae8c7a.zip /vdr-dvbsddevice-2.2.0.tgz /vdr-rcu-2.2.0.tgz -/3473a7b939d7.zip diff --git a/0001-Fix-build-with-systemd-230.patch b/0001-Fix-build-with-systemd-230.patch new file mode 100644 index 0000000..f96210f --- /dev/null +++ b/0001-Fix-build-with-systemd-230.patch @@ -0,0 +1,29 @@ +From 0376d2e4adc38528c0cbdde759a56700bee95872 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ville=20Skytt=C3=A4?= +Date: Fri, 17 Jun 2016 17:08:31 +0300 +Subject: [PATCH] Fix build with systemd >= 230 + +libsystemd-daemon has been merged to libsystemd. +--- + Makefile | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/Makefile b/Makefile +index b90cbd2..9193cde 100644 +--- a/Makefile ++++ b/Makefile +@@ -95,9 +95,9 @@ DEFINES += -DBIDI + LIBS += $(shell pkg-config --libs fribidi) + endif + ifdef SDNOTIFY +-INCLUDES += $(shell pkg-config --cflags libsystemd-daemon) ++INCLUDES += $(shell pkg-config --silence-errors --cflags libsystemd-daemon || pkg-config --cflags libsystemd) + DEFINES += -DSDNOTIFY +-LIBS += $(shell pkg-config --libs libsystemd-daemon) ++LIBS += $(shell pkg-config --silence-errors --libs libsystemd-daemon || pkg-config --libs libsystemd) + endif + + LIRC_DEVICE ?= /var/run/lirc/lircd +-- +2.5.5 + diff --git a/dvbhdffdevice.patch b/dvbhdffdevice.patch new file mode 100644 index 0000000..af751df --- /dev/null +++ b/dvbhdffdevice.patch @@ -0,0 +1,12 @@ +--- dvbhddevice/dvbhdffdevice.c.orig 2018-04-17 13:57:26.871268867 +0200 ++++ dvbhddevice/dvbhdffdevice.c 2018-04-17 13:58:45.698264299 +0200 +@@ -461,7 +461,8 @@ + const tTrackId *TrackId = GetTrack(Type); + if (TrackId && TrackId->id) { + int streamType = 0; +- cChannel * channel = Channels.GetByNumber(CurrentChannel()); ++ LOCK_CHANNELS_READ; ++ const cChannel * channel = Channels->GetByNumber(CurrentChannel()); + if (channel) { + if (IS_AUDIO_TRACK(Type)) + streamType = channel->Atype(Type - ttAudioFirst); diff --git a/missing-kernel-headers.patch b/missing-kernel-headers.patch new file mode 100644 index 0000000..2a8e225 --- /dev/null +++ b/missing-kernel-headers.patch @@ -0,0 +1,66 @@ +diff -Naur vdr-2.4.7/PLUGINS/src/dvbhddevice/dvbhdffdevice.c vdr-2.4.7-new/PLUGINS/src/dvbhddevice/dvbhdffdevice.c +--- vdr-2.4.7/PLUGINS/src/dvbhddevice/dvbhdffdevice.c 2018-04-20 22:23:47.000000000 +0200 ++++ vdr-2.4.7-new/PLUGINS/src/dvbhddevice/dvbhdffdevice.c 2021-08-03 14:22:36.172626332 +0200 +@@ -11,9 +11,9 @@ + #include + #include + #include +-#include ++#include <../kernel-headers-5.13/audio.h> + #include +-#include ++#include <../kernel-headers-5.13/video.h> + #include + #include + #include +diff -Naur vdr-2.4.7/PLUGINS/src/dvbhddevice/hdffosd.c vdr-2.4.7-new/PLUGINS/src/dvbhddevice/hdffosd.c +--- vdr-2.4.7/PLUGINS/src/dvbhddevice/hdffosd.c 2018-04-20 22:23:47.000000000 +0200 ++++ vdr-2.4.7-new/PLUGINS/src/dvbhddevice/hdffosd.c 2021-08-03 14:22:36.177626332 +0200 +@@ -5,7 +5,7 @@ + */ + + #include "hdffosd.h" +-#include ++#include <../kernel-headers-5.13/osd.h> + #include + #include + #include "hdffcmd.h" +diff -Naur vdr-2.4.7/PLUGINS/src/dvbhddevice/dvbsdffosd.c vdr-2.4.7-new/PLUGINS/src/dvbhddevice/dvbsdffosd.c +--- vdr-2.4.7/PLUGINS/src/dvbsddevice/dvbsdffosd.c 2011-04-17 14:55:09.000000000 +0200 ++++ vdr-2.4.7-new/PLUGINS/src/dvbsddevice/dvbsdffosd.c 2021-08-03 14:22:36.188626333 +0200 +@@ -7,7 +7,7 @@ + */ + + #include "dvbsdffosd.h" +-#include ++#include <../kernel-headers-5.13/osd.h> + #include + #include + #include +diff -Naur vdr-2.4.7/PLUGINS/src/dvbhddevice/libhdffcmd/hdffcmd_base.h vdr-2.4.7-new/PLUGINS/src/dvbhddevice/libhdffcmd/hdffcmd_base.h +--- vdr-2.4.7/PLUGINS/src/dvbhddevice/libhdffcmd/hdffcmd_base.h 2018-04-20 22:23:47.000000000 +0200 ++++ vdr-2.4.7-new/PLUGINS/src/dvbhddevice/libhdffcmd/hdffcmd_base.h 2021-08-03 14:22:36.193626333 +0200 +@@ -24,7 +24,7 @@ + #ifndef HDFFCMD_BASE_H + #define HDFFCMD_BASE_H + +-#include ++#include <../kernel-headers-5.13/osd.h> + + #if !defined OSD_RAW_CMD + typedef struct osd_raw_cmd_s { +diff -Naur vdr-2.4.7/PLUGINS/src/dvbsddevice/dvbsdffdevice.c vdr-2.4.7-new/PLUGINS/src/dvbsddevice/dvbsdffdevice.c +--- vdr-2.4.7/PLUGINS/src/dvbsddevice/dvbsdffdevice.c 2014-03-15 13:35:21.000000000 +0100 ++++ vdr-2.4.7-new/PLUGINS/src/dvbsddevice/dvbsdffdevice.c 2021-08-03 14:22:36.203626334 +0200 +@@ -10,9 +10,9 @@ + #include + #include + #include +-#include ++#include <../kernel-headers-5.13/audio.h> + #include +-#include ++#include <../kernel-headers-5.13/video.h> + #include + #include + #include diff --git a/MainMenuHooks-v1_0_4.diff.txt b/opt-42-x_MainMenuHooks-v1.0.3.patch similarity index 85% rename from MainMenuHooks-v1_0_4.diff.txt rename to opt-42-x_MainMenuHooks-v1.0.3.patch index f24df98..150f96b 100644 --- a/MainMenuHooks-v1_0_4.diff.txt +++ b/opt-42-x_MainMenuHooks-v1.0.3.patch @@ -1,12 +1,8 @@ +Description: This patch allows plugins to replace the VDR mainmenus "Schedule", "Channels", "Timers" and "Recordings" by a different implementation. +Author: Frank Schmirler This is a "patch" for the Video Disk Recorder (VDR). * History -2025-02-26: Version 1.0.4 -Update für vdr-2.7.4 (Jörg Riechardt) - -2013-11-08: Version 1.0.3 -Update für vdr-2.1.2 (Jörg Riechardt) - 2012-04-06: Version 1.0.2 - Update für aktuelle VDR-Entwickler-Versionen (Manuel Reimer) @@ -79,25 +75,11 @@ think you need to modify the patch, we'd encourage you to contact the authors first or at least use a service id which differs in more than just the version number. - -diff -Nrup vdr-2.7.4/config.h vdr-2.7.4-test/config.h ---- vdr-2.7.4/config.h 2025-02-26 10:35:03.000000000 +0100 -+++ vdr-2.7.4-test/config.h 2025-02-26 14:46:28.768820964 +0100 -@@ -35,6 +35,10 @@ - // only when there are changes to the plugin API. This allows compiled - // plugins to work with newer versions of the core VDR as long as no - // interfaces have changed. APIVERSNUM begins with "300.." for backwards -+ -+// The MainMenuHook Patch's version number: -+#define MAINMENUHOOKSVERSION "1.0.1" -+#define MAINMENUHOOKSVERSNUM 10001 // Version * 10000 + Major * 100 + Minor - // compatibility and can be used in #if preprocessor statements to handle - // version dependent code. - -diff -Nrup vdr-2.7.4/menu.c vdr-2.7.4-test/menu.c ---- vdr-2.7.4/menu.c 2025-02-26 10:35:03.000000000 +0100 -+++ vdr-2.7.4-test/menu.c 2025-02-26 15:20:42.252563574 +0100 -@@ -4493,15 +4493,31 @@ cMenuMain::cMenuMain(eOSState State, boo +Index: b/menu.c +=================================================================== +--- a/menu.c ++++ b/menu.c +@@ -4620,15 +4620,31 @@ // Initial submenus: @@ -123,7 +105,7 @@ diff -Nrup vdr-2.7.4/menu.c vdr-2.7.4-test/menu.c + break; + case osRecordings: + if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu)) -+ menu = new cMenuRecordings(NULL, 0, OpenSubMenus); ++ menu = new cMenuRecordings(NULL, 0, true); + break; + case osSetup: menu = new cMenuSetup; break; + case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break; @@ -135,7 +117,7 @@ diff -Nrup vdr-2.7.4/menu.c vdr-2.7.4-test/menu.c } cOsdObject *cMenuMain::PluginOsdObject(void) -@@ -4613,13 +4629,34 @@ eOSState cMenuMain::ProcessKey(eKeys Key +@@ -4765,13 +4781,34 @@ eOSState state = cOsdMenu::ProcessKey(Key); HadSubMenu |= HasSubMenu(); @@ -176,7 +158,7 @@ diff -Nrup vdr-2.7.4/menu.c vdr-2.7.4-test/menu.c case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) { if (cOsdItem *item = Get(Current())) { cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING))); -@@ -4670,6 +4707,12 @@ eOSState cMenuMain::ProcessKey(eKeys Key +@@ -4857,6 +4894,12 @@ default: break; } } @@ -185,7 +167,22 @@ diff -Nrup vdr-2.7.4/menu.c vdr-2.7.4-test/menu.c + return AddSubMenu((cOsdMenu *) menu); + pluginOsdObject = menu; + return osPlugin; -+ } - bool DoDisplay = Update(); ++ } + if (!HasSubMenu() && Update(HadSubMenu)) + Display(); if (Key != kNone) { - if (I18nCurrentLanguage() != osdLanguage) { +Index: b/config.h +=================================================================== +--- a/config.h ++++ b/config.h +@@ -36,6 +36,10 @@ + // plugins to work with newer versions of the core VDR as long as no + // VDR header files have changed. + ++// The MainMenuHook Patch's version number: ++#define MAINMENUHOOKSVERSION "1.0.1" ++#define MAINMENUHOOKSVERSNUM 10001 // Version * 10000 + Major * 100 + Minor ++ + #define MAXPRIORITY 99 + #define MINPRIORITY (-MAXPRIORITY) + #define LIVEPRIORITY 0 // priority used when selecting a device for live viewing diff --git a/skincurses-log-errors.patch b/skincurses-log-errors.patch new file mode 100644 index 0000000..0a43729 --- /dev/null +++ b/skincurses-log-errors.patch @@ -0,0 +1,21 @@ +Description: Log an error message when skincures can't initialize the screen +Author: Tobias Grimm + +--- a/PLUGINS/src/skincurses/skincurses.c ++++ b/PLUGINS/src/skincurses/skincurses.c +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + + static const char *VERSION = "2.4.0"; + static const char *DESCRIPTION = trNOOP("A text only skin"); +@@ -833,6 +834,7 @@ + ScOsdHeight = w->_maxy - w->_begy + 1; + return true; + } ++ esyslog("skincurses: unable to initialize curses screen"); + return false; + } + diff --git a/sources b/sources index 2a16883..fe0c53e 100644 --- a/sources +++ b/sources @@ -1,7 +1,7 @@ -SHA512 (vdr-2.7.7.tar.bz2) = 22c561aea6c2ae29f27d024249501c3c748cd77e9c182ed969a59f24b4b360b59cf807e1214665bbec7797b6ec11beeac0b2f02e8427f0c91cf9c29144bd5154 +SHA512 (vdr-2.6.3.tar.bz2) = d84ca2cd797c80134a28cebc28172f45081d4f9580122f823dde396d106a15fa88d5f040252b8ee7caa7d44fa6f1cfba37a13c6a36ecd3fd2734b0140f7c438c SHA512 (vdr-rcu-2.2.0.tgz) = 56abebcfde3511c4dcf6f414915c7aa70a16b8e138e2d9364080828edc9b0da045acaadaaa4094d1315ddefdafa5f966aebcfacf979c068e077f1e32e29773b7 SHA512 (vdr-dvbsddevice-2.2.0.tgz) = ac0b94b8b192208ad7e736d7c4f27cca6517134b17fc86a79cdd19453176d5f6076418bf679435899cd953053f3b54776342bcdab23a659e8331e9f2ed4ee364 -SHA512 (3473a7b939d7.zip) = fc405ac81cc1374de3a2d457f76972e605cf51c20c5e86f4fb683bb7fc78fde51690e65f463870c6881a65308722ccecd60d4a8957e3e2d43ecfd665f0e5aa8f +SHA512 (2ea854ae8c7a.zip) = 2b3e5c91646e4250afc53577393c9d10f11fcbc46353535e38e9ddcfbf5642a4dd0355bd7583262bd684ef76cf75373585b3f67fdf154f22ee85541625fa467f SHA512 (vdr_2.2.0-5.debian.tar.bz2) = 6ba924fee84673be2a2652a26bccde4dc07dd9c7bcc871868f44bdc2748136a8ac26feb026c5b5dd9b3831c641ef1e729052c1190a45cc805f3fa563c3a59798 SHA512 (vdr_1.4.5-2.ds.diff.gz) = dc82ca24e830a4fc4bfed51b2d112301b9058f2d1fea93761a42df8715dbe3ba0e9e8621464c8a6fe2dd3bd62d33efaff911182d3489ef741096654b7a3eb326 SHA512 (vdr-2.4.6-editrecording.patch.gz) = 0935fdbe6b557c8b03396ff31021b35758a040f297315f39bdf605961a8ad77897884b527e2655e36ff8241e06cd1b2592c3bd0be09e78434263e43e85368e86 diff --git a/vdr-1.5.17-progressbar-support-0.0.1.diff b/vdr-1.5.17-progressbar-support-0.0.1.diff new file mode 100644 index 0000000..98b887d --- /dev/null +++ b/vdr-1.5.17-progressbar-support-0.0.1.diff @@ -0,0 +1,106 @@ +diff -Nru vdr-1.5.17-orig/skinclassic.c vdr-1.5.17-progressbar/skinclassic.c +--- vdr-1.5.17-orig/skinclassic.c ++++ vdr-1.5.17-progressbar/skinclassic.c +@@ -314,8 +314,47 @@ + for (int i = 0; i < MaxTabs; i++) { + const char *s = GetTabbedText(Text, i); + if (s) { +- int xt = x0 + Tab(i); +- osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x2 - xt); ++ bool isprogressbar = false; ++ int now = 0, total = 0; ++ // check if progress bar: "[||||||| ]" ++ if ((strlen(s) > 5 && s[0] == '[' && s[strlen(s) - 1] == ']')) { ++ const char *p = s + 1; ++ // update status ++ isprogressbar = true; ++ for (; *p != ']'; ++p) { ++ // check if progressbar characters ++ if (*p == ' ' || *p == '|') { ++ // update counters ++ ++total; ++ if (*p == '|') ++ ++now; ++ } ++ else { ++ // wrong character detected; not a progressbar ++ isprogressbar = false; ++ break; ++ } ++ } ++ } ++ int xt = x0 + Tab(i); ++ if (isprogressbar) { ++ // define x coordinates of progressbar ++ int px0 = xt; ++ int px1 = (Tab(i + 1)?Tab(i+1):x1) - 5; ++ int px = px0 + max((int)((float) now * (float) (px1 - px0) / (float) total), 1); ++ // define y coordinates of progressbar ++ int py0 = y + 4; ++ int py1 = y + lineHeight - 4; ++ // draw background ++ osd->DrawRectangle(px0, y, (Tab(i + 1)?Tab(i+1):x1) - 1, y + lineHeight - 1, ColorBg); ++ // draw progressbar ++ osd->DrawRectangle(px0, py0, px, py1, ColorFg); ++ osd->DrawRectangle(px + 1, py0, px1, py0 + 1, ColorFg); ++ osd->DrawRectangle(px + 1, py1 - 1, px1, py1, ColorFg); ++ osd->DrawRectangle(px1 - 1, py0, px1, py1, ColorFg); ++ } ++ else ++ osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x2 - xt); + } + if (!Tab(i + 1)) + break; +diff -Nru vdr-1.5.17-orig/skinsttng.c vdr-1.5.17-progressbar/skinsttng.c +--- vdr-1.5.17-orig/skinsttng.c ++++ vdr-1.5.17-progressbar/skinsttng.c +@@ -558,8 +558,47 @@ + for (int i = 0; i < MaxTabs; i++) { + const char *s = GetTabbedText(Text, i); + if (s) { +- int xt = x3 + 5 + Tab(i); +- osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x4 - xt); ++ bool isprogressbar = false; ++ int now = 0, total = 0; ++ // check if progress bar: "[||||||| ]" ++ if ((strlen(s) > 5 && s[0] == '[' && s[strlen(s) - 1] == ']')) { ++ const char *p = s + 1; ++ // update status ++ isprogressbar = true; ++ for (; *p != ']'; ++p) { ++ // check if progressbar characters ++ if (*p == ' ' || *p == '|') { ++ // update counters ++ ++total; ++ if (*p == '|') ++ ++now; ++ } ++ else { ++ // wrong character detected; not a progressbar ++ isprogressbar = false; ++ break; ++ } ++ } ++ } ++ int xt = x3 + 5 + Tab(i); ++ if (isprogressbar) { ++ // define x coordinates of progressbar ++ int px0 = xt; ++ int px1 = x3 + (Tab(i + 1)?Tab(i + 1):x4-x3-5) - 1; ++ int px = px0 + max((int)((float) now * (float) (px1 - px0) / (float) total), 1); ++ // define y coordinates of progressbar ++ int py0 = y + 4; ++ int py1 = y + lineHeight - 4; ++ // draw background ++ osd->DrawRectangle(px0, y, (Tab(i + 1)?Tab(i + 1):x4-x3-5) - 1, y + lineHeight - 1, ColorBg); ++ // draw progressbar ++ osd->DrawRectangle(px0, py0, px, py1, ColorFg); ++ osd->DrawRectangle(px + 1, py0, px1, py0 + 1, ColorFg); ++ osd->DrawRectangle(px + 1, py1 - 1, px1, py1, ColorFg); ++ osd->DrawRectangle(px1 - 1, py0, px1, py1, ColorFg); ++ } ++ else ++ osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x4 - xt); + } + if (!Tab(i + 1)) + break; diff --git a/vdr-1.5.18-syncearly.patch b/vdr-1.5.18-syncearly.patch new file mode 100644 index 0000000..de06ca8 --- /dev/null +++ b/vdr-1.5.18-syncearly.patch @@ -0,0 +1,114 @@ +Sync early parts extracted from the non-dvbs2 patch at +http://article.gmane.org/gmane.linux.vdr/36097, fprintf(stderr) calls +changed to dsyslog(). + +diff -Nurp ../vdr-1.5.18-orig/device.c ./device.c +--- ../vdr-1.5.18-orig/device.c 2008-03-09 11:03:34.000000000 +0100 ++++ ./device.c 2008-03-19 22:34:40.000000000 +0100 +@@ -840,7 +840,7 @@ eSetChannelResult cDevice::SetChannel(co + } + for (int i = 0; i < MAXSPIDS; i++) + SetAvailableTrack(ttSubtitle, i, Channel->Spid(i), Channel->Slang(i)); +- if (!NeedsTransferMode) ++ if (!NeedsTransferMode || GetCurrentAudioTrack() == ttNone) + EnsureAudioTrack(true); + EnsureSubtitleTrack(); + } +diff -Nurp ../vdr-1.5.18-orig/remux.c ./remux.c +--- ../vdr-1.5.18-orig/remux.c 2007-11-25 14:56:03.000000000 +0100 ++++ ./remux.c 2008-02-24 19:47:40.000000000 +0100 +@@ -1896,12 +2526,13 @@ int cRingBufferLinearPes::DataReady(cons + + #define RESULTBUFFERSIZE KILOBYTE(256) + +-cRemux::cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure) ++cRemux::cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure, bool SyncEarly) + { + exitOnFailure = ExitOnFailure; + noVideo = VPid == 0 || VPid == 1 || VPid == 0x1FFF; + numUPTerrors = 0; + synced = false; ++ syncEarly = SyncEarly; + skipped = 0; + numTracks = 0; + resultSkipped = 0; +@@ -2105,12 +2840,14 @@ uchar *cRemux::Get(int &Count, uchar *Pi + } + } + else if (!synced) { +- if (pt == I_FRAME) { ++ if (pt == I_FRAME || syncEarly) { + if (PictureType) + *PictureType = pt; + resultSkipped = i; // will drop everything before this position +- SetBrokenLink(data + i, l); + synced = true; ++ if (pt == I_FRAME) // syncEarly: it's ok but there is no need to call SetBrokenLink() ++ SetBrokenLink(data + i, l); ++else dsyslog("video: synced early"); + } + } + else if (Count) +@@ -2123,17 +2860,19 @@ uchar *cRemux::Get(int &Count, uchar *Pi + l = GetPacketLength(data, resultCount, i); + if (l < 0) + return resultData; +- if (noVideo) { ++ if (noVideo || !synced && syncEarly) { ++ uchar pt = NO_PICTURE; + if (!synced) { +- if (PictureType) +- *PictureType = I_FRAME; ++ if (PictureType && noVideo) ++ *PictureType = pt; + resultSkipped = i; // will drop everything before this position + synced = true; ++if (!noVideo) dsyslog("audio: synced early"); + } + else if (Count) + return resultData; + else if (PictureType) +- *PictureType = I_FRAME; ++ *PictureType = pt; + } + } + if (synced) { +diff -Nurp ../vdr-1.5.18-orig/remux.h ./remux.h +--- ../vdr-1.5.18-orig/remux.h 2007-09-02 12:19:06.000000000 +0200 ++++ ./remux.h 2008-02-24 19:47:40.000000000 +0100 +@@ -40,6 +40,7 @@ + bool noVideo; + int numUPTerrors; + bool synced; ++ bool syncEarly; + int skipped; + cTS2PES *ts2pes[MAXTRACKS]; + int numTracks; +@@ -47,12 +48,13 @@ + int resultSkipped; + int GetPid(const uchar *Data); + public: +- cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure = false); ++ cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure = false, bool SyncEarly = false); + ///< Creates a new remuxer for the given PIDs. VPid is the video PID, while + ///< APids, DPids and SPids are pointers to zero terminated lists of audio, + ///< dolby and subtitle PIDs (the pointers may be NULL if there is no such + ///< PID). If ExitOnFailure is true, the remuxer will initiate an "emergency +- ///< exit" in case of problems with the data stream. ++ ///< exit" in case of problems with the data stream. SyncEarly causes cRemux ++ ///< to sync as soon as a video or audio frame is seen. + ~cRemux(); + void SetTimeouts(int PutTimeout, int GetTimeout) { resultBuffer->SetTimeouts(PutTimeout, GetTimeout); } + ///< By default cRemux assumes that Put() and Get() are called from different +diff -Nurp ../vdr-1.5.18-orig/transfer.c ./transfer.c +--- ../vdr-1.5.18-orig/transfer.c 2007-01-05 11:45:28.000000000 +0100 ++++ ./transfer.c 2008-02-24 19:47:40.000000000 +0100 +@@ -19,7 +19,7 @@ cTransfer::cTransfer(tChannelID ChannelI + ,cThread("transfer") + { + ringBuffer = new cRingBufferLinear(TRANSFERBUFSIZE, TS_SIZE * 2, true, "Transfer"); +- remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids); ++ remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, false, true); + } + + cTransfer::~cTransfer() diff --git a/vdr-1.7.21-timercmd.patch b/vdr-1.7.21-timercmd.patch new file mode 100644 index 0000000..8d31543 --- /dev/null +++ b/vdr-1.7.21-timercmd.patch @@ -0,0 +1,107 @@ +diff -up vdr-1.7.21/config.c~ vdr-1.7.21/config.c +--- vdr-1.7.21/config.c~ 2011-10-18 00:02:51.394223695 +0300 ++++ vdr-1.7.21/config.c 2011-10-18 00:12:30.255855027 +0300 +@@ -211,6 +211,7 @@ bool cNestedItemList::Save(void) + cNestedItemList Folders; + cNestedItemList Commands; + cNestedItemList RecordingCommands; ++cNestedItemList TimerCommands; + + // --- cSVDRPhosts ----------------------------------------------------------- + +diff -up vdr-1.7.21/config.h~ vdr-1.7.21/config.h +--- vdr-1.7.21/config.h~ 2011-10-18 00:02:51.438227770 +0300 ++++ vdr-1.7.21/config.h 2011-10-18 00:12:30.702896536 +0300 +@@ -187,6 +187,7 @@ public: + extern cNestedItemList Folders; + extern cNestedItemList Commands; + extern cNestedItemList RecordingCommands; ++extern cNestedItemList TimerCommands; + extern cSVDRPhosts SVDRPhosts; + + class cSetupLine : public cListObject { +diff -up vdr-1.7.21/menu.c~ vdr-1.7.21/menu.c +--- vdr-1.7.21/menu.c~ 2011-10-18 00:02:51.428226843 +0300 ++++ vdr-1.7.21/menu.c 2011-10-18 00:12:29.520786768 +0300 +@@ -1142,6 +1142,7 @@ void cTimerEntry::SetDiskStatus(char Dis + + class cMenuTimers : public cOsdMenu { + private: ++ eOSState Commands(eKeys Key = kNone); + int helpKeys; + eOSState Edit(void); + eOSState New(void); +@@ -1259,6 +1260,53 @@ eOSState cMenuTimers::Delete(void) + return osContinue; + } + ++#define CHECK_2PTR_NULL(x_,y_) ((x_)? ((y_)? y_:""):"") ++ ++eOSState cMenuTimers::Commands(eKeys Key) ++{ ++ if (HasSubMenu() || Count() == 0) ++ return osContinue; ++ cTimer *ti = CurrentTimer(); ++ if (ti) { ++ char *parameter = NULL; ++ const cEvent *pEvent = ti->Event(); ++ int iRecNumber=0; ++ ++ if(!pEvent) { ++ Timers.SetEvents(); ++ pEvent = ti->Event(); ++ } ++ if(pEvent) { ++// create a dummy recording to get the real filename ++ cRecording *rc_dummy = new cRecording(ti, pEvent); ++ Recordings.Load(); ++ cRecording *rc = Recordings.GetByName(rc_dummy->FileName()); ++ ++ delete rc_dummy; ++ if(rc) ++ iRecNumber=rc->Index() + 1; ++ } ++//Parameter format TimerNumber 'ChannelId' Start Stop 'Titel' 'Subtitel' 'file' RecNumer ++// 1 2 3 4 5 6 7 8 ++ asprintf(¶meter, "%d '%s' %d %d '%s' '%s' '%s' %d", ti->Index(), ++ *ti->Channel()->GetChannelID().ToString(), ++ (int)ti->StartTime(), ++ (int)ti->StopTime(), ++ CHECK_2PTR_NULL(pEvent, pEvent->Title()), ++ CHECK_2PTR_NULL(pEvent, pEvent->ShortText()), ++ ti->File(), ++ iRecNumber); ++ isyslog("timercmd: %s", parameter); ++ cMenuCommands *menu; ++ eOSState state = AddSubMenu(menu = new cMenuCommands(tr("Timer commands"), &TimerCommands, parameter)); ++ free(parameter); ++ if (Key != kNone) ++ state = menu->ProcessKey(Key); ++ return state; ++ } ++ return osContinue; ++} ++ + eOSState cMenuTimers::Info(void) + { + if (HasSubMenu() || Count() == 0) +@@ -1346,6 +1394,8 @@ eOSState cMenuTimers::ProcessKey(eKeys K + case kInfo: + case kBlue: return Info(); + break; ++ case k1...k9: return Commands(Key); ++ case k0: return (TimerCommands.Count()? Commands():osContinue); + default: break; + } + } +diff -up vdr-1.7.21/vdr.c~ vdr-1.7.21/vdr.c +--- vdr-1.7.21/vdr.c~ 2011-10-18 00:02:51.284213503 +0300 ++++ vdr-1.7.21/vdr.c 2011-10-18 00:12:31.901007794 +0300 +@@ -602,6 +602,7 @@ int main(int argc, char *argv[]) + Timers.Load(AddDirectory(ConfigDirectory, "timers.conf")); + Commands.Load(AddDirectory(ConfigDirectory, "commands.conf")); + RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf")); ++ TimerCommands.Load(AddDirectory(ConfigDirectory, "timercmds.conf")); + SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true); + Keys.Load(AddDirectory(ConfigDirectory, "remote.conf")); + KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true); diff --git a/vdr-1.7.29-hlcutter-0.2.3.diff b/vdr-1.7.29-hlcutter-0.2.3.diff new file mode 100644 index 0000000..776a278 --- /dev/null +++ b/vdr-1.7.29-hlcutter-0.2.3.diff @@ -0,0 +1,612 @@ +diff -Naur vdr-1.7.27/config.c vdr-1.7.27-hlcutter/config.c +--- vdr-1.7.27/config.c 2012-02-29 11:15:54.000000000 +0100 ++++ vdr-1.7.27-hlcutter/config.c 2012-03-31 13:25:56.000000000 +0200 +@@ -445,8 +445,10 @@ + FontSmlSize = 18; + FontFixSize = 20; + MaxVideoFileSize = MAXVIDEOFILESIZEDEFAULT; ++ MaxRecordingSize = DEFAULTRECORDINGSIZE; + SplitEditedFiles = 0; + DelTimeshiftRec = 0; ++ HardLinkCutter = 0; + MinEventTimeout = 30; + MinUserInactivity = 300; + NextWakeupTime = 0; +@@ -639,8 +641,10 @@ + else if (!strcasecmp(Name, "FontSmlSize")) FontSmlSize = atoi(Value); + else if (!strcasecmp(Name, "FontFixSize")) FontFixSize = atoi(Value); + else if (!strcasecmp(Name, "MaxVideoFileSize")) MaxVideoFileSize = atoi(Value); ++ else if (!strcasecmp(Name, "MaxRecordingSize")) MaxRecordingSize = atoi(Value); + else if (!strcasecmp(Name, "SplitEditedFiles")) SplitEditedFiles = atoi(Value); + else if (!strcasecmp(Name, "DelTimeshiftRec")) DelTimeshiftRec = atoi(Value); ++ else if (!strcasecmp(Name, "HardLinkCutter")) HardLinkCutter = atoi(Value); + else if (!strcasecmp(Name, "MinEventTimeout")) MinEventTimeout = atoi(Value); + else if (!strcasecmp(Name, "MinUserInactivity")) MinUserInactivity = atoi(Value); + else if (!strcasecmp(Name, "NextWakeupTime")) NextWakeupTime = atoi(Value); +@@ -736,8 +740,10 @@ + Store("FontSmlSize", FontSmlSize); + Store("FontFixSize", FontFixSize); + Store("MaxVideoFileSize", MaxVideoFileSize); ++ Store("MaxRecordingSize", MaxRecordingSize); + Store("SplitEditedFiles", SplitEditedFiles); + Store("DelTimeshiftRec", DelTimeshiftRec); ++ Store("HardLinkCutter", HardLinkCutter); + Store("MinEventTimeout", MinEventTimeout); + Store("MinUserInactivity", MinUserInactivity); + Store("NextWakeupTime", NextWakeupTime); +diff -Naur vdr-1.7.27/config.h vdr-1.7.27-hlcutter/config.h +--- vdr-1.7.27/config.h 2012-03-11 11:41:44.000000000 +0100 ++++ vdr-1.7.27-hlcutter/config.h 2012-03-31 13:25:56.000000000 +0200 +@@ -299,8 +299,10 @@ + int FontSmlSize; + int FontFixSize; + int MaxVideoFileSize; ++ int MaxRecordingSize; + int SplitEditedFiles; + int DelTimeshiftRec; ++ int HardLinkCutter; + int MinEventTimeout, MinUserInactivity; + time_t NextWakeupTime; + int MultiSpeedMode; +diff -Naur vdr-1.7.27/cutter.c vdr-1.7.27-hlcutter/cutter.c +--- vdr-1.7.27/cutter.c 2012-02-16 13:08:39.000000000 +0100 ++++ vdr-1.7.27-hlcutter/cutter.c 2012-03-31 13:26:51.000000000 +0200 +@@ -80,6 +80,7 @@ + Mark = fromMarks.Next(Mark); + off_t FileSize = 0; + int CurrentFileNumber = 0; ++ bool SkipThisSourceFile = false; + int LastIFrame = 0; + toMarks.Add(0); + toMarks.Save(); +@@ -98,13 +99,93 @@ + + // Read one frame: + +- if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) { +- if (FileNumber != CurrentFileNumber) { +- fromFile = fromFileName->SetOffset(FileNumber, FileOffset); +- if (fromFile) +- fromFile->SetReadAhead(MEGABYTE(20)); +- CurrentFileNumber = FileNumber; +- } ++ if (!fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) { ++ // Error, unless we're past last cut-in and there's no cut-out ++ if (Mark || LastMark) ++ error = "index"; ++ break; ++ } ++ ++ if (FileNumber != CurrentFileNumber) { ++ fromFile = fromFileName->SetOffset(FileNumber, FileOffset); ++ if (fromFile) ++ fromFile->SetReadAhead(MEGABYTE(20)); ++ CurrentFileNumber = FileNumber; ++ if (SkipThisSourceFile) { ++ // At end of fast forward: Always skip to next file ++ toFile = toFileName->NextFile(); ++ if (!toFile) { ++ error = "toFile 4"; ++ break; ++ } ++ FileSize = 0; ++ SkipThisSourceFile = false; ++ } ++ ++ ++ if (Setup.HardLinkCutter && FileOffset == 0) { ++ // We are at the beginning of a new source file. ++ // Do we need to copy the whole file? ++ ++ // if !Mark && LastMark, then we're past the last cut-out and continue to next I-frame ++ // if !Mark && !LastMark, then there's just a cut-in, but no cut-out ++ // if Mark, then we're between a cut-in and a cut-out ++ ++ uint16_t MarkFileNumber; ++ off_t MarkFileOffset; ++ // Get file number of next cut mark ++ if (!Mark && !LastMark ++ || Mark ++ && fromIndex->Get(Mark->Position(), &MarkFileNumber, &MarkFileOffset) ++ && (MarkFileNumber != CurrentFileNumber)) { ++ // The current source file will be copied completely. ++ // Start new output file unless we did that already ++ if (FileSize != 0) { ++ toFile = toFileName->NextFile(); ++ if (!toFile) { ++ error = "toFile 3"; ++ break; ++ } ++ FileSize = 0; ++ } ++ ++ // Safety check that file has zero size ++ struct stat buf; ++ if (stat(toFileName->Name(), &buf) == 0) { ++ if (buf.st_size != 0) { ++ esyslog("cCuttingThread: File %s exists and has nonzero size", toFileName->Name()); ++ error = "nonzero file exist"; ++ break; ++ } ++ } ++ else if (errno != ENOENT) { ++ esyslog("cCuttingThread: stat failed on %s", toFileName->Name()); ++ error = "stat"; ++ break; ++ } ++ ++ // Clean the existing 0-byte file ++ toFileName->Close(); ++ cString ActualToFileName(ReadLink(toFileName->Name()), true); ++ unlink(ActualToFileName); ++ unlink(toFileName->Name()); ++ ++ // Try to create a hard link ++ if (HardLinkVideoFile(fromFileName->Name(), toFileName->Name())) { ++ // Success. Skip all data transfer for this file ++ SkipThisSourceFile = true; ++ cutIn = false; ++ toFile = NULL; // was deleted by toFileName->Close() ++ } ++ else { ++ // Fallback: Re-open the file if necessary ++ toFile = toFileName->Open(); ++ } ++ } ++ } ++ } ++ ++ if (!SkipThisSourceFile) { + if (fromFile) { + int len = ReadFrame(fromFile, buffer, Length, sizeof(buffer)); + if (len < 0) { +@@ -121,19 +202,12 @@ + break; + } + } +- else { +- // Error, unless we're past the last cut-in and there's no cut-out +- if (Mark || LastMark) +- error = "index"; +- break; +- } +- + // Write one frame: + + if (Independent) { // every file shall start with an independent frame + if (LastMark) // edited version shall end before next I-frame + break; +- if (FileSize > maxVideoFileSize) { ++ if (!SkipThisSourceFile && FileSize > toFileName->MaxFileSize()) { + toFile = toFileName->NextFile(); + if (!toFile) { + error = "toFile 1"; +@@ -143,7 +217,7 @@ + } + CheckForSeamlessStream = false; + } +- if (cutIn) { ++ if (!SkipThisSourceFile && cutIn) { + if (isPesRecording) + cRemux::SetBrokenLink(buffer, Length); + else +@@ -151,7 +225,7 @@ + cutIn = false; + } + } +- if (toFile->Write(buffer, Length) < 0) { ++ if (!SkipThisSourceFile && toFile->Write(buffer, Length) < 0) { + error = "safe_write"; + break; + } +@@ -186,7 +260,7 @@ + } + } + else +- LastMark = true; ++ LastMark = true; // After last cut-out: Write on until next I-frame, then exit + } + } + Recordings.TouchUpdate(); +diff -Naur vdr-1.7.27/menu.c vdr-1.7.27-hlcutter/menu.c +--- vdr-1.7.27/menu.c 2012-03-13 14:14:38.000000000 +0100 ++++ vdr-1.7.27-hlcutter/menu.c 2012-03-31 13:25:56.000000000 +0200 +@@ -3116,8 +3116,10 @@ + 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)); + Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZETS)); ++ Add(new cMenuEditIntItem( tr("Setup.Recording$Max. recording size (GB)"), &data.MaxRecordingSize, MINRECORDINGSIZE, MAXRECORDINGSIZE)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles)); + Add(new cMenuEditStraItem(tr("Setup.Recording$Delete timeshift recording"),&data.DelTimeshiftRec, 3, delTimeshiftRecTexts)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Hard Link Cutter"), &data.HardLinkCutter)); + } + + // --- cMenuSetupReplay ------------------------------------------------------ +diff -Naur vdr-1.7.27/po/de_DE.po vdr-1.7.27-hlcutter/po/de_DE.po +--- vdr-1.7.27/po/de_DE.po 2012-03-11 11:44:44.000000000 +0100 ++++ vdr-1.7.27-hlcutter/po/de_DE.po 2012-03-31 13:36:31.000000000 +0200 +@@ -1071,12 +1071,18 @@ + msgid "Setup.Recording$Max. video file size (MB)" + msgstr "Max. Videodateigröße (MB)" + ++msgid "Setup.Recording$Max. recording size (GB)" ++msgstr "Max. Aufnahmegröße (GB)" ++ + msgid "Setup.Recording$Split edited files" + msgstr "Editierte Dateien aufteilen" + + msgid "Setup.Recording$Delete timeshift recording" + msgstr "Zeitversetzte Aufnahme löschen" + ++msgid "Setup.Recording$Hard Link Cutter" ++msgstr "Hard Link Cutter" ++ + msgid "Replay" + msgstr "Wiedergabe" + +diff -Naur vdr-1.7.27/po/fi_FI.po vdr-1.7.27-hlcutter/po/fi_FI.po +--- vdr-1.7.27/po/fi_FI.po 2012-03-11 11:44:43.000000000 +0100 ++++ vdr-1.7.27-hlcutter/po/fi_FI.po 2012-03-31 13:39:33.000000000 +0200 +@@ -1074,12 +1074,18 @@ + msgid "Setup.Recording$Max. video file size (MB)" + msgstr "Suurin tiedostokoko (Mt)" + ++msgid "Setup.Recording$Max. recording size (GB)" ++msgstr "Suurin tallennekoko (Gt)" ++ + msgid "Setup.Recording$Split edited files" + msgstr "Jaottele muokatut tallenteet" + + msgid "Setup.Recording$Delete timeshift recording" + msgstr "Poista ajansiirtotallenne" + ++msgid "Setup.Recording$Hard Link Cutter" ++msgstr "Käytä kovia linkkejä muokkauksessa" ++ + msgid "Replay" + msgstr "Toisto" + +diff -Naur vdr-1.7.27/README-HLCUTTER vdr-1.7.27-hlcutter/README-HLCUTTER +--- vdr-1.7.27/README-HLCUTTER 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.27-hlcutter/README-HLCUTTER 2012-03-31 13:40:55.000000000 +0200 +@@ -0,0 +1,128 @@ ++ ++ VDR-HLCUTTER README ++ ++ ++Written by: Udo Richter ++Available at: http://www.udo-richter.de/vdr/patches.html#hlcutter ++ http://www.udo-richter.de/vdr/patches.en.html#hlcutter ++Contact: udo_richter@gmx.de ++ ++ ++ ++About ++----- ++ ++The hard link cutter patch changes the recording editing algorithms of VDR to ++use filesystem hard links to 'copy' recording files whenever possible to speed ++up editing recordings noticeably. ++ ++The patch has matured to be quite stable, at least I'm using it without issues. ++Nevertheless the patch is still in development and should be used with caution. ++The patch is EXPERIMENTAL for multiple /videoxx folders. The safety checks ++should prevent data loss, but you should always carefully check the results. ++ ++While editing a recording, the patch searches for any 00x.vdr files that don't ++contain editing marks and would normally be copied 1:1 unmodified to the edited ++recording. In this case the current target 00x.vdr file will be aborted, and ++the cutter process attempts to duplicate the source file as a hard link, so ++that both files share the same disk space. If this succeeds, the editing ++process fast-forwards through the duplicated file and continues normally ++beginning with the next source file. If hard linking fails, the cutter process ++continues with plain old copying. (but does not take up the aborted last file.) ++ ++After editing, the un-edited recording can be deleted as usual, the hard linked ++copies will continue to exist as the only remaining copy. ++ ++To be effective, the default 'Max. video file size (MB)' should be lowered. ++The patch lowers the smallest possible file size to 1mb. Since VDR only ++supports up to 255 files, this would limit the recording size to 255Mb or ++10 minutes, in other words: This setting is insane! ++ ++To make sure that the 255 file limit will not be reached, the patch also ++introduces "Max. recording size (GB)" with a default of 100Gb (66 hours), and ++increases the file size to 2000Mb early enough, so that 100Gb-recordings will ++fit into the 255 files. ++ ++Picking the right parameters can be tricky. The smaller the file size, the ++faster the editing process works. However, with a small file size, long ++recordings will fall back to 2000Mb files soon, that are slow on editing again. ++ ++Here are some examples: ++ ++Max file size: 100Gb 100Gb 100Gb 100Gb 100Gb 100Gb 100Gb ++Max recording size: 1Mb 10Mb 20Mb 30Mb 40Mb 50Mb 100Mb ++ ++Small files: 1-203 1-204 1-205 1-206 1-207 1-209 1-214 ++ GBytes: 0.2 2.0 4.0 6.0 8.1 10.2 20.9 ++ Hours: 0.13 1.3 2.65 4 5.4 6.8 13.9 ++ ++Big (2000mb) files: 204-255 204-255 206-255 207-255 208-255 210-255 215-255 ++ GBytes: 101.5 99.6 97.7 95.7 93.8 89.8 80.1 ++ Hours: 67 66 65 63 62 60 53 ++ ++A recording limit of 100Gb keeps plenty of reserve without blocking too much ++file numbers. And with a file size of 30-40Mb, recordings of 4-5 hours fit into ++small files completely. (depends on bit rate of course) ++ ++ ++ ++The patch must be enabled in Setup-> Recordings-> Hard Link Cutter. When ++disabled, the cutter process behaves identical to VDR's default cutter. ++ ++There's a //#define HARDLINK_TEST_ONLY in the videodir.c file that enables a ++test-mode that hard-links 00x.vdr_ files only, and continues the classic ++editing. The resulting 00x.vdr and 00x.vdr_ files should be identical. If you ++delete the un-edited recording, don't forget to delete the *.vdr_ files too, ++they will now eat real disk space. ++ ++Note: 'du' displays the disk space of hard links only on first appearance, and ++usually you will see a noticeably smaller size on the edited recording. ++ ++ ++History ++------- ++ ++Version 0.2.3 ++ Fix: Compatible to VDR-1.7.27+ thx to Ville Skyttä ++ New: Add German translation ++ New: Add Finnish translation, thx to Ville Skyttä ++ ++Version 0.2.2 ++ Fix: Adapt to GCC-4.4, thx to Ville Skyttä ++ ++Version 0.2.1 ++ New: Support for TS recordings with up to 65535 files and up to 1TB per file ++ ++Version 0.2.0 ++ New: Support for multiple /videoXX recording folders, using advanced searching ++ for matching file systems where a hard link can be created. ++ Also supports deep mounted file systems. ++ Fix: Do not fail if last mark is a cut-in. (Again.) ++ ++Version 0.1.4 ++ New: Dynamic increase of file size before running out of xxx.vdr files ++ Fix: Last edit mark is not a cut-out ++ Fix: Write error if link-copied file is smaller than allowed file size ++ Fix: Broken index/marks if cut-in is at the start of a new file ++ Fix: Clear dangling pointer to free'd cUnbufferedFile, ++ thx to Matthias Schwarzott ++ ++Version 0.1.0 ++ Initial release ++ ++ ++ ++ ++Future plans ++------------ ++ ++Since original and edited copy share disk space, free space is wrong if one of ++them is moved to *.del. Free space should only count files with hard link ++count = 1. This still goes wrong if all copies get deleted. ++ ++ ++For more safety, the hard-linked files may be made read-only, as modifications ++to one copy will affect the other copy too. (except deleting, of course) ++ ++ ++SetBrokenLink may get lost on rare cases, this needs some more thoughts. +diff -Naur vdr-1.7.27/recorder.c vdr-1.7.27-hlcutter/recorder.c +--- vdr-1.7.27/recorder.c 2011-09-04 11:26:44.000000000 +0200 ++++ vdr-1.7.27-hlcutter/recorder.c 2012-03-31 13:25:56.000000000 +0200 +@@ -89,7 +89,7 @@ + bool cRecorder::NextFile(void) + { + if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame +- if (fileSize > MEGABYTE(off_t(Setup.MaxVideoFileSize)) || RunningLowOnDiskSpace()) { ++ if (fileSize > fileName->MaxFileSize() || RunningLowOnDiskSpace()) { + recordFile = fileName->NextFile(); + fileSize = 0; + } +diff -Naur vdr-1.7.27/recording.c vdr-1.7.27-hlcutter/recording.c +--- vdr-1.7.27/recording.c 2012-03-13 14:17:57.000000000 +0100 ++++ vdr-1.7.27-hlcutter/recording.c 2012-03-31 13:25:56.000000000 +0200 +@@ -2064,6 +2064,20 @@ + return NULL; + } + ++off_t cFileName::MaxFileSize() { ++ const int maxVideoFileSize = isPesRecording ? MAXVIDEOFILESIZEPES : MAXVIDEOFILESIZETS; ++ const int setupMaxVideoFileSize = min(maxVideoFileSize, Setup.MaxVideoFileSize); ++ const int maxFileNumber = isPesRecording ? 255 : 65535; ++ ++ const off_t smallFiles = (maxFileNumber * off_t(maxVideoFileSize) - 1024 * Setup.MaxRecordingSize) ++ / max(maxVideoFileSize - setupMaxVideoFileSize, 1); ++ ++ if (fileNumber <= smallFiles) ++ return MEGABYTE(off_t(setupMaxVideoFileSize)); ++ ++ return MEGABYTE(off_t(maxVideoFileSize)); ++} ++ + cUnbufferedFile *cFileName::NextFile(void) + { + return SetOffset(fileNumber + 1); +diff -Naur vdr-1.7.27/recording.h vdr-1.7.27-hlcutter/recording.h +--- vdr-1.7.27/recording.h 2012-03-13 13:41:05.000000000 +0100 ++++ vdr-1.7.27-hlcutter/recording.h 2012-03-31 13:25:56.000000000 +0200 +@@ -264,9 +264,17 @@ + // before the next independent frame, to have a complete Group Of Pictures): + #define MAXVIDEOFILESIZETS 1048570 // MB + #define MAXVIDEOFILESIZEPES 2000 // MB +-#define MINVIDEOFILESIZE 100 // MB ++#define MINVIDEOFILESIZE 1 // MB + #define MAXVIDEOFILESIZEDEFAULT MAXVIDEOFILESIZEPES + ++#define MINRECORDINGSIZE 25 // GB ++#define MAXRECORDINGSIZE 500 // GB ++#define DEFAULTRECORDINGSIZE 100 // GB ++// Dynamic recording size: ++// Keep recording file size at Setup.MaxVideoFileSize for as long as possible, ++// but switch to MAXVIDEOFILESIZE early enough, so that Setup.MaxRecordingSize ++// will be reached, before recording to file 65535.vdr ++ + struct tIndexTs; + class cIndexFileGenerator; + +@@ -319,6 +327,8 @@ + cUnbufferedFile *Open(void); + void Close(void); + cUnbufferedFile *SetOffset(int Number, off_t Offset = 0); // yes, Number is int for easier internal calculating ++ off_t MaxFileSize(); ++ // Dynamic file size for this file + cUnbufferedFile *NextFile(void); + }; + +diff -Naur vdr-1.7.27/videodir.c vdr-1.7.27-hlcutter/videodir.c +--- vdr-1.7.27/videodir.c 2008-02-16 14:00:03.000000000 +0100 ++++ vdr-1.7.27-hlcutter/videodir.c 2012-03-31 13:25:56.000000000 +0200 +@@ -19,6 +19,9 @@ + #include "recording.h" + #include "tools.h" + ++ ++//#define HARDLINK_TEST_ONLY ++ + const char *VideoDirectory = VIDEODIR; + + class cVideoDirectory { +@@ -168,6 +171,120 @@ + return RemoveFileOrDir(FileName, true); + } + ++static bool StatNearestDir(const char *FileName, struct stat *Stat) ++{ ++ cString Name(FileName); ++ char *p; ++ while ((p = strrchr((char*)(const char*)Name + 1, '/')) != NULL) { ++ *p = 0; // truncate at last '/' ++ if (stat(Name, Stat) == 0) { ++ isyslog("StatNearestDir: Stating %s", (const char*)Name); ++ return true; ++ } ++ } ++ return false; ++} ++ ++bool HardLinkVideoFile(const char *OldName, const char *NewName) ++{ ++ // Incoming name must be in base video directory: ++ if (strstr(OldName, VideoDirectory) != OldName) { ++ esyslog("ERROR: %s not in %s", OldName, VideoDirectory); ++ return false; ++ } ++ if (strstr(NewName, VideoDirectory) != NewName) { ++ esyslog("ERROR: %s not in %s", NewName, VideoDirectory); ++ return false; ++ } ++ ++ const char *ActualNewName = NewName; ++ cString ActualOldName(ReadLink(OldName), true); ++ ++ // Some safety checks: ++ struct stat StatOldName; ++ if (lstat(ActualOldName, &StatOldName) == 0) { ++ if (S_ISLNK(StatOldName.st_mode)) { ++ esyslog("HardLinkVideoFile: Failed to resolve symbolic link %s", (const char*)ActualOldName); ++ return false; ++ } ++ } ++ else { ++ esyslog("HardLinkVideoFile: lstat failed on %s", (const char*)ActualOldName); ++ return false; ++ } ++ isyslog("HardLinkVideoFile: %s is on %i", (const char*)ActualOldName, (int)StatOldName.st_dev); ++ ++ // Find the video directory where ActualOldName is located ++ ++ cVideoDirectory Dir; ++ struct stat StatDir; ++ if (!StatNearestDir(NewName, &StatDir)) { ++ esyslog("HardLinkVideoFile: stat failed on %s", NewName); ++ return false; ++ } ++ ++ isyslog("HardLinkVideoFile: %s is on %i", NewName, (int)StatDir.st_dev); ++ if (StatDir.st_dev != StatOldName.st_dev) { ++ // Not yet found. ++ ++ if (!Dir.IsDistributed()) { ++ esyslog("HardLinkVideoFile: No matching video folder to hard link %s", (const char*)ActualOldName); ++ return false; ++ } ++ ++ // Search in video01 and upwards ++ bool found = false; ++ while (Dir.Next()) { ++ Dir.Store(); ++ const char *TmpNewName = Dir.Adjust(NewName); ++ if (StatNearestDir(TmpNewName, &StatDir) && StatDir.st_dev == StatOldName.st_dev) { ++ isyslog("HardLinkVideoFile: %s is on %i (match)", TmpNewName, (int)StatDir.st_dev); ++ ActualNewName = TmpNewName; ++ found = true; ++ break; ++ } ++ isyslog("HardLinkVideoFile: %s is on %i", TmpNewName, (int)StatDir.st_dev); ++ } ++ if (ActualNewName == NewName) { ++ esyslog("HardLinkVideoFile: No matching video folder to hard link %s", (const char*)ActualOldName); ++ return false; ++ } ++ ++ // Looking good, we have a match. Create necessary folders. ++ if (!MakeDirs(ActualNewName, false)) ++ return false; ++ // There's no guarantee that the directory of ActualNewName ++ // is on the same device as the dir that StatNearestDir found. ++ // But worst case is that the link fails. ++ } ++ ++#ifdef HARDLINK_TEST_ONLY ++ // Do the hard link to *.vdr_ for testing only ++ char *name = NULL; ++ asprintf(&name, "%s_",ActualNewName); ++ link(ActualOldName, name); ++ free(name); ++ return false; ++#endif // HARDLINK_TEST_ONLY ++ ++ // Try creating the hard link ++ if (link(ActualOldName, ActualNewName) != 0) { ++ // Failed to hard link. Maybe not allowed on file system. ++ LOG_ERROR_STR(ActualNewName); ++ isyslog("HardLinkVideoFile: failed to hard link from %s to %s", (const char*)ActualOldName, ActualNewName); ++ return false; ++ } ++ ++ if (ActualNewName != NewName) { ++ // video01 and up. Do the remaining symlink ++ if (symlink(ActualNewName, NewName) < 0) { ++ LOG_ERROR_STR(NewName); ++ return false; ++ } ++ } ++ return true; ++} ++ + bool VideoFileSpaceAvailable(int SizeMB) + { + cVideoDirectory Dir; +diff -Naur vdr-1.7.27/videodir.h vdr-1.7.27-hlcutter/videodir.h +--- vdr-1.7.27/videodir.h 2008-02-16 13:53:11.000000000 +0100 ++++ vdr-1.7.27-hlcutter/videodir.h 2012-03-31 13:25:56.000000000 +0200 +@@ -19,6 +19,7 @@ + int CloseVideoFile(cUnbufferedFile *File); + bool RenameVideoFile(const char *OldName, const char *NewName); + bool RemoveVideoFile(const char *FileName); ++bool HardLinkVideoFile(const char *OldName, const char *NewName); + bool VideoFileSpaceAvailable(int SizeMB); + int VideoDiskSpace(int *FreeMB = NULL, int *UsedMB = NULL); // returns the used disk space in percent + cString PrefixVideoFileName(const char *FileName, char Prefix); diff --git a/vdr-2.7.4-fedora-pkgconfig.patch b/vdr-1.7.37-fedora-pkgconfig.patch similarity index 63% rename from vdr-2.7.4-fedora-pkgconfig.patch rename to vdr-1.7.37-fedora-pkgconfig.patch index 914eb08..a80fd82 100644 --- a/vdr-2.7.4-fedora-pkgconfig.patch +++ b/vdr-1.7.37-fedora-pkgconfig.patch @@ -1,6 +1,7 @@ ---- vdr-2.7.4/Makefile.orig 2025-02-26 14:07:24.323718942 +0100 -+++ vdr-2.7.4/Makefile 2025-02-26 14:08:33.583687374 +0100 -@@ -186,6 +186,12 @@ +diff -up vdr-1.7.37/Makefile~ vdr-1.7.37/Makefile +--- vdr-1.7.37/Makefile~ 2013-02-14 21:57:22.306077727 +0200 ++++ vdr-1.7.37/Makefile 2013-02-14 22:05:04.016086224 +0200 +@@ -152,6 +152,12 @@ vdr.pc: @echo "cflags=$(CFLAGS) $(CDEFINES) $(CINCLUDES) $(HDRDIR)" >> $@ @echo "cxxflags=$(CXXFLAGS) $(CDEFINES) $(CINCLUDES) $(HDRDIR)" >> $@ @echo "" >> $@ @@ -12,4 +13,4 @@ + @echo "" >> $@ @echo "Name: VDR" >> $@ @echo "Description: Video Disk Recorder" >> $@ - @echo "URL: https://www.tvdr.de/" >> $@ + @echo "URL: http://www.tvdr.de/" >> $@ diff --git a/vdr-1.7.41-paths.patch b/vdr-1.7.41-paths.patch new file mode 100644 index 0000000..742c291 --- /dev/null +++ b/vdr-1.7.41-paths.patch @@ -0,0 +1,84 @@ +diff -up vdr-1.7.41/epg2html~ vdr-1.7.41/epg2html +--- vdr-1.7.41/epg2html~ 2013-03-04 15:02:20.000000000 +0200 ++++ vdr-1.7.41/epg2html 2013-03-16 19:08:17.467701640 +0200 +@@ -2,12 +2,12 @@ + + # A simple EPG to HTML converter + # +-# Converts the EPG data written by 'vdr' into the file /video/epg.data ++# Converts the EPG data written by 'vdr' into the file __CACHEDIR__/epg.data + # into a simple HTML programme listing, consisting of one file per channel + # plus an 'index.htm' file. All output files are written into the current + # directory. + # +-# Usage: epg2html < /video/epg.data ++# Usage: epg2html < __CACHEDIR__/epg.data + # + # See the main source file 'vdr.c' for copyright information and + # how to reach the author. +diff -up vdr-1.7.41/newplugin~ vdr-1.7.41/newplugin +--- vdr-1.7.41/newplugin~ 2013-01-12 15:46:00.000000000 +0200 ++++ vdr-1.7.41/newplugin 2013-03-16 19:08:18.140713300 +0200 +@@ -24,7 +24,7 @@ $PLUGIN_VERSION = "0.0.1"; + $PLUGIN_DESCRIPTION = "Enter description for '$PLUGIN_NAME' plugin"; + $PLUGIN_MAINENTRY = $PLUGIN_CLASS; + +-$PLUGINS_SRC = "PLUGINS/src"; ++$PLUGINS_SRC = "."; + + $README = qq + {This is a "plugin" for the Video Disk Recorder (VDR). +diff -up vdr-1.7.41/vdr.1~ vdr-1.7.41/vdr.1 +--- vdr-1.7.41/vdr.1~ 2013-03-15 12:44:54.000000000 +0200 ++++ vdr-1.7.41/vdr.1 2013-03-16 19:08:16.621686914 +0200 +@@ -51,7 +51,7 @@ Save cache files in \fIdir\fR + .TP + .BI \-c\ dir ,\ \-\-config= dir + Read config files from directory \fIdir\fR +-(default is to read them from the video directory). ++(default is to read them from __CONFIGDIR__). + .TP + .B \-d, \-\-daemon + Run in daemon mode (implies \-\-no\-kbd). +@@ -130,7 +130,7 @@ If logging should be done to LOG_LOCAL\f + LOG_USER, add '.n' to LEVEL, as in 3.7 (n=0..7). + .TP + .BI \-L\ dir ,\ \-\-lib= dir +-Search for plugins in directory \fIdir\fR (default is ./PLUGINS/lib). ++Search for plugins in directory \fIdir\fR (default is __PLUGINDIR__). + There can be several \fB\-L\fR options with different \fIdir\fR values. + Each of them will apply to the \fB\-P\fR options following it. + .TP +@@ -205,7 +205,7 @@ For backwards compatibility (same as \-\ + .TP + .BI \-v\ dir ,\ \-\-video= dir + Use \fIdir\fR as video directory. +-The default is \fI/video\fR. ++The default is \fI__VIDEODIR__\fR. + .TP + .B \-V, \-\-version + Print version information and exit. +diff -up vdr-1.7.41/vdr.5~ vdr-1.7.41/vdr.5 +--- vdr-1.7.41/vdr.5~ 2013-03-11 15:17:12.000000000 +0200 ++++ vdr-1.7.41/vdr.5 2013-03-16 19:08:18.908726601 +0200 +@@ -711,7 +711,7 @@ The file \fIsetup.conf\fR contains the b + Each line contains one option in the format "Name = Value". + See the MANUAL file for a description of the available options. + .SS THEMES +-The files \fIthemes/\-.theme\fR in the config directory contain the ++The files \fI__VARDIR__/themes/\-.theme\fR contain the + color theme definitions for the various skins. In the actual file names \fI\fR + will be replaced by the name if the skin this theme belongs to, and \fI\fR + will be the name of this theme. +diff -up vdr-1.7.41/vdr.c~ vdr-1.7.41/vdr.c +--- vdr-1.7.41/vdr.c~ 2013-03-15 12:44:54.000000000 +0200 ++++ vdr-1.7.41/vdr.c 2013-03-16 19:08:15.861673670 +0200 +@@ -673,7 +673,7 @@ int main(int argc, char *argv[]) + if (!ResourceDirectory) + ResourceDirectory = DEFAULTRESDIR; + cPlugin::SetResourceDirectory(ResourceDirectory); +- cThemes::SetThemesDirectory(AddDirectory(ConfigDirectory, "themes")); ++ cThemes::SetThemesDirectory("__VARDIR__/themes"); + + // Configuration data: + diff --git a/vdr-2.0.4-mainmenuhooks101.patch b/vdr-2.0.4-mainmenuhooks101.patch new file mode 100644 index 0000000..b7b2a5c --- /dev/null +++ b/vdr-2.0.4-mainmenuhooks101.patch @@ -0,0 +1,109 @@ +diff -up vdr-2.0.4/config.h~ vdr-2.0.4/config.h +--- vdr-2.0.4/config.h~ 2013-09-07 13:25:10.000000000 +0300 ++++ vdr-2.0.4/config.h 2013-10-23 19:43:24.731445495 +0300 +@@ -47,6 +47,10 @@ + #define TIMERMACRO_TITLE "TITLE" + #define TIMERMACRO_EPISODE "EPISODE" + ++// The MainMenuHook Patch's version number: ++#define MAINMENUHOOKSVERSION "1.0.1" ++#define MAINMENUHOOKSVERSNUM 10001 // Version * 10000 + Major * 100 + Minor ++ + #define MINOSDWIDTH 480 + #define MAXOSDWIDTH 1920 + #define MINOSDHEIGHT 324 +diff -up vdr-2.0.4/menu.c~ vdr-2.0.4/menu.c +--- vdr-2.0.4/menu.c~ 2013-10-16 12:46:24.000000000 +0300 ++++ vdr-2.0.4/menu.c 2013-10-23 19:43:24.275436244 +0300 +@@ -3377,15 +3377,31 @@ cMenuMain::cMenuMain(eOSState State, boo + + // Initial submenus: + ++ cOsdObject *menu = NULL; + switch (State) { +- case osSchedule: AddSubMenu(new cMenuSchedule); break; +- case osChannels: AddSubMenu(new cMenuChannels); break; +- case osTimers: AddSubMenu(new cMenuTimers); break; +- case osRecordings: AddSubMenu(new cMenuRecordings(NULL, 0, OpenSubMenus)); break; +- case osSetup: AddSubMenu(new cMenuSetup); break; +- case osCommands: AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); break; ++ case osSchedule: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu)) ++ menu = new cMenuSchedule; ++ break; ++ case osChannels: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu)) ++ menu = new cMenuChannels; ++ break; ++ case osTimers: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu)) ++ menu = new cMenuTimers; ++ break; ++ case osRecordings: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu)) ++ menu = new cMenuRecordings(NULL, 0, OpenSubMenus); ++ break; ++ case osSetup: menu = new cMenuSetup; break; ++ case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break; + default: break; + } ++ if (menu) ++ if (menu->IsMenu()) ++ AddSubMenu((cOsdMenu *) menu); + } + + cOsdObject *cMenuMain::PluginOsdObject(void) +@@ -3493,13 +3509,34 @@ eOSState cMenuMain::ProcessKey(eKeys Key + eOSState state = cOsdMenu::ProcessKey(Key); + HadSubMenu |= HasSubMenu(); + ++ cOsdObject *menu = NULL; + switch (state) { +- case osSchedule: return AddSubMenu(new cMenuSchedule); +- case osChannels: return AddSubMenu(new cMenuChannels); +- case osTimers: return AddSubMenu(new cMenuTimers); +- case osRecordings: return AddSubMenu(new cMenuRecordings); +- case osSetup: return AddSubMenu(new cMenuSetup); +- case osCommands: return AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); ++ case osSchedule: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu)) ++ menu = new cMenuSchedule; ++ else ++ state = osContinue; ++ break; ++ case osChannels: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu)) ++ menu = new cMenuChannels; ++ else ++ state = osContinue; ++ break; ++ case osTimers: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu)) ++ menu = new cMenuTimers; ++ else ++ state = osContinue; ++ break; ++ case osRecordings: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu)) ++ menu = new cMenuRecordings; ++ else ++ state = osContinue; ++ break; ++ case osSetup: menu = new cMenuSetup; break; ++ case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break; + case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) { + cOsdItem *item = Get(Current()); + if (item) { +@@ -3551,6 +3588,12 @@ eOSState cMenuMain::ProcessKey(eKeys Key + default: break; + } + } ++ if (menu) { ++ if (menu->IsMenu()) ++ return AddSubMenu((cOsdMenu *) menu); ++ pluginOsdObject = menu; ++ return osPlugin; ++ } + if (!HasSubMenu() && Update(HadSubMenu)) + Display(); + if (Key != kNone) { diff --git a/vdr-2.1.5-naludump-0.1.diff b/vdr-2.1.5-naludump-0.1.diff new file mode 100644 index 0000000..4b338cd --- /dev/null +++ b/vdr-2.1.5-naludump-0.1.diff @@ -0,0 +1,621 @@ +diff -Naur vdr-2.1.6/config.c vdr-2.1.6-naludump-0.1/config.c +--- vdr-2.1.6/config.c 2013-08-31 14:41:28.000000000 +0200 ++++ vdr-2.1.6-naludump-0.1/config.c 2014-03-30 17:47:25.000000000 +0200 +@@ -462,6 +462,7 @@ + MaxVideoFileSize = MAXVIDEOFILESIZEDEFAULT; + SplitEditedFiles = 0; + DelTimeshiftRec = 0; ++ DumpNaluFill = 0; + MinEventTimeout = 30; + MinUserInactivity = 300; + NextWakeupTime = 0; +@@ -673,6 +674,7 @@ + else if (!strcasecmp(Name, "MaxVideoFileSize")) MaxVideoFileSize = atoi(Value); + else if (!strcasecmp(Name, "SplitEditedFiles")) SplitEditedFiles = atoi(Value); + else if (!strcasecmp(Name, "DelTimeshiftRec")) DelTimeshiftRec = atoi(Value); ++ else if (!strcasecmp(Name, "DumpNaluFill")) DumpNaluFill = atoi(Value); + else if (!strcasecmp(Name, "MinEventTimeout")) MinEventTimeout = atoi(Value); + else if (!strcasecmp(Name, "MinUserInactivity")) MinUserInactivity = atoi(Value); + else if (!strcasecmp(Name, "NextWakeupTime")) NextWakeupTime = atoi(Value); +@@ -788,6 +790,7 @@ + Store("MaxVideoFileSize", MaxVideoFileSize); + Store("SplitEditedFiles", SplitEditedFiles); + Store("DelTimeshiftRec", DelTimeshiftRec); ++ Store("DumpNaluFill", DumpNaluFill); + Store("MinEventTimeout", MinEventTimeout); + Store("MinUserInactivity", MinUserInactivity); + Store("NextWakeupTime", NextWakeupTime); +diff -Naur vdr-2.1.6/config.h vdr-2.1.6-naludump-0.1/config.h +--- vdr-2.1.6/config.h 2014-02-25 11:00:23.000000000 +0100 ++++ vdr-2.1.6-naludump-0.1/config.h 2014-03-30 17:47:25.000000000 +0200 +@@ -326,6 +326,7 @@ + int MaxVideoFileSize; + int SplitEditedFiles; + int DelTimeshiftRec; ++ int DumpNaluFill; + int MinEventTimeout, MinUserInactivity; + time_t NextWakeupTime; + int MultiSpeedMode; +diff -Naur vdr-2.1.6/menu.c vdr-2.1.6-naludump-0.1/menu.c +--- vdr-2.1.6/menu.c 2014-03-16 11:38:31.000000000 +0100 ++++ vdr-2.1.6-naludump-0.1/menu.c 2014-03-30 17:47:25.000000000 +0200 +@@ -3547,6 +3547,7 @@ + Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZETS)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles)); + Add(new cMenuEditStraItem(tr("Setup.Recording$Delete timeshift recording"),&data.DelTimeshiftRec, 3, delTimeshiftRecTexts)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Dump NALU Fill data"), &data.DumpNaluFill)); + } + + // --- cMenuSetupReplay ------------------------------------------------------ +diff -Naur vdr-2.1.6/recorder.c vdr-2.1.6-naludump-0.1/recorder.c +--- vdr-2.1.6/recorder.c 2014-02-21 10:19:52.000000000 +0100 ++++ vdr-2.1.6-naludump-0.1/recorder.c 2014-03-30 17:47:25.000000000 +0200 +@@ -46,6 +46,14 @@ + Type = 0x06; + } + frameDetector = new cFrameDetector(Pid, Type); ++ if ( Type == 0x1B // MPEG4 video ++ && (Setup.DumpNaluFill ? (strstr(FileName, "NALUKEEP") == NULL) : (strstr(FileName, "NALUDUMP") != NULL))) { // MPEG4 ++ isyslog("Starting NALU fill dumper"); ++ naluStreamProcessor = new cNaluStreamProcessor(); ++ naluStreamProcessor->SetPid(Pid); ++ } ++ else ++ naluStreamProcessor = NULL; + index = NULL; + fileSize = 0; + lastDiskSpaceCheck = time(NULL); +@@ -67,6 +75,12 @@ + cRecorder::~cRecorder() + { + Detach(); ++ if (naluStreamProcessor) { ++ long long int TotalPackets = naluStreamProcessor->GetTotalPackets(); ++ long long int DroppedPackets = naluStreamProcessor->GetDroppedPackets(); ++ isyslog("NALU fill dumper: %lld of %lld packets dropped, %lli%%", DroppedPackets, TotalPackets, TotalPackets ? DroppedPackets*100/TotalPackets : 0); ++ delete naluStreamProcessor; ++ } + delete index; + delete fileName; + delete frameDetector; +@@ -157,11 +171,32 @@ + } + t.Set(MAXBROKENTIMEOUT); + } +- if (recordFile->Write(b, Count) < 0) { +- LOG_ERROR_STR(fileName->Name()); +- break; ++ if (naluStreamProcessor) { ++ naluStreamProcessor->PutBuffer(b, Count); ++ bool Fail = false; ++ while (true) { ++ int OutLength = 0; ++ uchar *OutData = naluStreamProcessor->GetBuffer(OutLength); ++ if (!OutData || OutLength <= 0) ++ break; ++ if (recordFile->Write(OutData, OutLength) < 0) { ++ LOG_ERROR_STR(fileName->Name()); ++ Fail = true; ++ break; ++ } ++ fileSize += OutLength; ++ } ++ if (Fail) ++ break; ++ } ++ else { ++ if (recordFile->Write(b, Count) < 0) { ++ LOG_ERROR_STR(fileName->Name()); ++ break; ++ } ++ fileSize += Count; + } +- fileSize += Count; ++ + } + } + ringBuffer->Del(Count); +diff -Naur vdr-2.1.6/recorder.h vdr-2.1.6-naludump-0.1/recorder.h +--- vdr-2.1.6/recorder.h 2010-12-27 12:17:04.000000000 +0100 ++++ vdr-2.1.6-naludump-0.1/recorder.h 2014-03-30 17:47:25.000000000 +0200 +@@ -21,6 +21,7 @@ + cRingBufferLinear *ringBuffer; + cFrameDetector *frameDetector; + cPatPmtGenerator patPmtGenerator; ++ cNaluStreamProcessor *naluStreamProcessor; + cFileName *fileName; + cIndexFile *index; + cUnbufferedFile *recordFile; +diff -Naur vdr-2.1.6/remux.c vdr-2.1.6-naludump-0.1/remux.c +--- vdr-2.1.6/remux.c 2014-03-08 16:05:35.000000000 +0100 ++++ vdr-2.1.6-naludump-0.1/remux.c 2014-03-30 17:47:25.000000000 +0200 +@@ -343,6 +343,42 @@ + dsyslog("WARNING: required %d video TS packets to determine frame type", numPacketsPid); + } + ++void TsExtendAdaptionField(unsigned char *Packet, int ToLength) ++{ ++ // Hint: ExtenAdaptionField(p, TsPayloadOffset(p) - 4) is a null operation ++ ++ int Offset = TsPayloadOffset(Packet); // First byte after existing adaption field ++ ++ if (ToLength <= 0) ++ { ++ // Remove adaption field ++ Packet[3] = Packet[3] & ~TS_ADAPT_FIELD_EXISTS; ++ return; ++ } ++ ++ // Set adaption field present ++ Packet[3] = Packet[3] | TS_ADAPT_FIELD_EXISTS; ++ ++ // Set new length of adaption field: ++ Packet[4] = ToLength <= TS_SIZE-4 ? ToLength-1 : TS_SIZE-4-1; ++ ++ if (Packet[4] == TS_SIZE-4-1) ++ { ++ // No more payload, remove payload flag ++ Packet[3] = Packet[3] & ~TS_PAYLOAD_EXISTS; ++ } ++ ++ int NewPayload = TsPayloadOffset(Packet); // First byte after new adaption field ++ ++ // Fill new adaption field ++ if (Offset == 4 && Offset < NewPayload) ++ Offset++; // skip adaptation_field_length ++ if (Offset == 5 && Offset < NewPayload) ++ Packet[Offset++] = 0; // various flags set to 0 ++ while (Offset < NewPayload) ++ Packet[Offset++] = 0xff; // stuffing byte ++} ++ + // --- cPatPmtGenerator ------------------------------------------------------ + + cPatPmtGenerator::cPatPmtGenerator(const cChannel *Channel) +@@ -1547,3 +1583,344 @@ + } + return Processed; + } ++ ++// --- cNaluDumper --------------------------------------------------------- ++ ++cNaluDumper::cNaluDumper() ++{ ++ LastContinuityOutput = -1; ++ reset(); ++} ++ ++void cNaluDumper::reset() ++{ ++ LastContinuityInput = -1; ++ ContinuityOffset = 0; ++ PesId = -1; ++ PesOffset = 0; ++ NaluFillState = NALU_NONE; ++ NaluOffset = 0; ++ History = 0xffffffff; ++ DropAllPayload = false; ++} ++ ++void cNaluDumper::ProcessPayload(unsigned char *Payload, int size, bool PayloadStart, sPayloadInfo &Info) ++{ ++ Info.DropPayloadStartBytes = 0; ++ Info.DropPayloadEndBytes = 0; ++ int LastKeepByte = -1; ++ ++ if (PayloadStart) ++ { ++ History = 0xffffffff; ++ PesId = -1; ++ NaluFillState = NALU_NONE; ++ } ++ ++ for (int i=0; i= 0x00000180 && History <= 0x000001FF) ++ { ++ // Start of PES packet ++ PesId = History & 0xff; ++ PesOffset = 0; ++ NaluFillState = NALU_NONE; ++ } ++ else if (PesId >= 0xe0 && PesId <= 0xef // video stream ++ && History >= 0x00000100 && History <= 0x0000017F) // NALU start code ++ { ++ int NaluId = History & 0xff; ++ NaluOffset = 0; ++ NaluFillState = ((NaluId & 0x1f) == 0x0c) ? NALU_FILL : NALU_NONE; ++ } ++ ++ if (PesId >= 0xe0 && PesId <= 0xef // video stream ++ && PesOffset >= 1 && PesOffset <= 2) ++ { ++ Payload[i] = 0; // Zero out PES length field ++ } ++ ++ if (NaluFillState == NALU_FILL && NaluOffset > 0) // Within NALU fill data ++ { ++ // We expect a series of 0xff bytes terminated by a single 0x80 byte. ++ ++ if (Payload[i] == 0xFF) ++ { ++ DropByte = true; ++ } ++ else if (Payload[i] == 0x80) ++ { ++ NaluFillState = NALU_TERM; // Last byte of NALU fill, next byte sets NaluFillEnd=true ++ DropByte = true; ++ } ++ else // Invalid NALU fill ++ { ++ dsyslog("cNaluDumper: Unexpected NALU fill data: %02x", Payload[i]); ++ NaluFillState = NALU_END; ++ if (LastKeepByte == -1) ++ { ++ // Nalu fill from beginning of packet until last byte ++ // packet start needs to be dropped ++ Info.DropPayloadStartBytes = i; ++ } ++ } ++ } ++ else if (NaluFillState == NALU_TERM) // Within NALU fill data ++ { ++ // We are after the terminating 0x80 byte ++ NaluFillState = NALU_END; ++ if (LastKeepByte == -1) ++ { ++ // Nalu fill from beginning of packet until last byte ++ // packet start needs to be dropped ++ Info.DropPayloadStartBytes = i; ++ } ++ } ++ ++ if (!DropByte) ++ LastKeepByte = i; // Last useful byte ++ } ++ ++ Info.DropAllPayloadBytes = (LastKeepByte == -1); ++ Info.DropPayloadEndBytes = size-1-LastKeepByte; ++} ++ ++bool cNaluDumper::ProcessTSPacket(unsigned char *Packet) ++{ ++ bool HasAdaption = TsHasAdaptationField(Packet); ++ bool HasPayload = TsHasPayload(Packet); ++ ++ // Check continuity: ++ int ContinuityInput = TsContinuityCounter(Packet); ++ if (LastContinuityInput >= 0) ++ { ++ int NewContinuityInput = HasPayload ? (LastContinuityInput + 1) & TS_CONT_CNT_MASK : LastContinuityInput; ++ int Offset = (NewContinuityInput - ContinuityInput) & TS_CONT_CNT_MASK; ++ if (Offset > 0) ++ dsyslog("cNaluDumper: TS continuity offset %i", Offset); ++ if (Offset > ContinuityOffset) ++ ContinuityOffset = Offset; // max if packets get dropped, otherwise always the current one. ++ } ++ LastContinuityInput = ContinuityInput; ++ ++ if (HasPayload) { ++ sPayloadInfo Info; ++ int Offset = TsPayloadOffset(Packet); ++ ProcessPayload(Packet + Offset, TS_SIZE - Offset, TsPayloadStart(Packet), Info); ++ ++ if (DropAllPayload && !Info.DropAllPayloadBytes) ++ { ++ // Return from drop packet mode to normal mode ++ DropAllPayload = false; ++ ++ // Does the packet start with some remaining NALU fill data? ++ if (Info.DropPayloadStartBytes > 0) ++ { ++ // Add these bytes as stuffing to the adaption field. ++ ++ // Sample payload layout: ++ // FF FF FF FF FF 80 00 00 01 xx xx xx xx ++ // ^DropPayloadStartBytes ++ ++ TsExtendAdaptionField(Packet, Offset - 4 + Info.DropPayloadStartBytes); ++ } ++ } ++ ++ bool DropThisPayload = DropAllPayload; ++ ++ if (!DropAllPayload && Info.DropPayloadEndBytes > 0) // Payload ends with 0xff NALU Fill ++ { ++ // Last packet of useful data ++ // Do early termination of NALU fill data ++ Packet[TS_SIZE-1] = 0x80; ++ DropAllPayload = true; ++ // Drop all packets AFTER this one ++ ++ // Since we already wrote the 0x80, we have to make sure that ++ // as soon as we stop dropping packets, any beginning NALU fill of next ++ // packet gets dumped. (see DropPayloadStartBytes above) ++ } ++ ++ if (DropThisPayload && HasAdaption) ++ { ++ // Drop payload data, but keep adaption field data ++ TsExtendAdaptionField(Packet, TS_SIZE-4); ++ DropThisPayload = false; ++ } ++ ++ if (DropThisPayload) ++ { ++ return true; // Drop packet ++ } ++ } ++ ++ // Fix Continuity Counter and reproduce incoming offsets: ++ int NewContinuityOutput = TsHasPayload(Packet) ? (LastContinuityOutput + 1) & TS_CONT_CNT_MASK : LastContinuityOutput; ++ NewContinuityOutput = (NewContinuityOutput + ContinuityOffset) & TS_CONT_CNT_MASK; ++ TsSetContinuityCounter(Packet, NewContinuityOutput); ++ LastContinuityOutput = NewContinuityOutput; ++ ContinuityOffset = 0; ++ ++ return false; // Keep packet ++} ++ ++// --- cNaluStreamProcessor --------------------------------------------------------- ++ ++cNaluStreamProcessor::cNaluStreamProcessor() ++{ ++ pPatPmtParser = NULL; ++ vpid = -1; ++ data = NULL; ++ length = 0; ++ tempLength = 0; ++ tempLengthAtEnd = false; ++ TotalPackets = 0; ++ DroppedPackets = 0; ++} ++ ++void cNaluStreamProcessor::PutBuffer(uchar *Data, int Length) ++{ ++ if (length > 0) ++ esyslog("cNaluStreamProcessor::PutBuffer: New data before old data was processed!"); ++ ++ data = Data; ++ length = Length; ++} ++ ++uchar* cNaluStreamProcessor::GetBuffer(int &OutLength) ++{ ++ if (length <= 0) ++ { ++ // Need more data - quick exit ++ OutLength = 0; ++ return NULL; ++ } ++ if (tempLength > 0) // Data in temp buffer? ++ { ++ if (tempLengthAtEnd) // Data is at end, copy to beginning ++ { ++ // Overlapping src and dst! ++ for (int i=0; i 0) ++ { ++ int Size = min(TS_SIZE-tempLength, length); ++ memcpy(tempBuffer+tempLength, data, Size); ++ data += Size; ++ length -= Size; ++ tempLength += Size; ++ } ++ if (tempLength < TS_SIZE) ++ { ++ // All incoming data buffered, but need more data ++ tempLengthAtEnd = false; ++ OutLength = 0; ++ return NULL; ++ } ++ // Now: TempLength==TS_SIZE ++ if (tempBuffer[0] != TS_SYNC_BYTE) ++ { ++ // Need to sync on TS within temp buffer ++ int Skipped = 1; ++ while (Skipped < TS_SIZE && (tempBuffer[Skipped] != TS_SYNC_BYTE || (Skipped < length && data[Skipped] != TS_SYNC_BYTE))) ++ Skipped++; ++ esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped); ++ // Pass through skipped bytes ++ tempLengthAtEnd = true; ++ tempLength = TS_SIZE - Skipped; // may be 0, thats ok ++ OutLength = Skipped; ++ return tempBuffer; ++ } ++ // Now: TempBuffer is a TS packet ++ int Pid = TsPid(tempBuffer); ++ if (pPatPmtParser) ++ { ++ if (Pid == 0) ++ pPatPmtParser->ParsePat(tempBuffer, TS_SIZE); ++ else if (pPatPmtParser->IsPmtPid(Pid)) ++ pPatPmtParser->ParsePmt(tempBuffer, TS_SIZE); ++ } ++ ++ TotalPackets++; ++ bool Drop = false; ++ if (Pid == vpid || (pPatPmtParser && Pid == pPatPmtParser->Vpid() && pPatPmtParser->Vtype() == 0x1B)) ++ Drop = NaluDumper.ProcessTSPacket(tempBuffer); ++ if (!Drop) ++ { ++ // Keep this packet, then continue with new data ++ tempLength = 0; ++ OutLength = TS_SIZE; ++ return tempBuffer; ++ } ++ // Drop TempBuffer ++ DroppedPackets++; ++ tempLength = 0; ++ } ++ // Now: TempLength==0, just process data/length ++ ++ // Pointer to processed data / length: ++ uchar *Out = data; ++ uchar *OutEnd = Out; ++ ++ while (length >= TS_SIZE) ++ { ++ if (data[0] != TS_SYNC_BYTE) { ++ int Skipped = 1; ++ while (Skipped < length && (data[Skipped] != TS_SYNC_BYTE || (length - Skipped > TS_SIZE && data[Skipped + TS_SIZE] != TS_SYNC_BYTE))) ++ Skipped++; ++ esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped); ++ ++ // Pass through skipped bytes ++ if (OutEnd != data) ++ memcpy(OutEnd, data, Skipped); ++ OutEnd += Skipped; ++ continue; ++ } ++ // Now: Data starts with complete TS packet ++ ++ int Pid = TsPid(data); ++ if (pPatPmtParser) ++ { ++ if (Pid == 0) ++ pPatPmtParser->ParsePat(data, TS_SIZE); ++ else if (pPatPmtParser->IsPmtPid(Pid)) ++ pPatPmtParser->ParsePmt(data, TS_SIZE); ++ } ++ ++ TotalPackets++; ++ bool Drop = false; ++ if (Pid == vpid || (pPatPmtParser && Pid == pPatPmtParser->Vpid() && pPatPmtParser->Vtype() == 0x1B)) ++ Drop = NaluDumper.ProcessTSPacket(data); ++ if (!Drop) ++ { ++ if (OutEnd != data) ++ memcpy(OutEnd, data, TS_SIZE); ++ OutEnd += TS_SIZE; ++ } ++ else ++ { ++ DroppedPackets++; ++ } ++ data += TS_SIZE; ++ length -= TS_SIZE; ++ } ++ // Now: Less than a packet remains. ++ if (length > 0) ++ { ++ // copy remains into temp buffer ++ memcpy(tempBuffer, data, length); ++ tempLength = length; ++ tempLengthAtEnd = false; ++ length = 0; ++ } ++ OutLength = (OutEnd - Out); ++ return OutLength > 0 ? Out : NULL; ++} +diff -Naur vdr-2.1.6/remux.h vdr-2.1.6-naludump-0.1/remux.h +--- vdr-2.1.6/remux.h 2014-02-08 13:41:50.000000000 +0100 ++++ vdr-2.1.6-naludump-0.1/remux.h 2014-03-30 17:47:25.000000000 +0200 +@@ -62,6 +62,11 @@ + return p[3] & TS_PAYLOAD_EXISTS; + } + ++inline bool TsSetPayload(const uchar *p) ++{ ++ return p[3] & TS_PAYLOAD_EXISTS; ++} ++ + inline bool TsHasAdaptationField(const uchar *p) + { + return p[3] & TS_ADAPT_FIELD_EXISTS; +@@ -143,6 +148,7 @@ + int64_t TsGetDts(const uchar *p, int l); + void TsSetPts(uchar *p, int l, int64_t Pts); + void TsSetDts(uchar *p, int l, int64_t Dts); ++void TsExtendAdaptionField(unsigned char *Packet, int ToLength); + + // Some PES handling tools: + // The following functions that take a pointer to PES data all assume that +@@ -518,4 +524,78 @@ + ///< available. + }; + ++ ++#define PATCH_NALUDUMP 100 ++ ++class cNaluDumper { ++ unsigned int History; ++ ++ int LastContinuityInput; ++ int LastContinuityOutput; ++ int ContinuityOffset; ++ ++ bool DropAllPayload; ++ ++ int PesId; ++ int PesOffset; ++ ++ int NaluOffset; ++ ++ enum eNaluFillState { ++ NALU_NONE=0, // currently not NALU fill stream ++ NALU_FILL, // Within NALU fill stream, 0xff bytes and NALU start code in byte 0 ++ NALU_TERM, // Within NALU fill stream, read 0x80 terminating byte ++ NALU_END // Beyond end of NALU fill stream, expecting 0x00 0x00 0x01 now ++ }; ++ ++ eNaluFillState NaluFillState; ++ ++ struct sPayloadInfo { ++ int DropPayloadStartBytes; ++ int DropPayloadEndBytes; ++ bool DropAllPayloadBytes; ++ }; ++ ++public: ++ cNaluDumper(); ++ ++ void reset(); ++ ++ // Single packet interface: ++ bool ProcessTSPacket(unsigned char *Packet); ++ ++private: ++ void ProcessPayload(unsigned char *Payload, int size, bool PayloadStart, sPayloadInfo &Info); ++}; ++ ++class cNaluStreamProcessor { ++ //Buffer stream interface: ++ int vpid; ++ uchar *data; ++ int length; ++ uchar tempBuffer[TS_SIZE]; ++ int tempLength; ++ bool tempLengthAtEnd; ++ cPatPmtParser *pPatPmtParser; ++ cNaluDumper NaluDumper; ++ ++ long long int TotalPackets; ++ long long int DroppedPackets; ++public: ++ cNaluStreamProcessor(); ++ ++ void SetPid(int VPid) { vpid = VPid; } ++ void SetPatPmtParser(cPatPmtParser *_pPatPmtParser) { pPatPmtParser = _pPatPmtParser; } ++ // Set either a PID or set a pointer to an PatPmtParser that will detect _one_ PID ++ ++ void PutBuffer(uchar *Data, int Length); ++ // Add new data to be processed. Data must be valid until Get() returns NULL. ++ uchar* GetBuffer(int &OutLength); ++ // Returns filtered data, or NULL/0 to indicate that all data from Put() was processed ++ // or buffered. ++ ++ long long int GetTotalPackets() { return TotalPackets; } ++ long long int GetDroppedPackets() { return DroppedPackets; } ++}; ++ + #endif // __REMUX_H diff --git a/vdr-2.2.0-ttxtsubs.patch b/vdr-2.2.0-ttxtsubs.patch new file mode 100644 index 0000000..929210f --- /dev/null +++ b/vdr-2.2.0-ttxtsubs.patch @@ -0,0 +1,1016 @@ +diff --git a/MANUAL b/MANUAL +index 3c4003e..e8de3ad 100644 +--- a/MANUAL ++++ b/MANUAL +@@ -810,6 +810,9 @@ Version 2.2 + background transparency. By default the values as broadcast + are used. + ++ Enable teletext support = yes ++ If set to 'yes', enables teletext subtitles. ++ + LNB: + + Use DiSEqC = no Generally turns DiSEqC support on or off. +diff --git a/Makefile b/Makefile +index 9722036..92db319 100644 +--- a/Makefile ++++ b/Makefile +@@ -74,6 +74,8 @@ OBJS = args.o audio.o channels.o ci.o config.o cutter.o device.o diseqc.o dvbdev + skinclassic.o skinlcars.o skins.o skinsttng.o sourceparams.o sources.o spu.o status.o svdrp.o themes.o thread.o\ + timers.o tools.o transfer.o vdr.o videodir.o + ++OBJS += vdrttxtsubshooks.o ++ + DEFINES += $(CDEFINES) + INCLUDES += $(CINCLUDES) + +diff --git a/channels.c b/channels.c +index 564088f..d0fc3d8 100644 +--- a/channels.c ++++ b/channels.c +@@ -421,6 +421,26 @@ void cChannel::SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *Compos + } + } + ++void cChannel::SetTeletextSubtitlePages(tTeletextSubtitlePage pages[], int numberOfPages) ++{ ++ int mod = CHANNELMOD_NONE; ++ if (totalTtxtSubtitlePages != (fixedTtxtSubtitlePages + numberOfPages)) ++ mod |= CHANNELMOD_PIDS; ++ totalTtxtSubtitlePages = fixedTtxtSubtitlePages; ++ for (int i = 0; (i < numberOfPages) && (totalTtxtSubtitlePages < MAXTXTPAGES); i++) { ++ if (teletextSubtitlePages[totalTtxtSubtitlePages].ttxtMagazine != pages[i].ttxtMagazine || ++ teletextSubtitlePages[totalTtxtSubtitlePages].ttxtPage != pages[i].ttxtPage || ++ teletextSubtitlePages[totalTtxtSubtitlePages].ttxtType != pages[i].ttxtType || ++ strcmp(teletextSubtitlePages[totalTtxtSubtitlePages].ttxtLanguage, pages[i].ttxtLanguage)) { ++ mod |= CHANNELMOD_PIDS; ++ teletextSubtitlePages[totalTtxtSubtitlePages] = pages[i]; ++ } ++ totalTtxtSubtitlePages++; ++ } ++ modification |= mod; ++ Channels.SetModified(); ++} ++ + void cChannel::SetSeen(void) + { + seen = time(NULL); +@@ -556,10 +576,17 @@ cString cChannel::ToText(const cChannel *Channel) + q += IntArrayToString(q, Channel->dpids, 10, Channel->dlangs, Channel->dtypes); + } + *q = 0; +- const int TBufferSize = MAXSPIDS * (5 + 1 + MAXLANGCODE2) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod+cod', +10: paranoia and tpid ++ const int TBufferSize = (MAXTXTPAGES * MAXSPIDS) * (5 + 1 + MAXLANGCODE2) + 10; // 5 digits plus delimiting ',' or ';' plus optional '=cod+cod', +10: paranoia and tpid + char tpidbuf[TBufferSize]; + q = tpidbuf; + q += snprintf(q, sizeof(tpidbuf), "%d", Channel->tpid); ++ if (Channel->fixedTtxtSubtitlePages > 0) { ++ *q++ = '+'; ++ for (int i = 0; i < Channel->fixedTtxtSubtitlePages; ++i) { ++ tTeletextSubtitlePage page = Channel->teletextSubtitlePages[i]; ++ q += snprintf(q, sizeof(tpidbuf) - (q - tpidbuf), "%d=%s", page.PageNumber(), page.ttxtLanguage); ++ } ++ } + if (Channel->spids[0]) { + *q++ = ';'; + q += IntArrayToString(q, Channel->spids, 10, Channel->slangs); +@@ -730,6 +757,32 @@ bool cChannel::Parse(const char *s) + } + spids[NumSpids] = 0; + } ++ fixedTtxtSubtitlePages = 0; ++ if ((p = strchr(tpidbuf, '+')) != NULL) { ++ *p++ = 0; ++ char *q; ++ char *strtok_next; ++ while ((q = strtok_r(p, ",", &strtok_next)) != NULL) { ++ if (fixedTtxtSubtitlePages < MAXTXTPAGES) { ++ int page; ++ char *l = strchr(q, '='); ++ if (l) ++ *l++ = 0; ++ if (sscanf(q, "%d", &page) == 1) { ++ teletextSubtitlePages[fixedTtxtSubtitlePages] = tTeletextSubtitlePage(page); ++ if (l) ++ strn0cpy(teletextSubtitlePages[fixedTtxtSubtitlePages].ttxtLanguage, l, MAXLANGCODE2); ++ fixedTtxtSubtitlePages++; ++ } ++ else ++ esyslog("ERROR: invalid Teletext page!"); // no need to set ok to 'false' ++ } ++ else ++ esyslog("ERROR: too many Teletext pages!"); // no need to set ok to 'false' ++ p = NULL; ++ } ++ totalTtxtSubtitlePages = fixedTtxtSubtitlePages; ++ } + if (sscanf(tpidbuf, "%d", &tpid) != 1) + return false; + if (caidbuf) { +diff --git a/channels.h b/channels.h +index 3323882..c62fdec 100644 +--- a/channels.h ++++ b/channels.h +@@ -36,6 +36,7 @@ + #define MAXDPIDS 16 // dolby (AC3 + DTS) + #define MAXSPIDS 32 // subtitles + #define MAXCAIDS 12 // conditional access ++#define MAXTXTPAGES 8 // teletext pages + + #define MAXLANGCODE1 4 // a 3 letter language code, zero terminated + #define MAXLANGCODE2 8 // up to two 3 letter language codes, separated by '+' and zero terminated +@@ -72,6 +73,16 @@ public: + static const tChannelID InvalidID; + }; + ++struct tTeletextSubtitlePage { ++ tTeletextSubtitlePage(void) { ttxtPage = ttxtMagazine = 0; ttxtType = 0x02; strcpy(ttxtLanguage, "und"); } ++ tTeletextSubtitlePage(int page) { ttxtMagazine = (page / 100) & 0x7; ttxtPage = (((page % 100) / 10) << 4) + (page % 10); ttxtType = 0x02; strcpy(ttxtLanguage, "und"); } ++ char ttxtLanguage[MAXLANGCODE1]; ++ uchar ttxtPage; ++ uchar ttxtMagazine; ++ uchar ttxtType; ++ int PageNumber(void) const { return BCDCHARTOINT(ttxtMagazine) * 100 + BCDCHARTOINT(ttxtPage); } ++ }; ++ + class cChannel; + + class cLinkChannel : public cListObject { +@@ -116,6 +127,9 @@ private: + uint16_t compositionPageIds[MAXSPIDS]; + uint16_t ancillaryPageIds[MAXSPIDS]; + int tpid; ++ int fixedTtxtSubtitlePages; ++ int totalTtxtSubtitlePages; ++ tTeletextSubtitlePage teletextSubtitlePages[MAXTXTPAGES]; + int caids[MAXCAIDS + 1]; // list is zero-terminated + int nid; + int tid; +@@ -169,6 +183,8 @@ public: + uint16_t CompositionPageId(int i) const { return (0 <= i && i < MAXSPIDS) ? compositionPageIds[i] : uint16_t(0); } + uint16_t AncillaryPageId(int i) const { return (0 <= i && i < MAXSPIDS) ? ancillaryPageIds[i] : uint16_t(0); } + int Tpid(void) const { return tpid; } ++ const tTeletextSubtitlePage *TeletextSubtitlePages() const { return teletextSubtitlePages; } ++ int TotalTeletextSubtitlePages() const { return totalTtxtSubtitlePages; } + const int *Caids(void) const { return caids; } + int Ca(int Index = 0) const { return Index < MAXCAIDS ? caids[Index] : 0; } + int Nid(void) const { return nid; } +@@ -198,6 +214,7 @@ public: + void SetName(const char *Name, const char *ShortName, const char *Provider); + void SetPortalName(const char *PortalName); + 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 SetTeletextSubtitlePages(tTeletextSubtitlePage pages[], int numberOfPages); + void SetCaIds(const int *CaIds); // list must be zero-terminated + void SetCaDescriptors(int Level); + void SetLinkChannels(cLinkChannels *LinkChannels); +diff --git a/ci.c b/ci.c +index ffc7ff7..b6c5753 100644 +--- a/ci.c ++++ b/ci.c +@@ -2155,6 +2155,8 @@ void cCamSlot::AddChannel(const cChannel *Channel) + AddPid(Channel->Sid(), *Dpid, STREAM_TYPE_PRIVATE); + for (const int *Spid = Channel->Spids(); *Spid; Spid++) + AddPid(Channel->Sid(), *Spid, STREAM_TYPE_PRIVATE); ++ if (Channel->Tpid() && Setup.SupportTeletext) ++ AddPid(Channel->Sid(), Channel->Tpid(), STREAM_TYPE_PRIVATE); + } + } + +@@ -2178,6 +2180,9 @@ bool cCamSlot::CanDecrypt(const cChannel *Channel) + CaPmt.AddPid(*Dpid, STREAM_TYPE_PRIVATE); + for (const int *Spid = Channel->Spids(); *Spid; Spid++) + CaPmt.AddPid(*Spid, STREAM_TYPE_PRIVATE); ++ if (Channel->Tpid() && Setup.SupportTeletext) { ++ CaPmt.AddPid(Channel->Tpid(), STREAM_TYPE_PRIVATE); ++ } + cas->SendPMT(&CaPmt); + cTimeMs Timeout(QUERY_REPLY_TIMEOUT); + do { +diff --git a/config.c b/config.c +index 9c6b71e..83e2e6f 100644 +--- a/config.c ++++ b/config.c +@@ -403,6 +403,7 @@ cSetup::cSetup(void) + MarginStop = 10; + AudioLanguages[0] = -1; + DisplaySubtitles = 0; ++ SupportTeletext = 1; + SubtitleLanguages[0] = -1; + SubtitleOffset = 0; + SubtitleFgTransparency = 0; +@@ -625,6 +626,7 @@ bool cSetup::Parse(const char *Name, const char *Value) + else if (!strcasecmp(Name, "MarginStop")) MarginStop = atoi(Value); + else if (!strcasecmp(Name, "AudioLanguages")) return ParseLanguages(Value, AudioLanguages); + else if (!strcasecmp(Name, "DisplaySubtitles")) DisplaySubtitles = atoi(Value); ++ else if (!strcasecmp(Name, "SupportTeletext")) SupportTeletext = atoi(Value); + else if (!strcasecmp(Name, "SubtitleLanguages")) return ParseLanguages(Value, SubtitleLanguages); + else if (!strcasecmp(Name, "SubtitleOffset")) SubtitleOffset = atoi(Value); + else if (!strcasecmp(Name, "SubtitleFgTransparency")) SubtitleFgTransparency = atoi(Value); +@@ -751,6 +753,7 @@ bool cSetup::Save(void) + Store("MarginStop", MarginStop); + StoreLanguages("AudioLanguages", AudioLanguages); + Store("DisplaySubtitles", DisplaySubtitles); ++ Store("SupportTeletext", SupportTeletext); + StoreLanguages("SubtitleLanguages", SubtitleLanguages); + Store("SubtitleOffset", SubtitleOffset); + Store("SubtitleFgTransparency", SubtitleFgTransparency); +diff --git a/config.h b/config.h +index d1bae04..db1cbe1 100644 +--- a/config.h ++++ b/config.h +@@ -280,6 +280,7 @@ public: + int MarginStart, MarginStop; + int AudioLanguages[I18N_MAX_LANGUAGES + 1]; + int DisplaySubtitles; ++ int SupportTeletext; + int SubtitleLanguages[I18N_MAX_LANGUAGES + 1]; + int SubtitleOffset; + int SubtitleFgTransparency, SubtitleBgTransparency; +diff --git a/device.c b/device.c +index 14ab07d..d4a6f5d 100644 +--- a/device.c ++++ b/device.c +@@ -19,6 +19,7 @@ + #include "receiver.h" + #include "status.h" + #include "transfer.h" ++#include "vdrttxtsubshooks.h" + + // --- cLiveSubtitle --------------------------------------------------------- + +@@ -1321,6 +1322,13 @@ int cDevice::PlayPesPacket(const uchar *Data, int Length, bool VideoOnly) + } + break; + case 0xBD: { // private stream 1 ++ // EBU Teletext data, ETSI EN 300 472 ++ // if PES data header length = 24 and data_identifier = 0x10..0x1F (EBU Data) ++ if (Data[8] == 0x24 && Data[45] >= 0x10 && Data[45] < 0x20) { ++ cVDRTtxtsubsHookListener::Hook()->PlayerTeletextData((uint8_t*)Data, Length); ++ break; ++ } ++ + int PayloadOffset = Data[8] + 9; + + // Compatibility mode for old subtitles plugin: +@@ -1480,6 +1488,7 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly) + tsToPesVideo.Reset(); + tsToPesAudio.Reset(); + tsToPesSubtitle.Reset(); ++ tsToPesTeletext.Reset(); + } + else if (Length < TS_SIZE) { + esyslog("ERROR: skipped %d bytes of TS fragment", Length); +@@ -1524,6 +1533,17 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly) + if (!VideoOnly || HasIBPTrickSpeed()) + PlayTsSubtitle(Data, TS_SIZE); + } ++ else if (Pid == patPmtParser.Tpid()) { ++ if (!VideoOnly || HasIBPTrickSpeed()) { ++ int l; ++ tsToPesTeletext.PutTs(Data, Length); ++ if (const uchar *p = tsToPesTeletext.GetPes(l)) { ++ if ((l > 45) && (p[0] == 0x00) && (p[1] == 0x00) && (p[2] == 0x01) && (p[3] == 0xbd) && (p[8] == 0x24) && (p[45] >= 0x10) && (p[45] < 0x20)) ++ cVDRTtxtsubsHookListener::Hook()->PlayerTeletextData((uchar *)p, l, false, patPmtParser.TeletextSubtitlePages(), patPmtParser.TotalTeletextSubtitlePages()); ++ tsToPesTeletext.Reset(); ++ } ++ } ++ } + } + } + else if (Pid == patPmtParser.Ppid()) { +diff --git a/device.h b/device.h +index b06d977..25a7bbe 100644 +--- a/device.h ++++ b/device.h +@@ -602,6 +602,7 @@ private: + cTsToPes tsToPesVideo; + cTsToPes tsToPesAudio; + cTsToPes tsToPesSubtitle; ++ cTsToPes tsToPesTeletext; + bool isPlayingVideo; + protected: + const cPatPmtParser *PatPmtParser(void) const { return &patPmtParser; } +diff --git a/menu.c b/menu.c +index ae61c64..a0dba1b 100644 +--- a/menu.c ++++ b/menu.c +@@ -3326,6 +3326,7 @@ void cMenuSetupDVB::Setup(void) + Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle foreground transparency"), &data.SubtitleFgTransparency, 0, 9)); + Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle background transparency"), &data.SubtitleBgTransparency, 0, 10)); + } ++ Add(new cMenuEditBoolItem(tr("Setup.DVB$Enable teletext support"), &data.SupportTeletext)); + + SetCurrent(Get(current)); + Display(); +diff --git a/pat.c b/pat.c +index 98d306e..4ca0d5a 100644 +--- a/pat.c ++++ b/pat.c +@@ -12,6 +12,7 @@ + #include "channels.h" + #include "libsi/section.h" + #include "libsi/descriptor.h" ++#include "vdrttxtsubshooks.h" + + #define PMT_SCAN_TIMEOUT 1000 // ms + +@@ -426,6 +427,8 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length + char DLangs[MAXDPIDS][MAXLANGCODE2] = { "" }; + char SLangs[MAXSPIDS][MAXLANGCODE2] = { "" }; + int Tpid = 0; ++ tTeletextSubtitlePage TeletextSubtitlePages[MAXTXTPAGES]; ++ int NumTPages = 0; + int NumApids = 0; + int NumDpids = 0; + int NumSpids = 0; +@@ -517,8 +520,21 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length + NumSpids++; + } + break; +- case SI::TeletextDescriptorTag: ++ case SI::TeletextDescriptorTag: { + Tpid = esPid; ++ SI::TeletextDescriptor *sd = (SI::TeletextDescriptor *)d; ++ SI::TeletextDescriptor::Teletext ttxt; ++ for (SI::Loop::Iterator it; sd->teletextLoop.getNext(ttxt, it); ) { ++ bool isSubtitlePage = (ttxt.getTeletextType() == 0x02) || (ttxt.getTeletextType() == 0x05); ++ if ((NumTPages < MAXTXTPAGES) && ttxt.languageCode[0] && isSubtitlePage) { ++ strn0cpy(TeletextSubtitlePages[NumTPages].ttxtLanguage, I18nNormalizeLanguageCode(ttxt.languageCode), MAXLANGCODE1); ++ TeletextSubtitlePages[NumTPages].ttxtPage = ttxt.getTeletextPageNumber(); ++ TeletextSubtitlePages[NumTPages].ttxtMagazine = ttxt.getTeletextMagazineNumber(); ++ TeletextSubtitlePages[NumTPages].ttxtType = ttxt.getTeletextType(); ++ NumTPages++; ++ } ++ } ++ } + break; + case SI::ISO639LanguageDescriptorTag: { + SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; +@@ -630,6 +646,12 @@ void cPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length + } + if (Setup.UpdateChannels >= 2) { + Channel->SetPids(Vpid, Ppid, Vtype, Apids, Atypes, ALangs, Dpids, Dtypes, DLangs, Spids, SLangs, Tpid); ++ if (NumTPages < MAXTXTPAGES) { ++ int manualPageNumber = cVDRTtxtsubsHookListener::Hook()->ManualPageNumber(Channel); ++ if (manualPageNumber) ++ TeletextSubtitlePages[NumTPages++] = tTeletextSubtitlePage(manualPageNumber); ++ } ++ Channel->SetTeletextSubtitlePages(TeletextSubtitlePages, NumTPages); + Channel->SetCaIds(CaDescriptors->CaIds()); + Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds); + } +diff --git a/po/ca_ES.po b/po/ca_ES.po +index 8ea7bd3..5554051 100644 +--- a/po/ca_ES.po ++++ b/po/ca_ES.po +@@ -1059,6 +1059,9 @@ msgstr "Transpar + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Transparència fons subtítols" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "Configuració de l'LNB" + +diff --git a/po/cs_CZ.po b/po/cs_CZ.po +index 6c5ac1b..91625a8 100644 +--- a/po/cs_CZ.po ++++ b/po/cs_CZ.po +@@ -1059,6 +1059,9 @@ msgstr "Průhlednost písma titulků" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Průhlednost pozadí titulků" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/da_DK.po b/po/da_DK.po +index d0fbbf8..6504096 100644 +--- a/po/da_DK.po ++++ b/po/da_DK.po +@@ -1056,6 +1056,9 @@ msgstr "Undertekst forgrundsgennemsigtighed" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Undertekst baggrundsgennemsigtighed" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/de_DE.po b/po/de_DE.po +index 762c6fa..0e28962 100644 +--- a/po/de_DE.po ++++ b/po/de_DE.po +@@ -1057,6 +1057,9 @@ msgstr "Untertitel-Transparenz Vordergrund" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Untertitel-Transparenz Hintergrund" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "Videotext-Unterstützung aktivieren" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/el_GR.po b/po/el_GR.po +index a131cd7..6f131a3 100644 +--- a/po/el_GR.po ++++ b/po/el_GR.po +@@ -1056,6 +1056,9 @@ msgstr "" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/es_ES.po b/po/es_ES.po +index b2827b5..139da7a 100644 +--- a/po/es_ES.po ++++ b/po/es_ES.po +@@ -1057,6 +1057,9 @@ msgstr "Transparencia primer plano subt + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Transparencia fondo subtítulos" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/et_EE.po b/po/et_EE.po +index 8bf931a..a4c9a09 100644 +--- a/po/et_EE.po ++++ b/po/et_EE.po +@@ -1056,6 +1056,9 @@ msgstr "Subtiitri läbipaistvus" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Subtiitri tausta läbipaistvus" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "Teleteksti tugi" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/fi_FI.po b/po/fi_FI.po +index 05d4b0f..9d55f39 100644 +--- a/po/fi_FI.po ++++ b/po/fi_FI.po +@@ -1060,6 +1060,9 @@ msgstr "Tekstityksen läpinäkyvyys" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Tekstityksen taustan läpinäkyvyys" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "Salli teksti-TV-tuki" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/fr_FR.po b/po/fr_FR.po +index 06bb125..d3276f5 100644 +--- a/po/fr_FR.po ++++ b/po/fr_FR.po +@@ -1067,6 +1067,9 @@ msgstr "Transparence de l'avant-plan des sous-titres" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Transparence du fond des sous-titres" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/hr_HR.po b/po/hr_HR.po +index 0424a44..109af41 100644 +--- a/po/hr_HR.po ++++ b/po/hr_HR.po +@@ -1058,6 +1058,9 @@ msgstr "Transparentnost titla" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Transparentnost pozadine titla" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/hu_HU.po b/po/hu_HU.po +index c34bc0e..32a0ee3 100644 +--- a/po/hu_HU.po ++++ b/po/hu_HU.po +@@ -1061,6 +1061,9 @@ msgstr "Felirat transzparenciája" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Felirat hátterének transzparenciája" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/it_IT.po b/po/it_IT.po +index 79946ff..0b93c4a 100644 +--- a/po/it_IT.po ++++ b/po/it_IT.po +@@ -1062,6 +1062,9 @@ msgstr "Trasparenza sottotitoli" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Trasparenza sfondo sottotitoli" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/lt_LT.po b/po/lt_LT.po +index a77dfcc..3ad927d 100644 +--- a/po/lt_LT.po ++++ b/po/lt_LT.po +@@ -1056,6 +1056,9 @@ msgstr "Subtitrų fonto permatomumas" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Subtitrų fono permatomumas" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "Konverteris (LNB)" + +diff --git a/po/nl_NL.po b/po/nl_NL.po +index ab3fabd..b6e1a10 100644 +--- a/po/nl_NL.po ++++ b/po/nl_NL.po +@@ -1062,6 +1062,9 @@ msgstr "Transparantie voorgrond ondertiteling" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Transparantie achtergrond ondertiteling" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/nn_NO.po b/po/nn_NO.po +index ba20fc6..11ce81e 100644 +--- a/po/nn_NO.po ++++ b/po/nn_NO.po +@@ -1057,6 +1057,9 @@ msgstr "" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/pl_PL.po b/po/pl_PL.po +index becb8cb..9addb46 100644 +--- a/po/pl_PL.po ++++ b/po/pl_PL.po +@@ -1059,6 +1059,9 @@ msgstr "Prze + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Prze¼rocze podtytu³ów: T³o" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/pt_PT.po b/po/pt_PT.po +index 9a0f792..6a11cda 100644 +--- a/po/pt_PT.po ++++ b/po/pt_PT.po +@@ -1057,6 +1057,9 @@ msgstr "Transpar + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Transparência de fundo das legendas" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/ro_RO.po b/po/ro_RO.po +index c88dd0a..4f67d1c 100644 +--- a/po/ro_RO.po ++++ b/po/ro_RO.po +@@ -1058,6 +1058,9 @@ msgstr "TransparenÅ£a prim-planului subtitrării" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "TransparenÅ£a fundalului subtitrării" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/ru_RU.po b/po/ru_RU.po +index 3e5057d..5d50264 100644 +--- a/po/ru_RU.po ++++ b/po/ru_RU.po +@@ -1057,6 +1057,9 @@ msgstr " + msgid "Setup.DVB$Subtitle background transparency" + msgstr "¿àÞ×àÐçÝÞáâì äÞÝÐ áãÑâØâàÞÒ" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "ºÞÝÒÕàâÕà" + +diff --git a/po/sk_SK.po b/po/sk_SK.po +index cfc9bde..4fa36d0 100644 +--- a/po/sk_SK.po ++++ b/po/sk_SK.po +@@ -1057,6 +1057,9 @@ msgstr "Prieh + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Priehµadnos» pozadia titulkov" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB (nízko ¹umová jednotka)" + +diff --git a/po/sl_SI.po b/po/sl_SI.po +index d12ccb2..dd86028 100644 +--- a/po/sl_SI.po ++++ b/po/sl_SI.po +@@ -1057,6 +1057,9 @@ msgstr "Transparentnost podnapisov" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Transparentnost ozadja podnapisov" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/sv_SE.po b/po/sv_SE.po +index c164fa1..1c07570 100644 +--- a/po/sv_SE.po ++++ b/po/sv_SE.po +@@ -1061,6 +1061,9 @@ msgstr "Transparent f + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Transparent bakgrund textremsa" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/tr_TR.po b/po/tr_TR.po +index 46a6a08..b4a8c55 100644 +--- a/po/tr_TR.po ++++ b/po/tr_TR.po +@@ -1056,6 +1056,9 @@ msgstr "Altyaz + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Altyazý arka þeffaflýk" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "LNB" + +diff --git a/po/uk_UA.po b/po/uk_UA.po +index 9d7328b..feba2ca 100644 +--- a/po/uk_UA.po ++++ b/po/uk_UA.po +@@ -1057,6 +1057,9 @@ msgstr "ПрозоріÑть переднього плану Ñубтитрів" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "ПрозоріÑть заднього плану Ñубтитрів" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "Конвертер" + +diff --git a/po/zh_CN.po b/po/zh_CN.po +index 0dfbd6c..9c16c46 100644 +--- a/po/zh_CN.po ++++ b/po/zh_CN.po +@@ -1058,6 +1058,9 @@ msgstr "字幕剿™¯é€æ˜Žåº¦" + msgid "Setup.DVB$Subtitle background transparency" + msgstr "å­—å¹•èƒŒæ™¯é€æ˜Žåº¦" + ++msgid "Setup.DVB$Enable teletext support" ++msgstr "" ++ + msgid "LNB" + msgstr "切æ¢å™¨è®¾ç½®" + +diff --git a/receiver.c b/receiver.c +index d8f51e6..7e1c252 100644 +--- a/receiver.c ++++ b/receiver.c +@@ -72,7 +72,8 @@ bool cReceiver::SetPids(const cChannel *Channel) + (Channel->Ppid() == Channel->Vpid() || AddPid(Channel->Ppid())) && + AddPids(Channel->Apids()) && + AddPids(Channel->Dpids()) && +- AddPids(Channel->Spids()); ++ AddPids(Channel->Spids()) && ++ (!Setup.SupportTeletext || AddPid(Channel->Tpid())); + } + return true; + } +diff --git a/remux.c b/remux.c +index 23e8387..f1bf562 100644 +--- a/remux.c ++++ b/remux.c +@@ -416,6 +416,29 @@ int cPatPmtGenerator::MakeSubtitlingDescriptor(uchar *Target, const char *Langua + return i; + } + ++int cPatPmtGenerator::MakeTeletextDescriptor(uchar *Target, const tTeletextSubtitlePage *pages, int pageCount) ++{ ++ int i = 0, j = 0; ++ Target[i++] = SI::TeletextDescriptorTag; ++ int l = i; ++ Target[i++] = 0x00; // length ++ for (int n = 0; n < pageCount; n++) { ++ const char* Language = pages[n].ttxtLanguage; ++ Target[i++] = *Language++; ++ Target[i++] = *Language++; ++ Target[i++] = *Language++; ++ Target[i++] = (pages[n].ttxtType << 3) + pages[n].ttxtMagazine; ++ Target[i++] = pages[n].ttxtPage; ++ j++; ++ } ++ if (j > 0) { ++ Target[l] = j * 5; // update length ++ IncEsInfoLength(i); ++ return i; ++ } ++ return 0; ++} ++ + int cPatPmtGenerator::MakeLanguageDescriptor(uchar *Target, const char *Language) + { + int i = 0; +@@ -503,6 +526,7 @@ void cPatPmtGenerator::GeneratePmt(const cChannel *Channel) + if (Channel) { + int Vpid = Channel->Vpid(); + int Ppid = Channel->Ppid(); ++ int Tpid = Channel->Tpid(); + uchar *p = buf; + int i = 0; + p[i++] = 0x02; // table id +@@ -535,6 +559,10 @@ void cPatPmtGenerator::GeneratePmt(const cChannel *Channel) + i += MakeStream(buf + i, 0x06, Channel->Spid(n)); + i += MakeSubtitlingDescriptor(buf + i, Channel->Slang(n), Channel->SubtitlingType(n), Channel->CompositionPageId(n), Channel->AncillaryPageId(n)); + } ++ if (Tpid) { ++ i += MakeStream(buf + i, 0x06, Tpid); ++ i += MakeTeletextDescriptor(buf + i, Channel->TeletextSubtitlePages(), Channel->TotalTeletextSubtitlePages()); ++ } + + int sl = i - SectionLength - 2 + 4; // -2 = SectionLength storage, +4 = length of CRC + buf[SectionLength] |= (sl >> 8) & 0x0F; +@@ -608,6 +636,7 @@ void cPatPmtParser::Reset(void) + pmtPids[0] = 0; + vpid = vtype = 0; + ppid = 0; ++ tpid = 0; + } + + void cPatPmtParser::ParsePat(const uchar *Data, int Length) +@@ -696,11 +725,13 @@ void cPatPmtParser::ParsePmt(const uchar *Data, int Length) + int NumSpids = 0; + vpid = vtype = 0; + ppid = 0; ++ tpid = 0; + apids[0] = 0; + dpids[0] = 0; + spids[0] = 0; + atypes[0] = 0; + dtypes[0] = 0; ++ totalTtxtSubtitlePages = 0; + SI::PMT::Stream stream; + for (SI::Loop::Iterator it; Pmt.streamLoop.getNext(stream, it); ) { + dbgpatpmt(" stream type = %02X, pid = %d", stream.getStreamType(), stream.getPid()); +@@ -799,6 +830,28 @@ void cPatPmtParser::ParsePmt(const uchar *Data, int Length) + spids[NumSpids] = 0; + } + break; ++ case SI::TeletextDescriptorTag: { ++ dbgpatpmt(" teletext"); ++ tpid = stream.getPid(); ++ SI::TeletextDescriptor *sd = (SI::TeletextDescriptor *)d; ++ SI::TeletextDescriptor::Teletext ttxt; ++ if (totalTtxtSubtitlePages < MAXTXTPAGES) { ++ for (SI::Loop::Iterator it; sd->teletextLoop.getNext(ttxt, it); ) { ++ bool isSubtitlePage = (ttxt.getTeletextType() == 0x02) || (ttxt.getTeletextType() == 0x05); ++ if (isSubtitlePage && ttxt.languageCode[0]) { ++ dbgpatpmt(" '%s:%x.%x'", ttxt.languageCode, ttxt.getTeletextMagazineNumber(), ttxt.getTeletextPageNumber()); ++ strn0cpy(teletextSubtitlePages[totalTtxtSubtitlePages].ttxtLanguage, I18nNormalizeLanguageCode(ttxt.languageCode), MAXLANGCODE1); ++ teletextSubtitlePages[totalTtxtSubtitlePages].ttxtPage = ttxt.getTeletextPageNumber(); ++ teletextSubtitlePages[totalTtxtSubtitlePages].ttxtMagazine = ttxt.getTeletextMagazineNumber(); ++ teletextSubtitlePages[totalTtxtSubtitlePages].ttxtType = ttxt.getTeletextType(); ++ totalTtxtSubtitlePages++; ++ if (totalTtxtSubtitlePages >= MAXTXTPAGES) ++ break; ++ } ++ } ++ } ++ } ++ break; + case SI::ISO639LanguageDescriptorTag: { + SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; + dbgpatpmt(" '%s'", ld->languageCode); +diff --git a/remux.h b/remux.h +index 6bc91ad..039525d 100644 +--- a/remux.h ++++ b/remux.h +@@ -302,6 +302,7 @@ protected: + int MakeStream(uchar *Target, uchar Type, int Pid); + int MakeAC3Descriptor(uchar *Target, uchar Type); + int MakeSubtitlingDescriptor(uchar *Target, const char *Language, uchar SubtitlingType, uint16_t CompositionPageId, uint16_t AncillaryPageId); ++ int MakeTeletextDescriptor(uchar *Target, const tTeletextSubtitlePage *pages, int pageCount); + int MakeLanguageDescriptor(uchar *Target, const char *Language); + int MakeCRC(uchar *Target, const uchar *Data, int Length); + void GeneratePmtPid(const cChannel *Channel); +@@ -349,6 +350,7 @@ private: + int vpid; + int ppid; + int vtype; ++ int tpid; + int apids[MAXAPIDS + 1]; // list is zero-terminated + int atypes[MAXAPIDS + 1]; // list is zero-terminated + char alangs[MAXAPIDS][MAXLANGCODE2]; +@@ -361,6 +363,8 @@ private: + uint16_t compositionPageIds[MAXSPIDS]; + uint16_t ancillaryPageIds[MAXSPIDS]; + bool updatePrimaryDevice; ++ int totalTtxtSubtitlePages; ++ tTeletextSubtitlePage teletextSubtitlePages[MAXTXTPAGES]; + protected: + int SectionLength(const uchar *Data, int Length) { return (Length >= 3) ? ((int(Data[1]) & 0x0F) << 8)| Data[2] : 0; } + public: +@@ -397,6 +401,9 @@ public: + int Vtype(void) const { return vtype; } + ///< Returns the video stream type as defined by the current PMT, or 0 if no video + ///< stream type has been detected, yet. ++ int Tpid(void) { return tpid; } ++ ///< Returns the teletext pid as defined by the current PMT, or 0 if no teletext ++ ///< pid has been detected, yet. + const int *Apids(void) const { return apids; } + const int *Dpids(void) const { return dpids; } + const int *Spids(void) const { return spids; } +@@ -411,6 +418,8 @@ public: + uchar SubtitlingType(int i) const { return (0 <= i && i < MAXSPIDS) ? subtitlingTypes[i] : uchar(0); } + uint16_t CompositionPageId(int i) const { return (0 <= i && i < MAXSPIDS) ? compositionPageIds[i] : uint16_t(0); } + uint16_t AncillaryPageId(int i) const { return (0 <= i && i < MAXSPIDS) ? ancillaryPageIds[i] : uint16_t(0); } ++ const tTeletextSubtitlePage *TeletextSubtitlePages() const { return teletextSubtitlePages; } ++ int TotalTeletextSubtitlePages() const { return totalTtxtSubtitlePages; } + }; + + // TS to PES converter: +diff --git a/vdr.5 b/vdr.5 +index fa233d7..cc06655 100644 +--- a/vdr.5 ++++ b/vdr.5 +@@ -249,6 +249,12 @@ by an '=' sign, as in + + .B ...:201;2001=deu,2002=eng:... + ++Manual teletext subtitling pages can be defined separated by a '+' sign. ++The pages (separated by commas) can contain language codes, delimited by a '=' ++sign, as in ++ ++.B ...:201+150=deu,151=fin;2001,2002:... ++ + .TP + .B Conditional access + A hexadecimal integer defining how this channel can be accessed: +diff --git a/vdrttxtsubshooks.c b/vdrttxtsubshooks.c +new file mode 100644 +index 0000000..2471788 +--- /dev/null ++++ b/vdrttxtsubshooks.c +@@ -0,0 +1,63 @@ ++/* ++ * vdr-ttxtsubs - A plugin for the Linux Video Disk Recorder ++ * Copyright (c) 2003 - 2008 Ragnar Sundblad ++ * ++ * 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 ++ * ++ */ ++ ++#include ++#include ++#include ++ ++#include "vdrttxtsubshooks.h" ++ ++// XXX Really should be a list... ++static cVDRTtxtsubsHookListener *gListener; ++ ++// ------ class cVDRTtxtsubsHookProxy ------ ++ ++class cVDRTtxtsubsHookProxy : public cVDRTtxtsubsHookListener ++{ ++ public: ++ virtual void HideOSD(void) { if(gListener) gListener->HideOSD(); }; ++ virtual void ShowOSD(void) { if(gListener) gListener->ShowOSD(); }; ++ virtual void PlayerTeletextData(uint8_t *p, int length, bool IsPesRecording, const struct tTeletextSubtitlePage teletextSubtitlePages[] = NULL, int pageCount = 0) ++ { if(gListener) gListener->PlayerTeletextData(p, length, IsPesRecording, teletextSubtitlePages, pageCount); }; ++ virtual int ManualPageNumber(const cChannel *channel) ++ { if(gListener) return gListener->ManualPageNumber(channel); else return 0; }; ++}; ++ ++ ++// ------ class cVDRTtxtsubsHookListener ------ ++ ++cVDRTtxtsubsHookListener::~cVDRTtxtsubsHookListener() ++{ ++ gListener = 0; ++} ++ ++void cVDRTtxtsubsHookListener::HookAttach(void) ++{ ++ gListener = this; ++ //printf("cVDRTtxtsubsHookListener::HookAttach\n"); ++} ++ ++static cVDRTtxtsubsHookProxy gProxy; ++ ++cVDRTtxtsubsHookListener *cVDRTtxtsubsHookListener::Hook(void) ++{ ++ return &gProxy; ++} ++ +diff --git a/vdrttxtsubshooks.h b/vdrttxtsubshooks.h +new file mode 100644 +index 0000000..2f97969 +--- /dev/null ++++ b/vdrttxtsubshooks.h +@@ -0,0 +1,46 @@ ++/* ++ * vdr-ttxtsubs - A plugin for the Linux Video Disk Recorder ++ * Copyright (c) 2003 - 2008 Ragnar Sundblad ++ * ++ * 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 ++ * ++ */ ++ ++#ifndef __VDRTTXTSUBSHOOKS_H ++#define __VDRTTXTSUBSHOOKS_H ++ ++#define TTXTSUBSVERSNUM 2 ++ ++class cDevice; ++class cChannel; ++struct tTeletextSubtitlePage; ++ ++class cVDRTtxtsubsHookListener { ++ public: ++ cVDRTtxtsubsHookListener(void) {}; ++ virtual ~cVDRTtxtsubsHookListener(); ++ ++ void HookAttach(void); ++ ++ virtual void HideOSD(void) {}; ++ virtual void ShowOSD(void) {}; ++ virtual void PlayerTeletextData(uint8_t *p, int length, bool IsPesRecording = true, const struct tTeletextSubtitlePage teletextSubtitlePages[] = NULL, int pageCount = 0) {}; ++ virtual int ManualPageNumber(const cChannel *channel) { return 0; }; ++ ++ // used by VDR to call hook listeners ++ static cVDRTtxtsubsHookListener *Hook(void); ++}; ++ ++#endif diff --git a/vdr-2.3.2-unsignedtosigned.diff b/vdr-2.3.2-unsignedtosigned.diff new file mode 100644 index 0000000..e4acd09 --- /dev/null +++ b/vdr-2.3.2-unsignedtosigned.diff @@ -0,0 +1,102 @@ +--- a/diseqc.c 2015/01/26 12:02:14 4.0 ++++ b/diseqc.c 2017/01/09 15:10:40 +@@ -253,10 +253,10 @@ + return result; + } + +-uint cDiseqc::SetScrFrequency(uint SatFrequency, const cScr *Scr, uint8_t *Codes) const ++int cDiseqc::SetScrFrequency(int SatFrequency, const cScr *Scr, uint8_t *Codes) const + { + if ((Codes[0] & 0xF0) == 0x70 ) { // EN50607 aka JESS +- uint t = SatFrequency == 0 ? 0 : (SatFrequency - 100); ++ int t = SatFrequency == 0 ? 0 : (SatFrequency - 100); + if (t < 2048 && Scr->Channel() >= 0 && Scr->Channel() < 32) { + Codes[1] = t >> 8 | Scr->Channel() << 3; + Codes[2] = t; +@@ -266,7 +266,7 @@ + } + } + else { // EN50494 aka Unicable +- uint t = SatFrequency == 0 ? 0 : (SatFrequency + Scr->UserBand() + 2) / 4 - 350; // '+ 2' together with '/ 4' results in rounding! ++ int t = SatFrequency == 0 ? 0 : (SatFrequency + Scr->UserBand() + 2) / 4 - 350; // '+ 2' together with '/ 4' results in rounding! + if (t < 1024 && Scr->Channel() >= 0 && Scr->Channel() < 8) { + Codes[3] = t >> 8 | (t == 0 ? 0 : scrBank << 2) | Scr->Channel() << 5; + Codes[4] = t; +@@ -399,7 +399,7 @@ + return NULL; + } + +-cDiseqc::eDiseqcActions cDiseqc::Execute(const char **CurrentAction, uchar *Codes, uint8_t *MaxCodes, const cScr *Scr, uint *Frequency) const ++cDiseqc::eDiseqcActions cDiseqc::Execute(const char **CurrentAction, uchar *Codes, uint8_t *MaxCodes, const cScr *Scr, int *Frequency) const + { + if (!*CurrentAction) + *CurrentAction = commands; +--- a/diseqc.h 2013/06/12 11:52:17 4.0 ++++ b/diseqc.h 2017/01/09 15:11:19 +@@ -86,7 +86,7 @@ + mutable int scrBank; + char *commands; + bool parsing; +- uint SetScrFrequency(uint SatFrequency, const cScr *Scr, uint8_t *Codes) const; ++ int SetScrFrequency(int SatFrequency, const cScr *Scr, uint8_t *Codes) const; + int SetScrPin(const cScr *Scr, uint8_t *Codes) const; + const char *Wait(const char *s) const; + const char *GetPosition(const char *s) const; +@@ -96,7 +96,7 @@ + cDiseqc(void); + ~cDiseqc(); + bool Parse(const char *s); +- eDiseqcActions Execute(const char **CurrentAction, uchar *Codes, uint8_t *MaxCodes, const cScr *Scr, uint *Frequency) const; ++ eDiseqcActions Execute(const char **CurrentAction, uchar *Codes, uint8_t *MaxCodes, const cScr *Scr, int *Frequency) const; + ///< Parses the DiSEqC commands and returns the appropriate action code + ///< with every call. CurrentAction must be the address of a character pointer, + ///< which is initialized to NULL. This pointer is used internally while parsing +--- a/dvbdevice.c 2016/11/07 13:55:58 4.3 ++++ b/dvbdevice.c 2017/01/09 15:11:39 +@@ -329,7 +329,7 @@ + void ClearEventQueue(void) const; + bool GetFrontendStatus(fe_status_t &Status) const; + cPositioner *GetPositioner(void); +- void ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency); ++ void ExecuteDiseqc(const cDiseqc *Diseqc, int *Frequency); + void ResetToneAndVoltage(void); + bool SetFrontend(void); + virtual void Action(void); +@@ -696,7 +696,7 @@ + return positioner; + } + +-void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency) ++void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, int *Frequency) + { + if (!lnbPowerTurnedOn) { + CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power +@@ -806,7 +806,7 @@ + + SETCMD(DTV_DELIVERY_SYSTEM, frontendType); + if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) { +- unsigned int frequency = channel.Frequency(); ++ int frequency = channel.Frequency(); + if (Setup.DiSEqC) { + if (const cDiseqc *diseqc = Diseqcs.Get(device->CardIndex() + 1, channel.Source(), frequency, dtp.Polarization(), &scr)) { + frequency -= diseqc->Lof(); +@@ -829,7 +829,7 @@ + } + else { + int tone = SEC_TONE_OFF; +- if (frequency < (unsigned int)Setup.LnbSLOF) { ++ if (frequency < Setup.LnbSLOF) { + frequency -= Setup.LnbFrequLo; + tone = SEC_TONE_OFF; + } +--- a/remux.c.orig 2017-03-02 13:22:31.133182283 +0100 ++++ b/remux.c 2017-03-02 13:23:50.300598025 +0100 +@@ -1638,7 +1638,7 @@ + Div += parser->IFrameTemporalReferenceOffset(); + if (Div <= 0) + Div = 1; +- uint32_t Delta = ptsValues[0] / Div; ++ int Delta = ptsValues[0] / Div; + // determine frame info: + if (isVideo) { + if (abs(Delta - 3600) <= 1) diff --git a/vdr-2.4.0-01-fix-svdrp-modt-recflag.diff b/vdr-2.4.0-01-fix-svdrp-modt-recflag.diff new file mode 100644 index 0000000..cc49bd9 --- /dev/null +++ b/vdr-2.4.0-01-fix-svdrp-modt-recflag.diff @@ -0,0 +1,44 @@ +# This patch fixes a bug in handling the tfRecording flag in the SVDRP commands MODT +# and UPDT. The tfRecording flag must only be handled by the VDR that actually hosts +# and processes the timer. +# +--- svdrp.c 2018/03/19 12:16:33 5.0 ++++ svdrp.c 2018/04/19 09:45:08 +@@ -2036,6 +2036,7 @@ + LOCK_TIMERS_WRITE; + Timers->SetExplicitModify(); + if (cTimer *Timer = Timers->GetById(Id)) { ++ bool IsRecording = Timer->HasFlags(tfRecording); + cTimer t = *Timer; + if (strcasecmp(tail, "ON") == 0) + t.SetFlags(tfActive); +@@ -2046,6 +2047,10 @@ + return; + } + *Timer = t; ++ if (IsRecording) ++ Timer->SetFlags(tfRecording); ++ else ++ Timer->ClrFlags(tfRecording); + Timers->SetModified(); + isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive"); + Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true)); +@@ -2478,12 +2483,18 @@ + if (Timer->Parse(Option)) { + LOCK_TIMERS_WRITE; + if (cTimer *t = Timers->GetTimer(Timer)) { ++ bool IsRecording = t->HasFlags(tfRecording); + t->Parse(Option); + delete Timer; + Timer = t; ++ if (IsRecording) ++ Timer->SetFlags(tfRecording); ++ else ++ Timer->ClrFlags(tfRecording); + isyslog("SVDRP %s < %s updated timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr()); + } + else { ++ Timer->ClrFlags(tfRecording); + Timers->Add(Timer); + isyslog("SVDRP %s < %s added timer %s", Setup.SVDRPHostName, *clientName, *Timer->ToDescr()); + } diff --git a/vdr-2.4.0-mainmenuhooks101.patch b/vdr-2.4.0-mainmenuhooks101.patch new file mode 100644 index 0000000..6680612 --- /dev/null +++ b/vdr-2.4.0-mainmenuhooks101.patch @@ -0,0 +1,108 @@ +--- vdr-2.4.0/config.h.orig 2018-04-16 09:36:30.655036427 +0200 ++++ vdr-2.4.0/config.h 2018-04-16 09:37:43.068036894 +0200 +@@ -47,6 +47,10 @@ + #define TIMERMACRO_TITLE "TITLE" + #define TIMERMACRO_EPISODE "EPISODE" + ++// The MainMenuHook Patch's version number: ++#define MAINMENUHOOKSVERSION "1.0.1" ++#define MAINMENUHOOKSVERSNUM 10001 // Version * 10000 + Major * 100 + Minor ++ + #define MINOSDWIDTH 480 + #define MAXOSDWIDTH 1920 + #define MINOSDHEIGHT 324 +--- vdr-2.4.0/menu.c.orig 2018-04-16 09:39:22.013037531 +0200 ++++ vdr-2.4.0/menu.c 2018-04-16 10:24:50.584055109 +0200 +@@ -4475,16 +4475,31 @@ + Set(); + + // Initial submenus: +- ++ cOsdObject *menu = NULL; + switch (State) { +- case osSchedule: AddSubMenu(new cMenuSchedule); break; +- case osChannels: AddSubMenu(new cMenuChannels); break; +- case osTimers: AddSubMenu(new cMenuTimers); break; +- case osRecordings: AddSubMenu(new cMenuRecordings(NULL, 0, OpenSubMenus)); break; +- case osSetup: AddSubMenu(new cMenuSetup); break; +- case osCommands: AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); break; ++ case osSchedule: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu)) ++ menu = new cMenuSchedule; ++ break; ++ case osChannels: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu)) ++ menu = new cMenuChannels; ++ break; ++ case osTimers: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu)) ++ menu = new cMenuTimers; ++ break; ++ case osRecordings: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu)) ++ menu = new cMenuRecordings(NULL, 0, OpenSubMenus); ++ break; ++ case osSetup: menu = new cMenuSetup; break; ++ case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break; + default: break; + } ++ if (menu) ++ if (menu->IsMenu()) ++ AddSubMenu((cOsdMenu *) menu); + } + + cOsdObject *cMenuMain::PluginOsdObject(void) +@@ -4592,13 +4607,34 @@ + eOSState state = cOsdMenu::ProcessKey(Key); + HadSubMenu |= HasSubMenu(); + ++ cOsdObject *menu = NULL; + switch (state) { +- case osSchedule: return AddSubMenu(new cMenuSchedule); +- case osChannels: return AddSubMenu(new cMenuChannels); +- case osTimers: return AddSubMenu(new cMenuTimers); +- case osRecordings: return AddSubMenu(new cMenuRecordings); +- case osSetup: return AddSubMenu(new cMenuSetup); +- case osCommands: return AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); ++ case osSchedule: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu)) ++ menu = new cMenuSchedule; ++ else ++ state = osContinue; ++ break; ++ case osChannels: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu)) ++ menu = new cMenuChannels; ++ else ++ state = osContinue; ++ break; ++ case osTimers: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu)) ++ menu = new cMenuTimers; ++ else ++ state = osContinue; ++ break; ++ case osRecordings: ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu)) ++ menu = new cMenuRecordings; ++ else ++ state = osContinue; ++ break; ++ case osSetup: menu = new cMenuSetup; break; ++ case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break; + case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) { + if (cOsdItem *item = Get(Current())) { + cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING))); +@@ -4649,6 +4685,12 @@ + default: break; + } + } ++ if (menu) { ++ if (menu->IsMenu()) ++ return AddSubMenu((cOsdMenu *) menu); ++ pluginOsdObject = menu; ++ return osPlugin; ++ } + if (!HasSubMenu() && Update(HadSubMenu)) + Display(); + if (Key != kNone) { diff --git a/vdr-2.4.1-glibc231.patch b/vdr-2.4.1-glibc231.patch new file mode 100644 index 0000000..528d210 --- /dev/null +++ b/vdr-2.4.1-glibc231.patch @@ -0,0 +1,24 @@ +From dcfa8ba29f4c95edbdceb0f1bbae0e62fa40c4e2 Mon Sep 17 00:00:00 2001 +From: Manuel Reimer +Date: Sat, 14 Dec 2019 12:01:13 +0100 +Subject: [PATCH] Replace obsolete stime() function with clock_settime() + +--- + eit.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/eit.c b/eit.c +index 50d8229..82294dc 100644 +--- a/eit.c ++++ b/eit.c +@@ -391,7 +391,9 @@ cTDT::cTDT(const u_char *Data) + if (abs(diff) > MAX_TIME_DIFF) { + mutex.Lock(); + if (abs(diff) > MAX_ADJ_DIFF) { +- if (stime(&dvbtim) == 0) ++ timespec ts = {}; ++ ts.tv_sec = dvbtim; ++ if (clock_settime(CLOCK_REALTIME, &ts) == 0) + isyslog("system time changed from %s (%ld) to %s (%ld)", *TimeToString(loctim), loctim, *TimeToString(dvbtim), dvbtim); + else + esyslog("ERROR while setting system time: %m"); diff --git a/vdr-2.4.1-mark-obsolete-NidTid.patch b/vdr-2.4.1-mark-obsolete-NidTid.patch new file mode 100644 index 0000000..12b51b6 --- /dev/null +++ b/vdr-2.4.1-mark-obsolete-NidTid.patch @@ -0,0 +1,115 @@ +diff -Nurp vdr-2.4.1.orig/channels.c vdr-2.4.1.NitTid/channels.c +--- vdr-2.4.1.orig/channels.c 2017-06-10 17:08:56.000000000 +0200 ++++ vdr-2.4.1.NitTid/channels.c 2019-09-24 20:48:49.243342312 +0200 +@@ -1096,11 +1096,13 @@ cChannel *cChannels::NewChannel(const cC + #define CHANNELMARKOBSOLETE "OBSOLETE" + #define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before) + +-bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid) ++bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid, int Transponder) + { + bool ChannelsModified = false; + for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { + if (time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Source() == Source && Channel->Nid() == Nid && Channel->Tid() == Tid && Channel->Rid() == 0) { ++ if (Transponder && !ISTRANSPONDER(Channel->Transponder(), Transponder)) // mark channels only on specified transponder ++ continue; + int OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource; + Setup.ShowChannelNamesWithSource = 0; + if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE)) +diff -Nurp vdr-2.4.1.orig/channels.h vdr-2.4.1.NitTid/channels.h +--- vdr-2.4.1.orig/channels.h 2017-06-10 17:06:40.000000000 +0200 ++++ vdr-2.4.1.NitTid/channels.h 2019-09-23 22:46:20.231197911 +0200 +@@ -251,7 +251,7 @@ public: + ///< and will be set to the current value of the list's internal state variable upon + ///< return from this function. + cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0); +- bool MarkObsoleteChannels(int Source, int Nid, int Tid); ++ bool MarkObsoleteChannels(int Source, int Nid, int Tid, int Transponder = 0); + }; + + // Provide lock controlled access to the list: +diff -Nurp vdr-2.4.1.orig/eitscan.c vdr-2.4.1.NitTid/eitscan.c +--- vdr-2.4.1.orig/eitscan.c 2019-03-12 12:54:27.000000000 +0100 ++++ vdr-2.4.1.NitTid/eitscan.c 2019-09-23 23:31:26.778124945 +0200 +@@ -60,8 +60,11 @@ void cScanList::AddTransponder(const cCh + { + if (Channel->Source() && Channel->Transponder()) { + for (cScanData *sd = First(); sd; sd = Next(sd)) { +- if (sd->Source() == Channel->Source() && ISTRANSPONDER(sd->Transponder(), Channel->Transponder())) +- return; ++ if (sd->Source() == Channel->Source() && ISTRANSPONDER(sd->Transponder(), Channel->Transponder())) { ++ const cChannel *ch = sd->GetChannel(); ++ if (ch->Nid() == Channel->Nid() && ch->Tid() == Channel->Tid()) // add a channel for each unique transponder/Nid/Tid triple ++ return; ++ } + } + Add(new cScanData(Channel)); + } +@@ -78,8 +81,10 @@ void cTransponderList::AddTransponder(cC + { + for (cChannel *ch = First(); ch; ch = Next(ch)) { + if (ch->Source() == Channel->Source() && ch->Transponder() == Channel->Transponder()) { +- delete Channel; +- return; ++ if (ch->Nid() == Channel->Nid() && ch->Tid() == Channel->Tid()) { // add a channel for each unique transponder/Nid/Tid triple ++ delete Channel; ++ return; ++ } + } + } + Add(Channel); +diff -Nurp vdr-2.4.1.orig/nit.c vdr-2.4.1.NitTid/nit.c +--- vdr-2.4.1.orig/nit.c 2019-05-31 23:47:02.000000000 +0200 ++++ vdr-2.4.1.NitTid/nit.c 2019-09-25 00:09:04.140501158 +0200 +@@ -107,6 +107,7 @@ void cNitFilter::Process(u_short Pid, u_ + } + + for (SI::Loop::Iterator it2; (d = ts.transportStreamDescriptors.getNext(it2)); ) { ++ int tsTransponder = 0; + switch (d->getDescriptorTag()) { + case SI::SatelliteDeliverySystemDescriptorTag: { + SI::SatelliteDeliverySystemDescriptor *sd = (SI::SatelliteDeliverySystemDescriptor *)d; +@@ -125,6 +126,7 @@ void cNitFilter::Process(u_short Pid, u_ + dtp.SetRollOff(System ? RollOffs[sd->getRollOff()] : ROLLOFF_AUTO); + int SymbolRate = BCD2INT(sd->getSymbolRate()) / 10; + dbgnit(" %s %d %c %d %d DVB-S%d\n", *cSource::ToString(Source), Frequency, dtp.Polarization(), SymbolRate, cChannel::Transponder(Frequency, dtp.Polarization()), System ? 2 : 1); ++ tsTransponder = cChannel::Transponder(Frequency, dtp.Polarization()); + if (Setup.UpdateChannels >= 5) { + bool found = false; + bool forceTransponderUpdate = false; +@@ -192,6 +194,7 @@ void cNitFilter::Process(u_short Pid, u_ + dtp.SetModulation(Modulations[min(sd->getModulation(), 6)]); + int SymbolRate = BCD2INT(sd->getSymbolRate()) / 10; + dbgnit(" %s %d %d %d %d\n", *cSource::ToString(Source), Frequency, dtp.CoderateH(), dtp.Modulation(), SymbolRate); ++ tsTransponder = Frequency / 1000; + if (Setup.UpdateChannels >= 5) { + bool found = false; + bool forceTransponderUpdate = false; +@@ -248,6 +251,7 @@ void cNitFilter::Process(u_short Pid, u_ + static int TransmissionModes[] = { TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K, TRANSMISSION_MODE_4K, TRANSMISSION_MODE_AUTO }; + dtp.SetTransmission(TransmissionModes[sd->getTransmissionMode()]); + dbgnit(" %s %d %d %d %d %d %d %d %d\n", *cSource::ToString(Source), Frequency, dtp.Bandwidth(), dtp.Modulation(), dtp.Hierarchy(), dtp.CoderateH(), dtp.CoderateL(), dtp.Guard(), dtp.Transmission()); ++ tsTransponder = Frequency / 1000000; + if (Setup.UpdateChannels >= 5) { + bool found = false; + bool forceTransponderUpdate = false; +@@ -298,6 +302,7 @@ void cNitFilter::Process(u_short Pid, u_ + SI::ExtensionDescriptor *sd = (SI::ExtensionDescriptor *)d; + switch (sd->getExtensionDescriptorTag()) { + case SI::T2DeliverySystemDescriptorTag: { ++ tsTransponder = Transponder(); + if (Setup.UpdateChannels >= 5) { + for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) { + int Source = cSource::FromData(cSource::stTerr); +@@ -368,6 +373,11 @@ void cNitFilter::Process(u_short Pid, u_ + break; + default: ; + } ++ if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) { ++ // mark all channels with obsolete Nid/Tid on tuned transponder ++ if (tsTransponder && ISTRANSPONDER(tsTransponder, Transponder()) && (Channel()->Nid() != ts.getOriginalNetworkId() || Channel()->Tid() != ts.getTransportStreamId())) ++ ChannelsModified |= Channels->MarkObsoleteChannels(Source(), Channel()->Nid(), Channel()->Tid(), Transponder()); ++ } + delete d; + } + } diff --git a/vdr-2.4.1-skincurses-log-errors.patch b/vdr-2.4.1-skincurses-log-errors.patch new file mode 100644 index 0000000..593be22 --- /dev/null +++ b/vdr-2.4.1-skincurses-log-errors.patch @@ -0,0 +1,22 @@ + +Description: Log an error message when skincures can't initialize the screen +Author: Tobias Grimm + +--- a/PLUGINS/src/skincurses/skincurses.c.orig 2019-06-17 18:31:59.351079418 +0200 ++++ b/PLUGINS/src/skincurses/skincurses.c 2019-06-17 18:35:21.586034302 +0200 +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + + static const char *VERSION = "2.4.1"; + static const char *DESCRIPTION = trNOOP("A text only skin"); +@@ -841,6 +842,7 @@ + ScOsdHeight = maxy - begy + 1; + return true; + } ++ esyslog("skincurses: unable to initialize curses screen"); + return false; + } + diff --git a/vdr-2.4.4-RecordingInfo.patch b/vdr-2.4.4-RecordingInfo.patch new file mode 100644 index 0000000..6a613f1 --- /dev/null +++ b/vdr-2.4.4-RecordingInfo.patch @@ -0,0 +1,62 @@ +diff --git a/recording.h b/recording.h +index 6f26f2f6..718df343 100644 +--- a/recording.h ++++ b/recording.h +@@ -41,6 +41,11 @@ enum eRecordingUsage { + ruCanceled = 0x8000, // the operation has been canceled, waiting for cleanup + }; + ++// workaround patch for enabling extra features in extrecmenung until vdr 2.5.2 is released ++#ifndef EPGRENAME ++#define EPGRENAME ++#endif ++ + void RemoveDeletedRecordings(void); + void AssertFreeDiskSpace(int Priority = 0, bool Force = false); + ///< The special Priority value -1 means that we shall get rid of any +@@ -74,8 +79,6 @@ private: + char *fileName; + cRecordingInfo(const cChannel *Channel = NULL, const cEvent *Event = NULL); + bool Read(FILE *f); +- void SetData(const char *Title, const char *ShortText, const char *Description); +- void SetAux(const char *Aux); + public: + cRecordingInfo(const char *FileName); + ~cRecordingInfo(); +@@ -93,6 +96,8 @@ public: + bool Write(FILE *f, const char *Prefix = "") const; + bool Read(void); + bool Write(void) const; ++ void SetData(const char *Title, const char *ShortText, const char *Description); ++ void SetAux(const char *Aux); + }; + + class cRecording : public cListObject { +@@ -150,7 +155,7 @@ public: + ///< Returns the full path name to the recording directory, including the + ///< video directory and the actual '*.rec'. For disk file access use. + const char *Title(char Delimiter = ' ', bool NewIndicator = false, int Level = -1) const; +- const cRecordingInfo *Info(void) const { return info; } ++ cRecordingInfo *Info(void) const { return info; } + const char *PrefixFileName(char Prefix); + int HierarchyLevels(void) const; + void ResetResume(void) const; +diff --git a/recording.c b/recording.c +index 17467e36..a7f08a26 100644 +--- a/recording.c ++++ b/recording.c +@@ -430,11 +430,11 @@ cRecordingInfo::~cRecordingInfo() + + void cRecordingInfo::SetData(const char *Title, const char *ShortText, const char *Description) + { +- if (!isempty(Title)) ++ if (Title) + ((cEvent *)event)->SetTitle(Title); +- if (!isempty(ShortText)) ++ if (ShortText) + ((cEvent *)event)->SetShortText(ShortText); +- if (!isempty(Description)) ++ if (Description) + ((cEvent *)event)->SetDescription(Description); + } + diff --git a/vdr-2.4.6-ClearObsoleteChannels.diff b/vdr-2.4.6-ClearObsoleteChannels.diff new file mode 100644 index 0000000..fdde1a6 --- /dev/null +++ b/vdr-2.4.6-ClearObsoleteChannels.diff @@ -0,0 +1,78 @@ +diff -Nurp vdr-2.4.6.orig/channels.c vdr-2.4.6/channels.c +--- vdr-2.4.6.orig/channels.c 2020-04-11 11:22:05.000000000 +0200 ++++ vdr-2.4.6/channels.c 2021-01-02 14:56:49.883831519 +0100 +@@ -16,6 +16,8 @@ + // format characters in order to allow any number of blanks after a numeric + // value! + ++#define CHANNELMARKOBSOLETE "OBSOLETE" ++ + // --- tChannelID ------------------------------------------------------------ + + const tChannelID tChannelID::InvalidID; +@@ -434,6 +436,24 @@ void cChannel::SetSeen(void) + seen = time(NULL); + } + ++bool cChannel::ClearObsoleteChannel(void) ++{ ++ bool ChannelsModified = false; ++ if (endswith(name, CHANNELMARKOBSOLETE)) { ++ int mlen = strlen(CHANNELMARKOBSOLETE); ++ int e = strlen(name) - mlen - 1; ++ name[e] = '\0'; ++ cString clrname = cString::sprintf("%s", name); ++ name[e] = ' '; ++ ++ int OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource; ++ Setup.ShowChannelNamesWithSource = 0; ++ ChannelsModified |= SetName(clrname, shortName, provider + mlen + 1); ++ Setup.ShowChannelNamesWithSource = OldShowChannelNamesWithSource; ++ } ++ return ChannelsModified; ++} ++ + void cChannel::DelLinkChannel(cChannel *LinkChannel) + { + if (linkChannels) { +@@ -1112,7 +1132,6 @@ cChannel *cChannels::NewChannel(const cC + return NULL; + } + +-#define CHANNELMARKOBSOLETE "OBSOLETE" + #define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before) + + bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid) +diff -Nurp vdr-2.4.6.orig/channels.h vdr-2.4.6/channels.h +--- vdr-2.4.6.orig/channels.h 2020-06-10 16:00:36.000000000 +0200 ++++ vdr-2.4.6/channels.h 2021-01-02 12:58:13.693581025 +0100 +@@ -202,6 +202,7 @@ public: + void SetRefChannel(cChannel *RefChannel); + bool SetSubtitlingDescriptors(uchar *SubtitlingTypes, uint16_t *CompositionPageIds, uint16_t *AncillaryPageIds); + void SetSeen(void); ++ bool ClearObsoleteChannel(void); + void DelLinkChannel(cChannel *LinkChannel); + }; + +diff -Nurp vdr-2.4.6.orig/pat.c vdr-2.4.6/pat.c +--- vdr-2.4.6.orig/pat.c 2020-12-18 15:51:57.000000000 +0100 ++++ vdr-2.4.6/pat.c 2021-01-02 12:56:25.480243876 +0100 +@@ -489,6 +489,9 @@ void cPatFilter::Process(u_short Pid, u_ + SwitchToNextPmtPid(); + cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), pmt.getServiceId()); + if (Channel) { ++ bool seen = Channel->Seen(); ++ if (!seen) // not yet seen in sdt.c ++ Channel->SetSeen(); + SI::CaDescriptor *d; + cCaDescriptors *CaDescriptors = new cCaDescriptors(Channel->Source(), Channel->Transponder(), Channel->Sid(), Pid); + // Scan the common loop: +@@ -723,6 +726,8 @@ void cPatFilter::Process(u_short Pid, u_ + ChannelsModified |= Channel->SetCaIds(CaDescriptors->CaIds()); + ChannelsModified |= Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds); + } ++ if (!seen && (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3)) ++ ChannelsModified |= Channel->ClearObsoleteChannel(); // just in case + ChannelsModified |= Channel->SetCaDescriptors(CaDescriptorHandler.AddCaDescriptors(CaDescriptors)); + } + StateKey.Remove(ChannelsModified); diff --git a/vdr-2.4.6-MarkObsoleteChannels2.diff b/vdr-2.4.6-MarkObsoleteChannels2.diff new file mode 100644 index 0000000..a0c4185 --- /dev/null +++ b/vdr-2.4.6-MarkObsoleteChannels2.diff @@ -0,0 +1,62 @@ +diff -Nurp vdr-2.4.6.orig/channels.c vdr-2.4.6/channels.c +--- vdr-2.4.6.orig/channels.c 2020-04-11 11:22:05.000000000 +0200 ++++ vdr-2.4.6/channels.c 2021-01-01 12:35:10.946734352 +0100 +@@ -1115,11 +1116,20 @@ cChannel *cChannels::NewChannel(const cC + #define CHANNELMARKOBSOLETE "OBSOLETE" + #define CHANNELTIMEOBSOLETE 3600 // seconds to wait before declaring a channel obsolete (in case it has actually been seen before) + +-bool cChannels::MarkObsoleteChannels(int Source, int Nid, int Tid) ++bool cChannels::MarkObsoleteChannels(int Source, int Transponder, int StreamId, int Nid, int Tid) + { + bool ChannelsModified = false; + for (cChannel *Channel = First(); Channel; Channel = Next(Channel)) { +- if (time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Source() == Source && Channel->Nid() == Nid && Channel->Tid() == Tid && Channel->Rid() == 0) { ++ if (Channel->Source() != Source) ++ continue; ++ bool obsolete = false; ++ if (Channel->Nid() == Nid && Channel->Tid() == Tid) ++ obsolete = time(NULL) - Channel->Seen() > CHANNELTIMEOBSOLETE && Channel->Rid() == 0; // obsolete Sid ++ else if (ISTRANSPONDER(Channel->Transponder(), Transponder)) { ++ cDvbTransponderParameters dtp(Channel->Parameters()); ++ obsolete = dtp.StreamId() == StreamId; // obsolete Nid/Tid ++ } ++ if (obsolete) { + int OldShowChannelNamesWithSource = Setup.ShowChannelNamesWithSource; + Setup.ShowChannelNamesWithSource = 0; + if (!endswith(Channel->Name(), CHANNELMARKOBSOLETE)) +diff -Nurp vdr-2.4.6.orig/channels.h vdr-2.4.6/channels.h +--- vdr-2.4.6.orig/channels.h 2020-06-10 16:00:36.000000000 +0200 ++++ vdr-2.4.6/channels.h 2020-12-31 18:35:07.050191444 +0100 +@@ -252,7 +252,7 @@ public: + ///< and will be set to the current value of the list's internal state variable upon + ///< return from this function. + cChannel *NewChannel(const cChannel *Transponder, const char *Name, const char *ShortName, const char *Provider, int Nid, int Tid, int Sid, int Rid = 0); +- bool MarkObsoleteChannels(int Source, int Nid, int Tid); ++ bool MarkObsoleteChannels(int Source, int Transponder, int Streamid, int Nid, int Tid); + }; + + // Provide lock controlled access to the list: +diff -Nurp vdr-2.4.6.orig/sdt.c vdr-2.4.6/sdt.c +--- vdr-2.4.6.orig/sdt.c 2020-06-16 16:50:07.000000000 +0200 ++++ vdr-2.4.6/sdt.c 2020-12-31 18:40:31.853536210 +0100 +@@ -12,6 +12,7 @@ + #include "config.h" + #include "libsi/section.h" + #include "libsi/descriptor.h" ++#include "dvbdevice.h" + + // Set to 'true' for debug output: + static bool DebugSdt = false; +@@ -205,9 +206,10 @@ void cSdtFilter::Process(u_short Pid, u_ + } + if (sdt.getSectionNumber() == sdt.getLastSectionNumber()) { + if (Setup.UpdateChannels == 1 || Setup.UpdateChannels >= 3) { +- ChannelsModified |= Channels->MarkObsoleteChannels(source, sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); ++ cDvbTransponderParameters dtp(Channel()->Parameters()); ++ ChannelsModified |= Channels->MarkObsoleteChannels(source, Transponder(), dtp.StreamId(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); + if (source != Source()) +- ChannelsModified |= Channels->MarkObsoleteChannels(Source(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); ++ ChannelsModified |= Channels->MarkObsoleteChannels(Source(), Transponder(), dtp.StreamId(), sdt.getOriginalNetworkId(), sdt.getTransportStreamId()); + } + } + StateKey.Remove(ChannelsModified); diff --git a/vdr-2.4.7_gcc11.patch b/vdr-2.4.7_gcc11.patch new file mode 100644 index 0000000..1e37b2f --- /dev/null +++ b/vdr-2.4.7_gcc11.patch @@ -0,0 +1,26 @@ +Fix compile with gcc-11, officially from kls + +Signed-off-by: Martin Dummer + +--- a/tools.h 2021/01/19 20:38:28 5.3 ++++ b/tools.h 2021/05/05 15:16:45 +@@ -53,17 +53,15 @@ + + // In case some plugin needs to use the STL and gets an error message regarding one + // of these functions, you can #define DISABLE_TEMPLATES_COLLIDING_WITH_STL before +-// including tools.h. +-#if !defined(__STL_CONFIG_H) // for old versions of the STL +-#if !defined(DISABLE_TEMPLATES_COLLIDING_WITH_STL) && !defined(_STL_ALGOBASE_H) ++// including any VDR header files. ++#if !defined(DISABLE_TEMPLATES_COLLIDING_WITH_STL) + template inline T min(T a, T b) { return a <= b ? a : b; } + template inline T max(T a, T b) { return a >= b ? a : b; } + #endif + template inline int sgn(T a) { return a < 0 ? -1 : a > 0 ? 1 : 0; } +-#if !defined(DISABLE_TEMPLATES_COLLIDING_WITH_STL) && !defined(_MOVE_H) ++#if !defined(DISABLE_TEMPLATES_COLLIDING_WITH_STL) + template inline void swap(T &a, T &b) { T t = a; a = b; b = t; } + #endif +-#endif + + template inline T constrain(T v, T l, T h) { return v < l ? l : v > h ? h : v; } diff --git a/vdr-2.6.0-eit.patch b/vdr-2.6.0-eit.patch new file mode 100644 index 0000000..63b581d Binary files /dev/null and b/vdr-2.6.0-eit.patch differ diff --git a/vdr-2.6.0-fix-dvbplayer.diff b/vdr-2.6.0-fix-dvbplayer.diff new file mode 100644 index 0000000..38ae3d9 --- /dev/null +++ b/vdr-2.6.0-fix-dvbplayer.diff @@ -0,0 +1,11 @@ +--- a/dvbplayer.c 2019/05/27 13:54:19 5.0 ++++ b/dvbplayer.c 2022/01/13 10:18:27 +@@ -914,7 +914,7 @@ + ptsIndex.Put(isPesRecording ? PesGetPts(b) : TsGetPts(b, r), Index, true); + } + playMode = pmStill; +- readIndex = Index; ++ readIndex = Index - 1; // makes sure a later play starts with this I-frame + } + } + else { diff --git a/vdr-2.6.2-index-file-regeneration-failed.patch b/vdr-2.6.2-index-file-regeneration-failed.patch new file mode 100644 index 0000000..61f140f --- /dev/null +++ b/vdr-2.6.2-index-file-regeneration-failed.patch @@ -0,0 +1,11 @@ +--- a/recording.c 2022/11/28 14:39:23 5.19 ++++ b/recording.c 2022/12/01 12:39:42 +@@ -2579,7 +2579,7 @@ + cCondWait::SleepMs(INDEXFILETESTINTERVAL); + } + int delta = 0; +- if (!Record && (access(fileName, R_OK) != 0 || FileSize(fileName) == 0)) { ++ if (!Record && (access(fileName, R_OK) != 0 || FileSize(fileName) == 0 && time(NULL) - LastModifiedTime(fileName) > MAXWAITFORINDEXFILE)) { + // Index file doesn't exist, so try to regenerate it: + if (!isPesRecording) { // sorry, can only do this for TS recordings + resumeFile.Delete(); // just in case diff --git a/vdr-2.6.2-remux.patch b/vdr-2.6.2-remux.patch new file mode 100644 index 0000000..bf43afb --- /dev/null +++ b/vdr-2.6.2-remux.patch @@ -0,0 +1,31 @@ +diff --git a/remux.c b/remux.c +index 391fd6ff..54fc50fd 100644 +--- a/remux.c ++++ b/remux.c +@@ -1333,7 +1333,7 @@ int cMpeg2Parser::Parse(const uchar *Data, int Length, int Pid) + seenScanType = true; + if (debug) { + cString s = cString::sprintf("MPEG2: %d x %d%c %.2f fps", frameWidth, frameHeight, progressive ? 'p' : 'i', framesPerSecond); +- dsyslog(s); ++ dsyslog("%s",*s); + dbgframes("\n%s", *s); + } + } +@@ -1607,7 +1607,7 @@ void cH264Parser::ParseSequenceParameterSet(void) + } + if (debug) { + cString s = cString::sprintf("H.264: %d x %d%c %.2f fps %d Bit", frameWidth, frameHeight, progressive ? 'p':'i', framesPerSecond, bitDepth); +- dsyslog(s); ++ dsyslog("%s", *s); + dbgframes("\n%s", *s); + } + } +@@ -1908,7 +1908,7 @@ void cH265Parser::ParseSequenceParameterSet(void) + } + if (debug) { + cString s = cString::sprintf("H.265: %d x %d%c %.2f fps %d Bit", frameWidth, frameHeight, progressive ? 'p':'i', framesPerSecond, bitDepth); +- dsyslog(s); ++ dsyslog("%s", *s); + dbgframes("\n%s", *s); + } + } diff --git a/vdr-2.6.2-timer-still-recording.patch b/vdr-2.6.2-timer-still-recording.patch new file mode 100644 index 0000000..2be2cfb --- /dev/null +++ b/vdr-2.6.2-timer-still-recording.patch @@ -0,0 +1,11 @@ +--- a/menu.c 2022/11/22 15:53:07 5.8 ++++ b/menu.c 2022/12/01 12:55:34 +@@ -3250,7 +3250,7 @@ + int Id; + char *RemoteBuf = NULL; + cString Remote; +- if (2 == sscanf(TimerId, "%d@%m[^ \n]", &Id, &RemoteBuf)) { ++ if (2 == sscanf(TimerId, "%d@%m[^ \n]", &Id, &RemoteBuf) && Id != 0) { + Remote = RemoteBuf; + free(RemoteBuf); + if (Interface->Confirm(tr("Timer still recording - really delete?"))) { diff --git a/vdr-2.7.2-timers.patch b/vdr-2.7.2-timers.patch deleted file mode 100644 index 56669da..0000000 --- a/vdr-2.7.2-timers.patch +++ /dev/null @@ -1,44 +0,0 @@ ---- timers.c 2024/03/06 14:37:15 5.20 -+++ timers.c 2024/10/10 09:23:34 -@@ -726,10 +726,39 @@ - bool cTimer::Expired(void) const - { - if (IsSingleEvent() && !Recording()) { -+ time_t Now = time(NULL); - time_t ExpireTime = StopTimeEvent(); -- if (HasFlags(tfVps)) -+ if (HasFlags(tfVps)) { - ExpireTime += EXPIRELATENCY; -- return ExpireTime <= time(NULL); -+ if (ExpireTime <= Now) { -+ LOCK_SCHEDULES_READ; -+ const cSchedule *Schedule = event ? event->Schedule() : NULL; -+ const cEvent *FirstEvent = event; -+ if (Schedule) -+ FirstEvent = Schedule->Events()->Next(FirstEvent); -+ else if ((Schedule = Schedules->GetSchedule(Channel())) != NULL) { -+ FirstEvent = Schedule->Events()->First(); -+ if (FirstEvent) -+ dsyslog("timer %s had no event, got %s from channel/schedule", *ToDescr(), *FirstEvent->ToDescr()); -+ } -+ if (FirstEvent) { -+ if (Schedule) { -+ for (const cEvent *e = FirstEvent; e; e = Schedule->Events()->Next(e)) { -+ if (e->Vps() == startTime) { -+ ExpireTime = e->EndTime() + EXPIRELATENCY; -+ dsyslog("timer %s is waiting for next VPS event %s", *ToDescr(), *e->ToDescr()); -+ break; -+ } -+ } -+ } -+ } -+ else { -+ dsyslog("timer %s has no event, setting expiration to +24h", *ToDescr()); -+ ExpireTime += 3600 * 24; -+ } -+ } -+ } -+ return ExpireTime <= Now; - } - return false; - } diff --git a/vdr-channel+epg.patch b/vdr-channel+epg.patch new file mode 100644 index 0000000..9315b1c --- /dev/null +++ b/vdr-channel+epg.patch @@ -0,0 +1,26 @@ +diff -up vdr-1.6.0/menu.c~ vdr-1.6.0/menu.c +--- vdr-1.6.0/menu.c~ 2008-03-16 13:15:28.000000000 +0200 ++++ vdr-1.6.0/menu.c 2011-02-15 00:39:42.267224859 +0200 +@@ -399,11 +399,20 @@ int cMenuChannelItem::Compare(const cLis + void cMenuChannelItem::Set(void) + { + cString buffer; ++ const cEvent *Event = NULL; + if (!channel->GroupSep()) { ++ cSchedulesLock SchedulesLock; ++ const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); ++ const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID()); ++ if (Schedule) ++ Event = Schedule->GetPresentEvent(); ++ + if (sortMode == csmProvider) +- buffer = cString::sprintf("%d\t%s - %s", channel->Number(), channel->Provider(), channel->Name()); ++ buffer = cString::sprintf("%d\t%s - %s %c%s%c", channel->Number(), channel->Provider(), channel->Name(), ++ Event ? '(' : ' ', Event ? Event->Title() : "", Event ? ')' : ' '); + else +- buffer = cString::sprintf("%d\t%s", channel->Number(), channel->Name()); ++ buffer = cString::sprintf("%d\t%s %c%s%c", channel->Number(), channel->Name(), ++ Event ? '(' : ' ', Event ? Event->Title() : "", Event ? ')' : ' '); + } + else + buffer = cString::sprintf("---\t%s ----------------------------------------------------------------", channel->Name()); diff --git a/vdr-gcc11.patch b/vdr-gcc11.patch new file mode 100644 index 0000000..0ac2808 --- /dev/null +++ b/vdr-gcc11.patch @@ -0,0 +1,21 @@ +diff --git a/recording.c b/recording.c +index e493b57..e50ef7d 100644 +--- a/recording.c ++++ b/recording.c +@@ -3025,7 +3025,15 @@ cUnbufferedFile *cFileName::SetOffset(int Number, off_t Offset) + } + // found a non existing file suffix + } +- if (Open() >= 0) { ++ /* This used to test Open() >= 0, but Open() returns a pointer ++ and we shouldn't be doing ordered comparisons of pointers ++ against constants. Furthermore, on some systems pointers ++ are unsigned meaning this test always succeeded. ++ ++ Finally, AFAICT Open() is always going to return NULL or a ++ valid pointer and can never return a negative value based on my ++ reading of these sources. */ ++ if (1) { + if (!record && Offset >= 0 && file && file->Seek(Offset, SEEK_SET) != Offset) { + LOG_ERROR_STR(fileName); + return NULL; diff --git a/vdr-gcc7-fix.patch b/vdr-gcc7-fix.patch new file mode 100644 index 0000000..faaa88a --- /dev/null +++ b/vdr-gcc7-fix.patch @@ -0,0 +1,11 @@ +--- a/osdbase.c.orig 2017-02-15 15:55:34.555128665 +0100 ++++ b/osdbase.c 2017-02-15 15:56:30.898068614 +0100 +@@ -510,7 +510,7 @@ + const char *s = item->Text(); + i = 0; + item_nr = 0; +- if (s && (s = skipspace(s)) != '\0' && '0' <= s[i] && s[i] <= '9') { ++ if (s && (s = skipspace(s)) != NULL && '0' <= s[i] && s[i] <= '9') { + do { + item_nr = item_nr * 10 + (s[i] - '0'); + } diff --git a/vdr-timer-info-0.5-1.7.13.diff b/vdr-timer-info-0.5-1.7.13.diff new file mode 100644 index 0000000..a0e8adb --- /dev/null +++ b/vdr-timer-info-0.5-1.7.13.diff @@ -0,0 +1,306 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## opt-41-x_timer-info.dpatch by Andreas Brugger , Thomas Günther +## http://toms-cafe.de/vdr/download/vdr-timer-info-0.5-1.7.13.diff +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Shows info, if it is possible to record an event in the timer-info of +## DP: vdr - see README.timer-info for details. + +@DPATCH@ +diff -Naurp vdr-1.7.13/README.timer-info vdr-1.7.13-timer-info-0.5/README.timer-info +--- vdr-1.7.13/README.timer-info 1970-01-01 00:00:00.000000000 +0000 ++++ vdr-1.7.13-timer-info-0.5/README.timer-info 2010-02-28 18:26:31.000000000 +0000 +@@ -0,0 +1,69 @@ +++------------------------------------------------------------------------------+ ++| Info about the timer-info-patch by Brougs78 | ++| brougs78@gmx.net / home.pages.at/brougs78 | +++------------------------------------------------------------------------------+ ++ ++ ++README timer-info: ++------------------ ++ ++Features: ++ - Shows info, if it is possible to record an event in the timer menu of vdr. ++ For calculations the free space incl. the deleted recordings is used, ++ considering an average consumtion of 25.75 MB/min (also used by vdr itself). ++ The first column in the timer-list shows: ++ ( + ) recording will be most probably possible (enough space) ++ (+/-) recording may be possible ++ ( - ) recording will most probably fail (to less space) ++ The calculations also consider repeating timers. ++ - It is possible to deactivate the patch in the OSD-menu of VDR. ++ ++ ++HISTORY timer-info: ++------------------- ++ ++25.11.2004: v0.1 ++ - Initial release ++ ++11.01.2005: v0.1b ++ - Bugfixes for vdr-1.3.18 ++ - In the menu the free recording-time no longer includes the space of the ++ deleted recordings, because this slowed the vdr down to much. ++ ++08.07.2005: v0.1c ++ - Made the patch configurable ++ ++29.01.2006: v0.2 - Thomas Günther ++ - Rewritten great parts for vdr-1.3.38+ ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.2-1.3.38+.diff ++ ++05.02.2006: v0.3 - Thomas Günther ++ - Fixed refresh of timer menu in cMenuTimers::OnOff ++ - Fixed check of repeating timers ++ - Syslog debug messages can be enabled with Define DEBUG_TIMER_INFO ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.3-1.3.38+.diff ++ ++03.03.2006: v0.4 - Thomas Günther ++ - Adapted to vdr-1.3.44 ++ - Removed setup parameter "Show timer-info" ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.4-1.3.44.diff ++ ++26.03.2006: - Tobias Grimm ++ - Adapted to vdr-1.3.45 ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.4-1.3.45.diff ++ ++14.01.2008: - Thomas Günther ++ - Adapted to vdr-1.5.13 ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.4-1.5.13.diff ++ ++17.02.2008: - Tobias Grimm ++ - Adapted to vdr-1.5.15 ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.4-1.5.15.diff ++ ++12.04.2008: v0.5 - Thomas Günther ++ - Fixed display of +/- sign with UTF-8 ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.5-1.5.15.diff ++ ++28.02.2010: - Thomas Günther ++ - Adapted to vdr-1.7.13 ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.5-1.7.13.diff +diff -Naurp vdr-1.7.13/menu.c vdr-1.7.13-timer-info-0.5/menu.c +--- vdr-1.7.13/menu.c 2010-02-21 14:09:19.000000000 +0000 ++++ vdr-1.7.13-timer-info-0.5/menu.c 2010-02-28 18:24:26.000000000 +0000 +@@ -1010,8 +1010,10 @@ eOSState cMenuEditTimer::ProcessKey(eKey + class cMenuTimerItem : public cOsdItem { + private: + cTimer *timer; ++ char diskStatus; + public: + cMenuTimerItem(cTimer *Timer); ++ void SetDiskStatus(char DiskStatus); + virtual int Compare(const cListObject &ListObject) const; + virtual void Set(void); + cTimer *Timer(void) { return timer; } +@@ -1020,6 +1022,7 @@ public: + cMenuTimerItem::cMenuTimerItem(cTimer *Timer) + { + timer = Timer; ++ diskStatus = ' '; + Set(); + } + +@@ -1050,7 +1053,10 @@ void cMenuTimerItem::Set(void) + File++; + else + File = timer->File(); +- SetText(cString::sprintf("%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s", ++ cCharSetConv csc("ISO-8859-1", cCharSetConv::SystemCharacterTable()); ++ char diskStatusString[2] = { diskStatus, 0 }; ++ SetText(cString::sprintf("%s%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s", ++ csc.Convert(diskStatusString), + !(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>', + timer->Channel()->Number(), + *name, +@@ -1063,6 +1069,57 @@ void cMenuTimerItem::Set(void) + File)); + } + ++void cMenuTimerItem::SetDiskStatus(char DiskStatus) ++{ ++ diskStatus = DiskStatus; ++ Set(); ++} ++ ++// --- cTimerEntry ----------------------------------------------------------- ++ ++class cTimerEntry : public cListObject { ++private: ++ cMenuTimerItem *item; ++ const cTimer *timer; ++ time_t start; ++public: ++ cTimerEntry(cMenuTimerItem *item) : item(item), timer(item->Timer()), start(timer->StartTime()) {} ++ cTimerEntry(const cTimer *timer, time_t start) : item(NULL), timer(timer), start(start) {} ++ virtual int Compare(const cListObject &ListObject) const; ++ bool active(void) const { return timer->HasFlags(tfActive); } ++ time_t startTime(void) const { return start; } ++ int priority(void) const { return timer->Priority(); } ++ int duration(void) const; ++ bool repTimer(void) const { return !timer->IsSingleEvent(); } ++ bool isDummy(void) const { return item == NULL; } ++ const cTimer *Timer(void) const { return timer; } ++ void SetDiskStatus(char DiskStatus); ++ }; ++ ++int cTimerEntry::Compare(const cListObject &ListObject) const ++{ ++ cTimerEntry *entry = (cTimerEntry *)&ListObject; ++ int r = startTime() - entry->startTime(); ++ if (r == 0) ++ r = entry->priority() - priority(); ++ return r; ++} ++ ++int cTimerEntry::duration(void) const ++{ ++ int dur = (timer->Stop() / 100 * 60 + timer->Stop() % 100) - ++ (timer->Start() / 100 * 60 + timer->Start() % 100); ++ if (dur < 0) ++ dur += 24 * 60; ++ return dur; ++} ++ ++void cTimerEntry::SetDiskStatus(char DiskStatus) ++{ ++ if (item) ++ item->SetDiskStatus(DiskStatus); ++} ++ + // --- cMenuTimers ----------------------------------------------------------- + + class cMenuTimers : public cOsdMenu { +@@ -1075,14 +1132,17 @@ private: + eOSState Info(void); + cTimer *CurrentTimer(void); + void SetHelpKeys(void); ++ void ActualiseDiskStatus(void); ++ bool actualiseDiskStatus; + public: + cMenuTimers(void); + virtual ~cMenuTimers(); ++ virtual void Display(void); + virtual eOSState ProcessKey(eKeys Key); + }; + + cMenuTimers::cMenuTimers(void) +-:cOsdMenu(tr("Timers"), 2, CHNUMWIDTH, 10, 6, 6) ++:cOsdMenu(tr("Timers"), 3, CHNUMWIDTH, 10, 6, 6) + { + helpKeys = -1; + for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) { +@@ -1093,6 +1153,7 @@ cMenuTimers::cMenuTimers(void) + SetCurrent(First()); + SetHelpKeys(); + Timers.IncBeingEdited(); ++ actualiseDiskStatus = true; + } + + cMenuTimers::~cMenuTimers() +@@ -1131,7 +1192,7 @@ eOSState cMenuTimers::OnOff(void) + timer->OnOff(); + timer->SetEventFromSchedule(); + RefreshCurrent(); +- DisplayCurrent(true); ++ Display(); + if (timer->FirstDay()) + isyslog("timer %s first day set to %s", *timer->ToDescr(), *timer->PrintFirstDay()); + else +@@ -1190,6 +1251,67 @@ eOSState cMenuTimers::Info(void) + return osContinue; + } + ++void cMenuTimers::ActualiseDiskStatus(void) ++{ ++ if (!actualiseDiskStatus || !Count()) ++ return; ++ ++ // compute free disk space ++ int freeMB, freeMinutes, runshortMinutes; ++ VideoDiskSpace(&freeMB); ++ freeMinutes = int(double(freeMB) * 1.1 / MB_PER_MINUTE); // overestimate by 10 percent ++ runshortMinutes = freeMinutes / 5; // 20 Percent ++ ++ // fill entries list ++ cTimerEntry *entry; ++ cList entries; ++ for (cOsdItem *item = First(); item; item = Next(item)) ++ entries.Add(new cTimerEntry((cMenuTimerItem *)item)); ++ ++ // search last start time ++ time_t last = 0; ++ for (entry = entries.First(); entry; entry = entries.Next(entry)) ++ last = max(entry->startTime(), last); ++ ++ // add entries for repeating timers ++ for (entry = entries.First(); entry; entry = entries.Next(entry)) ++ if (entry->repTimer() && !entry->isDummy()) ++ for (time_t start = cTimer::IncDay(entry->startTime(), 1); ++ start <= last; ++ start = cTimer::IncDay(start, 1)) ++ if (entry->Timer()->DayMatches(start)) ++ entries.Add(new cTimerEntry(entry->Timer(), start)); ++ ++ // set the disk-status ++ entries.Sort(); ++ for (entry = entries.First(); entry; entry = entries.Next(entry)) { ++ char status = ' '; ++ if (entry->active()) { ++ freeMinutes -= entry->duration(); ++ status = freeMinutes > runshortMinutes ? '+' : freeMinutes > 0 ? 177 /* +/- */ : '-'; ++ } ++ entry->SetDiskStatus(status); ++#ifdef DEBUG_TIMER_INFO ++ dsyslog("timer-info: %c | %d | %s | %s | %3d | %+5d -> %+5d", ++ status, ++ entry->startTime(), ++ entry->active() ? "aktiv " : "n.akt.", ++ entry->repTimer() ? entry->isDummy() ? " dummy " : "mehrmalig" : "einmalig ", ++ entry->duration(), ++ entry->active() ? freeMinutes + entry->duration() : freeMinutes, ++ freeMinutes); ++#endif ++ } ++ ++ actualiseDiskStatus = false; ++} ++ ++void cMenuTimers::Display(void) ++{ ++ ActualiseDiskStatus(); ++ cOsdMenu::Display(); ++} ++ + eOSState cMenuTimers::ProcessKey(eKeys Key) + { + int TimerNumber = HasSubMenu() ? Count() : -1; +@@ -1198,18 +1320,22 @@ eOSState cMenuTimers::ProcessKey(eKeys K + if (state == osUnknown) { + switch (Key) { + case kOk: return Edit(); +- case kRed: state = OnOff(); break; // must go through SetHelpKeys()! ++ case kRed: actualiseDiskStatus = true; ++ state = OnOff(); break; // must go through SetHelpKeys()! + case kGreen: return New(); +- case kYellow: state = Delete(); break; ++ case kYellow: actualiseDiskStatus = true; ++ state = Delete(); break; + case kInfo: + case kBlue: return Info(); + break; + default: break; + } + } +- if (TimerNumber >= 0 && !HasSubMenu() && Timers.Get(TimerNumber)) { +- // a newly created timer was confirmed with Ok +- Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true); ++ if (TimerNumber >= 0 && !HasSubMenu()) { ++ if (Timers.Get(TimerNumber)) // a newly created timer was confirmed with Ok ++ Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true); ++ Sort(); ++ actualiseDiskStatus = true; + Display(); + } + if (Key != kNone) diff --git a/vdr-timer-info-2.2.0.patch b/vdr-timer-info-2.2.0.patch new file mode 100644 index 0000000..5f9fe2e --- /dev/null +++ b/vdr-timer-info-2.2.0.patch @@ -0,0 +1,15 @@ +diff --git a/menu.c b/menu.c +index 14be0ed..9c6a0ff 100644 +--- a/menu.c ++++ b/menu.c +@@ -1377,8 +1377,8 @@ void cMenuTimers::ActualiseDiskStatus(void) + + // compute free disk space + int freeMB, freeMinutes, runshortMinutes; +- VideoDiskSpace(&freeMB); +- freeMinutes = int(double(freeMB) * 1.1 / MB_PER_MINUTE); // overestimate by 10 percent ++ cVideoDirectory::VideoDiskSpace(&freeMB); ++ freeMinutes = int(double(freeMB) * 1.1 / 25.75); // overestimate by 10 percent + runshortMinutes = freeMinutes / 5; // 20 Percent + + // fill entries list diff --git a/vdr.epgsearch-exttimeredit-0.0.2.diff b/vdr.epgsearch-exttimeredit-0.0.2.diff new file mode 100644 index 0000000..c92ff8c --- /dev/null +++ b/vdr.epgsearch-exttimeredit-0.0.2.diff @@ -0,0 +1,110 @@ +--- menu.c.orig 2009-04-11 14:47:08.000000000 +0200 ++++ menu.c 2009-04-17 13:53:05.000000000 +0200 +@@ -853,6 +853,7 @@ eOSState cMenuEditTimer::ProcessKey(eKey + class cMenuTimerItem : public cOsdItem { + private: + cTimer *timer; ++ void DoSet(void); + public: + cMenuTimerItem(cTimer *Timer); + virtual int Compare(const cListObject &ListObject) const; +@@ -863,7 +864,7 @@ public: + cMenuTimerItem::cMenuTimerItem(cTimer *Timer) + { + timer = Timer; +- Set(); ++ DoSet(); + } + + int cMenuTimerItem::Compare(const cListObject &ListObject) const +@@ -873,6 +874,18 @@ int cMenuTimerItem::Compare(const cListO + + void cMenuTimerItem::Set(void) + { ++ // check for deleted timer ++ for (cTimer *t = Timers.First(); ; t = Timers.Next(t)) { ++ if (t == timer) ++ break; // timer still there ++ if (t == NULL) ++ return; // no matching timer found ++ } ++ DoSet(); ++} ++ ++void cMenuTimerItem::DoSet(void) ++{ + cString day, name(""); + if (timer->WeekDays()) + day = timer->PrintDay(0, timer->WeekDays(), false); +@@ -906,8 +919,7 @@ void cMenuTimerItem::Set(void) + class cMenuTimers : public cOsdMenu { + private: + int helpKeys; +- eOSState Edit(void); +- eOSState New(void); ++ eOSState Edit(bool New = false); + eOSState Delete(void); + eOSState OnOff(void); + eOSState Info(void); +@@ -980,19 +992,30 @@ eOSState cMenuTimers::OnOff(void) + return osContinue; + } + +-eOSState cMenuTimers::Edit(void) ++eOSState cMenuTimers::Edit(bool New) + { +- if (HasSubMenu() || Count() == 0) ++ if (HasSubMenu() || (Count() == 0 && !New)) + return osContinue; +- isyslog("editing timer %s", *CurrentTimer()->ToDescr()); +- return AddSubMenu(new cMenuEditTimer(CurrentTimer())); +-} ++ if (!New) ++ isyslog("editing timer %s", *CurrentTimer()->ToDescr()); + +-eOSState cMenuTimers::New(void) +-{ +- if (HasSubMenu()) +- return osContinue; +- return AddSubMenu(new cMenuEditTimer(new cTimer, true)); ++ // Data structure for service "Epgsearch-exttimeredit-v1.0" ++ struct Epgsearch_exttimeredit_v1_0 ++ { ++ // in ++ cTimer* timer; // pointer to the timer to edit ++ bool bNew; // flag that indicates, if this is a new timer or an existing one ++ const cEvent* event; // pointer to the event corresponding to this timer (may be NULL) ++ // out ++ cOsdMenu* pTimerMenu; // pointer to the menu of results ++ } exttimeredit; ++ exttimeredit.timer = New ? (new cTimer) : CurrentTimer(); ++ exttimeredit.bNew = New; ++ exttimeredit.event = exttimeredit.timer->Event(); ++ if (cPluginManager::CallFirstService("Epgsearch-exttimeredit-v1.0", &exttimeredit)) ++ return AddSubMenu(exttimeredit.pTimerMenu); ++ ++ return AddSubMenu(new cMenuEditTimer(exttimeredit.timer, New)); + } + + eOSState cMenuTimers::Delete(void) +@@ -1038,7 +1061,7 @@ eOSState cMenuTimers::ProcessKey(eKeys K + switch (Key) { + case kOk: return Edit(); + case kRed: state = OnOff(); break; // must go through SetHelpKeys()! +- case kGreen: return New(); ++ case kGreen: return Edit(true); + case kYellow: state = Delete(); break; + case kInfo: + case kBlue: return Info(); +@@ -1051,6 +1074,11 @@ eOSState cMenuTimers::ProcessKey(eKeys K + Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true); + Display(); + } ++ if (!HasSubMenu() && Timers.Count()= 136-1 for the audio, cdrom, dialout, and video groups Requires: udev >= 136-1 # sudo for the shutdown script, >= 1.7.2p2-3 for sudoers.d functionality @@ -98,6 +101,8 @@ Requires: sudo >= 1.7.2p2-3 # util-linux >= 2.15 for "rtcwake -m no" timer driven wakeups Requires: util-linux >= 2.15 Requires: vdrsymbol-fonts +# shadow-utils >= 4.1.1 for useradd -N +Requires(pre): shadow-utils >= 2:4.1.1 # systemd >= 189 for RestartPreventExitStatus= Requires(post,preun,postun): systemd >= 189 Provides: vdr(abi)%{?_isa} = %{apiver} @@ -112,7 +117,7 @@ is required to run VDR. %package devel Summary: Development files for VDR -Requires: gettext-runtime +Requires: gettext Provides: vdr-devel(api) = %{apiver} %description devel @@ -165,26 +170,26 @@ window, using only plain text output. %prep %setup -q -a 18 # dvbhddevice -unzip -o %{SOURCE30} -d $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src -mv $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src/powARman-dvbhddevice-3473a7b939d7 $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src/dvbhddevice +unzip -o %{SOURCE30} -d $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src +mv $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src/powARman-dvbhddevice-2ea854ae8c7a $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src/dvbhddevice cd PLUGINS/src -%patch 0 -p3 +%patch0 -p3 cd ../.. # dvbsddevice -tar -xzf %{SOURCE31} -C $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src -mv $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src/dvbsddevice-2.2.0 $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src/dvbsddevice +tar -xzf %{SOURCE31} -C $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src +mv $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src/dvbsddevice-2.2.0 $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src/dvbsddevice # rcu -tar -xzf %{SOURCE32} -C $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src -mv $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src/rcu-2.2.0 $RPM_BUILD_DIR/vdr-%{version}/PLUGINS/src/rcu +tar -xzf %{SOURCE32} -C $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src +mv $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src/rcu-2.2.0 $RPM_BUILD_DIR/vdr-%{apiver}/PLUGINS/src/rcu -%patch 1 -p1 +%patch1 -p1 # sort_options would be nice, but it conflicts with channel+epg which is nicer #patch -F 0 -i debian/patches/02_sort_options.dpatch # TODO: does not apply since 1.7.24 #patch -F 0 -i debian/patches/06_recording_scan_speedup.dpatch patch -F 2 -i debian/patches/07_blockify_define.dpatch -%patch 2 -p1 -%patch 3 -p1 +%patch2 -p1 +%patch3 -p1 sed \ -e 's|__CACHEDIR__|%{cachedir}|' \ -e 's|__CONFIGDIR__|%{configdir}|' \ @@ -192,10 +197,11 @@ sed \ -e 's|__VARDIR__|%{vardir}|' \ -e 's|__VIDEODIR__|%{videodir}|' \ %{PATCH4} | %{__patch} -p1 -%patch 5 -p1 -%patch 11 -p1 -%patch 15 -p1 -%patch 99 -p1 +%patch7 -p1 +%patch11 -p1 +%patch12 -p1 +%patch15 -p1 +%patch99 -p1 # Patch APIVERSION TO 2.4.8 to match VDRVERSION # sed -i 's/2\.4\.3/2.4.8/' config.h @@ -232,14 +238,6 @@ for g in COLLABORATION INCLUDE INCLUDED_BY ; do sed -i -e 's/^\(\s*'$g'_GRAPH\s*=\s*\).*/\1NO/' Doxyfile done -# Create a sysusers.d config file -cat >vdr.sysusers.conf < Make.config CC = %{__cc} @@ -298,7 +296,7 @@ done %if %{with docs} %make_build srcdoc -%endif +%endif # docs %install @@ -311,12 +309,7 @@ make install-bin install-dirs install-conf install-doc install-i18n \ install -pm 755 epg2html $RPM_BUILD_ROOT%{_bindir} install -dm 755 $RPM_BUILD_ROOT%{_sbindir} -#mv $RPM_BUILD_ROOT%{_bindir}/vdr $RPM_BUILD_ROOT%{_sbindir} - -# Avoid mv error by checking if source and destination are the same -if [ "$RPM_BUILD_ROOT%{_bindir}/vdr" != "$RPM_BUILD_ROOT%{_sbindir}/vdr" ]; then - mv $RPM_BUILD_ROOT%{_bindir}/vdr $RPM_BUILD_ROOT%{_sbindir} -fi +mv $RPM_BUILD_ROOT%{_bindir}/vdr $RPM_BUILD_ROOT%{_sbindir} install -dm 755 $RPM_BUILD_ROOT%{configdir}/plugins @@ -438,8 +431,6 @@ install -pm 644 %{SOURCE11} \ $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/vdr-plugins.d/skincurses.conf %find_lang %{name}-skincurses -install -m0644 -D vdr.sysusers.conf %{buildroot}%{_sysusersdir}/vdr.conf - %check export PKG_CONFIG_PATH=$RPM_BUILD_ROOT%{_libdir}/pkgconfig @@ -448,6 +439,12 @@ if [ "$(pkg-config vdr --variable=apiversion)" != "%{apiver}" ] ; then fi +%pre +# dialout for serial port remote controllers +getent passwd %{vdr_user} >/dev/null || \ +useradd -r -g %{vdr_group} -d %{vardir} -s /sbin/nologin -M -N \ + -G audio,cdrom,dialout -c "Video Disk Recorder" %{vdr_user} || : + %post %systemd_post %{name}.service systemctl daemon-reload @@ -499,14 +496,13 @@ systemctl daemon-reload %dir %{vardir}/ %dir %{vardir}/themes/ %dir %{cachedir}/ -%{_sysusersdir}/vdr.conf %files devel -f %{name}-devel.files %{!?_with_docs:%dir %{_pkgdocdir}} %license COPYING %if ! %{with docs} %{_pkgdocdir}/PLUGINS.html -%endif +%endif # with docs %{_bindir}/vdr-config %{_bindir}/vdr-newplugin %{_includedir}/libsi/ @@ -552,74 +548,6 @@ systemctl daemon-reload %changelog -* Fri Jul 25 2025 Martin Gansser - 2.7.7-1 -- Update to 2.7.7 - -* Fri Jul 25 2025 Fedora Release Engineering - 2.7.6-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild - -* Sat Jun 21 2025 Martin Gansser - 2.7.6-1 -- Update to 2.7.6 - -* Tue May 27 2025 Martin Gansser - 2.7.5-1 -- Update to 2.7.5 -- Add vdr-2.7.5-override-keyword.patch - -* Sun May 11 2025 Peter Bieringer - 2.7.4-3 -- Re-add MainMenuHooks patch for 2.7.x - -* Thu Apr 24 2025 Zbigniew JÄ™drzejewski-Szmek - 2.7.4-2 -- Add sysusers.d config file to allow rpm to create users/groups automatically - -* Wed Feb 26 2025 Martin Gansser - 2.7.4-1 -- Update to 2.7.4 -- Add vdr-2.7.4-fedora-pkgconfig.patch - -* Fri Jan 24 2025 Martin Gansser - 2.7.3-3 -- Fix FTBFS #2341505 - -* Sun Jan 19 2025 Fedora Release Engineering - 2.7.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild - -* Mon Oct 14 2024 Martin Gansser - 2.7.3-1 -- Update to 2.7.3 -- Use recent dvbhddevice Source file 3473a7b939d7.zip -- Add %%{name}-2.7.3-remux-radio.patch - -* Sat Jul 20 2024 Fedora Release Engineering - 2.6.9-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild - -* Mon Jul 15 2024 Martin Gansser - 2.6.9-1 -- Update to 2.6.9 - -* Sun Jul 14 2024 Martin Gansser - 2.6.8-2 -- Add vdr-2.6.8-fix-timeout-open-frontend.diff.txt -- Add vdr-2.6.8-fix-pause-epg-scan.diff.txt - -* Tue Jul 09 2024 Martin Gansser - 2.6.8-1 -- Update to 2.6.8 -- Add strreplace.patch - -* Tue Apr 02 2024 Martin Gansser - 2.6.7-1 -- Update to 2.6.7 - -* Fri Jan 26 2024 Martin Gansser - 2.6.6-1 -- Update to 2.6.6 - -* Wed Jan 03 2024 Martin Gansser - 2.6.5-1 -- Update to 2.6.5 -- vdr-devel does not require any translation management tools (BZ#2119032) - use RR gettext-runtime - -* Sat Jul 22 2023 Fedora Release Engineering - 2.6.4-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild - -* Mon Feb 20 2023 Martin Gansser - 2.6.4-1 -- Update to 2.6.4 - -* Fri Feb 10 2023 Fedora Release Engineering - 2.6.3-3 -- Rebuilt for rawhide - * Sat Jan 21 2023 Fedora Release Engineering - 2.6.3-2 - Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild