Implemented 'on disk editing'

This commit is contained in:
Klaus Schmidinger 2000-12-28 12:57:16 +01:00
parent be137ee37f
commit 4e354bc9a0
21 changed files with 1028 additions and 308 deletions

23
FORMATS
View File

@ -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).

View File

@ -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".

View File

@ -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.

46
MANUAL
View File

@ -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.

2
README
View File

@ -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.

3
TODO
View File

@ -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.

View File

@ -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,16 +167,20 @@ 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);
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;
@ -181,9 +195,8 @@ public:
}
fclose(f);
}
else {
else
LOG_ERROR_STR(fileName);
result = false;
}
return result;
}

603
dvbapi.c
View File

@ -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) {
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;
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,15 +988,20 @@ 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;) {
while (active) {
usleep(1); // this keeps the CPU load low
LOCK_THREAD;
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) {
@ -920,6 +1010,8 @@ void cReplayBuffer::Action(void)
}
FastForward = FastRewind = false;
SetMode(rmPlay);
if (!Paused)
stillIndex = -1;
break;
case rcPlay: if (FastForward || FastRewind || Paused) {
SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
@ -927,6 +1019,7 @@ void cReplayBuffer::Action(void)
FastForward = FastRewind = Paused = false;
SetMode(rmPlay);
}
stillIndex = -1;
break;
case rcForward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
Clear();
@ -940,6 +1033,7 @@ void cReplayBuffer::Action(void)
SetPlayMode(videoDev, VID_PLAY_NORMAL);
SetMode(FastForward ? rmFastForward : rmPlay);
}
stillIndex = -1;
break;
case rcBackward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER);
Clear();
@ -953,6 +1047,7 @@ void cReplayBuffer::Action(void)
SetPlayMode(videoDev, VID_PLAY_NORMAL);
SetMode(FastRewind ? rmFastRewind : rmPlay);
}
stillIndex = -1;
break;
default: break;
}
@ -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,7 +1136,7 @@ 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;
@ -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) {
fileOffset = FileOffset;
replayFile = fileName.SetOffset(FileNumber, FileOffset);
}
else if (replayFile >= 0 && EndOfFile()) {
Close();
fileNumber = FileNumber;
replayFile = fileName.NextFile();
}
}
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;
}
return false;
return replayFile >= 0;
}
int cReplayBuffer::Read(int Max = -1)
@ -1107,14 +1212,29 @@ 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) {
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;
}
}
else
command = rcPause;
}
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, &FileNumber, &FileOffset, &Length); // jump ahead one frame
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))
@ -1132,6 +1252,7 @@ int cReplayBuffer::Read(int Max = -1)
}
}
}
}
else
lastIndex = -1;
//XXX timeout as in recording???
@ -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,7 +1339,7 @@ void cTransferBuffer::Action(void)
Buffer.Read(); // initializes fromDevice for reading
usleep(1); // this keeps the CPU load low
}
for (; active;) {
while (active) {
if (Buffer.Read() < 0 || Buffer.Write() < 0)
break;
usleep(1); // this keeps the CPU load low
@ -1226,6 +1347,160 @@ void cTransferBuffer::Action(void)
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)

View File

@ -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 {

30
i18n.c
View File

@ -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 }
};

186
menu.c
View File

@ -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,10 +2053,12 @@ 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;

10
menu.h
View File

@ -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();

3
osd.h
View File

@ -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,

View File

@ -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;
}

View File

@ -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

View File

@ -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()???
}

View File

@ -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,10 +240,12 @@ 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 true;
}
// --- cFile -----------------------------------------------------------------

View File

@ -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(); }
};

7
vdr.c
View File

@ -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;

View File

@ -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;
}

View File

@ -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