vdr 2.7.6
cutter.c
Go to the documentation of this file.
1/*
2 * cutter.c: The video cutting facilities
3 *
4 * See the main source file 'vdr.c' for copyright information and
5 * how to reach the author.
6 *
7 * $Id: cutter.c 5.5 2025/03/02 11:03:35 kls Exp $
8 */
9
10#include "cutter.h"
11#include "menu.h"
12#include "remux.h"
13#include "videodir.h"
14
15// --- cPacketBuffer ---------------------------------------------------------
16
18private:
20 int size;
21 int length;
22public:
23 cPacketBuffer(void);
25 void Append(uchar *Data, int Length);
27 void Flush(uchar *Data, int &Length, int MaxLength);
32 };
33
35{
36 data = NULL;
37 size = length = 0;
38}
39
44
45void cPacketBuffer::Append(uchar *Data, int Length)
46{
47 if (length + Length >= size) {
48 int NewSize = (length + Length) * 3 / 2;
49 if (uchar *p = (uchar *)realloc(data, NewSize)) {
50 data = p;
51 size = NewSize;
52 }
53 else
54 return; // out of memory
55 }
56 memcpy(data + length, Data, Length);
57 length += Length;
58}
59
60void cPacketBuffer::Flush(uchar *Data, int &Length, int MaxLength)
61{
62 if (Data && length > 0 && Length + length <= MaxLength) {
63 memcpy(Data + Length, data, length);
64 Length += length;
65 }
66 length = 0;
67}
68
69// --- cPacketStorage --------------------------------------------------------
70
72private:
74public:
75 cPacketStorage(void);
77 void Append(int Pid, uchar *Data, int Length);
78 void Flush(int Pid, uchar *Data, int &Length, int MaxLength);
79 };
80
82{
83 for (int i = 0; i < MAXPID; i++)
84 buffers[i] = NULL;
85}
86
88{
89 for (int i = 0; i < MAXPID; i++)
90 delete buffers[i];
91}
92
93void cPacketStorage::Append(int Pid, uchar *Data, int Length)
94{
95 if (!buffers[Pid])
96 buffers[Pid] = new cPacketBuffer;
97 buffers[Pid]->Append(Data, Length);
98}
99
100void cPacketStorage::Flush(int Pid, uchar *Data, int &Length, int MaxLength)
101{
102 if (buffers[Pid])
103 buffers[Pid]->Flush(Data, Length, MaxLength);
104}
105
106// --- cMpeg2Fixer -----------------------------------------------------------
107
108class cMpeg2Fixer : private cTsPayload {
109private:
110 bool FindHeader(uint32_t Code, const char *Header);
111public:
112 cMpeg2Fixer(uchar *Data, int Length, int Vpid);
113 void SetBrokenLink(void);
114 void SetClosedGop(void);
115 int GetTref(void);
116 void AdjGopTime(int Offset, int FramesPerSecond);
117 void AdjTref(int TrefOffset);
118 };
119
120cMpeg2Fixer::cMpeg2Fixer(uchar *Data, int Length, int Vpid)
121{
122 // Go to first video packet:
123 for (; Length > 0; Data += TS_SIZE, Length -= TS_SIZE) {
124 if (TsPid(Data) == Vpid) {
125 Setup(Data, Length, Vpid);
126 break;
127 }
128 }
129}
130
131bool cMpeg2Fixer::FindHeader(uint32_t Code, const char *Header)
132{
133 Reset();
134 if (Find(Code))
135 return true;
136 esyslog("ERROR: %s header not found!", Header);
137 return false;
138}
139
141{
142 if (!FindHeader(0x000001B8, "GOP"))
143 return;
144 SkipBytes(3);
145 uchar b = GetByte();
146 if (!(b & 0x40)) { // GOP not closed
147 b |= 0x20;
148 SetByte(b, GetLastIndex());
149 }
150}
151
153{
154 if (!FindHeader(0x000001B8, "GOP"))
155 return;
156 SkipBytes(3);
157 uchar b = GetByte();
158 b |= 0x40;
159 SetByte(b, GetLastIndex());
160}
161
163{
164 if (!FindHeader(0x00000100, "picture"))
165 return 0;
166 int Tref = GetByte() << 2;
167 Tref |= GetByte() >> 6;
168 return Tref;
169}
170
171void cMpeg2Fixer::AdjGopTime(int Offset, int FramesPerSecond)
172{
173 if (!FindHeader(0x000001B8, "GOP"))
174 return;
175 uchar Byte1 = GetByte();
176 int Index1 = GetLastIndex();
177 uchar Byte2 = GetByte();
178 int Index2 = GetLastIndex();
179 uchar Byte3 = GetByte();
180 int Index3 = GetLastIndex();
181 uchar Byte4 = GetByte();
182 int Index4 = GetLastIndex();
183 uchar Frame = ((Byte3 & 0x1F) << 1) | (Byte4 >> 7);
184 uchar Sec = ((Byte2 & 0x07) << 3) | (Byte3 >> 5);
185 uchar Min = ((Byte1 & 0x03) << 4) | (Byte2 >> 4);
186 uchar Hour = ((Byte1 & 0x7C) >> 2);
187 int GopTime = ((Hour * 60 + Min) * 60 + Sec) * FramesPerSecond + Frame;
188 if (GopTime) { // do not fix when zero
189 GopTime += Offset;
190 if (GopTime < 0)
191 GopTime += 24 * 60 * 60 * FramesPerSecond;
192 Frame = GopTime % FramesPerSecond;
193 GopTime = GopTime / FramesPerSecond;
194 Sec = GopTime % 60;
195 GopTime = GopTime / 60;
196 Min = GopTime % 60;
197 GopTime = GopTime / 60;
198 Hour = GopTime % 24;
199 SetByte((Byte1 & 0x80) | (Hour << 2) | (Min >> 4), Index1);
200 SetByte(((Min & 0x0F) << 4) | 0x08 | (Sec >> 3), Index2);
201 SetByte(((Sec & 0x07) << 3) | (Frame >> 1), Index3);
202 SetByte((Byte4 & 0x7F) | ((Frame & 0x01) << 7), Index4);
203 }
204}
205
206void cMpeg2Fixer::AdjTref(int TrefOffset)
207{
208 if (!FindHeader(0x00000100, "picture"))
209 return;
210 int Tref = GetByte() << 2;
211 int Index1 = GetLastIndex();
212 uchar Byte2 = GetByte();
213 int Index2 = GetLastIndex();
214 Tref |= Byte2 >> 6;
215 Tref -= TrefOffset;
216 SetByte(Tref >> 2, Index1);
217 SetByte((Tref << 6) | (Byte2 & 0x3F), Index2);
218}
219
220// --- cCuttingThread --------------------------------------------------------
221
222class cCuttingThread : public cThread {
223private:
224 const char *error;
233 off_t fileSize;
239 int sequence; // cutting sequence
240 int delta; // time between two frames (PTS ticks)
241 int64_t lastVidPts; // the video PTS of the last frame (in display order)
242 bool multiFramePkt; // multiple frames within one PES packet
243 int64_t firstPts; // first valid PTS, for dangling packet stripping
244 int64_t offset; // offset to add to all timestamps
245 int tRefOffset; // number of stripped frames in GOP
246 uchar counter[MAXPID]; // the TS continuity counter for each PID
247 bool keepPkt[MAXPID]; // flag for each PID to keep packets, for dangling packet stripping
248 int numIFrames; // number of I-frames without pending packets
250 bool Throttled(void);
251 bool SwitchFile(bool Force = false);
252 bool LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length, bool *Errors = NULL, bool *Missing = NULL);
253 bool FramesAreEqual(int Index1, int Index2);
254 void GetPendingPackets(uchar *Buffer, int &Length, int Index);
255 // Gather all non-video TS packets from Index upward that either belong to
256 // payloads that started before Index, or have a PTS that is before lastVidPts,
257 // and add them to the end of the given Data.
258 bool FixFrame(uchar *Data, int &Length, bool Independent, int Index, bool CutIn, bool CutOut);
259 bool ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex);
260 void HandleErrors(bool Force = false);
261protected:
262 virtual void Action(void) override;
263public:
264 cCuttingThread(const char *FromFileName, const char *ToFileName, cRecordingInfo *RecordingInfo);
265 virtual ~cCuttingThread() override;
266 const char *Error(void) { return error; }
267 };
268
269cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName, cRecordingInfo *RecordingInfo)
270:cThread("video cutting", true)
271{
272 error = NULL;
273 fromFile = toFile = NULL;
274 fromFileName = toFileName = NULL;
275 fromIndex = toIndex = NULL;
276 cRecording Recording(FromFileName);
277 isPesRecording = Recording.IsPesRecording();
278 framesPerSecond = Recording.FramesPerSecond();
279 suspensionLogged = false;
280 fileSize = 0;
281 frameErrors = 0;
283 editedRecordingName = ToFileName;
284 recordingInfo = RecordingInfo;
285 sequence = 0;
286 delta = int(round(PTSTICKS / framesPerSecond));
287 lastVidPts = -1;
288 multiFramePkt = false;
289 firstPts = -1;
290 offset = 0;
291 tRefOffset = 0;
292 memset(counter, 0x00, sizeof(counter));
293 numIFrames = 0;
294 if (fromMarks.Load(FromFileName, framesPerSecond, isPesRecording) && fromMarks.Count()) {
295 numSequences = fromMarks.GetNumSequences();
296 if (numSequences > 0) {
297 fromFileName = new cFileName(FromFileName, false, true, isPesRecording);
298 toFileName = new cFileName(ToFileName, true, true, isPesRecording);
299 fromIndex = new cIndexFile(FromFileName, false, isPesRecording);
300 toIndex = new cIndexFile(ToFileName, true, isPesRecording);
301 toMarks.Load(ToFileName, framesPerSecond, isPesRecording); // doesn't actually load marks, just sets the file name
302 maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize);
305 if (fromIndex->GetErrors()->Size() > 0) {
306 recordingInfo->SetErrors(0); // the fromIndex has error indicators, so we reset the error count
307 recordingInfo->Write();
308 }
309 Start();
310 }
311 else
312 esyslog("no editing sequences found for %s", FromFileName);
313 }
314 else
315 esyslog("no editing marks found for %s", FromFileName);
316}
317
319{
320 Cancel(3);
321 delete fromFileName;
322 delete toFileName;
323 delete fromIndex;
324 delete toIndex;
325}
326
328{
329 if (cIoThrottle::Engaged()) {
330 if (!suspensionLogged) {
331 dsyslog("suspending cutter thread");
332 suspensionLogged = true;
333 }
334 return true;
335 }
336 else if (suspensionLogged) {
337 dsyslog("resuming cutter thread");
338 suspensionLogged = false;
339 }
340 return false;
341}
342
343bool cCuttingThread::LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length, bool *Errors, bool *Missing)
344{
345 uint16_t FileNumber;
346 off_t FileOffset;
347 if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length, Errors, Missing)) {
348 fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
349 if (fromFile) {
350 fromFile->SetReadAhead(MEGABYTE(20));
351 int len = ReadFrame(fromFile, Buffer, Length, MAXFRAMESIZE);
352 if (len < 0)
353 error = "ReadFrame";
354 else if (len != Length)
355 Length = len;
356 return error == NULL;
357 }
358 else
359 error = "fromFile";
360 }
361 return false;
362}
363
365{
366 if (fileSize > maxVideoFileSize || Force) {
367 toFile = toFileName->NextFile();
368 if (!toFile) {
369 error = "toFile";
370 return false;
371 }
372 fileSize = 0;
373 }
374 return true;
375}
376
378private:
380public:
381 cHeapBuffer(int Size) { buffer = MALLOC(uchar, Size); }
382 ~cHeapBuffer() { free(buffer); }
383 operator uchar * () { return buffer; }
384 };
385
386bool cCuttingThread::FramesAreEqual(int Index1, int Index2)
387{
388 cHeapBuffer Buffer1(MAXFRAMESIZE);
389 cHeapBuffer Buffer2(MAXFRAMESIZE);
390 if (!Buffer1 || !Buffer2)
391 return false;
392 bool Independent;
393 int Length1;
394 int Length2;
395 if (LoadFrame(Index1, Buffer1, Independent, Length1) && LoadFrame(Index2, Buffer2, Independent, Length2)) {
396 if (Length1 == Length2) {
397 int Diffs = 0;
398 for (int i = 0; i < Length1; i++) {
399 if (Buffer1[i] != Buffer2[i]) {
400 if (Diffs++ > 10) // the continuity counters of the PAT/PMT packets may differ
401 return false;
402 }
403 }
404 return true;
405 }
406 }
407 return false;
408}
409
410void cCuttingThread::GetPendingPackets(uchar *Data, int &Length, int Index)
411{
413 if (!Buffer)
414 return;
415 bool Processed[MAXPID] = { false };
416 cPacketStorage PacketStorage;
417 int64_t LastPts = lastVidPts + delta;// adding one frame length to fully cover the very last frame
418 Processed[patPmtParser.Vpid()] = true; // we only want non-video packets
419 for (int NumIndependentFrames = 0; NumIndependentFrames < 2; Index++) {
420 bool Independent;
421 int len;
422 if (LoadFrame(Index, Buffer, Independent, len)) {
423 if (Independent)
424 NumIndependentFrames++;
425 for (uchar *p = Buffer; len >= TS_SIZE && *p == TS_SYNC_BYTE; len -= TS_SIZE, p += TS_SIZE) {
426 int Pid = TsPid(p);
427 if (Pid != PATPID && !patPmtParser.IsPmtPid(Pid) && !Processed[Pid]) {
428 int64_t Pts = TsGetPts(p, TS_SIZE);
429 if (Pts >= 0) {
430 int64_t d = PtsDiff(LastPts, Pts);
431 if (d < 0) // Pts is before LastPts
432 PacketStorage.Flush(Pid, Data, Length, MAXFRAMESIZE);
433 else { // Pts is at or after LastPts
434 NumIndependentFrames = 0; // we search until we find two consecutive I-frames without any more pending packets
435 Processed[Pid] = true;
436 }
437 }
438 if (!Processed[Pid])
439 PacketStorage.Append(Pid, p, TS_SIZE);
440 }
441 }
442 }
443 else
444 break;
445 }
446}
447
448bool cCuttingThread::FixFrame(uchar *Data, int &Length, bool Independent, int Index, bool CutIn, bool CutOut)
449{
450 if (!patPmtParser.Vpid()) {
451 if (!patPmtParser.ParsePatPmt(Data, Length))
452 return false;
453 }
454 if (CutIn) {
455 sequence++;
456 memset(keepPkt, false, sizeof(keepPkt));
457 numIFrames = 0;
458 firstPts = TsGetPts(Data, Length);
459 // Determine the PTS offset at the beginning of each sequence (except the first one):
460 if (sequence != 1) {
461 if (firstPts >= 0)
463 }
464 }
465 if (CutOut)
466 GetPendingPackets(Data, Length, Index + 1);
467 if (Independent) {
468 numIFrames++;
469 tRefOffset = 0;
470 }
471 // Fix MPEG-2:
472 if (patPmtParser.Vtype() == 2) {
473 cMpeg2Fixer Mpeg2fixer(Data, Length, patPmtParser.Vpid());
474 if (CutIn) {
475 if (sequence == 1 || multiFramePkt)
476 Mpeg2fixer.SetBrokenLink();
477 else {
478 Mpeg2fixer.SetClosedGop();
479 tRefOffset = Mpeg2fixer.GetTref();
480 }
481 }
482 if (tRefOffset)
483 Mpeg2fixer.AdjTref(tRefOffset);
484 if (sequence > 1 && Independent)
485 Mpeg2fixer.AdjGopTime((offset - MAX33BIT) / delta, round(framesPerSecond));
486 }
487 bool DeletedFrame = false;
488 bool GotVidPts = false;
489 bool StripVideo = sequence > 1 && tRefOffset;
490 uchar *p;
491 int len;
492 for (p = Data, len = Length; len >= TS_SIZE && *p == TS_SYNC_BYTE; p += TS_SIZE, len -= TS_SIZE) {
493 int Pid = TsPid(p);
494 // Strip dangling packets:
495 if (numIFrames < 2 && Pid != PATPID && !patPmtParser.IsPmtPid(Pid)) {
496 if (Pid != patPmtParser.Vpid() || StripVideo) {
497 int64_t Pts = TsGetPts(p, TS_SIZE);
498 if (Pts >= 0)
499 keepPkt[Pid] = PtsDiff(firstPts, Pts) >= 0; // Pts is at or after FirstPts
500 if (!keepPkt[Pid]) {
501 TsHidePayload(p);
502 numIFrames = 0; // we search until we find two consecutive I-frames without any more dangling packets
503 if (Pid == patPmtParser.Vpid())
504 DeletedFrame = true;
505 }
506 }
507 }
508 // Adjust the TS continuity counter:
509 if (sequence > 1) {
510 if (TsHasPayload(p))
511 counter[Pid] = (counter[Pid] + 1) & TS_CONT_CNT_MASK;
513 }
514 else
515 counter[Pid] = TsContinuityCounter(p); // collect initial counters
516 // Adjust PTS:
517 int64_t Pts = TsGetPts(p, TS_SIZE);
518 if (Pts >= 0) {
519 if (sequence > 1) {
520 Pts = PtsAdd(Pts, offset);
521 TsSetPts(p, TS_SIZE, Pts);
522 }
523 // Keep track of the highest video PTS - in case of multiple fields per frame, take the first one
524 if (!GotVidPts && Pid == patPmtParser.Vpid()) {
525 GotVidPts = true;
526 if (lastVidPts < 0 || PtsDiff(lastVidPts, Pts) > 0)
527 lastVidPts = Pts;
528 }
529 }
530 // Adjust DTS:
531 if (sequence > 1) {
532 int64_t Dts = TsGetDts(p, TS_SIZE);
533 if (Dts >= 0) {
534 Dts = PtsAdd(Dts, offset);
535 if (CutIn)
536 Dts = PtsAdd(Dts, tRefOffset * delta);
537 TsSetDts(p, TS_SIZE, Dts);
538 }
539 int64_t Pcr = TsGetPcr(p);
540 if (Pcr >= 0) {
541 Pcr = Pcr + offset * PCRFACTOR;
542 if (Pcr > MAX27MHZ)
543 Pcr -= MAX27MHZ + 1;
544 TsSetPcr(p, Pcr);
545 }
546 }
547 }
548 if (!DeletedFrame && !GotVidPts) {
549 // Adjust PTS for multiple frames within a single PES packet:
551 multiFramePkt = true;
552 }
553 return DeletedFrame;
554}
555
556bool cCuttingThread::ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex)
557{
558 // Check for seamless connections:
559 bool SeamlessBegin = LastEndIndex >= 0 && FramesAreEqual(LastEndIndex, BeginIndex);
560 bool SeamlessEnd = NextBeginIndex >= 0 && FramesAreEqual(EndIndex, NextBeginIndex);
561 // Process all frames from BeginIndex (included) to EndIndex (excluded):
563 if (!Buffer) {
564 error = "malloc";
565 return false;
566 }
567 for (int Index = BeginIndex; Running() && Index < EndIndex; Index++) {
568 bool Independent;
569 int Length;
570 bool Errors;
571 bool Missing;
572 if (LoadFrame(Index, Buffer, Independent, Length, &Errors, &Missing)) {
573 // Make sure there is enough disk space:
575 bool CutIn = !SeamlessBegin && Index == BeginIndex;
576 bool CutOut = !SeamlessEnd && Index == EndIndex - 1;
577 bool DeletedFrame = false;
578 if (!isPesRecording) {
579 DeletedFrame = FixFrame(Buffer, Length, Independent, Index, CutIn, CutOut);
580 }
581 else if (CutIn)
582 cRemux::SetBrokenLink(Buffer, Length);
583 // Every file shall start with an independent frame:
584 if (Independent) {
585 if (!SwitchFile())
586 return false;
587 }
588 // Write index:
589 if (!DeletedFrame && !toIndex->Write(Independent, toFileName->Number(), fileSize, Errors, Missing)) {
590 error = "toIndex";
591 return false;
592 }
593 frameErrors += Errors + Missing;
594 HandleErrors();
595 // Write data:
596 if (toFile->Write(Buffer, Length) < 0) {
597 error = "safe_write";
598 return false;
599 }
600 fileSize += Length;
601 // Generate marks at the editing points in the edited recording:
602 if (numSequences > 1 && Index == BeginIndex) {
603 if (toMarks.Count() > 0)
604 toMarks.Add(toIndex->Last());
605 toMarks.Add(toIndex->Last());
606 toMarks.Save();
607 }
608 }
609 else
610 return false;
611 }
612 return true;
613}
614
615#define ERROR_HANDLING_DELTA 1 // seconds between handling errors
616
618{
619 if (Force || time(NULL) - lastErrorHandling >= ERROR_HANDLING_DELTA) {
620 if (frameErrors > recordingInfo->Errors()) {
621 recordingInfo->SetErrors(frameErrors);
622 recordingInfo->Write();
623 Force = true;
624 }
625 if (Force) {
626 cStateKey StateKey;
627 if (cRecordings *Recordings = cRecordings::GetRecordingsWrite(StateKey, 1)) {
628 Recordings->UpdateByName(editedRecordingName);
629 StateKey.Remove();
630 }
631 }
632 lastErrorHandling = time(NULL);
633 }
634}
635
637{
638 if (cMark *BeginMark = fromMarks.GetNextBegin()) {
639 fromFile = fromFileName->Open();
640 toFile = toFileName->Open();
641 if (!fromFile || !toFile)
642 return;
643 int LastEndIndex = -1;
644 HandleErrors(true); // to make sure an initially reset error count is displayed correctly
645 while (BeginMark && Running()) {
646 // Suspend cutting if we have severe throughput problems:
647 if (Throttled()) {
649 continue;
650 }
651 // Determine the actual begin and end marks, skipping any marks at the same position:
652 cMark *EndMark = fromMarks.GetNextEnd(BeginMark);
653 // Process the current sequence:
654 int EndIndex = EndMark ? EndMark->Position() : fromIndex->Last() + 1;
655 int NextBeginIndex = -1;
656 if (EndMark) {
657 if (cMark *NextBeginMark = fromMarks.GetNextBegin(EndMark))
658 NextBeginIndex = NextBeginMark->Position();
659 }
660 if (!ProcessSequence(LastEndIndex, BeginMark->Position(), EndIndex, NextBeginIndex))
661 break;
662 if (!EndMark)
663 break; // reached EOF
664 LastEndIndex = EndIndex;
665 // Switch to the next sequence:
666 BeginMark = fromMarks.GetNextBegin(EndMark);
667 if (BeginMark) {
668 // Split edited files:
669 if (Setup.SplitEditedFiles) {
670 if (!SwitchFile(true))
671 break;
672 }
673 }
674 }
675 HandleErrors(true);
676 }
677 else
678 esyslog("no editing marks found!");
679}
680
681// --- cCutter ---------------------------------------------------------------
682
683cCutter::cCutter(const char *FileName)
684:recordingInfo(FileName)
685{
686 cuttingThread = NULL;
687 error = false;
688 originalVersionName = FileName;
689}
690
692{
693 Stop();
694}
695
696cString cCutter::EditedFileName(const char *FileName)
697{
698 cRecording Recording(FileName);
699 cMarks Marks;
700 if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording())) {
701 if (cMark *First = Marks.GetNextBegin())
702 Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60);
703 return Recording.PrefixFileName('%');
704 }
705 return NULL;
706}
707
709{
710 if (!cuttingThread) {
711 error = false;
712 if (*originalVersionName) {
715 if (*editedVersionName) {
716 if (strcmp(originalVersionName, editedVersionName) != 0) { // names must be different!
719 recordingInfo.Read();
720 recordingInfo.SetFileName(editedVersionName);
721 recordingInfo.Write();
722 SetRecordingTimerId(editedVersionName, cString::sprintf("%d@%s", 0, Setup.SVDRPHostName));
724 return true;
725 }
726 }
727 }
728 }
729 }
730 return false;
731}
732
734{
735 bool Interrupted = cuttingThread && cuttingThread->Active();
736 const char *Error = cuttingThread ? cuttingThread->Error() : NULL;
737 delete cuttingThread;
738 cuttingThread = NULL;
740 if ((Interrupted || Error) && *editedVersionName) {
741 if (Interrupted)
742 isyslog("editing process has been interrupted");
743 if (Error)
744 esyslog("ERROR: '%s' during editing process", Error);
747 }
748}
749
751{
752 if (cuttingThread) {
753 if (cuttingThread->Active())
754 return true;
755 error = cuttingThread->Error();
756 Stop();
757 if (!error)
759 }
760 return false;
761}
762
764{
765 return error;
766}
767
768#define CUTTINGCHECKINTERVAL 500 // ms between checks for the active cutting process
769
770bool CutRecording(const char *FileName)
771{
772 if (DirectoryOk(FileName)) {
773 cRecording Recording(FileName);
774 if (Recording.Name()) {
775 cMarks Marks;
776 if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()) && Marks.Count()) {
777 if (Marks.GetNumSequences()) {
778 cCutter Cutter(FileName);
779 if (Cutter.Start()) {
780 while (Cutter.Active())
782 if (!Cutter.Error())
783 return true;
784 fprintf(stderr, "error while cutting\n");
785 }
786 else
787 fprintf(stderr, "can't start editing process\n");
788 }
789 else
790 fprintf(stderr, "'%s' has no editing sequences\n", FileName);
791 }
792 else
793 fprintf(stderr, "'%s' has no editing marks\n", FileName);
794 }
795 else
796 fprintf(stderr, "'%s' is not a recording\n", FileName);
797 }
798 else
799 fprintf(stderr, "'%s' is not a directory\n", FileName);
800 return false;
801}
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
Definition thread.c:73
static void Shutdown(void)
Definition player.c:99
cCuttingThread * cuttingThread
Definition cutter.h:24
bool Start(void)
Starts the actual cutting process.
Definition cutter.c:708
cString editedVersionName
Definition cutter.h:22
cCutter(const char *FileName)
Sets up a new cutter for the given FileName, which must be the full path name of an existing recordin...
Definition cutter.c:683
~cCutter()
Definition cutter.c:691
bool error
Definition cutter.h:25
cRecordingInfo recordingInfo
Definition cutter.h:23
void Stop(void)
Stops an ongoing cutting process.
Definition cutter.c:733
bool Error(void)
Returns true if an error occurred while cutting the recording.
Definition cutter.c:763
cString originalVersionName
Definition cutter.h:21
bool Active(void)
Returns true if the cutter is currently active.
Definition cutter.c:750
static cString EditedFileName(const char *FileName)
Returns the full path name of the edited version of the recording with the given FileName.
Definition cutter.c:696
virtual ~cCuttingThread() override
Definition cutter.c:318
bool Throttled(void)
Definition cutter.c:327
void HandleErrors(bool Force=false)
Definition cutter.c:617
cPatPmtParser patPmtParser
Definition cutter.c:249
time_t lastErrorHandling
Definition cutter.c:235
uchar counter[MAXPID]
Definition cutter.c:246
cString editedRecordingName
Definition cutter.c:236
int64_t lastVidPts
Definition cutter.c:241
cMarks toMarks
Definition cutter.c:230
virtual void Action(void) override
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
Definition cutter.c:636
const char * Error(void)
Definition cutter.c:266
bool suspensionLogged
Definition cutter.c:238
cUnbufferedFile * fromFile
Definition cutter.c:227
int64_t offset
Definition cutter.c:244
int frameErrors
Definition cutter.c:234
cIndexFile * toIndex
Definition cutter.c:229
cIndexFile * fromIndex
Definition cutter.c:229
cFileName * fromFileName
Definition cutter.c:228
bool SwitchFile(bool Force=false)
Definition cutter.c:364
cRecordingInfo * recordingInfo
Definition cutter.c:237
int64_t firstPts
Definition cutter.c:243
cMarks fromMarks
Definition cutter.c:230
cCuttingThread(const char *FromFileName, const char *ToFileName, cRecordingInfo *RecordingInfo)
Definition cutter.c:269
bool FixFrame(uchar *Data, int &Length, bool Independent, int Index, bool CutIn, bool CutOut)
Definition cutter.c:448
const char * error
Definition cutter.c:224
bool keepPkt[MAXPID]
Definition cutter.c:247
cFileName * toFileName
Definition cutter.c:228
bool isPesRecording
Definition cutter.c:225
int numSequences
Definition cutter.c:231
cUnbufferedFile * toFile
Definition cutter.c:227
bool FramesAreEqual(int Index1, int Index2)
Definition cutter.c:386
bool ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex)
Definition cutter.c:556
bool LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length, bool *Errors=NULL, bool *Missing=NULL)
Definition cutter.c:343
bool multiFramePkt
Definition cutter.c:242
double framesPerSecond
Definition cutter.c:226
off_t maxVideoFileSize
Definition cutter.c:232
off_t fileSize
Definition cutter.c:233
void GetPendingPackets(uchar *Buffer, int &Length, int Index)
Definition cutter.c:410
uchar * buffer
Definition cutter.c:379
~cHeapBuffer()
Definition cutter.c:382
cHeapBuffer(int Size)
Definition cutter.c:381
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
Definition thread.c:928
int Count(void) const
Definition tools.h:627
int Position(void) const
Definition recording.h:389
int GetNumSequences(void) const
Returns the actual number of sequences to be cut from the recording.
Definition recording.c:2459
const cMark * GetNextBegin(const cMark *EndMark=NULL) const
Returns the next "begin" mark after EndMark, skipping any marks at the same position as EndMark.
Definition recording.c:2425
bool Load(const char *RecordingFileName, double FramesPerSecond=DEFAULTFRAMESPERSECOND, bool IsPesRecording=false)
Definition recording.c:2316
void SetClosedGop(void)
Definition cutter.c:152
int GetTref(void)
Definition cutter.c:162
bool FindHeader(uint32_t Code, const char *Header)
Definition cutter.c:131
void AdjTref(int TrefOffset)
Definition cutter.c:206
void SetBrokenLink(void)
Definition cutter.c:140
void AdjGopTime(int Offset, int FramesPerSecond)
Definition cutter.c:171
cMpeg2Fixer(uchar *Data, int Length, int Vpid)
Definition cutter.c:120
void Append(uchar *Data, int Length)
Appends Length bytes of Data to this packet buffer.
Definition cutter.c:45
void Flush(uchar *Data, int &Length, int MaxLength)
Flushes the content of this packet buffer into the given Data, starting at position Length,...
Definition cutter.c:60
uchar * data
Definition cutter.c:19
~cPacketBuffer()
Definition cutter.c:40
cPacketBuffer(void)
Definition cutter.c:34
~cPacketStorage()
Definition cutter.c:87
cPacketBuffer * buffers[MAXPID]
Definition cutter.c:73
void Append(int Pid, uchar *Data, int Length)
Definition cutter.c:93
cPacketStorage(void)
Definition cutter.c:81
void Flush(int Pid, uchar *Data, int &Length, int MaxLength)
Definition cutter.c:100
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition recording.c:2512
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
Definition recording.c:1310
const char * Name(void) const
Returns the full name of the recording (without the video directory).
Definition recording.h:164
time_t Start(void) const
Definition recording.h:149
const char * PrefixFileName(char Prefix)
Definition recording.c:1239
double FramesPerSecond(void) const
Definition recording.h:175
bool IsPesRecording(void) const
Definition recording.h:196
static cRecordings * GetRecordingsWrite(cStateKey &StateKey, int TimeoutMs=0)
Gets the list of recordings for write access.
Definition recording.h:265
static void SetBrokenLink(uchar *Data, int Length)
Definition remux.c:102
static const char * NowReplaying(void)
Definition menu.c:5902
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
Definition thread.c:869
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition tools.c:1195
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
Definition thread.c:305
bool Running(void)
Returns false if a derived cThread object shall leave its Action() function.
Definition thread.h:101
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
Definition thread.c:239
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
Definition thread.c:355
cTsPayload(void)
Definition remux.c:246
void SetByte(uchar Byte, int Index)
Sets the TS data byte at the given Index to the value Byte.
Definition remux.c:330
uchar GetByte(void)
Gets the next byte of the TS payload, skipping any intermediate TS header data.
Definition remux.c:280
int GetLastIndex(void)
Returns the index into the TS data of the payload byte that has most recently been read.
Definition remux.c:325
bool Find(uint32_t Code)
Searches for the four byte sequence given in Code and returns true if it was found within the payload...
Definition remux.c:336
void Reset(void)
Definition remux.c:265
bool SkipBytes(int Bytes)
Skips the given number of bytes in the payload and returns true if there is still data left to read.
Definition remux.c:313
cUnbufferedFile is used for large files that are mainly written or read in a streaming manner,...
Definition tools.h:494
static bool RemoveVideoFile(const char *FileName)
Definition videodir.c:142
cSetup Setup
Definition config.c:372
#define ERROR_HANDLING_DELTA
Definition cutter.c:615
#define CUTTINGCHECKINTERVAL
Definition cutter.c:768
bool CutRecording(const char *FileName)
Definition cutter.c:770
void AssertFreeDiskSpace(int Priority, bool Force)
The special Priority value -1 means that we shall get rid of any deleted recordings faster than norma...
Definition recording.c:152
int ReadFrame(cUnbufferedFile *f, uchar *b, int Length, int Max)
Definition recording.c:3434
void SetRecordingTimerId(const char *Directory, const char *TimerId)
Definition recording.c:3487
#define MAXVIDEOFILESIZEPES
Definition recording.h:482
#define RUC_EDITINGRECORDING
Definition recording.h:457
#define RUC_EDITEDRECORDING
Definition recording.h:458
#define MAXFRAMESIZE
Definition recording.h:474
void TsSetPcr(uchar *p, int64_t Pcr)
Definition remux.c:131
int64_t PtsDiff(int64_t Pts1, int64_t Pts2)
Returns the difference between two PTS values.
Definition remux.c:234
void TsHidePayload(uchar *p)
Definition remux.c:121
#define MAXPID
Definition remux.c:466
int64_t TsGetDts(const uchar *p, int l)
Definition remux.c:173
void TsSetDts(uchar *p, int l, int64_t Dts)
Definition remux.c:200
void TsSetPts(uchar *p, int l, int64_t Pts)
Definition remux.c:186
int64_t TsGetPts(const uchar *p, int l)
Definition remux.c:160
int TsPid(const uchar *p)
Definition remux.h:82
bool TsHasPayload(const uchar *p)
Definition remux.h:62
#define MAX33BIT
Definition remux.h:59
#define PATPID
Definition remux.h:52
void TsSetContinuityCounter(uchar *p, uchar Counter)
Definition remux.h:103
uchar TsContinuityCounter(const uchar *p)
Definition remux.h:98
#define TS_SIZE
Definition remux.h:34
int64_t TsGetPcr(const uchar *p)
Definition remux.h:124
#define MAX27MHZ
Definition remux.h:60
#define PCRFACTOR
Definition remux.h:58
#define TS_SYNC_BYTE
Definition remux.h:33
#define PTSTICKS
Definition remux.h:57
#define TS_CONT_CNT_MASK
Definition remux.h:42
int64_t PtsAdd(int64_t Pts1, int64_t Pts2)
Adds the given PTS values, taking into account the 33bit wrap around.
Definition remux.h:216
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition tools.c:507
bool DirectoryOk(const char *DirName, bool LogErrors)
Definition tools.c:489
#define MEGABYTE(n)
Definition tools.h:45
unsigned char uchar
Definition tools.h:31
#define dsyslog(a...)
Definition tools.h:37
#define MALLOC(type, size)
Definition tools.h:47
#define esyslog(a...)
Definition tools.h:35
#define isyslog(a...)
Definition tools.h:36