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 
18 private:
20  int size;
21  int length;
22 public:
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 
41 {
42  free(data);
43 }
44 
45 void 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 
60 void 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 
72 private:
74 public:
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 
93 void 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 
100 void 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 
108 class cMpeg2Fixer : private cTsPayload {
109 private:
110  bool FindHeader(uint32_t Code, const char *Header);
111 public:
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 
120 cMpeg2Fixer::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 
131 bool 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 
171 void 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 
206 void 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 
222 class cCuttingThread : public cThread {
223 private:
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);
261 protected:
262  virtual void Action(void) override;
263 public:
264  cCuttingThread(const char *FromFileName, const char *ToFileName, cRecordingInfo *RecordingInfo);
265  virtual ~cCuttingThread() override;
266  const char *Error(void) { return error; }
267  };
268 
269 cCuttingThread::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;
282  lastErrorHandling = 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()) {
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
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 
343 bool 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) {
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) {
368  if (!toFile) {
369  error = "toFile";
370  return false;
371  }
372  fileSize = 0;
373  }
374  return true;
375 }
376 
377 class cHeapBuffer {
378 private:
380 public:
381  cHeapBuffer(int Size) { buffer = MALLOC(uchar, Size); }
382  ~cHeapBuffer() { free(buffer); }
383  operator uchar * () { return buffer; }
384  };
385 
386 bool 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 
410 void cCuttingThread::GetPendingPackets(uchar *Data, int &Length, int Index)
411 {
412  cHeapBuffer Buffer(MAXFRAMESIZE);
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 
448 bool 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 
556 bool 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):
562  cHeapBuffer Buffer(MAXFRAMESIZE);
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()) {
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()) {
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()) {
648  cCondWait::SleepMs(100);
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 
683 cCutter::cCutter(const char *FileName)
684 :recordingInfo(FileName)
685 {
686  cuttingThread = NULL;
687  error = false;
688  originalVersionName = FileName;
689 }
690 
692 {
693  Stop();
694 }
695 
696 cString 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 
708 bool cCutter::Start(void)
709 {
710  if (!cuttingThread) {
711  error = false;
712  if (*originalVersionName) {
713  cRecording Recording(originalVersionName);
715  if (*editedVersionName) {
716  if (strcmp(originalVersionName, editedVersionName) != 0) { // names must be different!
724  return true;
725  }
726  }
727  }
728  }
729  }
730  return false;
731 }
732 
733 void cCutter::Stop(void)
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 
750 bool cCutter::Active(void)
751 {
752  if (cuttingThread) {
753  if (cuttingThread->Active())
754  return true;
756  Stop();
757  if (!error)
759  }
760  return false;
761 }
762 
763 bool cCutter::Error(void)
764 {
765  return error;
766 }
767 
768 #define CUTTINGCHECKINTERVAL 500 // ms between checks for the active cutting process
769 
770 bool 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
Definition: cutter.h:19
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
int tRefOffset
Definition: cutter.c:245
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
int numIFrames
Definition: cutter.c:248
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
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(void)
Definition: cutter.c:266
const char * error
Definition: cutter.c:224
bool keepPkt[MAXPID]
Definition: cutter.c:247
cFileName * toFileName
Definition: cutter.c:228
int sequence
Definition: cutter.c:239
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
cUnbufferedFile * NextFile(void)
Definition: recording.c:3300
uint16_t Number(void)
Definition: recording.h:543
cUnbufferedFile * Open(void)
Definition: recording.c:3224
cUnbufferedFile * SetOffset(int Number, off_t Offset=0)
Definition: recording.c:3258
uchar * buffer
Definition: cutter.c:379
~cHeapBuffer()
Definition: cutter.c:382
cHeapBuffer(int Size)
Definition: cutter.c:381
bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset, bool Errors=false, bool Missing=false)
Definition: recording.c:2949
const cErrors * GetErrors(void)
Returns the frame indexes of errors in the recording (if any).
Definition: recording.c:2996
bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent=NULL, int *Length=NULL, bool *Errors=NULL, bool *Missing=NULL)
Definition: recording.c:2966
int Last(void)
Returns the index of the last entry in this file, or -1 if the file is empty.
Definition: recording.h:519
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
void Add(int Position)
If this cMarks object is used by multiple threads, the caller must Lock() it before calling Add() and...
Definition: recording.c:2392
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
const cMark * GetNextEnd(const cMark *BeginMark) const
Returns the next "end" mark after BeginMark, skipping any marks at the same position as BeginMark.
Definition: recording.c:2441
bool Save(void)
Definition: recording.c:2359
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
int length
Definition: cutter.c:21
void Append(uchar *Data, int Length)
Appends Length bytes of Data to this packet buffer.
Definition: cutter.c:45
int size
Definition: cutter.c:20
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
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...
Definition: remux.h:409
bool ParsePatPmt(const uchar *Data, int Length)
Parses the given Data (which may consist of several TS packets, typically an entire frame) and extrac...
Definition: remux.c:921
bool IsPmtPid(int Pid) const
Returns true if Pid the one of the PMT pids as defined by the current PAT.
Definition: remux.h:400
int Vpid(void) const
Returns the video pid as defined by the current PMT, or 0 if no video pid has been detected,...
Definition: remux.h:403
int Errors(void) const
Definition: recording.h:110
bool Write(FILE *f, const char *Prefix="") const
Definition: recording.c:600
void SetFileName(const char *FileName)
Definition: recording.c:488
void SetErrors(int Errors)
Definition: recording.c:495
bool Read(FILE *f, bool Force=false)
Definition: recording.c:500
static void InvokeCommand(const char *State, const char *RecordingFileName, const char *SourceFileName=NULL)
Definition: recording.c:2512
const char * Name(void) const
Returns the full name of the recording (without the video directory).
Definition: recording.h:164
void SetStartTime(time_t Start)
Sets the start time of this recording to the given value.
Definition: recording.c:1310
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
int SplitEditedFiles
Definition: config.h:355
int MaxVideoFileSize
Definition: config.h:354
char SVDRPHostName[HOST_NAME_MAX]
Definition: config.h:313
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
Definition: thread.c:869
Definition: tools.h:178
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1195
Definition: thread.h:79
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
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
bool Active(void)
Checks whether the thread is still alive.
Definition: thread.c:330
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
void Setup(uchar *Data, int Length, int Pid=-1)
Sets up this TS payload handler with the given Data, which points to a sequence of Length bytes of co...
Definition: remux.c:272
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
void SetReadAhead(size_t ra)
Definition: tools.c:1859
ssize_t Write(const void *Data, size_t Size)
Definition: tools.c:1936
int Size(void) const
Definition: tools.h:754
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