summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FORMATS23
-rw-r--r--HISTORY4
-rw-r--r--INSTALL5
-rw-r--r--MANUAL46
-rw-r--r--README2
-rw-r--r--TODO3
-rw-r--r--config.h59
-rw-r--r--dvbapi.c735
-rw-r--r--dvbapi.h32
-rw-r--r--i18n.c30
-rw-r--r--menu.c186
-rw-r--r--menu.h10
-rw-r--r--osd.h3
-rw-r--r--recording.c107
-rw-r--r--recording.h25
-rw-r--r--thread.c3
-rw-r--r--tools.c8
-rw-r--r--tools.h6
-rw-r--r--vdr.c7
-rw-r--r--videodir.c19
-rw-r--r--videodir.h3
21 files changed, 1018 insertions, 298 deletions
diff --git a/FORMATS b/FORMATS
index 3eee494f..ed52518d 100644
--- a/FORMATS
+++ b/FORMATS
@@ -90,3 +90,26 @@ Video Disk Recorder File Formats
CPU status : /usr/loval/bin/cpustatus 2>&1
Disk space : df -h | grep '/video' | awk '{ print 100 - $5 "% free"; }'
+* marks.vdr
+
+ This file (if present in a recording directory) contains the editing marks
+ defined for this recording.
+
+ Each line contains the definition of one mark in the following format:
+
+ hh:mm:ss.ff comment
+
+ where 'hh:mm:ss.ff' is a frame position within the recording, given as "hours,
+ minutes, seconds and (optional) frame number". 'comment' can be any string
+ and may be used to describe this mark. If present, 'comment' must be separated
+ from the frame position by at least one blank.
+
+ The lines in this file need not necessarily appear in the correct temporal
+ sequence, they will be automatically sorted by time index.
+
+ CURRENT RESTRICTIONS:
+
+ - the 'comment' is currently not used by VDR
+ - marks must have a frame number, and that frame MUST be an I-frame (this
+ means that only marks generated by VDR itself can be used, since they
+ will always be guaranteed to mark I-frames).
diff --git a/HISTORY b/HISTORY
index 9f4f09e7..34991b60 100644
--- a/HISTORY
+++ b/HISTORY
@@ -312,7 +312,7 @@ Video Disk Recorder Revision History
an early state and may still cause some problems, but it appears to work nice
already.
-2000-12-08: Version 0.70
+2000-12-28: Version 0.70
- VDR now requires driver version 0.80 or higher.
- Recordings are now saved in PES mode. Note that you now need to install the
@@ -327,3 +327,5 @@ Video Disk Recorder Revision History
- Fixed handling of channel switching with the "Blue" button in the "What's on
now/next?" menus.
- Fixed saving the MarginStop setup parameter.
+- Fixed missing initialization in cConfig.
+- Implemented "On Disk Editing".
diff --git a/INSTALL b/INSTALL
index 590138b9..6a9bb82d 100644
--- a/INSTALL
+++ b/INSTALL
@@ -37,7 +37,7 @@ following values 'make' call to activate the respective control mode:
REMOTE=RCU control via the "Remote Control Unit" receiver
(see http://www.cadsoft.de/people/kls/vdr/remote.htm)
REMOTE=LIRC control via the "Linux Infrared Remote Control"
- (see http://fsinfo.cs.uni-sb.de/~columbus/lirc)
+ (see http://www.lirc.org)
Adding "DEBUG_OSD=1" will use the PC screen (or current window)
to display texts instead of the DVB card's on-screen display
@@ -166,6 +166,5 @@ into learning mode.
If the program has been compiled with 'REMOTE=LIRC', no 'keys.conf' file
will be used. Instead, the key names as listed in the source file 'config.c'
-must be used when setting up LIRC. See http://www2.arnes.si/~mthale1 for
-more about LIRC.
+must be used when setting up LIRC. See http://www.lirc.org for more about LIRC.
diff --git a/MANUAL b/MANUAL
index 46466bf8..b0e9abe0 100644
--- a/MANUAL
+++ b/MANUAL
@@ -174,6 +174,52 @@ Video Disk Recorder User's Manual
used to easily delete a recording after watching it, or to switch
to a different recording.
+* Editing a Recording
+
+ While in Replay mode, the following keys can be used to manipulate editing
+ marks:
+
+ - 0 Toggles an editing mark. If the mark indicator shows a red triangle,
+ the current mark is deleted. Otherwise a new mark is set at the
+ current position.
+ - 4, 6 Move an editing mark back and forward. You need to first jump to
+ an editing mark for this to work.
+ - 7, 9 Jump back and forward between editing marks. Replay goes into still
+ mode after jumping to a mark.
+ - 8 Positions replay at a point 3 seconds before the current or next
+ "start" mark and starts replay.
+ - 2 Start the actual cutting process.
+
+ Editing marks are represented by black, vertical lines in the progress display.
+ A small black triangle at the top of the mark means that this is a "start"
+ mark, and a triangle at the bottom means that this is an "end" mark.
+ The cutting process will save all video data between "start" and "end" marks
+ into a new file (the original recording remains untouched). The new file will
+ have the same name as the original recording, preceeded with a '%' character
+ (imagine the '%' somehow looking like a pair of scissors ;-). Red bars in the
+ progress display indicate which video sequences will be saved by the cutting
+ process.
+
+ The video sequences to be saved by the cutting process are determined by an
+ "even/odd" algorithm. This means that every odd numbered editing mark (i.e.
+ 1, 3, 5,...) represents a "start" mark, while every even numbered mark (2, 4,
+ 6,...) is an "end" mark. Inserting or toggling a mark on or off automatically
+ adjusts the sequence to the right side of that mark.
+
+ Use the keys described under "Replay Control" to position to, e.g., the
+ beginning and end of commercial breaks and press the '0' key to set the
+ necessary editing marks. After that you may want to use the '7' and '9'
+ keys to jump to each mark and maybe use the '4' and '6' keys to fine tune
+ them. Once all marks are in place, press '2' to start the actual cutting
+ process, which will run as a background process. When replaying the edited
+ version of the recording you can use the '8' key to jump to a point just
+ before the next cut and have a look at the resulting sequence.
+
+ Currently editing marks can only be set at I-frames, which typically is
+ every 12th frame. So editing can be done with a resolution of roughly half
+ a second. A "start" mark marks the first frame of a resulting video
+ sequence, and an "end" mark marks the last frame of that sequence.
+
* Programming the Timer
Use the "Timer" menu to maintain your list of timer controlled recordings.
diff --git a/README b/README
index b1e071ee..0dfc875e 100644
--- a/README
+++ b/README
@@ -27,7 +27,7 @@ driver software (of course, the hardware still has to be bought).
The on screen menu system is simple, but shall provide all the
possibilites necessary to perform timer controlled recording,
-file management and, maybe, even "on disk editing". The menus
+file management and even "on disk editing". The menus
of commercial set-top-boxes usually are a lot more fancy than
the ones in this system, but here we have the full source code
and can modify the menus in whatever way desired.
diff --git a/TODO b/TODO
index f2fb5ead..1ae7d717 100644
--- a/TODO
+++ b/TODO
@@ -3,8 +3,5 @@ TODO list for the Video Disk Recorder project
* Implement simultaneous record/replay with a single DVB card once
the card driver/firmware allows this.
-* Implement "on-disk editing" to allow "cutting out" of certain
- scenes in order to archive them (or, reversely, cut out
- commercial breaks).
* Implement channel scanning.
* Implement remaining commands in SVDRP.
diff --git a/config.h b/config.h
index 655b5a0b..34e20182 100644
--- a/config.h
+++ b/config.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.h 1.35 2000/12/08 13:57:23 kls Exp $
+ * $Id: config.h 1.36 2000/12/25 14:20:09 kls Exp $
*/
#ifndef __CONFIG_H
@@ -14,6 +14,7 @@
#include <stdio.h>
#include <string.h>
#include <time.h>
+#include <unistd.h>
#include "dvbapi.h"
#include "eit.h"
#include "tools.h"
@@ -42,6 +43,15 @@ enum eKeys { // "Up" and "Down" must be the first two keys!
k_Flags = k_Repeat | k_Release,
};
+// This is in preparation for having more key codes:
+#define kMarkToggle k0
+#define kMarkMoveBack k4
+#define kMarkMoveForward k6
+#define kMarkJumpBack k7
+#define kMarkJumpForward k9
+#define kEditCut k2
+#define kEditTest k8
+
#define RAWKEY(k) ((k) & ~k_Flags)
#define ISRAWKEY(k) ((k) != kNone && ((k) & k_Flags) == 0)
#define NORMALKEY(k) ((k) & ~k_Repeat)
@@ -157,33 +167,36 @@ private:
cList<T>::Clear();
}
public:
+ cConfig(void) { fileName = NULL; }
+ virtual ~cConfig() { delete fileName; }
virtual bool Load(const char *FileName)
{
- isyslog(LOG_INFO, "loading %s", FileName);
- bool result = true;
Clear();
fileName = strdup(FileName);
- FILE *f = fopen(fileName, "r");
- if (f) {
- int line = 0;
- char buffer[MaxBuffer];
- while (fgets(buffer, sizeof(buffer), f) > 0) {
- line++;
- T *l = new T;
- if (l->Parse(buffer))
- Add(l);
- else {
- esyslog(LOG_ERR, "error in %s, line %d\n", fileName, line);
- delete l;
- result = false;
- break;
+ bool result = false;
+ if (access(FileName, F_OK) == 0) {
+ isyslog(LOG_INFO, "loading %s", FileName);
+ FILE *f = fopen(fileName, "r");
+ if (f) {
+ int line = 0;
+ char buffer[MaxBuffer];
+ result = true;
+ while (fgets(buffer, sizeof(buffer), f) > 0) {
+ line++;
+ T *l = new T;
+ if (l->Parse(buffer))
+ Add(l);
+ else {
+ esyslog(LOG_ERR, "error in %s, line %d\n", fileName, line);
+ delete l;
+ result = false;
+ break;
+ }
}
- }
- fclose(f);
- }
- else {
- LOG_ERROR_STR(fileName);
- result = false;
+ fclose(f);
+ }
+ else
+ LOG_ERROR_STR(fileName);
}
return result;
}
diff --git a/dvbapi.c b/dvbapi.c
index 29b3459c..a92fe415 100644
--- a/dvbapi.c
+++ b/dvbapi.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: dvbapi.c 1.43 2000/12/10 11:00:00 kls Exp $
+ * $Id: dvbapi.c 1.44 2000/12/25 15:18:02 kls Exp $
*/
#include "dvbapi.h"
@@ -21,6 +21,7 @@ extern "C" {
#include <unistd.h>
#include "config.h"
#include "interface.h"
+#include "recording.h"
#include "tools.h"
#include "videodir.h"
@@ -82,7 +83,7 @@ static void SetPlayMode(int VideoDev, int Mode)
}
}
-const char *IndexToStr(int Index, bool WithFrame)
+const char *IndexToHMSF(int Index, bool WithFrame)
{
static char buffer[16];
int f = (Index % FRAMESPERSEC) + 1;
@@ -94,6 +95,14 @@ const char *IndexToStr(int Index, bool WithFrame)
return buffer;
}
+int HMSFToIndex(const char *HMSF)
+{
+ int h, m, s, f = 0;
+ if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f))
+ return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1;
+ return 0;
+}
+
// --- cResumeFile ------------------------------------------------------------
cResumeFile::cResumeFile(const char *FileName)
@@ -156,13 +165,13 @@ private:
cResumeFile resumeFile;
bool CatchUp(void);
public:
- cIndexFile(const char *FileName, bool Record = false);
+ cIndexFile(const char *FileName, bool Record);
~cIndexFile();
void Write(uchar PictureType, uchar FileNumber, int FileOffset);
- bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL);
- int GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length = NULL);
+ bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL, int *Length = NULL);
+ int GetNextIFrame(int Index, bool Forward, uchar *FileNumber = NULL, int *FileOffset = NULL, int *Length = NULL);
int Get(uchar FileNumber, int FileOffset);
- int Last(void) { return last; }
+ int Last(void) { CatchUp(); return last; }
int GetResume(void) { return resumeFile.Read(); }
bool StoreResume(int Index) { return resumeFile.Save(Index); }
};
@@ -296,7 +305,7 @@ void cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset)
}
}
-bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType)
+bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length)
{
if (index) {
CatchUp();
@@ -305,6 +314,14 @@ bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *Pictu
*FileOffset = index[Index].offset;
if (PictureType)
*PictureType = index[Index].type;
+ if (Length) {
+ int fn = index[Index + 1].number;
+ int fo = index[Index + 1].offset;
+ if (fn == *FileNumber)
+ *Length = fo - *FileOffset;
+ else
+ *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly)
+ }
return true;
}
}
@@ -321,8 +338,14 @@ int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *F
Index += d;
if (Index >= 0 && Index <= last - 100) { // '- 100': need to stay off the end!
if (index[Index].type == I_FRAME) {
- *FileNumber = index[Index].number;
- *FileOffset = index[Index].offset;
+ if (FileNumber)
+ *FileNumber = index[Index].number;
+ else
+ FileNumber = &index[Index].number;
+ if (FileOffset)
+ *FileOffset = index[Index].offset;
+ else
+ FileOffset = &index[Index].offset;
if (Length) {
// all recordings end with a non-I_FRAME, so the following should be safe:
int fn = index[Index + 1].number;
@@ -387,6 +410,11 @@ protected:
int Writeable(void) { return (tail >= head) ? tail - head : size - head; }
int Byte(int Offset);
void Set(int Offset, int Length, int Value);
+protected:
+ int GetStartCode(int Offset) { return (Byte(Offset) == 0x00 && Byte(Offset + 1) == 0x00 && Byte(Offset + 2) == 0x01) ? Byte(Offset + 3) : -1; }
+ int GetPictureType(int Offset) { return (Byte(Offset + 5) >> 3) & 0x07; }
+ int FindStartCode(uchar Code, int Offset = 0);
+ int GetAudioPacketLength(int Offset = 0);
public:
cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit = 0, int AvailLimit = 0);
virtual ~cRingBuffer();
@@ -559,27 +587,53 @@ int cRingBuffer::Write(int Max)
return -1;
}
-// --- cFileBuffer -----------------------------------------------------------
+int cRingBuffer::FindStartCode(uchar Code, int Offset)
+{
+ // Searches for a start code (beginning at Offset) and returns the number
+ // of bytes from Offset to the start code.
-class cFileBuffer : public cRingBuffer {
-protected:
- cIndexFile *index;
- uchar fileNumber;
+ int n = Available() - Offset;
+
+ for (int i = 0; i < n; i++) {
+ int c = GetStartCode(Offset + i);
+ if (c == Code)
+ return i;
+ if (i > 0 && c == SC_BLOCK)
+ break; // found another block start while looking for a different code
+ }
+ return -1;
+}
+
+int cRingBuffer::GetAudioPacketLength(int Offset)
+{
+ // Returns the entire length of the audio packet starting at offset.
+ return (Byte(Offset + 4) << 8) + Byte(Offset + 5) + 6;
+}
+
+// --- cFileName -------------------------------------------------------------
+
+class cFileName {
+private:
+ int file;
+ int fileNumber;
char *fileName, *pFileNumber;
- int GetStartCode(int Offset) { return (Byte(Offset) == 0x00 && Byte(Offset + 1) == 0x00 && Byte(Offset + 2) == 0x01) ? Byte(Offset + 3) : -1; }
- int GetPictureType(int Offset) { return (Byte(Offset + 5) >> 3) & 0x07; }
- int FindStartCode(uchar Code, int Offset = 0);
- int GetAudioPacketLength(int Offset = 0);
+ bool record;
public:
- cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0);
- virtual ~cFileBuffer();
+ cFileName(const char *FileName, bool Record);
+ ~cFileName();
+ const char *Name(void) { return fileName; }
+ int Number(void) { return fileNumber; }
+ int Open(void);
+ void Close(void);
+ int SetOffset(int Number, int Offset = 0);
+ int NextFile(void);
};
-cFileBuffer::cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0)
-:cRingBuffer(InFile, OutFile, Size, FreeLimit, AvailLimit)
+cFileName::cFileName(const char *FileName, bool Record)
{
- index = NULL;
+ file = -1;
fileNumber = 0;
+ record = Record;
// Prepare the file name:
fileName = new char[strlen(FileName) + RECORDFILESUFFIXLEN];
if (!fileName) {
@@ -588,46 +642,86 @@ cFileBuffer::cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool R
}
strcpy(fileName, FileName);
pFileNumber = fileName + strlen(fileName);
- // Create the index file:
- index = new cIndexFile(FileName, Recording);
- if (!index)
- esyslog(LOG_ERR, "ERROR: can't allocate index");
- // let's continue without index, so we'll at least have the recording
+ SetOffset(1);
}
-cFileBuffer::~cFileBuffer()
+cFileName::~cFileName()
{
- delete index;
+ Close();
delete fileName;
}
-int cFileBuffer::FindStartCode(uchar Code, int Offset)
+int cFileName::Open(void)
{
- // Searches for a start code (beginning at Offset) and returns the number
- // of bytes from Offset to the start code.
+ if (file < 0) {
+ if (record) {
+ dsyslog(LOG_INFO, "recording to '%s'", fileName);
+ file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_NONBLOCK);
+ if (file < 0)
+ LOG_ERROR_STR(fileName);
+ }
+ else {
+ if (access(fileName, R_OK) == 0) {
+ dsyslog(LOG_INFO, "playing '%s'", fileName);
+ file = open(fileName, O_RDONLY | O_NONBLOCK);
+ if (file < 0)
+ LOG_ERROR_STR(fileName);
+ }
+ else if (errno != ENOENT)
+ LOG_ERROR_STR(fileName);
+ }
+ }
+ return file;
+}
- int n = Available() - Offset;
+void cFileName::Close(void)
+{
+ if (file >= 0) {
+ if ((record && CloseVideoFile(file) < 0) || (!record && close(file) < 0))
+ LOG_ERROR_STR(fileName);
+ file = -1;
+ }
+}
- for (int i = 0; i < n; i++) {
- int c = GetStartCode(Offset + i);
- if (c == Code)
- return i;
- if (i > 0 && c == SC_BLOCK)
- break; // found another block start while looking for a different code
- }
+int cFileName::SetOffset(int Number, int Offset)
+{
+ if (fileNumber != Number)
+ Close();
+ if (0 < Number && Number <= MAXFILESPERRECORDING) {
+ fileNumber = Number;
+ sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
+ if (record) {
+ if (access(fileName, F_OK) == 0) // file exists, let's try next suffix
+ return SetOffset(Number + 1);
+ else if (errno != ENOENT) { // something serious has happened
+ LOG_ERROR_STR(fileName);
+ return -1;
+ }
+ // found a non existing file suffix
+ }
+ if (Open() >= 0) {
+ if (!record && Offset >= 0 && lseek(file, Offset, SEEK_SET) != Offset) {
+ LOG_ERROR_STR(fileName);
+ return -1;
+ }
+ }
+ return file;
+ }
+ esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
return -1;
}
-int cFileBuffer::GetAudioPacketLength(int Offset)
+int cFileName::NextFile(void)
{
- // Returns the entire length of the audio packet starting at offset.
- return (Byte(Offset + 4) << 8) + Byte(Offset + 5) + 6;
+ return SetOffset(fileNumber + 1);
}
// --- cRecordBuffer ---------------------------------------------------------
-class cRecordBuffer : public cFileBuffer, public cThread {
+class cRecordBuffer : public cRingBuffer, public cThread {
private:
+ cFileName fileName;
+ cIndexFile *index;
uchar pictureType;
int fileSize;
int videoDev;
@@ -647,26 +741,23 @@ public:
};
cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName)
-:cFileBuffer(InFile, &recordFile, FileName, true, VIDEOBUFSIZE, VIDEOBUFSIZE / 10, 0)
+:cRingBuffer(InFile, &recordFile, VIDEOBUFSIZE, VIDEOBUFSIZE / 10, 0)
+,fileName(FileName, true)
{
+ index = NULL;
pictureType = NO_PICTURE;
fileSize = 0;
videoDev = *InFile;
- recordFile = -1;
+ recordFile = fileName.Open();
ok = synced = stop = false;
lastDiskSpaceCheck = time(NULL);
- if (!fileName)
- return;//XXX find a better way???
- // Find the highest existing file suffix:
- for (;;) {
- sprintf(pFileNumber, RECORDFILESUFFIX, ++fileNumber);
- if (access(fileName, F_OK) < 0) {
- if (errno == ENOENT)
- break; // found a non existing file suffix
- LOG_ERROR;
- return;
- }
- }
+ if (!fileName.Name())
+ return;
+ // Create the index file:
+ index = new cIndexFile(FileName, true);
+ if (!index)
+ esyslog(LOG_ERR, "ERROR: can't allocate index");
+ // let's continue without index, so we'll at least have the recording
ok = true;
Start();
}
@@ -675,8 +766,7 @@ cRecordBuffer::~cRecordBuffer()
{
stop = true;
Cancel(3);
- if (recordFile >= 0)
- CloseVideoFile(recordFile);
+ delete index;
}
void cRecordBuffer::Action(void)
@@ -707,7 +797,7 @@ void cRecordBuffer::Action(void)
bool cRecordBuffer::RunningLowOnDiskSpace(void)
{
if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) {
- uint Free = FreeDiskSpaceMB(fileName);
+ uint Free = FreeDiskSpaceMB(fileName.Name());
lastDiskSpaceCheck = time(NULL);
if (Free < MINFREEDISKSPACE) {
dsyslog(LOG_INFO, "low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE);
@@ -762,26 +852,11 @@ bool cRecordBuffer::NextFile(void)
{
if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME
if (fileSize > MAXVIDEOFILESIZE || RunningLowOnDiskSpace()) {
- if (CloseVideoFile(recordFile) < 0)
- LOG_ERROR;
- // don't return 'false', maybe we can still record into the next file
- recordFile = -1;
- fileNumber++;
- if (fileNumber == 0)
- esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
+ recordFile = fileName.NextFile();
fileSize = 0;
}
}
- if (recordFile < 0) {
- sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
- dsyslog(LOG_INFO, "recording to '%s'", fileName);
- recordFile = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_NONBLOCK);
- if (recordFile < 0) {
- LOG_ERROR;
- return false;
- }
- }
- return true;
+ return recordFile >= 0;
}
int cRecordBuffer::Write(int Max)
@@ -798,10 +873,10 @@ int cRecordBuffer::Write(int Max)
}
if (NextFile()) {
if (index && pictureType != NO_PICTURE)
- index->Write(pictureType, fileNumber, fileSize);
+ index->Write(pictureType, fileName.Number(), fileSize);
int written = 0;
for (;;) {
- int w = cFileBuffer::Write(n);
+ int w = cRingBuffer::Write(n);
if (w >= 0) {
fileSize += w;
written += w;
@@ -833,16 +908,18 @@ bool cRecordBuffer::WriteWithTimeout(void)
// --- cReplayBuffer ---------------------------------------------------------
-class cReplayBuffer : public cFileBuffer, public cThread {
+class cReplayBuffer : public cRingBuffer, public cThread {
private:
- enum eReplayCmd { rcNone, rcPause, rcPlay, rcForward, rcBackward };
- enum eReplayMode { rmPlay, rmFastForward, rmFastRewind, rmSlowRewind };
+ enum eReplayCmd { rcNone, rcStill, rcPause, rcPlay, rcForward, rcBackward };
+ enum eReplayMode { rmStill, rmPlay, rmFastForward, rmFastRewind, rmSlowRewind };
+ cIndexFile *index;
+ cFileName fileName;
int fileOffset;
int videoDev;
int replayFile;
eReplayMode mode;
- int lastIndex;
- int brakeCounter;
+ int lastIndex, stillIndex;
+ int brakeCounter, stillCounter;
eReplayCmd command;
bool active;
bool NextFile(uchar FileNumber = 0, int FileOffset = -1);
@@ -862,25 +939,32 @@ public:
void Play(void) { SetCmd(rcPlay); }
void Forward(void) { SetCmd(rcForward); }
void Backward(void) { SetCmd(rcBackward); }
+ int SkipFrames(int Frames);
void SkipSeconds(int Seconds);
- void GetIndex(int &Current, int &Total);
+ void Goto(int Position);
+ void GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
};
cReplayBuffer::cReplayBuffer(int *OutFile, const char *FileName)
-:cFileBuffer(&replayFile, OutFile, FileName, false, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10)
+:cRingBuffer(&replayFile, OutFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10)
+,fileName(FileName, false)
{
+ index = NULL;
fileOffset = 0;
videoDev = *OutFile;
- replayFile = -1;
+ replayFile = fileName.Open();
mode = rmPlay;
- brakeCounter = 0;
+ brakeCounter = stillCounter = 0;
command = rcNone;
- lastIndex = -1;
+ lastIndex = stillIndex = -1;
active = false;
- if (!fileName)
- return;//XXX find a better way???
- // All recordings start with '1':
- fileNumber = 1; //TODO what if it doesn't start with '1'???
+ if (!fileName.Name())
+ return;
+ // Create the index file:
+ index = new cIndexFile(FileName, false);
+ if (!index)
+ esyslog(LOG_ERR, "ERROR: can't allocate index");
+ // let's continue without index, so we'll at least have the recording
Start();
}
@@ -891,6 +975,7 @@ cReplayBuffer::~cReplayBuffer()
Close();
SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
SetPlayMode(videoDev, VID_PLAY_RESET);
+ delete index;
}
void cReplayBuffer::Action(void)
@@ -903,64 +988,74 @@ void cReplayBuffer::Action(void)
int ResumeIndex = Resume();
if (ResumeIndex >= 0)
- isyslog(LOG_INFO, "resuming replay at index %d (%s)", ResumeIndex, IndexToStr(ResumeIndex, true));
+ isyslog(LOG_INFO, "resuming replay at index %d (%s)", ResumeIndex, IndexToHMSF(ResumeIndex, true));
active = true;
- for (; active;) {
- usleep(1); // this keeps the CPU load low
+ while (active) {
+ usleep(1); // this keeps the CPU load low
- LOCK_THREAD;
+ LOCK_THREAD;
- if (command != rcNone) {
- switch (command) {
- case rcPause: SetPlayMode(videoDev, Paused ? VID_PLAY_NORMAL : VID_PLAY_PAUSE);
- Paused = !Paused;
- if (FastForward || FastRewind) {
- SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
- Clear();
- }
- FastForward = FastRewind = false;
- SetMode(rmPlay);
- break;
- case rcPlay: if (FastForward || FastRewind || Paused) {
- SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
- SetPlayMode(videoDev, VID_PLAY_NORMAL);
- FastForward = FastRewind = Paused = false;
- SetMode(rmPlay);
- }
- break;
- case rcForward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
- Clear();
- FastForward = !FastForward;
- FastRewind = false;
- if (Paused) {
- SetMode(rmPlay);
- SetPlayMode(videoDev, FastForward ? VID_PLAY_SLOW_MOTION : VID_PLAY_PAUSE);
- }
- else {
- SetPlayMode(videoDev, VID_PLAY_NORMAL);
- SetMode(FastForward ? rmFastForward : rmPlay);
- }
- break;
- case rcBackward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
- Clear();
- FastRewind = !FastRewind;
- FastForward = false;
- if (Paused) {
- SetMode(FastRewind ? rmSlowRewind : rmPlay);
- SetPlayMode(videoDev, FastRewind ? VID_PLAY_NORMAL : VID_PLAY_PAUSE);
- }
- else {
- SetPlayMode(videoDev, VID_PLAY_NORMAL);
- SetMode(FastRewind ? rmFastRewind : rmPlay);
- }
- break;
- default: break;
+ if (command != rcNone) {
+ switch (command) {
+ case rcStill: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
+ SetPlayMode(videoDev, VID_PLAY_NORMAL);
+ SetMode(rmStill);
+ Paused = FastForward = FastRewind = false;
+ break;
+ case rcPause: SetPlayMode(videoDev, Paused ? VID_PLAY_NORMAL : VID_PLAY_PAUSE);
+ Paused = !Paused;
+ if (FastForward || FastRewind) {
+ SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
+ Clear();
+ }
+ FastForward = FastRewind = false;
+ SetMode(rmPlay);
+ if (!Paused)
+ stillIndex = -1;
+ break;
+ case rcPlay: if (FastForward || FastRewind || Paused) {
+ SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
+ SetPlayMode(videoDev, VID_PLAY_NORMAL);
+ FastForward = FastRewind = Paused = false;
+ SetMode(rmPlay);
+ }
+ stillIndex = -1;
+ break;
+ case rcForward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
+ Clear();
+ FastForward = !FastForward;
+ FastRewind = false;
+ if (Paused) {
+ SetMode(rmPlay);
+ SetPlayMode(videoDev, FastForward ? VID_PLAY_SLOW_MOTION : VID_PLAY_PAUSE);
+ }
+ else {
+ SetPlayMode(videoDev, VID_PLAY_NORMAL);
+ SetMode(FastForward ? rmFastForward : rmPlay);
+ }
+ stillIndex = -1;
+ break;
+ case rcBackward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
+ Clear();
+ FastRewind = !FastRewind;
+ FastForward = false;
+ if (Paused) {
+ SetMode(FastRewind ? rmSlowRewind : rmPlay);
+ SetPlayMode(videoDev, FastRewind ? VID_PLAY_NORMAL : VID_PLAY_PAUSE);
+ }
+ else {
+ SetPlayMode(videoDev, VID_PLAY_NORMAL);
+ SetMode(FastRewind ? rmFastRewind : rmPlay);
+ }
+ stillIndex = -1;
+ break;
+ default: break;
+ }
+ command = rcNone;
}
- command = rcNone;
- }
- if (Read() < 0 || Write() < 0)
- break;
- }
+ if (Read() < 0 || Write() < 0)
+ break;
+ }
Save();
dsyslog(LOG_INFO, "end replaying thread");
@@ -969,8 +1064,7 @@ void cReplayBuffer::Action(void)
void cReplayBuffer::Close(void)
{
if (replayFile >= 0) {
- if (close(replayFile) < 0)
- LOG_ERROR;
+ fileName.Close();
replayFile = -1;
fileOffset = 0;
}
@@ -1001,14 +1095,11 @@ int cReplayBuffer::Resume(void)
bool cReplayBuffer::Save(void)
{
if (index) {
- int Index = index->Get(fileNumber, fileOffset);
+ int Index = index->Get(fileName.Number(), fileOffset);
if (Index >= 0) {
Index -= RESUMEBACKUP;
- if (Index > 0) {
- uchar FileNumber;
- int FileOffset;
- Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset);
- }
+ if (Index > 0)
+ Index = index->GetNextIFrame(Index, false);
else
Index = 0;
if (Index >= 0)
@@ -1018,6 +1109,21 @@ bool cReplayBuffer::Save(void)
return false;
}
+int cReplayBuffer::SkipFrames(int Frames)
+{
+ if (index && Frames) {
+
+ LOCK_THREAD;
+
+ int Current, Total;
+ GetIndex(Current, Total, true);
+ int OldCurrent = Current;
+ Current = index->GetNextIFrame(Current + Frames, Frames > 0);
+ return Current >= 0 ? Current : OldCurrent;
+ }
+ return -1;
+}
+
void cReplayBuffer::SkipSeconds(int Seconds)
{
LOCK_THREAD;
@@ -1030,12 +1136,12 @@ void cReplayBuffer::SkipSeconds(int Seconds)
Clear();
if (index && Seconds) {
- int Index = index->Get(fileNumber, fileOffset);
+ int Index = index->Get(fileName.Number(), fileOffset);
if (Index >= 0) {
if (Seconds < 0) {
int sec = index->Last() / FRAMESPERSEC;
if (Seconds < -sec)
- Seconds = - sec;
+ Seconds = -sec;
}
Index += Seconds * FRAMESPERSEC;
if (Index < 0)
@@ -1043,19 +1149,43 @@ void cReplayBuffer::SkipSeconds(int Seconds)
uchar FileNumber;
int FileOffset;
if (index->GetNextIFrame(Index, false, &FileNumber, &FileOffset) >= 0)
- if ((Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset)) >= 0)
NextFile(FileNumber, FileOffset);
}
}
}
-void cReplayBuffer::GetIndex(int &Current, int &Total)
+void cReplayBuffer::Goto(int Index)
+{
+ LOCK_THREAD;
+
+ command = rcStill;
+ if (++Index <= 0)
+ Index = 1; // not '0', to allow GetNextIFrame() below to work!
+ uchar FileNumber;
+ int FileOffset;
+ if ((stillIndex = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset)) >= 0)
+ NextFile(FileNumber, FileOffset);
+ stillCounter = 20; // apparently we need to repeat the still frame several times to flush all buffers?!
+ SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
+ Clear();
+}
+
+void cReplayBuffer::GetIndex(int &Current, int &Total, bool SnapToIFrame)
{
if (index) {
LOCK_THREAD;
- Current = index->Get(fileNumber, fileOffset);
+ if (stillIndex >= 0)
+ Current = stillIndex;
+ else {
+ Current = index->Get(fileName.Number(), fileOffset);
+ if (SnapToIFrame) {
+ int i1 = index->GetNextIFrame(Current + 1, false);
+ int i2 = index->GetNextIFrame(Current, true);
+ Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2;
+ }
+ }
Total = index->Last();
}
else
@@ -1066,39 +1196,14 @@ bool cReplayBuffer::NextFile(uchar FileNumber, int FileOffset)
{
if (FileNumber > 0) {
Clear();
- if (FileNumber != fileNumber) {
- Close();
- fileNumber = FileNumber;
- }
+ fileOffset = FileOffset;
+ replayFile = fileName.SetOffset(FileNumber, FileOffset);
}
- if (replayFile >= 0 && EndOfFile()) {
+ else if (replayFile >= 0 && EndOfFile()) {
Close();
- fileNumber++;
- if (fileNumber == 0)
- esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING);
- }
- if (replayFile < 0) {
- sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber);
- if (access(fileName, R_OK) == 0) {
- dsyslog(LOG_INFO, "playing '%s'", fileName);
- replayFile = open(fileName, O_RDONLY | O_NONBLOCK);
- if (replayFile < 0) {
- LOG_ERROR;
- return false;
- }
- }
- else if (errno != ENOENT)
- LOG_ERROR;
- }
- if (replayFile >= 0) {
- if (FileOffset >= 0) {
- if ((fileOffset = lseek(replayFile, FileOffset, SEEK_SET)) != FileOffset)
- LOG_ERROR;
- // don't return 'false', maybe we can still replay the file
- }
- return true;
+ replayFile = fileName.NextFile();
}
- return false;
+ return replayFile >= 0;
}
int cReplayBuffer::Read(int Max = -1)
@@ -1107,28 +1212,44 @@ int cReplayBuffer::Read(int Max = -1)
if (index) {
if (Available())
return 0; // write out the entire block
- int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileNumber, fileOffset);
- if (Index >= 0) {
- uchar FileNumber;
- int FileOffset, Length;
- if (mode == rmSlowRewind && (brakeCounter++ % 24) != 0) {
- // show every I_FRAME 24 times in rmSlowRewind mode to achieve roughly the same speed as in slow forward mode
- Index = index->GetNextIFrame(Index, true, &FileNumber, &FileOffset, &Length); // jump ahead one frame
+ if (mode == rmStill) {
+ if (stillCounter > 0) {
+ stillCounter--;
+ uchar FileNumber;
+ int FileOffset, Length;
+ if (index->GetNextIFrame(stillIndex + 1, false, &FileNumber, &FileOffset, &Length) >= 0) {
+ if (!NextFile(FileNumber, FileOffset))
+ return -1;
+ Max = Length;
+ }
}
- Index = index->GetNextIFrame(Index, mode == rmFastForward, &FileNumber, &FileOffset, &Length);
+ else
+ command = rcPause;
+ }
+ else {
+ int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileName.Number(), fileOffset);
if (Index >= 0) {
- if (!NextFile(FileNumber, FileOffset))
- return -1;
- Max = Length;
+ if (mode == rmSlowRewind && (brakeCounter++ % 24) != 0) {
+ // show every I_FRAME 24 times in rmSlowRewind mode to achieve roughly the same speed as in slow forward mode
+ Index = index->GetNextIFrame(Index, true); // jump ahead one frame
+ }
+ uchar FileNumber;
+ int FileOffset, Length;
+ Index = index->GetNextIFrame(Index, mode == rmFastForward, &FileNumber, &FileOffset, &Length);
+ if (Index >= 0) {
+ if (!NextFile(FileNumber, FileOffset))
+ return -1;
+ Max = Length;
+ }
+ lastIndex = Index;
+ }
+ if (Index < 0) {
+ // This results in normal replay after a fast rewind.
+ // After a fast forward it will stop.
+ // TODO Could we cause it to pause at the last frame?
+ SetMode(rmPlay);
+ return 0;
}
- lastIndex = Index;
- }
- if (Index < 0) {
- // This results in normal replay after a fast rewind.
- // After a fast forward it will stop.
- // TODO Could we cause it to pause at the last frame?
- SetMode(rmPlay);
- return 0;
}
}
}
@@ -1139,7 +1260,7 @@ int cReplayBuffer::Read(int Max = -1)
int readin = 0;
do {
// If Max is > 0 here we need to make sure we read in the entire block!
- int r = cFileBuffer::Read(Max);
+ int r = cRingBuffer::Read(Max);
if (r >= 0)
readin += r;
else
@@ -1164,7 +1285,7 @@ int cReplayBuffer::Write(int Max)
if (Max) {
int w;
do {
- w = cFileBuffer::Write(Max);
+ w = cRingBuffer::Write(Max);
if (w >= 0) {
fileOffset += w;
Written += w;
@@ -1218,14 +1339,168 @@ void cTransferBuffer::Action(void)
Buffer.Read(); // initializes fromDevice for reading
usleep(1); // this keeps the CPU load low
}
- for (; active;) {
- if (Buffer.Read() < 0 || Buffer.Write() < 0)
- break;
- usleep(1); // this keeps the CPU load low
- }
+ while (active) {
+ if (Buffer.Read() < 0 || Buffer.Write() < 0)
+ break;
+ usleep(1); // this keeps the CPU load low
+ }
dsyslog(LOG_INFO, "data transfer thread stopped (pid=%d)", getpid());
}
+// --- cCuttingBuffer --------------------------------------------------------
+
+class cCuttingBuffer : public cRingBuffer, public cThread {
+private:
+ bool active;
+ int fromFile, toFile;
+ cFileName *fromFileName, *toFileName;
+ cIndexFile *fromIndex, *toIndex;
+ cMarks fromMarks, toMarks;
+protected:
+ virtual void Action(void);
+public:
+ cCuttingBuffer(const char *FromFileName, const char *ToFileName);
+ virtual ~cCuttingBuffer();
+ };
+
+cCuttingBuffer::cCuttingBuffer(const char *FromFileName, const char *ToFileName)
+:cRingBuffer(&fromFile, &toFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10)
+{
+ active = false;
+ fromFile = toFile = -1;
+ fromFileName = toFileName = NULL;
+ fromIndex = toIndex = NULL;
+ if (fromMarks.Load(FromFileName) && fromMarks.Count()) {
+ fromFileName = new cFileName(FromFileName, false);
+ toFileName = new cFileName(ToFileName, 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(LOG_ERR, "no editing marks found for %s", FromFileName);
+}
+
+cCuttingBuffer::~cCuttingBuffer()
+{
+ active = false;
+ Cancel(3);
+ delete fromFileName;
+ delete toFileName;
+ delete fromIndex;
+ delete toIndex;
+}
+
+void cCuttingBuffer::Action(void)
+{
+ dsyslog(LOG_INFO, "video cutting thread started (pid=%d)", getpid());
+
+ cMark *Mark = fromMarks.First();
+ if (Mark) {
+ fromFile = fromFileName->Open();
+ toFile = toFileName->Open();
+ active = fromFile >= 0 && toFile >= 0;
+ int Index = Mark->position;
+ Mark = fromMarks.Next(Mark);
+ int FileSize = 0;
+ int CurrentFileNumber = 0;
+ int LastIFrame = 0;
+ toMarks.Add(0);
+ toMarks.Save();
+ while (active) {
+ uchar FileNumber;
+ int FileOffset, Length;
+ uchar PictureType;
+
+ Clear(); // makes sure one frame is completely read and written
+
+ // Read one frame:
+
+ if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) {
+ if (FileNumber != CurrentFileNumber) {
+ fromFile = fromFileName->SetOffset(FileNumber, FileOffset);
+ CurrentFileNumber = FileNumber;
+ }
+ if (fromFile >= 0)
+ Length = cRingBuffer::Read(Length);
+ else
+ break;
+ }
+ else
+ break;
+
+ // Write one frame:
+
+ if (PictureType == I_FRAME) { // every file shall start with an I_FRAME
+ if (FileSize > MAXVIDEOFILESIZE) {
+ toFile = toFileName->NextFile();
+ if (toFile < 0)
+ break;
+ FileSize = 0;
+ }
+ LastIFrame = 0;
+ }
+ cRingBuffer::Write(Length);
+ toIndex->Write(PictureType, toFileName->Number(), FileSize);
+ FileSize += Length;
+ if (!LastIFrame)
+ LastIFrame = toIndex->Last();
+
+ // Check editing marks:
+
+ if (Mark && Index >= Mark->position) {
+ Mark = fromMarks.Next(Mark);
+ if (Mark) {
+ Index = Mark->position;
+ Mark = fromMarks.Next(Mark);
+ CurrentFileNumber = 0; // triggers SetOffset before reading next frame
+ toMarks.Add(LastIFrame);
+ toMarks.Add(toIndex->Last() + 1);
+ toMarks.Save();
+ }
+ else
+ break; // final end mark reached
+ }
+ }
+ }
+ else
+ esyslog(LOG_ERR, "no editing marks found!");
+ dsyslog(LOG_INFO, "end video cutting thread");
+}
+
+// --- cVideoCutter ----------------------------------------------------------
+
+cCuttingBuffer *cVideoCutter::cuttingBuffer = NULL;
+
+bool cVideoCutter::Start(const char *FileName)
+{
+ if (!cuttingBuffer) {
+ const char *EditedVersionName = PrefixVideoFileName(FileName, '%');
+ if (EditedVersionName && RemoveVideoFile(EditedVersionName) && MakeDirs(EditedVersionName, true)) {
+ cuttingBuffer = new cCuttingBuffer(FileName, EditedVersionName);
+ return true;
+ }
+ }
+ return false;
+}
+
+void cVideoCutter::Stop(void)
+{
+ delete cuttingBuffer;
+ cuttingBuffer = NULL;
+}
+
+bool cVideoCutter::Active(void)
+{
+ if (cuttingBuffer) {
+ if (cuttingBuffer->Active())
+ return true;
+ Stop();
+ }
+ return false;
+}
+
// --- cDvbApi ---------------------------------------------------------------
int cDvbApi::NumDvbApis = 0;
@@ -1892,6 +2167,11 @@ void cDvbApi::StopTransfer(void)
}
}
+int cDvbApi::SecondsToFrames(int Seconds)
+{
+ return Seconds * FRAMESPERSEC;
+}
+
bool cDvbApi::Recording(void)
{
if (recordBuffer && !recordBuffer->Active())
@@ -2017,21 +2297,34 @@ void cDvbApi::Backward(void)
replayBuffer->Backward();
}
-void cDvbApi::Skip(int Seconds)
+void cDvbApi::SkipSeconds(int Seconds)
{
if (replayBuffer)
replayBuffer->SkipSeconds(Seconds);
}
-bool cDvbApi::GetIndex(int &Current, int &Total)
+int cDvbApi::SkipFrames(int Frames)
+{
+ if (replayBuffer)
+ return replayBuffer->SkipFrames(Frames);
+ return -1;
+}
+
+bool cDvbApi::GetIndex(int &Current, int &Total, bool SnapToIFrame)
{
if (replayBuffer) {
- replayBuffer->GetIndex(Current, Total);
+ replayBuffer->GetIndex(Current, Total, SnapToIFrame);
return true;
}
return false;
}
+void cDvbApi::Goto(int Position)
+{
+ if (replayBuffer)
+ replayBuffer->Goto(Position);
+}
+
// --- cEITScanner -----------------------------------------------------------
cEITScanner::cEITScanner(void)
diff --git a/dvbapi.h b/dvbapi.h
index 85828410..9b579e2c 100644
--- a/dvbapi.h
+++ b/dvbapi.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: dvbapi.h 1.28 2000/12/09 10:54:09 kls Exp $
+ * $Id: dvbapi.h 1.29 2000/12/25 15:17:03 kls Exp $
*/
#ifndef __DVBAPI_H
@@ -22,6 +22,7 @@ typedef unsigned char __u8;
#include <dvb.h>
#include "dvbosd.h"
#include "eit.h"
+#include "thread.h"
// Overlay facilities
#define MAXCLIPRECTS 100
@@ -42,11 +43,24 @@ public:
bool Save(int Index);
};
-const char *IndexToStr(int Index, bool WithFrame = false);
+const char *IndexToHMSF(int Index, bool WithFrame = false);
// Converts the given index to a string, optionally containing the frame number.
+int HMSFToIndex(const char *HMSF);
+ // Converts the given string (format: "hh:mm:ss.ff") to an index.
+
class cRecordBuffer;
class cReplayBuffer;
class cTransferBuffer;
+class cCuttingBuffer;
+
+class cVideoCutter {
+private:
+ static cCuttingBuffer *cuttingBuffer;
+public:
+ static bool Start(const char *FileName);
+ static void Stop(void);
+ static bool Active(void);
+ };
class cDvbApi {
private:
@@ -180,6 +194,8 @@ protected:
// Returns the priority of the current recording session (0..99),
// or -1 if no recording is currently active.
public:
+ int SecondsToFrames(int Seconds);
+ // Returns the number of frames corresponding to the given number of seconds.
bool Recording(void);
// Returns true if we are currently recording.
bool Replaying(void);
@@ -211,12 +227,20 @@ public:
// Runs the current replay session forward at a higher speed.
void Backward(void);
// Runs the current replay session backwards at a higher speed.
- void Skip(int Seconds);
+ void SkipSeconds(int Seconds);
// Skips the given number of seconds in the current replay session.
// The sign of 'Seconds' determines the direction in which to skip.
// Use a very large negative value to go all the way back to the
// beginning of the recording.
- bool GetIndex(int &Current, int &Total);
+ int SkipFrames(int Frames);
+ // Returns the new index into the current replay session after skipping
+ // the given number of frames (no actual repositioning is done!).
+ // The sign of 'Frames' determines the direction in which to skip.
+ bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false);
+ // Returns the current and total frame index, optionally snapped to the
+ // nearest I-frame.
+ void Goto(int Index);
+ // Positions to the given index and displays that frame as a still picture.
};
class cEITScanner {
diff --git a/i18n.c b/i18n.c
index 7b93062d..a6df4a37 100644
--- a/i18n.c
+++ b/i18n.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: i18n.c 1.6 2000/11/19 12:12:53 kls Exp $
+ * $Id: i18n.c 1.7 2000/12/25 17:51:55 kls Exp $
*
* Slovenian translations provided by Miha Setina <mihasetina@softhome.net>
*
@@ -165,22 +165,26 @@ const tPhrase Phrases[] = {
"Urnik",
},
// Confirmations:
- { "Delete Channel?",
+ { "Delete channel?",
"Kanal löschen?",
"Odstrani kanal?",
},
- { "Delete Timer?",
+ { "Delete timer?",
"Timer löschen?",
"Odstani termin?",
},
- { "Delete Recording?",
+ { "Delete recording?",
"Aufzeichnung löschen?",
"Odstrani posnetek?",
},
- { "Stop Recording?",
+ { "Stop recording?",
"Aufzeichnung beenden?",
"Koncaj snemanje?",
},
+ { "Cancel editing?",
+ "Schneiden abbrechen?",
+ "Zelite prekiniti urejanje?",
+ },
// Channel parameters:
{ "Name",
"Name",
@@ -280,6 +284,14 @@ const tPhrase Phrases[] = {
"Kanal blockiert (zeichnet auf)!",
"Zaklenjen kanal (snemanje)!",
},
+ { "Can't start editing process!",
+ "Schnitt kann nicht gestartet werden!",
+ "Ne morem zaceti urejanja!",
+ },
+ { "Editing process already active!",
+ "Schnitt bereits aktiv!",
+ "Urejanje je ze aktivno!",
+ },
// Setup parameters:
{ "OSD-Language",
"OSD-Sprache",
@@ -445,6 +457,10 @@ const tPhrase Phrases[] = {
"Aufzeichnung beenden ",
"Prekini shranjevanje ",
},
+ { "Cancel editing",
+ "Schneiden abbrechen",
+ "Prekini urejanje",
+ },
{ "Switching primary DVB...",
"Primäres Interface wird umgeschaltet...",
"Preklapljanje primarne naprave...",
@@ -453,6 +469,10 @@ const tPhrase Phrases[] = {
"Auf/Ab für neue Position - dann OK",
"Gor/Dol za novo poz. - Ok za premik",
},
+ { "Editing process started",
+ "Schnitt gestartet",
+ "Urejanje se je zacelo",
+ },
{ NULL }
};
diff --git a/menu.c b/menu.c
index df2fdeac..6f46800c 100644
--- a/menu.c
+++ b/menu.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: menu.c 1.55 2000/12/09 11:03:21 kls Exp $
+ * $Id: menu.c 1.56 2000/12/25 15:18:32 kls Exp $
*/
#include "menu.h"
@@ -669,7 +669,7 @@ eOSState cMenuChannels::Del(void)
return osContinue;
}
}
- if (Interface->Confirm(tr("Delete Channel?"))) {
+ if (Interface->Confirm(tr("Delete channel?"))) {
// Move and renumber the channels:
Channels.Del(channel);
Channels.ReNumber();
@@ -1039,7 +1039,7 @@ eOSState cMenuTimers::Del(void)
cTimer *ti = Timers.Get(Index);
if (ti) {
if (!ti->recording) {
- if (Interface->Confirm(tr("Delete Timer?"))) {
+ if (Interface->Confirm(tr("Delete timer?"))) {
Timers.Del(Timers.Get(Index));
cOsdMenu::Del(Index);
Timers.Save();
@@ -1489,7 +1489,7 @@ eOSState cMenuRecordings::Del(void)
if (ri) {
//XXX what if this recording's file is currently in use???
//XXX if (!ti->recording) {
- if (Interface->Confirm(tr("Delete Recording?"))) {
+ if (Interface->Confirm(tr("Delete recording?"))) {
if (ri->recording->Delete()) {
cReplayControl::ClearLastReplayed(ri->recording->FileName());
cOsdMenu::Del(Current());
@@ -1663,6 +1663,8 @@ cMenuMain::cMenuMain(bool Replaying)
Add(new cOsdItem(buffer, osStopRecord));
delete buffer;
}
+ if (cVideoCutter::Active())
+ Add(new cOsdItem(tr("Cancel editing"), osCancelEdit));
SetHelp(tr("Record"), NULL, NULL, cReplayControl::LastReplayed() ? tr("Resume") : NULL);
Display();
lastActivity = time(NULL);
@@ -1679,13 +1681,19 @@ eOSState cMenuMain::ProcessKey(eKeys Key)
case osRecordings: return AddSubMenu(new cMenuRecordings);
case osSetup: return AddSubMenu(new cMenuSetup);
case osCommands: return AddSubMenu(new cMenuCommands);
- case osStopRecord: if (Interface->Confirm(tr("Stop Recording?"))) {
+ case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) {
cOsdItem *item = Get(Current());
if (item) {
cRecordControls::Stop(item->Text() + strlen(STOP_RECORDING));
return osEnd;
}
}
+ break;
+ case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) {
+ cVideoCutter::Stop();
+ return osEnd;
+ }
+ break;
default: switch (Key) {
case kMenu: state = osEnd; break;
case kRed: if (!HasSubMenu())
@@ -1993,6 +2001,50 @@ void cRecordControls::Process(void)
}
}
+// --- cProgressBar ----------------------------------------------------------
+
+class cProgressBar : public cBitmap {
+protected:
+ int total;
+ int Pos(int p) { return p * width / total; }
+ void Mark(int x, bool Start, bool Current);
+public:
+ cProgressBar(int Width, int Height, int Current, int Total, const cMarks &Marks);
+ };
+
+cProgressBar::cProgressBar(int Width, int Height, int Current, int Total, const cMarks &Marks)
+:cBitmap(Width, Height)
+{
+ total = Total;
+ if (total > 0) {
+ int p = Pos(Current);
+ Fill(0, 0, p, Height - 1, clrGreen);
+ Fill(p + 1, 0, Width - 1, Height - 1, clrWhite);
+ bool Start = true;
+ for (const cMark *m = Marks.First(); m; m = Marks.Next(m)) {
+ int p1 = Pos(m->position);
+ if (Start) {
+ const cMark *m2 = Marks.Next(m);
+ int p2 = Pos(m2 ? m2->position : total);
+ int h = Height / 3;
+ Fill(p1, h, p2, Height - h, clrRed);
+ }
+ Mark(p1, Start, m->position == Current);
+ Start = !Start;
+ }
+ }
+}
+
+void cProgressBar::Mark(int x, bool Start, bool Current)
+{
+ Fill(x, 0, x, height - 1, clrBlack);
+ const int d = height / (Current ? 3 : 9);
+ for (int i = 0; i < d; i++) {
+ int h = Start ? i : height - 1 - i;
+ Fill(x - d + i, h, x + d - i, h, Current ? clrRed : clrBlack);
+ }
+}
+
// --- cReplayControl --------------------------------------------------------
char *cReplayControl::fileName = NULL;
@@ -2001,9 +2053,11 @@ char *cReplayControl::title = NULL;
cReplayControl::cReplayControl(void)
{
dvbApi = cDvbApi::PrimaryDvbApi;
- visible = shown = false;
- if (fileName)
+ visible = shown = displayFrames = false;
+ if (fileName) {
+ marks.Load(fileName);
dvbApi->StartReplay(fileName);
+ }
}
cReplayControl::~cReplayControl()
@@ -2054,37 +2108,110 @@ bool cReplayControl::ShowProgress(bool Initial)
{
int Current, Total;
- if (dvbApi->GetIndex(Current, Total)) {
+ if (dvbApi->GetIndex(Current, Total) && Total > 0) {
if (Initial) {
Interface->Clear();
if (title)
Interface->Write(0, 0, title);
+ displayFrames = marks.Count() > 0;
}
- Interface->Write(-7, 2, IndexToStr(Total));
+ Interface->Write(-7, 2, IndexToHMSF(Total));
Interface->Flush();
#ifdef DEBUG_OSD
int p = Width() * Current / Total;
Interface->Fill(0, 1, p, 1, clrGreen);
Interface->Fill(p, 1, Width() - p, 1, clrWhite);
#else
- int w = Width() * dvbApi->CellWidth();
- int h = dvbApi->LineHeight();
- int p = w * Current / Total;
- cBitmap ProgressBar(w, h);
-
- ProgressBar.Fill(0, 0, p, h - 1, clrGreen);
- ProgressBar.Fill(p + 1, 0, w - 1, h - 1, clrWhite);
+ cProgressBar ProgressBar(Width() * dvbApi->CellWidth(), dvbApi->LineHeight(), Current, Total, marks);
Interface->SetBitmap(0, dvbApi->LineHeight(), ProgressBar);
-
Interface->Flush();
#endif
- Interface->Write(0, 2, IndexToStr(Current));
+ Interface->Write(0, 2, IndexToHMSF(Current, displayFrames));
Interface->Flush();
return true;
}
return false;
}
+void cReplayControl::MarkToggle(void)
+{
+ int Current, Total;
+ if (dvbApi->GetIndex(Current, Total, true)) {
+ cMark *m = marks.Get(Current);
+ if (m)
+ marks.Del(m);
+ else
+ marks.Add(Current);
+ marks.Save();
+ }
+ displayFrames = marks.Count() > 0;
+ if (!displayFrames)
+ Interface->Fill(0, 2, Width() / 2, 1, clrBackground);
+}
+
+void cReplayControl::MarkJump(bool Forward)
+{
+ int Current, Total;
+ if (dvbApi->GetIndex(Current, Total)) {
+ cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current);
+ if (m)
+ dvbApi->Goto(m->position);
+ }
+}
+
+void cReplayControl::MarkMove(bool Forward)
+{
+ int Current, Total;
+ if (dvbApi->GetIndex(Current, Total)) {
+ cMark *m = marks.Get(Current);
+ if (m) {
+ int p = dvbApi->SkipFrames(Forward ? 1 : -1);
+ cMark *m2;
+ if (Forward) {
+ if ((m2 = marks.Next(m)) != NULL && m2->position <= p)
+ return;
+ }
+ else {
+ if ((m2 = marks.Prev(m)) != NULL && m2->position >= p)
+ return;
+ }
+ dvbApi->Goto(m->position = p);
+ marks.Save();
+ }
+ }
+}
+
+void cReplayControl::EditCut(void)
+{
+ Hide();
+ if (!cVideoCutter::Active()) {
+ if (!cVideoCutter::Start(fileName))
+ Interface->Error(tr("Can't start editing process!"));
+ else
+ Interface->Info(tr("Editing process started"));
+ }
+ else
+ Interface->Error(tr("Editing process already active!"));
+}
+
+void cReplayControl::EditTest(void)
+{
+ int Current, Total;
+ if (dvbApi->GetIndex(Current, Total)) {
+ cMark *m = marks.Get(Current);
+ if (!m)
+ m = marks.GetNext(Current);
+ if (m) {
+ if ((m->Index() & 0x01) != 0)
+ m = marks.Next(m);
+ if (m) {
+ dvbApi->Goto(m->position - dvbApi->SecondsToFrames(3));
+ dvbApi->Play();
+ }
+ }
+ }
+}
+
eOSState cReplayControl::ProcessKey(eKeys Key)
{
if (!dvbApi->Replaying())
@@ -2092,20 +2219,33 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
if (visible)
shown = ShowProgress(!shown) || shown;
switch (Key) {
+ // Positioning:
case kUp: dvbApi->Play(); break;
case kDown: dvbApi->Pause(); break;
- case kBlue: Hide();
- dvbApi->StopReplay();
- return osEnd;
case kLeft: dvbApi->Backward(); break;
case kRight: dvbApi->Forward(); break;
case kLeft|k_Release:
case kRight|k_Release:
dvbApi->Play(); break;
case kGreen|k_Repeat:
- case kGreen: dvbApi->Skip(-60); break;
+ case kGreen: dvbApi->SkipSeconds(-60); break;
case kYellow|k_Repeat:
- case kYellow: dvbApi->Skip(60); break;
+ case kYellow: dvbApi->SkipSeconds(60); break;
+ case kBlue: Hide();
+ dvbApi->StopReplay();
+ return osEnd;
+ // Editing:
+ //XXX should we do this only when the ProgressDisplay is on???
+ case kMarkToggle: MarkToggle(); break;
+ case kMarkJumpBack: MarkJump(false); break;
+ case kMarkJumpForward: MarkJump(true); break;
+ case kMarkMoveBack|k_Repeat:
+ case kMarkMoveBack: MarkMove(false); break;
+ case kMarkMoveForward|k_Repeat:
+ case kMarkMoveForward: MarkMove(true); break;
+ case kEditCut: EditCut(); break;
+ case kEditTest: EditTest(); break;
+ // Menu control:
case kMenu: Hide(); return osMenu; // allow direct switching to menu
case kOk: visible ? Hide() : Show(); break;
case kBack: return osRecordings;
diff --git a/menu.h b/menu.h
index 62ce1182..92321111 100644
--- a/menu.h
+++ b/menu.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: menu.h 1.15 2000/12/09 10:40:13 kls Exp $
+ * $Id: menu.h 1.16 2000/12/25 14:25:29 kls Exp $
*/
#ifndef _MENU_H
@@ -79,12 +79,18 @@ public:
class cReplayControl : public cOsdBase {
private:
cDvbApi *dvbApi;
- bool visible, shown;
+ cMarks marks;
+ bool visible, shown, displayFrames;
void Show(void);
void Hide(void);
static char *fileName;
static char *title;
bool ShowProgress(bool Initial);
+ void MarkToggle(void);
+ void MarkJump(bool Forward);
+ void MarkMove(bool Forward);
+ void EditCut(void);
+ void EditTest(void);
public:
cReplayControl(void);
virtual ~cReplayControl();
diff --git a/osd.h b/osd.h
index 0d8085df..16d0ec2c 100644
--- a/osd.h
+++ b/osd.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: osd.h 1.17 2000/11/12 15:27:34 kls Exp $
+ * $Id: osd.h 1.18 2000/12/24 10:16:52 kls Exp $
*/
#ifndef __OSD_H
@@ -29,6 +29,7 @@ enum eOSState { osUnknown,
osReplay,
osStopRecord,
osStopReplay,
+ osCancelEdit,
osSwitchDvb,
osBack,
osEnd,
diff --git a/recording.c b/recording.c
index f45be963..da90a0d8 100644
--- a/recording.c
+++ b/recording.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: recording.c 1.21 2000/11/18 16:22:29 kls Exp $
+ * $Id: recording.c 1.22 2000/12/16 14:25:14 kls Exp $
*/
#define _GNU_SOURCE
@@ -26,6 +26,7 @@
#define NAMEFORMAT "%s/%s/" DATAFORMAT
#define SUMMARYFILESUFFIX "/summary.vdr"
+#define MARKSFILESUFFIX "/marks.vdr"
#define FINDCMD "find %s -follow -type d -name '%s' 2> /dev/null | sort -df"
@@ -269,3 +270,107 @@ bool cRecordings::Load(bool Deleted)
return result;
}
+// --- cMark -----------------------------------------------------------------
+
+char *cMark::buffer = NULL;
+
+cMark::cMark(int Position, const char *Comment)
+{
+ position = Position;
+ comment = Comment ? strdup(Comment) : NULL;
+}
+
+cMark::~cMark()
+{
+ delete comment;
+}
+
+const char *cMark::ToText(void)
+{
+ delete buffer;
+ asprintf(&buffer, "%s%s%s\n", IndexToHMSF(position, true), comment ? " " : "", comment ? comment : "");
+ return buffer;
+}
+
+bool cMark::Parse(const char *s)
+{
+ delete comment;
+ comment = NULL;
+ position = HMSFToIndex(s);
+ const char *p = strchr(s, ' ');
+ if (p) {
+ p = skipspace(p);
+ if (*p) {
+ comment = strdup(p);
+ comment[strlen(comment) - 1] = 0; // strips trailing newline
+ }
+ }
+ return true;
+}
+
+bool cMark::Save(FILE *f)
+{
+ return fprintf(f, ToText()) > 0;
+}
+
+// --- cMarks ----------------------------------------------------------------
+
+bool cMarks::Load(const char *RecordingFileName)
+{
+ const char *MarksFile = AddDirectory(RecordingFileName, MARKSFILESUFFIX);
+ if (cConfig::Load(MarksFile)) {
+ Sort();
+ return true;
+ }
+ return false;
+}
+
+void cMarks::Sort(void)
+{
+ for (cMark *m1 = First(); m1; m1 = Next(m1)) {
+ for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) {
+ if (m2->position < m1->position) {
+ swap(m1->position, m2->position);
+ swap(m1->comment, m2->comment);
+ }
+ }
+ }
+}
+
+cMark *cMarks::Add(int Position)
+{
+ cMark *m = Get(Position);
+ if (!m) {
+ cConfig::Add(m = new cMark(Position));
+ Sort();
+ }
+ return m;
+}
+
+cMark *cMarks::Get(int Position)
+{
+ for (cMark *mi = First(); mi; mi = Next(mi)) {
+ if (mi->position == Position)
+ return mi;
+ }
+ return NULL;
+}
+
+cMark *cMarks::GetPrev(int Position)
+{
+ for (cMark *mi = Last(); mi; mi = Prev(mi)) {
+ if (mi->position < Position)
+ return mi;
+ }
+ return NULL;
+}
+
+cMark *cMarks::GetNext(int Position)
+{
+ for (cMark *mi = First(); mi; mi = Next(mi)) {
+ if (mi->position > Position)
+ return mi;
+ }
+ return NULL;
+}
+
diff --git a/recording.h b/recording.h
index 7511c659..454c356f 100644
--- a/recording.h
+++ b/recording.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: recording.h 1.10 2000/10/03 12:27:49 kls Exp $
+ * $Id: recording.h 1.11 2000/12/16 14:25:20 kls Exp $
*/
#ifndef __RECORDING_H
@@ -47,4 +47,27 @@ public:
bool Load(bool Deleted = false);
};
+class cMark : public cListObject {
+private:
+ static char *buffer;
+public:
+ int position;
+ char *comment;
+ cMark(int Position = 0, const char *Comment = NULL);
+ ~cMark();
+ const char *ToText(void);
+ bool Parse(const char *s);
+ bool Save(FILE *f);
+ };
+
+class cMarks : public cConfig<cMark> {
+public:
+ bool Load(const char *RecordingFileName);
+ void Sort(void);
+ cMark *Add(int Position);
+ cMark *Get(int Position);
+ cMark *GetPrev(int Position);
+ cMark *GetNext(int Position);
+ };
+
#endif //__RECORDING_H
diff --git a/thread.c b/thread.c
index d56e8808..363190d8 100644
--- a/thread.c
+++ b/thread.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: thread.c 1.6 2000/12/03 15:35:02 kls Exp $
+ * $Id: thread.c 1.7 2000/12/24 12:27:21 kls Exp $
*/
#include "thread.h"
@@ -54,6 +54,7 @@ bool cThread::Start(void)
running = true;
parentPid = getpid();
pthread_create(&thread, NULL, (void *(*) (void *))&StartThread, (void *)this);
+ usleep(10000); // otherwise calling Active() immediately after Start() causes a "pure virtual method called" error
}
return true; //XXX return value of pthread_create()???
}
diff --git a/tools.c b/tools.c
index 2397b2a3..f29dc004 100644
--- a/tools.c
+++ b/tools.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.c 1.24 2000/12/03 15:39:11 kls Exp $
+ * $Id: tools.c 1.25 2000/12/24 12:38:22 kls Exp $
*/
#define _GNU_SOURCE
@@ -240,9 +240,11 @@ bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks)
if (remove(FileName) == 0)
return true;
}
- else
+ else if (errno != ENOENT) {
LOG_ERROR_STR(FileName);
- return false;
+ return false;
+ }
+ return true;
}
// --- cFile -----------------------------------------------------------------
diff --git a/tools.h b/tools.h
index b76d7e95..83f3fa0c 100644
--- a/tools.h
+++ b/tools.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.h 1.21 2000/12/03 15:32:54 kls Exp $
+ * $Id: tools.h 1.22 2000/12/10 11:49:42 kls Exp $
*/
#ifndef __TOOLS_H
@@ -31,6 +31,8 @@ extern int SysLogLevel;
#define DELETENULL(p) (delete (p), p = NULL)
+template<class T> inline void swap(T &a, T &b) { T t = a; a = b; b = t; };
+
void writechar(int filedes, char c);
char *readline(FILE *f);
char *strn0cpy(char *dest, const char *src, size_t n);
@@ -98,6 +100,8 @@ template<class T> class cList : public cListBase {
public:
T *Get(int Index) const { return (T *)cListBase::Get(Index); }
T *First(void) const { return (T *)objects; }
+ T *Last(void) const { return (T *)lastObject; }
+ T *Prev(const T *object) const { return (T *)object->Prev(); }
T *Next(const T *object) const { return (T *)object->Next(); }
};
diff --git a/vdr.c b/vdr.c
index fbef0eb0..6a87ab63 100644
--- a/vdr.c
+++ b/vdr.c
@@ -22,7 +22,7 @@
*
* The project's page is at http://www.cadsoft.de/people/kls/vdr
*
- * $Id: vdr.c 1.47 2000/12/03 15:36:46 kls Exp $
+ * $Id: vdr.c 1.48 2000/12/25 09:43:08 kls Exp $
*/
#include <getopt.h>
@@ -306,10 +306,13 @@ int main(int argc, char *argv[])
default: break;
}
}
- if (!Menu)
+ if (!Menu) {
EITScanner.Process();
+ cVideoCutter::Active();
+ }
}
isyslog(LOG_INFO, "caught signal %d", Interrupted);
+ cVideoCutter::Stop();
delete Menu;
delete ReplayControl;
delete Interface;
diff --git a/videodir.c b/videodir.c
index 91d362db..4d5c2572 100644
--- a/videodir.c
+++ b/videodir.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: videodir.c 1.2 2000/09/15 13:23:47 kls Exp $
+ * $Id: videodir.c 1.3 2000/12/24 12:51:41 kls Exp $
*/
#include "videodir.h"
@@ -180,3 +180,20 @@ bool VideoFileSpaceAvailable(unsigned int SizeMB)
}
return Dir.FreeMB() >= SizeMB;
}
+
+const char *PrefixVideoFileName(const char *FileName, char Prefix)
+{
+ static char *PrefixedName = NULL;
+
+ if (!PrefixedName || strlen(PrefixedName) <= strlen(FileName))
+ PrefixedName = (char *)realloc(PrefixedName, strlen(FileName) + 2);
+ if (PrefixedName) {
+ strcpy(PrefixedName, VideoDirectory);
+ char *p = PrefixedName + strlen(PrefixedName);
+ *p++ = '/';
+ *p++ = Prefix;
+ strcpy(p, FileName + strlen(VideoDirectory) + 1);
+ }
+ return PrefixedName;
+}
+
diff --git a/videodir.h b/videodir.h
index 7ce15314..0716a284 100644
--- a/videodir.h
+++ b/videodir.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: videodir.h 1.1 2000/07/29 14:08:27 kls Exp $
+ * $Id: videodir.h 1.2 2000/12/24 12:41:10 kls Exp $
*/
#ifndef __VIDEODIR_H
@@ -17,5 +17,6 @@ int CloseVideoFile(int FileHandle);
bool RenameVideoFile(const char *OldName, const char *NewName);
bool RemoveVideoFile(const char *FileName);
bool VideoFileSpaceAvailable(unsigned int SizeMB);
+const char *PrefixVideoFileName(const char *FileName, char Prefix);
#endif //__VIDEODIR_H