Compare commits

...

139 Commits

Author SHA1 Message Date
Klaus Schmidinger
dd71a004e2 Official release of version 2.7.4 2025-02-26 10:35:03 +01:00
Klaus Schmidinger
45091fbd72 Revised locking in cMenuSchedule and cMenuWhatsOn 2025-02-25 15:53:43 +01:00
Klaus Schmidinger
988d5aebfa Added missing locks to SetMenuItem() functions 2025-02-20 10:23:15 +01:00
Klaus Schmidinger
8c3671fae6 Fixed cPtsIndex::FindFrameNumber() to handle the case where Pts points to an I-frame 2025-02-19 15:39:16 +01:00
Klaus Schmidinger
2a12af481a Fixed spurious fast frames when switching from "slow back" to "slow forward" 2025-02-18 17:06:15 +01:00
Klaus Schmidinger
7817e64695 Fixed progress display when switching from "pause" to "slow back" 2025-02-18 15:37:24 +01:00
Klaus Schmidinger
ebbaa39098 Added '~' to the list of delimiters in cTextWrapper 2025-02-17 11:13:13 +01:00
Klaus Schmidinger
d3dcbbd4f2 Fixed unnecessary redisplays of menus 2025-02-17 10:49:10 +01:00
Klaus Schmidinger
3045995bbc Fixed displaying the current item when pressing a hotkey 2025-02-13 13:58:07 +01:00
Klaus Schmidinger
1b4233d6ad The function cPlugin::MainThreadHook() has been deprecated 2025-02-12 22:22:20 +01:00
Klaus Schmidinger
34aa8fe8b4 Adjusted PLUGINS.html to the new API version numbering introduced in version 2.7.2 2025-02-12 22:02:25 +01:00
Klaus Schmidinger
baa97e9ff1 Added missing calls to cStatus::MsgOsdStatusMessage() and added the new virtual function cStatus::OsdStatusMessage2(), which can be used to get the type of the message 2025-02-12 21:18:53 +01:00
Klaus Schmidinger
03afc4a353 Fixed unnecessary calls to cStatus::OsdCurrentItem2() when scrolling 2025-02-05 22:12:32 +01:00
Klaus Schmidinger
ef4ebeb7ee The new virtual function cStatus::OsdCurrentItem2() can be used to get the index of the current menu item 2025-01-29 11:15:26 +01:00
Klaus Schmidinger
80d8851e62 Added cStatus::OsdCurrentItem2() 2025-01-29 11:15:26 +01:00
Klaus Schmidinger
ead135f716 Fixed unnecessary calls to DisplayCurrent() for editable menu items 2025-01-29 10:35:25 +01:00
Klaus Schmidinger
49dc61a92c Fixed an unnecessary redisplay of the menu when pressing a hotkey 2025-01-29 10:25:55 +01:00
Klaus Schmidinger
af0309cc40 Fixed an improper call of cStatus::OsdCurrentItem() before cStatus::OsdItem2() 2025-01-29 10:20:17 +01:00
Klaus Schmidinger
4ed7421b1c Activated logging of OsdItem2() 2025-01-28 10:41:03 +01:00
Klaus Schmidinger
3058354dba Fixed setting the file name of the info file after renaming a recording 2025-01-18 20:57:06 +01:00
Klaus Schmidinger
20a8c5d240 Added cStatus::OsdItem2() 2025-01-16 10:23:12 +01:00
Klaus Schmidinger
0749a34342 The new virtual function cStatus::OsdItem2() can be used to get the information whether a menu item is selectable 2025-01-16 09:42:11 +01:00
Klaus Schmidinger
e595eed57d The info files of recordings are now only re-read if they have been modified 2025-01-15 10:50:29 +01:00
Klaus Schmidinger
a7576f0b6c Added parameter checks to strn0cpy() and Utf8Strn0Cpy() 2025-01-15 08:57:45 +01:00
Klaus Schmidinger
657e5dda5d Added a header to the backtrace 2025-01-15 08:43:12 +01:00
Klaus Schmidinger
8fb6a2b24b Fixed handling margins for timers that are not VPS controlled and not spawned 2025-01-13 14:44:18 +01:00
Klaus Schmidinger
53cac302d8 Added 1 to Utf8BufSize() for worst case 2025-01-13 13:18:42 +01:00
Klaus Schmidinger
2c6c014dd8 Checking for VPS control is now limited to local timers 2025-01-13 12:34:18 +01:00
Klaus Schmidinger
a7071f580e Added some missing locking 2025-01-10 16:11:02 +01:00
Klaus Schmidinger
de5327a048 Fixed a possible deadlock when canceling an editing process 2025-01-10 13:12:04 +01:00
Klaus Schmidinger
7ab94c7bcb Fixed accessing a timer's event schedule in case the event has been removed from the schedule 2025-01-07 10:46:22 +01:00
Klaus Schmidinger
0f80fc5e86 Fixed handling the fps value if it can't be determined from the video data 2024-12-05 10:37:15 +01:00
Klaus Schmidinger
d169f30e5c TS packets with errors are now skipped when parsing for frames 2024-12-05 10:33:31 +01:00
Klaus Schmidinger
7a1842cba6 Fixed a typo 2024-12-04 14:33:22 +01:00
Klaus Schmidinger
e4e9d7a55f Added some missing locking 2024-12-02 12:40:56 +01:00
Klaus Schmidinger
ccbef6ce6c Fixed some typos in the translation files 2024-11-30 14:30:46 +01:00
Klaus Schmidinger
7461a1ba3a Updated the Finnish OSD texts 2024-10-24 09:58:43 +02:00
Klaus Schmidinger
bb55e3036e The VDR homepage is now accessible via HTTPS 2024-10-21 19:04:53 +02:00
Klaus Schmidinger
bbf2cca198 Updated the Italian OSD texts 2024-10-20 10:05:19 +02:00
Klaus Schmidinger
8ce034d124 Fixed wrong credits 2024-10-17 20:17:57 +02:00
Klaus Schmidinger
4030698007 Fixed error checking in case the fps value can't be determined by the frame parser 2024-10-13 13:34:32 +02:00
Klaus Schmidinger
66fea5c9f1 Removed all DEPRECATED_* code 2024-10-13 09:47:18 +02:00
Klaus Schmidinger
285574eeaa Official release of version 2.7.3 2024-10-12 13:32:46 +02:00
Klaus Schmidinger
55cfb057e0 Added vdrrootdir and incdir to vdr.pc 2024-10-11 14:21:04 +02:00
Klaus Schmidinger
b4c538cff7 The Channel+/- keys can now be used to jump between errors while replaying a recording 2024-10-11 14:10:50 +02:00
Klaus Schmidinger
5a626fef9f Fixed expiring of one-time VPS timers in case there is more than one event with the same VPS time 2024-10-11 13:58:11 +02:00
Klaus Schmidinger
2bcd8ba8f3 Fixed handling negative values in cSource::Position() on systems where 'int' is 64 bit 2024-10-09 10:36:16 +02:00
Klaus Schmidinger
2dacc776bd Fixed error checking in case of large PTS discontinuities 2024-10-08 08:46:38 +02:00
Klaus Schmidinger
a91d687a1a Removed defining DEPRECATED_* macros with value 0, because this is the preprocessor's default 2024-10-08 08:09:48 +02:00
Klaus Schmidinger
0d3882d43e Official release of version 2.7.2 2024-09-27 09:15:33 +02:00
Klaus Schmidinger
72ad601328 Added a note about re-generating the index of old recordings with errors 2024-09-27 09:14:23 +02:00
Klaus Schmidinger
2c6fd804f6 Fixed a problem with duplicate events if they are moved to a lower table ID and at the same time get a new event ID 2024-09-26 19:25:41 +02:00
Klaus Schmidinger
c590444b7d Fixed error checking when regenerating the index 2024-09-21 19:18:18 +02:00
Klaus Schmidinger
4805af7915 Increased the bpp of cProgressBar to 4 to handle all different colors 2024-09-21 16:21:08 +02:00
Klaus Schmidinger
171b20a80d Fixed singular when displaying number of errors in the recording info 2024-09-21 10:53:07 +02:00
Klaus Schmidinger
d00ae923ab Edited recordings now show error count of original if there are no error indicators in the index 2024-09-20 21:34:18 +02:00
Klaus Schmidinger
d8ab5dc5c6 Fixed syncing the frame checker to I-frames 2024-09-20 14:21:39 +02:00
Klaus Schmidinger
292af5d4f4 The info file of an edited recording now contains the number of errors in the edited version 2024-09-19 20:21:58 +02:00
Klaus Schmidinger
3d6b31b115 When the index file of a recording is regenerated, errors in the recording are now stored in the index file 2024-09-19 12:06:55 +02:00
Klaus Schmidinger
9e523073aa Errors are now shown as diamond shaped markers in the replay progress display of the default skins 2024-09-19 09:49:02 +02:00
Klaus Schmidinger
32d8e473fb Fixed description of cSkinDisplayReplay::SetRecording() 2024-09-18 11:10:56 +02:00
Klaus Schmidinger
5cd25df60c Recording errors are now marked in the index file 2024-09-18 09:23:07 +02:00
Klaus Schmidinger
52c4816c9c Now distinguishing between frames with errors and completely missing frames 2024-09-17 11:30:28 +02:00
Klaus Schmidinger
6f6b05ffcb The number of errors in a recording now represents the number of broken frames 2024-09-17 09:39:50 +02:00
Klaus Schmidinger
6dd5854b7a Moved error checking from recorder.c to remux.c 2024-09-17 09:31:15 +02:00
Klaus Schmidinger
83425df0b6 Updated the Italian OSD texts 2024-09-16 09:14:41 +02:00
Klaus Schmidinger
82b09eaa8e Silenced a compiler warning with gcc 14.1.0 2024-09-16 09:07:12 +02:00
Klaus Schmidinger
ec5b1aadab Fixed a problem in cSchedule::Sort(), in case hasRunning was true, but there was no event with RunningStatus() >= SI::RunningStatusPausing 2024-09-14 14:17:12 +02:00
Klaus Schmidinger
f786510ba2 Made APIVERSION independent from VDRVERSION to avoid irritation in case only VDRVERSION is incremented 2024-09-12 12:48:40 +02:00
Klaus Schmidinger
f006884e57 Deprecated code is now marked with [[deprecated]] to issue a compile time warning when used 2024-09-09 22:15:59 +02:00
Klaus Schmidinger
c0a005b3cd Fix for compilers that don't like non-constant format strings 2024-09-09 13:39:05 +02:00
Klaus Schmidinger
0c91893643 There will be no more distinction between "developer" and "stable" versions regarding version numbering 2024-09-09 10:58:55 +02:00
Klaus Schmidinger
796da9e0f6 Improved the error message when closing a frontend 2024-09-09 08:53:57 +02:00
Klaus Schmidinger
5d539be071 The '.update' file in the video directory is now created if it doesn't already exist 2024-09-01 20:43:40 +02:00
Klaus Schmidinger
1df138d876 Fixed a crash when deleting a recording that is currently being edited, and then immediately deleting the edited version, too 2024-08-30 20:43:26 +02:00
Klaus Schmidinger
71b0140003 The new SVDRP command 'AUDI' can be used to list the currently available audio tracks and select one of them 2024-08-30 09:55:15 +02:00
Klaus Schmidinger
a33adf365d Changed the error message when trying to attach a player to a primary device without an MPEG decoder 2024-07-16 12:33:31 +02:00
Klaus Schmidinger
8d82b05071 Disabled/removed deprecated code 2024-07-15 14:42:22 +02:00
Klaus Schmidinger
930e3b4200 Official release of version 2.6.9 2024-07-15 09:07:09 +02:00
Klaus Schmidinger
2543f2c486 Added the lines from 'Fixed a timeout in cDvbDevice while tuning after the frontend has been reopened' to cDvbTuner::ProvidesFrontend() 2024-07-14 15:41:01 +02:00
Klaus Schmidinger
d919817c35 Fixed restarting the EPG scan and keeping the frequency of calls to Device->SetPowerSaveIfUnused() low 2024-07-13 20:12:24 +02:00
Klaus Schmidinger
6bbb596968 Fixed setting the editable width in the LCARS skin 2024-07-13 15:25:22 +02:00
Klaus Schmidinger
8aec1974bb Fixed a timeout in cDvbDevice while tuning after the frontend has been reopened 2024-07-13 12:34:42 +02:00
Klaus Schmidinger
b3ad9ec699 Fixed handling of cSkinDisplayMenu::GetTextAreaFont() 2024-07-13 09:12:18 +02:00
Klaus Schmidinger
87410442b6 Fixed a crash in strreplace() for multiple replacements with strings of different lengths 2024-07-10 14:59:45 +02:00
Klaus Schmidinger
305735a886 Official release of version 2.6.8 2024-07-08 18:40:20 +02:00
Klaus Schmidinger
41b7e1546a Added 'lnbPowerTurnedOn = false' to cDvbTuner::ProvidesFrontend() 2024-07-08 18:39:18 +02:00
Klaus Schmidinger
f3972e4795 Implemented power save mode for cDvbDevice 2024-07-08 09:34:33 +02:00
Klaus Schmidinger
e7ea087a6e Unused devices can now be put into a power save mode 2024-07-06 11:19:21 +02:00
Klaus Schmidinger
749ba57dcc Updated the Italian OSD texts 2024-07-02 07:57:42 +02:00
Klaus Schmidinger
0360b0d0e7 The EIT scanner now checks whether there is a proper device before adding a channel to the scan list 2024-06-27 10:49:34 +02:00
Klaus Schmidinger
2b495e0f87 Removed leftover cMenuRecordings::SetPath() 2024-06-25 19:00:06 +02:00
Klaus Schmidinger
32b11e1a53 Added an EPG bugfix for broadcasters who put literal "\n" strings in their EPG 2024-06-21 06:27:20 +02:00
Klaus Schmidinger
db81c07b27 When starting an editing process, VDR now first checks whether there is enough free disk space to take up the edited version of the recording 2024-06-13 09:31:14 +02:00
Klaus Schmidinger
1c2401eb6c Fixed typos 2024-06-11 12:43:25 +02:00
Klaus Schmidinger
5828d347f7 Fixed setting T2 system ID from NIT 2024-05-29 11:13:34 +02:00
Klaus Schmidinger
9c64622718 Fixed a possible access of a deleted object in the EIT scanner 2024-05-19 14:55:57 +02:00
Klaus Schmidinger
f9260d0141 Updated the Italian OSD texts 2024-04-03 08:21:05 +02:00
Klaus Schmidinger
746cdaff01 Official release of version 2.6.7 2024-04-02 09:05:33 +02:00
Klaus Schmidinger
179d5b87fc A device is now always kept occupied if a timer is in VPS margin or needs the transponder 2024-03-29 21:46:50 +01:00
Klaus Schmidinger
51dca45a0c If the current channel is no longer available because of a VPS timer entering the VPS margin, live view now switches to the channel of that timer 2024-03-28 13:21:42 +01:00
Klaus Schmidinger
62ad9b41dd Added a missing initialization 2024-03-28 13:02:42 +01:00
Klaus Schmidinger
8b87a6968a The EIT scan is no longer inhibited if a timer is in VPS margin or needs the transponder 2024-03-10 11:26:54 +01:00
Klaus Schmidinger
faf562fd4e The EIT scan now skips scanList entries if a device is already tuned to that transponder 2024-03-10 11:16:29 +01:00
Klaus Schmidinger
6a09a2fbd6 The EIT scan no longer deletes the scanList if no device was switched in this pass 2024-03-10 10:38:18 +01:00
Klaus Schmidinger
bc32ffe2f9 Fixed logging when a timer has entered the VPS margin 2024-03-08 10:50:06 +01:00
Klaus Schmidinger
ff16bbd777 Logging event status changes now also shows the previous status 2024-03-06 20:16:51 +01:00
Klaus Schmidinger
f7f8a6b131 Improved handling present/following data for VPS timers 2024-03-06 14:37:15 +01:00
Klaus Schmidinger
a3310e2954 The new setup parameters "EPG scan max. channel number" and "EPG pause after scan" can be used to tune the behavior of the EPG scan 2024-03-04 21:14:27 +01:00
Klaus Schmidinger
0b08666310 Updated the Hungarian OSD texts 2024-03-04 15:04:26 +01:00
Klaus Schmidinger
42db3fbee0 The info file of a recording is now re-read if an update of the video directory is triggered, to make sure modifications from other VDRs are adopted 2024-03-04 14:12:37 +01:00
Klaus Schmidinger
bfa25d6276 The primary device no longer starts unnecessary threads if it doesn't have a decoder 2024-03-04 12:26:32 +01:00
Klaus Schmidinger
824c495d33 Added a 15 second grace period before actually stopping a VPS timer 2024-03-03 15:47:09 +01:00
Klaus Schmidinger
561be36958 Adapted "Setup/Miscellaneous/Show channel names with source" to the new handling in cChannel 2024-03-02 21:49:21 +01:00
Klaus Schmidinger
8bd0437497 Modified handling channel names with source to make it thread safe 2024-03-02 16:21:16 +01:00
Klaus Schmidinger
18c9cef1ea Added missing initialization of cChannel::nameSourceMode 2024-03-01 14:33:28 +01:00
Klaus Schmidinger
2c66d57d4b Fixed the move assignment operator to check for self-assignment 2024-02-15 14:57:56 +01:00
Klaus Schmidinger
29200d040e Official release of version 2.6.6 2024-01-25 09:44:48 +01:00
Klaus Schmidinger
385738cadd Fixed possible duplicate component entries in the info of an ongoing recording 2024-01-24 13:24:51 +01:00
Klaus Schmidinger
a2591d6e98 Workaround in detecting frame height for channels with wrong crop parameters 2024-01-23 19:33:45 +01:00
Klaus Schmidinger
fe97a38e77 Fixed handling primary device on headless systems 2024-01-22 12:10:30 +01:00
Klaus Schmidinger
552f5fc4e7 Fixed a typo 2024-01-22 12:09:22 +01:00
Klaus Schmidinger
21d3d489fd Added '/' to the list of fuzzy characters for pattern timers 2024-01-21 10:43:20 +01:00
Klaus Schmidinger
78b7e4e252 Added the total number of errors when logging new recording errors 2024-01-20 20:04:03 +01:00
Klaus Schmidinger
5d984b606e Added the move constructor to cString for better performance 2024-01-20 13:59:55 +01:00
Klaus Schmidinger
88b1e30494 Fixed an unnecessary double display of menu items in the Recordings menu 2024-01-19 12:17:05 +01:00
Klaus Schmidinger
939071bf25 Fixed an unnecessary double display of the current menu item in page up/down 2024-01-19 12:10:47 +01:00
Klaus Schmidinger
655682b5d2 Removed syslog calls in child process after fork() 2024-01-18 13:01:07 +01:00
Klaus Schmidinger
b1418b6bcd Implemented scaling images 2024-01-18 12:04:57 +01:00
Klaus Schmidinger
35c8b3d22c Using a dummy OSD if no OSD provider is available is not considered an error any more 2024-01-18 11:06:45 +01:00
Klaus Schmidinger
e5ae02e3fa Fixed a possible crash in cDevice::StopSectionHandler() 2024-01-18 10:58:39 +01:00
Klaus Schmidinger
f0da21ea13 Fixed height calculation in progress display 2024-01-17 21:15:48 +01:00
Klaus Schmidinger
eb35faaf7d Changed installing config files to handle potentially broken 'cp -n' 2024-01-05 14:16:16 +01:00
Klaus Schmidinger
05f03d6e38 Official release of version 2.6.5 2023-12-30 10:27:34 +01:00
Klaus Schmidinger
0d4284df29 The recording info of the default skins now shows the frame parameters of the recording at the end of the description (if such information is available) 2023-12-29 10:48:40 +01:00
Klaus Schmidinger
cade92cda1 Added the function cRecordingInfo::FrameParams(), which can be used to get a nicely formatted string with all the available frame data 2023-12-29 10:48:25 +01:00
Klaus Schmidinger
f0bbf64da0 Added cString::Append(char) 2023-12-29 10:24:29 +01:00
Klaus Schmidinger
6458f8b581 The frame width, height, scan type and apect ratio of a recording are now stored in the 'info' file under the 'F' tag 2023-12-28 21:23:19 +01:00
Klaus Schmidinger
1770a18598 Fixed broken video data streams on systems without output device when switching live channel to a different transponder while recording 2023-02-21 09:31:47 +01:00
103 changed files with 2663 additions and 1011 deletions

View File

@ -2210,6 +2210,8 @@ Marko M
for avoiding unnecessary processing in cDvbSubtitleConverter::FinishPage() if there
are no areas
for avoiding a zero sized array in cDevice::GetDevice()
for reporting a crash when deleting a recording that is currently being edited, and
then immediately deleting the edited version, too
Patrick Rother <krd-vdr@gulu.net>
for reporting a bug in defining timers that only differ in the day of week
@ -2468,6 +2470,10 @@ Christoph Haubrich <christoph1.haubrich@arcor.de>
for fixing handling zero bytes in cH264Parser
for implementing parsing frame rate and image size for MPEG2, H.264 and H.265
for reporting a bug in generating the index file in the cutter
for adding the frame width, height, scan type and aspect ratio of a recording to the 'F'
tag of the 'info' file
for adding a workaround in detecting frame height for channels with wrong crop parameters
for reporting duplicate component entries in the info of an ongoing recording
Pekka Mauno <pekka.mauno@iki.fi>
for fixing cSchedule::GetFollowingEvent() in case there is currently no present
@ -2552,6 +2558,44 @@ Markus Ehrnsperger <markus.ehrnsperger@googlemail.com>
for suggesting to add a chapter about locking to PLUGINS.html
for fixing unnecessary interruption of ongoing recordings if timers avoided the
transfer mode receiver device
for reporting broken video data streams on systems without output device when switching
live channel to a different transponder while recording
for fixing a possible crash in cDevice::StopSectionHandler()
for making using a dummy OSD if no OSD provider is available not be considered an error
any more
for removing syslog calls in child process after fork()
for adding the move constructor to cString for better performance
for fixing handling primary device on headless systems
for adding a 15 second grace period before actually stopping a VPS timer
for making the primary device no longer start unnecessary threads if it doesn't have
a decoder
for improving handling present/following data for VPS timers
for making logging event status changes also show the previous status
for making a device always being kept occupied if a timer is in VPS margin or needs the
transponder
for suggesting to enable unused devices to be put into a power save mode
for a patch that was used to implement power save mode for cDvbDevice
for suggesting to add 'lnbPowerTurnedOn = false' to cDvbTuner::ProvidesFrontend()
for reporting a crash in strreplace() for multiple replacements with strings of
different lengths
for suggesting to add the lines from 'Fixed a timeout in cDvbDevice while tuning after
the frontend has been reopened' to cDvbTuner::ProvidesFrontend()
for improving the error message when closing a frontend
for reporting a problem in cSchedule::Sort(), in case hasRunning was true, but there
was no event with RunningStatus() >= SI::RunningStatusPausing
for reporting problem with duplicate events if they are moved to a lower table ID and
at the same time get a new event ID
for reporting a bug in handling negative values in cSource::Position() on
systems where 'int' is 64 bit
for suggesting a fix for expiring of one-time VPS timers in case there is more than
one event with the same VPS time
for fixing handling margins for timers that are not VPS controlled and not spawned
for implementing cStatus::OsdItem2() with the information whether the item is selectable
for reporting an improper call of cStatus::OsdCurrentItem() before cStatus::OsdItem2()
for fixing unnecessary calls to DisplayCurrent() for editable menu items
for implementing cStatus::OsdCurrentItem2() with the index of the current item
for adding missing calls to cStatus::MsgOsdStatusMessage() and implementing
cStatus::OsdStatusMessage2() with the type of the message
Werner Färber <w.faerber@gmx.de>
for reporting a bug in handling the cPluginManager::Active() result when pressing
@ -2809,6 +2853,13 @@ Winfried K
for fixing default values for DVB-T
for adding missing rounding when dividing frequencies in processing the NIT
for suggesting to add note about not messing with event ids in EPG handlers
for adding a missing initialization of cChannel::nameSourceMode
for suggesting to make APIVERSION a simple number, independent from VDRVERSION
for reporting a compiler warning with gcc 14.1.0
for suggesting to remove defining DEPRECATED_* macros with value 0, because this
is the preprocessor's default
for suggesting a fix for handling negative values in cSource::Position() on
systems where 'int' is 64 bit
Hans-Werner Hilse <hilse@web.de>
for adding the command line option --userdump to enable core dumps in case VDR
@ -2979,6 +3030,7 @@ Manuel Reimer <Manuel.Reimer@gmx.de>
number is given
for reporting that LSTE doesn't work after PUTE in case a channel didn't already
have EPG data
for suggesting to make APIVERSION a simple number, independent from VDRVERSION
Rene van den Braken <rene@vandenbraken.name>
for reporting a bug in writing the PCR pid into the PMT in
@ -3032,6 +3084,7 @@ Lars Hanisch <dvb@flensrocker.de>
for fixing a typo in the description of cTimers::GetTimersRead()
for suggesting to use dynamic buffering in handling CA descriptors to avoid a
possible buffer overflow
for suggesting to make APIVERSION a simple number, independent from VDRVERSION
Alex Lasnier <alex@fepg.org>
for adding tuning support for ATSC devices
@ -3321,6 +3374,16 @@ Mike Hay <mike.hay@linenshorts.com>
Stefan Hofmann <stefan.hofmann@t-online.de>
for suggesting to implement support for remote controls that only have a combined
"Play/Pause" key instead of separate keys for "Play" and "Pause"
for a fix for compilers that don't like non-constant format strings
for suggesting to implement jumping between errors while replaying a recording
for adding vdrrootdir and incdir to vdr.pc
for fixing some typos in the translation files
for adding 1 to Utf8BufSize() for worst case
for adding a header to the backtrace
for adding parameter checks to strn0cpy()
for making the info files of recordings only be re-read if they have been modified
for reporting missing setting the file name of the info file after renaming a recording
for adding '~' to the list of delimiters in cTextWrapper
Stefan Blochberger <Stefan.Blochberger@gmx.de>
for suggesting to automatically display the progress display whenever replay of a
@ -3386,6 +3449,11 @@ Matthias Senzel <matthias.senzel@t-online.de>
for reporting that if an error occurs while recording, the respective entry in the list
of recordings was not updated immediately
for fixing restoring the volume at program start
for fixing height calculation in progress display
for fixing an unnecessary double display of the current menu item in page up/down
for fixing an unnecessary double display of menu items in the Recordings menu
for reporting a bug in handling cSkinDisplayMenu::GetTextAreaFont()
for reporting characters being cut off while editing in the LCARS skin
Marek Nazarko <mnazarko@gmail.com>
for translating OSD texts to the Polish language
@ -3581,6 +3649,9 @@ Stefan Herdler <herdler@gmx.de>
for adding failsafe defaults for 'make LCLBLD=1' to the Makefile
for reporting the index file of a recording not being regenerated in case it is
present, but empty
for reporting a missing check for self-assignment in the move assignment operator
for modifying handling channel names with source to make it thread safe
for fixing setting T2 system ID from NIT
Tobias Faust <tobias.faust@gmx.de>
for the original "jumpingseconds" patch
@ -3742,3 +3813,8 @@ Timo Weing
Jose Angel <joseangelpp@gmail.com>
for reporting a bug in default values for DVB-T
Andreas Baierl <post@andreasbaierl.de>
for implementing scaling images
for reporting a problem in the progress display when switching from "pause" to
"slow back"

238
HISTORY
View File

@ -9851,3 +9851,241 @@ Video Disk Recorder Revision History
- Fixed unnecessary interruption of ongoing recordings if timers avoided the transfer
mode receiver device (thanks to Markus Ehrnsperger).
- Revised support for kernel based LIRC driver (thanks to Marko Mäkelä).
2023-12-30: Version 2.6.5
- Fixed broken video data streams on systems without output device when switching live
channel to a different transponder while recording (reported by Markus Ehrnsperger).
- The frame width, height, scan type and aspect ratio of a recording are now stored in
the 'info' file under the 'F' tag (thanks to Christoph Haubrich).
- Added the function cRecordingInfo::FrameParams(), which can be used to get a nicely
formatted string with all the available frame data.
- The recording info of the default skins now shows the frame parameters of the
recording at the end of the description (if such information is available).
2024-01-24: Version 2.6.6
- Changed installing config files to handle potentially broken 'cp -n'.
- Fixed height calculation in progress display (thanks to Matthias Senzel).
- Fixed a possible crash in cDevice::StopSectionHandler() (thanks to Markus
Ehrnsperger).
- Using a dummy OSD if no OSD provider is available is not considered an error any
more (thanks to Markus Ehrnsperger).
- Implemented scaling images (thanks to Andreas Baierl).
- Removed syslog calls in child process after fork() (thanks to Markus Ehrnsperger).
- Fixed an unnecessary double display of the current menu item in page up/down
(thanks to Matthias Senzel).
- Fixed an unnecessary double display of menu items in the Recordings menu (thanks to
Matthias Senzel).
- Added the move constructor to cString for better performance (thanks to Markus
Ehrnsperger).
- Added the total number of errors when logging new recording errors.
- Added '/' to the list of fuzzy characters for pattern timers.
- Fixed handling primary device on headless systems (thanks to Markus Ehrnsperger).
- Workaround in detecting frame height for channels with wrong crop parameters
(thanks to Christoph Haubrich).
- Fixed possible duplicate component entries in the info of an ongoing recording
(reported by Christoph Haubrich).
2024-04-02: Version 2.6.7
- Fixed the move assignment operator to check for self-assignment (suggested by
Stefan Herdler).
- Added missing initialization of cChannel::nameSourceMode (thanks to Winfried Köhler).
- Modified handling channel names with source to make it thread safe (thanks to
Stefan Herdler).
- Adapted "Setup/Miscellaneous/Show channel names with source" to the new handling
in cChannel.
- Added a 15 second grace period before actually stopping a VPS timer (thanks to
Markus Ehrnsperger).
- The primary device no longer starts unnecessary threads if it doesn't have a decoder
(thanks to Markus Ehrnsperger).
- The info file of a recording is now re-read if an update of the video directory
is triggered, to make sure modifications from other VDRs are adopted.
- Updated the Hungarian OSD texts (thanks to István Füley).
- The new setup parameters "EPG scan max. channel number" and "EPG pause after scan"
can be used to tune the behavior of the EPG scan (see MANUAL for details).
- Improved handling present/following data for VPS timers (thanks to Markus Ehrnsperger).
- Logging event status changes now also shows the previous status (thanks to Markus
Ehrnsperger).
- Fixed logging when a timer has entered the VPS margin.
- The EIT scan no longer deletes the scanList if no device was switched in this pass.
- The EIT scan now skips scanList entries if a device is already tuned to that
transponder.
- The EIT scan is no longer inhibited if a timer is in VPS margin or needs the
transponder.
- If the current channel is no longer available because of a VPS timer entering the
VPS margin, live view now switches to the channel of that timer.
- A device is now always kept occupied if a timer is in VPS margin or needs the
transponder (thanks to Markus Ehrnsperger).
2024-07-08: Version 2.6.8
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- Fixed a possible access of a deleted object in the EIT scanner.
- Fixed setting T2 system ID from NIT (thanks to Stefan Herdler).
- When starting an editing process, VDR now first checks whether there is enough
free disk space to take up the edited version of the recording.
- Added an EPG bugfix for broadcasters who put literal "\n" strings in their EPG.
Note that the string version of strreplace() has been modified, so that it
replaces all occurrences of the search string, not just the first one.
- Removed leftover cMenuRecordings::SetPath().
- The EIT scanner now checks whether there is a proper device before adding a
channel to the scan list.
- Unused devices can now be put into a power save mode (suggested by Markus
Ehrnsperger). Device plugins need to implement the new function
cDevice::SetPowerSaveMode() to make this work.
- Implemented power save mode for cDvbDevice (based on a patch from Markus
Ehrnsperger).
- Added 'lnbPowerTurnedOn = false' to cDvbTuner::ProvidesFrontend() (suggested by
Markus Ehrnsperger).
2024-07-15: Version 2.6.9
- Fixed a crash in strreplace() for multiple replacements with strings of different
lengths (reported by Markus Ehrnsperger).
- Fixed handling of cSkinDisplayMenu::GetTextAreaFont() (reported by Matthias Senzel).
- Fixed a timeout in cDvbDevice while tuning after the frontend has been reopened.
- Fixed setting the editable width in the LCARS skin (reported by Matthias Senzel).
- Fixed restarting the EPG scan and keeping the frequency of calls to
Device->SetPowerSaveIfUnused() low.
- Added the lines from 'Fixed a timeout in cDvbDevice while tuning after the frontend
has been reopened' to cDvbTuner::ProvidesFrontend() (suggested by Markus Ehrnsperger).
2024-09-09: Version 2.7.1
- Removed deprecated function cDevice::SetCurrentChannel(const cChannel *Channel).
- Removed deprecated function cSkinDisplayMenu::SetItemEvent(const cEvent *Event, int Index,
bool Current, bool Selectable, const cChannel *Channel, bool WithDate, eTimerMatch TimerMatch).
- Removed deprecated functions from cFile.
- The default for DEPRECATED_SCHEDULE_GET_EVENT has been set to 0, which means that
the function GetEvent(tEventID EventID, time_t StartTime = 0) is no longer available.
You can add 'DEPRECATED_SCHEDULE_GET_EVENT=1' when compiling in order to restore this
functionality. However, it is recommended to use GetEventById() and GetEventByTime()
instead.
- The default for DEPRECATED_SECTIONSYNCER_SYNC_REPEAT has been set to 0, which means that
the functions Repeat() and Sync() are no longer available.
You can add 'DEPRECATED_SECTIONSYNCER_SYNC_REPEAT=1' when compiling in order to restore this
functionality. However, it is recommended to use Check() and Processed() instead.
- The default for DEPRECATED_CCONTROL has been set to 0, which means that
the function Control(bool Hidden) is no longer available.
You can add 'DEPRECATED_CCONTROL=1' when compiling in order to restore this
functionality. However, it is recommended to use Control(cMutexLock &MutexLock, bool Hidden)
instead.
- Changed the error message when trying to attach a player to a primary device without
an MPEG decoder.
- The new SVDRP command 'AUDI' can be used to list the currently available audio tracks
and select one of them.
- Fixed a crash when deleting a recording that is currently being edited, and then
immediately deleting the edited version, too (reported by Marko Mäkelä).
- The '.update' file in the video directory is now created if it doesn't already exist.
- Improved the error message when closing a frontend (thanks to Markus Ehrnsperger).
- There will be no more distinction between "developer" and "stable" versions regarding
version numbering. Version numbers are simply counted upwards, with each of the three
parts ("version", "major", "minor") always being a single digit, and '0' being skipped.
2024-09-27: Version 2.7.2
- Fix for compilers that don't like non-constant format strings (thanks to Stefan Hofmann).
- Deprecated code is now marked with [[deprecated]] to issue a compile time warning when
used.
- Made APIVERSION independent from VDRVERSION to avoid irritation in case only
VDRVERSION is incremented (originally suggested by Winfried Köhler, with contributions
from Lars Hanisch and Manuel Reimer).
APIVERSNUM is now 30003.
- Fixed a problem in cSchedule::Sort(), in case hasRunning was true, but there was no event
with RunningStatus() >= SI::RunningStatusPausing (reported by Markus Ehrnsperger).
- Silenced a compiler warning with gcc 14.1.0 (reported by Winfried Köhler).
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- Moved error checking from recorder.c to remux.c.
- The number of errors in a recording now represents the number of broken frames.
- Now distinguishing between frames with errors and completely missing frames.
- Recording errors are now marked in the index file.
- Fixed description of cSkinDisplayReplay::SetRecording().
- Errors are now shown as diamond shaped markers in the replay progress display of the
default skins. Plugin authors can switch to the new constructor of cProgressBar to
benefit from this feature. Of course this only works with recordings that have error
information stored in their index file. If you have existing recordings with errors
and want them to be displayed in the progress bar, you can re-generate their index
file, either by deleting the old index, or by running vdr with the --genindex option.
APIVERSNUM is now 30004.
- When the index file of a recording is regenerated, errors in the recording are now
stored in the index file.
- The info file of an edited recording now contains the number of errors in the edited
version. Note that this applies only to recordings that have errors stored in their
index file. If errors are not stored in the index file, the edited version will have
its number of errors set to that of the original recording.
APIVERSNUM is now 30005.
- Fixed singular when displaying number of errors in the recording info.
- Increased the bpp of cProgressBar to 4 to handle all different colors.
- Fixed a problem with duplicate events if they are moved to a lower table ID and at the
same time get a new event ID (reported by Markus Ehrnsperger).
2024-10-12: Version 2.7.3
- Removed defining DEPRECATED_* macros with value 0, because this is the preprocessor's
default (suggested by Winfried Köhler).
- Fixed error checking in case of large PTS discontinuities.
- Fixed handling negative values in cSource::Position() on systems where 'int' is 64 bit
(reported by Markus Ehrnsperger, fix suggested by Winfried Köhler).
- Fixed expiring of one-time VPS timers in case there is more than one event with the
same VPS time (suggested by Markus Ehrnsperger).
- The Channel+/- keys can now be used to jump between errors while replaying a recording
(suggested by Stefan Hofmann).
- Added vdrrootdir and incdir to vdr.pc (thanks to Stefan Hofmann).
2025-02-26: Version 2.7.4
- Removed all DEPRECATED_* code.
- Fixed error checking in case the fps value can't be determined by the frame parser.
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- The VDR homepage is now accessible via HTTPS.
- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
- Fixed some typos in the translation files (thanks to Stefan Hofmann).
- Added some missing locking.
- TS packets with errors are now skipped when parsing for frames.
- Fixed handling the fps value if it can't be determined from the video data.
- Fixed accessing a timer's event schedule in case the event has been removed from the
schedule.
- Fixed a possible deadlock when canceling an editing process.
- Checking for VPS control is now limited to local timers.
- Added 1 to Utf8BufSize() for worst case (thanks to Stefan Hofmann).
- Fixed handling margins for timers that are not VPS controlled and not spawned (thanks
to Markus Ehrnsperger).
- Added a header to the backtrace (thanks to Stefan Hofmann).
- Added parameter checks to strn0cpy() (thanks to Stefan Hofmann). Same for Utf8Strn0Cpy().
- The info files of recordings are now only re-read if they have been modified (thanks
to Stefan Hofmann).
- The new virtual function cStatus::OsdItem2() can be used to get the information whether
a menu item is selectable (thanks to Markus Ehrnsperger). Plugins that implemented
cStatus::OsdItem() will still work as before, because the default implementation of
cStatus::OsdItem2() calls cStatus::OsdItem().
APIVERSNUM is now 30006.
- Fixed setting the file name of the info file after renaming a recording (reported by
Stefan Hofmann).
- Fixed an improper call of cStatus::OsdCurrentItem() before cStatus::OsdItem2() (reported
by Markus Ehrnsperger).
- Fixed an unnecessary redisplay of the menu when pressing a hotkey.
- Fixed unnecessary calls to DisplayCurrent() for editable menu items (thanks to Markus
Ehrnsperger).
- The new virtual function cStatus::OsdCurrentItem2() can be used to get the index of the
current menu item (thanks to Markus Ehrnsperger). Plugins that implemented
cStatus::OsdCurrentItem() will still work as before, because the default implementation
of cStatus::OsdCurrentItem2() calls cStatus::OsdCurrentItem().
- Fixed unnecessary calls to cStatus::OsdCurrentItem2() when scrolling.
- Added missing calls to cStatus::MsgOsdStatusMessage() and added the new virtual function
cStatus::OsdStatusMessage2(), which can be used to get the type of the message (thanks
to Markus Ehrnsperger). Plugins that implemented cStatus::OsdStatusMessage() will still
work as before, because the default implementation of cStatus::OsdStatusMessage2() calls
cStatus::OsdStatusMessage().
- Adjusted PLUGINS.html to the new API version numbering introduced in version 2.7.2.
- The function cPlugin::MainThreadHook() has been deprecated and may be removed in future
versions. Use proper locking instead.
- Fixed unnecessary redisplays of menus.
- Added '~' to the list of delimiters in cTextWrapper (thanks to Stefan Hofmann).
- Fixed progress display when switching from "pause" to "slow back" (reported by Andreas
Baierl).
- Fixed spurious fast frames when switching from "slow back" to "slow forward".
- Fixed cPtsIndex::FindFrameNumber() to handle the case where Pts points to an I-frame.
- Added missing locks to SetMenuItem() functions.
- Revised locking in cMenuSchedule and cMenuWhatsOn.

View File

@ -1,7 +1,7 @@
Installation of the Video Disk Recorder
---------------------------------------
Version 2.6
Version 2.7
-----------
Compiling and running the program:

17
MANUAL
View File

@ -1,7 +1,7 @@
Video Disk Recorder User's Manual
---------------------------------
Version 2.6
Version 2.7
-----------
* Remote Control Keys
@ -50,8 +50,8 @@ Version 2.6
Next Next/previous channel group (in live tv mode)
Prev or next/previous editing mark (in replay mode)
Channel+ channel up
Channel- channel down
Channel+ channel up (live view), next error (replay)
Channel- channel down (live view), previous error (replay)
PrevChannel previous channel
Power shutdown
@ -645,6 +645,7 @@ following the matching pattern. There are three macros that can be used here:
As of VDR version 2.5.2, the characters ' ' (blank), ':' and '-' are ignored
when checking whether a particular recording has already been made by a pattern
timer, making "TITLE - EPISODE" and "TITLE: EPISODE" the same.
VDR version 2.6.6 added '/' to this list.
* Managing folders
@ -828,6 +829,16 @@ timer, making "TITLE - EPISODE" and "TITLE: EPISODE" the same.
A value of '0' completely turns off scanning on both single
and multiple card systems.
EPG scan max. channel number = 0
The EPG scan will only tune to transponders of channels with
numbers below this limit. By default all transponders will
be scanned.
EPG pause after scan = no
After a complete scan of all transponders (optionally limited
by "EPG scan max. channel number") the EPG scan pauses for
"EPG scan timeout" hours if this option is set to "yes".
EPG bugfix level = 3 Some tv stations transmit weirdly formatted EPG data.
VDR attempts to fix these bugs up to the given level:
0 = no EPG fixing

View File

@ -6,7 +6,7 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
# $Id: Make.config.template 5.1 2022/11/26 13:37:06 kls Exp $
# $Id: Make.config.template 5.2 2024/10/11 14:21:04 kls Exp $
### The C compiler and options:
@ -33,14 +33,15 @@ endif
# Default directories (adjust as necessary or desired):
#PREFIX = /usr/local
#BINDIR = $(PREFIX)/bin
#INCDIR = $(PREFIX)/include
#LIBDIR = $(PREFIX)/lib/vdr
#LOCDIR = $(PREFIX)/share/locale
#MANDIR = $(PREFIX)/share/man
#PCDIR = $(PREFIX)/lib/pkgconfig
#RESDIR = $(PREFIX)/share/vdr
#DVBDIR = /usr/src/v4l-dvb/linux/include/uapi
#VDRROOT = $(PREFIX)
#BINDIR = $(VDRROOT)/bin
#INCDIR = $(VDRROOT)/include
#LIBDIR = $(VDRROOT)/lib
#LOCDIR = $(VDRROOT)/locale
#MANDIR = $(VDRROOT)/man
#PCDIR = $(VDRROOT)/pkgconfig
#RESDIR = $(VDRROOT)/share
#DVBDIR = /usr/include
#VIDEODIR = /srv/vdr/video
#CONFDIR = /var/lib/vdr

View File

@ -4,7 +4,7 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
# $Id: Makefile 5.1 2020/12/31 11:11:53 kls Exp $
# $Id: Makefile 5.4 2024/10/21 19:01:16 kls Exp $
.DELETE_ON_ERROR:
@ -46,13 +46,14 @@ ARGSDIR ?= /etc/vdr/conf.d
CACHEDIR ?= /var/cache/vdr
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
INCDIR ?= $(PREFIX)/include
LIBDIR ?= $(PREFIX)/lib/vdr
LOCDIR ?= $(PREFIX)/share/locale
MANDIR ?= $(PREFIX)/share/man
PCDIR ?= $(PREFIX)/lib/pkgconfig
RESDIR ?= $(PREFIX)/share/vdr
VDRROOT ?= $(PREFIX)
BINDIR ?= $(VDRROOT)/bin
INCDIR ?= $(VDRROOT)/include
LIBDIR ?= $(VDRROOT)/lib/vdr
LOCDIR ?= $(VDRROOT)/share/locale
MANDIR ?= $(VDRROOT)/share/man
PCDIR ?= $(VDRROOT)/lib/pkgconfig
RESDIR ?= $(VDRROOT)/share/vdr
# Source documentation
@ -169,7 +170,9 @@ make-libsi: # empty rule makes sure the sub-make for libsi is always called
.PHONY: vdr.pc
vdr.pc:
@echo "bindir=$(BINDIR)" > $@
@echo "vdrrootdir=$(VDRROOT)" > $@
@echo "bindir=$(BINDIR)" >> $@
@echo "incdir=$(INCDIR)" >> $@
@echo "mandir=$(MANDIR)" >> $@
@echo "videodir=$(VIDEODIR)" >> $@
@echo "configdir=$(CONFDIR)" >> $@
@ -185,7 +188,7 @@ vdr.pc:
@echo "" >> $@
@echo "Name: VDR" >> $@
@echo "Description: Video Disk Recorder" >> $@
@echo "URL: http://www.tvdr.de/" >> $@
@echo "URL: https://www.tvdr.de/" >> $@
@echo "Version: $(VDRVERSION)" >> $@
@echo "Cflags: \$${cflags}" >> $@
@ -300,7 +303,12 @@ install-dirs:
@mkdir -p $(DESTDIR)$(RESDIR)
install-conf: install-dirs
@cp -pn *.conf $(DESTDIR)$(CONFDIR)
# 'cp -n' may be broken, so let's do it the hard way
@for i in *.conf; do\
if ! [ -e $(DESTDIR)$(CONFDIR)/$$i ] ; then\
cp -p $$i $(DESTDIR)$(CONFDIR);\
fi\
done
# Documentation:
@ -315,6 +323,7 @@ install-doc:
install-plugins: plugins
@-for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do\
echo; echo "*** Plugin $$i:";\
$(MAKE) --no-print-directory -C "$(PLUGINDIR)/src/$$i" VDRDIR=$(CWD) DESTDIR=$(DESTDIR) install;\
done
@if [ -d $(PLUGINDIR)/lib ] ; then\
@ -351,7 +360,7 @@ srcdoc:
clean:
@$(MAKE) --no-print-directory -C $(LSIDIR) clean
@-rm -f $(OBJS) $(DEPFILE) vdr vdr.pc core* *~
@-rm -rf $(LOCALEDIR) $(PODIR)/*.mo $(PODIR)/*.pot
@-rm -rf $(LOCALEDIR) $(PODIR)/*~ $(PODIR)/*.mo $(PODIR)/*.pot
@-rm -rf include
@-rm -rf srcdoc
CLEAN: clean

View File

@ -31,11 +31,11 @@ modified {
<div class="center">
<h1>The VDR Plugin System</h1>
<b>Version 2.6</b>
<b>Version 2.7</b>
<p>
Copyright &copy; 2021 Klaus Schmidinger<br>
<a href="mailto:vdr@tvdr.de">vdr@tvdr.de</a><br>
<a href="http://www.tvdr.de">www.tvdr.de</a>
<a href="https://www.tvdr.de">www.tvdr.de</a>
</div>
<p>
VDR provides an easy to use plugin interface that allows additional functionality
@ -74,7 +74,6 @@ structures and allows it to hook itself into specific areas to perform special a
<li><a href="#Main menu entry">Main menu entry</a>
<li><a href="#User interaction">User interaction</a>
<li><a href="#Housekeeping">Housekeeping</a>
<li><a href="#Main thread hook">Main thread hook</a>
<li><a href="#Activity">Activity</a>
<li><a href="#Wakeup">Wakeup</a>
<li><a href="#Setup parameters">Setup parameters</a>
@ -166,7 +165,7 @@ is used:
VDR/PLUGINS/src
VDR/PLUGINS/src/hello
VDR/PLUGINS/lib
VDR/PLUGINS/lib/libvdr-hello.so.1.1.0
VDR/PLUGINS/lib/libvdr-hello.so.1
</pre></td></tr></table><p>
The <tt>src</tt> directory contains one subdirectory for each plugin, which carries
@ -185,7 +184,7 @@ The <tt>lib</tt> directory contains the dynamically loadable libraries of all
available plugins. Note that the names of these files are created by concatenating
<p>
<table border=2>
<tr><td align=center><b><tt>libvdr-</tt></b></td><td align=center><b><tt>hello</tt></b></td><td align=center><b><tt>.so.</tt></b></td><td align=center><b><tt>1.1.0</tt></b></td></tr>
<tr><td align=center><b><tt>libvdr-</tt></b></td><td align=center><b><tt>hello</tt></b></td><td align=center><b><tt>.so.</tt></b></td><td align=center><b><tt>1</tt></b></td></tr>
<tr><td align=center><small>VDR plugin<br>library prefix</small></td><td align=center><small>name of<br>the plugin</small></td><td align=center><small>shared object<br>indicator</small></td><td align=center><small>API version number<br>this plugin was<br>compiled for</small></td></tr>
</table>
<p>
@ -196,6 +195,11 @@ the current VDR version. That way minor fixes to VDR, that don't require changes
to the VDR header files, can be made without requiring all plugins to be
recompiled.
<p>
While in earlier versions of VDR the API version number was closely related to the
VDR version number, starting with VDR version 2.7.2 the API version number was changed
from a dot separated, three part number to a single integer, completely unrelated to
the VDR version. This was done to avoid confusion.
<p>
The plugin library files can be stored in any directory. If the default organization
is not used, the path to the plugin directory has be be given to VDR through the
<b><tt>-L</tt></b> option.
@ -391,13 +395,7 @@ just like shown in the above example. This is a convention that allows the <tt>M
to extract the version number when generating the file name for the distribution archive.
<p>
A new plugin project should start with version number <tt>0.0.1</tt> and should reach
version <tt>1.0.0</tt> once it is completely operative and well tested. Following the
Linux kernel version numbering scheme, versions with <i>even</i> release numbers
(like <tt>1.0.x</tt>, <tt>1.2.x</tt>, <tt>1.4.x</tt>...) should be stable releases,
while those with <i>odd</i> release numbers (like <tt>1.1.x</tt>, <tt>1.3.x</tt>,
<tt>1.5.x</tt>...) are usually considered "under development". The three parts of
a version number are not limited to single digits, so a version number of <tt>1.2.15</tt>
would be acceptable.
version <tt>1.0.0</tt> once it is completely operative and well tested.
<hr><h2><a name="Description">Description</a></h2>
@ -693,27 +691,6 @@ interaction is possible. If a specific action takes longer than a few seconds,
the plugin should launch a separate thread to do this.
</b>
<hr><h2><a name="Main thread hook">Main thread hook</a></h2>
<div class="blurb">Pushing in...</div><p>
Normally a plugin only reacts on user input if directly called through its
<a href="#Main menu entry">main menu entry</a>, or performs some background
activity in a separate thread. However, sometimes a plugin may need to do
something in the context of the main program thread, without being explicitly
called up by the user. In such a case it can implement the function
<p><table><tr><td class="code"><pre>
virtual void MainThreadHook(void);
</pre></td></tr></table><p>
in which it can do this. This function is called for every plugin once during
every cycle of VDR's main program loop, which typically happens once every
second.
<b>Be very careful when using this function, and make sure you return from it
as soon as possible! If you spend too much time in this function, the user
interface performance will become sluggish!</b>
<hr><h2><a name="Activity">Activity</a></h2>
<div class="blurb">Now is not a good time!</div><p>
@ -2117,6 +2094,17 @@ new cMyDeviceHook;
</pre></td></tr></table><p>
and shall not delete this object. It will be automatically deleted when the program ends.
<p>
<b>Power management</b>
<p>
A device that can be put into a power save mode can implement the function
<p><table><tr><td class="code"><pre>
virtual void SetPowerSaveMode(bool On);
</pre></td></tr></table><p>
If On is true, power save mode shall be activated, if it is false,
normal operating mode shall be restored.
<hr><h2><a name="Positioners">Positioners</a></h2>

View File

@ -2,9 +2,9 @@ This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Klaus Schmidinger <vdr@tvdr.de>
Project's homepage: http://www.tvdr.de
Project's homepage: https://www.tvdr.de
Latest version available at: http://www.tvdr.de
Latest version available at: https://www.tvdr.de
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

View File

@ -2,9 +2,9 @@ This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Klaus Schmidinger <vdr@tvdr.de>
Project's homepage: http://www.tvdr.de
Project's homepage: https://www.tvdr.de
Latest version available at: http://www.tvdr.de
Latest version available at: https://www.tvdr.de
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

View File

@ -2,9 +2,9 @@ This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Klaus Schmidinger <vdr@tvdr.de>
Project's homepage: http://www.tvdr.de
Project's homepage: https://www.tvdr.de
Latest version available at: http://www.tvdr.de
Latest version available at: https://www.tvdr.de
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

View File

@ -2,9 +2,9 @@ This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Klaus Schmidinger <vdr@tvdr.de>
Project's homepage: http://www.tvdr.de
Project's homepage: https://www.tvdr.de
Latest version available at: http://www.tvdr.de
Latest version available at: https://www.tvdr.de
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

View File

@ -2,9 +2,9 @@ This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Udo Richter <udo_richter@gmx.de>
Project's homepage: http://www.tvdr.de
Project's homepage: https://www.tvdr.de
Latest version available at: http://www.tvdr.de
Latest version available at: https://www.tvdr.de
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

View File

@ -2,9 +2,9 @@ This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Klaus Schmidinger <vdr@tvdr.de>
Project's homepage: http://www.tvdr.de
Project's homepage: https://www.tvdr.de
Latest version available at: http://www.tvdr.de
Latest version available at: https://www.tvdr.de
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

View File

@ -76,3 +76,10 @@ VDR Plugin 'status' Revision History
2021-12-27: Version 2.6.0
- Official release.
2025-02-10: Version 2.6.1
- Added cStatus::OsdItem2().
- Activated logging of OsdItem2().
- Added cStatus::OsdCurrentItem2().
- Added cStatus::OsdStatusMessage2().

View File

@ -2,9 +2,9 @@ This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Klaus Schmidinger <vdr@tvdr.de>
Project's homepage: http://www.tvdr.de
Project's homepage: https://www.tvdr.de
Latest version available at: http://www.tvdr.de
Latest version available at: https://www.tvdr.de
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

View File

@ -3,13 +3,13 @@
*
* See the README file for copyright information and how to reach the author.
*
* $Id: status.c 4.1 2018/04/10 13:01:03 kls Exp $
* $Id: status.c 5.4 2025/02/12 21:18:53 kls Exp $
*/
#include <vdr/plugin.h>
#include <vdr/status.h>
static const char *VERSION = "2.4.0";
static const char *VERSION = "2.6.1";
static const char *DESCRIPTION = "Status monitor test";
static const char *MAINMENUENTRY = NULL;
@ -27,10 +27,10 @@ protected:
virtual void SetSubtitleTrack(int Index, const char * const *Tracks);
virtual void OsdClear(void);
virtual void OsdTitle(const char *Title);
virtual void OsdStatusMessage(const char *Message);
virtual void OsdStatusMessage2(eMessageType Type, const char *Message);
virtual void OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue);
virtual void OsdItem(const char *Text, int Index);
virtual void OsdCurrentItem(const char *Text);
virtual void OsdItem2(const char *Text, int Index, bool Selectable);
virtual void OsdCurrentItem2(const char *Text, int Index);
virtual void OsdTextItem(const char *Text, bool Scroll);
virtual void OsdChannel(const char *Text);
virtual void OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle);
@ -86,9 +86,9 @@ void cStatusTest::OsdTitle(const char *Title)
dsyslog("status: cStatusTest::OsdTitle '%s'", Title);
}
void cStatusTest::OsdStatusMessage(const char *Message)
void cStatusTest::OsdStatusMessage2(eMessageType Type, const char *Message)
{
dsyslog("status: cStatusTest::OsdStatusMessage '%s'", Message);
dsyslog("status: cStatusTest::OsdStatusMessage2 %d '%s'", Type, Message);
}
void cStatusTest::OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue)
@ -96,14 +96,14 @@ void cStatusTest::OsdHelpKeys(const char *Red, const char *Green, const char *Ye
dsyslog("status: cStatusTest::OsdHelpKeys %s - %s - %s - %s", Red, Green, Yellow, Blue);
}
void cStatusTest::OsdItem(const char *Text, int Index)
void cStatusTest::OsdItem2(const char *Text, int Index, bool Selected)
{
//dsyslog("status: cStatusTest::OsdItem %s %d", Text, Index);
dsyslog("status: cStatusTest::OsdItem2 %s %d %d", Text, Index, Selected);
}
void cStatusTest::OsdCurrentItem(const char *Text)
void cStatusTest::OsdCurrentItem2(const char *Text, int Index)
{
dsyslog("status: cStatusTest::OsdCurrentItem %s", Text);
dsyslog("status: cStatusTest::OsdCurrentItem %s %d", Text, Index);
}
void cStatusTest::OsdTextItem(const char *Text, bool Scroll)

View File

@ -2,9 +2,9 @@ This is a "plugin" for the Video Disk Recorder (VDR).
Written by: Klaus Schmidinger <Klaus.Schmidinger@tvdr.de>
Project's homepage: http://www.tvdr.de
Project's homepage: https://www.tvdr.de
Latest version available at: http://www.tvdr.de
Latest version available at: https://www.tvdr.de
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

4
README
View File

@ -4,7 +4,7 @@ Video Disk Recorder ('VDR')
These files contain the source code of the "Video Disk Recorder",
which is based on the DVB driver of the LinuxTV project (http://linuxtv.org).
For details about the "Video Disk Recorder" project please
refer to http://www.tvdr.de.
refer to https://www.tvdr.de.
Please see the INSTALL file for details on how to install
this program on your computer.
@ -33,7 +33,7 @@ the ones in this system, but here we have the full source code
and can modify the menus in whatever way desired.
If you actually use VDR, please add yourself to the "VDR User Counter"
at http://www.tvdr.de/counter.htm. You can also like VDR on facebook
at https://www.tvdr.de/counter.htm. You can also like VDR on facebook
at https://www.facebook.com/VideoDiskRecorder.

View File

@ -10,7 +10,7 @@ Plugins:
- Implemented a universal plugin interface. See the file PLUGINS.html
for a detailed description. The man page vdr(1) describes the new options '-L'
and '-P' used to load plugins.
See http://www.tvdr.de/plugins.htm for a list of available plugins.
See https://www.tvdr.de/plugins.htm for a list of available plugins.
- Rearranged the remote control key handling to allow plugins to implement
additional types of remote controls (see PLUGINS.html, section "Remote Control").
The previously used files 'keys.conf' and 'keys-pc.conf' have been replaced

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: channels.c 5.1 2021/05/21 09:38:34 kls Exp $
* $Id: channels.c 5.3 2024/03/02 16:21:16 kls Exp $
*/
#include "channels.h"
@ -97,23 +97,32 @@ cChannel& cChannel::operator= (const cChannel &Channel)
provider = strcpyrealloc(provider, Channel.provider);
portalName = strcpyrealloc(portalName, Channel.portalName);
memcpy(&__BeginData__, &Channel.__BeginData__, (char *)&Channel.__EndData__ - (char *)&Channel.__BeginData__);
nameSource = NULL; // these will be recalculated automatically
nameSourceMode = 0;
shortNameSource = NULL;
UpdateNameSource();
parameters = Channel.parameters;
return *this;
}
void cChannel::UpdateNameSource(void)
{
if (Setup.ShowChannelNamesWithSource == 0) {
nameSource = NULL;
shortNameSource = NULL;
return;
}
if (Setup.ShowChannelNamesWithSource == 1)
nameSource = cString::sprintf("%s (%c)", name, cSource::ToChar(source));
else
nameSource = cString::sprintf("%s (%s)", name, *cSource::ToString(source));
shortNameSource = cString::sprintf("%s (%c)", shortName, cSource::ToChar(source));
}
const char *cChannel::Name(void) const
{
if (Setup.ShowChannelNamesWithSource && !groupSep) {
if (isempty(nameSource) || nameSourceMode != Setup.ShowChannelNamesWithSource) {
if (Setup.ShowChannelNamesWithSource == 1)
nameSource = cString::sprintf("%s (%c)", name, cSource::ToChar(source));
else
nameSource = cString::sprintf("%s (%s)", name, *cSource::ToString(source));
}
return nameSource;
if (!isempty(nameSource))
return nameSource;
}
return name;
}
@ -123,9 +132,8 @@ const char *cChannel::ShortName(bool OrName) const
if (OrName && isempty(shortName))
return Name();
if (Setup.ShowChannelNamesWithSource && !groupSep) {
if (isempty(shortNameSource))
shortNameSource = cString::sprintf("%s (%c)", shortName, cSource::ToChar(source));
return shortNameSource;
if (!isempty(shortNameSource))
return shortNameSource;
}
return shortName;
}
@ -203,9 +211,7 @@ bool cChannel::SetTransponderData(int Source, int Frequency, int Srate, const ch
srate = Srate;
parameters = Parameters;
schedule = NULL;
nameSource = NULL;
nameSourceMode = 0;
shortNameSource = NULL;
UpdateNameSource();
if (Number() && !Quiet) {
dsyslog("changing transponder data of channel %d (%s) from %s to %s", Number(), name, *OldTransponderData, *TransponderDataToString());
modification |= CHANNELMOD_TRANSP;
@ -270,15 +276,12 @@ bool cChannel::SetName(const char *Name, const char *ShortName, const char *Prov
dsyslog("changing name of channel %d from '%s,%s;%s' to '%s,%s;%s'", Number(), name, shortName, provider, Name, ShortName, Provider);
modification |= CHANNELMOD_NAME;
}
if (nn) {
if (nn)
name = strcpyrealloc(name, Name);
nameSource = NULL;
nameSourceMode = 0;
}
if (ns) {
if (ns)
shortName = strcpyrealloc(shortName, ShortName);
shortNameSource = NULL;
}
if (nn || ns)
UpdateNameSource();
if (np)
provider = strcpyrealloc(provider, Provider);
return true;
@ -804,9 +807,7 @@ bool cChannel::Parse(const char *s)
free(tpidbuf);
free(caidbuf);
free(namebuf);
nameSource = NULL;
nameSourceMode = 0;
shortNameSource = NULL;
UpdateNameSource();
if (!GetChannelID().Valid()) {
esyslog("ERROR: channel data results in invalid ID!");
return false;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: channels.h 5.2 2021/05/21 09:38:34 kls Exp $
* $Id: channels.h 5.3 2024/03/02 16:21:16 kls Exp $
*/
#ifndef __CHANNELS_H
@ -87,6 +87,7 @@ class cChannels;
class cChannel : public cListObject {
friend class cSchedules;
friend class cMenuEditChannel;
friend class cMenuSetupMisc;
friend class cDvbSourceParam;
private:
static cString ToText(const cChannel *Channel);
@ -123,9 +124,8 @@ private:
int number; // Sequence number assigned on load
bool groupSep;
int __EndData__;
mutable cString nameSource;
mutable int nameSourceMode;
mutable cString shortNameSource;
cString nameSource;
cString shortNameSource;
cString parameters;
mutable int modification;
time_t seen; // When this channel was last seen in the SDT of its transponder
@ -133,6 +133,7 @@ private:
cLinkChannels *linkChannels;
cChannel *refChannel;
cString TransponderDataToString(void) const;
void UpdateNameSource(void);
public:
cChannel(void);
cChannel(const cChannel &Channel);

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: config.c 4.8 2018/02/15 14:40:36 kls Exp $
* $Id: config.c 5.1 2024/03/04 21:13:58 kls Exp $
*/
#include "config.h"
@ -409,6 +409,8 @@ cSetup::cSetup(void)
SubtitleBgTransparency = 0;
EPGLanguages[0] = -1;
EPGScanTimeout = 5;
EPGScanMaxChannel = 0;
EPGPauseAfterScan = 0;
EPGBugfixLevel = 3;
EPGLinger = 0;
SVDRPTimeout = 300;
@ -637,6 +639,8 @@ bool cSetup::Parse(const char *Name, const char *Value)
else if (!strcasecmp(Name, "SubtitleBgTransparency")) SubtitleBgTransparency = atoi(Value);
else if (!strcasecmp(Name, "EPGLanguages")) return ParseLanguages(Value, EPGLanguages);
else if (!strcasecmp(Name, "EPGScanTimeout")) EPGScanTimeout = atoi(Value);
else if (!strcasecmp(Name, "EPGScanMaxChannel")) EPGScanMaxChannel = atoi(Value);
else if (!strcasecmp(Name, "EPGPauseAfterScan")) EPGPauseAfterScan = atoi(Value);
else if (!strcasecmp(Name, "EPGBugfixLevel")) EPGBugfixLevel = atoi(Value);
else if (!strcasecmp(Name, "EPGLinger")) EPGLinger = atoi(Value);
else if (!strcasecmp(Name, "SVDRPTimeout")) SVDRPTimeout = atoi(Value);
@ -769,6 +773,8 @@ bool cSetup::Save(void)
Store("SubtitleBgTransparency", SubtitleBgTransparency);
StoreLanguages("EPGLanguages", EPGLanguages);
Store("EPGScanTimeout", EPGScanTimeout);
Store("EPGScanMaxChannel", EPGScanMaxChannel);
Store("EPGPauseAfterScan", EPGPauseAfterScan);
Store("EPGBugfixLevel", EPGBugfixLevel);
Store("EPGLinger", EPGLinger);
Store("SVDRPTimeout", SVDRPTimeout);

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: config.h 5.12 2023/02/17 12:04:45 kls Exp $
* $Id: config.h 5.26 2025/02/26 10:35:03 kls Exp $
*/
#ifndef __CONFIG_H
@ -22,19 +22,21 @@
// VDR's own version number:
#define VDRVERSION "2.6.4"
#define VDRVERSNUM 20604 // Version * 10000 + Major * 100 + Minor
#define VDRVERSION "2.7.4"
#define VDRVERSNUM 20704 // Version * 10000 + Major * 100 + Minor
// The plugin API's version number:
#define APIVERSION "2.6.3"
#define APIVERSNUM 20603 // Version * 10000 + Major * 100 + Minor
#define APIVERSION "6"
#define APIVERSNUM 30006
// When loading plugins, VDR searches them by their APIVERSION, which
// may be smaller than VDRVERSION in case there have been no changes to
// VDR header files since the last APIVERSION. This allows compiled
// When loading plugins, VDR searches files by their APIVERSION, which
// is different from VDRVERSION. APIVERSION is a plain number, incremented
// 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
// VDR header files have changed.
// interfaces have changed. APIVERSNUM begins with "300.." for backwards
// compatibility and can be used in #if preprocessor statements to handle
// version dependent code.
#define MAXPRIORITY 99
#define MINPRIORITY (-MAXPRIORITY)
@ -291,6 +293,8 @@ public:
int SubtitleOffset;
int SubtitleFgTransparency, SubtitleBgTransparency;
int EPGLanguages[I18N_MAX_LANGUAGES + 1];
int EPGScanMaxChannel;
int EPGPauseAfterScan;
int EPGScanTimeout;
int EPGBugfixLevel;
int EPGLinger;

View File

@ -4,12 +4,11 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: cutter.c 5.1 2022/11/06 11:25:13 kls Exp $
* $Id: cutter.c 5.4 2025/01/10 13:12:04 kls Exp $
*/
#include "cutter.h"
#include "menu.h"
#include "recording.h"
#include "remux.h"
#include "videodir.h"
@ -232,6 +231,10 @@ private:
int numSequences;
off_t maxVideoFileSize;
off_t fileSize;
int frameErrors;
time_t lastErrorHandling;
cString editedRecordingName;
cRecordingInfo *recordingInfo;
bool suspensionLogged;
int sequence; // cutting sequence
int delta; // time between two frames (PTS ticks)
@ -246,7 +249,7 @@ private:
cPatPmtParser patPmtParser;
bool Throttled(void);
bool SwitchFile(bool Force = false);
bool LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length);
bool LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length, bool *Errors = NULL, bool *Missing = NULL);
bool FramesAreEqual(int Index1, int Index2);
void GetPendingPackets(uchar *Buffer, int &Length, int Index);
// Gather all non-video TS packets from Index upward that either belong to
@ -254,15 +257,16 @@ private:
// and add them to the end of the given Data.
bool FixFrame(uchar *Data, int &Length, bool Independent, int Index, bool CutIn, bool CutOut);
bool ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex);
void HandleErrors(bool Force = false);
protected:
virtual void Action(void);
public:
cCuttingThread(const char *FromFileName, const char *ToFileName);
cCuttingThread(const char *FromFileName, const char *ToFileName, cRecordingInfo *RecordingInfo);
virtual ~cCuttingThread();
const char *Error(void) { return error; }
};
cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName)
cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName, cRecordingInfo *RecordingInfo)
:cThread("video cutting", true)
{
error = NULL;
@ -274,6 +278,10 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName)
framesPerSecond = Recording.FramesPerSecond();
suspensionLogged = false;
fileSize = 0;
frameErrors = 0;
lastErrorHandling = 0;
editedRecordingName = ToFileName;
recordingInfo = RecordingInfo;
sequence = 0;
delta = int(round(PTSTICKS / framesPerSecond));
lastVidPts = -1;
@ -294,6 +302,10 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName)
maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize);
if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES))
maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES);
if (fromIndex->GetErrors()->Size() > 0) {
recordingInfo->SetErrors(0); // the fromIndex has error indicators, so we reset the error count
recordingInfo->Write();
}
Start();
}
else
@ -328,11 +340,11 @@ bool cCuttingThread::Throttled(void)
return false;
}
bool cCuttingThread::LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length)
bool cCuttingThread::LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length, bool *Errors, bool *Missing)
{
uint16_t FileNumber;
off_t FileOffset;
if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length)) {
if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length, Errors, Missing)) {
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
if (fromFile) {
fromFile->SetReadAhead(MEGABYTE(20));
@ -555,7 +567,9 @@ bool cCuttingThread::ProcessSequence(int LastEndIndex, int BeginIndex, int EndIn
for (int Index = BeginIndex; Running() && Index < EndIndex; Index++) {
bool Independent;
int Length;
if (LoadFrame(Index, Buffer, Independent, Length)) {
bool Errors;
bool Missing;
if (LoadFrame(Index, Buffer, Independent, Length, &Errors, &Missing)) {
// Make sure there is enough disk space:
AssertFreeDiskSpace(-1);
bool CutIn = !SeamlessBegin && Index == BeginIndex;
@ -572,10 +586,12 @@ bool cCuttingThread::ProcessSequence(int LastEndIndex, int BeginIndex, int EndIn
return false;
}
// Write index:
if (!DeletedFrame && !toIndex->Write(Independent, toFileName->Number(), fileSize)) {
if (!DeletedFrame && !toIndex->Write(Independent, toFileName->Number(), fileSize, Errors, Missing)) {
error = "toIndex";
return false;
}
frameErrors += Errors + Missing;
HandleErrors();
// Write data:
if (toFile->Write(Buffer, Length) < 0) {
error = "safe_write";
@ -596,6 +612,27 @@ bool cCuttingThread::ProcessSequence(int LastEndIndex, int BeginIndex, int EndIn
return true;
}
#define ERROR_HANDLING_DELTA 1 // seconds between handling errors
void cCuttingThread::HandleErrors(bool Force)
{
if (Force || time(NULL) - lastErrorHandling >= ERROR_HANDLING_DELTA) {
if (frameErrors > recordingInfo->Errors()) {
recordingInfo->SetErrors(frameErrors);
recordingInfo->Write();
Force = true;
}
if (Force) {
cStateKey StateKey;
if (cRecordings *Recordings = cRecordings::GetRecordingsWrite(StateKey, 1)) {
Recordings->UpdateByName(editedRecordingName);
StateKey.Remove();
}
}
lastErrorHandling = time(NULL);
}
}
void cCuttingThread::Action(void)
{
if (cMark *BeginMark = fromMarks.GetNextBegin()) {
@ -604,6 +641,7 @@ void cCuttingThread::Action(void)
if (!fromFile || !toFile)
return;
int LastEndIndex = -1;
HandleErrors(true); // to make sure an initially reset error count is displayed correctly
while (BeginMark && Running()) {
// Suspend cutting if we have severe throughput problems:
if (Throttled()) {
@ -634,6 +672,7 @@ void cCuttingThread::Action(void)
}
}
}
HandleErrors(true);
}
else
esyslog("no editing marks found!");
@ -642,6 +681,7 @@ void cCuttingThread::Action(void)
// --- cCutter ---------------------------------------------------------------
cCutter::cCutter(const char *FileName)
:recordingInfo(FileName)
{
cuttingThread = NULL;
error = false;
@ -676,9 +716,11 @@ bool cCutter::Start(void)
if (strcmp(originalVersionName, editedVersionName) != 0) { // names must be different!
cRecordingUserCommand::InvokeCommand(RUC_EDITINGRECORDING, editedVersionName, originalVersionName);
if (cVideoDirectory::RemoveVideoFile(editedVersionName) && MakeDirs(editedVersionName, true)) {
Recording.WriteInfo(editedVersionName);
recordingInfo.Read();
recordingInfo.SetFileName(editedVersionName);
recordingInfo.Write();
SetRecordingTimerId(editedVersionName, cString::sprintf("%d@%s", 0, Setup.SVDRPHostName));
cuttingThread = new cCuttingThread(originalVersionName, editedVersionName);
cuttingThread = new cCuttingThread(originalVersionName, editedVersionName, &recordingInfo);
return true;
}
}

View File

@ -4,12 +4,13 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: cutter.h 3.1 2013/10/05 11:34:55 kls Exp $
* $Id: cutter.h 5.1 2024/09/19 20:21:58 kls Exp $
*/
#ifndef __CUTTER_H
#define __CUTTER_H
#include "recording.h"
#include "thread.h"
#include "tools.h"
@ -19,6 +20,7 @@ class cCutter {
private:
cString originalVersionName;
cString editedVersionName;
cRecordingInfo recordingInfo;
cCuttingThread *cuttingThread;
bool error;
public:

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: device.c 5.7 2023/02/16 14:53:38 kls Exp $
* $Id: device.c 5.14 2024/07/06 11:19:21 kls Exp $
*/
#include "device.h"
@ -95,7 +95,9 @@ cDevice::cDevice(void)
camSlot = NULL;
occupiedFrom = 0;
occupiedTimeout = 0;
occupiedPriority = MINPRIORITY;
player = NULL;
isPlayingVideo = false;
@ -284,8 +286,12 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
continue; // no CAM is able to decrypt this channel and the device uses vdr handled CAMs
if (NumUsableSlots && !HasInternalCam && !CamSlots.Get(j)->Assign(device[i], true))
continue; // CAM slot can't be used with this device
bool ndr;
if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basically able to do the job
bool ndr = false;
bool TunedToTransponder = device[i]->IsTunedToTransponder(Channel);
if (TunedToTransponder || device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basically able to do the job
bool OccupiedOtherTransponder = !TunedToTransponder && device[i]->Occupied();
if (OccupiedOtherTransponder)
ndr = true;
if (NumUsableSlots && !HasInternalCam) {
if (cCamSlot *csi = device[i]->CamSlot()) {
cCamSlot *csj = CamSlots.Get(j);
@ -303,7 +309,7 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
imp <<= 1; imp |= (LiveView && NumUsableSlots && !HasInternalCam) ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), CamSlots.Get(j)->MasterSlotNumber()) || ndr : 0; // prefer CAMs that are known to decrypt this channel for live viewing, if we don't need to detach existing receivers
imp <<= 1; imp |= LiveView ? !device[i]->IsPrimaryDevice() || ndr : 0; // prefer the primary device for live viewing if we don't need to detach existing receivers
imp <<= 1; imp |= !device[i]->Receiving() && (device[i] != cTransferControl::ReceiverDevice() || device[i]->IsPrimaryDevice()) || ndr; // use receiving devices if we don't need to detach existing receivers, but avoid primary device in local transfer mode
imp <<= 1; imp |= device[i]->Receiving(); // avoid devices that are receiving
imp <<= 1; imp |= device[i]->Receiving() || OccupiedOtherTransponder; // avoid devices that are receiving
imp <<= 5; imp |= GetClippedNumProvidedSystems(5, device[i]) - 1; // avoid cards which support multiple delivery systems
imp <<= 8; imp |= device[i]->Priority() - IDLEPRIORITY; // use the device with the lowest priority (- IDLEPRIORITY to assure that values -100..99 can be used)
imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice(); // avoid the Transfer Mode receiver device
@ -321,6 +327,7 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView
if (NumUsableSlots && !HasInternalCam)
s = CamSlots.Get(j);
}
//dsyslog("device %d provides channel %d prio %d ndr %d imp %.8X", device[i]->DeviceNumber() + 1, Channel->Number(), Priority, ndr, imp);
}
}
if (!NumUsableSlots)
@ -427,7 +434,8 @@ cDevice *cDevice::GetDeviceForTransponder(const cChannel *Channel, int Priority)
if (d->ProvidesTransponder(Channel)) {
if (d->MaySwitchTransponder(Channel))
return d; // this device may switch to the transponder without disturbing any receiver or live view
else if (!d->Occupied() && !d->IsBonded()) { // MaySwitchTransponder() implicitly calls Occupied()
else if (!d->Occupied(Priority) && !d->IsBonded() && d->Priority(true) < LIVEPRIORITY) { // MaySwitchTransponder() implicitly calls Occupied()
// we select only devices with priority < LIVEPRIORITY, so device can be switched without impact on recordings or live viewing
if (d->Priority() < Priority && (!Device || d->Priority() < Device->Priority()))
Device = d; // use this one only if no other with less impact can be found
}
@ -673,11 +681,11 @@ void cDevice::StartSectionHandler(void)
void cDevice::StopSectionHandler(void)
{
if (sectionHandler) {
delete sectionHandler; // automatically detaches filters
delete nitFilter;
delete sdtFilter;
delete patFilter;
delete eitFilter;
delete sectionHandler;
nitFilter = NULL;
sdtFilter = NULL;
patFilter = NULL;
@ -801,7 +809,17 @@ bool cDevice::IsTunedToTransponder(const cChannel *Channel) const
bool cDevice::MaySwitchTransponder(const cChannel *Channel) const
{
return time(NULL) > occupiedTimeout && !Receiving() && !(pidHandles[ptAudio].pid || pidHandles[ptVideo].pid || pidHandles[ptDolby].pid);
return !Occupied() && !Receiving() && !(pidHandles[ptAudio].pid || pidHandles[ptVideo].pid || pidHandles[ptDolby].pid);
}
void cDevice::SetPowerSaveMode(bool On)
{
}
void cDevice::SetPowerSaveIfUnused(void)
{
if (!Occupied() && !Receiving() && !(pidHandles[ptAudio].pid || pidHandles[ptVideo].pid || pidHandles[ptDolby].pid))
SetPowerSaveMode(true);
}
bool cDevice::SwitchChannel(const cChannel *Channel, bool LiveView)
@ -876,7 +894,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
DELETENULL(dvbSubtitleConverter);
}
cDevice *Device = (LiveView && IsPrimaryDevice()) ? GetDevice(Channel, LIVEPRIORITY, true) : this;
cDevice *Device = (LiveView && IsPrimaryDevice(false)) ? GetDevice(Channel, LIVEPRIORITY, true) : this;
bool NeedsTransferMode = LiveView && Device != PrimaryDevice();
// If the CAM slot wants the TS data, we need to switch to Transfer Mode:
@ -908,6 +926,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
// channel to it, for possible later decryption:
if (camSlot)
camSlot->AddChannel(Channel);
SetPowerSaveMode(false);
if (SetChannelDevice(Channel, LiveView)) {
// Start section handling:
if (sectionHandler) {
@ -923,23 +942,26 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
}
if (Result == scrOk) {
if (LiveView && IsPrimaryDevice()) {
if (patFilter) // this is only for FF DVB cards!
patFilter->Request(Channel->Sid());
currentChannel = Channel->Number();
// Set the available audio tracks:
ClrAvailableTracks();
for (int i = 0; i < MAXAPIDS; i++)
SetAvailableTrack(ttAudio, i, Channel->Apid(i), Channel->Alang(i));
if (Setup.UseDolbyDigital) {
for (int i = 0; i < MAXDPIDS; i++)
SetAvailableTrack(ttDolby, i, Channel->Dpid(i), Channel->Dlang(i));
if (LiveView) {
if (IsPrimaryDevice(false))
currentChannel = Channel->Number();
if (IsPrimaryDevice()) {
if (patFilter) // this is only for FF DVB cards!
patFilter->Request(Channel->Sid());
// Set the available audio tracks:
ClrAvailableTracks();
for (int i = 0; i < MAXAPIDS; i++)
SetAvailableTrack(ttAudio, i, Channel->Apid(i), Channel->Alang(i));
if (Setup.UseDolbyDigital) {
for (int i = 0; i < MAXDPIDS; i++)
SetAvailableTrack(ttDolby, i, Channel->Dpid(i), Channel->Dlang(i));
}
for (int i = 0; i < MAXSPIDS; i++)
SetAvailableTrack(ttSubtitle, i, Channel->Spid(i), Channel->Slang(i));
if (!NeedsTransferMode)
EnsureAudioTrack(true);
EnsureSubtitleTrack();
}
for (int i = 0; i < MAXSPIDS; i++)
SetAvailableTrack(ttSubtitle, i, Channel->Spid(i), Channel->Slang(i));
if (!NeedsTransferMode)
EnsureAudioTrack(true);
EnsureSubtitleTrack();
}
cStatus::MsgChannelSwitch(this, Channel->Number(), LiveView); // only report status if channel switch successful
}
@ -951,21 +973,34 @@ void cDevice::ForceTransferMode(void)
{
if (!cTransferControl::ReceiverDevice()) {
LOCK_CHANNELS_READ;
if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel()))
if (const cChannel *Channel = Channels->GetByNumber(CurrentChannel())) {
SetPowerSaveMode(false);
SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode
}
}
}
int cDevice::Occupied(void) const
int cDevice::Occupied(int Priority) const
{
if (Priority > occupiedPriority)
return 0;
int Seconds = occupiedTimeout - time(NULL);
return Seconds > 0 ? Seconds : 0;
}
void cDevice::SetOccupied(int Seconds)
void cDevice::SetOccupied(int Seconds, int Priority, time_t From)
{
if (Seconds >= 0)
occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT);
if (Seconds < 0)
return;
if (From == 0)
From = time(NULL);
if (From == occupiedFrom)
occupiedPriority = max(Priority, occupiedPriority);
else {
occupiedPriority = Priority;
occupiedFrom = From;
}
occupiedTimeout = From + min(Seconds, MAXOCCUPIEDTIMEOUT);
}
bool cDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
@ -1657,11 +1692,13 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly)
return Played;
}
int cDevice::Priority(void) const
int cDevice::Priority(bool IgnoreOccupied) const
{
int priority = IDLEPRIORITY;
if (IsPrimaryDevice() && !Replaying() && HasProgramme())
priority = TRANSFERPRIORITY; // we use the same value here, no matter whether it's actual Transfer Mode or real live viewing
if (!IgnoreOccupied && time(NULL) <= occupiedTimeout && occupiedPriority > priority)
priority = occupiedPriority - 1; // so timers with occupiedPriority can start
cMutexLock MutexLock(&mutexReceiver);
for (int i = 0; i < MAXRECEIVERS; i++) {
if (receiver[i])

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: device.h 5.2 2022/01/24 16:53:45 kls Exp $
* $Id: device.h 5.6 2024/07/15 14:42:22 kls Exp $
*/
#ifndef __DEVICE_H
@ -178,6 +178,9 @@ public:
///< the transponder of the given Channel, without disturbing any receiver
///< at priorities higher or equal to Priority.
///< If no such device is currently available, NULL will be returned.
///< Devices recording (Device->Priority(true) >= LIVEPRIORITY) will not be returned,
///< even if Priority >= LIVEPRIORITY. Such higher priorities are only used to
///< override occupied.
static void Shutdown(void);
///< Closes down all devices.
///< Must be called at the end of the program.
@ -217,7 +220,7 @@ protected:
///< May be dropped in a future version, if a better solution is found.
///< Do not use otherwise!
public:
bool IsPrimaryDevice(void) const { return this == primaryDevice && HasDecoder(); }
bool IsPrimaryDevice(bool CheckDecoder = true) const { return this == primaryDevice && (!CheckDecoder || HasDecoder()); }
int CardIndex(void) const { return cardIndex; }
///< Returns the card index of this device (0 ... MAXDEVICES - 1).
int DeviceNumber(void) const;
@ -259,7 +262,9 @@ public:
private:
mutable cMutex mutexChannel;
time_t occupiedFrom;
time_t occupiedTimeout;
int occupiedPriority;
protected:
static int currentChannel;
public:
@ -341,6 +346,14 @@ public:
///< device, without disturbing any other activities. If an occupied timeout
///< has been set for this device, and that timeout has not yet expired,
///< this function returns false.
virtual void SetPowerSaveMode(bool On);
///< Puts the device into power save mode, if applicable.
///< If On is true, power save mode shall be activated, if it is false,
///< normal operating mode shall be restored.
///< The default implementation does nothing.
void SetPowerSaveIfUnused(void);
///< Sets this device into a power save mode if it is not currently used and
///< has implemented SetPowerSaveMode().
bool SwitchChannel(const cChannel *Channel, bool LiveView);
///< Switches the device to the given Channel, initiating transfer mode
///< if necessary.
@ -357,21 +370,16 @@ protected:
public:
static int CurrentChannel(void) { return primaryDevice ? currentChannel : 0; }
///< Returns the number of the current channel on the primary device.
#ifndef DEPRECATED_SETCURRENTCHANNEL
#define DEPRECATED_SETCURRENTCHANNEL 0
#endif
#if DEPRECATED_SETCURRENTCHANNEL
static void SetCurrentChannel(const cChannel *Channel) { currentChannel = Channel ? Channel->Number() : 0; }
#endif
static void SetCurrentChannel(int ChannelNumber) { currentChannel = ChannelNumber; }
///< Sets the number of the current channel on the primary device, without
///< actually switching to it. This can be used to correct the current
///< channel number while replaying.
void ForceTransferMode(void);
///< Forces the device into transfermode for the current channel.
int Occupied(void) const;
///< Returns the number of seconds this device is still occupied for.
void SetOccupied(int Seconds);
int Occupied(int Priority = MINPRIORITY) const;
///< Returns the number of seconds this device is still occupied for
///< with a priority >= Priority.
void SetOccupied(int Seconds, int Priority = MINPRIORITY, time_t From = 0);
///< Sets the occupied timeout for this device to the given number of
///< Seconds, This can be used to tune a device to a particular transponder
///< and make sure it will stay there for a certain amount of time, for
@ -379,6 +387,10 @@ public:
///< after the device has been successfully tuned to the requested transponder.
///< Seconds will be silently limited to MAXOCCUPIEDTIMEOUT. Values less than
///< 0 will be silently ignored.
///< The timeout is counted from the given From time (by default the current time).
///< Calling this function several times with the same From time will set the
///< priority to the maximum of the given values.
///< Priority() may return a value >= Priority until the timeout.
virtual bool HasLock(int TimeoutMs = 0) const;
///< Returns true if the device has a lock on the requested transponder.
///< Default is true, a specific device implementation may return false
@ -833,9 +845,10 @@ private:
mutable cMutex mutexReceiver;
cReceiver *receiver[MAXRECEIVERS];
public:
int Priority(void) const;
int Priority(bool IgnoreOccupied = false) const;
///< Returns the priority of the current receiving session (-MAXPRIORITY..MAXPRIORITY),
///< or IDLEPRIORITY if no receiver is currently active.
///< If IgnoreOccupied is true, a priority set with SetOccupied() is ignored.
protected:
virtual bool OpenDvr(void);
///< Opens the DVR of this device and prepares it to deliver a Transport

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbdevice.c 5.3 2022/12/05 14:04:10 kls Exp $
* $Id: dvbdevice.c 5.8 2024/09/09 08:53:57 kls Exp $
*/
#include "dvbdevice.h"
@ -394,7 +394,7 @@ void cDvbFrontend::Close(void)
{
if (fd_frontend >= 0) {
if (close(fd_frontend) != 0)
esyslog("ERROR: frontend %d/%d", adapter, frontend);
esyslog("ERROR: frontend %d/%d: %m (%s:%d)", adapter, frontend, __FILE__, __LINE__);
fd_frontend = -1;
}
}
@ -549,12 +549,12 @@ private:
mutable uint32_t lastUncDelta;
mutable time_t lastUncChange;
cChannel channel;
const cDiseqc *lastDiseqc;
mutable const cDiseqc *lastDiseqc;
int diseqcOffset;
int lastSource;
mutable int lastSource;
cPositioner *positioner;
const cScr *scr;
bool lnbPowerTurnedOn;
mutable bool lnbPowerTurnedOn;
eTunerStatus tunerStatus;
mutable cMutex mutex;
cCondVar locked;
@ -593,6 +593,7 @@ public:
bool GetSignalStats(int &Valid, double *Strength = NULL, double *Cnr = NULL, double *BerPre = NULL, double *BerPost = NULL, double *Per = NULL, int *Status = NULL) const;
int GetSignalStrength(void) const;
int GetSignalQuality(void) const;
void SetPowerSaveMode(bool On);
};
cMutex cDvbTuner::bondMutex;
@ -696,9 +697,12 @@ bool cDvbTuner::ProvidesFrontend(const cChannel *Channel, bool Activate) const
fd_frontend = dvbFrontend->Open();
frontend = i;
dsyslog("using frontend %d/%d", adapter, frontend);
lastDiseqc = NULL;
lastSource = 0;
lastUncValue = 0;
lastUncDelta = 0;
lastUncChange = 0;
lnbPowerTurnedOn = false;
}
return true;
}
@ -859,6 +863,8 @@ void cDvbTuner::ClearEventQueue(void) const
bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const
{
if (fd_frontend == -1)
return false;
ClearEventQueue();
Status = (fe_status_t)0; // initialize here to fix buggy drivers
while (1) {
@ -876,6 +882,8 @@ bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const
bool cDvbTuner::GetSignalStats(int &Valid, double *Strength, double *Cnr, double *BerPre, double *BerPost, double *Per, int *Status) const
{
if (fd_frontend == -1)
return false;
ClearEventQueue();
fe_status_t FeStatus = (fe_status_t)0; // initialize here to fix buggy drivers
dtv_property Props[MAXFRONTENDCMDS];
@ -1226,6 +1234,8 @@ int SignalToSQI(const cChannel *Channel, int Signal, int Ber, int FeModulation,
int cDvbTuner::GetSignalStrength(void) const
{
if (fd_frontend == -1)
return 0;
ClearEventQueue();
// Try DVB API 5:
for (int i = 0; i < 1; i++) { // just a trick to break out with 'continue' ;-)
@ -1291,6 +1301,8 @@ int cDvbTuner::GetSignalStrength(void) const
int cDvbTuner::GetSignalQuality(void) const
{
if (fd_frontend == -1)
return 0;
// Try DVB API 5:
for (int i = 0; i < 1; i++) { // just a trick to break out with 'continue' ;-)
dtv_property Props[MAXFRONTENDCMDS];
@ -1536,6 +1548,8 @@ void cDvbTuner::ExecuteDiseqc(const cDiseqc *Diseqc, int *Frequency)
void cDvbTuner::ResetToneAndVoltage(void)
{
if (fd_frontend == -1)
return;
CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, bondedTuner ? SEC_VOLTAGE_OFF : SEC_VOLTAGE_13));
CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF));
}
@ -1768,6 +1782,32 @@ void cDvbTuner::Action(void)
}
}
void cDvbTuner::SetPowerSaveMode(bool On)
{
cMutexLock MutexLock(&mutex);
if (On) {
if (fd_frontend != -1) {
dsyslog("closing frontend %d/%d", adapter, frontend);
tunerStatus = tsIdle;
dvbFrontend->Close();
fd_frontend = -1;
channel = cChannel();
}
}
else {
if (fd_frontend == -1) {
dsyslog("opening frontend %d/%d", adapter, frontend);
fd_frontend = dvbFrontend->Open();
lastDiseqc = NULL;
lastSource = 0;
lastUncValue = 0;
lastUncDelta = 0;
lastUncChange = 0;
lnbPowerTurnedOn = false;
}
}
}
// --- cDvbSourceParam -------------------------------------------------------
class cDvbSourceParam : public cSourceParam {
@ -2300,6 +2340,11 @@ bool cDvbDevice::MaySwitchTransponder(const cChannel *Channel) const
return BondingOk(Channel, true) && cDevice::MaySwitchTransponder(Channel);
}
void cDvbDevice::SetPowerSaveMode(bool On)
{
dvbTuner->SetPowerSaveMode(On);
}
bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
{
if (dvbTuner->ProvidesFrontend(Channel, true)) {

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbdevice.h 4.7 2020/06/27 10:24:46 kls Exp $
* $Id: dvbdevice.h 5.1 2024/07/08 09:34:33 kls Exp $
*/
#ifndef __DVBDEVICE_H
@ -244,6 +244,7 @@ public:
virtual const cChannel *GetCurrentlyTunedTransponder(void) const;
virtual bool IsTunedToTransponder(const cChannel *Channel) const;
virtual bool MaySwitchTransponder(const cChannel *Channel) const;
virtual void SetPowerSaveMode(bool On);
protected:
virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
public:

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbplayer.c 5.3 2022/12/27 15:57:20 kls Exp $
* $Id: dvbplayer.c 5.7 2025/02/19 15:39:16 kls Exp $
*/
#include "dvbplayer.h"
@ -36,7 +36,7 @@ public:
bool IsEmpty(void);
void Put(uint32_t Pts, int Index, bool Independent);
int FindIndex(uint32_t Pts);
int FindFrameNumber(uint32_t Pts);
int FindFrameNumber(uint32_t Pts, bool Forward);
};
cPtsIndex::cPtsIndex(void)
@ -90,30 +90,30 @@ int cPtsIndex::FindIndex(uint32_t Pts)
return Index;
}
int cPtsIndex::FindFrameNumber(uint32_t Pts)
int cPtsIndex::FindFrameNumber(uint32_t Pts, bool Forward)
{
if (!Forward)
return FindIndex(Pts); // there are only I frames in backward
cMutexLock MutexLock(&mutex);
if (w == r)
return lastFound; // replay always starts at an I frame
bool Valid = false;
int d;
int FrameNumber = 0;
int UnplayedIFrame = 2; // GOPs may intersect, so we're looping until we found two unplayed I frames
int UnplayedIFrame = 2; // GOPs may intersect, so we loop until we processed a complete unplayed GOP
for (int i = r; i != w && UnplayedIFrame; ) {
d = Pts - pi[i].pts;
if (d > 0x7FFFFFFF)
d = 0xFFFFFFFF - d; // handle rollover
if (d > 0) {
int32_t d = int32_t(Pts - pi[i].pts); // typecast handles rollover
if (d >= 0) {
if (pi[i].independent) {
FrameNumber = pi[i].index; // an I frame's index represents its frame number
Valid = true;
if (d == 0)
UnplayedIFrame = 1; // if Pts is at an I frame we only need to check up to the next I frame
}
else
FrameNumber++; // for every played non-I frame, increase frame number
}
else
if (pi[i].independent)
--UnplayedIFrame;
else if (pi[i].independent)
--UnplayedIFrame;
if (++i >= PTSINDEX_ENTRIES)
i = 0;
}
@ -283,6 +283,7 @@ public:
void Goto(int Position, bool Still = false);
virtual double FramesPerSecond(void) { return framesPerSecond; }
virtual void SetAudioTrack(eTrackType Type, const tTrackId *TrackId);
virtual const cErrors *GetErrors(void);
virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
virtual bool GetFrameNumber(int &Current, int &Total);
virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed);
@ -792,15 +793,17 @@ void cDvbPlayer::Forward(void)
Pause();
break;
}
Empty();
// run into pmPause
case pmStill:
case pmPause:
case pmPause: {
LOCK_THREAD;
Empty();
DeviceMute();
playMode = pmSlow;
playDir = pdForward;
trickSpeed = NORMAL_SPEED;
TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS);
}
break;
default: esyslog("ERROR: unknown playMode %d (%s)", playMode, __FUNCTION__);
}
@ -841,7 +844,6 @@ void cDvbPlayer::Backward(void)
Pause();
break;
}
Empty();
// run into pmPause
case pmStill:
case pmPause: {
@ -940,6 +942,13 @@ void cDvbPlayer::SetAudioTrack(eTrackType Type, const tTrackId *TrackId)
resyncAfterPause = true;
}
const cErrors *cDvbPlayer::GetErrors(void)
{
if (index)
return index->GetErrors();
return NULL;
}
bool cDvbPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
{
if (index) {
@ -959,7 +968,7 @@ bool cDvbPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
bool cDvbPlayer::GetFrameNumber(int &Current, int &Total)
{
if (index) {
Current = ptsIndex.FindFrameNumber(DeviceGetSTC());
Current = ptsIndex.FindFrameNumber(DeviceGetSTC(), playDir == pdForward);
Total = index->Last();
return true;
}
@ -1047,6 +1056,13 @@ int cDvbPlayerControl::SkipFrames(int Frames)
return -1;
}
const cErrors *cDvbPlayerControl::GetErrors(void)
{
if (player)
return player->GetErrors();
return NULL;
}
bool cDvbPlayerControl::GetIndex(int &Current, int &Total, bool SnapToIFrame)
{
if (player) {

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: dvbplayer.h 4.2 2016/12/22 10:36:50 kls Exp $
* $Id: dvbplayer.h 5.1 2024/09/19 09:49:02 kls Exp $
*/
#ifndef __DVBPLAYER_H
@ -47,6 +47,8 @@ public:
// The sign of 'Seconds' determines the direction in which to skip.
// Use a very large negative value to go all the way back to the
// beginning of the recording.
const cErrors *GetErrors(void);
// Returns the frame indexes of errors in the recording (if any).
bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
// Returns the current and total frame index, optionally snapped to the
// nearest I-frame.

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: eitscan.c 4.3 2019/03/12 11:46:05 kls Exp $
* $Id: eitscan.c 5.7 2024/07/13 20:12:24 kls Exp $
*/
#include "eitscan.h"
@ -44,11 +44,23 @@ int cScanData::Compare(const cListObject &ListObject) const
// --- cScanList -------------------------------------------------------------
class cScanList : public cList<cScanData> {
private:
bool HasDeviceForChannelEIT(const cChannel *Channel) const;
public:
void AddTransponders(const cList<cChannel> *Channels);
void AddTransponder(const cChannel *Channel);
};
bool cScanList::HasDeviceForChannelEIT(const cChannel *Channel) const
{
for (int i = 0; i < cDevice::NumDevices(); i++) {
cDevice *Device = cDevice::GetDevice(i);
if (Device && Device->ProvidesEIT() && Device->ProvidesTransponder(Channel))
return true;
}
return false;
}
void cScanList::AddTransponders(const cList<cChannel> *Channels)
{
for (const cChannel *ch = Channels->First(); ch; ch = Channels->Next(ch))
@ -58,7 +70,9 @@ void cScanList::AddTransponders(const cList<cChannel> *Channels)
void cScanList::AddTransponder(const cChannel *Channel)
{
if (Channel->Source() && Channel->Transponder()) {
if (Channel->Source() && Channel->Transponder() && (Setup.EPGScanMaxChannel <= 0 || Channel->Number() < Setup.EPGScanMaxChannel)) {
if (!HasDeviceForChannelEIT(Channel))
return;
for (cScanData *sd = First(); sd; sd = Next(sd)) {
if (sd->Source() == Channel->Source() && ISTRANSPONDER(sd->Transponder(), Channel->Transponder()))
return;
@ -91,9 +105,11 @@ cEITScanner EITScanner;
cEITScanner::cEITScanner(void)
{
lastScan = lastActivity = time(NULL);
paused = false;
lastScan = 0;
lastActivity = time(NULL);
currentChannel = 0;
scanList = NULL;
scanList = new cScanList;
transponderList = NULL;
}
@ -130,25 +146,46 @@ void cEITScanner::Process(void)
if (Setup.EPGScanTimeout || !lastActivity) { // !lastActivity means a scan was forced
time_t now = time(NULL);
if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) {
if (Setup.EPGPauseAfterScan && scanList->Count() == 0 && lastActivity && lastScan && now - lastScan < Setup.EPGScanTimeout * 3600) {
if (!paused) {
dsyslog("pause EPG scan");
paused = true;
}
// Allow unused devices to go into power save mode:
if ((now - lastScan) % 10 == 0) { // let's not do this too often
for (int i = 0; i < cDevice::NumDevices(); i++) {
if (cDevice *Device = cDevice::GetDevice(i))
Device->SetPowerSaveIfUnused();
}
}
return; // pause for Setup.EPGScanTimeout hours
}
else if (paused) {
dsyslog("start EPG scan");
paused = false;
}
cStateKey StateKey;
if (const cChannels *Channels = cChannels::GetChannelsRead(StateKey, 10)) {
if (!scanList) {
scanList = new cScanList;
if (scanList->Count() == 0) {
if (transponderList) {
scanList->AddTransponders(transponderList);
delete transponderList;
transponderList = NULL;
}
scanList->AddTransponders(Channels);
//dsyslog("EIT scan: %d scanList entries", scanList->Count());
}
bool AnyDeviceSwitched = false;
for (int i = 0; i < cDevice::NumDevices(); i++) {
cDevice *Device = cDevice::GetDevice(i);
if (Device && Device->ProvidesEIT()) {
for (cScanData *ScanData = scanList->First(); ScanData; ScanData = scanList->Next(ScanData)) {
cScanData *Next = NULL;
for (cScanData *ScanData = scanList->First(); ScanData; ScanData = Next) {
Next = scanList->Next(ScanData);
const cChannel *Channel = ScanData->GetChannel();
if (Channel) {
if (!Channel->Ca() || Channel->Ca() == Device->DeviceNumber() + 1 || Channel->Ca() >= CA_ENCRYPTED_MIN) {
if (Device->IsTunedToTransponder(Channel))
scanList->Del(ScanData);
else if (!Channel->Ca() || Channel->Ca() == Device->DeviceNumber() + 1 || Channel->Ca() >= CA_ENCRYPTED_MIN) {
if (Device->ProvidesTransponder(Channel)) {
if (Device->Priority() < 0) {
if (const cPositioner *Positioner = Device->Positioner()) {
@ -164,10 +201,9 @@ void cEITScanner::Process(void)
Skins.Message(mtInfo, tr("Starting EPG scan"));
}
}
//dsyslog("EIT scan: device %d source %-8s tp %5d", Device->DeviceNumber() + 1, *cSource::ToString(Channel->Source()), Channel->Transponder());
//dsyslog("EIT scan: %d device %d source %-8s tp %5d", scanList->Count(), Device->DeviceNumber() + 1, *cSource::ToString(Channel->Source()), Channel->Transponder());
Device->SwitchChannel(Channel, false);
scanList->Del(ScanData);
AnyDeviceSwitched = true;
break;
}
}
@ -177,9 +213,7 @@ void cEITScanner::Process(void)
}
}
}
if (!AnyDeviceSwitched) {
delete scanList;
scanList = NULL;
if (scanList->Count() == 0) {
if (lastActivity == 0) // this was a triggered scan
Activity();
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: eitscan.h 2.1 2012/03/07 14:16:57 kls Exp $
* $Id: eitscan.h 5.1 2024/07/06 11:19:21 kls Exp $
*/
#ifndef __EITSCAN_H
@ -23,6 +23,7 @@ private:
enum { ActivityTimeout = 60,
ScanTimeout = 20
};
bool paused;
time_t lastScan, lastActivity;
int currentChannel;
cScanList *scanList;

83
epg.c
View File

@ -7,7 +7,7 @@
* Original version (as used in VDR before 1.3.0) written by
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
*
* $Id: epg.c 5.7 2022/11/22 14:33:48 kls Exp $
* $Id: epg.c 5.13 2024/11/30 14:30:46 kls Exp $
*/
#include "epg.h"
@ -145,7 +145,10 @@ cEvent::~cEvent()
int cEvent::Compare(const cListObject &ListObject) const
{
cEvent *e = (cEvent *)&ListObject;
return startTime - e->startTime;
int d = startTime - e->startTime;
if (d == 0)
d = int(tableID) - int(e->tableID);
return d;
}
tChannelID cEvent::ChannelID(void) const
@ -177,7 +180,7 @@ void cEvent::SetVersion(uchar Version)
void cEvent::SetRunningStatus(int RunningStatus, const cChannel *Channel)
{
if (Channel && runningStatus != RunningStatus && (RunningStatus > SI::RunningStatusNotRunning || runningStatus > SI::RunningStatusUndefined) && schedule && schedule->HasTimer())
isyslog("channel %d (%s) event %s status %d", Channel->Number(), Channel->Name(), *ToDescr(), RunningStatus);
isyslog("channel %d (%s) event %s status %d->%d", Channel->Number(), Channel->Name(), *ToDescr(), runningStatus, RunningStatus);
runningStatus = RunningStatus;
}
@ -300,7 +303,7 @@ const char *cEvent::ContentToString(uchar Content)
case 0x01: return tr("Content$News/Weather Report");
case 0x02: return tr("Content$News Magazine");
case 0x03: return tr("Content$Documentary");
case 0x04: return tr("Content$Discussion/Inverview/Debate");
case 0x04: return tr("Content$Discussion/Interview/Debate");
}
break;
case ecgShow:
@ -346,7 +349,7 @@ const char *cEvent::ContentToString(uchar Content)
case 0x00: return tr("Content$Music/Ballet/Dance");
case 0x01: return tr("Content$Rock/Pop");
case 0x02: return tr("Content$Serious/Classical Music");
case 0x03: return tr("Content$Folk/Tradional Music");
case 0x03: return tr("Content$Folk/Traditional Music");
case 0x04: return tr("Content$Jazz");
case 0x05: return tr("Content$Musical/Opera");
case 0x06: return tr("Content$Ballet");
@ -885,6 +888,23 @@ void cEvent::FixEpgBugs(void)
Final:
// And then there are the specially gifted people who put a literal "\n" string where there should be
// a '\n' character:
if (shortText) {
if (char *p = strstr(shortText, "\\n")) {
*p = 0;
p += 2;
char *s = strdup(shortText);
char *d = strdup(cString::sprintf("%s\n\n%s", p, description));
free(shortText);
free(description);
shortText = s;
description = d;
EpgBugFixStat(12, ChannelID());
}
}
description = strreplace(description, "\\n", " \n");
// VDR can't usefully handle newline characters in the title, shortText or component description of EPG
// data, so let's always convert them to blanks (independent of the setting of EPGBugfixLevel):
strreplace(title, '\n', ' ');
@ -911,7 +931,6 @@ cSchedule::cSchedule(tChannelID ChannelID)
channelID = ChannelID;
events.SetUseGarbageCollector();
numTimers = 0;
hasRunning = false;
modified = 0;
onActualTp = false;
presentSeen = 0;
@ -1011,18 +1030,6 @@ const cEvent *cSchedule::GetFollowingEvent(void) const
return p;
}
#if DEPRECATED_SCHEDULE_GET_EVENT
const cEvent *cSchedule::GetEvent(tEventID EventID, time_t StartTime) const
{
// Returns the event info with the given StartTime or, if no actual StartTime
// is given, the one with the given EventID.
if (StartTime > 0) // 'StartTime < 0' is apparently used with NVOD channels
return eventsHashStartTime.Get(StartTime);
else
return eventsHashID.Get(EventID);
}
#endif
const cEvent *cSchedule::GetEventById(tEventID EventID) const
{
return eventsHashID.Get(EventID);
@ -1051,7 +1058,6 @@ const cEvent *cSchedule::GetEventAround(time_t Time) const
void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, const cChannel *Channel)
{
hasRunning = false;
for (cEvent *p = events.First(); p; p = events.Next(p)) {
if (p == Event) {
if (p->RunningStatus() > SI::RunningStatusNotRunning || RunningStatus > SI::RunningStatusNotRunning) {
@ -1061,24 +1067,19 @@ void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, const cChanne
}
else if (RunningStatus >= SI::RunningStatusPausing && p->StartTime() < Event->StartTime())
p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
if (p->RunningStatus() >= SI::RunningStatusPausing)
hasRunning = true;
}
SetPresentSeen();
}
void cSchedule::ClrRunningStatus(cChannel *Channel)
{
if (hasRunning) {
for (cEvent *p = events.First(); p; p = events.Next(p)) {
if (p->RunningStatus() >= SI::RunningStatusPausing) {
p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
hasRunning = false;
SetModified();
break;
}
for (cEvent *p = events.First(); p; p = events.Next(p)) {
if (p->RunningStatus() >= SI::RunningStatusPausing) {
p->SetRunningStatus(SI::RunningStatusNotRunning, Channel);
SetModified();
break;
}
}
}
}
void cSchedule::ResetVersions(void)
@ -1090,14 +1091,6 @@ void cSchedule::ResetVersions(void)
void cSchedule::Sort(void)
{
events.Sort();
// Make sure there are no RunningStatusUndefined before the currently running event:
if (hasRunning) {
for (cEvent *p = events.First(); p; p = events.Next(p)) {
if (p->RunningStatus() >= SI::RunningStatusPausing)
break;
p->SetRunningStatus(SI::RunningStatusNotRunning);
}
}
SetModified();
}
@ -1124,6 +1117,20 @@ void cSchedule::DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar Table
p = n;
}
}
// Make sure there are no two events with the same start time:
for (cEvent *p = events.First(); p; p = events.Next(p)) {
cEvent *n = events.Next(p);
if (n && n->StartTime() == p->StartTime())
DelEvent(n);
}
// Make sure there are no RunningStatusUndefined before the currently running event:
for (cEvent *p = events.First(); p; p = events.Next(p)) {
if (p->RunningStatus() >= SI::RunningStatusPausing) {
for (p = events.Prev(p); p; p = events.Prev(p))
p->SetRunningStatus(SI::RunningStatusNotRunning);
break;
}
}
}
void cSchedule::Cleanup(void)

7
epg.h
View File

@ -7,7 +7,7 @@
* Original version (as used in VDR before 1.3.0) written by
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
*
* $Id: epg.h 5.3 2022/12/24 11:37:17 kls Exp $
* $Id: epg.h 5.8 2024/10/13 09:47:18 kls Exp $
*/
#ifndef __EPG_H
@ -157,7 +157,6 @@ private:
cHash<cEvent> eventsHashID;
cHash<cEvent> eventsHashStartTime;
mutable u_int16_t numTimers;// The number of timers that use this schedule
bool hasRunning;
bool onActualTp;
int modified;
time_t presentSeen;
@ -187,10 +186,6 @@ public:
const cList<cEvent> *Events(void) const { return &events; }
const cEvent *GetPresentEvent(void) const;
const cEvent *GetFollowingEvent(void) const;
#define DEPRECATED_SCHEDULE_GET_EVENT 1
#if DEPRECATED_SCHEDULE_GET_EVENT
const cEvent *GetEvent(tEventID EventID, time_t StartTime = 0) const;
#endif
const cEvent *GetEventById(tEventID EventID) const;
const cEvent *GetEventByTime(time_t StartTime) const;
const cEvent *GetEventAround(time_t Time) const;

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: filter.c 5.1 2021/03/16 15:10:54 kls Exp $
* $Id: filter.c 5.2 2024/10/13 09:47:18 kls Exp $
*/
#include "filter.h"
@ -73,35 +73,6 @@ bool cSectionSyncer::Processed(int SectionNumber, int LastSectionNumber, int Seg
return complete;
}
#if DEPRECATED_SECTIONSYNCER_SYNC_REPEAT
void cSectionSyncer::Repeat(void)
{
SetSectionFlag(currentSection, false);
synced = false;
complete = false;
}
bool cSectionSyncer::Sync(uchar Version, int Number, int LastNumber)
{
if (Version != currentVersion) {
Reset();
currentVersion = Version;
}
if (!synced) {
if (Number != 0)
return false;
else
synced = true;
}
currentSection = Number;
bool Result = !GetSectionFlag(Number);
SetSectionFlag(Number, true);
if (Number == LastNumber)
complete = true;
return Result;
}
#endif
// --- cFilterData -----------------------------------------------------------
cFilterData::cFilterData(void)

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: filter.h 5.1 2021/03/16 15:10:54 kls Exp $
* $Id: filter.h 5.5 2024/10/13 09:47:18 kls Exp $
*/
#ifndef __FILTER_H
@ -13,8 +13,6 @@
#include <sys/types.h>
#include "tools.h"
#define DEPRECATED_SECTIONSYNCER_SYNC_REPEAT 1
class cSectionSyncer {
private:
int currentVersion;
@ -49,10 +47,6 @@ public:
///< Returns true if all sections have been processed.
bool Complete(void) { return complete; }
///< Returns true if all sections have been processed.
#if DEPRECATED_SECTIONSYNCER_SYNC_REPEAT
void Repeat(void);
bool Sync(uchar Version, int Number, int LastNumber);
#endif
};
class cSectionSyncerRandom : public cSectionSyncer {

4
font.c
View File

@ -6,7 +6,7 @@
*
* BiDi support by Osama Alrawab <alrawab@hotmail.com> @2008 Tripoli-Libya.
*
* $Id: font.c 5.2 2022/12/06 12:30:13 kls Exp $
* $Id: font.c 5.3 2025/02/17 11:13:13 kls Exp $
*/
#include "font.h"
@ -618,7 +618,7 @@ void cTextWrapper::Set(const char *Text, const cFont *Font, int Width)
}
}
w += cw;
if (strchr("-.,:;!?_", *p)) {
if (strchr("-.,:;!?_~", *p)) {
Delim = p;
Blank = NULL;
}

147
menu.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: menu.c 5.9 2022/12/01 13:09:04 kls Exp $
* $Id: menu.c 5.23 2025/02/25 15:53:43 kls Exp $
*/
#include "menu.h"
@ -342,6 +342,7 @@ void cMenuChannelItem::Set(void)
void cMenuChannelItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
{
LOCK_CHANNELS_READ;
if (!DisplayMenu->SetItemChannel(channel, Index, Current, Selectable, sortMode == csmProvider))
DisplayMenu->SetItem(Text(), Index, Current, Selectable);
}
@ -443,8 +444,9 @@ eOSState cMenuChannels::Number(eKeys Key)
number = number * 10 + Key - k0;
for (cMenuChannelItem *ci = (cMenuChannelItem *)First(); ci; ci = (cMenuChannelItem *)ci->Next()) {
if (!ci->Channel()->GroupSep() && ci->Channel()->Number() == number) {
DisplayCurrent(false);
SetCurrent(ci);
Display();
DisplayCurrent(true);
break;
}
}
@ -1263,6 +1265,7 @@ void cMenuTimerItem::Set(void)
}
const char *File = timer->Pattern();
if (!*File) {
LOCK_SCHEDULES_READ;
if (timer->HasFlags(tfSpawned) && timer->Event() && timer->Event()->Title())
File = timer->Event()->Title();
else {
@ -1291,6 +1294,7 @@ void cMenuTimerItem::Set(void)
void cMenuTimerItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
{
LOCK_TIMERS_READ;
if (!DisplayMenu->SetItemTimer(timer, Index, Current, Selectable))
DisplayMenu->SetItem(Text(), Index, Current, Selectable);
}
@ -1334,12 +1338,15 @@ void cMenuTimers::Set(void)
const cTimer *CurrentTimer = GetTimer();
cMenuTimerItem *CurrentItem = NULL;
Clear();
for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
cMenuTimerItem *Item = new cMenuTimerItem(Timer);
Add(Item);
if (CurrentTimer && Timer->Id() == CurrentTimer->Id() && (!Timer->Remote() && !CurrentTimer->Remote() || Timer->Remote() && CurrentTimer->Remote() && strcmp(Timer->Remote(), CurrentTimer->Remote()) == 0))
CurrentItem = Item;
}
{
LOCK_SCHEDULES_READ;
for (const cTimer *Timer = Timers->First(); Timer; Timer = Timers->Next(Timer)) {
cMenuTimerItem *Item = new cMenuTimerItem(Timer);
Add(Item);
if (CurrentTimer && Timer->Id() == CurrentTimer->Id() && (!Timer->Remote() && !CurrentTimer->Remote() || Timer->Remote() && CurrentTimer->Remote() && strcmp(Timer->Remote(), CurrentTimer->Remote()) == 0))
CurrentItem = Item;
}
}
Sort();
SetCurrent(CurrentItem ? CurrentItem : First());
SetHelpKeys();
@ -1454,6 +1461,7 @@ eOSState cMenuTimers::Info(void)
return osContinue;
LOCK_TIMERS_READ;
LOCK_CHANNELS_READ;
LOCK_SCHEDULES_READ;
cTimer *Timer = GetTimer();
if (Timer && Timer->Event())
return AddSubMenu(new cMenuEvent(Timers, Channels, Timer->Event()));
@ -1513,6 +1521,7 @@ cMenuEvent::cMenuEvent(const cTimers *Timers, const cChannels *Channels, const c
void cMenuEvent::Display(void)
{
LOCK_SCHEDULES_READ;
cOsdMenu::Display();
DisplayMenu()->SetEvent(event);
if (event->Description())
@ -1599,6 +1608,8 @@ static const char *TimerMatchChars = " tT iI";
bool cMenuScheduleItem::Update(const cTimers *Timers, bool Force)
{
LOCK_CHANNELS_READ;
LOCK_SCHEDULES_READ;
eTimerMatch OldTimerMatch = timerMatch;
bool OldTimerActive = timerActive;
const cTimer *Timer = Timers->GetMatch(event, &timerMatch);
@ -1626,6 +1637,8 @@ bool cMenuScheduleItem::Update(const cTimers *Timers, bool Force)
void cMenuScheduleItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
{
LOCK_CHANNELS_READ;
LOCK_SCHEDULES_READ;
if (!DisplayMenu->SetItemEvent(event, Index, Current, Selectable, channel, withDate, timerMatch, timerActive))
DisplayMenu->SetItem(Text(), Index, Current, Selectable);
}
@ -1671,7 +1684,6 @@ cMenuWhatsOn::cMenuWhatsOn(const cTimers *Timers, const cChannels *Channels, con
}
}
currentChannel = CurrentChannelNr;
Display();
SetHelpKeys(Channels);
}
@ -1772,10 +1784,8 @@ eOSState cMenuWhatsOn::Record(void)
if (HasSubMenu())
CloseSubMenu();
}
if (Update()) {
LOCK_SCHEDULES_READ;
if (Update())
Display();
}
LOCK_CHANNELS_READ;
SetHelpKeys(Channels);
return osContinue;
@ -1795,6 +1805,7 @@ eOSState cMenuWhatsOn::ProcessKey(eKeys Key)
case kGreen: {
cMenuScheduleItem *mi = (cMenuScheduleItem *)Get(Current());
if (mi) {
LOCK_CHANNELS_READ;
scheduleEvent = mi->event;
currentChannel = mi->channel->Number();
}
@ -1807,14 +1818,12 @@ eOSState cMenuWhatsOn::ProcessKey(eKeys Key)
case kChanUp:
case kChanDn|k_Repeat:
case kChanDn: if (!HasSubMenu()) {
LOCK_CHANNELS_READ;
for (cOsdItem *item = First(); item; item = Next(item)) {
if (((cMenuScheduleItem *)item)->channel->Number() == cDevice::CurrentChannel()) {
DisplayCurrent(false);
SetCurrent(item);
{
LOCK_SCHEDULES_READ;
Display();
}
LOCK_CHANNELS_READ;
DisplayCurrent(true);
SetHelpKeys(Channels);
break;
}
@ -1832,10 +1841,8 @@ eOSState cMenuWhatsOn::ProcessKey(eKeys Key)
}
}
else if (!HasSubMenu()) {
if (HadSubMenu && Update()) {
LOCK_SCHEDULES_READ;
if (HadSubMenu && Update())
Display();
}
if (Key != kNone) {
LOCK_CHANNELS_READ;
SetHelpKeys(Channels);
@ -2083,10 +2090,8 @@ eOSState cMenuSchedule::Record(void)
if (HasSubMenu())
CloseSubMenu();
}
if (Update()) {
LOCK_SCHEDULES_READ;
if (Update())
Display();
}
SetHelpKeys();
return osContinue;
}
@ -2180,10 +2185,8 @@ eOSState cMenuSchedule::ProcessKey(eKeys Key)
Set(Timers, Channels, Channel, true);
}
}
else if (HadSubMenu && Update()) {
LOCK_SCHEDULES_READ;
else if (HadSubMenu && Update())
Display();
}
if (Key != kNone)
SetHelpKeys();
}
@ -2777,7 +2780,9 @@ eOSState cMenuRecordingEdit::Action(void)
RecordingsHandler.Del(recording->FileName());
else if (doCut) {
if (access(cCutter::EditedFileName(recording->FileName()), F_OK) != 0 || Interface->Confirm(tr("Edited version already exists - overwrite?"))) {
if (!RecordingsHandler.Add(ruCut, recording->FileName()))
if (!EnoughFreeDiskSpaceForEdit(recording->FileName()))
Skins.Message(mtError, tr("Not enough free disk space to start editing process!"));
else if (!RecordingsHandler.Add(ruCut, recording->FileName()))
Skins.Message(mtError, tr("Error while queueing recording for cutting!"));
}
}
@ -3040,6 +3045,7 @@ void cMenuRecordingItem::IncrementCounter(bool New)
void cMenuRecordingItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
{
LOCK_RECORDINGS_READ;
if (!DisplayMenu->SetItemRecording(recording, Index, Current, Selectable, level, totalEntries, newEntries))
DisplayMenu->SetItem(Text(), Index, Current, Selectable);
}
@ -3067,7 +3073,6 @@ cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus,
return;
}
}
Display();
SetHelpKeys();
}
@ -3160,11 +3165,6 @@ void cMenuRecordings::Set(bool Refresh)
}
}
void cMenuRecordings::SetPath(const char *Path)
{
path = Path;
}
void cMenuRecordings::SetRecording(const char *FileName)
{
fileName = FileName;
@ -3294,8 +3294,11 @@ eOSState cMenuRecordings::Delete(void)
if (RecordingsHandler.GetUsage(FileName)) {
if (!Interface->Confirm(tr("Recording is being edited - really delete?")))
return osContinue;
SetNeedsFastResponse(true); // makes sure the edited version is removed from the menu ASAP
}
}
else
return osContinue; // recording has already been deleted
}
RecordingsHandler.Del(FileName); // must do this w/o holding a lock, because the cleanup section in cDirCopier::Action() might request one!
if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), FileName) == 0)
@ -3654,6 +3657,8 @@ void cMenuSetupEPG::Setup(void)
Clear();
Add(new cMenuEditIntItem( tr("Setup.EPG$EPG scan timeout (h)"), &data.EPGScanTimeout));
Add(new cMenuEditIntItem( tr("Setup.EPG$EPG scan max. channel number (0=all)"), &data.EPGScanMaxChannel));
Add(new cMenuEditBoolItem(tr("Setup.EPG$EPG pause after scan"), &data.EPGPauseAfterScan));
Add(new cMenuEditIntItem( tr("Setup.EPG$EPG bugfix level"), &data.EPGBugfixLevel, 0, MAXEPGBUGFIXLEVEL));
Add(new cMenuEditIntItem( tr("Setup.EPG$EPG linger time (min)"), &data.EPGLinger, 0));
Add(new cMenuEditBoolItem(tr("Setup.EPG$Set system time"), &data.SetSystemTime));
@ -4286,9 +4291,17 @@ eOSState cMenuSetupMisc::ProcessKey(eKeys Key)
{
bool OldSVDRPPeering = data.SVDRPPeering;
bool ModifiedSVDRPSettings = false;
if (Key == kOk)
bool ModifiedShowChannelNamesWithSource = false;
if (Key == kOk) {
ModifiedSVDRPSettings = data.SVDRPPeering != Setup.SVDRPPeering || strcmp(data.SVDRPHostName, Setup.SVDRPHostName);
ModifiedShowChannelNamesWithSource = data.ShowChannelNamesWithSource != Setup.ShowChannelNamesWithSource;
}
eOSState state = cMenuSetupBase::ProcessKey(Key);
if (ModifiedShowChannelNamesWithSource) {
LOCK_CHANNELS_WRITE;
for (cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel))
Channel->UpdateNameSource();
}
if (data.SVDRPPeering != OldSVDRPPeering)
Set();
if (ModifiedSVDRPSettings) {
@ -4657,21 +4670,22 @@ eOSState cMenuMain::ProcessKey(eKeys Key)
default: break;
}
}
if (!HasSubMenu() && Update(HadSubMenu))
Display();
bool DoDisplay = Update();
if (Key != kNone) {
if (I18nCurrentLanguage() != osdLanguage) {
Set();
if (!HasSubMenu())
Display();
DoDisplay = true;
}
}
if (DoDisplay)
Display();
return state;
}
// --- SetTrackDescriptions --------------------------------------------------
static void SetTrackDescriptions(int LiveChannel)
void SetTrackDescriptions(int LiveChannel)
{
cDevice::PrimaryDevice()->ClrAvailableTracks(true);
const cComponents *Components = NULL;
@ -5700,6 +5714,7 @@ cReplayControl::cReplayControl(bool PauseLive)
displayReplay = NULL;
marksModified = false;
visible = modeOnly = shown = displayFrames = false;
lastErrors = 0;
lastCurrent = lastTotal = -1;
lastPlay = lastForward = false;
lastSpeed = -2; // an invalid value
@ -5873,6 +5888,7 @@ bool cReplayControl::ShowProgress(bool Initial)
if (!visible) {
displayReplay = Skins.Current()->DisplayReplay(modeOnly);
displayReplay->SetMarks(&marks);
displayReplay->SetErrors(GetErrors());
SetNeedsFastResponse(true);
visible = true;
}
@ -5884,7 +5900,9 @@ bool cReplayControl::ShowProgress(bool Initial)
}
lastCurrent = lastTotal = -1;
}
if (Current != lastCurrent || Total != lastTotal) {
const cErrors *Errors = GetErrors();
int NumErrors = Errors ? Errors->Size() : 0;
if (Current != lastCurrent || Total != lastTotal || NumErrors != lastErrors) {
if (Setup.ShowRemainingTime || Total != lastTotal) {
int Index = Total;
if (Setup.ShowRemainingTime)
@ -5893,10 +5911,12 @@ bool cReplayControl::ShowProgress(bool Initial)
}
displayReplay->SetProgress(Current, Total);
displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames, FramesPerSecond()));
displayReplay->SetErrors(Errors);
displayReplay->Flush();
lastCurrent = Current;
lastTotal = Total;
lastErrors = NumErrors;
}
lastTotal = Total;
ShowMode();
updateTimer.Set(PROGRESSTIMEOUT);
return true;
@ -6082,6 +6102,47 @@ void cReplayControl::MarkMove(int Frames, bool MarkRequired)
}
}
void cReplayControl::ErrorJump(bool Forward)
{
const cErrors *Errors = GetErrors();
int NumErrors = Errors ? Errors->Size() : 0;
if (NumErrors > 0) {
int Current, Total;
if (GetIndex(Current, Total)) {
if (Forward) {
int Offset = 0;
for (int i = 0; i < NumErrors; i++) {
int Position = Errors->At(i);
if (Position > Current + Offset) {
int NextIFrame = SkipFrames(Position - Current) + Offset; // this takes us to the I-frame at or right after Position
if (NextIFrame > Position) {
if (SkipFrames(Offset + 1) == NextIFrame) { // means Current is the I-frame right before Position
Offset = NextIFrame - Current;
continue;
}
}
Goto(Position, true); // this takes us to the I-frame at or right before Position
return;
}
}
if (Current < Total)
Goto(Total, true);
}
else {
for (int i = NumErrors - 1; i >= 0; i--) {
if (Errors->At(i) < Current) {
int Position = Errors->At(i);
Goto(Position, true); // this takes us to the I-frame at or right before Position
return;
}
}
if (Current > 0)
Goto(0, true);
}
}
}
}
void cReplayControl::EditCut(void)
{
if (*fileName) {
@ -6093,6 +6154,8 @@ void cReplayControl::EditCut(void)
Skins.Message(mtError, tr("No editing sequences defined!"));
else if (access(cCutter::EditedFileName(fileName), F_OK) == 0 && !Interface->Confirm(tr("Edited version already exists - overwrite?")))
;
else if (!EnoughFreeDiskSpaceForEdit(fileName))
Skins.Message(mtError, tr("Not enough free disk space to start editing process!"));
else if (!RecordingsHandler.Add(ruCut, fileName))
Skins.Message(mtError, tr("Can't start editing process!"));
else
@ -6224,6 +6287,10 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
case kMarkSkipBack: MarkMove(-adaptiveSkipper.GetValue(RAWKEY(Key)), false); break;
case kMarkSkipForward|k_Repeat:
case kMarkSkipForward: MarkMove(+adaptiveSkipper.GetValue(RAWKEY(Key)), false); break;
case kChanUp|k_Repeat:
case kChanUp: ErrorJump(true); break;
case kChanDn|k_Repeat:
case kChanDn: ErrorJump(false); break;
case kEditCut: EditCut(); break;
case kEditTest: EditTest(); break;
default: {

7
menu.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: menu.h 5.1 2020/12/26 15:49:01 kls Exp $
* $Id: menu.h 5.5 2024/10/11 14:10:50 kls Exp $
*/
#ifndef __MENU_H
@ -231,7 +231,6 @@ public:
cMenuRecordings(const char *Base = NULL, int Level = 0, bool OpenSubMenus = false, const cRecordingFilter *Filter = NULL);
~cMenuRecordings();
virtual eOSState ProcessKey(eKeys Key);
static void SetPath(const char *Path);
static void SetRecording(const char *FileName);
};
@ -298,6 +297,7 @@ private:
cMarks marks;
bool marksModified;
bool visible, modeOnly, shown, displayFrames;
int lastErrors;
int lastCurrent, lastTotal;
bool lastPlay, lastForward;
int lastSpeed;
@ -316,6 +316,7 @@ private:
void MarkToggle(void);
void MarkJump(bool Forward);
void MarkMove(int Frames, bool MarkRequired);
void ErrorJump(bool Forward);
void EditCut(void);
void EditTest(void);
public:
@ -335,4 +336,6 @@ public:
static void ClearLastReplayed(const char *FileName);
};
void SetTrackDescriptions(int LiveChannel);
#endif //__MENU_H

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: menuitems.c 5.1 2020/12/26 15:49:01 kls Exp $
* $Id: menuitems.c 5.3 2025/01/29 10:20:17 kls Exp $
*/
#include "menuitems.h"
@ -38,7 +38,6 @@ void cMenuEditItem::SetValue(const char *Value)
{
cString buffer = cString::sprintf("%s:\t%s", name, Value);
SetText(buffer);
cStatus::MsgOsdCurrentItem(buffer);
}
void cMenuEditItem::SetHelp(const char *Red, const char *Green, const char *Yellow, const char *Blue)
@ -497,10 +496,7 @@ void cMenuEditStrItem::AdvancePos(void)
void cMenuEditStrItem::Set(void)
{
if (InEditMode()) {
// This is an ugly hack to make editing strings work with the 'skincurses' plugin.
const cFont *font = dynamic_cast<cSkinDisplayMenu *>(cSkinDisplay::Current())->GetTextAreaFont(false);
if (!font || font->Width("W") != 1) // all characters have width == 1 in the font used by 'skincurses'
font = cFont::GetFont(fontOsd);
int width = cSkinDisplay::Current()->EditableWidth();
width -= font->Width("[]");

View File

@ -12,7 +12,7 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
# $Id: newplugin 5.1 2021/01/02 14:32:20 kls Exp $
# $Id: newplugin 5.2 2025/02/12 22:22:20 kls Exp $
$PLUGIN_NAME = $ARGV[0] || die "Usage: newplugin <name>\n";
@ -216,7 +216,6 @@ public:
virtual bool Start(void);
virtual void Stop(void);
virtual void Housekeeping(void);
virtual void MainThreadHook(void);
virtual cString Active(void);
virtual time_t WakeupTime(void);
virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
@ -274,12 +273,6 @@ void cPlugin${PLUGIN_CLASS}::Housekeeping(void)
// Perform any cleanup or other regular tasks.
}
void cPlugin${PLUGIN_CLASS}::MainThreadHook(void)
{
// Perform actions in the context of the main program thread.
// WARNING: Use with great care - see PLUGINS.html!
}
cString cPlugin${PLUGIN_CLASS}::Active(void)
{
// Return a message string if shutdown should be postponed

4
nit.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: nit.c 5.3 2022/11/30 12:02:00 kls Exp $
* $Id: nit.c 5.4 2024/05/29 11:13:34 kls Exp $
*/
#include "nit.h"
@ -257,7 +257,7 @@ void cNitFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length
cDvbTransponderParameters dtpc(Channel->Parameters());
dtp.SetSystem(dtpc.System());
dtp.SetStreamId(dtpc.StreamId());
dtp.SetT2SystemId(dtp.T2SystemId());
dtp.SetT2SystemId(dtpc.T2SystemId());
dtp.SetSisoMiso(dtpc.SisoMiso());
dtp.SetBandwidth(dtpc.Bandwidth());
dtp.SetGuard(dtpc.Guard());

79
osd.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: osd.c 4.11 2020/12/18 23:02:47 kls Exp $
* $Id: osd.c 5.2 2024/01/18 12:04:57 kls Exp $
*/
#include "osd.h"
@ -1139,6 +1139,49 @@ void cImage::Fill(tColor Color)
data[i] = Color;
}
cImage *cImage::Scaled(double FactorX, double FactorY, bool AntiAlias) const
{
int w = max(1, int(round(Width() * FactorX)));
int h = max(1, int(round(Height() * FactorY)));
cImage *i = new cImage(cSize(w, h));
int RatioX = (Width() << 16) / i->Width();
int RatioY = (Height() << 16) / i->Height();
if (!AntiAlias || FactorX <= 1.0 && FactorY <= 1.0) {
// Downscaling - no anti-aliasing:
int SourceY = 0;
for (int y = 0; y < i->Height(); y++) {
int SourceX = 0;
for (int x = 0; x < i->Width(); x++) {
tColor c1 = GetPixel(cPoint(SourceX >> 16, SourceY >> 16));
i->SetPixel(cPoint(x, y), c1);
SourceX += RatioX;
}
SourceY += RatioY;
}
}
else {
// Upscaling - anti-aliasing:
int SourceY = 0;
for (int y = 0; y < i->Height(); y++) {
int SourceX = 0;
int sy = min(SourceY >> 16, Height() - 2);
uint8_t BlendY = 0xFF - ((SourceY >> 8) & 0xFF);
for (int x = 0; x < i->Width(); x++) {
int sx = min(SourceX >> 16, Width() - 2);
uint8_t BlendX = 0xFF - ((SourceX >> 8) & 0xFF);
tColor c1 = AlphaBlend(GetPixel(cPoint(sx, sy)), GetPixel(cPoint(sx + 1, sy)), BlendX);
tColor c2 = AlphaBlend(GetPixel(cPoint(sx, sy + 1)), GetPixel(cPoint(sx + 1, sy + 1)), BlendX);
tColor c3 = AlphaBlend(c1, c2, BlendY);
i->SetPixel(cPoint(x, y), c3);
SourceX += RatioX;
}
SourceY += RatioY;
}
}
return i;
}
// --- cPixmapMemory ---------------------------------------------------------
cPixmapMemory::cPixmapMemory(void)
@ -1259,6 +1302,26 @@ void cPixmapMemory::DrawImage(const cPoint &Point, int ImageHandle)
Unlock();
}
void cPixmapMemory::DrawScaledImage(const cPoint &Point, const cImage &Image, double FactorX, double FactorY, bool AntiAlias)
{
Lock();
const cImage *i = &Image;
if (!DoubleEqual(FactorX, 1.0) || !DoubleEqual(FactorY, 1.0))
i = i->Scaled(FactorX, FactorY, AntiAlias);
DrawImage(Point, *i);
if (i != &Image)
delete i;
Unlock();
}
void cPixmapMemory::DrawScaledImage(const cPoint &Point, int ImageHandle, double FactorX, double FactorY, bool AntiAlias)
{
Lock();
if (const cImage *Image = cOsdProvider::GetImageData(ImageHandle))
DrawScaledImage(Point, *Image, FactorX, FactorY, AntiAlias);
Unlock();
}
void cPixmapMemory::DrawPixel(const cPoint &Point, tColor Color)
{
Lock();
@ -2118,6 +2181,18 @@ void cOsd::DrawImage(const cPoint &Point, int ImageHandle)
pixmaps[0]->DrawImage(Point, ImageHandle);
}
void cOsd::DrawScaledImage(const cPoint &Point, const cImage &Image, double FactorX, double FactorY, bool AntiAlias)
{
if (isTrueColor)
pixmaps[0]->DrawScaledImage(Point, Image, FactorX, FactorY, AntiAlias);
}
void cOsd::DrawScaledImage(const cPoint &Point, int ImageHandle, double FactorX, double FactorY, bool AntiAlias)
{
if (isTrueColor)
pixmaps[0]->DrawScaledImage(Point, ImageHandle, FactorX, FactorY, AntiAlias);
}
void cOsd::DrawPixel(int x, int y, tColor Color)
{
if (isTrueColor)
@ -2228,7 +2303,7 @@ cOsd *cOsdProvider::NewOsd(int Left, int Top, uint Level)
return Osd;
}
else
esyslog("ERROR: no OSD provider available - using dummy OSD!");
isyslog("no OSD provider available - using dummy OSD!");
return new cOsd(Left, Top, 999); // create a dummy cOsd, so that access won't result in a segfault
}

39
osd.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: osd.h 5.1 2021/05/21 12:54:08 kls Exp $
* $Id: osd.h 5.2 2024/01/18 12:04:57 kls Exp $
*/
#ifndef __OSD_H
@ -447,6 +447,11 @@ public:
///< Clears the image data by setting all pixels to be fully transparent.
void Fill(tColor Color);
///< Fills the image data with the given Color.
cImage *Scaled(double FactorX, double FactorY, bool AntiAlias = false) const;
///< Creates a copy of this image, scaled by the given factors.
///< If AntiAlias is true and either of the factors is greater than 1.0,
///< anti-aliasing is applied.
///< The caller must delete the returned image once it is no longer used.
};
#define MAXPIXMAPLAYERS 8
@ -602,6 +607,23 @@ public:
///< the given Point. ImageHandle must be a value that has previously been
///< returned by a call to cOsdProvider::StoreImage(). If ImageHandle
///< has an invalid value, nothing happens.
virtual void DrawScaledImage(const cPoint &Point, const cImage &Image, double FactorX, double FactorY, bool AntiAlias = false) {};
///< Draws the given Image into this pixmap at the given Point and scales it.
///< If AntiAlias is true and either of the factors is greater than
///< 1.0, anti-aliasing is applied.
///< This function should be 'pure', just like all the others. However, since it was
///< introduced when this class was already in widespread use, the default was made
///< empty, so that existing code will still compile.
virtual void DrawScaledImage(const cPoint &Point, int ImageHandle, double FactorX, double FactorY, bool AntiAlias = false) {};
///< Draws the image referenced by the given ImageHandle into this pixmap at
///< the given Point and scales it. ImageHandle must be a value that has
///< previously been returned by a call to cOsdProvider::StoreImage().
///< If ImageHandle has an invalid value, nothing happens.
///< If AntiAlias is true and either of the factors is greater than
///< 1.0, anti-aliasing is applied.
///< This function should be 'pure', just like all the others. However, since it was
///< introduced when this class was already in widespread use, the default was made
///< empty, so that existing code will still compile.
virtual void DrawPixel(const cPoint &Point, tColor Color) = 0;
///< Sets the pixel at the given Point to the given Color, which is
///< a full 32 bit ARGB value. If the alpha value of Color is not 0xFF
@ -700,6 +722,8 @@ public:
virtual void Fill(tColor Color);
virtual void DrawImage(const cPoint &Point, const cImage &Image);
virtual void DrawImage(const cPoint &Point, int ImageHandle);
virtual void DrawScaledImage(const cPoint &Point, const cImage &Image, double FactorX, double FactorY, bool AntiAlias = false);
virtual void DrawScaledImage(const cPoint &Point, int ImageHandle, double FactorX, double FactorY, bool AntiAlias = false);
virtual void DrawPixel(const cPoint &Point, tColor Color);
virtual void DrawBlendedPixel(const cPoint &Point, tColor Color, uint8_t AlphaLayer = ALPHA_OPAQUE);
virtual void DrawBitmap(const cPoint &Point, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0, bool Overlay = false);
@ -857,6 +881,19 @@ public:
///< returned by a call to cOsdProvider::StoreImage(). If ImageHandle
///< has an invalid value, nothing happens.
///< If this is not a true color OSD, this function does nothing.
virtual void DrawScaledImage(const cPoint &Point, const cImage &Image, double FactorX, double FactorY, bool AntiAlias = false);
///< Draws the given Image on this OSD at the given Point and scales it.
///< If AntiAlias is true and either of the factors is greater than
///< 1.0, anti-aliasing is applied.
///< If this is not a true color OSD, this function does nothing.
virtual void DrawScaledImage(const cPoint &Point, int ImageHandle, double FactorX, double FactorY, bool AntiAlias = false);
///< Draws the image referenced by the given ImageHandle on this OSD at
///< the given Point and scales it. ImageHandle must be a value that has
///< previously been returned by a call to cOsdProvider::StoreImage().
///< If ImageHandle has an invalid value, nothing happens.
///< If AntiAlias is true and either of the factors is greater than
///< 1.0, anti-aliasing is applied.
///< If this is not a true color OSD, this function does nothing.
virtual eOsdError CanHandleAreas(const tArea *Areas, int NumAreas);
///< Checks whether the OSD can display the given set of sub-areas.
///< The return value indicates whether a call to SetAreas() with this

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: osdbase.c 4.5 2018/03/24 11:47:45 kls Exp $
* $Id: osdbase.c 5.9 2025/02/17 10:49:10 kls Exp $
*/
#include "osdbase.h"
@ -78,12 +78,16 @@ void cOsdObject::Show(void)
cSkinDisplayMenu *cOsdMenu::displayMenu = NULL;
int cOsdMenu::displayMenuCount = 0;
int cOsdMenu::osdState = 0;
cOsdMenu *cOsdMenu::topMenu = NULL;
cOsdMenu::cOsdMenu(const char *Title, int c0, int c1, int c2, int c3, int c4)
{
isMenu = true;
digit = 0;
hasHotkeys = false;
if (!topMenu)
topMenu = this;
active = this == topMenu;
displayMenuItems = 0;
title = NULL;
menuCategory = mcUnknown;
@ -93,6 +97,7 @@ cOsdMenu::cOsdMenu(const char *Title, int c0, int c1, int c2, int c3, int c4)
SetCols(c0, c1, c2, c3, c4);
first = 0;
lastOffset = -1;
conveyStatus = true;
current = marked = -1;
subMenu = NULL;
helpRed = helpGreen = helpYellow = helpBlue = NULL;
@ -113,6 +118,8 @@ cOsdMenu::~cOsdMenu()
cStatus::MsgOsdClear();
if (!--displayMenuCount)
DELETENULL(displayMenu);
if (this == topMenu)
topMenu = NULL;
}
void cOsdMenu::SetMenuCategory(eMenuCategory MenuCategory)
@ -125,6 +132,11 @@ void cOsdMenu::SetMenuSortMode(eMenuSortMode MenuSortMode)
menuSortMode = MenuSortMode;
}
void cOsdMenu::SetActive(bool Active)
{
active = Active;
}
void cOsdMenu::SetDisplayMenu(void)
{
if (displayMenu) {
@ -169,6 +181,7 @@ void cOsdMenu::SetStatus(const char *s)
free(status);
status = s ? strdup(s) : NULL;
displayMenu->SetMessage(mtStatus, s);
cStatus::MsgOsdStatusMessage(mtStatus, s);
}
void cOsdMenu::SetTitle(const char *Title)
@ -181,7 +194,8 @@ void cOsdMenu::DisplayHelp(bool Force)
{
if (!helpDisplayed || Force) {
displayMenu->SetButtons(helpRed, helpGreen, helpYellow, helpBlue);
cStatus::MsgOsdHelpKeys(helpRed, helpGreen, helpYellow, helpBlue);
if (conveyStatus)
cStatus::MsgOsdHelpKeys(helpRed, helpGreen, helpYellow, helpBlue);
helpDisplayed = true;
}
}
@ -224,17 +238,28 @@ void cOsdMenu::Ins(cOsdItem *Item, bool Current, cOsdItem *Before)
current = Item->Index();
}
void cOsdMenu::DisplayNoStatus(void)
{
conveyStatus = false;
Display();
conveyStatus = true;
}
void cOsdMenu::Display(void)
{
if (subMenu) {
subMenu->Display();
return;
}
if (!active)
return;
if (cOsdProvider::OsdSizeChanged(osdState))
SetDisplayMenu();
displayMenu->SetMessage(mtStatus, NULL);
cStatus::MsgOsdStatusMessage(mtStatus, NULL);
displayMenu->Clear();
cStatus::MsgOsdClear();
if (conveyStatus)
cStatus::MsgOsdClear();
if (menuCategory != displayMenu->MenuCategory())
displayMenu->SetMenuCategory(menuCategory);
displayMenu->SetMenuSortMode(menuSortMode);
@ -242,13 +267,15 @@ void cOsdMenu::Display(void)
displayMenuItems = displayMenu->MaxItems();
displayMenu->SetTabs(cols[0], cols[1], cols[2], cols[3], cols[4]);//XXX
displayMenu->SetTitle(title);
cStatus::MsgOsdTitle(title);
if (conveyStatus)
cStatus::MsgOsdTitle(title);
DisplayHelp(true);
int count = Count();
if (count > 0) {
int ni = 0;
for (cOsdItem *item = First(); item; item = Next(item)) {
cStatus::MsgOsdItem(item->Text(), ni++);
if (conveyStatus)
cStatus::MsgOsdItem(item->Text(), ni++, item->Selectable());
if (current < 0 && item->Selectable())
current = item->Index();
}
@ -267,16 +294,18 @@ void cOsdMenu::Display(void)
for (cOsdItem *item = Get(first); item; item = Next(item)) {
bool CurrentSelectable = (i == current) && item->Selectable();
item->SetMenuItem(displayMenu, i - first, CurrentSelectable, item->Selectable());
if (CurrentSelectable)
cStatus::MsgOsdCurrentItem(item->Text());
if (CurrentSelectable) // not checking conveyStatus here!
cStatus::MsgOsdCurrentItem(item->Text(), i);
if (++n == displayMenuItems)
break;
i++;
}
}
displayMenu->SetScrollbar(count, first);
if (!isempty(status))
if (!isempty(status)) {
displayMenu->SetMessage(mtStatus, status);
cStatus::MsgOsdStatusMessage(mtStatus, status);
}
}
void cOsdMenu::SetCurrent(cOsdItem *Item)
@ -299,9 +328,13 @@ void cOsdMenu::DisplayCurrent(bool Current)
cOsdItem *item = Get(current);
if (item) {
item->SetMenuItem(displayMenu, current - first, Current && item->Selectable(), item->Selectable());
if (Current && item->Selectable())
cStatus::MsgOsdCurrentItem(item->Text());
if (!Current)
if (Current) {
if (current - first >= displayMenuItems || current < first)
DisplayNoStatus();
else if (item->Selectable())
cStatus::MsgOsdCurrentItem(item->Text(), current);
}
else
item->SetFresh(true); // leaving the current item resets 'fresh'
if (cMenuEditItem *MenuEditItem = dynamic_cast<cMenuEditItem *>(item)) {
if (!MenuEditItem->DisplayHelp(Current))
@ -321,7 +354,7 @@ void cOsdMenu::DisplayItem(cOsdItem *Item)
bool Current = Index == current;
Item->SetMenuItem(displayMenu, Offset, Current && Item->Selectable(), Item->Selectable());
if (Current && Item->Selectable())
cStatus::MsgOsdCurrentItem(Item->Text());
cStatus::MsgOsdCurrentItem(Item->Text(), Index);
}
}
}
@ -355,7 +388,7 @@ void cOsdMenu::CursorUp(void)
if (first > 0) {
// make non-selectable items at the beginning visible:
first = 0;
Display();
DisplayNoStatus();
return;
}
if (Setup.MenuScrollWrap)
@ -371,11 +404,11 @@ void cOsdMenu::CursorUp(void)
current = tmpCurrent;
if (current < first) {
first = Setup.MenuScrollPage ? max(0, current - displayMenuItems + 1) : current;
Display();
DisplayNoStatus();
}
else if (current > lastOnScreen) {
first = max(0, current - displayMenuItems + 1);
Display();
DisplayNoStatus();
}
else
DisplayCurrent(true);
@ -393,7 +426,7 @@ void cOsdMenu::CursorDown(void)
if (first < last - displayMenuItems) {
// make non-selectable items at the end visible:
first = last - displayMenuItems + 1;
Display();
DisplayNoStatus();
return;
}
if (Setup.MenuScrollWrap)
@ -411,11 +444,11 @@ void cOsdMenu::CursorDown(void)
first = Setup.MenuScrollPage ? current : max(0, current - displayMenuItems + 1);
if (first + displayMenuItems > last)
first = max(0, last - displayMenuItems + 1);
Display();
DisplayNoStatus();
}
else if (current < first) {
first = current;
Display();
DisplayNoStatus();
}
else
DisplayCurrent(true);
@ -447,10 +480,8 @@ void cOsdMenu::PageUp(void)
else if (current - first >= displayMenuItems)
first = current - displayMenuItems + 1;
}
if (current != oldCurrent || first != oldFirst) {
Display();
DisplayCurrent(true);
}
if (current != oldCurrent || first != oldFirst)
DisplayNoStatus();
else if (Setup.MenuScrollWrap)
CursorUp();
}
@ -481,10 +512,8 @@ void cOsdMenu::PageDown(void)
else if (current - first >= displayMenuItems)
first = current - displayMenuItems + 1;
}
if (current != oldCurrent || first != oldFirst) {
Display();
DisplayCurrent(true);
}
if (current != oldCurrent || first != oldFirst)
DisplayNoStatus();
else if (Setup.MenuScrollWrap)
CursorDown();
}
@ -503,9 +532,10 @@ eOSState cOsdMenu::HotKey(eKeys Key)
const char *s = item->Text();
if (s && (s = skipspace(s)) != NULL) {
if (*s == Key - k1 + '1') {
DisplayCurrent(false);
current = item->Index();
RefreshCurrent();
Display();
DisplayCurrent(true);
cRemote::Put(kOk, true);
break;
}
@ -516,8 +546,10 @@ eOSState cOsdMenu::HotKey(eKeys Key)
eOSState cOsdMenu::AddSubMenu(cOsdMenu *SubMenu)
{
SetActive(false);
delete subMenu;
subMenu = SubMenu;
subMenu->SetActive(true);
subMenu->Display();
return osContinue; // convenience return value
}
@ -526,6 +558,7 @@ eOSState cOsdMenu::CloseSubMenu(bool ReDisplay)
{
delete subMenu;
subMenu = NULL;
SetActive(true);
if (ReDisplay) {
RefreshCurrent();
Display();
@ -546,7 +579,8 @@ eOSState cOsdMenu::ProcessKey(eKeys Key)
if (marked < 0 && item) {
eOSState state = item->ProcessKey(Key);
if (state != osUnknown) {
DisplayCurrent(true);
if (Key != kNone)
DisplayCurrent(true);
return state;
}
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: osdbase.h 4.5 2018/01/25 15:09:23 kls Exp $
* $Id: osdbase.h 5.2 2025/02/17 10:49:10 kls Exp $
*/
#ifndef __OSDBASE_H
@ -87,11 +87,13 @@ private:
static cSkinDisplayMenu *displayMenu;
static int displayMenuCount;
static int osdState;
static cOsdMenu *topMenu;
int displayMenuItems;
char *title;
int cols[cSkinDisplayMenu::MaxTabs];
int first, current, marked;
int lastOffset;
bool conveyStatus;
eMenuCategory menuCategory;
eMenuSortMode menuSortMode;
eMenuOrientation menuOrientation;
@ -101,7 +103,10 @@ private:
char *status;
int digit;
bool hasHotkeys;
bool active;
void SetActive(bool Active);
void DisplayHelp(bool Force = false);
void DisplayNoStatus(void);
protected:
void SetDisplayMenu(void);
cSkinDisplayMenu *DisplayMenu(void) { return displayMenu; }

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: player.c 4.1 2020/05/18 16:47:29 kls Exp $
* $Id: player.c 5.2 2024/10/13 09:47:18 kls Exp $
*/
#include "player.h"
@ -70,14 +70,6 @@ cString cControl::GetHeader(void)
return "";
}
#if DEPRECATED_CCONTROL
cControl *cControl::Control(bool Hidden)
{
cMutexLock MutexLock(&mutex);
return (control && (!control->hidden || Hidden)) ? control : NULL;
}
#endif
cControl *cControl::Control(cMutexLock &MutexLock, bool Hidden)
{
MutexLock.Lock(&mutex);
@ -87,9 +79,8 @@ cControl *cControl::Control(cMutexLock &MutexLock, bool Hidden)
void cControl::Launch(cControl *Control)
{
cMutexLock MutexLock(&mutex);
cControl *c = control; // keeps control from pointing to uninitialized memory TODO obsolete once DEPRECATED_CCONTROL is gone
delete control;
control = Control;
delete c;
}
void cControl::Attach(void)
@ -99,7 +90,7 @@ void cControl::Attach(void)
if (cDevice::PrimaryDevice()->AttachPlayer(control->player))
control->attached = true;
else {
Skins.Message(mtError, tr("Channel locked (recording)!"));
Skins.Message(mtError, tr("Primary device has no MPEG decoder, can't attach player!"));
Shutdown();
}
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: player.h 5.1 2022/12/05 14:45:51 kls Exp $
* $Id: player.h 5.6 2024/10/13 09:47:18 kls Exp $
*/
#ifndef __PLAYER_H
@ -54,6 +54,8 @@ public:
bool IsAttached(void) { return device != NULL; }
virtual double FramesPerSecond(void) { return DEFAULTFRAMESPERSECOND; }
// Returns the number of frames per second of the currently played material.
virtual const cErrors *GetErrors(void) { return NULL; }
// Returns the frame indexes of errors in the recording (if any).
virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return false; }
// Returns the current and total frame index, optionally snapped to the
// nearest I-frame.
@ -115,15 +117,6 @@ public:
static void Launch(cControl *Control);
static void Attach(void);
static void Shutdown(void);
#define DEPRECATED_CCONTROL 1
#if DEPRECATED_CCONTROL
static cControl *Control(bool Hidden = false);
///< Old version of this function, for backwards compatibility with plugins.
///< Plugins should be changed to use the new version below, which does
///< proper locking.
///< Use of this function may result in program crashes in case replay is
///< stopped immediately after starting it.
#endif
static cControl *Control(cMutexLock &MutexLock, bool Hidden = false);
///< Returns the current replay control (if any) in case it is currently
///< visible. If Hidden is true, the control will be returned even if it is

View File

@ -4,9 +4,10 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: plugin.c 4.4 2020/12/16 11:54:06 kls Exp $
* $Id: plugin.c 5.1 2025/02/12 22:22:20 kls Exp $
*/
#define MUTE_DEPRECATED_MAINTHREADHOOK
#include "plugin.h"
#include <ctype.h>
#include <dirent.h>

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: plugin.h 4.1 2020/06/29 09:29:06 kls Exp $
* $Id: plugin.h 5.1 2025/02/12 22:22:20 kls Exp $
*/
#ifndef __PLUGIN_H
@ -43,6 +43,9 @@ public:
virtual bool Start(void);
virtual void Stop(void);
virtual void Housekeeping(void);
#ifndef MUTE_DEPRECATED_MAINTHREADHOOK
[[deprecated("use proper locking instead")]]
#endif
virtual void MainThreadHook(void);
virtual cString Active(void);
virtual time_t WakeupTime(void);

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2008-10-16 11:16-0400\n"
"Last-Translator: Osama Alrawab <alrawab@hotmail.com>\n"
"Language-Team: Arabic <ar@li.org>\n"
@ -134,8 +134,8 @@ msgstr "News Magazine"
msgid "Content$Documentary"
msgstr "Documentary"
msgid "Content$Discussion/Inverview/Debate"
msgstr "Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Discussion/Interview/Debate"
msgid "Content$Show/Game Show"
msgstr "Show/Game Show"
@ -212,8 +212,8 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Serious/Classical Music"
msgid "Content$Folk/Tradional Music"
msgstr "Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Folk/Traditional Music"
msgid "Content$Jazz"
msgstr "Jazz"
@ -845,6 +845,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1028,6 +1031,12 @@ msgstr "بحث"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "اﻻنتهاء من البحث على الدليل الالكترونى للقنوات"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "معدل اصلاح اخطاء الدليل الالكترونى"
@ -1525,8 +1534,8 @@ msgstr "الملحق"
msgid "Up/Dn for new location - OK to move"
msgstr "اعلى او اسفل ل الموضع الجديد و موافق للتحريك"
msgid "Channel locked (recording)!"
msgstr "القناة مقفلة للتسجيل"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "مساحة القرص غير كافية"
@ -1569,9 +1578,13 @@ msgstr "Recording - restart anyway?"
msgid "restart anyway?"
msgstr "restart anyway?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "الصوت "

View File

@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2008-03-02 19:02+0100\n"
"Last-Translator: Luca Olivetti <luca@ventoso.org>\n"
"Language-Team: Catalan <vdr@linuxtv.org>\n"
@ -133,7 +133,7 @@ msgstr "Revista de not
msgid "Content$Documentary"
msgstr "Documental"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Discussió/Entrevista/Debat"
msgid "Content$Show/Game Show"
@ -211,7 +211,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Música clàssica/seriosa"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Música folklòrica/tradicional"
msgid "Content$Jazz"
@ -844,6 +844,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1027,6 +1030,12 @@ msgstr "Escaneig"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Màxim d'Hores a cercar per la Guia"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Nivell de correcció de la Guia"
@ -1524,8 +1533,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Amunt/Avall per una nova localització - OK per moure"
msgid "Channel locked (recording)!"
msgstr "Canal bloquejat (gravant)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Disc gairebé ple!"
@ -1568,9 +1577,13 @@ msgstr "Gravant - Reiniciar de totes maneres?"
msgid "restart anyway?"
msgstr "Reiniciar de totes maneres?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volum "

View File

@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2010-05-06 11:00+0200\n"
"Last-Translator: Aleš Juřík <ajurik@quick.cz>\n"
"Language-Team: Czech <vdr@linuxtv.org>\n"
@ -133,7 +133,7 @@ msgstr "zprávy"
msgid "Content$Documentary"
msgstr "dokumentární"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "diskuze/rozhovor/debata"
msgid "Content$Show/Game Show"
@ -211,7 +211,7 @@ msgstr "rock/pop"
msgid "Content$Serious/Classical Music"
msgstr "vážná/klasická hudba"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "folk/country"
msgid "Content$Jazz"
@ -844,6 +844,9 @@ msgstr "Nahrávka zmizela!"
msgid "Edited version already exists - overwrite?"
msgstr "Editovaná verze již existuje - přepsat?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Chyba při vložení nahrávky do fronty pro střih"
@ -1027,6 +1030,12 @@ msgstr "Snímat"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Časový limit pro snímání EPG (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "EPG úroveň chyb"
@ -1524,8 +1533,8 @@ msgstr "Modul"
msgid "Up/Dn for new location - OK to move"
msgstr "Nahoru/Dolu pro novou pozici - Ok přesune"
msgid "Channel locked (recording)!"
msgstr "Kanál je blokovaný (nahrává se)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Disk bude brzy zaplněn!"
@ -1568,9 +1577,13 @@ msgstr "Nahrávání - přesto restartovat?"
msgid "restart anyway?"
msgstr "přesto restartovat?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Hlasitost "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2007-08-12 14:17+0200\n"
"Last-Translator: Mogens Elneff <mogens@elneff.dk>\n"
"Language-Team: Danish <vdr@linuxtv.org>\n"
@ -130,7 +130,7 @@ msgstr ""
msgid "Content$Documentary"
msgstr ""
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr ""
msgid "Content$Show/Game Show"
@ -208,7 +208,7 @@ msgstr ""
msgid "Content$Serious/Classical Music"
msgstr ""
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr ""
msgid "Content$Jazz"
@ -841,6 +841,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1024,6 +1027,12 @@ msgstr "Skan"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Tid før EPG skanning (t)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Niveau for EPG fejlrettelse"
@ -1521,8 +1530,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Op/Ned for ny placering - OK for at flytte"
msgid "Channel locked (recording)!"
msgstr "Kanal blokeret (optagelse i gang)"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Kun lidt diskplads tilbage!"
@ -1565,9 +1574,13 @@ msgstr "Optagelse igang - genstart alligevel?"
msgid "restart anyway?"
msgstr "genstart alligevel?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Lydstyrke "

View File

@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 14:54+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2015-02-10 13:45+0100\n"
"Last-Translator: Klaus Schmidinger <vdr@tvdr.de>\n"
"Language-Team: German <vdr@linuxtv.org>\n"
@ -132,7 +132,7 @@ msgstr "Nachrichtenmagazin"
msgid "Content$Documentary"
msgstr "Dokumentation"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Diskussion/Interview/Debatte"
msgid "Content$Show/Game Show"
@ -142,7 +142,7 @@ msgid "Content$Game Show/Quiz/Contest"
msgstr "Spielshow/Quiz/Wettbewerb"
msgid "Content$Variety Show"
msgstr "Variete-Show"
msgstr "Varieté-Show"
msgid "Content$Talk Show"
msgstr "Talkshow"
@ -210,7 +210,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Ernste/Klassische Musik"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Volks/Traditionelle Musik"
msgid "Content$Jazz"
@ -843,6 +843,9 @@ msgstr "Aufnahme verschwunden!"
msgid "Edited version already exists - overwrite?"
msgstr "Geschnittene Version existiert bereits - überschreiben?"
msgid "Not enough free disk space to start editing process!"
msgstr "Nicht genug freier Plattenplatz zum Schneiden!"
msgid "Error while queueing recording for cutting!"
msgstr "Fehler beim Hinzufügen der Aufnahme zur Schnittwarteschlange"
@ -1026,6 +1029,12 @@ msgstr "Scan"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Zeit bis zur EPG-Aktualisierung (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr "Maximaler Kanal für EPG-Scan (0=alle)"
msgid "Setup.EPG$EPG pause after scan"
msgstr "Pause nach EPG-Scan"
msgid "Setup.EPG$EPG bugfix level"
msgstr "EPG-Fehlerbereinigungsstufe"
@ -1523,8 +1532,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Auf/Ab für neue Position - dann OK"
msgid "Channel locked (recording)!"
msgstr "Kanal blockiert (zeichnet auf)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr "Primäres Device hat keinen MPEG-Decoder, Wiedergabe nicht möglich!"
msgid "Low disk space!"
msgstr "Platte beinahe voll!"
@ -1567,9 +1576,13 @@ msgstr "Aufnahme l
msgid "restart anyway?"
msgstr "trotzdem neu starten?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr "Fehler"
msgid "error"
msgstr "Fehler"
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Lautstärke "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2007-08-12 14:17+0200\n"
"Last-Translator: Dimitrios Dimitrakos <mail@dimitrios.de>\n"
"Language-Team: Greek <vdr@linuxtv.org>\n"
@ -130,7 +130,7 @@ msgstr ""
msgid "Content$Documentary"
msgstr ""
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr ""
msgid "Content$Show/Game Show"
@ -208,7 +208,7 @@ msgstr ""
msgid "Content$Serious/Classical Music"
msgstr ""
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr ""
msgid "Content$Jazz"
@ -841,6 +841,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1024,6 +1027,12 @@ msgstr "
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "×ñüíïò äéÜñêåéáò åîÝôáóçò EPG óå þñåò"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Âáèìüò äéüñèùóçò ïäçãïý EPG"
@ -1521,8 +1530,8 @@ msgstr "
msgid "Up/Dn for new location - OK to move"
msgstr "ÐÜíù/ÊÜôù ãéÜ íÝá èÝóç. ÌåôÜ ÏÊ"
msgid "Channel locked (recording)!"
msgstr "Ôï êáíÜëé åßíáé áðïó÷ïëéìÝíï (Ãßíåôå åããñáöÞ)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Ï óêëçñüò êïíôåýåé íÜ ãåìßóåé!"
@ -1565,9 +1574,13 @@ msgstr "
msgid "restart anyway?"
msgstr "ÔåëéêÜ íá ãßíåé åðáíåêêßíçóç?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "¸íôáóç "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2015-02-19 23:00+0100\n"
"Last-Translator: Gabriel Bonich <gbonich@gmail.com>\n"
"Language-Team: Spanish <vdr@linuxtv.org>\n"
@ -131,7 +131,7 @@ msgstr "Revista de noticias"
msgid "Content$Documentary"
msgstr "Documental"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Discusión/Entrevista/Debate"
msgid "Content$Show/Game Show"
@ -209,7 +209,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Musica clásica"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Musica Folk/Tradicional"
msgid "Content$Jazz"
@ -842,6 +842,9 @@ msgstr "Grabaci
msgid "Edited version already exists - overwrite?"
msgstr "Se está editando una versión - sobreescribir?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Error mientras la grabación está en cola!"
@ -1025,6 +1028,12 @@ msgstr "Escanear"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Tiempo de exploración de EPG (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Nivel de corrección de EPG"
@ -1522,8 +1531,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Arriba/Abajo para mover - OK para confirmar"
msgid "Channel locked (recording)!"
msgstr "¡Canal bloqueado (grabando)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "¡Poco espacio en disco!"
@ -1566,9 +1575,13 @@ msgstr "Grabaci
msgid "restart anyway?"
msgstr "¿reiniciar igualmente?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volumen "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2007-08-12 14:17+0200\n"
"Last-Translator: Arthur Konovalov <artlov@gmail.com>\n"
"Language-Team: Estonian <vdr@linuxtv.org>\n"
@ -130,7 +130,7 @@ msgstr "Uudisteajakiri"
msgid "Content$Documentary"
msgstr "Dokumentaal"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Diskussioon/intervjuu/debatt"
msgid "Content$Show/Game Show"
@ -208,7 +208,7 @@ msgstr "Rock/pop"
msgid "Content$Serious/Classical Music"
msgstr "Klassikaline muusika"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Folk/rahvamuusika"
msgid "Content$Jazz"
@ -841,6 +841,9 @@ msgstr "Salvestis puudub!"
msgid "Edited version already exists - overwrite?"
msgstr "Muudetud versioon juba olemas - kirjutada üle?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Salvestise lisamine lõikamiseks ebaõnnestus!"
@ -1024,6 +1027,12 @@ msgstr "Uuenda"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "EPG skaneerimise viide (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "EPG veaparandustase"
@ -1521,8 +1530,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "'Üles/Alla' uus asukoht - 'OK' kinnitus"
msgid "Channel locked (recording)!"
msgstr "Kanal lukus (salvestamine aktiivne)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Kõvaketas täis!"
@ -1565,9 +1574,13 @@ msgstr "Salvestamine aktiivne - taaskäivitada ikkagi?"
msgid "restart anyway?"
msgstr "taaskäivitada ikkagi?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Helitugevus "

View File

@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2007-08-15 15:52+0200\n"
"Last-Translator: Matti Lehtimäki <matti.lehtimaki@gmail.com>\n"
"Language-Team: Finnish <vdr@linuxtv.org>\n"
@ -134,7 +134,7 @@ msgstr "Uutismakasiini"
msgid "Content$Documentary"
msgstr "Dokumentti"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Keskustelu/haastattelu/väittely"
msgid "Content$Show/Game Show"
@ -212,7 +212,7 @@ msgstr "Rock/pop"
msgid "Content$Serious/Classical Music"
msgstr "Vakava/klassinen musiikki"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Folk/kansanmusiikki"
msgid "Content$Jazz"
@ -797,7 +797,7 @@ msgid "Folder"
msgstr "Kansio"
msgid "Size"
msgstr ""
msgstr "Koko"
msgid "This folder is currently in use - no changes are possible!"
msgstr "Kansio on käytössä - muokkaukset eivät mahdollisia!"
@ -845,6 +845,9 @@ msgstr "Tallenne katosi!"
msgid "Edited version already exists - overwrite?"
msgstr "Muokattava versio on jo olemassa - ylikirjoitetaanko?"
msgid "Not enough free disk space to start editing process!"
msgstr "Tallennustila ei riitä muokkaamiseen!"
msgid "Error while queueing recording for cutting!"
msgstr "Tallenteen lisääminen leikkausjonoon epäonnistui!"
@ -1028,6 +1031,12 @@ msgstr "Päivitä"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Ohjelmaoppaan taustapäivitys (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Ohjelmaoppaan korjaustaso"
@ -1517,7 +1526,7 @@ msgid "Button$Insert"
msgstr "Lisää"
msgid "Button$Macro"
msgstr ""
msgstr "Makro"
msgid "Plugin"
msgstr "Laajennos"
@ -1525,8 +1534,8 @@ msgstr "Laajennos"
msgid "Up/Dn for new location - OK to move"
msgstr "'Ylös/Alas' uusi paikka - 'OK' hyväksy"
msgid "Channel locked (recording)!"
msgstr "Kanava lukittu (tallennus käynnissä)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr "Ensisijaissa laitteessa ei ole MPEG-toistinta!"
msgid "Low disk space!"
msgstr "Tallennustila loppumassa!"
@ -1569,8 +1578,12 @@ msgstr "Tallennus kesken - käynnistetäänkö uudelleen?"
msgid "restart anyway?"
msgstr "käynnistetäänkö uudelleen?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgstr "virhettä"
msgid "error"
msgstr "virhe"
#. TRANSLATORS: note the trailing blank!
msgid "Volume "

View File

@ -18,7 +18,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2018-04-14 10:16+0100\n"
"Last-Translator: Bernard Jaulin <bernard.jaulin@gmail.com>\n"
"Language-Team: French <vdr@linuxtv.org>\n"
@ -141,7 +141,7 @@ msgstr "Magazine d'information"
msgid "Content$Documentary"
msgstr "Documentaire"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Discussion/Interview/Débat"
msgid "Content$Show/Game Show"
@ -219,7 +219,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Sérieux/Musique Classique"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Folk/Musique Traditionnelle"
msgid "Content$Jazz"
@ -852,6 +852,9 @@ msgstr "L'enregistrement a disparu !"
msgid "Edited version already exists - overwrite?"
msgstr "La version éditée existe déjà - écraser ?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Erreur lors de la mise en file d'attente pour la coupe !"
@ -1035,6 +1038,12 @@ msgstr "Scanner"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Inactivité avant mise à jour EPG (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Niveau de correction du guide-EPG"
@ -1532,8 +1541,8 @@ msgstr "Module"
msgid "Up/Dn for new location - OK to move"
msgstr "Haut/Bas -> nouvelle place - OK -> déplacer"
msgid "Channel locked (recording)!"
msgstr "Chaîne verrouillée (enregistrement en cours) !"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Disque presque plein !"
@ -1576,9 +1585,13 @@ msgstr "Enregistrement en cours - confirmer redémarrage ?"
msgid "restart anyway?"
msgstr "confirmer redémarrage ?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volume "

View File

@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2008-03-17 19:00+0100\n"
"Last-Translator: Adrian Caval <anrxc@sysphere.org>\n"
"Language-Team: Croatian <vdr@linuxtv.org>\n"
@ -132,7 +132,7 @@ msgstr ""
msgid "Content$Documentary"
msgstr ""
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr ""
msgid "Content$Show/Game Show"
@ -210,7 +210,7 @@ msgstr ""
msgid "Content$Serious/Classical Music"
msgstr ""
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr ""
msgid "Content$Jazz"
@ -843,6 +843,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1026,6 +1029,12 @@ msgstr "Pretra
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Vrijeme do EPG pregleda (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Razina popravka EPG gre¹aka"
@ -1523,8 +1532,8 @@ msgstr "Dodatak"
msgid "Up/Dn for new location - OK to move"
msgstr "Gore/Dolje na novu poziciju - OK za potvdru"
msgid "Channel locked (recording)!"
msgstr "Program zakljuèan (snimanje)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Malo prostora na disku!"
@ -1567,9 +1576,13 @@ msgstr "Snimanje je u tijeku - ponovno pokreni unato
msgid "restart anyway?"
msgstr "ponovno pokreni unatoè?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Glasnoæa "

View File

@ -4,22 +4,25 @@
# Istvan Koenigsberger <istvnko@hotmail.com>, 2002, 2003, 2006
# Guido Josten <guido.josten@t-online.de>, 2002, 2003, 2006
# Thomas Günther <tom@toms-cafe.de>, 2007
# István Füley <ifuley@tigercomp.ro>, 2007, 2012, 2013, 2015, 2018
# István Füley <ifuley@tigercomp.ro>, 2007, 2012, 2013, 2015, 2018, 2024
# Albert Danis <a.danis@gmx.de>, 2015
#
msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Project-Id-Version: VDR 2.6.6\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"PO-Revision-Date: 2018-04-09 21:42+0300\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2024-02-09 06:29+0000\n"
"Last-Translator: István Füley <ifuley@tigercomp.ro>\n"
"Language-Team: Hungarian <vdr@linuxtv.org>\n"
"Language-Team: Hungarian\n"
"Language: hu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.6\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Loco-Source-Locale: hu_RO\n"
"X-Generator: Loco https://localise.biz/\n"
"X-Loco-Parser: loco_parse_po\n"
msgid "*** Invalid Channel ***"
msgstr "*** Érvénytelen csatorna ***"
@ -135,7 +138,7 @@ msgstr "Hírmagazin"
msgid "Content$Documentary"
msgstr "Dokumentumfilm"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Beszélgetés/Interjú/Vita"
msgid "Content$Show/Game Show"
@ -213,7 +216,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Komolyzene/Klasszikus zene"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Népzene/Hagyományos zene"
msgid "Content$Jazz"
@ -349,28 +352,28 @@ msgid "LanguageCode$eng"
msgstr "hun"
msgid "LanguageName$original language (qaa)"
msgstr ""
msgstr "eredeti nyelv"
msgid "LanguageName$audio description (qad)"
msgstr ""
msgstr "audio leírás"
msgid "LanguageName$clear speech (qks)"
msgstr ""
msgstr "csak beszéd (qks)"
msgid "LanguageName$uncoded languages (mis)"
msgstr ""
msgstr "kód nélküli nyelv (mis)"
msgid "LanguageName$multiple languages (mul)"
msgstr ""
msgstr "többnyelvű (mul)"
msgid "LanguageName$narrative (nar)"
msgstr ""
msgstr "elbeszélő (nar)"
msgid "LanguageName$undetermined (und)"
msgstr ""
msgstr "meghatározatlan (und)"
msgid "LanguageName$no linguistic content (zxx)"
msgstr ""
msgstr "nincs nyelvi tartalom (zxx)"
msgid "Phase 1: Detecting RC code type"
msgstr "Első lépés: távirányító kódjának meghatározása"
@ -687,7 +690,7 @@ msgid "File"
msgstr "Fájl"
msgid "Record on"
msgstr "Felvétel be"
msgstr "Felvétel itt"
msgid "Button$Folder"
msgstr "Könyvtár"
@ -699,19 +702,19 @@ msgid "Button$Repeating"
msgstr "Ismétlődő"
msgid "Button$Regular"
msgstr ""
msgstr "Szabályos"
msgid "Button$Pattern"
msgstr ""
msgstr "Minta"
msgid "First day"
msgstr "Első nap"
msgid "Timer is recording!"
msgstr ""
msgstr "Az időzítő felvételt készít!"
msgid "Pattern"
msgstr ""
msgstr "Minta"
msgid "Error while accessing remote timer"
msgstr "Távoli időzítő nem elérhető"
@ -798,7 +801,7 @@ msgid "Folder"
msgstr "Könyvtár"
msgid "Size"
msgstr ""
msgstr "Méret"
msgid "This folder is currently in use - no changes are possible!"
msgstr "Ez a könyvtár használatban van - módosítás nem lehetséges!"
@ -846,6 +849,9 @@ msgstr "Ez a felvétel eltűnt!"
msgid "Edited version already exists - overwrite?"
msgstr "A szerkesztett változat már létezik - felülírjam?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Hiba a felvétel vágásra előkészítése közben!"
@ -1029,6 +1035,12 @@ msgstr "EPG keresés"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Fennmaradt idő az EPG-frissítésig (ó)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Hibaelhárítás szintje"
@ -1518,7 +1530,7 @@ msgid "Button$Insert"
msgstr "Beillesztés"
msgid "Button$Macro"
msgstr ""
msgstr "Makró"
msgid "Plugin"
msgstr "Plugin"
@ -1526,8 +1538,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Fel/Le egy új pozícióért - aztán OK"
msgid "Channel locked (recording)!"
msgstr "Csatorna blokkolva (felvétel)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Adattároló majdnem tele!"
@ -1570,7 +1582,11 @@ msgstr "Felvétel folyamatban - mégis újraindítani?"
msgid "restart anyway?"
msgstr "mégis újraindítani?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr "hiba"
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!

View File

@ -5,14 +5,14 @@
# Antonio Ospite <ospite@studenti.unina.it>, 2003, 2006
# Sean Carlos <seanc@libero.it>, 2005
# Nino Gerbino <ngerb@interfree.it>, 2006, 2015
# Diego Pierotto <vdr-italian@tiscali.it>, 2007-2010, 2012, 2013, 2015, 2018, 2020, 2022
# Diego Pierotto <vdr-italian@tiscali.it>, 2007-2010, 2012, 2013, 2015, 2018, 2020, 2022, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"PO-Revision-Date: 2022-12-15 20:22+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2024-10-15 00:35+0200\n"
"Last-Translator: Gringo <openpli@tiscali.it>\n"
"Language-Team: Italian <vdr@linuxtv.org>\n"
"Language: it\n"
@ -136,7 +136,7 @@ msgstr "Settimanale di attualità"
msgid "Content$Documentary"
msgstr "Documentario"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Discussione/Intervista/Dibattito"
msgid "Content$Show/Game Show"
@ -214,7 +214,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Musica Classica/Seria"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Musica Tradizionale/Folclore"
msgid "Content$Jazz"
@ -847,6 +847,9 @@ msgstr "Registrazione sparita!"
msgid "Edited version already exists - overwrite?"
msgstr "Versione modificata già esistente. Sovrascrivere?"
msgid "Not enough free disk space to start editing process!"
msgstr "Spazio libero su disco insufficiente per avviare il processo di modifica!"
msgid "Error while queueing recording for cutting!"
msgstr "Errore durante l'accodamento della registrazione per il taglio!"
@ -1030,6 +1033,12 @@ msgstr "Scansione"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Scadenza aggiorn. EPG (ore)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr "Numero massimo canali scansione EPG (0=tutti)"
msgid "Setup.EPG$EPG pause after scan"
msgstr "Pausa EPG dopo scansione"
msgid "Setup.EPG$EPG bugfix level"
msgstr "Livello correzione EPG"
@ -1527,8 +1536,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Su/Giù per nuova posizione - OK per spostare"
msgid "Channel locked (recording)!"
msgstr "Canale bloccato (in registrazione)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr "Il dispositivo primario non ha un decoder MPEG, impossibile collegare il lettore!"
msgid "Low disk space!"
msgstr "Poco spazio su disco!"
@ -1571,9 +1580,13 @@ msgstr "Registrazione in corso - riavviare comunque?"
msgid "restart anyway?"
msgstr "riavviare comunque?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr "errori"
msgid "error"
msgstr "errore"
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volume "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2015-02-11 14:02+0200\n"
"Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n"
"Language-Team: Lithuanian <vdr@linuxtv.org>\n"
@ -130,7 +130,7 @@ msgstr "Naujienų žurnalas"
msgid "Content$Documentary"
msgstr "Dokumentika"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Diskusijos/Interviu"
msgid "Content$Show/Game Show"
@ -208,7 +208,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Klasikinė muzika"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Folk/Tradizinė muzika"
msgid "Content$Jazz"
@ -841,6 +841,9 @@ msgstr "Įrašai išvalyti!"
msgid "Edited version already exists - overwrite?"
msgstr "Tokia koreguota versija jau yra, vistiek perašyti?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Klaida sustatant įrašus į karpymo eilę!"
@ -1024,6 +1027,12 @@ msgstr "Skanuoti"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "EPG skanavimo užlaikymas (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "EPG klaidų taisymo lygis"
@ -1521,8 +1530,8 @@ msgstr "Įskiepas"
msgid "Up/Dn for new location - OK to move"
msgstr "Spauskite mygtukus į viršų / į apačią ir OK kad perkelti į kitą vietą"
msgid "Channel locked (recording)!"
msgstr "Kanalas užblokuotas (įrašinėjama)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Mažai vietos diske!"
@ -1565,9 +1574,13 @@ msgstr "Įrašinėjama - perkrauti vistiek?"
msgid "restart anyway?"
msgstr "perkrati bet kokiu atveju?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Garsas "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2018-03-31 21:47+0100\n"
"Last-Translator: Dimitar Petrovski <dimeptr@gmail.com>\n"
"Language-Team: Macedonian <kde-i18n-doc@kde.org>\n"
@ -132,7 +132,7 @@ msgstr "Магазин"
msgid "Content$Documentary"
msgstr "Документарен"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Дискусија/Интервју/Дебата"
msgid "Content$Show/Game Show"
@ -210,7 +210,7 @@ msgstr "Рок/Поп"
msgid "Content$Serious/Classical Music"
msgstr "Сериозна/Класична музика"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Фолк/Народна музика"
msgid "Content$Jazz"
@ -843,6 +843,9 @@ msgstr "Снимката исчезна!"
msgid "Edited version already exists - overwrite?"
msgstr "Уредена верзија веќе постои - пребриши?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Грешка при редење на снимка за сечење!"
@ -1026,6 +1029,12 @@ msgstr "Барај"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Време до барање на EPG (ч)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Ниво на поправка на EPG грешки"
@ -1523,8 +1532,8 @@ msgstr "Додаток"
msgid "Up/Dn for new location - OK to move"
msgstr "Горе/Доле за нова позиција - OK за потврда"
msgid "Channel locked (recording)!"
msgstr "Каналот е заклучен (снимање)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Малку простор на дискот!"
@ -1567,9 +1576,13 @@ msgstr "Снимање во тек - рестартирај?"
msgid "restart anyway?"
msgstr "сеедно рестартирај?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Глас "

View File

@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2015-02-10 19:43+0100\n"
"Last-Translator: Erik Oomen <oomen.e@gmail.com>\n"
"Language-Team: Dutch <vdr@linuxtv.org>\n"
@ -136,7 +136,7 @@ msgstr "Nieuwsbulletin"
msgid "Content$Documentary"
msgstr "Documentaire"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Discussie/Interview/Debat"
msgid "Content$Show/Game Show"
@ -214,7 +214,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Serieuze muziek/Klassieke muziek"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Folk/Traditionele muziek"
msgid "Content$Jazz"
@ -847,6 +847,9 @@ msgstr "Opname verdwenen!"
msgid "Edited version already exists - overwrite?"
msgstr "Bewerkte versie bestaat al - overschrijven?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Kan opname voor knippen niet in wachtrij plaatsen!"
@ -1030,6 +1033,12 @@ msgstr "Scan"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "EPG-scan Timeout (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "EPG foutcorrectieniveau"
@ -1527,8 +1536,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Gebruik Omhoog/Omlaag - daarna Ok"
msgid "Channel locked (recording)!"
msgstr "Kanaal geblokkeerd (neemt op)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Hardeschijf bijna vol!"
@ -1571,9 +1580,13 @@ msgstr "Opname loopt - toch opnieuw starten?"
msgid "restart anyway?"
msgstr "toch opnieuw starten?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volume "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2007-08-12 14:17+0200\n"
"Last-Translator: Truls Slevigen <truls@slevigen.no>\n"
"Language-Team: Norwegian Nynorsk <vdr@linuxtv.org>\n"
@ -131,7 +131,7 @@ msgstr ""
msgid "Content$Documentary"
msgstr ""
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr ""
msgid "Content$Show/Game Show"
@ -209,7 +209,7 @@ msgstr ""
msgid "Content$Serious/Classical Music"
msgstr ""
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr ""
msgid "Content$Jazz"
@ -842,6 +842,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1025,6 +1028,12 @@ msgstr ""
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Ledig tid før EPG-søk (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Nivå for EPG-feilretting"
@ -1522,8 +1531,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Opp/Ned for ny plass - OK for å flytte"
msgid "Channel locked (recording)!"
msgstr "Kanalen er låst (opptak)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Lite ledig diskplass!"
@ -1566,9 +1575,13 @@ msgstr "Gj
msgid "restart anyway?"
msgstr "starte på nytt likevel?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volum "

View File

@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2018-02-19 00:42+0100\n"
"Last-Translator: Tomasz Maciej Nowak <tmn505@gmail.com>\n"
"Language-Team: Polish <vdr@linuxtv.org>\n"
@ -135,7 +135,7 @@ msgstr "Magazyny informacyjne"
msgid "Content$Documentary"
msgstr "Dokumentalne"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Dyskusje/Wywiady/Debaty"
msgid "Content$Show/Game Show"
@ -213,7 +213,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Muzyka klasyczna"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Folk/Muzyka tradycyjna"
msgid "Content$Jazz"
@ -846,6 +846,9 @@ msgstr "Nagranie zniknęło!"
msgid "Edited version already exists - overwrite?"
msgstr "Edytowana wersja już istnieje - nadpisać?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Błąd podczas zakolejkowania nagrania do przycinania!"
@ -1029,6 +1032,12 @@ msgstr "Skanuj"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Czas skanowania EPG (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Stopień poprawek błędów EPG"
@ -1526,8 +1535,8 @@ msgstr "Wtyczka"
msgid "Up/Dn for new location - OK to move"
msgstr "Do góry/Na dół na nową pozycję - Ok zatwierdza"
msgid "Channel locked (recording)!"
msgstr "Kanał zablokowany (trwa nagrywanie)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Mało miejsca na dysku!"
@ -1570,9 +1579,13 @@ msgstr "Trwa nagrywanie - zrestartować mimo to?"
msgid "restart anyway?"
msgstr "zrestartować mimo to?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Głośność "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2010-03-28 22:49+0100\n"
"Last-Translator: Cris Silva <hudokkow@gmail.com>\n"
"Language-Team: Portuguese <vdr@linuxtv.org>\n"
@ -131,7 +131,7 @@ msgstr "Notici
msgid "Content$Documentary"
msgstr "Documentário"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Discussão/Entrevista/Debate"
msgid "Content$Show/Game Show"
@ -209,7 +209,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Música Clássica/Séria"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Música Tradicional/Folclore"
msgid "Content$Jazz"
@ -842,6 +842,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1025,6 +1028,12 @@ msgstr "Procurar"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Procura do EPG termina após (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Nível de correcção do EPG"
@ -1522,8 +1531,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Cima/Baixo para nova posição - OK para mover"
msgid "Channel locked (recording)!"
msgstr "Canal bloqueado (a gravar)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Espaço em disco baixo!"
@ -1566,9 +1575,13 @@ msgstr "Grava
msgid "restart anyway?"
msgstr "reiniciar mesmo assim?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volume "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2015-02-11 22:26+0100\n"
"Last-Translator: Lucian Muresan <lucianm@users.sourceforge.net>\n"
"Language-Team: Romanian <vdr@linuxtv.org>\n"
@ -132,7 +132,7 @@ msgstr "Magazin de ştiri"
msgid "Content$Documentary"
msgstr "Documentar"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Discuţie/Interviu/Dezbatere"
msgid "Content$Show/Game Show"
@ -210,7 +210,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Muzică clasică/serioasă"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Muzică folk/tradiţională"
msgid "Content$Jazz"
@ -843,6 +843,9 @@ msgstr "Înregistrarea a dispărut!"
msgid "Edited version already exists - overwrite?"
msgstr "Deja există o versiune editată - o suprascriu?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Eroare la punerea în coada pentru tăiere!"
@ -1026,6 +1029,12 @@ msgstr "Căutare canale"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Interval achiziţie EPG (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Nivel corecţie EPG"
@ -1523,8 +1532,8 @@ msgstr "Plugin"
msgid "Up/Dn for new location - OK to move"
msgstr "Sus/Jos pentru noua locaţie - OK pentru a muta"
msgid "Channel locked (recording)!"
msgstr "Canal blocat (înregistrare)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Spaţiul pe disc e foarte scăzut!"
@ -1567,9 +1576,13 @@ msgstr "Tocmai se înregistrează - repornesc, totuşi?"
msgid "restart anyway?"
msgstr "repornesc, totuşi?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volum "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2016-12-27 17:13+0100\n"
"Last-Translator: Pridvorov Andrey <ua0lnj@bk.ru>\n"
"Language-Team: Russian <vdr@linuxtv.org>\n"
@ -131,7 +131,7 @@ msgstr "Новостной журнал"
msgid "Content$Documentary"
msgstr "Документальные"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Дискуссия/Интервью/Дебаты"
msgid "Content$Show/Game Show"
@ -209,7 +209,7 @@ msgstr "Рок/Поп"
msgid "Content$Serious/Classical Music"
msgstr "Классическая музыка"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Фольклор/Традиционная музыка"
msgid "Content$Jazz"
@ -842,6 +842,9 @@ msgstr "Запись исчезла!"
msgid "Edited version already exists - overwrite?"
msgstr "Изменённая версия уже есть - переписать?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Ошибка очереди записи для обрезки!"
@ -1025,6 +1028,12 @@ msgstr "Сканировать"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Задержка сканирования телегида (ч)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Уровень коррекции ошибок"
@ -1522,8 +1531,8 @@ msgstr "Модуль"
msgid "Up/Dn for new location - OK to move"
msgstr "Нажимайте \"Вверх\"/\"Вниз\" для выбора позиции, а затем \"OK\""
msgid "Channel locked (recording)!"
msgstr "Канал заблокирован (идёт запись)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Недостаточно места на диске!"
@ -1566,9 +1575,13 @@ msgstr "Идёт запись - действительно перезапуст
msgid "restart anyway?"
msgstr "действительно перезапустить?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Громкость "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2015-02-17 18:59+0100\n"
"Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n"
"Language-Team: Slovak <vdr@linuxtv.org>\n"
@ -131,7 +131,7 @@ msgstr "Spravodajsk
msgid "Content$Documentary"
msgstr "Dokumentárny"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Diskusia/Rozhovor/debata"
msgid "Content$Show/Game Show"
@ -209,7 +209,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Vá¾na/Klasická hudba"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "¥udová/Tradièná Hudba"
msgid "Content$Jazz"
@ -842,6 +842,9 @@ msgstr "Z
msgid "Edited version already exists - overwrite?"
msgstr "Upravená verzia u¾ existuje - prepísa»?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Chyba v poradí pri zostrihu nahrávky!"
@ -1025,6 +1028,12 @@ msgstr "Skenova
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Interval snímania EPG (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Úroveò opravy EPG chýb"
@ -1522,8 +1531,8 @@ msgstr "Modul"
msgid "Up/Dn for new location - OK to move"
msgstr "Hore/Dole na novú pozíciu - Ok presune"
msgid "Channel locked (recording)!"
msgstr "Kanál je zamknutý (nahráva sa)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Nedostatok miesta na disku!"
@ -1566,9 +1575,13 @@ msgstr "Nahr
msgid "restart anyway?"
msgstr "Urèite re¹tartova»?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Hlasitos» "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2013-03-04 12:46+0100\n"
"Last-Translator: Matjaz Thaler <matjaz.thaler@guest.arnes.si>\n"
"Language-Team: Slovenian <vdr@linuxtv.org>\n"
@ -131,7 +131,7 @@ msgstr "Poro
msgid "Content$Documentary"
msgstr "Dokumentarec"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Diskudija/Interviju/Debata"
msgid "Content$Show/Game Show"
@ -209,7 +209,7 @@ msgstr "Rok/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Resna/Klasièna Glasba"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Narodna/Tradicionalna glasba"
msgid "Content$Jazz"
@ -842,6 +842,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1025,6 +1028,12 @@ msgstr "I
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Èas do EPG pregleda (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Nivo za popravilo EPG napak"
@ -1522,8 +1531,8 @@ msgstr "Vstavek"
msgid "Up/Dn for new location - OK to move"
msgstr "Gor/Dol za novo poz. - Ok za premik"
msgid "Channel locked (recording)!"
msgstr "Zaklenjen kanal (snemanje)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Premalo prostora na disku!"
@ -1566,9 +1575,13 @@ msgstr "Snemanje - zares ponoven zagon?"
msgid "restart anyway?"
msgstr "zares ponoven zagon?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Glasnost "

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2013-03-16 15:05+0100\n"
"Last-Translator: Zoran Turalija <zoran.turalija@gmail.com>\n"
"Language-Team: Serbian <vdr@linuxtv.org>\n"
@ -131,7 +131,7 @@ msgstr "Novosti"
msgid "Content$Documentary"
msgstr "Dokumentarni"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Diskusija/Razgovor/Debata"
msgid "Content$Show/Game Show"
@ -209,7 +209,7 @@ msgstr "Rok/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Ozbiljna/Klasièna muzika"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Narodna/Izvorna muzika"
msgid "Content$Jazz"
@ -842,6 +842,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1025,6 +1028,12 @@ msgstr "Pretra
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Interval do a¾uriranja EPG (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Nivo EPG korekcije gre¹aka"
@ -1522,8 +1531,8 @@ msgstr "Dodatak"
msgid "Up/Dn for new location - OK to move"
msgstr "Gore/Dole za novu poziciju - OK za potvrdu"
msgid "Channel locked (recording)!"
msgstr "Kanal zakljuèan (snimanje)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Malo prostora na disku!"
@ -1566,9 +1575,13 @@ msgstr "Snimanje u toku - svejedno ponovno pokreni!!!?"
msgid "restart anyway?"
msgstr "svejedno ponovo pokreni?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Jaèina "

View File

@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2015-02-12 21:58+0100\n"
"Last-Translator: Magnus Sirviö <sirwio@hotmail.com>\n"
"Language-Team: Swedish <vdr@linuxtv.org>\n"
@ -135,7 +135,7 @@ msgstr "Nyhetsmagasin"
msgid "Content$Documentary"
msgstr "Dokumentär"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Diskussion/Intervju/Debatt"
msgid "Content$Show/Game Show"
@ -213,7 +213,7 @@ msgstr "Rock/Pop"
msgid "Content$Serious/Classical Music"
msgstr "Klassisk musik"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Folkmusik"
msgid "Content$Jazz"
@ -846,6 +846,9 @@ msgstr "Inspelningen f
msgid "Edited version already exists - overwrite?"
msgstr "En redigerad version finns redan - skall den ersättas?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Ett fel upstod när inspelningen köades för klippning!"
@ -1029,6 +1032,12 @@ msgstr "Avs
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Tidsgräns för EPG-sökning (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Nivå för EPG bugfix"
@ -1526,8 +1535,8 @@ msgstr "Modul"
msgid "Up/Dn for new location - OK to move"
msgstr "Upp/Ner för nya platsen - därefter OK för att flytta"
msgid "Channel locked (recording)!"
msgstr "Kanalen är låst (inspelning pågår)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Lågt diskutrymme!"
@ -1570,9 +1579,13 @@ msgstr "Inspelning p
msgid "restart anyway?"
msgstr "vill du starta om ändå?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volym "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2008-02-28 00:33+0100\n"
"Last-Translator: Oktay Yolgeçen <oktay_73@yahoo.de>\n"
"Language-Team: Turkish <vdr@linuxtv.org>\n"
@ -130,7 +130,7 @@ msgstr ""
msgid "Content$Documentary"
msgstr ""
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr ""
msgid "Content$Show/Game Show"
@ -208,7 +208,7 @@ msgstr ""
msgid "Content$Serious/Classical Music"
msgstr ""
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr ""
msgid "Content$Jazz"
@ -841,6 +841,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1024,6 +1027,12 @@ msgstr "Tara"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "EPG tarama zaman aþýmý (sa)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "EPG hata çözümleme ölçüsü"
@ -1521,8 +1530,8 @@ msgstr "Eklenti"
msgid "Up/Dn for new location - OK to move"
msgstr "'Yukarý/Aþaðý' yeni posizyon seç - 'Tamam' taþý"
msgid "Channel locked (recording)!"
msgstr "Kanal geçersiz (kayýt ediliyor)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Kayýt kapasitesi az!"
@ -1565,9 +1574,13 @@ msgstr "Kay
msgid "restart anyway?"
msgstr "buna raðmen yeniden baþlat?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Volüm "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2018-03-18 20:00+0100\n"
"Last-Translator: Yarema aka Knedlyk <yupadmin@gmail.com>\n"
"Language-Team: Ukrainian <vdr@linuxtv.org>\n"
@ -131,7 +131,7 @@ msgstr "Журнал новин"
msgid "Content$Documentary"
msgstr "Документальне"
msgid "Content$Discussion/Inverview/Debate"
msgid "Content$Discussion/Interview/Debate"
msgstr "Дискусія/Інтерв’ю/Дебати"
msgid "Content$Show/Game Show"
@ -209,7 +209,7 @@ msgstr "Рок/Поп"
msgid "Content$Serious/Classical Music"
msgstr "Серйозна/Класична музика"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "Фльк/Традиційна музика"
msgid "Content$Jazz"
@ -842,6 +842,9 @@ msgstr "Запис зник!"
msgid "Edited version already exists - overwrite?"
msgstr "Вже існує редагована версія - перезаписати?"
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr "Помилка перешуковування запису для обрізання!"
@ -1025,6 +1028,12 @@ msgstr "Сканувати"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "Затримка сканування телегіда (г)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "Рівень корекції помилок EPG"
@ -1522,8 +1531,8 @@ msgstr "Модуль"
msgid "Up/Dn for new location - OK to move"
msgstr "Натискайте \"Вверх\"/\"Вниз\" для вибору позиції, а потім \"OK\""
msgid "Channel locked (recording)!"
msgstr "Канал заблоковано (йде запис)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "Недостатньо місця на диску!"
@ -1566,9 +1575,13 @@ msgstr "Йде запис - дійсно перезапустити?"
msgid "restart anyway?"
msgstr "дійсно перезапустити?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "Гучність "

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.6.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2022-12-01 21:45+0100\n"
"POT-Creation-Date: 2024-09-21 12:45+0200\n"
"PO-Revision-Date: 2013-03-04 14:52+0800\n"
"Last-Translator: NFVDR <nfvdr@live.com>\n"
"Language-Team: Chinese (simplified) <nfvdr@live.com>\n"
@ -132,8 +132,8 @@ msgstr "新闻杂志"
msgid "Content$Documentary"
msgstr "记录片"
msgid "Content$Discussion/Inverview/Debate"
msgstr "讨论/ Inverview/辩论"
msgid "Content$Discussion/Interview/Debate"
msgstr "讨论/ Interview/辩论"
msgid "Content$Show/Game Show"
msgstr "显示/游戏展"
@ -210,7 +210,7 @@ msgstr "摇滚/流行"
msgid "Content$Serious/Classical Music"
msgstr "严重/古典音乐"
msgid "Content$Folk/Tradional Music"
msgid "Content$Folk/Traditional Music"
msgstr "民谣/传统体育专业课程设置的音乐"
msgid "Content$Jazz"
@ -843,6 +843,9 @@ msgstr ""
msgid "Edited version already exists - overwrite?"
msgstr ""
msgid "Not enough free disk space to start editing process!"
msgstr ""
msgid "Error while queueing recording for cutting!"
msgstr ""
@ -1026,6 +1029,12 @@ msgstr "扫描"
msgid "Setup.EPG$EPG scan timeout (h)"
msgstr "节目单扫描超时 (h)"
msgid "Setup.EPG$EPG scan max. channel number (0=all)"
msgstr ""
msgid "Setup.EPG$EPG pause after scan"
msgstr ""
msgid "Setup.EPG$EPG bugfix level"
msgstr "节目单问题修复级别"
@ -1523,8 +1532,8 @@ msgstr "插件"
msgid "Up/Dn for new location - OK to move"
msgstr "上/下 新的定位 - OK 移动"
msgid "Channel locked (recording)!"
msgstr "频道已经锁定 (录像)!"
msgid "Primary device has no MPEG decoder, can't attach player!"
msgstr ""
msgid "Low disk space!"
msgstr "磁盘空间不足!"
@ -1567,9 +1576,13 @@ msgstr "录像中,是否现在重启?"
msgid "restart anyway?"
msgstr "现在重启?"
#. TRANSLATORS: note the plural/singular!
msgid "errors"
msgstr ""
msgid "error"
msgstr ""
#. TRANSLATORS: note the trailing blank!
msgid "Volume "
msgstr "音量调整 "

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recorder.c 5.4 2021/06/19 14:21:16 kls Exp $
* $Id: recorder.c 5.10 2024/09/19 09:49:02 kls Exp $
*/
#include "recorder.h"
@ -19,160 +19,18 @@
#define MINFREEDISKSPACE (512) // MB
#define DISKCHECKINTERVAL 100 // seconds
static bool DebugChecks = false;
// cTsChecker and cFrameChecker are used to detect errors in the recorded data stream.
// While cTsChecker checks the continuity counter of the incoming TS packets, cFrameChecker
// works on entire frames, checking their PTS (Presentation Time Stamps) to see whether
// all expected frames arrive. The resulting number of errors is not a precise value.
// If it is zero, the recording can be safely considered error free. The higher the value,
// the more damaged the recording is.
// --- cTsChecker ------------------------------------------------------------
#define TS_CC_UNKNOWN 0xFF
class cTsChecker {
private:
uchar counter[MAXPID];
int errors;
void Report(int Pid, const char *Message);
public:
cTsChecker(void);
void CheckTs(const uchar *Data, int Length);
int Errors(void) { return errors; }
};
cTsChecker::cTsChecker(void)
{
memset(counter, TS_CC_UNKNOWN, sizeof(counter));
errors = 0;
}
void cTsChecker::Report(int Pid, const char *Message)
{
errors++;
if (DebugChecks)
fprintf(stderr, "%s: TS error #%d on PID %d (%s)\n", *TimeToString(time(NULL)), errors, Pid, Message);
}
void cTsChecker::CheckTs(const uchar *Data, int Length)
{
int Pid = TsPid(Data);
uchar Cc = TsContinuityCounter(Data);
if (TsHasPayload(Data)) {
if (TsError(Data))
Report(Pid, "tei");
else if (TsIsScrambled(Data))
Report(Pid, "scrambled");
else {
uchar OldCc = counter[Pid];
if (OldCc != TS_CC_UNKNOWN) {
uchar NewCc = (OldCc + 1) & TS_CONT_CNT_MASK;
if (Cc != NewCc)
Report(Pid, "continuity");
}
}
}
counter[Pid] = Cc;
}
// --- cFrameChecker ---------------------------------------------------------
#define MAX_BACK_REFS 32
class cFrameChecker {
private:
int frameDelta;
int64_t lastPts;
uint32_t backRefs;
int lastFwdRef;
int errors;
void Report(const char *Message, int NumErrors = 1);
public:
cFrameChecker(void);
void SetFrameDelta(int FrameDelta) { frameDelta = FrameDelta; }
void CheckFrame(const uchar *Data, int Length);
void ReportBroken(void);
int Errors(void) { return errors; }
};
cFrameChecker::cFrameChecker(void)
{
frameDelta = PTSTICKS / DEFAULTFRAMESPERSECOND;
lastPts = -1;
backRefs = 0;
lastFwdRef = 0;
errors = 0;
}
void cFrameChecker::Report(const char *Message, int NumErrors)
{
errors += NumErrors;
if (DebugChecks)
fprintf(stderr, "%s: frame error #%d (%s)\n", *TimeToString(time(NULL)), errors, Message);
}
void cFrameChecker::CheckFrame(const uchar *Data, int Length)
{
int64_t Pts = TsGetPts(Data, Length);
if (Pts >= 0) {
if (lastPts >= 0) {
int Diff = int(round((PtsDiff(lastPts, Pts) / double(frameDelta))));
if (Diff > 0) {
if (Diff <= MAX_BACK_REFS) {
if (lastFwdRef > 1) {
if (backRefs != uint32_t((1 << (lastFwdRef - 1)) - 1))
Report("missing backref");
}
}
else
Report("missed", Diff);
backRefs = 0;
lastFwdRef = Diff;
lastPts = Pts;
}
else if (Diff < 0) {
Diff = -Diff;
if (Diff <= MAX_BACK_REFS) {
int b = 1 << (Diff - 1);
if ((backRefs & b) != 0)
Report("duplicate backref");
backRefs |= b;
}
else
Report("rev diff too big");
}
else
Report("zero diff");
}
else
lastPts = Pts;
}
else
Report("no PTS");
}
void cFrameChecker::ReportBroken(void)
{
int MissedFrames = MAXBROKENTIMEOUT / 1000 * PTSTICKS / frameDelta;
Report("missed", MissedFrames);
}
// --- cRecorder -------------------------------------------------------------
cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority)
:cReceiver(Channel, Priority)
,cThread("recording")
{
tsChecker = new cTsChecker;
frameChecker = new cFrameChecker;
recordingName = strdup(FileName);
recordingInfo = new cRecordingInfo(recordingName);
recordingInfo->Read();
oldErrors = max(0, recordingInfo->Errors()); // in case this is a re-started recording
errors = oldErrors;
lastErrors = errors;
errors = 0;
lastErrors = 0;
firstIframeSeen = false;
// Make sure the disk is up and running:
@ -220,8 +78,6 @@ cRecorder::~cRecorder()
delete fileName;
delete frameDetector;
delete ringBuffer;
delete frameChecker;
delete tsChecker;
free(recordingName);
}
@ -231,18 +87,15 @@ void cRecorder::HandleErrors(bool Force)
{
// We don't log every single error separately, to avoid spamming the log file:
if (Force || time(NULL) - lastErrorLog >= ERROR_LOG_DELTA) {
errors = tsChecker->Errors() + frameChecker->Errors();
if (errors > lastErrors) {
int d = errors - lastErrors;
if (DebugChecks)
fprintf(stderr, "%s: %s: %d error%s\n", *TimeToString(time(NULL)), recordingName, d, d > 1 ? "s" : "");
esyslog("%s: %d error%s", recordingName, d, d > 1 ? "s" : "");
esyslog("%s: %d new error%s (total %d)", recordingName, d, d > 1 ? "s" : "", oldErrors + errors);
recordingInfo->SetErrors(oldErrors + errors);
recordingInfo->Write();
LOCK_RECORDINGS_WRITE;
Recordings->UpdateByName(recordingName);
lastErrors = errors;
}
lastErrors = errors;
lastErrorLog = time(NULL);
}
}
@ -307,8 +160,6 @@ void cRecorder::Receive(const uchar *Data, int Length)
int p = ringBuffer->Put(Data, Length);
if (p != Length && Running())
ringBuffer->ReportOverflow(Length - p);
else if (firstIframeSeen) // we ignore any garbage before the first I-frame
tsChecker->CheckTs(Data, Length);
}
}
@ -316,6 +167,15 @@ void cRecorder::Action(void)
{
cTimeMs t(MAXBROKENTIMEOUT);
bool InfoWritten = false;
bool pendIndependentFrame = false;
uint16_t pendNumber = 0;
off_t pendFileSize = 0;
bool pendErrors = false;
bool pendMissing = false;
// Check if this is a resumed recording, in which case we definitely missed frames:
NextFile();
if (fileName->Number() > 1 || oldErrors)
frameDetector->SetMissing();
while (Running()) {
int r;
uchar *b = ringBuffer->Get(r);
@ -326,25 +186,39 @@ void cRecorder::Action(void)
break;
if (frameDetector->Synced()) {
if (!InfoWritten) {
if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(recordingInfo->FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(recordingInfo->FramesPerSecond(), frameDetector->FramesPerSecond())) {
if ((frameDetector->FramesPerSecond() > 0 && DoubleEqual(recordingInfo->FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(recordingInfo->FramesPerSecond(), frameDetector->FramesPerSecond())) ||
frameDetector->FrameWidth() != recordingInfo->FrameWidth() ||
frameDetector->FrameHeight() != recordingInfo->FrameHeight() ||
frameDetector->AspectRatio() != recordingInfo->AspectRatio()) {
recordingInfo->SetFramesPerSecond(frameDetector->FramesPerSecond());
recordingInfo->SetFrameParams(frameDetector->FrameWidth(), frameDetector->FrameHeight(), frameDetector->ScanType(), frameDetector->AspectRatio());
recordingInfo->Write();
LOCK_RECORDINGS_WRITE;
Recordings->UpdateByName(recordingName);
}
InfoWritten = true;
cRecordingUserCommand::InvokeCommand(RUC_STARTRECORDING, recordingName);
frameChecker->SetFrameDelta(PTSTICKS / frameDetector->FramesPerSecond());
}
if (firstIframeSeen || frameDetector->IndependentFrame()) {
firstIframeSeen = true; // start recording with the first I-frame
if (!NextFile())
break;
if (frameDetector->NewFrame()) {
if (index)
index->Write(frameDetector->IndependentFrame(), fileName->Number(), fileSize);
if (frameChecker)
frameChecker->CheckFrame(b, Count);
int PreviousErrors = 0;
int MissingFrames = 0;
if (frameDetector->NewFrame(&PreviousErrors, &MissingFrames)) {
if (index) {
if (pendNumber > 0)
index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
pendIndependentFrame = frameDetector->IndependentFrame();
pendNumber = fileName->Number();
pendFileSize = fileSize;
pendErrors = PreviousErrors;
pendMissing = MissingFrames;
}
if (PreviousErrors)
errors++;
if (MissingFrames)
errors++;
}
if (frameDetector->IndependentFrame()) {
recordFile->Write(patPmtGenerator.GetPat(), TS_SIZE);
@ -368,12 +242,17 @@ void cRecorder::Action(void)
}
}
if (t.TimedOut()) {
frameChecker->ReportBroken();
if (pendNumber > 0)
index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
frameDetector->SetMissing();
errors += MAXBROKENTIMEOUT / 1000 * frameDetector->FramesPerSecond();
HandleErrors(true);
esyslog("ERROR: video data stream broken");
ShutdownHandler.RequestEmergencyExit();
t.Set(MAXBROKENTIMEOUT);
}
}
if (pendNumber > 0)
index->Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
HandleErrors(true);
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recorder.h 5.1 2021/05/19 11:22:20 kls Exp $
* $Id: recorder.h 5.3 2024/09/17 09:39:50 kls Exp $
*/
#ifndef __RECORDER_H
@ -16,13 +16,8 @@
#include "ringbuffer.h"
#include "thread.h"
class cTsChecker;
class cFrameChecker;
class cRecorder : public cReceiver, cThread {
private:
cTsChecker *tsChecker;
cFrameChecker *frameChecker;
cRingBufferLinear *ringBuffer;
cFrameDetector *frameDetector;
cPatPmtGenerator patPmtGenerator;
@ -56,6 +51,9 @@ public:
virtual ~cRecorder();
int Errors(void) { return oldErrors + errors; };
///< Returns the number of errors that were detected during recording.
///< Each frame that is missing or contains (any number of) errors counts as one error.
///< If this is a resumed recording, this includes errors that occurred
///< in the previous parts.
};
#endif //__RECORDER_H

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recording.c 5.22 2023/02/15 14:59:25 kls Exp $
* $Id: recording.c 5.37 2025/01/18 20:57:06 kls Exp $
*/
#include "recording.h"
@ -24,7 +24,6 @@
#include "i18n.h"
#include "interface.h"
#include "menu.h"
#include "remux.h"
#include "ringbuffer.h"
#include "skins.h"
#include "svdrp.h"
@ -357,12 +356,17 @@ void cResumeFile::Delete(void)
cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event)
{
modified = 0;
channelID = Channel ? Channel->GetChannelID() : tChannelID::InvalidID;
channelName = Channel ? strdup(Channel->Name()) : NULL;
ownEvent = Event ? NULL : new cEvent(0);
event = ownEvent ? ownEvent : Event;
aux = NULL;
framesPerSecond = DEFAULTFRAMESPERSECOND;
frameWidth = 0;
frameHeight = 0;
scanType = stUnknown;
aspectRatio = arUnknown;
priority = MAXPRIORITY;
lifetime = MAXLIFETIME;
fileName = NULL;
@ -417,6 +421,7 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event)
cRecordingInfo::cRecordingInfo(const char *FileName)
{
modified = 0;
channelID = tChannelID::InvalidID;
channelName = NULL;
ownEvent = new cEvent(0);
@ -424,6 +429,10 @@ cRecordingInfo::cRecordingInfo(const char *FileName)
aux = NULL;
errors = -1;
framesPerSecond = DEFAULTFRAMESPERSECOND;
frameWidth = 0;
frameHeight = 0;
scanType = stUnknown;
aspectRatio = arUnknown;
priority = MAXPRIORITY;
lifetime = MAXLIFETIME;
fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX));
@ -458,6 +467,14 @@ void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond)
framesPerSecond = FramesPerSecond;
}
void cRecordingInfo::SetFrameParams(uint16_t FrameWidth, uint16_t FrameHeight, eScanType ScanType, eAspectRatio AspectRatio)
{
frameWidth = FrameWidth;
frameHeight = FrameHeight;
scanType = ScanType;
aspectRatio = AspectRatio;
}
void cRecordingInfo::SetFileName(const char *FileName)
{
bool IsPesRecording = fileName && endswith(fileName, ".vdr");
@ -473,6 +490,12 @@ void cRecordingInfo::SetErrors(int Errors)
bool cRecordingInfo::Read(FILE *f)
{
if (ownEvent) {
struct stat st;
if (fstat(fileno(f), &st))
return false;
if (modified == st.st_mtime)
return true;
modified = st.st_mtime;
cReadLine ReadLine;
char *s;
int line = 0;
@ -504,10 +527,39 @@ bool cRecordingInfo::Read(FILE *f)
ownEvent->SetDuration(Duration);
ownEvent->SetTableID(uchar(TableID));
ownEvent->SetVersion(uchar(Version));
ownEvent->SetComponents(NULL);
}
}
break;
case 'F': framesPerSecond = atod(t);
case 'F': {
char *fpsBuf = NULL;
char scanTypeCode;
char *arBuf = NULL;
int n = sscanf(t, "%m[^ ] %hu %hu %c %m[^\n]", &fpsBuf, &frameWidth, &frameHeight, &scanTypeCode, &arBuf);
if (n >= 1) {
framesPerSecond = atod(fpsBuf);
if (n >= 4) {
scanType = stUnknown;
for (int st = stUnknown + 1; st < stMax; st++) {
if (ScanTypeChars[st] == scanTypeCode) {
scanType = eScanType(st);
break;
}
}
aspectRatio = arUnknown;
if (n == 5) {
for (int ar = arUnknown + 1; ar < arMax; ar++) {
if (strcmp(arBuf, AspectRatioTexts[ar]) == 0) {
aspectRatio = eAspectRatio(ar);
break;
}
}
}
}
}
free(fpsBuf);
free(arBuf);
}
break;
case 'L': lifetime = atoi(t);
break;
@ -536,7 +588,10 @@ bool cRecordingInfo::Write(FILE *f, const char *Prefix) const
if (channelID.Valid())
fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : "");
event->Dump(f, Prefix, true);
fprintf(f, "%sF %s\n", Prefix, *dtoa(framesPerSecond, "%.10g"));
if (frameWidth > 0 && frameHeight > 0)
fprintf(f, "%sF %s %s %s %c %s\n", Prefix, *dtoa(framesPerSecond, "%.10g"), *itoa(frameWidth), *itoa(frameHeight), ScanTypeChars[scanType], AspectRatioTexts[aspectRatio]);
else
fprintf(f, "%sF %s\n", Prefix, *dtoa(framesPerSecond, "%.10g"));
fprintf(f, "%sP %d\n", Prefix, priority);
fprintf(f, "%sL %d\n", Prefix, lifetime);
fprintf(f, "%sO %d\n", Prefix, errors);
@ -579,6 +634,27 @@ bool cRecordingInfo::Write(void) const
return Result;
}
cString cRecordingInfo::FrameParams(void) const
{
cString s;
if (frameWidth && frameHeight) {
s = cString::sprintf("%dx%d", frameWidth, frameHeight);
if (framesPerSecond > 0) {
if (*s)
s.Append("/");
s.Append(dtoa(framesPerSecond, "%.2g"));
if (scanType != stUnknown)
s.Append(ScanTypeChar());
}
if (aspectRatio != arUnknown) {
if (*s)
s.Append(" ");
s.Append(AspectRatioText());
}
}
return s;
}
// --- cRecording ------------------------------------------------------------
#define RESUME_NOT_INITIALIZED (-2)
@ -1278,6 +1354,7 @@ bool cRecording::ChangeName(const char *NewName)
fileName = strdup(OldFileName);
return false;
}
info->SetFileName(NewFileName);
isOnVideoDirectoryFileSystem = -1; // it might have been moved to a different file system
ClearSortName();
}
@ -1379,6 +1456,17 @@ int cRecording::NumFrames(void) const
return numFrames;
}
int cRecording::NumFramesAfterEdit(void) const
{
int IndexLength = cIndexFile::GetLength(fileName, isPesRecording);
if (IndexLength > 0) {
cMarks Marks;
if (Marks.Load(fileName, framesPerSecond, isPesRecording))
return Marks.GetFrameAfterEdit(IndexLength - 1, IndexLength - 1);
}
return -1;
}
int cRecording::LengthInSeconds(void) const
{
int nf = NumFrames();
@ -1387,6 +1475,14 @@ int cRecording::LengthInSeconds(void) const
return -1;
}
int cRecording::LengthInSecondsAfterEdit(void) const
{
int nf = NumFramesAfterEdit();
if (nf >= 0)
return int(nf / FramesPerSecond());
return -1;
}
int cRecording::FileSizeMB(void) const
{
if (fileSizeMB < 0) {
@ -1475,7 +1571,8 @@ void cVideoDirectoryScannerThread::ScanVideoDir(const char *DirName, int LinkLev
dsyslog("activated name checking for initial read of video directory");
initial = false;
}
if (Recordings == deletedRecordings || initial || !Recordings->GetByName(buffer)) {
cRecording *Recording = NULL;
if (Recordings == deletedRecordings || initial || !(Recording = Recordings->GetByName(buffer))) {
cRecording *r = new cRecording(buffer);
if (r->Name()) {
r->NumFrames(); // initializes the numFrames member
@ -1489,6 +1586,8 @@ void cVideoDirectoryScannerThread::ScanVideoDir(const char *DirName, int LinkLev
else
delete r;
}
else if (Recording)
Recording->ReadInfo();
StateKey.Remove();
}
else
@ -1541,7 +1640,7 @@ const char *cRecordings::UpdateFileName(void)
void cRecordings::TouchUpdate(void)
{
bool needsUpdate = NeedsUpdate();
TouchFile(UpdateFileName());
TouchFile(UpdateFileName(), true);
if (!needsUpdate)
lastUpdate = time(NULL); // make sure we don't trigger ourselves
}
@ -1988,8 +2087,10 @@ void cRecordingsHandlerEntry::Cleanup(cRecordings *Recordings)
delete cutter;
cutter = NULL;
}
cVideoDirectory::RemoveVideoFile(fileNameDst);
if (cRecording *Recording = Recordings->GetByName(fileNameDst))
Recording->Delete();
Recordings->DelByName(fileNameDst);
Recordings->SetModified();
}
}
if ((usage & (ruMove | ruCopy)) // this was a move/copy operation...
@ -2000,10 +2101,12 @@ void cRecordingsHandlerEntry::Cleanup(cRecordings *Recordings)
delete copier;
copier = NULL;
}
cVideoDirectory::RemoveVideoFile(fileNameDst);
if (cRecording *Recording = Recordings->GetByName(fileNameDst))
Recording->Delete();
if ((usage & ruMove) != 0)
Recordings->AddByName(fileNameSrc);
Recordings->DelByName(fileNameDst);
Recordings->SetModified();
}
}
@ -2114,6 +2217,24 @@ int cRecordingsHandler::GetUsage(const char *FileName)
return ruNone;
}
int cRecordingsHandler::GetRequiredDiskSpaceMB(const char *FileName)
{
int RequiredDiskSpaceMB = 0;
for (cRecordingsHandlerEntry *r = operations.First(); r; r = operations.Next(r)) {
if ((r->Usage() & ruCanceled) != 0)
continue;
if ((r->Usage() & ruCut) != 0) {
if (!FileName || EntriesOnSameFileSystem(FileName, r->FileNameDst()))
RequiredDiskSpaceMB += FileSizeMBafterEdit(r->FileNameSrc());
}
else if ((r->Usage() & (ruMove | ruCopy)) != 0) {
if (!FileName || EntriesOnSameFileSystem(FileName, r->FileNameDst()))
RequiredDiskSpaceMB += DirSizeMB(r->FileNameSrc());
}
}
return RequiredDiskSpaceMB;
}
bool cRecordingsHandler::Finished(bool &Error)
{
cMutexLock MutexLock(&mutex);
@ -2344,6 +2465,38 @@ int cMarks::GetNumSequences(void) const
return NumSequences;
}
int cMarks::GetFrameAfterEdit(int Frame, int LastFrame) const
{
if (Count() == 0 || LastFrame < 0 || Frame < 0 || Frame > LastFrame)
return -1;
int EditedFrame = 0;
int PrevPos = -1;
bool InEdit = false;
for (const cMark *mi = First(); mi; mi = Next(mi)) {
int p = mi->Position();
if (InEdit) {
EditedFrame += p - PrevPos;
InEdit = false;
if (Frame <= p) {
EditedFrame -= p - Frame;
return EditedFrame;
}
}
else {
if (Frame <= p)
return EditedFrame;
PrevPos = p;
InEdit = true;
}
}
if (InEdit) {
EditedFrame += LastFrame - PrevPos; // the last sequence had no actual "end" mark
if (Frame < LastFrame)
EditedFrame -= LastFrame - Frame;
}
return EditedFrame;
}
// --- cRecordingUserCommand -------------------------------------------------
const char *cRecordingUserCommand::command = NULL;
@ -2406,6 +2559,12 @@ void cIndexFileGenerator::Action(void)
uint16_t FileNumber = 1;
off_t FileOffset = 0;
int Last = -1;
bool pendIndependentFrame = false;
uint16_t pendNumber = 0;
off_t pendFileSize = 0;
bool pendErrors = false;
bool pendMissing = false;
int Errors = 0;
if (update) {
// Look for current index and position to end of it if present:
bool Independent;
@ -2441,11 +2600,24 @@ void cIndexFileGenerator::Action(void)
FrameOffset = FileSize; // the PAT/PMT is at the beginning of an I-frame
int Processed = FrameDetector.Analyze(Data, Length);
if (Processed > 0) {
if (FrameDetector.NewFrame()) {
if (IndexFileWritten || Last < 0) // check for first frame and do not write if in update mode
IndexFile.Write(FrameDetector.IndependentFrame(), FileName.Number(), FrameOffset >= 0 ? FrameOffset : FileSize);
int PreviousErrors = 0;
int MissingFrames = 0;
if (FrameDetector.NewFrame(&PreviousErrors, &MissingFrames)) {
if (IndexFileWritten || Last < 0) { // check for first frame and do not write if in update mode
if (pendNumber > 0)
IndexFile.Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
pendIndependentFrame = FrameDetector.IndependentFrame();
pendNumber = FileName.Number();
pendFileSize = FrameOffset >= 0 ? FrameOffset : FileSize;
pendErrors = PreviousErrors;
pendMissing = MissingFrames;
}
FrameOffset = -1;
IndexFileWritten = true;
if (PreviousErrors)
Errors++;
if (MissingFrames)
Errors++;
}
FileSize += Processed;
Buffer.Del(Processed);
@ -2453,7 +2625,7 @@ void cIndexFileGenerator::Action(void)
}
else if (PatPmtParser.Completed()) {
// Step 2 - sync FrameDetector:
int Processed = FrameDetector.Analyze(Data, Length);
int Processed = FrameDetector.Analyze(Data, Length, false);
if (Processed > 0) {
if (FrameDetector.Synced()) {
// Synced FrameDetector, so rewind for actual processing:
@ -2511,6 +2683,8 @@ void cIndexFileGenerator::Action(void)
}
// Recording has been processed:
else {
if (pendNumber > 0)
IndexFile.Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
IndexFileComplete = true;
break;
}
@ -2520,8 +2694,14 @@ void cIndexFileGenerator::Action(void)
if (IndexFileWritten) {
cRecordingInfo RecordingInfo(recordingName);
if (RecordingInfo.Read()) {
if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) {
if ((FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) ||
FrameDetector.FrameWidth() != RecordingInfo.FrameWidth() ||
FrameDetector.FrameHeight() != RecordingInfo.FrameHeight() ||
FrameDetector.AspectRatio() != RecordingInfo.AspectRatio() ||
Errors != RecordingInfo.Errors()) {
RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond());
RecordingInfo.SetFrameParams(FrameDetector.FrameWidth(), FrameDetector.FrameHeight(), FrameDetector.ScanType(), FrameDetector.AspectRatio());
RecordingInfo.SetErrors(Errors);
RecordingInfo.Write();
LOCK_RECORDINGS_WRITE;
Recordings->UpdateByName(recordingName);
@ -2554,13 +2734,17 @@ struct __attribute__((packed)) tIndexPes {
struct __attribute__((packed)) tIndexTs {
uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!)
int reserved:7; // reserved for future use
int reserved:5; // reserved for future use
int errors:1; // 1=this frame contains errors
int missing:1; // 1=there are frames missing after this one
int independent:1; // marks frames that can be displayed by themselves (for trick modes)
uint16_t number:16; // up to 64K files per recording
tIndexTs(off_t Offset, bool Independent, uint16_t Number)
tIndexTs(off_t Offset, bool Independent, uint16_t Number, bool Errors, bool Missing)
{
offset = Offset;
reserved = 0;
errors = Errors;
missing = Missing;
independent = Independent;
number = Number;
}
@ -2576,6 +2760,7 @@ cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsPesRecording, b
f = -1;
size = 0;
last = -1;
lastErrorIndex = last;
index = NULL;
isPesRecording = IsPesRecording;
indexFileGenerator = NULL;
@ -2753,10 +2938,10 @@ bool cIndexFile::CatchUp(int Index)
return index != NULL;
}
bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset, bool Errors, bool Missing)
{
if (f >= 0) {
tIndexTs i(FileOffset, Independent, FileNumber);
tIndexTs i(FileOffset, Independent, FileNumber, Errors, Missing);
if (isPesRecording)
ConvertToPes(&i, 1);
if (safe_write(f, &i, sizeof(i)) < 0) {
@ -2770,7 +2955,7 @@ bool cIndexFile::Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
return f >= 0;
}
bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length)
bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent, int *Length, bool *Errors, bool *Missing)
{
if (CatchUp(Index)) {
if (Index >= 0 && Index <= last) {
@ -2790,12 +2975,27 @@ bool cIndexFile::Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *I
else
*Length = -1;
}
if (Errors)
*Errors = index[Index].errors;
if (Missing)
*Missing = index[Index].missing;
return true;
}
}
return false;
}
const cErrors *cIndexFile::GetErrors(void)
{
for (int Index = lastErrorIndex + 1; Index <= last; Index++) {
tIndexTs *p = &index[Index];
if (p->errors || p->missing)
errors.Append(Index);
}
lastErrorIndex = last;
return &errors;
}
int cIndexFile::GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber, off_t *FileOffset, int *Length)
{
if (CatchUp()) {
@ -3157,7 +3357,7 @@ void cDoneRecordings::Append(const char *Title)
}
}
static const char *FuzzyChars = " -:";
static const char *FuzzyChars = " -:/";
static const char *SkipFuzzyChars(const char *s)
{
@ -3308,3 +3508,39 @@ cString GetRecordingTimerId(const char *Directory)
}
return Id;
}
// --- Disk space calculation for editing ------------------------------------
int FileSizeMBafterEdit(const char *FileName)
{
int FileSizeMB = DirSizeMB(FileName);
if (FileSizeMB > 0) {
cRecording r(FileName);
int NumFramesOrg = r.NumFrames();
if (NumFramesOrg > 0) {
int NumFramesEdit = r.NumFramesAfterEdit();
if (NumFramesEdit > 0)
return max(1, int(FileSizeMB * (double(NumFramesEdit) / NumFramesOrg)));
}
}
return -1;
}
bool EnoughFreeDiskSpaceForEdit(const char *FileName)
{
int FileSizeMB = FileSizeMBafterEdit(FileName);
if (FileSizeMB > 0) {
int FreeDiskMB;
cVideoDirectory::VideoDiskSpace(&FreeDiskMB);
cString EditedFileName = cCutter::EditedFileName(FileName);
if (access(EditedFileName, F_OK)) {
int ExistingEditedSizeMB = DirSizeMB(EditedFileName);
if (ExistingEditedSizeMB > 0)
FreeDiskMB += ExistingEditedSizeMB;
}
FreeDiskMB -= RecordingsHandler.GetRequiredDiskSpaceMB(FileName);
FreeDiskMB -= MINDISKSPACE;
return FileSizeMB < FreeDiskMB;
}
return false;
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: recording.h 5.5 2021/05/23 15:03:17 kls Exp $
* $Id: recording.h 5.12 2025/01/15 10:50:29 kls Exp $
*/
#ifndef __RECORDING_H
@ -17,6 +17,7 @@
#include "thread.h"
#include "timers.h"
#include "tools.h"
#include "remux.h"
#define FOLDERDELIMCHAR '~'
@ -63,12 +64,17 @@ public:
class cRecordingInfo {
friend class cRecording;
private:
time_t modified;
tChannelID channelID;
char *channelName;
const cEvent *event;
cEvent *ownEvent;
char *aux;
double framesPerSecond;
uint16_t frameWidth;
uint16_t frameHeight;
eScanType scanType;
eAspectRatio aspectRatio;
int priority;
int lifetime;
char *fileName;
@ -87,7 +93,15 @@ public:
const cComponents *Components(void) const { return event->Components(); }
const char *Aux(void) const { return aux; }
double FramesPerSecond(void) const { return framesPerSecond; }
uint16_t FrameWidth(void) const { return frameWidth; }
uint16_t FrameHeight(void) const { return frameHeight; }
eScanType ScanType(void) const { return scanType; }
char ScanTypeChar(void) const { return ScanTypeChars[scanType]; }
eAspectRatio AspectRatio(void) const { return aspectRatio; }
const char *AspectRatioText(void) const { return AspectRatioTexts[aspectRatio]; }
cString FrameParams(void) const;
void SetFramesPerSecond(double FramesPerSecond);
void SetFrameParams(uint16_t FrameWidth, uint16_t FrameHeight, eScanType ScanType, eAspectRatio AspectRatio);
void SetFileName(const char *FileName);
int Errors(void) const { return errors; } // returns -1 if undefined
void SetErrors(int Errors);
@ -161,8 +175,15 @@ public:
int NumFrames(void) const;
///< Returns the number of frames in this recording.
///< If the number of frames is unknown, -1 will be returned.
int NumFramesAfterEdit(void) const;
///< Returns the number of frames in the edited version of this recording.
///< If there are no editing marks, 0 will be returned.
///< If the number of frames is unknown, -1 will be returned.
int LengthInSeconds(void) const;
///< Returns the length (in seconds) of this recording, or -1 in case of error.
int LengthInSecondsAfterEdit(void) const;
///< Returns the length (in seconds) of the edited version of this recording, or -1 in case of error.
///< If there are no editing marks, 0 will be returned.
int FileSizeMB(void) const;
///< Returns the total file size of this recording (in MB), or -1 if the file
///< size is unknown.
@ -340,6 +361,10 @@ public:
///< Deletes/terminates all operations.
int GetUsage(const char *FileName);
///< Returns the usage type for the given FileName.
int GetRequiredDiskSpaceMB(const char *FileName = NULL);
///< Returns the total disk space required to process all actions.
///< If FileName is given, only the drive that contains that file is taken
///< into account.
bool Finished(bool &Error);
///< Returns true if all operations in the list have been finished.
///< If there have been any errors, Errors will be set to true.
@ -411,6 +436,10 @@ public:
///< 0 (the beginning of the recording), and there is no "end" mark, the
///< return value is 0, which means that the result is the same as the original
///< recording.
int GetFrameAfterEdit(int Frame, int LastFrame) const;
///< Returns the number of the given Frame within the region covered by begin/end sequences.
///< LastFrame must be given by the caller.
///< If there are no editing marks or in case of an error -1 is returned.
cMark *Get(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->Get(Position)); }
cMark *GetPrev(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetPrev(Position)); }
cMark *GetNext(int Position) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNext(Position)); }
@ -418,6 +447,9 @@ public:
cMark *GetNextEnd(const cMark *BeginMark) { return const_cast<cMark *>(static_cast<const cMarks *>(this)->GetNextEnd(BeginMark)); }
};
class cErrors : public cVector<int> {
};
#define RUC_BEFORERECORDING "before"
#define RUC_STARTRECORDING "started"
#define RUC_AFTERRECORDING "after"
@ -458,9 +490,11 @@ private:
int f;
cString fileName;
int size, last;
int lastErrorIndex;
tIndexTs *index;
bool isPesRecording;
cResumeFile resumeFile;
cErrors errors;
cIndexFileGenerator *indexFileGenerator;
cMutex mutex;
void ConvertFromPes(tIndexTs *IndexTs, int Count);
@ -470,8 +504,10 @@ public:
cIndexFile(const char *FileName, bool Record, bool IsPesRecording = false, bool PauseLive = false, bool Update = false);
~cIndexFile();
bool Ok(void) { return index != NULL; }
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset);
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL);
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset, bool Errors = false, bool Missing = false);
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL, bool *Errors = NULL, bool *Missing = NULL);
const cErrors *GetErrors(void);
///< Returns the frame indexes of errors in the recording (if any).
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL);
int GetClosestIFrame(int Index);
///< Returns the index of the I-frame that is closest to the given Index (or Index itself,
@ -557,4 +593,7 @@ void IncRecordingsSortMode(const char *Directory);
void SetRecordingTimerId(const char *Directory, const char *TimerId);
cString GetRecordingTimerId(const char *Directory);
int FileSizeMBafterEdit(const char *FileName);
bool EnoughFreeDiskSpaceForEdit(const char *FileName);
#endif //__RECORDING_H

259
remux.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: remux.c 5.5 2022/11/30 14:38:46 kls Exp $
* $Id: remux.c 5.18 2024/12/05 10:37:15 kls Exp $
*/
#include "remux.h"
@ -287,6 +287,8 @@ uchar cTsPayload::GetByte(void)
if (TsPid(p) == pid) { // only handle TS packets for the initial PID
if (++numPacketsPid > MAX_TS_PACKETS_FOR_VIDEO_FRAME_DETECTION)
return SetEof();
if (TsError(p))
return SetEof(); // don't parse TS packets with errors
if (TsHasPayload(p)) {
if (index > 0 && TsPayloadStart(p)) // checking index to not skip the very first TS packet
return SetEof();
@ -1177,7 +1179,8 @@ protected:
uint16_t frameWidth;
uint16_t frameHeight;
double framesPerSecond;
bool progressive;
eScanType scanType;
eAspectRatio aspectRatio;
public:
cFrameParser(void);
virtual ~cFrameParser() {};
@ -1195,7 +1198,8 @@ public:
uint16_t FrameWidth(void) { return frameWidth; }
uint16_t FrameHeight(void) { return frameHeight; }
double FramesPerSecond(void) { return framesPerSecond; }
bool Progressive(void) { return progressive; }
eScanType ScanType(void) { return scanType; }
eAspectRatio AspectRatio(void) { return aspectRatio; }
};
cFrameParser::cFrameParser(void)
@ -1207,7 +1211,8 @@ cFrameParser::cFrameParser(void)
frameWidth = 0;
frameHeight = 0;
framesPerSecond = 0.0;
progressive = false;
scanType = stUnknown;
aspectRatio = arUnknown;
}
// --- cAudioParser ----------------------------------------------------------
@ -1322,17 +1327,24 @@ int cMpeg2Parser::Parse(const uchar *Data, int Length, int Pid)
uchar b = tsPayload.GetByte(); // ignoring two MSB of width and height in sequence extension
frameWidth |= b >> 4; // as 12 Bit = max 4095 should be sufficient for all available MPEG2 streams
frameHeight = (b & 0x0F) << 8 | tsPayload.GetByte();
b = tsPayload.GetByte();
b = tsPayload.GetByte(); // hi: aspect ratio info, lo: frame rate code
switch (b >> 4) {
case 1: aspectRatio = ar_1_1; break;
case 2: aspectRatio = ar_4_3; break;
case 3: aspectRatio = ar_16_9; break;
case 4: aspectRatio = ar_2_21_1; break;
default: aspectRatio = arUnknown;
}
uchar frame_rate_value = b & 0x0F;
if (frame_rate_value > 0 && frame_rate_value <= 8)
framesPerSecond = frame_rate_table[frame_rate_value];
}
else if (!seenScanType && scanner == 0x000001B5) { // Extension start code
if ((tsPayload.GetByte() & 0xF0) == 0x10) { // Sequence Extension
progressive = (tsPayload.GetByte() & 0x40) != 0;
scanType = (tsPayload.GetByte() & 0x40) ? stProgressive : stInterlaced;
seenScanType = true;
if (debug) {
cString s = cString::sprintf("MPEG2: %d x %d%c %.2f fps", frameWidth, frameHeight, progressive ? 'p' : 'i', framesPerSecond);
cString s = cString::sprintf("MPEG2: %d x %d%c %.2f fps %s", frameWidth, frameHeight, ScanTypeChars[scanType], framesPerSecond, AspectRatioTexts[aspectRatio]);
dsyslog("%s", *s);
dbgframes("\n%s", *s);
}
@ -1553,7 +1565,7 @@ void cH264Parser::ParseSequenceParameterSet(void)
uint16_t frame_Height = 16 * (1 + GetGolombUe()); // pic_height_in_map_units_minus1
frame_mbs_only_flag = GetBit(); // frame_mbs_only_flag
if (frameWidth == 0) {
progressive = frame_mbs_only_flag;
scanType = frame_mbs_only_flag ? stProgressive : stInterlaced;
if (!frame_mbs_only_flag) {
GetBit(); // mb_adaptive_frame_field_flag
frame_Height *= 2;
@ -1577,6 +1589,8 @@ void cH264Parser::ParseSequenceParameterSet(void)
}
frame_Width -= CropUnitX * (frame_crop_left_offset + frame_crop_right_offset);
frame_Height -= CropUnitY * (frame_crop_top_offset + frame_crop_bottom_offset);
if (frame_Height > 1080 && frame_Height <= 1090) // workaround for channels with wrong crop parameters
frame_Height = 1080;
}
frameWidth = frame_Width;
frameHeight = frame_Height;
@ -1584,8 +1598,11 @@ void cH264Parser::ParseSequenceParameterSet(void)
if (GetBit()) { // vui_parameters_present_flag
if (GetBit()) { // aspect_ratio_info_present
int aspect_ratio_idc = GetBits(8); // aspect_ratio_idc
if (aspect_ratio_idc == 255)
GetBits(32);
if (aspect_ratio_idc == 255) // EXTENDED_SAR
GetBits(32); // sar_width, sar_height
else if (frameHeight >= 720 && (aspect_ratio_idc == 1 || aspect_ratio_idc == 14 || aspect_ratio_idc == 15 || aspect_ratio_idc == 16))
aspectRatio = ar_16_9;
// implement decoding of other aspect_ratio_idc values when they are required
}
if (GetBit()) // overscan_info_present_flag
GetBit(); // overscan_approriate_flag
@ -1606,7 +1623,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);
cString s = cString::sprintf("H.264: %d x %d%c %.2f fps %d Bit %s", frameWidth, frameHeight, ScanTypeChars[scanType], framesPerSecond, bitDepth, AspectRatioTexts[aspectRatio]);
dsyslog("%s", *s);
dbgframes("\n%s", *s);
}
@ -1728,7 +1745,7 @@ void cH265Parser::ParseSequenceParameterSet(void)
uint8_t sub_layer_profile_present_flag[8];
uint8_t sub_layer_level_present_flag[8];
GetBits(4); // sps_video_parameter_set_id
int sps_max_sub_layers_minus1 = GetBits(3); // sps_max_sub_layers_minus1
int sps_max_sub_layers_minus1 = GetBits(3) & 7; // sps_max_sub_layers_minus1 ("& 7" to silence a compiler warning with gcc 14.1.0)
GetBit(); // sps_temporal_id_nesting_flag
// begin profile_tier_level(1, sps_max_sub_layers_minus1)
GetByte();
@ -1737,7 +1754,7 @@ void cH265Parser::ParseSequenceParameterSet(void)
GetByte();
GetByte();
bool general_progressive_source_flag = GetBit(); // general_progressive_source_flag
progressive = general_progressive_source_flag;
scanType = general_progressive_source_flag ? stProgressive : stInterlaced;
GetBit(); // general_interlaced_source_flag
GetBits(6);
GetByte();
@ -1880,6 +1897,9 @@ void cH265Parser::ParseSequenceParameterSet(void)
int aspect_ratio_idc = GetBits(8); // aspect_ratio_idc
if (aspect_ratio_idc == 255) // EXTENDED_SAR
GetBits(32); // sar_width, sar_height
else if (aspect_ratio_idc == 1 || aspect_ratio_idc == 14)
aspectRatio = ar_16_9;
// implement decoding of other aspect_ratio_idc values when they are required
}
if (GetBit()) // overscan_info_present_flag
GetBit(); // overscan_appropriate_flag
@ -1907,14 +1927,181 @@ 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);
cString s = cString::sprintf("H.265: %d x %d%c %.2f fps %d Bit %s", frameWidth, frameHeight, ScanTypeChars[scanType], framesPerSecond, bitDepth, AspectRatioTexts[aspectRatio]);
dsyslog("%s", *s);
dbgframes("\n%s", *s);
}
}
static bool DebugChecks = false;
// cTsChecker and cFrameChecker are used to detect errors in the recorded data stream.
// While cTsChecker checks the continuity counter of the incoming TS packets, cFrameChecker
// works on entire frames, checking their PTS (Presentation Time Stamps) to see whether
// all expected frames arrive. The resulting number of errors is not a precise value.
// If it is zero, the recording can be safely considered error free. The higher the value,
// the more damaged the recording is.
// --- cTsChecker ------------------------------------------------------------
#define TS_CC_UNKNOWN 0xFF
class cTsChecker {
private:
uchar counter[MAXPID];
int errors;
void Report(int Pid, const char *Message);
public:
cTsChecker(void);
void CheckTs(const uchar *Data, int Length);
int Errors(void) { return errors; }
void Clear(void) { errors = 0; }
};
cTsChecker::cTsChecker(void)
{
memset(counter, TS_CC_UNKNOWN, sizeof(counter));
errors = 0;
}
void cTsChecker::Report(int Pid, const char *Message)
{
errors++;
if (DebugChecks)
fprintf(stderr, "%s: TS error #%d on PID %d (%s)\n", *TimeToString(time(NULL)), errors, Pid, Message);
}
void cTsChecker::CheckTs(const uchar *Data, int Length)
{
while (Length >= TS_SIZE) {
int Pid = TsPid(Data);
uchar Cc = TsContinuityCounter(Data);
if (TsHasPayload(Data)) {
if (TsError(Data))
Report(Pid, "tei");
else if (TsIsScrambled(Data))
Report(Pid, "scrambled");
else {
uchar OldCc = counter[Pid];
if (OldCc != TS_CC_UNKNOWN) {
uchar NewCc = (OldCc + 1) & TS_CONT_CNT_MASK;
if (Cc != NewCc)
Report(Pid, "continuity");
}
}
}
counter[Pid] = Cc;
Data += TS_SIZE;
Length -= TS_SIZE;
}
}
// --- cFrameChecker ---------------------------------------------------------
#define MAX_BACK_REFS 32
class cFrameChecker {
private:
cTsChecker tsChecker;
int frameDelta;
int64_t lastPts;
uint32_t backRefs;
int lastFwdRef;
int errors;
int previousErrors;
int missingFrames;
void Report(const char *Message, int NumErrors = 1);
public:
cFrameChecker(void);
void SetMissing(void) { missingFrames++; }
void SetFrameDelta(int FrameDelta) { frameDelta = FrameDelta; }
void CheckTs(const uchar *Data, int Length);
void CheckFrame(const uchar *Data, int Length, bool IndependentFrame);
int PreviousErrors(void) { return previousErrors; }
int MissingFrames(void) { return missingFrames; }
};
cFrameChecker::cFrameChecker(void)
{
frameDelta = PTSTICKS / DEFAULTFRAMESPERSECOND;
lastPts = -1;
backRefs = 0;
lastFwdRef = 0;
errors = 0;
previousErrors = 0;
missingFrames = 0;
}
void cFrameChecker::Report(const char *Message, int NumErrors)
{
errors += NumErrors;
if (DebugChecks)
fprintf(stderr, "%s: frame error #%d (%s)\n", *TimeToString(time(NULL)), errors, Message);
}
void cFrameChecker::CheckTs(const uchar *Data, int Length)
{
tsChecker.CheckTs(Data, Length);
}
void cFrameChecker::CheckFrame(const uchar *Data, int Length, bool IndependentFrame)
{
previousErrors = tsChecker.Errors();
missingFrames = errors;
errors = 0;
tsChecker.Clear();
int64_t Pts = TsGetPts(Data, Length);
if (Pts >= 0) {
if (lastPts >= 0) {
int Diff = int(round((PtsDiff(lastPts, Pts) / double(frameDelta))));
if (Diff > 0) {
if (Diff <= MAX_BACK_REFS) {
if (lastFwdRef > 1) {
if (backRefs != uint32_t((1 << (lastFwdRef - 1)) - 1))
Report("missing backref");
}
}
else
Report("missed", Diff);
backRefs = 0;
lastFwdRef = Diff;
lastPts = Pts;
}
else if (Diff < 0) {
Diff = -Diff;
if (Diff <= MAX_BACK_REFS) {
int b = 1 << (Diff - 1);
if ((backRefs & b) != 0)
Report("duplicate backref");
backRefs |= b;
}
else {
Report("rev diff too big");
lastPts = Pts;
}
}
else
Report("zero diff");
}
else if (IndependentFrame)
lastPts = Pts;
}
else
Report("no PTS");
}
// --- cFrameDetector --------------------------------------------------------
const char *ScanTypeChars = "-pi"; // index is eScanType
const char *AspectRatioTexts[] = { // index is eAspectRatio
"-",
"1:1",
"4:3",
"16:9",
"2.21:1",
NULL
};
cFrameDetector::cFrameDetector(int Pid, int Type)
{
parser = NULL;
@ -1924,8 +2111,19 @@ cFrameDetector::cFrameDetector(int Pid, int Type)
numPtsValues = 0;
numIFrames = 0;
framesPerSecond = 0;
frameWidth = 0;
frameHeight = 0;
scanType = stUnknown;
aspectRatio = arUnknown;
framesInPayloadUnit = framesPerPayloadUnit = 0;
scanning = false;
firstIframeSeen = false;
frameChecker = new cFrameChecker;
}
cFrameDetector::~cFrameDetector()
{
delete frameChecker;
}
static int CmpUint32(const void *p1, const void *p2)
@ -1954,7 +2152,23 @@ void cFrameDetector::SetPid(int Pid, int Type)
esyslog("ERROR: unknown stream type %d (PID %d) in frame detector", type, pid);
}
int cFrameDetector::Analyze(const uchar *Data, int Length)
void cFrameDetector::SetMissing(void)
{
frameChecker->SetMissing();
}
bool cFrameDetector::NewFrame(int *PreviousErrors, int *MissingFrames)
{
if (newFrame) {
if (PreviousErrors)
*PreviousErrors = frameChecker->PreviousErrors();
if (MissingFrames)
*MissingFrames = frameChecker->MissingFrames();
}
return newFrame;
}
int cFrameDetector::Analyze(const uchar *Data, int Length, bool ErrorCheck)
{
if (!parser)
return 0;
@ -1984,13 +2198,21 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
if (parser->NewFrame()) {
newFrame = true;
independentFrame = parser->IndependentFrame();
firstIframeSeen |= independentFrame;
if (synced) {
if (ErrorCheck)
frameChecker->CheckFrame(Data, n, independentFrame);
if (framesPerPayloadUnit <= 1)
scanning = false;
}
else {
if (parser->FramesPerSecond() > 0.0) {
framesPerSecond = parser->FramesPerSecond();
frameWidth = parser->FrameWidth();
frameHeight = parser->FrameHeight();
scanType = parser->ScanType();
aspectRatio = parser->AspectRatio();
frameChecker->SetFrameDelta(PTSTICKS / framesPerSecond);
synced = true;
parser->SetDebug(false);
}
@ -2007,7 +2229,7 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
if (framesPerSecond <= 0.0) {
// frame rate unknown, so collect a sequence of PTS values:
if (numPtsValues < 2 || numPtsValues < MaxPtsValues && numIFrames < 2) { // collect a sequence containing at least two I-frames
if (newFrame) { // only take PTS values at the beginning of a frame (in case if fields!)
if (newFrame) { // only take PTS values at the beginning of a frame (in case of fields!)
const uchar *Pes = Data + TsPayloadOffset(Data);
if (numIFrames && PesHasPts(Pes)) {
ptsValues[numPtsValues] = PesGetPts(Pes);
@ -2049,11 +2271,12 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
framesPerSecond = 60.0 / 1.001;
else {
framesPerSecond = DEFAULTFRAMESPERSECOND;
dsyslog("unknown frame delta (%d), assuming %5.2f fps", Delta, DEFAULTFRAMESPERSECOND);
dsyslog("unknown frame delta (%d), assuming %5.2f fps", Delta, framesPerSecond);
}
}
else // audio
framesPerSecond = double(PTSTICKS) / Delta; // PTS of audio frames is always increasing
frameChecker->SetFrameDelta(PTSTICKS / framesPerSecond);
dbgframes("\nDelta = %d FPS = %5.2f FPPU = %d NF = %d TRO = %d\n", Delta, framesPerSecond, framesPerPayloadUnit, numPtsValues + 1, parser->IFrameTemporalReferenceOffset());
synced = true;
parser->SetDebug(false);
@ -2064,6 +2287,8 @@ int cFrameDetector::Analyze(const uchar *Data, int Length)
else if (Pid == PATPID && synced && Processed)
return Processed; // allow the caller to see any PAT packets
}
if (firstIframeSeen && ErrorCheck)
frameChecker->CheckTs(Data, Handled);
Data += Handled;
Length -= Handled;
Processed += Handled;

53
remux.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: remux.h 5.2 2021/12/25 14:11:39 kls Exp $
* $Id: remux.h 5.8 2024/12/04 14:33:22 kls Exp $
*/
#ifndef __REMUX_H
@ -504,6 +504,27 @@ void PesDump(const char *Name, const u_char *Data, int Length);
class cFrameParser;
enum eScanType {
stUnknown = 0,
stProgressive = 1,
stInterlaced = 2,
stMax
};
enum eAspectRatio {
arUnknown = 0,
ar_1_1 = 1,
ar_4_3 = 2,
ar_16_9 = 3,
ar_2_21_1 = 4,
arMax
};
extern const char *ScanTypeChars;
extern const char *AspectRatioTexts[];
class cFrameChecker;
class cFrameDetector {
private:
enum { MaxPtsValues = 150 };
@ -517,29 +538,45 @@ private:
int numIFrames;
bool isVideo;
double framesPerSecond;
uint16_t frameWidth;
uint16_t frameHeight;
eScanType scanType;
eAspectRatio aspectRatio;
int framesInPayloadUnit;
int framesPerPayloadUnit; // Some broadcasters send one frame per payload unit (== 1),
// while others put an entire GOP into one payload unit (> 1).
bool scanning;
bool firstIframeSeen;
cFrameParser *parser;
public:
cFrameChecker *frameChecker;
cFrameDetector(int Pid = 0, int Type = 0);
///< Sets up a frame detector for the given Pid and stream Type.
///< If no Pid and Type is given, they need to be set by a separate
///< call to SetPid().
~cFrameDetector();
void SetPid(int Pid, int Type);
///< Sets the Pid and stream Type to detect frames for.
int Analyze(const uchar *Data, int Length);
void SetMissing(void);
///< Call if this is a resumed recording, which has missing frames.
int Analyze(const uchar *Data, int Length, bool ErrorCheck = true);
///< Analyzes the TS packets pointed to by Data. Length is the number of
///< bytes Data points to, and must be a multiple of TS_SIZE.
///< Returns the number of bytes that have been analyzed.
///< If the return value is 0, the data was not sufficient for analyzing and
///< Analyze() needs to be called again with more actual data.
///< Error checks can be turned off by setting ErrorCheck to false. This is
///< used when regenerating the index file, where the first chunk of data
///< is scanned for the PAT/PMT and then a rewind is done on the file.
bool Synced(void) { return synced; }
///< Returns true if the frame detector has synced on the data stream.
bool NewFrame(void) { return newFrame; }
bool NewFrame(int *PreviousErrors = NULL, int *MissingFrames = NULL);
///< Returns true if the data given to the last call to Analyze() started a
///< new frame.
///< new frame. If PreviousErrors is given, it will be set to the number of errors in
///< the previous frame. If MissingFrames is given, it will be set to the number of
///< missing frames between the previous frame and this one.
///< The results returned in PreviousErrors and MissingFrames are only valid if the
///< function returns true.
bool IndependentFrame(void) { return independentFrame; }
///< Returns true if a new frame was detected and this is an independent frame
///< (i.e. one that can be displayed by itself, without using data from any
@ -547,6 +584,14 @@ public:
double FramesPerSecond(void) { return framesPerSecond; }
///< Returns the number of frames per second, or 0 if this information is not
///< available.
uint16_t FrameWidth(void) { return frameWidth; }
///< Returns the frame width, or 0 if this information is not available.
uint16_t FrameHeight(void) { return frameHeight; }
///< Returns the frame height, or 0 if this information is not available.
eScanType ScanType(void) { return scanType; }
///< Returns the scan type, or stUnknown if this information is not available.
eAspectRatio AspectRatio(void) { return aspectRatio; }
///< Returns the aspect ratio, or arUnknown if this information is not available.
};
#endif // __REMUX_H

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: skinclassic.c 5.1 2021/07/01 15:40:46 kls Exp $
* $Id: skinclassic.c 5.4 2024/09/21 10:53:07 kls Exp $
*/
#include "skinclassic.h"
@ -71,6 +71,7 @@ THEME_CLR(Theme, clrReplayProgressRest, clrWhite);
THEME_CLR(Theme, clrReplayProgressSelected, clrRed);
THEME_CLR(Theme, clrReplayProgressMark, clrBlack);
THEME_CLR(Theme, clrReplayProgressCurrent, clrRed);
THEME_CLR(Theme, clrReplayProgressError, clrBlack);
// --- cSkinClassicDisplayChannel --------------------------------------------
@ -402,7 +403,8 @@ void cSkinClassicDisplayMenu::SetRecording(const cRecording *Recording)
xt -= w + 5;
}
if (Info->Errors() > 0) {
cString buffer = cString::sprintf(" %d %s ", Info->Errors(), tr("errors"));
// TRANSLATORS: note the plural/singular!
cString buffer = cString::sprintf(" %d %s ", Info->Errors(), Info->Errors() > 1 ? tr("errors") : tr("error"));
const cFont *font = cFont::GetFont(fontSml);
int w = font->Width(buffer);
osd->DrawText(xt - w, y, buffer, Theme.Color(clrMenuEventVpsFg), Theme.Color(clrMenuEventVpsBg), font, w);
@ -421,7 +423,13 @@ void cSkinClassicDisplayMenu::SetRecording(const cRecording *Recording)
}
y += font->Height();
if (!isempty(Info->Description())) {
textScroller.Set(osd, x1, y, x2 - x1, y3 - y, Info->Description(), font, Theme.Color(clrMenuEventDescription), Theme.Color(clrBackground));
cString d = Info->Description();
cString f = Info->FrameParams();
if (*f) {
d.Append("\n\n");
d.Append(f);
}
textScroller.Set(osd, x1, y, x2 - x1, y3 - y, d, font, Theme.Color(clrMenuEventDescription), Theme.Color(clrBackground));
SetTextScrollbar();
}
}
@ -528,7 +536,7 @@ void cSkinClassicDisplayReplay::SetMode(bool Play, bool Forward, int Speed)
void cSkinClassicDisplayReplay::SetProgress(int Current, int Total)
{
cProgressBar pb(x1 - x0, y2 - y1, Current, Total, marks, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent));
cProgressBar pb(x1 - x0, y2 - y1, Current, Total, marks, errors, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent), Theme.Color(clrReplayProgressError));
osd->DrawBitmap(x0, y1, pb);
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: skinlcars.c 5.3 2021/07/01 15:40:46 kls Exp $
* $Id: skinlcars.c 5.8 2024/12/02 12:40:56 kls Exp $
*/
// "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
@ -188,6 +188,7 @@ THEME_CLR(Theme, clrReplayProgressRest, RgbShade(CLR_WHITE, -0.2));
THEME_CLR(Theme, clrReplayProgressSelected, CLR_EXPOSED);
THEME_CLR(Theme, clrReplayProgressMark, CLR_BLACK);
THEME_CLR(Theme, clrReplayProgressCurrent, CLR_EXPOSED);
THEME_CLR(Theme, clrReplayProgressError, CLR_BLACK);
// Track display:
@ -1258,6 +1259,7 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
int NumDevices = 0;
int y = ys04;
// Timers and recording devices:
LOCK_SCHEDULES_READ;
while (1) {
int NumTimers = 0;
const cDevice *Device = NULL;
@ -1627,7 +1629,7 @@ void cSkinLCARSDisplayMenu::SetItem(const char *Text, int Index, bool Current, b
if (!Tab(i + 1))
break;
}
SetEditableWidth(xi02 - xi00 - TextSpacing - Tab(1));
SetEditableWidth(xi01 - xi00 - TextSpacing - Tab(1));
}
void cSkinLCARSDisplayMenu::SetScrollbar(int Total, int Offset)
@ -1702,7 +1704,8 @@ void cSkinLCARSDisplayMenu::SetRecording(const cRecording *Recording)
xt -= w + xi02 - xi01;
}
if (Info->Errors() > 0) {
cString buffer = cString::sprintf(" %d %s ", Info->Errors(), tr("errors"));
// TRANSLATORS: note the plural/singular!
cString buffer = cString::sprintf(" %d %s ", Info->Errors(), Info->Errors() > 1 ? tr("errors") : tr("error"));
const cFont *font = cFont::GetFont(fontSml);
int w = font->Width(buffer);
osd->DrawText(xt - w, y, buffer, Theme.Color(clrMenuFrameFg), frameColor, font, w);
@ -1729,7 +1732,13 @@ void cSkinLCARSDisplayMenu::SetRecording(const cRecording *Recording)
if (!isempty(Info->Description())) {
int yt = y;
int yb = yi01;
textScroller.Set(osd, xl, yt, xi01 - xl, yb - yt, Info->Description(), font, Theme.Color(clrEventDescription), Theme.Color(clrBackground));
cString d = Info->Description();
cString f = Info->FrameParams();
if (*f) {
d.Append("\n\n");
d.Append(f);
}
textScroller.Set(osd, xl, yt, xi01 - xl, yb - yt, d, font, Theme.Color(clrEventDescription), Theme.Color(clrBackground));
DrawTextScrollbar();
}
}
@ -1922,7 +1931,7 @@ void cSkinLCARSDisplayReplay::SetMode(bool Play, bool Forward, int Speed)
void cSkinLCARSDisplayReplay::SetProgress(int Current, int Total)
{
cProgressBar pb(xp13 - xp03, lineHeight, Current, Total, marks, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent));
cProgressBar pb(xp13 - xp03, lineHeight, Current, Total, marks, errors, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent), Theme.Color(clrReplayProgressError));
osd->DrawBitmap(xp03, yp02, pb);
}

48
skins.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: skins.c 4.2 2019/05/29 16:43:09 kls Exp $
* $Id: skins.c 5.5 2025/02/12 21:18:53 kls Exp $
*/
#include "skins.h"
@ -139,15 +139,20 @@ int cSkinDisplayMenu::GetTextAreaWidth(void) const
return 0;
}
const cFont *cSkinDisplayMenu::GetTextAreaFont(bool) const
const cFont *cSkinDisplayMenu::GetTextAreaFont(bool FixedFont) const
{
return NULL;
return cFont::GetFont(FixedFont ? fontFix : fontOsd);
}
// --- cSkinDisplayReplay::cProgressBar --------------------------------------
cSkinDisplayReplay::cProgressBar::cProgressBar(int Width, int Height, int Current, int Total, const cMarks *Marks, tColor ColorSeen, tColor ColorRest, tColor ColorSelected, tColor ColorMark, tColor ColorCurrent)
:cBitmap(Width, Height, 2)
:cSkinDisplayReplay::cProgressBar::cProgressBar(Width, Height, Current, Total, Marks, NULL, ColorSeen, ColorRest, ColorSelected, ColorMark, ColorCurrent, clrBlack)
{
}
cSkinDisplayReplay::cProgressBar::cProgressBar(int Width, int Height, int Current, int Total, const cMarks *Marks, const cErrors *Errors, tColor ColorSeen, tColor ColorRest, tColor ColorSelected, tColor ColorMark, tColor ColorCurrent, tColor ColorError)
:cBitmap(Width, Height, 4)
{
total = Total;
if (total > 0) {
@ -162,12 +167,22 @@ cSkinDisplayReplay::cProgressBar::cProgressBar(int Width, int Height, int Curren
const cMark *m2 = Marks->Next(m);
int p2 = Pos(m2 ? m2->Position() : total);
int h = Height / 3;
DrawRectangle(p1, h, p2, Height - h, ColorSelected);
DrawRectangle(p1, h, p2, Height - h - 1, ColorSelected);
}
Mark(p1, Start, m->Position() == Current, ColorMark, ColorCurrent);
Start = !Start;
}
}
if (Errors) {
int LastPos = -1;
for (int i = 0; i < Errors->Size(); i++) {
int p1 = Errors->At(i);
if (p1 != LastPos) {
Error(Pos(Errors->At(i)), ColorError);
LastPos = p1;
}
}
}
}
}
@ -181,11 +196,25 @@ void cSkinDisplayReplay::cProgressBar::Mark(int x, bool Start, bool Current, tCo
}
}
void cSkinDisplayReplay::cProgressBar::Error(int x, tColor ColorError)
{
const int d = (Height() / 9) & ~0x01; // must be even
const int h = Height() / 2;
const int e = Height() / 4;
DrawRectangle(x, e, x, Height() -e - 1, ColorError);
DrawRectangle(x - d, h, x + d, h, ColorError);
for (int i = 1; i <= d; i++) {
DrawRectangle(x - d + i, h - i, x + d - i, h - i, ColorError);
DrawRectangle(x - d + i, h + i, x + d - i, h + i, ColorError);
}
}
// --- cSkinDisplayReplay ----------------------------------------------------
cSkinDisplayReplay::cSkinDisplayReplay(void)
{
marks = NULL;
errors = NULL;
}
void cSkinDisplayReplay::SetRecording(const cRecording *Recording)
@ -198,6 +227,11 @@ void cSkinDisplayReplay::SetMarks(const cMarks *Marks)
marks = Marks;
}
void cSkinDisplayReplay::SetErrors(const cErrors *Errors)
{
errors = Errors;
}
// --- cSkin -----------------------------------------------------------------
cSkin::cSkin(const char *Name, cTheme *Theme)
@ -271,7 +305,7 @@ eKeys cSkins::Message(eMessageType Type, const char *s, int Seconds)
}
cSkinDisplay::Current()->SetMessage(Type, s);
cSkinDisplay::Current()->Flush();
cStatus::MsgOsdStatusMessage(s);
cStatus::MsgOsdStatusMessage(Type, s);
eKeys k = kNone;
if (Type != mtStatus) {
k = Interface->Wait(Seconds);
@ -282,7 +316,7 @@ eKeys cSkins::Message(eMessageType Type, const char *s, int Seconds)
}
else {
cSkinDisplay::Current()->SetMessage(Type, NULL);
cStatus::MsgOsdStatusMessage(NULL);
cStatus::MsgOsdStatusMessage(Type, NULL);
}
}
else if (!s && displayMessage) {

30
skins.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: skins.h 5.1 2021/05/21 13:03:42 kls Exp $
* $Id: skins.h 5.6 2024/09/19 09:49:02 kls Exp $
*/
#ifndef __SKINS_H
@ -244,15 +244,6 @@ public:
///< If the skin displays the Event item in its own way, it shall return true.
///< The default implementation does nothing and returns false, which results in
///< a call to SetItem() with a proper text.
#ifndef DEPRECATED_SKIN_SETITEMEVENT
#define DEPRECATED_SKIN_SETITEMEVENT 0
#endif
#if DEPRECATED_SKIN_SETITEMEVENT
virtual bool SetItemEvent(const cEvent *Event, int Index, bool Current, bool Selectable, const cChannel *Channel, bool WithDate, eTimerMatch TimerMatch) { return SetItemEvent(Event, Index, Current, Selectable, Channel, WithDate, TimerMatch, true); }
///< This function is here for comaptibility with older plugins and may be removed
///< in a future version. Use the above version of SetItemEvent() with the TimerActive
///< parameter instead.
#endif
virtual bool SetItemTimer(const cTimer *Timer, int Index, bool Current, bool Selectable) { return false; }
///< Sets the item at the given Index to Timer. See SetItem() for more information.
///< If a derived skin class implements this function, it can display a Timer item
@ -310,8 +301,7 @@ public:
virtual const cFont *GetTextAreaFont(bool FixedFont) const;
///< Returns a pointer to the font which is used to display text with SetText().
///< The parameter FixedFont has the same meaning as in SetText(). The default
///< implementation returns NULL. Therefore a caller of this method must be
///< prepared to receive NULL if the plugin doesn't implement this method.
///< implementation returns the font defined in the setup.
///< The returned pointer is valid a long as the instance of cSkinDisplayMenu
///< exists.
};
@ -321,23 +311,29 @@ class cSkinDisplayReplay : public cSkinDisplay {
///< a recording.
protected:
const cMarks *marks;
const cErrors *errors;
class cProgressBar : public cBitmap {
protected:
int total;
int Pos(int p) { return int(int64_t(p) * Width() / total); }
void Mark(int x, bool Start, bool Current, tColor ColorMark, tColor ColorCurrent);
void Error(int x, tColor ColorError);
public:
cProgressBar(int Width, int Height, int Current, int Total, const cMarks *Marks, tColor ColorSeen, tColor ColorRest, tColor ColorSelected, tColor ColorMark, tColor ColorCurrent);
cProgressBar(int Width, int Height, int Current, int Total, const cMarks *Marks, tColor ColorSeen, tColor ColorRest, tColor ColorSelected, tColor ColorMark, tColor ColorCurrent); // for backwards compatibility
cProgressBar(int Width, int Height, int Current, int Total, const cMarks *Marks, const cErrors *Errors, tColor ColorSeen, tColor ColorRest, tColor ColorSelected, tColor ColorMark, tColor ColorCurrent, tColor ColorError);
};
public:
cSkinDisplayReplay(void);
virtual void SetMarks(const cMarks *Marks);
///< Sets the editing marks to Marks, which shall be used to display the
///< progress bar through a cProgressBar object.
virtual void SetErrors(const cErrors *Errors);
///< Sets the errors found in the recording to Errors, which shall be used to display the
///< progress bar through a cProgressBar object.
virtual void SetRecording(const cRecording *Recording);
///< Sets the recording that is currently being played.
///< The default implementation calls SetTitle() with the title and short
///< text of the Recording. A derived class can use any information provided
///< The default implementation calls SetTitle() with the title
///< of the Recording. A derived class can use any information provided
///< by the given Recording and display it.
virtual void SetTitle(const char *Title) = 0;
///< Sets the title of the recording.
@ -351,13 +347,13 @@ public:
///< used to display a progress indicator.
virtual void SetCurrent(const char *Current) = 0;
///< Sets the current position within the recording, as a user readable
///< string if the form "h:mm:ss.ff". The ".ff" part, indicating the
///< string in the form "h:mm:ss.ff". The ".ff" part, indicating the
///< frame number, is optional and the actual implementation needs to
///< take care that it is erased from the display when a Current string
///< _with_ ".ff" is followed by one without it.
virtual void SetTotal(const char *Total) = 0;
///< Sets the total length of the recording, as a user readable
///< string if the form "h:mm:ss".
///< string in the form "h:mm:ss".
virtual void SetJump(const char *Jump) = 0;
///< Sets the prompt that allows the user to enter a jump point.
///< Jump is a string of the form "Jump: mm:ss". The actual implementation

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: skinsttng.c 5.1 2021/07/01 15:40:46 kls Exp $
* $Id: skinsttng.c 5.4 2024/09/21 10:53:07 kls Exp $
*/
// "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures
@ -122,6 +122,7 @@ THEME_CLR(Theme, clrReplayProgressRest, clrWhite);
THEME_CLR(Theme, clrReplayProgressSelected, clrRed);
THEME_CLR(Theme, clrReplayProgressMark, clrBlack);
THEME_CLR(Theme, clrReplayProgressCurrent, clrRed);
THEME_CLR(Theme, clrReplayProgressError, clrBlack);
// --- cSkinSTTNGDisplayChannel ----------------------------------------------
@ -718,7 +719,8 @@ void cSkinSTTNGDisplayMenu::SetRecording(const cRecording *Recording)
xt -= w + x5 - x4;
}
if (Info->Errors() > 0) {
cString buffer = cString::sprintf(" %d %s ", Info->Errors(), tr("errors"));
// TRANSLATORS: note the plural/singular!
cString buffer = cString::sprintf(" %d %s ", Info->Errors(), Info->Errors() > 1 ? tr("errors") : tr("error"));
const cFont *font = cFont::GetFont(fontSml);
int w = font->Width(buffer);
osd->DrawText(xt - w, y, buffer, Theme.Color(clrMenuEventVps), frameColor, font, w);
@ -745,7 +747,13 @@ void cSkinSTTNGDisplayMenu::SetRecording(const cRecording *Recording)
if (!isempty(Info->Description())) {
int yt = y;
int yb = y4 - Roundness;
textScroller.Set(osd, xl, yt, x4 - xl, yb - yt, Info->Description(), font, Theme.Color(clrMenuEventDescription), Theme.Color(clrBackground));
cString d = Info->Description();
cString f = Info->FrameParams();
if (*f) {
d.Append("\n\n");
d.Append(f);
}
textScroller.Set(osd, xl, yt, x4 - xl, yb - yt, d, font, Theme.Color(clrMenuEventDescription), Theme.Color(clrBackground));
yb = yt + textScroller.Height();
osd->DrawEllipse (x1, yt - Roundness, x2, yt, frameColor, -3);
osd->DrawRectangle(x1, yt, x2, yb, frameColor);
@ -898,7 +906,7 @@ void cSkinSTTNGDisplayReplay::SetMode(bool Play, bool Forward, int Speed)
void cSkinSTTNGDisplayReplay::SetProgress(int Current, int Total)
{
cProgressBar pb(x4 - x3, y4 - y3, Current, Total, marks, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent));
cProgressBar pb(x4 - x3, y4 - y3, Current, Total, marks, errors, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent), Theme.Color(clrReplayProgressError));
osd->DrawBitmap(x3, y3, pb);
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: sources.c 3.6 2014/03/09 12:05:42 kls Exp $
* $Id: sources.c 5.1 2024/10/09 10:36:16 kls Exp $
*/
#include "sources.h"
@ -46,10 +46,7 @@ bool cSource::Matches(int Code1, int Code2)
int cSource::Position(int Code)
{
int n = (Code & st_Pos);
if (n > 0x00007FFF)
n |= 0xFFFF0000;
return n;
return int16_t(Code & st_Pos);
}
cString cSource::ToString(int Code)

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: status.c 4.1 2018/01/29 13:36:53 kls Exp $
* $Id: status.c 5.3 2025/02/12 21:18:53 kls Exp $
*/
#include "status.h"
@ -95,10 +95,10 @@ void cStatus::MsgOsdTitle(const char *Title)
sm->OsdTitle(Title);
}
void cStatus::MsgOsdStatusMessage(const char *Message)
void cStatus::MsgOsdStatusMessage(eMessageType Type, const char *Message)
{
for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
sm->OsdStatusMessage(Message);
sm->OsdStatusMessage2(Type, Message);
}
void cStatus::MsgOsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue)
@ -107,16 +107,16 @@ void cStatus::MsgOsdHelpKeys(const char *Red, const char *Green, const char *Yel
sm->OsdHelpKeys(Red, Green, Yellow, Blue);
}
void cStatus::MsgOsdItem(const char *Text, int Index)
void cStatus::MsgOsdItem(const char *Text, int Index, bool Selectable)
{
for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
sm->OsdItem(Text, Index);
sm->OsdItem2(Text, Index, Selectable);
}
void cStatus::MsgOsdCurrentItem(const char *Text)
void cStatus::MsgOsdCurrentItem(const char *Text, int Index)
{
for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
sm->OsdCurrentItem(Text);
sm->OsdCurrentItem2(Text, Index);
}
void cStatus::MsgOsdTextItem(const char *Text, bool Scroll)

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: status.h 4.4 2018/01/29 13:42:17 kls Exp $
* $Id: status.h 5.3 2025/02/12 21:18:53 kls Exp $
*/
#ifndef __STATUS_H
@ -13,6 +13,7 @@
#include "config.h"
#include "device.h"
#include "player.h"
#include "skins.h"
#include "tools.h"
// Several member functions of the following classes are called with a pointer to
@ -81,14 +82,21 @@ protected:
virtual void OsdTitle(const char *Title) {}
// Title has been displayed in the title line of the menu.
virtual void OsdStatusMessage(const char *Message) {}
virtual void OsdStatusMessage2(eMessageType Type, const char *Message) { OsdStatusMessage(Message); }
// Message has been displayed in the status line of the menu.
// If Message is NULL, the status line has been cleared.
virtual void OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue) {}
// The help keys have been set to the given values (may be NULL).
virtual void OsdItem(const char *Text, int Index) {}
// The OSD displays the given single line Text as menu item at Index.
virtual void OsdItem2(const char *Text, int Index, bool Selectable) { OsdItem(Text, Index); }
// The OSD displays the given single line Text as menu item at Index.
// Selectable is true if this item can be selected.
virtual void OsdCurrentItem(const char *Text) {}
// The OSD displays the given single line Text as the current menu item.
virtual void OsdCurrentItem2(const char *Text, int Index) { OsdCurrentItem(Text); }
// The OSD displays the given single line Text as the current menu item.
// Index is the one that was given in OsdItem[2]() for this item.
virtual void OsdTextItem(const char *Text, bool Scroll) {}
// The OSD displays the given multi line text. If Text points to an
// actual string, that text shall be displayed and Scroll has no
@ -115,10 +123,12 @@ public:
static void MsgSetSubtitleTrack(int Index, const char * const *Tracks);
static void MsgOsdClear(void);
static void MsgOsdTitle(const char *Title);
static void MsgOsdStatusMessage(const char *Message);
[[deprecated("use MsgOsdStatusMessage(eMessageType Type, const char *Message) instead")]]
static void MsgOsdStatusMessage(const char *Message) { MsgOsdStatusMessage(mtStatus, Message); }
static void MsgOsdStatusMessage(eMessageType Type, const char *Message);
static void MsgOsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue);
static void MsgOsdItem(const char *Text, int Index);
static void MsgOsdCurrentItem(const char *Text);
static void MsgOsdItem(const char *Text, int Index, bool Selectable = true);
static void MsgOsdCurrentItem(const char *Text, int Index = -1);
static void MsgOsdTextItem(const char *Text, bool Scroll = false);
static void MsgOsdChannel(const char *Text);
static void MsgOsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle);

57
svdrp.c
View File

@ -10,7 +10,7 @@
* and interact with the Video Disk Recorder - or write a full featured
* graphical interface that sits on top of an SVDRP connection.
*
* $Id: svdrp.c 5.7 2023/02/16 17:20:09 kls Exp $
* $Id: svdrp.c 5.10 2024/09/09 13:39:05 kls Exp $
*/
#include "svdrp.h"
@ -823,6 +823,14 @@ bool cPUTEhandler::Process(const char *s)
// adjust the help for CLRE accordingly if changing this!
const char *HelpPages[] = {
"AUDI [ <number> ]\n"
" Lists the currently available audio tracks in the format 'number language description'.\n"
" The number indicates the track type (1..32 = MP2, 33..48 = Dolby).\n"
" The currently selected track has its description prefixed with '*'.\n"
" If a number is given (which must be one of the track numbers listed)\n"
" audio is switched to that track.\n"
" Note that the list may not be fully available or current immediately after\n"
" switching the channel or starting a replay.",
"CHAN [ + | - | <number> | <name> | <id> ]\n"
" Switch channel up, down or to the given channel number, name or id.\n"
" Without option (or after successfully switching to the channel)\n"
@ -1071,6 +1079,7 @@ private:
bool Send(const char *s);
void Reply(int Code, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
void PrintHelpTopics(const char **hp);
void CmdAUDI(const char *Option);
void CmdCHAN(const char *Option);
void CmdCLRE(const char *Option);
void CmdCONN(const char *Option);
@ -1228,6 +1237,45 @@ void cSVDRPServer::PrintHelpTopics(const char **hp)
}
}
void cSVDRPServer::CmdAUDI(const char *Option)
{
if (*Option) {
if (isnumber(Option)) {
int o = strtol(Option, NULL, 10);
if (o >= ttAudioFirst && o <= ttDolbyLast) {
const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(o));
if (TrackId && TrackId->id) {
cDevice::PrimaryDevice()->SetCurrentAudioTrack(eTrackType(o));
Reply(250, "%d %s %s", eTrackType(o), *TrackId->language ? TrackId->language : "---", *TrackId->description ? TrackId->description : "-");
}
else
Reply(501, "Audio track \"%s\" not available", Option);
}
else
Reply(501, "Invalid audio track \"%s\"", Option);
}
else
Reply(501, "Error in audio track \"%s\"", Option);
}
else {
SetTrackDescriptions(!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring() ? cDevice::CurrentChannel() : 0);
eTrackType CurrentAudioTrack = cDevice::PrimaryDevice()->GetCurrentAudioTrack();
cString s;
for (int i = ttAudioFirst; i <= ttDolbyLast; i++) {
const tTrackId *TrackId = cDevice::PrimaryDevice()->GetTrack(eTrackType(i));
if (TrackId && TrackId->id) {
if (*s)
Reply(-250, "%s", *s);
s = cString::sprintf("%d %s %s%s", eTrackType(i), *TrackId->language ? TrackId->language : "---", i == CurrentAudioTrack ? "*" : "", *TrackId->description ? TrackId->description : "-");
}
}
if (*s)
Reply(250, "%s", *s);
else
Reply(550, "No audio tracks available");
}
}
void cSVDRPServer::CmdCHAN(const char *Option)
{
LOCK_CHANNELS_READ;
@ -1505,7 +1553,9 @@ void cSVDRPServer::CmdEDIT(const char *Option)
if (const cRecording *Recording = Recordings->GetById(strtol(Option, NULL, 10))) {
cMarks Marks;
if (Marks.Load(Recording->FileName(), Recording->FramesPerSecond(), Recording->IsPesRecording()) && Marks.Count()) {
if (RecordingsHandler.Add(ruCut, Recording->FileName()))
if (!EnoughFreeDiskSpaceForEdit(Recording->FileName()))
Reply(550, "Not enough free disk space to start editing process");
else if (RecordingsHandler.Add(ruCut, Recording->FileName()))
Reply(250, "Editing recording \"%s\" [%s]", Option, Recording->Title());
else
Reply(554, "Can't start editing process");
@ -2569,7 +2619,8 @@ void cSVDRPServer::Execute(char *Cmd)
if (*s)
*s++ = 0;
s = skipspace(s);
if (CMD("CHAN")) CmdCHAN(s);
if (CMD("AUDI")) CmdAUDI(s);
else if (CMD("CHAN")) CmdCHAN(s);
else if (CMD("CLRE")) CmdCLRE(s);
else if (CMD("CONN")) CmdCONN(s);
else if (CMD("DELC")) CmdDELC(s);

View File

@ -1,4 +1,4 @@
.TH "svdrpsend" "1" "27 Dec 2021" "2.6" "Video Disk Recorder"
.TH "svdrpsend" "1" "27 Dec 2021" "2.7" "Video Disk Recorder"
.SH NAME
.LP
svdrpsend \- sends commands to VDR

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: thread.c 5.1 2022/11/13 15:25:52 kls Exp $
* $Id: thread.c 5.3 2025/01/15 08:43:12 kls Exp $
*/
#include "thread.h"
@ -597,6 +597,7 @@ cStateLockLog::cStateLockLog(void)
void cStateLockLog::Dump(const char *Name, tThreadId ThreadId)
{
dsyslog("--- begin invalid lock sequence report");
dsyslog("TID T C R DR S ST");
int LastFlags = 0;
for (int i = 0; i < SLL_SIZE; i++) {
if (tThreadId tid = logThreadIds[logIndex]) {
@ -949,11 +950,11 @@ bool cPipe::Open(const char *Command, const char *Mode)
int fd[2];
if (pipe(fd) < 0) {
LOG_ERROR;
LOG_ERROR_STR(Command);
return false;
}
if ((pid = fork()) < 0) { // fork failed
LOG_ERROR;
LOG_ERROR_STR(Command);
close(fd[0]);
close(fd[1]);
return false;
@ -969,7 +970,7 @@ bool cPipe::Open(const char *Command, const char *Mode)
}
close(fd[iopipe]);
if ((f = fdopen(fd[1 - iopipe], mode)) == NULL) {
LOG_ERROR;
LOG_ERROR_STR(Command);
close(fd[1 - iopipe]);
}
return f != NULL;
@ -982,7 +983,6 @@ bool cPipe::Open(const char *Command, const char *Mode)
}
close(fd[iopipe]);
if (dup2(fd[1 - iopipe], iofd) == -1) { // now redirect
LOG_ERROR;
close(fd[1 - iopipe]);
_exit(-1);
}
@ -991,7 +991,6 @@ bool cPipe::Open(const char *Command, const char *Mode)
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
close(i); //close all dup'ed filedescriptors
if (execl("/bin/sh", "sh", "-c", Command, NULL) == -1) {
LOG_ERROR_STR(Command);
close(fd[1 - iopipe]);
_exit(-1);
}
@ -1044,14 +1043,14 @@ int SystemExec(const char *Command, bool Detached)
pid_t pid;
if ((pid = fork()) < 0) { // fork failed
LOG_ERROR;
LOG_ERROR_STR(Command);
return -1;
}
if (pid > 0) { // parent process
int status = 0;
if (waitpid(pid, &status, 0) < 0) {
LOG_ERROR;
LOG_ERROR_STR(Command);
return -1;
}
return status;
@ -1064,19 +1063,17 @@ int SystemExec(const char *Command, bool Detached)
// Start a new session
pid_t sid = setsid();
if (sid < 0)
LOG_ERROR;
_exit(-1);
// close STDIN and re-open as /dev/null
int devnull = open("/dev/null", O_RDONLY);
if (devnull < 0 || dup2(devnull, 0) < 0)
LOG_ERROR;
_exit(-1);
}
int MaxPossibleFileDescriptors = getdtablesize();
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
close(i); //close all dup'ed filedescriptors
if (execl("/bin/sh", "sh", "-c", Command, NULL) == -1) {
LOG_ERROR_STR(Command);
if (execl("/bin/sh", "sh", "-c", Command, NULL) == -1)
_exit(-1);
}
_exit(0);
}
}

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: timers.c 5.18 2022/11/20 10:57:31 kls Exp $
* $Id: timers.c 5.25 2025/01/13 14:44:18 kls Exp $
*/
#include "timers.h"
@ -21,6 +21,8 @@
// format characters in order to allow any number of blanks after a numeric
// value!
#define VPSGRACE 15 // seconds we still record after the running status of a VPS event has changed to "not running"
// --- cTimer ----------------------------------------------------------------
cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
@ -47,6 +49,8 @@ cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
weekdays = 0;
start = now->tm_hour * 100 + now->tm_min;
stop = 0;
vpsNotRunning = 0;
vpsActive = false;
if (!Setup.InstantRecordTime && channel && (Instant || Pause)) {
LOCK_SCHEDULES_READ;
if (const cSchedule *Schedule = Schedules->GetSchedule(channel)) {
@ -181,11 +185,15 @@ cTimer::cTimer(const cEvent *Event, const char *FileName, const cTimer *PatternT
deferred = 0;
pending = inVpsMargin = false;
flags = tfActive;
if (PatternTimer)
SetFlags(tfSpawned);
*pattern = 0;
*file = 0;
aux = NULL;
remote = NULL;
event = NULL;
vpsNotRunning = 0;
vpsActive = false;
if (!PatternTimer || PatternTimer->HasFlags(tfVps)) {
if (Event->Vps() && (PatternTimer || Setup.UseVps))
SetFlags(tfVps);
@ -255,6 +263,8 @@ cTimer& cTimer::operator= (const cTimer &Timer)
stop = Timer.stop;
priority = Timer.priority;
lifetime = Timer.lifetime;
vpsNotRunning = 0;
vpsActive = false;
strncpy(pattern, Timer.pattern, sizeof(pattern));
strncpy(file, Timer.file, sizeof(file));
free(aux);
@ -276,10 +286,12 @@ void cTimer::CalcMargins(int &MarginStart, int &MarginStop, const cEvent *Event)
MarginStop = Setup.MarginStop * 60;
// To make sure the timer gets assigned to the correct event, we must
// make sure that this is the only event that overlaps 100%:
if (const cEvent *e = dynamic_cast<const cEvent *>(Event->Prev()))
MarginStart = max(0, min(MarginStart, e->Duration() - 60));
if (const cEvent *e = dynamic_cast<const cEvent *>(Event->Next()))
MarginStop = max(0, min(MarginStop, e->Duration() - 60));
if (HasFlags(tfSpawned)) {
if (const cEvent *e = dynamic_cast<const cEvent *>(Event->Prev()))
MarginStart = max(0, min(MarginStart, e->Duration() - 60));
if (const cEvent *e = dynamic_cast<const cEvent *>(Event->Next()))
MarginStop = max(0, min(MarginStop, e->Duration() - 60));
}
}
int cTimer::Compare(const cListObject &ListObject) const
@ -556,6 +568,7 @@ void cTimer::SetFile(const char *File)
}
#define EITPRESENTFOLLOWINGRATE 10 // max. seconds between two occurrences of the "EIT present/following table for the actual multiplex" (2s by the standard, using some more for safety)
#define EITPRESENTFOLLOWINGGRACE 60 // max. seconds before reporting a loss of VPS control of an ongoing recording
bool cTimer::Matches(time_t t, bool Directly, int Margin) const
{
@ -603,7 +616,7 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
return false;
deferred = 0;
if (HasFlags(tfActive)) {
if (HasFlags(tfActive) && !Remote()) {
if (event) {
if (HasFlags(tfVps)) {
if (event->Vps()) {
@ -611,9 +624,33 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
startTime = event->StartTime();
stopTime = event->EndTime();
if (!Margin) { // this is an actual check
if (event->Schedule()->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) // VPS control can only work with up-to-date events...
return event->IsRunning(true);
// ...otherwise we fall back to normal timer handling below (note: Margin == 0!)
const cSchedule *Schedule = event->Schedule();
if (Schedule && Schedule->PresentSeenWithin(EITPRESENTFOLLOWINGRATE)) { // VPS control can only work with up-to-date events...
if (!vpsActive) {
vpsActive = true;
if (Recording())
dsyslog("timer %s regained VPS control", *ToDescr());
}
bool running = event->IsRunning(true);
if (!running) {
if (Recording() && vpsNotRunning == 0)
vpsNotRunning = time(NULL);
}
else
vpsNotRunning = 0;
return running || time(NULL) < vpsNotRunning + VPSGRACE;
}
if (Recording()) {
if (Schedule && Schedule->PresentSeenWithin(EITPRESENTFOLLOWINGGRACE))
return event->IsRunning(true); // give it a chance to recover - worst case: the recording will be 60 seconds too long
if (vpsActive) {
vpsActive = false;
esyslog("ERROR: timer %s lost VPS control", *ToDescr());
}
// ...otherwise we fall back to normal timer handling below (note: Margin == 0!)
}
else
return false; // relying on vdr.c to ensure that a transponder is tuned to this channel
}
}
}
@ -694,10 +731,39 @@ eTimerMatch cTimer::Matches(const cEvent *Event, int *Overlap) const
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());
// no break here - let's play it safe and look at *all* events
}
}
}
}
else {
dsyslog("timer %s has no event, setting expiration to +24h", *ToDescr());
ExpireTime += 3600 * 24;
}
}
}
return ExpireTime <= Now;
}
return false;
}
@ -751,7 +817,6 @@ cTimer *cTimer::SpawnPatternTimer(const cEvent *Event, cTimers *Timers)
cString FileName = MakePatternFileName(Pattern(), Event->Title(), Event->ShortText(), File());
isyslog("spawning timer %s for event %s", *ToDescr(), *Event->ToDescr());
cTimer *t = new cTimer(Event, FileName, this);
t->SetFlags(tfSpawned);
if (startswith(Pattern(), TIMERPATTERN_AVOID))
t->SetFlags(tfAvoid);
Timers->Add(t);
@ -1096,6 +1161,7 @@ const cTimer *cTimers::GetMatch(time_t t) const
{
static int LastPending = -1;
const cTimer *t0 = NULL;
LOCK_SCHEDULES_READ;
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Remote() && !ti->Recording() && ti->Matches(t)) {
if (ti->Pending()) {

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: timers.h 5.9 2022/11/20 10:57:31 kls Exp $
* $Id: timers.h 5.11 2024/03/06 14:37:15 kls Exp $
*/
#ifndef __TIMERS_H
@ -37,6 +37,8 @@ private:
int scheduleStateSpawn;
int scheduleStateAdjust;
mutable time_t deferred; ///< Matches(time_t, ...) will return false if the current time is before this value
mutable time_t vpsNotRunning; ///< the time when a VPS event's running status changed to "not running"
mutable bool vpsActive; ///< true if this is a VPS timer and the event is current
bool pending, inVpsMargin;
uint flags;
const cChannel *channel;

167
tools.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: tools.c 5.7 2022/11/22 14:33:48 kls Exp $
* $Id: tools.c 5.15 2025/01/15 08:57:45 kls Exp $
*/
#include "tools.h"
@ -131,8 +131,11 @@ char *strcpyrealloc(char *dest, const char *src)
char *strn0cpy(char *dest, const char *src, size_t n)
{
char *s = dest;
for ( ; --n && (*dest = *src) != 0; dest++, src++) ;
*dest = 0;
if (dest && n) {
if (src)
for ( ; --n && (*dest = *src) != 0; dest++, src++) ;
*dest = 0;
}
return s;
}
@ -151,26 +154,31 @@ char *strreplace(char *s, char c1, char c2)
char *strreplace(char *s, const char *s1, const char *s2)
{
if (!s || !s1 || !s2)
if (!s || !s1 || !s2 || strcmp(s1, s2) == 0)
return s;
char *p = strstr(s, s1);
if (p) {
int of = p - s;
char *q = s;
if (char *p = strstr(s, s1)) {
int l = strlen(s);
int l1 = strlen(s1);
int l2 = strlen(s2);
if (l2 > l1) {
if (char *NewBuffer = (char *)realloc(s, l + l2 - l1 + 1))
s = NewBuffer;
else {
esyslog("ERROR: out of memory");
return s;
do {
int of = p - s;
if (l2 > l1) {
if (char *NewBuffer = (char *)realloc(s, l + l2 - l1 + 1))
s = NewBuffer;
else {
esyslog("ERROR: out of memory");
return s;
}
}
}
char *sof = s + of;
if (l2 != l1)
memmove(sof + l2, sof + l1, l - of - l1 + 1);
memcpy(sof, s2, l2);
char *sof = s + of;
if (l2 != l1) {
memmove(sof + l2, sof + l1, l - of - l1 + 1);
l += l2 - l1;
}
memcpy(sof, s2, l2);
q = sof + l2;
} while (p = strstr(q, s1));
}
return s;
}
@ -714,9 +722,17 @@ bool SpinUpDisk(const char *FileName)
return false;
}
void TouchFile(const char *FileName)
void TouchFile(const char *FileName, bool Create)
{
if (utime(FileName, NULL) == -1 && errno != ENOENT)
if (Create && access(FileName, F_OK) != 0) { // the file does not exist
isyslog("creating file '%s'", FileName);
int f = open(FileName, O_WRONLY | O_CREAT, DEFFILEMODE);
if (f >= 0)
close(f);
else
LOG_ERROR_STR(FileName);
}
if (utime(FileName, NULL) == -1 && errno != ENOENT)
LOG_ERROR_STR(FileName);
}
@ -901,17 +917,21 @@ char *Utf8Strn0Cpy(char *Dest, const char *Src, int n)
if (cCharSetConv::SystemCharacterTable())
return strn0cpy(Dest, Src, n);
char *d = Dest;
while (*Src) {
int sl = Utf8CharLen(Src);
n -= sl;
if (n > 0) {
while (sl--)
*d++ = *Src++;
}
else
break;
if (Dest && n > 0) {
if (Src) {
while (*Src) {
int sl = Utf8CharLen(Src);
n -= sl;
if (n > 0) {
while (sl--)
*d++ = *Src++;
}
else
break;
}
}
*d = 0;
*d = 0;
}
return Dest;
}
@ -1106,6 +1126,16 @@ cString &cString::operator=(const cString &String)
return *this;
}
cString &cString::operator=(cString &&String)
{
if (this != &String) {
free(s);
s = String.s;
String.s = NULL;
}
return *this;
}
cString &cString::operator=(const char *String)
{
if (s == String)
@ -1130,6 +1160,22 @@ cString &cString::Append(const char *String)
return *this;
}
cString &cString::Append(char c)
{
if (c) {
int l1 = s ? strlen(s) : 0;
int l2 = 1;
if (char *p = (char *)realloc(s, l1 + l2 + 1)) {
s = p;
*(s + l1) = c;
*(s + l1 + 1) = 0;
}
else
esyslog("ERROR: out of memory");
}
return *this;
}
cString &cString::Truncate(int Index)
{
int l = strlen(s);
@ -1633,11 +1679,6 @@ bool cFileNameList::Load(const char *Directory, bool DirsOnly)
// --- cFile -----------------------------------------------------------------
#if DEPRECATED_CFILE
bool cFile::files[FD_SETSIZE] = { false };
int cFile::maxFiles = 0;
#endif
cFile::cFile(void)
{
f = -1;
@ -1659,24 +1700,8 @@ bool cFile::Open(const char *FileName, int Flags, mode_t Mode)
bool cFile::Open(int FileDes)
{
if (FileDes >= 0) {
if (!IsOpen()) {
if (!IsOpen())
f = FileDes;
#if DEPRECATED_CFILE
if (f >= 0) {
if (f < FD_SETSIZE) {
if (f >= maxFiles)
maxFiles = f + 1;
if (!files[f])
files[f] = true;
else
esyslog("ERROR: file descriptor %d already in files[]", f);
return true;
}
else
esyslog("ERROR: file descriptor %d is larger than FD_SETSIZE (%d)", f, FD_SETSIZE);
}
#endif
}
else
esyslog("ERROR: attempt to re-open file descriptor %d", FileDes);
}
@ -1687,9 +1712,6 @@ void cFile::Close(void)
{
if (f >= 0) {
close(f);
#if DEPRECATED_CFILE
files[f] = false;
#endif
f = -1;
}
}
@ -1699,26 +1721,6 @@ bool cFile::Ready(bool Wait)
return f >= 0 && FileReady(f, Wait ? 1000 : 0);
}
#if DEPRECATED_CFILE
bool cFile::AnyFileReady(int FileDes, int TimeoutMs)
{
fd_set set;
FD_ZERO(&set);
for (int i = 0; i < maxFiles; i++) {
if (files[i])
FD_SET(i, &set);
}
if (0 <= FileDes && FileDes < FD_SETSIZE && !files[FileDes])
FD_SET(FileDes, &set); // in case we come in with an arbitrary descriptor
if (TimeoutMs == 0)
TimeoutMs = 10; // load gets too heavy with 0
struct timeval timeout;
timeout.tv_sec = TimeoutMs / 1000;
timeout.tv_usec = (TimeoutMs % 1000) * 1000;
return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && (FileDes < 0 || FD_ISSET(FileDes, &set));
}
#endif
bool cFile::FileReady(int FileDes, int TimeoutMs)
{
fd_set set;
@ -1734,21 +1736,6 @@ bool cFile::FileReady(int FileDes, int TimeoutMs)
return select(FD_SETSIZE, &set, NULL, NULL, (TimeoutMs >= 0) ? &timeout : NULL) > 0 && FD_ISSET(FileDes, &set);
}
#if DEPRECATED_CFILE
bool cFile::FileReadyForWriting(int FileDes, int TimeoutMs)
{
fd_set set;
struct timeval timeout;
FD_ZERO(&set);
FD_SET(FileDes, &set);
if (TimeoutMs < 100)
TimeoutMs = 100;
timeout.tv_sec = 0;
timeout.tv_usec = TimeoutMs * 1000;
return select(FD_SETSIZE, NULL, &set, NULL, &timeout) > 0 && FD_ISSET(FileDes, &set);
}
#endif
// --- cSafeFile -------------------------------------------------------------
cSafeFile::cSafeFile(const char *FileName)

22
tools.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: tools.h 5.6 2021/05/26 13:37:53 kls Exp $
* $Id: tools.h 5.11 2025/01/13 13:18:42 kls Exp $
*/
#ifndef __TOOLS_H
@ -140,7 +140,7 @@ int Utf8FromArray(const uint *a, char *s, int Size, int Max = -1);
// When allocating buffer space, make sure we reserve enough space to hold
// a string in UTF-8 representation:
#define Utf8BufSize(s) ((s) * 4)
#define Utf8BufSize(s) ((s) * 4 + 1)
// The following macros automatically use the correct versions of the character
// class functions:
@ -182,13 +182,16 @@ public:
cString(const char *S = NULL, bool TakePointer = false);
cString(const char *S, const char *To); ///< Copies S up to To (exclusive). To must be a valid pointer into S. If To is NULL, everything is copied.
cString(const cString &String);
cString(cString &&String): s(String.s) { String.s = NULL; }
virtual ~cString();
operator const void * () const { return s; } // to catch cases where operator*() should be used
operator const char * () const { return s; } // for use in (const char *) context
const char * operator*() const { return s; } // for use in (const void *) context (printf() etc.)
cString &operator=(const cString &String);
cString &operator=(cString &&String);
cString &operator=(const char *String);
cString &Append(const char *String);
cString &Append(char c);
cString &Truncate(int Index); ///< Truncate the string at the given Index (if Index is < 0 it is counted from the end of the string).
cString &CompactChars(char c); ///< Compact any sequence of characters 'c' to a single character, and strip all of them from the beginning and end of this string.
static cString sprintf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
@ -316,7 +319,7 @@ bool RemoveEmptyDirectories(const char *DirName, bool RemoveThis = false, const
int DirSizeMB(const char *DirName); ///< returns the total size of the files in the given directory, or -1 in case of an error
char *ReadLink(const char *FileName); ///< returns a new string allocated on the heap, which the caller must delete (or NULL in case of an error)
bool SpinUpDisk(const char *FileName);
void TouchFile(const char *FileName);
void TouchFile(const char *FileName, bool Create = false);
time_t LastModifiedTime(const char *FileName);
off_t FileSize(const char *FileName); ///< returns the size of the given file, or -1 in case of an error (e.g. if the file doesn't exist)
cString WeekDayName(int WeekDay);
@ -457,15 +460,8 @@ public:
struct dirent *Next(void);
};
#ifndef DEPRECATED_CFILE
#define DEPRECATED_CFILE 0
#endif
class cFile {
private:
#if DEPRECATED_CFILE
static bool files[];
static int maxFiles;
#endif
int f;
public:
cFile(void);
@ -476,13 +472,7 @@ public:
void Close(void);
bool IsOpen(void) { return f >= 0; }
bool Ready(bool Wait = true);
#if DEPRECATED_CFILE
static bool AnyFileReady(int FileDes = -1, int TimeoutMs = 1000);
#endif
static bool FileReady(int FileDes, int TimeoutMs = 1000);
#if DEPRECATED_CFILE
static bool FileReadyForWriting(int FileDes, int TimeoutMs = 1000);
#endif
};
class cSafeFile {

Some files were not shown because too many files have changed in this diff Show More