Go to the documentation of this file.
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;
102 bool interrupted =
false;
104 for (
cRecording *r = DeletedRecordings->First(); r; ) {
116 DeletedRecordings->Del(r);
121 r = DeletedRecordings->
Next(r);
139 static time_t LastRemoveCheck = 0;
143 for (
const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->
Next(r)) {
150 LastRemoveCheck = time(NULL);
161 static time_t LastFreeDiskCheck = 0;
162 int Factor = (Priority == -1) ? 10 : 1;
163 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
167 if (!LockFile.
Lock())
170 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
171 int NumDeletedRecordings = 0;
174 NumDeletedRecordings = DeletedRecordings->Count();
175 if (NumDeletedRecordings) {
183 r = DeletedRecordings->
Next(r);
188 DeletedRecordings->Del(r0);
193 if (NumDeletedRecordings == 0) {
198 if (DeletedRecordings->Count())
203 isyslog(
"...no deleted recording found, trying to delete an old recording...");
205 Recordings->SetExplicitModify();
206 if (Recordings->Count()) {
223 r = Recordings->
Next(r);
227 Recordings->SetModified();
232 isyslog(
"...no old recording found, giving up");
235 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
238 LastFreeDiskCheck = time(NULL);
254 esyslog(
"ERROR: can't allocate memory for resume file name");
268 if ((st.st_mode & S_IWUSR) == 0)
274 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
280 else if (errno != ENOENT)
289 while ((s = ReadLine.
Read(f)) != NULL) {
293 case 'I': resume = atoi(t);
300 else if (errno != ENOENT)
311 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
324 fprintf(f,
"I %d\n", Index);
344 else if (errno != ENOENT)
370 for (
int i = 0; i <
MAXAPIDS; i++) {
371 const char *s = Channel->
Alang(i);
376 else if (strlen(s) > strlen(Component->
language))
383 for (
int i = 0; i <
MAXDPIDS; i++) {
384 const char *s = Channel->
Dlang(i);
391 else if (strlen(s) > strlen(Component->
language))
396 for (
int i = 0; i <
MAXSPIDS; i++) {
397 const char *s = Channel->
Slang(i);
402 else if (strlen(s) > strlen(Component->
language))
472 while ((s = ReadLine.
Read(f)) != NULL) {
477 char *p = strchr(t,
' ');
488 unsigned int EventID;
491 unsigned int TableID = 0;
492 unsigned int Version = 0xFF;
493 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
494 if (n >= 3 && n <= 5) {
509 case 'O':
errors = atoi(t);
516 esyslog(
"ERROR: EPG data problem in line %d", line);
531 event->Dump(f, Prefix,
true);
533 fprintf(f,
"%sP %d\n", Prefix,
priority);
534 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
535 fprintf(f,
"%sO %d\n", Prefix,
errors);
537 fprintf(f,
"%s@ %s\n", Prefix,
aux);
553 else if (errno != ENOENT)
577 #define RESUME_NOT_INITIALIZED (-2)
610 case ' ': *p =
'_';
break;
617 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
621 sprintf(buf,
"#%02X", (
unsigned char)*p);
622 memmove(p + 2, p, strlen(p) + 1);
627 esyslog(
"ERROR: out of memory");
634 case '_': *p =
' ';
break;
639 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
641 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
645 memmove(p + 1, p + 3, strlen(p) - 2);
651 case '\x01': *p =
'\'';
break;
652 case '\x02': *p =
'/';
break;
653 case '\x03': *p =
':';
break;
660 if (*p == (ToFileSystem ? ce->a : ce->b)) {
661 *p = ToFileSystem ? ce->b : ce->a;
683 int Length = strlen(s);
686 bool NameTooLong =
false;
690 for (
char *p = s; *p; p++) {
693 NameTooLong |= NameLength > NameMax;
714 NameTooLong |= NameLength > NameMax;
722 while (i-- > 0 && a[i] >= 0) {
727 if (NameLength > NameMax) {
730 while (i-- > 0 && a[i] >= 0) {
732 if (NameLength - l <= NameMax) {
733 memmove(s + i, s + n, Length - n + 1);
734 memmove(a + i, a + n, Length - n + 1);
747 while (PathLength > PathMax && n > 0) {
752 while (--i > 0 && a[i - 1] >= 0) {
756 if (PathLength - l <= PathMax)
762 memmove(s + b, s + n, Length - n + 1);
789 const char *
Title = Event ? Event->
Title() : NULL;
790 const char *Subtitle = Event ? Event->
ShortText() : NULL;
797 if (macroTITLE || macroEPISODE) {
802 int l = strlen(
name);
849 const char *p = strrchr(
FileName,
'/');
854 time_t now = time(NULL);
856 struct tm t = *localtime_r(&now, &tm_r);
875 FILE *f = fopen(InfoFileName,
"r");
878 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
886 else if (errno != ENOENT)
888 #ifdef SUMMARYFALLBACK
892 FILE *f = fopen(SummaryFileName,
"r");
895 char *data[3] = { NULL };
898 while ((s = ReadLine.
Read(f)) != NULL) {
899 if (*s || line > 1) {
902 len += strlen(data[line]) + 1;
903 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
904 data[line] = NewBuffer;
905 strcat(data[line],
"\n");
906 strcat(data[line], s);
909 esyslog(
"ERROR: out of memory");
912 data[line] = strdup(s);
922 else if (data[1] && data[2]) {
926 int len = strlen(data[1]);
928 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
930 strcat(data[1],
"\n");
931 strcat(data[1], data[2]);
937 esyslog(
"ERROR: out of memory");
941 for (
int i = 0; i < 3; i ++)
944 else if (errno != ENOENT)
965 char *t = s, *s1 = NULL, *s2 = NULL;
986 memmove(s1, s2, t - s2 + 1);
999 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&
start, &tm_r));
1007 int l = strxfrm(NULL, s, 0) + 1;
1050 int l = strlen(Path);
1070 struct tm *t = localtime_r(&
start, &tm_r);
1086 const char *New = NewIndicator &&
IsNew() ?
"*" :
"";
1087 const char *Err = NewIndicator && (
info->
Errors() > 0) ?
"!" :
"";
1092 struct tm *t = localtime_r(&
start, &tm_r);
1127 const char *s =
name;
1160 const char *s =
name;
1203 if (!OtherFileName) {
1206 if (ExistingInfo.
Read())
1231 dsyslog(
"changing priority/lifetime of '%s' to %d/%d",
Name(), NewPriority, NewLifetime);
1255 if (strcmp(NewName,
Name())) {
1256 dsyslog(
"changing name of '%s' to '%s'",
Name(), NewName);
1262 name = strdup(NewName);
1264 bool Exists = access(NewFileName, F_OK) == 0;
1266 esyslog(
"ERROR: recording '%s' already exists", NewName);
1269 name = strdup(OldName);
1283 char *NewName = strdup(
FileName());
1284 char *ext = strrchr(NewName,
'.');
1285 if (ext && strcmp(ext,
RECEXT) == 0) {
1286 strncpy(ext,
DELEXT, strlen(ext));
1287 if (access(NewName, F_OK) == 0) {
1289 isyslog(
"removing recording '%s'", NewName);
1293 if (access(
FileName(), F_OK) == 0) {
1320 char *NewName = strdup(
FileName());
1321 char *ext = strrchr(NewName,
'.');
1322 if (ext && strcmp(ext,
DELEXT) == 0) {
1323 strncpy(ext,
RECEXT, strlen(ext));
1324 if (access(NewName, F_OK) == 0) {
1326 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1397 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1399 virtual void Action(
void);
1406 :
cThread(
"video directory scanner", true)
1442 if (lstat(buffer, &st) == 0) {
1444 if (S_ISLNK(st.st_mode)) {
1446 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1450 if (stat(buffer, &st) != 0)
1453 if (S_ISDIR(st.st_mode)) {
1461 Recordings->
Lock(StateKey,
true);
1463 dsyslog(
"activated name checking for initial read of video directory");
1488 if (!
initial && DirLevel == 0) {
1494 if (access(r->
FileName(), F_OK) != 0)
1540 if (lastModified > time(NULL))
1560 if (Recording->Id() == Id)
1570 if (strcmp(Recording->FileName(), FileName) == 0)
1597 Recording = dummy =
new cRecording(FileName);
1600 Del(Recording,
false);
1601 char *ext = strrchr(Recording->
fileName,
'.');
1603 strncpy(ext,
DELEXT, strlen(ext));
1604 if (access(Recording->
FileName(), F_OK) == 0) {
1606 DeletedRecordings->Add(Recording);
1617 Recording->ReadInfo();
1624 int FileSizeMB = Recording->FileSizeMB();
1625 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1636 if (Recording->IsOnVideoDirectoryFileSystem()) {
1637 int FileSizeMB = Recording->FileSizeMB();
1638 if (FileSizeMB > 0) {
1639 int LengthInSeconds = Recording->LengthInSeconds();
1640 if (LengthInSeconds > 0) {
1643 length += LengthInSeconds;
1649 return (size && length) ? double(size) * 60 / length : -1;
1656 if (Recording->IsInPath(Path))
1657 Use |= Recording->IsInUse();
1666 if (Recording->IsInPath(Path))
1674 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1675 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1678 if (Recording->IsInPath(OldPath)) {
1679 const char *p = Recording->Name() + strlen(OldPath);
1681 if (!Recording->ChangeName(NewName))
1695 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1696 Recording->ResetResume();
1703 Recording->ClearSortName();
1715 virtual void Action(
void);
1717 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1740 dsyslog(
"suspending copy thread");
1746 dsyslog(
"resuming copy thread");
1763 size_t BufferSize = BUFSIZ;
1764 uchar *Buffer = NULL;
1778 size_t Read =
safe_read(From, Buffer, BufferSize);
1780 size_t Written =
safe_write(To, Buffer, Read);
1781 if (Written != Read) {
1782 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1786 else if (Read == 0) {
1788 if (fsync(To) < 0) {
1789 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1792 if (close(From) < 0) {
1793 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1796 if (close(To) < 0) {
1797 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1801 off_t FileSizeSrc =
FileSize(FileNameSrc);
1802 off_t FileSizeDst =
FileSize(FileNameDst);
1803 if (FileSizeSrc != FileSizeDst) {
1804 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1809 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1813 else if ((e = d.
Next()) != NULL) {
1818 if (stat(FileNameSrc, &st) < 0) {
1819 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1822 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1823 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1826 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1828 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1831 esyslog(
"ERROR: out of memory");
1835 if (access(FileNameDst, F_OK) == 0) {
1836 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1839 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1840 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1843 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1844 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1883 int Usage(
const char *FileName = NULL)
const;
1911 if (FileName && *FileName) {
1960 if (Recording.
Delete()) {
2003 :
cThread(
"recordings handler")
2020 Recordings->SetExplicitModify();
2023 if (!r->Active(Recordings)) {
2024 error |= r->Error();
2025 r->Cleanup(Recordings);
2041 if (FileName && *FileName) {
2045 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2054 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2057 if (FileNameSrc && *FileNameSrc) {
2058 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2060 if (Usage ==
ruCut && !FileNameDst)
2062 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2070 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2073 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2076 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2079 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2101 return r->Usage(FileName);
2143 const char *p = strchr(s,
' ');
2154 return fprintf(f,
"%s\n", *
ToText()) > 0;
2167 if (errno != ENOENT) {
2175 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2189 time_t t = time(NULL);
2193 lastChange = LastModified > 0 ? LastModified : t;
2232 if (m->Position() - p) {
2243 if (m2->Position() < m1->Position()) {
2244 swap(m1->position, m2->position);
2245 swap(m1->comment, m2->comment);
2260 if (mi->Position() == Position)
2269 if (mi->Position() < Position)
2278 if (mi->Position() > Position)
2287 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2288 while (
const cMark *NextMark =
Next(BeginMark)) {
2289 if (BeginMark->
Position() == NextMark->Position()) {
2290 if (!(BeginMark =
Next(NextMark)))
2305 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2306 while (
const cMark *NextMark =
Next(EndMark)) {
2307 if (EndMark->
Position() == NextMark->Position()) {
2308 if (!(EndMark =
Next(NextMark)))
2320 int NumSequences = 0;
2328 if (NumSequences == 1 && BeginMark->Position() == 0)
2332 return NumSequences;
2347 isyslog(
"executing '%s'", *cmd);
2354 #define IFG_BUFFER_SIZE KILOBYTE(100)
2361 virtual void Action(
void);
2368 :
cThread(
"index file generator")
2369 ,recordingName(RecordingName)
2382 bool IndexFileComplete =
false;
2383 bool IndexFileWritten =
false;
2384 bool Rewind =
false;
2393 off_t FrameOffset = -1;
2394 uint16_t FileNumber = 1;
2395 off_t FileOffset = 0;
2401 Last = IndexFile.
Last();
2402 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2406 isyslog(
"updating index file");
2409 isyslog(
"generating index file");
2412 bool Stuffed =
false;
2416 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2425 if (FrameDetector.
Synced()) {
2429 int Processed = FrameDetector.
Analyze(Data, Length);
2430 if (Processed > 0) {
2432 if (IndexFileWritten || Last < 0)
2435 IndexFileWritten =
true;
2438 Buffer.
Del(Processed);
2443 int Processed = FrameDetector.
Analyze(Data, Length);
2444 if (Processed > 0) {
2445 if (FrameDetector.
Synced()) {
2449 Buffer.
Del(Processed);
2459 else if (PatPmtParser.
IsPmtPid(Pid))
2465 FrameDetector.
SetPid(PatPmtParser.
Vpid() ? PatPmtParser.
Vpid() : PatPmtParser.
Apid(0), PatPmtParser.
Vpid() ? PatPmtParser.
Vtype() : PatPmtParser.
Atype(0));
2471 Buffer.
Del(p - Data);
2475 else if (ReplayFile) {
2476 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2478 if (Buffer.
Available() > 0 && !Stuffed) {
2487 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2501 IndexFileComplete =
true;
2505 if (IndexFileComplete) {
2506 if (IndexFileWritten) {
2508 if (RecordingInfo.
Read()) {
2511 RecordingInfo.
Write();
2528 #define INDEXFILESUFFIX "/index"
2531 #define MAXINDEXCATCHUP 8 // number of retries
2532 #define INDEXCATCHUPWAIT 100 // milliseconds
2546 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2550 independent = Independent;
2555 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
2556 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
2557 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
2560 :resumeFile(FileName, IsPesRecording)
2570 if (!Record && PauseLive) {
2573 while (time(NULL) < tmax &&
FileSize(
fileName) < off_t(2 *
sizeof(tIndexTs)))
2577 if (!Record && access(
fileName, R_OK) != 0) {
2586 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2592 delta = int(buf.st_size %
sizeof(tIndexTs));
2594 delta =
sizeof(tIndexTs) - delta;
2595 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2597 last = int((buf.st_size + delta) /
sizeof(tIndexTs) - 1);
2598 if ((!Record || Update) &&
last >= 0) {
2623 esyslog(
"ERROR: can't allocate %zd bytes for index '%s'",
size *
sizeof(tIndexTs), *
fileName);
2635 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2637 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2664 while (Count-- > 0) {
2665 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2666 IndexTs->offset = IndexPes.offset;
2667 IndexTs->independent = IndexPes.type == 1;
2668 IndexTs->number = IndexPes.number;
2676 while (Count-- > 0) {
2677 IndexPes.offset = uint32_t(IndexTs->offset);
2678 IndexPes.type =
uchar(IndexTs->independent ? 1 : 2);
2679 IndexPes.number =
uchar(IndexTs->number);
2680 IndexPes.reserved = 0;
2681 memcpy((
void *)IndexTs, &IndexPes,
sizeof(*IndexTs));
2695 if (fstat(
f, &buf) == 0) {
2696 int newLast = int(buf.st_size /
sizeof(tIndexTs) - 1);
2697 if (newLast >
last) {
2699 if (NewSize <= newLast) {
2701 if (NewSize <= newLast)
2702 NewSize = newLast + 1;
2704 if (tIndexTs *NewBuffer = (tIndexTs *)realloc(
index, NewSize *
sizeof(tIndexTs))) {
2707 int offset = (
last + 1) *
sizeof(tIndexTs);
2708 int delta = (newLast -
last) *
sizeof(tIndexTs);
2709 if (lseek(
f, offset, SEEK_SET) == offset) {
2711 esyslog(
"ERROR: can't read from index");
2726 esyslog(
"ERROR: can't realloc() index");
2739 return index != NULL;
2745 tIndexTs i(FileOffset, Independent, FileNumber);
2759 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2762 if (Index >= 0 && Index <=
last) {
2763 *FileNumber =
index[Index].number;
2764 *FileOffset =
index[Index].offset;
2766 *Independent =
index[Index].independent;
2769 uint16_t fn =
index[Index + 1].number;
2770 off_t fo =
index[Index + 1].offset;
2771 if (fn == *FileNumber)
2772 *Length = int(fo - *FileOffset);
2788 int d = Forward ? 1 : -1;
2791 if (Index >= 0 && Index <=
last) {
2792 if (
index[Index].independent) {
2799 *FileNumber =
index[Index].number;
2800 *FileOffset =
index[Index].offset;
2803 uint16_t fn =
index[Index + 1].number;
2804 off_t fo =
index[Index + 1].offset;
2805 if (fn == *FileNumber)
2806 *Length = int(fo - *FileOffset);
2827 if (
index[Index].independent)
2833 if (
index[il].independent)
2840 if (
index[ih].independent)
2856 for (i = 0; i <=
last; i++) {
2857 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2886 if (*s && stat(s, &buf) == 0)
2887 return buf.st_size / (IsPesRecording ?
sizeof(tIndexTs) :
sizeof(tIndexPes));
2895 if (Recording.
Name()) {
2899 unlink(IndexFileName);
2901 while (IndexFileGenerator->
Active())
2903 if (access(IndexFileName, R_OK) == 0)
2906 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2909 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2912 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2915 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2921 #define MAXFILESPERRECORDINGPES 255
2922 #define RECORDFILESUFFIXPES "/%03d.vdr"
2923 #define MAXFILESPERRECORDINGTS 65535
2924 #define RECORDFILESUFFIXTS "/%05d.ts"
2925 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2937 esyslog(
"ERROR: can't copy file name '%s'", FileName);
2967 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2969 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2973 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2975 int Pid =
TsPid(buf);
2977 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2978 else if (PatPmtParser.
IsPmtPid(Pid)) {
2979 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2980 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2991 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
3005 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
3019 else if (errno != ENOENT)
3049 if (buf.st_size != 0)
3053 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3060 else if (errno != ENOENT) {
3067 if (!
record && Offset >= 0 &&
file->
Seek(Offset, SEEK_SET) != Offset) {
3074 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3096 while ((s = ReadLine.
Read(f)) != NULL)
3114 if (fputs(
doneRecordings[i], f) == EOF || fputc(
'\n', f) == EOF) {
3136 if (FILE *f = fopen(
fileName,
"a")) {
3142 esyslog(
"ERROR: can't open '%s' for appending '%s'", *
fileName, Title);
3159 const char *t = Title;
3165 if (toupper(
uchar(*s)) != toupper(
uchar(*t)))
3180 const char *Sign =
"";
3186 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3187 int s = int(Seconds);
3188 int m = s / 60 % 60;
3191 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3197 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3201 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3207 return int(round(Seconds * FramesPerSecond));
3216 else if (Length > Max) {
3217 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3220 int r = f->
Read(b, Length);
3240 if (fgets(buf,
sizeof(buf), f))
3269 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3270 if (FILE *f = fopen(FileName,
"w")) {
3271 fprintf(f,
"%s\n", TimerId);
3278 dsyslog(
"removing %s", *FileName);
3286 const char *Id = NULL;
3287 if (FILE *f = fopen(FileName,
"r")) {
3288 char buf[HOST_NAME_MAX + 10];
3289 if (fgets(buf,
sizeof(buf), f)) {
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder).
cIndexFileGenerator(const char *RecordingName, bool Update=false)
char language[MAXLANGCODE2]
#define RUC_COPIEDRECORDING
void SetEventID(tEventID EventID)
void SetErrors(int Errors)
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
static cRecordControl * GetRecordControl(const char *FileName)
static cString sprintf(const char *fmt,...) __attribute__((format(printf
void ResetResume(void) const
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
void ResetResume(const char *ResumeFileName=NULL)
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists).
cDoneRecordings DoneRecordingsPattern
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 cChannel * Channel(void) const
cResumeFile(const char *FileName, bool IsPesRecording)
void Add(cRecording *Recording)
void SetFramesPerSecond(double FramesPerSecond)
bool CatchUp(int Index=-1)
bool IsPesRecording(void) const
void DelAll(void)
Deletes/terminates all operations.
void SetFile(const char *File)
const cMark * GetNext(int Position) const
void ConvertToPes(tIndexTs *IndexTs, int Count)
const char * Name(void) const
virtual void Clear(void)
Immediately clears the ring buffer.
void AddByName(const char *FileName, bool TriggerUpdate=true)
static int GetLength(const char *FileName, bool IsPesRecording=false)
Calculates the recording length (number of frames) without actually reading the index file.
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
void ConvertFromPes(tIndexTs *IndexTs, int Count)
static const char * NowReplaying(void)
bool HasRecordingsSortMode(const char *Directory)
#define RECORDFILESUFFIXPES
cRecordingsHandler RecordingsHandler
#define INDEXFILETESTINTERVAL
struct dirent * Next(void)
const char * Title(void) const
#define RESUME_NOT_INITIALIZED
bool Completed(void)
Returns true if the PMT has been completely parsed.
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
virtual int Available(void)
const cComponents * Components(void) const
const char * Alang(int i) const
static bool MoveVideoFile(const char *FromName, const char *ToName)
const cRecording * GetByName(const char *FileName) const
#define MAXFILESPERRECORDINGPES
void SetData(const char *Title, const char *ShortText, const char *Description)
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
int PathIsInUse(const char *Path) const
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
cUnbufferedFile * NextFile(void)
cUnbufferedFile * Open(void)
static const char * Name(void)
void Cleanup(cRecordings *Recordings)
bool IsOnVideoDirectoryFileSystem(void) const
bool Finished(bool &Error)
Returns true if all operations in the list have been finished.
const char * Description(void) const
static bool IsOnVideoDirectoryFileSystem(const char *FileName)
void SetComponent(int Index, const char *s)
#define LOCK_DELETEDRECORDINGS_READ
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
static cString IndexFileName(const char *FileName, bool IsPesRecording)
void Add(cListObject *Object, cListObject *After=NULL)
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
static const char * FuzzyChars
const char * Title(void) const
static bool VideoFileSpaceAvailable(int SizeMB)
double FramesPerSecond(void) const
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
void SetTableID(uchar TableID)
ssize_t Read(void *Data, size_t Size)
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
static bool RenameVideoFile(const char *OldName, const char *NewName)
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
cDirCopier(const char *DirNameSrc, const char *DirNameDst)
static cRecordings recordings
int SecondsToFrames(int Seconds, double FramesPerSecond)
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
int Read(int FileHandle, int Max=0)
Reads at most Max bytes from FileHandle and stores them in the ring buffer.
const char * ShortText(void) const
int NumComponents(void) const
#define MAXWAITFORINDEXFILE
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.
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...
const cMark * GetPrev(int Position) const
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
const cComponents * Components(void) const
static const char * SkipFuzzyChars(const char *s)
void ParsePat(const uchar *Data, int Length)
Parses the PAT data from the single TS packet in Data.
static cRecordings deletedRecordings
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
const char * Aux(void) const
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 char * StripEpisodeName(char *s, bool Strip)
const char * Name(void) const
Returns the full name of the recording (without the video directory).
cRecordings * deletedRecordings
void Del(const char *FileName)
Deletes the given FileName from the list of operations.
bool Active(cRecordings *Recordings)
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
cRecordingsHandlerEntry * Get(const char *FileName)
const cMark * Get(int Position) const
void UpdateByName(const char *FileName)
bool IsSingleEvent(void) const
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected,...
int TsPid(const uchar *p)
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
void SetVersion(uchar Version)
int isOnVideoDirectoryFileSystem
void Del(cListObject *Object, bool DeleteObject=true)
#define RECORDFILESUFFIXLEN
#define LOCK_DELETEDRECORDINGS_WRITE
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
cString recordingFileName
const char * FileNameSrc(void) const
bool Contains(const char *Title) const
void Add(const char *Title)
const cMark * Last(void) const
Returns the last element in this list, or NULL if the list is empty.
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
int AlwaysSortFoldersFirst
static bool HasKeys(void)
const cRecording * GetById(int Id) const
const char * Dlang(int i) const
double FramesPerSecond(void) const
const char * PrefixFileName(char Prefix)
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
tCharExchange CharExchange[]
void SetAux(const char *Aux)
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
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 * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark.
int TotalFileSizeMB(void) const
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
time_t StartTime(void) const
the start time as given by the user
bool Active(void)
Returns true if the cutter is currently active.
void RemoveDeletedRecordings(void)
int GetClosestIFrame(int Index)
Returns the index of the I-frame that is closest to the given Index (or Index itself,...
#define SUMMARYFILESUFFIX
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
cString ToString(void) const
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...
static bool RemoveVideoFile(const char *FileName)
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
void SetStartTime(time_t StartTime)
static cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
#define INDEXFILECHECKINTERVAL
static int lastRecordingId
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
#define DEFAULTFRAMESPERSECOND
void SetTitle(const char *Title)
const char * InvalidChars
const char * File(void) const
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
const char * Aux(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.
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false, bool Update=false)
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
const char * Comment(void) const
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
const char * FileNameDst(void) const
#define RUC_DELETERECORDING
bool IsStillRecording(void)
uchar * Get(int &Count)
Gets data from the ring buffer.
static cVideoDirectoryScannerThread * videoDirectoryScannerThread
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
#define RUC_MOVEDRECORDING
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]=NULL)
cStringList doneRecordings
void SetFileName(const char *FileName)
static const tChannelID InvalidID
void ClearSortNames(void)
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
bool HasMarks(void) const
Returns true if this recording has any editing marks.
char * ExchangeChars(char *s, bool ToFileSystem)
const cMark * Prev(const cMark *Object) const
bool Parse(const char *s)
static const char * UpdateFileName(void)
cIndexFileGenerator * indexFileGenerator
cString GetRecordingTimerId(const char *Directory)
int HierarchyLevels(void) const
int GetNumRecordingsInPath(const char *Path) const
Returns the total number of recordings in the given Path, including all sub-folders of Path.
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
void Append(const char *Title)
static bool NeedsUpdate(void)
bool Lock(int WaitSeconds=0)
const char * Slang(int i) const
cRecordings(bool Deleted=false)
void GetRecordingsSortMode(const char *Directory)
#define RUC_COPYINGRECORDING
static int Utf8CharLen(const char *s)
static bool DeleteMarksFile(const cRecording *Recording)
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
cListObject * Next(void) const
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
static char * updateFileName
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
void DelByName(const char *FileName)
cList< cRecordingsHandlerEntry > operations
cMutex MutexMarkFramesPerSecond
void ScanVideoDir(const char *DirName, int LinkLevel=0, int DirLevel=0)
bool Undelete(void)
Changes the file name so that it will be visible in the "Recordings" menu again and not processed by ...
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.
bool Error(void)
Returns true if an error occurred while cutting the recording.
virtual ~cRecordingsHandler()
bool Write(FILE *f, const char *Prefix="") const
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
bool Active(void)
Checks whether the thread is still alive.
tChannelID GetChannelID(void) const
bool NeedsConversion(const char *p)
void IncRecordingsSortMode(const char *Directory)
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with.
bool TimedWait(cMutex &Mutex, int TimeoutMs)
void SetModified(void)
Unconditionally marks this list as modified.
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...
static cString PrefixVideoFileName(const char *FileName, char Prefix)
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
cRecording(const cRecording &)
#define TIMERMACRO_EPISODE
void SetRecordingTimerId(const char *Directory, const char *TimerId)
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
~cVideoDirectoryScannerThread()
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
virtual void Append(T Data)
int SystemExec(const char *Command, bool Detached)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
off_t Seek(off_t Offset, int Whence)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
#define MAXFILESPERRECORDINGTS
bool IsInPath(const char *Path) const
Returns true if this recording is stored anywhere under the given Path.
cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
~cRecordingsHandlerEntry()
#define LIMIT_SECS_PER_MB_RADIO
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
char * LimitNameLengths(char *s, int PathMax, int NameMax)
double MarkFramesPerSecond
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
int Usage(const char *FileName=NULL) const
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL)
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame.
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
char * SortName(void) const
static tChannelID FromString(const char *s)
static const char * command
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
bool IsEdited(void) const
void SetDuration(int Duration)
bool Start(void)
Starts the actual cutting process.
cRemoveDeletedRecordingsThread(void)
bool DeleteMarks(void)
Deletes the editing marks from this recording (if any).
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
int NumFrames(void) const
Returns the number of frames in this recording.
double MBperMinute(void) const
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown.
#define RECORDFILESUFFIXTS
const char * ShortText(void) const
#define LOCK_RECORDINGS_WRITE
bool Load(const char *FileName)
eRecordingsSortMode RecordingsSortMode
bool WriteInfo(const char *OtherFileName=NULL)
Writes in info file of this recording.
struct __attribute__((packed))
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.