16 #define __STDC_FORMAT_MACROS // Required for format specifiers
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"
53 #ifdef SUMMARYFALLBACK
54 #define SUMMARYFILESUFFIX "/summary.vdr"
56 #define INFOFILESUFFIX "/info"
57 #define MARKSFILESUFFIX "/marks"
59 #define SORTMODEFILE ".sort"
61 #define MINDISKSPACE 1024 // MB
63 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files
64 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed
65 #define DISKCHECKDELTA 100 // seconds between checks for free disk space
66 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file
67 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks
68 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written
69 #define MAXREMOVETIME 10 // seconds after which to return from removing deleted recordings
71 #define MAX_LINK_LEVEL 6
73 #define LIMIT_SECS_PER_MB_RADIO 5 // radio recordings typically have more than this
93 :
cThread(
"remove deleted recordings", true)
101 if (LockFile.
Lock()) {
102 time_t StartTime = time(NULL);
103 bool deleted =
false;
135 static time_t LastRemoveCheck = 0;
137 if (!RemoveDeletedRecordingsThread.
Active()) {
141 RemoveDeletedRecordingsThread.
Start();
146 LastRemoveCheck = time(NULL);
157 static time_t LastFreeDiskCheck = 0;
158 int Factor = (Priority == -1) ? 10 : 1;
159 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
163 if (!LockFile.
Lock())
166 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
194 isyslog(
"...no deleted recording found, trying to delete an old recording...");
221 isyslog(
"...no old recording found, giving up");
224 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
227 LastFreeDiskCheck = time(NULL);
236 VanishedRecordings.
Clear();
251 esyslog(
"ERROR: can't allocate memory for resume file name");
265 if ((st.st_mode & S_IWUSR) == 0)
271 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
277 else if (errno != ENOENT)
286 while ((s = ReadLine.
Read(f)) != NULL) {
290 case 'I': resume = atoi(t);
297 else if (errno != ENOENT)
308 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
320 fprintf(f,
"I %d\n", Index);
337 else if (errno != ENOENT)
349 event = ownEvent ? ownEvent : Event;
362 for (
int i = 0; i <
MAXAPIDS; i++) {
363 const char *s = Channel->
Alang(i);
368 else if (strlen(s) > strlen(Component->
language))
375 for (
int i = 0; i <
MAXDPIDS; i++) {
376 const char *s = Channel->
Dlang(i);
383 else if (strlen(s) > strlen(Component->
language))
388 for (
int i = 0; i <
MAXSPIDS; i++) {
389 const char *s = Channel->
Slang(i);
394 else if (strlen(s) > strlen(Component->
language))
429 ((
cEvent *)event)->SetShortText(ShortText);
431 ((
cEvent *)event)->SetDescription(Description);
437 aux = Aux ? strdup(Aux) : NULL;
458 while ((s = ReadLine.
Read(f)) != NULL) {
463 char *p = strchr(t,
' ');
474 unsigned int EventID;
477 unsigned int TableID = 0;
478 unsigned int Version = 0xFF;
479 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
480 if (n >= 3 && n <= 5) {
500 esyslog(
"ERROR: EPG data problem in line %d", line);
515 event->Dump(f, Prefix,
true);
517 fprintf(f,
"%sP %d\n", Prefix,
priority);
518 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
520 fprintf(f,
"%s@ %s\n", Prefix,
aux);
536 else if (errno != ENOENT)
560 #define RESUME_NOT_INITIALIZED (-2)
593 case ' ': *p =
'_';
break;
600 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
604 sprintf(buf,
"#%02X", (
unsigned char)*p);
605 memmove(p + 2, p, strlen(p) + 1);
610 esyslog(
"ERROR: out of memory");
617 case '_': *p =
' ';
break;
622 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
624 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
628 memmove(p + 1, p + 3, strlen(p) - 2);
634 case '\x01': *p =
'\'';
break;
635 case '\x02': *p =
'/';
break;
636 case '\x03': *p =
':';
break;
642 for (
struct tCharExchange *ce = CharExchange; ce->
a && ce->b; ce++) {
643 if (*p == (ToFileSystem ? ce->a : ce->b)) {
644 *p = ToFileSystem ? ce->b : ce->a;
666 int Length = strlen(s);
669 bool NameTooLong =
false;
673 for (
char *p = s; *p; p++) {
676 NameTooLong |= NameLength > NameMax;
697 NameTooLong |= NameLength > NameMax;
705 while (i-- > 0 && a[i] >= 0) {
710 if (NameLength > NameMax) {
713 while (i-- > 0 && a[i] >= 0) {
715 if (NameLength - l <= NameMax) {
716 memmove(s + i, s + n, Length - n + 1);
717 memmove(a + i, a + n, Length - n + 1);
730 while (PathLength > PathMax && n > 0) {
735 while (--i > 0 && a[i - 1] >= 0) {
739 if (PathLength - l <= PathMax)
745 memmove(s + b, s + n, Length - n + 1);
771 const char *
Title = Event ? Event->
Title() : NULL;
772 const char *Subtitle = Event ? Event->
ShortText() : NULL;
779 if (macroTITLE || macroEPISODE) {
784 int l = strlen(name);
827 FileName =
fileName = strdup(FileName);
832 const char *p = strrchr(FileName,
'/');
837 time_t now = time(NULL);
839 struct tm t = *localtime_r(&now, &tm_r);
848 strncpy(
name, FileName, p - FileName);
858 FILE *f = fopen(InfoFileName,
"r");
861 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
869 else if (errno == ENOENT)
873 #ifdef SUMMARYFALLBACK
877 FILE *f = fopen(SummaryFileName,
"r");
880 char *data[3] = { NULL };
883 while ((s = ReadLine.
Read(f)) != NULL) {
884 if (*s || line > 1) {
887 len += strlen(data[line]) + 1;
888 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
889 data[line] = NewBuffer;
890 strcat(data[line],
"\n");
891 strcat(data[line], s);
894 esyslog(
"ERROR: out of memory");
897 data[line] = strdup(s);
907 else if (data[1] && data[2]) {
911 int len = strlen(data[1]);
913 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
915 strcat(data[1],
"\n");
916 strcat(data[1], data[2]);
922 esyslog(
"ERROR: out of memory");
926 for (
int i = 0; i < 3; i ++)
929 else if (errno != ENOENT)
948 char *t = s, *s1 = NULL, *s2 = NULL;
969 memmove(s1, s2, t - s2 + 1);
982 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&
start, &tm_r));
990 int l = strxfrm(NULL, s, 0) + 1;
1025 int l = strlen(Path);
1047 struct tm *t = localtime_r(&
start, &tm_r);
1052 if (strcmp(Name,
name) != 0)
1053 dsyslog(
"recording file name '%s' truncated to '%s'",
name, Name);
1068 struct tm *t = localtime_r(&
start, &tm_r);
1102 const char *s =
name;
1135 const char *s =
name;
1147 s = !s ?
name : s + 1;
1166 if (errno != ENOENT) {
1205 dsyslog(
"changing priority/lifetime of '%s' to %d/%d",
Name(), NewPriority, NewLifetime);
1231 if (strcmp(NewName,
Name())) {
1232 dsyslog(
"changing name of '%s' to '%s'",
Name(), NewName);
1238 name = strdup(NewName);
1242 name = strdup(OldName);
1258 char *NewName = strdup(
FileName());
1259 char *ext = strrchr(NewName,
'.');
1260 if (ext && strcmp(ext,
RECEXT) == 0) {
1261 strncpy(ext,
DELEXT, strlen(ext));
1262 if (access(NewName, F_OK) == 0) {
1264 isyslog(
"removing recording '%s'", NewName);
1268 if (access(
FileName(), F_OK) == 0) {
1295 char *NewName = strdup(
FileName());
1296 char *ext = strrchr(NewName,
'.');
1297 if (ext && strcmp(ext,
DELEXT) == 0) {
1298 strncpy(ext,
RECEXT, strlen(ext));
1299 if (access(NewName, F_OK) == 0) {
1301 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1371 :
cThread(
"video directory scanner")
1411 bool DoChangeState =
false;
1415 while ((Foreground ||
Running()) && (e = d.
Next()) != NULL) {
1418 if (lstat(buffer, &st) == 0) {
1420 if (S_ISLNK(st.st_mode)) {
1422 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1426 if (stat(buffer, &st) != 0)
1429 if (S_ISDIR(st.st_mode)) {
1444 DoChangeState =
true;
1452 DoChangeState |=
ScanVideoDir(buffer, Foreground, LinkLevel + Link, DirLevel + 1);
1460 recording =
Next(recording);
1461 if (access(r->
FileName(), F_OK) != 0) {
1464 VanishedRecordings.
Add(r);
1465 DoChangeState =
true;
1470 if (DoChangeState && DirLevel == 0)
1472 return DoChangeState;
1477 int NewState =
state;
1478 bool Result = State != NewState;
1494 if (lastModified > time(NULL))
1515 if (strcmp(recording->FileName(), FileName) == 0)
1541 recording = dummy =
new cRecording(FileName);
1544 Del(recording,
false);
1545 char *ext = strrchr(recording->
fileName,
'.');
1547 strncpy(ext,
DELEXT, strlen(ext));
1548 if (access(recording->
FileName(), F_OK) == 0) {
1549 recording->
deleted = time(NULL);
1572 int FileSizeMB = recording->FileSizeMB();
1573 if (FileSizeMB > 0 && recording->IsOnVideoDirectoryFileSystem())
1585 if (recording->IsOnVideoDirectoryFileSystem()) {
1586 int FileSizeMB = recording->FileSizeMB();
1587 if (FileSizeMB > 0) {
1588 int LengthInSeconds = recording->LengthInSeconds();
1589 if (LengthInSeconds > 0) {
1592 length += LengthInSeconds;
1598 return (size && length) ? double(size) * 60 / length : -1;
1606 if (recording->IsInPath(Path))
1607 Use |= recording->IsInUse();
1617 if (recording->IsInPath(Path))
1625 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1627 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1629 if (recording->IsInPath(OldPath)) {
1630 const char *p = recording->Name() + strlen(OldPath);
1632 if (!recording->ChangeName(NewName))
1645 if (!ResumeFileName || strncmp(ResumeFileName, recording->FileName(), strlen(recording->FileName())) == 0)
1646 recording->ResetResume();
1655 recording->ClearSortName();
1667 virtual void Action(
void);
1669 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1693 dsyslog(
"suspending copy thread");
1699 dsyslog(
"resuming copy thread");
1716 size_t BufferSize = BUFSIZ;
1726 uchar Buffer[BufferSize];
1727 size_t Read =
safe_read(From, Buffer,
sizeof(Buffer));
1729 size_t Written =
safe_write(To, Buffer, Read);
1730 if (Written != Read) {
1731 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1735 else if (Read == 0) {
1737 if (fsync(To) < 0) {
1738 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1741 if (close(From) < 0) {
1742 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1745 if (close(To) < 0) {
1746 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1750 off_t FileSizeSrc =
FileSize(FileNameSrc);
1751 off_t FileSizeDst =
FileSize(FileNameDst);
1752 if (FileSizeSrc != FileSizeDst) {
1753 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1758 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1762 else if ((e = d.
Next()) != NULL) {
1767 if (stat(FileNameSrc, &st) < 0) {
1768 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1771 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1772 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1775 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1776 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1777 if (access(FileNameDst, F_OK) == 0) {
1778 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1781 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1782 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1785 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1786 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1832 int Usage(
const char *FileName = NULL)
const;
1835 bool Active(
bool &Error);
1856 if (FileName && *FileName) {
1867 bool CopierFinishedOk =
false;
1899 if (CopierFinishedOk && (
Usage() &
ruMove) != 0) {
1925 if (FileName && *FileName) {
1927 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
1936 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1939 if (FileNameSrc && *FileNameSrc) {
1940 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
1942 if (Usage ==
ruCut && !FileNameDst)
1944 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
1953 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1956 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1959 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1962 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
1986 return r->Usage(FileName);
1994 if (r->Active(
error))
2040 const char *p = strchr(s,
' ');
2051 return fprintf(f,
"%s", *
ToText()) > 0;
2061 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2077 time_t t = time(NULL);
2081 lastChange = LastModified > 0 ? LastModified : t;
2094 cMutexLock MutexLock(&MutexMarkFramesPerSecond);
2122 if (
int d = m->Position() - p) {
2134 if (m2->Position() < m1->Position()) {
2135 swap(m1->position, m2->position);
2136 swap(m1->comment, m2->comment);
2152 if (mi->Position() == Position)
2161 if (mi->Position() < Position)
2170 if (mi->Position() > Position)
2179 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2180 while (
cMark *NextMark =
Next(BeginMark)) {
2181 if (BeginMark->
Position() == NextMark->Position()) {
2182 if (!(BeginMark =
Next(NextMark)))
2197 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2198 while (
cMark *NextMark =
Next(EndMark)) {
2199 if (EndMark->
Position() == NextMark->Position()) {
2200 if (!(EndMark =
Next(NextMark)))
2213 int NumSequences = 0;
2221 if (NumSequences == 1 && BeginMark->Position() == 0)
2225 return NumSequences;
2240 isyslog(
"executing '%s'", *cmd);
2247 #define IFG_BUFFER_SIZE KILOBYTE(100)
2254 virtual void Action(
void);
2261 :
cThread(
"index file generator")
2262 ,recordingName(RecordingName)
2275 bool IndexFileComplete =
false;
2276 bool IndexFileWritten =
false;
2277 bool Rewind =
false;
2286 off_t FrameOffset = -1;
2287 uint16_t FileNumber = 1;
2288 off_t FileOffset = 0;
2294 Last = IndexFile.
Last();
2295 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2299 isyslog(
"updating index file");
2302 isyslog(
"generating index file");
2305 bool Stuffed =
false;
2309 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2310 FileSize = FileOffset;
2318 if (FrameDetector.
Synced()) {
2321 FrameOffset = FileSize;
2322 int Processed = FrameDetector.
Analyze(Data, Length);
2323 if (Processed > 0) {
2325 if (IndexFileWritten || Last < 0)
2328 IndexFileWritten =
true;
2330 FileSize += Processed;
2331 Buffer.
Del(Processed);
2334 else if (PatPmtParser.
Vpid()) {
2336 int Processed = FrameDetector.
Analyze(Data, Length);
2337 if (Processed > 0) {
2338 if (FrameDetector.
Synced()) {
2342 Buffer.
Del(Processed);
2348 while (Length >= TS_SIZE) {
2352 else if (PatPmtParser.
IsPmtPid(Pid))
2356 if (PatPmtParser.
Vpid()) {
2364 Buffer.
Del(p - Data);
2368 else if (ReplayFile) {
2369 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2371 if (Buffer.
Available() > 0 && !Stuffed) {
2380 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2394 IndexFileComplete =
true;
2398 if (IndexFileComplete) {
2399 if (IndexFileWritten) {
2401 if (RecordingInfo.
Read()) {
2404 RecordingInfo.
Write();
2420 #define INDEXFILESUFFIX "/index"
2423 #define MAXINDEXCATCHUP 8 // number of retries
2424 #define INDEXCATCHUPWAIT 100 // milliseconds
2438 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2447 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
2448 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
2449 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
2452 :resumeFile(FileName, IsPesRecording)
2462 if (!Record && PauseLive) {
2469 if (!Record && access(
fileName, R_OK) != 0) {
2478 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2484 delta = int(buf.st_size %
sizeof(
tIndexTs));
2487 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2489 last = int((buf.st_size + delta) /
sizeof(
tIndexTs) - 1);
2490 if ((!Record || Update) &&
last >= 0) {
2522 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2524 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2551 while (Count-- > 0) {
2552 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2563 while (Count-- > 0) {
2568 memcpy(IndexTs, &IndexPes,
sizeof(*IndexTs));
2580 for (
int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >=
last); i++) {
2582 if (fstat(
f, &buf) == 0) {
2583 int newLast = int(buf.st_size /
sizeof(
tIndexTs) - 1);
2584 if (newLast >
last) {
2586 if (NewSize <= newLast) {
2588 if (NewSize <= newLast)
2589 NewSize = newLast + 1;
2596 if (lseek(
f, offset, SEEK_SET) == offset) {
2598 esyslog(
"ERROR: can't read from index");
2613 esyslog(
"ERROR: can't realloc() index");
2626 return index != NULL;
2632 tIndexTs i(FileOffset, Independent, FileNumber);
2646 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2649 if (Index >= 0 && Index <=
last) {
2658 if (fn == *FileNumber)
2659 *Length = int(fo - *FileOffset);
2675 int d = Forward ? 1 : -1;
2678 if (Index >= 0 && Index <=
last) {
2679 if (
index[Index].independent) {
2692 if (fn == *FileNumber)
2693 *Length = int(fo - *FileOffset);
2714 if (
index[Index].independent)
2720 if (
index[il].independent)
2727 if (
index[ih].independent)
2743 for (i = 0; i <=
last; i++) {
2744 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2773 if (*s && stat(s, &buf) == 0)
2782 if (Recording.
Name()) {
2786 unlink(IndexFileName);
2788 while (IndexFileGenerator->
Active())
2790 if (access(IndexFileName, R_OK) == 0)
2793 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2796 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2799 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2802 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2808 #define MAXFILESPERRECORDINGPES 255
2809 #define RECORDFILESUFFIXPES "/%03d.vdr"
2810 #define MAXFILESPERRECORDINGTS 65535
2811 #define RECORDFILESUFFIXTS "/%05d.ts"
2812 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2850 for (; Number > 0; Number--) {
2854 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2856 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2860 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2862 int Pid =
TsPid(buf);
2864 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2865 else if (PatPmtParser.
IsPmtPid(Pid)) {
2866 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2867 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2878 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2892 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
2906 else if (errno != ENOENT)
2928 if (0 < Number && Number <= MaxFilesPerRecording) {
2936 if (buf.st_size != 0)
2940 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
2947 else if (errno != ENOENT) {
2961 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
2974 const char *Sign =
"";
2980 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
2981 int s = int(Seconds);
2982 int m = s / 60 % 60;
2985 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
2991 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
2995 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3001 return int(round(Seconds * FramesPerSecond));
3010 else if (Length > Max) {
3011 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3014 int r = f->
Read(b, Length);
3034 if (fgets(buf,
sizeof(buf), f))
bool Start(void)
Starts the actual cutting process.
struct dirent * Next(void)
static bool RenameVideoFile(const char *OldName, const char *NewName)
void ClearVanishedRecordings(void)
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.
bool Update(bool Wait=false)
Triggers an update of the list of recordings, which will run as a separate thread if Wait is false...
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists)...
int TotalFileSizeMB(void)
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
int NumFrames(void) const
Returns the number of frames in this recording.
static tChannelID FromString(const char *s)
void Refresh(bool Foreground=false)
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
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)
cMark * GetPrev(int Position)
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
void ResetResume(const char *ResumeFileName=NULL)
void SetTableID(uchar TableID)
void Add(cListObject *Object, cListObject *After=NULL)
bool CatchUp(int Index=-1)
cResumeFile(const char *FileName, bool IsPesRecording)
bool IsEdited(void) const
char * LimitNameLengths(char *s, int PathMax, int NameMax)
bool IsInPath(const char *Path)
Returns true if this recording is stored anywhere under the given Path.
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
double FramesPerSecond(void) const
eRecordingsSortMode RecordingsSortMode
ssize_t Read(void *Data, size_t Size)
char language[MAXLANGCODE2]
cMark * GetNextBegin(cMark *EndMark=NULL)
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark...
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
const char * Title(char Delimiter= ' ', bool NewIndicator=false, int Level=-1) const
#define TIMERMACRO_EPISODE
static cString sprintf(const char *fmt,...) __attribute__((format(printf
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.
bool IsOnVideoDirectoryFileSystem(void) const
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
cUnbufferedFile * NextFile(void)
static cRecordings VanishedRecordings
#define RECORDFILESUFFIXTS
int AlwaysSortFoldersFirst
double MarkFramesPerSecond
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected...
const cComponents * Components(void) const
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
double FramesPerSecond(void) const
#define MAXWAITFORINDEXFILE
void ResetResume(void) const
static bool VideoFileSpaceAvailable(int SizeMB)
time_t StartTime(void) const
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
cRecording(const cRecording &)
const char * Dlang(int i) const
#define INDEXFILETESTINTERVAL
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
void SetAux(const char *Aux)
#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 * Alang(int i) 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)
const cChannel * Channel(void) const
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.
static const char * Name(void)
char * SortName(void) const
#define MAXFILESPERRECORDINGPES
cMark * GetNextEnd(cMark *BeginMark)
Returns the next "end" mark after BeginMark, skipping any marks at the same position as BeginMark...
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
int PathIsInUse(const char *Path)
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
void SetTitle(const char *Title)
tCharExchange CharExchange[]
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...
cRecording * GetByName(const char *FileName)
const char * Name(void) const
#define LIMIT_SECS_PER_MB_RADIO
cMark * GetNext(int Position)
T * Next(const T *object) const
const char * Comment(void) const
void GetRecordingsSortMode(const char *Directory)
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)
bool Write(FILE *f, const char *Prefix="") const
void SetData(const char *Title, const char *ShortText, const char *Description)
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...
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
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...
void UpdateByName(const char *FileName)
int GetNumRecordingsInPath(const char *Path)
Returns the total number of recordings in the given Path, including all sub-folders of Path...
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...
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.
int Usage(const char *FileName=NULL) const
bool NeedsConversion(const char *p)
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
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...
static int Utf8CharLen(const char *s)
tChannelID GetChannelID(void) const
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, if it already points to an I-frame).
static bool HasKeys(void)
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
static char * updateFileName
bool HasRecordingsSortMode(const char *Directory)
bool TimedWait(cMutex &Mutex, int TimeoutMs)
cRecordingsHandler RecordingsHandler
int SystemExec(const char *Command, bool Detached)
int HierarchyLevels(void) const
void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cIndexFileGenerator(const char *RecordingName, bool Update=false)
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 char * FileNameDst(void) const
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
bool Parse(const char *s)
#define MAXFILESPERRECORDINGTS
const char * UpdateFileName(void)
const char * Title(void) const
const char * FileNameSrc(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.
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder). ...
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
cRecordings DeletedRecordings
cIndexFileGenerator * indexFileGenerator
cRecordings(bool Deleted=false)
#define RECORDFILESUFFIXLEN
int GetNumSequences(void)
Returns the actual number of sequences to be cut from the recording.
static bool RemoveVideoFile(const char *FileName)
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
void Del(cListObject *Object, bool DeleteObject=true)
cString ToString(void) const
void DelAll(void)
Deletes/terminates all operations.
static bool MoveVideoFile(const char *FromName, const char *ToName)
cMark * Get(int Position)
bool Active(void)
Checks whether the thread is still alive.
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
cRemoveDeletedRecordingsThread(void)
#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.
const char * File(void) 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...
bool IsSingleEvent(void) const
cRecordings Recordings
Any access to Recordings that loops through the list of recordings needs to hold a thread lock on thi...
uchar * Get(int &Count)
Gets data from the ring buffer.
double MBperMinute(void)
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown...
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)
int NumComponents(void) const
const char * Name(void) const
Returns the full name of the recording (without the video 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...
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)
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
const char * Title(void) const
cList< cRecordingsHandlerEntry > operations
void SetVersion(uchar Version)
void ClearSortNames(void)
int SecondsToFrames(int Seconds, double FramesPerSecond)
bool StateChanged(int &State)
const char * Slang(int i) const
cMutex MutexMarkFramesPerSecond
const cComponents * Components(void) const
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
static const tChannelID InvalidID
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
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 Active(void)
Checks whether there is currently any operation running and starts the next one form the list if the ...
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...
void SetEventID(tEventID EventID)
#define INDEXFILECHECKINTERVAL
char * ExchangeChars(char *s, bool ToFileSystem)
cString recordingFileName
const char * ShortText(void) const
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
bool Error(void)
Returns true if an error occurred while cutting the recording.
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater", and a negative value if it is "smaller".
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)
const char * Aux(void) const
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...
cMark * Prev(const cMark *object) const
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
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)
bool IsPesRecording(void) const
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
~cRecordingsHandlerEntry()
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
#define RUC_DELETERECORDING
#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 ...
static const char * NowReplaying(void)
bool HasMarks(void)
Returns true if this recording has any editing marks.
bool ScanVideoDir(const char *DirName, bool Foreground=false, int LinkLevel=0, int DirLevel=0)
virtual int Available(void)