15 #define __STDC_FORMAT_MACROS
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
64 #define REMOVECHECKDELTA 60
65 #define DELETEDLIFETIME 300
66 #define DISKCHECKDELTA 100
67 #define REMOVELATENCY 10
68 #define MARKSUPDATEDELTA 10
69 #define MININDEXAGE 3600
70 #define MAXREMOVETIME 10
72 #define MAX_LINK_LEVEL 6
74 #define LIMIT_SECS_PER_MB_RADIO 5
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);
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);
1380 void ScanVideoDir(
const char *DirName,
int LinkLevel = 0,
int DirLevel = 0);
1382 virtual void Action(
void);
1389 :
cThread(
"video directory scanner", true)
1425 if (lstat(buffer, &st) == 0) {
1427 if (S_ISLNK(st.st_mode)) {
1429 isyslog(
"max link level exceeded - not scanning %s", *buffer);
1433 if (stat(buffer, &st) != 0)
1436 if (S_ISDIR(st.st_mode)) {
1444 Recordings->
Lock(StateKey,
true);
1446 dsyslog(
"activated name checking for initial read of video directory");
1471 if (!
initial && DirLevel == 0) {
1477 if (access(r->
FileName(), F_OK) != 0)
1523 if (lastModified > time(NULL))
1543 if (Recording->Id() == Id)
1553 if (strcmp(Recording->FileName(), FileName) == 0)
1580 Recording = dummy =
new cRecording(FileName);
1583 Del(Recording,
false);
1584 char *ext = strrchr(Recording->
fileName,
'.');
1586 strncpy(ext,
DELEXT, strlen(ext));
1587 if (access(Recording->
FileName(), F_OK) == 0) {
1589 DeletedRecordings->Add(Recording);
1600 Recording->ReadInfo();
1607 int FileSizeMB = Recording->FileSizeMB();
1608 if (FileSizeMB > 0 && Recording->IsOnVideoDirectoryFileSystem())
1619 if (Recording->IsOnVideoDirectoryFileSystem()) {
1620 int FileSizeMB = Recording->FileSizeMB();
1621 if (FileSizeMB > 0) {
1622 int LengthInSeconds = Recording->LengthInSeconds();
1623 if (LengthInSeconds > 0) {
1626 length += LengthInSeconds;
1632 return (size && length) ? double(size) * 60 / length : -1;
1639 if (Recording->IsInPath(Path))
1640 Use |= Recording->IsInUse();
1649 if (Recording->IsInPath(Path))
1657 if (OldPath && NewPath && strcmp(OldPath, NewPath)) {
1658 dsyslog(
"moving '%s' to '%s'", OldPath, NewPath);
1661 if (Recording->IsInPath(OldPath)) {
1662 const char *p = Recording->Name() + strlen(OldPath);
1664 if (!Recording->ChangeName(NewName))
1678 if (!ResumeFileName || strncmp(ResumeFileName, Recording->FileName(), strlen(Recording->FileName())) == 0)
1679 Recording->ResetResume();
1686 Recording->ClearSortName();
1698 virtual void Action(
void);
1700 cDirCopier(
const char *DirNameSrc,
const char *DirNameDst);
1723 dsyslog(
"suspending copy thread");
1729 dsyslog(
"resuming copy thread");
1746 size_t BufferSize = BUFSIZ;
1747 uchar *Buffer = NULL;
1761 size_t Read =
safe_read(From, Buffer, BufferSize);
1763 size_t Written =
safe_write(To, Buffer, Read);
1764 if (Written != Read) {
1765 esyslog(
"ERROR: can't write to destination file '%s': %m", *FileNameDst);
1769 else if (Read == 0) {
1771 if (fsync(To) < 0) {
1772 esyslog(
"ERROR: can't sync destination file '%s': %m", *FileNameDst);
1775 if (close(From) < 0) {
1776 esyslog(
"ERROR: can't close source file '%s': %m", *FileNameSrc);
1779 if (close(To) < 0) {
1780 esyslog(
"ERROR: can't close destination file '%s': %m", *FileNameDst);
1784 off_t FileSizeSrc =
FileSize(FileNameSrc);
1785 off_t FileSizeDst =
FileSize(FileNameDst);
1786 if (FileSizeSrc != FileSizeDst) {
1787 esyslog(
"ERROR: file size discrepancy: %" PRId64
" != %" PRId64, FileSizeSrc, FileSizeDst);
1792 esyslog(
"ERROR: can't read from source file '%s': %m", *FileNameSrc);
1796 else if ((e = d.
Next()) != NULL) {
1801 if (stat(FileNameSrc, &st) < 0) {
1802 esyslog(
"ERROR: can't access source file '%s': %m", *FileNameSrc);
1805 if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
1806 esyslog(
"ERROR: source file '%s' is neither a regular file nor a symbolic link", *FileNameSrc);
1809 dsyslog(
"copying file '%s' to '%s'", *FileNameSrc, *FileNameDst);
1811 BufferSize =
max(
size_t(st.st_blksize * 10),
size_t(BUFSIZ));
1814 esyslog(
"ERROR: out of memory");
1818 if (access(FileNameDst, F_OK) == 0) {
1819 esyslog(
"ERROR: destination file '%s' already exists", *FileNameDst);
1822 if ((From = open(FileNameSrc, O_RDONLY)) < 0) {
1823 esyslog(
"ERROR: can't open source file '%s': %m", *FileNameSrc);
1826 if ((To = open(FileNameDst, O_WRONLY | O_CREAT | O_EXCL, DEFFILEMODE)) < 0) {
1827 esyslog(
"ERROR: can't open destination file '%s': %m", *FileNameDst);
1866 int Usage(
const char *FileName = NULL)
const;
1894 if (FileName && *FileName) {
1978 :
cThread(
"recordings handler")
1995 Recordings->SetExplicitModify();
1998 if (!r->Active(Recordings)) {
1999 error |= r->Error();
2000 r->Cleanup(Recordings);
2016 if (FileName && *FileName) {
2020 if (strcmp(FileName, r->FileNameSrc()) == 0 || strcmp(FileName, r->FileNameDst()) == 0)
2029 dsyslog(
"recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2032 if (FileNameSrc && *FileNameSrc) {
2033 if (Usage ==
ruCut || FileNameDst && *FileNameDst) {
2035 if (Usage ==
ruCut && !FileNameDst)
2037 if (!
Get(FileNameSrc) && !
Get(FileNameDst)) {
2045 esyslog(
"ERROR: file name already present in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2048 esyslog(
"ERROR: missing dst file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2051 esyslog(
"ERROR: missing src file name in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2054 esyslog(
"ERROR: invalid usage in recordings handler add %d '%s' '%s'", Usage, FileNameSrc, FileNameDst);
2076 return r->Usage(FileName);
2118 const char *p = strchr(s,
' ');
2129 return fprintf(f,
"%s\n", *
ToText()) > 0;
2142 if (errno != ENOENT) {
2150 bool cMarks::Load(
const char *RecordingFileName,
double FramesPerSecond,
bool IsPesRecording)
2164 time_t t = time(NULL);
2168 lastChange = LastModified > 0 ? LastModified : t;
2207 if (m->Position() - p) {
2218 if (m2->Position() < m1->Position()) {
2219 swap(m1->position, m2->position);
2220 swap(m1->comment, m2->comment);
2235 if (mi->Position() == Position)
2244 if (mi->Position() < Position)
2253 if (mi->Position() > Position)
2262 if (BeginMark && EndMark && BeginMark->
Position() == EndMark->
Position()) {
2263 while (
const cMark *NextMark =
Next(BeginMark)) {
2264 if (BeginMark->
Position() == NextMark->Position()) {
2265 if (!(BeginMark =
Next(NextMark)))
2280 if (EndMark && BeginMark && BeginMark->
Position() == EndMark->
Position()) {
2281 while (
const cMark *NextMark =
Next(EndMark)) {
2282 if (EndMark->
Position() == NextMark->Position()) {
2283 if (!(EndMark =
Next(NextMark)))
2295 int NumSequences = 0;
2303 if (NumSequences == 1 && BeginMark->Position() == 0)
2307 return NumSequences;
2322 isyslog(
"executing '%s'", *cmd);
2329 #define IFG_BUFFER_SIZE KILOBYTE(100)
2336 virtual void Action(
void);
2343 :
cThread(
"index file generator")
2344 ,recordingName(RecordingName)
2357 bool IndexFileComplete =
false;
2358 bool IndexFileWritten =
false;
2359 bool Rewind =
false;
2368 off_t FrameOffset = -1;
2369 uint16_t FileNumber = 1;
2370 off_t FileOffset = 0;
2376 Last = IndexFile.
Last();
2377 if (Last >= 0 && !IndexFile.
Get(Last, &FileNumber, &FileOffset, &Independent, &Length))
2381 isyslog(
"updating index file");
2384 isyslog(
"generating index file");
2387 bool Stuffed =
false;
2391 ReplayFile = FileName.
SetOffset(FileNumber, FileOffset);
2400 if (FrameDetector.
Synced()) {
2404 int Processed = FrameDetector.
Analyze(Data, Length);
2405 if (Processed > 0) {
2407 if (IndexFileWritten || Last < 0)
2410 IndexFileWritten =
true;
2413 Buffer.
Del(Processed);
2418 int Processed = FrameDetector.
Analyze(Data, Length);
2419 if (Processed > 0) {
2420 if (FrameDetector.
Synced()) {
2424 Buffer.
Del(Processed);
2434 else if (PatPmtParser.
IsPmtPid(Pid))
2440 FrameDetector.
SetPid(PatPmtParser.
Vpid() ? PatPmtParser.
Vpid() : PatPmtParser.
Apid(0), PatPmtParser.
Vpid() ? PatPmtParser.
Vtype() : PatPmtParser.
Atype(0));
2446 Buffer.
Del(p - Data);
2450 else if (ReplayFile) {
2451 int Result = Buffer.
Read(ReplayFile, BufferChunks);
2453 if (Buffer.
Available() > 0 && !Stuffed) {
2462 Buffer.
Put(StuffingPacket,
sizeof(StuffingPacket));
2476 IndexFileComplete =
true;
2480 if (IndexFileComplete) {
2481 if (IndexFileWritten) {
2483 if (RecordingInfo.
Read()) {
2486 RecordingInfo.
Write();
2503 #define INDEXFILESUFFIX "/index"
2506 #define MAXINDEXCATCHUP 8
2507 #define INDEXCATCHUPWAIT 100
2521 tIndexTs(off_t Offset,
bool Independent, uint16_t Number)
2525 independent = Independent;
2530 #define MAXWAITFORINDEXFILE 10
2531 #define INDEXFILECHECKINTERVAL 500
2532 #define INDEXFILETESTINTERVAL 10
2535 :resumeFile(FileName, IsPesRecording)
2545 if (!Record && PauseLive) {
2548 while (time(NULL) < tmax &&
FileSize(
fileName) < off_t(2 *
sizeof(tIndexTs)))
2552 if (!Record && access(
fileName, R_OK) != 0) {
2561 }
while (access(
fileName, R_OK) != 0 && time(NULL) < tmax);
2567 delta = int(buf.st_size %
sizeof(tIndexTs));
2569 delta =
sizeof(tIndexTs) - delta;
2570 esyslog(
"ERROR: invalid file size (%" PRId64
") in '%s'", buf.st_size, *
fileName);
2572 last = int((buf.st_size + delta) /
sizeof(tIndexTs) - 1);
2573 if ((!Record || Update) &&
last >= 0) {
2596 esyslog(
"ERROR: can't allocate %zd bytes for index '%s'",
size *
sizeof(tIndexTs), *
fileName);
2605 if ((
f = open(
fileName, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE)) >= 0) {
2607 esyslog(
"ERROR: padding index file with %d '0' bytes", delta);
2634 while (Count-- > 0) {
2635 memcpy(&IndexPes, IndexTs,
sizeof(IndexPes));
2636 IndexTs->offset = IndexPes.offset;
2637 IndexTs->independent = IndexPes.type == 1;
2638 IndexTs->number = IndexPes.number;
2646 while (Count-- > 0) {
2647 IndexPes.offset = uint32_t(IndexTs->offset);
2648 IndexPes.type =
uchar(IndexTs->independent ? 1 : 2);
2649 IndexPes.number =
uchar(IndexTs->number);
2650 IndexPes.reserved = 0;
2651 memcpy((
void *)IndexTs, &IndexPes,
sizeof(*IndexTs));
2665 if (fstat(
f, &buf) == 0) {
2666 int newLast = int(buf.st_size /
sizeof(tIndexTs) - 1);
2667 if (newLast >
last) {
2669 if (NewSize <= newLast) {
2671 if (NewSize <= newLast)
2672 NewSize = newLast + 1;
2674 if (tIndexTs *NewBuffer = (tIndexTs *)realloc(
index, NewSize *
sizeof(tIndexTs))) {
2677 int offset = (
last + 1) *
sizeof(tIndexTs);
2678 int delta = (newLast -
last) *
sizeof(tIndexTs);
2679 if (lseek(
f, offset, SEEK_SET) == offset) {
2681 esyslog(
"ERROR: can't read from index");
2696 esyslog(
"ERROR: can't realloc() index");
2709 return index != NULL;
2715 tIndexTs i(FileOffset, Independent, FileNumber);
2729 bool cIndexFile::Get(
int Index, uint16_t *FileNumber, off_t *FileOffset,
bool *Independent,
int *Length)
2732 if (Index >= 0 && Index <=
last) {
2733 *FileNumber =
index[Index].number;
2734 *FileOffset =
index[Index].offset;
2736 *Independent =
index[Index].independent;
2739 uint16_t fn =
index[Index + 1].number;
2740 off_t fo =
index[Index + 1].offset;
2741 if (fn == *FileNumber)
2742 *Length = int(fo - *FileOffset);
2758 int d = Forward ? 1 : -1;
2761 if (Index >= 0 && Index <=
last) {
2762 if (
index[Index].independent) {
2769 *FileNumber =
index[Index].number;
2770 *FileOffset =
index[Index].offset;
2773 uint16_t fn =
index[Index + 1].number;
2774 off_t fo =
index[Index + 1].offset;
2775 if (fn == *FileNumber)
2776 *Length = int(fo - *FileOffset);
2797 if (
index[Index].independent)
2803 if (
index[il].independent)
2810 if (
index[ih].independent)
2826 for (i = 0; i <=
last; i++) {
2827 if (
index[i].number > FileNumber || (
index[i].number == FileNumber) && off_t(
index[i].offset) >= FileOffset)
2856 if (*s && stat(s, &buf) == 0)
2857 return buf.st_size / (IsPesRecording ?
sizeof(tIndexTs) :
sizeof(tIndexPes));
2865 if (Recording.
Name()) {
2869 unlink(IndexFileName);
2871 while (IndexFileGenerator->
Active())
2873 if (access(IndexFileName, R_OK) == 0)
2876 fprintf(stderr,
"cannot create '%s'\n", *IndexFileName);
2879 fprintf(stderr,
"'%s' is not a TS recording\n", FileName);
2882 fprintf(stderr,
"'%s' is not a recording\n", FileName);
2885 fprintf(stderr,
"'%s' is not a directory\n", FileName);
2891 #define MAXFILESPERRECORDINGPES 255
2892 #define RECORDFILESUFFIXPES "/%03d.vdr"
2893 #define MAXFILESPERRECORDINGTS 65535
2894 #define RECORDFILESUFFIXTS "/%05d.ts"
2895 #define RECORDFILESUFFIXLEN 20
2907 esyslog(
"ERROR: can't copy file name '%s'", FileName);
2937 int fd = open(
fileName, O_RDONLY | O_LARGEFILE, DEFFILEMODE);
2939 off_t pos = lseek(fd, -
TS_SIZE, SEEK_END);
2943 while (read(fd, buf,
sizeof(buf)) ==
sizeof(buf)) {
2945 int Pid =
TsPid(buf);
2947 PatPmtParser.
ParsePat(buf,
sizeof(buf));
2948 else if (PatPmtParser.
IsPmtPid(Pid)) {
2949 PatPmtParser.
ParsePmt(buf,
sizeof(buf));
2950 if (PatPmtParser.
GetVersions(PatVersion, PmtVersion)) {
2961 pos = lseek(fd, pos -
TS_SIZE, SEEK_SET);
2975 int BlockingFlag =
blocking ? 0 : O_NONBLOCK;
2989 else if (errno != ENOENT)
3019 if (buf.st_size != 0)
3023 dsyslog(
"cFileName::SetOffset: removing zero-sized file %s",
fileName);
3030 else if (errno != ENOENT) {
3037 if (!
record && Offset >= 0 &&
file->
Seek(Offset, SEEK_SET) != Offset) {
3044 esyslog(
"ERROR: max number of files (%d) exceeded", MaxFilesPerRecording);
3057 const char *Sign =
"";
3063 int f = int(modf((Index + 0.5) / FramesPerSecond, &Seconds) * FramesPerSecond);
3064 int s = int(Seconds);
3065 int m = s / 60 % 60;
3068 return cString::sprintf(WithFrame ?
"%s%d:%02d:%02d.%02d" :
"%s%d:%02d:%02d", Sign, h, m, s, f);
3074 int n = sscanf(HMSF,
"%d:%d:%d.%d", &h, &m, &s, &f);
3078 return int(round((h * 3600 + m * 60 + s) * FramesPerSecond)) + f;
3084 return int(round(Seconds * FramesPerSecond));
3093 else if (Length > Max) {
3094 esyslog(
"ERROR: frame larger than buffer (%d > %d)", Length, Max);
3097 int r = f->
Read(b, Length);
3117 if (fgets(buf,
sizeof(buf), f))
3146 dsyslog(
"writing timer id '%s' to %s", TimerId, *FileName);
3147 if (FILE *f = fopen(FileName,
"w")) {
3148 fprintf(f,
"%s\n", TimerId);
3155 dsyslog(
"removing %s", *FileName);
3163 const char *Id = NULL;
3164 if (FILE *f = fopen(FileName,
"r")) {
3165 char buf[HOST_NAME_MAX + 10];
3166 if (fgets(buf,
sizeof(buf), f)) {
const char * Alang(int i) const
const char * Name(void) const
tChannelID GetChannelID(void) const
const char * Slang(int i) const
const char * Dlang(int i) const
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
int NumComponents(void) const
void SetComponent(int Index, const char *s)
bool TimedWait(cMutex &Mutex, int TimeoutMs)
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
bool Start(void)
Starts the actual cutting process.
bool Error(void)
Returns true if an error occurred while cutting the recording.
bool Active(void)
Returns true if the cutter is currently active.
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
cDirCopier(const char *DirNameSrc, const char *DirNameDst)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
const cComponents * Components(void) const
void SetStartTime(time_t StartTime)
const char * Title(void) const
void SetEventID(tEventID EventID)
void SetVersion(uchar Version)
void SetDuration(int Duration)
const char * ShortText(void) const
void SetTitle(const char *Title)
void SetTableID(uchar TableID)
cUnbufferedFile * NextFile(void)
cUnbufferedFile * Open(void)
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
bool GetLastPatPmtVersions(int &PatVersion, int &PmtVersion)
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
bool Synced(void)
Returns true if the frame detector has synced on the data stream.
bool IndependentFrame(void)
Returns true if a new frame was detected and this is an independent frame (i.e.
double FramesPerSecond(void)
Returns the number of frames per second, or 0 if this information is not available.
int Analyze(const uchar *Data, int Length)
Analyzes the TS packets pointed to by Data.
void SetPid(int Pid, int Type)
Sets the Pid and stream Type to detect frames for.
bool NewFrame(void)
Returns true if the data given to the last call to Analyze() started a new frame.
cIndexFileGenerator(const char *RecordingName, bool Update=false)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber=NULL, off_t *FileOffset=NULL, int *Length=NULL)
bool IsStillRecording(void)
void ConvertFromPes(tIndexTs *IndexTs, int Count)
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
static int GetLength(const char *FileName, bool IsPesRecording=false)
Calculates the recording length (number of frames) without actually reading the index file.
bool CatchUp(int Index=-1)
void ConvertToPes(tIndexTs *IndexTs, int Count)
cIndexFile(const char *FileName, bool Record, bool IsPesRecording=false, bool PauseLive=false, bool Update=false)
cIndexFileGenerator * indexFileGenerator
static cString IndexFileName(const char *FileName, bool IsPesRecording)
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL)
int GetClosestIFrame(int Index)
Returns the index of the I-frame that is closest to the given Index (or Index itself,...
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
void Del(cListObject *Object, bool DeleteObject=true)
void SetModified(void)
Unconditionally marks this list as modified.
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0) const
Tries to get a lock on this list and returns true if successful.
void Add(cListObject *Object, cListObject *After=NULL)
cListObject * Next(void) const
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
const T * Last(void) const
Returns the last element in this list, or NULL if the list is empty.
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
const T * Prev(const T *Object) const
bool Lock(int WaitSeconds=0)
cMark(int Position=0, const char *Comment=NULL, double FramesPerSecond=DEFAULTFRAMESPERSECOND)
bool Parse(const char *s)
const char * Comment(void) const
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
const cMark * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark.
const cMark * GetNext(int Position) const
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
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 * Get(int Position) const
cString recordingFileName
static bool DeleteMarksFile(const cRecording *Recording)
static cString MarksFileName(const cRecording *Recording)
Returns the marks file name for the given Recording (regardless whether such a file actually exists).
const cMark * GetPrev(int Position) 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...
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...
void ParsePat(const uchar *Data, int Length)
Parses the PAT data from the single TS packet in Data.
void ParsePmt(const uchar *Data, int Length)
Parses the PMT data from the single TS packet in Data.
bool Completed(void)
Returns true if the PMT has been completely parsed.
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected,...
struct dirent * Next(void)
static cRecordControl * GetRecordControl(const char *FileName)
void SetFramesPerSecond(double FramesPerSecond)
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
bool Write(FILE *f, const char *Prefix="") const
const char * Title(void) const
const char * Description(void) const
void SetFileName(const char *FileName)
const char * ShortText(void) const
const char * Aux(void) const
void SetAux(const char *Aux)
void SetData(const char *Title, const char *ShortText, const char *Description)
const cComponents * Components(void) const
double FramesPerSecond(void) const
static const char * command
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
int isOnVideoDirectoryFileSystem
virtual int Compare(const cListObject &ListObject) const
Must return 0 if this object is equal to ListObject, a positive value if it is "greater",...
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
bool HasMarks(void) const
Returns true if this recording has any editing marks.
bool WriteInfo(const char *OtherFileName=NULL)
Writes in info file of this recording.
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with.
bool ChangeName(const char *NewName)
Changes the name of this recording to the given value.
bool Undelete(void)
Changes the file name so that it will be visible in the "Recordings" menu again and not processed by ...
void ResetResume(void) const
bool Delete(void)
Changes the file name so that it will no longer be visible in the "Recordings" menu Returns false in ...
const char * Name(void) const
Returns the full name of the recording (without the video directory).
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
int NumFrames(void) const
Returns the number of frames in this recording.
bool IsEdited(void) const
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...
bool IsInPath(const char *Path) const
Returns true if this recording is stored anywhere under the given Path.
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
char * SortName(void) const
const char * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
const char * PrefixFileName(char Prefix)
bool DeleteMarks(void)
Deletes the editing marks from this recording (if any).
bool IsOnVideoDirectoryFileSystem(void) const
int HierarchyLevels(void) const
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder).
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
bool Remove(void)
Actually removes the file from the disk Returns false in case of error.
cRecording(const cRecording &)
double FramesPerSecond(void) const
bool IsPesRecording(void) const
static char * StripEpisodeName(char *s, bool Strip)
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
void Cleanup(cRecordings *Recordings)
const char * FileNameDst(void) const
~cRecordingsHandlerEntry()
int Usage(const char *FileName=NULL) const
bool Active(cRecordings *Recordings)
const char * FileNameSrc(void) const
cRecordingsHandlerEntry(int Usage, const char *FileNameSrc, const char *FileNameDst)
void DelAll(void)
Deletes/terminates all operations.
cRecordingsHandlerEntry * Get(const char *FileName)
bool Add(int Usage, const char *FileNameSrc, const char *FileNameDst=NULL)
Adds the given FileNameSrc to the recordings handler for (later) processing.
bool Finished(bool &Error)
Returns true if all operations in the list have been finished.
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
cList< cRecordingsHandlerEntry > operations
void Del(const char *FileName)
Deletes the given FileName from the list of operations.
virtual ~cRecordingsHandler()
void ResetResume(const char *ResumeFileName=NULL)
void UpdateByName(const char *FileName)
static const char * UpdateFileName(void)
double MBperMinute(void) const
Returns the average data rate (in MB/min) of all recordings, or -1 if this value is unknown.
cRecordings(bool Deleted=false)
int GetNumRecordingsInPath(const char *Path) const
Returns the total number of recordings in the given Path, including all sub-folders of Path.
const cRecording * GetById(int Id) const
static cRecordings deletedRecordings
void AddByName(const char *FileName, bool TriggerUpdate=true)
static cRecordings recordings
int TotalFileSizeMB(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.
static void TouchUpdate(void)
Touches the '.update' file in the video directory, so that other instances of VDR that access the sam...
void Add(cRecording *Recording)
static cVideoDirectoryScannerThread * videoDirectoryScannerThread
void DelByName(const char *FileName)
bool MoveRecordings(const char *OldPath, const char *NewPath)
Moves all recordings in OldPath to NewPath.
static bool NeedsUpdate(void)
void ClearSortNames(void)
static int lastRecordingId
const cRecording * GetByName(const char *FileName) const
static char * updateFileName
int PathIsInUse(const char *Path) const
Checks whether any recording in the given Path is currently in use and therefore the whole Path shall...
static bool HasKeys(void)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
cRemoveDeletedRecordingsThread(void)
static const char * NowReplaying(void)
cResumeFile(const char *FileName, bool IsPesRecording)
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
virtual int Available(void)
virtual void Clear(void)
Immediately clears the ring buffer.
uchar * Get(int &Count)
Gets data from the ring buffer.
int Read(int FileHandle, int Max=0)
Reads at most Max bytes from FileHandle and stores them in the ring buffer.
int AlwaysSortFoldersFirst
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.
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
static cString sprintf(const char *fmt,...) __attribute__((format(printf
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
bool Active(void)
Checks whether the thread is still alive.
bool IsSingleEvent(void) const
void SetFile(const char *File)
const char * Aux(void) const
const char * File(void) const
time_t StartTime(void) const
const cChannel * Channel(void) const
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
static cUnbufferedFile * Create(const char *FileName, int Flags, mode_t Mode=DEFFILEMODE)
ssize_t Read(void *Data, size_t Size)
off_t Seek(off_t Offset, int Whence)
cRecordings * deletedRecordings
void ScanVideoDir(const char *DirName, int LinkLevel=0, int DirLevel=0)
~cVideoDirectoryScannerThread()
cVideoDirectoryScannerThread(cRecordings *Recordings, cRecordings *DeletedRecordings)
virtual void Action(void)
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
static cString PrefixVideoFileName(const char *FileName, char Prefix)
static void RemoveEmptyVideoDirectories(const char *IgnoreFiles[]=NULL)
static bool IsOnVideoDirectoryFileSystem(const char *FileName)
static const char * Name(void)
static cUnbufferedFile * OpenVideoFile(const char *FileName, int Flags)
static bool VideoFileSpaceAvailable(int SizeMB)
static bool MoveVideoFile(const char *FromName, const char *ToName)
static bool RenameVideoFile(const char *OldName, const char *NewName)
static bool RemoveVideoFile(const char *FileName)
#define TIMERMACRO_EPISODE
static int Utf8CharLen(const char *s)
#define MAXFILESPERRECORDINGTS
tCharExchange CharExchange[]
cString GetRecordingTimerId(const char *Directory)
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
cString IndexToHMSF(int Index, bool WithFrame, double FramesPerSecond)
char * ExchangeChars(char *s, bool ToFileSystem)
void AssertFreeDiskSpace(int Priority, bool Force)
The special Priority value -1 means that we shall get rid of any deleted recordings faster than norma...
void GetRecordingsSortMode(const char *Directory)
bool NeedsConversion(const char *p)
int SecondsToFrames(int Seconds, double FramesPerSecond)
eRecordingsSortMode RecordingsSortMode
bool HasRecordingsSortMode(const char *Directory)
#define MAXFILESPERRECORDINGPES
#define INDEXFILETESTINTERVAL
#define MAXWAITFORINDEXFILE
#define INDEXFILECHECKINTERVAL
void IncRecordingsSortMode(const char *Directory)
int HMSFToIndex(const char *HMSF, double FramesPerSecond)
#define LIMIT_SECS_PER_MB_RADIO
void SetRecordingsSortMode(const char *Directory, eRecordingsSortMode SortMode)
static cRemoveDeletedRecordingsThread RemoveDeletedRecordingsThread
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
cRecordingsHandler RecordingsHandler
cMutex MutexMarkFramesPerSecond
struct __attribute__((packed))
#define RESUME_NOT_INITIALIZED
#define RECORDFILESUFFIXLEN
#define RECORDFILESUFFIXPES
void SetRecordingTimerId(const char *Directory, const char *TimerId)
#define RECORDFILESUFFIXTS
char * LimitNameLengths(char *s, int PathMax, int NameMax)
double MarkFramesPerSecond
const char * InvalidChars
void RemoveDeletedRecordings(void)
#define SUMMARYFILESUFFIX
#define DEFAULTFRAMESPERSECOND
#define LOCK_DELETEDRECORDINGS_WRITE
#define RUC_DELETERECORDING
#define LOCK_DELETEDRECORDINGS_READ
#define LOCK_RECORDINGS_WRITE
int TsPid(const uchar *p)
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
static const tChannelID InvalidID
static tChannelID FromString(const char *s)
cString ToString(void) const
char language[MAXLANGCODE2]
int SystemExec(const char *Command, bool Detached)