16 #define __STDC_FORMAT_MACROS // Required for format specifiers 35 #define SUMMARYFALLBACK 48 #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT 49 #define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT 50 #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT 51 #define NAMEFORMATTS "%s/%s/" DATAFORMATTS 53 #define RESUMEFILESUFFIX "/resume%s%s" 54 #ifdef SUMMARYFALLBACK 55 #define SUMMARYFILESUFFIX "/summary.vdr" 57 #define INFOFILESUFFIX "/info" 58 #define MARKSFILESUFFIX "/marks" 60 #define SORTMODEFILE ".sort" 61 #define TIMERRECFILE ".timer" 63 #define MINDISKSPACE 1024 // MB 65 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files 66 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed 67 #define DISKCHECKDELTA 100 // seconds between checks for free disk space 68 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file 69 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks 70 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written 71 #define MAXREMOVETIME 10 // seconds after which to return from removing deleted recordings 73 #define MAX_LINK_LEVEL 6 75 #define LIMIT_SECS_PER_MB_RADIO 5 // radio recordings typically have more than this 92 :
cThread(
"remove deleted recordings", true)
100 if (LockFile.
Lock()) {
101 time_t StartTime = time(NULL);
102 bool deleted =
false;
104 for (
cRecording *r = DeletedRecordings->First(); r; ) {
114 DeletedRecordings->Del(r);
119 r = DeletedRecordings->
Next(r);
134 static time_t LastRemoveCheck = 0;
138 for (
const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->
Next(r)) {
145 LastRemoveCheck = time(NULL);
156 static time_t LastFreeDiskCheck = 0;
157 int Factor = (Priority == -1) ? 10 : 1;
158 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
162 if (!LockFile.
Lock())
165 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
166 int NumDeletedRecordings = 0;
169 NumDeletedRecordings = DeletedRecordings->Count();
170 if (NumDeletedRecordings) {
178 r = DeletedRecordings->
Next(r);
183 DeletedRecordings->Del(r0);
188 if (NumDeletedRecordings == 0) {
193 if (DeletedRecordings->Count())
198 isyslog(
"...no deleted recording found, trying to delete an old recording...");
200 Recordings->SetExplicitModify();
201 if (Recordings->Count()) {
218 r = Recordings->
Next(r);
222 Recordings->SetModified();
227 isyslog(
"...no old recording found, giving up");
230 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
233 LastFreeDiskCheck = time(NULL);
249 esyslog(
"ERROR: can't allocate memory for resume file name");
263 if ((st.st_mode & S_IWUSR) == 0)
269 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
275 else if (errno != ENOENT)
284 while ((s = ReadLine.
Read(f)) != NULL) {
288 case 'I': resume = atoi(t);
295 else if (errno != ENOENT)
306 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
319 fprintf(f,
"I %d\n", Index);
339 else if (errno != ENOENT)
364 for (
int i = 0; i <
MAXAPIDS; i++) {
365 const char *s = Channel->
Alang(i);
370 else if (strlen(s) > strlen(Component->
language))
377 for (
int i = 0; i <
MAXDPIDS; i++) {
378 const char *s = Channel->
Dlang(i);
385 else if (strlen(s) > strlen(Component->
language))
390 for (
int i = 0; i <
MAXSPIDS; i++) {
391 const char *s = Channel->
Slang(i);
396 else if (strlen(s) > strlen(Component->
language))
460 while ((s = ReadLine.
Read(f)) != NULL) {
465 char *p = strchr(t,
' ');
476 unsigned int EventID;
479 unsigned int TableID = 0;
480 unsigned int Version = 0xFF;
481 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
482 if (n >= 3 && n <= 5) {
502 esyslog(
"ERROR: EPG data problem in line %d", line);
517 event->Dump(f, Prefix,
true);
519 fprintf(f,
"%sP %d\n", Prefix,
priority);
520 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
522 fprintf(f,
"%s@ %s\n", Prefix,
aux);
538 else if (errno != ENOENT)
562 #define RESUME_NOT_INITIALIZED (-2) 595 case ' ': *p =
'_';
break;
602 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
606 sprintf(buf,
"#%02X", (
unsigned char)*p);
607 memmove(p + 2, p, strlen(p) + 1);
612 esyslog(
"ERROR: out of memory");
619 case '_': *p =
' ';
break;
624 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
626 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
630 memmove(p + 1, p + 3, strlen(p) - 2);
636 case '\x01': *p =
'\'';
break;
637 case '\x02': *p =
'/';
break;
638 case '\x03': *p =
':';
break;
645 if (*p == (ToFileSystem ? ce->a : ce->b)) {
646 *p = ToFileSystem ? ce->b : ce->a;
668 int Length = strlen(s);
671 bool NameTooLong =
false;
675 for (
char *p = s; *p; p++) {
678 NameTooLong |= NameLength > NameMax;
699 NameTooLong |= NameLength > NameMax;
707 while (i-- > 0 && a[i] >= 0) {
712 if (NameLength > NameMax) {
715 while (i-- > 0 && a[i] >= 0) {
717 if (NameLength - l <= NameMax) {
718 memmove(s + i, s + n, Length - n + 1);
719 memmove(a + i, a + n, Length - n + 1);
732 while (PathLength > PathMax && n > 0) {
737 while (--i > 0 && a[i - 1] >= 0) {
741 if (PathLength - l <= PathMax)
747 memmove(s + b, s + n, Length - n + 1);
774 const char *
Title = Event ? Event->
Title() : NULL;
775 const char *Subtitle = Event ? Event->
ShortText() : NULL;
782 if (macroTITLE || macroEPISODE) {
787 int l = strlen(
name);
834 const char *p = strrchr(
FileName,
'/');
839 time_t now = time(NULL);
841 struct tm t = *localtime_r(&now, &tm_r);
860 FILE *f = fopen(InfoFileName,
"r");
863 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
871 else if (errno == ENOENT)
875 #ifdef SUMMARYFALLBACK 879 FILE *f = fopen(SummaryFileName,
"r");
882 char *data[3] = { NULL };
885 while ((s = ReadLine.
Read(f)) != NULL) {
886 if (*s || line > 1) {
889 len += strlen(data[line]) + 1;
890 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
891 data[line] = NewBuffer;
892 strcat(data[line],
"\n");
893 strcat(data[line], s);
896 esyslog(
"ERROR: out of memory");
899 data[line] = strdup(s);
909 else if (data[1] && data[2]) {
913 int len = strlen(data[1]);
915 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
917 strcat(data[1],
"\n");
918 strcat(data[1], data[2]);
924 esyslog(
"ERROR: out of memory");
928 for (
int i = 0; i < 3; i ++)
931 else if (errno != ENOENT)
950 char *t = s, *s1 = NULL, *s2 = NULL;
971 memmove(s1, s2, t - s2 + 1);
984 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&
start, &tm_r));
992 int l = strxfrm(NULL, s, 0) + 1;
1035 int l = strlen(Path);
1057 struct tm *t = localtime_r(&
start, &tm_r);
1078 struct tm *t = localtime_r(&
start, &tm_r);
1112 const char *s =
name;
1145 const char *s =
name;
1157 s = !s ?
name : s + 1;
1209 dsyslog(
"changing priority/lifetime of '%s' to %d/%d",
Name(), NewPriority, NewLifetime);
1233 if (strcmp(NewName,
Name())) {
1234 dsyslog(
"changing name of '%s' to '%s'",
Name(), NewName);
1240 name = strdup(NewName);
1242 bool Exists = access(NewFileName, F_OK) == 0;
1244 esyslog(
"ERROR: recording '%s' already exists", NewName);
1247 name = strdup(OldName);
1261 char *NewName = strdup(
FileName());
1262 char *ext = strrchr(NewName,
'.');
1263 if (ext && strcmp(ext,
RECEXT) == 0) {
1264 strncpy(ext,
DELEXT, strlen(ext));
1265 if (access(NewName, F_OK) == 0) {
1267 isyslog(
"removing recording '%s'", NewName);
1271 if (access(
FileName(), F_OK) == 0) {
1298 char *NewName = strdup(
FileName());
1299 char *ext = strrchr(NewName,
'.');
1300 if (ext && strcmp(ext,
DELEXT) == 0) {
1301 strncpy(ext,
RECEXT, strlen(ext));
1302 if (access(NewName, F_OK) == 0) {
1304 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1374 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1376 virtual void Action(
void);
1383 :
cThread(
"video directory scanner", true)
1417 if (lstat(buffer, &st) == 0) {
1419 if (S_ISLNK(st.st_mode)) {
1421 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1425 if (stat(buffer, &st) != 0)
1428 if (S_ISDIR(st.st_mode)) {
1436 Recordings->
Lock(StateKey,
true);
1458 if (!
initial && DirLevel == 0) {
1464 if (access(r->
FileName(), F_OK) != 0)
1510 if (lastModified > time(NULL))
1530 if (Recording->Id() == Id)
1540 if (strcmp(Recording->FileName(), FileName) == 0)
1567 Recording = dummy =
new cRecording(FileName);
1570 Del(Recording,
false);
1571 char *ext = strrchr(Recording->
fileName,
'.');
1573 strncpy(ext,
DELEXT, strlen(ext));
1574 if (access(Recording->
FileName(), F_OK) == 0) {
1576 DeletedRecordings->Add(Recording);
1587 Recording->ReadInfo();
1594 int FileSizeMB = Recording->FileSizeMB();
1595 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1606 if (Recording->IsOnVideoDirectoryFileSystem()) {
1607 int FileSizeMB = Recording->FileSizeMB();
1608 if (FileSizeMB > 0) {
1609 int LengthInSeconds = Recording->LengthInSeconds();
1610 if (LengthInSeconds > 0) {
1613 length += LengthInSeconds;
1619 return (size && length) ? double(size) * 60 / length : -1;
1626 if (Recording->IsInPath(Path))
1627 Use |= Recording->IsInUse();
1636 if (Recording->IsInPath(Path))
1644 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1645 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1648 if (Recording->IsInPath(OldPath)) {
1649 const char *p = Recording->Name() + strlen(OldPath);
1651 if (!Recording->ChangeName(NewName))
1665 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1666 Recording->ResetResume();
1673 Recording->ClearSortName();
1685 virtual void Action(
void);
1687 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1710 dsyslog(
"suspending copy thread");
1716 dsyslog(
"resuming copy thread");
1733 size_t BufferSize = BUFSIZ;
1734 uchar *Buffer = NULL;
1748 size_t Read =
safe_read(From, Buffer, BufferSize);
1750 size_t Written =
safe_write(To, Buffer, Read);
1751 if (Written != Read) {
1752 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1756 else if (Read == 0) {
1758 if (fsync(To) < 0) {
1759 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1762 if (close(From) < 0) {
1763 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1766 if (close(To) < 0) {
1767 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1771 off_t FileSizeSrc =
FileSize(FileNameSrc);
1772 off_t FileSizeDst =
FileSize(FileNameDst);
1773 if (FileSizeSrc != FileSizeDst) {
1774 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1779 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1783 else if ((e = d.
Next()) != NULL) {
1788 if (stat(FileNameSrc, &st) < 0) {
1789 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1792 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1793 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1796 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1798 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1801 esyslog(
"ERROR: out of memory");
1805 if (access(FileNameDst, F_OK) == 0) {
1806 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1809 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1810 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1813 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1814 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1853 int Usage(
const char *FileName = NULL)
const;
1881 if (FileName && *FileName) {
1965 :
cThread(
"recordings handler")
1982 Recordings->SetExplicitModify();
1985 if (!r->Active(Recordings)) {
1986 error |= r->Error();
1987 r->Cleanup(Recordings);
2003 if (FileName && *FileName) {
2007 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2016 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2019 if (FileNameSrc && *FileNameSrc) {
2020 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2022 if (Usage ==
ruCut && !FileNameDst)
2024 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2032 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2035 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2038 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2041 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2063 return r->Usage(FileName);
2105 const char *p = strchr(s,
' ');
2116 return fprintf(f,
"%s\n", *
ToText()) > 0;
2129 if (errno != ENOENT) {
2137 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2151 time_t t = time(NULL);
2155 lastChange = LastModified > 0 ? LastModified : t;
2194 if (m->Position() - p) {
2205 if (m2->Position() < m1->Position()) {
2206 swap(m1->position, m2->position);
2207 swap(m1->comment, m2->comment);
2222 if (mi->Position() == Position)
2231 if (mi->Position() < Position)
2240 if (mi->Position() > Position)
2249 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2250 while (
const cMark *NextMark =
Next(BeginMark)) {
2251 if (BeginMark->
Position() == NextMark->Position()) {
2252 if (!(BeginMark =
Next(NextMark)))
2267 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2268 while (
const cMark *NextMark =
Next(EndMark)) {
2269 if (EndMark->
Position() == NextMark->Position()) {
2270 if (!(EndMark =
Next(NextMark)))
2282 int NumSequences = 0;
2290 if (NumSequences == 1 && BeginMark->Position() == 0)
2294 return NumSequences;
2309 isyslog(
"executing '%s'", *cmd);
2316 #define IFG_BUFFER_SIZE KILOBYTE(100) 2323 virtual void Action(
void);
2330 :
cThread(
"index file generator")
2331 ,recordingName(RecordingName)
2344 bool IndexFileComplete =
false;
2345 bool IndexFileWritten =
false;
2346 bool Rewind =
false;
2355 off_t FrameOffset = -1;
2356 uint16_t FileNumber = 1;
2357 off_t FileOffset = 0;
2363 Last = IndexFile.
Last();
2364 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2368 isyslog(
"updating index file");
2371 isyslog(
"generating index file");
2374 bool Stuffed =
false;
2378 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2387 if (FrameDetector.
Synced()) {
2391 int Processed = FrameDetector.
Analyze(Data, Length);
2392 if (Processed > 0) {
2394 if (IndexFileWritten || Last < 0)
2397 IndexFileWritten =
true;
2400 Buffer.
Del(Processed);
2405 int Processed = FrameDetector.
Analyze(Data, Length);
2406 if (Processed > 0) {
2407 if (FrameDetector.
Synced()) {
2411 Buffer.
Del(Processed);
2421 else if (PatPmtParser.
IsPmtPid(Pid))
2427 FrameDetector.
SetPid(PatPmtParser.
Vpid() ? PatPmtParser.
Vpid() : PatPmtParser.
Apid(0), PatPmtParser.
Vpid() ? PatPmtParser.
Vtype() : PatPmtParser.
Atype(0));
2433 Buffer.
Del(p - Data);
2437 else if (ReplayFile) {
2438 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2440 if (Buffer.
Available() > 0 && !Stuffed) {
2449 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2463 IndexFileComplete =
true;
2467 if (IndexFileComplete) {
2468 if (IndexFileWritten) {
2470 if (RecordingInfo.
Read()) {
2473 RecordingInfo.
Write();
2490 #define INDEXFILESUFFIX "/index" 2493 #define MAXINDEXCATCHUP 8 // number of retries 2494 #define INDEXCATCHUPWAIT 100 // milliseconds 2508 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2517 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds) 2518 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file 2519 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video 2522 :resumeFile(FileName, IsPesRecording)
2532 if (!Record && PauseLive) {
2539 if (!Record && access(
fileName, R_OK) != 0) {
2548 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2554 delta = int(buf.st_size %
sizeof(
tIndexTs));
2557 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2559 last = int((buf.st_size + delta) /
sizeof(
tIndexTs) - 1);
2560 if ((!Record || Update) &&
last >= 0) {
2592 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2594 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2621 while (Count-- > 0) {
2622 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2633 while (Count-- > 0) {
2638 memcpy(IndexTs, &IndexPes,
sizeof(*IndexTs));
2652 if (fstat(
f, &buf) == 0) {
2653 int newLast = int(buf.st_size /
sizeof(
tIndexTs) - 1);
2654 if (newLast >
last) {
2656 if (NewSize <= newLast) {
2658 if (NewSize <= newLast)
2659 NewSize = newLast + 1;
2666 if (lseek(
f, offset, SEEK_SET) == offset) {
2668 esyslog(
"ERROR: can't read from index");
2683 esyslog(
"ERROR: can't realloc() index");
2696 return index != NULL;
2702 tIndexTs i(FileOffset, Independent, FileNumber);
2716 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2719 if (Index >= 0 && Index <=
last) {
2728 if (fn == *FileNumber)
2729 *Length = int(fo - *FileOffset);
2745 int d = Forward ? 1 : -1;
2748 if (Index >= 0 && Index <=
last) {
2749 if (
index[Index].independent) {
2762 if (fn == *FileNumber)
2763 *Length = int(fo - *FileOffset);
2784 if (
index[Index].independent)
2790 if (
index[il].independent)
2797 if (
index[ih].independent)
2813 for (i = 0; i <=
last; i++) {
2814 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2843 if (*s && stat(s, &buf) == 0)
2852 if (Recording.
Name()) {
2856 unlink(IndexFileName);
2858 while (IndexFileGenerator->
Active())
2860 if (access(IndexFileName, R_OK) == 0)
2863 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2866 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2869 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2872 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2878 #define MAXFILESPERRECORDINGPES 255 2879 #define RECORDFILESUFFIXPES "/%03d.vdr" 2880 #define MAXFILESPERRECORDINGTS 65535 2881 #define RECORDFILESUFFIXTS "/%05d.ts" 2882 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... 2924 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2926 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2930 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2932 int Pid =
TsPid(buf);
2934 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2935 else if (PatPmtParser.
IsPmtPid(Pid)) {
2936 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2937 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2948 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2962 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
2976 else if (errno != ENOENT)
3006 if (buf.st_size != 0)
3010 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3017 else if (errno != ENOENT) {
3031 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3044 const char *Sign =
"";
3050 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3051 int s = int(Seconds);
3052 int m = s / 60 % 60;
3055 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3061 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3065 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3071 return int(round(Seconds * FramesPerSecond));
3080 else if (Length > Max) {
3081 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3084 int r = f->
Read(b, Length);
3104 if (fgets(buf,
sizeof(buf), f))
3133 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3134 if (FILE *f = fopen(FileName,
"w")) {
3135 fprintf(f,
"%s\n", TimerId);
3142 dsyslog(
"removing %s", *FileName);
3150 const char *Id = NULL;
3151 if (FILE *f = fopen(FileName,
"r")) {
3152 char buf[HOST_NAME_MAX + 10];
3153 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 ...