vdr/cutter.c
Klaus Schmidinger 33f1491b18 Version 1.4.1-3
- Fixed assigning schedules to channels in case there is no initial EPG information
  (thanks to Frank Schmirler).
- Increased the APIVERSION to allow plugins that relied on the cStatus::MsgSetVolume()
  bug to react properly (suggested by Stefan Huelswitt).
- Fixed cDevice::ToggleMute() (thanks to Christoph Haubrich).
- Fixed deleting the last character of a string menu item in insert mode (thanks
  to Udo Richter).
- The /video/.update file is now touched _after_ an editing process is finished
  in order to avoid excessive disk access (thanks to Artur Skawina).
- Fixed handling the running status of EPG events before the currently running one,
  in case they are added after the current event (cont'd from version 1.4.1-2).
- Modified the shutdown mechanism, so that the shutdown script is never given a
  time in the past (reported by Helmut Auer). If a timer is currently recording,
  or a recording would start within the next 30 minutes (default for the "Min.
  event timeout" setup parameter), and the user insists in shutting down now, the
  reboot time given to the shutdown script will correspond to a time that is
  "Min. event timeout" minutes (default is 30) in the future.
- Avoiding shutdown message "Recording in ... minutes, shut down anyway?" with
  a negative number of minutes (reported by Udo Richter).
- Fixed getting the next active timer when shutting down (thanks to Udo Richter).
- Modified the cSVDRP::Close() function to avoid code duplication.
2006-08-06 18:00:00 +02:00

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.16 2006/07/30 10:22:08 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));
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;
}
}
Recordings.TouchUpdate();
}
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, false);
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;
}