From cca2cd35ad7ef20ae7d124e06d05e896c4d8f9b6 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 18 Nov 2012 12:19:51 +0100 Subject: [PATCH] Improved editing TS recordings --- HISTORY | 17 +- MANUAL | 20 +- cutter.c | 654 +++++++++++++++++++++++++++++++++++++++------------- menu.c | 4 +- po/ar.po | 5 +- po/ca_ES.po | 5 +- po/cs_CZ.po | 5 +- po/da_DK.po | 5 +- po/de_DE.po | 5 +- po/el_GR.po | 5 +- po/es_ES.po | 5 +- po/et_EE.po | 5 +- po/fi_FI.po | 5 +- po/fr_FR.po | 5 +- po/hr_HR.po | 5 +- po/hu_HU.po | 5 +- po/it_IT.po | 5 +- po/lt_LT.po | 5 +- po/mk_MK.po | 5 +- po/nl_NL.po | 5 +- po/nn_NO.po | 5 +- po/pl_PL.po | 5 +- po/pt_PT.po | 5 +- po/ro_RO.po | 5 +- po/ru_RU.po | 5 +- po/sk_SK.po | 5 +- po/sl_SI.po | 5 +- po/sr_SR.po | 5 +- po/sv_SE.po | 5 +- po/tr_TR.po | 5 +- po/uk_UA.po | 5 +- po/zh_CN.po | 5 +- recording.c | 50 +++- recording.h | 15 +- remux.c | 112 +++++++-- remux.h | 66 +++++- 36 files changed, 850 insertions(+), 228 deletions(-) diff --git a/HISTORY b/HISTORY index 50470824..93e5fd09 100644 --- a/HISTORY +++ b/HISTORY @@ -7272,7 +7272,7 @@ Video Disk Recorder Revision History ".keep" to prevent a directory from being deleted when it is empty. Currently the only file name that is ignored is ".sort". -2012-11-12: Version 1.7.32 +2012-11-18: Version 1.7.32 - Pressing the Play key during normal live viewing mode now opens the Recordings menu if there is no "last viewed" recording (thanks to Alexander Wenzel). @@ -7315,3 +7315,18 @@ Video Disk Recorder Revision History - The return type of cMarks::Add() has been changed to void, since due to the sorting of the list of marks the returned pointer might have pointed to a totally different mark. Besides, the return value was never actually used. +- Improved editing TS recordings by + + stripping dangling TS packets from the beginning of a sequence + + including pending TS packets at the end of a sequence + + fixing all timestamps and continuity counters + + generating editing marks for the edited version in such a way that each cutting + point is marked by an "end" and "begin" mark with the same offset + + no longer generating an editing mark at the "end" of the edited recording (this + was actually generated at the beginning of the last GOP, so that a subsequent + edit would have cut off the last GOP) + + no longer generating any editing marks if the edited recording results on just + one single sequence + + ignoring pairs of editing marks that are placed at exactly the same position of + a recording when actually cutting the recording + + not doing anything if the editing marks in place would result in the edited + version being the same as the original recording diff --git a/MANUAL b/MANUAL index fb28462b..272659cc 100644 --- a/MANUAL +++ b/MANUAL @@ -367,13 +367,13 @@ Version 1.6 - 7, 9 Jump back and forward between editing marks. Replay goes into still mode after jumping to a mark. - 8 Positions replay at a point 3 seconds before the current or next - "start" mark and starts replay. + "begin" mark and starts replay. - 2 Start the actual cutting process. Editing marks are represented by black, vertical lines in the progress display. - A small black triangle at the top of the mark means that this is a "start" + A small black triangle at the top of the mark means that this is a "begin" mark, and a triangle at the bottom means that this is an "end" mark. - The cutting process will save all video data between "start" and "end" marks + The cutting process will save all video data between "begin" and "end" marks into a new file (the original recording remains untouched). The new file will have the same name as the original recording, preceded with a '%' character (imagine the '%' somehow looking like a pair of scissors ;-). Red bars in the @@ -382,7 +382,7 @@ Version 1.6 The video sequences to be saved by the cutting process are determined by an "even/odd" algorithm. This means that every odd numbered editing mark (i.e. - 1, 3, 5,...) represents a "start" mark, while every even numbered mark (2, 4, + 1, 3, 5,...) represents a "begin" mark, while every even numbered mark (2, 4, 6,...) is an "end" mark. Inserting or toggling a mark on or off automatically adjusts the sequence to the right side of that mark. @@ -395,11 +395,13 @@ Version 1.6 version of the recording you can use the '8' key to jump to a point just before the next cut and have a look at the resulting sequence. - Currently editing marks can only be set at I-frames, which typically is - every 12th frame. So editing can be done with a resolution of roughly half - a second. A "start" mark marks the first frame of a resulting video - sequence, and an "end" mark marks the last frame of that sequence. - + Currently editing marks can only be set at I-frames, which typically appear + every half of a second to a second. A "begin" mark marks the first frame of + a resulting video sequence, and an "end" mark marks the last frame of that + sequence. Note that the actual frame indicated by the an "end" mark will + not be included in the edited version of the recording. That's because every + recording (and every sequence of an edited recording) begins with an I-frame + and ends right before the next I-frame. An edited recording (indicated by the '%' character) will never be deleted automatically in case the disk runs full (no matter what "lifetime" it has). diff --git a/cutter.c b/cutter.c index a0e2b477..ace1893b 100644 --- a/cutter.c +++ b/cutter.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: cutter.c 2.15 2012/10/04 12:19:37 kls Exp $ + * $Id: cutter.c 2.16 2012/11/18 12:09:00 kls Exp $ */ #include "cutter.h" @@ -13,17 +13,261 @@ #include "remux.h" #include "videodir.h" +// --- cPacketBuffer --------------------------------------------------------- + +class cPacketBuffer { +private: + uchar *data; + int size; + int length; +public: + cPacketBuffer(void); + ~cPacketBuffer(); + void Append(uchar *Data, int Length); + ///< Appends Length bytes of Data to this packet buffer. + void Flush(uchar *Data, int &Length, int MaxLength); + ///< Flushes the content of this packet buffer into the given Data, starting + ///< at position Length, and clears the buffer afterwards. Length will be + ///< incremented accordingly. If Length plus the total length of the stored + ///< packets would exceed MaxLength, nothing is copied. + }; + +cPacketBuffer::cPacketBuffer(void) +{ + data = NULL; + size = length = 0; +} + +cPacketBuffer::~cPacketBuffer() +{ + free(data); +} + +void cPacketBuffer::Append(uchar *Data, int Length) +{ + if (length + Length >= size) { + int NewSize = (length + Length) * 3 / 2; + if (uchar *p = (uchar *)realloc(data, NewSize)) { + data = p; + size = NewSize; + } + else + return; // out of memory + } + memcpy(data + length, Data, Length); + length += Length; +} + +void cPacketBuffer::Flush(uchar *Data, int &Length, int MaxLength) +{ + if (Data && length > 0 && Length + length <= MaxLength) { + memcpy(Data + Length, data, length); + Length += length; + } + length = 0; +} + +// --- cPacketStorage -------------------------------------------------------- + +class cPacketStorage { +private: + cPacketBuffer *buffers[MAXPID]; +public: + cPacketStorage(void); + ~cPacketStorage(); + void Append(int Pid, uchar *Data, int Length); + void Flush(int Pid, uchar *Data, int &Length, int MaxLength); + }; + +cPacketStorage::cPacketStorage(void) +{ + for (int i = 0; i < MAXPID; i++) + buffers[i] = NULL; +} + +cPacketStorage::~cPacketStorage() +{ + for (int i = 0; i < MAXPID; i++) + delete buffers[i]; +} + +void cPacketStorage::Append(int Pid, uchar *Data, int Length) +{ + if (!buffers[Pid]) + buffers[Pid] = new cPacketBuffer; + buffers[Pid]->Append(Data, Length); +} + +void cPacketStorage::Flush(int Pid, uchar *Data, int &Length, int MaxLength) +{ + if (buffers[Pid]) + buffers[Pid]->Flush(Data, Length, MaxLength); +} + +// --- cDanglingPacketStripper ----------------------------------------------- + +class cDanglingPacketStripper { +private: + bool processed[MAXPID]; + cPatPmtParser patPmtParser; +public: + cDanglingPacketStripper(void); + bool Process(uchar *Data, int Length, int64_t FirstPts); + ///< Scans the frame given in Data and hides the payloads of any TS packets + ///< that either didn't start within this frame, or have a PTS that is + ///< before FirstPts. The TS packets in question are not physically removed + ///< from Data in order to keep any frame counts and PCR timestamps intact. + ///< Returns true if any dangling packets have been found. + }; + +cDanglingPacketStripper::cDanglingPacketStripper(void) +{ + memset(processed, 0x00, sizeof(processed)); +} + +bool cDanglingPacketStripper::Process(uchar *Data, int Length, int64_t FirstPts) +{ + bool Found = false; + while (Length >= TS_SIZE && *Data == TS_SYNC_BYTE) { + int Pid = TsPid(Data); + if (Pid == PATPID) + patPmtParser.ParsePat(Data, TS_SIZE); + else if (Pid == patPmtParser.PmtPid()) + patPmtParser.ParsePmt(Data, TS_SIZE); + else { + int64_t Pts = TsGetPts(Data, TS_SIZE); + if (Pts >= 0) + processed[Pid] = PtsDiff(FirstPts, Pts) >= 0; // Pts is at or after FirstPts + if (!processed[Pid]) { + TsHidePayload(Data); + Found = true; + } + } + Length -= TS_SIZE; + Data += TS_SIZE; + } + return Found; +} + +// --- cPtsFixer ------------------------------------------------------------- + +class cPtsFixer { +private: + int delta; // time between two frames + int64_t last; // the last (i.e. highest) video PTS value seen + int64_t offset; // offset to add to PTS values + bool fixCounters; // controls fixing the TS continuity counters (only from the second CutIn up) + uchar counter[MAXPID]; // the TS continuity counter for each PID + cPatPmtParser patPmtParser; +public: + cPtsFixer(void); + void Setup(double FramesPerSecond); + void Fix(uchar *Data, int Length, bool CutIn); + }; + +cPtsFixer::cPtsFixer(void) +{ + delta = 0; + last = -1; + offset = -1; + fixCounters = false; + memset(counter, 0x00, sizeof(counter)); +} + +void cPtsFixer::Setup(double FramesPerSecond) +{ + delta = int(round(PTSTICKS / FramesPerSecond)); +} + +void cPtsFixer::Fix(uchar *Data, int Length, bool CutIn) +{ + if (!patPmtParser.Vpid()) { + if (!patPmtParser.ParsePatPmt(Data, Length)) + return; + } + // Determine the PTS offset at the beginning of each sequence (except the first one): + if (CutIn && last >= 0) { + int64_t Pts = TsGetPts(Data, Length); + if (Pts >= 0) { + // offset is calculated so that Pts + offset results in last + delta: + offset = Pts - PtsAdd(last, delta); + if (offset <= 0) + offset = -offset; + else + offset = MAX33BIT + 1 - offset; + } + fixCounters = true; + } + // Keep track of the highest video PTS: + uchar *p = Data; + int len = Length; + while (len >= TS_SIZE && *p == TS_SYNC_BYTE) { + int Pid = TsPid(p); + if (Pid == patPmtParser.Vpid()) { + int64_t Pts = PtsAdd(TsGetPts(p, TS_SIZE), offset); // offset is taken into account here, to make last have the "new" value already! + if (Pts >= 0 && (last < 0 || PtsDiff(last, Pts) > 0)) + last = Pts; + } + // Adjust the TS continuity counter: + if (fixCounters) { + counter[Pid] = (counter[Pid] + 1) & TS_CONT_CNT_MASK; + TsSetContinuityCounter(p, counter[Pid]); + } + else + counter[Pid] = TsGetContinuityCounter(p); // collect initial counters + p += TS_SIZE; + len -= TS_SIZE; + } + // Apply the PTS offset: + if (offset > 0) { + uchar *p = Data; + int len = Length; + while (len >= TS_SIZE && *p == TS_SYNC_BYTE) { + // Adjust the various timestamps: + int64_t Pts = TsGetPts(p, TS_SIZE); + if (Pts >= 0) + TsSetPts(p, TS_SIZE, PtsAdd(Pts, offset)); + int64_t Dts = TsGetDts(p, TS_SIZE); + if (Dts >= 0) + TsSetDts(p, TS_SIZE, PtsAdd(Dts, offset)); + int64_t Pcr = TsGetPcr(p); + if (Pcr >= 0) { + int64_t NewPcr = Pcr + offset * PCRFACTOR; + if (NewPcr >= MAX27MHZ) + NewPcr -= MAX27MHZ + 1; + TsSetPcr(p, NewPcr); + } + p += TS_SIZE; + len -= TS_SIZE; + } + } +} + // --- cCuttingThread -------------------------------------------------------- class cCuttingThread : public cThread { private: const char *error; bool isPesRecording; + double framesPerSecond; cUnbufferedFile *fromFile, *toFile; cFileName *fromFileName, *toFileName; cIndexFile *fromIndex, *toIndex; cMarks fromMarks, toMarks; + int numSequences; off_t maxVideoFileSize; + off_t fileSize; + cPtsFixer ptsFixer; + bool suspensionLogged; + bool Throttled(void); + bool SwitchFile(bool Force = false); + bool LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length); + bool FramesAreEqual(int Index1, int Index2); + void GetPendingPackets(uchar *Buffer, int &Length, int Index, int64_t LastPts); + // Gather all non-video TS packets from Index upward that either belong to + // payloads that started before Index, or have a PTS that is before LastPts, + // and add them to the end of the given Data. + bool ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex); protected: virtual void Action(void); public: @@ -41,16 +285,25 @@ cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName) fromIndex = toIndex = NULL; cRecording Recording(FromFileName); isPesRecording = Recording.IsPesRecording(); - if (fromMarks.Load(FromFileName, Recording.FramesPerSecond(), isPesRecording) && fromMarks.Count()) { - fromFileName = new cFileName(FromFileName, false, true, isPesRecording); - toFileName = new cFileName(ToFileName, true, true, isPesRecording); - fromIndex = new cIndexFile(FromFileName, false, isPesRecording); - toIndex = new cIndexFile(ToFileName, true, isPesRecording); - toMarks.Load(ToFileName, Recording.FramesPerSecond(), isPesRecording); // doesn't actually load marks, just sets the file name - maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize); - if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES)) - maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES); - Start(); + framesPerSecond = Recording.FramesPerSecond(); + suspensionLogged = false; + fileSize = 0; + ptsFixer.Setup(framesPerSecond); + if (fromMarks.Load(FromFileName, framesPerSecond, isPesRecording) && fromMarks.Count()) { + numSequences = fromMarks.GetNumSequences(); + if (numSequences > 0) { + fromFileName = new cFileName(FromFileName, false, true, isPesRecording); + toFileName = new cFileName(ToFileName, true, true, isPesRecording); + fromIndex = new cIndexFile(FromFileName, false, isPesRecording); + toIndex = new cIndexFile(ToFileName, true, isPesRecording); + toMarks.Load(ToFileName, framesPerSecond, isPesRecording); // doesn't actually load marks, just sets the file name + maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize); + if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES)) + maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES); + Start(); + } + else + esyslog("no editing sequences found for %s", FromFileName); } else esyslog("no editing marks found for %s", FromFileName); @@ -65,168 +318,235 @@ cCuttingThread::~cCuttingThread() delete toIndex; } +bool cCuttingThread::Throttled(void) +{ + if (cIoThrottle::Engaged()) { + if (!suspensionLogged) { + dsyslog("suspending cutter thread"); + suspensionLogged = true; + } + return true; + } + else if (suspensionLogged) { + dsyslog("resuming cutter thread"); + suspensionLogged = false; + } + return false; +} + +bool cCuttingThread::LoadFrame(int Index, uchar *Buffer, bool &Independent, int &Length) +{ + uint16_t FileNumber; + off_t FileOffset; + if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length)) { + fromFile = fromFileName->SetOffset(FileNumber, FileOffset); + if (fromFile) { + fromFile->SetReadAhead(MEGABYTE(20)); + int len = ReadFrame(fromFile, Buffer, Length, MAXFRAMESIZE); + if (len < 0) + error = "ReadFrame"; + else if (len != Length) + Length = len; + return error == NULL; + } + else + error = "fromFile"; + } + return false; +} + +bool cCuttingThread::SwitchFile(bool Force) +{ + if (fileSize > maxVideoFileSize || Force) { + toFile = toFileName->NextFile(); + if (!toFile) { + error = "toFile"; + return false; + } + fileSize = 0; + } + return true; +} + +bool cCuttingThread::FramesAreEqual(int Index1, int Index2) +{ + bool Independent; + uchar Buffer1[MAXFRAMESIZE]; + uchar Buffer2[MAXFRAMESIZE]; + int Length1; + int Length2; + if (LoadFrame(Index1, Buffer1, Independent, Length1) && LoadFrame(Index2, Buffer2, Independent, Length2)) { + if (Length1 == Length2) { + int Diffs = 0; + for (int i = 0; i < Length1; i++) { + if (Buffer1[i] != Buffer2[i]) { + if (Diffs++ > 10) // the continuity counters of the PAT/PMT packets may differ + return false; + } + } + return true; + } + } + return false; +} + +void cCuttingThread::GetPendingPackets(uchar *Data, int &Length, int Index, int64_t LastPts) +{ + bool Processed[MAXPID] = { false }; + int NumIndependentFrames = 0; + cPatPmtParser PatPmtParser; + cPacketStorage PacketStorage; + for (; NumIndependentFrames < 2; Index++) { + uchar Buffer[MAXFRAMESIZE]; + bool Independent; + int len; + if (LoadFrame(Index, Buffer, Independent, len)) { + if (Independent) + NumIndependentFrames++; + uchar *p = Buffer; + while (len >= TS_SIZE && *p == TS_SYNC_BYTE) { + int Pid = TsPid(p); + if (Pid == PATPID) + PatPmtParser.ParsePat(p, TS_SIZE); + else if (Pid == PatPmtParser.PmtPid()) + PatPmtParser.ParsePmt(p, TS_SIZE); + else if (!Processed[Pid]) { + int64_t Pts = TsGetPts(p, TS_SIZE); + if (Pts >= 0) { + int64_t d = PtsDiff(LastPts, Pts); + if (d <= 0) // Pts is before or at LastPts + PacketStorage.Flush(Pid, Data, Length, MAXFRAMESIZE); + if (d >= 0) { // Pts is at or after LastPts + NumIndependentFrames = 0; // we search until we find two consecutive I-frames without any more pending packets + Processed[Pid] = true; + } + } + if (!Processed[Pid]) + PacketStorage.Append(Pid, p, TS_SIZE); + } + len -= TS_SIZE; + p += TS_SIZE; + } + } + else + break; + } +} + +bool cCuttingThread::ProcessSequence(int LastEndIndex, int BeginIndex, int EndIndex, int NextBeginIndex) +{ + // Check for seamless connections: + bool SeamlessBegin = LastEndIndex >= 0 && FramesAreEqual(LastEndIndex, BeginIndex); + bool SeamlessEnd = NextBeginIndex >= 0 && FramesAreEqual(EndIndex, NextBeginIndex); + // Process all frames from BeginIndex (included) to EndIndex (excluded): + cDanglingPacketStripper DanglingPacketStripper; + int NumIndependentFrames = 0; + int64_t FirstPts = -1; + int64_t LastPts = -1; + for (int Index = BeginIndex; Running() && Index < EndIndex; Index++) { + uchar Buffer[MAXFRAMESIZE]; + bool Independent; + int Length; + if (LoadFrame(Index, Buffer, Independent, Length)) { + if (!isPesRecording) { + int64_t Pts = TsGetPts(Buffer, Length); + if (FirstPts < 0) + FirstPts = Pts; // the PTS of the first frame in the sequence + else if (LastPts < 0 || PtsDiff(LastPts, Pts) > 0) + LastPts = Pts; // the PTS of the frame that is displayed as the very last one of the sequence + } + // Fixup data at the beginning of the sequence: + if (!SeamlessBegin) { + if (isPesRecording) { + if (Index == BeginIndex) + cRemux::SetBrokenLink(Buffer, Length); + } + else if (NumIndependentFrames < 2) { + if (DanglingPacketStripper.Process(Buffer, Length, FirstPts)) + NumIndependentFrames = 0; // we search until we find two consecutive I-frames without any more dangling packets + } + } + // Fixup data at the end of the sequence: + if (!SeamlessEnd) { + if (Index == EndIndex - 1) { + if (!isPesRecording) + GetPendingPackets(Buffer, Length, EndIndex, LastPts + int(round(PTSTICKS / framesPerSecond))); // adding one frame length to fully cover the very last frame + } + } + // Fixup timestamps and continuity counters: + if (!isPesRecording) { + if (numSequences > 1) + ptsFixer.Fix(Buffer, Length, !SeamlessBegin && Index == BeginIndex); + } + // Every file shall start with an independent frame: + if (Independent) { + NumIndependentFrames++; + if (!SwitchFile()) + return false; + } + // Write index: + if (!toIndex->Write(Independent, toFileName->Number(), fileSize)) { + error = "toIndex"; + return false; + } + // Write data: + if (toFile->Write(Buffer, Length) < 0) { + error = "safe_write"; + return false; + } + fileSize += Length; + // Generate marks at the editing points in the edited recording: + if (numSequences > 0 && Index == BeginIndex) { + if (toMarks.Count() > 0) + toMarks.Add(toIndex->Last()); + toMarks.Add(toIndex->Last()); + toMarks.Save(); + } + } + else + return false; + } + return true; +} + void cCuttingThread::Action(void) { - cMark *Mark = fromMarks.First(); - if (Mark) { + if (cMark *BeginMark = fromMarks.GetNextBegin()) { fromFile = fromFileName->Open(); toFile = toFileName->Open(); if (!fromFile || !toFile) return; - fromFile->SetReadAhead(MEGABYTE(20)); - int Index = Mark->Position(); - Mark = fromMarks.Next(Mark); - off_t FileSize = 0; - int CurrentFileNumber = 0; - int LastIFrame = 0; - toMarks.Add(0); - toMarks.Save(); - uchar buffer[MAXFRAMESIZE], buffer2[MAXFRAMESIZE]; - int Length2; - bool CheckForSeamlessStream = false; - bool LastMark = false; - bool cutIn = true; - bool suspensionLogged = false; - while (Running()) { - uint16_t FileNumber; - off_t FileOffset; - int Length; - bool Independent; - + int LastEndIndex = -1; + while (BeginMark && Running()) { // Suspend cutting if we have severe throughput problems: - - if (cIoThrottle::Engaged()) { - if (!suspensionLogged) { - dsyslog("suspending cutter thread"); - suspensionLogged = true; - } + if (Throttled()) { cCondWait::SleepMs(100); continue; } - else if (suspensionLogged) { - dsyslog("resuming cutter thread"); - suspensionLogged = false; - } - // Make sure there is enough disk space: - AssertFreeDiskSpace(-1); - - // Read one frame: - - if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) { - if (FileNumber != CurrentFileNumber) { - fromFile = fromFileName->SetOffset(FileNumber, FileOffset); - if (fromFile) - fromFile->SetReadAhead(MEGABYTE(20)); - CurrentFileNumber = FileNumber; - } - if (fromFile) { - int len = ReadFrame(fromFile, buffer, Length, sizeof(buffer)); - if (len < 0) { - error = "ReadFrame"; + // Determine the actual begin and end marks, skipping any marks at the same position: + cMark *EndMark = fromMarks.GetNextEnd(BeginMark); + // Process the current sequence: + int EndIndex = EndMark ? EndMark->Position() : fromIndex->Last() + 1; + int NextBeginIndex = -1; + if (EndMark) { + if (cMark *NextBeginMark = fromMarks.GetNextBegin(EndMark)) + NextBeginIndex = NextBeginMark->Position(); + } + if (!ProcessSequence(LastEndIndex, BeginMark->Position(), EndIndex, NextBeginIndex)) + break; + if (!EndMark) + break; // reached EOF + LastEndIndex = EndIndex; + // Switch to the next sequence: + BeginMark = fromMarks.GetNextBegin(EndMark); + if (BeginMark) { + // Split edited files: + if (Setup.SplitEditedFiles) { + if (!SwitchFile(true)) break; - } - if (len != Length) { - CurrentFileNumber = 0; // this re-syncs in case the frame was larger than the buffer - Length = len; - } } - else { - error = "fromFile"; - break; - } - } - else { - // Error, unless we're past the last cut-in and there's no cut-out - if (Mark || LastMark) - error = "index"; - break; - } - - // Write one frame: - - if (Independent) { // every file shall start with an independent frame - if (LastMark) // edited version shall end before next I-frame - break; - if (FileSize > maxVideoFileSize) { - toFile = toFileName->NextFile(); - if (!toFile) { - error = "toFile 1"; - break; - } - FileSize = 0; - } - LastIFrame = 0; - // Compare the current frame with the previously stored one, to see if this is a seamlessly merged recording of the same stream: - if (CheckForSeamlessStream) { - if (Length == Length2) { - int diffs = 0; - for (int i = 0; i < Length; i++) { - if (buffer[i] != buffer2[i]) { - if (diffs++ > 10) - break; - } - } - if (diffs < 10) // the continuity counters of the PAT/PMT packets may differ - cutIn = false; // it's apparently a seamless stream, so no need for "broken" handling - } - CheckForSeamlessStream = false; - } - if (cutIn) { - if (isPesRecording) - cRemux::SetBrokenLink(buffer, Length); - else - TsSetTeiOnBrokenPackets(buffer, Length); - cutIn = false; - } - } - if (toFile->Write(buffer, Length) < 0) { - error = "safe_write"; - break; - } - if (!toIndex->Write(Independent, toFileName->Number(), FileSize)) { - error = "toIndex"; - break; - } - FileSize += Length; - if (!LastIFrame) - LastIFrame = toIndex->Last(); - - // Check editing marks: - - if (Mark && Index >= Mark->Position()) { - Mark = fromMarks.Next(Mark); - toMarks.Add(LastIFrame); - if (Mark) - toMarks.Add(toIndex->Last() + 1); - toMarks.Save(); - if (Mark) { - // Read the next frame, for later comparison with the first frame at this mark: - if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length2)) { - if (FileNumber != CurrentFileNumber) - fromFile = fromFileName->SetOffset(FileNumber, FileOffset); - if (fromFile) { - int len = ReadFrame(fromFile, buffer2, Length2, sizeof(buffer2)); - if (len >= 0 && len == Length2) - CheckForSeamlessStream = true; - } - } - Index = Mark->Position(); - Mark = fromMarks.Next(Mark); - CurrentFileNumber = 0; // triggers SetOffset before reading next frame - cutIn = true; - if (Setup.SplitEditedFiles) { - toFile = toFileName->NextFile(); - if (!toFile) { - error = "toFile 2"; - break; - } - FileSize = 0; - } - } - else - LastMark = true; } } Recordings.TouchUpdate(); @@ -255,7 +575,7 @@ bool cCutter::Start(const char *FileName) cMarks FromMarks; FromMarks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()); - if (cMark *First = FromMarks.First()) + if (cMark *First = FromMarks.GetNextBegin()) Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60); const char *evn = Recording.PrefixFileName('%'); @@ -343,13 +663,17 @@ bool CutRecording(const char *FileName) if (Recording.Name()) { cMarks Marks; if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()) && Marks.Count()) { - if (cCutter::Start(FileName)) { - while (cCutter::Active()) - cCondWait::SleepMs(CUTTINGCHECKINTERVAL); - return true; + if (Marks.GetNumSequences()) { + if (cCutter::Start(FileName)) { + while (cCutter::Active()) + cCondWait::SleepMs(CUTTINGCHECKINTERVAL); + return true; + } + else + fprintf(stderr, "can't start editing process\n"); } else - fprintf(stderr, "can't start editing process\n"); + fprintf(stderr, "'%s' has no editing sequences\n", FileName); } else fprintf(stderr, "'%s' has no editing marks\n", FileName); diff --git a/menu.c b/menu.c index d50be124..7837922f 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 2.62 2012/10/03 10:14:53 kls Exp $ + * $Id: menu.c 2.63 2012/11/13 11:23:25 kls Exp $ */ #include "menu.h" @@ -4796,6 +4796,8 @@ void cReplayControl::EditCut(void) if (!cCutter::Active()) { if (!marks.Count()) Skins.Message(mtError, tr("No editing marks defined!")); + else if (!marks.GetNumSequences()) + Skins.Message(mtError, tr("No editing sequences defined!")); else if (!cCutter::Start(fileName)) Skins.Message(mtError, tr("Can't start editing process!")); else diff --git a/po/ar.po b/po/ar.po index 14e8126b..c0fb31f4 100644 --- a/po/ar.po +++ b/po/ar.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.7.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-10-16 11:16-0400\n" "Last-Translator: Osama Alrawab \n" "Language-Team: Arabic \n" @@ -1241,6 +1241,9 @@ msgstr "اقفز الى " msgid "No editing marks defined!" msgstr "لاتوجد علامات تعديل معرفة" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "لا يمكن البدء فى عملية التعديل" diff --git a/po/ca_ES.po b/po/ca_ES.po index c67265b1..f08cd57b 100644 --- a/po/ca_ES.po +++ b/po/ca_ES.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-03-02 19:02+0100\n" "Last-Translator: Luca Olivetti \n" "Language-Team: Catalan \n" @@ -1216,6 +1216,9 @@ msgstr "Salta a:" msgid "No editing marks defined!" msgstr "No hi ha marques d'edici definides" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "No puc iniciar el procs d'edici!" diff --git a/po/cs_CZ.po b/po/cs_CZ.po index 55053f95..1a621026 100644 --- a/po/cs_CZ.po +++ b/po/cs_CZ.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.7.14\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2010-05-06 11:00+0200\n" "Last-Translator: Radek Šťastný \n" "Language-Team: Czech \n" @@ -1215,6 +1215,9 @@ msgstr "Skok: " msgid "No editing marks defined!" msgstr "Nejsou definovány editační značky!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Nelze začít editační proces!" diff --git a/po/da_DK.po b/po/da_DK.po index fac58003..597539ff 100644 --- a/po/da_DK.po +++ b/po/da_DK.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2007-08-12 14:17+0200\n" "Last-Translator: Mogens Elneff \n" "Language-Team: Danish \n" @@ -1213,6 +1213,9 @@ msgstr "Hop: " msgid "No editing marks defined!" msgstr "Der er ikke sat nogen redigeringsmrker!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Kan ikke starte redigeringsprocessen!" diff --git a/po/de_DE.po b/po/de_DE.po index a321b501..c958758c 100644 --- a/po/de_DE.po +++ b/po/de_DE.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2010-01-16 16:46+0100\n" "Last-Translator: Klaus Schmidinger \n" "Language-Team: German \n" @@ -1213,6 +1213,9 @@ msgstr "Springen: " msgid "No editing marks defined!" msgstr "Keine Schnittmarken gesetzt!" +msgid "No editing sequences defined!" +msgstr "Keine Schnittsequenzen definiert!" + msgid "Can't start editing process!" msgstr "Schnitt kann nicht gestartet werden!" diff --git a/po/el_GR.po b/po/el_GR.po index 8936408c..1347812d 100644 --- a/po/el_GR.po +++ b/po/el_GR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2007-08-12 14:17+0200\n" "Last-Translator: Dimitrios Dimitrakos \n" "Language-Team: Greek \n" @@ -1213,6 +1213,9 @@ msgstr " msgid "No editing marks defined!" msgstr " " +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr " !" diff --git a/po/es_ES.po b/po/es_ES.po index bc80a117..8e76063d 100644 --- a/po/es_ES.po +++ b/po/es_ES.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-03-02 19:02+0100\n" "Last-Translator: Luca Olivetti \n" "Language-Team: Spanish \n" @@ -1214,6 +1214,9 @@ msgstr "Saltar: " msgid "No editing marks defined!" msgstr "No se definieron marcas de edicin!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "No se puede iniciar el proceso de edicin!" diff --git a/po/et_EE.po b/po/et_EE.po index 0dea9992..12c1f259 100644 --- a/po/et_EE.po +++ b/po/et_EE.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2007-08-12 14:17+0200\n" "Last-Translator: Arthur Konovalov \n" "Language-Team: Estonian \n" @@ -1213,6 +1213,9 @@ msgstr "Hüpe: " msgid "No editing marks defined!" msgstr "Redigeerimise markerid puuduvad!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Redigeerimise start nurjus!" diff --git a/po/fi_FI.po b/po/fi_FI.po index daad22ae..901328ae 100644 --- a/po/fi_FI.po +++ b/po/fi_FI.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-13 13:15+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2007-08-15 15:52+0200\n" "Last-Translator: Rolf Ahrenberg \n" "Language-Team: Finnish \n" @@ -1216,6 +1216,9 @@ msgstr "Siirry: " msgid "No editing marks defined!" msgstr "Muokkausmerkinnät puuttuvat!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Muokkauksen aloitus epäonnistui!" diff --git a/po/fr_FR.po b/po/fr_FR.po index 3ca3c06f..a0995aeb 100644 --- a/po/fr_FR.po +++ b/po/fr_FR.po @@ -13,7 +13,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-02-27 18:14+0100\n" "Last-Translator: Jean-Claude Repetto \n" "Language-Team: French \n" @@ -1219,6 +1219,9 @@ msgstr "Acc msgid "No editing marks defined!" msgstr "Pas de marques d'dition dfinies !" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Impossible de commencer le montage !" diff --git a/po/hr_HR.po b/po/hr_HR.po index dcb1a217..44cfd09f 100644 --- a/po/hr_HR.po +++ b/po/hr_HR.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-03-17 19:00+0100\n" "Last-Translator: Adrian Caval \n" "Language-Team: Croatian \n" @@ -1215,6 +1215,9 @@ msgstr "Sko msgid "No editing marks defined!" msgstr "Nijedna toka rezanja nije odreena!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Ne mogu zapoeti ureivanje!" diff --git a/po/hu_HU.po b/po/hu_HU.po index c391b67e..30ab8ab6 100644 --- a/po/hu_HU.po +++ b/po/hu_HU.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2012-01-02 11:54+0200\n" "Last-Translator: Istvn Fley \n" "Language-Team: Hungarian \n" @@ -1217,6 +1217,9 @@ msgstr "Ugr msgid "No editing marks defined!" msgstr "Nincs vgpont kijellve" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "A vgs nem indthat!" diff --git a/po/it_IT.po b/po/it_IT.po index b63d93d8..1258f23f 100644 --- a/po/it_IT.po +++ b/po/it_IT.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2012-06-06 22:50+0100\n" "Last-Translator: Diego Pierotto \n" "Language-Team: Italian \n" @@ -1220,6 +1220,9 @@ msgstr "Vai a: " msgid "No editing marks defined!" msgstr "Nessun marcatore di modifica definito!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Impossibile avviare il processo di modifica!" diff --git a/po/lt_LT.po b/po/lt_LT.po index 95f8e831..b53c7627 100644 --- a/po/lt_LT.po +++ b/po/lt_LT.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.7.16\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2010-10-30 11:55+0200\n" "Last-Translator: Valdemaras Pipiras \n" "Language-Team: Lithuanian \n" @@ -1213,6 +1213,9 @@ msgstr "Peršokti: " msgid "No editing marks defined!" msgstr "Nenustatytos koregavimo žymės!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Negali pradėti koregavimo!" diff --git a/po/mk_MK.po b/po/mk_MK.po index fd16ec88..89e8b739 100644 --- a/po/mk_MK.po +++ b/po/mk_MK.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR-1.7.14\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2010-03-11 00:54+0100\n" "Last-Translator: Dimitar Petrovski \n" "Language-Team: Macedonian \n" @@ -1214,6 +1214,9 @@ msgstr "Скокни:" msgid "No editing marks defined!" msgstr "Нема одредено ознаки за сечење!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Не може да почне уредување!" diff --git a/po/nl_NL.po b/po/nl_NL.po index 6c3825d9..5e515c36 100644 --- a/po/nl_NL.po +++ b/po/nl_NL.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-02-26 17:20+0100\n" "Last-Translator: Johan Schuring \n" "Language-Team: Dutch \n" @@ -1217,6 +1217,9 @@ msgstr "Springen: " msgid "No editing marks defined!" msgstr "Geen bewerkingsmarkeringen gedefinieerd!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Kan niet beginnen met bewerken!" diff --git a/po/nn_NO.po b/po/nn_NO.po index 41ce5c30..f3662605 100644 --- a/po/nn_NO.po +++ b/po/nn_NO.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2007-08-12 14:17+0200\n" "Last-Translator: Truls Slevigen \n" "Language-Team: Norwegian Nynorsk \n" @@ -1214,6 +1214,9 @@ msgstr "Hopp: " msgid "No editing marks defined!" msgstr "" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Kan ikke starte redigeringsprosessen!" diff --git a/po/pl_PL.po b/po/pl_PL.po index f4c18761..fbe8a860 100644 --- a/po/pl_PL.po +++ b/po/pl_PL.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-03-09 12:59+0100\n" "Last-Translator: Michael Rakowski \n" "Language-Team: Polish \n" @@ -1214,6 +1214,9 @@ msgstr "Skok: " msgid "No editing marks defined!" msgstr "Nie zdefiniowano znacznikw montau!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Nie mona uruchomi procesu edycji!" diff --git a/po/pt_PT.po b/po/pt_PT.po index 5cfc2037..d69e0690 100644 --- a/po/pt_PT.po +++ b/po/pt_PT.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.7.15\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2010-03-28 22:49+0100\n" "Last-Translator: Cris Silva \n" "Language-Team: Portuguese \n" @@ -1214,6 +1214,9 @@ msgstr "Saltar: " msgid "No editing marks defined!" msgstr "Marcas de edio no foram definidas!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Impossvel iniciar processo de edio!" diff --git a/po/ro_RO.po b/po/ro_RO.po index 7db9a15a..03ae8f02 100644 --- a/po/ro_RO.po +++ b/po/ro_RO.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.7.12\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2012-11-05 01:28+0100\n" "Last-Translator: Lucian Muresan \n" "Language-Team: Romanian \n" @@ -1216,6 +1216,9 @@ msgstr "Salt la: " msgid "No editing marks defined!" msgstr "Nu s-au pus marcaje de montaj pentru această înregistrare" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Nu pot porni montajul înregistrării!" diff --git a/po/ru_RU.po b/po/ru_RU.po index d8e1e8e1..b58fd9a1 100644 --- a/po/ru_RU.po +++ b/po/ru_RU.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-12-15 14:37+0100\n" "Last-Translator: Oleg Roitburd \n" "Language-Team: Russian \n" @@ -1214,6 +1214,9 @@ msgstr " msgid "No editing marks defined!" msgstr " !" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr " !" diff --git a/po/sk_SK.po b/po/sk_SK.po index 0de2d061..ab5035e8 100644 --- a/po/sk_SK.po +++ b/po/sk_SK.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.7.16\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2011-02-15 16:29+0100\n" "Last-Translator: Milan Hrala \n" "Language-Team: Slovak \n" @@ -1213,6 +1213,9 @@ msgstr "Skok: " msgid "No editing marks defined!" msgstr "Nie s uren znaky prav!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Neme zaa spracovanie prav!" diff --git a/po/sl_SI.po b/po/sl_SI.po index 95568682..9326d858 100644 --- a/po/sl_SI.po +++ b/po/sl_SI.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-02-28 19:44+0100\n" "Last-Translator: Matjaz Thaler \n" "Language-Team: Slovenian \n" @@ -1214,6 +1214,9 @@ msgstr "Sko msgid "No editing marks defined!" msgstr "Nobena prekinitvena toka ni definirana!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Ne morem zaeti urejanja!" diff --git a/po/sr_SR.po b/po/sr_SR.po index c4149dda..c7b97d57 100644 --- a/po/sr_SR.po +++ b/po/sr_SR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.7.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2011-01-09 15:57+0100\n" "Last-Translator: Milan Cvijanovi \n" "Language-Team: Serbian \n" @@ -1239,6 +1239,9 @@ msgstr "Sko msgid "No editing marks defined!" msgstr "Nijedna taka rezanja nije odreena!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Ne mogu zapoeti ureivanje!" diff --git a/po/sv_SE.po b/po/sv_SE.po index 951dd044..dc4d6208 100644 --- a/po/sv_SE.po +++ b/po/sv_SE.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-03-12 18:25+0100\n" "Last-Translator: Magnus Andersson \n" "Language-Team: Swedish \n" @@ -1216,6 +1216,9 @@ msgstr "Hopp: " msgid "No editing marks defined!" msgstr "Det finns inga redigeringsmrken" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Kan inte starta redigering!" diff --git a/po/tr_TR.po b/po/tr_TR.po index 8b161b3f..1f619441 100644 --- a/po/tr_TR.po +++ b/po/tr_TR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2008-02-28 00:33+0100\n" "Last-Translator: Oktay Yolgeen \n" "Language-Team: Turkish \n" @@ -1213,6 +1213,9 @@ msgstr "Atla: " msgid "No editing marks defined!" msgstr "Kesim iaretleri belirtilmemi!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Kesim balatlamyor!" diff --git a/po/uk_UA.po b/po/uk_UA.po index 45e57ee5..72fe84c5 100644 --- a/po/uk_UA.po +++ b/po/uk_UA.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.7.7\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2010-04-25 16:35+0200\n" "Last-Translator: Yarema aka Knedlyk \n" "Language-Team: Ukrainian \n" @@ -1213,6 +1213,9 @@ msgstr "Перейти: " msgid "No editing marks defined!" msgstr "Не задано міток для монтажу!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "Неможливо почати монтаж запису!" diff --git a/po/zh_CN.po b/po/zh_CN.po index 4e2b951f..56b2de0d 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.6.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-09-15 14:04+0200\n" +"POT-Creation-Date: 2012-11-18 14:31+0100\n" "PO-Revision-Date: 2009-09-23 23:50+0800\n" "Last-Translator: Nan Feng \n" "Language-Team: Chinese (simplified) \n" @@ -1216,6 +1216,9 @@ msgstr "跳过: " msgid "No editing marks defined!" msgstr "无编辑标记定义!" +msgid "No editing sequences defined!" +msgstr "" + msgid "Can't start editing process!" msgstr "不能开始编辑处理" diff --git a/recording.c b/recording.c index 79db1c74..497bf2dd 100644 --- a/recording.c +++ b/recording.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 2.72 2012/11/12 14:51:09 kls Exp $ + * $Id: recording.c 2.73 2012/11/13 13:46:49 kls Exp $ */ #include "recording.h" @@ -1456,6 +1456,54 @@ cMark *cMarks::GetNext(int Position) return NULL; } +cMark *cMarks::GetNextBegin(cMark *EndMark) +{ + cMark *BeginMark = EndMark ? Next(EndMark) : First(); + if (BeginMark) { + while (cMark *NextMark = Next(BeginMark)) { + if (BeginMark->Position() == NextMark->Position()) { // skip Begin/End at the same position + if (!(BeginMark = Next(NextMark))) + break; + } + else + break; + } + } + return BeginMark; +} + +cMark *cMarks::GetNextEnd(cMark *BeginMark) +{ + if (!BeginMark) + return NULL; + cMark *EndMark = Next(BeginMark); + if (EndMark) { + while (cMark *NextMark = Next(EndMark)) { + if (EndMark->Position() == NextMark->Position()) { // skip End/Begin at the same position + if (!(EndMark = Next(NextMark))) + break; + } + else + break; + } + } + return EndMark; +} + +int cMarks::GetNumSequences(void) +{ + int NumSequences = 0; + if (cMark *BeginMark = GetNextBegin()) { + while (cMark *EndMark = GetNextEnd(BeginMark)) { + NumSequences++; + BeginMark = GetNextBegin(EndMark); + } + if (NumSequences == 0 && BeginMark->Position() > 0) + NumSequences = 1; // there is only one actual "begin" mark at a non-zero offset, and no actual "end" mark + } + return NumSequences; +} + // --- cRecordingUserCommand ------------------------------------------------- const char *cRecordingUserCommand::command = NULL; diff --git a/recording.h b/recording.h index 7118e783..9ae9b1ec 100644 --- a/recording.h +++ b/recording.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.h 2.39 2012/11/12 14:51:09 kls Exp $ + * $Id: recording.h 2.40 2012/11/13 11:43:59 kls Exp $ */ #ifndef __RECORDING_H @@ -238,6 +238,19 @@ public: cMark *Get(int Position); cMark *GetPrev(int Position); cMark *GetNext(int Position); + cMark *GetNextBegin(cMark *EndMark = NULL); + ///< Returns the next "begin" mark after EndMark, skipping any marks at the + ///< same position as EndMark. If EndMark is NULL, the first actual "begin" + ///< will be returned (if any). + cMark *GetNextEnd(cMark *BeginMark); + ///< Returns the next "end" mark after BeginMark, skipping any marks at the + ///< same position as BeginMark. + int GetNumSequences(void); + ///< Returns the actual number of sequences to be cut from the recording. + ///< If there is only one actual "begin" mark, and it is positioned at index + ///< 0 (the beginning of the recording), and there is no "end" mark, the + ///< return value is 0, which means that the result is the same as the original + ///< recording. }; #define RUC_BEFORERECORDING "before" diff --git a/remux.c b/remux.c index bc7cd0de..e3b34c66 100644 --- a/remux.c +++ b/remux.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.c 2.70 2012/11/13 10:00:00 kls Exp $ + * $Id: remux.c 2.71 2012/11/18 12:18:08 kls Exp $ */ #include "remux.h" @@ -114,6 +114,32 @@ void cRemux::SetBrokenLink(uchar *Data, int Length) // --- Some TS handling tools ------------------------------------------------ +void TsHidePayload(uchar *p) +{ + p[1] &= ~TS_PAYLOAD_START; + p[3] |= TS_ADAPT_FIELD_EXISTS; + p[3] &= ~TS_PAYLOAD_EXISTS; + p[4] = TS_SIZE - 5; + p[5] = 0x00; + memset(p + 6, 0xFF, TS_SIZE - 6); +} + +void TsSetPcr(uchar *p, int64_t Pcr) +{ + if (TsHasAdaptationField(p)) { + if (p[4] >= 7 && (p[5] & TS_ADAPT_PCR)) { + int64_t b = Pcr / PCRFACTOR; + int e = Pcr % PCRFACTOR; + p[ 6] = b >> 25; + p[ 7] = b >> 17; + p[ 8] = b >> 9; + p[ 9] = b >> 1; + p[10] = (b << 7) | (p[10] & 0x7E) | ((e >> 8) & 0x01); + p[11] = e; + } + } +} + int64_t TsGetPts(const uchar *p, int l) { // Find the first packet with a PTS and use it: @@ -127,25 +153,75 @@ int64_t TsGetPts(const uchar *p, int l) return -1; } -void TsSetTeiOnBrokenPackets(uchar *p, int l) +int64_t TsGetDts(const uchar *p, int l) { - bool Processed[MAXPID] = { false }; - while (l >= TS_SIZE) { - if (*p != TS_SYNC_BYTE) - break; - int Pid = TsPid(p); - if (!Processed[Pid]) { - if (!TsPayloadStart(p)) - p[1] |= TS_ERROR; - else { - Processed[Pid] = true; - int offs = TsPayloadOffset(p); - cRemux::SetBrokenLink(p + offs, TS_SIZE - offs); - } - } - l -= TS_SIZE; + // Find the first packet with a DTS and use it: + while (l > 0) { + const uchar *d = p; + if (TsPayloadStart(d) && TsGetPayload(&d) && PesHasDts(d)) + return PesGetDts(d); p += TS_SIZE; + l -= TS_SIZE; } + return -1; +} + +void TsSetPts(uchar *p, int l, int64_t Pts) +{ + // Find the first packet with a PTS and use it: + while (l > 0) { + const uchar *d = p; + if (TsPayloadStart(d) && TsGetPayload(&d) && PesHasPts(d)) { + PesSetPts(const_cast(d), Pts); + return; + } + p += TS_SIZE; + l -= TS_SIZE; + } +} + +void TsSetDts(uchar *p, int l, int64_t Dts) +{ + // Find the first packet with a DTS and use it: + while (l > 0) { + const uchar *d = p; + if (TsPayloadStart(d) && TsGetPayload(&d) && PesHasDts(d)) { + PesSetDts(const_cast(d), Dts); + return; + } + p += TS_SIZE; + l -= TS_SIZE; + } +} + +// --- Some PES handling tools ----------------------------------------------- + +void PesSetPts(uchar *p, int64_t Pts) +{ + p[ 9] = ((Pts >> 29) & 0x0E) | (p[9] & 0xF1); + p[10] = Pts >> 22; + p[11] = ((Pts >> 14) & 0xFE) | 0x01; + p[12] = Pts >> 7; + p[13] = ((Pts << 1) & 0xFE) | 0x01; +} + +void PesSetDts(uchar *p, int64_t Dts) +{ + p[14] = ((Dts >> 29) & 0x0E) | (p[14] & 0xF1); + p[15] = Dts >> 22; + p[16] = ((Dts >> 14) & 0xFE) | 0x01; + p[17] = Dts >> 7; + p[18] = ((Dts << 1) & 0xFE) | 0x01; +} + +int64_t PtsDiff(int64_t Pts1, int64_t Pts2) +{ + int64_t d = Pts2 - Pts1; + if (d > MAX33BIT / 2) + return d - (MAX33BIT + 1); + if (d < -MAX33BIT / 2) + return d + (MAX33BIT + 1); + return d; } // --- cTsPayload ------------------------------------------------------------ @@ -1395,7 +1471,7 @@ int cFrameDetector::Analyze(const uchar *Data, int Length) } } else // audio - framesPerSecond = 90000.0 / Delta; // PTS of audio frames is always increasing + framesPerSecond = double(PTSTICKS) / Delta; // PTS of audio frames is always increasing dbgframes("\nDelta = %d FPS = %5.2f FPPU = %d NF = %d\n", Delta, framesPerSecond, framesPerPayloadUnit, numPtsValues + 1); synced = true; parser->SetDebug(false); diff --git a/remux.h b/remux.h index 9b156a69..dd17e0d9 100644 --- a/remux.h +++ b/remux.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.h 2.34 2012/11/06 11:03:06 kls Exp $ + * $Id: remux.h 2.35 2012/11/18 12:17:23 kls Exp $ */ #ifndef __REMUX_H @@ -52,6 +52,11 @@ public: #define PATPID 0x0000 // PAT PID (constant 0) #define MAXPID 0x2000 // for arrays that use a PID as the index +#define PTSTICKS 90000 // number of PTS ticks per second +#define PCRFACTOR 300 // conversion from 27MHz PCR extension to 90kHz PCR base +#define MAX33BIT 0x00000001FFFFFFFFLL // max. possible value with 33 bit +#define MAX27MHZ ((MAX33BIT + 1) * PCRFACTOR - 1) // max. possible PCR value + inline bool TsHasPayload(const uchar *p) { return p[3] & TS_PAYLOAD_EXISTS; @@ -82,6 +87,16 @@ inline bool TsIsScrambled(const uchar *p) return p[3] & TS_SCRAMBLING_CONTROL; } +inline uchar TsGetContinuityCounter(const uchar *p) +{ + return p[3] & TS_CONT_CNT_MASK; +} + +inline void TsSetContinuityCounter(uchar *p, uchar Counter) +{ + p[3] = (p[3] & ~TS_CONT_CNT_MASK) | (Counter & TS_CONT_CNT_MASK); +} + inline int TsPayloadOffset(const uchar *p) { int o = TsHasAdaptationField(p) ? p[4] + 5 : 4; @@ -103,15 +118,31 @@ inline int TsContinuityCounter(const uchar *p) return p[3] & TS_CONT_CNT_MASK; } -inline int TsGetAdaptationField(const uchar *p) +inline int64_t TsGetPcr(const uchar *p) { - return TsHasAdaptationField(p) ? p[5] : 0x00; + if (TsHasAdaptationField(p)) { + if (p[4] >= 7 && (p[5] & TS_ADAPT_PCR)) { + return ((((int64_t)p[ 6]) << 25) | + (((int64_t)p[ 7]) << 17) | + (((int64_t)p[ 8]) << 9) | + (((int64_t)p[ 9]) << 1) | + (((int64_t)p[10]) >> 7)) * PCRFACTOR + + (((((int)p[10]) & 0x01) << 8) | + ( ((int)p[11]))); + } + } + return -1; } +void TsHidePayload(uchar *p); +void TsSetPcr(uchar *p, int64_t Pcr); + // The following functions all take a pointer to a sequence of complete TS packets. int64_t TsGetPts(const uchar *p, int l); -void TsSetTeiOnBrokenPackets(uchar *p, int l); +int64_t TsGetDts(const uchar *p, int l); +void TsSetPts(uchar *p, int l, int64_t Pts); +void TsSetDts(uchar *p, int l, int64_t Dts); // Some PES handling tools: // The following functions that take a pointer to PES data all assume that @@ -142,6 +173,11 @@ inline bool PesHasPts(const uchar *p) return (p[7] & 0x80) && p[8] >= 5; } +inline bool PesHasDts(const uchar *p) +{ + return (p[7] & 0x40) && p[8] >= 10; +} + inline int64_t PesGetPts(const uchar *p) { return ((((int64_t)p[ 9]) & 0x0E) << 29) | @@ -151,6 +187,28 @@ inline int64_t PesGetPts(const uchar *p) ((((int64_t)p[13]) & 0xFE) >> 1); } +inline int64_t PesGetDts(const uchar *p) +{ + return ((((int64_t)p[14]) & 0x0E) << 29) | + (( (int64_t)p[15]) << 22) | + ((((int64_t)p[16]) & 0xFE) << 14) | + (( (int64_t)p[17]) << 7) | + ((((int64_t)p[18]) & 0xFE) >> 1); +} + +void PesSetPts(uchar *p, int64_t Pts); +void PesSetDts(uchar *p, int64_t Dts); + +// PTS handling: + +inline int64_t PtsAdd(int64_t Pts1, int64_t Pts2) { return (Pts1 + Pts2) & MAX33BIT; } + ///< Adds the given PTS values, taking into account the 33bit wrap around. +int64_t PtsDiff(int64_t Pts1, int64_t Pts2); + ///< Returns the difference between two PTS values. The result of Pts2 - Pts1 + ///< is the actual number of 90kHz time ticks that pass from Pts1 to Pts2, + ///< properly taking into account the 33bit wrap around. If Pts2 is "before" + ///< Pts1, the result is negative. + // A transprent TS payload handler: class cTsPayload {