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_SUBTITLE_LENGTH 40
69 #define MAX_LINK_LEVEL 6
86 :
cThread(
"remove deleted recordings")
96 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...");
181 isyslog(
"...no deleted recording found, trying to delete an old recording...");
208 isyslog(
"...no old recording found, giving up");
211 LastFreeDiskCheck = time(NULL);
227 esyslog(
"ERROR: can't allocate memory for resume file name");
241 if ((st.st_mode & S_IWUSR) == 0)
247 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
253 else if (errno != ENOENT)
262 while ((s = ReadLine.
Read(f)) != NULL) {
266 case 'I': resume = atoi(t);
273 else if (errno != ENOENT)
284 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
296 fprintf(f,
"I %d\n", Index);
313 else if (errno != ENOENT)
325 event = ownEvent ? ownEvent : Event;
338 for (
int i = 0; i <
MAXAPIDS; i++) {
339 const char *s = Channel->
Alang(i);
344 else if (strlen(s) > strlen(Component->
language))
351 for (
int i = 0; i <
MAXDPIDS; i++) {
352 const char *s = Channel->
Dlang(i);
359 else if (strlen(s) > strlen(Component->
language))
364 for (
int i = 0; i <
MAXSPIDS; i++) {
365 const char *s = Channel->
Slang(i);
370 else if (strlen(s) > strlen(Component->
language))
405 ((
cEvent *)event)->SetShortText(ShortText);
407 ((
cEvent *)event)->SetDescription(Description);
413 aux = Aux ? strdup(Aux) : NULL;
427 while ((s = ReadLine.
Read(f)) != NULL) {
432 char *p = strchr(t,
' ');
443 unsigned int EventID;
446 unsigned int TableID = 0;
447 unsigned int Version = 0xFF;
448 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
449 if (n >= 3 && n <= 5) {
469 esyslog(
"ERROR: EPG data problem in line %d", line);
484 event->Dump(f, Prefix,
true);
486 fprintf(f,
"%sP %d\n", Prefix,
priority);
487 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
489 fprintf(f,
"%s@ %s\n", Prefix,
aux);
505 else if (errno != ENOENT)
529 #define RESUME_NOT_INITIALIZED (-2)
551 const char *InvalidChars =
"\"\\/:*?|<>#";
554 case ' ': *p =
'_';
break;
558 if (strchr(InvalidChars, *p) || *p ==
'.' && (!*(p + 1) || *(p + 1) ==
FOLDERDELIMCHAR)) {
560 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
564 sprintf(buf,
"#%02X", (
unsigned char)*p);
565 memmove(p + 2, p, strlen(p) + 1);
570 esyslog(
"ERROR: out of memory");
577 case '_': *p =
' ';
break;
581 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
583 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
587 memmove(p + 1, p + 3, strlen(p) - 2);
593 case '\x01': *p =
'\'';
break;
594 case '\x02': *p =
'/';
break;
595 case '\x03': *p =
':';
break;
601 for (
struct tCharExchange *ce = CharExchange; ce->
a && ce->b; ce++) {
602 if (*p == (ToFileSystem ? ce->a : ce->b)) {
603 *p = ToFileSystem ? ce->b : ce->a;
629 const char *
Title = Event ? Event->
Title() : NULL;
630 const char *Subtitle = Event ? Event->
ShortText() : NULL;
639 Subtitle = SubtitleBuffer;
643 if (macroTITLE || macroEPISODE) {
648 int l = strlen(name);
691 FileName =
fileName = strdup(FileName);
695 const char *p = strrchr(FileName,
'/');
700 time_t now = time(NULL);
702 struct tm t = *localtime_r(&now, &tm_r);
711 strncpy(
name, FileName, p - FileName);
721 FILE *f = fopen(InfoFileName,
"r");
724 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
732 else if (errno == ENOENT)
736 #ifdef SUMMARYFALLBACK
740 FILE *f = fopen(SummaryFileName,
"r");
743 char *data[3] = { NULL };
746 while ((s = ReadLine.
Read(f)) != NULL) {
747 if (*s || line > 1) {
750 len += strlen(data[line]) + 1;
751 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
752 data[line] = NewBuffer;
753 strcat(data[line],
"\n");
754 strcat(data[line], s);
757 esyslog(
"ERROR: out of memory");
760 data[line] = strdup(s);
770 else if (data[1] && data[2]) {
774 int len = strlen(data[1]);
776 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
778 strcat(data[1],
"\n");
779 strcat(data[1], data[2]);
785 esyslog(
"ERROR: out of memory");
789 for (
int i = 0; i < 3; i ++)
792 else if (errno != ENOENT)
811 char *t = s, *s1 = NULL, *s2 = NULL;
831 memmove(s1, s2, t - s2 + 1);
843 int l = strxfrm(NULL, s, 0) + 1;
870 struct tm *t = localtime_r(&
start, &tm_r);
883 char New = NewIndicator &&
IsNew() ?
'*' :
' ';
888 struct tm *t = localtime_r(&
start, &tm_r);
922 const char *s =
name;
955 if (FileName && *FileName) {
965 const char *s =
name;
977 s = !s ?
name : s + 1;
999 FILE *f = fopen(InfoFileName,
"w");
1019 char *NewName = strdup(
FileName());
1020 char *ext = strrchr(NewName,
'.');
1021 if (ext && strcmp(ext,
RECEXT) == 0) {
1022 strncpy(ext,
DELEXT, strlen(ext));
1023 if (access(NewName, F_OK) == 0) {
1025 isyslog(
"removing recording '%s'", NewName);
1029 if (access(
FileName(), F_OK) == 0) {
1056 char *NewName = strdup(
FileName());
1057 char *ext = strrchr(NewName,
'.');
1058 if (ext && strcmp(ext,
DELEXT) == 0) {
1059 strncpy(ext,
RECEXT, strlen(ext));
1060 if (access(NewName, F_OK) == 0) {
1062 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1121 :
cThread(
"video directory scanner")
1159 while ((Foreground ||
Running()) && (e = d.
Next()) != NULL) {
1162 if (lstat(buffer, &st) == 0) {
1164 if (S_ISLNK(st.st_mode)) {
1166 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1170 if (stat(buffer, &st) != 0)
1173 if (S_ISDIR(st.st_mode)) {
1198 int NewState =
state;
1199 bool Result = State != NewState;
1215 if (lastModified > time(NULL))
1235 if (strcmp(recording->FileName(), FileName) == 0)
1261 Del(recording,
false);
1262 char *ext = strrchr(recording->
fileName,
'.');
1263 if (ext && RemoveRecording) {
1264 strncpy(ext,
DELEXT, strlen(ext));
1265 if (access(recording->
FileName(), F_OK) == 0) {
1266 recording->
deleted = time(NULL);
1290 int FileSizeMB = recording->FileSizeMB();
1291 if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
1303 if (recording->IsOnVideoDirectoryFileSystem()) {
1304 int FileSizeMB = recording->FileSizeMB();
1305 if (FileSizeMB > 0) {
1306 int LengthInSeconds = recording->LengthInSeconds();
1307 if (LengthInSeconds > 0) {
1309 length += LengthInSeconds;
1314 return (size && length) ? double(size) * 60 / length : -1;
1321 if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
1322 recording->ResetResume();
1353 const char *p = strchr(s,
' ');
1364 return fprintf(f,
"%s", *
ToText()) > 0;
1369 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
1381 time_t t = time(NULL);
1385 lastChange = LastModified > 0 ? LastModified : t;
1398 cMutexLock MutexLock(&MutexMarkFramesPerSecond);
1413 if (m2->Position() < m1->Position()) {
1414 swap(m1->position, m2->position);
1415 swap(m1->comment, m2->comment);
1434 if (mi->Position() == Position)
1443 if (mi->Position() < Position)
1452 if (mi->Position() > Position)
1470 isyslog(
"executing '%s'", *cmd);
1477 #define IFG_BUFFER_SIZE KILOBYTE(100)
1483 virtual void Action(
void);
1490 :
cThread(
"index file generator")
1491 ,recordingName(RecordingName)
1503 bool IndexFileComplete =
false;
1504 bool IndexFileWritten =
false;
1505 bool Rewind =
false;
1514 off_t FrameOffset = -1;
1527 if (FrameDetector.
Synced()) {
1530 FrameOffset = FileSize;
1531 int Processed = FrameDetector.
Analyze(Data, Length);
1532 if (Processed > 0) {
1536 IndexFileWritten =
true;
1538 FileSize += Processed;
1539 Buffer.
Del(Processed);
1542 else if (PatPmtParser.
Vpid()) {
1544 int Processed = FrameDetector.
Analyze(Data, Length);
1545 if (Processed > 0) {
1546 if (FrameDetector.
Synced()) {
1548 FrameDetector.
Reset();
1551 Buffer.
Del(Processed);
1557 while (Length >= TS_SIZE) {
1561 else if (Pid == PatPmtParser.
PmtPid())
1565 if (PatPmtParser.
Vpid()) {
1573 Buffer.
Del(p - Data);
1577 else if (ReplayFile) {
1578 int Result = Buffer.
Read(ReplayFile, BufferChunks);
1587 IndexFileComplete =
true;
1591 if (IndexFileComplete) {
1592 if (IndexFileWritten) {
1605 #define INDEXFILESUFFIX "/index"
1608 #define MAXINDEXCATCHUP 8 // seconds
1622 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
1631 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
1632 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
1633 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
1639 :resumeFile(FileName, IsPesRecording)
1649 if (!Record && PauseLive) {
1656 if (!Record && access(
fileName, R_OK) != 0) {
1665 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
1671 delta = int(buf.st_size %
sizeof(
tIndexTs));
1674 esyslog(
"ERROR: invalid file size (%"PRId64
") in '%s'", buf.st_size, *
fileName);
1676 last = int((buf.st_size + delta) /
sizeof(
tIndexTs) - 1);
1677 if (!Record &&
last >= 0) {
1707 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
1709 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
1739 while (Count-- > 0) {
1740 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
1751 while (Count-- > 0) {
1756 memcpy(IndexTs, &IndexPes,
sizeof(*IndexTs));
1766 for (
int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >=
last); i++) {
1768 if (fstat(
f, &buf) == 0) {
1774 int newLast = int(buf.st_size /
sizeof(
tIndexTs) - 1);
1775 if (newLast >
last) {
1777 if (NewSize <= newLast) {
1779 if (NewSize <= newLast)
1780 NewSize = newLast + 1;
1787 if (lseek(
f, offset, SEEK_SET) == offset) {
1789 esyslog(
"ERROR: can't read from index");
1804 esyslog(
"ERROR: can't realloc() index");
1816 return index != NULL;
1822 tIndexTs i(FileOffset, Independent, FileNumber);
1836 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
1839 if (Index >= 0 && Index <
last) {
1847 if (fn == *FileNumber)
1848 *Length = int(fo - *FileOffset);
1861 int d = Forward ? 1 : -1;
1864 if (Index >= 0 && Index <
last) {
1865 if (
index[Index].independent) {
1878 if (fn == *FileNumber)
1879 *Length = int(fo - *FileOffset);
1881 esyslog(
"ERROR: 'I' frame at end of file #%d", *FileNumber);
1900 for (i = 0; i <
last; i++) {
1901 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
1930 if (*s && stat(s, &buf) == 0)
1972 if (Recording.
Name()) {
1975 unlink(IndexFileName);
1977 while (IndexFileGenerator->
Active())
1979 if (access(IndexFileName, R_OK) == 0)
1982 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
1985 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
1988 fprintf(stderr,
"'%s' is not a recording\n", FileName);
1991 fprintf(stderr,
"'%s' is not a directory\n", FileName);
1997 #define MAXFILESPERRECORDINGPES 255
1998 #define RECORDFILESUFFIXPES "/%03d.vdr"
1999 #define MAXFILESPERRECORDINGTS 65535
2000 #define RECORDFILESUFFIXTS "/%05d.ts"
2001 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2039 for (; Number > 0; Number--) {
2043 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2045 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2049 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2051 int Pid =
TsPid(buf);
2053 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2054 else if (Pid == PatPmtParser.
PmtPid()) {
2055 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2056 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2067 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2081 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
2095 else if (errno != ENOENT)
2116 if (0 < Number && Number <= MaxFilesPerRecording) {
2124 if (buf.st_size != 0)
2128 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
2135 else if (errno != ENOENT) {
2149 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
2159 /
max(maxVideoFileSize - setupMaxVideoFileSize, 1);
2162 return MEGABYTE(off_t(setupMaxVideoFileSize));
2164 return MEGABYTE(off_t(maxVideoFileSize));
2176 const char *Sign =
"";
2182 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1);
2183 int s = int(Seconds);
2184 int m = s / 60 % 60;
2187 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
2193 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
2197 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1;
2203 return int(round(Seconds * FramesPerSecond));
2212 else if (Length > Max) {
2213 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
2216 int r = f->
Read(b, Length);
2235 if (fgets(buf,
sizeof(buf), f))