15#define __STDC_FORMAT_MACROS
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"
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
64#define REMOVECHECKDELTA 60
65#define DELETEDLIFETIME 300
66#define DISKCHECKDELTA 100
67#define REMOVELATENCY 10
68#define MARKSUPDATEDELTA 10
69#define MAXREMOVETIME 10
71#define MAX_LINK_LEVEL 6
73#define LIMIT_SECS_PER_MB_RADIO 5
90:
cThread(
"remove deleted recordings", true)
98 if (LockFile.
Lock()) {
99 time_t StartTime = time(NULL);
100 bool deleted =
false;
101 bool interrupted =
false;
103 for (
cRecording *r = DeletedRecordings->First(); r; ) {
115 DeletedRecordings->Del(r);
120 r = DeletedRecordings->
Next(r);
138 static time_t LastRemoveCheck = 0;
142 for (
const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->Next(r)) {
149 LastRemoveCheck = time(NULL);
160 static time_t LastFreeDiskCheck = 0;
161 int Factor = (Priority == -1) ? 10 : 1;
162 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
166 if (!LockFile.
Lock())
169 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
170 int NumDeletedRecordings = 0;
173 NumDeletedRecordings = DeletedRecordings->Count();
174 if (NumDeletedRecordings) {
182 r = DeletedRecordings->
Next(r);
187 DeletedRecordings->Del(r0);
192 if (NumDeletedRecordings == 0) {
197 if (DeletedRecordings->Count())
202 isyslog(
"...no deleted recording found, trying to delete an old recording...");
204 Recordings->SetExplicitModify();
205 if (Recordings->Count()) {
222 r = Recordings->
Next(r);
226 Recordings->SetModified();
231 isyslog(
"...no old recording found, giving up");
234 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
237 LastFreeDiskCheck = time(NULL);
253 esyslog(
"ERROR: can't allocate memory for resume file name");
267 if ((st.st_mode & S_IWUSR) == 0)
273 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
279 else if (errno != ENOENT)
288 while ((s = ReadLine.
Read(f)) != NULL) {
292 case 'I': resume = atoi(t);
299 else if (errno != ENOENT)
310 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
322 fprintf(f,
"I %d\n", Index);
351 else if (errno != ENOENT)
377 for (
int i = 0; i <
MAXAPIDS; i++) {
378 const char *s = Channel->
Alang(i);
383 else if (strlen(s) > strlen(Component->
language))
390 for (
int i = 0; i <
MAXDPIDS; i++) {
391 const char *s = Channel->
Dlang(i);
398 else if (strlen(s) > strlen(Component->
language))
403 for (
int i = 0; i <
MAXSPIDS; i++) {
404 const char *s = Channel->
Slang(i);
409 else if (strlen(s) > strlen(Component->
language))
479 while ((s = ReadLine.
Read(f)) != NULL) {
484 char *p = strchr(t,
' ');
495 unsigned int EventID;
498 unsigned int TableID = 0;
499 unsigned int Version = 0xFF;
500 int n = sscanf(t,
"%u %jd %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
501 if (n >= 3 && n <= 5) {
516 case 'O':
errors = atoi(t);
523 esyslog(
"ERROR: EPG data problem in line %d", line);
538 event->Dump(f, Prefix,
true);
540 fprintf(f,
"%sP %d\n", Prefix,
priority);
541 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
542 fprintf(f,
"%sO %d\n", Prefix,
errors);
544 fprintf(f,
"%s@ %s\n", Prefix,
aux);
560 else if (errno != ENOENT)
584#define RESUME_NOT_INITIALIZED (-2)
617 case ' ': *p =
'_';
break;
624 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
628 sprintf(buf,
"#%02X", (
unsigned char)*p);
629 memmove(p + 2, p, strlen(p) + 1);
634 esyslog(
"ERROR: out of memory");
641 case '_': *p =
' ';
break;
646 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
648 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
652 memmove(p + 1, p + 3, strlen(p) - 2);
658 case '\x01': *p =
'\'';
break;
659 case '\x02': *p =
'/';
break;
660 case '\x03': *p =
':';
break;
667 if (*p == (ToFileSystem ? ce->a : ce->b)) {
668 *p = ToFileSystem ? ce->b : ce->a;
690 int Length = strlen(s);
693 bool NameTooLong =
false;
697 for (
char *p = s; *p; p++) {
700 NameTooLong |= NameLength > NameMax;
721 NameTooLong |= NameLength > NameMax;
729 while (i-- > 0 && a[i] >= 0) {
734 if (NameLength > NameMax) {
737 while (i-- > 0 && a[i] >= 0) {
739 if (NameLength - l <= NameMax) {
740 memmove(s + i, s + n, Length - n + 1);
741 memmove(a + i, a + n, Length - n + 1);
754 while (PathLength > PathMax && n > 0) {
759 while (--i > 0 && a[i - 1] >= 0) {
763 if (PathLength - l <= PathMax)
769 memmove(s + b, s + n, Length - n + 1);
796 const char *
Title = Event ? Event->
Title() : NULL;
797 const char *Subtitle = Event ? Event->
ShortText() : NULL;
804 if (macroTITLE || macroEPISODE) {
809 int l = strlen(
name);
856 const char *p = strrchr(
FileName,
'/');
861 time_t now = time(NULL);
863 struct tm t = *localtime_r(&now, &tm_r);
882 FILE *f = fopen(InfoFileName,
"r");
885 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
893 else if (errno != ENOENT)
895#ifdef SUMMARYFALLBACK
899 FILE *f = fopen(SummaryFileName,
"r");
902 char *data[3] = { NULL };
905 while ((s = ReadLine.
Read(f)) != NULL) {
906 if (*s || line > 1) {
909 len += strlen(data[line]) + 1;
910 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
911 data[line] = NewBuffer;
912 strcat(data[line],
"\n");
913 strcat(data[line], s);
916 esyslog(
"ERROR: out of memory");
919 data[line] = strdup(s);
929 else if (data[1] && data[2]) {
933 int len = strlen(data[1]);
935 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
937 strcat(data[1],
"\n");
938 strcat(data[1], data[2]);
944 esyslog(
"ERROR: out of memory");
948 for (
int i = 0; i < 3; i ++)
951 else if (errno != ENOENT)
972 char *t = s, *s1 = NULL, *s2 = NULL;
993 memmove(s1, s2, t - s2 + 1);
1006 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&
start, &tm_r));
1014 int l = strxfrm(NULL, s, 0) + 1;
1057 int l = strlen(Path);
1077 struct tm *t = localtime_r(&
start, &tm_r);
1093 const char *New = NewIndicator &&
IsNew() ?
"*" :
"";
1094 const char *Err = NewIndicator && (
info->
Errors() > 0) ?
"!" :
"";
1099 struct tm *t = localtime_r(&
start, &tm_r);
1134 const char *s =
name;
1167 const char *s =
name;
1210 if (!OtherFileName) {
1213 if (ExistingInfo.
Read())
1238 dsyslog(
"changing priority/lifetime of '%s' to %d/%d",
Name(), NewPriority, NewLifetime);
1262 if (strcmp(NewName,
Name())) {
1263 dsyslog(
"changing name of '%s' to '%s'",
Name(), NewName);
1269 name = strdup(NewName);
1271 bool Exists = access(NewFileName, F_OK) == 0;
1273 esyslog(
"ERROR: recording '%s' already exists", NewName);
1276 name = strdup(OldName);
1290 char *NewName = strdup(
FileName());
1291 char *ext = strrchr(NewName,
'.');
1292 if (ext && strcmp(ext,
RECEXT) == 0) {
1293 strncpy(ext,
DELEXT, strlen(ext));
1294 if (access(NewName, F_OK) == 0) {
1296 isyslog(
"removing recording '%s'", NewName);
1300 if (access(
FileName(), F_OK) == 0) {
1327 char *NewName = strdup(
FileName());
1328 char *ext = strrchr(NewName,
'.');
1329 if (ext && strcmp(ext,
DELEXT) == 0) {
1330 strncpy(ext,
RECEXT, strlen(ext));
1331 if (access(NewName, F_OK) == 0) {
1333 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1409 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1411 virtual void Action(
void);
1418:
cThread(
"video directory scanner", true)
1454 if (lstat(buffer, &st) == 0) {
1456 if (S_ISLNK(st.st_mode)) {
1458 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1462 if (stat(buffer, &st) != 0)
1465 if (S_ISDIR(st.st_mode)) {
1473 Recordings->
Lock(StateKey,
true);
1475 dsyslog(
"activated name checking for initial read of video directory");
1500 if (!
initial && DirLevel == 0) {
1506 if (access(r->
FileName(), F_OK) != 0)
1552 if (lastModified > time(NULL))
1572 if (Recording->Id() == Id)
1582 if (strcmp(Recording->FileName(), FileName) == 0)
1609 Recording = dummy =
new cRecording(FileName);
1612 Del(Recording,
false);
1613 char *ext = strrchr(Recording->
fileName,
'.');
1615 strncpy(ext,
DELEXT, strlen(ext));
1616 if (access(Recording->
FileName(), F_OK) == 0) {
1618 DeletedRecordings->Add(Recording);
1629 Recording->ReadInfo();
1636 int FileSizeMB = Recording->FileSizeMB();
1637 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1648 if (Recording->IsOnVideoDirectoryFileSystem()) {
1649 int FileSizeMB = Recording->FileSizeMB();
1650 if (FileSizeMB > 0) {
1651 int LengthInSeconds = Recording->LengthInSeconds();
1652 if (LengthInSeconds > 0) {
1655 length += LengthInSeconds;
1661 return (size && length) ? double(size) * 60 / length : -1;
1668 if (Recording->IsInPath(Path))
1669 Use |= Recording->IsInUse();
1678 if (Recording->IsInPath(Path))
1686 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1687 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1690 if (Recording->IsInPath(OldPath)) {
1691 const char *p = Recording->Name() + strlen(OldPath);
1693 if (!Recording->ChangeName(NewName))
1707 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1708 Recording->ResetResume();
1715 Recording->ClearSortName();
1727 virtual void Action(
void);
1729 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1752 dsyslog(
"suspending copy thread");
1758 dsyslog(
"resuming copy thread");
1775 size_t BufferSize = BUFSIZ;
1776 uchar *Buffer = NULL;
1790 size_t Read =
safe_read(From, Buffer, BufferSize);
1792 size_t Written =
safe_write(To, Buffer, Read);
1793 if (Written != Read) {
1794 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1798 else if (Read == 0) {
1800 if (fsync(To) < 0) {
1801 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1804 if (close(From) < 0) {
1805 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1808 if (close(To) < 0) {
1809 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1813 off_t FileSizeSrc =
FileSize(FileNameSrc);
1814 off_t FileSizeDst =
FileSize(FileNameDst);
1815 if (FileSizeSrc != FileSizeDst) {
1816 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1821 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1825 else if ((e = d.
Next()) != NULL) {
1830 if (stat(FileNameSrc, &st) < 0) {
1831 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1834 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1835 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1838 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1840 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1843 esyslog(
"ERROR: out of memory");
1847 if (access(FileNameDst, F_OK) == 0) {
1848 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1851 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1852 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1855 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1856 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1895 int Usage(
const char *FileName = NULL)
const;
1923 if (FileName && *FileName) {
1972 if (Recording.
Delete()) {
2032 Recordings->SetExplicitModify();
2035 if (!r->Active(Recordings)) {
2036 error |= r->Error();
2037 r->Cleanup(Recordings);
2053 if (FileName && *FileName) {
2057 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2066 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2069 if (FileNameSrc && *FileNameSrc) {
2070 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2072 if (Usage ==
ruCut && !FileNameDst)
2074 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2082 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2085 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2088 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2091 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2113 return r->Usage(FileName);
2155 const char *p = strchr(s,
' ');
2166 return fprintf(f,
"%s\n", *
ToText()) > 0;
2179 if (errno != ENOENT) {
2187bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2201 time_t t = time(NULL);
2205 lastChange = LastModified > 0 ? LastModified : t;
2244 if (m->Position() - p) {
2255 if (m2->Position() < m1->Position()) {
2256 swap(m1->position, m2->position);
2257 swap(m1->comment, m2->comment);
2272 if (mi->Position() == Position)
2281 if (mi->Position() < Position)
2290 if (mi->Position() > Position)
2299 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2300 while (
const cMark *NextMark =
Next(BeginMark)) {
2301 if (BeginMark->
Position() == NextMark->Position()) {
2302 if (!(BeginMark =
Next(NextMark)))
2317 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2318 while (
const cMark *NextMark =
Next(EndMark)) {
2319 if (EndMark->
Position() == NextMark->Position()) {
2320 if (!(EndMark =
Next(NextMark)))
2332 int NumSequences = 0;
2340 if (NumSequences == 1 && BeginMark->Position() == 0)
2344 return NumSequences;
2359 isyslog(
"executing '%s'", *cmd);
2366#define IFG_BUFFER_SIZE KILOBYTE(100)
2373 virtual void Action(
void);
2380:
cThread(
"index file generator")
2381,recordingName(RecordingName)
2394 bool IndexFileComplete =
false;
2395 bool IndexFileWritten =
false;
2396 bool Rewind =
false;
2405 off_t FrameOffset = -1;
2406 uint16_t FileNumber = 1;
2407 off_t FileOffset = 0;
2413 Last = IndexFile.
Last();
2414 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2418 isyslog(
"updating index file");
2421 isyslog(
"generating index file");
2425 bool Stuffed =
false;
2429 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2438 if (FrameDetector.
Synced()) {
2442 int Processed = FrameDetector.
Analyze(Data, Length);
2443 if (Processed > 0) {
2445 if (IndexFileWritten || Last < 0)
2448 IndexFileWritten =
true;
2451 Buffer.
Del(Processed);
2456 int Processed = FrameDetector.
Analyze(Data, Length);
2457 if (Processed > 0) {
2458 if (FrameDetector.
Synced()) {
2462 Buffer.
Del(Processed);
2472 else if (PatPmtParser.
IsPmtPid(Pid))
2478 FrameDetector.
SetPid(PatPmtParser.
Vpid() ? PatPmtParser.
Vpid() : PatPmtParser.
Apid(0), PatPmtParser.
Vpid() ? PatPmtParser.
Vtype() : PatPmtParser.
Atype(0));
2484 Buffer.
Del(p - Data);
2488 else if (ReplayFile) {
2489 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2491 if (Buffer.
Available() > 0 && !Stuffed) {
2500 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2514 IndexFileComplete =
true;
2519 if (IndexFileComplete) {
2520 if (IndexFileWritten) {
2522 if (RecordingInfo.
Read()) {
2525 RecordingInfo.
Write();
2542#define INDEXFILESUFFIX "/index"
2545#define MAXINDEXCATCHUP 8
2546#define INDEXCATCHUPWAIT 100
2560 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2564 independent = Independent;
2569#define MAXWAITFORINDEXFILE 10
2570#define INDEXFILECHECKINTERVAL 500
2571#define INDEXFILETESTINTERVAL 10
2574:resumeFile(FileName, IsPesRecording)
2584 if (!Record && PauseLive) {
2587 while (time(NULL) < tmax &&
FileSize(
fileName) < off_t(2 *
sizeof(tIndexTs)))
2600 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2606 delta = int(buf.st_size %
sizeof(tIndexTs));
2608 delta =
sizeof(tIndexTs) - delta;
2609 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2611 last = int((buf.st_size + delta) /
sizeof(tIndexTs) - 1);
2612 if ((!Record || Update) &&
last >= 0) {
2637 esyslog(
"ERROR: can't allocate %zd bytes for index '%s'",
size *
sizeof(tIndexTs), *
fileName);
2649 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2651 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2678 while (Count-- > 0) {
2679 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2680 IndexTs->offset = IndexPes.offset;
2681 IndexTs->independent = IndexPes.type == 1;
2682 IndexTs->number = IndexPes.number;
2690 while (Count-- > 0) {
2691 IndexPes.offset = uint32_t(IndexTs->offset);
2692 IndexPes.type =
uchar(IndexTs->independent ? 1 : 2);
2693 IndexPes.number =
uchar(IndexTs->number);
2694 IndexPes.reserved = 0;
2695 memcpy((
void *)IndexTs, &IndexPes,
sizeof(*IndexTs));
2709 if (fstat(
f, &buf) == 0) {
2710 int newLast = int(buf.st_size /
sizeof(tIndexTs) - 1);
2711 if (newLast >
last) {
2713 if (NewSize <= newLast) {
2715 if (NewSize <= newLast)
2716 NewSize = newLast + 1;
2718 if (tIndexTs *NewBuffer = (tIndexTs *)realloc(
index, NewSize *
sizeof(tIndexTs))) {
2721 int offset = (
last + 1) *
sizeof(tIndexTs);
2722 int delta = (newLast -
last) *
sizeof(tIndexTs);
2723 if (lseek(
f, offset, SEEK_SET) == offset) {
2725 esyslog(
"ERROR: can't read from index");
2740 esyslog(
"ERROR: can't realloc() index");
2753 return index != NULL;
2759 tIndexTs i(FileOffset, Independent, FileNumber);
2773bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2776 if (Index >= 0 && Index <=
last) {
2777 *FileNumber =
index[Index].number;
2778 *FileOffset =
index[Index].offset;
2780 *Independent =
index[Index].independent;
2783 uint16_t fn =
index[Index + 1].number;
2784 off_t fo =
index[Index + 1].offset;
2785 if (fn == *FileNumber)
2786 *Length = int(fo - *FileOffset);
2802 int d = Forward ? 1 : -1;
2805 if (Index >= 0 && Index <=
last) {
2806 if (
index[Index].independent) {
2813 *FileNumber =
index[Index].number;
2814 *FileOffset =
index[Index].offset;
2817 uint16_t fn =
index[Index + 1].number;
2818 off_t fo =
index[Index + 1].offset;
2819 if (fn == *FileNumber)
2820 *Length = int(fo - *FileOffset);
2841 if (
index[Index].independent)
2847 if (
index[il].independent)
2854 if (
index[ih].independent)
2870 for (i = 0; i <=
last; i++) {
2871 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2900 if (*s && stat(s, &buf) == 0)
2901 return buf.st_size / (IsPesRecording ?
sizeof(tIndexTs) :
sizeof(tIndexPes));
2909 if (Recording.
Name()) {
2913 unlink(IndexFileName);
2915 while (IndexFileGenerator->
Active())
2917 if (access(IndexFileName, R_OK) == 0)
2920 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2923 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2926 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2929 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2935#define MAXFILESPERRECORDINGPES 255
2936#define RECORDFILESUFFIXPES "/%03d.vdr"
2937#define MAXFILESPERRECORDINGTS 65535
2938#define RECORDFILESUFFIXTS "/%05d.ts"
2939#define RECORDFILESUFFIXLEN 20
2951 esyslog(
"ERROR: can't copy file name '%s'", FileName);
2981 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2983 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2987 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2989 int Pid =
TsPid(buf);
2991 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2992 else if (PatPmtParser.
IsPmtPid(Pid)) {
2993 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2994 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
3005 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
3019 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
3033 else if (errno != ENOENT)
3063 if (buf.st_size != 0)
3067 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3074 else if (errno != ENOENT) {
3081 if (!
record && Offset >= 0 &&
file->
Seek(Offset, SEEK_SET) != Offset) {
3088 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3110 while ((s = ReadLine.
Read(f)) != NULL)
3128 if (fputs(
doneRecordings[i], f) == EOF || fputc(
'\n', f) == EOF) {
3150 if (FILE *f = fopen(
fileName,
"a")) {
3156 esyslog(
"ERROR: can't open '%s' for appending '%s'", *
fileName, Title);
3173 const char *t = Title;
3179 if (toupper(
uchar(*s)) != toupper(
uchar(*t)))
3194 const char *Sign =
"";
3200 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3201 int s = int(Seconds);
3202 int m = s / 60 % 60;
3205 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3211 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3215 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3221 return int(round(Seconds * FramesPerSecond));
3230 else if (Length > Max) {
3231 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3234 int r = f->
Read(b, Length);
3254 if (fgets(buf,
sizeof(buf), f))
3283 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3284 if (FILE *f = fopen(FileName,
"w")) {
3285 fprintf(f,
"%s\n", TimerId);
3292 dsyslog(
"removing %s", *FileName);
3300 const char *Id = NULL;
3301 if (FILE *f = fopen(FileName,
"r")) {
3302 char buf[HOST_NAME_MAX + 10];
3303 if (fgets(buf,
sizeof(buf), f)) {
const char * Slang(int i) const
const char * Name(void) const
tChannelID GetChannelID(void) const
const char * Dlang(int i) const
const char * Alang(int i) const
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
int NumComponents(void) const
void SetComponent(int Index, const char *s)
bool TimedWait(cMutex &Mutex, int TimeoutMs)
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
bool Start(void)
Starts the actual cutting process.
bool Error(void)
Returns true if an error occurred while cutting the recording.
bool Active(void)
Returns true if the cutter is currently active.
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
cDirCopier(const char *DirNameSrc, const char *DirNameDst)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cStringList doneRecordings
void Add(const char *Title)
void Append(const char *Title)
bool Load(const char *FileName)
bool Contains(const char *Title) const
const char * ShortText(void) const
const cComponents * Components(void) const
const char * Title(void) const
void SetStartTime(time_t StartTime)
void SetEventID(tEventID EventID)
void SetVersion(uchar Version)
void SetDuration(int Duration)
void SetTitle(const char *Title)
void SetTableID(uchar TableID)
cUnbufferedFile * NextFile(void)
cUnbufferedFile * Open(void)
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame.
cIndexFileGenerator(const char *RecordingName, bool Update=false)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
bool IsStillRecording(void)
void ConvertFromPes(tIndexTs *IndexTs, int Count)
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
static int GetLength(const char *FileName, bool IsPesRecording=false)
Calculates the recording length (number of frames) without actually reading the index file.
bool CatchUp(int Index=-1)
void ConvertToPes(tIndexTs *IndexTs, int Count)
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false, bool Update=false)
cIndexFileGenerator * indexFileGenerator
static cString IndexFileName(const char *FileName, bool IsPesRecording)
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL)
int GetClosestIFrame(int Index)
Returns the index of the I-frame that is closest to the given Index (or Index itself,...
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
void Del(cListObject *Object, bool DeleteObject=true)
void SetModified(void)
Unconditionally marks this list as modified.
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
void Add(cListObject *Object, cListObject *After=NULL)
cListObject * Next(void) const
const T * Prev(const T *Object) const
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
const T * Last(void) const
Returns the last element in this list, or NULL if the list is empty.
bool Lock(int WaitSeconds=0)
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
bool Parse(const char *s)
const char * Comment(void) const
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
const cMark * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark.
const cMark * GetNext(int Position) const
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
const cMark * GetNextEnd(const cMark *BeginMark) const
Returns the next "end" mark after BeginMark, skipping any marks at the same position as BeginMark.
const cMark * Get(int Position) const
cString recordingFileName
static bool DeleteMarksFile(const cRecording *Recording)
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists).
const cMark * GetPrev(int Position) const
bool GetVersions(int &PatVersion, int &PmtVersion) const
Returns true if a valid PAT/PMT has been parsed and stores the current version numbers in the given v...
int Vtype(void) const
Returns the video stream type as defined by the current PMT, or 0 if no video stream type has been de...
void ParsePat(const uchar *Data, int Length)
Parses the PAT data from the single TS packet in Data.
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
bool Completed(void)
Returns true if the PMT has been completely parsed.
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected,...
struct dirent * Next(void)
static cRecordControl * GetRecordControl(const char *FileName)
void SetFramesPerSecond(double FramesPerSecond)
const char * ShortText(void) const
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
bool Write(FILE *f, const char *Prefix="") const
const char * Title(void) const
const char * Aux(void) const
void SetFileName(const char *FileName)
void SetErrors(int Errors)
void SetAux(const char *Aux)
void SetData(const char *Title, const char *ShortText, const char *Description)
const char * Description(void) const
double FramesPerSecond(void) const
const cComponents * Components(void) const
static const char * command
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
int isOnVideoDirectoryFileSystem
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater",...
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
bool HasMarks(void) const
Returns true if this recording has any editing marks.
bool WriteInfo(const char *OtherFileName=NULL)
Writes in info file of this recording.
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with.
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
bool Undelete(void)
Changes the file name so that it will be visible in the "Recordings" menu again and not processed by ...
void ResetResume(void) const
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
int NumFrames(void) const
Returns the number of frames in this recording.
bool IsEdited(void) const
int GetResume(void) const
Returns the index of the frame where replay of this recording shall be resumed, or -1 in case of an e...
bool IsInPath(const char *Path) const
Returns true if this recording is stored anywhere under the given Path.
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
char * SortName(void) const
const char * Name(void) const
Returns the full name of the recording (without the video directory).
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
const char * PrefixFileName(char Prefix)
bool DeleteMarks(void)
Deletes the editing marks from this recording (if any).
bool IsOnVideoDirectoryFileSystem(void) const
int HierarchyLevels(void) const
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder).
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
cRecording(const cRecording &)
double FramesPerSecond(void) const
bool IsPesRecording(void) const
static char * StripEpisodeName(char *s, bool Strip)
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
const char * FileNameSrc(void) const
void Cleanup(cRecordings *Recordings)
~cRecordingsHandlerEntry()
int Usage(const char *FileName=NULL) const
bool Active(cRecordings *Recordings)
const char * FileNameDst(void) const
cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
void DelAll(void)
Deletes/terminates all operations.
cRecordingsHandlerEntry * Get(const char *FileName)
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
bool Finished(bool &Error)
Returns true if all operations in the list have been finished.
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
cList< cRecordingsHandlerEntry > operations
void Del(const char *FileName)
Deletes the given FileName from the list of operations.
virtual ~cRecordingsHandler()
void ResetResume(const char *ResumeFileName=NULL)
void UpdateByName(const char *FileName)
static const char * UpdateFileName(void)
double MBperMinute(void) const
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown.
cRecordings(bool Deleted=false)
int GetNumRecordingsInPath(const char *Path) const
Returns the total number of recordings in the given Path, including all sub-folders of Path.
const cRecording * GetById(int Id) const
static cRecordings deletedRecordings
void AddByName(const char *FileName, bool TriggerUpdate=true)
static cRecordings recordings
int TotalFileSizeMB(void) const
static void Update(bool Wait=false)
Triggers an update of the list of recordings, which will run as a separate thread if Wait is false.
static cRecordings * GetRecordingsWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for write access.
static void TouchUpdate(void)
Touches the '.update' file in the video directory, so that other instances of VDR that access the sam...
void Add(cRecording *Recording)
static cVideoDirectoryScannerThread * videoDirectoryScannerThread
void DelByName(const char *FileName)
bool MoveRecordings(const char *OldPath, const char *NewPath)
Moves all recordings in OldPath to NewPath.
static bool NeedsUpdate(void)
void ClearSortNames(void)
static int lastRecordingId
const cRecording * GetByName(const char *FileName) const
static char * updateFileName
int PathIsInUse(const char *Path) const
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
static bool HasKeys(void)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cRemoveDeletedRecordingsThread(void)
static const char * NowReplaying(void)
cResumeFile(const char *FileName, bool IsPesRecording)
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
virtual int Available(void)
virtual void Clear(void)
Immediately clears the ring buffer.
uchar * Get(int &Count)
Gets data from the ring buffer.
int Read(int FileHandle, int Max=0)
Reads at most Max bytes from FileHandle and stores them in the ring buffer.
int AlwaysSortFoldersFirst
char SVDRPHostName[HOST_NAME_MAX]
int QueueMessage(eMessageType Type, const char *s, int Seconds=0, int Timeout=0)
Like Message(), but this function may be called from a background thread.
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
static cString sprintf(const char *fmt,...) __attribute__((format(printf
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
bool Active(void)
Checks whether the thread is still alive.
const char * Aux(void) const
const char * File(void) const
bool IsSingleEvent(void) const
void SetFile(const char *File)
time_t StartTime(void) const
the start time as given by the user
const cChannel * Channel(void) const
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
ssize_t Read(void *Data, size_t Size)
off_t Seek(off_t Offset, int Whence)
virtual void Append(T Data)
cRecordings * deletedRecordings
void ScanVideoDir(const char *DirName, int LinkLevel=0, int DirLevel=0)
~cVideoDirectoryScannerThread()
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
static cString PrefixVideoFileName(const char *FileName, char Prefix)
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]=NULL)
static bool IsOnVideoDirectoryFileSystem(const char *FileName)
static const char * Name(void)
static cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
static bool VideoFileSpaceAvailable(int SizeMB)
static bool MoveVideoFile(const char *FromName, const char *ToName)
static bool RenameVideoFile(const char *OldName, const char *NewName)
static bool RemoveVideoFile(const char *FileName)
#define TIMERMACRO_EPISODE
#define MAXFILESPERRECORDINGTS
tCharExchange CharExchange[]
cString GetRecordingTimerId(const char *Directory)
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
static const char * SkipFuzzyChars(const char *s)
void AssertFreeDiskSpace(int Priority, bool Force)
The special Priority value -1 means that we shall get rid of any deleted recordings faster than norma...
void GetRecordingsSortMode(const char *Directory)
char * LimitNameLengths(char *s, int PathMax, int NameMax)
static const char * FuzzyChars
bool NeedsConversion(const char *p)
int SecondsToFrames(int Seconds, double FramesPerSecond)
eRecordingsSortMode RecordingsSortMode
bool HasRecordingsSortMode(const char *Directory)
#define MAXFILESPERRECORDINGPES
#define INDEXFILETESTINTERVAL
#define MAXWAITFORINDEXFILE
#define INDEXFILECHECKINTERVAL
char * ExchangeChars(char *s, bool ToFileSystem)
void IncRecordingsSortMode(const char *Directory)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
#define LIMIT_SECS_PER_MB_RADIO
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
cDoneRecordings DoneRecordingsPattern
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
cRecordingsHandler RecordingsHandler
cMutex MutexMarkFramesPerSecond
static bool StillRecording(const char *Directory)
struct __attribute__((packed))
#define RESUME_NOT_INITIALIZED
#define RECORDFILESUFFIXLEN
#define RECORDFILESUFFIXPES
void SetRecordingTimerId(const char *Directory, const char *TimerId)
#define RECORDFILESUFFIXTS
double MarkFramesPerSecond
const char * InvalidChars
void RemoveDeletedRecordings(void)
#define SUMMARYFILESUFFIX
#define DEFAULTFRAMESPERSECOND
int HMSFToIndex(const char *HMSF, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
eRecordingsSortMode RecordingsSortMode
#define RUC_COPIEDRECORDING
#define LOCK_DELETEDRECORDINGS_WRITE
char * ExchangeChars(char *s, bool ToFileSystem)
#define RUC_DELETERECORDING
#define RUC_MOVEDRECORDING
cRecordingsHandler RecordingsHandler
#define RUC_COPYINGRECORDING
#define LOCK_DELETEDRECORDINGS_READ
#define LOCK_RECORDINGS_WRITE
cString IndexToHMSF(int Index, bool WithFrame=false, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
int TsPid(const uchar *p)
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
static const tChannelID InvalidID
static tChannelID FromString(const char *s)
cString ToString(void) const
char language[MAXLANGCODE2]
int SystemExec(const char *Command, bool Detached)