15 #define __STDC_FORMAT_MACROS // Required for format specifiers 34 #define SUMMARYFALLBACK 47 #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT 48 #define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT 49 #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT 50 #define NAMEFORMATTS "%s/%s/" DATAFORMATTS 52 #define RESUMEFILESUFFIX "/resume%s%s" 53 #ifdef SUMMARYFALLBACK 54 #define SUMMARYFILESUFFIX "/summary.vdr" 56 #define INFOFILESUFFIX "/info" 57 #define MARKSFILESUFFIX "/marks" 59 #define SORTMODEFILE ".sort" 60 #define TIMERRECFILE ".timer" 62 #define MINDISKSPACE 1024 // MB 64 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files 65 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed 66 #define DISKCHECKDELTA 100 // seconds between checks for free disk space 67 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file 68 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks 69 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written 70 #define MAXREMOVETIME 10 // seconds after which to return from removing deleted recordings 72 #define MAX_LINK_LEVEL 6 74 #define LIMIT_SECS_PER_MB_RADIO 5 // radio recordings typically have more than this 91 :
cThread(
"remove deleted recordings", true)
99 if (LockFile.
Lock()) {
100 time_t StartTime = time(NULL);
101 bool deleted =
false;
103 for (
cRecording *r = DeletedRecordings->First(); r; ) {
113 DeletedRecordings->Del(r);
118 r = DeletedRecordings->
Next(r);
133 static time_t LastRemoveCheck = 0;
137 for (
const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->
Next(r)) {
144 LastRemoveCheck = time(NULL);
155 static time_t LastFreeDiskCheck = 0;
156 int Factor = (Priority == -1) ? 10 : 1;
157 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
161 if (!LockFile.
Lock())
164 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
165 int NumDeletedRecordings = 0;
168 NumDeletedRecordings = DeletedRecordings->Count();
169 if (NumDeletedRecordings) {
177 r = DeletedRecordings->
Next(r);
182 DeletedRecordings->Del(r0);
187 if (NumDeletedRecordings == 0) {
192 if (DeletedRecordings->Count())
197 isyslog(
"...no deleted recording found, trying to delete an old recording...");
199 Recordings->SetExplicitModify();
200 if (Recordings->Count()) {
217 r = Recordings->
Next(r);
221 Recordings->SetModified();
226 isyslog(
"...no old recording found, giving up");
229 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
232 LastFreeDiskCheck = time(NULL);
248 esyslog(
"ERROR: can't allocate memory for resume file name");
262 if ((st.st_mode & S_IWUSR) == 0)
268 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
274 else if (errno != ENOENT)
283 while ((s = ReadLine.
Read(f)) != NULL) {
287 case 'I': resume = atoi(t);
294 else if (errno != ENOENT)
305 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
318 fprintf(f,
"I %d\n", Index);
338 else if (errno != ENOENT)
363 for (
int i = 0; i <
MAXAPIDS; i++) {
364 const char *s = Channel->
Alang(i);
369 else if (strlen(s) > strlen(Component->
language))
376 for (
int i = 0; i <
MAXDPIDS; i++) {
377 const char *s = Channel->
Dlang(i);
384 else if (strlen(s) > strlen(Component->
language))
389 for (
int i = 0; i <
MAXSPIDS; i++) {
390 const char *s = Channel->
Slang(i);
395 else if (strlen(s) > strlen(Component->
language))
459 while ((s = ReadLine.
Read(f)) != NULL) {
464 char *p = strchr(t,
' ');
475 unsigned int EventID;
478 unsigned int TableID = 0;
479 unsigned int Version = 0xFF;
480 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
481 if (n >= 3 && n <= 5) {
501 esyslog(
"ERROR: EPG data problem in line %d", line);
516 event->Dump(f, Prefix,
true);
518 fprintf(f,
"%sP %d\n", Prefix,
priority);
519 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
521 fprintf(f,
"%s@ %s\n", Prefix,
aux);
537 else if (errno != ENOENT)
561 #define RESUME_NOT_INITIALIZED (-2) 594 case ' ': *p =
'_';
break;
601 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
605 sprintf(buf,
"#%02X", (
unsigned char)*p);
606 memmove(p + 2, p, strlen(p) + 1);
611 esyslog(
"ERROR: out of memory");
618 case '_': *p =
' ';
break;
623 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
625 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
629 memmove(p + 1, p + 3, strlen(p) - 2);
635 case '\x01': *p =
'\'';
break;
636 case '\x02': *p =
'/';
break;
637 case '\x03': *p =
':';
break;
644 if (*p == (ToFileSystem ? ce->a : ce->b)) {
645 *p = ToFileSystem ? ce->b : ce->a;
667 int Length = strlen(s);
670 bool NameTooLong =
false;
674 for (
char *p = s; *p; p++) {
677 NameTooLong |= NameLength > NameMax;
698 NameTooLong |= NameLength > NameMax;
706 while (i-- > 0 && a[i] >= 0) {
711 if (NameLength > NameMax) {
714 while (i-- > 0 && a[i] >= 0) {
716 if (NameLength - l <= NameMax) {
717 memmove(s + i, s + n, Length - n + 1);
718 memmove(a + i, a + n, Length - n + 1);
731 while (PathLength > PathMax && n > 0) {
736 while (--i > 0 && a[i - 1] >= 0) {
740 if (PathLength - l <= PathMax)
746 memmove(s + b, s + n, Length - n + 1);
773 const char *
Title = Event ? Event->
Title() : NULL;
774 const char *Subtitle = Event ? Event->
ShortText() : NULL;
781 if (macroTITLE || macroEPISODE) {
786 int l = strlen(
name);
833 const char *p = strrchr(
FileName,
'/');
838 time_t now = time(NULL);
840 struct tm t = *localtime_r(&now, &tm_r);
859 FILE *f = fopen(InfoFileName,
"r");
862 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
870 else if (errno == ENOENT)
874 #ifdef SUMMARYFALLBACK 878 FILE *f = fopen(SummaryFileName,
"r");
881 char *data[3] = { NULL };
884 while ((s = ReadLine.
Read(f)) != NULL) {
885 if (*s || line > 1) {
888 len += strlen(data[line]) + 1;
889 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
890 data[line] = NewBuffer;
891 strcat(data[line],
"\n");
892 strcat(data[line], s);
895 esyslog(
"ERROR: out of memory");
898 data[line] = strdup(s);
908 else if (data[1] && data[2]) {
912 int len = strlen(data[1]);
914 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
916 strcat(data[1],
"\n");
917 strcat(data[1], data[2]);
923 esyslog(
"ERROR: out of memory");
927 for (
int i = 0; i < 3; i ++)
930 else if (errno != ENOENT)
949 char *t = s, *s1 = NULL, *s2 = NULL;
970 memmove(s1, s2, t - s2 + 1);
983 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&
start, &tm_r));
991 int l = strxfrm(NULL, s, 0) + 1;
1034 int l = strlen(Path);
1056 struct tm *t = localtime_r(&
start, &tm_r);
1072 char New = NewIndicator &&
IsNew() ?
'*' :
' ';
1077 struct tm *t = localtime_r(&
start, &tm_r);
1111 const char *s =
name;
1144 const char *s =
name;
1156 s = !s ?
name : s + 1;
1208 dsyslog(
"changing priority/lifetime of '%s' to %d/%d",
Name(), NewPriority, NewLifetime);
1232 if (strcmp(NewName,
Name())) {
1233 dsyslog(
"changing name of '%s' to '%s'",
Name(), NewName);
1239 name = strdup(NewName);
1241 bool Exists = access(NewFileName, F_OK) == 0;
1243 esyslog(
"ERROR: recording '%s' already exists", NewName);
1246 name = strdup(OldName);
1260 char *NewName = strdup(
FileName());
1261 char *ext = strrchr(NewName,
'.');
1262 if (ext && strcmp(ext,
RECEXT) == 0) {
1263 strncpy(ext,
DELEXT, strlen(ext));
1264 if (access(NewName, F_OK) == 0) {
1266 isyslog(
"removing recording '%s'", NewName);
1270 if (access(
FileName(), F_OK) == 0) {
1297 char *NewName = strdup(
FileName());
1298 char *ext = strrchr(NewName,
'.');
1299 if (ext && strcmp(ext,
DELEXT) == 0) {
1300 strncpy(ext,
RECEXT, strlen(ext));
1301 if (access(NewName, F_OK) == 0) {
1303 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1373 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1375 virtual void Action(
void);
1382 :
cThread(
"video directory scanner", true)
1416 if (lstat(buffer, &st) == 0) {
1418 if (S_ISLNK(st.st_mode)) {
1420 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1424 if (stat(buffer, &st) != 0)
1427 if (S_ISDIR(st.st_mode)) {
1435 Recordings->
Lock(StateKey,
true);
1457 if (!
initial && DirLevel == 0) {
1463 if (access(r->
FileName(), F_OK) != 0)
1509 if (lastModified > time(NULL))
1529 if (Recording->Id() == Id)
1539 if (strcmp(Recording->FileName(), FileName) == 0)
1566 Recording = dummy =
new cRecording(FileName);
1569 Del(Recording,
false);
1570 char *ext = strrchr(Recording->
fileName,
'.');
1572 strncpy(ext,
DELEXT, strlen(ext));
1573 if (access(Recording->
FileName(), F_OK) == 0) {
1575 DeletedRecordings->Add(Recording);
1586 Recording->ReadInfo();
1593 int FileSizeMB = Recording->FileSizeMB();
1594 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1605 if (Recording->IsOnVideoDirectoryFileSystem()) {
1606 int FileSizeMB = Recording->FileSizeMB();
1607 if (FileSizeMB > 0) {
1608 int LengthInSeconds = Recording->LengthInSeconds();
1609 if (LengthInSeconds > 0) {
1612 length += LengthInSeconds;
1618 return (size && length) ? double(size) * 60 / length : -1;
1625 if (Recording->IsInPath(Path))
1626 Use |= Recording->IsInUse();
1635 if (Recording->IsInPath(Path))
1643 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1644 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1647 if (Recording->IsInPath(OldPath)) {
1648 const char *p = Recording->Name() + strlen(OldPath);
1650 if (!Recording->ChangeName(NewName))
1664 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1665 Recording->ResetResume();
1672 Recording->ClearSortName();
1684 virtual void Action(
void);
1686 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1709 dsyslog(
"suspending copy thread");
1715 dsyslog(
"resuming copy thread");
1732 size_t BufferSize = BUFSIZ;
1733 uchar *Buffer = NULL;
1747 size_t Read =
safe_read(From, Buffer, BufferSize);
1749 size_t Written =
safe_write(To, Buffer, Read);
1750 if (Written != Read) {
1751 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1755 else if (Read == 0) {
1757 if (fsync(To) < 0) {
1758 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1761 if (close(From) < 0) {
1762 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1765 if (close(To) < 0) {
1766 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1770 off_t FileSizeSrc =
FileSize(FileNameSrc);
1771 off_t FileSizeDst =
FileSize(FileNameDst);
1772 if (FileSizeSrc != FileSizeDst) {
1773 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1778 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1782 else if ((e = d.
Next()) != NULL) {
1787 if (stat(FileNameSrc, &st) < 0) {
1788 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1791 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1792 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1795 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1797 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1800 esyslog(
"ERROR: out of memory");
1804 if (access(FileNameDst, F_OK) == 0) {
1805 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1808 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1809 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1812 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1813 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1852 int Usage(
const char *FileName = NULL)
const;
1880 if (FileName && *FileName) {
1964 :
cThread(
"recordings handler")
1981 Recordings->SetExplicitModify();
1984 if (!r->Active(Recordings)) {
1985 error |= r->Error();
1986 r->Cleanup(Recordings);
2002 if (FileName && *FileName) {
2006 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2015 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2018 if (FileNameSrc && *FileNameSrc) {
2019 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2021 if (Usage ==
ruCut && !FileNameDst)
2023 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2031 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2034 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2037 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2040 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2062 return r->Usage(FileName);
2104 const char *p = strchr(s,
' ');
2115 return fprintf(f,
"%s\n", *
ToText()) > 0;
2128 if (errno != ENOENT) {
2136 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2150 time_t t = time(NULL);
2154 lastChange = LastModified > 0 ? LastModified : t;
2193 if (m->Position() - p) {
2204 if (m2->Position() < m1->Position()) {
2205 swap(m1->position, m2->position);
2206 swap(m1->comment, m2->comment);
2221 if (mi->Position() == Position)
2230 if (mi->Position() < Position)
2239 if (mi->Position() > Position)
2248 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2249 while (
const cMark *NextMark =
Next(BeginMark)) {
2250 if (BeginMark->
Position() == NextMark->Position()) {
2251 if (!(BeginMark =
Next(NextMark)))
2266 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2267 while (
const cMark *NextMark =
Next(EndMark)) {
2268 if (EndMark->
Position() == NextMark->Position()) {
2269 if (!(EndMark =
Next(NextMark)))
2281 int NumSequences = 0;
2289 if (NumSequences == 1 && BeginMark->Position() == 0)
2293 return NumSequences;
2308 isyslog(
"executing '%s'", *cmd);
2315 #define IFG_BUFFER_SIZE KILOBYTE(100) 2322 virtual void Action(
void);
2329 :
cThread(
"index file generator")
2330 ,recordingName(RecordingName)
2343 bool IndexFileComplete =
false;
2344 bool IndexFileWritten =
false;
2345 bool Rewind =
false;
2354 off_t FrameOffset = -1;
2355 uint16_t FileNumber = 1;
2356 off_t FileOffset = 0;
2362 Last = IndexFile.
Last();
2363 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2367 isyslog(
"updating index file");
2370 isyslog(
"generating index file");
2373 bool Stuffed =
false;
2377 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2386 if (FrameDetector.
Synced()) {
2390 int Processed = FrameDetector.
Analyze(Data, Length);
2391 if (Processed > 0) {
2393 if (IndexFileWritten || Last < 0)
2396 IndexFileWritten =
true;
2399 Buffer.
Del(Processed);
2404 int Processed = FrameDetector.
Analyze(Data, Length);
2405 if (Processed > 0) {
2406 if (FrameDetector.
Synced()) {
2410 Buffer.
Del(Processed);
2420 else if (PatPmtParser.
IsPmtPid(Pid))
2426 FrameDetector.
SetPid(PatPmtParser.
Vpid() ? PatPmtParser.
Vpid() : PatPmtParser.
Apid(0), PatPmtParser.
Vpid() ? PatPmtParser.
Vtype() : PatPmtParser.
Atype(0));
2432 Buffer.
Del(p - Data);
2436 else if (ReplayFile) {
2437 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2439 if (Buffer.
Available() > 0 && !Stuffed) {
2448 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2462 IndexFileComplete =
true;
2466 if (IndexFileComplete) {
2467 if (IndexFileWritten) {
2469 if (RecordingInfo.
Read()) {
2472 RecordingInfo.
Write();
2489 #define INDEXFILESUFFIX "/index" 2492 #define MAXINDEXCATCHUP 8 // number of retries 2493 #define INDEXCATCHUPWAIT 100 // milliseconds 2507 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2516 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds) 2517 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file 2518 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video 2521 :resumeFile(FileName, IsPesRecording)
2531 if (!Record && PauseLive) {
2538 if (!Record && access(
fileName, R_OK) != 0) {
2547 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2553 delta = int(buf.st_size %
sizeof(
tIndexTs));
2556 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2558 last = int((buf.st_size + delta) /
sizeof(
tIndexTs) - 1);
2559 if ((!Record || Update) &&
last >= 0) {
2591 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2593 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2620 while (Count-- > 0) {
2621 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2632 while (Count-- > 0) {
2637 memcpy(IndexTs, &IndexPes,
sizeof(*IndexTs));
2651 if (fstat(
f, &buf) == 0) {
2652 int newLast = int(buf.st_size /
sizeof(
tIndexTs) - 1);
2653 if (newLast >
last) {
2655 if (NewSize <= newLast) {
2657 if (NewSize <= newLast)
2658 NewSize = newLast + 1;
2665 if (lseek(
f, offset, SEEK_SET) == offset) {
2667 esyslog(
"ERROR: can't read from index");
2682 esyslog(
"ERROR: can't realloc() index");
2695 return index != NULL;
2701 tIndexTs i(FileOffset, Independent, FileNumber);
2715 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2718 if (Index >= 0 && Index <=
last) {
2727 if (fn == *FileNumber)
2728 *Length = int(fo - *FileOffset);
2744 int d = Forward ? 1 : -1;
2747 if (Index >= 0 && Index <=
last) {
2748 if (
index[Index].independent) {
2761 if (fn == *FileNumber)
2762 *Length = int(fo - *FileOffset);
2783 if (
index[Index].independent)
2789 if (
index[il].independent)
2796 if (
index[ih].independent)
2812 for (i = 0; i <=
last; i++) {
2813 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2842 if (*s && stat(s, &buf) == 0)
2851 if (Recording.
Name()) {
2855 unlink(IndexFileName);
2857 while (IndexFileGenerator->
Active())
2859 if (access(IndexFileName, R_OK) == 0)
2862 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2865 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2868 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2871 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2877 #define MAXFILESPERRECORDINGPES 255 2878 #define RECORDFILESUFFIXPES "/%03d.vdr" 2879 #define MAXFILESPERRECORDINGTS 65535 2880 #define RECORDFILESUFFIXTS "/%05d.ts" 2881 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... 2923 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2925 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2929 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2931 int Pid =
TsPid(buf);
2933 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2934 else if (PatPmtParser.
IsPmtPid(Pid)) {
2935 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2936 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2947 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2961 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
2975 else if (errno != ENOENT)
3005 if (buf.st_size != 0)
3009 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3016 else if (errno != ENOENT) {
3030 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3043 const char *Sign =
"";
3049 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3050 int s = int(Seconds);
3051 int m = s / 60 % 60;
3054 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3060 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3064 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3070 return int(round(Seconds * FramesPerSecond));
3079 else if (Length > Max) {
3080 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3083 int r = f->
Read(b, Length);
3103 if (fgets(buf,
sizeof(buf), f))
3132 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3133 if (FILE *f = fopen(FileName,
"w")) {
3134 fprintf(f,
"%s\n", TimerId);
3141 dsyslog(
"removing %s", *FileName);
3149 const char *Id = NULL;
3150 if (FILE *f = fopen(FileName,
"r")) {
3151 char buf[HOST_NAME_MAX + 10];
3152 if (fgets(buf,
sizeof(buf), f)) {
bool Start(void)
Starts the actual cutting process.
struct dirent * Next(void)
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
static bool RenameVideoFile(const char *OldName, const char *NewName)
int Usage(const char *FileName=NULL) const
void ParsePat(const uchar *Data, int Length)
Parses the PAT data from the single TS packet in Data.
void SetFramesPerSecond(double FramesPerSecond)
virtual void Clear(void)
Immediately clears the ring buffer.
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 cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists).
const cMark * GetNextEnd(const cMark *BeginMark) const
Returns the next "end" mark after BeginMark, skipping any marks at the same position as BeginMark.
const char * Aux(void) const
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
static tChannelID FromString(const char *s)
void Cleanup(cRecordings *Recordings)
static char * StripEpisodeName(char *s, bool Strip)
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
void SetComponent(int Index, const char *s)
bool Active(void)
Returns true if the cutter is currently active.
#define DEFAULTFRAMESPERSECOND
bool IsOnVideoDirectoryFileSystem(void) const
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
const char * InvalidChars
void SetStartTime(time_t StartTime)
void SetDuration(int Duration)
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
void ResetResume(const char *ResumeFileName=NULL)
void SetTableID(uchar TableID)
void SetRecordingTimerId(const char *Directory, const char *TimerId)
void Add(cListObject *Object, cListObject *After=NULL)
bool CatchUp(int Index=-1)
cResumeFile(const char *FileName, bool IsPesRecording)
const cRecording * GetByName(const char *FileName) const
const char * Description(void) const
int NumComponents(void) const
char * LimitNameLengths(char *s, int PathMax, int NameMax)
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder).
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
eRecordingsSortMode RecordingsSortMode
ssize_t Read(void *Data, size_t Size)
char language[MAXLANGCODE2]
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
static cRecordings deletedRecordings
#define TIMERMACRO_EPISODE
static cString sprintf(const char *fmt,...) __attribute__((format(printf
int NumFrames(void) const
Returns the number of frames in this recording.
off_t Seek(off_t Offset, int Whence)
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
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.
const char * Name(void) const
Returns the full name of the recording (without the video directory).
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...
const char * Alang(int i) const
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater",...
static cRecordings recordings
cUnbufferedFile * NextFile(void)
#define RECORDFILESUFFIXTS
int AlwaysSortFoldersFirst
double MarkFramesPerSecond
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
const char * Slang(int i) const
const cComponents * Components(void) const
char * SortName(void) const
virtual ~cRecordingsHandler()
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
#define MAXWAITFORINDEXFILE
static bool VideoFileSpaceAvailable(int SizeMB)
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
cRecording(const cRecording &)
#define INDEXFILETESTINTERVAL
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
const char * Dlang(int i) const
void SetAux(const char *Aux)
~cVideoDirectoryScannerThread()
void ScanVideoDir(const char *DirName, int LinkLevel=0, int DirLevel=0)
#define RECORDFILESUFFIXPES
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
static cString IndexFileName(const char *FileName, bool IsPesRecording)
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
const char * FileNameSrc(void) const
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
static const char * command
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
int TsPid(const uchar *p)
static cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
static cString PrefixVideoFileName(const char *FileName, char Prefix)
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
const char * Aux(void) const
static const char * Name(void)
static int lastRecordingId
#define MAXFILESPERRECORDINGPES
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
void SetTitle(const char *Title)
tCharExchange CharExchange[]
#define LIMIT_SECS_PER_MB_RADIO
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...
void GetRecordingsSortMode(const char *Directory)
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
int TotalFileSizeMB(void) const
const cMark * Get(int Position) const
void SetFileName(const char *FileName)
int Read(int FileHandle, int Max=0)
Reads at most Max bytes from FileHandle and stores them in the ring buffer.
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]=NULL)
cString ToString(void) const
bool Active(cRecordings *Recordings)
void SetData(const char *Title, const char *ShortText, const char *Description)
const cMark * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark.
#define LOCK_RECORDINGS_WRITE
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
void RemoveDeletedRecordings(void)
tIndexTs(off_t Offset, bool Independent, uint16_t Number)
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
const char * FileNameDst(void) const
void UpdateByName(const char *FileName)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
const char * ShortText(void) const
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
bool Finished(bool &Error)
Returns true if all operations in the list have been finished.
bool NeedsConversion(const char *p)
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
void ConvertToPes(tIndexTs *IndexTs, int Count)
cUnbufferedFile * Open(void)
static int GetLength(const char *FileName, bool IsPesRecording=false)
Calculates the recording length (number of frames) without actually reading the index file.
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
static int Utf8CharLen(const char *s)
int isOnVideoDirectoryFileSystem
void ConvertFromPes(tIndexTs *IndexTs, int Count)
int GetClosestIFrame(int Index)
Returns the index of the I-frame that is closest to the given Index (or Index itself,...
static bool HasKeys(void)
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 Running(void)
Returns false if a derived cThread object shall leave its Action() function.
const cMark * Last(void) const
Returns the last element in this list, or NULL if the list is empty.
static char * updateFileName
bool HasRecordingsSortMode(const char *Directory)
const cChannel * Channel(void) const
bool IsEdited(void) const
bool TimedWait(cMutex &Mutex, int TimeoutMs)
cRecordingsHandler RecordingsHandler
int SystemExec(const char *Command, bool Detached)
bool HasMarks(void) const
Returns true if this recording has any editing marks.
cIndexFileGenerator(const char *RecordingName, bool Update=false)
static void TouchUpdate(void)
Touches the '.update' file in the video directory, so that other instances of VDR that access the sam...
bool MoveRecordings(const char *OldPath, const char *NewPath)
Moves all recordings in OldPath to NewPath.
const cRecording * GetById(int Id) const
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
const cMark * Prev(const cMark *Object) const
double FramesPerSecond(void) const
bool Parse(const char *s)
#define MAXFILESPERRECORDINGTS
const char * Comment(void) const
static const char * UpdateFileName(void)
bool Completed(void)
Returns true if the PMT has been completely parsed.
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
bool IsPesRecording(void) const
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false, bool Update=false)
bool Lock(int WaitSeconds=0)
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
const char * Name(void) const
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
cIndexFileGenerator * indexFileGenerator
cRecordings(bool Deleted=false)
const cComponents * Components(void) const
cString GetRecordingTimerId(const char *Directory)
#define RECORDFILESUFFIXLEN
const cMark * GetPrev(int Position) const
const char * Title(void) const
static bool RemoveVideoFile(const char *FileName)
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected,...
static bool DeleteMarksFile(const cRecording *Recording)
#define LOCK_DELETEDRECORDINGS_READ
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
void Del(cListObject *Object, bool DeleteObject=true)
void DelAll(void)
Deletes/terminates all operations.
const cMark * GetNext(int Position) const
cRecordings * deletedRecordings
static bool MoveVideoFile(const char *FromName, const char *ToName)
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.
tChannelID GetChannelID(void) const
bool Active(void)
Checks whether the thread is still alive.
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
cRemoveDeletedRecordingsThread(void)
const char * ShortText(void) const
static bool NeedsUpdate(void)
int HierarchyLevels(void) const
#define RESUME_NOT_INITIALIZED
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame.
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
cListObject * Next(void) const
uchar * Get(int &Count)
Gets data from the ring buffer.
static cVideoDirectoryScannerThread * videoDirectoryScannerThread
static bool IsOnVideoDirectoryFileSystem(const char *FileName)
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
cRecordingsHandlerEntry * Get(const char *FileName)
void IncRecordingsSortMode(const char *Directory)
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 ResetResume(void) const
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
static cRecordControl * GetRecordControl(const char *FileName)
void DelByName(const char *FileName)
bool IsSingleEvent(void) const
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
cList< cRecordingsHandlerEntry > operations
void SetVersion(uchar Version)
void ClearSortNames(void)
int SecondsToFrames(int Seconds, double FramesPerSecond)
bool IsInPath(const char *Path) const
Returns true if this recording is stored anywhere under the given Path.
cMutex MutexMarkFramesPerSecond
double FramesPerSecond(void) const
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with.
static const tChannelID InvalidID
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
int GetNumRecordingsInPath(const char *Path) const
Returns the total number of recordings in the given Path, including all sub-folders of Path.
bool DeleteMarks(void)
Deletes the editing marks from this recording (if any).
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL)
bool IsStillRecording(void)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
bool Write(FILE *f, const char *Prefix="") const
#define LOCK_DELETEDRECORDINGS_WRITE
void SetEventID(tEventID EventID)
#define INDEXFILECHECKINTERVAL
char * ExchangeChars(char *s, bool ToFileSystem)
cString recordingFileName
double MBperMinute(void) const
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown.
bool Error(void)
Returns true if an error occurred while cutting the recording.
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
const char * PrefixFileName(char Prefix)
cDirCopier(const char *DirNameSrc, const char *DirNameDst)
bool WriteInfo(const char *OtherFileName=NULL)
Writes in info file of this recording.
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
void Add(cRecording *Recording)
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
void SetModified(void)
Unconditionally marks this list as modified.
void SetFile(const char *File)
void Del(const char *FileName)
Deletes the given FileName from the list of operations.
cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
void AddByName(const char *FileName, bool TriggerUpdate=true)
~cRecordingsHandlerEntry()
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
#define RUC_DELETERECORDING
const char * Title(void) const
#define SUMMARYFILESUFFIX
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
bool Undelete(void)
Changes the file name so that it will be visible in the "Recordings" menu again and not processed by ...
time_t StartTime(void) const
static const char * NowReplaying(void)
virtual int Available(void)
const char * File(void) const
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...