Gentoo Archives: gentoo-commits

From: Joonas Niilola <juippis@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] repo/gentoo:master commit in: media-video/vdr/, media-video/vdr/files/
Date: Sun, 03 Jul 2022 08:58:02
Message-Id: 1656838674.8fb604b710052829970783edc8de32804b2ad7ca.juippis@gentoo
1 commit: 8fb604b710052829970783edc8de32804b2ad7ca
2 Author: Martin Dummer <martin.dummer <AT> gmx <DOT> net>
3 AuthorDate: Mon Jun 20 09:38:59 2022 +0000
4 Commit: Joonas Niilola <juippis <AT> gentoo <DOT> org>
5 CommitDate: Sun Jul 3 08:57:54 2022 +0000
6 URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=8fb604b7
7
8 media-video/vdr: version bump to 2.6.1
9
10 - new stable release
11 - rebase most patches for this version
12 - retirement of http://projects.vdr-developer.org: move some SRC_URI
13 to github.com/vdr-projects
14
15 Package-Manager: Portage-3.0.30, Repoman-3.0.3
16 Signed-off-by: Martin Dummer <martin.dummer <AT> gmx.net>
17 Closes: https://github.com/gentoo/gentoo/pull/26000
18 Signed-off-by: Joonas Niilola <juippis <AT> gentoo.org>
19
20 media-video/vdr/Manifest | 2 +
21 .../vdr/files/vdr-2.4.1_mainmenuhook-1.0.1.patch | 114 ++++
22 .../vdr/files/vdr-2.6.1-patch-for-permashift.patch | 517 ++++++++++++++++++
23 media-video/vdr/files/vdr-2.6.1_naludump.patch | 598 +++++++++++++++++++++
24 media-video/vdr/files/vdr-2.6.1_pinplugin.patch | 447 +++++++++++++++
25 media-video/vdr/vdr-2.6.1.ebuild | 197 +++++++
26 6 files changed, 1875 insertions(+)
27
28 diff --git a/media-video/vdr/Manifest b/media-video/vdr/Manifest
29 index 42d12f50673f..b4c6683d67bb 100644
30 --- a/media-video/vdr/Manifest
31 +++ b/media-video/vdr/Manifest
32 @@ -6,4 +6,6 @@ DIST vdr-2.4.1_mainmenuhook-1.0.1.patch.bz2 1463 BLAKE2B 2be3597a273a92d02be0d07
33 DIST vdr-2.4.6_pinplugin.patch.bz2 5176 BLAKE2B 142ad6551b8e37d223acf86f8c2f511cf2cb3664b5ff968bac5e44420c56bf5fdb974360df574141c424cd7a4b1e1489c4a504207420af10f722a77e6d2bad2f SHA512 d5c7263b908302ebf88a5e5ddcb658f56ee92656b40797fd48af1ff9852454adc1d1672cc97411e7744cc31a56f4d0f51df5ece102c21b2aa25d87e3c3c8fe12
34 DIST vdr-2.4.6_ttxtsubs_v2.patch.bz2 51971 BLAKE2B e4915314e8f659df1c3e0ff631fd0c3dcec89053ff727fd28400dc6eecc49c1d4743a9bedaf3b11fce03fc6082309d4b4658d00a5b01aa5cbe8686e05412f882 SHA512 14c7b4397ba65e0ff9a5fb0705872f1cb6f1cdd1752b14f83f260540da25b5957632900232f83904c3d4fd6759e537c85bda2cce61455729eab3050a96441548
35 DIST vdr-2.4.7.tbz2 919632 BLAKE2B be4e1ec365330855e4b2b26f44b1f35dc323e4783e96ef344a67b3e9fe2c0499760ab0f3d27c5e3bdddf5a65ebb65b0c81a62092301d34370aa19d0dd63bb1ab SHA512 41b8453bc6cdbb0db728b5d7d6624ab3ff9034b58ba443a8196f5fcc9fa78d18698cc91905890244e69d482ffc493374e6da2c97baed95fe742b888bdc63e42b
36 +DIST vdr-2.6.1.tbz2 937103 BLAKE2B d8bbf8cb74f79c328ba7a367cd120d9472be667def851fbdff2f2fda3addbf33ae1f0ab0f20dc0ea6b3a03667e36164a5acf5a17cff8f7934c15f4e61b3a8db2 SHA512 235ffd1654d8f13ba658533bfa5db9c9669e93106a63a770297997f9b8342807f270f26e7a6f5a3c127cd9f760bb94ae77f884dcad42a500615c28e1cf3fd92f
37 +DIST vdr-2.6.1_ttxtsubs_v2.patch 40549 BLAKE2B 525c05b72fa957372cc1ef91428428f664f42d92eebc06fd0b179afbe99e4ecc4919c69cef000cc15c8340c9a2506d38cbf755fd6665cde1708ce17d4320a35f SHA512 590c43e63818de8be9637cb3b32bf3399b82a181ca151d67e2341232669619020ab78ba9934da3a9c58cdb3bac56fc2a332a5838a92ee0851e42c3707726eb49
38 DIST vdr-menuorg-2.3.x.diff 8852 BLAKE2B 19b98d51a69f52ecda5500f51ef1741a8397953b20c490055eab0393da5f56ff9598c3e1e8ed8b915f5877e08deeb9ba7a9ef8d9356ad3a1fa12e3778869174a SHA512 7b41c3a529858a4953a57f21619ea01864e140cc1755ee0b03caf1c4de41e80c3f805653502bc8d39d02a4dfcddf720acd4a8c8bd91f4871eef31d86e8e915c0
39
40 diff --git a/media-video/vdr/files/vdr-2.4.1_mainmenuhook-1.0.1.patch b/media-video/vdr/files/vdr-2.4.1_mainmenuhook-1.0.1.patch
41 new file mode 100644
42 index 000000000000..deec2cd7cfdb
43 --- /dev/null
44 +++ b/media-video/vdr/files/vdr-2.4.1_mainmenuhook-1.0.1.patch
45 @@ -0,0 +1,114 @@
46 +original https://raw.githubusercontent.com/VDR4Arch/vdr4arch/master/vdr/vdr-MainMenuHooks.patch
47 +
48 +rebased for media-video/vdr-2.4.1
49 +
50 +Signed-off-by: Joerg Bornkessel <hd_brummy@g.o> ( 2019 Dez 22 )
51 +diff -Naur vdr-2.4.1.orig/config.h vdr-2.4.1/config.h
52 +--- vdr-2.4.1.orig/config.h 2019-12-22 00:04:59.000000000 +0100
53 ++++ vdr-2.4.1/config.h 2019-12-22 00:11:25.000000000 +0100
54 +@@ -36,6 +36,10 @@
55 + // plugins to work with newer versions of the core VDR as long as no
56 + // VDR header files have changed.
57 +
58 ++// The MainMenuHook Patch's version number:
59 ++#define MAINMENUHOOKSVERSION "1.0.1"
60 ++#define MAINMENUHOOKSVERSNUM 10001 // Version * 10000 + Major * 100 + Minor
61 ++
62 + #define MAXPRIORITY 99
63 + #define MINPRIORITY (-MAXPRIORITY)
64 + #define LIVEPRIORITY 0 // priority used when selecting a device for live viewing
65 +diff -Naur vdr-2.4.1.orig/menu.c vdr-2.4.1/menu.c
66 +--- vdr-2.4.1.orig/menu.c 2019-12-22 00:04:59.000000000 +0100
67 ++++ vdr-2.4.1/menu.c 2019-12-22 00:11:25.000000000 +0100
68 +@@ -4395,15 +4395,31 @@
69 +
70 + // Initial submenus:
71 +
72 ++ cOsdObject *menu = NULL;
73 + switch (State) {
74 +- case osSchedule: AddSubMenu(new cMenuSchedule); break;
75 +- case osChannels: AddSubMenu(new cMenuChannels); break;
76 +- case osTimers: AddSubMenu(new cMenuTimers); break;
77 +- case osRecordings: AddSubMenu(new cMenuRecordings(NULL, 0, OpenSubMenus)); break;
78 +- case osSetup: AddSubMenu(new cMenuSetup); break;
79 +- case osCommands: AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); break;
80 ++ case osSchedule:
81 ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu))
82 ++ menu = new cMenuSchedule;
83 ++ break;
84 ++ case osChannels:
85 ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu))
86 ++ menu = new cMenuChannels;
87 ++ break;
88 ++ case osTimers:
89 ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu))
90 ++ menu = new cMenuTimers;
91 ++ break;
92 ++ case osRecordings:
93 ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu))
94 ++ menu = new cMenuRecordings(NULL, 0, OpenSubMenus);
95 ++ break;
96 ++ case osSetup: menu = new cMenuSetup; break;
97 ++ case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break;
98 + default: break;
99 + }
100 ++ if (menu)
101 ++ if (menu->IsMenu())
102 ++ AddSubMenu((cOsdMenu *) menu);
103 + }
104 +
105 + cOsdObject *cMenuMain::PluginOsdObject(void)
106 +@@ -4511,13 +4527,34 @@
107 + eOSState state = cOsdMenu::ProcessKey(Key);
108 + HadSubMenu |= HasSubMenu();
109 +
110 ++ cOsdObject *menu = NULL;
111 + switch (state) {
112 +- case osSchedule: return AddSubMenu(new cMenuSchedule);
113 +- case osChannels: return AddSubMenu(new cMenuChannels);
114 +- case osTimers: return AddSubMenu(new cMenuTimers);
115 +- case osRecordings: return AddSubMenu(new cMenuRecordings);
116 +- case osSetup: return AddSubMenu(new cMenuSetup);
117 +- case osCommands: return AddSubMenu(new cMenuCommands(tr("Commands"), &Commands));
118 ++ case osSchedule:
119 ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu))
120 ++ menu = new cMenuSchedule;
121 ++ else
122 ++ state = osContinue;
123 ++ break;
124 ++ case osChannels:
125 ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu))
126 ++ menu = new cMenuChannels;
127 ++ else
128 ++ state = osContinue;
129 ++ break;
130 ++ case osTimers:
131 ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu))
132 ++ menu = new cMenuTimers;
133 ++ else
134 ++ state = osContinue;
135 ++ break;
136 ++ case osRecordings:
137 ++ if (!cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu))
138 ++ menu = new cMenuRecordings;
139 ++ else
140 ++ state = osContinue;
141 ++ break;
142 ++ case osSetup: menu = new cMenuSetup; break;
143 ++ case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break;
144 + case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) {
145 + if (cOsdItem *item = Get(Current())) {
146 + cRecordControls::Stop(item->Text() + strlen(tr(STOP_RECORDING)));
147 +@@ -4568,6 +4605,12 @@
148 + default: break;
149 + }
150 + }
151 ++ if (menu) {
152 ++ if (menu->IsMenu())
153 ++ return AddSubMenu((cOsdMenu *) menu);
154 ++ pluginOsdObject = menu;
155 ++ return osPlugin;
156 ++ }
157 + if (!HasSubMenu() && Update(HadSubMenu))
158 + Display();
159 + if (Key != kNone) {
160
161 diff --git a/media-video/vdr/files/vdr-2.6.1-patch-for-permashift.patch b/media-video/vdr/files/vdr-2.6.1-patch-for-permashift.patch
162 new file mode 100644
163 index 000000000000..03b476f986bb
164 --- /dev/null
165 +++ b/media-video/vdr/files/vdr-2.6.1-patch-for-permashift.patch
166 @@ -0,0 +1,517 @@
167 +Adapted Patch from forum post
168 +https://www.vdr-portal.de/forum/index.php?thread/134171-permashift-1-0-4-f%C3%BCr-vdr-2-4-betaversion/&postID=1341243#post1341243
169 +
170 +SRC Url https://www.vdr-portal.de/index.php?attachment/45632-vdr-2-5-4-patch-for-permashift-diff-gz/ adapted for vdr-2.6.1
171 +
172 +Signed-off-by: Martin Dummer <martin.dummer@×××.net>
173 +
174 +diff -Naur vdr-2.6.1.orig/device.c vdr-2.6.1/device.c
175 +--- vdr-2.6.1.orig/device.c 2022-02-02 10:56:43.000000000 +0100
176 ++++ vdr-2.6.1/device.c 2022-02-06 18:05:26.452890690 +0100
177 +@@ -1880,6 +1880,17 @@
178 + ReleaseCamSlot();
179 + }
180 +
181 ++cRecorder* cDevice::GetPreRecording(const cChannel *Channel)
182 ++{
183 ++ cMutexLock MutexLock(&mutexReceiver);
184 ++ for (int i = 0; i < MAXRECEIVERS; i++) {
185 ++ if (receiver[i])
186 ++ if (receiver[i]->IsPreRecording(Channel))
187 ++ return (cRecorder*)receiver[i];
188 ++ }
189 ++ return NULL;
190 ++}
191 ++
192 + // --- cTSBuffer -------------------------------------------------------------
193 +
194 + cTSBuffer::cTSBuffer(int File, int Size, int DeviceNumber)
195 +diff -Naur vdr-2.6.1.orig/device.h vdr-2.6.1/device.h
196 +--- vdr-2.6.1.orig/device.h 2022-02-02 10:56:43.000000000 +0100
197 ++++ vdr-2.6.1/device.h 2022-02-06 18:05:51.541429884 +0100
198 +@@ -85,6 +85,7 @@
199 +
200 + class cPlayer;
201 + class cReceiver;
202 ++class cRecorder;
203 + class cLiveSubtitle;
204 +
205 + class cDeviceHook : public cListObject {
206 +@@ -854,6 +855,8 @@
207 + ///< Returns true if we are currently receiving. The parameter has no meaning (for backwards compatibility only).
208 + bool AttachReceiver(cReceiver *Receiver);
209 + ///< Attaches the given receiver to this device.
210 ++ cRecorder* GetPreRecording(const cChannel *Channel);
211 ++ ///< Get precocious recording for the channel if there is one.
212 + void Detach(cReceiver *Receiver, bool ReleaseCam = true);
213 + ///< Detaches the given receiver from this device.
214 + ///< If ReleaseCam is true, the CAM slot will be released if it
215 +diff -Naur vdr-2.6.1.orig/dvbplayer.c vdr-2.6.1/dvbplayer.c
216 +--- vdr-2.6.1.orig/dvbplayer.c 2022-02-02 10:56:43.000000000 +0100
217 ++++ vdr-2.6.1/dvbplayer.c 2022-02-06 18:05:26.452890690 +0100
218 +@@ -249,13 +249,14 @@
219 + cUnbufferedFile *replayFile;
220 + double framesPerSecond;
221 + bool isPesRecording;
222 +- bool pauseLive;
223 ++ ReplayState replayState;
224 + bool eof;
225 + bool firstPacket;
226 + ePlayModes playMode;
227 + ePlayDirs playDir;
228 + int trickSpeed;
229 + int readIndex;
230 ++ int startIndex;
231 + bool readIndependent;
232 + cFrame *readFrame;
233 + cFrame *playFrame;
234 +@@ -271,6 +272,8 @@
235 + virtual void Action(void);
236 + public:
237 + cDvbPlayer(const char *FileName, bool PauseLive);
238 ++ cDvbPlayer(const char *FileName, ReplayState newReplayState);
239 ++ void Construct(const char *FileName, ReplayState newReplayState);
240 + virtual ~cDvbPlayer();
241 + void SetMarks(const cMarks *Marks);
242 + bool Active(void) { return cThread::Running(); }
243 +@@ -297,6 +300,17 @@
244 + cDvbPlayer::cDvbPlayer(const char *FileName, bool PauseLive)
245 + :cThread("dvbplayer")
246 + {
247 ++ Construct(FileName, PauseLive? restPauseLive : restNormal);
248 ++}
249 ++
250 ++cDvbPlayer::cDvbPlayer(const char *FileName, ReplayState newReplayState)
251 ++:cThread("dvbplayer")
252 ++{
253 ++ Construct(FileName, newReplayState);
254 ++}
255 ++
256 ++void cDvbPlayer::Construct(const char *FileName, ReplayState newReplayState)
257 ++{
258 + nonBlockingFileReader = NULL;
259 + ringBuffer = NULL;
260 + marks = NULL;
261 +@@ -304,7 +318,8 @@
262 + cRecording Recording(FileName);
263 + framesPerSecond = Recording.FramesPerSecond();
264 + isPesRecording = Recording.IsPesRecording();
265 +- pauseLive = PauseLive;
266 ++ replayState = newReplayState;
267 ++ bool reuse = (replayState == restReusePause || replayState == restReuseRewind);
268 + eof = false;
269 + firstPacket = true;
270 + playMode = pmPlay;
271 +@@ -323,15 +338,21 @@
272 + return;
273 + ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE);
274 + // Create the index file:
275 +- index = new cIndexFile(FileName, false, isPesRecording, pauseLive);
276 ++ index = new cIndexFile(FileName, false, isPesRecording, replayState == restPauseLive);
277 + if (!index)
278 + esyslog("ERROR: can't allocate index");
279 + else if (!index->Ok()) {
280 + delete index;
281 + index = NULL;
282 + }
283 +- else if (PauseLive)
284 ++ else if (reuse)
285 + framesPerSecond = cRecording(FileName).FramesPerSecond(); // the fps rate might have changed from the default
286 ++ startIndex = 0;
287 ++ if (replayState == restReuseRewind || replayState == restReusePause) {
288 ++ int Current, Total;
289 ++ GetIndex(Current, Total, false);
290 ++ startIndex = max(Total - 1, 0);
291 ++ }
292 + }
293 +
294 + cDvbPlayer::~cDvbPlayer()
295 +@@ -481,8 +502,21 @@
296 + bool CutIn = false;
297 + bool AtLastMark = false;
298 +
299 +- if (pauseLive)
300 +- Goto(0, true);
301 ++ if (replayState == restPauseLive) {
302 ++ Goto(0, true);
303 ++ }
304 ++ else if (replayState == restReuseRewind || replayState == restReusePause) {
305 ++ readIndex = startIndex;
306 ++ Goto(readIndex, true);
307 ++ playMode = pmPlay;
308 ++ if (replayState == restReuseRewind) {
309 ++ Backward();
310 ++ }
311 ++ else if (replayState == restReusePause) {
312 ++ Pause();
313 ++ }
314 ++ }
315 ++
316 + while (Running()) {
317 + if (WaitingForData)
318 + WaitingForData = !nonBlockingFileReader->WaitForDataMs(3); // this keeps the CPU load low, but reacts immediately on new data
319 +@@ -985,6 +1019,11 @@
320 + {
321 + }
322 +
323 ++cDvbPlayerControl::cDvbPlayerControl(const char *FileName, ReplayState replayState)
324 ++:cControl(player = new cDvbPlayer(FileName, replayState))
325 ++{
326 ++}
327 ++
328 + cDvbPlayerControl::~cDvbPlayerControl()
329 + {
330 + Stop();
331 +diff -Naur vdr-2.6.1.orig/dvbplayer.h vdr-2.6.1/dvbplayer.h
332 +--- vdr-2.6.1.orig/dvbplayer.h 2022-02-02 10:56:43.000000000 +0100
333 ++++ vdr-2.6.1/dvbplayer.h 2022-02-06 18:05:26.452890690 +0100
334 +@@ -16,6 +16,14 @@
335 +
336 + class cDvbPlayer;
337 +
338 ++enum ReplayState
339 ++{
340 ++ restNormal,
341 ++ restPauseLive,
342 ++ restReusePause,
343 ++ restReuseRewind
344 ++};
345 ++
346 + class cDvbPlayerControl : public cControl {
347 + private:
348 + cDvbPlayer *player;
349 +@@ -25,6 +33,8 @@
350 + // If PauseLive is true, special care is taken to make sure the index
351 + // file of the recording is long enough to allow the player to display
352 + // the first frame in still picture mode.
353 ++ cDvbPlayerControl(const char *FileName, ReplayState replayState);
354 ++ // Sets up a player for the given file. replayState represents the initial state.
355 + virtual ~cDvbPlayerControl();
356 + void SetMarks(const cMarks *Marks);
357 + bool Active(void);
358 +diff -Naur vdr-2.6.1.orig/menu.c vdr-2.6.1/menu.c
359 +--- vdr-2.6.1.orig/menu.c 2022-02-02 10:56:43.000000000 +0100
360 ++++ vdr-2.6.1/menu.c 2022-02-06 18:05:26.452890690 +0100
361 +@@ -5303,6 +5303,16 @@
362 +
363 + cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause)
364 + {
365 ++ Construct(Device, Timers, Timer, Pause, NULL);
366 ++}
367 ++
368 ++cRecordControl::cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused)
369 ++{
370 ++ Construct(Device, Timers, Timer, Pause, reused);
371 ++}
372 ++
373 ++void cRecordControl::Construct(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused)
374 ++{
375 + const char *LastReplayed = cReplayControl::LastReplayed(); // must do this before locking schedules!
376 + // Whatever happens here, the timers will be modified in some way...
377 + Timers->SetModified();
378 +@@ -5331,6 +5341,7 @@
379 + timer->SetPending(true);
380 + timer->SetRecording(true);
381 + event = timer->Event();
382 ++ if (reused != NULL) *reused = false;
383 +
384 + if (event || GetEvent())
385 + dsyslog("Title: '%s' Subtitle: '%s'", event->Title(), event->ShortText());
386 +@@ -5360,8 +5371,21 @@
387 + if (MakeDirs(fileName, true)) {
388 + Recording.WriteInfo(); // we write this *before* attaching the recorder to the device, to make sure the info file is present when the recorder needs to update the fps value!
389 + const cChannel *ch = timer->Channel();
390 +- recorder = new cRecorder(fileName, ch, timer->Priority());
391 +- if (device->AttachReceiver(recorder)) {
392 ++
393 ++ if (!Timer) {
394 ++ recorder = device->GetPreRecording(ch);
395 ++ if (recorder != NULL) {
396 ++ recorder->ActivatePreRecording(fileName, timer->Priority());
397 ++ if (reused != NULL) *reused = true;
398 ++ }
399 ++ }
400 ++
401 ++ if (recorder == NULL) {
402 ++ recorder = new cRecorder(fileName, ch, timer->Priority());
403 ++ if (!device->AttachReceiver(recorder)) DELETENULL(recorder);
404 ++ }
405 ++
406 ++ if (recorder != NULL) {
407 + cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true);
408 + if (!Timer && !LastReplayed) // an instant recording, maybe from cRecordControls::PauseLiveVideo()
409 + cReplayControl::SetRecording(fileName);
410 +@@ -5371,8 +5395,6 @@
411 + Recordings->AddByName(fileName);
412 + return;
413 + }
414 +- else
415 +- DELETENULL(recorder);
416 + }
417 + else
418 + timer->SetDeferred(DEFERTIMER);
419 +@@ -5452,7 +5474,7 @@
420 + cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL };
421 + int cRecordControls::state = 0;
422 +
423 +-bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause)
424 ++bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause, bool* reused)
425 + {
426 + static time_t LastNoDiskSpaceMessage = 0;
427 + int FreeMB = 0;
428 +@@ -5490,7 +5512,7 @@
429 + if (!Timer || Timer->Matches()) {
430 + for (int i = 0; i < MAXRECORDCONTROLS; i++) {
431 + if (!RecordControls[i]) {
432 +- RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause);
433 ++ RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause, reused);
434 + return RecordControls[i]->Process(time(NULL));
435 + }
436 + }
437 +@@ -5514,6 +5536,11 @@
438 + return Start(Timers, NULL, Pause);
439 + }
440 +
441 ++bool cRecordControls::Start(cTimers *Timers, cTimer *Timer, bool Pause)
442 ++{
443 ++ return Start(Timers, Timer, Pause, NULL);
444 ++}
445 ++
446 + void cRecordControls::Stop(const char *InstantId)
447 + {
448 + LOCK_TIMERS_WRITE;
449 +@@ -5549,10 +5576,17 @@
450 +
451 + bool cRecordControls::PauseLiveVideo(void)
452 + {
453 ++ return PauseLiveVideo(false);
454 ++}
455 ++
456 ++bool cRecordControls::PauseLiveVideo(bool rewind)
457 ++{
458 + Skins.Message(mtStatus, tr("Pausing live video..."));
459 ++ bool reused = false;
460 + cReplayControl::SetRecording(NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed()
461 +- if (Start(true)) {
462 +- cReplayControl *rc = new cReplayControl(true);
463 ++ LOCK_TIMERS_WRITE;
464 ++ if (Start(Timers, NULL, true, &reused)) {
465 ++ cReplayControl *rc = new cReplayControl(rewind? restReuseRewind : reused? restReusePause : restPauseLive);
466 + cControl::Launch(rc);
467 + cControl::Attach();
468 + Skins.Message(mtStatus, NULL);
469 +@@ -5695,7 +5729,18 @@
470 + cReplayControl::cReplayControl(bool PauseLive)
471 + :cDvbPlayerControl(fileName, PauseLive)
472 + {
473 +- cDevice::PrimaryDevice()->SetKeepTracks(PauseLive);
474 ++ Construct(PauseLive? restPauseLive : restNormal);
475 ++}
476 ++
477 ++cReplayControl::cReplayControl(ReplayState replayState)
478 ++:cDvbPlayerControl(fileName, replayState)
479 ++{
480 ++ Construct(replayState);
481 ++}
482 ++
483 ++void cReplayControl::Construct(ReplayState replayState)
484 ++{
485 ++ cDevice::PrimaryDevice()->SetKeepTracks(replayState == restPauseLive);
486 + currentReplayControl = this;
487 + displayReplay = NULL;
488 + marksModified = false;
489 +diff -Naur vdr-2.6.1.orig/menu.h vdr-2.6.1/menu.h
490 +--- vdr-2.6.1.orig/menu.h 2022-02-02 10:56:43.000000000 +0100
491 ++++ vdr-2.6.1/menu.h 2022-02-06 18:05:26.452890690 +0100
492 +@@ -246,6 +246,8 @@
493 + bool GetEvent(void);
494 + public:
495 + cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer = NULL, bool Pause = false);
496 ++ cRecordControl(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused);
497 ++ void Construct(cDevice *Device, cTimers *Timers, cTimer *Timer, bool Pause, bool* reused);
498 + virtual ~cRecordControl();
499 + bool Process(time_t t);
500 + cDevice *Device(void) { return device; }
501 +@@ -261,10 +263,12 @@
502 + static int state;
503 + public:
504 + static bool Start(cTimers *Timers, cTimer *Timer, bool Pause = false);
505 ++ static bool Start(cTimers *Timers, cTimer *Timer, bool Pause, bool* reused);
506 + static bool Start(bool Pause = false);
507 + static void Stop(const char *InstantId);
508 + static void Stop(cTimer *Timer);
509 + static bool PauseLiveVideo(void);
510 ++ static bool PauseLiveVideo(bool rewind);
511 + static const char *GetInstantId(const char *LastInstantId);
512 + static cRecordControl *GetRecordControl(const char *FileName);
513 + static cRecordControl *GetRecordControl(const cTimer *Timer);
514 +@@ -320,6 +324,8 @@
515 + void EditTest(void);
516 + public:
517 + cReplayControl(bool PauseLive = false);
518 ++ cReplayControl(ReplayState replayState);
519 ++ void Construct(ReplayState replayState);
520 + virtual ~cReplayControl();
521 + void Stop(void);
522 + virtual cOsdObject *GetInfo(void);
523 +diff -Naur vdr-2.6.1.orig/receiver.h vdr-2.6.1/receiver.h
524 +--- vdr-2.6.1.orig/receiver.h 2022-02-02 10:56:43.000000000 +0100
525 ++++ vdr-2.6.1/receiver.h 2022-02-06 18:05:26.452890690 +0100
526 +@@ -85,6 +85,10 @@
527 + ///< case the device is needed otherwise, so code that uses a cReceiver
528 + ///< should repeatedly check whether it is still attached, and if
529 + ///< it isn't, delete it (or take any other appropriate measures).
530 ++ virtual bool IsPreRecording(const cChannel *Channel) { return false; }
531 ++ ///< prerecords given channel; may be turned into a disc recording.
532 ++ virtual bool ActivatePreRecording(const char* fileName, int Priority) { return false; }
533 ++ ///< turn prerecording into a disc recording
534 + };
535 +
536 + #endif //__RECEIVER_H
537 +diff -Naur vdr-2.6.1.orig/recorder.c vdr-2.6.1/recorder.c
538 +--- vdr-2.6.1.orig/recorder.c 2022-02-02 10:56:43.000000000 +0100
539 ++++ vdr-2.6.1/recorder.c 2022-02-06 18:05:26.452890690 +0100
540 +@@ -164,11 +164,25 @@
541 + cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
542 + :cReceiver(Channel, Priority)
543 + ,cThread("recording")
544 ++,tsChecker(NULL), frameChecker(NULL), recordingInfo(NULL), ringBuffer(NULL), frameDetector(NULL), fileName(NULL), index(NULL), recordFile(NULL), recordingName(NULL)
545 + {
546 +- tsChecker = new cTsChecker;
547 +- frameChecker = new cFrameChecker;
548 ++ if (FileName != NULL) {
549 ++ InitializeFile(FileName, Channel);
550 ++ }
551 ++}
552 ++
553 ++void cRecorder::InitializeFile(const char *FileName, const cChannel *Channel)
554 ++{
555 ++ if (tsChecker == NULL) {
556 ++ tsChecker = new cTsChecker;
557 ++ }
558 ++ if (frameChecker == NULL) {
559 ++ frameChecker = new cFrameChecker;
560 ++ }
561 + recordingName = strdup(FileName);
562 +- recordingInfo = new cRecordingInfo(recordingName);
563 ++ if (recordingInfo == NULL) {
564 ++ recordingInfo = new cRecordingInfo(recordingName);
565 ++ }
566 + recordingInfo->Read();
567 + oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording
568 + errors = oldErrors;
569 +@@ -193,7 +207,9 @@
570 + Pid = Channel->Dpid(0);
571 + Type = 0x06;
572 + }
573 +- frameDetector = new cFrameDetector(Pid, Type);
574 ++ if (frameDetector == NULL) {
575 ++ frameDetector = new cFrameDetector(Pid, Type);
576 ++ }
577 + index = NULL;
578 + fileSize = 0;
579 + lastDiskSpaceCheck = time(NULL);
580 +diff -Naur vdr-2.6.1.orig/recorder.h vdr-2.6.1/recorder.h
581 +--- vdr-2.6.1.orig/recorder.h 2022-02-02 10:56:43.000000000 +0100
582 ++++ vdr-2.6.1/recorder.h 2022-02-06 18:05:26.452890690 +0100
583 +@@ -19,8 +19,8 @@
584 + class cTsChecker;
585 + class cFrameChecker;
586 +
587 +-class cRecorder : public cReceiver, cThread {
588 +-private:
589 ++class cRecorder : public cReceiver, protected cThread {
590 ++protected:
591 + cTsChecker *tsChecker;
592 + cFrameChecker *frameChecker;
593 + cRingBufferLinear *ringBuffer;
594 +@@ -41,7 +41,6 @@
595 + bool RunningLowOnDiskSpace(void);
596 + bool NextFile(void);
597 + void HandleErrors(bool Force = false);
598 +-protected:
599 + virtual void Activate(bool On);
600 + ///< If you override Activate() you need to call Detach() (which is a
601 + ///< member of the cReceiver class) from your own destructor in order
602 +@@ -49,6 +48,9 @@
603 + ///< destroyed.
604 + virtual void Receive(const uchar *Data, int Length);
605 + virtual void Action(void);
606 ++ void InitializeFile(const char *FileName, const cChannel *Channel);
607 ++ ///< Starts recording to file.
608 ++ ///< Called in constructor if file name has been given.
609 + public:
610 + cRecorder(const char *FileName, const cChannel *Channel, int Priority);
611 + ///< Creates a new recorder for the given Channel and
612 +diff -Naur vdr-2.6.1.orig/ringbuffer.c vdr-2.6.1/ringbuffer.c
613 +--- vdr-2.6.1.orig/ringbuffer.c 2022-02-02 10:56:43.000000000 +0100
614 ++++ vdr-2.6.1/ringbuffer.c 2022-02-06 18:05:26.452890690 +0100
615 +@@ -368,6 +368,25 @@
616 + return NULL;
617 + }
618 +
619 ++uchar *cRingBufferLinear::GetRest(int &Count)
620 ++{
621 ++ int Head = head;
622 ++ if (getThreadTid <= 0)
623 ++ getThreadTid = cThread::ThreadId();
624 ++ int rest = Size() - tail;
625 ++ int diff = Head - tail;
626 ++ int cont = (diff >= 0) ? diff : Size() + diff - margin;
627 ++ if (cont > rest)
628 ++ cont = rest;
629 ++ uchar *p = buffer + tail;
630 ++ if (cont > 0) {
631 ++ Count = gotten = cont;
632 ++ return p;
633 ++ }
634 ++ WaitForGet();
635 ++ return NULL;
636 ++}
637 ++
638 + void cRingBufferLinear::Del(int Count)
639 + {
640 + if (Count > gotten) {
641 +diff -Naur vdr-2.6.1.orig/ringbuffer.h vdr-2.6.1/ringbuffer.h
642 +--- vdr-2.6.1.orig/ringbuffer.h 2022-02-02 10:56:43.000000000 +0100
643 ++++ vdr-2.6.1/ringbuffer.h 2022-02-06 18:05:26.452890690 +0100
644 +@@ -98,6 +98,12 @@
645 + ///< The data will remain in the buffer until a call to Del() deletes it.
646 + ///< Returns a pointer to the data, and stores the number of bytes
647 + ///< actually available in Count. If the returned pointer is NULL, Count has no meaning.
648 ++ uchar *GetRest(int &Count);
649 ++ ///< Gets data from the ring buffer disregarding the margin.
650 ++ ///< Might have to be called several times to get all data.
651 ++ ///< The data will remain in the buffer until a call to Del() deletes it.
652 ++ ///< Returns a pointer to the data, and stores the number of bytes
653 ++ ///< actually available in Count. If the returned pointer is NULL, Count has no meaning.
654 + void Del(int Count);
655 + ///< Deletes at most Count bytes from the ring buffer.
656 + ///< Count must be less or equal to the number that was returned by a previous
657 +diff -Naur vdr-2.6.1.orig/vdr.c vdr-2.6.1/vdr.c
658 +--- vdr-2.6.1.orig/vdr.c 2022-02-02 10:56:43.000000000 +0100
659 ++++ vdr-2.6.1/vdr.c 2022-02-06 18:05:26.452890690 +0100
660 +@@ -1352,13 +1352,22 @@
661 + key = kNone;
662 + break;
663 + // Pausing live video:
664 ++ case kFastRew:
665 ++ {
666 ++ // test if there's a live buffer to rewind into...
667 ++ LOCK_CHANNELS_READ;
668 ++ if (cDevice::ActualDevice()->GetPreRecording(Channels->GetByNumber(cDevice::CurrentChannel())) == NULL) {
669 ++ break;
670 ++ }
671 ++ }
672 ++ // fall through to pause
673 + case kPlayPause:
674 + case kPause:
675 + if (!Control) {
676 + DELETE_MENU;
677 + if (Setup.PauseKeyHandling) {
678 + if (Setup.PauseKeyHandling > 1 || Interface->Confirm(tr("Pause live video?"))) {
679 +- if (!cRecordControls::PauseLiveVideo())
680 ++ if (!cRecordControls::PauseLiveVideo(int(key) == kFastRew))
681 + Skins.QueueMessage(mtError, tr("No free DVB device to record!"));
682 + }
683 + }
684
685 diff --git a/media-video/vdr/files/vdr-2.6.1_naludump.patch b/media-video/vdr/files/vdr-2.6.1_naludump.patch
686 new file mode 100644
687 index 000000000000..efea3a1d74f7
688 --- /dev/null
689 +++ b/media-video/vdr/files/vdr-2.6.1_naludump.patch
690 @@ -0,0 +1,598 @@
691 +diff -a -U 2 -r a/config.c b/config.c
692 +--- a/config.c
693 ++++ b/config.c
694 +@@ -469,4 +469,5 @@
695 + SplitEditedFiles = 0;
696 + DelTimeshiftRec = 0;
697 ++ DumpNaluFill = 0;
698 + MinEventTimeout = 30;
699 + MinUserInactivity = 300;
700 +@@ -697,4 +698,5 @@
701 + else if (!strcasecmp(Name, "SplitEditedFiles")) SplitEditedFiles = atoi(Value);
702 + else if (!strcasecmp(Name, "DelTimeshiftRec")) DelTimeshiftRec = atoi(Value);
703 ++ else if (!strcasecmp(Name, "DumpNaluFill")) DumpNaluFill = atoi(Value);
704 + else if (!strcasecmp(Name, "MinEventTimeout")) MinEventTimeout = atoi(Value);
705 + else if (!strcasecmp(Name, "MinUserInactivity")) MinUserInactivity = atoi(Value);
706 +@@ -829,4 +831,5 @@
707 + Store("SplitEditedFiles", SplitEditedFiles);
708 + Store("DelTimeshiftRec", DelTimeshiftRec);
709 ++ Store("DumpNaluFill", DumpNaluFill);
710 + Store("MinEventTimeout", MinEventTimeout);
711 + Store("MinUserInactivity", MinUserInactivity);
712 +diff -a -U 2 -r a/config.h b/config.h
713 +--- a/config.h
714 ++++ b/config.h
715 +@@ -341,4 +341,5 @@
716 + int SplitEditedFiles;
717 + int DelTimeshiftRec;
718 ++ int DumpNaluFill;
719 + int MinEventTimeout, MinUserInactivity;
720 + time_t NextWakeupTime;
721 +diff -a -U 2 -r a/menu.c b/menu.c
722 +--- a/menu.c
723 ++++ b/menu.c
724 +@@ -4185,4 +4185,5 @@
725 + Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles));
726 + Add(new cMenuEditStraItem(tr("Setup.Recording$Delete timeshift recording"),&data.DelTimeshiftRec, 3, delTimeshiftRecTexts));
727 ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Dump NALU Fill data"), &data.DumpNaluFill));
728 + }
729 +
730 +diff -a -U 2 -r a/recorder.c b/recorder.c
731 +--- a/recorder.c
732 ++++ b/recorder.c
733 +@@ -195,4 +195,12 @@
734 + }
735 + frameDetector = new cFrameDetector(Pid, Type);
736 ++ if ( Type == 0x1B // MPEG4 video
737 ++ && (Setup.DumpNaluFill ? (strstr(FileName, "NALUKEEP") == NULL) : (strstr(FileName, "NALUDUMP") != NULL))) { // MPEG4
738 ++ isyslog("Starting NALU fill dumper");
739 ++ naluStreamProcessor = new cNaluStreamProcessor();
740 ++ naluStreamProcessor->SetPid(Pid);
741 ++ }
742 ++ else
743 ++ naluStreamProcessor = NULL;
744 + index = NULL;
745 + fileSize = 0;
746 +@@ -217,4 +225,10 @@
747 + {
748 + Detach();
749 ++ if (naluStreamProcessor) {
750 ++ long long int TotalPackets = naluStreamProcessor->GetTotalPackets();
751 ++ long long int DroppedPackets = naluStreamProcessor->GetDroppedPackets();
752 ++ isyslog("NALU fill dumper: %lld of %lld packets dropped, %lli%%", DroppedPackets, TotalPackets, TotalPackets ? DroppedPackets*100/TotalPackets : 0);
753 ++ delete naluStreamProcessor;
754 ++ }
755 + delete index;
756 + delete fileName;
757 +@@ -357,10 +371,32 @@
758 + t.Set(MAXBROKENTIMEOUT);
759 + }
760 +- if (recordFile->Write(b, Count) < 0) {
761 +- LOG_ERROR_STR(fileName->Name());
762 +- break;
763 ++ if (naluStreamProcessor) {
764 ++ naluStreamProcessor->PutBuffer(b, Count);
765 ++ bool Fail = false;
766 ++ while (true) {
767 ++ int OutLength = 0;
768 ++ uchar *OutData = naluStreamProcessor->GetBuffer(OutLength);
769 ++ if (!OutData || OutLength <= 0)
770 ++ break;
771 ++ if (recordFile->Write(OutData, OutLength) < 0) {
772 ++ LOG_ERROR_STR(fileName->Name());
773 ++ Fail = true;
774 ++ break;
775 ++ }
776 ++ HandleErrors();
777 ++ fileSize += OutLength;
778 ++ }
779 ++ if (Fail)
780 ++ break;
781 ++ }
782 ++ else {
783 ++ if (recordFile->Write(b, Count) < 0) {
784 ++ LOG_ERROR_STR(fileName->Name());
785 ++ break;
786 ++ }
787 ++ HandleErrors();
788 ++ fileSize += Count;
789 + }
790 +- HandleErrors();
791 +- fileSize += Count;
792 ++
793 + }
794 + }
795 +diff -a -U 2 -r a/recorder.h b/recorder.h
796 +--- a/recorder.h
797 ++++ b/recorder.h C2022-01-08 17:51:00.397595525 +0100
798 +@@ -27,4 +27,5 @@
799 + cFrameDetector *frameDetector;
800 + cPatPmtGenerator patPmtGenerator;
801 ++ cNaluStreamProcessor *naluStreamProcessor;
802 + cFileName *fileName;
803 + cRecordingInfo *recordingInfo;
804 +diff -a -U 2 -r a/remux.c b/remux.c
805 +--- a/remux.c
806 ++++ b/remux.c
807 +@@ -357,4 +357,40 @@
808 + }
809 +
810 ++void TsExtendAdaptionField(unsigned char *Packet, int ToLength)
811 ++{
812 ++ // Hint: ExtenAdaptionField(p, TsPayloadOffset(p) - 4) is a null operation
813 ++
814 ++ int Offset = TsPayloadOffset(Packet); // First byte after existing adaption field
815 ++
816 ++ if (ToLength <= 0)
817 ++ {
818 ++ // Remove adaption field
819 ++ Packet[3] = Packet[3] & ~TS_ADAPT_FIELD_EXISTS;
820 ++ return;
821 ++ }
822 ++
823 ++ // Set adaption field present
824 ++ Packet[3] = Packet[3] | TS_ADAPT_FIELD_EXISTS;
825 ++
826 ++ // Set new length of adaption field:
827 ++ Packet[4] = ToLength <= TS_SIZE-4 ? ToLength-1 : TS_SIZE-4-1;
828 ++
829 ++ if (Packet[4] == TS_SIZE-4-1)
830 ++ {
831 ++ // No more payload, remove payload flag
832 ++ Packet[3] = Packet[3] & ~TS_PAYLOAD_EXISTS;
833 ++ }
834 ++
835 ++ int NewPayload = TsPayloadOffset(Packet); // First byte after new adaption field
836 ++
837 ++ // Fill new adaption field
838 ++ if (Offset == 4 && Offset < NewPayload)
839 ++ Offset++; // skip adaptation_field_length
840 ++ if (Offset == 5 && Offset < NewPayload)
841 ++ Packet[Offset++] = 0; // various flags set to 0
842 ++ while (Offset < NewPayload)
843 ++ Packet[Offset++] = 0xff; // stuffing byte
844 ++}
845 ++
846 + // --- cPatPmtGenerator ------------------------------------------------------
847 +
848 +@@ -1765,2 +1801,343 @@
849 + return Processed;
850 + }
851 ++
852 ++// --- cNaluDumper ---------------------------------------------------------
853 ++
854 ++cNaluDumper::cNaluDumper()
855 ++{
856 ++ LastContinuityOutput = -1;
857 ++ reset();
858 ++}
859 ++
860 ++void cNaluDumper::reset()
861 ++{
862 ++ LastContinuityInput = -1;
863 ++ ContinuityOffset = 0;
864 ++ PesId = -1;
865 ++ PesOffset = 0;
866 ++ NaluFillState = NALU_NONE;
867 ++ NaluOffset = 0;
868 ++ History = 0xffffffff;
869 ++ DropAllPayload = false;
870 ++}
871 ++
872 ++void cNaluDumper::ProcessPayload(unsigned char *Payload, int size, bool PayloadStart, sPayloadInfo &Info)
873 ++{
874 ++ Info.DropPayloadStartBytes = 0;
875 ++ Info.DropPayloadEndBytes = 0;
876 ++ int LastKeepByte = -1;
877 ++
878 ++ if (PayloadStart)
879 ++ {
880 ++ History = 0xffffffff;
881 ++ PesId = -1;
882 ++ NaluFillState = NALU_NONE;
883 ++ }
884 ++
885 ++ for (int i=0; i<size; i++) {
886 ++ History = (History << 8) | Payload[i];
887 ++
888 ++ PesOffset++;
889 ++ NaluOffset++;
890 ++
891 ++ bool DropByte = false;
892 ++
893 ++ if (History >= 0x00000180 && History <= 0x000001FF)
894 ++ {
895 ++ // Start of PES packet
896 ++ PesId = History & 0xff;
897 ++ PesOffset = 0;
898 ++ NaluFillState = NALU_NONE;
899 ++ }
900 ++ else if (PesId >= 0xe0 && PesId <= 0xef // video stream
901 ++ && History >= 0x00000100 && History <= 0x0000017F) // NALU start code
902 ++ {
903 ++ int NaluId = History & 0xff;
904 ++ NaluOffset = 0;
905 ++ NaluFillState = ((NaluId & 0x1f) == 0x0c) ? NALU_FILL : NALU_NONE;
906 ++ }
907 ++
908 ++ if (PesId >= 0xe0 && PesId <= 0xef // video stream
909 ++ && PesOffset >= 1 && PesOffset <= 2)
910 ++ {
911 ++ Payload[i] = 0; // Zero out PES length field
912 ++ }
913 ++
914 ++ if (NaluFillState == NALU_FILL && NaluOffset > 0) // Within NALU fill data
915 ++ {
916 ++ // We expect a series of 0xff bytes terminated by a single 0x80 byte.
917 ++
918 ++ if (Payload[i] == 0xFF)
919 ++ {
920 ++ DropByte = true;
921 ++ }
922 ++ else if (Payload[i] == 0x80)
923 ++ {
924 ++ NaluFillState = NALU_TERM; // Last byte of NALU fill, next byte sets NaluFillEnd=true
925 ++ DropByte = true;
926 ++ }
927 ++ else // Invalid NALU fill
928 ++ {
929 ++ dsyslog("cNaluDumper: Unexpected NALU fill data: %02x", Payload[i]);
930 ++ NaluFillState = NALU_END;
931 ++ if (LastKeepByte == -1)
932 ++ {
933 ++ // Nalu fill from beginning of packet until last byte
934 ++ // packet start needs to be dropped
935 ++ Info.DropPayloadStartBytes = i;
936 ++ }
937 ++ }
938 ++ }
939 ++ else if (NaluFillState == NALU_TERM) // Within NALU fill data
940 ++ {
941 ++ // We are after the terminating 0x80 byte
942 ++ NaluFillState = NALU_END;
943 ++ if (LastKeepByte == -1)
944 ++ {
945 ++ // Nalu fill from beginning of packet until last byte
946 ++ // packet start needs to be dropped
947 ++ Info.DropPayloadStartBytes = i;
948 ++ }
949 ++ }
950 ++
951 ++ if (!DropByte)
952 ++ LastKeepByte = i; // Last useful byte
953 ++ }
954 ++
955 ++ Info.DropAllPayloadBytes = (LastKeepByte == -1);
956 ++ Info.DropPayloadEndBytes = size-1-LastKeepByte;
957 ++}
958 ++
959 ++bool cNaluDumper::ProcessTSPacket(unsigned char *Packet)
960 ++{
961 ++ bool HasAdaption = TsHasAdaptationField(Packet);
962 ++ bool HasPayload = TsHasPayload(Packet);
963 ++
964 ++ // Check continuity:
965 ++ int ContinuityInput = TsContinuityCounter(Packet);
966 ++ if (LastContinuityInput >= 0)
967 ++ {
968 ++ int NewContinuityInput = HasPayload ? (LastContinuityInput + 1) & TS_CONT_CNT_MASK : LastContinuityInput;
969 ++ int Offset = (NewContinuityInput - ContinuityInput) & TS_CONT_CNT_MASK;
970 ++ if (Offset > 0)
971 ++ dsyslog("cNaluDumper: TS continuity offset %i", Offset);
972 ++ if (Offset > ContinuityOffset)
973 ++ ContinuityOffset = Offset; // max if packets get dropped, otherwise always the current one.
974 ++ }
975 ++ LastContinuityInput = ContinuityInput;
976 ++
977 ++ if (HasPayload) {
978 ++ sPayloadInfo Info;
979 ++ int Offset = TsPayloadOffset(Packet);
980 ++ ProcessPayload(Packet + Offset, TS_SIZE - Offset, TsPayloadStart(Packet), Info);
981 ++
982 ++ if (DropAllPayload && !Info.DropAllPayloadBytes)
983 ++ {
984 ++ // Return from drop packet mode to normal mode
985 ++ DropAllPayload = false;
986 ++
987 ++ // Does the packet start with some remaining NALU fill data?
988 ++ if (Info.DropPayloadStartBytes > 0)
989 ++ {
990 ++ // Add these bytes as stuffing to the adaption field.
991 ++
992 ++ // Sample payload layout:
993 ++ // FF FF FF FF FF 80 00 00 01 xx xx xx xx
994 ++ // ^DropPayloadStartBytes
995 ++
996 ++ TsExtendAdaptionField(Packet, Offset - 4 + Info.DropPayloadStartBytes);
997 ++ }
998 ++ }
999 ++
1000 ++ bool DropThisPayload = DropAllPayload;
1001 ++
1002 ++ if (!DropAllPayload && Info.DropPayloadEndBytes > 0) // Payload ends with 0xff NALU Fill
1003 ++ {
1004 ++ // Last packet of useful data
1005 ++ // Do early termination of NALU fill data
1006 ++ Packet[TS_SIZE-1] = 0x80;
1007 ++ DropAllPayload = true;
1008 ++ // Drop all packets AFTER this one
1009 ++
1010 ++ // Since we already wrote the 0x80, we have to make sure that
1011 ++ // as soon as we stop dropping packets, any beginning NALU fill of next
1012 ++ // packet gets dumped. (see DropPayloadStartBytes above)
1013 ++ }
1014 ++
1015 ++ if (DropThisPayload && HasAdaption)
1016 ++ {
1017 ++ // Drop payload data, but keep adaption field data
1018 ++ TsExtendAdaptionField(Packet, TS_SIZE-4);
1019 ++ DropThisPayload = false;
1020 ++ }
1021 ++
1022 ++ if (DropThisPayload)
1023 ++ {
1024 ++ return true; // Drop packet
1025 ++ }
1026 ++ }
1027 ++
1028 ++ // Fix Continuity Counter and reproduce incoming offsets:
1029 ++ int NewContinuityOutput = TsHasPayload(Packet) ? (LastContinuityOutput + 1) & TS_CONT_CNT_MASK : LastContinuityOutput;
1030 ++ NewContinuityOutput = (NewContinuityOutput + ContinuityOffset) & TS_CONT_CNT_MASK;
1031 ++ TsSetContinuityCounter(Packet, NewContinuityOutput);
1032 ++ LastContinuityOutput = NewContinuityOutput;
1033 ++ ContinuityOffset = 0;
1034 ++
1035 ++ return false; // Keep packet
1036 ++}
1037 ++
1038 ++// --- cNaluStreamProcessor ---------------------------------------------------------
1039 ++
1040 ++cNaluStreamProcessor::cNaluStreamProcessor()
1041 ++{
1042 ++ pPatPmtParser = NULL;
1043 ++ vpid = -1;
1044 ++ data = NULL;
1045 ++ length = 0;
1046 ++ tempLength = 0;
1047 ++ tempLengthAtEnd = false;
1048 ++ TotalPackets = 0;
1049 ++ DroppedPackets = 0;
1050 ++}
1051 ++
1052 ++void cNaluStreamProcessor::PutBuffer(uchar *Data, int Length)
1053 ++{
1054 ++ if (length > 0)
1055 ++ esyslog("cNaluStreamProcessor::PutBuffer: New data before old data was processed!");
1056 ++
1057 ++ data = Data;
1058 ++ length = Length;
1059 ++}
1060 ++
1061 ++uchar* cNaluStreamProcessor::GetBuffer(int &OutLength)
1062 ++{
1063 ++ if (length <= 0)
1064 ++ {
1065 ++ // Need more data - quick exit
1066 ++ OutLength = 0;
1067 ++ return NULL;
1068 ++ }
1069 ++ if (tempLength > 0) // Data in temp buffer?
1070 ++ {
1071 ++ if (tempLengthAtEnd) // Data is at end, copy to beginning
1072 ++ {
1073 ++ // Overlapping src and dst!
1074 ++ for (int i=0; i<tempLength; i++)
1075 ++ tempBuffer[i] = tempBuffer[TS_SIZE-tempLength+i];
1076 ++ }
1077 ++ // Normalize TempBuffer fill
1078 ++ if (tempLength < TS_SIZE && length > 0)
1079 ++ {
1080 ++ int Size = min(TS_SIZE-tempLength, length);
1081 ++ memcpy(tempBuffer+tempLength, data, Size);
1082 ++ data += Size;
1083 ++ length -= Size;
1084 ++ tempLength += Size;
1085 ++ }
1086 ++ if (tempLength < TS_SIZE)
1087 ++ {
1088 ++ // All incoming data buffered, but need more data
1089 ++ tempLengthAtEnd = false;
1090 ++ OutLength = 0;
1091 ++ return NULL;
1092 ++ }
1093 ++ // Now: TempLength==TS_SIZE
1094 ++ if (tempBuffer[0] != TS_SYNC_BYTE)
1095 ++ {
1096 ++ // Need to sync on TS within temp buffer
1097 ++ int Skipped = 1;
1098 ++ while (Skipped < TS_SIZE && (tempBuffer[Skipped] != TS_SYNC_BYTE || (Skipped < length && data[Skipped] != TS_SYNC_BYTE)))
1099 ++ Skipped++;
1100 ++ esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped);
1101 ++ // Pass through skipped bytes
1102 ++ tempLengthAtEnd = true;
1103 ++ tempLength = TS_SIZE - Skipped; // may be 0, thats ok
1104 ++ OutLength = Skipped;
1105 ++ return tempBuffer;
1106 ++ }
1107 ++ // Now: TempBuffer is a TS packet
1108 ++ int Pid = TsPid(tempBuffer);
1109 ++ if (pPatPmtParser)
1110 ++ {
1111 ++ if (Pid == 0)
1112 ++ pPatPmtParser->ParsePat(tempBuffer, TS_SIZE);
1113 ++ else if (pPatPmtParser->IsPmtPid(Pid))
1114 ++ pPatPmtParser->ParsePmt(tempBuffer, TS_SIZE);
1115 ++ }
1116 ++
1117 ++ TotalPackets++;
1118 ++ bool Drop = false;
1119 ++ if (Pid == vpid || (pPatPmtParser && Pid == pPatPmtParser->Vpid() && pPatPmtParser->Vtype() == 0x1B))
1120 ++ Drop = NaluDumper.ProcessTSPacket(tempBuffer);
1121 ++ if (!Drop)
1122 ++ {
1123 ++ // Keep this packet, then continue with new data
1124 ++ tempLength = 0;
1125 ++ OutLength = TS_SIZE;
1126 ++ return tempBuffer;
1127 ++ }
1128 ++ // Drop TempBuffer
1129 ++ DroppedPackets++;
1130 ++ tempLength = 0;
1131 ++ }
1132 ++ // Now: TempLength==0, just process data/length
1133 ++
1134 ++ // Pointer to processed data / length:
1135 ++ uchar *Out = data;
1136 ++ uchar *OutEnd = Out;
1137 ++
1138 ++ while (length >= TS_SIZE)
1139 ++ {
1140 ++ if (data[0] != TS_SYNC_BYTE) {
1141 ++ int Skipped = 1;
1142 ++ while (Skipped < length && (data[Skipped] != TS_SYNC_BYTE || (length - Skipped > TS_SIZE && data[Skipped + TS_SIZE] != TS_SYNC_BYTE)))
1143 ++ Skipped++;
1144 ++ esyslog("ERROR: skipped %d bytes to sync on start of TS packet", Skipped);
1145 ++
1146 ++ // Pass through skipped bytes
1147 ++ if (OutEnd != data)
1148 ++ memcpy(OutEnd, data, Skipped);
1149 ++ OutEnd += Skipped;
1150 ++ continue;
1151 ++ }
1152 ++ // Now: Data starts with complete TS packet
1153 ++
1154 ++ int Pid = TsPid(data);
1155 ++ if (pPatPmtParser)
1156 ++ {
1157 ++ if (Pid == 0)
1158 ++ pPatPmtParser->ParsePat(data, TS_SIZE);
1159 ++ else if (pPatPmtParser->IsPmtPid(Pid))
1160 ++ pPatPmtParser->ParsePmt(data, TS_SIZE);
1161 ++ }
1162 ++
1163 ++ TotalPackets++;
1164 ++ bool Drop = false;
1165 ++ if (Pid == vpid || (pPatPmtParser && Pid == pPatPmtParser->Vpid() && pPatPmtParser->Vtype() == 0x1B))
1166 ++ Drop = NaluDumper.ProcessTSPacket(data);
1167 ++ if (!Drop)
1168 ++ {
1169 ++ if (OutEnd != data)
1170 ++ memcpy(OutEnd, data, TS_SIZE);
1171 ++ OutEnd += TS_SIZE;
1172 ++ }
1173 ++ else
1174 ++ {
1175 ++ DroppedPackets++;
1176 ++ }
1177 ++ data += TS_SIZE;
1178 ++ length -= TS_SIZE;
1179 ++ }
1180 ++ // Now: Less than a packet remains.
1181 ++ if (length > 0)
1182 ++ {
1183 ++ // copy remains into temp buffer
1184 ++ memcpy(tempBuffer, data, length);
1185 ++ tempLength = length;
1186 ++ tempLengthAtEnd = false;
1187 ++ length = 0;
1188 ++ }
1189 ++ OutLength = (OutEnd - Out);
1190 ++ return OutLength > 0 ? Out : NULL;
1191 ++}
1192 +diff -a -U 2 -r a/remux.h b/remux.h
1193 +--- a/remux.h
1194 ++++ b/remux.h
1195 +@@ -65,4 +65,9 @@
1196 + }
1197 +
1198 ++inline bool TsSetPayload(const uchar *p)
1199 ++{
1200 ++ return p[3] & TS_PAYLOAD_EXISTS;
1201 ++}
1202 ++
1203 + inline bool TsHasAdaptationField(const uchar *p)
1204 + {
1205 +@@ -156,4 +161,5 @@
1206 + void TsSetPts(uchar *p, int l, int64_t Pts);
1207 + void TsSetDts(uchar *p, int l, int64_t Dts);
1208 ++void TsExtendAdaptionField(unsigned char *Packet, int ToLength);
1209 +
1210 + // Some PES handling tools:
1211 +@@ -550,3 +556,77 @@
1212 + };
1213 +
1214 ++
1215 ++#define PATCH_NALUDUMP 100
1216 ++
1217 ++class cNaluDumper {
1218 ++ unsigned int History;
1219 ++
1220 ++ int LastContinuityInput;
1221 ++ int LastContinuityOutput;
1222 ++ int ContinuityOffset;
1223 ++
1224 ++ bool DropAllPayload;
1225 ++
1226 ++ int PesId;
1227 ++ int PesOffset;
1228 ++
1229 ++ int NaluOffset;
1230 ++
1231 ++ enum eNaluFillState {
1232 ++ NALU_NONE=0, // currently not NALU fill stream
1233 ++ NALU_FILL, // Within NALU fill stream, 0xff bytes and NALU start code in byte 0
1234 ++ NALU_TERM, // Within NALU fill stream, read 0x80 terminating byte
1235 ++ NALU_END // Beyond end of NALU fill stream, expecting 0x00 0x00 0x01 now
1236 ++ };
1237 ++
1238 ++ eNaluFillState NaluFillState;
1239 ++
1240 ++ struct sPayloadInfo {
1241 ++ int DropPayloadStartBytes;
1242 ++ int DropPayloadEndBytes;
1243 ++ bool DropAllPayloadBytes;
1244 ++ };
1245 ++
1246 ++public:
1247 ++ cNaluDumper();
1248 ++
1249 ++ void reset();
1250 ++
1251 ++ // Single packet interface:
1252 ++ bool ProcessTSPacket(unsigned char *Packet);
1253 ++
1254 ++private:
1255 ++ void ProcessPayload(unsigned char *Payload, int size, bool PayloadStart, sPayloadInfo &Info);
1256 ++};
1257 ++
1258 ++class cNaluStreamProcessor {
1259 ++ //Buffer stream interface:
1260 ++ int vpid;
1261 ++ uchar *data;
1262 ++ int length;
1263 ++ uchar tempBuffer[TS_SIZE];
1264 ++ int tempLength;
1265 ++ bool tempLengthAtEnd;
1266 ++ cPatPmtParser *pPatPmtParser;
1267 ++ cNaluDumper NaluDumper;
1268 ++
1269 ++ long long int TotalPackets;
1270 ++ long long int DroppedPackets;
1271 ++public:
1272 ++ cNaluStreamProcessor();
1273 ++
1274 ++ void SetPid(int VPid) { vpid = VPid; }
1275 ++ void SetPatPmtParser(cPatPmtParser *_pPatPmtParser) { pPatPmtParser = _pPatPmtParser; }
1276 ++ // Set either a PID or set a pointer to an PatPmtParser that will detect _one_ PID
1277 ++
1278 ++ void PutBuffer(uchar *Data, int Length);
1279 ++ // Add new data to be processed. Data must be valid until Get() returns NULL.
1280 ++ uchar* GetBuffer(int &OutLength);
1281 ++ // Returns filtered data, or NULL/0 to indicate that all data from Put() was processed
1282 ++ // or buffered.
1283 ++
1284 ++ long long int GetTotalPackets() { return TotalPackets; }
1285 ++ long long int GetDroppedPackets() { return DroppedPackets; }
1286 ++};
1287 ++
1288 + #endif // __REMUX_H
1289
1290 diff --git a/media-video/vdr/files/vdr-2.6.1_pinplugin.patch b/media-video/vdr/files/vdr-2.6.1_pinplugin.patch
1291 new file mode 100644
1292 index 000000000000..2552f9e7109f
1293 --- /dev/null
1294 +++ b/media-video/vdr/files/vdr-2.6.1_pinplugin.patch
1295 @@ -0,0 +1,447 @@
1296 +original vdr-pinplugin_vdr-2.3.1.diff
1297 +rebased for media-video/vdr-2.6.1
1298 +
1299 +Signed-off-by: Christian Kunkel <ch.kunkel@×××.de> ( 2021 Feb 12 )
1300 +Reviewed-by: Martin Dummer <martin.dummer@×××.net> ( 2022-06-22 )
1301 +diff -Naur a/Makefile b/Makefile
1302 +--- a/Makefile 2022-02-02 10:56:43.000000000 +0100
1303 ++++ b/Makefile 2022-06-20 08:08:11.346956148 +0200
1304 +@@ -351,7 +351,7 @@
1305 + clean:
1306 + @$(MAKE) --no-print-directory -C $(LSIDIR) clean
1307 + @-rm -f $(OBJS) $(DEPFILE) vdr vdr.pc core* *~
1308 +- @-rm -rf $(LOCALEDIR) $(PODIR)/*.mo $(PODIR)/*.pot
1309 ++ @-rm -rf $(LOCALEDIR) $(PODIR)/*~ $(PODIR)/*.mo $(PODIR)/*.pot
1310 + @-rm -rf include
1311 + @-rm -rf srcdoc
1312 + CLEAN: clean
1313 +diff -Naur a/device.c b/device.c
1314 +--- a/device.c 2022-02-02 10:56:43.000000000 +0100
1315 ++++ b/device.c 2022-06-20 08:08:11.346956148 +0200
1316 +@@ -839,6 +839,7 @@
1317 + const cChannel *Channel;
1318 + while ((Channel = Channels->GetByNumber(n, Direction)) != NULL) {
1319 + // try only channels which are currently available
1320 ++ if (!cStatus::MsgChannelProtected(0, Channel)) // PIN PATCH
1321 + if (GetDevice(Channel, LIVEPRIORITY, true, true))
1322 + break;
1323 + n = Channel->Number() + Direction;
1324 +@@ -860,6 +861,12 @@
1325 +
1326 + eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
1327 + {
1328 ++ // I hope 'LiveView = false' indicates a channel switch for recording, // PIN PATCH
1329 ++ // I really don't know, but it works ... // PIN PATCH
1330 ++
1331 ++ if (LiveView && cStatus::MsgChannelProtected(this, Channel)) // PIN PATCH
1332 ++ return scrNotAvailable; // PIN PATCH
1333 ++
1334 + cMutexLock MutexLock(&mutexChannel); // to avoid a race between SVDRP CHAN and HasProgramme()
1335 + cStatus::MsgChannelSwitch(this, 0, LiveView);
1336 +
1337 +diff -Naur a/menu.c b/menu.c
1338 +--- a/menu.c 2022-02-02 10:56:43.000000000 +0100
1339 ++++ b/menu.c 2022-06-20 08:08:11.346956148 +0200
1340 +@@ -1035,6 +1035,18 @@
1341 + Add(new cMenuEditBitItem( tr("VPS"), &data.flags, tfVps));
1342 + Add(new cMenuEditIntItem( tr("Priority"), &data.priority, 0, MAXPRIORITY));
1343 + Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME));
1344 ++
1345 ++ // PIN PATCH
1346 ++ if (cOsd::pinValid || !data.fskProtection) Add(new cMenuEditBoolItem(tr("Childlock"),&data.fskProtection));
1347 ++ else {
1348 ++ char* buf = 0;
1349 ++ int res = 0;
1350 ++ res = asprintf(&buf, "%s\t%s", tr("Childlock"), data.fskProtection ? tr("yes") : tr("no"));
1351 ++ if (res < 0) ; // memory problems :o
1352 ++ Add(new cOsdItem(buf));
1353 ++ free(buf);
1354 ++ }
1355 ++
1356 + Add(file = new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file)));
1357 + SetFirstDayItem();
1358 + SetPatternItem(true);
1359 +@@ -3130,7 +3142,8 @@
1360 + }
1361 + }
1362 + }
1363 +- if (*Item->Text() && !LastDir) {
1364 ++ if (*Item->Text() && !LastDir
1365 ++ && (!cStatus::MsgReplayProtected(Item->Recording(), Item->Name(), base, Item->IsDirectory(), true))) { // PIN PATCH
1366 + Add(Item);
1367 + LastItem = Item;
1368 + if (Item->IsDirectory())
1369 +@@ -3201,6 +3214,9 @@
1370 + {
1371 + cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
1372 + if (ri) {
1373 ++ if (cStatus::MsgReplayProtected(ri->Recording(), ri->Name(), base,
1374 ++ ri->IsDirectory()) == true) // PIN PATCH
1375 ++ return osContinue;
1376 + if (ri->IsDirectory())
1377 + Open();
1378 + else {
1379 +@@ -4506,28 +4522,32 @@
1380 +
1381 + // Basic menu items:
1382 +
1383 +- Add(new cOsdItem(hk(tr("Schedule")), osSchedule));
1384 +- Add(new cOsdItem(hk(tr("Channels")), osChannels));
1385 +- Add(new cOsdItem(hk(tr("Timers")), osTimers));
1386 +- Add(new cOsdItem(hk(tr("Recordings")), osRecordings));
1387 ++ // PIN PATCH
1388 ++ if (!cStatus::MsgMenuItemProtected("Schedule", true)) Add(new cOsdItem(hk(tr("Schedule")), osSchedule));
1389 ++ if (!cStatus::MsgMenuItemProtected("Channels", true)) Add(new cOsdItem(hk(tr("Channels")), osChannels));
1390 ++ if (!cStatus::MsgMenuItemProtected("Timers", true)) Add(new cOsdItem(hk(tr("Timers")), osTimers));
1391 ++ if (!cStatus::MsgMenuItemProtected("Recordings", true)) Add(new cOsdItem(hk(tr("Recordings")), osRecordings));
1392 +
1393 + // Plugins:
1394 +
1395 + for (int i = 0; ; i++) {
1396 + cPlugin *p = cPluginManager::GetPlugin(i);
1397 + if (p) {
1398 ++ if (!cStatus::MsgPluginProtected(p, true)) { // PIN PATCH
1399 + const char *item = p->MainMenuEntry();
1400 + if (item)
1401 + Add(new cMenuPluginItem(hk(item), i));
1402 + }
1403 ++ }
1404 + else
1405 + break;
1406 + }
1407 +
1408 + // More basic menu items:
1409 +
1410 +- Add(new cOsdItem(hk(tr("Setup")), osSetup));
1411 ++ if (!cStatus::MsgMenuItemProtected("Setup", true)) Add(new cOsdItem(hk(tr("Setup")), osSetup)); // PIN PATCH
1412 + if (Commands.Count())
1413 ++ if (!cStatus::MsgMenuItemProtected("Commands", true)) // PIN PATCH
1414 + Add(new cOsdItem(hk(tr("Commands")), osCommands));
1415 +
1416 + Update(true);
1417 +@@ -4600,6 +4620,14 @@
1418 + eOSState state = cOsdMenu::ProcessKey(Key);
1419 + HadSubMenu |= HasSubMenu();
1420 +
1421 ++ // > PIN PATCH
1422 ++ cOsdItem* item = Get(Current());
1423 ++
1424 ++ if (item && item->Text() && state != osContinue && state != osUnknown && state != osBack)
1425 ++ if (cStatus::MsgMenuItemProtected(item->Text()))
1426 ++ return osContinue;
1427 ++ // PIN PATCH <
1428 ++
1429 + switch (state) {
1430 + case osSchedule: return AddSubMenu(new cMenuSchedule);
1431 + case osChannels: return AddSubMenu(new cMenuChannels);
1432 +@@ -4624,6 +4652,7 @@
1433 + if (item) {
1434 + cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex());
1435 + if (p) {
1436 ++ if (!cStatus::MsgPluginProtected(p)) { // PIN PATCH
1437 + cOsdObject *menu = p->MainMenuAction();
1438 + if (menu) {
1439 + if (menu->IsMenu())
1440 +@@ -4635,6 +4664,7 @@
1441 + }
1442 + }
1443 + }
1444 ++ }
1445 + state = osEnd;
1446 + }
1447 + break;
1448 +@@ -4814,6 +4844,7 @@
1449 + Channel = Direction > 0 ? Channels->Next(Channel) : Channels->Prev(Channel);
1450 + if (!Channel && Setup.ChannelsWrap)
1451 + Channel = Direction > 0 ? Channels->First() : Channels->Last();
1452 ++ if (!cStatus::MsgChannelProtected(0, Channel)) // PIN PATCH
1453 + if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, LIVEPRIORITY, true, true))
1454 + return Channel;
1455 + }
1456 +@@ -5491,6 +5522,7 @@
1457 + for (int i = 0; i < MAXRECORDCONTROLS; i++) {
1458 + if (!RecordControls[i]) {
1459 + RecordControls[i] = new cRecordControl(device, Timers, Timer, Pause);
1460 ++ cStatus::MsgRecordingFile(RecordControls[i]->FileName()); // PIN PATCH
1461 + return RecordControls[i]->Process(time(NULL));
1462 + }
1463 + }
1464 +diff -Naur a/osd.c b/osd.c
1465 +--- a/osd.c 2022-02-02 10:56:43.000000000 +0100
1466 ++++ b/osd.c 2022-06-20 08:08:11.346956148 +0200
1467 +@@ -1844,6 +1844,7 @@
1468 + cSize cOsd::maxPixmapSize(INT_MAX, INT_MAX);
1469 + cVector<cOsd *> cOsd::Osds;
1470 + cMutex cOsd::mutex;
1471 ++bool cOsd::pinValid = false; // PIN PATCH
1472 +
1473 + cOsd::cOsd(int Left, int Top, uint Level)
1474 + {
1475 +diff -Naur a/osd.h b/osd.h
1476 +--- a/osd.h 2022-02-02 10:56:43.000000000 +0100
1477 ++++ b/osd.h 2022-06-20 08:08:11.346956148 +0200
1478 +@@ -957,6 +957,7 @@
1479 + ///<
1480 + ///< If a plugin uses a derived cPixmap implementation, it needs to use that
1481 + ///< type instead of cPixmapMemory.
1482 ++ static bool pinValid; // PIN PATCH
1483 + };
1484 +
1485 + #define MAXOSDIMAGES 64
1486 +diff -Naur a/status.c b/status.c
1487 +--- a/status.c 2022-02-02 10:56:43.000000000 +0100
1488 ++++ b/status.c 2022-06-20 08:08:11.346956148 +0200
1489 +@@ -136,3 +136,55 @@
1490 + for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
1491 + sm->OsdProgramme(PresentTime, PresentTitle, PresentSubtitle, FollowingTime, FollowingTitle, FollowingSubtitle);
1492 + }
1493 ++
1494 ++bool cStatus::MsgChannelProtected(const cDevice* Device, const cChannel* Channel) // PIN PATCH
1495 ++{
1496 ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
1497 ++ if (sm->ChannelProtected(Device, Channel) == true)
1498 ++ return true;
1499 ++
1500 ++ return false;
1501 ++}
1502 ++
1503 ++bool cStatus::MsgReplayProtected(const cRecording* Recording, const char* Name,
1504 ++ const char* Base, bool isDirectory, int menuView) // PIN PATCH
1505 ++{
1506 ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
1507 ++ if (sm->ReplayProtected(Recording, Name, Base, isDirectory, menuView) == true)
1508 ++ return true;
1509 ++ return false;
1510 ++}
1511 ++
1512 ++void cStatus::MsgRecordingFile(const char* FileName)
1513 ++{
1514 ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) // PIN PATCH
1515 ++ sm->RecordingFile(FileName);
1516 ++}
1517 ++
1518 ++void cStatus::MsgTimerCreation(cTimer* Timer, const cEvent *Event)
1519 ++{
1520 ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) // PIN PATCH
1521 ++ sm->TimerCreation(Timer, Event);
1522 ++}
1523 ++
1524 ++bool cStatus::MsgPluginProtected(cPlugin* Plugin, int menuView) // PIN PATCH
1525 ++{
1526 ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
1527 ++ if (sm->PluginProtected(Plugin, menuView) == true)
1528 ++ return true;
1529 ++ return false;
1530 ++}
1531 ++
1532 ++void cStatus::MsgUserAction(const eKeys key) // PIN PATCH
1533 ++{
1534 ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
1535 ++ sm->UserAction(key);
1536 ++}
1537 ++
1538 ++bool cStatus::MsgMenuItemProtected(const char* Name, int menuView) // PIN PATCH
1539 ++{
1540 ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
1541 ++ if (sm->MenuItemProtected(Name, menuView) == true)
1542 ++ return true;
1543 ++ return false;
1544 ++}
1545 +diff -Naur a/status.h b/status.h
1546 +--- a/status.h 2022-02-02 10:56:43.000000000 +0100
1547 ++++ b/status.h 2022-06-20 08:08:11.350956230 +0200
1548 +@@ -14,6 +14,7 @@
1549 + #include "device.h"
1550 + #include "player.h"
1551 + #include "tools.h"
1552 ++#include "plugin.h"
1553 +
1554 + // Several member functions of the following classes are called with a pointer to
1555 + // an object from a global list (cTimer, cChannel, cRecording or cEvent). In these
1556 +@@ -99,6 +100,22 @@
1557 + // The OSD displays the single line Text with the current channel information.
1558 + virtual void OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle) {}
1559 + // The OSD displays the given programme information.
1560 ++ virtual bool ChannelProtected(const cDevice *Device, const cChannel* Channel) { return false; } // PIN PATCH
1561 ++ // Checks if a channel is protected.
1562 ++ virtual bool ReplayProtected(const cRecording* Recording, const char* Name,
1563 ++ const char* Base, bool isDirectory, int menuView = false) { return false; } // PIN PATCH
1564 ++ // Checks if a recording is protected.
1565 ++ virtual void RecordingFile(const char* FileName) {} // PIN PATCH
1566 ++ // The given DVB device has started recording to FileName. FileName is the name of the
1567 ++ // recording directory
1568 ++ virtual void TimerCreation(cTimer* Timer, const cEvent *Event) {} // PIN PATCH
1569 ++ // The given timer is created
1570 ++ virtual bool PluginProtected(cPlugin* Plugin, int menuView = false) { return false; } // PIN PATCH
1571 ++ // Checks if a plugin is protected.
1572 ++ virtual void UserAction(const eKeys key) {} // PIN PATCH
1573 ++ // report user action
1574 ++ virtual bool MenuItemProtected(const char* Name, int menuView = false) { return false; } // PIN PATCH
1575 ++
1576 + public:
1577 + cStatus(void);
1578 + virtual ~cStatus();
1579 +@@ -122,6 +139,14 @@
1580 + static void MsgOsdTextItem(const char *Text, bool Scroll = false);
1581 + static void MsgOsdChannel(const char *Text);
1582 + static void MsgOsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle);
1583 ++ static bool MsgChannelProtected(const cDevice* Device, const cChannel* Channel); // PIN PATCH
1584 ++ static bool MsgReplayProtected(const cRecording* Recording, const char* Name,
1585 ++ const char* Base, bool isDirectory, int menuView = false); // PIN PATCH
1586 ++ static void MsgRecordingFile(const char* FileName); // PIN PATCH
1587 ++ static void MsgTimerCreation(cTimer* Timer, const cEvent *Event); // PIN PATCH
1588 ++ static bool MsgPluginProtected(cPlugin* Plugin, int menuView = false); // PIN PATCH
1589 ++ static void MsgUserAction(const eKeys key); // PIN PATCH
1590 ++ static bool MsgMenuItemProtected(const char* Name, int menuView = false); // PIN PATCH
1591 + };
1592 +
1593 + #endif //__STATUS_H
1594 +diff -Naur a/timers.c b/timers.c
1595 +--- a/timers.c 2022-02-02 10:56:43.000000000 +0100
1596 ++++ b/timers.c 2022-06-20 08:14:07.898392829 +0200
1597 +@@ -81,6 +81,7 @@
1598 + stop -= 2400;
1599 + priority = Pause ? Setup.PausePriority : Setup.DefaultPriority;
1600 + lifetime = Pause ? Setup.PauseLifetime : Setup.DefaultLifetime;
1601 ++ fskProtection = 0; // PIN PATCH
1602 + if (Instant && channel)
1603 + snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", *Setup.NameInstantRecord ? Setup.NameInstantRecord : channel->Name());
1604 + }
1605 +@@ -212,11 +213,13 @@
1606 + stop -= 2400;
1607 + priority = PatternTimer ? PatternTimer->Priority() : Setup.DefaultPriority;
1608 + lifetime = PatternTimer ? PatternTimer->Lifetime() : Setup.DefaultLifetime;
1609 ++ fskProtection = 0; // PIN PATCH
1610 + if (!FileName)
1611 + FileName = Event->Title();
1612 + if (!isempty(FileName))
1613 + Utf8Strn0Cpy(file, FileName, sizeof(file));
1614 + SetEvent(Event);
1615 ++ cStatus::MsgTimerCreation(this, Event); // PIN PATCH
1616 + }
1617 +
1618 + cTimer::cTimer(const cTimer &Timer)
1619 +@@ -255,6 +258,7 @@
1620 + stop = Timer.stop;
1621 + priority = Timer.priority;
1622 + lifetime = Timer.lifetime;
1623 ++ fskProtection = Timer.fskProtection; // PIN PATCH
1624 + strncpy(pattern, Timer.pattern, sizeof(pattern));
1625 + strncpy(file, Timer.file, sizeof(file));
1626 + free(aux);
1627 +@@ -484,6 +488,7 @@
1628 + result = false;
1629 + }
1630 + }
1631 ++ fskProtection = aux && strstr(aux, "<pin-plugin><protected>yes</protected></pin-plugin>"); // PIN PATCH
1632 + free(channelbuffer);
1633 + free(daybuffer);
1634 + free(filebuffer);
1635 +@@ -1037,6 +1042,36 @@
1636 + Matches(); // refresh start and end time
1637 + }
1638 +
1639 ++void cTimer::SetFskProtection(int aFlag) // PIN PATCH
1640 ++{
1641 ++ char* p;
1642 ++ char* tmp = 0;
1643 ++ int res = 0;
1644 ++
1645 ++ fskProtection = aFlag;
1646 ++
1647 ++ if (fskProtection && (!aux || !strstr(aux, "<pin-plugin><protected>yes</protected></pin-plugin>")))
1648 ++ {
1649 ++ // add protection info to aux
1650 ++
1651 ++ if (aux) { tmp = strdup(aux); free(aux); }
1652 ++ res = asprintf(&aux, "%s<pin-plugin><protected>yes</protected></pin-plugin>", tmp ? tmp : "");
1653 ++ }
1654 ++ else if (!fskProtection && aux && (p = strstr(aux, "<pin-plugin><protected>yes</protected></pin-plugin>")))
1655 ++ {
1656 ++ // remove protection info from aux
1657 ++
1658 ++ res = asprintf(&tmp, "%.*s%s", (int)(p-aux), aux, p+strlen("<pin-plugin><protected>yes</protected></pin-plugin>"));
1659 ++ free(aux);
1660 ++ aux = strdup(tmp);
1661 ++ }
1662 ++
1663 ++ if (res < 0) ; // memory problems :o
1664 ++
1665 ++ if (tmp)
1666 ++ free(tmp);
1667 ++}
1668 ++
1669 + // --- cTimers ---------------------------------------------------------------
1670 +
1671 + cTimers cTimers::timers;
1672 +diff -Naur a/timers.h b/timers.h
1673 +--- a/timers.h 2022-02-02 10:56:43.000000000 +0100
1674 ++++ b/timers.h 2022-06-20 08:08:11.350956230 +0200
1675 +@@ -45,6 +45,7 @@
1676 + int start; ///< the start and stop time of this timer as given by the user,
1677 + int stop; ///< in the form hhmm, with hh (00..23) and mm (00..59) added as hh*100+mm
1678 + int priority;
1679 ++ int fskProtection; // PIN PATCH
1680 + int lifetime;
1681 + mutable char pattern[NAME_MAX * 2 + 1]; // same size as 'file', to be able to initially fill 'pattern' with 'file' in the 'Edit timer' menu
1682 + mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long
1683 +@@ -70,6 +71,7 @@
1684 + int Start(void) const { return start; }
1685 + int Stop(void) const { return stop; }
1686 + int Priority(void) const { return priority; }
1687 ++ int FskProtection(void) const { return fskProtection; } // PIN PATCH
1688 + int Lifetime(void) const { return lifetime; }
1689 + const char *Pattern(void) const { return pattern; }
1690 + const char *File(void) const { return file; }
1691 +@@ -120,6 +122,7 @@
1692 + void SetRemote(const char *Remote);
1693 + void SetDeferred(int Seconds);
1694 + void SetFlags(uint Flags);
1695 ++ void SetFskProtection(int aFlag); // PIN PATCH
1696 + void ClrFlags(uint Flags);
1697 + void InvFlags(uint Flags);
1698 + bool HasFlags(uint Flags) const;
1699 +diff -Naur a/vdr.c b/vdr.c
1700 +--- a/vdr.c 2022-02-02 10:56:43.000000000 +0100
1701 ++++ b/vdr.c 2022-06-20 08:08:11.350956230 +0200
1702 +@@ -71,6 +71,7 @@
1703 + #include "tools.h"
1704 + #include "transfer.h"
1705 + #include "videodir.h"
1706 ++#include "status.h" // PIN PATCH
1707 +
1708 + #define MINCHANNELWAIT 10 // seconds to wait between failed channel switchings
1709 + #define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping
1710 +@@ -1210,6 +1211,7 @@
1711 + if (!Menu)
1712 + Interact = Control = cControl::Control(ControlMutexLock);
1713 + if (ISREALKEY(key)) {
1714 ++ cStatus::MsgUserAction(key); // PIN PATCH
1715 + EITScanner.Activity();
1716 + // Cancel shutdown countdown:
1717 + if (ShutdownHandler.countdown)
1718 +@@ -1282,10 +1284,12 @@
1719 + Control->Hide();
1720 + cPlugin *plugin = cPluginManager::GetPlugin(PluginName);
1721 + if (plugin) {
1722 ++ if (!cStatus::MsgPluginProtected(plugin)) { // PIN PATCH
1723 + Menu = plugin->MainMenuAction();
1724 + if (Menu)
1725 + Menu->Show();
1726 + }
1727 ++ }
1728 + else
1729 + esyslog("ERROR: unknown plugin '%s'", PluginName);
1730 + }
1731 +@@ -1505,9 +1509,11 @@
1732 + case kPlay:
1733 + if (cReplayControl::LastReplayed()) {
1734 + Control = NULL;
1735 ++ if (cStatus::MsgReplayProtected(0, cReplayControl::LastReplayed(), 0, false) == false) { // PIN PATCH
1736 + cControl::Shutdown();
1737 + cControl::Launch(new cReplayControl);
1738 + }
1739 ++ }
1740 + else
1741 + DirectMainFunction(osRecordings); // no last viewed recording, so enter the Recordings menu
1742 + break;
1743
1744 diff --git a/media-video/vdr/vdr-2.6.1.ebuild b/media-video/vdr/vdr-2.6.1.ebuild
1745 new file mode 100644
1746 index 000000000000..4300433dcc75
1747 --- /dev/null
1748 +++ b/media-video/vdr/vdr-2.6.1.ebuild
1749 @@ -0,0 +1,197 @@
1750 +# Copyright 2021-2022 Gentoo Authors
1751 +# Distributed under the terms of the GNU General Public License v2
1752 +
1753 +EAPI=8
1754 +
1755 +inherit flag-o-matic strip-linguas toolchain-funcs user-info
1756 +
1757 +DESCRIPTION="Video Disk Recorder - turns a pc into a powerful set top box for DVB"
1758 +HOMEPAGE="http://www.tvdr.de/"
1759 +SRC_URI="http://git.tvdr.de/?p=vdr.git;a=snapshot;h=refs/tags/${PV};sf=tbz2 -> ${P}.tbz2
1760 + menuorg? ( https://github.com/vdr-projects/vdr-plugin-menuorg/raw/master/vdr-patch/vdr-menuorg-2.3.x.diff )
1761 + ttxtsubs? ( https://md11.it.cx/download/${PN}/${PN}-2.6.1_ttxtsubs_v2.patch )"
1762 +
1763 +LICENSE="GPL-2+"
1764 +SLOT="0"
1765 +KEYWORDS="~amd64 ~arm ~arm64 ~ppc ~x86"
1766 +IUSE="bidi debug demoplugins html keyboard mainmenuhooks menuorg naludump permashift pinplugin systemd ttxtsubs verbose"
1767 +
1768 +COMMON_DEPEND="
1769 + media-libs/fontconfig
1770 + media-libs/freetype
1771 + media-libs/libjpeg-turbo
1772 + sys-libs/libcap"
1773 +DEPEND="${COMMON_DEPEND}
1774 + >=virtual/linuxtv-dvb-headers-5.3"
1775 +RDEPEND="${COMMON_DEPEND}
1776 + dev-lang/perl
1777 + media-tv/gentoo-vdr-scripts
1778 + media-fonts/corefonts
1779 + bidi? ( dev-libs/fribidi )
1780 + systemd? ( sys-apps/systemd )"
1781 +BDEPEND="
1782 + acct-user/vdr
1783 + sys-devel/gettext"
1784 +
1785 +REQUIRED_USE="permashift? ( !naludump !pinplugin )"
1786 +
1787 +CONF_DIR="/etc/vdr"
1788 +CAP_FILE="${S}/capabilities.sh"
1789 +CAPS="# Capabilities of the vdr-executable for use by startscript etc."
1790 +
1791 +pkg_setup() {
1792 + use debug && append-flags -g
1793 +
1794 + PLUGIN_LIBDIR="/usr/$(get_libdir)/vdr/plugins"
1795 + VIDEO_DIR="$(egethome vdr)/video"
1796 +
1797 + tc-export CC CXX AR
1798 +}
1799 +
1800 +add_cap() {
1801 + local arg
1802 + for arg; do
1803 + CAPS="${CAPS}\n${arg}=1"
1804 + done
1805 +}
1806 +
1807 +lang_po() {
1808 + LING_PO=$( ls ${S}/po | sed -e "s:.po::g" | cut -d_ -f1 | tr \\\012 ' ' )
1809 +}
1810 +
1811 +src_prepare() {
1812 + # apply maintenance-patches
1813 + ebegin "Changing paths for gentoo"
1814 +
1815 + local DVBDIR=/usr/include
1816 + local i
1817 + for i in ${DVB_HEADER_PATH} /usr/include/v4l-dvb-hg /usr/include; do
1818 + [[ -d ${i} ]] || continue
1819 + if [[ -f ${i}/linux/dvb/dmx.h ]]; then
1820 + einfo "Found DVB header files in ${i}"
1821 + DVBDIR=${i}
1822 + break
1823 + fi
1824 + done
1825 +
1826 + # checking for s2api headers
1827 + local api_version
1828 + api_version=$(awk -F' ' '/define DVB_API_VERSION / {print $3}' "${DVBDIR}"/linux/dvb/version.h)
1829 + api_version=${api_version}*$(awk -F' ' '/define DVB_API_VERSION_MINOR / {print $3}' "${DVBDIR}"/linux/dvb/version.h)
1830 +
1831 + if [[ ${api_version:-0} -lt 5*3 ]]; then
1832 + eerror "DVB header files do not contain s2api support or too old for ${P}"
1833 + eerror "You cannot compile VDR against old dvb-header"
1834 + die "DVB headers too old"
1835 + fi
1836 +
1837 + cat > Make.config <<-EOT || die "cannot write to Make.config"
1838 + #
1839 + # Generated by ebuild ${PF}
1840 + #
1841 + PREFIX = /usr
1842 + DVBDIR = ${DVBDIR}
1843 + PLUGINLIBDIR = ${PLUGIN_LIBDIR}
1844 + CONFDIR = ${CONF_DIR}
1845 + ARGSDIR = \$(CONFDIR)/conf.d
1846 + VIDEODIR = ${VIDEO_DIR}
1847 + LOCDIR = \$(PREFIX)/share/locale
1848 + INCDIR = \$(PREFIX)/include
1849 +
1850 + DEFINES += -DCONFDIR=\"\$(CONFDIR)\"
1851 + INCLUDES += -I\$(DVBDIR)
1852 +
1853 + # >=vdr-1.7.36-r1; parameter only used for compiletime on vdr
1854 + # PLUGINLIBDIR (plugin Makefile old) = LIBDIR (plugin Makefile new)
1855 + LIBDIR = ${PLUGIN_LIBDIR}
1856 + PCDIR = /usr/$(get_libdir)/pkgconfig
1857 +
1858 + EOT
1859 + eend 0
1860 +
1861 + eapply "${FILESDIR}/${PN}-2.4.6_gentoo.patch"
1862 + use demoplugins || eapply "${FILESDIR}/vdr-2.4_remove_plugins.patch"
1863 + eapply "${FILESDIR}/${PN}-2.4.6_makefile-variables.patch"
1864 +
1865 + # fix clang/LLVM compile
1866 + eapply "${FILESDIR}/${PN}-2.4.6_clang.patch"
1867 +
1868 + use naludump && eapply "${FILESDIR}/${P}_naludump.patch"
1869 + use permashift && eapply "${FILESDIR}/${P}-patch-for-permashift.patch"
1870 + use pinplugin && eapply "${FILESDIR}/${P}_pinplugin.patch"
1871 + use ttxtsubs && eapply "${DISTDIR}/${P}_ttxtsubs_v2.patch"
1872 + use menuorg && eapply "${DISTDIR}/vdr-menuorg-2.3.x.diff"
1873 + use mainmenuhooks && eapply "${FILESDIR}/${PN}-2.4.1_mainmenuhook-1.0.1.patch"
1874 +
1875 + add_cap CAP_UTF8 \
1876 + CAP_IRCTRL_RUNTIME_PARAM \
1877 + CAP_VFAT_RUNTIME_PARAM \
1878 + CAP_CHUID \
1879 + CAP_SHUTDOWN_AUTO_RETRY
1880 +
1881 + echo -e ${CAPS} > "${CAP_FILE}" || die "cannot write to CAP_FILE"
1882 +
1883 + # LINGUAS support
1884 + einfo "\n \t VDR supports the LINGUAS values"
1885 +
1886 + lang_po
1887 +
1888 + einfo "\t Please set one of this values in your sytem make.conf"
1889 + einfo "\t LINGUAS=\"${LING_PO}\"\n"
1890 +
1891 + if [[ -z ${LINGUAS} ]]; then
1892 + einfo "\n \t No values in LINGUAS="
1893 + einfo "\t You will get only english text on OSD \n"
1894 + fi
1895 +
1896 + strip-linguas ${LING_PO} en
1897 +
1898 + default
1899 +}
1900 +
1901 +src_configure() {
1902 + # support languages, written from right to left
1903 + export "BIDI=$(usex bidi 1 0)"
1904 + # systemd notification support
1905 + export "SDNOTIFY=$(usex systemd 1 0)"
1906 + # with/without keyboard
1907 + export "USE_KBD=$(usex keyboard 1 0)"
1908 + # detailed compile output for debug
1909 + export "VERBOSE=$(usex verbose 1 0)"
1910 +}
1911 +
1912 +src_install() {
1913 + # trick the makefile to not create a VIDEODIR by supplying it with an
1914 + # existing directory
1915 + emake VIDEODIR="/" DESTDIR="${ED}" install
1916 +
1917 + keepdir "${PLUGIN_LIBDIR}"
1918 +
1919 + # backup for plugins they don't be able to create this dir
1920 + keepdir "${CONF_DIR}/plugins"
1921 +
1922 + if use html; then
1923 + local HTML_DOCS=( *.html )
1924 + fi
1925 + local DOCS=( MANUAL INSTALL README* HISTORY CONTRIBUTORS UPDATE-2* )
1926 + einstalldocs
1927 +
1928 + insinto /usr/share/vdr
1929 + doins "${CAP_FILE}"
1930 +
1931 + fowners vdr:vdr "${CONF_DIR}" -R
1932 +}
1933 +
1934 +pkg_postinst() {
1935 + elog "Please read the /usr/share/doc/${PF}/UPDATE-2.4"
1936 + elog "for major changes in this version\n"
1937 +
1938 + elog "It is a good idea to run vdrplugin-rebuild now.\n"
1939 +
1940 + elog "To get nice symbols in OSD we recommend to install"
1941 + elog "\t1. emerge media-fonts/vdrsymbols-ttf"
1942 + elog "\t2. select font VDRSymbolsSans in Setup\n"
1943 +
1944 + elog "To get an idea how to proceed now, have a look at our vdr-guide:"
1945 + elog "\thttps://wiki.gentoo.org/wiki/VDR"
1946 +}