/*
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#include
#include
#include
#include
#include
#include "hb.h"
struct start_and_end {
unsigned long start, end;
};
enum
{
k_state_inEntry,
k_state_potential_new_entry,
k_state_timecode,
};
typedef struct srt_entry_s {
long offset, duration;
long start, stop;
char text[1024];
} srt_entry_t;
/*
* Store all context in the work private struct,
*/
struct hb_work_private_s
{
hb_job_t *job;
FILE *file;
unsigned long current_time;
unsigned long number_of_entries;
unsigned long current_state;
srt_entry_t current_entry;
iconv_t *iconv_context;
hb_subtitle_t *subtitle;
uint64_t start_time; // In HB time
uint64_t stop_time; // In HB time
};
static struct start_and_end read_time_from_string( const char* timeString )
{
// for ex. 00:00:15,248 --> 00:00:16,545
long houres1, minutes1, seconds1, milliseconds1,
houres2, minutes2, seconds2, milliseconds2;
sscanf(timeString, "%ld:%ld:%ld,%ld --> %ld:%ld:%ld,%ld\n", &houres1, &minutes1, &seconds1, &milliseconds1,
&houres2, &minutes2, &seconds2, &milliseconds2);
struct start_and_end result = {
milliseconds1 + seconds1*1000 + minutes1*60*1000 + houres1*60*60*1000,
milliseconds2 + seconds2*1000 + minutes2*60*1000 + houres2*60*60*1000};
return result;
}
/*
* Read the SRT file and put the entries into the subtitle fifo for all to read
*/
static hb_buffer_t *srt_read( hb_work_private_t *pv )
{
char line_buffer[1024];
if( !pv->file )
{
return NULL;
}
while( fgets( line_buffer, sizeof( line_buffer ), pv->file ) )
{
switch (pv->current_state)
{
case k_state_timecode:
{
struct start_and_end timing = read_time_from_string( line_buffer );
pv->current_entry.duration = timing.end - timing.start;
pv->current_entry.offset = timing.start - pv->current_time;
pv->current_time = timing.end;
pv->current_entry.start = timing.start;
pv->current_entry.stop = timing.end;
pv->current_state = k_state_inEntry;
continue;
}
case k_state_inEntry:
{
char *p, *q;
size_t in_size;
size_t out_size;
size_t retval;
// If the current line is empty, we assume this is the
// seperation betwene two entries. In case we are wrong,
// the mistake is corrected in the next state.
if (strcmp(line_buffer, "\n") == 0 || strcmp(line_buffer, "\r\n") == 0) {
pv->current_state = k_state_potential_new_entry;
continue;
}
for( q = pv->current_entry.text; (q < pv->current_entry.text+1024) && *q; q++);
p = line_buffer;
in_size = strlen(line_buffer);
out_size = (pv->current_entry.text+1024) - q;
retval = iconv( pv->iconv_context, &p, &in_size, &q, &out_size);
*q = '\0';
if( ( retval == -1 ) && ( errno == EINVAL ) )
{
hb_error( "Invalid shift sequence" );
} else if ( ( retval == -1 ) && ( errno == EILSEQ ) )
{
hb_error( "Invalid byte for codeset in input, %"PRId64" bytes discarded", (int64_t)in_size);
} else if ( ( retval == -1 ) && ( errno == E2BIG ) )
{
hb_error( "Not enough space in output buffer");
}
break;
}
case k_state_potential_new_entry:
{
const char endpoint[] = "\0";
const unsigned long potential_entry_number = strtol(line_buffer, (char**)&endpoint, 10);
hb_buffer_t *buffer = NULL;
/*
* Is this really new next entry begin?
*/
if (potential_entry_number == pv->number_of_entries + 1) {
/*
* We found the next entry - or a really rare error condition
*/
if( *pv->current_entry.text )
{
long length;
char *p;
uint64_t start_time = ( pv->current_entry.start +
pv->subtitle->config.offset ) * 90;
uint64_t stop_time = ( pv->current_entry.stop +
pv->subtitle->config.offset ) * 90;
if( !( start_time > pv->start_time && stop_time < pv->stop_time ) )
{
hb_deep_log( 3, "Discarding SRT at time start %"PRId64", stop %"PRId64, start_time, stop_time);
memset( &pv->current_entry, 0, sizeof( srt_entry_t ) );
++(pv->number_of_entries);
pv->current_state = k_state_timecode;
continue;
}
length = strlen( pv->current_entry.text );
for( p = pv->current_entry.text; *p; p++)
{
if( *p == '\n' || *p == '\r' )
{
*p = ' ';
}
}
buffer = hb_buffer_init( length + 1 );
if( buffer )
{
buffer->start = start_time - pv->start_time;
buffer->stop = stop_time - pv->start_time;
memcpy( buffer->data, pv->current_entry.text, length + 1 );
}
}
memset( &pv->current_entry, 0, sizeof( srt_entry_t ) );
++(pv->number_of_entries);
pv->current_state = k_state_timecode;
if( buffer )
{
return buffer;
}
continue;
} else {
/*
* Well.. looks like we are in the wrong mode.. lets add the
* newline we misinterpreted...
*/
strncat(pv->current_entry.text, " ", 1024);
pv->current_state = k_state_inEntry;
}
break;
}
}
}
return NULL;
}
static int decsrtInit( hb_work_object_t * w, hb_job_t * job )
{
int retval = 1;
hb_work_private_t * pv;
hb_buffer_t *buffer;
int i;
hb_chapter_t * chapter;
hb_title_t *title = job->title;
pv = calloc( 1, sizeof( hb_work_private_t ) );
if( pv )
{
w->private_data = pv;
pv->job = job;
buffer = hb_buffer_init( 0 );
hb_fifo_push( w->fifo_in, buffer);
pv->file = fopen( w->subtitle->config.src_filename, "r" );
pv->current_state = k_state_potential_new_entry;
pv->number_of_entries = 0;
pv->current_time = 0;
pv->subtitle = w->subtitle;
/*
* Figure out the start and stop times from teh chapters being
* encoded - drop subtitle not in this range.
*/
pv->start_time = 0;
for( i = 1; i < job->chapter_start; ++i )
{
chapter = hb_list_item( title->list_chapter, i - 1 );
if( chapter )
{
pv->start_time += chapter->duration;
} else {
hb_error( "Could not locate chapter %d for SRT start time", i );
retval = 0;
}
}
chapter = hb_list_item( title->list_chapter, i - 1 );
if( chapter )
{
pv->stop_time = pv->start_time + chapter->duration;
} else {
hb_error( "Could not locate chapter %d for SRT stop time", i );
retval = 0;
}
hb_deep_log( 3, "SRT Start time %"PRId64", stop time %"PRId64, pv->start_time, pv->stop_time);
pv->iconv_context = iconv_open( "utf-8", pv->subtitle->config.src_codeset );
if( pv->iconv_context == (iconv_t) -1 )
{
hb_error("Could not open the iconv library with those file formats\n");
} else {
memset( &pv->current_entry, 0, sizeof( srt_entry_t ) );
pv->file = fopen( w->subtitle->config.src_filename, "r" );
if( !pv->file )
{
hb_error("Could not open the SRT subtitle file '%s'\n",
w->subtitle->config.src_filename);
} else {
retval = 0;
}
}
}
return retval;
}
static int decsrtWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
hb_buffer_t ** buf_out )
{
hb_work_private_t * pv = w->private_data;
hb_buffer_t * in = *buf_in;
hb_buffer_t * out = NULL;
out = srt_read( pv );
if( out )
{
/*
* Keep a buffer in our input fifo so that we get run.
*/
hb_fifo_push( w->fifo_in, in);
*buf_in = NULL;
*buf_out = out;
} else {
*buf_out = NULL;
return HB_WORK_OK;
}
return HB_WORK_OK;
}
static void decsrtClose( hb_work_object_t * w )
{
hb_work_private_t * pv = w->private_data;
fclose( pv->file );
iconv_close(pv->iconv_context);
free( w->private_data );
}
hb_work_object_t hb_decsrtsub =
{
WORK_DECSRTSUB,
"SRT Subtitle Decoder",
decsrtInit,
decsrtWork,
decsrtClose
};