Group :: Video
RPM: vdr
Main Changelog Spec Patches Sources Download Gear Bugs and FR Repocop
Patch: vdr-1.5.18-hlcutter-0.2.0.diff
Download
Download
Index: menu.c
===================================================================
--- menu.c (.../base) (Revision 1008)
+++ menu.c (.../hlcutter) (Revision 1008)
@@ -2723,2 +2723,4 @@
Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZE));
+ Add(new cMenuEditIntItem( tr("Setup.Recording$Max. recording size (GB)"), &data.MaxRecordingSize, MINRECORDINGSIZE, MAXRECORDINGSIZE));
Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles));
+ Add(new cMenuEditBoolItem(tr("Setup.Recording$Hard Link Cutter"), &data.HardLinkCutter));
Index: cutter.c
===================================================================
--- cutter.c (.../base) (Revision 1008)
+++ cutter.c (.../hlcutter) (Revision 1008)
@@ -71,6 +71,7 @@
Mark = fromMarks.Next(Mark);
int FileSize = 0;
int CurrentFileNumber = 0;
+ bool SkipThisSourceFile = false;
int LastIFrame = 0;
toMarks.Add(0);
toMarks.Save();
@@ -88,12 +89,92 @@
// 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 (!fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) {
+ // Error, unless we're past last cut-in and there's no cut-out
+ if (Mark || LastMark)
+ error = "index";
+ break;
+ }
+
+ if (FileNumber != CurrentFileNumber) {
+ fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
+ fromFile->SetReadAhead(MEGABYTE(20));
+ CurrentFileNumber = FileNumber;
+ if (SkipThisSourceFile) {
+ // At end of fast forward: Always skip to next file
+ toFile = toFileName->NextFile();
+ if (!toFile) {
+ error = "toFile 4";
+ break;
+ }
+ FileSize = 0;
+ SkipThisSourceFile = false;
+ }
+
+
+ if (Setup.HardLinkCutter && FileOffset == 0) {
+ // We are at the beginning of a new source file.
+ // Do we need to copy the whole file?
+
+ // if !Mark && LastMark, then we're past the last cut-out and continue to next I-frame
+ // if !Mark && !LastMark, then there's just a cut-in, but no cut-out
+ // if Mark, then we're between a cut-in and a cut-out
+
+ uchar MarkFileNumber;
+ int MarkFileOffset;
+ // Get file number of next cut mark
+ if (!Mark && !LastMark
+ || Mark
+ && fromIndex->Get(Mark->position, &MarkFileNumber, &MarkFileOffset)
+ && (MarkFileNumber != CurrentFileNumber)) {
+ // The current source file will be copied completely.
+ // Start new output file unless we did that already
+ if (FileSize != 0) {
+ toFile = toFileName->NextFile();
+ if (!toFile) {
+ error = "toFile 3";
+ break;
+ }
+ FileSize = 0;
+ }
+
+ // Safety check that file has zero size
+ struct stat buf;
+ if (stat(toFileName->Name(), &buf) == 0) {
+ if (buf.st_size != 0) {
+ esyslog("cCuttingThread: File %s exists and has nonzero size", toFileName->Name());
+ error = "nonzero file exist";
+ break;
+ }
+ }
+ else if (errno != ENOENT) {
+ esyslog("cCuttingThread: stat failed on %s", toFileName->Name());
+ error = "stat";
+ break;
+ }
+
+ // Clean the existing 0-byte file
+ toFileName->Close();
+ cString ActualToFileName(ReadLink(toFileName->Name()), true);
+ unlink(ActualToFileName);
+ unlink(toFileName->Name());
+
+ // Try to create a hard link
+ if (HardLinkVideoFile(fromFileName->Name(), toFileName->Name())) {
+ // Success. Skip all data transfer for this file
+ SkipThisSourceFile = true;
+ cutIn = false;
+ toFile = NULL; // was deleted by toFileName->Close()
+ }
+ else {
+ // Fallback: Re-open the file if necessary
+ toFile = toFileName->Open();
+ }
+ }
+ }
+ }
+
+ if (!SkipThisSourceFile) {
if (fromFile) {
int len = ReadFrame(fromFile, buffer, Length, sizeof(buffer));
if (len < 0) {
@@ -110,19 +191,12 @@
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 (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)) {
+ if (!SkipThisSourceFile && FileSize > toFileName->MaxFileSize()) {
toFile = toFileName->NextFile();
if (!toFile) {
error = "toFile 1";
@@ -132,12 +206,12 @@
}
LastIFrame = 0;
- if (cutIn) {
+ if (!SkipThisSourceFile && cutIn) {
cRemux::SetBrokenLink(buffer, Length);
cutIn = false;
}
}
- if (toFile->Write(buffer, Length) < 0) {
+ if (!SkipThisSourceFile && toFile->Write(buffer, Length) < 0) {
error = "safe_write";
break;
}
@@ -172,7 +246,7 @@
}
}
else
- LastMark = true;
+ LastMark = true; // After last cut-out: Write on until next I-frame, then exit
}
}
Recordings.TouchUpdate();
Index: README-HLCUTTER
===================================================================
--- README-HLCUTTER (.../base) (Revision 0)
+++ README-HLCUTTER (.../hlcutter) (Revision 1008)
@@ -0,0 +1,117 @@
+
+ VDR-HLCUTTER README
+
+
+Written by: Udo Richter
+Available at: http://www.udo-richter.de/vdr/patches.html#hlcutter
+ http://www.udo-richter.de/vdr/patches.en.html#hlcutter
+Contact: udo_richter@gmx.de
+
+
+
+About
+-----
+
+The hard link cutter patch changes the recording editing algorithms of VDR to
+use filesystem hard links to 'copy' recording files whenever possible to speed
+up editing recordings noticeably.
+
+The patch has matured to be quite stable, at least I'm using it without issues.
+Nevertheless the patch is still in development and should be used with caution.
+The patch is EXPERIMENTAL for multiple /videoxx folders. The safety checks
+should prevent data loss, but you should always carefully check the results.
+
+While editing a recording, the patch searches for any 00x.vdr files that dont
+contain editing marks and would normally be copied 1:1 unmodified to the edited
+recording. In this case the current target 00x.vdr file will be aborted, and
+the cutter process attempts to duplicate the source file as a hard link, so
+that both files share the same disk space. If this succeeds, the editing
+process fast-forwards through the duplicated file and continues normally
+beginning with the next source file. If hard linking fails, the cutter process
+continues with plain old copying. (but does not take up the aborted last file.)
+
+After editing, the un-edited recording can be deleted as usual, the hard linked
+copies will continue to exist as the only remaining copy.
+
+To be effective, the default 'Max. video file size (MB)' should be lowered.
+The patch lowers the smallest possible file size to 1mb. Since VDR only
+supports up to 255 files, this would limit the recording size to 255Mb or
+10 minutes, in other words: This setting is insane!
+
+To make sure that the 255 file limit will not be reached, the patch also
+introduces "Max. recording size (GB)" with a default of 100Gb (66 hours), and
+increases the file size to 2000Mb early enough, so that 100Gb-recordings will
+fit into the 255 files.
+
+Picking the right parameters can be tricky. The smaller the file size, the
+faster the editing process works. However, with a small file size, long
+recordings will fall back to 2000Mb files soon, that are slow on editing again.
+
+Here are some examples:
+
+Max file size: 100Gb 100Gb 100Gb 100Gb 100Gb 100Gb 100Gb
+Max recording size: 1Mb 10Mb 20Mb 30Mb 40Mb 50Mb 100Mb
+
+Small files: 1-203 1-204 1-205 1-206 1-207 1-209 1-214
+ GBytes: 0.2 2.0 4.0 6.0 8.1 10.2 20.9
+ Hours: 0.13 1.3 2.65 4 5.4 6.8 13.9
+
+Big (2000mb) files: 204-255 204-255 206-255 207-255 208-255 210-255 215-255
+ GBytes: 101.5 99.6 97.7 95.7 93.8 89.8 80.1
+ Hours: 67 66 65 63 62 60 53
+
+A recording limit of 100Gb keeps plenty of reserve without blocking too much
+file numbers. And with a file size of 30-40Mb, recordings of 4-5 hours fit into
+small files completely. (depends on bit rate of course)
+
+
+
+The patch must be enabled in Setup-> Recordings-> Hard Link Cutter. When
+disabled, the cutter process behaves identical to VDR's default cutter.
+
+There's a //#define HARDLINK_TEST_ONLY in the videodir.c file that enables a
+test-mode that hard-links 00x.vdr_ files only, and continues the classic
+editing. The resulting 00x.vdr and 00x.vdr_ files should be identical. If you
+delete the un-edited recording, dont forget to delete the *.vdr_ files too,
+they will now eat real disk space.
+
+Note: 'du' displays the disk space of hard links only on first appearance, and
+usually you will see a noticeably smaller size on the edited recording.
+
+
+History
+-------
+
+Version 0.2.0
+ New: Support for multiple /videoXX recording folders, using advanced searching
+ for matching file systems where a hard link can be created.
+ Also supports deep mounted file systems.
+ Fix: Do not fail if last mark is a cut-in. (Again.)
+
+Version 0.1.4
+ New: Dynamic increase of file size before running out of xxx.vdr files
+ Fix: Last edit mark is not a cut-out
+ Fix: Write error if link-copied file is smaller than allowed file size
+ Fix: Broken index/marks if cut-in is at the start of a new file
+ Fix: Clear dangeling pointer to free'd cUnbufferedFile,
+ thx to Matthias Schwarzott
+
+Version 0.1.0
+ Initial release
+
+
+
+
+Future plans
+------------
+
+Since original and edited copy share disk space, free space is wrong if one of
+them is moved to *.del. Free space should only count files with hard link
+count = 1. This still goes wrong if all copies get deleted.
+
+
+For more safety, the hard-linked files may be made read-only, as modifications
+to one copy will affect the other copy too. (except deleting, of course)
+
+
+SetBrokenLink may get lost on rare cases, this needs some more thoughts.
Eigenschaftsänderungen: README-HLCUTTER
___________________________________________________________________
Name: svn:executable
+ *
Index: recorder.c
===================================================================
--- recorder.c (.../base) (Revision 1008)
+++ recorder.c (.../hlcutter) (Revision 1008)
@@ -85,7 +85,7 @@
bool cFileWriter::NextFile(void)
{
if (recordFile && pictureType == I_FRAME) { // every file shall start with an I_FRAME
- if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) {
+ if (fileSize > fileName->MaxFileSize() || RunningLowOnDiskSpace()) {
recordFile = fileName->NextFile();
fileSize = 0;
}
Index: config.c
===================================================================
--- config.c (.../base) (Revision 1008)
+++ config.c (.../hlcutter) (Revision 1008)
@@ -277,7 +277,9 @@
FontSmlSize = 18;
FontFixSize = 20;
MaxVideoFileSize = MAXVIDEOFILESIZE;
+ MaxRecordingSize = DEFAULTRECORDINGSIZE;
SplitEditedFiles = 0;
+ HardLinkCutter = 0;
MinEventTimeout = 30;
MinUserInactivity = 300;
NextWakeupTime = 0;
@@ -453,7 +455,9 @@
else if (!strcasecmp(Name, "FontSmlSize")) FontSmlSize = atoi(Value);
else if (!strcasecmp(Name, "FontFixSize")) FontFixSize = atoi(Value);
else if (!strcasecmp(Name, "MaxVideoFileSize")) MaxVideoFileSize = atoi(Value);
+ else if (!strcasecmp(Name, "MaxRecordingSize")) MaxRecordingSize = atoi(Value);
else if (!strcasecmp(Name, "SplitEditedFiles")) SplitEditedFiles = atoi(Value);
+ else if (!strcasecmp(Name, "HardLinkCutter")) HardLinkCutter = atoi(Value);
else if (!strcasecmp(Name, "MinEventTimeout")) MinEventTimeout = atoi(Value);
else if (!strcasecmp(Name, "MinUserInactivity")) MinUserInactivity = atoi(Value);
else if (!strcasecmp(Name, "NextWakeupTime")) NextWakeupTime = atoi(Value);
@@ -536,7 +540,9 @@
Store("FontSmlSize", FontSmlSize);
Store("FontFixSize", FontFixSize);
Store("MaxVideoFileSize", MaxVideoFileSize);
+ Store("MaxRecordingSize", MaxRecordingSize);
Store("SplitEditedFiles", SplitEditedFiles);
+ Store("HardLinkCutter", HardLinkCutter);
Store("MinEventTimeout", MinEventTimeout);
Store("MinUserInactivity", MinUserInactivity);
Store("NextWakeupTime", NextWakeupTime);
Index: config.h
===================================================================
--- config.h (.../base) (Revision 1008)
+++ config.h (.../hlcutter) (Revision 1008)
@@ -255,7 +255,9 @@
int FontSmlSize;
int FontFixSize;
int MaxVideoFileSize;
+ int MaxRecordingSize;
int SplitEditedFiles;
+ int HardLinkCutter;
int MinEventTimeout, MinUserInactivity;
time_t NextWakeupTime;
int MultiSpeedMode;
Index: recording.c
===================================================================
--- recording.c (.../base) (Revision 1008)
+++ recording.c (.../hlcutter) (Revision 1008)
@@ -1514,6 +1514,16 @@
return NULL;
}
+int cFileName::MaxFileSize() {
+ const int smallFiles = (255 * MAXVIDEOFILESIZE - 1024 * Setup.MaxRecordingSize)
+ / max(MAXVIDEOFILESIZE - Setup.MaxVideoFileSize, 1);
+
+ if (fileNumber <= smallFiles)
+ return MEGABYTE(Setup.MaxVideoFileSize);
+
+ return MEGABYTE(MAXVIDEOFILESIZE);
+}
+
cUnbufferedFile *cFileName::NextFile(void)
{
return SetOffset(fileNumber + 1);
Index: recording.h
===================================================================
--- recording.h (.../base) (Revision 1008)
+++ recording.h (.../hlcutter) (Revision 1008)
@@ -195,8 +195,16 @@
// may be slightly higher because we stop recording only before the next
// 'I' frame, to have a complete Group Of Pictures):
#define MAXVIDEOFILESIZE 2000 // MB
-#define MINVIDEOFILESIZE 100 // MB
+#define MINVIDEOFILESIZE 1 // MB
+#define MINRECORDINGSIZE 25 // GB
+#define MAXRECORDINGSIZE 500 // GB
+#define DEFAULTRECORDINGSIZE 100 // GB
+// Dynamic recording size:
+// Keep recording file size at Setup.MaxVideoFileSize for as long as possible,
+// but switch to MAXVIDEOFILESIZE early enough, so that Setup.MaxRecordingSize
+// will be reached, before recording to file 255.vdr
+
class cIndexFile {
private:
struct tIndex { int offset; uchar type; uchar number; short reserved; };
@@ -236,6 +244,8 @@
cUnbufferedFile *Open(void);
void Close(void);
cUnbufferedFile *SetOffset(int Number, int Offset = 0);
+ int MaxFileSize();
+ // Dynamic file size for this file
cUnbufferedFile *NextFile(void);
};
Index: videodir.c
===================================================================
--- videodir.c (.../base) (Revision 1008)
+++ videodir.c (.../hlcutter) (Revision 1008)
@@ -19,6 +19,9 @@
#include "recording.h"
#include "tools.h"
+
+//#define HARDLINK_TEST_ONLY
+
const char *VideoDirectory = VIDEODIR;
class cVideoDirectory {
@@ -168,6 +171,120 @@
return RemoveFileOrDir(FileName, true);
}
+static bool StatNearestDir(const char *FileName, struct stat *Stat)
+{
+ cString Name(FileName);
+ char *p;
+ while ((p = strrchr((const char*)Name + 1, '/')) != NULL) {
+ *p = 0; // truncate at last '/'
+ if (stat(Name, Stat) == 0) {
+ isyslog("StatNearestDir: Stating %s", (const char*)Name);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool HardLinkVideoFile(const char *OldName, const char *NewName)
+{
+ // Incoming name must be in base video directory:
+ if (strstr(OldName, VideoDirectory) != OldName) {
+ esyslog("ERROR: %s not in %s", OldName, VideoDirectory);
+ return false;
+ }
+ if (strstr(NewName, VideoDirectory) != NewName) {
+ esyslog("ERROR: %s not in %s", NewName, VideoDirectory);
+ return false;
+ }
+
+ const char *ActualNewName = NewName;
+ cString ActualOldName(ReadLink(OldName), true);
+
+ // Some safety checks:
+ struct stat StatOldName;
+ if (lstat(ActualOldName, &StatOldName) == 0) {
+ if (S_ISLNK(StatOldName.st_mode)) {
+ esyslog("HardLinkVideoFile: Failed to resolve symbolic link %s", (const char*)ActualOldName);
+ return false;
+ }
+ }
+ else {
+ esyslog("HardLinkVideoFile: lstat failed on %s", (const char*)ActualOldName);
+ return false;
+ }
+ isyslog("HardLinkVideoFile: %s is on %i", (const char*)ActualOldName, (int)StatOldName.st_dev);
+
+ // Find the video directory where ActualOldName is located
+
+ cVideoDirectory Dir;
+ struct stat StatDir;
+ if (!StatNearestDir(NewName, &StatDir)) {
+ esyslog("HardLinkVideoFile: stat failed on %s", NewName);
+ return false;
+ }
+
+ isyslog("HardLinkVideoFile: %s is on %i", NewName, (int)StatDir.st_dev);
+ if (StatDir.st_dev != StatOldName.st_dev) {
+ // Not yet found.
+
+ if (!Dir.IsDistributed()) {
+ esyslog("HardLinkVideoFile: No matching video folder to hard link %s", (const char*)ActualOldName);
+ return false;
+ }
+
+ // Search in video01 and upwards
+ bool found = false;
+ while (Dir.Next()) {
+ Dir.Store();
+ const char *TmpNewName = Dir.Adjust(NewName);
+ if (StatNearestDir(TmpNewName, &StatDir) && StatDir.st_dev == StatOldName.st_dev) {
+ isyslog("HardLinkVideoFile: %s is on %i (match)", TmpNewName, (int)StatDir.st_dev);
+ ActualNewName = TmpNewName;
+ found = true;
+ break;
+ }
+ isyslog("HardLinkVideoFile: %s is on %i", TmpNewName, (int)StatDir.st_dev);
+ }
+ if (ActualNewName == NewName) {
+ esyslog("HardLinkVideoFile: No matching video folder to hard link %s", (const char*)ActualOldName);
+ return false;
+ }
+
+ // Looking good, we have a match. Create necessary folders.
+ if (!MakeDirs(ActualNewName, false))
+ return false;
+ // There's no guarantee that the directory of ActualNewName
+ // is on the same device as the dir that StatNearestDir found.
+ // But worst case is that the link fails.
+ }
+
+#ifdef HARDLINK_TEST_ONLY
+ // Do the hard link to *.vdr_ for testing only
+ char *name = NULL;
+ asprintf(&name, "%s_",ActualNewName);
+ link(ActualOldName, name);
+ free(name);
+ return false;
+#endif // HARDLINK_TEST_ONLY
+
+ // Try creating the hard link
+ if (link(ActualOldName, ActualNewName) != 0) {
+ // Failed to hard link. Maybe not allowed on file system.
+ LOG_ERROR_STR(ActualNewName);
+ isyslog("HardLinkVideoFile: failed to hard link from %s to %s", (const char*)ActualOldName, ActualNewName);
+ return false;
+ }
+
+ if (ActualNewName != NewName) {
+ // video01 and up. Do the remaining symlink
+ if (symlink(ActualNewName, NewName) < 0) {
+ LOG_ERROR_STR(NewName);
+ return false;
+ }
+ }
+ return true;
+}
+
bool VideoFileSpaceAvailable(int SizeMB)
{
cVideoDirectory Dir;
Index: videodir.h
===================================================================
--- videodir.h (.../base) (Revision 1008)
+++ videodir.h (.../hlcutter) (Revision 1008)
@@ -19,6 +19,7 @@
int CloseVideoFile(cUnbufferedFile *File);
bool RenameVideoFile(const char *OldName, const char *NewName);
bool RemoveVideoFile(const char *FileName);
+bool HardLinkVideoFile(const char *OldName, const char *NewName);
bool VideoFileSpaceAvailable(int SizeMB);
int VideoDiskSpace(int *FreeMB = NULL, int *UsedMB = NULL); // returns the used disk space in percent
cString PrefixVideoFileName(const char *FileName, char Prefix);
Eigenschaftsänderungen: .
___________________________________________________________________
Name: svnmerge-integrated
+ /vdr/trunk:1-1003