vdr/cutter.c
Klaus Schmidinger 046b506748 Version 1.7.29
Original announce message:
VDR developer version 1.7.29 is now available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.29.tar.bz2

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.28-1.7.29.diff

MD5 checksums:

a3f0ae42ba456aa1865c9ed065a64d80  vdr-1.7.29.tar.bz2
39db6b495210c293726126fbcba3e631  vdr-1.7.28-1.7.29.diff

WARNING:
========

This is a developer version. Even though I use it in my productive
environment. I strongly recommend that you only use it under controlled
conditions and for testing and debugging.

The default skin "LCARS" displays the signal strengths and qualities of
all devices in its main menu. For devices that have an stb0899 frontend chip
(like the TT-budget S2-3200) retrieving this information from the driver is
rather slow, which results in a sluggish response to user input in the main
menu. To speed this up you may want to apply the patches from

   ftp://ftp.tvdr.de/vdr/Developer/Driver-Patches

to the LinuxDVB driver source.

From the HISTORY file:
- Added a missing template specification to the c'tor of cSortedTimers (thanks to Udo
  Richter).
- Fixed the background color of the Transfer Mode indicator bitmap in the LCARS skin.
- The LCARS skin now only displays devices that can actually receive channels, leaving
  out, for instance, pure replay devices (suggested by Reinhard Nissl).
- Now scaling down the Transfer Mode indicator bitmap in the LCARS skin in case it
  doesn't fit with the selected font size (reported by Reinhard Nissl).
- Fixed making LCARS the default skin.
- Adjusted the default values for OSD and font sizes to better fit HDTV.
- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
- Fixed the call to ChannelString() in cSkinLCARSDisplayChannel::SetChannel() (thanks
  to Rolf Ahrenberg).
- Removed DeleteEvent() from the EPG handler interface (turned out not to be useful)
  and replaced it with HandledExternally() (thanks to Jörg Wendel).
- Added SetComponents() to the EPG handler interface (thanks to Dirk Heiser).
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- Changed the button colors in the LCARS skin to better fit with the rest of the theme.
- Removed the gap from the main menu buttons in the LCARS skin.
- Fixed some copy&paste errors in PLUGINS.html (thanks to Winfried Köhler).
- The LCARS skin's main menu now only displays timers that are actually activated.
- Within the "Recordings" menu, pressing the '0' key now toggles sorting between
  "by time" and "by name". The selected sort mode is stored separately for each
  folder (provided you have write access to that folder).
  If a folder is newly created by a repeating timer, the sort mode for that
  folder is initially set to "by time".
- Fixed several spelling errors (thanks to Ville Skyttä).
- Fixed handling recording with more than two bonded devices.
- Fixed the type of MBperMinute in cVideoDiskUsage::HasChanged() (thanks to Andreas
  Mair).
- Setting the "broken link" or "TEI" flags when cutting recordings is now suppressed
  if the editing point merges two seamlessly fitting parts of the same stream (thanks
  to Torsten Lang).
- Fixed displaying messages in the LCARS skin.
- Fixed checking for a visible live programme in case a menu or the channel display
  is currently open.
- Changed some of the colors in the LCARS skin (you may need to delete the file
  lcars-default.theme from your themes directory to see these changes).
- The new setup option "Miscellaneous/Show channel names with source" can be used to
  turn on adding the source character to channel names whenever they are displayed
  (suggested by Ludi Kaleni).
2012-07-15 20:49:35 +02:00

350 lines
11 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 2.13 2012/06/10 14:33:36 kls Exp $
*/
#include "cutter.h"
#include "menu.h"
#include "recording.h"
#include "remux.h"
#include "videodir.h"
// --- cCuttingThread --------------------------------------------------------
class cCuttingThread : public cThread {
private:
const char *error;
bool isPesRecording;
cUnbufferedFile *fromFile, *toFile;
cFileName *fromFileName, *toFileName;
cIndexFile *fromIndex, *toIndex;
cMarks fromMarks, toMarks;
off_t maxVideoFileSize;
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;
cRecording Recording(FromFileName);
isPesRecording = Recording.IsPesRecording();
if (fromMarks.Load(FromFileName, Recording.FramesPerSecond(), isPesRecording) && fromMarks.Count()) {
fromFileName = new cFileName(FromFileName, false, true, isPesRecording);
toFileName = new cFileName(ToFileName, true, true, isPesRecording);
fromIndex = new cIndexFile(FromFileName, false, isPesRecording);
toIndex = new cIndexFile(ToFileName, true, isPesRecording);
toMarks.Load(ToFileName, Recording.FramesPerSecond(), isPesRecording); // doesn't actually load marks, just sets the file name
maxVideoFileSize = MEGABYTE(Setup.MaxVideoFileSize);
if (isPesRecording && maxVideoFileSize > MEGABYTE(MAXVIDEOFILESIZEPES))
maxVideoFileSize = MEGABYTE(MAXVIDEOFILESIZEPES);
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) {
SetPriority(19);
SetIOPriority(7);
fromFile = fromFileName->Open();
toFile = toFileName->Open();
if (!fromFile || !toFile)
return;
fromFile->SetReadAhead(MEGABYTE(20));
int Index = Mark->Position();
Mark = fromMarks.Next(Mark);
off_t FileSize = 0;
int CurrentFileNumber = 0;
int LastIFrame = 0;
toMarks.Add(0);
toMarks.Save();
uchar buffer[MAXFRAMESIZE], buffer2[MAXFRAMESIZE];
int Length2;
bool CheckForSeamlessStream = false;
bool LastMark = false;
bool cutIn = true;
while (Running()) {
uint16_t FileNumber;
off_t FileOffset;
int Length;
bool Independent;
// Make sure there is enough disk space:
AssertFreeDiskSpace(-1);
// Read one frame:
if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) {
if (FileNumber != CurrentFileNumber) {
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
if (fromFile)
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 {
// Error, unless we're past the last cut-in and there's no cut-out
if (Mark || LastMark)
error = "index";
break;
}
// Write one frame:
if (Independent) { // every file shall start with an independent frame
if (LastMark) // edited version shall end before next I-frame
break;
if (FileSize > maxVideoFileSize) {
toFile = toFileName->NextFile();
if (!toFile) {
error = "toFile 1";
break;
}
FileSize = 0;
}
LastIFrame = 0;
// Compare the current frame with the previously stored one, to see if this is a seamlessly merged recording of the same stream:
if (CheckForSeamlessStream) {
if (Length == Length2) {
int diffs = 0;
for (int i = 0; i < Length; i++) {
if (buffer[i] != buffer2[i]) {
if (diffs++ > 10)
break;
}
}
if (diffs < 10) // the continuity counters of the PAT/PMT packets may differ
cutIn = false; // it's apparently a seamless stream, so no need for "broken" handling
}
CheckForSeamlessStream = false;
}
if (cutIn) {
if (isPesRecording)
cRemux::SetBrokenLink(buffer, Length);
else
TsSetTeiOnBrokenPackets(buffer, Length);
cutIn = false;
}
}
if (toFile->Write(buffer, Length) < 0) {
error = "safe_write";
break;
}
if (!toIndex->Write(Independent, 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) {
// Read the next frame, for later comparison with the first frame at this mark:
if (fromIndex->Get(Index, &FileNumber, &FileOffset, &Independent, &Length2)) {
if (FileNumber != CurrentFileNumber)
fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
if (fromFile) {
int len = ReadFrame(fromFile, buffer2, Length2, sizeof(buffer2));
if (len >= 0 && len == Length2)
CheckForSeamlessStream = true;
}
}
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 ---------------------------------------------------------------
cMutex cCutter::mutex;
cString cCutter::originalVersionName;
cString cCutter::editedVersionName;
cCuttingThread *cCutter::cuttingThread = NULL;
bool cCutter::error = false;
bool cCutter::ended = false;
bool cCutter::Start(const char *FileName)
{
cMutexLock MutexLock(&mutex);
if (!cuttingThread) {
error = false;
ended = false;
originalVersionName = FileName;
cRecording Recording(FileName);
cMarks FromMarks;
FromMarks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording());
if (cMark *First = FromMarks.First())
Recording.SetStartTime(Recording.Start() + (int(First->Position() / Recording.FramesPerSecond() + 30) / 60) * 60);
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 = evn;
Recording.WriteInfo();
Recordings.AddByName(editedVersionName, false);
cuttingThread = new cCuttingThread(FileName, editedVersionName);
return true;
}
}
return false;
}
void cCutter::Stop(void)
{
cMutexLock MutexLock(&mutex);
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);
if (cReplayControl::NowReplaying() && strcmp(cReplayControl::NowReplaying(), editedVersionName) == 0)
cControl::Shutdown();
RemoveVideoFile(editedVersionName);
Recordings.DelByName(editedVersionName);
}
}
bool cCutter::Active(const char *FileName)
{
cMutexLock MutexLock(&mutex);
if (cuttingThread) {
if (cuttingThread->Active())
return !FileName || strcmp(FileName, originalVersionName) == 0 || strcmp(FileName, editedVersionName) == 0;
error = cuttingThread->Error();
Stop();
if (!error)
cRecordingUserCommand::InvokeCommand(RUC_EDITEDRECORDING, editedVersionName, originalVersionName);
originalVersionName = NULL;
editedVersionName = NULL;
ended = true;
}
return false;
}
bool cCutter::Error(void)
{
cMutexLock MutexLock(&mutex);
bool result = error;
error = false;
return result;
}
bool cCutter::Ended(void)
{
cMutexLock MutexLock(&mutex);
bool result = ended;
ended = false;
return result;
}
#define CUTTINGCHECKINTERVAL 500 // ms between checks for the active cutting process
bool CutRecording(const char *FileName)
{
if (DirectoryOk(FileName)) {
cRecording Recording(FileName);
if (Recording.Name()) {
cMarks Marks;
if (Marks.Load(FileName, Recording.FramesPerSecond(), Recording.IsPesRecording()) && Marks.Count()) {
if (cCutter::Start(FileName)) {
while (cCutter::Active())
cCondWait::SleepMs(CUTTINGCHECKINTERVAL);
return true;
}
else
fprintf(stderr, "can't start editing process\n");
}
else
fprintf(stderr, "'%s' has no editing marks\n", FileName);
}
else
fprintf(stderr, "'%s' is not a recording\n", FileName);
}
else
fprintf(stderr, "'%s' is not a directory\n", FileName);
return false;
}