vdr/cutter.c

262 lines
7.5 KiB
C

/*
* 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.14 2006/02/05 11:06:47 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;
cUnbufferedFile *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 = NULL;
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 || !toFile)
return;
fromFile->SetReadAhead(MEGABYTE(20));
toFile->SetReadAhead(MEGABYTE(20));
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);
fromFile->SetReadAhead(MEGABYTE(20));
CurrentFileNumber = FileNumber;
}
if (fromFile) {
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) {
error = "toFile 1";
break;
}
FileSize = 0;
}
LastIFrame = 0;
if (cutIn) {
cRemux::SetBrokenLink(buffer, Length);
cutIn = false;
}
}
if (toFile->Write(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) {
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;
}