print: Avoid Variable Length Arrays.
[L-SMASH.git] / cli / remuxer.c
blob4810185d6af5e9c35bf8136e4214441134eac301
1 /*****************************************************************************
2 * remuxer.c:
3 *****************************************************************************
4 * Copyright (C) 2011-2014 L-SMASH project
6 * Authors: Yusuke Nakamura <muken.the.vfrmaniac@gmail.com>
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 *****************************************************************************/
21 /* This file is available under an ISC license. */
23 #include "cli.h"
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <strings.h>
29 #include <inttypes.h>
30 #include <stdarg.h>
32 #include "config.h"
34 typedef struct
36 uint32_t track_ID;
37 uint32_t last_sample_delta;
38 uint32_t current_sample_number;
39 uint32_t *summary_remap;
40 uint64_t skip_dt_interval;
41 uint64_t last_sample_dts;
42 lsmash_track_parameters_t track_param;
43 lsmash_media_parameters_t media_param;
44 } output_track_t;
46 typedef struct
48 output_track_t *track;
49 lsmash_movie_parameters_t param;
50 uint32_t num_tracks;
51 uint32_t current_track_number;
52 } output_movie_t;
54 typedef struct
56 const char *name;
57 lsmash_file_t *fh;
58 lsmash_file_parameters_t param;
59 lsmash_file_parameters_t seg_param;
60 output_movie_t movie;
61 uint32_t current_subseg_number;
62 } output_file_t;
64 typedef struct
66 lsmash_root_t *root;
67 output_file_t file;
68 uint32_t current_seg_number;
69 } output_t;
71 typedef struct
73 int active;
74 lsmash_summary_t *summary;
75 } input_summary_t;
77 typedef struct
79 lsmash_file_t *fh;
80 lsmash_file_parameters_t param;
81 } input_data_ref_t;
83 typedef struct
85 lsmash_media_parameters_t param;
86 uint32_t num_data_refs;
87 input_data_ref_t *data_refs;
88 } input_media_t;
90 typedef struct
92 int active;
93 lsmash_sample_t *sample;
94 double dts;
95 uint64_t composition_delay;
96 uint64_t skip_duration;
97 int reach_end_of_media_timeline;
98 uint32_t track_ID;
99 uint32_t last_sample_delta;
100 uint32_t current_sample_number;
101 uint32_t current_sample_index;
102 uint32_t num_summaries;
103 input_summary_t *summaries;
104 lsmash_track_parameters_t track_param;
105 input_media_t media;
106 } input_track_t;
108 typedef struct
110 input_track_t *track;
111 lsmash_itunes_metadata_t *itunes_metadata;
112 lsmash_movie_parameters_t param;
113 uint32_t movie_ID;
114 uint32_t num_tracks;
115 uint32_t num_itunes_metadata;
116 uint32_t current_track_number;
117 } input_movie_t;
119 typedef struct
121 lsmash_file_t *fh;
122 lsmash_file_parameters_t param;
123 input_movie_t movie;
124 } input_file_t;
126 typedef struct
128 lsmash_root_t *root;
129 input_file_t file;
130 } input_t;
132 typedef struct
134 char *raw_track_option;
135 int remove;
136 int disable;
137 int16_t alternate_group;
138 uint16_t ISO_language;
139 uint32_t seek;
140 int consider_rap;
141 char *handler_name;
142 } track_media_option;
144 typedef struct
146 output_t *output;
147 input_t *input;
148 track_media_option **track_option;
149 int num_input;
150 int add_bom_to_chpl;
151 int ref_chap_available;
152 uint32_t chap_track;
153 char *chap_file;
154 uint16_t default_language;
155 uint32_t frag_base_track;
156 uint32_t subseg_per_seg;
157 int dash;
158 } remuxer_t;
160 typedef struct
162 char *whole_track_option;
163 int num_track_delimiter;
164 } file_option;
166 static void cleanup_input_movie( input_t *input )
168 if( !input )
169 return;
170 input_movie_t *in_movie = &input->file.movie;
171 if( in_movie->itunes_metadata )
173 for( uint32_t i = 0; i < in_movie->num_itunes_metadata; i++ )
174 lsmash_cleanup_itunes_metadata( &in_movie->itunes_metadata[i] );
175 lsmash_freep( &in_movie->itunes_metadata );
177 if( in_movie->track )
179 for( uint32_t i = 0; i < in_movie->num_tracks; i++ )
181 input_track_t *in_track = &in_movie->track[i];
182 if( in_track->summaries )
184 for( uint32_t j = 0; j < in_track->num_summaries; j++ )
185 lsmash_cleanup_summary( in_track->summaries[j].summary );
186 lsmash_free( in_track->summaries );
188 input_media_t *in_media = &in_track->media;
189 for( uint32_t j = 0; j < in_media->num_data_refs; j++ )
190 if( input->file.fh != in_media->data_refs[j].fh )
191 lsmash_close_file( &in_media->data_refs[j].param );
192 lsmash_free( in_media->data_refs );
194 lsmash_freep( &in_movie->track );
196 lsmash_close_file( &input->file.param );
197 lsmash_destroy_root( input->root );
198 input->root = NULL;
201 static void cleanup_output_movie( output_t *output )
203 if( !output )
204 return;
205 output_movie_t *out_movie = &output->file.movie;
206 if( out_movie->track )
208 for( uint32_t i = 0; i < out_movie->num_tracks; i++ )
209 lsmash_free( out_movie->track[i].summary_remap );
210 lsmash_freep( &out_movie->track );
212 if( !(output->file.seg_param.mode & LSMASH_FILE_MODE_INITIALIZATION) )
214 lsmash_freep( &output->file.seg_param.brands );
215 lsmash_close_file( &output->file.seg_param );
217 lsmash_freep( &output->file.param.brands );
218 lsmash_close_file( &output->file.param );
219 lsmash_destroy_root( output->root );
220 output->root = NULL;
223 static void cleanup_remuxer( remuxer_t *remuxer )
225 for( int i = 0; i < remuxer->num_input; i++ )
227 if( remuxer->input )
229 cleanup_input_movie( &remuxer->input[i] );
230 lsmash_free( remuxer->input );
232 if( remuxer->track_option )
234 if( remuxer->track_option[i] )
235 lsmash_free( remuxer->track_option[i] );
236 lsmash_free( remuxer->track_option );
239 cleanup_output_movie( remuxer->output );
242 #define eprintf( ... ) fprintf( stderr, __VA_ARGS__ )
243 #define REFRESH_CONSOLE eprintf( " \r" )
245 static int remuxer_error( remuxer_t *remuxer, const char *message, ... )
247 cleanup_remuxer( remuxer );
248 REFRESH_CONSOLE;
249 eprintf( "[Error] " );
250 va_list args;
251 va_start( args, message );
252 vfprintf( stderr, message, args );
253 va_end( args );
254 return -1;
257 static int error_message( const char *message, ... )
259 REFRESH_CONSOLE;
260 eprintf( "[Error] " );
261 va_list args;
262 va_start( args, message );
263 vfprintf( stderr, message, args );
264 va_end( args );
265 return -1;
268 static int warning_message( const char *message, ... )
270 REFRESH_CONSOLE;
271 eprintf( "[Warning] " );
272 va_list args;
273 va_start( args, message );
274 vfprintf( stderr, message, args );
275 va_end( args );
276 return -1;
279 #define REMUXER_ERR( ... ) remuxer_error( &remuxer, __VA_ARGS__ )
280 #define ERROR_MSG( ... ) error_message( __VA_ARGS__ )
281 #define WARNING_MSG( ... ) warning_message( __VA_ARGS__ )
283 static void display_version( void )
285 eprintf( "\n"
286 "L-SMASH isom/mov re-muliplexer rev%s %s\n"
287 "Built on %s %s\n"
288 "Copyright (C) 2011-2014 L-SMASH project\n",
289 LSMASH_REV, LSMASH_GIT_HASH, __DATE__, __TIME__ );
292 static void display_help( void )
294 display_version();
295 eprintf( "\n"
296 "Usage: remuxer -i input1 [-i input2 -i input3 ...] -o output\n"
297 "Global options:\n"
298 " --help Display help.\n"
299 " --version Display version information.\n"
300 " --chapter <string> Set chapters from the file.\n"
301 " --chpl-with-bom Add UTF-8 BOM to the chapter strings\n"
302 " in the chapter list. (experimental)\n"
303 " --chapter-track <integer> Set which track the chapter applies to.\n"
304 " This option takes effect only when reference\n"
305 " chapter is available.\n"
306 " If this option is not used, it defaults to 1.\n"
307 " --language <string> Specify the default language for all the output tracks.\n"
308 " This option is overridden by the track options.\n"
309 " --fragment <integer> Enable fragmentation per random accessible point.\n"
310 " Set which track the fragmentation is based on.\n"
311 " --dash <integer> Enable DASH ISOBMFF-based Media segmentation.\n"
312 " The value is the number of subsegments per segment.\n"
313 " If zero, Indexed self-initializing Media Segment.\n"
314 " This option requires --fragment.\n"
315 "Track options:\n"
316 " remove Remove this track\n"
317 " disable Disable this track\n"
318 " language=<string> Specify media language\n"
319 " alternate-group=<integer> Specify alternate group\n"
320 " handler=<string> Set media handler name\n"
321 " seek=<integer> Specify starting point in media\n"
322 " safe-seek=<integer> Same as seek except for considering random accessible point\n"
323 " Media starts from the closest random accessible point\n"
324 "How to use track options:\n"
325 " -i input?[track_number1]:[track_option1],[track_option2]?[track_number2]:...\n"
326 "For example:\n"
327 " remuxer -i input1 -i input2?2:alternate-group=1?3:language=jpn,alternate-group=1 -o output\n" );
330 static char *duplicate_string( char *src )
332 if( !src )
333 return NULL;
334 int dst_size = strlen( src ) + 1;
335 char *dst = lsmash_malloc( dst_size );
336 if( !dst )
337 return NULL;
338 memcpy( dst, src, dst_size );
339 return dst;
342 static int get_itunes_metadata( lsmash_root_t *root, uint32_t metadata_number, lsmash_itunes_metadata_t *metadata )
344 memset( metadata, 0, sizeof(lsmash_itunes_metadata_t) );
345 if( lsmash_get_itunes_metadata( root, metadata_number, metadata ) )
346 return -1;
347 lsmash_itunes_metadata_t shadow = *metadata;
348 metadata->meaning = NULL;
349 metadata->name = NULL;
350 memset( &metadata->value, 0, sizeof(lsmash_itunes_metadata_value_t) );
351 if( shadow.meaning )
353 metadata->meaning = duplicate_string( shadow.meaning );
354 if( !metadata->meaning )
355 return -1;
357 if( shadow.name )
359 metadata->name = duplicate_string( shadow.name );
360 if( !metadata->name )
361 goto fail;
363 if( shadow.type == ITUNES_METADATA_TYPE_STRING )
365 metadata->value.string = duplicate_string( shadow.value.string );
366 if( !metadata->value.string )
367 goto fail;
369 else if( shadow.type == ITUNES_METADATA_TYPE_BINARY )
371 metadata->value.binary.data = lsmash_malloc( shadow.value.binary.size );
372 if( !metadata->value.binary.data )
373 goto fail;
374 memcpy( metadata->value.binary.data, shadow.value.binary.data, shadow.value.binary.size );
375 metadata->value.binary.size = shadow.value.binary.size;
376 metadata->value.binary.subtype = shadow.value.binary.subtype;
378 else
379 metadata->value = shadow.value;
380 return 0;
381 fail:
382 lsmash_freep( &metadata->meaning );
383 lsmash_freep( &metadata->name );
384 return -1;
387 static inline int is_relative_path( const char *path )
389 return path[0] == '/' || path[0] == '\\' || (path[0] != '\0' && path[1] == ':') ? 0 : 1;
392 static int input_data_reference
394 input_t *input,
395 uint32_t track_ID,
396 input_data_ref_t *in_data_ref,
397 lsmash_data_reference_t *data_ref
400 if( lsmash_open_file( data_ref->location, 1, &in_data_ref->param ) < 0 )
402 WARNING_MSG( "failed to open an external media file.\n" );
403 return -1;
405 in_data_ref->param.mode |= LSMASH_FILE_MODE_MEDIA;
406 in_data_ref->fh = lsmash_set_file( input->root, &in_data_ref->param );
407 if( !in_data_ref->fh )
409 WARNING_MSG( "failed to set an external media file as a data reference.\n" );
410 return -1;
412 if( lsmash_assign_data_reference( input->root, track_ID, data_ref->index, in_data_ref->fh ) < 0 )
414 WARNING_MSG( "failed to assign an external media a data reference.\n" );
415 return -1;
417 return 0;
420 static int get_movie( input_t *input, char *input_name )
422 if( !strcmp( input_name, "-" ) )
423 return ERROR_MSG( "standard input not supported.\n" );
424 /* Read an input file. */
425 input->root = lsmash_create_root();
426 if( !input->root )
427 return ERROR_MSG( "failed to create a ROOT for an input file.\n" );
428 input_file_t *in_file = &input->file;
429 if( lsmash_open_file( input_name, 1, &in_file->param ) < 0 )
430 return ERROR_MSG( "failed to open an input file.\n" );
431 in_file->fh = lsmash_set_file( input->root, &in_file->param );
432 if( !in_file->fh )
433 return ERROR_MSG( "failed to add an input file into a ROOT.\n" );
434 if( lsmash_read_file( in_file->fh, &in_file->param ) < 0 )
435 return ERROR_MSG( "failed to read an input file\n" );
436 /* Get iTunes metadata. */
437 input_movie_t *in_movie = &in_file->movie;
438 in_movie->num_itunes_metadata = lsmash_count_itunes_metadata( input->root );
439 if( in_movie->num_itunes_metadata )
441 in_movie->itunes_metadata = lsmash_malloc( in_movie->num_itunes_metadata * sizeof(lsmash_itunes_metadata_t) );
442 if( !in_movie->itunes_metadata )
443 return ERROR_MSG( "failed to alloc iTunes metadata.\n" );
444 uint32_t itunes_metadata_count = 0;
445 for( uint32_t i = 1; i <= in_movie->num_itunes_metadata; i++ )
447 if( get_itunes_metadata( input->root, i, &in_movie->itunes_metadata[itunes_metadata_count] ) )
449 WARNING_MSG( "failed to get an iTunes metadata.\n" );
450 continue;
452 ++itunes_metadata_count;
454 in_movie->num_itunes_metadata = itunes_metadata_count;
456 in_movie->current_track_number = 1;
457 lsmash_initialize_movie_parameters( &in_movie->param );
458 if( lsmash_get_movie_parameters( input->root, &in_movie->param ) )
459 return ERROR_MSG( "failed to get movie parameters.\n" );
460 uint32_t num_tracks = in_movie->num_tracks = in_movie->param.number_of_tracks;
461 /* Create tracks. */
462 input_track_t *in_track = in_movie->track = lsmash_malloc_zero( num_tracks * sizeof(input_track_t) );
463 if( !in_track )
464 return ERROR_MSG( "failed to alloc input tracks.\n" );
465 for( uint32_t i = 0; i < num_tracks; i++ )
467 in_track[i].track_ID = lsmash_get_track_ID( input->root, i + 1 );
468 if( !in_track[i].track_ID )
469 return ERROR_MSG( "failed to get track_ID.\n" );
471 for( uint32_t i = 0; i < num_tracks; i++ )
473 lsmash_initialize_track_parameters( &in_track[i].track_param );
474 if( lsmash_get_track_parameters( input->root, in_track[i].track_ID, &in_track[i].track_param ) )
476 WARNING_MSG( "failed to get track parameters.\n" );
477 continue;
479 lsmash_initialize_media_parameters( &in_track[i].media.param );
480 if( lsmash_get_media_parameters( input->root, in_track[i].track_ID, &in_track[i].media.param ) )
482 WARNING_MSG( "failed to get media parameters.\n" );
483 continue;
485 uint32_t data_ref_count = lsmash_count_data_reference( input->root, in_track[i].track_ID );
486 if( data_ref_count == 0 )
488 WARNING_MSG( "failed to get the number of data references.\n" );
489 continue;
491 in_track[i].media.data_refs = lsmash_malloc_zero( data_ref_count * sizeof(input_data_ref_t) );
492 if( !in_track[i].media.data_refs )
494 WARNING_MSG( "failed to allocate handles of data reference.\n" );
495 continue;
497 for( uint32_t j = 0; j < data_ref_count; j++ )
499 input_data_ref_t *in_data_ref = &in_track[i].media.data_refs[j];
500 lsmash_data_reference_t data_ref = { .index = j + 1 };
501 if( lsmash_get_data_reference( input->root, in_track[i].track_ID, &data_ref ) < 0 )
503 WARNING_MSG( "failed to get a data references.\n" );
504 continue;
506 if( data_ref.location )
508 if( is_relative_path( data_ref.location ) && !is_relative_path( input_name ) )
510 /* Append the directory path from the referencing file. */
511 int location_length = strlen( data_ref.location );
512 int input_name_length = strlen( input_name );
513 char *p = input_name + input_name_length;
514 while( p != input_name && *p != '/' && *p != '\\' )
515 --p;
516 int relative_path_length = p == input_name ? 2 : p - input_name;
517 char *location = lsmash_malloc( relative_path_length + location_length + 2 );
518 if( location )
520 memcpy( location, input_name, relative_path_length );
521 memcpy( location + relative_path_length + 1, data_ref.location, location_length );
522 location[relative_path_length] = '/';
523 location[relative_path_length + location_length + 1] = '\0';
524 lsmash_cleanup_data_reference( &data_ref );
525 data_ref.location = location;
528 int ret = input_data_reference( input, in_track[i].track_ID, in_data_ref, &data_ref );
529 lsmash_cleanup_data_reference( &data_ref );
530 if( ret < 0 )
531 continue;
533 else
535 in_data_ref->fh = in_file->fh;
536 in_data_ref->param = in_file->param;
539 if( lsmash_construct_timeline( input->root, in_track[i].track_ID ) )
541 WARNING_MSG( "failed to construct timeline.\n" );
542 continue;
544 if( lsmash_get_last_sample_delta_from_media_timeline( input->root, in_track[i].track_ID, &in_track[i].last_sample_delta ) )
546 WARNING_MSG( "failed to get the last sample delta.\n" );
547 continue;
549 in_track[i].num_summaries = lsmash_count_summary( input->root, in_track[i].track_ID );
550 if( in_track[i].num_summaries == 0 )
552 WARNING_MSG( "failed to find valid summaries.\n" );
553 continue;
555 in_track[i].summaries = lsmash_malloc_zero( in_track[i].num_summaries * sizeof(input_summary_t) );
556 if( !in_track[i].summaries )
557 return ERROR_MSG( "failed to alloc input summaries.\n" );
558 for( uint32_t j = 0; j < in_track[i].num_summaries; j++ )
560 lsmash_summary_t *summary = lsmash_get_summary( input->root, in_track[i].track_ID, j + 1 );
561 if( !summary )
563 WARNING_MSG( "failed to get a summary.\n" );
564 continue;
566 if( !LSMASH_FLAGS_SATISFIED( lsmash_check_codec_support( summary->sample_type ), LSMASH_CODEC_SUPPORT_FLAG_REMUX ) )
568 lsmash_cleanup_summary( summary );
569 WARNING_MSG( "no support to remux this stream.\n" );
570 continue;
572 in_track[i].summaries[j].summary = summary;
573 in_track[i].summaries[j].active = 1;
575 in_track[i].active = 1;
576 in_track[i].current_sample_number = 1;
577 in_track[i].sample = NULL;
578 in_track[i].dts = 0;
579 in_track[i].composition_delay = 0;
580 in_track[i].skip_duration = 0;
582 lsmash_destroy_children( lsmash_file_as_box( in_file->fh ) );
583 return 0;
586 static int parse_track_option( remuxer_t *remuxer )
588 track_media_option **track = remuxer->track_option;
589 for( int i = 0; i < remuxer->num_input; i++ )
590 for( uint32_t j = 0; j < remuxer->input[i].file.movie.num_tracks; j++ )
592 track_media_option *current_track_opt = &track[i][j];
593 if( current_track_opt->raw_track_option == NULL )
594 break;
595 if( !strchr( current_track_opt->raw_track_option, ':' )
596 || strchr( current_track_opt->raw_track_option, ':' ) == current_track_opt->raw_track_option )
597 return ERROR_MSG( "track number is not specified in %s\n", current_track_opt->raw_track_option );
598 if( strchr( current_track_opt->raw_track_option, ':' ) != strrchr( current_track_opt->raw_track_option, ':' ) )
599 return ERROR_MSG( "multiple colons inside one track option in %s.\n", current_track_opt->raw_track_option );
600 uint32_t track_number = atoi( strtok( current_track_opt->raw_track_option, ":" ) );
601 if( track_number == 0 )
602 return ERROR_MSG( "%s is an invalid track number.\n", strtok( current_track_opt->raw_track_option, ":" ) );
603 if( track_number > remuxer->input[i].file.movie.num_tracks )
604 return ERROR_MSG( "%d is an invalid track number.\n", track_number );
605 char *track_option;
606 while( (track_option = strtok( NULL, "," )) != NULL )
608 if( strchr( track_option, '=' ) != strrchr( track_option, '=' ) )
609 return ERROR_MSG( "multiple equal signs inside one track option in %s\n", track_option );
610 current_track_opt = &track[i][track_number - 1];
611 if( strstr( track_option, "remove" ) )
613 current_track_opt->remove = 1;
614 /* No need to parse track options for this track anymore. */
615 break;
617 else if( strstr( track_option, "disable" ) )
618 current_track_opt->disable = 1;
619 else if( strstr( track_option, "alternate-group=" ) )
621 char *track_parameter = strchr( track_option, '=' ) + 1;
622 current_track_opt->alternate_group = atoi( track_parameter );
624 else if( strstr( track_option, "language=" ) )
626 char *track_parameter = strchr( track_option, '=' ) + 1;
627 current_track_opt->ISO_language = lsmash_pack_iso_language( track_parameter );
629 else if( strstr( track_option, "handler=" ) )
631 char *track_parameter = strchr( track_option, '=' ) + 1;
632 current_track_opt->handler_name = track_parameter;
634 else if( strstr( track_option, "safe-seek=" ) )
636 char *track_parameter = strchr( track_option, '=' ) + 1;
637 current_track_opt->seek = atoi( track_parameter );
638 current_track_opt->consider_rap = 1;
640 else if( strstr( track_option, "seek=" ) )
642 char *track_parameter = strchr( track_option, '=' ) + 1;
643 current_track_opt->seek = atoi( track_parameter );
645 else
646 return ERROR_MSG( "unknown track option %s\n", track_option );
649 return 0;
652 static int parse_cli_option( int argc, char **argv, remuxer_t *remuxer )
654 #define FAILED_PARSE_CLI_OPTION( ... ) \
655 do \
657 lsmash_free( input_file_option ); \
658 return ERROR_MSG( __VA_ARGS__ ); \
660 while( 0 )
661 input_t *input = remuxer->input;
662 track_media_option **track_option = remuxer->track_option;
663 file_option *input_file_option = lsmash_malloc( remuxer->num_input * sizeof(file_option) );
664 if( !input_file_option )
665 return ERROR_MSG( "failed to allocate per-file option.\n" );
666 int input_movie_number = 0;
667 for( int i = 1; i < argc ; i++ )
669 /* Get input movies. */
670 if( !strcasecmp( argv[i], "-i" ) || !strcasecmp( argv[i], "--input" ) ) /* input file */
672 if( ++i == argc )
673 FAILED_PARSE_CLI_OPTION( "-i requires an argument.\n" );
674 input_file_option[input_movie_number].num_track_delimiter = 0;
675 char *p = argv[i];
676 while( *p )
677 input_file_option[input_movie_number].num_track_delimiter += (*p++ == '?');
678 if( get_movie( &input[input_movie_number], strtok( argv[i], "?" ) ) )
679 FAILED_PARSE_CLI_OPTION( "failed to get input movie.\n" );
680 uint32_t num_tracks = input[input_movie_number].file.movie.num_tracks;
681 track_option[input_movie_number] = lsmash_malloc_zero( num_tracks * sizeof(track_media_option) );
682 if( !track_option[input_movie_number] )
683 FAILED_PARSE_CLI_OPTION( "couldn't allocate memory.\n" );
684 input_file_option[input_movie_number].whole_track_option = strtok( NULL, "" );
685 input[input_movie_number].file.movie.movie_ID = input_movie_number + 1;
686 ++input_movie_number;
688 /* Create output movie. */
689 else if( !strcasecmp( argv[i], "-o" ) || !strcasecmp( argv[i], "--output" ) ) /* output file */
691 if( ++i == argc )
692 FAILED_PARSE_CLI_OPTION( "-o requires an argument.\n" );
693 output_t *output = remuxer->output;
694 output->root = lsmash_create_root();
695 if( !output->root )
696 FAILED_PARSE_CLI_OPTION( "failed to create a ROOT.\n" );
697 if( lsmash_open_file( argv[i], 0, &output->file.param ) < 0 )
698 FAILED_PARSE_CLI_OPTION( "failed to open an output file.\n" );
699 output->file.name = argv[i];
701 else if( !strcasecmp( argv[i], "--chapter" ) ) /* chapter file */
703 if( ++i == argc )
704 FAILED_PARSE_CLI_OPTION( "--chapter requires an argument.\n" );
705 remuxer->chap_file = argv[i];
707 else if( !strcasecmp( argv[i], "--chpl-with-bom" ) )
708 remuxer->add_bom_to_chpl = 1;
709 else if( !strcasecmp( argv[i], "--chapter-track" ) ) /* track to apply reference chapter to */
711 if( ++i == argc )
712 FAILED_PARSE_CLI_OPTION( "--chapter-track requires an argument.\n" );
713 remuxer->chap_track = atoi( argv[i] );
714 if( !remuxer->chap_track )
715 FAILED_PARSE_CLI_OPTION( "%s is an invalid track number.\n", argv[i] );
717 else if( !strcasecmp( argv[i], "--language" ) )
719 if( ++i == argc )
720 FAILED_PARSE_CLI_OPTION( "--chapter requires an argument.\n" );
721 remuxer->default_language = lsmash_pack_iso_language( argv[i] );
723 else if( !strcasecmp( argv[i], "--fragment" ) )
725 if( ++i == argc )
726 FAILED_PARSE_CLI_OPTION( "--fragment requires an argument.\n" );
727 remuxer->frag_base_track = atoi( argv[i] );
728 if( remuxer->frag_base_track == 0 )
729 FAILED_PARSE_CLI_OPTION( "%s is an invalid track number.\n", argv[i] );
731 else if( !strcasecmp( argv[i], "--dash" ) )
733 if( ++i == argc )
734 FAILED_PARSE_CLI_OPTION( "--dash requires an argument.\n" );
735 remuxer->subseg_per_seg = atoi( argv[i] );
736 remuxer->dash = 1;
738 else
739 FAILED_PARSE_CLI_OPTION( "unkown option found: %s\n", argv[i] );
741 if( !remuxer->output->root )
742 FAILED_PARSE_CLI_OPTION( "output file name is not specified.\n" );
743 /* Parse track options */
744 /* Get the current track and media parameters */
745 for( int i = 0; i < remuxer->num_input; i++ )
746 for( uint32_t j = 0; j < input[i].file.movie.num_tracks; j++ )
748 input_track_t *in_track = &input[i].file.movie.track[j];
749 if( !in_track->active )
750 continue;
751 track_option[i][j].alternate_group = in_track->track_param.alternate_group;
752 track_option[i][j].ISO_language = in_track->media.param.ISO_language;
753 track_option[i][j].handler_name = in_track->media.param.media_handler_name;
755 /* Set the default language */
756 if( remuxer->default_language )
757 for( int i = 0; i < remuxer->num_input; i++ )
758 for( uint32_t j = 0; j < input[i].file.movie.num_tracks; j++ )
759 track_option[i][j].ISO_language = remuxer->default_language;
760 /* Get the track and media parameters specified by users */
761 for( int i = 0; i < remuxer->num_input; i++ )
763 if( input_file_option[i].num_track_delimiter > input[i].file.movie.num_tracks )
764 FAILED_PARSE_CLI_OPTION( "more track options specified than the actual number of the tracks (%"PRIu32").\n",
765 input[i].file.movie.num_tracks );
766 if( input_file_option[i].num_track_delimiter )
768 track_option[i][0].raw_track_option = strtok( input_file_option[i].whole_track_option, "?" );
769 for( int j = 1; j < input_file_option[i].num_track_delimiter ; j++ )
770 track_option[i][j].raw_track_option = strtok( NULL, "?" );
773 if( parse_track_option( remuxer ) )
774 FAILED_PARSE_CLI_OPTION( "failed to parse track options.\n" );
775 lsmash_free( input_file_option );
776 return 0;
777 #undef FAILED_PARSE_CLI_OPTION
780 static void replace_with_valid_brand( remuxer_t *remuxer )
782 static const lsmash_brand_type brand_filter_list[] =
784 ISOM_BRAND_TYPE_3G2A,
785 ISOM_BRAND_TYPE_3GG6,
786 ISOM_BRAND_TYPE_3GG9,
787 ISOM_BRAND_TYPE_3GP4,
788 ISOM_BRAND_TYPE_3GP5,
789 ISOM_BRAND_TYPE_3GP6,
790 ISOM_BRAND_TYPE_3GP7,
791 ISOM_BRAND_TYPE_3GP8,
792 ISOM_BRAND_TYPE_3GP9,
793 ISOM_BRAND_TYPE_3GR6,
794 ISOM_BRAND_TYPE_3GR9,
795 ISOM_BRAND_TYPE_M4A ,
796 ISOM_BRAND_TYPE_M4B ,
797 ISOM_BRAND_TYPE_M4V ,
798 ISOM_BRAND_TYPE_AVC1,
799 ISOM_BRAND_TYPE_DBY1,
800 ISOM_BRAND_TYPE_ISO2,
801 ISOM_BRAND_TYPE_ISO3,
802 ISOM_BRAND_TYPE_ISO4,
803 ISOM_BRAND_TYPE_ISO5,
804 ISOM_BRAND_TYPE_ISO6,
805 ISOM_BRAND_TYPE_ISO7,
806 ISOM_BRAND_TYPE_ISOM,
807 ISOM_BRAND_TYPE_MP41,
808 ISOM_BRAND_TYPE_MP42,
809 ISOM_BRAND_TYPE_QT ,
812 input_t *input = remuxer->input;
813 /* Check the number of video and audio tracks, and the number of video
814 * and audio sample descriptions for the restrictions of 3GPP Basic Profile.
815 * - the maximum number of tracks shall be one for video (or alternatively
816 * one for scene description), one for audio and one for text
817 * - the maximum number of sample entries shall be one per track for video
818 * and audio (but unrestricted for text and scene description) */
819 uint32_t video_track_count = 0;
820 uint32_t audio_track_count = 0;
821 uint32_t video_num_summaries = 0;
822 uint32_t audio_num_summaries = 0;
823 for( int i = 0; i < remuxer->num_input; i++ )
825 input_movie_t *movie = &input[i].file.movie;
826 for( int j = 0; j < movie->num_tracks; j++ )
828 if( movie->track[j].media.param.handler_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK )
830 if( ++video_track_count == 1 )
831 video_num_summaries = movie->track[j].num_summaries;
833 else if( movie->track[j].media.param.handler_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK )
835 if( ++audio_track_count == 1 )
836 audio_num_summaries = movie->track[j].num_summaries;
840 for( int i = 0; i < remuxer->num_input; i++ )
841 for( uint32_t j = 0; j <= input[i].file.param.brand_count; j++ )
843 int invalid = 1;
844 uint32_t *brand = j ? &input[i].file.param.brands[j - 1] : &input[i].file.param.major_brand;
845 uint32_t *version = j ? NULL : &input[i].file.param.minor_version;
846 for( int k = 0; brand_filter_list[k]; k++ )
848 if( *brand == brand_filter_list[k] )
850 if( ((*brand >> 24) & 0xFF) == '3'
851 && ((*brand >> 16) & 0xFF) == 'g'
852 && (((*brand >> 8) & 0xFF) == 'p' || ((*brand >> 8) & 0xFF) == 'r') )
854 if( remuxer->frag_base_track == 0 /* Movie fragments are not allowed in '3gp4' and '3gp5'. */
855 && video_track_count <= 1 && audio_track_count <= 1
856 && video_num_summaries <= 1 && audio_num_summaries <= 1 )
857 continue;
858 /* Replace with the General Profile for maximum compatibility. */
859 if( (*brand & 0xFF) < '6' )
861 /* 3GPP version 6.7.0 General Profile */
862 *brand = ISOM_BRAND_TYPE_3GG6;
863 if( version )
864 *version = 0x00000700;
866 else
867 *brand = LSMASH_4CC( '3', 'g', 'g', *brand & 0xFF );
869 if( remuxer->dash
870 && (*brand == ISOM_BRAND_TYPE_AVC1
871 || (((*brand >> 24) & 0xFF) == 'i'
872 && ((*brand >> 16) & 0xFF) == 's'
873 && ((*brand >> 8) & 0xFF) == 'o'
874 && ((*brand & 0xFF) == 'm' || (*brand & 0xFF) < '6'))) )
875 *brand = ISOM_BRAND_TYPE_ISO6;
876 invalid = 0;
877 break;
880 if( invalid )
882 /* Replace with the 'mp42' brand. */
883 *brand = ISOM_BRAND_TYPE_MP42;
884 if( version )
885 *version = 0;
890 static int pick_most_used_major_brand( input_t *input, output_file_t *out_file, int num_input )
892 void *heap = lsmash_malloc( num_input * (sizeof(lsmash_brand_type) + 2 * sizeof(uint32_t)) );
893 if( !heap )
894 return ERROR_MSG( "failed to allocate memory.\n" );
895 lsmash_brand_type *major_brand = (lsmash_brand_type *)heap;
896 uint32_t *minor_version = (uint32_t *)((lsmash_brand_type *)heap + num_input);
897 uint32_t *major_brand_count = minor_version + num_input;
898 uint32_t num_major_brand = 0;
899 for( int i = 0; i < num_input; i++ )
901 major_brand [num_major_brand] = input[i].file.param.major_brand;
902 minor_version [num_major_brand] = input[i].file.param.minor_version;
903 major_brand_count[num_major_brand] = 0;
904 for( int j = 0; j < num_input; j++ )
905 if( (major_brand [num_major_brand] == input[j].file.param.major_brand)
906 && (minor_version[num_major_brand] == input[j].file.param.minor_version) )
908 if( i <= j )
909 ++major_brand_count[num_major_brand];
910 else
912 /* This major_brand already exists. Skip this. */
913 major_brand_count[num_major_brand] = 0;
914 --num_major_brand;
915 break;
918 ++num_major_brand;
920 uint32_t most_used_count = 0;
921 for( uint32_t i = 0; i < num_major_brand; i++ )
922 if( major_brand_count[i] > most_used_count )
924 most_used_count = major_brand_count[i];
925 out_file->param.major_brand = major_brand [i];
926 out_file->param.minor_version = minor_version[i];
928 lsmash_free( heap );
929 return 0;
932 static int set_movie_parameters( remuxer_t *remuxer )
934 int num_input = remuxer->num_input;
935 input_t *input = remuxer->input;
936 output_t *output = remuxer->output;
937 output_file_t *out_file = &output->file;
938 if( remuxer->frag_base_track )
939 out_file->param.mode |= LSMASH_FILE_MODE_FRAGMENTED;
940 int self_containd_segment = (remuxer->dash && remuxer->subseg_per_seg == 0);
941 if( remuxer->dash )
943 if( remuxer->frag_base_track )
945 if( self_containd_segment )
946 out_file->param.mode |= LSMASH_FILE_MODE_INDEX;
947 else
949 out_file->param.mode &= ~LSMASH_FILE_MODE_MEDIA;
950 out_file->param.mode |= LSMASH_FILE_MODE_SEGMENT;
953 else
954 WARNING_MSG( "--dash requires --fragment.\n" );
956 replace_with_valid_brand( remuxer );
957 if( self_containd_segment )
959 out_file->param.major_brand = ISOM_BRAND_TYPE_DASH;
960 out_file->param.minor_version = 0;
962 else if( pick_most_used_major_brand( input, out_file, num_input ) < 0 )
963 return ERROR_MSG( "failed to pick the most used major brand.\n" );
964 /* Deduplicate compatible brands. */
965 uint32_t num_input_brands = num_input + (self_containd_segment ? 1 : 0);
966 for( int i = 0; i < num_input; i++ )
967 num_input_brands += input[i].file.param.brand_count;
968 lsmash_brand_type *input_brands = lsmash_malloc( num_input_brands * sizeof(lsmash_brand_type) );
969 if( !input_brands )
970 return ERROR_MSG( "failed to allocate brands for an input file.\n" );
971 num_input_brands = 0;
972 if( self_containd_segment )
973 input_brands[num_input_brands++] = ISOM_BRAND_TYPE_DASH;
974 for( int i = 0; i < num_input; i++ )
976 input_brands[num_input_brands++] = input[i].file.param.major_brand;
977 for( uint32_t j = 0; j < input[i].file.param.brand_count; j++ )
978 if( input[i].file.param.brands[j] )
979 input_brands[num_input_brands++] = input[i].file.param.brands[j];
981 lsmash_brand_type *output_brands = lsmash_malloc_zero( num_input_brands * sizeof(lsmash_brand_type) );
982 if( !output_brands )
984 lsmash_free( input_brands );
985 return ERROR_MSG( "failed to allocate brands for an output file.\n" );
987 uint32_t num_output_brands = 0;
988 for( uint32_t i = 0; i < num_input_brands; i++ )
990 output_brands[num_output_brands] = input_brands[i];
991 for( uint32_t j = 0; j < num_output_brands; j++ )
992 if( output_brands[num_output_brands] == output_brands[j] )
994 /* This brand already exists. Skip this. */
995 --num_output_brands;
996 break;
998 ++num_output_brands;
1000 lsmash_free( input_brands );
1001 out_file->param.brand_count = num_output_brands;
1002 out_file->param.brands = output_brands;
1003 /* Set up a file. */
1004 out_file->fh = lsmash_set_file( output->root, &out_file->param );
1005 if( !out_file->fh )
1006 return ERROR_MSG( "failed to add an output file into a ROOT.\n" );
1007 out_file->seg_param = out_file->param;
1008 /* Check whether a reference chapter track is allowed or not. */
1009 if( remuxer->chap_file )
1010 for( uint32_t i = 0; i < out_file->param.brand_count; i++ )
1012 uint32_t brand = out_file->param.brands[i];
1013 /* According to the restrictions of 3GPP Basic Profile,
1014 * - there shall be no references between tracks, e.g., a scene description track
1015 * shall not refer to a media track since all tracks are on equal footing and
1016 * played in parallel by a conforming player.
1017 * Therefore, the referenced chapter track is forbidden to use for 3GPP Basic Profile. */
1018 if( ((brand >> 24) & 0xFF) == '3'
1019 && ((brand >> 16) & 0xFF) == 'g'
1020 && ((brand >> 8) & 0xFF) == 'p' )
1021 break;
1022 /* QuickTime file and iTunes MP4 file can contain the referenced chapter track. */
1023 if( brand == ISOM_BRAND_TYPE_QT || brand == ISOM_BRAND_TYPE_M4A
1024 || brand == ISOM_BRAND_TYPE_M4B || brand == ISOM_BRAND_TYPE_M4P
1025 || brand == ISOM_BRAND_TYPE_M4V )
1027 remuxer->ref_chap_available = 1;
1028 break;
1031 /* Set the movie timescale in order to match the media timescale if only one track is there. */
1032 lsmash_initialize_movie_parameters( &out_file->movie.param );
1033 if( out_file->movie.num_tracks == 1 )
1034 for( int i = 0; i < remuxer->num_input; i++ )
1036 input_movie_t *in_movie = &input[i].file.movie;
1037 for( uint32_t j = 0; j < in_movie->num_tracks; j++ )
1038 if( in_movie->track[j].active )
1040 out_file->movie.param.timescale = in_movie->track[j].media.param.timescale;
1041 break;
1044 return lsmash_set_movie_parameters( output->root, &out_file->movie.param );
1047 static void set_itunes_metadata( output_t *output, input_t *input, int num_input )
1049 for( int i = 0; i < num_input; i++ )
1050 for( uint32_t j = 0; j < input[i].file.movie.num_itunes_metadata; j++ )
1051 if( lsmash_set_itunes_metadata( output->root, input[i].file.movie.itunes_metadata[j] ) )
1053 WARNING_MSG( "failed to set an iTunes metadata.\n" );
1054 continue;
1058 static int set_starting_point( input_t *input, input_track_t *in_track, uint32_t seek_point, int consider_rap )
1060 if( seek_point == 0 )
1061 return 0;
1062 uint32_t rap_number;
1063 if( lsmash_get_closest_random_accessible_point_from_media_timeline( input->root, in_track->track_ID, 1, &rap_number ) )
1065 if( consider_rap )
1066 return ERROR_MSG( "failed to get the first random accessible point.\n" );
1067 else
1069 WARNING_MSG( "no random access point!\n" );
1070 /* Set number of the first sample to be muxed. */
1071 in_track->current_sample_number = seek_point;
1072 return 0;
1075 /* Get composition delay. */
1076 uint64_t rap_dts;
1077 uint64_t rap_cts;
1078 uint32_t ctd_shift;
1079 if( lsmash_get_dts_from_media_timeline( input->root, in_track->track_ID, rap_number, &rap_dts ) )
1080 return ERROR_MSG( "failed to get CTS of the first random accessible sample of seek point.\n" );
1081 if( lsmash_get_cts_from_media_timeline( input->root, in_track->track_ID, rap_number, &rap_cts ) )
1082 return ERROR_MSG( "failed to get CTS of the first random accessible sample of seek point.\n" );
1083 if( lsmash_get_composition_to_decode_shift_from_media_timeline( input->root, in_track->track_ID, &ctd_shift ) )
1084 return ERROR_MSG( "failed to get composition to decode timeline shfit.\n" );
1085 in_track->composition_delay = rap_cts - rap_dts + ctd_shift;
1086 /* Check if starting point is random accessible. */
1087 if( lsmash_get_closest_random_accessible_point_from_media_timeline( input->root, in_track->track_ID, seek_point, &rap_number ) )
1088 return ERROR_MSG( "failed to get a random accessible point.\n" );
1089 if( rap_number != seek_point )
1091 WARNING_MSG( "starting point you specified is not a random accessible point.\n" );
1092 if( consider_rap )
1094 /* Get duration that should be skipped. */
1095 if( lsmash_get_cts_from_media_timeline( input->root, in_track->track_ID, rap_number, &rap_cts ) )
1096 return ERROR_MSG( "failed to get CTS of the closest and past random accessible sample of starting point.\n" );
1097 uint64_t seek_cts;
1098 if( lsmash_get_cts_from_media_timeline( input->root, in_track->track_ID, seek_point, &seek_cts ) )
1099 return ERROR_MSG( "failed to get CTS of starting point.\n" );
1100 if( rap_cts < seek_cts )
1101 in_track->skip_duration = seek_cts - rap_cts;
1104 /* Set number of the first sample to be muxed. */
1105 in_track->current_sample_number = consider_rap ? rap_number : seek_point;
1106 return 0;
1109 static void exclude_invalid_output_track( output_t *output, output_track_t *out_track,
1110 input_movie_t *in_movie, input_track_t *in_track,
1111 const char *message, ... )
1113 REFRESH_CONSOLE;
1114 eprintf( "[Warning] in %"PRIu32"/%"PRIu32" -> out %"PRIu32": ", in_movie->movie_ID, in_track->track_ID, out_track->track_ID );
1115 va_list args;
1116 va_start( args, message );
1117 vfprintf( stderr, message, args );
1118 va_end( args );
1119 lsmash_delete_track( output->root, out_track->track_ID );
1120 -- output->file.movie.num_tracks;
1121 in_track->active = 0;
1124 static int prepare_output( remuxer_t *remuxer )
1126 input_t *input = remuxer->input;
1127 output_t *output = remuxer->output;
1128 output_movie_t *out_movie = &output->file.movie;
1129 /* Count the number of output tracks. */
1130 for( int i = 0; i < remuxer->num_input; i++ )
1131 out_movie->num_tracks += input[i].file.movie.num_tracks;
1132 for( int i = 0; i < remuxer->num_input; i++ )
1134 input_movie_t *in_movie = &input[i].file.movie;
1135 for( uint32_t j = 0; j < in_movie->num_tracks; j++ )
1137 /* Don't remux tracks specified as 'remove' by a user. */
1138 if( remuxer->track_option[i][j].remove )
1139 in_movie->track[j].active = 0;
1140 if( !in_movie->track[j].active )
1141 -- out_movie->num_tracks;
1144 if( set_movie_parameters( remuxer ) < 0 )
1145 return ERROR_MSG( "failed to set output movie parameters.\n" );
1146 set_itunes_metadata( output, input, remuxer->num_input );
1147 /* Allocate output tracks. */
1148 out_movie->track = lsmash_malloc( out_movie->num_tracks * sizeof(output_track_t) );
1149 if( !out_movie->track )
1150 return ERROR_MSG( "failed to alloc output tracks.\n" );
1151 out_movie->current_track_number = 1;
1152 for( int i = 0; i < remuxer->num_input; i++ )
1154 input_movie_t *in_movie = &input[i].file.movie;
1155 for( uint32_t j = 0; j < in_movie->num_tracks; j++ )
1157 track_media_option *current_track_opt = &remuxer->track_option[i][j];
1158 input_track_t *in_track = &in_movie->track[j];
1159 if( !in_track->active )
1160 continue;
1161 output_track_t *out_track = &out_movie->track[ out_movie->current_track_number - 1 ];
1162 out_track->summary_remap = lsmash_malloc_zero( in_track->num_summaries * sizeof(uint32_t) );
1163 if( !out_track->summary_remap )
1164 return ERROR_MSG( "failed to create summary mapping for a track.\n" );
1165 out_track->track_ID = lsmash_create_track( output->root, in_track->media.param.handler_type );
1166 if( !out_track->track_ID )
1167 return ERROR_MSG( "failed to create a track.\n" );
1168 /* Copy track and media parameters except for track_ID. */
1169 out_track->track_param = in_track->track_param;
1170 out_track->media_param = in_track->media.param;
1171 /* Set track and media parameters specified by users */
1172 out_track->track_param.alternate_group = current_track_opt->alternate_group;
1173 out_track->media_param.ISO_language = current_track_opt->ISO_language;
1174 out_track->media_param.media_handler_name = current_track_opt->handler_name;
1175 out_track->track_param.track_ID = out_track->track_ID;
1176 if( current_track_opt->disable )
1177 out_track->track_param.mode &= ~ISOM_TRACK_ENABLED;
1178 if( lsmash_set_track_parameters( output->root, out_track->track_ID, &out_track->track_param ) < 0 )
1180 exclude_invalid_output_track( output, out_track, in_movie, in_track, "failed to set track parameters.\n" );
1181 continue;
1183 if( lsmash_set_media_parameters( output->root, out_track->track_ID, &out_track->media_param ) < 0 )
1185 exclude_invalid_output_track( output, out_track, in_movie, in_track, "failed to set media parameters.\n" );
1186 continue;
1188 lsmash_data_reference_t data_ref = { .index = 1, .location = NULL };
1189 if( lsmash_create_data_reference( output->root, out_track->track_ID, &data_ref, output->file.fh ) < 0 )
1190 return ERROR_MSG( "failed to create a data reference for output movie.\n" );
1191 uint32_t valid_summary_count = 0;
1192 for( uint32_t k = 0; k < in_track->num_summaries; k++ )
1194 if( !in_track->summaries[k].active )
1196 out_track->summary_remap[k] = 0;
1197 continue;
1199 lsmash_summary_t *summary = in_track->summaries[k].summary;
1200 summary->data_ref_index = 1;
1201 if( lsmash_add_sample_entry( output->root, out_track->track_ID, summary ) == 0 )
1203 WARNING_MSG( "failed to append a summary.\n" );
1204 lsmash_cleanup_summary( summary );
1205 in_track->summaries[k].summary = NULL;
1206 in_track->summaries[k].active = 0;
1207 out_track->summary_remap[k] = 0;
1208 continue;
1210 out_track->summary_remap[k] = ++valid_summary_count;
1212 if( valid_summary_count == 0 )
1214 exclude_invalid_output_track( output, out_track, in_movie, in_track, "failed to append all summaries.\n" );
1215 continue;
1217 out_track->last_sample_delta = in_track->last_sample_delta;
1218 if( set_starting_point( input, in_track, current_track_opt->seek, current_track_opt->consider_rap ) < 0 )
1220 exclude_invalid_output_track( output, out_track, in_movie, in_track, "failed to set starting point.\n" );
1221 continue;
1223 out_track->current_sample_number = 1;
1224 out_track->skip_dt_interval = 0;
1225 out_track->last_sample_dts = 0;
1226 ++ out_movie->current_track_number;
1229 if( out_movie->num_tracks == 0 )
1230 return ERROR_MSG( "failed to create the output movie.\n" );
1231 out_movie->current_track_number = 1;
1232 output->current_seg_number = 1;
1233 return 0;
1236 static void set_reference_chapter_track( remuxer_t *remuxer )
1238 if( remuxer->ref_chap_available )
1239 lsmash_create_reference_chapter_track( remuxer->output->root, remuxer->chap_track, remuxer->chap_file );
1242 static int flush_movie_fragment( remuxer_t *remuxer )
1244 input_t *inputs = remuxer->input;
1245 output_t *output = remuxer->output;
1246 output_movie_t *out_movie = &output->file.movie;
1247 uint32_t out_current_track_number = 1;
1248 for( uint32_t i = 1; i <= remuxer->num_input; i++ )
1250 input_t *in = &inputs[i - 1];
1251 input_movie_t *in_movie = &in->file.movie;
1252 for( uint32_t j = 1; j <= in_movie->num_tracks; j++ )
1254 input_track_t *in_track = &in_movie->track[j - 1];
1255 if( !in_track->active )
1256 continue;
1257 output_track_t *out_track = &out_movie->track[out_current_track_number - 1];
1258 if( !in_track->reach_end_of_media_timeline )
1260 lsmash_sample_t sample;
1261 if( lsmash_get_sample_info_from_media_timeline( in->root, in_track->track_ID, in_track->current_sample_number, &sample ) < 0 )
1262 return ERROR_MSG( "failed to get the information of the next sample.\n" );
1263 uint64_t sample_dts = sample.dts - out_track->skip_dt_interval;
1264 if( lsmash_flush_pooled_samples( output->root, out_track->track_ID, sample_dts - out_track->last_sample_dts ) < 0 )
1265 return ERROR_MSG( "failed to flush the rest of samples in a fragment.\n" );
1267 else
1268 if( lsmash_flush_pooled_samples( output->root, out_track->track_ID, out_track->last_sample_delta ) < 0 )
1269 return ERROR_MSG( "failed to flush the rest of samples in a fragment.\n" );
1270 if( ++out_current_track_number > out_movie->num_tracks )
1271 break;
1274 return 0;
1277 static int moov_to_front_callback( void *param, uint64_t written_movie_size, uint64_t total_movie_size )
1279 REFRESH_CONSOLE;
1280 eprintf( "Finalizing: [%5.2lf%%]\r", ((double)written_movie_size / total_movie_size) * 100.0 );
1281 return 0;
1284 static lsmash_adhoc_remux_t moov_to_front =
1286 .func = moov_to_front_callback,
1287 .buffer_size = 4 * 1024 * 1024, /* 4MiB */
1288 .param = NULL
1291 static int open_media_segment( output_t *output, lsmash_file_parameters_t *seg_param )
1293 /* Open a media segment file.
1294 * Each file is named as follows.
1295 * a.mp4
1296 * a_1.mp4
1297 * a_2.mp4
1298 * ...
1299 * a_N.mp4
1300 * N is the number of segment files excluding the initialization segment file.
1302 output_file_t *out_file = &output->file;
1303 int out_file_name_length = strlen( out_file->name );
1304 const char *end = &out_file->name[ out_file_name_length ];
1305 const char *p = end;
1306 while( p >= out_file->name && *p != '.' && *p != '/' && *p != '\\' )
1307 --p;
1308 if( p < out_file->name )
1309 ++p;
1310 if( *p != '.' )
1311 p = end;
1312 int suffix_length = 1;
1313 for( uint32_t i = output->current_seg_number; i; i /= 10 )
1314 ++suffix_length;
1315 int seg_name_length = out_file_name_length + suffix_length;
1316 int suffixless_length = p - out_file->name;
1317 char *seg_name = lsmash_malloc( (seg_name_length + 1) * sizeof(char) );
1318 if( !seg_name )
1319 return ERROR_MSG( "failed to allocate to store a segment filename.\n" );
1320 seg_name[ seg_name_length ] = '\0';
1321 memcpy( seg_name, out_file->name, suffixless_length );
1322 sprintf( seg_name + suffixless_length, "_%"PRIu32, output->current_seg_number );
1323 if( *p == '.' )
1324 memcpy( seg_name + suffixless_length + suffix_length, p, end - p );
1325 int ret = lsmash_open_file( seg_name, 0, seg_param );
1326 if( ret == 0 )
1327 eprintf( "[Segment] out: %s\n", seg_name );
1328 lsmash_free( seg_name );
1329 return ret;
1332 static int switch_segment( remuxer_t *remuxer )
1334 output_t *output = remuxer->output;
1335 output_file_t *out_file = &output->file;
1336 lsmash_file_parameters_t seg_param = { 0 };
1337 if( open_media_segment( output, &seg_param ) < 0 )
1338 return ERROR_MSG( "failed to open an output file for segmentation.\n" );
1339 /* Set up the media segment file.
1340 * Copy the parameters of the previous segment if the previous is not the initialization segment. */
1341 if( out_file->seg_param.mode & LSMASH_FILE_MODE_INITIALIZATION )
1343 uint32_t brand_count = out_file->param.brand_count + 2;
1344 lsmash_brand_type *brands = lsmash_malloc_zero( brand_count * sizeof(lsmash_brand_type) );
1345 if( !brands )
1346 return ERROR_MSG( "failed to allocate brands for an output segment file.\n" );
1347 brands[0] = ISOM_BRAND_TYPE_MSDH;
1348 brands[1] = ISOM_BRAND_TYPE_MSIX;
1349 for( uint32_t i = 0; i < out_file->param.brand_count; i++ )
1350 brands[i + 2] = out_file->param.brands[i];
1351 seg_param.major_brand = ISOM_BRAND_TYPE_MSDH;
1352 seg_param.brand_count = brand_count;
1353 seg_param.brands = brands;
1354 seg_param.mode = LSMASH_FILE_MODE_WRITE | LSMASH_FILE_MODE_FRAGMENTED
1355 | LSMASH_FILE_MODE_BOX | LSMASH_FILE_MODE_MEDIA
1356 | LSMASH_FILE_MODE_INDEX | LSMASH_FILE_MODE_SEGMENT;
1358 else
1360 void *opaque = seg_param.opaque;
1361 seg_param = out_file->seg_param;
1362 seg_param.opaque = opaque;
1364 lsmash_file_t *segment = lsmash_set_file( output->root, &seg_param );
1365 if( !segment )
1366 return ERROR_MSG( "failed to add an output segment file into a ROOT.\n" );
1367 /* Switch to the next segment.
1368 * After switching, close the previous segment if the previous is not the initialization segment. */
1369 if( lsmash_switch_media_segment( output->root, segment, &moov_to_front ) < 0 )
1370 return ERROR_MSG( "failed to switch to the next segment.\n" );
1371 if( !(out_file->seg_param.mode & LSMASH_FILE_MODE_INITIALIZATION) )
1372 return lsmash_close_file( &out_file->seg_param );
1373 out_file->seg_param = seg_param;
1374 return 0;
1377 static int handle_segmentation( remuxer_t *remuxer )
1379 if( remuxer->subseg_per_seg == 0 )
1380 return 0;
1381 output_t *output = remuxer->output;
1382 if( remuxer->subseg_per_seg == output->file.current_subseg_number
1383 || output->current_seg_number == 1 )
1385 if( switch_segment( remuxer ) < 0 )
1387 ERROR_MSG( "failed to switch to a segment.\n" );
1388 return -1;
1390 output->file.current_subseg_number = 1;
1391 ++ output->current_seg_number;
1393 else
1394 ++ output->file.current_subseg_number;
1395 return 0;
1398 static void adapt_description_index( output_track_t *out_track, input_track_t *in_track, lsmash_sample_t *sample )
1400 sample->index = sample->index > in_track->num_summaries ? in_track->num_summaries
1401 : sample->index == 0 ? 1
1402 : sample->index;
1403 sample->index = out_track->summary_remap[ sample->index - 1 ];
1404 if( in_track->current_sample_index == 0 )
1405 in_track->current_sample_index = sample->index;
1408 static void adjust_timestamp( output_track_t *out_track, lsmash_sample_t *sample )
1410 /* The first DTS must be 0. */
1411 if( out_track->current_sample_number == 1 )
1412 out_track->skip_dt_interval = sample->dts;
1413 if( out_track->skip_dt_interval )
1415 sample->dts -= out_track->skip_dt_interval;
1416 sample->cts -= out_track->skip_dt_interval;
1420 static int do_remux( remuxer_t *remuxer )
1422 #define LSMASH_MAX( a, b ) ((a) > (b) ? (a) : (b))
1423 input_t *inputs = remuxer->input;
1424 output_t *output = remuxer->output;
1425 output_movie_t *out_movie = &output->file.movie;
1426 set_reference_chapter_track( remuxer );
1427 double largest_dts = 0; /* in seconds */
1428 double frag_base_dts = 0; /* in seconds */
1429 uint32_t input_movie_number = 1;
1430 uint32_t num_consecutive_sample_skip = 0;
1431 uint32_t num_active_input_tracks = out_movie->num_tracks;
1432 uint64_t total_media_size = 0;
1433 uint8_t sample_count = 0;
1434 uint8_t pending_flush_fragments = (remuxer->frag_base_track != 0); /* For non-fragmented movie, always set to 0. */
1435 while( 1 )
1437 input_t *in = &inputs[input_movie_number - 1];
1438 input_movie_t *in_movie = &in->file.movie;
1439 input_track_t *in_track = &in_movie->track[ in_movie->current_track_number - 1 ];
1440 if( !in_track->active )
1442 /* Move the next track. */
1443 if( ++ in_movie->current_track_number > in_movie->num_tracks )
1445 /* Move the next input movie. */
1446 in_movie->current_track_number = 1;
1447 ++input_movie_number;
1449 if( input_movie_number > remuxer->num_input )
1450 input_movie_number = 1; /* Back the first input movie. */
1451 continue;
1453 /* Try append a sample in an input track where we didn't reach the end of media timeline. */
1454 if( !in_track->reach_end_of_media_timeline )
1456 lsmash_sample_t *sample = in_track->sample;
1457 /* Get a new sample data if the track doesn't hold any one. */
1458 if( !sample )
1460 sample = lsmash_get_sample_from_media_timeline( in->root, in_track->track_ID, in_track->current_sample_number );
1461 if( sample )
1463 output_track_t *out_track = &out_movie->track[ out_movie->current_track_number - 1 ];
1464 adapt_description_index( out_track, in_track, sample );
1465 adjust_timestamp( out_track, sample );
1466 in_track->sample = sample;
1467 in_track->dts = (double)sample->dts / in_track->media.param.timescale;
1469 else
1471 if( lsmash_check_sample_existence_in_media_timeline( in->root, in_track->track_ID, in_track->current_sample_number ) )
1473 ERROR_MSG( "failed to get a sample.\n" );
1474 break;
1476 lsmash_sample_t sample_info = { 0 };
1477 if( lsmash_get_sample_info_from_media_timeline( in->root, in_track->track_ID, in_track->current_sample_number, &sample_info ) < 0 )
1479 /* No more appendable samples in this track. */
1480 in_track->sample = NULL;
1481 in_track->reach_end_of_media_timeline = 1;
1482 if( --num_active_input_tracks == 0 )
1483 break; /* end of muxing */
1485 else
1487 ERROR_MSG( "failed to get a sample.\n" );
1488 break;
1492 if( sample )
1494 /* Flushing the active movie fragment is pending until random accessible point sample within all active tracks are ready. */
1495 if( remuxer->frag_base_track )
1497 if( pending_flush_fragments == 0 )
1499 if( remuxer->frag_base_track == out_movie->current_track_number
1500 && sample->prop.ra_flags != ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE )
1502 pending_flush_fragments = 1;
1503 frag_base_dts = in_track->dts;
1506 else if( num_consecutive_sample_skip == num_active_input_tracks || total_media_size == 0 )
1508 if( flush_movie_fragment( remuxer ) < 0 )
1510 ERROR_MSG( "failed to flush a movie fragment.\n" );
1511 break;
1513 if( handle_segmentation( remuxer ) < 0 )
1514 break;
1515 if( lsmash_create_fragment_movie( output->root ) < 0 )
1517 ERROR_MSG( "failed to create a movie fragment.\n" );
1518 break;
1520 pending_flush_fragments = 0;
1523 /* Append a sample if meeting a condition. */
1524 int append = 0;
1525 int need_new_fragment = (remuxer->frag_base_track && sample->index != in_track->current_sample_index);
1526 if( pending_flush_fragments == 0 )
1527 append = (in_track->dts <= largest_dts || num_consecutive_sample_skip == num_active_input_tracks) && !need_new_fragment;
1528 else if( remuxer->frag_base_track != out_movie->current_track_number && !need_new_fragment )
1530 /* Wait as much as possible both to make the last sample within each track fragment close to the DTS of
1531 * the first sample within the track fragment corresponding to the base track within the next movie
1532 * fragment and to make all the track fragments within the next movie fragment start with RAP. */
1533 if( sample->prop.ra_flags == ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE )
1534 append = 1;
1535 else
1537 /* Check the DTS and random accessibilities of the next sample. */
1538 lsmash_sample_t info;
1539 if( lsmash_get_sample_info_from_media_timeline( in->root, in_track->track_ID, in_track->current_sample_number + 1, &info ) < 0 )
1540 append = 0;
1541 else
1542 append = (info.prop.ra_flags != ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE)
1543 && ((double)info.dts / in_track->media.param.timescale <= frag_base_dts);
1546 if( append )
1548 if( sample->index )
1550 output_track_t *out_track = &out_movie->track[ out_movie->current_track_number - 1 ];
1551 uint64_t sample_size = sample->length; /* sample might be deleted internally after appending. */
1552 uint64_t last_sample_dts = sample->dts; /* same as above */
1553 uint32_t sample_index = sample->index; /* same as above */
1554 /* Append a sample into output movie. */
1555 if( lsmash_append_sample( output->root, out_track->track_ID, sample ) < 0 )
1557 lsmash_delete_sample( sample );
1558 return ERROR_MSG( "failed to append a sample.\n" );
1560 largest_dts = LSMASH_MAX( largest_dts, in_track->dts );
1561 in_track->sample = NULL;
1562 in_track->current_sample_number += 1;
1563 in_track->current_sample_index = sample_index;
1564 out_track->current_sample_number += 1;
1565 out_track->last_sample_dts = last_sample_dts;
1566 num_consecutive_sample_skip = 0;
1567 total_media_size += sample_size;
1568 /* Print, per 256 samples, total size of imported media. */
1569 if( ++sample_count == 0 )
1570 eprintf( "Importing: %"PRIu64" bytes\r", total_media_size );
1572 else
1574 lsmash_delete_sample( sample );
1575 in_track->sample = NULL;
1576 in_track->current_sample_number += 1;
1579 else
1580 ++num_consecutive_sample_skip; /* Skip appendig sample. */
1583 /* Move the next track. */
1584 if( ++ in_movie->current_track_number > in_movie->num_tracks )
1586 /* Move the next input movie. */
1587 in_movie->current_track_number = 1;
1588 ++input_movie_number;
1590 if( input_movie_number > remuxer->num_input )
1591 input_movie_number = 1; /* Back the first input movie. */
1592 if( ++ out_movie->current_track_number > out_movie->num_tracks )
1593 out_movie->current_track_number = 1; /* Back the first track in the output movie. */
1595 for( uint32_t i = 0; i < out_movie->num_tracks; i++ )
1596 if( lsmash_flush_pooled_samples( output->root, out_movie->track[i].track_ID, out_movie->track[i].last_sample_delta ) )
1597 return ERROR_MSG( "failed to flush samples.\n" );
1598 return 0;
1599 #undef LSMASH_MAX
1602 static int construct_timeline_maps( remuxer_t *remuxer )
1604 input_t *input = remuxer->input;
1605 output_t *output = remuxer->output;
1606 output_movie_t *out_movie = &output->file.movie;
1607 track_media_option **track_option = remuxer->track_option;
1608 out_movie->current_track_number = 1;
1609 for( int i = 0; i < remuxer->num_input; i++ )
1610 for( uint32_t j = 0; j < input[i].file.movie.num_tracks; j++ )
1612 input_track_t *in_track = &input[i].file.movie.track[j];
1613 if( !in_track->active )
1614 continue;
1615 output_track_t *out_track = &out_movie->track[ out_movie->current_track_number ++ - 1 ];
1616 if( track_option[i][j].seek )
1618 /* Reconstruct timeline maps. */
1619 if( lsmash_delete_explicit_timeline_map( output->root, out_track->track_ID ) )
1620 return ERROR_MSG( "failed to delete explicit timeline maps.\n" );
1621 uint32_t movie_timescale = lsmash_get_movie_timescale( output->root );
1622 uint32_t media_timescale = lsmash_get_media_timescale( output->root, out_track->track_ID );
1623 if( !media_timescale )
1624 return ERROR_MSG( "media timescale is broken.\n" );
1625 double timescale_convert_multiplier = (double)movie_timescale / media_timescale;
1626 lsmash_edit_t edit;
1627 edit.start_time = in_track->composition_delay + in_track->skip_duration;
1628 if( edit.start_time )
1630 uint64_t empty_duration = edit.start_time + lsmash_get_composition_to_decode_shift( output->root, out_track->track_ID );
1631 lsmash_edit_t empty_edit;
1632 empty_edit.duration = empty_duration * timescale_convert_multiplier + 0.5;
1633 empty_edit.start_time = ISOM_EDIT_MODE_EMPTY;
1634 empty_edit.rate = ISOM_EDIT_MODE_NORMAL;
1635 if( lsmash_create_explicit_timeline_map( output->root, out_track->track_ID, empty_edit ) )
1636 return ERROR_MSG( "failed to create a empty duration.\n" );
1638 if( remuxer->frag_base_track == 0 )
1639 edit.duration = (out_track->last_sample_dts + out_track->last_sample_delta - in_track->skip_duration) * timescale_convert_multiplier;
1640 else
1641 edit.duration = ISOM_EDIT_DURATION_IMPLICIT;
1642 edit.rate = ISOM_EDIT_MODE_NORMAL;
1643 if( lsmash_create_explicit_timeline_map( output->root, out_track->track_ID, edit ) )
1644 return ERROR_MSG( "failed to create a explicit timeline map.\n" );
1646 else if( lsmash_copy_timeline_map( output->root, out_track->track_ID, input[i].root, in_track->track_ID ) )
1647 return ERROR_MSG( "failed to copy timeline maps.\n" );
1649 out_movie->current_track_number = 1;
1650 return 0;
1653 static int finish_movie( remuxer_t *remuxer )
1655 output_t *output = remuxer->output;
1656 /* Set chapter list */
1657 if( remuxer->chap_file )
1658 lsmash_set_tyrant_chapter( output->root, remuxer->chap_file, remuxer->add_bom_to_chpl );
1659 /* Finish muxing. */
1660 REFRESH_CONSOLE;
1661 if( lsmash_finish_movie( output->root, &moov_to_front ) )
1662 return -1;
1663 return remuxer->frag_base_track ? 0 : lsmash_write_lsmash_indicator( output->root );
1666 int main( int argc, char *argv[] )
1668 if ( argc < 2 )
1670 display_help();
1671 return -1;
1673 else if( !strcasecmp( argv[1], "-h" ) || !strcasecmp( argv[1], "--help" ) )
1675 display_help();
1676 return 0;
1678 else if( !strcasecmp( argv[1], "-v" ) || !strcasecmp( argv[1], "--version" ) )
1680 display_version();
1681 return 0;
1683 else if( argc < 5 )
1685 display_help();
1686 return -1;
1689 lsmash_get_mainargs( &argc, &argv );
1690 int num_input = 0;
1691 for( int i = 1 ; i < argc ; i++ )
1692 if( !strcasecmp( argv[i], "-i" ) || !strcasecmp( argv[i], "--input" ) )
1693 num_input++;
1694 if( !num_input )
1695 return ERROR_MSG( "no input file specified.\n" );
1696 output_t output = { 0 };
1697 input_t *input = lsmash_malloc_zero( num_input * sizeof(input_t) );
1698 if( !input )
1699 return ERROR_MSG( "failed to allocate the input handler.\n" );
1700 track_media_option **track_option = lsmash_malloc_zero( num_input * sizeof(track_media_option *) );
1701 if( !track_option )
1703 lsmash_free( input );
1704 return ERROR_MSG( "failed to allocate the track option handler.\n" );
1706 remuxer_t remuxer =
1708 .output = &output,
1709 .input = input,
1710 .track_option = track_option,
1711 .num_input = num_input,
1712 .add_bom_to_chpl = 0,
1713 .ref_chap_available = 0,
1714 .chap_track = 1,
1715 .chap_file = NULL,
1716 .default_language = 0,
1717 .frag_base_track = 0,
1718 .subseg_per_seg = 0,
1719 .dash = 0
1721 if( parse_cli_option( argc, argv, &remuxer ) )
1722 return REMUXER_ERR( "failed to parse command line options.\n" );
1723 if( prepare_output( &remuxer ) )
1724 return REMUXER_ERR( "failed to set up preparation for output.\n" );
1725 if( remuxer.frag_base_track && construct_timeline_maps( &remuxer ) )
1726 return REMUXER_ERR( "failed to construct timeline maps.\n" );
1727 if( do_remux( &remuxer ) )
1728 return REMUXER_ERR( "failed to remux movies.\n" );
1729 if( remuxer.frag_base_track == 0 && construct_timeline_maps( &remuxer ) )
1730 return REMUXER_ERR( "failed to construct timeline maps.\n" );
1731 if( finish_movie( &remuxer ) )
1732 return REMUXER_ERR( "failed to finish output movie.\n" );
1733 REFRESH_CONSOLE;
1734 eprintf( "%s completed!\n", !remuxer.dash || remuxer.subseg_per_seg == 0 ? "Remuxing" : "Segmentation" );
1735 cleanup_remuxer( &remuxer );
1736 return 0;