mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- Removed leftover 'needsBufferReserve' variable from cTransfer (thanks to Marco Schlüßler). - Fixed setting "No title" for broken event data (reported by Ronny Kornexl). - Fixed channel up/down switching on single card systems (reported by Stefan Huelswitt). - Fixed handling "pending" timers that blocked others that actually could record (reported by Thomas Koch). - Speeded up cVideoRepacker (thanks to Reinhard Nissl). - Added an 'Id' parameter to cDevice::PlayAudio() to allow plugins to easier process the audio data (thanks to Marco Schlüßler). - Added Czech language texts (thanks to Vladimír Bárta). Plugin authors may want to add the new entries to their I18N texts and contact the translators to have their texts translated. Note that there are now 21 different OSD languages, so please make sure you have 21 versions for each of your texts. - Updated the Polish OSD texts (thanks to Jaroslaw Swierczynski). - Fixed auto advance in string entry fields when pressing Up/Down in insert mode (reported by Udo Richter). - Fixed handling the "Setup/OSD/Menu button closes" option when set to 'yes' in case a replay is active (thanks to Udo Richter). - Improved cUnbufferedFile; USE_FADVISE is now defined in tools.c by default, so if you don't want to use "fadvise" you need to comment out that line (thanks to Artur Skawina). - Fixed a missing ',' in the Swedish OSD texts (thanks to Arthur Konovalov). - cDevice::Transferring() can now be used to determine whether the (primary) device is currently playing in Transfer Mode (based on a suggestion by Reinhard Nissl). - The 'runvdr' script no longer uses the $VDRUSR environment variable to set the user id under which 'vdr' shall run. Just add the '-u username' option when you call 'runvdr'. - Fixed multiple entries of the same subdirectory in the "Recordings" menu (reported by Christian Jacobsen). - Enabled generating a core dump if VDR is run with a different user id (thanks to Ville Skyttä). - Fixed handling the "Blue" key in the "Schedule" menu for the current channel (thanks to Rolf Ahrenberg). - Renamed the Makefile target 'plugins-clean' to 'clean-plugins' (suggested by Sebastian Frei). - Made all font and image data 'const' (thanks to Darren Salt). - Fixed scrolling with Up/Down in case there are non-selectable items at the beginning or end of the menu (reported by Helmut Auer). - Added cSkin::GetTextAreaWidth() and cSkin::GetTextAreaFont(), so that a plugin that wants to do special text formatting can do so (thanks to Alexander Rieger).
262 lines
7.5 KiB
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;
|
|
}
|