1 /*****************************************************************************
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 "common/internal.h"
38 #define LSMASH_MAX( a, b ) ((a) > (b) ? (a) : (b))
40 #define eprintf( ... ) fprintf( stderr, __VA_ARGS__ )
41 #define REFRESH_CONSOLE eprintf( " \r" )
46 lsmash_summary_t
*summary
;
53 uint32_t last_sample_delta
;
54 uint32_t current_sample_number
;
55 int reach_end_of_media_timeline
;
56 uint32_t *summary_remap
;
57 uint32_t num_summaries
;
59 lsmash_track_parameters_t track_param
;
60 lsmash_media_parameters_t media_param
;
65 lsmash_itunes_metadata_t
*itunes_metadata
;
67 lsmash_movie_parameters_t param
;
69 uint32_t num_itunes_metadata
;
70 uint32_t current_track_number
;
76 lsmash_file_parameters_t param
;
90 uint32_t sample_count
;
91 int auto_media_timescale
;
92 int auto_media_timebase
;
93 uint64_t media_timescale
;
94 uint64_t media_timebase
;
96 uint64_t composition_delay
;
104 timecode_t
*timecode
;
109 uint32_t track_number
;
110 uint32_t media_timescale
;
111 uint32_t media_timebase
;
112 uint32_t skip_duration
;
113 uint32_t empty_delay
;
117 static void cleanup_root( root_t
*h
)
121 movie_t
*movie
= &h
->file
.movie
;
122 if( movie
->itunes_metadata
)
124 for( uint32_t i
= 0; i
< movie
->num_itunes_metadata
; i
++ )
126 lsmash_itunes_metadata_t
*metadata
= &movie
->itunes_metadata
[i
];
127 if( metadata
->type
== ITUNES_METADATA_TYPE_STRING
)
129 if( metadata
->value
.string
)
130 lsmash_free( metadata
->value
.string
);
132 else if( metadata
->type
== ITUNES_METADATA_TYPE_BINARY
)
133 if( metadata
->value
.binary
.data
)
134 lsmash_free( metadata
->value
.binary
.data
);
135 if( metadata
->meaning
)
136 lsmash_free( metadata
->meaning
);
138 lsmash_free( metadata
->name
);
140 lsmash_freep( &movie
->itunes_metadata
);
143 lsmash_freep( &movie
->track
);
144 lsmash_close_file( &h
->file
.param
);
145 lsmash_destroy_root( h
->root
);
149 static void cleanup_timecode( timecode_t
*timecode
)
155 fclose( timecode
->file
);
156 timecode
->file
= NULL
;
159 lsmash_freep( &timecode
->ts
);
162 static int error_message( const char* message
, ... )
165 eprintf( "Error: " );
167 va_start( args
, message
);
168 vfprintf( stderr
, message
, args
);
173 static int warning_message( const char* message
, ... )
176 eprintf( "Warning: " );
178 va_start( args
, message
);
179 vfprintf( stderr
, message
, args
);
184 static int timelineeditor_error( movie_io_t
*io
, const char *message
, ... )
186 cleanup_root( io
->input
);
187 cleanup_root( io
->output
);
188 cleanup_timecode( io
->timecode
);
190 va_start( args
, message
);
191 error_message( message
, args
);
196 #define TIMELINEEDITOR_ERR( ... ) timelineeditor_error( &io, __VA_ARGS__ )
197 #define ERROR_MSG( ... ) error_message( __VA_ARGS__ )
198 #define WARNING_MSG( ... ) warning_message( __VA_ARGS__ )
200 static char *duplicate_string( char *src
)
204 int dst_size
= strlen( src
) + 1;
205 char *dst
= lsmash_malloc( dst_size
);
208 memcpy( dst
, src
, dst_size
);
212 static int get_itunes_metadata( lsmash_root_t
*root
, uint32_t metadata_number
, lsmash_itunes_metadata_t
*metadata
)
214 memset( metadata
, 0, sizeof(lsmash_itunes_metadata_t
) );
215 if( lsmash_get_itunes_metadata( root
, metadata_number
, metadata
) )
217 lsmash_itunes_metadata_t shadow
= *metadata
;
218 metadata
->meaning
= NULL
;
219 metadata
->name
= NULL
;
220 memset( &metadata
->value
, 0, sizeof(lsmash_itunes_metadata_value_t
) );
223 metadata
->meaning
= duplicate_string( shadow
.meaning
);
224 if( !metadata
->meaning
)
229 metadata
->name
= duplicate_string( shadow
.name
);
230 if( !metadata
->name
)
233 if( shadow
.type
== ITUNES_METADATA_TYPE_STRING
)
235 metadata
->value
.string
= duplicate_string( shadow
.value
.string
);
236 if( !metadata
->value
.string
)
239 else if( shadow
.type
== ITUNES_METADATA_TYPE_BINARY
)
241 metadata
->value
.binary
.data
= lsmash_malloc( shadow
.value
.binary
.size
);
242 if( !metadata
->value
.binary
.data
)
244 memcpy( metadata
->value
.binary
.data
, shadow
.value
.binary
.data
, shadow
.value
.binary
.size
);
248 if( metadata
->meaning
)
249 lsmash_free( metadata
->meaning
);
251 lsmash_free( metadata
->name
);
255 static int get_summaries( root_t
*input
, track_t
*track
)
257 track
->num_summaries
= lsmash_count_summary( input
->root
, track
->track_ID
);
258 if( track
->num_summaries
== 0 )
259 return ERROR_MSG( "Failed to get find valid summaries.\n" );
260 track
->summaries
= lsmash_malloc( track
->num_summaries
* sizeof(summary_t
) );
261 if( !track
->summaries
)
262 return ERROR_MSG( "failed to alloc input summaries.\n" );
263 memset( track
->summaries
, 0, track
->num_summaries
* sizeof(summary_t
) );
264 for( uint32_t j
= 0; j
< track
->num_summaries
; j
++ )
266 lsmash_summary_t
*summary
= lsmash_get_summary( input
->root
, track
->track_ID
, j
+ 1 );
269 WARNING_MSG( "failed to get a summary.\n" );
272 track
->summaries
[j
].summary
= summary
;
273 track
->summaries
[j
].active
= 1;
278 static int get_movie( root_t
*input
, char *input_name
)
280 if( !strcmp( input_name
, "-" ) )
281 return ERROR_MSG( "Standard input not supported.\n" );
282 input
->root
= lsmash_create_root();
284 return ERROR_MSG( "failed to create a ROOT for an input file.\n" );
285 file_t
*in_file
= &input
->file
;
286 if( lsmash_open_file( input_name
, 1, &in_file
->param
) < 0 )
287 return ERROR_MSG( "failed to open an input file.\n" );
288 in_file
->fh
= lsmash_set_file( input
->root
, &in_file
->param
);
290 return ERROR_MSG( "failed to add an input file into a ROOT.\n" );
291 if( lsmash_read_file( in_file
->fh
, &in_file
->param
) < 0 )
292 return ERROR_MSG( "failed to read an input file\n" );
293 movie_t
*movie
= &in_file
->movie
;
294 movie
->num_itunes_metadata
= lsmash_count_itunes_metadata( input
->root
);
295 if( movie
->num_itunes_metadata
)
297 movie
->itunes_metadata
= lsmash_malloc( movie
->num_itunes_metadata
* sizeof(lsmash_itunes_metadata_t
) );
298 if( !movie
->itunes_metadata
)
299 return ERROR_MSG( "failed to alloc iTunes metadata.\n" );
300 uint32_t itunes_metadata_count
= 0;
301 for( uint32_t i
= 1; i
<= movie
->num_itunes_metadata
; i
++ )
303 if( get_itunes_metadata( input
->root
, i
, &movie
->itunes_metadata
[itunes_metadata_count
] ) )
305 WARNING_MSG( "failed to get an iTunes metadata.\n" );
308 ++itunes_metadata_count
;
310 movie
->num_itunes_metadata
= itunes_metadata_count
;
312 lsmash_initialize_movie_parameters( &movie
->param
);
313 lsmash_get_movie_parameters( input
->root
, &movie
->param
);
314 movie
->num_tracks
= movie
->param
.number_of_tracks
;
315 movie
->current_track_number
= 1;
317 track_t
*track
= movie
->track
= lsmash_malloc( movie
->num_tracks
* sizeof(track_t
) );
319 return ERROR_MSG( "Failed to alloc input tracks.\n" );
320 memset( track
, 0, movie
->num_tracks
* sizeof(track_t
) );
321 for( uint32_t i
= 0; i
< movie
->num_tracks
; i
++ )
323 track
[i
].track_ID
= lsmash_get_track_ID( input
->root
, i
+ 1 );
324 if( !track
[i
].track_ID
)
325 return ERROR_MSG( "Failed to get track_ID.\n" );
327 for( uint32_t i
= 0; i
< movie
->num_tracks
; i
++ )
329 lsmash_initialize_track_parameters( &track
[i
].track_param
);
330 if( lsmash_get_track_parameters( input
->root
, track
[i
].track_ID
, &track
[i
].track_param
) )
332 WARNING_MSG( "failed to get track parameters.\n" );
335 lsmash_initialize_media_parameters( &track
[i
].media_param
);
336 if( lsmash_get_media_parameters( input
->root
, track
[i
].track_ID
, &track
[i
].media_param
) )
338 WARNING_MSG( "failed to get media parameters.\n" );
341 if( lsmash_construct_timeline( input
->root
, track
[i
].track_ID
) )
343 WARNING_MSG( "failed to construct timeline.\n" );
346 if( lsmash_get_last_sample_delta_from_media_timeline( input
->root
, track
[i
].track_ID
, &track
[i
].last_sample_delta
) )
348 WARNING_MSG( "failed to get the last sample delta.\n" );
351 if( get_summaries( input
, &track
[i
] ) )
353 WARNING_MSG( "failed to get valid summaries.\n" );
357 track
[i
].current_sample_number
= 1;
359 lsmash_destroy_children( lsmash_file_as_box( in_file
->fh
) );
363 static inline uint64_t get_gcd( uint64_t a
, uint64_t b
)
377 static inline uint64_t get_lcm( uint64_t a
, uint64_t b
)
381 return (a
/ get_gcd( a
, b
)) * b
;
384 static uint64_t get_media_timebase( lsmash_media_ts_list_t
*ts_list
)
386 uint64_t timebase
= ts_list
->timestamp
[0].cts
;
387 for( uint32_t i
= 1; i
< ts_list
->sample_count
; i
++ )
388 timebase
= get_gcd( timebase
, ts_list
->timestamp
[i
].cts
);
389 for( uint32_t i
= 0; i
< ts_list
->sample_count
; i
++ )
390 timebase
= get_gcd( timebase
, ts_list
->timestamp
[i
].dts
);
394 static inline double sigexp10( double value
, double *exponent
)
396 /* This function separates significand and exp10 from double floating point. */
411 #define DOUBLE_EPSILON 5e-6
412 #define MATROSKA_TIMESCALE 1000000000
413 #define SKIP_LINE_CHARACTER( x ) ((x) == '#' || (x) == '\n' || (x) == '\r')
415 static double correct_fps( double fps
, timecode_t
*timecode
)
418 uint64_t fps_num
, fps_den
;
420 double fps_sig
= sigexp10( fps
, &exponent
);
423 fps_den
= i
* timecode
->media_timebase
;
424 fps_num
= round( fps_den
* fps_sig
) * exponent
;
425 if( fps_num
> UINT32_MAX
)
426 return ERROR_MSG( "framerate correction failed.\n"
427 "Specify an appropriate timebase manually or remake timecode file.\n" );
428 if( fabs( ((double)fps_num
/ fps_den
) / exponent
- fps_sig
) < DOUBLE_EPSILON
)
432 if( timecode
->auto_media_timescale
)
434 timecode
->media_timescale
= timecode
->media_timescale
435 ? get_lcm( timecode
->media_timescale
, fps_num
)
437 if( timecode
->media_timescale
> UINT32_MAX
)
438 timecode
->auto_media_timescale
= 0;
440 return (double)fps_num
/ fps_den
;
443 static int try_matroska_timescale( double *fps_array
, timecode_t
*timecode
, uint32_t num_loops
)
445 timecode
->media_timebase
= 0;
446 timecode
->media_timescale
= MATROSKA_TIMESCALE
;
447 for( uint32_t i
= 0; i
< num_loops
; i
++ )
451 double fps_sig
= sigexp10( fps_array
[i
], &exponent
);
452 fps_den
= round( MATROSKA_TIMESCALE
/ fps_sig
) / exponent
;
453 timecode
->media_timebase
= fps_den
&& timecode
->media_timebase
454 ? get_gcd( timecode
->media_timebase
, fps_den
)
456 if( timecode
->media_timebase
> UINT32_MAX
|| !timecode
->media_timebase
)
457 return ERROR_MSG( "Automatic media timescale generation failed.\n"
458 "Specify media timescale manually.\n" );
463 static int parse_timecode( timecode_t
*timecode
, uint32_t sample_count
)
466 int ret
= fscanf( timecode
->file
, "# timecode format v%d", &tcfv
);
467 if( ret
!= 1 || (tcfv
!= 1 && tcfv
!= 2) )
468 return ERROR_MSG( "Unsupported timecode format\n" );
470 double *timecode_array
= NULL
;
473 double assume_fps
= 0;
474 /* Get assumed framerate. */
475 while( fgets( buff
, sizeof(buff
), timecode
->file
) )
477 if( SKIP_LINE_CHARACTER( buff
[0] ) )
479 if( sscanf( buff
, "assume %lf", &assume_fps
) != 1
480 && sscanf( buff
, "Assume %lf", &assume_fps
) != 1 )
481 return ERROR_MSG( "Assumed fps not found\n" );
484 if( assume_fps
<= 0 )
485 return ERROR_MSG( "Invalid assumed fps\n" );
486 uint64_t file_pos
= ftell( timecode
->file
);
487 /* Check whether valid or not and count number of sequences. */
488 uint32_t num_sequences
= 0;
490 int64_t prev_start
= -1, prev_end
= -1;
492 while( fgets( buff
, sizeof(buff
), timecode
->file
) )
494 if( SKIP_LINE_CHARACTER( buff
[0] ) )
496 ret
= sscanf( buff
, "%"SCNd64
",%"SCNd64
",%lf", &start
, &end
, &sequence_fps
);
497 if( ret
!= 3 && ret
!= EOF
)
498 return ERROR_MSG( "Invalid input timecode file\n" );
499 if( start
> end
|| start
<= prev_start
|| end
<= prev_end
|| sequence_fps
<= 0 )
500 return ERROR_MSG( "Invalid input timecode file\n" );
503 if( timecode
->auto_media_timescale
|| timecode
->auto_media_timebase
)
506 fseek( timecode
->file
, file_pos
, SEEK_SET
);
507 /* Preparation storing timecodes. */
508 double fps_array
[ (timecode
->auto_media_timescale
|| timecode
->auto_media_timebase
) * num_sequences
+ 1 ];
509 double corrected_assume_fps
= correct_fps( assume_fps
, timecode
);
510 if( corrected_assume_fps
< 0 )
511 return ERROR_MSG( "Failed to correct the assumed framerate\n" );
512 timecode_array
= lsmash_malloc( sample_count
* sizeof(double) );
513 if( !timecode_array
)
514 return ERROR_MSG( "Failed to alloc timecodes\n" );
515 timecode_array
[0] = 0;
518 while( i
< sample_count
- 1 && fgets( buff
, sizeof(buff
), timecode
->file
) )
520 if( SKIP_LINE_CHARACTER( buff
[0] ) )
522 ret
= sscanf( buff
, "%"SCNd64
",%"SCNd64
",%lf", &start
, &end
, &sequence_fps
);
524 start
= end
= sample_count
- 1;
525 for( ; i
< start
&& i
< sample_count
- 1; i
++ )
526 timecode_array
[i
+ 1] = timecode_array
[i
] + 1 / corrected_assume_fps
;
527 if( i
< sample_count
- 1 )
529 if( timecode
->auto_media_timescale
|| timecode
->auto_media_timebase
)
530 fps_array
[num_sequences
++] = sequence_fps
;
531 sequence_fps
= correct_fps( sequence_fps
, timecode
);
532 if( sequence_fps
< 0 )
534 lsmash_free( timecode_array
);
535 return ERROR_MSG( "Failed to correct the framerate of a sequence.\n" );
537 for( i
= start
; i
<= end
&& i
< sample_count
- 1; i
++ )
538 timecode_array
[i
+ 1] = timecode_array
[i
] + 1 / sequence_fps
;
541 for( ; i
< sample_count
- 1; i
++ )
542 timecode_array
[i
+ 1] = timecode_array
[i
] + 1 / corrected_assume_fps
;
543 if( timecode
->auto_media_timescale
|| timecode
->auto_media_timebase
)
544 fps_array
[num_sequences
] = assume_fps
;
545 /* Assume matroska timebase if automatic timescale generation isn't done yet. */
546 if( timecode
->auto_media_timebase
&& !timecode
->auto_media_timescale
)
549 double assume_fps_sig
, sequence_fps_sig
;
550 if( try_matroska_timescale( fps_array
, timecode
, num_sequences
+ 1 ) < 0 )
552 lsmash_free( timecode_array
);
553 return ERROR_MSG( "Failed to try matroska timescale.\n" );
555 fseek( timecode
->file
, file_pos
, SEEK_SET
);
556 assume_fps_sig
= sigexp10( assume_fps
, &exponent
);
557 corrected_assume_fps
= MATROSKA_TIMESCALE
/ ( round( MATROSKA_TIMESCALE
/ assume_fps_sig
) / exponent
);
558 for( i
= 0; i
< sample_count
- 1 && fgets( buff
, sizeof(buff
), timecode
->file
); )
560 if( SKIP_LINE_CHARACTER( buff
[0] ) )
562 ret
= sscanf( buff
, "%"SCNd64
",%"SCNd64
",%lf", &start
, &end
, &sequence_fps
);
564 start
= end
= sample_count
- 1;
565 sequence_fps_sig
= sigexp10( sequence_fps
, &exponent
);
566 sequence_fps
= MATROSKA_TIMESCALE
/ ( round( MATROSKA_TIMESCALE
/ sequence_fps_sig
) / exponent
);
567 for( ; i
< start
&& i
< sample_count
- 1; i
++ )
568 timecode_array
[i
+ 1] = timecode_array
[i
] + 1 / corrected_assume_fps
;
569 for( i
= start
; i
<= end
&& i
< sample_count
- 1; i
++ )
570 timecode_array
[i
+ 1] = timecode_array
[i
] + 1 / sequence_fps
;
572 for( ; i
< sample_count
- 1; i
++ )
573 timecode_array
[i
+ 1] = timecode_array
[i
] + 1 / corrected_assume_fps
;
578 uint32_t num_timecodes
= 0;
579 uint64_t file_pos
= ftell( timecode
->file
);
580 while( fgets( buff
, sizeof(buff
), timecode
->file
) )
582 if( SKIP_LINE_CHARACTER( buff
[0] ) )
585 file_pos
= ftell( timecode
->file
);
591 return ERROR_MSG( "No timecodes!\n" );
592 if( sample_count
> num_timecodes
)
593 return ERROR_MSG( "Lack number of timecodes.\n" );
594 fseek( timecode
->file
, file_pos
, SEEK_SET
);
595 timecode_array
= lsmash_malloc( sample_count
* sizeof(uint64_t) );
596 if( !timecode_array
)
597 return ERROR_MSG( "Failed to alloc timecodes.\n" );
599 if( fgets( buff
, sizeof(buff
), timecode
->file
) )
601 ret
= sscanf( buff
, "%lf", &timecode_array
[0] );
604 lsmash_free( timecode_array
);
605 return ERROR_MSG( "Invalid timecode number: 0\n" );
607 timecode_array
[i
++] *= 1e-3; /* Timescale of timecode format v2 is 1000. */
608 while( i
< sample_count
&& fgets( buff
, sizeof(buff
), timecode
->file
) )
610 if( SKIP_LINE_CHARACTER( buff
[0] ) )
612 ret
= sscanf( buff
, "%lf", &timecode_array
[i
] );
613 timecode_array
[i
] *= 1e-3; /* Timescale of timecode format v2 is 1000. */
614 if( ret
!= 1 || timecode_array
[i
] <= timecode_array
[i
- 1] )
616 lsmash_free( timecode_array
);
617 return ERROR_MSG( "Invalid input timecode.\n" );
622 if( i
< sample_count
)
624 lsmash_free( timecode_array
);
625 return ERROR_MSG( "Failed to get timecodes.\n" );
627 /* Generate media timescale automatically if needed. */
628 if( sample_count
!= 1 && timecode
->auto_media_timescale
)
630 double fps_array
[sample_count
- 1];
631 for( i
= 0; i
< sample_count
- 1; i
++ )
633 fps_array
[i
] = 1 / (timecode_array
[i
+ 1] - timecode_array
[i
]);
634 if( timecode
->auto_media_timescale
)
637 uint64_t fps_num
, fps_den
;
639 double fps_sig
= sigexp10( fps_array
[i
], &exponent
);
642 fps_den
= j
* timecode
->media_timebase
;
643 fps_num
= round( fps_den
* fps_sig
) * exponent
;
644 if( fps_num
> UINT32_MAX
645 || fabs( ((double)fps_num
/ fps_den
) / exponent
- fps_sig
) < DOUBLE_EPSILON
)
649 timecode
->media_timescale
= fps_num
&& timecode
->media_timescale
650 ? get_lcm( timecode
->media_timescale
, fps_num
)
652 if( timecode
->media_timescale
> UINT32_MAX
)
654 timecode
->auto_media_timescale
= 0;
655 continue; /* Don't break because all framerate is needed for try_matroska_timescale. */
659 if( timecode
->auto_media_timebase
&& !timecode
->auto_media_timescale
660 && try_matroska_timescale( fps_array
, timecode
, sample_count
- 1 ) < 0 )
662 lsmash_free( timecode_array
);
663 return ERROR_MSG( "Failed to try matroska timescale.\n" );
667 if( timecode
->auto_media_timescale
|| timecode
->auto_media_timebase
)
669 uint64_t reduce
= get_gcd( timecode
->media_timebase
, timecode
->media_timescale
);
670 timecode
->media_timebase
/= reduce
;
671 timecode
->media_timescale
/= reduce
;
673 else if( timecode
->media_timescale
> UINT32_MAX
|| !timecode
->media_timescale
)
675 lsmash_free( timecode_array
);
676 return ERROR_MSG( "Failed to generate media timescale automatically.\n"
677 "Specify an appropriate media timescale manually.\n" );
679 uint32_t timescale
= timecode
->media_timescale
;
680 uint32_t timebase
= timecode
->media_timebase
;
681 double delay_tc
= timecode_array
[0];
682 timecode
->empty_delay
= ((uint64_t)(delay_tc
* ((double)timescale
/ timebase
) + 0.5)) * timebase
;
683 timecode
->ts
= lsmash_malloc( sample_count
* sizeof(uint64_t) );
686 lsmash_free( timecode_array
);
687 return ERROR_MSG( "Failed to allocate timestamps.\n" );
690 for( uint32_t i
= 1; i
< sample_count
; i
++ )
692 timecode
->ts
[i
] = ((uint64_t)((timecode_array
[i
] - delay_tc
) * ((double)timescale
/ timebase
) + 0.5)) * timebase
;
693 if( timecode
->ts
[i
] <= timecode
->ts
[i
- 1] )
695 lsmash_free( timecode_array
);
696 lsmash_free( timecode
->ts
);
698 return ERROR_MSG( "Invalid timecode.\n" );
701 lsmash_free( timecode_array
);
705 #undef DOUBLE_EPSILON
706 #undef MATROSKA_TIMESCALE
707 #undef SKIP_LINE_CHARACTER
709 static int edit_media_timeline( root_t
*input
, timecode_t
*timecode
, opt_t
*opt
)
711 if( !timecode
->file
&& !opt
->media_timescale
&& !opt
->media_timebase
&& !opt
->dts_compression
)
713 track_t
*in_track
= &input
->file
.movie
.track
[opt
->track_number
- 1];
714 uint32_t track_ID
= in_track
->track_ID
;
715 lsmash_media_ts_list_t ts_list
;
716 if( lsmash_get_media_timestamps( input
->root
, track_ID
, &ts_list
) )
717 return ERROR_MSG( "Failed to get media timestamps.\n" );
718 uint64_t timebase
= get_media_timebase( &ts_list
);
720 return ERROR_MSG( "Failed to get media timebase.\n" );
721 lsmash_media_ts_t
*timestamp
= ts_list
.timestamp
;
722 uint32_t sample_count
= ts_list
.sample_count
;
723 uint32_t orig_timebase
= timebase
;
725 double timebase_convert_multiplier
;
726 if( opt
->media_timescale
|| opt
->media_timebase
)
728 uint32_t orig_timescale
= in_track
->media_param
.timescale
;
729 timescale
= opt
->media_timescale
? opt
->media_timescale
: orig_timescale
;
730 timebase
= opt
->media_timebase
? opt
->media_timebase
: orig_timebase
;
731 if( !opt
->media_timescale
&& opt
->media_timebase
&& (timebase
> orig_timebase
) )
732 timescale
= timescale
* ((double)timebase
/ orig_timebase
) + 0.5;
733 timebase_convert_multiplier
= ((double)timescale
/ orig_timescale
) * ((double)orig_timebase
/ timebase
);
737 /* Reduce timescale and timebase. */
738 timescale
= in_track
->media_param
.timescale
;
739 uint64_t reduce
= get_gcd( timescale
, timebase
);
742 timebase_convert_multiplier
= 1;
744 /* Parse timecode file. */
747 timecode
->auto_media_timescale
= !opt
->media_timescale
;
748 timecode
->auto_media_timebase
= !opt
->media_timebase
;
749 timecode
->media_timescale
= timecode
->auto_media_timescale
? 0 : timescale
;
750 timecode
->media_timebase
= timebase
;
751 if( parse_timecode( timecode
, sample_count
) )
752 return ERROR_MSG( "Failed to parse timecode file.\n" );
753 timescale
= timecode
->media_timescale
;
754 timebase
= timecode
->media_timebase
;
756 /* Get maximum composition sample delay for DTS generation. */
757 uint32_t sample_delay
;
758 if( lsmash_get_max_sample_delay( &ts_list
, &sample_delay
) )
759 return ERROR_MSG( "Failed to get maximum composition sample delay.\n" );
760 if( sample_delay
) /* Reorder composition order. */
761 lsmash_sort_timestamps_composition_order( &ts_list
);
762 if( !timecode
->file
)
764 /* Genarate timestamps timescale converted. */
765 timecode
->ts
= lsmash_malloc( sample_count
* sizeof(uint64_t) );
767 return ERROR_MSG( "Failed to alloc timestamps\n" );
768 for( uint32_t i
= 0; i
< sample_count
; i
++ )
770 timecode
->ts
[i
] = (timestamp
[i
].cts
- timestamp
[0].cts
) / orig_timebase
;
771 timecode
->ts
[i
] = ((uint64_t)(timecode
->ts
[i
] * timebase_convert_multiplier
+ 0.5)) * timebase
;
772 if( i
&& (timecode
->ts
[i
] <= timecode
->ts
[i
- 1]) )
773 return ERROR_MSG( "Invalid timescale conversion.\n" );
778 /* If media timescale is specified, disable DTS compression multiplier. */
779 uint32_t dts_compression_multiplier
= opt
->dts_compression
* !opt
->media_timescale
* sample_delay
+ 1;
780 uint64_t initial_delta
= timecode
->ts
[1];
781 timescale
*= dts_compression_multiplier
;
782 if( dts_compression_multiplier
> 1 )
783 for( uint32_t i
= 0; i
< sample_count
; i
++ )
784 timecode
->ts
[i
] *= dts_compression_multiplier
;
786 uint64_t sample_delay_time
= timecode
->composition_delay
= opt
->dts_compression
? 0 : timecode
->ts
[sample_delay
];
787 for( uint32_t i
= 0; i
< sample_count
; i
++ )
788 timestamp
[i
].cts
= timecode
->ts
[i
] + sample_delay_time
;
789 /* Reorder decode order and generate new DTS from CTS. */
790 lsmash_sort_timestamps_decoding_order( &ts_list
);
791 uint64_t prev_reordered_cts
[sample_delay
];
792 for( uint32_t i
= 0; i
<= sample_delay
; i
++ )
794 if( !opt
->dts_compression
)
795 timestamp
[i
].dts
= timecode
->ts
[i
];
798 timestamp
[i
].dts
= (i
* initial_delta
) / (!!opt
->media_timescale
* sample_delay
+ 1);
799 if( i
&& (timestamp
[i
].dts
<= timestamp
[i
- 1].dts
) )
800 return ERROR_MSG( "Failed to do DTS compression.\n" );
802 prev_reordered_cts
[ i
% sample_delay
] = timecode
->ts
[i
] + sample_delay_time
;
804 for( uint32_t i
= sample_delay
+ 1; i
< sample_count
; i
++ )
806 timestamp
[i
].dts
= prev_reordered_cts
[ (i
- sample_delay
) % sample_delay
];
807 prev_reordered_cts
[ i
% sample_delay
] = timecode
->ts
[i
] + sample_delay_time
;
811 for( uint32_t i
= 0; i
< sample_count
; i
++ )
812 timestamp
[i
].cts
= timestamp
[i
].dts
= timecode
->ts
[i
];
813 if( sample_count
> 1 )
815 in_track
->last_sample_delta
= timecode
->ts
[sample_count
- 1] - timecode
->ts
[sample_count
- 2];
816 timecode
->duration
= timecode
->ts
[sample_count
- 1] + in_track
->last_sample_delta
;
818 else /* still image */
819 timecode
->duration
= in_track
->last_sample_delta
= UINT32_MAX
;
820 in_track
->media_param
.timescale
= timescale
;
821 if( lsmash_set_media_timestamps( input
->root
, track_ID
, &ts_list
) )
822 return ERROR_MSG( "Failed to set media timestamps.\n" );
823 lsmash_delete_media_timestamps( &ts_list
);
827 static int check_white_brand( lsmash_brand_type brand
)
829 static const lsmash_brand_type brand_white_list
[] =
831 ISOM_BRAND_TYPE_3G2A
,
832 ISOM_BRAND_TYPE_3GG6
,
833 ISOM_BRAND_TYPE_3GG9
,
834 ISOM_BRAND_TYPE_3GP4
,
835 ISOM_BRAND_TYPE_3GP5
,
836 ISOM_BRAND_TYPE_3GP6
,
837 ISOM_BRAND_TYPE_3GP7
,
838 ISOM_BRAND_TYPE_3GP8
,
839 ISOM_BRAND_TYPE_3GP9
,
840 ISOM_BRAND_TYPE_3GR6
,
841 ISOM_BRAND_TYPE_3GR9
,
842 ISOM_BRAND_TYPE_M4A
,
843 ISOM_BRAND_TYPE_M4B
,
844 ISOM_BRAND_TYPE_M4V
,
845 ISOM_BRAND_TYPE_AVC1
,
846 ISOM_BRAND_TYPE_DBY1
,
847 ISOM_BRAND_TYPE_ISO2
,
848 ISOM_BRAND_TYPE_ISO3
,
849 ISOM_BRAND_TYPE_ISO4
,
850 ISOM_BRAND_TYPE_ISO5
,
851 ISOM_BRAND_TYPE_ISO6
,
852 ISOM_BRAND_TYPE_ISOM
,
853 ISOM_BRAND_TYPE_MP41
,
854 ISOM_BRAND_TYPE_MP42
,
858 for( int i
= 0; brand_white_list
[i
]; i
++ )
859 if( brand
== brand_white_list
[i
] )
864 static int moov_to_front_callback( void *param
, uint64_t written_movie_size
, uint64_t total_movie_size
)
866 eprintf( "Finalizing: [%5.2lf%%]\r", ((double)written_movie_size
/ total_movie_size
) * 100.0 );
870 static void display_version( void )
873 "L-SMASH isom/mov timeline editor rev%s %s\n"
875 "Copyright (C) 2011-2014 L-SMASH project\n",
876 LSMASH_REV
, LSMASH_GIT_HASH
, __DATE__
, __TIME__
);
879 static void display_help( void )
883 "Usage: timelineeditor [options] input output\n"
885 " --help Display help\n"
886 " --version Display version information\n"
887 " --track <integer> Specify track number to edit [1]\n"
888 " --timecode <string> Specify timecode file to edit timeline\n"
889 " --media-timescale <integer> Specify media timescale to convert\n"
890 " --media-timebase <integer> Specify media timebase to convert\n"
891 " --skip <integer> Skip start of media presentation in milliseconds\n"
892 " --delay <integer> Insert blank clip before actual media presentation in milliseconds\n"
893 " --dts-compression Eliminate composition delay with DTS hack\n"
894 " Multiply media timescale and timebase automatically\n" );
897 int main( int argc
, char *argv
[] )
904 else if( !strcasecmp( argv
[1], "-h" ) || !strcasecmp( argv
[1], "--help" ) )
909 else if( !strcasecmp( argv
[1], "-v" ) || !strcasecmp( argv
[1], "--version" ) )
919 root_t output
= { 0 };
920 root_t input
= { 0 };
921 timecode_t timecode
= { 0 };
922 movie_io_t io
= { &output
, &input
, &timecode
};
923 opt_t opt
= { 1, 0, 0, 0, 0, 0 };
925 lsmash_get_mainargs( &argc
, &argv
);
927 while( argn
< argc
- 2 )
929 if( !strcasecmp( argv
[argn
], "--track" ) )
931 opt
.track_number
= atoi( argv
[++argn
] );
932 if( !opt
.track_number
)
933 return TIMELINEEDITOR_ERR( "Invalid track number.\n" );
936 else if( !strcasecmp( argv
[argn
], "--timecode" ) )
938 timecode
.file
= lsmash_fopen( argv
[++argn
], "rb" );
940 return TIMELINEEDITOR_ERR( "Failed to open timecode file.\n" );
943 else if( !strcasecmp( argv
[argn
], "--media-timescale" ) )
945 opt
.media_timescale
= atoi( argv
[++argn
] );
946 if( !opt
.media_timescale
)
947 return TIMELINEEDITOR_ERR( "Invalid media timescale.\n" );
950 else if( !strcasecmp( argv
[argn
], "--media-timebase" ) )
952 opt
.media_timebase
= atoi( argv
[++argn
] );
953 if( !opt
.media_timebase
)
954 return TIMELINEEDITOR_ERR( "Invalid media timebase.\n" );
957 else if( !strcasecmp( argv
[argn
], "--skip" ) )
959 opt
.skip_duration
= atoi( argv
[++argn
] );
960 if( !opt
.skip_duration
)
961 return TIMELINEEDITOR_ERR( "Invalid skip duration.\n" );
964 else if( !strcasecmp( argv
[argn
], "--delay" ) )
966 opt
.empty_delay
= atoi( argv
[++argn
] );
967 if( !opt
.empty_delay
)
968 return TIMELINEEDITOR_ERR( "Invalid delay time.\n" );
971 else if( !strcasecmp( argv
[argn
], "--dts-compression" ) )
973 opt
.dts_compression
= 1;
977 return TIMELINEEDITOR_ERR( "Invalid option.\n" );
979 if( argn
> argc
- 2 )
980 return TIMELINEEDITOR_ERR( "Invalid arguments.\n" );
981 /* Get input movies. */
982 if( get_movie( &input
, argv
[argn
++] ) )
983 return TIMELINEEDITOR_ERR( "Failed to get input movie.\n" );
984 movie_t
*in_movie
= &input
.file
.movie
;
985 if( opt
.track_number
&& (opt
.track_number
> in_movie
->num_tracks
) )
986 return TIMELINEEDITOR_ERR( "Invalid track number.\n" );
987 /* Create output movie. */
988 file_t
*out_file
= &output
.file
;
989 output
.root
= lsmash_create_root();
991 return TIMELINEEDITOR_ERR( "failed to create a ROOT for an output file.\n" );
992 if( lsmash_open_file( argv
[argn
], 0, &out_file
->param
) < 0 )
993 return TIMELINEEDITOR_ERR( "failed to open an output file.\n" );
994 file_t
*in_file
= &input
.file
;
995 out_file
->param
.major_brand
= in_file
->param
.major_brand
;
996 out_file
->param
.minor_version
= in_file
->param
.minor_version
;
997 out_file
->param
.brands
= in_file
->param
.brands
;
998 out_file
->param
.brand_count
= in_file
->param
.brand_count
;
999 out_file
->param
.max_chunk_duration
= 0.5;
1000 out_file
->param
.max_async_tolerance
= 2.0;
1001 out_file
->param
.max_chunk_size
= 4*1024*1024;
1002 if( !check_white_brand( out_file
->param
.major_brand
) )
1004 /* Replace with whitelisted brand 'mp42'. */
1005 out_file
->param
.major_brand
= ISOM_BRAND_TYPE_MP42
;
1006 out_file
->param
.minor_version
= 0;
1008 for( i
= 0; i
< out_file
->param
.brand_count
; i
++ )
1009 if( out_file
->param
.brands
[i
] == ISOM_BRAND_TYPE_MP42
)
1011 if( i
== out_file
->param
.brand_count
)
1013 /* Add 'mp42' into the list of compatible brands. */
1014 out_file
->param
.brands
= lsmash_malloc( (i
+ 1) * sizeof(lsmash_brand_type
) );
1015 if( out_file
->param
.brands
)
1017 memcpy( out_file
->param
.brands
, in_file
->param
.brands
, i
* sizeof(lsmash_brand_type
) );
1018 out_file
->param
.brands
[i
] = ISOM_BRAND_TYPE_MP42
;
1022 out_file
->fh
= lsmash_set_file( output
.root
, &out_file
->param
);
1024 return TIMELINEEDITOR_ERR( "failed to add an output file into a ROOT.\n" );
1025 if( out_file
->param
.brands
!= in_file
->param
.brands
)
1026 lsmash_freep( &out_file
->param
.brands
);
1027 /* Set movie parameters. */
1028 movie_t
*out_movie
= &out_file
->movie
;
1029 out_movie
->param
= in_movie
->param
; /* Copy movie parameters. */
1030 if( in_movie
->num_tracks
== 1 )
1031 out_movie
->param
.timescale
= in_movie
->track
[0].media_param
.timescale
;
1032 if( lsmash_set_movie_parameters( output
.root
, &out_movie
->param
) )
1033 return TIMELINEEDITOR_ERR( "Failed to set output movie parameters.\n" );
1034 /* Set iTunes metadata. */
1035 for( uint32_t i
= 0; i
< in_movie
->num_itunes_metadata
; i
++ )
1036 if( lsmash_set_itunes_metadata( output
.root
, in_movie
->itunes_metadata
[i
] ) )
1038 WARNING_MSG( "failed to set an iTunes metadata.\n" );
1041 /* Create tracks of the output movie. */
1042 out_movie
->track
= lsmash_malloc( in_movie
->num_tracks
* sizeof(track_t
) );
1043 if( !out_movie
->track
)
1044 return TIMELINEEDITOR_ERR( "Failed to alloc output tracks.\n" );
1045 /* Edit timeline. */
1046 if( edit_media_timeline( &input
, &timecode
, &opt
) )
1047 return TIMELINEEDITOR_ERR( "Failed to edit timeline.\n" );
1048 out_movie
->num_tracks
= in_movie
->num_tracks
;
1049 out_movie
->current_track_number
= 1;
1050 for( uint32_t i
= 0; i
< in_movie
->num_tracks
; i
++ )
1052 track_t
*in_track
= &in_movie
->track
[i
];
1053 if( !in_track
->active
)
1055 -- out_movie
->num_tracks
;
1058 track_t
*out_track
= &out_movie
->track
[i
];
1059 out_track
->summary_remap
= lsmash_malloc( in_track
->num_summaries
* sizeof(uint32_t) );
1060 if( !out_track
->summary_remap
)
1061 return TIMELINEEDITOR_ERR( "failed to create summary mapping for a track.\n" );
1062 memset( out_track
->summary_remap
, 0, in_track
->num_summaries
* sizeof(uint32_t) );
1063 out_track
->track_ID
= lsmash_create_track( output
.root
, in_track
->media_param
.handler_type
);
1064 if( !out_track
->track_ID
)
1065 return TIMELINEEDITOR_ERR( "Failed to create a track.\n" );
1066 /* Copy track and media parameters except for track_ID. */
1067 out_track
->track_param
= in_track
->track_param
;
1068 out_track
->media_param
= in_track
->media_param
;
1069 out_track
->track_param
.track_ID
= out_track
->track_ID
;
1070 if( lsmash_set_track_parameters( output
.root
, out_track
->track_ID
, &out_track
->track_param
) )
1071 return TIMELINEEDITOR_ERR( "Failed to set track parameters.\n" );
1072 if( lsmash_set_media_parameters( output
.root
, out_track
->track_ID
, &out_track
->media_param
) )
1073 return TIMELINEEDITOR_ERR( "Failed to set media parameters.\n" );
1074 uint32_t valid_summary_count
= 0;
1075 for( uint32_t k
= 0; k
< in_track
->num_summaries
; k
++ )
1077 if( !in_track
->summaries
[k
].active
)
1079 out_track
->summary_remap
[k
] = 0;
1082 lsmash_summary_t
*summary
= in_track
->summaries
[k
].summary
;
1083 if( lsmash_add_sample_entry( output
.root
, out_track
->track_ID
, summary
) == 0 )
1085 WARNING_MSG( "failed to append a summary.\n" );
1086 lsmash_cleanup_summary( summary
);
1087 in_track
->summaries
[k
].summary
= NULL
;
1088 in_track
->summaries
[k
].active
= 0;
1089 out_track
->summary_remap
[k
] = 0;
1092 out_track
->summary_remap
[k
] = ++valid_summary_count
;
1094 if( valid_summary_count
== 0 )
1095 return TIMELINEEDITOR_ERR( "failed to append all summaries.\n" );
1096 out_track
->last_sample_delta
= in_track
->last_sample_delta
;
1097 out_track
->current_sample_number
= 1;
1098 out_track
->reach_end_of_media_timeline
= 0;
1101 double largest_dts
= 0;
1102 uint32_t num_consecutive_sample_skip
= 0;
1103 uint32_t num_active_input_tracks
= out_movie
->num_tracks
;
1104 uint64_t total_media_size
= 0;
1105 uint8_t sample_count
= 0;
1108 track_t
*in_track
= &in_movie
->track
[ in_movie
->current_track_number
- 1 ];
1109 /* Try append a sample in an input track where we didn't reach the end of media timeline. */
1110 if( !in_track
->reach_end_of_media_timeline
)
1112 track_t
*out_track
= &out_movie
->track
[ out_movie
->current_track_number
- 1 ];
1113 uint32_t in_track_ID
= in_track
->track_ID
;
1114 uint32_t out_track_ID
= out_track
->track_ID
;
1115 uint32_t input_media_timescale
= in_track
->media_param
.timescale
;
1116 /* Get a DTS from a track in an input movie. */
1118 if( lsmash_get_dts_from_media_timeline( input
.root
, in_track_ID
, in_track
->current_sample_number
, &dts
) )
1120 if( lsmash_check_sample_existence_in_media_timeline( input
.root
, in_track_ID
, in_track
->current_sample_number
) )
1121 return TIMELINEEDITOR_ERR( "Failed to get the DTS.\n" );
1124 in_track
->reach_end_of_media_timeline
= 1;
1125 if( --num_active_input_tracks
== 0 )
1126 break; /* end of muxing */
1129 /* Get and append a sample if it's good time. */
1130 else if( ((double)dts
/ input_media_timescale
) <= largest_dts
1131 || num_consecutive_sample_skip
== num_active_input_tracks
)
1133 /* Get an actual sample data from a track in an input movie. */
1134 lsmash_sample_t
*sample
= lsmash_get_sample_from_media_timeline( input
.root
, in_track_ID
, in_track
->current_sample_number
);
1136 return TIMELINEEDITOR_ERR( "Failed to get sample.\n" );
1137 sample
->index
= sample
->index
> in_track
->num_summaries
? in_track
->num_summaries
1138 : sample
->index
== 0 ? 1
1140 sample
->index
= out_track
->summary_remap
[ sample
->index
- 1 ];
1143 /* Append sample into output movie. */
1144 uint64_t sample_size
= sample
->length
; /* sample will be deleted internally after appending. */
1145 if( lsmash_append_sample( output
.root
, out_track_ID
, sample
) )
1147 lsmash_delete_sample( sample
);
1148 return TIMELINEEDITOR_ERR( "Failed to append a sample.\n" );
1150 largest_dts
= LSMASH_MAX( largest_dts
, (double)dts
/ input_media_timescale
);
1151 total_media_size
+= sample_size
;
1152 ++ in_track
->current_sample_number
;
1153 num_consecutive_sample_skip
= 0;
1154 /* Print, per 256 samples, total size of imported media. */
1155 if( ++sample_count
== 0 )
1156 eprintf( "Importing: %"PRIu64
" bytes\r", total_media_size
);
1160 ++num_consecutive_sample_skip
; /* Skip appendig sample. */
1162 /* Move the next track. */
1163 if( ++ in_movie
->current_track_number
> in_movie
->num_tracks
)
1164 in_movie
->current_track_number
= 1; /* Back the first track. */
1165 if( ++ out_movie
->current_track_number
> out_movie
->num_tracks
)
1166 out_movie
->current_track_number
= 1; /* Back the first track in the output movie. */
1168 for( uint32_t i
= 0; i
< out_movie
->num_tracks
; i
++ )
1169 if( lsmash_flush_pooled_samples( output
.root
, out_movie
->track
[i
].track_ID
, out_movie
->track
[i
].last_sample_delta
) )
1170 return TIMELINEEDITOR_ERR( "Failed to flush samples.\n" );
1171 /* Copy timeline maps. */
1172 for( uint32_t i
= 0; i
< out_movie
->num_tracks
; i
++ )
1173 if( lsmash_copy_timeline_map( output
.root
, out_movie
->track
[i
].track_ID
, input
.root
, in_movie
->track
[i
].track_ID
) )
1174 return TIMELINEEDITOR_ERR( "Failed to copy a timeline map.\n" );
1175 /* Edit timeline map. */
1178 track_t
*out_track
= &out_movie
->track
[ opt
.track_number
- 1 ];
1179 uint32_t track_ID
= out_track
->track_ID
;
1180 uint32_t movie_timescale
= lsmash_get_movie_timescale( output
.root
);
1181 uint32_t media_timescale
= lsmash_get_media_timescale( output
.root
, track_ID
);
1182 uint64_t empty_delay
= timecode
.empty_delay
+ (uint64_t)(opt
.empty_delay
* (1e-3 * media_timescale
) + 0.5);
1183 uint64_t duration
= timecode
.duration
+ empty_delay
;
1184 if( lsmash_delete_explicit_timeline_map( output
.root
, track_ID
) )
1185 return TIMELINEEDITOR_ERR( "Failed to delete explicit timeline maps.\n" );
1186 if( timecode
.empty_delay
)
1188 lsmash_edit_t empty_edit
;
1189 empty_edit
.duration
= ((double)timecode
.empty_delay
/ media_timescale
) * movie_timescale
;
1190 empty_edit
.start_time
= ISOM_EDIT_MODE_EMPTY
;
1191 empty_edit
.rate
= ISOM_EDIT_MODE_NORMAL
;
1192 if( lsmash_create_explicit_timeline_map( output
.root
, track_ID
, empty_edit
) )
1193 return TIMELINEEDITOR_ERR( "Failed to create a empty duration.\n" );
1194 duration
= ((double)duration
/ media_timescale
) * movie_timescale
;
1195 duration
-= empty_edit
.duration
;
1198 duration
= ((double)duration
/ media_timescale
) * movie_timescale
;
1200 edit
.duration
= duration
;
1201 edit
.start_time
= timecode
.composition_delay
+ (uint64_t)(opt
.skip_duration
* (1e-3 * media_timescale
) + 0.5);
1202 edit
.rate
= ISOM_EDIT_MODE_NORMAL
;
1203 if( lsmash_create_explicit_timeline_map( output
.root
, track_ID
, edit
) )
1204 return TIMELINEEDITOR_ERR( "Failed to create a explicit timeline map.\n" );
1206 /* Finish muxing. */
1207 lsmash_adhoc_remux_t moov_to_front
;
1208 moov_to_front
.func
= moov_to_front_callback
;
1209 moov_to_front
.buffer_size
= 4*1024*1024;
1210 moov_to_front
.param
= NULL
;
1212 if( lsmash_finish_movie( output
.root
, &moov_to_front
)
1213 || lsmash_write_lsmash_indicator( output
.root
) )
1214 return TIMELINEEDITOR_ERR( "Failed to finish output movie.\n" );
1215 cleanup_root( io
.input
);
1216 cleanup_root( io
.output
);
1217 cleanup_timecode( io
.timecode
);
1218 eprintf( "Timeline editing completed! \n" );