Go to the documentation of this file.
15 #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"
60 #define TIMERRECFILE ".timer"
62 #define MINDISKSPACE 1024 // MB
64 #define REMOVECHECKDELTA 60 // seconds between checks for removing deleted files
65 #define DELETEDLIFETIME 300 // seconds after which a deleted recording will be actually removed
66 #define DISKCHECKDELTA 100 // seconds between checks for free disk space
67 #define REMOVELATENCY 10 // seconds to wait until next check after removing a file
68 #define MARKSUPDATEDELTA 10 // seconds between checks for updating editing marks
69 #define MININDEXAGE 3600 // seconds before an index file is considered no longer to be written
70 #define MAXREMOVETIME 10 // seconds after which to return from removing deleted recordings
72 #define MAX_LINK_LEVEL 6
74 #define LIMIT_SECS_PER_MB_RADIO 5 // radio recordings typically have more than this
91 :
cThread(
"remove deleted recordings", true)
99 if (LockFile.
Lock()) {
100 time_t StartTime = time(NULL);
101 bool deleted =
false;
102 bool interrupted =
false;
104 for (
cRecording *r = DeletedRecordings->First(); r; ) {
116 DeletedRecordings->Del(r);
121 r = DeletedRecordings->
Next(r);
139 static time_t LastRemoveCheck = 0;
143 for (
const cRecording *r = DeletedRecordings->First(); r; r = DeletedRecordings->
Next(r)) {
150 LastRemoveCheck = time(NULL);
161 static time_t LastFreeDiskCheck = 0;
162 int Factor = (Priority == -1) ? 10 : 1;
163 if (Force || time(NULL) - LastFreeDiskCheck >
DISKCHECKDELTA / Factor) {
167 if (!LockFile.
Lock())
170 isyslog(
"low disk space while recording, trying to remove a deleted recording...");
171 int NumDeletedRecordings = 0;
174 NumDeletedRecordings = DeletedRecordings->Count();
175 if (NumDeletedRecordings) {
183 r = DeletedRecordings->
Next(r);
188 DeletedRecordings->Del(r0);
193 if (NumDeletedRecordings == 0) {
198 if (DeletedRecordings->Count())
203 isyslog(
"...no deleted recording found, trying to delete an old recording...");
205 Recordings->SetExplicitModify();
206 if (Recordings->Count()) {
223 r = Recordings->
Next(r);
227 Recordings->SetModified();
232 isyslog(
"...no old recording found, giving up");
235 isyslog(
"...no deleted recording found, priority %d too low to trigger deleting an old recording", Priority);
238 LastFreeDiskCheck = time(NULL);
254 esyslog(
"ERROR: can't allocate memory for resume file name");
268 if ((st.st_mode & S_IWUSR) == 0)
274 if (
safe_read(f, &resume,
sizeof(resume)) !=
sizeof(resume)) {
280 else if (errno != ENOENT)
289 while ((s = ReadLine.
Read(f)) != NULL) {
293 case 'I': resume = atoi(t);
300 else if (errno != ENOENT)
311 int f = open(
fileName, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE);
324 fprintf(f,
"I %d\n", Index);
344 else if (errno != ENOENT)
369 for (
int i = 0; i <
MAXAPIDS; i++) {
370 const char *s = Channel->
Alang(i);
375 else if (strlen(s) > strlen(Component->
language))
382 for (
int i = 0; i <
MAXDPIDS; i++) {
383 const char *s = Channel->
Dlang(i);
390 else if (strlen(s) > strlen(Component->
language))
395 for (
int i = 0; i <
MAXSPIDS; i++) {
396 const char *s = Channel->
Slang(i);
401 else if (strlen(s) > strlen(Component->
language))
465 while ((s = ReadLine.
Read(f)) != NULL) {
470 char *p = strchr(t,
' ');
481 unsigned int EventID;
484 unsigned int TableID = 0;
485 unsigned int Version = 0xFF;
486 int n = sscanf(t,
"%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version);
487 if (n >= 3 && n <= 5) {
507 esyslog(
"ERROR: EPG data problem in line %d", line);
522 event->Dump(f, Prefix,
true);
524 fprintf(f,
"%sP %d\n", Prefix,
priority);
525 fprintf(f,
"%sL %d\n", Prefix,
lifetime);
527 fprintf(f,
"%s@ %s\n", Prefix,
aux);
543 else if (errno != ENOENT)
567 #define RESUME_NOT_INITIALIZED (-2)
600 case ' ': *p =
'_';
break;
607 if (
char *NewBuffer = (
char *)realloc(s, strlen(s) + 10)) {
611 sprintf(buf,
"#%02X", (
unsigned char)*p);
612 memmove(p + 2, p, strlen(p) + 1);
617 esyslog(
"ERROR: out of memory");
624 case '_': *p =
' ';
break;
629 if (strlen(p) > 2 && isxdigit(*(p + 1)) && isxdigit(*(p + 2))) {
631 sprintf(buf,
"%c%c", *(p + 1), *(p + 2));
635 memmove(p + 1, p + 3, strlen(p) - 2);
641 case '\x01': *p =
'\'';
break;
642 case '\x02': *p =
'/';
break;
643 case '\x03': *p =
':';
break;
650 if (*p == (ToFileSystem ? ce->a : ce->b)) {
651 *p = ToFileSystem ? ce->b : ce->a;
673 int Length = strlen(s);
676 bool NameTooLong =
false;
680 for (
char *p = s; *p; p++) {
683 NameTooLong |= NameLength > NameMax;
704 NameTooLong |= NameLength > NameMax;
712 while (i-- > 0 && a[i] >= 0) {
717 if (NameLength > NameMax) {
720 while (i-- > 0 && a[i] >= 0) {
722 if (NameLength - l <= NameMax) {
723 memmove(s + i, s + n, Length - n + 1);
724 memmove(a + i, a + n, Length - n + 1);
737 while (PathLength > PathMax && n > 0) {
742 while (--i > 0 && a[i - 1] >= 0) {
746 if (PathLength - l <= PathMax)
752 memmove(s + b, s + n, Length - n + 1);
779 const char *
Title = Event ? Event->
Title() : NULL;
780 const char *Subtitle = Event ? Event->
ShortText() : NULL;
787 if (macroTITLE || macroEPISODE) {
792 int l = strlen(
name);
839 const char *p = strrchr(
FileName,
'/');
844 time_t now = time(NULL);
846 struct tm t = *localtime_r(&now, &tm_r);
865 FILE *f = fopen(InfoFileName,
"r");
868 esyslog(
"ERROR: EPG data problem in file %s", *InfoFileName);
876 else if (errno == ENOENT)
880 #ifdef SUMMARYFALLBACK
884 FILE *f = fopen(SummaryFileName,
"r");
887 char *data[3] = { NULL };
890 while ((s = ReadLine.
Read(f)) != NULL) {
891 if (*s || line > 1) {
894 len += strlen(data[line]) + 1;
895 if (
char *NewBuffer = (
char *)realloc(data[line], len + 1)) {
896 data[line] = NewBuffer;
897 strcat(data[line],
"\n");
898 strcat(data[line], s);
901 esyslog(
"ERROR: out of memory");
904 data[line] = strdup(s);
914 else if (data[1] && data[2]) {
918 int len = strlen(data[1]);
920 if (
char *NewBuffer = (
char *)realloc(data[1], len + 1 + strlen(data[2]) + 1)) {
922 strcat(data[1],
"\n");
923 strcat(data[1], data[2]);
929 esyslog(
"ERROR: out of memory");
933 for (
int i = 0; i < 3; i ++)
936 else if (errno != ENOENT)
955 char *t = s, *s1 = NULL, *s2 = NULL;
976 memmove(s1, s2, t - s2 + 1);
989 strftime(buf,
sizeof(buf),
"%Y%m%d%H%I", localtime_r(&
start, &tm_r));
997 int l = strxfrm(NULL, s, 0) + 1;
1040 int l = strlen(Path);
1062 struct tm *t = localtime_r(&
start, &tm_r);
1067 if (strcmp(Name,
name) != 0)
1068 dsyslog(
"recording file name '%s' truncated to '%s'",
name, Name);
1078 char New = NewIndicator &&
IsNew() ?
'*' :
' ';
1083 struct tm *t = localtime_r(&
start, &tm_r);
1117 const char *s =
name;
1150 const char *s =
name;
1162 s = !s ?
name : s + 1;
1214 dsyslog(
"changing priority/lifetime of '%s' to %d/%d", Name(), NewPriority, NewLifetime);
1238 if (strcmp(NewName, Name())) {
1239 dsyslog(
"changing name of '%s' to '%s'", Name(), NewName);
1245 name = strdup(NewName);
1247 bool Exists = access(NewFileName, F_OK) == 0;
1249 esyslog(
"ERROR: recording '%s' already exists", NewName);
1252 name = strdup(OldName);
1266 char *NewName = strdup(
FileName());
1267 char *ext = strrchr(NewName,
'.');
1268 if (ext && strcmp(ext,
RECEXT) == 0) {
1269 strncpy(ext,
DELEXT, strlen(ext));
1270 if (access(NewName, F_OK) == 0) {
1272 isyslog(
"removing recording '%s'", NewName);
1276 if (access(
FileName(), F_OK) == 0) {
1303 char *NewName = strdup(
FileName());
1304 char *ext = strrchr(NewName,
'.');
1305 if (ext && strcmp(ext,
DELEXT) == 0) {
1306 strncpy(ext,
RECEXT, strlen(ext));
1307 if (access(NewName, F_OK) == 0) {
1309 esyslog(
"ERROR: attempt to undelete '%s', while recording '%s' exists",
FileName(), NewName);
1379 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1381 virtual void Action(
void);
1388 :
cThread(
"video directory scanner", true)
1417 while (Running() && (e = d.
Next()) != NULL) {
1422 if (lstat(buffer, &st) == 0) {
1424 if (S_ISLNK(st.st_mode)) {
1426 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1430 if (stat(buffer, &st) != 0)
1433 if (S_ISDIR(st.st_mode)) {
1441 Recordings->
Lock(StateKey,
true);
1463 if (!
initial && DirLevel == 0) {
1469 if (access(r->
FileName(), F_OK) != 0)
1515 if (lastModified > time(NULL))
1534 for (
const cRecording *Recording = First(); Recording; Recording =
Next(Recording)) {
1535 if (Recording->Id() == Id)
1544 for (
const cRecording *Recording = First(); Recording; Recording =
Next(Recording)) {
1545 if (strcmp(Recording->FileName(), FileName) == 0)
1572 Recording = dummy =
new cRecording(FileName);
1575 Del(Recording,
false);
1576 char *ext = strrchr(Recording->
fileName,
'.');
1578 strncpy(ext,
DELEXT, strlen(ext));
1579 if (access(Recording->
FileName(), F_OK) == 0) {
1581 DeletedRecordings->Add(Recording);
1592 Recording->ReadInfo();
1598 for (
const cRecording *Recording = First(); Recording; Recording =
Next(Recording)) {
1599 int FileSizeMB = Recording->FileSizeMB();
1600 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1610 for (
const cRecording *Recording = First(); Recording; Recording =
Next(Recording)) {
1611 if (Recording->IsOnVideoDirectoryFileSystem()) {
1612 int FileSizeMB = Recording->FileSizeMB();
1613 if (FileSizeMB > 0) {
1614 int LengthInSeconds = Recording->LengthInSeconds();
1615 if (LengthInSeconds > 0) {
1618 length += LengthInSeconds;
1624 return (size && length) ? double(size) * 60 / length : -1;
1630 for (
const cRecording *Recording = First(); Recording; Recording =
Next(Recording)) {
1631 if (Recording->IsInPath(Path))
1632 Use |= Recording->IsInUse();
1640 for (
const cRecording *Recording = First(); Recording; Recording =
Next(Recording)) {
1641 if (Recording->IsInPath(Path))
1649 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1650 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1652 for (
cRecording *Recording = First(); Recording; Recording =
Next(Recording)) {
1653 if (Recording->IsInPath(OldPath)) {
1654 const char *p = Recording->Name() + strlen(OldPath);
1656 if (!Recording->ChangeName(NewName))
1669 for (
cRecording *Recording = First(); Recording; Recording =
Next(Recording)) {
1670 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1671 Recording->ResetResume();
1677 for (
cRecording *Recording = First(); Recording; Recording =
Next(Recording))
1678 Recording->ClearSortName();
1690 virtual void Action(
void);
1692 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1715 dsyslog(
"suspending copy thread");
1721 dsyslog(
"resuming copy thread");
1738 size_t BufferSize = BUFSIZ;
1739 uchar *Buffer = NULL;
1753 size_t Read =
safe_read(From, Buffer, BufferSize);
1755 size_t Written =
safe_write(To, Buffer, Read);
1756 if (Written != Read) {
1757 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1761 else if (Read == 0) {
1763 if (fsync(To) < 0) {
1764 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1767 if (close(From) < 0) {
1768 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1771 if (close(To) < 0) {
1772 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1776 off_t FileSizeSrc =
FileSize(FileNameSrc);
1777 off_t FileSizeDst =
FileSize(FileNameDst);
1778 if (FileSizeSrc != FileSizeDst) {
1779 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1784 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1788 else if ((e = d.
Next()) != NULL) {
1793 if (stat(FileNameSrc, &st) < 0) {
1794 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1797 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1798 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1801 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1803 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1806 esyslog(
"ERROR: out of memory");
1810 if (access(FileNameDst, F_OK) == 0) {
1811 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1814 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1815 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1818 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1819 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1858 int Usage(
const char *FileName = NULL)
const;
1886 if (FileName && *FileName) {
1970 :
cThread(
"recordings handler")
1987 Recordings->SetExplicitModify();
1990 if (!r->Active(Recordings)) {
1991 error |= r->Error();
1992 r->Cleanup(Recordings);
2008 if (FileName && *FileName) {
2012 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2021 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2024 if (FileNameSrc && *FileNameSrc) {
2025 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2027 if (Usage ==
ruCut && !FileNameDst)
2029 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2037 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2040 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2043 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2046 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2068 return r->Usage(FileName);
2110 const char *p = strchr(s,
' ');
2121 return fprintf(f,
"%s\n", *
ToText()) > 0;
2134 if (errno != ENOENT) {
2142 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2156 time_t t = time(NULL);
2160 lastChange = LastModified > 0 ? LastModified : t;
2197 for (
cMark *m = First(); m; m =
Next(m)) {
2199 if (m->Position() - p) {
2208 for (
cMark *m1 = First(); m1; m1 =
Next(m1)) {
2210 if (m2->Position() < m1->Position()) {
2211 swap(m1->position, m2->position);
2212 swap(m1->comment, m2->comment);
2226 for (
const cMark *mi = First(); mi; mi =
Next(mi)) {
2227 if (mi->Position() == Position)
2235 for (
const cMark *mi = Last(); mi; mi =
Prev(mi)) {
2236 if (mi->Position() < Position)
2244 for (
const cMark *mi = First(); mi; mi =
Next(mi)) {
2245 if (mi->Position() > Position)
2253 const cMark *BeginMark = EndMark ?
Next(EndMark) : First();
2254 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2255 while (
const cMark *NextMark =
Next(BeginMark)) {
2256 if (BeginMark->
Position() == NextMark->Position()) {
2257 if (!(BeginMark =
Next(NextMark)))
2272 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2273 while (
const cMark *NextMark =
Next(EndMark)) {
2274 if (EndMark->
Position() == NextMark->Position()) {
2275 if (!(EndMark =
Next(NextMark)))
2287 int NumSequences = 0;
2295 if (NumSequences == 1 && BeginMark->Position() == 0)
2299 return NumSequences;
2314 isyslog(
"executing '%s'", *cmd);
2321 #define IFG_BUFFER_SIZE KILOBYTE(100)
2328 virtual void Action(
void);
2335 :
cThread(
"index file generator")
2336 ,recordingName(RecordingName)
2349 bool IndexFileComplete =
false;
2350 bool IndexFileWritten =
false;
2351 bool Rewind =
false;
2360 off_t FrameOffset = -1;
2361 uint16_t FileNumber = 1;
2362 off_t FileOffset = 0;
2368 Last = IndexFile.Last();
2369 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2373 isyslog(
"updating index file");
2376 isyslog(
"generating index file");
2379 bool Stuffed =
false;
2383 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2392 if (FrameDetector.Synced()) {
2396 int Processed = FrameDetector.
Analyze(Data, Length);
2397 if (Processed > 0) {
2398 if (FrameDetector.NewFrame()) {
2399 if (IndexFileWritten || Last < 0)
2400 IndexFile.
Write(FrameDetector.IndependentFrame(), FileName.
Number(), FrameOffset >= 0 ? FrameOffset :
FileSize);
2402 IndexFileWritten =
true;
2405 Buffer.
Del(Processed);
2408 else if (PatPmtParser.Completed()) {
2410 int Processed = FrameDetector.
Analyze(Data, Length);
2411 if (Processed > 0) {
2412 if (FrameDetector.Synced()) {
2416 Buffer.
Del(Processed);
2426 else if (PatPmtParser.IsPmtPid(Pid))
2430 if (PatPmtParser.Completed()) {
2432 FrameDetector.
SetPid(PatPmtParser.Vpid() ? PatPmtParser.Vpid() : PatPmtParser.
Apid(0), PatPmtParser.Vpid() ? PatPmtParser.Vtype() : PatPmtParser.
Atype(0));
2438 Buffer.
Del(p - Data);
2442 else if (ReplayFile) {
2443 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2445 if (Buffer.
Available() > 0 && !Stuffed) {
2454 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2468 IndexFileComplete =
true;
2472 if (IndexFileComplete) {
2473 if (IndexFileWritten) {
2475 if (RecordingInfo.
Read()) {
2478 RecordingInfo.
Write();
2495 #define INDEXFILESUFFIX "/index"
2498 #define MAXINDEXCATCHUP 8 // number of retries
2499 #define INDEXCATCHUPWAIT 100 // milliseconds
2513 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2517 independent = Independent;
2522 #define MAXWAITFORINDEXFILE 10 // max. time to wait for the regenerated index file (seconds)
2523 #define INDEXFILECHECKINTERVAL 500 // ms between checks for existence of the regenerated index file
2524 #define INDEXFILETESTINTERVAL 10 // ms between tests for the size of the index file in case of pausing live video
2527 :resumeFile(FileName, IsPesRecording)
2537 if (!Record && PauseLive) {
2540 while (time(NULL) < tmax &&
FileSize(
fileName) < off_t(2 *
sizeof(tIndexTs)))
2544 if (!Record && access(
fileName, R_OK) != 0) {
2553 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2559 delta = int(buf.st_size %
sizeof(tIndexTs));
2561 delta =
sizeof(tIndexTs) - delta;
2562 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2564 last = int((buf.st_size + delta) /
sizeof(tIndexTs) - 1);
2565 if ((!Record || Update) &&
last >= 0) {
2588 esyslog(
"ERROR: can't allocate %zd bytes for index '%s'",
size *
sizeof(tIndexTs), *
fileName);
2597 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2599 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2626 while (Count-- > 0) {
2627 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2628 IndexTs->offset = IndexPes.offset;
2629 IndexTs->independent = IndexPes.type == 1;
2630 IndexTs->number = IndexPes.number;
2638 while (Count-- > 0) {
2639 IndexPes.offset = uint32_t(IndexTs->offset);
2640 IndexPes.type =
uchar(IndexTs->independent ? 1 : 2);
2641 IndexPes.number =
uchar(IndexTs->number);
2642 IndexPes.reserved = 0;
2643 memcpy((
void *)IndexTs, &IndexPes,
sizeof(*IndexTs));
2657 if (fstat(
f, &buf) == 0) {
2658 int newLast = int(buf.st_size /
sizeof(tIndexTs) - 1);
2659 if (newLast >
last) {
2661 if (NewSize <= newLast) {
2663 if (NewSize <= newLast)
2664 NewSize = newLast + 1;
2666 if (tIndexTs *NewBuffer = (tIndexTs *)realloc(
index, NewSize *
sizeof(tIndexTs))) {
2669 int offset = (
last + 1) *
sizeof(tIndexTs);
2670 int delta = (newLast -
last) *
sizeof(tIndexTs);
2671 if (lseek(
f, offset, SEEK_SET) == offset) {
2673 esyslog(
"ERROR: can't read from index");
2688 esyslog(
"ERROR: can't realloc() index");
2701 return index != NULL;
2707 tIndexTs i(FileOffset, Independent, FileNumber);
2721 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2724 if (Index >= 0 && Index <=
last) {
2725 *FileNumber =
index[Index].number;
2726 *FileOffset =
index[Index].offset;
2728 *Independent =
index[Index].independent;
2731 uint16_t fn =
index[Index + 1].number;
2732 off_t fo =
index[Index + 1].offset;
2733 if (fn == *FileNumber)
2734 *Length = int(fo - *FileOffset);
2750 int d = Forward ? 1 : -1;
2753 if (Index >= 0 && Index <=
last) {
2754 if (
index[Index].independent) {
2761 *FileNumber =
index[Index].number;
2762 *FileOffset =
index[Index].offset;
2765 uint16_t fn =
index[Index + 1].number;
2766 off_t fo =
index[Index + 1].offset;
2767 if (fn == *FileNumber)
2768 *Length = int(fo - *FileOffset);
2789 if (
index[Index].independent)
2795 if (
index[il].independent)
2802 if (
index[ih].independent)
2818 for (i = 0; i <=
last; i++) {
2819 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2848 if (*s && stat(s, &buf) == 0)
2849 return buf.st_size / (IsPesRecording ?
sizeof(tIndexTs) :
sizeof(tIndexPes));
2857 if (Recording.Name()) {
2861 unlink(IndexFileName);
2863 while (IndexFileGenerator->
Active())
2865 if (access(IndexFileName, R_OK) == 0)
2868 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2871 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2874 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2877 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2883 #define MAXFILESPERRECORDINGPES 255
2884 #define RECORDFILESUFFIXPES "/%03d.vdr"
2885 #define MAXFILESPERRECORDINGTS 65535
2886 #define RECORDFILESUFFIXTS "/%05d.ts"
2887 #define RECORDFILESUFFIXLEN 20 // some additional bytes for safety...
2899 esyslog(
"ERROR: can't copy file name '%s'", FileName);
2929 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2931 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2935 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2937 int Pid =
TsPid(buf);
2939 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2940 else if (PatPmtParser.IsPmtPid(Pid)) {
2941 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2942 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2953 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2967 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
2981 else if (errno != ENOENT)
3011 if (buf.st_size != 0)
3015 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3022 else if (errno != ENOENT) {
3036 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3049 const char *Sign =
"";
3055 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3056 int s = int(Seconds);
3057 int m = s / 60 % 60;
3060 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3066 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3070 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3076 return int(round(Seconds * FramesPerSecond));
3085 else if (Length > Max) {
3086 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3089 int r = f->
Read(b, Length);
3109 if (fgets(buf,
sizeof(buf), f))
3138 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3139 if (FILE *f = fopen(FileName,
"w")) {
3140 fprintf(f,
"%s\n", TimerId);
3147 dsyslog(
"removing %s", *FileName);
3155 const char *Id = NULL;
3156 if (FILE *f = fopen(FileName,
"r")) {
3157 char buf[HOST_NAME_MAX + 10];
3158 if (fgets(buf,
sizeof(buf), f)) {
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder).
cIndexFileGenerator(const char *RecordingName, bool Update=false)
char language[MAXLANGCODE2]
void SetEventID(tEventID EventID)
static cRecordControl * GetRecordControl(const char *FileName)
static cString sprintf(const char *fmt,...) __attribute__((format(printf
void ResetResume(void) const
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
void ResetResume(const char *ResumeFileName=NULL)
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists).
void AssertFreeDiskSpace(int Priority, bool Force)
The special Priority value -1 means that we shall get rid of any deleted recordings faster than norma...
const cChannel * Channel(void) const
cResumeFile(const char *FileName, bool IsPesRecording)
void Add(cRecording *Recording)
void SetFramesPerSecond(double FramesPerSecond)
bool CatchUp(int Index=-1)
bool IsPesRecording(void) const
void DelAll(void)
Deletes/terminates all operations.
void SetFile(const char *File)
const cMark * GetNext(int Position) const
void ConvertToPes(tIndexTs *IndexTs, int Count)
const char * Name(void) const
virtual void Clear(void)
Immediately clears the ring buffer.
void AddByName(const char *FileName, bool TriggerUpdate=true)
static int GetLength(const char *FileName, bool IsPesRecording=false)
Calculates the recording length (number of frames) without actually reading the index file.
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
void ConvertFromPes(tIndexTs *IndexTs, int Count)
static const char * NowReplaying(void)
bool HasRecordingsSortMode(const char *Directory)
#define RECORDFILESUFFIXPES
cRecordingsHandler RecordingsHandler
#define INDEXFILETESTINTERVAL
struct dirent * Next(void)
const char * Title(void) const
#define RESUME_NOT_INITIALIZED
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
virtual int Available(void)
const cComponents * Components(void) const
const char * Alang(int i) const
static bool MoveVideoFile(const char *FromName, const char *ToName)
const cRecording * GetByName(const char *FileName) const
#define MAXFILESPERRECORDINGPES
void SetData(const char *Title, const char *ShortText, const char *Description)
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
int PathIsInUse(const char *Path) const
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
cUnbufferedFile * NextFile(void)
cUnbufferedFile * Open(void)
static const char * Name(void)
void Cleanup(cRecordings *Recordings)
bool IsOnVideoDirectoryFileSystem(void) const
bool Finished(bool &Error)
Returns true if all operations in the list have been finished.
const char * Description(void) const
static bool IsOnVideoDirectoryFileSystem(const char *FileName)
void SetComponent(int Index, const char *s)
#define LOCK_DELETEDRECORDINGS_READ
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
static cString IndexFileName(const char *FileName, bool IsPesRecording)
void Add(cListObject *Object, cListObject *After=NULL)
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
const char * Title(void) const
static bool VideoFileSpaceAvailable(int SizeMB)
double FramesPerSecond(void) const
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
void SetTableID(uchar TableID)
ssize_t Read(void *Data, size_t Size)
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
static bool RenameVideoFile(const char *OldName, const char *NewName)
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
cDirCopier(const char *DirNameSrc, const char *DirNameDst)
static cRecordings recordings
int SecondsToFrames(int Seconds, double FramesPerSecond)
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
int Read(int FileHandle, int Max=0)
Reads at most Max bytes from FileHandle and stores them in the ring buffer.
const char * ShortText(void) const
int NumComponents(void) const
#define MAXWAITFORINDEXFILE
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 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...
const cMark * GetPrev(int Position) const
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
const cComponents * Components(void) const
void ParsePat(const uchar *Data, int Length)
Parses the PAT data from the single TS packet in Data.
static cRecordings deletedRecordings
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
const char * Aux(void) const
virtual int Compare(const cListObject &ListObject) const
static char * StripEpisodeName(char *s, bool Strip)
cRecordings * deletedRecordings
void Del(const char *FileName)
Deletes the given FileName from the list of operations.
bool Active(cRecordings *Recordings)
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
cRecordingsHandlerEntry * Get(const char *FileName)
const cMark * Get(int Position) const
void UpdateByName(const char *FileName)
bool IsSingleEvent(void) const
int TsPid(const uchar *p)
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
void SetVersion(uchar Version)
int isOnVideoDirectoryFileSystem
void Del(cListObject *Object, bool DeleteObject=true)
#define RECORDFILESUFFIXLEN
#define LOCK_DELETEDRECORDINGS_WRITE
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
cString recordingFileName
const char * FileNameSrc(void) const
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
int AlwaysSortFoldersFirst
static bool HasKeys(void)
const cRecording * GetById(int Id) const
const char * Dlang(int i) const
double FramesPerSecond(void) const
const char * PrefixFileName(char Prefix)
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
tCharExchange CharExchange[]
void SetAux(const char *Aux)
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
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 * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark.
int TotalFileSizeMB(void) const
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
time_t StartTime(void) const
bool Active(void)
Returns true if the cutter is currently active.
void RemoveDeletedRecordings(void)
int GetClosestIFrame(int Index)
Returns the index of the I-frame that is closest to the given Index (or Index itself,...
#define SUMMARYFILESUFFIX
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
cString ToString(void) const
static bool RemoveVideoFile(const char *FileName)
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
void SetStartTime(time_t StartTime)
static cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
#define INDEXFILECHECKINTERVAL
static int lastRecordingId
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
#define DEFAULTFRAMESPERSECOND
void SetTitle(const char *Title)
const char * InvalidChars
const char * File(void) const
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
const char * Aux(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.
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false, bool Update=false)
const char * Comment(void) const
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
const char * FileNameDst(void) const
#define RUC_DELETERECORDING
bool IsStillRecording(void)
uchar * Get(int &Count)
Gets data from the ring buffer.
static cVideoDirectoryScannerThread * videoDirectoryScannerThread
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]=NULL)
void SetFileName(const char *FileName)
static const tChannelID InvalidID
void ClearSortNames(void)
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
bool HasMarks(void) const
Returns true if this recording has any editing marks.
char * ExchangeChars(char *s, bool ToFileSystem)
const cMark * Prev(const cMark *Object) const
bool Parse(const char *s)
static const char * UpdateFileName(void)
cIndexFileGenerator * indexFileGenerator
cString GetRecordingTimerId(const char *Directory)
int HierarchyLevels(void) const
int GetNumRecordingsInPath(const char *Path) const
Returns the total number of recordings in the given Path, including all sub-folders of Path.
static bool NeedsUpdate(void)
bool Lock(int WaitSeconds=0)
const char * Slang(int i) const
cRecordings(bool Deleted=false)
void GetRecordingsSortMode(const char *Directory)
static int Utf8CharLen(const char *s)
static bool DeleteMarksFile(const cRecording *Recording)
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
cListObject * Next(void) const
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
static char * updateFileName
void DelByName(const char *FileName)
cList< cRecordingsHandlerEntry > operations
cMutex MutexMarkFramesPerSecond
void ScanVideoDir(const char *DirName, int LinkLevel=0, int DirLevel=0)
bool Undelete(void)
Changes the file name so that it will be visible in the "Recordings" menu again and not processed by ...
static 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.
bool Error(void)
Returns true if an error occurred while cutting the recording.
virtual ~cRecordingsHandler()
bool Write(FILE *f, const char *Prefix="") const
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
bool Active(void)
Checks whether the thread is still alive.
tChannelID GetChannelID(void) const
bool NeedsConversion(const char *p)
void IncRecordingsSortMode(const char *Directory)
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with.
bool TimedWait(cMutex &Mutex, int TimeoutMs)
void SetModified(void)
Unconditionally marks this list as modified.
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...
static cString PrefixVideoFileName(const char *FileName, char Prefix)
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
cRecording(const cRecording &)
#define TIMERMACRO_EPISODE
void SetRecordingTimerId(const char *Directory, const char *TimerId)
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
~cVideoDirectoryScannerThread()
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
int SystemExec(const char *Command, bool Detached)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
off_t Seek(off_t Offset, int Whence)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
#define MAXFILESPERRECORDINGTS
bool IsInPath(const char *Path) const
Returns true if this recording is stored anywhere under the given Path.
cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
~cRecordingsHandlerEntry()
#define LIMIT_SECS_PER_MB_RADIO
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
char * LimitNameLengths(char *s, int PathMax, int NameMax)
double MarkFramesPerSecond
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
int Usage(const char *FileName=NULL) const
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
char * SortName(void) const
static tChannelID FromString(const char *s)
static const char * command
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
bool IsEdited(void) const
void SetDuration(int Duration)
bool Start(void)
Starts the actual cutting process.
cRemoveDeletedRecordingsThread(void)
bool DeleteMarks(void)
Deletes the editing marks from this recording (if any).
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
int NumFrames(void) const
Returns the number of frames in this recording.
double MBperMinute(void) const
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown.
#define RECORDFILESUFFIXTS
const char * ShortText(void) const
#define LOCK_RECORDINGS_WRITE
eRecordingsSortMode RecordingsSortMode
bool WriteInfo(const char *OtherFileName=NULL)
Writes in info file of this recording.
struct __attribute__((packed))
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.