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;
135 if (!RemoveDeletedRecordingsThread.
Active()) {
137 for (
const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->
Next(r)) {
139 RemoveDeletedRecordingsThread.
Start();
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)
350 event = ownEvent ? ownEvent : Event;
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))
430 ((
cEvent *)event)->SetShortText(ShortText);
432 ((
cEvent *)event)->SetDescription(Description);
438 aux = Aux ? strdup(Aux) : NULL;
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;
643 for (
struct tCharExchange *ce = CharExchange; ce->
a && ce->b; ce++) {
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);
828 FileName =
fileName = strdup(FileName);
833 const char *p = strrchr(FileName,
'/');
838 time_t now = time(NULL);
840 struct tm t = *localtime_r(&now, &tm_r);
849 strncpy(
name, FileName, p - FileName);
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);
1061 if (strcmp(Name,
name) != 0)
1062 dsyslog(
"recording file name '%s' truncated to '%s'",
name, Name);
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;
2167 cMutexLock MutexLock(&MutexMarkFramesPerSecond);
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);
2378 FileSize = FileOffset;
2386 if (FrameDetector.
Synced()) {
2389 FrameOffset = FileSize;
2390 int Processed = FrameDetector.
Analyze(Data, Length);
2391 if (Processed > 0) {
2393 if (IndexFileWritten || Last < 0)
2396 IndexFileWritten =
true;
2398 FileSize += Processed;
2399 Buffer.
Del(Processed);
2404 int Processed = FrameDetector.
Analyze(Data, Length);
2405 if (Processed > 0) {
2406 if (FrameDetector.
Synced()) {
2410 Buffer.
Del(Processed);
2416 while (Length >= TS_SIZE) {
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));
2649 for (
int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >=
last); i++) {
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...
2919 for (; Number > 0; Number--) {
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)
2997 if (0 < Number && Number <= MaxFilesPerRecording) {
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.
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
struct dirent * Next(void)
static bool RenameVideoFile(const char *OldName, const char *NewName)
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.
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists)...
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
int NumFrames(void) const
Returns the number of frames in this recording.
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
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
const cMark * GetNext(int Position) const
const char * InvalidChars
void SetStartTime(time_t StartTime)
void SetDuration(int Duration)
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
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)
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
bool IsEdited(void) const
char * LimitNameLengths(char *s, int PathMax, int NameMax)
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)
double FramesPerSecond(void) const
eRecordingsSortMode RecordingsSortMode
ssize_t Read(void *Data, size_t Size)
char language[MAXLANGCODE2]
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
static cRecordings deletedRecordings
#define TIMERMACRO_EPISODE
static cString sprintf(const char *fmt,...) __attribute__((format(printf
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.
int GetNumRecordingsInPath(const char *Path) const
Returns the total number of recordings in the given Path, including all sub-folders of Path...
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
bool IsOnVideoDirectoryFileSystem(void) const
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
const cRecording * GetByName(const char *FileName) const
static cRecordings recordings
cUnbufferedFile * NextFile(void)
#define RECORDFILESUFFIXTS
int AlwaysSortFoldersFirst
double MarkFramesPerSecond
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected...
const cComponents * Components(void) const
virtual ~cRecordingsHandler()
bool HasMarks(void) const
Returns true if this recording has any editing marks.
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
const cMark * GetNextEnd(const cMark *BeginMark) const
Returns the next "end" mark after BeginMark, skipping any marks at the same position as BeginMark...
double FramesPerSecond(void) const
#define MAXWAITFORINDEXFILE
void ResetResume(void) const
static bool VideoFileSpaceAvailable(int SizeMB)
time_t StartTime(void) const
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
cRecording(const cRecording &)
const char * Dlang(int i) const
#define INDEXFILETESTINTERVAL
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
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 * Alang(int i) const
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
static const char * command
const cRecording * GetById(int Id) const
const cMark * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark...
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
const cChannel * Channel(void) const
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.
static const char * Name(void)
static int lastRecordingId
char * SortName(void) const
#define MAXFILESPERRECORDINGPES
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
void SetTitle(const char *Title)
int PathIsInUse(const char *Path) const
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
tCharExchange CharExchange[]
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 * Name(void) const
#define LIMIT_SECS_PER_MB_RADIO
const char * Comment(void) const
void GetRecordingsSortMode(const char *Directory)
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)
cListObject * Next(void) const
bool Active(cRecordings *Recordings)
bool Write(FILE *f, const char *Prefix="") const
void SetData(const char *Title, const char *ShortText, const char *Description)
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...
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
#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...
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...
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.
int Usage(const char *FileName=NULL) const
bool NeedsConversion(const char *p)
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
double MBperMinute(void) const
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown...
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
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)
tChannelID GetChannelID(void) const
int isOnVideoDirectoryFileSystem
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
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, if it already points to an I-frame).
static bool HasKeys(void)
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
static char * updateFileName
bool HasRecordingsSortMode(const char *Directory)
bool TimedWait(cMutex &Mutex, int TimeoutMs)
cRecordingsHandler RecordingsHandler
int SystemExec(const char *Command, bool Detached)
int HierarchyLevels(void) const
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 char * FileNameDst(void) const
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
bool Parse(const char *s)
#define MAXFILESPERRECORDINGTS
static const char * UpdateFileName(void)
bool Completed(void)
Returns true if the PMT has been completely parsed.
const char * Title(void) const
const char * FileNameSrc(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.
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder). ...
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
cIndexFileGenerator * indexFileGenerator
cRecordings(bool Deleted=false)
const cMark * Get(int Position) const
cString GetRecordingTimerId(const char *Directory)
bool IsInPath(const char *Path) const
Returns true if this recording is stored anywhere under the given Path.
#define RECORDFILESUFFIXLEN
const cMark * Last(void) const
Returns the last element in this list, or NULL if the list is empty.
static bool RemoveVideoFile(const char *FileName)
static bool DeleteMarksFile(const cRecording *Recording)
#define LOCK_DELETEDRECORDINGS_READ
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
void Del(cListObject *Object, bool DeleteObject=true)
cString ToString(void) const
void DelAll(void)
Deletes/terminates all operations.
cRecordings * deletedRecordings
const cMark * GetPrev(int Position) const
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...
bool Active(void)
Checks whether the thread is still alive.
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
cRemoveDeletedRecordingsThread(void)
static bool NeedsUpdate(void)
#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.
const char * File(void) 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...
bool IsSingleEvent(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)
int NumComponents(void) const
const char * Name(void) const
Returns the full name of the recording (without the video 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...
const cMark * Prev(const cMark *Object) 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)
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
const char * Title(void) const
cList< cRecordingsHandlerEntry > operations
void SetVersion(uchar Version)
void ClearSortNames(void)
int SecondsToFrames(int Seconds, double FramesPerSecond)
int TotalFileSizeMB(void) const
const char * Slang(int i) const
cMutex MutexMarkFramesPerSecond
const cComponents * Components(void) const
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
static const tChannelID InvalidID
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
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...
#define LOCK_DELETEDRECORDINGS_WRITE
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
void SetEventID(tEventID EventID)
#define INDEXFILECHECKINTERVAL
char * ExchangeChars(char *s, bool ToFileSystem)
cString recordingFileName
const char * ShortText(void) const
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
bool Error(void)
Returns true if an error occurred while cutting the recording.
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater", and a negative value if it is "smaller".
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)
const char * Aux(void) const
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)
bool IsPesRecording(void) const
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
~cRecordingsHandlerEntry()
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
#define RUC_DELETERECORDING
#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 ...
static const char * NowReplaying(void)
virtual int Available(void)