mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- Fixed a race condition in cTransfer (thanks to Klaus Heppenheimer for reporting this one). In doing so, the 'active' variables used by the actual derived cThread classes have been replaced by the cThread::Running() function. Plugin authors may want to check their derived cThread classes and replace any 'active' variables the same way as, for instance, done in transfer.c. - Fixed handling EPG data for time shifted events (thanks to Marco Schlüßler). - Increased the default value for 'Min. user inactivity' to 300 minutes (suggested by Helmut Auer). - Now storing the channel id in the info.vdr file even if there is no EPG info available (thanks to Andreas Brachold for reporting that there are empty info.vdr files created in that case). - Added some 'mkdir -p' to the Makefile's 'install' target (thanks to Wayne Keer). - Changed the title of the recording info menu (thanks to Rolf Ahrenberg). - Fixed handling the frame number display if '7' is pressed before the first editing mark, or '9' after the last one (thanks to Thomas Günther). - Now discarding any previous numerical input to switch channels if Up, Down, Channel+, Channel-, Left or Right is pressed (thanks to Wolfgang Rohdewald for reporting a problem with this). - Pressing Ok while entering a channel number now immediately switches to that channel, without waiting for further input. - Avoiding unnecessary OSD draw operations caused by the audio track description display in the ST:TNG skin's channel display (thanks to Oliver Endriss for reporting this). - Removed the VIDEO_STILLPICTURE_WORKS_WITH_VDR_FRAMES stuff from cDvbDevice::StillPicture(), since apparently the VIDEO_STILLPICTURE call works.
259 lines
7.3 KiB
C
259 lines
7.3 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.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;
|
|
}
|