Beispiel #1
0
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;
}
Beispiel #2
0
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;
}
Beispiel #3
0
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;
}
Beispiel #4
0
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 );
    }
}
Beispiel #5
0
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 );
}
Beispiel #6
0
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;
}
Beispiel #7
0
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;
}
Beispiel #8
0
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;
}
Beispiel #9
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;
}
Beispiel #10
0
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;
}