15 #define __STDC_FORMAT_MACROS // Required for format specifiers
31 #define SUMMARYFALLBACK
44 #define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT
45 #define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT
46 #define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT
47 #define NAMEFORMATTS "%s/%s/" DATAFORMATTS
49 #define RESUMEFILESUFFIX "/resume%s%s"
50 #ifdef SUMMARYFALLBACK
51 #define SUMMARYFILESUFFIX "/summary.vdr"
53 #define INFOFILESUFFIX "/info"
54 #define MARKSFILESUFFIX "/marks"
56 #define SORTMODEFILE ".sort"
58 #define MINDISKSPACE 1024 // MB
60 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files
61 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed
62 #define DISKCHECKDELTA 100 // seconds between checks for free disk space
63 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file
64 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks
65 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written
67 #define MAX_LINK_LEVEL 6
86 :
cThread(
"remove deleted recordings", true)
94 if (LockFile.
Lock()) {
123 static time_t LastRemoveCheck = 0;
125 if (!RemoveDeletedRecordingsThread.
Active()) {
129 RemoveDeletedRecordingsThread.
Start();
134 LastRemoveCheck = time(NULL);
145 static time_t LastFreeDiskCheck = 0;
146 int Factor = (Priority == -1) ? 10 : 1;
147 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
151 if (!LockFile.
Lock())
154 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
182 isyslog(
"...no deleted recording found, trying to delete an old recording...");
209 isyslog(
"...no old recording found, giving up");
212 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
215 LastFreeDiskCheck = time(NULL);
231 esyslog(
"ERROR: can't allocate memory for resume file name");
245 if ((st.st_mode & S_IWUSR) == 0)
251 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
257 else if (errno != ENOENT)
266 while ((s = ReadLine.
Read(f)) != NULL) {
270 case 'I': resume = atoi(t);
277 else if (errno != ENOENT)
288 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
300 fprintf(f,
"I %d\n", Index);
317 else if (errno != ENOENT)
329 event = ownEvent ? ownEvent : Event;
342 for (
int i = 0; i <
MAXAPIDS; i++) {
343 const char *s = Channel->
Alang(i);
348 else if (strlen(s) > strlen(Component->
language))
355 for (
int i = 0; i <
MAXDPIDS; i++) {
356 const char *s = Channel->
Dlang(i);
363 else if (strlen(s) > strlen(Component->
language))
368 for (
int i = 0; i <
MAXSPIDS; i++) {
369 const char *s = Channel->
Slang(i);
374 else if (strlen(s) > strlen(Component->
language))
409 ((
cEvent *)event)->SetShortText(ShortText);
411 ((
cEvent *)event)->SetDescription(Description);
417 aux = Aux ? strdup(Aux) : NULL;
431 while ((s = ReadLine.
Read(f)) != NULL) {
436 char *p = strchr(t,
' ');
447 unsigned int EventID;
450 unsigned int TableID = 0;
451 unsigned int Version = 0xFF;
452 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
453 if (n >= 3 && n <= 5) {
473 esyslog(
"ERROR: EPG data problem in line %d", line);
488 event->Dump(f, Prefix,
true);
490 fprintf(f,
"%sP %d\n", Prefix,
priority);
491 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
493 fprintf(f,
"%s@ %s\n", Prefix,
aux);
509 else if (errno != ENOENT)
533 #define RESUME_NOT_INITIALIZED (-2)
566 case ' ': *p =
'_';
break;
573 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
577 sprintf(buf,
"#%02X", (
unsigned char)*p);
578 memmove(p + 2, p, strlen(p) + 1);
583 esyslog(
"ERROR: out of memory");
590 case '_': *p =
' ';
break;
595 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
597 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
601 memmove(p + 1, p + 3, strlen(p) - 2);
607 case '\x01': *p =
'\'';
break;
608 case '\x02': *p =
'/';
break;
609 case '\x03': *p =
':';
break;
615 for (
struct tCharExchange *ce = CharExchange; ce->
a && ce->b; ce++) {
616 if (*p == (ToFileSystem ? ce->a : ce->b)) {
617 *p = ToFileSystem ? ce->b : ce->a;
639 int Length = strlen(s);
642 bool NameTooLong =
false;
646 for (
char *p = s; *p; p++) {
649 NameTooLong |= NameLength > NameMax;
670 NameTooLong |= NameLength > NameMax;
678 while (i-- > 0 && a[i] >= 0) {
683 if (NameLength > NameMax) {
686 while (i-- > 0 && a[i] >= 0) {
688 if (NameLength - l <= NameMax) {
689 memmove(s + i, s + n, Length - n + 1);
690 memmove(a + i, a + n, Length - n + 1);
703 while (PathLength > PathMax && n > 0) {
708 while (--i > 0 && a[i - 1] >= 0) {
712 if (PathLength - l <= PathMax)
718 memmove(s + b, s + n, Length - n + 1);
744 const char *
Title = Event ? Event->
Title() : NULL;
745 const char *Subtitle = Event ? Event->
ShortText() : NULL;
752 if (macroTITLE || macroEPISODE) {
757 int l = strlen(name);
800 FileName =
fileName = strdup(FileName);
805 const char *p = strrchr(FileName,
'/');
810 time_t now = time(NULL);
812 struct tm t = *localtime_r(&now, &tm_r);
821 strncpy(
name, FileName, p - FileName);
831 FILE *f = fopen(InfoFileName,
"r");
834 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
842 else if (errno == ENOENT)
846 #ifdef SUMMARYFALLBACK
850 FILE *f = fopen(SummaryFileName,
"r");
853 char *data[3] = { NULL };
856 while ((s = ReadLine.
Read(f)) != NULL) {
857 if (*s || line > 1) {
860 len += strlen(data[line]) + 1;
861 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
862 data[line] = NewBuffer;
863 strcat(data[line],
"\n");
864 strcat(data[line], s);
867 esyslog(
"ERROR: out of memory");
870 data[line] = strdup(s);
880 else if (data[1] && data[2]) {
884 int len = strlen(data[1]);
886 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
888 strcat(data[1],
"\n");
889 strcat(data[1], data[2]);
895 esyslog(
"ERROR: out of memory");
899 for (
int i = 0; i < 3; i ++)
902 else if (errno != ENOENT)
921 char *t = s, *s1 = NULL, *s2 = NULL;
942 memmove(s1, s2, t - s2 + 1);
956 int l = strxfrm(NULL, s, 0) + 1;
989 struct tm *t = localtime_r(&
start, &tm_r);
994 if (strcmp(Name,
name) != 0)
995 dsyslog(
"recording file name '%s' truncated to '%s'",
name, Name);
1005 char New = NewIndicator &&
IsNew() ?
'*' :
' ';
1010 struct tm *t = localtime_r(&
start, &tm_r);
1044 const char *s =
name;
1077 if (FileName && *FileName) {
1087 const char *s =
name;
1099 s = !s ?
name : s + 1;
1121 FILE *f = fopen(InfoFileName,
"w");
1141 char *NewName = strdup(
FileName());
1142 char *ext = strrchr(NewName,
'.');
1143 if (ext && strcmp(ext,
RECEXT) == 0) {
1144 strncpy(ext,
DELEXT, strlen(ext));
1145 if (access(NewName, F_OK) == 0) {
1147 isyslog(
"removing recording '%s'", NewName);
1151 if (access(
FileName(), F_OK) == 0) {
1178 char *NewName = strdup(
FileName());
1179 char *ext = strrchr(NewName,
'.');
1180 if (ext && strcmp(ext,
DELEXT) == 0) {
1181 strncpy(ext,
RECEXT, strlen(ext));
1182 if (access(NewName, F_OK) == 0) {
1184 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1243 :
cThread(
"video directory scanner")
1281 while ((Foreground ||
Running()) && (e = d.
Next()) != NULL) {
1284 if (lstat(buffer, &st) == 0) {
1286 if (S_ISLNK(st.st_mode)) {
1288 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1292 if (stat(buffer, &st) != 0)
1295 if (S_ISDIR(st.st_mode)) {
1320 int NewState =
state;
1321 bool Result = State != NewState;
1337 if (lastModified > time(NULL))
1357 if (strcmp(recording->FileName(), FileName) == 0)
1383 Del(recording,
false);
1384 char *ext = strrchr(recording->
fileName,
'.');
1385 if (ext && RemoveRecording) {
1386 strncpy(ext,
DELEXT, strlen(ext));
1387 if (access(recording->
FileName(), F_OK) == 0) {
1388 recording->
deleted = time(NULL);
1412 int FileSizeMB = recording->FileSizeMB();
1413 if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
1425 if (recording->IsOnVideoDirectoryFileSystem()) {
1426 int FileSizeMB = recording->FileSizeMB();
1427 if (FileSizeMB > 0) {
1428 int LengthInSeconds = recording->LengthInSeconds();
1429 if (LengthInSeconds > 0) {
1431 length += LengthInSeconds;
1436 return (size && length) ? double(size) * 60 / length : -1;
1443 if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
1444 recording->ResetResume();
1453 recording->ClearSortName();
1482 const char *p = strchr(s,
' ');
1493 return fprintf(f,
"%s", *
ToText()) > 0;
1498 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
1512 time_t t = time(NULL);
1516 lastChange = LastModified > 0 ? LastModified : t;
1529 cMutexLock MutexLock(&MutexMarkFramesPerSecond);
1555 if (
int d = m->Position() - p) {
1566 if (m2->Position() < m1->Position()) {
1567 swap(m1->position, m2->position);
1568 swap(m1->comment, m2->comment);
1583 if (mi->Position() == Position)
1592 if (mi->Position() < Position)
1601 if (mi->Position() > Position)
1611 while (
cMark *NextMark =
Next(BeginMark)) {
1612 if (BeginMark->
Position() == NextMark->Position()) {
1613 if (!(BeginMark =
Next(NextMark)))
1629 while (
cMark *NextMark =
Next(EndMark)) {
1630 if (EndMark->
Position() == NextMark->Position()) {
1631 if (!(EndMark =
Next(NextMark)))
1643 int NumSequences = 0;
1651 if (NumSequences == 1 && BeginMark->Position() == 0)
1655 return NumSequences;
1670 isyslog(
"executing '%s'", *cmd);
1677 #define IFG_BUFFER_SIZE KILOBYTE(100)
1683 virtual void Action(
void);
1690 :
cThread(
"index file generator")
1691 ,recordingName(RecordingName)
1703 bool IndexFileComplete =
false;
1704 bool IndexFileWritten =
false;
1705 bool Rewind =
false;
1714 off_t FrameOffset = -1;
1727 if (FrameDetector.
Synced()) {
1730 FrameOffset = FileSize;
1731 int Processed = FrameDetector.
Analyze(Data, Length);
1732 if (Processed > 0) {
1736 IndexFileWritten =
true;
1738 FileSize += Processed;
1739 Buffer.
Del(Processed);
1742 else if (PatPmtParser.
Vpid()) {
1744 int Processed = FrameDetector.
Analyze(Data, Length);
1745 if (Processed > 0) {
1746 if (FrameDetector.
Synced()) {
1750 Buffer.
Del(Processed);
1756 while (Length >= TS_SIZE) {
1760 else if (PatPmtParser.
IsPmtPid(Pid))
1764 if (PatPmtParser.
Vpid()) {
1772 Buffer.
Del(p - Data);
1776 else if (ReplayFile) {
1777 int Result = Buffer.
Read(ReplayFile, BufferChunks);
1787 IndexFileComplete =
true;
1791 if (IndexFileComplete) {
1792 if (IndexFileWritten) {
1794 if (RecordingInfo.
Read()) {
1797 RecordingInfo.
Write();
1813 #define INDEXFILESUFFIX "/index"
1816 #define MAXINDEXCATCHUP 8 // number of retries
1817 #define INDEXCATCHUPWAIT 100 // milliseconds
1831 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
1840 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
1841 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
1842 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
1845 :resumeFile(FileName, IsPesRecording)
1855 if (!Record && PauseLive) {
1862 if (!Record && access(
fileName, R_OK) != 0) {
1871 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
1877 delta = int(buf.st_size %
sizeof(
tIndexTs));
1880 esyslog(
"ERROR: invalid file size (%"PRId64
") in '%s'", buf.st_size, *
fileName);
1882 last = int((buf.st_size + delta) /
sizeof(
tIndexTs) - 1);
1883 if (!Record &&
last >= 0) {
1915 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
1917 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
1944 while (Count-- > 0) {
1945 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
1956 while (Count-- > 0) {
1961 memcpy(IndexTs, &IndexPes,
sizeof(*IndexTs));
1973 for (
int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >=
last); i++) {
1975 if (fstat(
f, &buf) == 0) {
1976 int newLast = int(buf.st_size /
sizeof(
tIndexTs) - 1);
1977 if (newLast >
last) {
1979 if (NewSize <= newLast) {
1981 if (NewSize <= newLast)
1982 NewSize = newLast + 1;
1989 if (lseek(
f, offset, SEEK_SET) == offset) {
1991 esyslog(
"ERROR: can't read from index");
2006 esyslog(
"ERROR: can't realloc() index");
2019 return index != NULL;
2025 tIndexTs i(FileOffset, Independent, FileNumber);
2039 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2042 if (Index >= 0 && Index <=
last) {
2051 if (fn == *FileNumber)
2052 *Length = int(fo - *FileOffset);
2068 int d = Forward ? 1 : -1;
2071 if (Index >= 0 && Index <=
last) {
2072 if (
index[Index].independent) {
2085 if (fn == *FileNumber)
2086 *Length = int(fo - *FileOffset);
2107 if (
index[Index].independent)
2113 if (
index[il].independent)
2120 if (
index[ih].independent)
2136 for (i = 0; i <=
last; i++) {
2137 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2166 if (*s && stat(s, &buf) == 0)
2175 if (Recording.
Name()) {
2178 unlink(IndexFileName);
2180 while (IndexFileGenerator->
Active())
2182 if (access(IndexFileName, R_OK) == 0)
2185 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2188 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2191 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2194 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2200 #define MAXFILESPERRECORDINGPES 255
2201 #define RECORDFILESUFFIXPES "/%03d.vdr"
2202 #define MAXFILESPERRECORDINGTS 65535
2203 #define RECORDFILESUFFIXTS "/%05d.ts"
2204 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2242 for (; Number > 0; Number--) {
2246 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2248 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2252 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2254 int Pid =
TsPid(buf);
2256 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2257 else if (PatPmtParser.
IsPmtPid(Pid)) {
2258 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2259 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2270 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2284 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
2298 else if (errno != ENOENT)
2319 if (0 < Number && Number <= MaxFilesPerRecording) {
2327 if (buf.st_size != 0)
2331 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
2338 else if (errno != ENOENT) {
2352 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
2365 const char *Sign =
"";
2371 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1);
2372 int s = int(Seconds);
2373 int m = s / 60 % 60;
2376 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
2382 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
2386 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1;
2392 return int(round(Seconds * FramesPerSecond));
2401 else if (Length > Max) {
2402 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
2405 int r = f->
Read(b, Length);
2424 if (fgets(buf,
sizeof(buf), f))