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;
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);
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)
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))
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;
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);
832 const char *p = strrchr(
FileName,
'/');
837 time_t now = time(NULL);
839 struct tm t = *localtime_r(&now, &tm_r);
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);
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) {
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;
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);
2318 if (FrameDetector.
Synced()) {
2322 int Processed = FrameDetector.
Analyze(Data, Length);
2323 if (Processed > 0) {
2325 if (IndexFileWritten || Last < 0)
2328 IndexFileWritten =
true;
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);
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... 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)
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)
const char * Title(char Delimiter=' ', bool NewIndicator=false, int Level=-1) const
static bool RenameVideoFile(const char *OldName, const char *NewName)
void ClearVanishedRecordings(void)
int Usage(const char *FileName=NULL) const
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)...
const char * Aux(void) const
int TotalFileSizeMB(void)
tComponent * GetComponent(int Index, uchar Stream, uchar Type)
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
bool IsOnVideoDirectoryFileSystem(void) const
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)
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)
const char * Description(void) const
int NumComponents(void) const
char * LimitNameLengths(char *s, int PathMax, int NameMax)
cString BaseName(void) const
Returns the base name of this recording (without the video directory and folder). ...
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)
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 * FileName(void) const
Returns the full path name to the recording directory, including the video directory and the actual '...
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
#define TIMERMACRO_EPISODE
static cString sprintf(const char *fmt,...) __attribute__((format(printf
int NumFrames(void) const
Returns the number of frames in this recording.
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.
const char * Name(void) const
Returns the full name of the recording (without the video directory.
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...
const char * Alang(int i) const
bool ChangePriorityLifetime(int NewPriority, int NewLifetime)
Changes the priority and lifetime of this recording to the given values.
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".
cUnbufferedFile * NextFile(void)
static cRecordings VanishedRecordings
#define RECORDFILESUFFIXTS
int AlwaysSortFoldersFirst
double MarkFramesPerSecond
const char * Slang(int i) const
const cComponents * Components(void) const
char * SortName(void) const
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
#define MAXWAITFORINDEXFILE
static bool VideoFileSpaceAvailable(int SizeMB)
cString Folder(void) const
Returns the name of the folder this recording is stored in (without the video directory).
int Put(const uchar *Data, int Count)
Puts at most Count bytes of Data into the ring buffer.
cRecording(const cRecording &)
#define INDEXFILETESTINTERVAL
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
const char * Dlang(int i) const
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 * FileNameSrc(void) 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)
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.
const char * Aux(void) const
static const char * Name(void)
#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[]
cRecording * GetByName(const char *FileName)
#define LIMIT_SECS_PER_MB_RADIO
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...
cMark * GetNext(int Position)
void GetRecordingsSortMode(const char *Directory)
int LengthInSeconds(void) const
Returns the length (in seconds) of this recording, or -1 in case of error.
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)
cString ToString(void) const
void SetData(const char *Title, const char *ShortText, const char *Description)
T * Next(const T *object) const
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...
const char * FileNameDst(void) const
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...
const char * ShortText(void) const
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.
bool NeedsConversion(const char *p)
bool GenerateIndex(const char *FileName, bool Update)
Generates the index of the existing recording with the given FileName.
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)
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)
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 Running(void)
Returns false if a derived cThread object shall leave its Action() function.
static char * updateFileName
bool HasRecordingsSortMode(const char *Directory)
const cChannel * Channel(void) const
bool IsEdited(void) const
bool TimedWait(cMutex &Mutex, int TimeoutMs)
cRecordingsHandler RecordingsHandler
int SystemExec(const char *Command, bool Detached)
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.
void Del(int Count)
Deletes at most Count bytes from the ring buffer.
double FramesPerSecond(void) const
bool Parse(const char *s)
#define MAXFILESPERRECORDINGTS
const char * Comment(void) const
const char * UpdateFileName(void)
bool IsPesRecording(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.
const char * Name(void) const
int GetUsage(const char *FileName)
Returns the usage type for the given FileName.
cIndexFileGenerator * indexFileGenerator
cRecordings(bool Deleted=false)
const cComponents * Components(void) const
#define RECORDFILESUFFIXLEN
int GetNumSequences(void)
Returns the actual number of sequences to be cut from the recording.
const char * Title(void) const
static bool RemoveVideoFile(const char *FileName)
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected...
#define MIN_TS_PACKETS_FOR_FRAME_DETECTOR
void Del(cListObject *Object, bool DeleteObject=true)
void DelAll(void)
Deletes/terminates all operations.
static bool MoveVideoFile(const char *FromName, const char *ToName)
cMark * Get(int Position)
tChannelID GetChannelID(void) const
bool Active(void)
Checks whether the thread is still alive.
cFileName(const char *FileName, bool Record, bool Blocking=false, bool IsPesRecording=false)
cRemoveDeletedRecordingsThread(void)
const char * ShortText(void) const
int HierarchyLevels(void) const
#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.
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)
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 ResetResume(void) const
cMark * Prev(const cMark *object) const
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)
bool IsSingleEvent(void) const
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
cList< cRecordingsHandlerEntry > operations
void SetVersion(uchar Version)
void ClearSortNames(void)
int SecondsToFrames(int Seconds, double FramesPerSecond)
bool StateChanged(int &State)
cMutex MutexMarkFramesPerSecond
double FramesPerSecond(void) const
int IsInUse(void) const
Checks whether this recording is currently in use and therefore shall not be tampered with...
static const tChannelID InvalidID
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
cRecordingInfo(const cChannel *Channel=NULL, const cEvent *Event=NULL)
int FileSizeMB(void) const
Returns the total file size of this recording (in MB), or -1 if the file size is unknown.
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...
bool Write(FILE *f, const char *Prefix="") const
void SetEventID(tEventID EventID)
#define INDEXFILECHECKINTERVAL
char * ExchangeChars(char *s, bool ToFileSystem)
cString recordingFileName
bool Error(void)
Returns true if an error occurred while cutting the recording.
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)
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...
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)
~cRecordingsHandlerEntry()
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset)
#define RUC_DELETERECORDING
cRecordings DeletedRecordings(true)
const char * Title(void) const
#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 ...
time_t StartTime(void) const
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)
const char * File(void) const