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