/* * cutter.c: The video cutting facilities * * See the main source file 'vdr.c' for copyright information and * how to reach the author. * * $Id: cutter.c 1.10 2005/08/14 10:51:54 kls Exp $ */ #include "cutter.h" #include "recording.h" #include "remux.h" #include "thread.h" #include "videodir.h" // --- cCuttingThread -------------------------------------------------------- class cCuttingThread : public cThread { private: const char *error; int fromFile, toFile; cFileName *fromFileName, *toFileName; cIndexFile *fromIndex, *toIndex; cMarks fromMarks, toMarks; protected: virtual void Action(void); public: cCuttingThread(const char *FromFileName, const char *ToFileName); virtual ~cCuttingThread(); const char *Error(void) { return error; } }; cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName) :cThread("video cutting") { error = NULL; fromFile = toFile = -1; fromFileName = toFileName = NULL; fromIndex = toIndex = NULL; if (fromMarks.Load(FromFileName) && fromMarks.Count()) { fromFileName = new cFileName(FromFileName, false, true); toFileName = new cFileName(ToFileName, true, true); fromIndex = new cIndexFile(FromFileName, false); toIndex = new cIndexFile(ToFileName, true); toMarks.Load(ToFileName); // doesn't actually load marks, just sets the file name Start(); } else esyslog("no editing marks found for %s", FromFileName); } cCuttingThread::~cCuttingThread() { Cancel(3); delete fromFileName; delete toFileName; delete fromIndex; delete toIndex; } void cCuttingThread::Action(void) { cMark *Mark = fromMarks.First(); if (Mark) { fromFile = fromFileName->Open(); toFile = toFileName->Open(); if (fromFile < 0 || toFile < 0) return; int Index = Mark->position; Mark = fromMarks.Next(Mark); int FileSize = 0; int CurrentFileNumber = 0; int LastIFrame = 0; toMarks.Add(0); toMarks.Save(); uchar buffer[MAXFRAMESIZE]; bool LastMark = false; bool cutIn = true; while (Running()) { uchar FileNumber; int FileOffset, Length; uchar PictureType; // Make sure there is enough disk space: AssertFreeDiskSpace(-1); // Read one frame: if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) { if (FileNumber != CurrentFileNumber) { fromFile = fromFileName->SetOffset(FileNumber, FileOffset); CurrentFileNumber = FileNumber; } if (fromFile >= 0) { int len = ReadFrame(fromFile, buffer, Length, sizeof(buffer)); if (len < 0) { error = "ReadFrame"; 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 break; // Write one frame: if (PictureType == I_FRAME) { // every file shall start with an I_FRAME if (LastMark) // edited version shall end before next I-frame break; if (FileSize > MEGABYTE(Setup.MaxVideoFileSize)) { toFile = toFileName->NextFile(); if (toFile < 0) { error = "toFile 1"; break; } FileSize = 0; } LastIFrame = 0; if (cutIn) { cRemux::SetBrokenLink(buffer, Length); cutIn = false; } } if (safe_write(toFile, buffer, Length) < 0) { error = "safe_write"; break; } if (!toIndex->Write(PictureType, 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) { 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 < 0) { error = "toFile 2"; break; } FileSize = 0; } } else LastMark = true; } } } else esyslog("no editing marks found!"); } // --- cCutter --------------------------------------------------------------- char *cCutter::editedVersionName = NULL; cCuttingThread *cCutter::cuttingThread = NULL; bool cCutter::error = false; bool cCutter::ended = false; bool cCutter::Start(const char *FileName) { if (!cuttingThread) { error = false; ended = false; cRecording Recording(FileName); const char *evn = Recording.PrefixFileName('%'); if (evn && RemoveVideoFile(evn) && MakeDirs(evn, true)) { // XXX this can be removed once RenameVideoFile() follows symlinks (see videodir.c) // remove a possible deleted recording with the same name to avoid symlink mixups: char *s = strdup(evn); char *e = strrchr(s, '.'); if (e) { if (strcmp(e, ".rec") == 0) { strcpy(e, ".del"); RemoveVideoFile(s); } } free(s); // XXX editedVersionName = strdup(evn); Recording.WriteInfo(); Recordings.AddByName(editedVersionName); cuttingThread = new cCuttingThread(FileName, editedVersionName); return true; } } return false; } void cCutter::Stop(void) { bool Interrupted = cuttingThread && cuttingThread->Active(); const char *Error = cuttingThread ? cuttingThread->Error() : NULL; delete cuttingThread; cuttingThread = NULL; if ((Interrupted || Error) && editedVersionName) { if (Interrupted) isyslog("editing process has been interrupted"); if (Error) esyslog("ERROR: '%s' during editing process", Error); RemoveVideoFile(editedVersionName); //XXX what if this file is currently being replayed? Recordings.DelByName(editedVersionName); } } bool cCutter::Active(void) { if (cuttingThread) { if (cuttingThread->Active()) return true; error = cuttingThread->Error(); Stop(); if (!error) cRecordingUserCommand::InvokeCommand(RUC_EDITEDRECORDING, editedVersionName); free(editedVersionName); editedVersionName = NULL; ended = true; } return false; } bool cCutter::Error(void) { bool result = error; error = false; return result; } bool cCutter::Ended(void) { bool result = ended; ended = false; return result; }