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