vdr
1.7.27
|
00001 /* 00002 * cutter.c: The video cutting facilities 00003 * 00004 * See the main source file 'vdr.c' for copyright information and 00005 * how to reach the author. 00006 * 00007 * $Id: cutter.c 2.11 2012/02/16 12:08:39 kls Exp $ 00008 */ 00009 00010 #include "cutter.h" 00011 #include "interface.h" 00012 #include "menu.h" 00013 #include "recording.h" 00014 #include "remux.h" 00015 #include "videodir.h" 00016 00017 // --- cCuttingThread -------------------------------------------------------- 00018 00019 class cCuttingThread : public cThread { 00020 private: 00021 const char *error; 00022 bool isPesRecording; 00023 cUnbufferedFile *fromFile, *toFile; 00024 cFileName *fromFileName, *toFileName; 00025 cIndexFile *fromIndex, *toIndex; 00026 cMarks fromMarks, toMarks; 00027 off_t maxVideoFileSize; 00028 protected: 00029 virtual void Action(void); 00030 public: 00031 cCuttingThread(const char *FromFileName, const char *ToFileName); 00032 virtual ~cCuttingThread(); 00033 const char *Error(void) { return error; } 00034 }; 00035 00036 cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName) 00037 :cThread("video cutting") 00038 { 00039 error = NULL; 00040 fromFile = toFile = NULL; 00041 fromFileName = toFileName = NULL; 00042 fromIndex = toIndex = NULL; 00043 cRecording Recording(FromFileName); 00044 isPesRecording = Recording.IsPesRecording(); 00045 if (fromMarks.Load(FromFileName, Recording.FramesPerSecond(), isPesRecording) && fromMarks.Count()) { 00046 fromFileName = new cFileName(FromFileName, false, true, isPesRecording); 00047 toFileName = new cFileName(ToFileName, true, true, isPesRecording); 00048 fromIndex = new cIndexFile(FromFileName, false, isPesRecording); 00049 toIndex = new cIndexFile(ToFileName, true, isPesRecording); 00050 toMarks.Load(ToFileName, Recording.FramesPerSecond(), isPesRecording); // doesn't actually load marks, just sets the file name 00051 maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize); 00052 if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES)) 00053 maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES); 00054 Start(); 00055 } 00056 else 00057 esyslog("no editing marks found for %s", FromFileName); 00058 } 00059 00060 cCuttingThread::~cCuttingThread() 00061 { 00062 Cancel(3); 00063 delete fromFileName; 00064 delete toFileName; 00065 delete fromIndex; 00066 delete toIndex; 00067 } 00068 00069 void cCuttingThread::Action(void) 00070 { 00071 cMark *Mark = fromMarks.First(); 00072 if (Mark) { 00073 SetPriority(19); 00074 SetIOPriority(7); 00075 fromFile = fromFileName->Open(); 00076 toFile = toFileName->Open(); 00077 if (!fromFile || !toFile) 00078 return; 00079 fromFile->SetReadAhead(MEGABYTE(20)); 00080 int Index = Mark->Position(); 00081 Mark = fromMarks.Next(Mark); 00082 off_t FileSize = 0; 00083 int CurrentFileNumber = 0; 00084 bool SkipThisSourceFile = false; 00085 int LastIFrame = 0; 00086 toMarks.Add(0); 00087 toMarks.Save(); 00088 uchar buffer[MAXFRAMESIZE]; 00089 bool LastMark = false; 00090 bool cutIn = true; 00091 while (Running()) { 00092 uint16_t FileNumber; 00093 off_t FileOffset; 00094 int Length; 00095 bool Independent; 00096 00097 // Make sure there is enough disk space: 00098 00099 AssertFreeDiskSpace(-1); 00100 00101 // Read one frame: 00102 00103 if (!fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) { 00104 // Error, unless we're past last cut-in and there's no cut-out 00105 if (Mark || LastMark) 00106 error = "index"; 00107 break; 00108 } 00109 00110 if (FileNumber != CurrentFileNumber) { 00111 fromFile = fromFileName->SetOffset(FileNumber, FileOffset); 00112 if (fromFile) 00113 fromFile->SetReadAhead(MEGABYTE(20)); 00114 CurrentFileNumber = FileNumber; 00115 if (SkipThisSourceFile) { 00116 // At end of fast forward: Always skip to next file 00117 toFile = toFileName->NextFile(); 00118 if (!toFile) { 00119 error = "toFile 4"; 00120 break; 00121 } 00122 FileSize = 0; 00123 SkipThisSourceFile = false; 00124 } 00125 00126 00127 if (Setup.HardLinkCutter && FileOffset == 0) { 00128 // We are at the beginning of a new source file. 00129 // Do we need to copy the whole file? 00130 00131 // if !Mark && LastMark, then we're past the last cut-out and continue to next I-frame 00132 // if !Mark && !LastMark, then there's just a cut-in, but no cut-out 00133 // if Mark, then we're between a cut-in and a cut-out 00134 00135 uint16_t MarkFileNumber; 00136 off_t MarkFileOffset; 00137 // Get file number of next cut mark 00138 if (!Mark && !LastMark 00139 || Mark 00140 && fromIndex->Get(Mark->Position(), &MarkFileNumber, &MarkFileOffset) 00141 && (MarkFileNumber != CurrentFileNumber)) { 00142 // The current source file will be copied completely. 00143 // Start new output file unless we did that already 00144 if (FileSize != 0) { 00145 toFile = toFileName->NextFile(); 00146 if (!toFile) { 00147 error = "toFile 3"; 00148 break; 00149 } 00150 FileSize = 0; 00151 } 00152 00153 // Safety check that file has zero size 00154 struct stat buf; 00155 if (stat(toFileName->Name(), &buf) == 0) { 00156 if (buf.st_size != 0) { 00157 esyslog("cCuttingThread: File %s exists and has nonzero size", toFileName->Name()); 00158 error = "nonzero file exist"; 00159 break; 00160 } 00161 } 00162 else if (errno != ENOENT) { 00163 esyslog("cCuttingThread: stat failed on %s", toFileName->Name()); 00164 error = "stat"; 00165 break; 00166 } 00167 00168 // Clean the existing 0-byte file 00169 toFileName->Close(); 00170 cString ActualToFileName(ReadLink(toFileName->Name()), true); 00171 unlink(ActualToFileName); 00172 unlink(toFileName->Name()); 00173 00174 // Try to create a hard link 00175 if (HardLinkVideoFile(fromFileName->Name(), toFileName->Name())) { 00176 // Success. Skip all data transfer for this file 00177 SkipThisSourceFile = true; 00178 cutIn = false; 00179 toFile = NULL; // was deleted by toFileName->Close() 00180 } 00181 else { 00182 // Fallback: Re-open the file if necessary 00183 toFile = toFileName->Open(); 00184 } 00185 } 00186 } 00187 } 00188 00189 if (!SkipThisSourceFile) { 00190 if (fromFile) { 00191 int len = ReadFrame(fromFile, buffer, Length, sizeof(buffer)); 00192 if (len < 0) { 00193 error = "ReadFrame"; 00194 break; 00195 } 00196 if (len != Length) { 00197 CurrentFileNumber = 0; // this re-syncs in case the frame was larger than the buffer 00198 Length = len; 00199 } 00200 } 00201 else { 00202 error = "fromFile"; 00203 break; 00204 } 00205 } 00206 // Write one frame: 00207 00208 if (Independent) { // every file shall start with an independent frame 00209 if (LastMark) // edited version shall end before next I-frame 00210 break; 00211 if (!SkipThisSourceFile && FileSize > toFileName->MaxFileSize()) { 00212 toFile = toFileName->NextFile(); 00213 if (!toFile) { 00214 error = "toFile 1"; 00215 break; 00216 } 00217 FileSize = 0; 00218 } 00219 LastIFrame = 0; 00220 00221 if (!SkipThisSourceFile && cutIn) { 00222 if (isPesRecording) 00223 cRemux::SetBrokenLink(buffer, Length); 00224 else 00225 TsSetTeiOnBrokenPackets(buffer, Length); 00226 cutIn = false; 00227 } 00228 } 00229 if (!SkipThisSourceFile && toFile->Write(buffer, Length) < 0) { 00230 error = "safe_write"; 00231 break; 00232 } 00233 if (!toIndex->Write(Independent, toFileName->Number(), FileSize)) { 00234 error = "toIndex"; 00235 break; 00236 } 00237 FileSize += Length; 00238 if (!LastIFrame) 00239 LastIFrame = toIndex->Last(); 00240 00241 // Check editing marks: 00242 00243 if (Mark && Index >= Mark->Position()) { 00244 Mark = fromMarks.Next(Mark); 00245 toMarks.Add(LastIFrame); 00246 if (Mark) 00247 toMarks.Add(toIndex->Last() + 1); 00248 toMarks.Save(); 00249 if (Mark) { 00250 Index = Mark->Position(); 00251 Mark = fromMarks.Next(Mark); 00252 CurrentFileNumber = 0; // triggers SetOffset before reading next frame 00253 cutIn = true; 00254 if (Setup.SplitEditedFiles) { 00255 toFile = toFileName->NextFile(); 00256 if (!toFile) { 00257 error = "toFile 2"; 00258 break; 00259 } 00260 FileSize = 0; 00261 } 00262 } 00263 else 00264 LastMark = true; // After last cut-out: Write on until next I-frame, then exit 00265 } 00266 } 00267 Recordings.TouchUpdate(); 00268 } 00269 else 00270 esyslog("no editing marks found!"); 00271 } 00272 00273 // --- cCutter --------------------------------------------------------------- 00274 00275 cMutex cCutter::mutex; 00276 cString cCutter::originalVersionName; 00277 cString cCutter::editedVersionName; 00278 cCuttingThread *cCutter::cuttingThread = NULL; 00279 bool cCutter::error = false; 00280 bool cCutter::ended = false; 00281 00282 bool cCutter::Start(const char *FileName, const char *TargetFileName, bool Overwrite) 00283 { 00284 cMutexLock MutexLock(&mutex); 00285 if (!cuttingThread) { 00286 error = false; 00287 ended = false; 00288 originalVersionName = FileName; 00289 cRecording Recording(FileName); 00290 00291 cMarks FromMarks; 00292 FromMarks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()); 00293 if (cMark *First = FromMarks.First()) 00294 Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60); 00295 00296 cString evn = (TargetFileName && *TargetFileName) ? Recording.UpdateFileName(TargetFileName) : Recording.PrefixFileName('%'); 00297 if (!Overwrite && *evn && (access(*evn, F_OK) == 0) && !Interface->Confirm(tr("File already exists - overwrite?"))) { 00298 do { 00299 evn = PrefixVideoFileName(*evn, '%'); 00300 } while (*evn && (access(*evn, F_OK) == 0)); 00301 } 00302 if (*evn && RemoveVideoFile(*evn) && MakeDirs(*evn, true)) { 00303 // XXX this can be removed once RenameVideoFile() follows symlinks (see videodir.c) 00304 // remove a possible deleted recording with the same name to avoid symlink mixups: 00305 char *s = strdup(*evn); 00306 char *e = strrchr(s, '.'); 00307 if (e) { 00308 if (strcmp(e, ".rec") == 0) { 00309 strcpy(e, ".del"); 00310 RemoveVideoFile(s); 00311 } 00312 } 00313 free(s); 00314 // XXX 00315 editedVersionName = evn; 00316 Recording.WriteInfo(); 00317 Recordings.AddByName(editedVersionName, false); 00318 cuttingThread = new cCuttingThread(FileName, editedVersionName); 00319 return true; 00320 } 00321 } 00322 return false; 00323 } 00324 00325 void cCutter::Stop(void) 00326 { 00327 cMutexLock MutexLock(&mutex); 00328 bool Interrupted = cuttingThread && cuttingThread->Active(); 00329 const char *Error = cuttingThread ? cuttingThread->Error() : NULL; 00330 delete cuttingThread; 00331 cuttingThread = NULL; 00332 if ((Interrupted || Error) && *editedVersionName) { 00333 if (Interrupted) 00334 isyslog("editing process has been interrupted"); 00335 if (Error) 00336 esyslog("ERROR: '%s' during editing process", Error); 00337 if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0) 00338 cControl::Shutdown(); 00339 RemoveVideoFile(editedVersionName); 00340 Recordings.DelByName(editedVersionName); 00341 } 00342 } 00343 00344 bool cCutter::Active(const char *FileName) 00345 { 00346 cMutexLock MutexLock(&mutex); 00347 if (cuttingThread) { 00348 if (cuttingThread->Active()) 00349 return !FileName || strcmp(FileName, originalVersionName) == 0 || strcmp(FileName, editedVersionName) == 0; 00350 error = cuttingThread->Error(); 00351 Stop(); 00352 if (!error) 00353 cRecordingUserCommand::InvokeCommand(RUC_EDITEDRECORDING, editedVersionName); 00354 originalVersionName = NULL; 00355 editedVersionName = NULL; 00356 ended = true; 00357 } 00358 return false; 00359 } 00360 00361 bool cCutter::Error(void) 00362 { 00363 cMutexLock MutexLock(&mutex); 00364 bool result = error; 00365 error = false; 00366 return result; 00367 } 00368 00369 bool cCutter::Ended(void) 00370 { 00371 cMutexLock MutexLock(&mutex); 00372 bool result = ended; 00373 ended = false; 00374 return result; 00375 } 00376 00377 #define CUTTINGCHECKINTERVAL 500 // ms between checks for the active cutting process 00378 00379 bool CutRecording(const char *FileName) 00380 { 00381 if (DirectoryOk(FileName)) { 00382 cRecording Recording(FileName); 00383 if (Recording.Name()) { 00384 cMarks Marks; 00385 if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()) && Marks.Count()) { 00386 if (cCutter::Start(FileName)) { 00387 while (cCutter::Active()) 00388 cCondWait::SleepMs(CUTTINGCHECKINTERVAL); 00389 return true; 00390 } 00391 else 00392 fprintf(stderr, "can't start editing process\n"); 00393 } 00394 else 00395 fprintf(stderr, "'%s' has no editing marks\n", FileName); 00396 } 00397 else 00398 fprintf(stderr, "'%s' is not a recording\n", FileName); 00399 } 00400 else 00401 fprintf(stderr, "'%s' is not a directory\n", FileName); 00402 return false; 00403 }