Implemented "Pattern Timers"

This commit is contained in:
Klaus Schmidinger 2020-12-26 15:49:01 +01:00
parent d2e0087c4e
commit 2b3556b460
46 changed files with 1161 additions and 86 deletions

View File

@ -9562,3 +9562,9 @@ Video Disk Recorder Revision History
- Fixed a compiler warning (thanks to Winfried Köhler).
- Fixed convertCharacterTable() in case iconv_open() fails (thanks to Helmut Binder).
- Official release.
2020-12-26: Version 2.5.1
- Implemented "Pattern Timers" (see MANUAL, vdr.1 and vdr.5 for details).
- Events in the past are no longer marked as having a timer in the Schedules
menu.

125
MANUAL
View File

@ -480,6 +480,8 @@ Version 2.4
"forever", and a value of 0 means that this recording can be
deleted any time if a recording with a higher priority needs disk
space.
Pattern: The pattern to use for recordings matching events (only available
for pattern timers). See section "Pattern timers" below.
File: The name under which a recording created through this timer will
be stored on disk (the actual name will also contain the date and
time, so it is possible to have a "repeating timer" store all its
@ -511,6 +513,129 @@ Version 2.4
The "Red" key in the "Edit timer" menu opens a list of folders, which can be
used to define the file name in which the recording will be stored.
The "Yellow" key in the "Edit timer" menu toggles the timer between "Pattern"
and "Regular".
When editing the "File" field, the "Blue" key in can be used to insert useful
macros.
* Pattern timers
There are cases where it would make sense to have a more flexible kind of
recording timer. For instance, some channels that provide VPS don't always
use the exact same VPS time for a series, which is extremely annoying.
Or you might want to record all films that have a certain pattern in their
title, no matter when they are broadcast. In such cases, "pattern timers"
can help.
In the "Edit timer" menu press the Yellow button to turn a regular timer into
a pattern timer. Pressing this button again switches back to regular.
The following rules apply to pattern timers:
- Pattern timers can only work for channels that provide EPG data.
- When using pattern timers, there should always be at least one free device that
can be used to regularly receive the EPG of the pattern timer's channel.
- A pattern timer records every matching event on the given channel that overlaps
with the given start/stop time. Overlapping events are recorded in full,
even if they extend outside the given start/stop interval.
- In order to actually record an event, a pattern timer "spawns" a separate timer
that does the recording. At most two timers are spawned from a pattern timer at
any given time, one for the next upcoming matching event, and one for
the event immediately following that one, in case it also matches.
- Spawned timers are marked with the flag tfSpawned.
- Spawned timers take the Priority, Lifetime and VPS settings from the pattern timer.
- The special pattern "*" matches every event. So a timer with
a start/stop time of 00:00/23:59 will record every event of that day
into separate recordings. Note that when using this pattern there should
be no other timers for the same channel, because these might interfere.
- Once a timer has been spawned, it is treated like any other regular
timer. Any changes made to the corresponding pattern timer thereafter will have
no effect on spawned timers. Note that after deleting a spawned timer,
the corresponding pattern timer may respawn it.
- Recording is done according to the event's begin/end times, either
by adding the start/stop margins (for non-VPS timers) or by using the
event's running status (for VPS timers).
- The recording of a pattern timer is stored under the given file name, just like
regular timers do. In addition to the "TITLE" and "EPISODE" macros the file
name of a pattern timer can also use "{<}" and "{>}" to reference the part of the
event's title before and after the pattern, respectively. For instance,
if the event's title is "Abc def ghi" and the pattern is "def ", "{<}"
would contain "Abc " and "{>}" would contain "ghi" (note the matching of the
blanks). For completeness, "{=}" can be used to reference the matching
pattern itself.
- In the "Timers" menu pressing the Red button on a pattern timer only toggles the
timer between "on" and "off", even if this is a repeating timer.
- In the "Timers" menu pattern timers are sorted alphabetically to the end of the
list of timers.
- A regular timer that is currently recording can't be changed into a pattern timer.
- In the "Edit timer" menu the file name and pattern are displayed as
separate items. The Yellow button can be used to toggle between a regular
timer and a pattern timer. When going from regular to pattern, the Pattern item will
be initialized with the base part of the file name.
- The characters '^' and '$' can be used at the very beginning and end of
the pattern to anchor the pattern to the begin or end of the title.
Using both of these will match only titles that consist of exactly the given pattern,
with nothing before and nothing after it.
- The Pattern field in the "Edit timer" menu allows blanks at the end of the string,
which may help to separate the text after the matching pattern.
- If the first character of the pattern is '@', an event that matches the
rest of the pattern is only recorded if the resulting recording's file
name (without any folders) is not contained in the donerecs.data file.
This avoids duplicate recordings of the same programme. Timers spawned from
such a pattern timer are marked with the flag tfAvoid.
- When editing the "File" field of a timer, the Blue button can be pressed to
insert one of the macros "TITLE", "EPISODE", "{<}", "{=}" or "{>}",
respectively. Pressing the Blue button repeatedly loops through the available
macros. The "{...}" macros are only available for pattern timers.
- In the "Schedule" and "What's on...?" menus the events that will be recorded
by a pattern timer are marked in the same way as regular timers.
- The TIMERS column in the LCARS skin doesn't show the basic definitions of
pattern timers, it only shows timers actually spawned from pattern timers.
If the pattern is prepended with '@', the name of the resulting recording (everything
after the rightmost '~', or the entire file name, if there is no '~') will be stored
in the file donerecs.data, so that multiple recordings of the same programme can be
avoided. When using this feature, special care must be taken regarding the recording's
file name. For instance, with a combination of
pattern file name
@Columbo Movies~TITLE
if the event's title is just "Columbo", this pattern timer would only record once,
and ignore any future events with that title, even if the episode would be different.
So you may want to use the episode name, as in
pattern file name
@Columbo Movies~TITLE - EPISODE
to make the file name unique. If you have several pattern timers for the same show on
different channels, chances are that the broadcasters handle title and episode
differently, as for example in
TITLE EPISODE pattern file name
Columbo Blueprint for Murder @^Columbo$ TITLE - EPISODE
Columbo - Blueprint for Murder @^Columbo TITLE
Columbo: Blueprint for Murder @^Columbo:_ Columbo - {>}
(note the '_' in the pattern of the third example; this is just used to visualize
the blank at the end of the pattern)
In order to have the same episode result in the same recording file name on all
channels, the file name needs to be generated differently for each channel. First
you need to decide on a proper combination of title and episode name, preferably
one that is already used by one of your channels (let's say the second one).
In the first case, title and episode name are correctly put in their respective
places, and "TITLE - EPISODE" as file name will do. The second case is our common
version, where everything is in the title, so TITLE is just fine. The third case
poses a problem, because everything is in the title, but with a different separator.
Here the special macro "{>}" can be used in the file name, which contains everything
following the matching pattern. There are three macros that can be used here:
{<} everything before the matching pattern
{>} everything after the matching pattern
{=} the matching pattern itself (just for completeness)
* Managing folders
The "Select folder" menu, which can be accessed by pressing the "Red" key in

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 4.21 2020/12/22 17:23:51 kls Exp $
* $Id: config.h 5.1 2020/12/26 15:49:01 kls Exp $
*/
#ifndef __CONFIG_H
@ -22,13 +22,13 @@
// VDR's own version number:
#define VDRVERSION "2.4.6"
#define VDRVERSNUM 20406 // Version * 10000 + Major * 100 + Minor
#define VDRVERSION "2.5.1"
#define VDRVERSNUM 20501 // Version * 10000 + Major * 100 + Minor
// The plugin API's version number:
#define APIVERSION "2.4.6"
#define APIVERSNUM 20406 // Version * 10000 + Major * 100 + Minor
#define APIVERSION "2.5.1"
#define APIVERSNUM 20501 // Version * 10000 + Major * 100 + Minor
// When loading plugins, VDR searches them by their APIVERSION, which
// may be smaller than VDRVERSION in case there have been no changes to
@ -46,6 +46,13 @@
#define TIMERMACRO_TITLE "TITLE"
#define TIMERMACRO_EPISODE "EPISODE"
#define TIMERMACRO_BEFORE "{<}"
#define TIMERMACRO_MATCH "{=}"
#define TIMERMACRO_AFTER "{>}"
#define TIMERPATTERN_AVOID "@"
#define TIMERPATTERN_BEGIN "^"
#define TIMERPATTERN_END "$"
#define MINOSDWIDTH 480
#define MAXOSDWIDTH 1920

94
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 4.88 2020/12/12 22:01:01 kls Exp $
* $Id: menu.c 5.1 2020/12/26 15:49:01 kls Exp $
*/
#include "menu.h"
@ -993,6 +993,23 @@ eOSState cMenuFolder::ProcessKey(eKeys Key)
// --- cMenuEditTimer --------------------------------------------------------
static const char *TimerFileMacrosForPattern[] = {
TIMERMACRO_TITLE,
TIMERMACRO_EPISODE,
TIMERMACRO_BEFORE,
TIMERMACRO_MATCH,
TIMERMACRO_AFTER,
"",
NULL
};
static const char *TimerFileMacros[] = {
TIMERMACRO_TITLE,
TIMERMACRO_EPISODE,
"",
NULL
};
const cTimer *cMenuEditTimer::addedTimer = NULL;
cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New)
@ -1000,6 +1017,7 @@ cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New)
{
SetMenuCategory(mcTimerEdit);
addedTimer = NULL;
pattern = NULL;
file = NULL;
day = firstday = NULL;
timer = Timer;
@ -1019,6 +1037,7 @@ cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New)
Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME));
Add(file = new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file)));
SetFirstDayItem();
SetPatternItem(true);
if (data.remote)
strn0cpy(remote, data.remote, sizeof(remote));
else
@ -1047,7 +1066,7 @@ const cTimer *cMenuEditTimer::AddedTimer(void)
void cMenuEditTimer::SetHelpKeys(void)
{
SetHelp(tr("Button$Folder"), data.weekdays ? tr("Button$Single") : tr("Button$Repeating"));
SetHelp(tr("Button$Folder"), data.weekdays ? tr("Button$Single") : tr("Button$Repeating"), *data.pattern ? tr("Button$Regular") : tr("Button$Pattern"));
}
void cMenuEditTimer::SetFirstDayItem(void)
@ -1063,6 +1082,40 @@ void cMenuEditTimer::SetFirstDayItem(void)
}
}
void cMenuEditTimer::SetPatternItem(bool Initial)
{
if (Initial && !*data.pattern) {
file->SetMacros(TimerFileMacros);
return;
}
if (!pattern) {
if (data.HasFlags(tfRecording)) {
Skins.Message(mtWarning, tr("Timer is recording!"));
return;
}
if (!*data.pattern) {
char *p = strrchr(data.file, FOLDERDELIMCHAR);
if (p)
p++;
else
p = data.file;
strn0cpy(data.pattern, p, sizeof(data.pattern));
}
Ins(pattern = new cMenuEditStrItem( tr("Pattern"), data.pattern, sizeof(data.pattern)), true, file);
pattern->SetKeepSpace();
file->SetMacros(TimerFileMacrosForPattern);
Display();
}
else {
Del(pattern->Index());
pattern = NULL;
*data.pattern = 0;
file->SetMacros(TimerFileMacros);
Display();
}
SetHelpKeys();
}
eOSState cMenuEditTimer::SetFolder(void)
{
if (cMenuFolder *mf = dynamic_cast<cMenuFolder *>(SubMenu())) {
@ -1142,6 +1195,8 @@ eOSState cMenuEditTimer::ProcessKey(eKeys Key)
cRecordControls::Stop(timer);
if (timer->Remote() && data.Remote())
Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
if (data.Local() && !timer->IsPatternTimer() && data.IsPatternTimer())
data.SetEvent(NULL);
*timer = data;
}
LOCK_SCHEDULES_READ;
@ -1159,7 +1214,8 @@ eOSState cMenuEditTimer::ProcessKey(eKeys Key)
Display();
}
return osContinue;
case kYellow:
case kYellow: SetPatternItem();
return osContinue;
case kBlue: return osContinue;
default: break;
}
@ -1212,12 +1268,19 @@ void cMenuTimerItem::Set(void)
strftime(buffer, sizeof(buffer), "%Y%m%d", &tm_r);
day = buffer;
}
const char *File = Setup.FoldersInTimerMenu ? NULL : strrchr(timer->File(), FOLDERDELIMCHAR);
if (File && strcmp(File + 1, TIMERMACRO_TITLE) && strcmp(File + 1, TIMERMACRO_EPISODE))
File++;
else
File = timer->File();
SetText(cString::sprintf("%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s%s",
const char *File = timer->Pattern();
if (!*File) {
if (timer->HasFlags(tfSpawned) && timer->Event() && timer->Event()->Title())
File = timer->Event()->Title();
else {
File = Setup.FoldersInTimerMenu ? NULL : strrchr(timer->File(), FOLDERDELIMCHAR);
if (File && strcmp(File + 1, TIMERMACRO_TITLE) && strcmp(File + 1, TIMERMACRO_EPISODE))
File++;
else
File = timer->File();
}
}
SetText(cString::sprintf("%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s%s%s%s",
!(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>',
timer->Channel()->Number(),
*name,
@ -1228,7 +1291,9 @@ void cMenuTimerItem::Set(void)
timer->Stop() / 100,
timer->Stop() % 100,
timer->Remote() ? *cString::sprintf("@%s: ", timer->Remote()) : "",
File));
timer->IsPatternTimer() ? "{" : "",
File,
timer->IsPatternTimer() ? "}" : ""));
}
void cMenuTimerItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
@ -1544,6 +1609,8 @@ bool cMenuScheduleItem::Update(const cTimers *Timers, bool Force)
eTimerMatch OldTimerMatch = timerMatch;
bool OldTimerActive = timerActive;
const cTimer *Timer = Timers->GetMatch(event, &timerMatch);
if (event->EndTime() < time(NULL) && !event->IsRunning())
timerMatch = tmNone;
timerActive = Timer && Timer->HasFlags(tfActive);
if (Force || timerMatch != OldTimerMatch || timerActive != OldTimerActive) {
cString buffer;
@ -5354,8 +5421,13 @@ void cRecordControl::Stop(bool ExecuteUserCommand)
bool cRecordControl::Process(time_t t)
{
if (!recorder || !recorder->IsAttached() || !timer || !timer->Matches(t)) {
if (timer)
if (timer) {
timer->SetPending(false);
if (timer->HasFlags(tfAvoid)) {
const char *p = strgetlast(timer->File(), FOLDERDELIMCHAR);
DoneRecordingsPattern.Append(p);
}
}
return false;
}
return true;

4
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 4.8 2018/04/14 10:24:41 kls Exp $
* $Id: menu.h 5.1 2020/12/26 15:49:01 kls Exp $
*/
#ifndef __MENU_H
@ -79,11 +79,13 @@ private:
bool addIfConfirmed;
cStringList svdrpServerNames;
char remote[HOST_NAME_MAX];
cMenuEditStrItem *pattern;
cMenuEditStrItem *file;
cMenuEditDateItem *day;
cMenuEditDateItem *firstday;
eOSState SetFolder(void);
void SetFirstDayItem(void);
void SetPatternItem(bool Initial = false);
void SetHelpKeys(void);
public:
cMenuEditTimer(cTimer *Timer, bool New = false);

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 4.3 2018/03/23 15:37:02 kls Exp $
* $Id: menuitems.c 5.1 2020/12/26 15:49:01 kls Exp $
*/
#include "menuitems.h"
@ -390,6 +390,10 @@ cMenuEditStrItem::cMenuEditStrItem(const char *Name, char *Value, int Length, co
allowed = Allowed ? Allowed : tr(FileNameChars);
pos = -1;
offset = 0;
keepSpace = false;
macro = -1;
lastMacro = -1;
macros = NULL;
insert = uppercase = false;
newchar = true;
lengthUtf8 = 0;
@ -408,6 +412,13 @@ cMenuEditStrItem::~cMenuEditStrItem()
delete[] charMapUtf8;
}
void cMenuEditStrItem::SetMacros(const char **Macros)
{
macros = Macros;
macro = 0;
lastMacro = -1;
}
void cMenuEditStrItem::EnterEditMode(void)
{
if (!valueUtf8) {
@ -430,7 +441,8 @@ void cMenuEditStrItem::LeaveEditMode(bool SaveValue)
if (valueUtf8) {
if (SaveValue) {
Utf8FromArray(valueUtf8, value, length);
stripspace(value);
if (!keepSpace)
stripspace(value);
}
lengthUtf8 = 0;
delete[] valueUtf8;
@ -448,7 +460,7 @@ void cMenuEditStrItem::LeaveEditMode(bool SaveValue)
void cMenuEditStrItem::SetHelpKeys(void)
{
if (InEditMode())
SetHelp(tr("Button$ABC/abc"), insert ? tr("Button$Overwrite") : tr("Button$Insert"), tr("Button$Delete"));
SetHelp(tr("Button$ABC/abc"), insert ? tr("Button$Overwrite") : tr("Button$Insert"), tr("Button$Delete"), macros ? tr("Button$Macro") : NULL);
else
SetHelp(NULL);
}
@ -581,11 +593,39 @@ void cMenuEditStrItem::Delete(void)
lengthUtf8--;
}
void cMenuEditStrItem::InsertMacro(void)
{
if (!macros)
return;
if (lastMacro >= 0) {
int l = strlen(macros[lastMacro]);
while (l-- > 0)
Delete();
}
const char *p = macros[macro];
int oldPos = pos;
bool oldInsert = insert;
insert = true;
newchar = true;
while (*p) {
Type(*p);
p++;
}
insert = oldInsert;
pos = oldPos;
lastMacro = macro;
if (!macros[++macro])
macro = 0;
}
eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
{
bool SameKey = NORMALKEY(Key) == lastKey;
if (Key != kNone)
if (Key != kNone) {
lastKey = NORMALKEY(Key);
if (Key != kBlue)
lastMacro = -1;
}
else if (!newchar && k0 <= lastKey && lastKey <= k9 && autoAdvanceTimeout.TimedOut()) {
AdvancePos();
newchar = true;
@ -635,8 +675,9 @@ eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
return osUnknown;
break;
case kBlue|k_Repeat:
case kBlue: // consume the key only if in edit-mode
if (!InEditMode())
case kBlue: if (InEditMode())
InsertMacro();
else
return osUnknown;
break;
case kLeft|k_Repeat:

View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: menuitems.h 4.1 2015/09/06 10:38:37 kls Exp $
* $Id: menuitems.h 5.1 2020/12/26 15:49:01 kls Exp $
*/
#ifndef __MENUITEMS_H
@ -111,6 +111,9 @@ private:
int length;
const char *allowed;
int pos, offset;
bool keepSpace;
const char **macros;
int macro, lastMacro;
bool insert, newchar, uppercase;
int lengthUtf8;
uint *valueUtf8;
@ -127,6 +130,7 @@ private:
void Type(uint c);
void Insert(void);
void Delete(void);
void InsertMacro(void);
protected:
void EnterEditMode(void);
void LeaveEditMode(bool SaveValue = false);
@ -134,6 +138,8 @@ protected:
public:
cMenuEditStrItem(const char *Name, char *Value, int Length, const char *Allowed = NULL);
~cMenuEditStrItem();
void SetKeepSpace(void) { keepSpace = true; }
void SetMacros(const char **Macros);
virtual eOSState ProcessKey(eKeys Key);
};

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -691,9 +691,21 @@ msgstr "Single"
msgid "Button$Repeating"
msgstr "Repeating"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "اليوم الاول"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1495,6 +1507,9 @@ msgstr "اعادة الكتابة"
msgid "Button$Insert"
msgstr "ادراج"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "الملحق"

View File

@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -690,9 +690,21 @@ msgstr "Individual"
msgid "Button$Repeating"
msgstr "Repetitiu"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Primer dia"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1494,6 +1506,9 @@ msgstr "Sobrescriure"
msgid "Button$Insert"
msgstr "Inserir"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -690,9 +690,21 @@ msgstr "Bez opakování"
msgid "Button$Repeating"
msgstr "S opakováním"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "První den"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1494,6 +1506,9 @@ msgstr "Přepsat"
msgid "Button$Insert"
msgstr "Vložit"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Modul"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -687,9 +687,21 @@ msgstr ""
msgid "Button$Repeating"
msgstr ""
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Første dag"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1491,6 +1503,9 @@ msgstr "Overskriv"
msgid "Button$Insert"
msgstr "Indsæt"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr "Einmalig"
msgid "Button$Repeating"
msgstr "Wiederholend"
msgid "Button$Regular"
msgstr "Normal"
msgid "Button$Pattern"
msgstr "Muster"
msgid "First day"
msgstr "Erster Tag"
msgid "Timer is recording!"
msgstr "Timer nimmt auf!"
msgid "Pattern"
msgstr "Muster"
msgid "Error while accessing remote timer"
msgstr "Fehler beim Ansprechen des fernen Timers"
@ -1492,6 +1504,9 @@ msgstr "
msgid "Button$Insert"
msgstr "Einfügen"
msgid "Button$Macro"
msgstr "Makro"
msgid "Plugin"
msgstr "Plugin"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -687,9 +687,21 @@ msgstr ""
msgid "Button$Repeating"
msgstr ""
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Ðñþôç ìÝñá"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1491,6 +1503,9 @@ msgstr "
msgid "Button$Insert"
msgstr "ÅéóáãùãÞ"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "ÅðÝêôáóç"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr "Individual"
msgid "Button$Repeating"
msgstr "Periódico"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Primer día"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1492,6 +1504,9 @@ msgstr "Sobreescribir"
msgid "Button$Insert"
msgstr "Insertar"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -687,9 +687,21 @@ msgstr "Üksik"
msgid "Button$Repeating"
msgstr "Korduv"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "1. päev"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Kaugtaimeri viga"
@ -1491,6 +1503,9 @@ msgstr "Asenda (OVR)"
msgid "Button$Insert"
msgstr "Lisa (INS)"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -691,9 +691,21 @@ msgstr "Yksittäinen"
msgid "Button$Repeating"
msgstr "Toistuva"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "1. päivä"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Etäajastimen hakeminen epäonnistui"
@ -1495,6 +1507,9 @@ msgstr "Korvaa"
msgid "Button$Insert"
msgstr "Lisää"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Laajennos"

View File

@ -18,7 +18,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -698,9 +698,21 @@ msgstr "Simple"
msgid "Button$Repeating"
msgstr "Périodique"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Premier jour"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Erreur pendant l'accès à la programmation"
@ -1502,6 +1514,9 @@ msgstr "Écraser"
msgid "Button$Insert"
msgstr "Insérer"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Module"

View File

@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -689,9 +689,21 @@ msgstr ""
msgid "Button$Repeating"
msgstr ""
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Prvi dan"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1493,6 +1505,9 @@ msgstr "Prepi
msgid "Button$Insert"
msgstr "Umetni"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Dodatak"

View File

@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\n"
"PO-Revision-Date: 2018-04-09 21:42+0300\n"
"Last-Translator: István Füley <ifuley@tigercomp.ro>\n"
"Language-Team: Hungarian <vdr@linuxtv.org>\n"
@ -692,9 +692,21 @@ msgstr "Egyszeri"
msgid "Button$Repeating"
msgstr "Ismétlődő"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Első nap"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Távoli időzítő nem elérhető"
@ -1496,6 +1508,9 @@ msgstr "Felülírás"
msgid "Button$Insert"
msgstr "Beillesztés"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\n"
"PO-Revision-Date: 2018-04-06 19:13+0100\n"
"Last-Translator: Gringo <vdr-italian@tiscali.it>\n"
"Language-Team: Italian <vdr@linuxtv.org>\n"
@ -693,9 +693,21 @@ msgstr "Una volta"
msgid "Button$Repeating"
msgstr "Repliche"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "1° giorno"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Errore durante l'accesso al timer remoto"
@ -1497,6 +1509,9 @@ msgstr "Sovrascrivi"
msgid "Button$Insert"
msgstr "Inserisci"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -687,9 +687,21 @@ msgstr "Vienas"
msgid "Button$Repeating"
msgstr "Kartotinas"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Pirma diena"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1491,6 +1503,9 @@ msgstr "Perrąšyti"
msgid "Button$Insert"
msgstr "Įterpti"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Įskiepas"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -689,9 +689,21 @@ msgstr "Единчен"
msgid "Button$Repeating"
msgstr "Периодичен"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Прв ден"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Грешка при пристап на далечен тајмер"
@ -1493,6 +1505,9 @@ msgstr "Препиши"
msgid "Button$Insert"
msgstr "Вметни"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Додаток"

View File

@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -693,9 +693,21 @@ msgstr "Eenmalig"
msgid "Button$Repeating"
msgstr "Herhalen"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Eerste dag"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1497,6 +1509,9 @@ msgstr "Overschrijven"
msgid "Button$Insert"
msgstr "Invoegen"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr ""
msgid "Button$Repeating"
msgstr ""
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Første dag"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1492,6 +1504,9 @@ msgstr ""
msgid "Button$Insert"
msgstr ""
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -692,9 +692,21 @@ msgstr "Pojedynczy"
msgid "Button$Repeating"
msgstr "Powtarzanie"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Pierwszy dzień"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Błąd podczas dostępu do zdalnego timera"
@ -1496,6 +1508,9 @@ msgstr "Nadpisz"
msgid "Button$Insert"
msgstr "Wstaw"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Wtyczka"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr ""
msgid "Button$Repeating"
msgstr ""
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "1° dia"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1492,6 +1504,9 @@ msgstr "Sobrescrever"
msgid "Button$Insert"
msgstr "Inserir"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -689,9 +689,21 @@ msgstr "Odată"
msgid "Button$Repeating"
msgstr "Repetitiv"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Prima zi"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1493,6 +1505,9 @@ msgstr "Suprascrie"
msgid "Button$Insert"
msgstr "Inserează"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Plugin"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr "Один раз"
msgid "Button$Repeating"
msgstr "Повтор"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Первый день"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Ошибка доступа к таймеру"
@ -1492,6 +1504,9 @@ msgstr "Замена"
msgid "Button$Insert"
msgstr "Вставка"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Модуль"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr "bez opakovania"
msgid "Button$Repeating"
msgstr "s opakovaním"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Odo dòa"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1492,6 +1504,9 @@ msgstr "Prep
msgid "Button$Insert"
msgstr "Vlo¾i»"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Modul"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr "Enkraten"
msgid "Button$Repeating"
msgstr "Ponavljajoèe"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Prvi dan"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1492,6 +1504,9 @@ msgstr "Prepi
msgid "Button$Insert"
msgstr "Vstavi"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Vstavek"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr "Jedinstven"
msgid "Button$Repeating"
msgstr "Ponavljajuæi"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Prvi dan"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1492,6 +1504,9 @@ msgstr "Zameni"
msgid "Button$Insert"
msgstr "Ubaci"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Dodatak"

View File

@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -692,9 +692,21 @@ msgstr "Enskilld"
msgid "Button$Repeating"
msgstr "Repeterande"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Första dag"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1496,6 +1508,9 @@ msgstr "Skriv
msgid "Button$Insert"
msgstr "Infoga"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Modul"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -687,9 +687,21 @@ msgstr ""
msgid "Button$Repeating"
msgstr ""
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Ýlk gün"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1491,6 +1503,9 @@ msgstr "
msgid "Button$Insert"
msgstr "Ekle"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Eklenti"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -688,9 +688,21 @@ msgstr "Одинарне"
msgid "Button$Repeating"
msgstr "Повтор"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "Перший день"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr "Помилка доступу до віддаленого таймера"
@ -1492,6 +1504,9 @@ msgstr "Заміна"
msgid "Button$Insert"
msgstr "Вставка"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "Модуль"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: VDR 2.4.0\n"
"Report-Msgid-Bugs-To: <vdr-bugs@tvdr.de>\n"
"POT-Creation-Date: 2020-06-15 17:50+0200\n"
"POT-Creation-Date: 2020-12-24 17:42+0100\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"
@ -689,9 +689,21 @@ msgstr "单个"
msgid "Button$Repeating"
msgstr "重复"
msgid "Button$Regular"
msgstr ""
msgid "Button$Pattern"
msgstr ""
msgid "First day"
msgstr "第一天"
msgid "Timer is recording!"
msgstr ""
msgid "Pattern"
msgstr ""
msgid "Error while accessing remote timer"
msgstr ""
@ -1493,6 +1505,9 @@ msgstr "覆盖"
msgid "Button$Insert"
msgstr "插入"
msgid "Button$Macro"
msgstr ""
msgid "Plugin"
msgstr "插件"

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 4.29 2020/10/30 16:08:29 kls Exp $
* $Id: recording.c 5.1 2020/12/26 15:49:01 kls Exp $
*/
#include "recording.h"
@ -3050,6 +3050,74 @@ cUnbufferedFile *cFileName::NextFile(void)
return SetOffset(fileNumber + 1);
}
// --- cDoneRecordings -------------------------------------------------------
cDoneRecordings DoneRecordingsPattern;
bool cDoneRecordings::Load(const char *FileName)
{
fileName = FileName;
if (*fileName && access(fileName, F_OK) == 0) {
isyslog("loading %s", *fileName);
FILE *f = fopen(fileName, "r");
if (f) {
char *s;
cReadLine ReadLine;
while ((s = ReadLine.Read(f)) != NULL)
Add(s);
fclose(f);
}
else {
LOG_ERROR_STR(*fileName);
return false;
}
}
return true;
}
bool cDoneRecordings::Save(void) const
{
bool result = true;
cSafeFile f(fileName);
if (f.Open()) {
for (int i = 0; i < doneRecordings.Size(); i++) {
if (fputs(doneRecordings[i], f) == EOF || fputc('\n', f) == EOF) {
result = false;
break;
}
}
if (!f.Close())
result = false;
}
else
result = false;
return result;
}
void cDoneRecordings::Add(const char *Title)
{
doneRecordings.Append(strdup(Title));
}
void cDoneRecordings::Append(const char *Title)
{
if (!Contains(Title)) {
Add(Title);
if (FILE *f = fopen(fileName, "a")) {
fputs(Title, f);
fputc('\n', f);
fclose(f);
}
else
esyslog("ERROR: can't open '%s' for appending '%s'", *fileName, Title);
}
}
bool cDoneRecordings::Contains(const char *Title) const
{
return doneRecordings.Find(Title) >= 0;
}
// --- Index stuff -----------------------------------------------------------
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)

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 4.10 2020/09/16 13:48:33 kls Exp $
* $Id: recording.h 5.1 2020/12/26 15:49:01 kls Exp $
*/
#ifndef __RECORDING_H
@ -504,6 +504,20 @@ public:
cUnbufferedFile *NextFile(void);
};
class cDoneRecordings {
private:
cString fileName;
cStringList doneRecordings;
void Add(const char *Title);
public:
bool Load(const char *FileName);
bool Save(void) const;
void Append(const char *Title);
bool Contains(const char *Title) const;
};
extern cDoneRecordings DoneRecordingsPattern;
cString IndexToHMSF(int Index, bool WithFrame = false, double FramesPerSecond = DEFAULTFRAMESPERSECOND);
// Converts the given index to a string, optionally containing the frame number.
int HMSFToIndex(const char *HMSF, double FramesPerSecond = DEFAULTFRAMESPERSECOND);

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 4.7 2020/05/18 16:47:29 kls Exp $
* $Id: skinlcars.c 5.1 2020/12/26 15:49:01 kls Exp $
*/
// "Star Trek: The Next Generation"(R) is a registered trademark of Paramount Pictures,
@ -1264,7 +1264,9 @@ void cSkinLCARSDisplayMenu::DrawTimers(void)
if (y + lineHeight > ys05)
break;
if (const cTimer *Timer = SortedTimers[i]) {
if (Timer->Recording()) {
if (Timer->IsPatternTimer())
SortedTimers[i] = NULL;
else if (Timer->Recording()) {
if (Timer->Remote()) {
if (!Device && Timer->HasFlags(tfActive)) {
DrawTimer(Timer, y, false);

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 4.43 2020/06/22 20:59:49 kls Exp $
* $Id: svdrp.c 5.1 2020/12/26 15:49:01 kls Exp $
*/
#include "svdrp.h"
@ -2048,6 +2048,10 @@ void cSVDRPServer::CmdMODT(const char *Option)
Reply(501, "Error in timer settings");
return;
}
if (IsRecording && t.IsPatternTimer()) {
Reply(550, "Timer is recording");
return;
}
*Timer = t;
if (IsRecording)
Timer->SetFlags(tfRecording);
@ -2055,6 +2059,8 @@ void cSVDRPServer::CmdMODT(const char *Option)
Timer->ClrFlags(tfRecording);
Timers->SetModified();
isyslog("SVDRP %s < %s modified timer %s (%s)", Setup.SVDRPHostName, *clientName, *Timer->ToDescr(), Timer->HasFlags(tfActive) ? "active" : "inactive");
if (Timer->IsPatternTimer())
Timer->SetEvent(NULL);
Reply(250, "%d %s", Timer->Id(), *Timer->ToText(true));
}
else

250
timers.c
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 4.20 2020/09/16 13:48:33 kls Exp $
* $Id: timers.c 5.1 2020/12/26 15:49:01 kls Exp $
*/
#include "timers.h"
@ -31,6 +31,7 @@ cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
deferred = 0;
pending = inVpsMargin = false;
flags = tfNone;
*pattern = 0;
*file = 0;
aux = NULL;
remote = NULL;
@ -81,7 +82,94 @@ cTimer::cTimer(bool Instant, bool Pause, const cChannel *Channel)
snprintf(file, sizeof(file), "%s%s", Setup.MarkInstantRecord ? "@" : "", *Setup.NameInstantRecord ? Setup.NameInstantRecord : channel->Name());
}
cTimer::cTimer(const cEvent *Event)
static bool MatchPattern(const char *Pattern, const char *Title, cString *Before = NULL, cString *Match = NULL, cString *After = NULL)
{
if (Title) {
bool AvoidDuplicates = startswith(Pattern, TIMERPATTERN_AVOID);
if (AvoidDuplicates)
Pattern++;
if (strcmp(Pattern, "*") == 0) {
if (Before)
*Before = "";
if (Match)
*Match = Title;
if (After)
*After = "";
return true;
}
bool AnchorBegin = startswith(Pattern, TIMERPATTERN_BEGIN);
if (AnchorBegin)
Pattern++;
bool AnchorEnd = endswith(Pattern, TIMERPATTERN_END);
cNullTerminate nt;
if (AnchorEnd)
nt.Set(const_cast<char *>(Pattern + strlen(Pattern) - 1));
if (AnchorBegin && AnchorEnd) {
if (strcmp(Title, Pattern) == 0) {
if (Before)
*Before = "";
if (Match)
*Match = Title;
if (After)
*After = "";
return true;
}
}
else if (AnchorBegin) {
if (strstr(Title, Pattern) == Title) {
if (Before)
*Before = "";
if (Match)
*Match = Pattern;
if (After)
*After = cString(Title + strlen(Pattern));
return true;
}
}
else if (AnchorEnd) {
if (endswith(Title, Pattern)) {
if (Before)
*Before = cString(Title, Title + strlen(Title) - strlen(Pattern));
if (Match)
*Match = Pattern;
if (After)
*After = "";
return true;
}
}
else if (const char *p = strstr(Title, Pattern)) {
if (Before)
*Before = cString(Title, p);
if (Match)
*Match = Pattern;
if (After)
*After = cString(p + strlen(Pattern));
return true;
}
}
return false;
}
static cString MakePatternFileName(const char *Pattern, const char *Title, const char *Episode, const char *File)
{
if (!Pattern || !Title || !File)
return NULL;
cString Before = "";
cString Match = "";
cString After = "";
if (MatchPattern(Pattern, Title, &Before, &Match, &After)) {
char *Result = strdup(File);
Result = strreplace(Result, TIMERMACRO_TITLE, Title);
Result = strreplace(Result, TIMERMACRO_EPISODE, Episode);
Result = strreplace(Result, TIMERMACRO_BEFORE, Before);
Result = strreplace(Result, TIMERMACRO_MATCH, Match);
Result = strreplace(Result, TIMERMACRO_AFTER, After);
return cString(Result, true);;
}
return NULL;
}
cTimer::cTimer(const cEvent *Event, const char *FileName, const cTimer *PatternTimer)
{
id = 0;
startTime = stopTime = 0;
@ -89,12 +177,15 @@ cTimer::cTimer(const cEvent *Event)
deferred = 0;
pending = inVpsMargin = false;
flags = tfActive;
*pattern = 0;
*file = 0;
aux = NULL;
remote = NULL;
event = NULL;
if (Event->Vps() && Setup.UseVps)
SetFlags(tfVps);
if (!PatternTimer || PatternTimer->HasFlags(tfVps)) {
if (Event->Vps() && Setup.UseVps)
SetFlags(tfVps);
}
LOCK_CHANNELS_READ;
channel = Channels->GetByChannelID(Event->ChannelID(), true);
time_t tstart = (flags & tfVps) ? Event->Vps() : Event->StartTime();
@ -112,11 +203,12 @@ cTimer::cTimer(const cEvent *Event)
stop = time->tm_hour * 100 + time->tm_min;
if (stop >= 2400)
stop -= 2400;
priority = Setup.DefaultPriority;
lifetime = Setup.DefaultLifetime;
const char *Title = Event->Title();
if (!isempty(Title))
Utf8Strn0Cpy(file, Event->Title(), sizeof(file));
priority = PatternTimer ? PatternTimer->Priority() : Setup.DefaultPriority;
lifetime = PatternTimer ? PatternTimer->Lifetime() : Setup.DefaultLifetime;
if (!FileName)
FileName = Event->Title();
if (!isempty(FileName))
Utf8Strn0Cpy(file, FileName, sizeof(file));
SetEvent(Event);
}
@ -156,6 +248,7 @@ cTimer& cTimer::operator= (const cTimer &Timer)
stop = Timer.stop;
priority = Timer.priority;
lifetime = Timer.lifetime;
strncpy(pattern, Timer.pattern, sizeof(pattern));
strncpy(file, Timer.file, sizeof(file));
free(aux);
aux = Timer.aux ? strdup(Timer.aux) : NULL;
@ -178,20 +271,37 @@ int cTimer::Compare(const cListObject &ListObject) const
int r = t1 - t2;
if (r == 0)
r = ti->priority - priority;
if (IsPatternTimer() ^ ti->IsPatternTimer()) {
if (IsPatternTimer())
r = 1;
else
r = -1;
}
else if (IsPatternTimer() && ti->IsPatternTimer())
r = strcoll(Pattern(), ti->Pattern());
return r;
}
cString cTimer::PatternAndFile(void) const
{
if (IsPatternTimer())
return cString::sprintf("{%s}%s", pattern, file);
return file;
}
cString cTimer::ToText(bool UseChannelID) const
{
strreplace(pattern, ':', '|');
strreplace(file, ':', '|');
cString buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux ? aux : "");
cString buffer = cString::sprintf("%u:%s:%s:%04d:%04d:%d:%d:%s:%s", flags, UseChannelID ? *Channel()->GetChannelID().ToString() : *itoa(Channel()->Number()), *PrintDay(day, weekdays, true), start, stop, priority, lifetime, *PatternAndFile(), aux ? aux : "");
strreplace(pattern, '|', ':');
strreplace(file, '|', ':');
return buffer;
}
cString cTimer::ToDescr(void) const
{
return cString::sprintf("%d%s%s (%d %04d-%04d %s'%s')", Id(), remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", file);
return cString::sprintf("%d%s%s (%d %04d-%04d %s'%s')", Id(), remote ? "@" : "", remote ? remote : "", Channel()->Number(), start, stop, HasFlags(tfVps) ? "VPS " : "", *PatternAndFile());
}
int cTimer::TimeToInt(int t)
@ -332,7 +442,18 @@ bool cTimer::Parse(const char *s)
}
//TODO add more plausibility checks
result = ParseDay(daybuffer, day, weekdays);
Utf8Strn0Cpy(file, filebuffer, sizeof(file));
char *fb = filebuffer;
if (*fb == '{') {
if (char *p = strchr(fb, '}')) {
*p = 0;
Utf8Strn0Cpy(pattern, fb + 1, sizeof(pattern));
strreplace(pattern, '|', ':');
fb = p + 1;
}
}
else
*pattern = 0;
Utf8Strn0Cpy(file, fb, sizeof(file));
strreplace(file, '|', ':');
LOCK_CHANNELS_READ;
if (isnumber(channelbuffer))
@ -404,6 +525,11 @@ time_t cTimer::SetTime(time_t t, int SecondsFromMidnight)
return mktime(&tm);
}
void cTimer::SetPattern(const char *Pattern)
{
Utf8Strn0Cpy(pattern, Pattern, sizeof(pattern));
}
void cTimer::SetFile(const char *File)
{
if (!isempty(File))
@ -451,6 +577,9 @@ bool cTimer::Matches(time_t t, bool Directly, int Margin) const
day = 0;
}
if (IsPatternTimer())
return false; // we only need to have start/stopTime initialized
if (t < deferred)
return false;
deferred = 0;
@ -483,6 +612,21 @@ eTimerMatch cTimer::Matches(const cEvent *Event, int *Overlap) const
// gets 200 added to the FULLMATCH.
if (channel->GetChannelID() == Event->ChannelID()) {
bool UseVps = HasFlags(tfVps) && Event->Vps();
if (IsPatternTimer()) {
if (startswith(Pattern(), TIMERPATTERN_AVOID)) {
cString FileName = MakePatternFileName(Pattern(), Event->Title(), Event->ShortText(), File());
if (*FileName) {
const char *p = strgetlast(*FileName, FOLDERDELIMCHAR);
if (DoneRecordingsPattern.Contains(p))
return tmNone;
}
else
return tmNone;
}
else if (!MatchPattern(Pattern(), Event->Title()))
return tmNone;
UseVps = false;
}
Matches(UseVps ? Event->Vps() : Event->StartTime(), true);
int overlap = 0;
if (UseVps) {
@ -499,8 +643,11 @@ eTimerMatch cTimer::Matches(const cEvent *Event, int *Overlap) const
overlap = FULLMATCH;
else if (stopTime <= Event->StartTime() || Event->EndTime() <= startTime)
overlap = 0;
else
else {
overlap = (min(stopTime, Event->EndTime()) - max(startTime, Event->StartTime())) * FULLMATCH / max(Event->Duration(), 1);
if (IsPatternTimer() && overlap > 0)
overlap = FULLMATCH;
}
}
startTime = stopTime = 0;
if (Overlap)
@ -542,8 +689,60 @@ void cTimer::SetId(int Id)
id = Id;
}
void 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);
HandleRemoteTimerModifications(t);
}
bool cTimer::SpawnPatternTimers(const cSchedules *Schedules, cTimers *Timers)
{
bool TimersSpawned = false;
const cSchedule *Schedule = Schedules->GetSchedule(Channel());
if (Schedule && Schedule->Events()->First()) {
if (Schedule->Modified(scheduleState)) {
time_t Now = time(NULL);
for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e)) {
if (Matches(e) != tmNone) {
bool CheckThis = false;
bool CheckNext = false;
if (e->HasTimer()) // a matching event that already has a timer
CheckNext = true;
else if (e->EndTime() > Now) { // only look at events that have not yet ended
CheckThis = true;
CheckNext = true;
}
if (CheckThis) {
SpawnPatternTimer(e, Timers);
TimersSpawned = true;
}
if (CheckNext) {
// We also check the event immediately following this one:
e = Schedule->Events()->Next(e);
if (e && !e->HasTimer() && Matches(e) != tmNone) {
SpawnPatternTimer(e, Timers);
TimersSpawned = true;
}
}
if (CheckThis || CheckNext)
break;
}
}
}
}
return TimersSpawned;
}
bool cTimer::SetEventFromSchedule(const cSchedules *Schedules)
{
if (IsPatternTimer())
return SetEvent(NULL);
const cSchedule *Schedule = Schedules->GetSchedule(Channel());
if (Schedule && Schedule->Events()->First()) {
if (Schedule->Modified(scheduleState)) {
@ -707,7 +906,7 @@ void cTimer::Skip(void)
void cTimer::OnOff(void)
{
if (IsSingleEvent())
if (IsSingleEvent() || IsPatternTimer())
InvFlags(tfActive);
else if (day) {
day = 0;
@ -718,6 +917,8 @@ void cTimer::OnOff(void)
else
SetFlags(tfActive);
SetEvent(NULL);
if (HasFlags(tfActive))
scheduleState = -1; // have pattern timers spawn if necessary
Matches(); // refresh start and end time
}
@ -831,7 +1032,7 @@ const cTimer *cTimers::GetNextActiveTimer(void) const
{
const cTimer *t0 = NULL;
for (const cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->Remote()) {
if (!ti->Remote() && !ti->IsPatternTimer()) {
ti->Matches();
if ((ti->HasFlags(tfActive)) && (!t0 || ti->StopTime() > time(NULL) && ti->Compare(*t0) < 0))
t0 = ti;
@ -882,8 +1083,22 @@ const cTimer *cTimers::UsesChannel(const cChannel *Channel) const
bool cTimers::SetEvents(const cSchedules *Schedules)
{
bool TimersModified = false;
for (cTimer *ti = First(); ti; ti = Next(ti))
TimersModified |= ti->SetEventFromSchedule(Schedules);
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (!ti->IsPatternTimer())
TimersModified |= ti->SetEventFromSchedule(Schedules);
}
return TimersModified;
}
bool cTimers::SpawnPatternTimers(const cSchedules *Schedules)
{
bool TimersModified = false;
for (cTimer *ti = First(); ti; ti = Next(ti)) {
if (ti->IsPatternTimer() && ti->Local()) {
if (ti->HasFlags(tfActive))
TimersModified |= ti->SpawnPatternTimers(Schedules, this);
}
}
return TimersModified;
}
@ -896,6 +1111,7 @@ bool cTimers::DeleteExpired(void)
while (ti) {
cTimer *next = Next(ti);
if (!ti->Remote() && ti->Expired()) {
ti->SetEvent(NULL); // Del() doesn't call ~cTimer() right away, so this is necessary here
isyslog("deleting timer %s", *ti->ToDescr());
Del(ti);
TimersModified = true;

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 4.12 2019/05/23 09:47:19 kls Exp $
* $Id: timers.h 5.1 2020/12/26 15:49:01 kls Exp $
*/
#ifndef __TIMERS_H
@ -20,10 +20,14 @@ enum eTimerFlags { tfNone = 0x0000,
tfInstant = 0x0002,
tfVps = 0x0004,
tfRecording = 0x0008,
tfSpawned = 0x0010,
tfAvoid = 0x0020,
tfAll = 0xFFFF,
};
enum eTimerMatch { tmNone, tmPartial, tmFull };
class cTimers;
class cTimer : public cListObject {
friend class cMenuEditTimer;
private:
@ -40,13 +44,14 @@ private:
int stop;
int priority;
int lifetime;
mutable char pattern[NAME_MAX * 2 + 1]; // same size as 'file', to be able to initially fill 'pattern' with 'file' in the 'Edit timer' menu
mutable char file[NAME_MAX * 2 + 1]; // *2 to be able to hold 'title' and 'episode', which can each be up to 255 characters long
char *aux;
char *remote;
const cEvent *event;
public:
cTimer(bool Instant = false, bool Pause = false, const cChannel *Channel = NULL);
cTimer(const cEvent *Event);
cTimer(const cEvent *Event, const char *FileName = NULL, const cTimer *PatternTimer = NULL);
cTimer(const cTimer &Timer);
virtual ~cTimer();
cTimer& operator= (const cTimer &Timer);
@ -63,12 +68,14 @@ public:
int Stop(void) const { return stop; }
int Priority(void) const { return priority; }
int Lifetime(void) const { return lifetime; }
const char *Pattern(void) const { return pattern; }
const char *File(void) const { return file; }
time_t FirstDay(void) const { return weekdays ? day : 0; }
const char *Aux(void) const { return aux; }
const char *Remote(void) const { return remote; }
bool Local(void) const { return !remote; } // convenience
time_t Deferred(void) const { return deferred; }
cString PatternAndFile(void) const;
cString ToText(bool UseChannelID = false) const;
cString ToDescr(void) const;
const cEvent *Event(void) const { return event; }
@ -80,13 +87,17 @@ public:
bool DayMatches(time_t t) const;
static time_t IncDay(time_t t, int Days);
static time_t SetTime(time_t t, int SecondsFromMidnight);
void SetPattern(const char *Pattern);
void SetFile(const char *File);
bool IsPatternTimer(void) const { return *pattern; }
bool Matches(time_t t = 0, bool Directly = false, int Margin = 0) const;
eTimerMatch Matches(const cEvent *Event, int *Overlap = NULL) const;
bool Expired(void) const;
time_t StartTime(void) const;
time_t StopTime(void) const;
void SetId(int Id);
void SpawnPatternTimer(const cEvent *Event, cTimers *Timers);
bool SpawnPatternTimers(const cSchedules *Schedules, cTimers *Timers);
bool SetEventFromSchedule(const cSchedules *Schedules);
bool SetEvent(const cEvent *Event);
void SetRecording(bool Recording);
@ -182,6 +193,7 @@ public:
const cTimer *GetNextActiveTimer(void) const;
const cTimer *UsesChannel(const cChannel *Channel) const;
bool SetEvents(const cSchedules *Schedules);
bool SpawnPatternTimers(const cSchedules *Schedules);
bool DeleteExpired(void);
void Add(cTimer *Timer, cTimer *After = NULL);
void Ins(cTimer *Timer, cTimer *Before = NULL);

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 4.13 2020/11/22 13:32:05 kls Exp $
* $Id: tools.c 5.1 2020/12/26 15:49:01 kls Exp $
*/
#include "tools.h"
@ -198,6 +198,12 @@ int strcountchr(const char *s, char c)
return n;
}
const char *strgetlast(const char *s, char c)
{
const char *p = strrchr(s, c);
return p ? p + 1 : s;
}
char *stripspace(char *s)
{
if (s && *s) {

30
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 4.18 2020/09/16 13:48:33 kls Exp $
* $Id: tools.h 5.1 2020/12/26 15:49:01 kls Exp $
*/
#ifndef __TOOLS_H
@ -193,6 +193,33 @@ public:
static cString vsprintf(const char *fmt, va_list &ap);
};
class cNullTerminate {
private:
char *p;
char c;
public:
cNullTerminate(void) {
p = NULL;
c = 0;
}
cNullTerminate(char *s) {
Set(s);
}
~cNullTerminate() {
if (p)
*p = c;
}
void Set(char *s) {
if (s) {
p = s;
c = *s;
*s = 0;
}
else
p = NULL;
}
};
ssize_t safe_read(int filedes, void *buffer, size_t size);
ssize_t safe_write(int filedes, const void *buffer, size_t size);
void writechar(int filedes, char c);
@ -206,6 +233,7 @@ char *strreplace(char *s, char c1, char c2);
char *strreplace(char *s, const char *s1, const char *s2); ///< re-allocates 's' and deletes the original string if necessary!
const char *strchrn(const char *s, char c, size_t n); ///< returns a pointer to the n'th occurrence (counting from 1) of c in s, or NULL if no such character was found. If n is 0, s is returned.
int strcountchr(const char *s, char c); ///< returns the number of occurrences of 'c' in 's'.
const char *strgetlast(const char *s, char c); // returns the part of 's' after the last occurrence of 'c', or 's' if there is no 'c'.
inline char *skipspace(const char *s)
{
if ((uchar)*s > ' ') // most strings don't have any leading space, so handle this case as fast as possible

7
vdr.1
View File

@ -8,7 +8,7 @@
.\" License as specified in the file COPYING that comes with the
.\" vdr distribution.
.\"
.\" $Id: vdr.1 4.4 2018/04/10 13:58:06 kls Exp $
.\" $Id: vdr.1 5.1 2020/12/26 15:49:01 kls Exp $
.\"
.TH vdr 1 "15 Apr 2018" "2.4" "Video Disk Recorder"
.SH NAME
@ -305,6 +305,11 @@ The actual data files of a recording.
Contains all current EPG data. Can be used for external processing and will
also be read at program startup to have the full EPG data available immediately.
.TP
.I donerecs.data
Contains the names of recordings that have been done by pattern timers with '@'
as the first character of the pattern. File names are appended to this file after
a recording has finished, and the entire file is read upon startup of VDR.
.TP
.I .update
If this file is present in the video directory, its last modification time will
be used to trigger an update of the list of recordings in the "Recordings" menu.

43
vdr.5
View File

@ -8,7 +8,7 @@
.\" License as specified in the file COPYING that comes with the
.\" vdr distribution.
.\"
.\" $Id: vdr.5 4.8 2018/04/10 13:58:16 kls Exp $
.\" $Id: vdr.5 5.1 2020/12/26 15:49:01 kls Exp $
.\"
.TH vdr 5 "15 Apr 2018" "2.4" "Video Disk Recorder Files"
.SH NAME
@ -317,10 +317,12 @@ The individual bits in this field have the following meaning:
.TS
tab (@);
l l.
\fB1\fR@the timer is active (and will record if it hits)
\fB2\fR@this is an instant recording timer
\fB4\fR@this timer uses VPS
\fB8\fR@this timer is currently recording (may only be up-to-date with SVDRP)
\fB0x0001\fR@the timer is active (and will record if it hits)
\fB0x0002\fR@this is an instant recording timer
\fB0x0004\fR@this timer uses VPS
\fB0x0008\fR@this timer is currently recording (may only be up-to-date with SVDRP)
\fB0x0010\fR@this timer was spawned from a pattern timer
\fB0x0020\fR@this timer will store the recording's name in donerecs.data
.TE
All other bits are reserved for future use.
@ -425,6 +427,37 @@ by the title and episode information from the EPG data at the time of
recording (if that data is available). If at the time of recording either
of these cannot be determined, \fBTITLE\fR will default to the channel name, and
\fBEPISODE\fR will default to a blank.
The file name can be prepended with a pattern, enclosed in curly braces, as in
{Columbo}Movies~TITLE
which makes this a "pattern timer". A pattern timer records every event on the
given channel where the title contains the pattern (case sensitive).
The following special characters can be used in a pattern:
.TS
tab (;);
l l.
\fB^\fR;anchor to the beginning of the event's title
\fB$\fR;anchor to the end of the event's title
\fB*\fR;match every event
\fB@\fR;avoid duplicate recordings
.TE
If \fB@\fR is used, it must be the very first character of the pattern.
If both \fB@\fR and \fB^\fR are used, \fB@\fR must come first.
If \fB*\fR is used, it must be the only character in the pattern and may only be
prepended with \fB@\fR.
In addition to TITLE and EPISODE you can use the following macros to compose the file
name (the curly braces are part of the macros):
.TS
tab (@);
l l.
{<}@everything before the matching pattern
{>}@everything after the matching pattern
{=}@the matching pattern itself (just for completeness)
.TE
.TP
.B Auxiliary data
An arbitrary string that can be used by external applications to store any

10
vdr.c
View File

@ -22,7 +22,7 @@
*
* The project's page is at http://www.tvdr.de
*
* $Id: vdr.c 4.34 2020/11/20 13:49:58 kls Exp $
* $Id: vdr.c 5.1 2020/12/26 15:49:01 kls Exp $
*/
#include <getopt.h>
@ -784,6 +784,7 @@ int main(int argc, char *argv[])
KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true);
Folders.Load(AddDirectory(ConfigDirectory, "folders.conf"));
CamResponsesLoad(AddDirectory(ConfigDirectory, "camresponses.conf"), true);
DoneRecordingsPattern.Load(AddDirectory(CacheDirectory, "donerecs.data"));
if (!*cFont::GetFontFileName(Setup.FontOsd)) {
const char *msg = "no fonts available - OSD will not show any text!";
@ -1098,15 +1099,20 @@ int main(int argc, char *argv[])
static cStateKey TimersStateKey;
cTimers *Timers = cTimers::GetTimersWrite(TimersStateKey);
{
LOCK_CHANNELS_READ; // Channels are needed for spawning pattern timers!
// Assign events to timers:
static cStateKey SchedulesStateKey;
if (TimersStateKey.StateChanged())
SchedulesStateKey.Reset(); // we assign events if either the Timers or the Schedules have changed
bool TimersModified = false;
if (const cSchedules *Schedules = cSchedules::GetSchedulesRead(SchedulesStateKey)) {
Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll);
Timers->SetSyncStateKey(StateKeySVDRPRemoteTimersPoll); // setting events shall not trigger a remote timer poll...
if (Timers->SetEvents(Schedules))
TimersModified = true;
if (Timers->SpawnPatternTimers(Schedules)) {
StateKeySVDRPRemoteTimersPoll.Reset(); // ...but spawning new timers must!
TimersModified = true;
}
SchedulesStateKey.Remove();
}
TimersStateKey.Remove(TimersModified); // we need to remove the key here, so that syncing StateKeySVDRPRemoteTimersPoll takes effect!