void LSMASHVideoSource::get_video_track( const char *source, uint32_t track_number, int threads, IScriptEnvironment *env ) { uint32_t number_of_tracks = open_file( source, env ); if( track_number && track_number > number_of_tracks ) env->ThrowError( "LSMASHVideoSource: the number of tracks equals %I32u.", number_of_tracks ); /* L-SMASH */ uint32_t i; lsmash_media_parameters_t media_param; if( track_number == 0 ) { /* Get the first video track. */ for( i = 1; i <= number_of_tracks; i++ ) { vdh.track_ID = lsmash_get_track_ID( vdh.root, i ); if( vdh.track_ID == 0 ) env->ThrowError( "LSMASHVideoSource: failed to find video track." ); lsmash_initialize_media_parameters( &media_param ); if( lsmash_get_media_parameters( vdh.root, vdh.track_ID, &media_param ) ) env->ThrowError( "LSMASHVideoSource: failed to get media parameters." ); if( media_param.handler_type == ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) break; } if( i > number_of_tracks ) env->ThrowError( "LSMASHVideoSource: failed to find video track." ); } else { /* Get the desired video track. */ vdh.track_ID = lsmash_get_track_ID( vdh.root, track_number ); if( vdh.track_ID == 0 ) env->ThrowError( "LSMASHVideoSource: failed to find video track." ); lsmash_initialize_media_parameters( &media_param ); if( lsmash_get_media_parameters( vdh.root, vdh.track_ID, &media_param ) ) env->ThrowError( "LSMASHVideoSource: failed to get media parameters." ); if( media_param.handler_type != ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ) env->ThrowError( "LSMASHVideoSource: the track you specified is not a video track." ); } if( lsmash_construct_timeline( vdh.root, vdh.track_ID ) ) env->ThrowError( "LSMASHVideoSource: failed to get construct timeline." ); if( get_summaries( vdh.root, vdh.track_ID, &vdh.config ) ) env->ThrowError( "LSMASHVideoSource: failed to get summaries." ); vi.num_frames = lsmash_get_sample_count_in_media_timeline( vdh.root, vdh.track_ID ); setup_timestamp_info( &vdh, &vi, media_param.timescale, env ); /* libavformat */ for( i = 0; i < format_ctx->nb_streams && format_ctx->streams[i]->codec->codec_type != AVMEDIA_TYPE_VIDEO; i++ ); if( i == format_ctx->nb_streams ) env->ThrowError( "LSMASHVideoSource: failed to find stream by libavformat." ); /* libavcodec */ AVStream *stream = format_ctx->streams[i]; AVCodecContext *ctx = stream->codec; vdh.config.ctx = ctx; AVCodec *codec = avcodec_find_decoder( ctx->codec_id ); if( !codec ) env->ThrowError( "LSMASHVideoSource: failed to find %s decoder.", codec->name ); ctx->thread_count = threads; if( avcodec_open2( ctx, codec, NULL ) < 0 ) env->ThrowError( "LSMASHVideoSource: failed to avcodec_open2." ); }
static uint32_t libavsmash_video_fetch_media_timescale ( libavsmash_video_decode_handler_t *vdhp ) { if( !vdhp ) return 0; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters( &media_param ); if( lsmash_get_media_parameters( vdhp->root, vdhp->track_id, &media_param ) < 0 ) return 0; vdhp->media_timescale = media_param.timescale; return vdhp->media_timescale; }
int main( int argc, char *argv[] ) { if ( argc < 2 ) { display_help(); return -1; } else if( !strcasecmp( argv[1], "-h" ) || !strcasecmp( argv[1], "--help" ) ) { display_help(); return 0; } else if( !strcasecmp( argv[1], "-v" ) || !strcasecmp( argv[1], "--version" ) ) { display_version(); return 0; } int dump_box = 1; int chapter = 0; char *filename; lsmash_get_mainargs( &argc, &argv ); if( argc > 2 ) { if( !strcasecmp( argv[1], "--box" ) ) DO_NOTHING; else if( !strcasecmp( argv[1], "--chapter" ) ) chapter = 1; else if( !strcasecmp( argv[1], "--timestamp" ) ) dump_box = 0; else { display_help(); return -1; } filename = argv[2]; } else { filename = argv[1]; } /* Open the input file. */ lsmash_root_t *root = lsmash_create_root(); if( !root ) { fprintf( stderr, "Failed to create a ROOT.\n" ); return -1; } lsmash_file_parameters_t file_param = { 0 }; if( lsmash_open_file( filename, 1, &file_param ) < 0 ) return BOXDUMPER_ERR( "Failed to open an input file.\n" ); if( dump_box ) file_param.mode |= LSMASH_FILE_MODE_DUMP; lsmash_file_t *file = lsmash_set_file( root, &file_param ); if( !file ) return BOXDUMPER_ERR( "Failed to add a file into a ROOT.\n" ); if( lsmash_read_file( file, &file_param ) < 0 ) return BOXDUMPER_ERR( "Failed to read a file\n" ); /* Dump the input file. */ if( chapter ) { if( lsmash_print_chapter_list( root ) ) return BOXDUMPER_ERR( "Failed to extract chapter.\n" ); } else if( dump_box ) { if( lsmash_print_movie( root, "-" ) ) return BOXDUMPER_ERR( "Failed to dump box structure.\n" ); } else { lsmash_movie_parameters_t movie_param; lsmash_initialize_movie_parameters( &movie_param ); lsmash_get_movie_parameters( root, &movie_param ); uint32_t num_tracks = movie_param.number_of_tracks; for( uint32_t track_number = 1; track_number <= num_tracks; track_number++ ) { uint32_t track_ID = lsmash_get_track_ID( root, track_number ); if( !track_ID ) return BOXDUMPER_ERR( "Failed to get track_ID.\n" ); lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters( &media_param ); if( lsmash_get_media_parameters( root, track_ID, &media_param ) ) return BOXDUMPER_ERR( "Failed to get media parameters.\n" ); if( lsmash_construct_timeline( root, track_ID ) ) return BOXDUMPER_ERR( "Failed to construct timeline.\n" ); uint32_t timeline_shift; if( lsmash_get_composition_to_decode_shift_from_media_timeline( root, track_ID, &timeline_shift ) ) return BOXDUMPER_ERR( "Failed to get timestamps.\n" ); lsmash_media_ts_list_t ts_list; if( lsmash_get_media_timestamps( root, track_ID, &ts_list ) ) return BOXDUMPER_ERR( "Failed to get timestamps.\n" ); fprintf( stdout, "track_ID: %"PRIu32"\n", track_ID ); fprintf( stdout, "Media timescale: %"PRIu32"\n", media_param.timescale ); lsmash_media_ts_t *ts_array = ts_list.timestamp; if( !ts_array ) { fprintf( stdout, "\n" ); continue; } for( uint32_t i = 0; i < ts_list.sample_count; i++ ) fprintf( stdout, "DTS = %"PRIu64", CTS = %"PRIu64"\n", ts_array[i].dts, ts_array[i].cts + timeline_shift ); lsmash_free( ts_array ); fprintf( stdout, "\n" ); } } lsmash_destroy_root( root ); return 0; }
static int set_param( hnd_t handle, x264_param_t *p_param ) { mp4_hnd_t *p_mp4 = handle; uint64_t i_media_timescale; p_mp4->i_delay_frames = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0; p_mp4->i_dts_compress_multiplier = p_mp4->b_dts_compress * p_mp4->i_delay_frames + 1; i_media_timescale = (uint64_t)p_param->i_timebase_den * p_mp4->i_dts_compress_multiplier; p_mp4->i_time_inc = (uint64_t)p_param->i_timebase_num * p_mp4->i_dts_compress_multiplier; MP4_FAIL_IF_ERR( i_media_timescale > UINT32_MAX, "MP4 media timescale %"PRIu64" exceeds maximum\n", i_media_timescale ); /* Select brands. */ lsmash_brand_type brands[6] = { 0 }; uint32_t brand_count = 0; brands[brand_count++] = ISOM_BRAND_TYPE_MP42; brands[brand_count++] = ISOM_BRAND_TYPE_MP41; brands[brand_count++] = ISOM_BRAND_TYPE_ISOM; if( p_mp4->b_use_recovery ) { brands[brand_count++] = ISOM_BRAND_TYPE_AVC1; /* sdtp, sgpd, sbgp and visual roll recovery grouping */ if( p_param->b_open_gop ) brands[brand_count++] = ISOM_BRAND_TYPE_ISO6; /* cslg and visual random access grouping */ } /* Set file */ lsmash_file_parameters_t *file_param = &p_mp4->file_param; file_param->major_brand = brands[0]; file_param->brands = brands; file_param->brand_count = brand_count; file_param->minor_version = 0; MP4_FAIL_IF_ERR( !lsmash_set_file( p_mp4->p_root, file_param ), "failed to add an output file into a ROOT.\n" ); /* Set movie parameters. */ lsmash_movie_parameters_t movie_param; lsmash_initialize_movie_parameters( &movie_param ); MP4_FAIL_IF_ERR( lsmash_set_movie_parameters( p_mp4->p_root, &movie_param ), "failed to set movie parameters.\n" ); p_mp4->i_movie_timescale = lsmash_get_movie_timescale( p_mp4->p_root ); MP4_FAIL_IF_ERR( !p_mp4->i_movie_timescale, "movie timescale is broken.\n" ); /* Create a video track. */ p_mp4->i_track = lsmash_create_track( p_mp4->p_root, ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK ); MP4_FAIL_IF_ERR( !p_mp4->i_track, "failed to create a video track.\n" ); p_mp4->summary->width = p_param->i_width; p_mp4->summary->height = p_param->i_height; uint32_t i_display_width = p_param->i_width << 16; uint32_t i_display_height = p_param->i_height << 16; if( p_param->vui.i_sar_width && p_param->vui.i_sar_height ) { double sar = (double)p_param->vui.i_sar_width / p_param->vui.i_sar_height; if( sar > 1.0 ) i_display_width *= sar; else i_display_height /= sar; p_mp4->summary->par_h = p_param->vui.i_sar_width; p_mp4->summary->par_v = p_param->vui.i_sar_height; } p_mp4->summary->color.primaries_index = p_param->vui.i_colorprim; p_mp4->summary->color.transfer_index = p_param->vui.i_transfer; p_mp4->summary->color.matrix_index = p_param->vui.i_colmatrix >= 0 ? p_param->vui.i_colmatrix : ISOM_MATRIX_INDEX_UNSPECIFIED; p_mp4->summary->color.full_range = p_param->vui.b_fullrange >= 0 ? p_param->vui.b_fullrange : 0; /* Set video track parameters. */ lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters( &track_param ); lsmash_track_mode track_mode = ISOM_TRACK_ENABLED | ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; track_param.mode = track_mode; track_param.display_width = i_display_width; track_param.display_height = i_display_height; MP4_FAIL_IF_ERR( lsmash_set_track_parameters( p_mp4->p_root, p_mp4->i_track, &track_param ), "failed to set track parameters for video.\n" ); /* Set video media parameters. */ lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters( &media_param ); media_param.timescale = i_media_timescale; media_param.media_handler_name = "L-SMASH Video Media Handler"; if( p_mp4->b_use_recovery ) { media_param.roll_grouping = p_param->b_intra_refresh; media_param.rap_grouping = p_param->b_open_gop; } MP4_FAIL_IF_ERR( lsmash_set_media_parameters( p_mp4->p_root, p_mp4->i_track, &media_param ), "failed to set media parameters for video.\n" ); p_mp4->i_video_timescale = lsmash_get_media_timescale( p_mp4->p_root, p_mp4->i_track ); MP4_FAIL_IF_ERR( !p_mp4->i_video_timescale, "media timescale for video is broken.\n" ); return 0; }
void LSMASHAudioSource::get_audio_track( const char *source, uint32_t track_number, bool skip_priming, IScriptEnvironment *env ) { uint32_t number_of_tracks = open_file( source, env ); if( track_number && track_number > number_of_tracks ) env->ThrowError( "LSMASHAudioSource: the number of tracks equals %I32u.", number_of_tracks ); /* L-SMASH */ uint32_t i; lsmash_media_parameters_t media_param; if( track_number == 0 ) { /* Get the first audio track. */ for( i = 1; i <= number_of_tracks; i++ ) { adh.track_ID = lsmash_get_track_ID( adh.root, i ); if( adh.track_ID == 0 ) env->ThrowError( "LSMASHAudioSource: failed to find audio track." ); lsmash_initialize_media_parameters( &media_param ); if( lsmash_get_media_parameters( adh.root, adh.track_ID, &media_param ) ) env->ThrowError( "LSMASHAudioSource: failed to get media parameters." ); if( media_param.handler_type == ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) break; } if( i > number_of_tracks ) env->ThrowError( "LSMASHAudioSource: failed to find audio track." ); } else { /* Get the desired audio track. */ adh.track_ID = lsmash_get_track_ID( adh.root, track_number ); if( adh.track_ID == 0 ) env->ThrowError( "LSMASHAudioSource: failed to find audio track." ); lsmash_initialize_media_parameters( &media_param ); if( lsmash_get_media_parameters( adh.root, adh.track_ID, &media_param ) ) env->ThrowError( "LSMASHAudioSource: failed to get media parameters." ); if( media_param.handler_type != ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK ) env->ThrowError( "LSMASHAudioSource: the track you specified is not an audio track." ); } if( lsmash_construct_timeline( adh.root, adh.track_ID ) ) env->ThrowError( "LSMASHAudioSource: failed to get construct timeline." ); if( get_summaries( adh.root, adh.track_ID, &adh.config ) ) env->ThrowError( "LSMASHAudioSource: failed to get summaries." ); adh.frame_count = lsmash_get_sample_count_in_media_timeline( adh.root, adh.track_ID ); vi.num_audio_samples = lsmash_get_media_duration_from_media_timeline( adh.root, adh.track_ID ); if( skip_priming ) { uint32_t itunes_metadata_count = lsmash_count_itunes_metadata( adh.root ); for( i = 1; i <= itunes_metadata_count; i++ ) { lsmash_itunes_metadata_t metadata; if( lsmash_get_itunes_metadata( adh.root, i, &metadata ) < 0 ) continue; if( metadata.item != ITUNES_METADATA_ITEM_CUSTOM || (metadata.type != ITUNES_METADATA_TYPE_STRING && metadata.type != ITUNES_METADATA_TYPE_BINARY) || !metadata.meaning || !metadata.name || memcmp( "com.apple.iTunes", metadata.meaning, strlen( metadata.meaning ) ) || memcmp( "iTunSMPB", metadata.name, strlen( metadata.name ) ) ) { lsmash_cleanup_itunes_metadata( &metadata ); continue; } char *value = NULL; if( metadata.type == ITUNES_METADATA_TYPE_STRING ) { int length = strlen( metadata.value.string ); if( length >= 116 ) value = duplicate_as_string( metadata.value.string, length ); } else /* metadata.type == ITUNES_METADATA_TYPE_BINARY */ { if( metadata.value.binary.size >= 116 ) value = duplicate_as_string( metadata.value.binary.data, metadata.value.binary.size ); } lsmash_cleanup_itunes_metadata( &metadata ); if( !value ) continue; uint32_t dummy[9]; uint32_t priming_samples; uint32_t padding; uint64_t duration; if( 12 != sscanf( value, " %I32x %I32x %I32x %I64x %I32x %I32x %I32x %I32x %I32x %I32x %I32x %I32x", &dummy[0], &priming_samples, &padding, &duration, &dummy[1], &dummy[2], &dummy[3], &dummy[4], &dummy[5], &dummy[6], &dummy[7], &dummy[8] ) ) { delete [] value; continue; } delete [] value; adh.implicit_preroll = 1; aoh.skip_decoded_samples = priming_samples; vi.num_audio_samples = duration + priming_samples; break; } if( aoh.skip_decoded_samples == 0 ) { uint32_t ctd_shift; if( lsmash_get_composition_to_decode_shift_from_media_timeline( adh.root, adh.track_ID, &ctd_shift ) ) env->ThrowError( "LSMASHAudioSource: failed to get the timeline shift." ); aoh.skip_decoded_samples = ctd_shift + get_start_time( adh.root, adh.track_ID ); } } /* libavformat */ for( i = 0; i < format_ctx->nb_streams && format_ctx->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO; i++ ); if( i == format_ctx->nb_streams ) env->ThrowError( "LSMASHAudioSource: failed to find stream by libavformat." ); /* libavcodec */ AVStream *stream = format_ctx->streams[i]; AVCodecContext *ctx = stream->codec; adh.config.ctx = ctx; AVCodec *codec = avcodec_find_decoder( ctx->codec_id ); if( !codec ) env->ThrowError( "LSMASHAudioSource: failed to find %s decoder.", codec->name ); ctx->thread_count = 0; if( avcodec_open2( ctx, codec, NULL ) < 0 ) env->ThrowError( "LSMASHAudioSource: failed to avcodec_open2." ); }
bool fcMP4Muxer::mux(const Params ¶ms) { lsmash_root_t *root; lsmash_file_parameters_t mp4_stream; lsmash_file_parameters_t h264_stream; lsmash_brand_type major_brand = ISOM_BRAND_TYPE_MP42; lsmash_brand_type compatible_brands[2] = { ISOM_BRAND_TYPE_MP42, ISOM_BRAND_TYPE_ISOM }; uint32_t fps_num = params.frame_rate; uint32_t fps_den = 1; // 出力 mp4 root = lsmash_create_root(); if (lsmash_open_file(params.out_mp4_path, 0, &mp4_stream) != 0) { return false; } mp4_stream.major_brand = major_brand; mp4_stream.brands = compatible_brands; mp4_stream.brand_count = sizeof(compatible_brands) / sizeof(compatible_brands[0]); mp4_stream.minor_version = 0; lsmash_set_file(root, &mp4_stream); lsmash_movie_parameters_t movie_param; lsmash_initialize_movie_parameters(&movie_param); lsmash_set_movie_parameters(root, &movie_param); int track_number = 1; MP4TrackData track_data[2]; int num_track_data = 0; if (params.in_h264_path) { importer_t *h264_importer = lsmash_importer_open(params.in_h264_path, "H.264"); if (h264_importer == nullptr) { return false; } int h264_track_id = lsmash_create_track(root, ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK); lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters(&track_param); (int&)track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; int sample_entry = 0; lsmash_summary_t *summary = lsmash_duplicate_summary(h264_importer, track_number); auto video_summary = (lsmash_video_summary_t*)summary; track_param.display_width = video_summary->width << 16; track_param.display_height = video_summary->height << 16; lsmash_set_track_parameters(root, h264_track_id, &track_param); sample_entry = lsmash_add_sample_entry(root, h264_track_id, summary); auto& td = track_data[num_track_data++]; td.importer = h264_importer; td.summary = summary; td.sample_entry = sample_entry; td.track_number = track_number; td.track_id = h264_track_id; td.timescale = video_summary->timescale; td.timebase = video_summary->timebase; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters(&media_param); media_param.timescale = td.timescale; lsmash_set_media_parameters(root, h264_track_id, &media_param); } if (params.in_aac_path) { importer_t *aac_importer = lsmash_importer_open(params.in_aac_path, "adts"); if (aac_importer == nullptr) { return false; } int aac_track_id = lsmash_create_track(root, ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK); lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters(&track_param); (int&)track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; int sample_entry = 0; lsmash_summary_t *summary = lsmash_duplicate_summary(aac_importer, track_number); auto audio_summary = (lsmash_audio_summary_t*)summary; lsmash_set_track_parameters(root, aac_track_id, &track_param); sample_entry = lsmash_add_sample_entry(root, aac_track_id, summary); auto& td = track_data[num_track_data++]; td.importer = aac_importer; td.summary = summary; td.sample_entry = sample_entry; td.track_number = track_number; td.track_id = aac_track_id; td.timescale = audio_summary->frequency; td.timebase = 1; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters(&media_param); media_param.timescale = td.timescale; lsmash_set_media_parameters(root, aac_track_id, &media_param); } double largest_dts = 0.0; uint32_t num_consecutive_sample_skip = 0; for (int ti = 0;;) { auto &td = track_data[ti]; if (!td.sample) { int ret = lsmash_importer_get_access_unit(td.importer, td.track_number, &td.sample); if (ret <= -1) // error { lsmash_delete_sample(td.sample); break; } else if (ret == 1) /* a change of stream's properties */ { lsmash_cleanup_summary(td.summary); td.summary = lsmash_duplicate_summary(td.importer, td.track_number); td.sample_entry = lsmash_add_sample_entry(root, td.track_id, td.summary); if (!td.sample_entry) { break; } } else if (ret == 2) /* EOF */ { lsmash_delete_sample(td.sample); break; } if (td.sample) { td.sample->index = td.sample_entry; td.sample->dts *= td.timebase; td.sample->cts *= td.timebase; td.dts = (double)td.sample->dts / td.timescale; } } if (td.sample) { if (td.dts <= largest_dts || num_consecutive_sample_skip == num_track_data) { uint64_t sample_size = td.sample->length; uint64_t sample_dts = td.sample->dts; uint64_t sample_cts = td.sample->cts; if (lsmash_append_sample(root, td.track_id, td.sample)) { return false; } td.prev_dts = sample_dts; td.sample = nullptr; largest_dts = std::max<double>(largest_dts, td.dts); num_consecutive_sample_skip = 0; } else { // skip ++num_consecutive_sample_skip; } } ti = (ti + 1) % num_track_data; } for (int i = 0; i < num_track_data; ++i) { lsmash_flush_pooled_samples(root, track_data[i].track_id, fps_den); lsmash_cleanup_summary(track_data[i].summary); lsmash_importer_close(track_data[i].importer); } lsmash_finish_movie(root, nullptr); lsmash_close_file(&mp4_stream); lsmash_destroy_root(root); return true; }
int lsmash_create_reference_chapter_track( lsmash_root_t *root, uint32_t track_ID, char *file_name ) { if( isom_check_initializer_present( root ) < 0 ) goto error_message; lsmash_file_t *file = root->file; if( !file || !file->moov || !file->moov->mvhd ) goto error_message; if( file->forbid_tref || (!file->qt_compatible && !file->itunes_movie) ) { lsmash_log( NULL, LSMASH_LOG_ERROR, "reference chapter is not available for this file.\n" ); goto error_message; } FILE *chapter = NULL; /* shut up 'uninitialized' warning */ /* Create a Track Reference Box. */ isom_trak_t *trak = isom_get_trak( file, track_ID ); if( !trak ) { lsmash_log( NULL, LSMASH_LOG_ERROR, "the specified track ID to apply the chapter doesn't exist.\n" ); goto error_message; } if( !trak->tref && !isom_add_tref( trak ) ) goto error_message; /* Create a track_ID for a new chapter track. */ uint32_t *id = (uint32_t *)lsmash_malloc( sizeof(uint32_t) ); if( !id ) goto error_message; uint32_t chapter_track_ID = *id = file->moov->mvhd->next_track_ID; /* Create a Track Reference Type Box. */ isom_tref_type_t *chap = isom_add_track_reference_type( trak->tref, QT_TREF_TYPE_CHAP ); if( !chap ) { lsmash_free( id ); goto error_message; } chap->ref_count = 1; chap->track_ID = id; /* Create a reference chapter track. */ if( chapter_track_ID != lsmash_create_track( root, ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) ) goto error_message; /* Set track parameters. */ lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters( &track_param ); track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; if( lsmash_set_track_parameters( root, chapter_track_ID, &track_param ) < 0 ) goto fail; /* Set media parameters. */ uint64_t media_timescale = lsmash_get_media_timescale( root, track_ID ); if( !media_timescale ) goto fail; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters( &media_param ); media_param.timescale = media_timescale; media_param.ISO_language = file->max_3gpp_version >= 6 || file->itunes_movie ? ISOM_LANGUAGE_CODE_UNDEFINED : 0; media_param.MAC_language = 0; if( lsmash_set_media_parameters( root, chapter_track_ID, &media_param ) < 0 ) goto fail; /* Create a sample description. */ lsmash_codec_type_t sample_type = file->max_3gpp_version >= 6 || file->itunes_movie ? ISOM_CODEC_TYPE_TX3G_TEXT : QT_CODEC_TYPE_TEXT_TEXT; lsmash_summary_t summary = { .sample_type = sample_type, .data_ref_index = 1 }; uint32_t sample_entry = lsmash_add_sample_entry( root, chapter_track_ID, &summary ); if( !sample_entry ) goto fail; /* Check each line format. */ fn_get_chapter_data fnc = isom_check_chap_line( file_name ); if( !fnc ) goto fail; /* Open chapter format file. */ chapter = lsmash_fopen( file_name, "rb" ); if( !chapter ) { lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); goto fail; } /* Parse the file and write text samples. */ isom_chapter_entry_t data; while( !fnc( chapter, &data ) ) { /* set start_time */ data.start_time = data.start_time * 1e-9 * media_timescale + 0.5; /* write a text sample here */ int is_qt_text = lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TEXT_TEXT ); uint16_t name_length = strlen( data.chapter_name ); lsmash_sample_t *sample = lsmash_create_sample( 2 + name_length + 12 * is_qt_text ); if( !sample ) { lsmash_free( data.chapter_name ); goto fail; } sample->data[0] = (name_length >> 8) & 0xff; sample->data[1] = name_length & 0xff; memcpy( sample->data + 2, data.chapter_name, name_length ); if( is_qt_text ) { /* QuickTime Player requires Text Encoding Attribute Box ('encd') if media language is ISO language codes : undefined. * Also this box can avoid garbling if the QuickTime text sample is encoded by Unicode characters. * Note: 3GPP Timed Text supports only UTF-8 or UTF-16, so this box isn't needed. */ static const uint8_t encd[12] = { 0x00, 0x00, 0x00, 0x0C, /* size: 12 */ 0x65, 0x6E, 0x63, 0x64, /* type: 'encd' */ 0x00, 0x00, 0x01, 0x00 /* Unicode Encoding */ }; memcpy( sample->data + 2 + name_length, encd, 12 ); } sample->dts = data.start_time; sample->cts = data.start_time; sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; sample->index = sample_entry; if( lsmash_append_sample( root, chapter_track_ID, sample ) < 0 ) { lsmash_free( data.chapter_name ); goto fail; } lsmash_freep( &data.chapter_name ); } if( lsmash_flush_pooled_samples( root, chapter_track_ID, 0 ) < 0 ) goto fail; isom_trak_t *chapter_trak = isom_get_trak( file, chapter_track_ID ); if( !chapter_trak ) goto fail; fclose( chapter ); chapter_trak->is_chapter = 1; chapter_trak->related_track_ID = track_ID; return 0; fail: if( chapter ) fclose( chapter ); /* Remove chapter track reference. */ if( trak->tref->ref_list.tail ) isom_remove_box_by_itself( trak->tref->ref_list.tail->data ); if( trak->tref->ref_list.entry_count == 0 ) isom_remove_box_by_itself( trak->tref ); /* Remove the reference chapter track attached at tail of the list. */ if( file->moov->trak_list.tail ) isom_remove_box_by_itself( file->moov->trak_list.tail->data ); error_message: lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to set reference chapter.\n" ); return LSMASH_ERR_NAMELESS; } uint32_t lsmash_count_tyrant_chapter( lsmash_root_t *root ) { if( isom_check_initializer_present( root ) < 0 && root->file->initializer->moov && root->file->initializer->moov->udta && root->file->initializer->moov->udta->chpl && root->file->initializer->moov->udta->chpl->list ) return root->file->initializer->moov->udta->chpl->list->entry_count; return 0; } char *lsmash_get_tyrant_chapter( lsmash_root_t *root, uint32_t index, double *timestamp ) { if( isom_check_initializer_present( root ) < 0 ) return NULL; lsmash_file_t *file = root->file->initializer; if( !file->moov || !file->moov->mvhd || !file->moov->udta || !file->moov->udta->chpl ) return NULL; isom_chpl_t *chpl = file->moov->udta->chpl; isom_chpl_entry_t *data = (isom_chpl_entry_t *)lsmash_get_entry_data( chpl->list, index ); if( !data ) return NULL; double timescale = chpl->version ? 10000000.0 : file->moov->mvhd->timescale; *timestamp = data->start_time / timescale; if( !memcmp( data->chapter_name, UTF8_BOM, UTF8_BOM_LENGTH ) ) return data->chapter_name + UTF8_BOM_LENGTH; return data->chapter_name; } int lsmash_print_chapter_list( lsmash_root_t *root ) { if( isom_check_initializer_present( root ) < 0 || !(root->file->initializer->flags & LSMASH_FILE_MODE_READ) ) return LSMASH_ERR_FUNCTION_PARAM; lsmash_file_t *file = root->file->initializer; if( file->moov && file->moov->udta && file->moov->udta->chpl ) { isom_chpl_t *chpl = file->moov->udta->chpl; uint32_t timescale; if( !chpl->version ) { if( !file->moov && !file->moov->mvhd ) return LSMASH_ERR_NAMELESS; timescale = file->moov->mvhd->timescale; } else timescale = 10000000; uint32_t i = 1; for( lsmash_entry_t *entry = chpl->list->head; entry; entry = entry->next ) { isom_chpl_entry_t *data = (isom_chpl_entry_t *)entry->data; int64_t start_time = data->start_time / timescale; int hh = start_time / 3600; int mm = (start_time / 60) % 60; int ss = start_time % 60; int ms = ((data->start_time / (double)timescale) - hh * 3600 - mm * 60 - ss) * 1e3 + 0.5; if( !memcmp( data->chapter_name, UTF8_BOM, UTF8_BOM_LENGTH ) ) /* detect BOM */ { data->chapter_name += UTF8_BOM_LENGTH; #ifdef _WIN32 if( i == 1 ) printf( UTF8_BOM ); /* add BOM on Windows */ #endif } printf( "CHAPTER%02"PRIu32"=%02d:%02d:%02d.%03d\n", i, hh, mm, ss, ms ); printf( "CHAPTER%02"PRIu32"NAME=%s\n", i++, data->chapter_name ); } return 0; } lsmash_log( NULL, LSMASH_LOG_ERROR, "this file doesn't have a chapter list.\n" ); return LSMASH_ERR_NAMELESS; }
int lsmash_create_reference_chapter_track( lsmash_root_t *root, uint32_t track_ID, char *file_name ) { if( !root || !root->moov || !root->moov->mvhd || !root->moov->trak_list ) goto error_message; if( !root->qt_compatible && !root->itunes_movie ) { lsmash_log( LSMASH_LOG_ERROR, "reference chapter is not available for this file.\n" ); goto error_message; } FILE *chapter = NULL; /* shut up 'uninitialized' warning */ /* Create a Track Reference Box. */ isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); if( !trak ) { lsmash_log( LSMASH_LOG_ERROR, "the specified track ID to apply the chapter doesn't exist.\n" ); goto error_message; } if( isom_add_tref( trak ) ) goto error_message; /* Create a track_ID for a new chapter track. */ uint32_t *id = (uint32_t *)malloc( sizeof(uint32_t) ); if( !id ) goto error_message; uint32_t chapter_track_ID = *id = root->moov->mvhd->next_track_ID; /* Create a Track Reference Type Box. */ isom_tref_type_t *chap = isom_add_track_reference_type( trak->tref, QT_TREF_TYPE_CHAP, 1, id ); if( !chap ) goto error_message; /* no need to free id */ /* Create a reference chapter track. */ if( chapter_track_ID != lsmash_create_track( root, ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) ) goto error_message; /* Set track parameters. */ lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters( &track_param ); track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; if( lsmash_set_track_parameters( root, chapter_track_ID, &track_param ) ) goto fail; /* Set media parameters. */ uint64_t media_timescale = lsmash_get_media_timescale( root, track_ID ); if( !media_timescale ) goto fail; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters( &media_param ); media_param.timescale = media_timescale; media_param.ISO_language = root->max_3gpp_version >= 6 || root->itunes_movie ? ISOM_LANGUAGE_CODE_UNDEFINED : 0; media_param.MAC_language = 0; if( lsmash_set_media_parameters( root, chapter_track_ID, &media_param ) ) goto fail; /* Create a sample description. */ lsmash_codec_type_t sample_type = root->max_3gpp_version >= 6 || root->itunes_movie ? ISOM_CODEC_TYPE_TX3G_TEXT : QT_CODEC_TYPE_TEXT_TEXT; lsmash_summary_t summary = { .sample_type = sample_type }; uint32_t sample_entry = lsmash_add_sample_entry( root, chapter_track_ID, &summary ); if( !sample_entry ) goto fail; /* Check each line format. */ fn_get_chapter_data fnc = isom_check_chap_line( file_name ); if( !fnc ) goto fail; /* Open chapter format file. */ chapter = fopen( file_name, "rb" ); if( !chapter ) { lsmash_log( LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); goto fail; } /* Parse the file and write text samples. */ isom_chapter_entry_t data; while( !fnc( chapter, &data ) ) { /* set start_time */ data.start_time = data.start_time * 1e-9 * media_timescale + 0.5; /* write a text sample here */ int is_qt_text = lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TEXT_TEXT ); uint16_t name_length = strlen( data.chapter_name ); lsmash_sample_t *sample = lsmash_create_sample( 2 + name_length + 12 * is_qt_text ); if( !sample ) { free( data.chapter_name ); goto fail; } sample->data[0] = (name_length >> 8) & 0xff; sample->data[1] = name_length & 0xff; memcpy( sample->data + 2, data.chapter_name, name_length ); if( is_qt_text ) { /* QuickTime Player requires Text Encoding Attribute Box ('encd') if media language is ISO language codes : undefined. * Also this box can avoid garbling if the QuickTime text sample is encoded by Unicode characters. * Note: 3GPP Timed Text supports only UTF-8 or UTF-16, so this box isn't needed. */ static const uint8_t encd[12] = { 0x00, 0x00, 0x00, 0x0C, /* size: 12 */ 0x65, 0x6E, 0x63, 0x64, /* type: 'encd' */ 0x00, 0x00, 0x01, 0x00 /* Unicode Encoding */ }; memcpy( sample->data + 2 + name_length, encd, 12 ); } sample->dts = sample->cts = data.start_time; sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; sample->index = sample_entry; if( lsmash_append_sample( root, chapter_track_ID, sample ) ) { free( data.chapter_name ); goto fail; } free( data.chapter_name ); data.chapter_name = NULL; } if( lsmash_flush_pooled_samples( root, chapter_track_ID, 0 ) ) goto fail; isom_trak_entry_t *chapter_trak = isom_get_trak( root, chapter_track_ID ); if( !chapter_trak ) goto fail; fclose( chapter ); chapter_trak->is_chapter = 1; chapter_trak->related_track_ID = track_ID; return 0; fail: if( chapter ) fclose( chapter ); /* Remove chapter track reference. */ lsmash_remove_entry_direct( trak->tref->ref_list, trak->tref->ref_list->tail, isom_remove_track_reference_type ); if( trak->tref->ref_list->entry_count == 0 ) isom_remove_tref( trak->tref ); /* Remove the reference chapter track attached at tail of the list. */ lsmash_remove_entry_direct( root->moov->trak_list, root->moov->trak_list->tail, isom_remove_trak ); error_message: lsmash_log( LSMASH_LOG_ERROR, "failed to set reference chapter.\n" ); return -1; }
int main( int argc, char *argv[] ) { if( argc < 2 ) return print_help( -1 ); int dump_box = 1; int chapter = 0; char *filename; if( argc > 2 ) { if( !strcasecmp( argv[1], "--box" ) ) DO_NOTHING; else if( !strcasecmp( argv[1], "--chapter" ) ) chapter = 1; else if( !strcasecmp( argv[1], "--timestamp" ) ) dump_box = 0; else return print_help( -1 ); filename = argv[2]; } else { if( !strcasecmp( argv[1], "-h" ) || !strcasecmp( argv[1], "--help" ) ) return print_help( 0 ); filename = argv[1]; } #ifdef _WIN32 _setmode( _fileno(stdin), _O_BINARY ); #endif lsmash_file_mode mode = LSMASH_FILE_MODE_READ; if( dump_box ) mode |= LSMASH_FILE_MODE_DUMP; lsmash_root_t *root = lsmash_open_movie( filename, mode ); if( !root ) { fprintf( stderr, "Failed to open input file.\n" ); return -1; } if( chapter ) { if( lsmash_print_chapter_list( root ) ) return BOXDUMPER_ERR( "Failed to extract chapter.\n" ); } else if( dump_box ) { if( lsmash_print_movie( root, "-" ) ) return BOXDUMPER_ERR( "Failed to dump box structure.\n" ); } else { lsmash_movie_parameters_t movie_param; lsmash_initialize_movie_parameters( &movie_param ); lsmash_get_movie_parameters( root, &movie_param ); uint32_t num_tracks = movie_param.number_of_tracks; for( uint32_t track_number = 1; track_number <= num_tracks; track_number++ ) { uint32_t track_ID = lsmash_get_track_ID( root, track_number ); if( !track_ID ) return BOXDUMPER_ERR( "Failed to get track_ID.\n" ); lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters( &media_param ); if( lsmash_get_media_parameters( root, track_ID, &media_param ) ) return BOXDUMPER_ERR( "Failed to get media parameters.\n" ); if( lsmash_construct_timeline( root, track_ID ) ) return BOXDUMPER_ERR( "Failed to construct timeline.\n" ); uint32_t timeline_shift; if( lsmash_get_composition_to_decode_shift_from_media_timeline( root, track_ID, &timeline_shift ) ) return BOXDUMPER_ERR( "Failed to get timestamps.\n" ); lsmash_media_ts_list_t ts_list; if( lsmash_get_media_timestamps( root, track_ID, &ts_list ) ) return BOXDUMPER_ERR( "Failed to get timestamps.\n" ); fprintf( stdout, "track_ID: %"PRIu32"\n", track_ID ); fprintf( stdout, "Media timescale: %"PRIu32"\n", media_param.timescale ); lsmash_media_ts_t *ts_array = ts_list.timestamp; if( !ts_array ) { fprintf( stdout, "\n" ); continue; } for( uint32_t i = 0; i < ts_list.sample_count; i++ ) fprintf( stdout, "DTS = %"PRIu64", CTS = %"PRIu64"\n", ts_array[i].dts, ts_array[i].cts + timeline_shift ); free( ts_array ); fprintf( stdout, "\n" ); } } lsmash_destroy_root( root ); return 0; }