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...");
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)
562 case ' ': *p =
'_';
break;
569 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
573 sprintf(buf,
"#%02X", (
unsigned char)*p);
574 memmove(p + 2, p, strlen(p) + 1);
579 esyslog(
"ERROR: out of memory");
586 case '_': *p =
' ';
break;
591 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
593 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
597 memmove(p + 1, p + 3, strlen(p) - 2);
603 case '\x01': *p =
'\'';
break;
604 case '\x02': *p =
'/';
break;
605 case '\x03': *p =
':';
break;
611 for (
struct tCharExchange *ce = CharExchange; ce->
a && ce->b; ce++) {
612 if (*p == (ToFileSystem ? ce->a : ce->b)) {
613 *p = ToFileSystem ? ce->b : ce->a;
635 int Length = strlen(s);
638 bool NameTooLong =
false;
642 for (
char *p = s; *p; p++) {
645 NameTooLong |= NameLength > NameMax;
666 NameTooLong |= NameLength > NameMax;
674 while (i-- > 0 && a[i] >= 0) {
679 if (NameLength > NameMax) {
682 while (i-- > 0 && a[i] >= 0) {
684 if (NameLength - l <= NameMax) {
685 memmove(s + i, s + n, Length - n + 1);
686 memmove(a + i, a + n, Length - n + 1);
699 while (PathLength > PathMax && n > 0) {
704 while (--i > 0 && a[i - 1] >= 0) {
708 if (PathLength - l <= PathMax)
714 memmove(s + b, s + n, Length - n + 1);
740 const char *
Title = Event ? Event->
Title() : NULL;
741 const char *Subtitle = Event ? Event->
ShortText() : NULL;
748 if (macroTITLE || macroEPISODE) {
753 int l = strlen(name);
796 FileName =
fileName = strdup(FileName);
801 const char *p = strrchr(FileName,
'/');
806 time_t now = time(NULL);
808 struct tm t = *localtime_r(&now, &tm_r);
817 strncpy(
name, FileName, p - FileName);
827 FILE *f = fopen(InfoFileName,
"r");
830 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
838 else if (errno == ENOENT)
842 #ifdef SUMMARYFALLBACK
846 FILE *f = fopen(SummaryFileName,
"r");
849 char *data[3] = { NULL };
852 while ((s = ReadLine.
Read(f)) != NULL) {
853 if (*s || line > 1) {
856 len += strlen(data[line]) + 1;
857 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
858 data[line] = NewBuffer;
859 strcat(data[line],
"\n");
860 strcat(data[line], s);
863 esyslog(
"ERROR: out of memory");
866 data[line] = strdup(s);
876 else if (data[1] && data[2]) {
880 int len = strlen(data[1]);
882 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
884 strcat(data[1],
"\n");
885 strcat(data[1], data[2]);
891 esyslog(
"ERROR: out of memory");
895 for (
int i = 0; i < 3; i ++)
898 else if (errno != ENOENT)
917 char *t = s, *s1 = NULL, *s2 = NULL;
938 memmove(s1, s2, t - s2 + 1);
952 int l = strxfrm(NULL, s, 0) + 1;
985 struct tm *t = localtime_r(&
start, &tm_r);
990 if (strcmp(Name,
name) != 0)
991 dsyslog(
"recording file name '%s' truncated to '%s'",
name, Name);
1001 char New = NewIndicator &&
IsNew() ?
'*' :
' ';
1006 struct tm *t = localtime_r(&
start, &tm_r);
1040 const char *s =
name;
1073 if (FileName && *FileName) {
1083 const char *s =
name;
1095 s = !s ?
name : s + 1;
1117 FILE *f = fopen(InfoFileName,
"w");
1137 char *NewName = strdup(
FileName());
1138 char *ext = strrchr(NewName,
'.');
1139 if (ext && strcmp(ext,
RECEXT) == 0) {
1140 strncpy(ext,
DELEXT, strlen(ext));
1141 if (access(NewName, F_OK) == 0) {
1143 isyslog(
"removing recording '%s'", NewName);
1147 if (access(
FileName(), F_OK) == 0) {
1174 char *NewName = strdup(
FileName());
1175 char *ext = strrchr(NewName,
'.');
1176 if (ext && strcmp(ext,
DELEXT) == 0) {
1177 strncpy(ext,
RECEXT, strlen(ext));
1178 if (access(NewName, F_OK) == 0) {
1180 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1239 :
cThread(
"video directory scanner")
1277 while ((Foreground ||
Running()) && (e = d.
Next()) != NULL) {
1280 if (lstat(buffer, &st) == 0) {
1282 if (S_ISLNK(st.st_mode)) {
1284 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1288 if (stat(buffer, &st) != 0)
1291 if (S_ISDIR(st.st_mode)) {
1316 int NewState =
state;
1317 bool Result = State != NewState;
1333 if (lastModified > time(NULL))
1353 if (strcmp(recording->FileName(), FileName) == 0)
1379 Del(recording,
false);
1380 char *ext = strrchr(recording->
fileName,
'.');
1381 if (ext && RemoveRecording) {
1382 strncpy(ext,
DELEXT, strlen(ext));
1383 if (access(recording->
FileName(), F_OK) == 0) {
1384 recording->
deleted = time(NULL);
1408 int FileSizeMB = recording->FileSizeMB();
1409 if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
1421 if (recording->IsOnVideoDirectoryFileSystem()) {
1422 int FileSizeMB = recording->FileSizeMB();
1423 if (FileSizeMB > 0) {
1424 int LengthInSeconds = recording->LengthInSeconds();
1425 if (LengthInSeconds > 0) {
1427 length += LengthInSeconds;
1432 return (size && length) ? double(size) * 60 / length : -1;
1439 if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
1440 recording->ResetResume();
1449 recording->ClearSortName();
1478 const char *p = strchr(s,
' ');
1489 return fprintf(f,
"%s", *
ToText()) > 0;
1494 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
1508 time_t t = time(NULL);
1512 lastChange = LastModified > 0 ? LastModified : t;
1525 cMutexLock MutexLock(&MutexMarkFramesPerSecond);
1551 if (
int d = m->Position() - p) {
1562 if (m2->Position() < m1->Position()) {
1563 swap(m1->position, m2->position);
1564 swap(m1->comment, m2->comment);
1579 if (mi->Position() == Position)
1588 if (mi->Position() < Position)
1597 if (mi->Position() > Position)
1607 while (
cMark *NextMark =
Next(BeginMark)) {
1608 if (BeginMark->
Position() == NextMark->Position()) {
1609 if (!(BeginMark =
Next(NextMark)))
1625 while (
cMark *NextMark =
Next(EndMark)) {
1626 if (EndMark->
Position() == NextMark->Position()) {
1627 if (!(EndMark =
Next(NextMark)))
1639 int NumSequences = 0;
1647 if (NumSequences == 1 && BeginMark->Position() == 0)
1651 return NumSequences;
1666 isyslog(
"executing '%s'", *cmd);
1673 #define IFG_BUFFER_SIZE KILOBYTE(100)
1679 virtual void Action(
void);
1686 :
cThread(
"index file generator")
1687 ,recordingName(RecordingName)
1699 bool IndexFileComplete =
false;
1700 bool IndexFileWritten =
false;
1701 bool Rewind =
false;
1710 off_t FrameOffset = -1;
1723 if (FrameDetector.
Synced()) {
1726 FrameOffset = FileSize;
1727 int Processed = FrameDetector.
Analyze(Data, Length);
1728 if (Processed > 0) {
1732 IndexFileWritten =
true;
1734 FileSize += Processed;
1735 Buffer.
Del(Processed);
1738 else if (PatPmtParser.
Vpid()) {
1740 int Processed = FrameDetector.
Analyze(Data, Length);
1741 if (Processed > 0) {
1742 if (FrameDetector.
Synced()) {
1746 Buffer.
Del(Processed);
1752 while (Length >= TS_SIZE) {
1756 else if (PatPmtParser.
IsPmtPid(Pid))
1760 if (PatPmtParser.
Vpid()) {
1768 Buffer.
Del(p - Data);
1772 else if (ReplayFile) {
1773 int Result = Buffer.
Read(ReplayFile, BufferChunks);
1783 IndexFileComplete =
true;
1787 if (IndexFileComplete) {
1788 if (IndexFileWritten) {
1790 if (RecordingInfo.
Read()) {
1793 RecordingInfo.
Write();
1809 #define INDEXFILESUFFIX "/index"
1812 #define MAXINDEXCATCHUP 8 // number of retries
1813 #define INDEXCATCHUPWAIT 100 // milliseconds
1827 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
1836 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
1837 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
1838 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
1841 :resumeFile(FileName, IsPesRecording)
1851 if (!Record && PauseLive) {
1858 if (!Record && access(
fileName, R_OK) != 0) {
1867 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
1873 delta = int(buf.st_size %
sizeof(
tIndexTs));
1876 esyslog(
"ERROR: invalid file size (%"PRId64
") in '%s'", buf.st_size, *
fileName);
1878 last = int((buf.st_size + delta) /
sizeof(
tIndexTs) - 1);
1879 if (!Record &&
last >= 0) {
1911 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
1913 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
1940 while (Count-- > 0) {
1941 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
1952 while (Count-- > 0) {
1957 memcpy(IndexTs, &IndexPes,
sizeof(*IndexTs));
1969 for (
int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >=
last); i++) {
1971 if (fstat(
f, &buf) == 0) {
1972 int newLast = int(buf.st_size /
sizeof(
tIndexTs) - 1);
1973 if (newLast >
last) {
1975 if (NewSize <= newLast) {
1977 if (NewSize <= newLast)
1978 NewSize = newLast + 1;
1985 if (lseek(
f, offset, SEEK_SET) == offset) {
1987 esyslog(
"ERROR: can't read from index");
2002 esyslog(
"ERROR: can't realloc() index");
2015 return index != NULL;
2021 tIndexTs i(FileOffset, Independent, FileNumber);
2035 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2038 if (Index >= 0 && Index <=
last) {
2047 if (fn == *FileNumber)
2048 *Length = int(fo - *FileOffset);
2064 int d = Forward ? 1 : -1;
2067 if (Index >= 0 && Index <=
last) {
2068 if (
index[Index].independent) {
2081 if (fn == *FileNumber)
2082 *Length = int(fo - *FileOffset);
2103 if (
index[Index].independent)
2109 if (
index[il].independent)
2116 if (
index[ih].independent)
2132 for (i = 0; i <=
last; i++) {
2133 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2162 if (*s && stat(s, &buf) == 0)
2171 if (Recording.
Name()) {
2174 unlink(IndexFileName);
2176 while (IndexFileGenerator->
Active())
2178 if (access(IndexFileName, R_OK) == 0)
2181 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2184 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2187 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2190 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2196 #define MAXFILESPERRECORDINGPES 255
2197 #define RECORDFILESUFFIXPES "/%03d.vdr"
2198 #define MAXFILESPERRECORDINGTS 65535
2199 #define RECORDFILESUFFIXTS "/%05d.ts"
2200 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2238 for (; Number > 0; Number--) {
2242 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2244 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2248 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2250 int Pid =
TsPid(buf);
2252 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2253 else if (PatPmtParser.
IsPmtPid(Pid)) {
2254 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2255 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2266 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2280 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
2294 else if (errno != ENOENT)
2315 if (0 < Number && Number <= MaxFilesPerRecording) {
2323 if (buf.st_size != 0)
2327 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
2334 else if (errno != ENOENT) {
2348 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
2361 const char *Sign =
"";
2367 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond + 1);
2368 int s = int(Seconds);
2369 int m = s / 60 % 60;
2372 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
2378 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
2382 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f - 1;
2388 return int(round(Seconds * FramesPerSecond));
2397 else if (Length > Max) {
2398 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
2401 int r = f->
Read(b, Length);
2420 if (fgets(buf,
sizeof(buf), f))