15#define __STDC_FORMAT_MACROS
33#define SUMMARYFALLBACK
46#define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
47#define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
48#define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT
49#define NAMEFORMATTS "%s/%s/" DATAFORMATTS
51#define RESUMEFILESUFFIX "/resume%s%s"
53#define SUMMARYFILESUFFIX "/summary.vdr"
55#define INFOFILESUFFIX "/info"
56#define MARKSFILESUFFIX "/marks"
58#define SORTMODEFILE ".sort"
59#define TIMERRECFILE ".timer"
61#define MINDISKSPACE 1024
63#define REMOVECHECKDELTA 60
64#define DELETEDLIFETIME 300
65#define DISKCHECKDELTA 100
66#define REMOVELATENCY 10
67#define MARKSUPDATEDELTA 10
68#define MAXREMOVETIME 10
70#define MAX_LINK_LEVEL 6
72#define LIMIT_SECS_PER_MB_RADIO 5
89:
cThread(
"remove deleted recordings", true)
97 if (LockFile.
Lock()) {
98 time_t StartTime = time(NULL);
100 bool interrupted =
false;
102 for (
cRecording *r = DeletedRecordings->First(); r; ) {
114 DeletedRecordings->Del(r);
119 r = DeletedRecordings->Next(r);
137 static time_t LastRemoveCheck = 0;
141 for (
const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->
Next(r)) {
148 LastRemoveCheck = time(NULL);
159 static time_t LastFreeDiskCheck = 0;
160 int Factor = (Priority == -1) ? 10 : 1;
161 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
165 if (!LockFile.
Lock())
168 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
169 int NumDeletedRecordings = 0;
172 NumDeletedRecordings = DeletedRecordings->Count();
173 if (NumDeletedRecordings) {
181 r = DeletedRecordings->
Next(r);
186 DeletedRecordings->Del(r0);
191 if (NumDeletedRecordings == 0) {
196 if (DeletedRecordings->Count())
201 isyslog(
"...no deleted recording found, trying to delete an old recording...");
203 Recordings->SetExplicitModify();
204 if (Recordings->Count()) {
221 r = Recordings->
Next(r);
225 Recordings->SetModified();
230 isyslog(
"...no old recording found, giving up");
233 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
236 LastFreeDiskCheck = time(NULL);
252 esyslog(
"ERROR: can't allocate memory for resume file name");
266 if ((st.st_mode & S_IWUSR) == 0)
272 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
278 else if (errno != ENOENT)
287 while ((s = ReadLine.
Read(f)) != NULL) {
291 case 'I': resume = atoi(t);
298 else if (errno != ENOENT)
309 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
321 fprintf(f,
"I %d\n", Index);
350 else if (errno != ENOENT)
381 for (
int i = 0; i <
MAXAPIDS; i++) {
382 const char *s = Channel->
Alang(i);
387 else if (strlen(s) > strlen(Component->
language))
394 for (
int i = 0; i <
MAXDPIDS; i++) {
395 const char *s = Channel->
Dlang(i);
399 Component =
Components->GetComponent(i, 2, 5);
402 else if (strlen(s) > strlen(Component->
language))
407 for (
int i = 0; i <
MAXSPIDS; i++) {
408 const char *s = Channel->
Slang(i);
413 else if (strlen(s) > strlen(Component->
language))
494 if (fstat(fileno(f), &st))
502 while ((s = ReadLine.
Read(f)) != NULL) {
507 char *p = strchr(t,
' ');
518 unsigned int EventID;
521 unsigned int TableID = 0;
522 unsigned int Version = 0xFF;
523 int n = sscanf(t,
"%u %jd %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
524 if (n >= 3 && n <= 5) {
538 int n = sscanf(t,
"%m[^ ] %hu %hu %c %m[^\n]", &fpsBuf, &
frameWidth, &
frameHeight, &scanTypeCode, &arBuf);
568 case 'O':
errors = atoi(t);
575 esyslog(
"ERROR: EPG data problem in line %d", line);
590 event->Dump(f, Prefix,
true);
595 fprintf(f,
"%sP %d\n", Prefix,
priority);
596 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
597 fprintf(f,
"%sO %d\n", Prefix,
errors);
599 fprintf(f,
"%s@ %s\n", Prefix,
aux);
615 else if (errno != ENOENT)
660#define RESUME_NOT_INITIALIZED (-2)
693 case ' ': *p =
'_';
break;
700 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
704 sprintf(buf,
"#%02X", (
unsigned char)*p);
705 memmove(p + 2, p, strlen(p) + 1);
710 esyslog(
"ERROR: out of memory");
717 case '_': *p =
' ';
break;
722 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
724 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
728 memmove(p + 1, p + 3, strlen(p) - 2);
734 case '\x01': *p =
'\'';
break;
735 case '\x02': *p =
'/';
break;
736 case '\x03': *p =
':';
break;
743 if (*p == (ToFileSystem ? ce->a : ce->b)) {
744 *p = ToFileSystem ? ce->b : ce->a;
766 int Length = strlen(s);
769 bool NameTooLong =
false;
773 for (
char *p = s; *p; p++) {
776 NameTooLong |= NameLength > NameMax;
797 NameTooLong |= NameLength > NameMax;
805 while (i-- > 0 && a[i] >= 0) {
810 if (NameLength > NameMax) {
813 while (i-- > 0 && a[i] >= 0) {
815 if (NameLength - l <= NameMax) {
816 memmove(s + i, s + n, Length - n + 1);
817 memmove(a + i, a + n, Length - n + 1);
830 while (PathLength > PathMax && n > 0) {
835 while (--i > 0 && a[i - 1] >= 0) {
839 if (PathLength - l <= PathMax)
845 memmove(s + b, s + n, Length - n + 1);
872 const char *
Title = Event ? Event->
Title() : NULL;
873 const char *Subtitle = Event ? Event->
ShortText() : NULL;
880 if (macroTITLE || macroEPISODE) {
885 int l = strlen(
name);
932 const char *p = strrchr(
FileName,
'/');
937 time_t now = time(NULL);
939 struct tm t = *localtime_r(&now, &tm_r);
958 FILE *f = fopen(InfoFileName,
"r");
961 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
969 else if (errno != ENOENT)
971#ifdef SUMMARYFALLBACK
975 FILE *f = fopen(SummaryFileName,
"r");
978 char *data[3] = { NULL };
981 while ((s = ReadLine.
Read(f)) != NULL) {
982 if (*s || line > 1) {
985 len += strlen(data[line]) + 1;
986 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
987 data[line] = NewBuffer;
988 strcat(data[line],
"\n");
989 strcat(data[line], s);
992 esyslog(
"ERROR: out of memory");
995 data[line] = strdup(s);
1005 else if (data[1] && data[2]) {
1009 int len = strlen(data[1]);
1011 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
1012 data[1] = NewBuffer;
1013 strcat(data[1],
"\n");
1014 strcat(data[1], data[2]);
1020 esyslog(
"ERROR: out of memory");
1023 info->SetData(data[0], data[1], data[2]);
1024 for (
int i = 0; i < 3; i ++)
1027 else if (errno != ENOENT)
1048 char *t = s, *s1 = NULL, *s2 = NULL;
1069 memmove(s1, s2, t - s2 + 1);
1082 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&
start, &tm_r));
1090 int l = strxfrm(NULL, s, 0) + 1;
1133 int l = strlen(Path);
1153 struct tm *t = localtime_r(&
start, &tm_r);
1169 const char *New = NewIndicator &&
IsNew() ?
"*" :
"";
1170 const char *Err = NewIndicator && (
info->Errors() > 0) ?
"!" :
"";
1175 struct tm *t = localtime_r(&
start, &tm_r);
1210 const char *s =
name;
1243 const char *s =
name;
1286 if (!OtherFileName) {
1289 if (ExistingInfo.
Read())
1314 dsyslog(
"changing priority/lifetime of '%s' to %d/%d",
Name(), NewPriority, NewLifetime);
1324 info->SetFileName(NewFileName);
1338 if (strcmp(NewName,
Name())) {
1339 dsyslog(
"changing name of '%s' to '%s'",
Name(), NewName);
1345 name = strdup(NewName);
1347 bool Exists = access(NewFileName, F_OK) == 0;
1349 esyslog(
"ERROR: recording '%s' already exists", NewName);
1352 name = strdup(OldName);
1357 info->SetFileName(NewFileName);
1367 char *NewName = strdup(
FileName());
1368 char *ext = strrchr(NewName,
'.');
1369 if (ext && strcmp(ext,
RECEXT) == 0) {
1370 strncpy(ext,
DELEXT, strlen(ext));
1371 if (access(NewName, F_OK) == 0) {
1373 isyslog(
"removing recording '%s'", NewName);
1377 if (access(
FileName(), F_OK) == 0) {
1404 char *NewName = strdup(
FileName());
1405 char *ext = strrchr(NewName,
'.');
1406 if (ext && strcmp(ext,
DELEXT) == 0) {
1407 strncpy(ext,
RECEXT, strlen(ext));
1408 if (access(NewName, F_OK) == 0) {
1410 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1462 if (IndexLength > 0) {
1505 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1507 virtual void Action(
void);
1514:
cThread(
"video directory scanner", true)
1550 if (lstat(buffer, &st) == 0) {
1552 if (S_ISLNK(st.st_mode)) {
1554 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1558 if (stat(buffer, &st) != 0)
1561 if (S_ISDIR(st.st_mode)) {
1569 Recordings->
Lock(StateKey,
true);
1571 dsyslog(
"activated name checking for initial read of video directory");
1599 if (!
initial && DirLevel == 0) {
1605 if (access(r->
FileName(), F_OK) != 0)
1651 if (lastModified > time(NULL))
1671 if (Recording->Id() == Id)
1681 if (strcmp(Recording->FileName(), FileName) == 0)
1708 Recording = dummy =
new cRecording(FileName);
1711 Del(Recording,
false);
1712 char *ext = strrchr(Recording->
fileName,
'.');
1714 strncpy(ext,
DELEXT, strlen(ext));
1715 if (access(Recording->
FileName(), F_OK) == 0) {
1717 DeletedRecordings->Add(Recording);
1728 Recording->ReadInfo();
1735 int FileSizeMB = Recording->FileSizeMB();
1736 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1747 if (Recording->IsOnVideoDirectoryFileSystem()) {
1748 int FileSizeMB = Recording->FileSizeMB();
1749 if (FileSizeMB > 0) {
1750 int LengthInSeconds = Recording->LengthInSeconds();
1751 if (LengthInSeconds > 0) {
1754 length += LengthInSeconds;
1760 return (size && length) ? double(size) * 60 / length : -1;
1767 if (Recording->IsInPath(Path))
1768 Use |= Recording->IsInUse();
1777 if (Recording->IsInPath(Path))
1785 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1786 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1789 if (Recording->IsInPath(OldPath)) {
1790 const char *p = Recording->Name() + strlen(OldPath);
1792 if (!Recording->ChangeName(NewName))
1806 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1807 Recording->ResetResume();
1814 Recording->ClearSortName();
1826 virtual void Action(
void);
1828 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1851 dsyslog(
"suspending copy thread");
1857 dsyslog(
"resuming copy thread");
1874 size_t BufferSize = BUFSIZ;
1875 uchar *Buffer = NULL;
1889 size_t Read =
safe_read(From, Buffer, BufferSize);
1891 size_t Written =
safe_write(To, Buffer, Read);
1892 if (Written != Read) {
1893 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1897 else if (Read == 0) {
1899 if (fsync(To) < 0) {
1900 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1903 if (close(From) < 0) {
1904 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1907 if (close(To) < 0) {
1908 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1912 off_t FileSizeSrc =
FileSize(FileNameSrc);
1913 off_t FileSizeDst =
FileSize(FileNameDst);
1914 if (FileSizeSrc != FileSizeDst) {
1915 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1920 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1924 else if ((e = d.
Next()) != NULL) {
1929 if (stat(FileNameSrc, &st) < 0) {
1930 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1933 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1934 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1937 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1939 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1942 esyslog(
"ERROR: out of memory");
1946 if (access(FileNameDst, F_OK) == 0) {
1947 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1950 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1951 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1954 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1955 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1994 int Usage(
const char *FileName = NULL)
const;
2022 if (FileName && *FileName) {
2071 if (Recording.
Delete()) {
2091 Recording->Delete();
2105 Recording->Delete();
2135 Recordings->SetExplicitModify();
2138 if (!r->Active(Recordings)) {
2139 error |= r->Error();
2140 r->Cleanup(Recordings);
2156 if (FileName && *FileName) {
2160 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2169 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2172 if (FileNameSrc && *FileNameSrc) {
2173 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2175 if (Usage ==
ruCut && !FileNameDst)
2177 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2185 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2188 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2191 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2194 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2216 return r->Usage(FileName);
2222 int RequiredDiskSpaceMB = 0;
2226 if ((r->Usage() &
ruCut) != 0) {
2232 RequiredDiskSpaceMB +=
DirSizeMB(r->FileNameSrc());
2235 return RequiredDiskSpaceMB;
2276 const char *p = strchr(s,
' ');
2287 return fprintf(f,
"%s\n", *
ToText()) > 0;
2300 if (errno != ENOENT) {
2308bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2322 time_t t = time(NULL);
2326 lastChange = LastModified > 0 ? LastModified : t;
2365 if (m->Position() - p) {
2376 if (m2->Position() < m1->Position()) {
2377 swap(m1->position, m2->position);
2378 swap(m1->comment, m2->comment);
2393 if (mi->Position() == Position)
2402 if (mi->Position() < Position)
2411 if (mi->Position() > Position)
2420 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2421 while (
const cMark *NextMark =
Next(BeginMark)) {
2422 if (BeginMark->
Position() == NextMark->Position()) {
2423 if (!(BeginMark =
Next(NextMark)))
2438 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2439 while (
const cMark *NextMark =
Next(EndMark)) {
2440 if (EndMark->
Position() == NextMark->Position()) {
2441 if (!(EndMark =
Next(NextMark)))
2453 int NumSequences = 0;
2461 if (NumSequences == 1 && BeginMark->Position() == 0)
2465 return NumSequences;
2470 if (
Count() == 0 || LastFrame < 0 || Frame < 0 || Frame > LastFrame)
2472 int EditedFrame = 0;
2474 bool InEdit =
false;
2476 int p = mi->Position();
2478 EditedFrame += p - PrevPos;
2481 EditedFrame -= p - Frame;
2493 EditedFrame += LastFrame - PrevPos;
2494 if (Frame < LastFrame)
2495 EditedFrame -= LastFrame - Frame;
2512 isyslog(
"executing '%s'", *cmd);
2519#define IFG_BUFFER_SIZE KILOBYTE(100)
2526 virtual void Action(
void);
2533:
cThread(
"index file generator")
2547 bool IndexFileComplete =
false;
2548 bool IndexFileWritten =
false;
2549 bool Rewind =
false;
2558 off_t FrameOffset = -1;
2559 uint16_t FileNumber = 1;
2560 off_t FileOffset = 0;
2562 bool pendIndependentFrame =
false;
2563 uint16_t pendNumber = 0;
2564 off_t pendFileSize = 0;
2565 bool pendErrors =
false;
2566 bool pendMissing =
false;
2572 Last = IndexFile.
Last();
2573 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2577 isyslog(
"updating index file");
2580 isyslog(
"generating index file");
2584 bool Stuffed =
false;
2588 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2597 if (FrameDetector.
Synced()) {
2601 int Processed = FrameDetector.
Analyze(Data, Length);
2602 if (Processed > 0) {
2603 int PreviousErrors = 0;
2604 int MissingFrames = 0;
2605 if (FrameDetector.
NewFrame(&PreviousErrors, &MissingFrames)) {
2606 if (IndexFileWritten || Last < 0) {
2608 IndexFile.
Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
2610 pendNumber = FileName.
Number();
2611 pendFileSize = FrameOffset >= 0 ? FrameOffset :
FileSize;
2612 pendErrors = PreviousErrors;
2613 pendMissing = MissingFrames;
2616 IndexFileWritten =
true;
2623 Buffer.
Del(Processed);
2628 int Processed = FrameDetector.
Analyze(Data, Length,
false);
2629 if (Processed > 0) {
2630 if (FrameDetector.
Synced()) {
2634 Buffer.
Del(Processed);
2644 else if (PatPmtParser.
IsPmtPid(Pid))
2650 FrameDetector.
SetPid(PatPmtParser.
Vpid() ? PatPmtParser.
Vpid() : PatPmtParser.
Apid(0), PatPmtParser.
Vpid() ? PatPmtParser.
Vtype() : PatPmtParser.
Atype(0));
2656 Buffer.
Del(p - Data);
2660 else if (ReplayFile) {
2661 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2663 if (Buffer.
Available() > 0 && !Stuffed) {
2672 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2687 IndexFile.
Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
2688 IndexFileComplete =
true;
2693 if (IndexFileComplete) {
2694 if (IndexFileWritten) {
2696 if (RecordingInfo.
Read()) {
2701 Errors != RecordingInfo.
Errors()) {
2705 RecordingInfo.
Write();
2710 Skins.QueueMessage(
mtInfo,
tr(
"Index file regeneration complete"));
2714 Skins.QueueMessage(
mtError,
tr(
"Index file regeneration failed!"));
2722#define INDEXFILESUFFIX "/index"
2725#define MAXINDEXCATCHUP 8
2726#define INDEXCATCHUPWAIT 100
2742 tIndexTs(off_t Offset,
bool Independent, uint16_t Number,
bool Errors,
bool Missing)
2748 independent = Independent;
2753#define MAXWAITFORINDEXFILE 10
2754#define INDEXFILECHECKINTERVAL 500
2755#define INDEXFILETESTINTERVAL 10
2769 if (!Record && PauseLive) {
2772 while (time(NULL) < tmax &&
FileSize(
fileName) < off_t(2 *
sizeof(tIndexTs)))
2785 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2791 delta = int(buf.st_size %
sizeof(tIndexTs));
2793 delta =
sizeof(tIndexTs) - delta;
2794 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2796 last = int((buf.st_size + delta) /
sizeof(tIndexTs) - 1);
2797 if ((!Record || Update) &&
last >= 0) {
2822 esyslog(
"ERROR: can't allocate %zd bytes for index '%s'",
size *
sizeof(tIndexTs), *
fileName);
2834 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2836 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2863 while (Count-- > 0) {
2864 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2865 IndexTs->offset = IndexPes.offset;
2866 IndexTs->independent = IndexPes.type == 1;
2867 IndexTs->number = IndexPes.number;
2875 while (Count-- > 0) {
2876 IndexPes.offset = uint32_t(IndexTs->offset);
2877 IndexPes.type =
uchar(IndexTs->independent ? 1 : 2);
2878 IndexPes.number =
uchar(IndexTs->number);
2879 IndexPes.reserved = 0;
2880 memcpy((
void *)IndexTs, &IndexPes,
sizeof(*IndexTs));
2894 if (fstat(
f, &buf) == 0) {
2895 int newLast = int(buf.st_size /
sizeof(tIndexTs) - 1);
2896 if (newLast >
last) {
2898 if (NewSize <= newLast) {
2900 if (NewSize <= newLast)
2901 NewSize = newLast + 1;
2903 if (tIndexTs *NewBuffer = (tIndexTs *)realloc(
index, NewSize *
sizeof(tIndexTs))) {
2906 int offset = (
last + 1) *
sizeof(tIndexTs);
2907 int delta = (newLast -
last) *
sizeof(tIndexTs);
2908 if (lseek(
f, offset, SEEK_SET) == offset) {
2910 esyslog(
"ERROR: can't read from index");
2925 esyslog(
"ERROR: can't realloc() index");
2938 return index != NULL;
2941bool cIndexFile::Write(
bool Independent, uint16_t FileNumber, off_t FileOffset,
bool Errors,
bool Missing)
2944 tIndexTs i(FileOffset, Independent, FileNumber, Errors, Missing);
2958bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length,
bool *Errors,
bool *Missing)
2961 if (Index >= 0 && Index <=
last) {
2962 *FileNumber =
index[Index].number;
2963 *FileOffset =
index[Index].offset;
2965 *Independent =
index[Index].independent;
2968 uint16_t fn =
index[Index + 1].number;
2969 off_t fo =
index[Index + 1].offset;
2970 if (fn == *FileNumber)
2971 *Length = int(fo - *FileOffset);
2979 *Errors =
index[Index].errors;
2981 *Missing =
index[Index].missing;
2991 tIndexTs *p = &
index[Index];
2992 if (p->errors || p->missing)
3002 int d = Forward ? 1 : -1;
3005 if (Index >= 0 && Index <=
last) {
3006 if (
index[Index].independent) {
3013 *FileNumber =
index[Index].number;
3014 *FileOffset =
index[Index].offset;
3017 uint16_t fn =
index[Index + 1].number;
3018 off_t fo =
index[Index + 1].offset;
3019 if (fn == *FileNumber)
3020 *Length = int(fo - *FileOffset);
3041 if (
index[Index].independent)
3047 if (
index[il].independent)
3054 if (
index[ih].independent)
3070 for (i = 0; i <=
last; i++) {
3071 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
3100 if (*s && stat(s, &buf) == 0)
3101 return buf.st_size / (IsPesRecording ?
sizeof(tIndexTs) :
sizeof(tIndexPes));
3109 if (Recording.
Name()) {
3113 unlink(IndexFileName);
3115 while (IndexFileGenerator->
Active())
3117 if (access(IndexFileName, R_OK) == 0)
3120 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
3123 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
3126 fprintf(stderr,
"'%s' is not a recording\n", FileName);
3129 fprintf(stderr,
"'%s' is not a directory\n", FileName);
3135#define MAXFILESPERRECORDINGPES 255
3136#define RECORDFILESUFFIXPES "/%03d.vdr"
3137#define MAXFILESPERRECORDINGTS 65535
3138#define RECORDFILESUFFIXTS "/%05d.ts"
3139#define RECORDFILESUFFIXLEN 20
3151 esyslog(
"ERROR: can't copy file name '%s'", FileName);
3181 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
3183 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
3187 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
3189 int Pid =
TsPid(buf);
3191 PatPmtParser.
ParsePat(buf,
sizeof(buf));
3192 else if (PatPmtParser.
IsPmtPid(Pid)) {
3193 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
3194 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
3205 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
3219 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
3233 else if (errno != ENOENT)
3243 if (
file->Close() < 0)
3263 if (buf.st_size != 0)
3267 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3274 else if (errno != ENOENT) {
3281 if (!
record && Offset >= 0 &&
file->Seek(Offset, SEEK_SET) != Offset) {
3288 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3310 while ((s = ReadLine.
Read(f)) != NULL)
3328 if (fputs(
doneRecordings[i], f) == EOF || fputc(
'\n', f) == EOF) {
3350 if (FILE *f = fopen(
fileName,
"a")) {
3356 esyslog(
"ERROR: can't open '%s' for appending '%s'", *
fileName, Title);
3373 const char *t = Title;
3379 if (toupper(
uchar(*s)) != toupper(
uchar(*t)))
3394 const char *Sign =
"";
3400 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3401 int s = int(Seconds);
3402 int m = s / 60 % 60;
3405 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3411 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3415 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3421 return int(round(Seconds * FramesPerSecond));
3430 else if (Length > Max) {
3431 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3434 int r = f->
Read(b, Length);
3454 if (fgets(buf,
sizeof(buf), f))
3483 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3484 if (FILE *f = fopen(FileName,
"w")) {
3485 fprintf(f,
"%s\n", TimerId);
3492 dsyslog(
"removing %s", *FileName);
3500 const char *Id = NULL;
3501 if (FILE *f = fopen(FileName,
"r")) {
3502 char buf[HOST_NAME_MAX + 10];
3503 if (fgets(buf,
sizeof(buf), f)) {
3517 if (FileSizeMB > 0) {
3520 if (NumFramesOrg > 0) {
3522 if (NumFramesEdit > 0)
3523 return max(1,
int(FileSizeMB * (
double(NumFramesEdit) / NumFramesOrg)));
3532 if (FileSizeMB > 0) {
3536 if (access(EditedFileName, F_OK)) {
3537 int ExistingEditedSizeMB =
DirSizeMB(EditedFileName);
3538 if (ExistingEditedSizeMB > 0)
3539 FreeDiskMB += ExistingEditedSizeMB;
3543 return FileSizeMB < FreeDiskMB;
const char * Slang(int i) const
const char * Name(void) const
tChannelID GetChannelID(void) const
const char * Dlang(int i) const
const char * Alang(int i) const
bool TimedWait(cMutex &Mutex, int TimeoutMs)
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
bool Load(const char *FileName=NULL, bool AllowComments=false, bool MustExist=false)
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
cDirCopier(const char *DirNameSrc, const char *DirNameDst)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cStringList doneRecordings
void Add(const char *Title)
void Append(const char *Title)
bool Load(const char *FileName)
bool Contains(const char *Title) const
const char * ShortText(void) const
const char * Title(void) const
cUnbufferedFile * NextFile(void)
cUnbufferedFile * Open(void)
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
uint16_t FrameWidth(void)
Returns the frame width, or 0 if this information is not available.
eScanType ScanType(void)
Returns the scan type, or stUnknown if this information is not available.
bool NewFrame(int *PreviousErrors=NULL, int *MissingFrames=NULL)
Returns true if the data given to the last call to Analyze() started a new frame.
int Analyze(const uchar *Data, int Length, bool ErrorCheck=true)
Analyzes the TS packets pointed to by Data.
uint16_t FrameHeight(void)
Returns the frame height, or 0 if this information is not available.
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
eAspectRatio AspectRatio(void)
Returns the aspect ratio, or arUnknown if this information is not available.
cIndexFileGenerator(const char *RecordingName, bool Update=false)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset, bool Errors=false, bool Missing=false)
bool IsStillRecording(void)
void ConvertFromPes(tIndexTs *IndexTs, int Count)
static int GetLength(const char *FileName, bool IsPesRecording=false)
Calculates the recording length (number of frames) without actually reading the index file.
bool CatchUp(int Index=-1)
const cErrors * GetErrors(void)
Returns the frame indexes of errors in the recording (if any).
void ConvertToPes(tIndexTs *IndexTs, int Count)
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false, bool Update=false)
cIndexFileGenerator * indexFileGenerator
static cString IndexFileName(const char *FileName, bool IsPesRecording)
int GetClosestIFrame(int Index)
Returns the index of the I-frame that is closest to the given Index (or Index itself,...
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL, bool *Errors=NULL, bool *Missing=NULL)
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
void Del(cListObject *Object, bool DeleteObject=true)
void SetModified(void)
Unconditionally marks this list as modified.
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
void Add(cListObject *Object, cListObject *After=NULL)
cListObject(const cListObject &ListObject)
cListObject * Next(void) const
const cMark * Prev(const cMark *Object) const
const cRecording * First(void) const
cList(const char *NeedsLocking=NULL)
const cRecording * Next(const cRecording *Object) const
const cMark * Last(void) const
bool Lock(int WaitSeconds=0)
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
bool Parse(const char *s)
const char * Comment(void) const
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
const cMark * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark.
const cMark * GetNext(int Position) const
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
const cMark * GetNextEnd(const cMark *BeginMark) const
Returns the next "end" mark after BeginMark, skipping any marks at the same position as BeginMark.
const cMark * Get(int Position) const
cString recordingFileName
static bool DeleteMarksFile(const cRecording *Recording)
int GetFrameAfterEdit(int Frame, int LastFrame) const
Returns the number of the given Frame within the region covered by begin/end sequences.
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists).
const cMark * GetPrev(int Position) const
bool GetVersions(int &PatVersion, int &PmtVersion) const
Returns true if a valid PAT/PMT has been parsed and stores the current version numbers in the given v...
int Vtype(void) const
Returns the video stream type as defined by the current PMT, or 0 if no video stream type has been de...
void ParsePat(const uchar *Data, int Length)
Parses the PAT data from the single TS packet in Data.
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
bool Completed(void)
Returns true if the PMT has been completely parsed.
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected,...
struct dirent * Next(void)
static cRecordControl * GetRecordControl(const char *FileName)
char ScanTypeChar(void) const
void SetFramesPerSecond(double FramesPerSecond)
uint16_t FrameHeight(void) const
const char * AspectRatioText(void) const
const char * ShortText(void) const
eScanType ScanType(void) const
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
bool Write(FILE *f, const char *Prefix="") const
const char * Title(void) const
cString FrameParams(void) const
const char * Aux(void) const
void SetFileName(const char *FileName)
uint16_t FrameWidth(void) const
void SetFrameParams(uint16_t FrameWidth, uint16_t FrameHeight, eScanType ScanType, eAspectRatio AspectRatio)
void SetErrors(int Errors)
void SetAux(const char *Aux)
void SetData(const char *Title, const char *ShortText, const char *Description)
const char * Description(void) const
eAspectRatio AspectRatio(void) const
double FramesPerSecond(void) const
const cComponents * Components(void) const
static const char * command
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
int isOnVideoDirectoryFileSystem
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater",...
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
bool HasMarks(void) const
Returns true if this recording has any editing marks.
bool WriteInfo(const char *OtherFileName=NULL)
Writes in info file of this recording.
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with.
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
bool Undelete(void)
Changes the file name so that it will be visible in the "Recordings" menu again and not processed by ...
void ResetResume(void) const
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
int NumFrames(void) const
Returns the number of frames in this recording.
bool IsEdited(void) const
int GetResume(void) const
Returns the index of the frame where replay of this recording shall be resumed, or -1 in case of an e...
bool IsInPath(const char *Path) const
Returns true if this recording is stored anywhere under the given Path.
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
char * SortName(void) const
const char * Name(void) const
Returns the full name of the recording (without the video directory).
int NumFramesAfterEdit(void) const
Returns the number of frames in the edited version of this recording.
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
const char * PrefixFileName(char Prefix)
bool DeleteMarks(void)
Deletes the editing marks from this recording (if any).
bool IsOnVideoDirectoryFileSystem(void) const
int HierarchyLevels(void) const
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder).
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
cRecording(const cRecording &)
int LengthInSecondsAfterEdit(void) const
Returns the length (in seconds) of the edited version of this recording, or -1 in case of error.
double FramesPerSecond(void) const
bool IsPesRecording(void) const
static char * StripEpisodeName(char *s, bool Strip)
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
const char * FileNameSrc(void) const
void Cleanup(cRecordings *Recordings)
~cRecordingsHandlerEntry()
int Usage(const char *FileName=NULL) const
bool Active(cRecordings *Recordings)
const char * FileNameDst(void) const
cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
void DelAll(void)
Deletes/terminates all operations.
cRecordingsHandlerEntry * Get(const char *FileName)
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
bool Finished(bool &Error)
Returns true if all operations in the list have been finished.
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
cList< cRecordingsHandlerEntry > operations
void Del(const char *FileName)
Deletes the given FileName from the list of operations.
virtual ~cRecordingsHandler()
int GetRequiredDiskSpaceMB(const char *FileName=NULL)
Returns the total disk space required to process all actions.
void ResetResume(const char *ResumeFileName=NULL)
void UpdateByName(const char *FileName)
static const char * UpdateFileName(void)
double MBperMinute(void) const
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown.
cRecordings(bool Deleted=false)
int GetNumRecordingsInPath(const char *Path) const
Returns the total number of recordings in the given Path, including all sub-folders of Path.
const cRecording * GetById(int Id) const
static cRecordings deletedRecordings
void AddByName(const char *FileName, bool TriggerUpdate=true)
static cRecordings recordings
int TotalFileSizeMB(void) const
static void Update(bool Wait=false)
Triggers an update of the list of recordings, which will run as a separate thread if Wait is false.
static cRecordings * GetRecordingsWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for write access.
static void TouchUpdate(void)
Touches the '.update' file in the video directory, so that other instances of VDR that access the sam...
void Add(cRecording *Recording)
static cVideoDirectoryScannerThread * videoDirectoryScannerThread
void DelByName(const char *FileName)
bool MoveRecordings(const char *OldPath, const char *NewPath)
Moves all recordings in OldPath to NewPath.
static bool NeedsUpdate(void)
void ClearSortNames(void)
static int lastRecordingId
const cRecording * GetByName(const char *FileName) const
static char * updateFileName
int PathIsInUse(const char *Path) const
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
static bool HasKeys(void)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cRemoveDeletedRecordingsThread(void)
static const char * NowReplaying(void)
cResumeFile(const char *FileName, bool IsPesRecording)
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
virtual int Available(void)
virtual void Clear(void)
Immediately clears the ring buffer.
uchar * Get(int &Count)
Gets data from the ring buffer.
int Read(int FileHandle, int Max=0)
Reads at most Max bytes from FileHandle and stores them in the ring buffer.
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
static cString sprintf(const char *fmt,...) __attribute__((format(printf
cString & Append(const char *String)
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
bool Active(void)
Checks whether the thread is still alive.
const char * Aux(void) const
const char * File(void) const
bool IsSingleEvent(void) const
void SetFile(const char *File)
time_t StartTime(void) const
the start time as given by the user
const cChannel * Channel(void) const
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
ssize_t Read(void *Data, size_t Size)
cRecordings * deletedRecordings
void ScanVideoDir(const char *DirName, int LinkLevel=0, int DirLevel=0)
~cVideoDirectoryScannerThread()
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
static cString PrefixVideoFileName(const char *FileName, char Prefix)
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]=NULL)
static bool IsOnVideoDirectoryFileSystem(const char *FileName)
static const char * Name(void)
static cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
static bool VideoFileSpaceAvailable(int SizeMB)
static bool MoveVideoFile(const char *FromName, const char *ToName)
static int VideoDiskSpace(int *FreeMB=NULL, int *UsedMB=NULL)
static bool RenameVideoFile(const char *OldName, const char *NewName)
static bool RemoveVideoFile(const char *FileName)
#define TIMERMACRO_EPISODE
#define MAXFILESPERRECORDINGTS
tCharExchange CharExchange[]
cString GetRecordingTimerId(const char *Directory)
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
static const char * SkipFuzzyChars(const char *s)
void AssertFreeDiskSpace(int Priority, bool Force)
The special Priority value -1 means that we shall get rid of any deleted recordings faster than norma...
void GetRecordingsSortMode(const char *Directory)
char * LimitNameLengths(char *s, int PathMax, int NameMax)
static const char * FuzzyChars
bool NeedsConversion(const char *p)
int SecondsToFrames(int Seconds, double FramesPerSecond)
eRecordingsSortMode RecordingsSortMode
bool HasRecordingsSortMode(const char *Directory)
#define MAXFILESPERRECORDINGPES
#define INDEXFILETESTINTERVAL
#define MAXWAITFORINDEXFILE
bool EnoughFreeDiskSpaceForEdit(const char *FileName)
#define INDEXFILECHECKINTERVAL
char * ExchangeChars(char *s, bool ToFileSystem)
void IncRecordingsSortMode(const char *Directory)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
#define LIMIT_SECS_PER_MB_RADIO
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
cDoneRecordings DoneRecordingsPattern
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
int FileSizeMBafterEdit(const char *FileName)
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
cRecordingsHandler RecordingsHandler
cMutex MutexMarkFramesPerSecond
static bool StillRecording(const char *Directory)
struct __attribute__((packed))
#define RESUME_NOT_INITIALIZED
#define RECORDFILESUFFIXLEN
#define RECORDFILESUFFIXPES
void SetRecordingTimerId(const char *Directory, const char *TimerId)
#define RECORDFILESUFFIXTS
double MarkFramesPerSecond
const char * InvalidChars
void RemoveDeletedRecordings(void)
#define SUMMARYFILESUFFIX
#define DEFAULTFRAMESPERSECOND
int HMSFToIndex(const char *HMSF, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
eRecordingsSortMode RecordingsSortMode
#define RUC_COPIEDRECORDING
#define LOCK_DELETEDRECORDINGS_WRITE
char * ExchangeChars(char *s, bool ToFileSystem)
#define RUC_DELETERECORDING
#define RUC_MOVEDRECORDING
int FileSizeMBafterEdit(const char *FileName)
cRecordingsHandler RecordingsHandler
#define RUC_COPYINGRECORDING
#define LOCK_DELETEDRECORDINGS_READ
#define LOCK_RECORDINGS_WRITE
cString IndexToHMSF(int Index, bool WithFrame=false, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
const char * AspectRatioTexts[]
const char * ScanTypeChars
int TsPid(const uchar *p)
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
static const tChannelID InvalidID
static tChannelID FromString(const char *s)
char language[MAXLANGCODE2]
int SystemExec(const char *Command, bool Detached)