#include <unistd.h>
#include <inttypes.h>
-#ifdef PTW32_STATIC_LIB
+#if defined( __MINGW32__ )
+#include <conio.h>
+#endif
+
+#if defined( PTW32_STATIC_LIB )
#include <pthread.h>
#endif
#include "hb.h"
#include "parsecsv.h"
-#ifdef __APPLE_CC__
+#if defined( __APPLE_CC__ )
#import <CoreServices/CoreServices.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOMedia.h>
static int titleindex = 1;
static int longest_title = 0;
static char * native_language = NULL;
+static int native_dub = 0;
static int twoPass = 0;
static int deinterlace = 0;
static char * deinterlace_opt = 0;
static char * anames = NULL;
static int default_acodec = HB_ACODEC_FAAC;
static int default_abitrate = 160;
-static char * subtracks = NULL;
-static char * subforce = NULL;
+static int audio_explicit = 0;
+static char ** subtracks = NULL;
+static char ** subforce = NULL;
static char * subburn = NULL;
static char * subdefault = NULL;
+static char ** srtfile = NULL;
+static char ** srtcodeset = NULL;
+static char ** srtoffset = NULL;
+static char ** srtlang = NULL;
static int subtitle_scan = 0;
static int width = 0;
static int height = 0;
static int device_is_dvd(char *device);
static io_service_t get_iokit_service( char *device );
static int is_dvd_service( io_service_t service );
-static is_whole_media_service( io_service_t service );
+static int is_whole_media_service( io_service_t service );
#endif
/* Only print the "Muxing..." message once */
return 1;
}
-#ifdef PTW32_STATIC_LIB
+#if defined( PTW32_STATIC_LIB )
pthread_win32_process_attach_np();
pthread_win32_thread_attach_np();
#endif
/* Wait... */
while( !die )
{
-#if !defined(SYS_BEOS) && !defined(__MINGW32__)
+#if defined( __MINGW32__ )
+ if( _kbhit() ) {
+ switch( _getch() )
+ {
+ case 0x03: /* ctrl-c */
+ case 'q':
+ fprintf( stdout, "\nEncoding Quit by user command\n" );
+ die = 1;
+ break;
+ case 'p':
+ fprintf( stdout, "\nEncoding Paused by user command, 'r' to resume\n" );
+ hb_pause( h );
+ break;
+ case 'r':
+ hb_resume( h );
+ break;
+ case 'h':
+ ShowCommands();
+ break;
+ }
+ }
+ hb_snooze( 200 );
+#elif !defined(SYS_BEOS)
fd_set fds;
struct timeval tv;
int ret;
fprintf( stderr, "HandBrake has exited.\n" );
-#ifdef PTW32_STATIC_LIB
+#if defined( PTW32_STATIC_LIB )
pthread_win32_thread_detach_np();
pthread_win32_process_detach_np();
#endif
static void PrintTitleInfo( hb_title_t * title )
{
hb_chapter_t * chapter;
- hb_audio_config_t * audio;
hb_subtitle_t * subtitle;
int i;
}
-static int search_csv( const char * list, char * needle )
+static int test_sub_list( char ** list, char * needle, int pos )
{
- char * token;
- char * copy_list;
- int pos = 1;
+ int i;
if ( list == NULL || needle == NULL )
return 0;
- copy_list = strdup( list );
- token = strtok(copy_list, ",");
- if (token == NULL)
- token = copy_list;
- while( token != NULL )
+ if ( list[0] == NULL && pos == 1 )
+ return 1;
+
+ for ( i = 0; list[i] != NULL; i++ )
{
- if( !strcasecmp(token, needle ) )
- {
- free( copy_list );
- return pos;
- }
- pos++;
- token = strtok(NULL, ",");
+ if ( strcasecmp( list[i], needle ) == 0 )
+ return i + 1;
}
- free( copy_list );
return 0;
}
-static int test_sub_list( const char * list, char * needle, int pos )
-{
- if ( list == NULL || needle == NULL )
- return 0;
-
- if ( list[0] == '\0' && pos == 1 )
- return 1;
-
- return search_csv( list, needle );
-}
-
static int HandleEvents( hb_handle_t * h )
{
hb_state_t s;
}
/* Parse audio tracks */
- if( hb_list_count(audios) == 0 )
+ if( native_language && native_dub )
{
+ if( hb_list_count( audios ) == 0 || !audio_explicit )
+ {
+ for( i = 0; i < hb_list_count( title->list_audio ); i++ )
+ {
+ char audio_lang[4];
+ int track = i;
+
+ audio = hb_list_audio_config_item( title->list_audio, i );
+
+ strncpy( audio_lang, audio->lang.iso639_2, sizeof( audio_lang ) );
+
+ if( strncasecmp( native_language, audio_lang,
+ sizeof( audio_lang ) ) == 0 &&
+ audio->lang.type != 3 && // Directors 1
+ audio->lang.type != 4) // Directors 2
+ {
+ /*
+ * Matched an audio to our native language - use it.
+ * Replace any existing audio tracks that a preset may
+ * have put here.
+ */
+ if( hb_list_count(audios) == 0) {
+ audio = calloc(1, sizeof(*audio));
+ hb_audio_config_init(audio);
+ audio->in.track = track;
+ audio->out.track = num_audio_tracks++;
+ /* Add it to our audios */
+ hb_list_add(audios, audio);
+ } else {
+ /*
+ * Update the track numbers on what is already in
+ * there.
+ */
+ for( i=0; i < hb_list_count( audios ); i++ )
+ {
+ audio = hb_list_item( audios, i );
+
+ audio->in.track = track;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ fprintf( stderr, "Warning: Native language (dubbing) selection ignored since an audio track has already been selected\n");
+ }
+ }
+
+ if( hb_list_count(audios) == 0 )
+ {
/* Create a new audio track with default settings */
audio = calloc(1, sizeof(*audio));
hb_audio_config_init(audio);
if( subtracks )
{
- int pos;
-
- pos = search_csv(subtracks, "scan");
- if ( pos )
- {
- int burn, force, def;
-
- burn = test_sub_list(subburn, "scan", pos);
- force = test_sub_list(subforce, "scan", pos);
- def = test_sub_list(subdefault, "scan", pos);
-
- if ( !burn && mux == HB_MUX_MKV )
- {
- job->select_subtitle_config.dest = PASSTHRUSUB;
- }
- if ( !( !burn && mux == HB_MUX_MP4 ) )
- {
- job->select_subtitle_config.force = force;
- job->select_subtitle_config.default_track = def;
- subtitle_scan = 1;
- sub_burned = burn;
- }
- }
+ char * token;
+ int i, pos;
- char * save;
- char * token = strtok_r(subtracks, ",", &save);
- if (token == NULL)
- token = subtracks;
pos = 0;
- while( token != NULL )
+ for ( i = 0; subtracks[i] != NULL; i++ )
{
pos++;
- if( !strcasecmp(token, "scan" ) )
+ token = subtracks[i];
+ if( strcasecmp(token, "scan" ) == 0 )
{
- token = strtok_r(NULL, ",", &save);
- continue;
+ int burn = 0, force = 0, def = 0;
+
+ if ( subburn != NULL )
+ {
+ burn = ( pos == 1 && subburn[0] == 0 ) ||
+ ( strcmp( "scan", subburn ) == 0 );
+ }
+ if ( subdefault != NULL )
+ {
+ def = ( pos == 1 && subdefault[0] == 0 ) ||
+ ( strcmp( "scan", subdefault ) == 0 );
+ }
+ force = test_sub_list( subforce, "scan", pos );
+
+ if ( !burn && mux == HB_MUX_MKV )
+ {
+ job->select_subtitle_config.dest = PASSTHRUSUB;
+ }
+ else if ( burn )
+ {
+ if ( sub_burned )
+ {
+ continue;
+ }
+ sub_burned = 1;
+ }
+ if ( !( !burn && mux == HB_MUX_MP4 ) )
+ {
+ job->select_subtitle_config.force = force;
+ job->select_subtitle_config.default_track = def;
+ subtitle_scan = 1;
+ } else {
+ fprintf( stderr, "Warning: Subtitle Scan for MP4 requires the '--subtitle-burn' option to be selected\n");
+ }
}
else
{
hb_subtitle_t * subtitle;
hb_subtitle_config_t sub_config;
int track;
- int burn, force, def;
+ int burn = 0, force = 0, def = 0;
track = atoi(token) - 1;
subtitle = hb_list_item(title->list_subtitle, track);
if( subtitle == NULL )
{
- fprintf( stderr, "Could not find subtitle track %d, skipped\n", track );
- token = strtok_r(NULL, ",", &save);
+ fprintf( stderr, "Warning: Could not find subtitle track %d, skipped\n", track+1 );
continue;
}
sub_config = subtitle->config;
- burn = test_sub_list(subburn, token, pos);
+ if ( subburn != NULL )
+ {
+ burn = ( pos == 1 && subburn[0] == 0 ) ||
+ ( strcmp( token, subburn ) == 0 );
+ }
+ if ( subdefault != NULL )
+ {
+ def = ( pos == 1 && subdefault[0] == 0 ) ||
+ ( strcmp( token, subdefault ) == 0 );
+ }
+
force = test_sub_list(subforce, token, pos);
- def = test_sub_list(subdefault, token, pos);
if ( !burn && mux == HB_MUX_MKV &&
subtitle->format == PICTURESUB)
subtitle->format == PICTURESUB)
{
// Skip any non-burned vobsubs when output is mp4
- token = strtok_r(NULL, ",", &save);
+ fprintf( stderr, "Warning: Skipping subtitle track %d, can't pass-through VOBSUBs in an MP4 container,\nadd '--subtitle-burn %d' to the command line\n", track+1, track+1 );
continue;
}
else if ( burn && subtitle->format == PICTURESUB )
// Only allow one subtitle to be burned into video
if ( sub_burned )
{
- token = strtok_r(NULL, ",", &save);
+ fprintf( stderr, "Warning: Skipping subtitle track %d, can't have more than one track burnt in\n", track+1 );
continue;
}
sub_burned = 1;
}
-
sub_config.force = force;
sub_config.default_track = def;
hb_subtitle_add( job, &sub_config, track );
}
- token = strtok_r(NULL, ",", &save);
}
}
- if( native_language )
+ if( srtfile )
{
- job->native_language = strdup( native_language );
- if ( subtracks == NULL )
+ char * token;
+ int i, pos;
+ hb_subtitle_config_t sub_config;
+
+ pos = 0;
+ for( i=0; srtfile[i] != NULL; i++ )
{
- if ( subforce )
- job->select_subtitle_config.force = 1;
+ char *codeset = "L1";
+ int64_t offset = 0;
+ char *lang = "und";
+
+ pos++;
+ token = srtfile[i];
+ if( srtcodeset && srtcodeset[i] )
+ {
+ codeset = srtcodeset[i];
+ }
+ if( srtoffset && srtoffset[i] )
+ {
+ offset = strtoll( srtoffset[i], &srtoffset[i], 0 );
+ }
+ if ( srtlang && srtlang[i] )
+ {
+ lang = srtlang[i];
+ }
+ sub_config.force = 0;
+ sub_config.default_track = 0;
+ strncpy( sub_config.src_filename, srtfile[i], 128);
+ strncpy( sub_config.src_codeset, codeset, 40);
+ sub_config.offset = offset;
- if ( subburn == NULL && mux != HB_MUX_MP4 )
- job->select_subtitle_config.force = 1;
+ hb_srt_add( job, &sub_config, lang);
+ }
+ }
+
+ if( native_language )
+ {
+ char audio_lang[4];
+
+ audio = hb_list_audio_config_item(job->list_audio, 0);
+
+ if( audio )
+ {
+ strncpy( audio_lang, audio->lang.iso639_2, sizeof( audio_lang ) );
+
+ if( strncasecmp( native_language, audio_lang,
+ sizeof( audio_lang ) ) != 0 )
+ {
+ /*
+ * Audio language is not the same as our native language.
+ * If we have any subtitles in our native language they
+ * should be selected here if they haven't already been.
+ */
+ hb_subtitle_t *subtitle, *subtitle2 = NULL;
+ int matched_track = 0;
- if ( subdefault )
- job->select_subtitle_config.default_track = 1;
+ for( i = 0; i < hb_list_count( title->list_subtitle ); i++ )
+ {
+ subtitle = hb_list_item( title->list_subtitle, i );
+ matched_track = i;
+ if( strcmp( subtitle->iso639_2, native_language ) == 0 )
+ {
+ /*
+ * Found the first matching subtitle in our
+ * native language. Is it already selected?
+ */
+ for( i = 0; i < hb_list_count( job->list_subtitle ); i++ )
+ {
+ subtitle2 = hb_list_item( job->list_subtitle, i );
+
+ if( subtitle2->track == subtitle->track) {
+ /*
+ * Already selected
+ */
+ break;
+ }
+ subtitle2 = NULL;
+ }
+
+ if( subtitle2 == NULL )
+ {
+ /*
+ * Not already selected, so select it.
+ */
+ hb_subtitle_config_t sub_config;
+
+ if( native_dub )
+ {
+ fprintf( stderr, "Warning: no matching audio for native language - using subtitles instead.\n");
+ }
+ sub_config = subtitle->config;
+
+ if( mux == HB_MUX_MKV || subtitle->format == TEXTSUB)
+ {
+ sub_config.dest = PASSTHRUSUB;
+ }
+
+ sub_config.force = 0;
+ sub_config.default_track = 1;
+ hb_subtitle_add( job, &sub_config, matched_track);
+ }
+ /*
+ * Stop searching.
+ */
+ break;
+ }
+ }
+ }
}
}
job->indepth_scan = subtitle_scan;
fprintf( stderr, "Subtitle Scan Enabled - enabling "
"subtitles if found for foreign language segments\n");
- job->select_subtitle = malloc(sizeof(hb_subtitle_t*));
- *(job->select_subtitle) = NULL;
/*
* Add the pre-scan job
* for the first pass and then off again for the
* second.
*/
- hb_subtitle_t **subtitle_tmp = job->select_subtitle;
-
- job->select_subtitle = NULL;
-
job->pass = 1;
job->indepth_scan = 0;
}
hb_add( h, job );
- job->select_subtitle = subtitle_tmp;
-
job->pass = 2;
/*
* On the second pass we turn off subtitle scan so that we
" <number> to be displayed upon playback. Settings no default\n"
" means no subtitle will be automatically displayed\n"
" If \"number\" is omitted, the first trac is default.\n"
- " -N, --native-language Select subtitles with this language if it does not\n"
- " <string> match the Audio language. Provide the language's\n"
- " iso639-2 code (fre, eng, spa, dut, et cetera)\n"
- " --native-language may be used in conjunction with\n"
- " a subtitle \"scan\", in which case all tracks\n"
- " matching native language will be scanned.\n"
- " Otherwise --native-language is mutually exclusive\n"
- " with --subtitle\n"
-
-
+ " -N, --native-language Specifiy the your language preference. When the first\n"
+ " <string> audio track does not match your native language then\n"
+ " select the first subtitle that does. When used in\n"
+ " conjunction with --native-dub the audio track is\n"
+ " changed in preference to subtitles. Provide the\n"
+ " language's iso639-2 code (fre, eng, spa, dut, et cetera)\n"
+ " --native-dub Used in conjunction with --native-language\n"
+ " requests that if no audio tracks are selected the\n"
+ " default selected audio track will be the first one\n"
+ " that matches the --native-language. If there are no\n"
+ " matching audio tracks then the first matching\n"
+ " subtitle track is used instead.\n"
+ " --srt-file <string> SubRip SRT filename(s), separated by commas.\n"
+ " --srt-codeset Character codeset(s) that the SRT file(s) are\n"
+ " <string> encoded in, separted by commas.\n"
+ " Use 'iconv -l' for a list of valid\n"
+ " codesets. If not specified latin1 is assumed\n"
+ " --srt-offset Offset in milli-seconds to apply to the SRT file(s)\n"
+ " <string> separted by commas. If not specified zero is assumed.\n"
+ " Offsets may be negative.\n"
+ " --srt-lang <string> Language as an iso639-2 code fra, eng, spa et cetera)\n"
+ " for the SRT file(s) separated by commas. If not specified\n"
+ " then 'und' is used.\n"
"\n"
printf("\n>\n");
}
+static char** str_split( char *str, char *delem )
+{
+ char * token;
+ char * copy_str;
+ char * pos;
+ char ** ret;
+ int count, i;
+
+ if ( str == NULL || delem == NULL || str[0] == 0 )
+ {
+ ret = malloc( sizeof(char*) );
+ *ret = NULL;
+ return ret;
+ }
+
+ // Find number of elements in the string
+ count = 1;
+ pos = str;
+ while ( ( pos = strstr( pos+1, delem ) ) != NULL )
+ {
+ if ( *(pos+1) != 0 )
+ count++;
+ }
+ ret = calloc( ( count + 1 ), sizeof(char*) );
+
+ copy_str = strdup( str );
+ token = strtok( copy_str, delem );
+
+ i = 0;
+ while( token != NULL && i < count )
+ {
+ ret[i] = strdup( token );
+ token = strtok(NULL, ",");
+ i++;
+ }
+ free( copy_str );
+ return ret;
+}
+
/****************************************************************************
* ParseOptions:
****************************************************************************/
#define KEEP_DISPLAY_ASPECT 265
#define SUB_BURNED 266
#define SUB_DEFAULT 267
+ #define NATIVE_DUB 268
+ #define SRT_FILE 269
+ #define SRT_CODESET 270
+ #define SRT_OFFSET 271
+ #define SRT_LANG 272
for( ;; )
{
{ "subtitle-forced", optional_argument, NULL, 'F' },
{ "subtitle-burned", optional_argument, NULL, SUB_BURNED },
{ "subtitle-default", optional_argument, NULL, SUB_DEFAULT },
+ { "srt-file", required_argument, NULL, SRT_FILE },
+ { "srt-codeset", required_argument, NULL, SRT_CODESET },
+ { "srt-offset", required_argument, NULL, SRT_OFFSET },
+ { "srt-lang", required_argument, NULL, SRT_LANG },
{ "native-language", required_argument, NULL,'N' },
-
+ { "native-dub", no_argument, NULL, NATIVE_DUB },
{ "encoder", required_argument, NULL, 'e' },
{ "aencoder", required_argument, NULL, 'E' },
{ "two-pass", no_argument, NULL, '2' },
if( optarg != NULL )
{
atracks = strdup( optarg );
+ audio_explicit = 1;
}
else
{
}
break;
case 's':
- if( optarg != NULL )
- {
- subtracks = strdup( optarg );
- }
+ subtracks = str_split( optarg, "," );
break;
case 'F':
- if( optarg != NULL )
- {
- subforce = strdup( optarg );
- }
- else
- {
- subforce = "" ;
- }
+ subforce = str_split( optarg, "," );
break;
case SUB_BURNED:
if( optarg != NULL )
case 'N':
native_language = strdup( optarg );
break;
+ case NATIVE_DUB:
+ native_dub = 1;
+ break;
+ case SRT_FILE:
+ srtfile = str_split( optarg, "," );
+ break;
+ case SRT_CODESET:
+ srtcodeset = str_split( optarg, "," );
+ break;
+ case SRT_OFFSET:
+ srtoffset = str_split( optarg, "," );
+ break;
+ case SRT_LANG:
+ srtlang = str_split( optarg, "," );
+ break;
case '2':
twoPass = 1;
break;
* The whole media object is indicated in the IORegistry by the presence of a
* property with the key "Whole" and value "Yes".
****************************************************************************/
-static is_whole_media_service( io_service_t service )
+static int is_whole_media_service( io_service_t service )
{
int result = 0;