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)
380 for (
int i = 0; i <
MAXAPIDS; i++) {
381 const char *s = Channel->
Alang(i);
386 else if (strlen(s) > strlen(Component->
language))
393 for (
int i = 0; i <
MAXDPIDS; i++) {
394 const char *s = Channel->
Dlang(i);
401 else if (strlen(s) > strlen(Component->
language))
406 for (
int i = 0; i <
MAXSPIDS; i++) {
407 const char *s = Channel->
Slang(i);
412 else if (strlen(s) > strlen(Component->
language))
494 while ((s = ReadLine.
Read(f)) != NULL) {
499 char *p = strchr(t,
' ');
510 unsigned int EventID;
513 unsigned int TableID = 0;
514 unsigned int Version = 0xFF;
515 int n = sscanf(t,
"%u %jd %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
516 if (n >= 3 && n <= 5) {
530 int n = sscanf(t,
"%m[^ ] %hu %hu %c %m[^\n]", &fpsBuf, &
frameWidth, &
frameHeight, &scanTypeCode, &arBuf);
560 case 'O':
errors = atoi(t);
567 esyslog(
"ERROR: EPG data problem in line %d", line);
582 event->Dump(f, Prefix,
true);
587 fprintf(f,
"%sP %d\n", Prefix,
priority);
588 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
589 fprintf(f,
"%sO %d\n", Prefix,
errors);
591 fprintf(f,
"%s@ %s\n", Prefix,
aux);
607 else if (errno != ENOENT)
652#define RESUME_NOT_INITIALIZED (-2)
685 case ' ': *p =
'_';
break;
692 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
696 sprintf(buf,
"#%02X", (
unsigned char)*p);
697 memmove(p + 2, p, strlen(p) + 1);
702 esyslog(
"ERROR: out of memory");
709 case '_': *p =
' ';
break;
714 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
716 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
720 memmove(p + 1, p + 3, strlen(p) - 2);
726 case '\x01': *p =
'\'';
break;
727 case '\x02': *p =
'/';
break;
728 case '\x03': *p =
':';
break;
735 if (*p == (ToFileSystem ? ce->a : ce->b)) {
736 *p = ToFileSystem ? ce->b : ce->a;
758 int Length = strlen(s);
761 bool NameTooLong =
false;
765 for (
char *p = s; *p; p++) {
768 NameTooLong |= NameLength > NameMax;
789 NameTooLong |= NameLength > NameMax;
797 while (i-- > 0 && a[i] >= 0) {
802 if (NameLength > NameMax) {
805 while (i-- > 0 && a[i] >= 0) {
807 if (NameLength - l <= NameMax) {
808 memmove(s + i, s + n, Length - n + 1);
809 memmove(a + i, a + n, Length - n + 1);
822 while (PathLength > PathMax && n > 0) {
827 while (--i > 0 && a[i - 1] >= 0) {
831 if (PathLength - l <= PathMax)
837 memmove(s + b, s + n, Length - n + 1);
864 const char *
Title = Event ? Event->
Title() : NULL;
865 const char *Subtitle = Event ? Event->
ShortText() : NULL;
872 if (macroTITLE || macroEPISODE) {
877 int l = strlen(
name);
924 const char *p = strrchr(
FileName,
'/');
929 time_t now = time(NULL);
931 struct tm t = *localtime_r(&now, &tm_r);
950 FILE *f = fopen(InfoFileName,
"r");
953 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
961 else if (errno != ENOENT)
963#ifdef SUMMARYFALLBACK
967 FILE *f = fopen(SummaryFileName,
"r");
970 char *data[3] = { NULL };
973 while ((s = ReadLine.
Read(f)) != NULL) {
974 if (*s || line > 1) {
977 len += strlen(data[line]) + 1;
978 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
979 data[line] = NewBuffer;
980 strcat(data[line],
"\n");
981 strcat(data[line], s);
984 esyslog(
"ERROR: out of memory");
987 data[line] = strdup(s);
997 else if (data[1] && data[2]) {
1001 int len = strlen(data[1]);
1003 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
1004 data[1] = NewBuffer;
1005 strcat(data[1],
"\n");
1006 strcat(data[1], data[2]);
1012 esyslog(
"ERROR: out of memory");
1016 for (
int i = 0; i < 3; i ++)
1019 else if (errno != ENOENT)
1040 char *t = s, *s1 = NULL, *s2 = NULL;
1061 memmove(s1, s2, t - s2 + 1);
1074 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&
start, &tm_r));
1082 int l = strxfrm(NULL, s, 0) + 1;
1125 int l = strlen(Path);
1145 struct tm *t = localtime_r(&
start, &tm_r);
1161 const char *New = NewIndicator &&
IsNew() ?
"*" :
"";
1162 const char *Err = NewIndicator && (
info->
Errors() > 0) ?
"!" :
"";
1167 struct tm *t = localtime_r(&
start, &tm_r);
1202 const char *s =
name;
1235 const char *s =
name;
1278 if (!OtherFileName) {
1281 if (ExistingInfo.
Read())
1306 dsyslog(
"changing priority/lifetime of '%s' to %d/%d",
Name(), NewPriority, NewLifetime);
1330 if (strcmp(NewName,
Name())) {
1331 dsyslog(
"changing name of '%s' to '%s'",
Name(), NewName);
1337 name = strdup(NewName);
1339 bool Exists = access(NewFileName, F_OK) == 0;
1341 esyslog(
"ERROR: recording '%s' already exists", NewName);
1344 name = strdup(OldName);
1358 char *NewName = strdup(
FileName());
1359 char *ext = strrchr(NewName,
'.');
1360 if (ext && strcmp(ext,
RECEXT) == 0) {
1361 strncpy(ext,
DELEXT, strlen(ext));
1362 if (access(NewName, F_OK) == 0) {
1364 isyslog(
"removing recording '%s'", NewName);
1368 if (access(
FileName(), F_OK) == 0) {
1395 char *NewName = strdup(
FileName());
1396 char *ext = strrchr(NewName,
'.');
1397 if (ext && strcmp(ext,
DELEXT) == 0) {
1398 strncpy(ext,
RECEXT, strlen(ext));
1399 if (access(NewName, F_OK) == 0) {
1401 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1453 if (IndexLength > 0) {
1496 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1498 virtual void Action(
void);
1505:
cThread(
"video directory scanner", true)
1541 if (lstat(buffer, &st) == 0) {
1543 if (S_ISLNK(st.st_mode)) {
1545 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1549 if (stat(buffer, &st) != 0)
1552 if (S_ISDIR(st.st_mode)) {
1560 Recordings->
Lock(StateKey,
true);
1562 dsyslog(
"activated name checking for initial read of video directory");
1590 if (!
initial && DirLevel == 0) {
1596 if (access(r->
FileName(), F_OK) != 0)
1642 if (lastModified > time(NULL))
1662 if (Recording->Id() == Id)
1672 if (strcmp(Recording->FileName(), FileName) == 0)
1699 Recording = dummy =
new cRecording(FileName);
1702 Del(Recording,
false);
1703 char *ext = strrchr(Recording->
fileName,
'.');
1705 strncpy(ext,
DELEXT, strlen(ext));
1706 if (access(Recording->
FileName(), F_OK) == 0) {
1708 DeletedRecordings->Add(Recording);
1719 Recording->ReadInfo();
1726 int FileSizeMB = Recording->FileSizeMB();
1727 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1738 if (Recording->IsOnVideoDirectoryFileSystem()) {
1739 int FileSizeMB = Recording->FileSizeMB();
1740 if (FileSizeMB > 0) {
1741 int LengthInSeconds = Recording->LengthInSeconds();
1742 if (LengthInSeconds > 0) {
1745 length += LengthInSeconds;
1751 return (size && length) ? double(size) * 60 / length : -1;
1758 if (Recording->IsInPath(Path))
1759 Use |= Recording->IsInUse();
1768 if (Recording->IsInPath(Path))
1776 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1777 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1780 if (Recording->IsInPath(OldPath)) {
1781 const char *p = Recording->Name() + strlen(OldPath);
1783 if (!Recording->ChangeName(NewName))
1797 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1798 Recording->ResetResume();
1805 Recording->ClearSortName();
1817 virtual void Action(
void);
1819 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1842 dsyslog(
"suspending copy thread");
1848 dsyslog(
"resuming copy thread");
1865 size_t BufferSize = BUFSIZ;
1866 uchar *Buffer = NULL;
1880 size_t Read =
safe_read(From, Buffer, BufferSize);
1882 size_t Written =
safe_write(To, Buffer, Read);
1883 if (Written != Read) {
1884 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1888 else if (Read == 0) {
1890 if (fsync(To) < 0) {
1891 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1894 if (close(From) < 0) {
1895 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1898 if (close(To) < 0) {
1899 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1903 off_t FileSizeSrc =
FileSize(FileNameSrc);
1904 off_t FileSizeDst =
FileSize(FileNameDst);
1905 if (FileSizeSrc != FileSizeDst) {
1906 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1911 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1915 else if ((e = d.
Next()) != NULL) {
1920 if (stat(FileNameSrc, &st) < 0) {
1921 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1924 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1925 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1928 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1930 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1933 esyslog(
"ERROR: out of memory");
1937 if (access(FileNameDst, F_OK) == 0) {
1938 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1941 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1942 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1945 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1946 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1985 int Usage(
const char *FileName = NULL)
const;
2013 if (FileName && *FileName) {
2062 if (Recording.
Delete()) {
2082 Recording->Delete();
2096 Recording->Delete();
2126 Recordings->SetExplicitModify();
2129 if (!r->Active(Recordings)) {
2130 error |= r->Error();
2131 r->Cleanup(Recordings);
2147 if (FileName && *FileName) {
2151 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2160 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2163 if (FileNameSrc && *FileNameSrc) {
2164 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2166 if (Usage ==
ruCut && !FileNameDst)
2168 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2176 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2179 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2182 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2185 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2207 return r->Usage(FileName);
2213 int RequiredDiskSpaceMB = 0;
2217 if ((r->Usage() &
ruCut) != 0) {
2223 RequiredDiskSpaceMB +=
DirSizeMB(r->FileNameSrc());
2226 return RequiredDiskSpaceMB;
2267 const char *p = strchr(s,
' ');
2278 return fprintf(f,
"%s\n", *
ToText()) > 0;
2291 if (errno != ENOENT) {
2299bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2313 time_t t = time(NULL);
2317 lastChange = LastModified > 0 ? LastModified : t;
2356 if (m->Position() - p) {
2367 if (m2->Position() < m1->Position()) {
2368 swap(m1->position, m2->position);
2369 swap(m1->comment, m2->comment);
2384 if (mi->Position() == Position)
2393 if (mi->Position() < Position)
2402 if (mi->Position() > Position)
2411 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2412 while (
const cMark *NextMark =
Next(BeginMark)) {
2413 if (BeginMark->
Position() == NextMark->Position()) {
2414 if (!(BeginMark =
Next(NextMark)))
2429 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2430 while (
const cMark *NextMark =
Next(EndMark)) {
2431 if (EndMark->
Position() == NextMark->Position()) {
2432 if (!(EndMark =
Next(NextMark)))
2444 int NumSequences = 0;
2452 if (NumSequences == 1 && BeginMark->Position() == 0)
2456 return NumSequences;
2461 if (
Count() == 0 || LastFrame < 0 || Frame < 0 || Frame > LastFrame)
2463 int EditedFrame = 0;
2465 bool InEdit =
false;
2467 int p = mi->Position();
2469 EditedFrame += p - PrevPos;
2472 EditedFrame -= p - Frame;
2484 EditedFrame += LastFrame - PrevPos;
2485 if (Frame < LastFrame)
2486 EditedFrame -= LastFrame - Frame;
2503 isyslog(
"executing '%s'", *cmd);
2510#define IFG_BUFFER_SIZE KILOBYTE(100)
2517 virtual void Action(
void);
2524:
cThread(
"index file generator")
2525,recordingName(RecordingName)
2538 bool IndexFileComplete =
false;
2539 bool IndexFileWritten =
false;
2540 bool Rewind =
false;
2549 off_t FrameOffset = -1;
2550 uint16_t FileNumber = 1;
2551 off_t FileOffset = 0;
2553 bool pendIndependentFrame =
false;
2554 uint16_t pendNumber = 0;
2555 off_t pendFileSize = 0;
2556 bool pendErrors =
false;
2557 bool pendMissing =
false;
2563 Last = IndexFile.
Last();
2564 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2568 isyslog(
"updating index file");
2571 isyslog(
"generating index file");
2575 bool Stuffed =
false;
2579 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2588 if (FrameDetector.
Synced()) {
2592 int Processed = FrameDetector.
Analyze(Data, Length);
2593 if (Processed > 0) {
2594 int PreviousErrors = 0;
2595 int MissingFrames = 0;
2596 if (FrameDetector.
NewFrame(&PreviousErrors, &MissingFrames)) {
2597 if (IndexFileWritten || Last < 0) {
2599 IndexFile.
Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
2601 pendNumber = FileName.
Number();
2602 pendFileSize = FrameOffset >= 0 ? FrameOffset :
FileSize;
2603 pendErrors = PreviousErrors;
2604 pendMissing = MissingFrames;
2607 IndexFileWritten =
true;
2614 Buffer.
Del(Processed);
2619 int Processed = FrameDetector.
Analyze(Data, Length,
false);
2620 if (Processed > 0) {
2621 if (FrameDetector.
Synced()) {
2625 Buffer.
Del(Processed);
2635 else if (PatPmtParser.
IsPmtPid(Pid))
2641 FrameDetector.
SetPid(PatPmtParser.
Vpid() ? PatPmtParser.
Vpid() : PatPmtParser.
Apid(0), PatPmtParser.
Vpid() ? PatPmtParser.
Vtype() : PatPmtParser.
Atype(0));
2647 Buffer.
Del(p - Data);
2651 else if (ReplayFile) {
2652 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2654 if (Buffer.
Available() > 0 && !Stuffed) {
2663 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2678 IndexFile.
Write(pendIndependentFrame, pendNumber, pendFileSize, pendErrors, pendMissing);
2679 IndexFileComplete =
true;
2684 if (IndexFileComplete) {
2685 if (IndexFileWritten) {
2687 if (RecordingInfo.
Read()) {
2692 Errors != RecordingInfo.
Errors()) {
2696 RecordingInfo.
Write();
2713#define INDEXFILESUFFIX "/index"
2716#define MAXINDEXCATCHUP 8
2717#define INDEXCATCHUPWAIT 100
2733 tIndexTs(off_t Offset,
bool Independent, uint16_t Number,
bool Errors,
bool Missing)
2739 independent = Independent;
2744#define MAXWAITFORINDEXFILE 10
2745#define INDEXFILECHECKINTERVAL 500
2746#define INDEXFILETESTINTERVAL 10
2749:resumeFile(FileName, IsPesRecording)
2760 if (!Record && PauseLive) {
2763 while (time(NULL) < tmax &&
FileSize(
fileName) < off_t(2 *
sizeof(tIndexTs)))
2776 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2782 delta = int(buf.st_size %
sizeof(tIndexTs));
2784 delta =
sizeof(tIndexTs) - delta;
2785 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2787 last = int((buf.st_size + delta) /
sizeof(tIndexTs) - 1);
2788 if ((!Record || Update) &&
last >= 0) {
2813 esyslog(
"ERROR: can't allocate %zd bytes for index '%s'",
size *
sizeof(tIndexTs), *
fileName);
2825 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2827 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2854 while (Count-- > 0) {
2855 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2856 IndexTs->offset = IndexPes.offset;
2857 IndexTs->independent = IndexPes.type == 1;
2858 IndexTs->number = IndexPes.number;
2866 while (Count-- > 0) {
2867 IndexPes.offset = uint32_t(IndexTs->offset);
2868 IndexPes.type =
uchar(IndexTs->independent ? 1 : 2);
2869 IndexPes.number =
uchar(IndexTs->number);
2870 IndexPes.reserved = 0;
2871 memcpy((
void *)IndexTs, &IndexPes,
sizeof(*IndexTs));
2885 if (fstat(
f, &buf) == 0) {
2886 int newLast = int(buf.st_size /
sizeof(tIndexTs) - 1);
2887 if (newLast >
last) {
2889 if (NewSize <= newLast) {
2891 if (NewSize <= newLast)
2892 NewSize = newLast + 1;
2894 if (tIndexTs *NewBuffer = (tIndexTs *)realloc(
index, NewSize *
sizeof(tIndexTs))) {
2897 int offset = (
last + 1) *
sizeof(tIndexTs);
2898 int delta = (newLast -
last) *
sizeof(tIndexTs);
2899 if (lseek(
f, offset, SEEK_SET) == offset) {
2901 esyslog(
"ERROR: can't read from index");
2916 esyslog(
"ERROR: can't realloc() index");
2929 return index != NULL;
2932bool cIndexFile::Write(
bool Independent, uint16_t FileNumber, off_t FileOffset,
bool Errors,
bool Missing)
2935 tIndexTs i(FileOffset, Independent, FileNumber, Errors, Missing);
2949bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length,
bool *Errors,
bool *Missing)
2952 if (Index >= 0 && Index <=
last) {
2953 *FileNumber =
index[Index].number;
2954 *FileOffset =
index[Index].offset;
2956 *Independent =
index[Index].independent;
2959 uint16_t fn =
index[Index + 1].number;
2960 off_t fo =
index[Index + 1].offset;
2961 if (fn == *FileNumber)
2962 *Length = int(fo - *FileOffset);
2970 *Errors =
index[Index].errors;
2972 *Missing =
index[Index].missing;
2982 tIndexTs *p = &
index[Index];
2983 if (p->errors || p->missing)
2993 int d = Forward ? 1 : -1;
2996 if (Index >= 0 && Index <=
last) {
2997 if (
index[Index].independent) {
3004 *FileNumber =
index[Index].number;
3005 *FileOffset =
index[Index].offset;
3008 uint16_t fn =
index[Index + 1].number;
3009 off_t fo =
index[Index + 1].offset;
3010 if (fn == *FileNumber)
3011 *Length = int(fo - *FileOffset);
3032 if (
index[Index].independent)
3038 if (
index[il].independent)
3045 if (
index[ih].independent)
3061 for (i = 0; i <=
last; i++) {
3062 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
3091 if (*s && stat(s, &buf) == 0)
3092 return buf.st_size / (IsPesRecording ?
sizeof(tIndexTs) :
sizeof(tIndexPes));
3100 if (Recording.
Name()) {
3104 unlink(IndexFileName);
3106 while (IndexFileGenerator->
Active())
3108 if (access(IndexFileName, R_OK) == 0)
3111 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
3114 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
3117 fprintf(stderr,
"'%s' is not a recording\n", FileName);
3120 fprintf(stderr,
"'%s' is not a directory\n", FileName);
3126#define MAXFILESPERRECORDINGPES 255
3127#define RECORDFILESUFFIXPES "/%03d.vdr"
3128#define MAXFILESPERRECORDINGTS 65535
3129#define RECORDFILESUFFIXTS "/%05d.ts"
3130#define RECORDFILESUFFIXLEN 20
3142 esyslog(
"ERROR: can't copy file name '%s'", FileName);
3172 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
3174 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
3178 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
3180 int Pid =
TsPid(buf);
3182 PatPmtParser.
ParsePat(buf,
sizeof(buf));
3183 else if (PatPmtParser.
IsPmtPid(Pid)) {
3184 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
3185 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
3196 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
3210 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
3224 else if (errno != ENOENT)
3254 if (buf.st_size != 0)
3258 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3265 else if (errno != ENOENT) {
3272 if (!
record && Offset >= 0 &&
file->
Seek(Offset, SEEK_SET) != Offset) {
3279 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3301 while ((s = ReadLine.
Read(f)) != NULL)
3319 if (fputs(
doneRecordings[i], f) == EOF || fputc(
'\n', f) == EOF) {
3341 if (FILE *f = fopen(
fileName,
"a")) {
3347 esyslog(
"ERROR: can't open '%s' for appending '%s'", *
fileName, Title);
3364 const char *t = Title;
3370 if (toupper(
uchar(*s)) != toupper(
uchar(*t)))
3385 const char *Sign =
"";
3391 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3392 int s = int(Seconds);
3393 int m = s / 60 % 60;
3396 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3402 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3406 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3412 return int(round(Seconds * FramesPerSecond));
3421 else if (Length > Max) {
3422 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3425 int r = f->
Read(b, Length);
3445 if (fgets(buf,
sizeof(buf), f))
3474 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3475 if (FILE *f = fopen(FileName,
"w")) {
3476 fprintf(f,
"%s\n", TimerId);
3483 dsyslog(
"removing %s", *FileName);
3491 const char *Id = NULL;
3492 if (FILE *f = fopen(FileName,
"r")) {
3493 char buf[HOST_NAME_MAX + 10];
3494 if (fgets(buf,
sizeof(buf), f)) {
3508 if (FileSizeMB > 0) {
3511 if (NumFramesOrg > 0) {
3513 if (NumFramesEdit > 0)
3514 return max(1,
int(FileSizeMB * (
double(NumFramesEdit) / NumFramesOrg)));
3523 if (FileSizeMB > 0) {
3527 if (access(EditedFileName, F_OK)) {
3528 int ExistingEditedSizeMB =
DirSizeMB(EditedFileName);
3529 if (ExistingEditedSizeMB > 0)
3530 FreeDiskMB += ExistingEditedSizeMB;
3534 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
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
int NumComponents(void) const
void SetComponent(int Index, const char *s)
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 Start(void)
Starts the actual cutting process.
bool Error(void)
Returns true if an error occurred while cutting the recording.
bool Active(void)
Returns true if the cutter is currently active.
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 cComponents * Components(void) const
const char * Title(void) const
void SetStartTime(time_t StartTime)
void SetComponents(cComponents *Components)
void SetEventID(tEventID EventID)
void SetVersion(uchar Version)
void SetDuration(int Duration)
void SetTitle(const char *Title)
void SetTableID(uchar TableID)
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 * Next(void) const
const cMark * Prev(const cMark *Object) const
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
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.
int AlwaysSortFoldersFirst
char SVDRPHostName[HOST_NAME_MAX]
int QueueMessage(eMessageType Type, const char *s, int Seconds=0, int Timeout=0)
Like Message(), but this function may be called from a background thread.
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.
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)
off_t Seek(off_t Offset, int Whence)
virtual void Append(T Data)
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)
cString ToString(void) const
char language[MAXLANGCODE2]
int SystemExec(const char *Command, bool Detached)