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