From 4e354bc9a0f9a67e842932b1de9da889488c8a2b Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Thu, 28 Dec 2000 12:57:16 +0100 Subject: [PATCH] Implemented 'on disk editing' --- FORMATS | 23 ++ HISTORY | 4 +- INSTALL | 5 +- MANUAL | 46 ++++ README | 2 +- TODO | 3 - config.h | 59 ++-- dvbapi.c | 755 ++++++++++++++++++++++++++++++++++++---------------- dvbapi.h | 32 ++- i18n.c | 30 ++- menu.c | 186 +++++++++++-- menu.h | 10 +- osd.h | 3 +- recording.c | 107 +++++++- recording.h | 25 +- thread.c | 3 +- tools.c | 8 +- tools.h | 6 +- vdr.c | 7 +- videodir.c | 19 +- videodir.h | 3 +- 21 files changed, 1028 insertions(+), 308 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 #include #include +#include #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::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 #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,49 +587,7 @@ int cRingBuffer::Write(int Max) return -1; } -// --- cFileBuffer ----------------------------------------------------------- - -class cFileBuffer : public cRingBuffer { -protected: - cIndexFile *index; - uchar 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); -public: - cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0); - virtual ~cFileBuffer(); - }; - -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) -{ - index = NULL; - fileNumber = 0; - // Prepare the file name: - fileName = new char[strlen(FileName) + RECORDFILESUFFIXLEN]; - if (!fileName) { - esyslog(LOG_ERR, "ERROR: can't copy file name '%s'", fileName); - return; - } - 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 -} - -cFileBuffer::~cFileBuffer() -{ - delete index; - delete fileName; -} - -int cFileBuffer::FindStartCode(uchar Code, int Offset) +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. @@ -618,16 +604,124 @@ int cFileBuffer::FindStartCode(uchar Code, int Offset) return -1; } -int cFileBuffer::GetAudioPacketLength(int Offset) +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; + bool record; +public: + 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); + }; + +cFileName::cFileName(const char *FileName, bool Record) +{ + file = -1; + fileNumber = 0; + record = Record; + // Prepare the file name: + fileName = new char[strlen(FileName) + RECORDFILESUFFIXLEN]; + if (!fileName) { + esyslog(LOG_ERR, "ERROR: can't copy file name '%s'", fileName); + return; + } + strcpy(fileName, FileName); + pFileNumber = fileName + strlen(fileName); + SetOffset(1); +} + +cFileName::~cFileName() +{ + Close(); + delete fileName; +} + +int cFileName::Open(void) +{ + 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; +} + +void cFileName::Close(void) +{ + if (file >= 0) { + if ((record && CloseVideoFile(file) < 0) || (!record && close(file) < 0)) + LOG_ERROR_STR(fileName); + file = -1; + } +} + +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 cFileName::NextFile(void) +{ + 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); + replayFile = fileName.NextFile(); } - 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; - } - 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); - if (Index >= 0) { - if (!NextFile(FileNumber, FileOffset)) - return -1; - Max = Length; - } - lastIndex = Index; + else + command = rcPause; } - 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; + else { + int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileName.Number(), fileOffset); + if (Index >= 0) { + 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; + } } } } @@ -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 #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 * @@ -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 { +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 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 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 @@ -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