2002-06-22 10:11:59 +02:00
/*
* cutter . c : The video cutting facilities
*
* See the main source file ' vdr . c ' for copyright information and
* how to reach the author .
*
2017-12-14 14:17:35 +01:00
* $ Id : cutter . c 4.4 2017 / 12 / 14 14 : 09 : 46 kls Exp $
2002-06-22 10:11:59 +02:00
*/
# include "cutter.h"
2012-02-16 12:20:46 +01:00
# include "menu.h"
2002-06-22 10:11:59 +02:00
# include "recording.h"
# include "remux.h"
# include "videodir.h"
2012-11-18 12:19:51 +01:00
// --- cPacketBuffer ---------------------------------------------------------
class cPacketBuffer {
private :
uchar * data ;
int size ;
int length ;
public :
cPacketBuffer ( void ) ;
~ cPacketBuffer ( ) ;
void Append ( uchar * Data , int Length ) ;
///< Appends Length bytes of Data to this packet buffer.
void Flush ( uchar * Data , int & Length , int MaxLength ) ;
///< Flushes the content of this packet buffer into the given Data, starting
///< at position Length, and clears the buffer afterwards. Length will be
///< incremented accordingly. If Length plus the total length of the stored
///< packets would exceed MaxLength, nothing is copied.
} ;
cPacketBuffer : : cPacketBuffer ( void )
{
data = NULL ;
size = length = 0 ;
}
cPacketBuffer : : ~ cPacketBuffer ( )
{
free ( data ) ;
}
void cPacketBuffer : : Append ( uchar * Data , int Length )
{
if ( length + Length > = size ) {
int NewSize = ( length + Length ) * 3 / 2 ;
if ( uchar * p = ( uchar * ) realloc ( data , NewSize ) ) {
data = p ;
size = NewSize ;
}
else
return ; // out of memory
}
memcpy ( data + length , Data , Length ) ;
length + = Length ;
}
void cPacketBuffer : : Flush ( uchar * Data , int & Length , int MaxLength )
{
if ( Data & & length > 0 & & Length + length < = MaxLength ) {
memcpy ( Data + Length , data , length ) ;
Length + = length ;
}
length = 0 ;
}
// --- cPacketStorage --------------------------------------------------------
class cPacketStorage {
private :
cPacketBuffer * buffers [ MAXPID ] ;
public :
cPacketStorage ( void ) ;
~ cPacketStorage ( ) ;
void Append ( int Pid , uchar * Data , int Length ) ;
void Flush ( int Pid , uchar * Data , int & Length , int MaxLength ) ;
} ;
cPacketStorage : : cPacketStorage ( void )
{
for ( int i = 0 ; i < MAXPID ; i + + )
buffers [ i ] = NULL ;
}
cPacketStorage : : ~ cPacketStorage ( )
{
for ( int i = 0 ; i < MAXPID ; i + + )
delete buffers [ i ] ;
}
void cPacketStorage : : Append ( int Pid , uchar * Data , int Length )
{
if ( ! buffers [ Pid ] )
buffers [ Pid ] = new cPacketBuffer ;
buffers [ Pid ] - > Append ( Data , Length ) ;
}
void cPacketStorage : : Flush ( int Pid , uchar * Data , int & Length , int MaxLength )
{
if ( buffers [ Pid ] )
buffers [ Pid ] - > Flush ( Data , Length , MaxLength ) ;
}
2013-02-17 14:41:29 +01:00
// --- cMpeg2Fixer -----------------------------------------------------------
2012-11-18 12:19:51 +01:00
2013-01-20 12:19:42 +01:00
class cMpeg2Fixer : private cTsPayload {
2012-11-18 12:19:51 +01:00
private :
2013-01-20 12:19:42 +01:00
bool FindHeader ( uint32_t Code , const char * Header ) ;
2012-11-18 12:19:51 +01:00
public :
2013-01-20 12:19:42 +01:00
cMpeg2Fixer ( uchar * Data , int Length , int Vpid ) ;
void SetBrokenLink ( void ) ;
void SetClosedGop ( void ) ;
int GetTref ( void ) ;
void AdjGopTime ( int Offset , int FramesPerSecond ) ;
void AdjTref ( int TrefOffset ) ;
2012-11-18 12:19:51 +01:00
} ;
2013-01-20 12:19:42 +01:00
cMpeg2Fixer : : cMpeg2Fixer ( uchar * Data , int Length , int Vpid )
2012-11-18 12:19:51 +01:00
{
2013-01-20 12:19:42 +01:00
// Go to first video packet:
for ( ; Length > 0 ; Data + = TS_SIZE , Length - = TS_SIZE ) {
if ( TsPid ( Data ) = = Vpid ) {
Setup ( Data , Length , Vpid ) ;
break ;
}
}
2012-11-18 12:19:51 +01:00
}
2013-01-20 12:19:42 +01:00
bool cMpeg2Fixer : : FindHeader ( uint32_t Code , const char * Header )
2012-11-18 12:19:51 +01:00
{
2013-01-20 12:19:42 +01:00
Reset ( ) ;
if ( Find ( Code ) )
return true ;
esyslog ( " ERROR: %s header not found! " , Header ) ;
return false ;
2012-11-18 12:19:51 +01:00
}
2013-01-20 12:19:42 +01:00
void cMpeg2Fixer : : SetBrokenLink ( void )
{
if ( ! FindHeader ( 0x000001B8 , " GOP " ) )
return ;
SkipBytes ( 3 ) ;
uchar b = GetByte ( ) ;
if ( ! ( b & 0x40 ) ) { // GOP not closed
b | = 0x20 ;
SetByte ( b , GetLastIndex ( ) ) ;
}
}
2012-11-18 12:19:51 +01:00
2013-01-20 12:19:42 +01:00
void cMpeg2Fixer : : SetClosedGop ( void )
2012-11-18 12:19:51 +01:00
{
2013-01-20 12:19:42 +01:00
if ( ! FindHeader ( 0x000001B8 , " GOP " ) )
return ;
SkipBytes ( 3 ) ;
uchar b = GetByte ( ) ;
b | = 0x40 ;
SetByte ( b , GetLastIndex ( ) ) ;
2012-11-18 12:19:51 +01:00
}
2013-01-20 12:19:42 +01:00
int cMpeg2Fixer : : GetTref ( void )
2012-11-18 12:19:51 +01:00
{
2013-01-20 12:19:42 +01:00
if ( ! FindHeader ( 0x00000100 , " picture " ) )
return 0 ;
int Tref = GetByte ( ) < < 2 ;
Tref | = GetByte ( ) > > 6 ;
return Tref ;
2012-11-18 12:19:51 +01:00
}
2013-01-20 12:19:42 +01:00
void cMpeg2Fixer : : AdjGopTime ( int Offset , int FramesPerSecond )
2012-11-18 12:19:51 +01:00
{
2013-01-20 12:19:42 +01:00
if ( ! FindHeader ( 0x000001B8 , " GOP " ) )
return ;
uchar Byte1 = GetByte ( ) ;
int Index1 = GetLastIndex ( ) ;
uchar Byte2 = GetByte ( ) ;
int Index2 = GetLastIndex ( ) ;
uchar Byte3 = GetByte ( ) ;
int Index3 = GetLastIndex ( ) ;
uchar Byte4 = GetByte ( ) ;
int Index4 = GetLastIndex ( ) ;
uchar Frame = ( ( Byte3 & 0x1F ) < < 1 ) | ( Byte4 > > 7 ) ;
uchar Sec = ( ( Byte2 & 0x07 ) < < 3 ) | ( Byte3 > > 5 ) ;
uchar Min = ( ( Byte1 & 0x03 ) < < 4 ) | ( Byte2 > > 4 ) ;
uchar Hour = ( ( Byte1 & 0x7C ) > > 2 ) ;
int GopTime = ( ( Hour * 60 + Min ) * 60 + Sec ) * FramesPerSecond + Frame ;
if ( GopTime ) { // do not fix when zero
GopTime + = Offset ;
if ( GopTime < 0 )
GopTime + = 24 * 60 * 60 * FramesPerSecond ;
Frame = GopTime % FramesPerSecond ;
GopTime = GopTime / FramesPerSecond ;
Sec = GopTime % 60 ;
GopTime = GopTime / 60 ;
Min = GopTime % 60 ;
GopTime = GopTime / 60 ;
Hour = GopTime % 24 ;
SetByte ( ( Byte1 & 0x80 ) | ( Hour < < 2 ) | ( Min > > 4 ) , Index1 ) ;
SetByte ( ( ( Min & 0x0F ) < < 4 ) | 0x08 | ( Sec > > 3 ) , Index2 ) ;
SetByte ( ( ( Sec & 0x07 ) < < 3 ) | ( Frame > > 1 ) , Index3 ) ;
SetByte ( ( Byte4 & 0x7F ) | ( ( Frame & 0x01 ) < < 7 ) , Index4 ) ;
2012-11-18 12:19:51 +01:00
}
2013-01-20 12:19:42 +01:00
}
void cMpeg2Fixer : : AdjTref ( int TrefOffset )
{
2013-01-23 10:42:41 +01:00
if ( ! FindHeader ( 0x00000100 , " picture " ) )
2013-01-20 12:19:42 +01:00
return ;
int Tref = GetByte ( ) < < 2 ;
int Index1 = GetLastIndex ( ) ;
uchar Byte2 = GetByte ( ) ;
int Index2 = GetLastIndex ( ) ;
Tref | = Byte2 > > 6 ;
Tref - = TrefOffset ;
SetByte ( Tref > > 2 , Index1 ) ;
SetByte ( ( Tref < < 6 ) | ( Byte2 & 0x3F ) , Index2 ) ;
2012-11-18 12:19:51 +01:00
}
2002-06-22 10:11:59 +02:00
// --- cCuttingThread --------------------------------------------------------
class cCuttingThread : public cThread {
private :
2017-12-14 14:17:35 +01:00
cString editedVersionName ; // we add the edited version's name to Recordings only after the cutting process has successfully started, so we need to store that name here
2002-06-22 10:11:59 +02:00
const char * error ;
2009-04-19 11:07:07 +02:00
bool isPesRecording ;
2012-11-18 12:19:51 +01:00
double framesPerSecond ;
2005-10-31 13:14:26 +01:00
cUnbufferedFile * fromFile , * toFile ;
2002-06-22 10:11:59 +02:00
cFileName * fromFileName , * toFileName ;
cIndexFile * fromIndex , * toIndex ;
cMarks fromMarks , toMarks ;
2012-11-18 12:19:51 +01:00
int numSequences ;
2009-01-24 15:24:19 +01:00
off_t maxVideoFileSize ;
2012-11-18 12:19:51 +01:00
off_t fileSize ;
bool suspensionLogged ;
2013-01-20 12:19:42 +01:00
int sequence ; // cutting sequence
int delta ; // time between two frames (PTS ticks)
int64_t lastVidPts ; // the video PTS of the last frame (in display order)
bool multiFramePkt ; // multiple frames within one PES packet
int64_t firstPts ; // first valid PTS, for dangling packet stripping
int64_t offset ; // offset to add to all timestamps
int tRefOffset ; // number of stripped frames in GOP
uchar counter [ MAXPID ] ; // the TS continuity counter for each PID
bool keepPkt [ MAXPID ] ; // flag for each PID to keep packets, for dangling packet stripping
int numIFrames ; // number of I-frames without pending packets
cPatPmtParser patPmtParser ;
2012-11-18 12:19:51 +01:00
bool Throttled ( void ) ;
bool SwitchFile ( bool Force = false ) ;
bool LoadFrame ( int Index , uchar * Buffer , bool & Independent , int & Length ) ;
bool FramesAreEqual ( int Index1 , int Index2 ) ;
2013-01-20 12:19:42 +01:00
void GetPendingPackets ( uchar * Buffer , int & Length , int Index ) ;
2012-11-18 12:19:51 +01:00
// Gather all non-video TS packets from Index upward that either belong to
2013-01-20 12:19:42 +01:00
// payloads that started before Index, or have a PTS that is before lastVidPts,
2012-11-18 12:19:51 +01:00
// and add them to the end of the given Data.
2013-01-20 12:19:42 +01:00
bool FixFrame ( uchar * Data , int & Length , bool Independent , int Index , bool CutIn , bool CutOut ) ;
2012-11-18 12:19:51 +01:00
bool ProcessSequence ( int LastEndIndex , int BeginIndex , int EndIndex , int NextBeginIndex ) ;
2002-06-22 10:11:59 +02:00
protected :
virtual void Action ( void ) ;
public :
cCuttingThread ( const char * FromFileName , const char * ToFileName ) ;
virtual ~ cCuttingThread ( ) ;
const char * Error ( void ) { return error ; }
} ;
cCuttingThread : : cCuttingThread ( const char * FromFileName , const char * ToFileName )
2012-10-04 12:32:31 +02:00
: cThread ( " video cutting " , true )
2002-06-22 10:11:59 +02:00
{
error = NULL ;
2005-10-31 13:14:26 +01:00
fromFile = toFile = NULL ;
2002-06-22 10:11:59 +02:00
fromFileName = toFileName = NULL ;
fromIndex = toIndex = NULL ;
2009-01-06 14:41:11 +01:00
cRecording Recording ( FromFileName ) ;
2009-04-19 11:07:07 +02:00
isPesRecording = Recording . IsPesRecording ( ) ;
2012-11-18 12:19:51 +01:00
framesPerSecond = Recording . FramesPerSecond ( ) ;
suspensionLogged = false ;
fileSize = 0 ;
2013-01-20 12:19:42 +01:00
sequence = 0 ;
delta = int ( round ( PTSTICKS / framesPerSecond ) ) ;
lastVidPts = - 1 ;
multiFramePkt = false ;
firstPts = - 1 ;
offset = 0 ;
tRefOffset = 0 ;
memset ( counter , 0x00 , sizeof ( counter ) ) ;
numIFrames = 0 ;
2012-11-18 12:19:51 +01:00
if ( fromMarks . Load ( FromFileName , framesPerSecond , isPesRecording ) & & fromMarks . Count ( ) ) {
numSequences = fromMarks . GetNumSequences ( ) ;
if ( numSequences > 0 ) {
2017-12-14 14:17:35 +01:00
editedVersionName = ToFileName ;
2012-11-18 12:19:51 +01:00
fromFileName = new cFileName ( FromFileName , false , true , isPesRecording ) ;
toFileName = new cFileName ( ToFileName , true , true , isPesRecording ) ;
fromIndex = new cIndexFile ( FromFileName , false , isPesRecording ) ;
toIndex = new cIndexFile ( ToFileName , true , isPesRecording ) ;
toMarks . Load ( ToFileName , framesPerSecond , isPesRecording ) ; // doesn't actually load marks, just sets the file name
maxVideoFileSize = MEGABYTE ( Setup . MaxVideoFileSize ) ;
if ( isPesRecording & & maxVideoFileSize > MEGABYTE ( MAXVIDEOFILESIZEPES ) )
maxVideoFileSize = MEGABYTE ( MAXVIDEOFILESIZEPES ) ;
Start ( ) ;
}
else
esyslog ( " no editing sequences found for %s " , FromFileName ) ;
2002-06-22 10:11:59 +02:00
}
else
esyslog ( " no editing marks found for %s " , FromFileName ) ;
}
cCuttingThread : : ~ cCuttingThread ( )
{
Cancel ( 3 ) ;
delete fromFileName ;
delete toFileName ;
delete fromIndex ;
delete toIndex ;
}
2012-11-18 12:19:51 +01:00
bool cCuttingThread : : Throttled ( void )
{
if ( cIoThrottle : : Engaged ( ) ) {
if ( ! suspensionLogged ) {
dsyslog ( " suspending cutter thread " ) ;
suspensionLogged = true ;
}
return true ;
}
else if ( suspensionLogged ) {
dsyslog ( " resuming cutter thread " ) ;
suspensionLogged = false ;
}
return false ;
}
bool cCuttingThread : : LoadFrame ( int Index , uchar * Buffer , bool & Independent , int & Length )
{
uint16_t FileNumber ;
off_t FileOffset ;
if ( fromIndex - > Get ( Index , & FileNumber , & FileOffset , & Independent , & Length ) ) {
fromFile = fromFileName - > SetOffset ( FileNumber , FileOffset ) ;
if ( fromFile ) {
fromFile - > SetReadAhead ( MEGABYTE ( 20 ) ) ;
int len = ReadFrame ( fromFile , Buffer , Length , MAXFRAMESIZE ) ;
if ( len < 0 )
error = " ReadFrame " ;
else if ( len ! = Length )
Length = len ;
return error = = NULL ;
}
else
error = " fromFile " ;
}
return false ;
}
bool cCuttingThread : : SwitchFile ( bool Force )
{
if ( fileSize > maxVideoFileSize | | Force ) {
toFile = toFileName - > NextFile ( ) ;
if ( ! toFile ) {
error = " toFile " ;
return false ;
}
fileSize = 0 ;
}
return true ;
}
2013-03-18 09:54:00 +01:00
class cHeapBuffer {
private :
uchar * buffer ;
public :
cHeapBuffer ( int Size ) { buffer = MALLOC ( uchar , Size ) ; }
~ cHeapBuffer ( ) { free ( buffer ) ; }
operator uchar * ( ) { return buffer ; }
} ;
2012-11-18 12:19:51 +01:00
bool cCuttingThread : : FramesAreEqual ( int Index1 , int Index2 )
{
2013-03-18 09:54:00 +01:00
cHeapBuffer Buffer1 ( MAXFRAMESIZE ) ;
cHeapBuffer Buffer2 ( MAXFRAMESIZE ) ;
if ( ! Buffer1 | | ! Buffer2 )
return false ;
2012-11-18 12:19:51 +01:00
bool Independent ;
int Length1 ;
int Length2 ;
if ( LoadFrame ( Index1 , Buffer1 , Independent , Length1 ) & & LoadFrame ( Index2 , Buffer2 , Independent , Length2 ) ) {
if ( Length1 = = Length2 ) {
int Diffs = 0 ;
for ( int i = 0 ; i < Length1 ; i + + ) {
if ( Buffer1 [ i ] ! = Buffer2 [ i ] ) {
if ( Diffs + + > 10 ) // the continuity counters of the PAT/PMT packets may differ
return false ;
}
}
return true ;
}
}
return false ;
}
2013-01-20 12:19:42 +01:00
void cCuttingThread : : GetPendingPackets ( uchar * Data , int & Length , int Index )
2012-11-18 12:19:51 +01:00
{
2013-03-18 09:54:00 +01:00
cHeapBuffer Buffer ( MAXFRAMESIZE ) ;
if ( ! Buffer )
return ;
2012-11-18 12:19:51 +01:00
bool Processed [ MAXPID ] = { false } ;
cPacketStorage PacketStorage ;
2013-01-20 12:19:42 +01:00
int64_t LastPts = lastVidPts + delta ; // adding one frame length to fully cover the very last frame
Processed [ patPmtParser . Vpid ( ) ] = true ; // we only want non-video packets
for ( int NumIndependentFrames = 0 ; NumIndependentFrames < 2 ; Index + + ) {
2012-11-18 12:19:51 +01:00
bool Independent ;
int len ;
if ( LoadFrame ( Index , Buffer , Independent , len ) ) {
if ( Independent )
NumIndependentFrames + + ;
2013-01-20 12:19:42 +01:00
for ( uchar * p = Buffer ; len > = TS_SIZE & & * p = = TS_SYNC_BYTE ; len - = TS_SIZE , p + = TS_SIZE ) {
int Pid = TsPid ( p ) ;
if ( Pid ! = PATPID & & ! patPmtParser . IsPmtPid ( Pid ) & & ! Processed [ Pid ] ) {
int64_t Pts = TsGetPts ( p , TS_SIZE ) ;
if ( Pts > = 0 ) {
int64_t d = PtsDiff ( LastPts , Pts ) ;
if ( d < 0 ) // Pts is before LastPts
PacketStorage . Flush ( Pid , Data , Length , MAXFRAMESIZE ) ;
else { // Pts is at or after LastPts
NumIndependentFrames = 0 ; // we search until we find two consecutive I-frames without any more pending packets
Processed [ Pid ] = true ;
}
}
if ( ! Processed [ Pid ] )
PacketStorage . Append ( Pid , p , TS_SIZE ) ;
}
}
2012-11-18 12:19:51 +01:00
}
else
break ;
}
}
2013-01-20 12:19:42 +01:00
bool cCuttingThread : : FixFrame ( uchar * Data , int & Length , bool Independent , int Index , bool CutIn , bool CutOut )
{
if ( ! patPmtParser . Vpid ( ) ) {
if ( ! patPmtParser . ParsePatPmt ( Data , Length ) )
return false ;
}
if ( CutIn ) {
sequence + + ;
memset ( keepPkt , false , sizeof ( keepPkt ) ) ;
numIFrames = 0 ;
firstPts = TsGetPts ( Data , Length ) ;
// Determine the PTS offset at the beginning of each sequence (except the first one):
if ( sequence ! = 1 ) {
if ( firstPts > = 0 )
offset = ( lastVidPts + delta - firstPts ) & MAX33BIT ;
}
}
if ( CutOut )
GetPendingPackets ( Data , Length , Index + 1 ) ;
if ( Independent ) {
numIFrames + + ;
tRefOffset = 0 ;
}
// Fix MPEG-2:
if ( patPmtParser . Vtype ( ) = = 2 ) {
cMpeg2Fixer Mpeg2fixer ( Data , Length , patPmtParser . Vpid ( ) ) ;
if ( CutIn ) {
if ( sequence = = 1 | | multiFramePkt )
Mpeg2fixer . SetBrokenLink ( ) ;
else {
Mpeg2fixer . SetClosedGop ( ) ;
tRefOffset = Mpeg2fixer . GetTref ( ) ;
}
}
if ( tRefOffset )
Mpeg2fixer . AdjTref ( tRefOffset ) ;
if ( sequence > 1 & & Independent )
Mpeg2fixer . AdjGopTime ( ( offset - MAX33BIT ) / delta , round ( framesPerSecond ) ) ;
}
bool DeletedFrame = false ;
bool GotVidPts = false ;
bool StripVideo = sequence > 1 & & tRefOffset ;
uchar * p ;
int len ;
for ( p = Data , len = Length ; len > = TS_SIZE & & * p = = TS_SYNC_BYTE ; p + = TS_SIZE , len - = TS_SIZE ) {
int Pid = TsPid ( p ) ;
// Strip dangling packets:
if ( numIFrames < 2 & & Pid ! = PATPID & & ! patPmtParser . IsPmtPid ( Pid ) ) {
if ( Pid ! = patPmtParser . Vpid ( ) | | StripVideo ) {
int64_t Pts = TsGetPts ( p , TS_SIZE ) ;
if ( Pts > = 0 )
keepPkt [ Pid ] = PtsDiff ( firstPts , Pts ) > = 0 ; // Pts is at or after FirstPts
if ( ! keepPkt [ Pid ] ) {
TsHidePayload ( p ) ;
numIFrames = 0 ; // we search until we find two consecutive I-frames without any more dangling packets
if ( Pid = = patPmtParser . Vpid ( ) )
DeletedFrame = true ;
}
}
}
// Adjust the TS continuity counter:
if ( sequence > 1 ) {
if ( TsHasPayload ( p ) )
counter [ Pid ] = ( counter [ Pid ] + 1 ) & TS_CONT_CNT_MASK ;
TsSetContinuityCounter ( p , counter [ Pid ] ) ;
}
else
2017-05-21 09:53:27 +02:00
counter [ Pid ] = TsContinuityCounter ( p ) ; // collect initial counters
2013-01-20 12:19:42 +01:00
// Adjust PTS:
int64_t Pts = TsGetPts ( p , TS_SIZE ) ;
if ( Pts > = 0 ) {
if ( sequence > 1 ) {
Pts = PtsAdd ( Pts , offset ) ;
TsSetPts ( p , TS_SIZE , Pts ) ;
}
// Keep track of the highest video PTS - in case of multiple fields per frame, take the first one
if ( ! GotVidPts & & Pid = = patPmtParser . Vpid ( ) ) {
GotVidPts = true ;
if ( lastVidPts < 0 | | PtsDiff ( lastVidPts , Pts ) > 0 )
lastVidPts = Pts ;
}
}
// Adjust DTS:
if ( sequence > 1 ) {
int64_t Dts = TsGetDts ( p , TS_SIZE ) ;
if ( Dts > = 0 ) {
Dts = PtsAdd ( Dts , offset ) ;
if ( CutIn )
Dts = PtsAdd ( Dts , tRefOffset * delta ) ;
TsSetDts ( p , TS_SIZE , Dts ) ;
}
int64_t Pcr = TsGetPcr ( p ) ;
if ( Pcr > = 0 ) {
Pcr = Pcr + offset * PCRFACTOR ;
if ( Pcr > MAX27MHZ )
Pcr - = MAX27MHZ + 1 ;
TsSetPcr ( p , Pcr ) ;
}
}
}
if ( ! DeletedFrame & & ! GotVidPts ) {
// Adjust PTS for multiple frames within a single PES packet:
lastVidPts = ( lastVidPts + delta ) & MAX33BIT ;
multiFramePkt = true ;
}
return DeletedFrame ;
}
2012-11-18 12:19:51 +01:00
bool cCuttingThread : : ProcessSequence ( int LastEndIndex , int BeginIndex , int EndIndex , int NextBeginIndex )
{
// Check for seamless connections:
bool SeamlessBegin = LastEndIndex > = 0 & & FramesAreEqual ( LastEndIndex , BeginIndex ) ;
bool SeamlessEnd = NextBeginIndex > = 0 & & FramesAreEqual ( EndIndex , NextBeginIndex ) ;
// Process all frames from BeginIndex (included) to EndIndex (excluded):
2013-03-18 09:54:00 +01:00
cHeapBuffer Buffer ( MAXFRAMESIZE ) ;
if ( ! Buffer ) {
error = " malloc " ;
return false ;
}
2012-11-18 12:19:51 +01:00
for ( int Index = BeginIndex ; Running ( ) & & Index < EndIndex ; Index + + ) {
bool Independent ;
int Length ;
if ( LoadFrame ( Index , Buffer , Independent , Length ) ) {
2013-08-21 13:22:19 +02:00
// Make sure there is enough disk space:
AssertFreeDiskSpace ( - 1 ) ;
2013-01-20 12:19:42 +01:00
bool CutIn = ! SeamlessBegin & & Index = = BeginIndex ;
bool CutOut = ! SeamlessEnd & & Index = = EndIndex - 1 ;
bool DeletedFrame = false ;
2012-11-18 12:19:51 +01:00
if ( ! isPesRecording ) {
2013-01-20 12:19:42 +01:00
DeletedFrame = FixFrame ( Buffer , Length , Independent , Index , CutIn , CutOut ) ;
2012-11-18 12:19:51 +01:00
}
2013-01-20 12:19:42 +01:00
else if ( CutIn )
cRemux : : SetBrokenLink ( Buffer , Length ) ;
2012-11-18 12:19:51 +01:00
// Every file shall start with an independent frame:
if ( Independent ) {
if ( ! SwitchFile ( ) )
return false ;
}
// Write index:
2013-01-20 12:19:42 +01:00
if ( ! DeletedFrame & & ! toIndex - > Write ( Independent , toFileName - > Number ( ) , fileSize ) ) {
2012-11-18 12:19:51 +01:00
error = " toIndex " ;
return false ;
}
// Write data:
if ( toFile - > Write ( Buffer , Length ) < 0 ) {
error = " safe_write " ;
return false ;
}
fileSize + = Length ;
// Generate marks at the editing points in the edited recording:
2013-05-02 09:17:55 +02:00
if ( numSequences > 1 & & Index = = BeginIndex ) {
2012-11-18 12:19:51 +01:00
if ( toMarks . Count ( ) > 0 )
toMarks . Add ( toIndex - > Last ( ) ) ;
toMarks . Add ( toIndex - > Last ( ) ) ;
toMarks . Save ( ) ;
}
}
else
return false ;
}
return true ;
}
2002-06-22 10:11:59 +02:00
void cCuttingThread : : Action ( void )
{
2012-11-18 12:19:51 +01:00
if ( cMark * BeginMark = fromMarks . GetNextBegin ( ) ) {
2002-06-22 10:11:59 +02:00
fromFile = fromFileName - > Open ( ) ;
toFile = toFileName - > Open ( ) ;
2005-10-31 13:14:26 +01:00
if ( ! fromFile | | ! toFile )
2005-08-13 13:17:24 +02:00
return ;
2017-12-14 14:17:35 +01:00
{
LOCK_RECORDINGS_WRITE ;
Recordings - > AddByName ( editedVersionName , false ) ;
}
2012-11-18 12:19:51 +01:00
int LastEndIndex = - 1 ;
while ( BeginMark & & Running ( ) ) {
2012-09-22 11:52:33 +02:00
// Suspend cutting if we have severe throughput problems:
2012-11-18 12:19:51 +01:00
if ( Throttled ( ) ) {
2012-09-22 11:52:33 +02:00
cCondWait : : SleepMs ( 100 ) ;
continue ;
}
2012-11-18 12:19:51 +01:00
// Determine the actual begin and end marks, skipping any marks at the same position:
cMark * EndMark = fromMarks . GetNextEnd ( BeginMark ) ;
// Process the current sequence:
int EndIndex = EndMark ? EndMark - > Position ( ) : fromIndex - > Last ( ) + 1 ;
int NextBeginIndex = - 1 ;
if ( EndMark ) {
if ( cMark * NextBeginMark = fromMarks . GetNextBegin ( EndMark ) )
NextBeginIndex = NextBeginMark - > Position ( ) ;
2002-06-22 10:11:59 +02:00
}
2012-11-18 12:19:51 +01:00
if ( ! ProcessSequence ( LastEndIndex , BeginMark - > Position ( ) , EndIndex , NextBeginIndex ) )
2002-06-22 10:11:59 +02:00
break ;
2012-11-18 12:19:51 +01:00
if ( ! EndMark )
break ; // reached EOF
LastEndIndex = EndIndex ;
// Switch to the next sequence:
BeginMark = fromMarks . GetNextBegin ( EndMark ) ;
if ( BeginMark ) {
// Split edited files:
if ( Setup . SplitEditedFiles ) {
if ( ! SwitchFile ( true ) )
2002-06-22 10:11:59 +02:00
break ;
2003-04-26 15:11:17 +02:00
}
2002-06-22 10:11:59 +02:00
}
}
}
else
esyslog ( " no editing marks found! " ) ;
}
// --- cCutter ---------------------------------------------------------------
2013-10-10 13:13:30 +02:00
cCutter : : cCutter ( const char * FileName )
{
cuttingThread = NULL ;
error = false ;
originalVersionName = FileName ;
}
2002-06-22 10:11:59 +02:00
2013-10-10 13:13:30 +02:00
cCutter : : ~ cCutter ( )
2002-06-22 10:11:59 +02:00
{
2013-10-10 13:13:30 +02:00
Stop ( ) ;
}
2011-08-20 10:09:05 +02:00
2013-10-10 13:13:30 +02:00
cString cCutter : : EditedFileName ( const char * FileName )
{
cRecording Recording ( FileName ) ;
cMarks Marks ;
if ( Marks . Load ( FileName , Recording . FramesPerSecond ( ) , Recording . IsPesRecording ( ) ) ) {
if ( cMark * First = Marks . GetNextBegin ( ) )
2011-08-21 11:34:30 +02:00
Recording . SetStartTime ( Recording . Start ( ) + ( int ( First - > Position ( ) / Recording . FramesPerSecond ( ) + 30 ) / 60 ) * 60 ) ;
2013-10-10 13:13:30 +02:00
return Recording . PrefixFileName ( ' % ' ) ;
}
return NULL ;
}
2011-08-20 10:09:05 +02:00
2013-10-10 13:13:30 +02:00
bool cCutter : : Start ( void )
{
if ( ! cuttingThread ) {
error = false ;
if ( * originalVersionName ) {
cRecording Recording ( originalVersionName ) ;
editedVersionName = EditedFileName ( originalVersionName ) ;
if ( * editedVersionName ) {
if ( strcmp ( originalVersionName , editedVersionName ) ! = 0 ) { // names must be different!
2015-04-11 12:12:43 +02:00
cRecordingUserCommand : : InvokeCommand ( RUC_EDITINGRECORDING , editedVersionName , originalVersionName ) ;
2013-10-10 13:13:30 +02:00
if ( cVideoDirectory : : RemoveVideoFile ( editedVersionName ) & & MakeDirs ( editedVersionName , true ) ) {
Recording . WriteInfo ( editedVersionName ) ;
cuttingThread = new cCuttingThread ( originalVersionName , editedVersionName ) ;
return true ;
}
}
}
2002-06-22 10:11:59 +02:00
}
}
return false ;
}
void cCutter : : Stop ( void )
{
2005-08-14 11:24:57 +02:00
bool Interrupted = cuttingThread & & cuttingThread - > Active ( ) ;
2002-06-22 10:11:59 +02:00
const char * Error = cuttingThread ? cuttingThread - > Error ( ) : NULL ;
delete cuttingThread ;
cuttingThread = NULL ;
2012-02-16 12:20:46 +01:00
if ( ( Interrupted | | Error ) & & * editedVersionName ) {
2002-06-22 10:11:59 +02:00
if ( Interrupted )
isyslog ( " editing process has been interrupted " ) ;
if ( Error )
esyslog ( " ERROR: '%s' during editing process " , Error ) ;
2012-02-16 12:20:46 +01:00
if ( cReplayControl : : NowReplaying ( ) & & strcmp ( cReplayControl : : NowReplaying ( ) , editedVersionName ) = = 0 )
cControl : : Shutdown ( ) ;
2013-09-11 12:20:37 +02:00
cVideoDirectory : : RemoveVideoFile ( editedVersionName ) ;
2015-09-01 11:14:27 +02:00
LOCK_RECORDINGS_WRITE ;
Recordings - > DelByName ( editedVersionName ) ;
2002-06-22 10:11:59 +02:00
}
}
2013-10-10 13:13:30 +02:00
bool cCutter : : Active ( void )
2002-06-22 10:11:59 +02:00
{
if ( cuttingThread ) {
2005-08-14 11:24:57 +02:00
if ( cuttingThread - > Active ( ) )
2013-10-10 13:13:30 +02:00
return true ;
2002-06-22 10:11:59 +02:00
error = cuttingThread - > Error ( ) ;
Stop ( ) ;
if ( ! error )
2012-06-02 13:57:41 +02:00
cRecordingUserCommand : : InvokeCommand ( RUC_EDITEDRECORDING , editedVersionName , originalVersionName ) ;
2002-06-22 10:11:59 +02:00
}
return false ;
}
bool cCutter : : Error ( void )
{
2013-10-10 13:13:30 +02:00
return error ;
2002-06-22 10:11:59 +02:00
}
2010-01-02 14:02:48 +01:00
# define CUTTINGCHECKINTERVAL 500 // ms between checks for the active cutting process
bool CutRecording ( const char * FileName )
{
if ( DirectoryOk ( FileName ) ) {
cRecording Recording ( FileName ) ;
if ( Recording . Name ( ) ) {
cMarks Marks ;
if ( Marks . Load ( FileName , Recording . FramesPerSecond ( ) , Recording . IsPesRecording ( ) ) & & Marks . Count ( ) ) {
2012-11-18 12:19:51 +01:00
if ( Marks . GetNumSequences ( ) ) {
2013-10-10 13:13:30 +02:00
cCutter Cutter ( FileName ) ;
if ( Cutter . Start ( ) ) {
while ( Cutter . Active ( ) )
2012-11-18 12:19:51 +01:00
cCondWait : : SleepMs ( CUTTINGCHECKINTERVAL ) ;
2013-10-10 13:13:30 +02:00
if ( ! Cutter . Error ( ) )
return true ;
fprintf ( stderr , " error while cutting \n " ) ;
2012-11-18 12:19:51 +01:00
}
else
fprintf ( stderr , " can't start editing process \n " ) ;
2010-01-02 14:02:48 +01:00
}
else
2012-11-18 12:19:51 +01:00
fprintf ( stderr , " '%s' has no editing sequences \n " , FileName ) ;
2010-01-02 14:02:48 +01:00
}
else
fprintf ( stderr , " '%s' has no editing marks \n " , FileName ) ;
}
else
fprintf ( stderr , " '%s' is not a recording \n " , FileName ) ;
}
else
fprintf ( stderr , " '%s' is not a directory \n " , FileName ) ;
return false ;
}