int lsmash_set_tyrant_chapter( lsmash_root_t *root, char *file_name, int add_bom ) { /* This function should be called after updating of the latest movie duration. */ if( !root || !root->moov || !root->moov->mvhd || !root->moov->mvhd->timescale || !root->moov->mvhd->duration ) goto error_message; /* check each line format */ fn_get_chapter_data fnc = isom_check_chap_line( file_name ); if( !fnc ) goto error_message; FILE *chapter = fopen( file_name, "rb" ); if( !chapter ) { lsmash_log( LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); goto error_message; } if( isom_add_udta( root, 0 ) || isom_add_chpl( root->moov ) ) goto fail; isom_chapter_entry_t data = {0}; while( !fnc( chapter, &data ) ) { if( add_bom ) { char *chapter_name_with_bom = (char *)malloc( strlen( data.chapter_name ) + 1 + UTF8_BOM_LENGTH ); if( !chapter_name_with_bom ) goto fail2; sprintf( chapter_name_with_bom, "%s%s", UTF8_BOM, data.chapter_name ); free( data.chapter_name ); data.chapter_name = chapter_name_with_bom; } data.start_time = (data.start_time + 50) / 100; /* convert to 100ns unit */ if( data.start_time / 1e7 > (double)root->moov->mvhd->duration / root->moov->mvhd->timescale ) { lsmash_log( LSMASH_LOG_WARNING, "a chapter point exceeding the actual duration detected. This chapter point and the following ones (if any) will be cut off.\n" ); free( data.chapter_name ); break; } if( isom_add_chpl_entry( root->moov->udta->chpl, &data ) ) goto fail2; free( data.chapter_name ); data.chapter_name = NULL; } fclose( chapter ); return 0; fail2: if( data.chapter_name ) free( data.chapter_name ); fail: fclose( chapter ); error_message: lsmash_log( LSMASH_LOG_ERROR, "failed to set chapter list.\n" ); return -1; }
static fn_get_chapter_data isom_check_chap_line( char *file_name ) { FILE *fp = lsmash_fopen( file_name, "rb" ); if( !fp ) { lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); return NULL; } char buff[CHAPTER_BUFSIZE]; fn_get_chapter_data fnc = NULL; if( fgets( buff, CHAPTER_BUFSIZE, fp ) != NULL ) { char *p_buff = &buff[ !memcmp( buff, UTF8_BOM, UTF8_BOM_LENGTH ) ? UTF8_BOM_LENGTH : 0 ]; /* BOM detection */ if( !strncmp( p_buff, "CHAPTER", 7 ) ) fnc = isom_read_simple_chapter; else if( isdigit( (unsigned char)p_buff[0] ) && isdigit( (unsigned char)p_buff[1] ) && p_buff[2] == ':' && isdigit( (unsigned char)p_buff[3] ) && isdigit( (unsigned char)p_buff[4] ) && p_buff[5] == ':' ) fnc = isom_read_minimum_chapter; else lsmash_log( NULL, LSMASH_LOG_ERROR, "the chapter file is malformed.\n" ); } fclose( fp ); return fnc; }
int lsmash_print_chapter_list( lsmash_root_t *root ) { if( !root || !(root->flags & LSMASH_FILE_MODE_READ) ) return -1; if( root->moov && root->moov->udta && root->moov->udta->chpl ) { isom_chpl_t *chpl = root->moov->udta->chpl; uint32_t timescale; if( !chpl->version ) { if( !root->moov && !root->moov->mvhd ) return -1; timescale = root->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; } else lsmash_log( LSMASH_LOG_ERROR, "this file doesn't have a chapter list.\n" ); return -1; }
static int vc1_importer_get_access_unit_internal( importer_t *importer, int probe ) { vc1_importer_t *vc1_imp = (vc1_importer_t *)importer->info; vc1_info_t *info = &vc1_imp->info; vc1_stream_buffer_t *sb = &info->buffer; vc1_access_unit_t *access_unit = &info->access_unit; lsmash_bs_t *bs = vc1_imp->bs; int complete_au = 0; access_unit->data_length = 0; while( 1 ) { uint8_t bdu_type; uint64_t trailing_zero_bytes; uint64_t ebdu_length = vc1_find_next_start_code_prefix( bs, &bdu_type, &trailing_zero_bytes ); if( ebdu_length <= VC1_START_CODE_LENGTH && lsmash_bs_is_end( bs, ebdu_length ) ) { /* For the last EBDU. * This EBDU already has been appended into the latest access unit and parsed. */ vc1_complete_au( access_unit, &info->picture, probe ); return vc1_get_au_internal_succeeded( vc1_imp ); } else if( bdu_type == 0xFF ) { lsmash_log( importer, LSMASH_LOG_ERROR, "a forbidden BDU type is detected.\n" ); return vc1_get_au_internal_failed( vc1_imp, complete_au ); } uint64_t next_ebdu_head_pos = info->ebdu_head_pos + ebdu_length + trailing_zero_bytes; #if 0 if( probe ) { fprintf( stderr, "BDU type: %"PRIu8" \n", bdu_type ); fprintf( stderr, " EBDU position: %"PRIx64" \n", info->ebdu_head_pos ); fprintf( stderr, " EBDU length: %"PRIx64" (%"PRIu64")\n", ebdu_length, ebdu_length ); fprintf( stderr, " trailing_zero_bytes: %"PRIx64" \n", trailing_zero_bytes ); fprintf( stderr, " Next EBDU position: %"PRIx64" \n", next_ebdu_head_pos ); } #endif if( bdu_type >= 0x0A && bdu_type <= 0x0F ) { /* Complete the current access unit if encountered delimiter of current access unit. */ if( vc1_find_au_delimit_by_bdu_type( bdu_type, info->prev_bdu_type ) ) /* The last video coded EBDU belongs to the access unit you want at this time. */ complete_au = vc1_complete_au( access_unit, &info->picture, probe ); /* Increase the buffer if needed. */ uint64_t possible_au_length = access_unit->incomplete_data_length + ebdu_length; if( sb->bank->buffer_size < possible_au_length && vc1_supplement_buffer( sb, access_unit, 2 * possible_au_length ) < 0 ) { lsmash_log( importer, LSMASH_LOG_ERROR, "failed to increase the buffer size.\n" ); return vc1_get_au_internal_failed( vc1_imp, complete_au ); } /* Process EBDU by its BDU type and append it to access unit. */ uint8_t *ebdu = lsmash_bs_get_buffer_data( bs ); switch( bdu_type ) { /* FRM_SC: Frame start code * FLD_SC: Field start code * SLC_SC: Slice start code * SEQ_SC: Sequence header start code * EP_SC: Entry-point start code * PIC_L: Picture layer * SLC_L: Slice layer * SEQ_L: Sequence layer * EP_L: Entry-point layer */ case 0x0D : /* Frame * For the Progressive or Frame Interlace mode, shall signal the beginning of a new video frame. * For the Field Interlace mode, shall signal the beginning of a sequence of two independently coded video fields. * [FRM_SC][PIC_L][[FLD_SC][PIC_L] (optional)][[SLC_SC][SLC_L] (optional)] ... */ if( vc1_parse_advanced_picture( info->bits, &info->sequence, &info->picture, sb->rbdu, ebdu, ebdu_length ) < 0 ) { lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse a frame.\n" ); return vc1_get_au_internal_failed( vc1_imp, complete_au ); } case 0x0C : /* Field * Shall only be used for Field Interlaced frames * and shall only be used to signal the beginning of the second field of the frame. * [FRM_SC][PIC_L][FLD_SC][PIC_L][[SLC_SC][SLC_L] (optional)] ... * Field start code is followed by INTERLACE_FIELD_PICTURE_FIELD2() which doesn't have info of its field picture type.*/ break; case 0x0B : /* Slice * Shall not be used for start code of the first slice of a frame. * Shall not be used for start code of the first slice of an interlace field coded picture. * [FRM_SC][PIC_L][[FLD_SC][PIC_L] (optional)][SLC_SC][SLC_L][[SLC_SC][SLC_L] (optional)] ... * Slice layer may repeat frame header. We just ignore it. */ info->dvc1_param.slice_present = 1; break; case 0x0E : /* Entry-point header * Entry-point indicates the direct followed frame is a start of group of frames. * Entry-point doesn't indicates the frame is a random access point when multiple sequence headers are present, * since it is necessary to decode sequence header which subsequent frames belong to for decoding them. * Entry point shall be followed by * 1. I-picture - progressive or frame interlace * 2. I/I-picture, I/P-picture, or P/I-picture - field interlace * [[SEQ_SC][SEQ_L] (optional)][EP_SC][EP_L][FRM_SC][PIC_L] ... */ if( vc1_parse_entry_point_header( info, ebdu, ebdu_length, probe ) < 0 ) { lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse an entry point.\n" ); return vc1_get_au_internal_failed( vc1_imp, complete_au ); } /* Signal random access type of the frame that follows this entry-point header. */ info->picture.closed_gop = info->entry_point.closed_entry_point; info->picture.random_accessible = info->dvc1_param.multiple_sequence ? info->picture.start_of_sequence : 1; break; case 0x0F : /* Sequence header * [SEQ_SC][SEQ_L][EP_SC][EP_L][FRM_SC][PIC_L] ... */ if( vc1_parse_sequence_header( info, ebdu, ebdu_length, probe ) < 0 ) { lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse a sequence header.\n" ); return vc1_get_au_internal_failed( vc1_imp, complete_au ); } /* The frame that is the first frame after this sequence header shall be a random accessible point. */ info->picture.start_of_sequence = 1; if( probe && !vc1_imp->first_sequence.present ) vc1_imp->first_sequence = info->sequence; break; default : /* End-of-sequence (0x0A) */ break; } /* Append the current EBDU into the end of an incomplete access unit. */ vc1_append_ebdu_to_au( access_unit, ebdu, ebdu_length, probe ); } else /* We don't support other BDU types such as user data yet. */ return vc1_get_au_internal_failed( vc1_imp, complete_au ); /* Move to the first byte of the next EBDU. */ info->prev_bdu_type = bdu_type; if( lsmash_bs_read_seek( bs, next_ebdu_head_pos, SEEK_SET ) != next_ebdu_head_pos ) { lsmash_log( importer, LSMASH_LOG_ERROR, "failed to seek the next start code suffix.\n" ); return vc1_get_au_internal_failed( vc1_imp, complete_au ); } /* Check if no more data to read from the stream. */ if( !lsmash_bs_is_end( bs, VC1_START_CODE_PREFIX_LENGTH ) ) info->ebdu_head_pos = next_ebdu_head_pos; /* If there is no more data in the stream, and flushed chunk of EBDUs, flush it as complete AU here. */ else if( access_unit->incomplete_data_length && access_unit->data_length == 0 ) { vc1_complete_au( access_unit, &info->picture, probe ); return vc1_get_au_internal_succeeded( vc1_imp ); } if( complete_au ) return vc1_get_au_internal_succeeded( vc1_imp ); } }
static int isom_set_brands ( lsmash_file_t *file, lsmash_brand_type major_brand, uint32_t minor_version, lsmash_brand_type *brands, uint32_t brand_count ) { if( brand_count > 50 ) return LSMASH_ERR_FUNCTION_PARAM; /* We support setting brands up to 50. */ if( major_brand == 0 && (!brands || brand_count == 0 || brands[0] == 0) ) { if( file->flags & LSMASH_FILE_MODE_INITIALIZATION ) { /* Absence of File Type Box means this file is a QuickTime or MP4 version 1 format file. */ isom_remove_box_by_itself( file->ftyp ); /* Anyway we use QTFF as a default file format. */ isom_clear_compat_flags( file ); file->qt_compatible = 1; } else { /* The absence of the Segment Type Box is allowed. * We set brands from the initialization segment after switching to this segment. */ for( lsmash_entry_t *entry = file->styp_list.head; entry; entry = entry->next ) isom_remove_box_by_itself( entry->data ); if( file->initializer ) { /* Copy flags for compatibility. */ memcpy( (int8_t *)file + COMPAT_FLAGS_OFFSET, file->initializer, sizeof(lsmash_file_t) - COMPAT_FLAGS_OFFSET ); file->isom_compatible = 1; file->allow_moof_base = 1; file->media_segment = 1; if( file->min_isom_version < 5 ) file->min_isom_version = 5; if( file->max_isom_version < 6 ) file->max_isom_version = 6; } } return 0; } else if( major_brand == 0 ) { major_brand = brands[0]; lsmash_log( NULL, LSMASH_LOG_WARNING, "major_brand is not specified. Use the first brand in the compatible brand list as major_brand.\n" ); } else if( !brands ) brand_count = 0; isom_ftyp_t *ftyp; if( file->flags & LSMASH_FILE_MODE_INITIALIZATION ) { /* Add File Type Box if absent yet. */ if( !file->ftyp && !isom_add_ftyp( file ) ) return LSMASH_ERR_NAMELESS; ftyp = file->ftyp; } else { /* Add Segment Type Box if absent yet. */ ftyp = file->styp_list.head && file->styp_list.head->data ? (isom_styp_t *)file->styp_list.head->data : isom_add_styp( file ); if( !ftyp ) return LSMASH_ERR_NAMELESS; } /* Allocate an array of compatible brands. * ISO/IEC 14496-12 doesn't forbid the absence of brands in the compatible brand list. * For a reason of safety, however, we set at least one brand in the list. */ size_t alloc_size = (brand_count ? brand_count : 1) * sizeof(uint32_t); lsmash_brand_type *compatible_brands; if( !file->compatible_brands ) compatible_brands = lsmash_malloc( alloc_size ); else compatible_brands = lsmash_realloc( file->compatible_brands, alloc_size ); if( !compatible_brands ) return LSMASH_ERR_MEMORY_ALLOC; /* Set compatible brands. */ if( brand_count ) for( uint32_t i = 0; i < brand_count; i++ ) compatible_brands[i] = brands[i]; else { /* At least one compatible brand. */ compatible_brands[0] = major_brand; brand_count = 1; } file->compatible_brands = compatible_brands; /* Duplicate an array of compatible brands. */ lsmash_free( ftyp->compatible_brands ); ftyp->compatible_brands = lsmash_memdup( compatible_brands, alloc_size ); if( !ftyp->compatible_brands ) { lsmash_freep( &file->compatible_brands ); return LSMASH_ERR_MEMORY_ALLOC; } ftyp->size = ISOM_BASEBOX_COMMON_SIZE + 8 + brand_count * 4; ftyp->major_brand = major_brand; ftyp->minor_version = minor_version; ftyp->brand_count = brand_count; file->brand_count = brand_count; return isom_check_compatibility( file ); }
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_set_tyrant_chapter( lsmash_root_t *root, char *file_name, int add_bom ) { if( isom_check_initializer_present( root ) < 0 ) goto error_message; /* This function should be called after updating of the latest movie duration. */ lsmash_file_t *file = root->file; if( !file || !file->moov || !file->moov->mvhd || file->moov->mvhd->timescale == 0 || file->moov->mvhd->duration == 0 ) goto error_message; /* check each line format */ fn_get_chapter_data fnc = isom_check_chap_line( file_name ); if( !fnc ) goto error_message; 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 error_message; } if( (!file->moov->udta && !isom_add_udta( file->moov )) || (!file->moov->udta->chpl && !isom_add_chpl( file->moov->udta )) ) goto fail; file->moov->udta->chpl->version = 1; /* version = 1 is popular. */ isom_chapter_entry_t data = { 0 }; while( !fnc( chapter, &data ) ) { if( add_bom ) { char *chapter_name_with_bom = (char *)lsmash_malloc( strlen( data.chapter_name ) + 1 + UTF8_BOM_LENGTH ); if( !chapter_name_with_bom ) goto fail2; sprintf( chapter_name_with_bom, "%s%s", UTF8_BOM, data.chapter_name ); lsmash_free( data.chapter_name ); data.chapter_name = chapter_name_with_bom; } data.start_time = (data.start_time + 50) / 100; /* convert to 100ns unit */ if( data.start_time / 1e7 > (double)file->moov->mvhd->duration / file->moov->mvhd->timescale ) { lsmash_log( NULL, LSMASH_LOG_WARNING, "a chapter point exceeding the actual duration detected." "This chapter point and the following ones (if any) will be cut off.\n" ); lsmash_free( data.chapter_name ); break; } if( isom_add_chpl_entry( file->moov->udta->chpl, &data ) < 0 ) goto fail2; lsmash_freep( &data.chapter_name ); } fclose( chapter ); return 0; fail2: lsmash_free( data.chapter_name ); fail: fclose( chapter ); error_message: lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to set chapter list.\n" ); return LSMASH_ERR_NAMELESS; }
static int dts_importer_get_next_accessunit_internal( importer_t *importer ) { int au_completed = 0; dts_importer_t *dts_imp = (dts_importer_t *)importer->info; dts_info_t *info = &dts_imp->info; lsmash_bs_t *bs = info->bits->bs; while( !au_completed ) { /* Read data from the stream if needed. */ dts_imp->next_frame_pos += info->frame_size; lsmash_bs_read_seek( bs, dts_imp->next_frame_pos, SEEK_SET ); uint64_t remain_size = lsmash_bs_get_remaining_buffer_size( bs ); if( remain_size < DTS_MAX_EXSS_SIZE ) { int err = lsmash_bs_read( bs, bs->buffer.max_size ); if( err < 0 ) { lsmash_log( importer, LSMASH_LOG_ERROR, "failed to read data from the stream.\n" ); return err; } remain_size = lsmash_bs_get_remaining_buffer_size( bs ); } memcpy( dts_imp->buffer, lsmash_bs_get_buffer_data( bs ), LSMASH_MIN( remain_size, DTS_MAX_EXSS_SIZE ) ); /* Check the remainder length of the buffer. * If there is enough length, then parse the frame in it. * The length 10 is the required byte length to get frame size. */ if( bs->eob || (bs->eof && remain_size < 10) ) { /* Reached the end of stream. */ importer->status = IMPORTER_EOF; au_completed = !!dts_imp->incomplete_au_length; if( !au_completed ) { /* No more access units in the stream. */ if( lsmash_bs_get_remaining_buffer_size( bs ) ) { lsmash_log( importer, LSMASH_LOG_WARNING, "the stream is truncated at the end.\n" ); return LSMASH_ERR_INVALID_DATA; } return 0; } if( !info->ddts_param_initialized ) dts_update_specific_param( info ); } else { /* Parse substream frame. */ dts_substream_type prev_substream_type = info->substream_type; info->substream_type = dts_get_substream_type( info ); int err; int (*dts_parse_frame)( dts_info_t * ) = NULL; switch( info->substream_type ) { /* Decide substream frame parser and check if this frame and the previous frame belong to the same AU. */ case DTS_SUBSTREAM_TYPE_CORE : if( prev_substream_type != DTS_SUBSTREAM_TYPE_NONE ) au_completed = 1; dts_parse_frame = dts_parse_core_substream; break; case DTS_SUBSTREAM_TYPE_EXTENSION : { uint8_t prev_exss_index = info->exss_index; if( (err = dts_get_exss_index( info, &info->exss_index )) < 0 ) { lsmash_log( importer, LSMASH_LOG_ERROR, "failed to get the index of an extension substream.\n" ); return err; } if( prev_substream_type == DTS_SUBSTREAM_TYPE_EXTENSION && info->exss_index <= prev_exss_index ) au_completed = 1; dts_parse_frame = dts_parse_extension_substream; break; } default : lsmash_log( importer, LSMASH_LOG_ERROR, "unknown substream type is detected.\n" ); return LSMASH_ERR_NAMELESS; } if( !info->ddts_param_initialized && au_completed ) dts_update_specific_param( info ); info->frame_size = 0; if( (err = dts_parse_frame( info )) < 0 ) { lsmash_log( importer, LSMASH_LOG_ERROR, "failed to parse a frame.\n" ); return err; } } if( au_completed ) { memcpy( dts_imp->au, dts_imp->incomplete_au, dts_imp->incomplete_au_length ); dts_imp->au_length = dts_imp->incomplete_au_length; dts_imp->incomplete_au_length = 0; info->exss_count = (info->substream_type == DTS_SUBSTREAM_TYPE_EXTENSION); if( importer->status == IMPORTER_EOF ) break; } /* Increase buffer size to store AU if short. */ if( dts_imp->incomplete_au_length + info->frame_size > dts_imp->au_buffers->buffer_size ) { lsmash_multiple_buffers_t *temp = lsmash_resize_multiple_buffers( dts_imp->au_buffers, dts_imp->au_buffers->buffer_size + DTS_MAX_EXSS_SIZE ); if( !temp ) return LSMASH_ERR_MEMORY_ALLOC; dts_imp->au_buffers = temp; dts_imp->au = lsmash_withdraw_buffer( dts_imp->au_buffers, 1 ); dts_imp->incomplete_au = lsmash_withdraw_buffer( dts_imp->au_buffers, 2 ); } /* Append frame data. */ memcpy( dts_imp->incomplete_au + dts_imp->incomplete_au_length, dts_imp->buffer, info->frame_size ); dts_imp->incomplete_au_length += info->frame_size; } return bs->error ? LSMASH_ERR_NAMELESS : 0; }
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; }
static int vc1_analyze_whole_stream ( importer_t *importer ) { /* Parse all EBDU in the stream for preparation of calculating timestamps. */ uint32_t cts_alloc = (1 << 12) * sizeof(uint64_t); uint64_t *cts = lsmash_malloc( cts_alloc ); if( !cts ) return LSMASH_ERR_MEMORY_ALLOC; /* Failed to allocate CTS list */ uint32_t num_access_units = 0; uint32_t num_consecutive_b = 0; lsmash_class_t *logger = &(lsmash_class_t){ "VC-1" }; lsmash_log( &logger, LSMASH_LOG_INFO, "Analyzing stream as VC-1\r" ); vc1_importer_t *vc1_imp = (vc1_importer_t *)importer->info; vc1_info_t *info = &vc1_imp->info; importer->status = IMPORTER_OK; int err; while( importer->status != IMPORTER_EOF ) { #if 0 lsmash_log( &logger, LSMASH_LOG_INFO, "Analyzing stream as VC-1: %"PRIu32"\n", num_access_units + 1 ); #endif if( (err = vc1_importer_get_access_unit_internal( importer, 1 )) < 0 ) goto fail; vc1_importer_check_eof( importer, &info->access_unit ); /* In the case where B-pictures exist * Decode order * I[0]P[1]P[2]B[3]B[4]P[5]... * DTS * 0 1 2 3 4 5 ... * Composition order * I[0]P[1]B[3]B[4]P[2]P[5]... * CTS * 1 2 3 4 5 6 ... * We assumes B or BI-pictures always be present in the stream here. */ if( !info->access_unit.disposable ) { /* Apply CTS of the last B-picture plus 1 to the last non-B-picture. */ if( num_access_units > num_consecutive_b ) cts[ num_access_units - num_consecutive_b - 1 ] = num_access_units; num_consecutive_b = 0; } else /* B or BI-picture */ { /* B and BI-pictures shall be output or displayed in the same order as they are encoded. */ cts[ num_access_units ] = num_access_units; ++num_consecutive_b; info->dvc1_param.bframe_present = 1; } if( cts_alloc <= num_access_units * sizeof(uint64_t) ) { uint32_t alloc = 2 * num_access_units * sizeof(uint64_t); uint64_t *temp = lsmash_realloc( cts, alloc ); if( !temp ) { err = LSMASH_ERR_MEMORY_ALLOC; goto fail; /* Failed to re-allocate CTS list */ } cts = temp; cts_alloc = alloc; } vc1_imp->max_au_length = LSMASH_MAX( info->access_unit.data_length, vc1_imp->max_au_length ); ++num_access_units; } if( num_access_units > num_consecutive_b ) cts[ num_access_units - num_consecutive_b - 1 ] = num_access_units; else { err = LSMASH_ERR_INVALID_DATA; goto fail; } /* Construct timestamps. */ lsmash_media_ts_t *timestamp = lsmash_malloc( num_access_units * sizeof(lsmash_media_ts_t) ); if( !timestamp ) { err = LSMASH_ERR_MEMORY_ALLOC; goto fail; /* Failed to allocate timestamp list */ } for( uint32_t i = 1; i < num_access_units; i++ ) if( cts[i] < cts[i - 1] ) { vc1_imp->composition_reordering_present = 1; break; } if( vc1_imp->composition_reordering_present ) for( uint32_t i = 0; i < num_access_units; i++ ) { timestamp[i].cts = cts[i]; timestamp[i].dts = i; } else for( uint32_t i = 0; i < num_access_units; i++ ) timestamp[i].cts = timestamp[i].dts = i; lsmash_free( cts ); lsmash_log_refresh_line( &logger ); #if 0 for( uint32_t i = 0; i < num_access_units; i++ ) fprintf( stderr, "Timestamp[%"PRIu32"]: DTS=%"PRIu64", CTS=%"PRIu64"\n", i, timestamp[i].dts, timestamp[i].cts ); #endif vc1_imp->ts_list.sample_count = num_access_units; vc1_imp->ts_list.timestamp = timestamp; return 0; fail: lsmash_log_refresh_line( &logger ); lsmash_free( cts ); return err; }