| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / mp4box application | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include "mp4box.h" | |
| #include <gpac/media_tools.h> | |
| #include <gpac/constants.h> | |
| #include <gpac/scene_manager.h> | |
| #include <gpac/network.h> | |
| #include <gpac/base_coding.h> | |
| #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_X3D) && !defined(GPAC_DISABLE_SVG) | |
| #include <gpac/scenegraph.h> | |
| #endif | |
| #ifndef GPAC_DISABLE_BIFS | |
| #include <gpac/bifs.h> | |
| #endif | |
| #ifndef GPAC_DISABLE_VRML | |
| #include <gpac/nodes_mpeg4.h> | |
| #endif | |
| #ifndef GPAC_DISABLE_ISOM_WRITE | |
| #include <gpac/xml.h> | |
| #include <gpac/isomedia.h> | |
| typedef struct | |
| { | |
| const char *root_file; | |
| const char *dir; | |
| GF_List *imports; | |
| } WGTEnum; | |
| GF_Err set_file_udta(GF_ISOFile *dest, u32 tracknum, u32 udta_type, char *src, Bool is_box_array, Bool is_string) | |
| { | |
| u8 *data = NULL; | |
| GF_Err res = GF_OK; | |
| u32 size; | |
| bin128 uuid; | |
| memset(uuid, 0 , 16); | |
| if (!udta_type && !is_box_array) return GF_BAD_PARAM; | |
| if (!src || !strlen(src)) { | |
| GF_Err e = gf_isom_remove_user_data(dest, tracknum, udta_type, uuid); | |
| if (e==GF_EOS) { | |
| e = GF_OK; | |
| M4_LOG(GF_LOG_WARNING, ("No track.udta found, ignoring\n")); | |
| } | |
| return e; | |
| } | |
| if (!strnicmp(src, "base64", 6)) { | |
| src += 7; | |
| size = (u32) strlen(src); | |
| data = gf_malloc(sizeof(char) * size); | |
| size = gf_base64_decode((u8 *)src, size, data, size); | |
| } else if (is_string) { | |
| data = (u8 *) src; | |
| size = (u32) strlen(src); | |
| is_box_array = 0; | |
| } else { | |
| GF_Err e = gf_file_load_data(src, (u8 **) &data, &size); | |
| if (e) return e; | |
| } | |
| if (size && data) { | |
| if (is_box_array) { | |
| res = gf_isom_add_user_data_boxes(dest, tracknum, data, size); | |
| } else { | |
| res = gf_isom_add_user_data(dest, tracknum, udta_type, uuid, data, size); | |
| } | |
| if (!is_string) | |
| gf_free(data); | |
| } | |
| return res; | |
| } | |
| #ifndef GPAC_DISABLE_MEDIA_IMPORT | |
| #ifndef GPAC_DISABLE_SWF_IMPORT | |
| extern u32 swf_flags; | |
| #endif | |
| extern Double swf_flatten_angle; | |
| extern Bool keep_sys_tracks; | |
| extern u32 fs_dump_flags; | |
| void scene_coding_log(void *cbk, GF_LOG_Level log_level, GF_LOG_Tool log_tool, const char *fmt, va_list vlist); | |
| GF_Err convert_file_info(char *inName, TrackIdentifier *track_id) | |
| { | |
| GF_Err e; | |
| u32 i, cur=1; | |
| Bool found; | |
| GF_MediaImporter *import = gf_malloc(sizeof(GF_MediaImporter)); | |
| if (!import) return GF_OUT_OF_MEM; | |
| memset(import, 0, sizeof(GF_MediaImporter)); | |
| if (!track_id->type) | |
| import->trackID = track_id->ID_or_num; | |
| import->in_name = inName; | |
| import->flags = GF_IMPORT_PROBE_ONLY; | |
| import->print_stats_graph = fs_dump_flags; | |
| e = gf_media_import(import); | |
| if (e) { | |
| M4_LOG(GF_LOG_ERROR, ("Error probing file %s: %s\n", inName, gf_error_to_string(e))); | |
| gf_free(import); | |
| return e; | |
| } | |
| if (track_id->ID_or_num) { | |
| fprintf(stderr, "Import probing results for %s track %s%d:\n", inName, track_id->type ? "#" : "ID ", track_id->ID_or_num); | |
| } else { | |
| fprintf(stderr, "Import probing results for %s:\n", inName); | |
| if (!import->nb_tracks) { | |
| M4_LOG(GF_LOG_WARNING, ("File has no selectable tracks\n")); | |
| gf_free(import); | |
| return GF_OK; | |
| } | |
| fprintf(stderr, "File has %d tracks\n", import->nb_tracks); | |
| } | |
| if (import->probe_duration) { | |
| fprintf(stderr, "Duration: %g s\n", (Double) (import->probe_duration/1000.0)); | |
| } | |
| found = 0; | |
| for (i=0; i<MIN(import->nb_tracks, GF_IMPORT_MAX_TRACKS); i++) { | |
| u32 stype = import->tk_info[i].stream_type; | |
| switch (track_id->type) { | |
| case 0: //by trackID | |
| if (track_id->ID_or_num && (track_id->ID_or_num != import->tk_info[i].track_num) ) continue; | |
| break; | |
| case 1: //by track nul | |
| if (track_id->ID_or_num && (track_id->ID_or_num != i+1) ) continue; | |
| break; | |
| case 2: //by video | |
| if (stype != GF_STREAM_VISUAL) continue; | |
| break; | |
| case 3: //by audio | |
| if (stype != GF_STREAM_AUDIO) continue; | |
| break; | |
| case 4: //by text | |
| if (stype != GF_STREAM_VISUAL) continue; | |
| break; | |
| } | |
| if ((track_id->type>1) && track_id->ID_or_num && (track_id->ID_or_num<cur)) continue; | |
| cur++; | |
| if (!track_id->ID_or_num) fprintf(stderr, "- Track %d type: ", import->tk_info[i].track_num); | |
| else fprintf(stderr, "Track type: "); | |
| switch (stype) { | |
| case GF_STREAM_VISUAL: | |
| if (import->tk_info[i].media_subtype == GF_ISOM_MEDIA_AUXV) fprintf(stderr, "Auxiliary Video"); | |
| else if (import->tk_info[i].media_subtype == GF_ISOM_MEDIA_PICT) fprintf(stderr, "Picture Sequence"); | |
| else fprintf(stderr, "Video"); | |
| if (import->tk_info[i].video_info.temporal_enhancement) fprintf(stderr, " Temporal Enhancement"); | |
| break; | |
| case GF_STREAM_AUDIO: | |
| fprintf(stderr, "Audio"); | |
| break; | |
| case GF_STREAM_TEXT: | |
| fprintf(stderr, "Text"); | |
| break; | |
| case GF_STREAM_SCENE: | |
| fprintf(stderr, "Scene"); | |
| break; | |
| case GF_STREAM_OD: | |
| fprintf(stderr, "OD"); | |
| break; | |
| case GF_STREAM_METADATA: | |
| fprintf(stderr, "Metadata"); | |
| break; | |
| default: | |
| fprintf(stderr, "Other (%s)", gf_4cc_to_str(import->tk_info[i].stream_type)); | |
| break; | |
| } | |
| if (import->tk_info[i].codecid) fprintf(stderr, " - Codec %s", gf_codecid_name(import->tk_info[i].codecid)); | |
| if (import->tk_info[i].lang) fprintf(stderr, " lang %s", gf_4cc_to_str(import->tk_info[i].lang)); | |
| if (import->tk_info[i].mpeg4_es_id) fprintf(stderr, " MPEG-4 ESID %d", import->tk_info[i].mpeg4_es_id); | |
| if (import->tk_info[i].prog_num) { | |
| if (!import->nb_progs) { | |
| fprintf(stderr, " Program %d", import->tk_info[i].prog_num); | |
| } else { | |
| u32 j; | |
| for (j=0; j<MIN(import->nb_progs, GF_IMPORT_MAX_TRACKS); j++) { | |
| if (import->tk_info[i].prog_num != import->pg_info[j].number) continue; | |
| fprintf(stderr, " Program %s", import->pg_info[j].name); | |
| break; | |
| } | |
| } | |
| } | |
| fprintf(stderr, "\n"); | |
| if (import->tk_info[i].video_info.width && import->tk_info[i].video_info.height | |
| ) { | |
| fprintf(stderr, "\tSize %dx%d", import->tk_info[i].video_info.width, import->tk_info[i].video_info.height); | |
| if (import->tk_info[i].video_info.FPS) fprintf(stderr, " @ %g FPS", import->tk_info[i].video_info.FPS); | |
| if (import->tk_info[i].stream_type==GF_STREAM_VISUAL) { | |
| if (import->tk_info[i].video_info.par) fprintf(stderr, " PAR: %d:%d", import->tk_info[i].video_info.par >> 16, import->tk_info[i].video_info.par & 0xFFFF); | |
| } | |
| fprintf(stderr, "\n"); | |
| } | |
| else if ((import->tk_info[i].stream_type==GF_STREAM_AUDIO) && import->tk_info[i].audio_info.sample_rate) { | |
| fprintf(stderr, "\tSampleRate %d - %d channels\n", import->tk_info[i].audio_info.sample_rate, import->tk_info[i].audio_info.nb_channels); | |
| } | |
| if (track_id->ID_or_num || (track_id->type>1)) { | |
| found = 1; | |
| break; | |
| } | |
| } | |
| fprintf(stderr, "\n"); | |
| if (!found && track_id->ID_or_num) { | |
| M4_LOG(GF_LOG_ERROR, ("Cannot find track %u in file\n", track_id->ID_or_num)); | |
| gf_free(import); | |
| return GF_BAD_PARAM; | |
| } | |
| M4_LOG(GF_LOG_INFO, ("For more details, use `gpac -i %s inspect[:deep][:analyze=on|bs]`\n", gf_file_basename(inName))); | |
| gf_free(import); | |
| return GF_OK; | |
| } | |
| static GF_Err set_chapter_track(GF_ISOFile *file, u32 track, u32 chapter_ref_trak) | |
| { | |
| u64 ref_duration, chap_duration; | |
| Double scale; | |
| GF_Err e; | |
| e = gf_isom_set_track_reference(file, chapter_ref_trak, GF_ISOM_REF_CHAP, gf_isom_get_track_id(file, track) ); | |
| if (e) return e; | |
| e = gf_isom_set_track_enabled(file, track, GF_FALSE); | |
| if (e) return e; | |
| ref_duration = gf_isom_get_media_duration(file, chapter_ref_trak); | |
| chap_duration = gf_isom_get_media_duration(file, track); | |
| scale = (Double) (s64) gf_isom_get_media_timescale(file, track); | |
| scale /= gf_isom_get_media_timescale(file, chapter_ref_trak); | |
| ref_duration = (u64) (ref_duration * scale); | |
| if (chap_duration < ref_duration) { | |
| chap_duration -= gf_isom_get_sample_duration(file, track, gf_isom_get_sample_count(file, track)); | |
| chap_duration = ref_duration - chap_duration; | |
| e = gf_isom_set_last_sample_duration(file, track, (u32) chap_duration); | |
| if (e) return e; | |
| } | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| u8 NbBits; | |
| u32 switchGroupID, nb_crit, size, reserved; | |
| u8 priority; | |
| Bool discardable; | |
| GF_AudioChannelLayout layout; | |
| gf_isom_get_sample_size(file, track, 1); | |
| gf_isom_get_sample_dts(file, track, 1); | |
| gf_isom_get_sample_from_dts(file, track, 0); | |
| gf_isom_set_sample_padding(file, track, 0); | |
| gf_isom_has_padding_bits(file, track); | |
| gf_isom_get_sample_padding_bits(file, track, 1, &NbBits); | |
| gf_isom_keep_utc_times(file, 1); | |
| #ifndef GPAC_DISABLE_ISOM_FRAGMENTS | |
| gf_isom_set_single_moof_mode(file, GF_TRUE); | |
| gf_isom_reset_sample_count(NULL); | |
| gf_isom_set_traf_mss_timeext(NULL, 0, 0, 0); | |
| gf_isom_get_next_moof_number(NULL); | |
| gf_isom_set_fragment_reference_time(NULL, 0, 0, 0, 0); | |
| #endif | |
| //this one is not tested in master due to old-arch compat, to remove when we enable tests without old-arch | |
| gf_isom_get_audio_layout(file, track, 1, &layout); | |
| gf_isom_get_track_switch_parameter(file, track, 1, &switchGroupID, &nb_crit); | |
| gf_isom_sample_has_subsamples(file, track, 1, 0); | |
| gf_isom_sample_get_subsample(file, track, 1, 0, 1, &size, &priority, &reserved, &discardable); | |
| #ifndef GPAC_DISABLE_ISOM_HINTING | |
| gf_isom_hint_blank_data(NULL, 0, 0); | |
| gf_isom_hint_sample_description_data(NULL, 0, 0, 1, 0, 0, 0); | |
| gf_isom_get_payt_info(NULL, 0, 0, NULL); | |
| #endif | |
| gf_isom_estimate_size(file); | |
| } | |
| #endif | |
| return GF_OK; | |
| } | |
| GF_Err parse_fracs(char *str, GF_Fraction64 *f, GF_Fraction64 *dur) | |
| { | |
| GF_Err e = GF_OK; | |
| if (dur) { | |
| char *sep = strchr(str, '-'); | |
| if (sep) { | |
| sep[0] = 0; | |
| if (!gf_parse_lfrac(str, f)) e = GF_BAD_PARAM; | |
| if (!gf_parse_lfrac(sep+1, dur)) e = GF_BAD_PARAM; | |
| sep[0] = '-'; | |
| return e; | |
| } | |
| dur->num = 0; | |
| dur->den = 0; | |
| } | |
| if (!gf_parse_lfrac(str, f)) e = GF_BAD_PARAM; | |
| return e; | |
| } | |
| Bool scan_color(char *val, u32 *clr_prim, u32 *clr_tranf, u32 *clr_mx, Bool *clr_full_range) | |
| { | |
| char *sep = strchr(val, ','); | |
| if (!sep) return GF_FALSE; | |
| sep[0] = 0; | |
| *clr_prim = gf_cicp_parse_color_primaries(val); | |
| sep[0] = ','; | |
| if (*clr_prim == (u32) -1) return GF_FALSE; | |
| val = sep+1; | |
| sep = strchr(val, ','); | |
| if (!sep) return GF_FALSE; | |
| sep[0] = 0; | |
| *clr_tranf = gf_cicp_parse_color_transfer(val); | |
| sep[0] = ','; | |
| if (*clr_tranf == (u32) -1) return GF_FALSE; | |
| val = sep+1; | |
| sep = strchr(val, ','); | |
| if (sep) sep[0] = 0; | |
| *clr_mx = gf_cicp_parse_color_matrix(val); | |
| if (sep) sep[0] = ','; | |
| if (*clr_mx == (u32) -1) return GF_FALSE; | |
| if (!sep) return GF_TRUE; | |
| val = sep+1; | |
| if (!strcmp(val, "yes") || !strcmp(val, "on")) { | |
| *clr_full_range = GF_TRUE; | |
| return GF_TRUE; | |
| } | |
| if (!strcmp(val, "no") || !strcmp(val, "off")) { | |
| *clr_full_range = GF_FALSE; | |
| return GF_TRUE; | |
| } | |
| if (sscanf(val, "%d", (s32*) clr_full_range) == 1) | |
| return GF_TRUE; | |
| return GF_FALSE; | |
| } | |
| #include <gpac/internal/media_dev.h> | |
| static GF_Err set_dv_profile(GF_ISOFile *dest, u32 track, char *dv_profile_str, u32 dv_md_compression, u16 dv_flags) | |
| { | |
| GF_Err e; | |
| Bool remove=GF_FALSE; | |
| Bool force_dv=GF_FALSE; | |
| u32 dv_profile = 0; | |
| u32 dv_compat_id=0; | |
| u32 dv_version_major = 1; | |
| s32 flag_idx; | |
| char *sep = strchr(dv_profile_str, '.'); | |
| if (sep) { | |
| sep[0] = 0; | |
| if (!strcmp(sep+1, "none")) dv_compat_id=0; | |
| else if (!strcmp(sep+1, "hdr10")) dv_compat_id=1; | |
| else if (!strcmp(sep+1, "bt709")) dv_compat_id=2; | |
| else if (!strcmp(sep+1, "hlg709")) dv_compat_id=3; | |
| else if (!strcmp(sep+1, "hlg2100")) dv_compat_id=4; | |
| else if (!strcmp(sep+1, "bt2020")) dv_compat_id=5; | |
| else if (!strcmp(sep+1, "brd")) dv_compat_id=6; | |
| else if ((sep[1]>='0') && (sep[1]<='9')) dv_compat_id = parse_u32(sep+1, "DV compatibility mode"); | |
| else { | |
| M4_LOG(GF_LOG_WARNING, ("DV compatibility mode %s not recognized, using none\n", sep+1)); | |
| } | |
| } | |
| if (dv_profile_str[0]=='f') { | |
| force_dv = GF_TRUE; | |
| dv_profile_str++; | |
| } | |
| if (!strcmp(dv_profile_str, "none")) { | |
| remove = GF_TRUE; | |
| } else { | |
| dv_profile = parse_u32(dv_profile_str, "DV profile"); | |
| if (dv_profile==8) { | |
| if ((dv_compat_id!=1) && (dv_compat_id!=2) && (dv_compat_id!=4)) { | |
| M4_LOG(GF_LOG_ERROR, ("DV profile 8 must indicate a compatibility mode `hdr10`, `bt709` or `hlg2100`\n")); | |
| return GF_BAD_PARAM; | |
| } | |
| } else if (dv_profile==20) { | |
| // Dolby Vision Streams Within the ISO Base Media File Format specification version 2.6 section 2.2 | |
| dv_version_major = 3; | |
| } | |
| } | |
| GF_DOVIDecoderConfigurationRecord *dovi = gf_isom_dovi_config_get(dest, track, 1); | |
| if (dovi) { | |
| dovi->dv_version_major = dv_version_major; | |
| dovi->dv_profile = dv_profile; | |
| dovi->dv_bl_signal_compatibility_id = dv_compat_id; | |
| dovi->force_dv = force_dv; | |
| dovi->dv_md_compression = dv_md_compression; | |
| dovi->rpu_present_flag = 1; | |
| for (flag_idx = 9; flag_idx >= 0; flag_idx--) { | |
| dovi->dv_feature_flags[flag_idx] = dv_flags & 0x1; | |
| dv_flags = dv_flags >> 1; | |
| } | |
| e = gf_isom_set_dolby_vision_profile(dest, track, 1, remove ? NULL : dovi); | |
| gf_odf_dovi_cfg_del(dovi); | |
| return e; | |
| } | |
| if (remove) return GF_OK; | |
| u32 nb_samples = gf_isom_get_sample_count(dest, track); | |
| if (!nb_samples) { | |
| M4_LOG(GF_LOG_ERROR, ("No DV config in file and no samples, cannot guess DV config\n")); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| GF_DOVIDecoderConfigurationRecord _dovi; | |
| memset(&_dovi, 0, sizeof(GF_DOVIDecoderConfigurationRecord)); | |
| _dovi.dv_version_major = dv_version_major; | |
| _dovi.dv_version_minor = 0; | |
| _dovi.dv_profile = dv_profile; | |
| _dovi.dv_bl_signal_compatibility_id = dv_compat_id; | |
| _dovi.force_dv = force_dv; | |
| _dovi.dv_md_compression = dv_md_compression; | |
| for (flag_idx = 9; flag_idx >= 0; flag_idx--) { | |
| _dovi.dv_feature_flags[flag_idx] = dv_flags & 0x1; | |
| dv_flags = dv_flags >> 1; | |
| } | |
| // This flag must always be set to 1 given the deprecation of certain profiles | |
| // Dolby Vision Streams Within the ISO Base Media File Format specification version 2.6 section 2.2 | |
| _dovi.rpu_present_flag = 1; | |
| u32 w, h; | |
| u32 codec_id = 0; | |
| u32 probe_frames=50; | |
| e = gf_isom_get_visual_info(dest, track, 1, &w, &h); | |
| if (e) return e; | |
| //no DV profile present in file, we need to guess | |
| GF_AVCConfig *avcc = gf_isom_avc_config_get(dest, track, 1); | |
| GF_HEVCConfig *hvcc = gf_isom_hevc_config_get(dest, track, 1); | |
| // GF_VVCConfig *vvcc = gf_isom_vvc_config_get(dest, track, 1); | |
| GF_AV1Config *av1c = gf_isom_av1_config_get(dest, track, 1); | |
| u32 nalu_length = 0; | |
| if (avcc) { | |
| nalu_length = avcc->nal_unit_size; | |
| codec_id = GF_CODECID_AVC; | |
| gf_odf_avc_cfg_del(avcc); | |
| } | |
| else if (hvcc) { | |
| codec_id = GF_CODECID_HEVC; | |
| nalu_length = hvcc->nal_unit_size; | |
| gf_odf_hevc_cfg_del(hvcc); | |
| } | |
| /* else if (vvcc) { | |
| codec_id = GF_CODECID_VVC; | |
| nalu_length = 4; | |
| probe_frames = 0; //parsing not supported for VVC | |
| gf_odf_vvc_cfg_del(vvcc); | |
| } | |
| */ else if (av1c) { | |
| codec_id = GF_CODECID_AV1; | |
| gf_odf_av1_cfg_del(av1c); | |
| } else { | |
| M4_LOG(GF_LOG_WARNING, ("DV profile can only be set on AVC, HEVC and AV1 tracks\n")); | |
| return GF_BAD_PARAM; | |
| } | |
| //inspect at most first 50 samples for NAL based | |
| u32 i; | |
| for (i=0; i<probe_frames; i++) { | |
| u32 stsd_idx; | |
| GF_BitStream *bs; | |
| GF_ISOSample *samp = gf_isom_get_sample(dest, track, i+1, &stsd_idx); | |
| if (!samp) break; | |
| bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ); | |
| while (gf_bs_available(bs)) { | |
| if (codec_id==GF_CODECID_AVC) { | |
| u32 nal_size = gf_bs_read_int(bs, nalu_length*8); | |
| u32 nal_type = gf_bs_read_u8(bs); | |
| nal_type = nal_type & 0x1F; | |
| nal_size--; | |
| if (nal_type == GF_AVC_NALU_DV_EL) _dovi.el_present_flag = 1; | |
| else if (nal_type <= GF_AVC_NALU_IDR_SLICE) _dovi.bl_present_flag = 1; | |
| gf_bs_skip_bytes(bs, nal_size-1); | |
| } else if (codec_id==GF_CODECID_HEVC) { | |
| u32 nal_size = gf_bs_read_int(bs, nalu_length*8); | |
| gf_bs_read_int(bs, 1); | |
| u32 nal_type = gf_bs_read_int(bs, 6); | |
| gf_bs_read_int(bs, 1); | |
| nal_size--; | |
| if (nal_type == GF_HEVC_NALU_DV_EL) _dovi.el_present_flag = 1; | |
| else if (nal_type <= GF_HEVC_NALU_SLICE_CRA) _dovi.bl_present_flag = 1; | |
| gf_bs_skip_bytes(bs, nal_size-1); | |
| } | |
| else if (codec_id==GF_CODECID_AV1) { | |
| #ifndef GPAC_DISABLE_AV_PARSERS | |
| u64 obu_size, obu_start = (u32) gf_bs_get_position(bs); | |
| u32 obu_hdr_size; | |
| ObuType obu_type; | |
| Bool obu_extension_flag, obu_has_size_field; | |
| u8 temporal_id, spatial_id; | |
| gf_av1_parse_obu_header(bs, &obu_type, &obu_extension_flag, &obu_has_size_field, &temporal_id, &spatial_id); | |
| obu_hdr_size = (u32) (gf_bs_get_position(bs) - obu_start); | |
| if (obu_has_size_field) { | |
| obu_size = (u32)gf_av1_leb128_read(bs, NULL); | |
| obu_hdr_size = (u32) (gf_bs_get_position(bs) - obu_start); | |
| } else { | |
| obu_size = samp->dataLength - (u32) gf_bs_get_position(bs); | |
| } | |
| obu_size += obu_hdr_size; | |
| if (obu_type<=OBU_TILE_LIST) { | |
| _dovi.bl_present_flag = 1; | |
| } | |
| gf_bs_seek(bs, obu_start+obu_size); | |
| #else | |
| return GF_NOT_SUPPORTED; | |
| #endif | |
| } | |
| } | |
| gf_bs_del(bs); | |
| gf_isom_sample_del(&samp); | |
| } | |
| //get dur and timescale to compute fps | |
| //we don't divide mdur by nb_samples but rather multiple timescale with nb_samples | |
| //this avoids rounding errors with non FPS-aligned input timescale (eg mkv using 1000 regardless of FPS) | |
| u64 mdur; | |
| u64 timescale = gf_isom_get_media_timescale(dest, track); | |
| mdur = gf_isom_get_media_duration(dest, track); | |
| if (timescale % 1000 == 0) { | |
| timescale/=1000; | |
| mdur/=1000; | |
| } | |
| else if (timescale % 1001 == 0) { | |
| timescale/=1001; | |
| mdur/=1001; | |
| } | |
| timescale *= nb_samples; | |
| _dovi.dv_level = gf_dolby_vision_level(w, h, timescale, mdur, codec_id); | |
| return gf_isom_set_dolby_vision_profile(dest, track, 1, &_dovi); | |
| } | |
| GF_Err apply_edits(GF_ISOFile *dest, u32 track, char *edits) | |
| { | |
| u32 movie_ts = gf_isom_get_timescale(dest); | |
| u32 media_ts = gf_isom_get_media_timescale(dest, track); | |
| u64 media_dur = gf_isom_get_media_duration(dest, track); | |
| if (!movie_ts) movie_ts = 600; | |
| if (!media_ts) media_ts = movie_ts; | |
| while (edits) { | |
| GF_Err e; | |
| char c=0; | |
| char *sep = strchr(edits+1, 'r'); | |
| if (!sep) sep = strchr(edits+1, 'e'); | |
| if (sep) { | |
| c = sep[0]; | |
| sep[0] = 0; | |
| } | |
| //remove all edits | |
| if (edits[0] == 'r') { | |
| e = gf_isom_remove_edits(dest, track); | |
| if (e) goto error; | |
| } | |
| else if (edits[0]=='e') { | |
| u64 movie_t, edur; | |
| GF_Fraction64 movie_time, media_time, media_rate, edit_dur; | |
| char *mtime_sep; | |
| edits+=1; | |
| mtime_sep = strchr(edits, ','); | |
| movie_time.den = media_time.den = media_rate.den = 0; | |
| if (!mtime_sep) { | |
| e = parse_fracs(edits, &movie_time, &edit_dur); | |
| if (e) goto error; | |
| } | |
| else { | |
| mtime_sep[0] = 0; | |
| e = parse_fracs(edits, &movie_time, &edit_dur); | |
| if (e) goto error; | |
| mtime_sep[0] = ','; | |
| edits = mtime_sep+1; | |
| mtime_sep = strchr(edits, ','); | |
| if (!mtime_sep) { | |
| e = parse_fracs(edits, &media_time, NULL); | |
| if (e) goto error; | |
| media_rate.num = media_rate.den = 1; | |
| } else { | |
| mtime_sep[0] = 0; | |
| e = parse_fracs(edits, &media_time, NULL); | |
| if (e) goto error; | |
| mtime_sep[0] = ','; | |
| e = parse_fracs(mtime_sep+1, &media_rate, NULL); | |
| if (e) goto error; | |
| } | |
| } | |
| if (!movie_time.den || (movie_time.num<0)) { | |
| e = GF_BAD_PARAM; | |
| fprintf(stderr, "Wrong edit format %s, movie time must be valid and >= 0\n", edits); | |
| goto error; | |
| } | |
| movie_t = movie_time.num * movie_ts / movie_time.den; | |
| if (!edit_dur.den || !edit_dur.num) { | |
| edur = gf_timestamp_rescale(media_dur, media_ts, movie_ts); | |
| } else { | |
| edur = gf_timestamp_rescale(edit_dur.num, edit_dur.den, movie_ts); | |
| } | |
| //empty edit | |
| if (!media_time.den) { | |
| e = gf_isom_set_edit(dest, track, movie_t, edur, 0, GF_ISOM_EDIT_EMPTY); | |
| } else { | |
| //adjust media duration based on media time | |
| u64 media_t = media_time.num * media_ts / media_time.den; | |
| if (media_dur<media_t) { | |
| fprintf(stderr, "Media time "LLU" larger than duration "LLU", using last media frame\n", media_t, media_dur); | |
| media_t = media_dur; | |
| media_dur = 0; | |
| } else { | |
| media_dur -= media_t; | |
| } | |
| u32 rate = 0; | |
| if (media_rate.den) { | |
| u64 frac; | |
| rate = (u32) ( media_rate.num / media_rate.den ); | |
| frac = media_rate.num - rate*media_rate.den; | |
| frac *= 0xFFFF; | |
| frac /= media_rate.den; | |
| rate = (rate<<16) | (u16) frac; | |
| //adjust media dur if rate > 1 | |
| if (media_rate.num > (s64) media_rate.den) { | |
| media_dur = media_dur*media_rate.den / media_rate.num; | |
| } | |
| } | |
| //clamp edit dur to available media time | |
| if (gf_timestamp_greater(edur, movie_ts, media_dur, media_ts)) { | |
| u64 ndur = gf_timestamp_rescale(media_dur, media_ts, movie_ts); | |
| fprintf(stderr, "Clamping edit duration from "LLU"/%u to "LLU"/%u due to available media duration "LLU"/%u\n", edur, movie_ts, ndur, movie_ts, media_dur, media_ts); | |
| edur = ndur; | |
| } | |
| e = gf_isom_set_edit_with_rate(dest, track, movie_t, edur, media_t, rate); | |
| } | |
| if (e==GF_EOS) { | |
| fprintf(stderr, "Inserted empty edit before edit at start time "LLD"/"LLU"\n", movie_time.num, movie_time.den); | |
| e = GF_OK; | |
| } | |
| if (e) goto error; | |
| } else { | |
| e = GF_BAD_PARAM; | |
| fprintf(stderr, "Wrong edit format %s, should start with 'e' or 'r'\n", edits); | |
| goto error; | |
| } | |
| error: | |
| if (sep) sep[0] = c; | |
| if (e) return e; | |
| if (!sep) break; | |
| edits = sep; | |
| } | |
| return GF_OK; | |
| } | |
| static const char *videofmt_names[] = { "component", "pal", "ntsc", "secam", "mac", "undef"}; | |
| static GF_Err apply_timestamps(GF_ISOFile *file, GF_ISOTrackID trackID, const char *timestamp_source); | |
| GF_Err import_file(GF_ISOFile *dest, char *inName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, GF_FilterSession *fsess, char **mux_args_if_first_pass, char **mux_sid_if_first_pass, u32 tk_idx) | |
| { | |
| u32 track_id, i, j, timescale, track, stype, profile, compat, level, new_timescale, rescale_num, rescale_den, svc_mode, txt_flags, split_tile_mode, temporal_mode, nb_tracks; | |
| s32 par_d, par_n, prog_id, force_rate, moov_timescale; | |
| s32 tw, th, tx, ty, tz, txtw, txth, txtx, txty; | |
| Bool do_audio, do_video, do_auxv,do_pict, do_all, track_layout, text_layout, chap_ref, is_chap, is_chap_file, keep_handler, rap_only, refs_only, force_par, rewrite_bs; | |
| u32 group, handler, rvc_predefined, check_track_for_svc, check_track_for_lhvc, check_track_for_hevc, do_disable; | |
| const char *szLan; | |
| GF_Err e = GF_OK; | |
| GF_Fraction delay; | |
| u32 tmcd_track = 0, neg_ctts_mode=0; | |
| Bool keep_audelim = GF_FALSE; | |
| u32 print_stats_graph=fs_dump_flags; | |
| char *ext, *final_name=NULL, *handler_name, *rvc_config, *chapter_name; | |
| GF_List *kinds = NULL; | |
| GF_TextFlagsMode txt_mode = GF_ISOM_TEXT_FLAGS_OVERWRITE; | |
| u8 max_layer_id_plus_one, max_temporal_id_plus_one; | |
| u32 clap_wn, clap_wd, clap_hn, clap_hd, clap_hon, clap_hod, clap_von, clap_vod; | |
| Bool has_clap=GF_FALSE; | |
| Bool use_stz2=GF_FALSE; | |
| Bool has_mx=GF_FALSE; | |
| s32 mx[9]; | |
| u32 bitdepth=0; | |
| char dv_profile[100]; /*Dolby Vision*/ | |
| u32 dv_md_compression; /*Dolby Vision*/ | |
| u16 dv_flags=0; /*Dolby Vision*/ | |
| u32 clr_type=0; | |
| u32 clr_prim; | |
| u32 clr_tranf; | |
| u32 clr_mx; | |
| Bool rescale_override=GF_FALSE; | |
| Bool clr_full_range=GF_FALSE; | |
| Bool fmt_ok = GF_TRUE; | |
| u32 icc_size=0, track_flags=0; | |
| u8 *icc_data = NULL; | |
| u32 tc_fps_num=0, tc_fps_den=0, tc_h=0, tc_m=0, tc_s=0, tc_f=0, tc_frames_per_tick=0; | |
| Bool tc_force_counter=GF_FALSE; | |
| Bool tc_drop_frame = GF_FALSE; | |
| char *ext_start; | |
| u32 xps_inband=0; | |
| u64 source_magic=0; | |
| char *opt_src = NULL; | |
| char *opt_dst = NULL; | |
| char *fchain = NULL; | |
| char *edits = NULL; | |
| const char *fail_msg = NULL; | |
| char *hdr_file=NULL; | |
| char *first_ext=NULL; | |
| char *timestamp_source=NULL; | |
| Bool set_ccst=GF_FALSE; | |
| Bool has_last_sample_dur=GF_FALSE; | |
| u32 fake_import = 0; | |
| GF_Fraction last_sample_dur = {0,0}; | |
| s32 fullrange, videofmt, colorprim, colortfc, colormx; | |
| clap_wn = clap_wd = clap_hn = clap_hd = clap_hon = clap_hod = clap_von = clap_vod = 0; | |
| GF_ISOMTrackFlagOp track_flags_mode=0; | |
| u32 roll_change=0; | |
| s32 roll = 0; | |
| Bool src_is_isom = GF_FALSE; | |
| s32 dlb_mode = -2; | |
| u32 tkgp_type=0; | |
| s32 tkgp_id=0; | |
| Bool tkgp_add = GF_TRUE; | |
| u32 is_extk = 0; | |
| u32 set_tk_idx=0; | |
| u32 *reorder_tk_ids = NULL; | |
| u32 reorder_tk_ids_count = 0; | |
| Bool has_non_extk_opt=GF_FALSE; | |
| GF_MediaImporter *import = NULL; | |
| dv_profile[0] = 0; | |
| dv_md_compression = 0; | |
| rvc_predefined = 0; | |
| chapter_name = NULL; | |
| new_timescale = 1; | |
| moov_timescale = 0; | |
| rescale_num = rescale_den = 0; | |
| text_layout = 0; | |
| /*0: merge all | |
| 1: split base and all SVC in two tracks | |
| 2: split all base and SVC layers in dedicated tracks | |
| */ | |
| svc_mode = 0; | |
| if (import_flags==0xFFFFFFFF) { | |
| import_flags = 0; | |
| fake_import = 1; | |
| } | |
| import = gf_malloc(sizeof(GF_MediaImporter)); | |
| if (!import) { | |
| e = GF_OUT_OF_MEM; | |
| goto exit; | |
| } | |
| memset(import, 0, sizeof(GF_MediaImporter)); | |
| final_name = gf_strdup(inName); | |
| #ifdef WIN32 | |
| /*dirty hack for msys&mingw: when we use import options, the ':' separator used prevents msys from translating the path | |
| we do this for regular cases where the path starts with the drive letter. If the path start with anything else (/home , /opt, ...) we're screwed :( */ | |
| if ( (final_name[0]=='/') && (final_name[2]=='/')) { | |
| final_name[0] = final_name[1]; | |
| final_name[1] = ':'; | |
| } | |
| #endif | |
| is_chap_file = 0; | |
| handler = 0; | |
| do_disable = 0; | |
| chap_ref = 0; | |
| is_chap = 0; | |
| kinds = gf_list_new(); | |
| track_layout = 0; | |
| szLan = NULL; | |
| delay.num = delay.den = 0; | |
| group = 0; | |
| stype = 0; | |
| profile = compat = level = 0; | |
| fullrange = videofmt = colorprim = colortfc = colormx = -1; | |
| split_tile_mode = 0; | |
| temporal_mode = 0; | |
| rap_only = 0; | |
| refs_only = 0; | |
| txt_flags = 0; | |
| max_layer_id_plus_one = max_temporal_id_plus_one = 0; | |
| force_rate = -1; | |
| tw = th = tx = ty = tz = txtw = txth = txtx = txty = 0; | |
| par_d = par_n = -1; | |
| force_par = rewrite_bs = GF_FALSE; | |
| ext_start = gf_file_ext_start(final_name); | |
| ext = strrchr(ext_start ? ext_start : final_name, '#'); | |
| if (!ext) ext = gf_url_colon_suffix(final_name, '='); | |
| char c_sep = ext ? ext[0] : 0; | |
| if (ext) ext[0] = 0; | |
| char *frag = strrchr(final_name, '#'); | |
| if (frag) frag[0] = 0; | |
| if (!strlen(final_name) || !strcmp(final_name, "self")) { | |
| fake_import = 2; | |
| src_is_isom = GF_TRUE; | |
| } | |
| else if (gf_isom_probe_file(final_name)) | |
| src_is_isom = GF_TRUE; | |
| if (frag) frag[0] = '#'; | |
| if (ext) ext[0] = c_sep; | |
| reparse_opts: | |
| ext = gf_url_colon_suffix(final_name, '='); | |
| #define GOTO_EXIT(_msg) if (e) { fail_msg = _msg; goto exit; } | |
| #define CHECK_FAKEIMPORT(_opt) if (fake_import) { M4_LOG(GF_LOG_ERROR, ("Option %s not available for cat or self-reference import\n", _opt)); e = GF_BAD_PARAM; goto exit; } | |
| #define CHECK_FAKEIMPORT_2(_opt) if (fake_import==1) { M4_LOG(GF_LOG_ERROR, ("Option %s not available for cat or self-reference import\n", _opt)); e = GF_BAD_PARAM; goto exit; } | |
| handler_name = NULL; | |
| rvc_config = NULL; | |
| while (ext) { | |
| char *ext2 = gf_url_colon_suffix(ext+1, '='); | |
| if (ext2) ext2[0] = 0; | |
| /*all extensions for track-based importing*/ | |
| if (!strnicmp(ext+1, "dur=", 4)) { | |
| CHECK_FAKEIMPORT("dur") | |
| if (strchr(ext, '-')) { | |
| import->duration.num = parse_s32(ext+5, "dur"); | |
| import->duration.den = 1; | |
| } else { | |
| gf_parse_frac(ext+5, &import->duration); | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "start=", 6)) { | |
| CHECK_FAKEIMPORT("start") | |
| import->start_time = atof(ext+7); | |
| } | |
| else if (!strnicmp(ext+1, "lang=", 5)) { | |
| /* prevent leak if param is set twice */ | |
| if (szLan) | |
| gf_free((char*) szLan); | |
| szLan = gf_strdup(ext+6); | |
| } | |
| else if (!strnicmp(ext+1, "delay=", 6)) { | |
| if (sscanf(ext+7, "%d/%u", &delay.num, &delay.den)!=2) { | |
| delay.num = parse_s32(ext+7, "delay"); | |
| delay.den = 1000; //in ms | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "par=", 4)) { | |
| if (!stricmp(ext + 5, "none")) { | |
| par_n = par_d = 0; | |
| } else if (!stricmp(ext + 5, "auto")) { | |
| force_par = GF_TRUE; | |
| } else if (!stricmp(ext + 5, "force")) { | |
| par_n = par_d = 1; | |
| force_par = GF_TRUE; | |
| } else { | |
| if (ext2) { | |
| ext2[0] = ':'; | |
| ext2 = strchr(ext2+1, ':'); | |
| if (ext2) ext2[0] = 0; | |
| } | |
| if (ext[5]=='w') { | |
| rewrite_bs = GF_TRUE; | |
| if (sscanf(ext+6, "%d:%d", &par_n, &par_d)!=2) { | |
| M4_LOG(GF_LOG_ERROR, ("Unrecognized syntax for par=, expecting N:D got %s\n", ext+5)); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| } else { | |
| if (sscanf(ext+5, "%d:%d", &par_n, &par_d) != 2) { | |
| M4_LOG(GF_LOG_ERROR, ("Unrecognized syntax for par=, expecting N:D got %s\n", ext+5)); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| } | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "clap=", 5)) { | |
| if (!stricmp(ext+6, "none")) { | |
| has_clap=GF_TRUE; | |
| } else { | |
| if (sscanf(ext+6, "%d,%d,%d,%d,%d,%d,%d,%d", &clap_wn, &clap_wd, &clap_hn, &clap_hd, &clap_hon, &clap_hod, &clap_von, &clap_vod)==8) { | |
| has_clap=GF_TRUE; | |
| } | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "mx=", 3)) { | |
| if (strstr(ext+4, "0x")) { | |
| if (sscanf(ext+4, "0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%d", &mx[0], &mx[1], &mx[2], &mx[3], &mx[4], &mx[5], &mx[6], &mx[7], &mx[8])==9) { | |
| has_mx=GF_TRUE; | |
| } | |
| } else if (sscanf(ext+4, "%d,%d,%d,%d,%d,%d,%d,%d,%d", &mx[0], &mx[1], &mx[2], &mx[3], &mx[4], &mx[5], &mx[6], &mx[7], &mx[8])==9) { | |
| has_mx=GF_TRUE; | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "name=", 5)) { | |
| handler_name = gf_strdup(ext+6); | |
| } | |
| else if (!strnicmp(ext+1, "ext=", 4)) { | |
| CHECK_FAKEIMPORT("ext") | |
| /*extensions begin with '.'*/ | |
| if (*(ext+5) == '.') | |
| import->force_ext = gf_strdup(ext+5); | |
| else { | |
| import->force_ext = gf_strdup("."); | |
| gf_dynstrcat(&import->force_ext, ext+5, NULL); | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "hdlr=", 5)) handler = GF_4CC(ext[6], ext[7], ext[8], ext[9]); | |
| else if (!strnicmp(ext+1, "stype=", 6)) stype = GF_4CC(ext[7], ext[8], ext[9], ext[10]); | |
| else if (!strnicmp(ext+1, "tkhd", 4)) { | |
| char *flags = ext+6; | |
| if (flags[0]=='+') { track_flags_mode = GF_ISOM_TKFLAGS_ADD; flags += 1; } | |
| else if (flags[0]=='-') { track_flags_mode = GF_ISOM_TKFLAGS_REM; flags += 1; } | |
| else track_flags_mode = GF_ISOM_TKFLAGS_SET; | |
| if (strstr(flags, "enable")) track_flags |= GF_ISOM_TK_ENABLED; | |
| if (strstr(flags, "movie")) track_flags |= GF_ISOM_TK_IN_MOVIE; | |
| if (strstr(flags, "preview")) track_flags |= GF_ISOM_TK_IN_PREVIEW; | |
| if (strstr(flags, "size_ar")) track_flags |= GF_ISOM_TK_SIZE_IS_AR; | |
| if (!track_flags) { | |
| if (!strnicmp(flags, "0x", 2)) flags += 2; | |
| sscanf(flags, "%X", &track_flags); | |
| } | |
| } else if (!strnicmp(ext+1, "disable", 7)) { | |
| do_disable = !stricmp(ext+1, "disable=no") ? 2 : 1; | |
| } | |
| else if (!strnicmp(ext+1, "group=", 6)) { | |
| group = parse_u32(ext+7, "group"); | |
| if (!group) group = gf_isom_get_next_alternate_group_id(dest); | |
| } | |
| else if (!strnicmp(ext+1, "fps=", 4)) { | |
| u32 ticks, dts_inc; | |
| if (!strcmp(ext+5, "auto")) { | |
| M4_LOG(GF_LOG_ERROR, ("Warning, fps=auto option is deprecated\n")); | |
| } else if ((sscanf(ext+5, "%u-%u", &ticks, &dts_inc) == 2) || (sscanf(ext+5, "%u/%u", &ticks, &dts_inc) == 2)) { | |
| if (!dts_inc) dts_inc=1; | |
| force_fps.num = ticks; | |
| force_fps.den = dts_inc; | |
| } else { | |
| if (gf_sys_old_arch_compat()) { | |
| force_fps.den = 1000; | |
| force_fps.num = (u32) (atof(ext+5) * force_fps.den); | |
| } else { | |
| gf_parse_frac(ext+5, &force_fps); | |
| } | |
| } | |
| } | |
| else if (!stricmp(ext+1, "rap")) rap_only = 1; | |
| else if (!stricmp(ext+1, "refs")) refs_only = 1; | |
| else if (!stricmp(ext+1, "trailing")) { CHECK_FAKEIMPORT("trailing") import_flags |= GF_IMPORT_KEEP_TRAILING; } | |
| else if (!strnicmp(ext+1, "agg=", 4)) { CHECK_FAKEIMPORT("agg") frames_per_sample = parse_u32(ext+5, "agg"); } | |
| else if (!stricmp(ext+1, "dref")) { CHECK_FAKEIMPORT("dref") import_flags |= GF_IMPORT_USE_DATAREF; } | |
| else if (!stricmp(ext+1, "keep_refs")) { CHECK_FAKEIMPORT("keep_refs") import_flags |= GF_IMPORT_KEEP_REFS; } | |
| else if (!stricmp(ext+1, "nodrop")) { CHECK_FAKEIMPORT("nodrop") import_flags |= GF_IMPORT_NO_FRAME_DROP; } | |
| else if (!stricmp(ext+1, "packed")) { CHECK_FAKEIMPORT("packed") import_flags |= GF_IMPORT_FORCE_PACKED; } | |
| else if (!stricmp(ext+1, "sbr")) { CHECK_FAKEIMPORT("sbr") import_flags |= GF_IMPORT_SBR_IMPLICIT; } | |
| else if (!stricmp(ext+1, "sbrx")) { CHECK_FAKEIMPORT("sbrx") import_flags |= GF_IMPORT_SBR_EXPLICIT; } | |
| else if (!stricmp(ext+1, "ovsbr")) { CHECK_FAKEIMPORT("ovsbr") import_flags |= GF_IMPORT_OVSBR; } | |
| else if (!stricmp(ext+1, "ps")) { CHECK_FAKEIMPORT("ps") import_flags |= GF_IMPORT_PS_IMPLICIT; } | |
| else if (!stricmp(ext+1, "psx")) { CHECK_FAKEIMPORT("psx") import_flags |= GF_IMPORT_PS_EXPLICIT; } | |
| else if (!stricmp(ext+1, "mpeg4")) { CHECK_FAKEIMPORT("mpeg4") import_flags |= GF_IMPORT_FORCE_MPEG4; } | |
| else if (!stricmp(ext+1, "nosei")) { CHECK_FAKEIMPORT("nosei") import_flags |= GF_IMPORT_NO_SEI; } | |
| else if (!stricmp(ext+1, "svc") || !stricmp(ext+1, "lhvc") ) { CHECK_FAKEIMPORT("svc/lhvc") import_flags |= GF_IMPORT_SVC_EXPLICIT; } | |
| else if (!stricmp(ext+1, "nosvc") || !stricmp(ext+1, "nolhvc")) { CHECK_FAKEIMPORT("nosvc/nolhvc") import_flags |= GF_IMPORT_SVC_NONE; } | |
| else if (!stricmp(ext+1, "extk")) { | |
| if (!src_is_isom) { | |
| M4_LOG(GF_LOG_ERROR, ("extk option can only be used with ISOBMF/QT files\n")); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| if (!is_extk) is_extk = 1; | |
| } | |
| /*split SVC layers*/ | |
| else if (!strnicmp(ext+1, "svcmode=", 8) || !strnicmp(ext+1, "lhvcmode=", 9)) { | |
| char *mode = ext+9; | |
| CHECK_FAKEIMPORT_2("svcmode/lhvcmode") | |
| if (mode[0]=='=') mode = ext+10; | |
| if (!stricmp(mode, "splitnox")) | |
| svc_mode = 3; | |
| else if (!stricmp(mode, "splitnoxib")) | |
| svc_mode = 4; | |
| else if (!stricmp(mode, "splitall") || !stricmp(mode, "split")) | |
| svc_mode = 2; | |
| else if (!stricmp(mode, "splitbase")) | |
| svc_mode = 1; | |
| else if (!stricmp(mode, "merged") || !stricmp(mode, "merge")) | |
| svc_mode = 0; | |
| } | |
| /*split SHVC temporal sublayers*/ | |
| else if (!strnicmp(ext+1, "temporal=", 9)) { | |
| char *mode = ext+10; | |
| CHECK_FAKEIMPORT_2("svcmode/lhvcmode") | |
| if (!stricmp(mode, "split")) | |
| temporal_mode = 2; | |
| else if (!stricmp(mode, "splitnox")) | |
| temporal_mode = 3; | |
| else if (!stricmp(mode, "splitbase")) | |
| temporal_mode = 1; | |
| else { | |
| M4_LOG(GF_LOG_ERROR, ("Unrecognized temporal mode %s, ignoring\n", mode)); | |
| temporal_mode = 0; | |
| } | |
| } | |
| else if (!stricmp(ext+1, "subsamples")) { CHECK_FAKEIMPORT("subsamples") import_flags |= GF_IMPORT_SET_SUBSAMPLES; } | |
| else if (!stricmp(ext+1, "deps")) { CHECK_FAKEIMPORT("deps") import_flags |= GF_IMPORT_SAMPLE_DEPS; } | |
| else if (!stricmp(ext+1, "ccst")) { CHECK_FAKEIMPORT("ccst") set_ccst = GF_TRUE; } | |
| else if (!stricmp(ext+1, "alpha")) { CHECK_FAKEIMPORT("alpha") import->is_alpha = GF_TRUE; } | |
| else if (!stricmp(ext+1, "forcesync")) { CHECK_FAKEIMPORT("forcesync") import_flags |= GF_IMPORT_FORCE_SYNC; } | |
| else if (!stricmp(ext+1, "xps_inband")) { CHECK_FAKEIMPORT("xps_inband") xps_inband = 1; } | |
| else if (!stricmp(ext+1, "xps_inbandx")) { CHECK_FAKEIMPORT("xps_inbandx") xps_inband = 2; } | |
| else if (!stricmp(ext+1, "au_delim")) { CHECK_FAKEIMPORT("au_delim") keep_audelim = GF_TRUE; } | |
| else if (!strnicmp(ext+1, "max_lid=", 8) || !strnicmp(ext+1, "max_tid=", 8)) { | |
| s32 val = parse_u32(ext+9, "Max TID/LID"); | |
| CHECK_FAKEIMPORT_2("max_lid/lhvcmode") | |
| if (val < 0) { | |
| M4_LOG(GF_LOG_ERROR, ("Warning: request max layer/temporal id is negative - ignoring\n")); | |
| } else { | |
| if (!strnicmp(ext+1, "max_lid=", 8)) | |
| max_layer_id_plus_one = 1 + (u8) val; | |
| else | |
| max_temporal_id_plus_one = 1 + (u8) val; | |
| } | |
| } | |
| else if (!stricmp(ext+1, "tiles")) { CHECK_FAKEIMPORT_2("tiles") split_tile_mode = 2; } | |
| else if (!stricmp(ext+1, "tiles_rle")) { CHECK_FAKEIMPORT_2("tiles_rle") split_tile_mode = 3; } | |
| else if (!stricmp(ext+1, "split_tiles")) { CHECK_FAKEIMPORT_2("split_tiles") split_tile_mode = 1; } | |
| /*force all composition offsets to be positive*/ | |
| else if (!strnicmp(ext+1, "negctts", 7)) { | |
| neg_ctts_mode = !strnicmp(ext+1, "negctts=no", 10) ? 2 : 1; | |
| } | |
| else if (!stricmp(ext+1, "chap")) is_chap = 1; | |
| else if (!strnicmp(ext+1, "chapter=", 8)) { | |
| chapter_name = gf_strdup(ext+9); | |
| } | |
| else if (!strnicmp(ext+1, "chapfile=", 9)) { | |
| chapter_name = gf_strdup(ext+10); | |
| is_chap_file=1; | |
| } | |
| else if (!strnicmp(ext+1, "layout=", 7)) { | |
| track_layout = 1; | |
| if ( sscanf(ext+13, "%dx%dx%dx%dx%d", &tw, &th, &tx, &ty, &tz)==5) { | |
| } else if ( sscanf(ext+13, "%dx%dx%dx%d", &tw, &th, &tx, &ty)==4) { | |
| tz = 0; | |
| } else if ( sscanf(ext+13, "%dx%dx%d", &tw, &th, &tz)==3) { | |
| tx = ty = 0; | |
| } else if ( sscanf(ext+8, "%dx%d", &tw, &th)==2) { | |
| tx = ty = tz = 0; | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "rescale=", 8)) { | |
| if (sscanf(ext+9, "%u/%u", &rescale_num, &rescale_den) != 2) { | |
| rescale_num = parse_u32(ext+9, "rescale"); | |
| rescale_den = 0; | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "sampdur=", 8)) { | |
| if (sscanf(ext+9, "%u/%u", &rescale_den, &rescale_num) != 2) { | |
| rescale_den = parse_u32(ext+9, "sampdur"); | |
| rescale_num = 0; | |
| } | |
| rescale_override = GF_TRUE; | |
| } | |
| else if (!strnicmp(ext+1, "timescale=", 10)) { | |
| new_timescale = parse_u32(ext+11, "timescale"); | |
| } | |
| else if (!strnicmp(ext+1, "moovts=", 7)) { | |
| moov_timescale = parse_u32(ext+8, "moovts"); | |
| } | |
| else if (!stricmp(ext+1, "noedit")) { import_flags |= GF_IMPORT_NO_EDIT_LIST; } | |
| else if (!strnicmp(ext+1, "rvc=", 4)) { | |
| if (sscanf(ext+5, "%d", &rvc_predefined) != 1) { | |
| rvc_config = gf_strdup(ext+5); | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "fmt=", 4)) import->streamFormat = gf_strdup(ext+5); | |
| else if (!strnicmp(ext+1, "profile=", 8)) { | |
| if (!stricmp(ext+9, "high444")) profile = 244; | |
| else if (!stricmp(ext+9, "high")) profile = 100; | |
| else if (!stricmp(ext+9, "extended")) profile = 88; | |
| else if (!stricmp(ext+9, "main")) profile = 77; | |
| else if (!stricmp(ext+9, "baseline")) profile = 66; | |
| else profile = parse_u32(ext+9, "profile"); | |
| } | |
| else if (!strnicmp(ext+1, "level=", 6)) { | |
| if( atof(ext+7) < 6 ) | |
| level = (int)(10*atof(ext+7)+.5); | |
| else | |
| level = parse_u32(ext+7, "level"); | |
| } | |
| else if (!strnicmp(ext+1, "compat=", 7)) { | |
| compat = parse_u32(ext+8, "compat"); | |
| } | |
| else if (!strnicmp(ext+1, "dlba=", 5)) { | |
| if (!strcmp(ext+6, "no")) dlb_mode=0; | |
| else if (!strcmp(ext+6, "auto")) dlb_mode=-1; | |
| else if (sscanf(ext+6, "%d", &dlb_mode) != 1) { | |
| GOTO_EXIT("Unrecognized dolby atmos mode") | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "novpsext", 8)) { CHECK_FAKEIMPORT("novpsext") import_flags |= GF_IMPORT_NO_VPS_EXTENSIONS; } | |
| else if (!strnicmp(ext+1, "keepav1t", 8)) { CHECK_FAKEIMPORT("keepav1t") import_flags |= GF_IMPORT_KEEP_AV1_TEMPORAL_OBU; } | |
| else if (!strnicmp(ext+1, "font=", 5)) { CHECK_FAKEIMPORT("font") import->fontName = gf_strdup(ext+6); } | |
| else if (!strnicmp(ext+1, "size=", 5)) { CHECK_FAKEIMPORT("size") import->fontSize = parse_u32(ext+6, "size"); } | |
| else if (!strnicmp(ext+1, "text_layout=", 12)) { | |
| if ( sscanf(ext+13, "%dx%dx%dx%d", &txtw, &txth, &txtx, &txty)==4) { | |
| text_layout = 1; | |
| } else if ( sscanf(ext+8, "%dx%d", &txtw, &txth)==2) { | |
| track_layout = 1; | |
| txtx = txty = 0; | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_SWF_IMPORT | |
| else if (!stricmp(ext+1, "swf-global")) { CHECK_FAKEIMPORT("swf-global") import->swf_flags |= GF_SM_SWF_STATIC_DICT; } | |
| else if (!stricmp(ext+1, "swf-no-ctrl")) { CHECK_FAKEIMPORT("swf-no-ctrl") import->swf_flags &= ~GF_SM_SWF_SPLIT_TIMELINE; } | |
| else if (!stricmp(ext+1, "swf-no-text")) { CHECK_FAKEIMPORT("swf-no-text") import->swf_flags |= GF_SM_SWF_NO_TEXT; } | |
| else if (!stricmp(ext+1, "swf-no-font")) { CHECK_FAKEIMPORT("swf-no-font") import->swf_flags |= GF_SM_SWF_NO_FONT; } | |
| else if (!stricmp(ext+1, "swf-no-line")) { CHECK_FAKEIMPORT("swf-no-line") import->swf_flags |= GF_SM_SWF_NO_LINE; } | |
| else if (!stricmp(ext+1, "swf-no-grad")) { CHECK_FAKEIMPORT("swf-no-grad") import->swf_flags |= GF_SM_SWF_NO_GRADIENT; } | |
| else if (!stricmp(ext+1, "swf-quad")) { CHECK_FAKEIMPORT("swf-quad") import->swf_flags |= GF_SM_SWF_QUAD_CURVE; } | |
| else if (!stricmp(ext+1, "swf-xlp")) { CHECK_FAKEIMPORT("swf-xlp") import->swf_flags |= GF_SM_SWF_SCALABLE_LINE; } | |
| else if (!stricmp(ext+1, "swf-ic2d")) { CHECK_FAKEIMPORT("swf-ic2d") import->swf_flags |= GF_SM_SWF_USE_IC2D; } | |
| else if (!stricmp(ext+1, "swf-same-app")) { CHECK_FAKEIMPORT("swf-same-app") import->swf_flags |= GF_SM_SWF_REUSE_APPEARANCE; } | |
| else if (!strnicmp(ext+1, "swf-flatten=", 12)) { CHECK_FAKEIMPORT("swf-flatten") import->swf_flatten_angle = (Float) atof(ext+13); } | |
| #endif | |
| else if (!strnicmp(ext+1, "kind=", 5)) { | |
| char *kind_scheme, *kind_value; | |
| char *kind_data = ext+6; | |
| char *sep = strchr(kind_data, '='); | |
| if (sep) { | |
| *sep = 0; | |
| } | |
| kind_scheme = gf_strdup(kind_data); | |
| if (sep) { | |
| *sep = '='; | |
| kind_value = gf_strdup(sep+1); | |
| } else { | |
| kind_value = NULL; | |
| } | |
| gf_list_add(kinds, kind_scheme); | |
| gf_list_add(kinds, kind_value); | |
| } | |
| else if (!strnicmp(ext+1, "txtflags", 8)) { | |
| if (!strnicmp(ext+1, "txtflags=", 9)) { | |
| sscanf(ext+10, "%x", &txt_flags); | |
| } | |
| else if (!strnicmp(ext+1, "txtflags+=", 10)) { | |
| sscanf(ext+11, "%x", &txt_flags); | |
| txt_mode = GF_ISOM_TEXT_FLAGS_TOGGLE; | |
| } | |
| else if (!strnicmp(ext+1, "txtflags-=", 10)) { | |
| sscanf(ext+11, "%x", &txt_flags); | |
| txt_mode = GF_ISOM_TEXT_FLAGS_UNTOGGLE; | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "rate=", 5)) { | |
| force_rate = parse_s32(ext+6, "rate"); | |
| } | |
| else if (!strnicmp(ext+1, "times=", 6)) { | |
| timestamp_source = gf_strdup(ext+7); | |
| } | |
| else if (!stricmp(ext+1, "stats") || !stricmp(ext+1, "fstat")) | |
| print_stats_graph |= 1; | |
| else if (!stricmp(ext+1, "graph") || !stricmp(ext+1, "graph")) | |
| print_stats_graph |= 2; | |
| else if (!strncmp(ext+1, "sopt", 4) || !strncmp(ext+1, "dopt", 4) || !strncmp(ext+1, "@", 1)) { | |
| if (ext2) ext2[0] = ':'; | |
| opt_src = strstr(ext, ":sopt:"); | |
| opt_dst = strstr(ext, ":dopt:"); | |
| fchain = strstr(ext, ":@"); | |
| if (opt_src) opt_src[0] = 0; | |
| if (opt_dst) opt_dst[0] = 0; | |
| if (fchain) fchain[0] = 0; | |
| if (opt_src) import->filter_src_opts = opt_src+6; | |
| if (opt_dst) import->filter_dst_opts = opt_dst+6; | |
| if (fchain) { | |
| //check for old syntax (0.9->1.0) :@@ | |
| if (fchain[2]=='@') { | |
| import->filter_chain = fchain + 3; | |
| import->is_chain_old_syntax = GF_TRUE; | |
| } else { | |
| import->filter_chain = fchain + 2; | |
| import->is_chain_old_syntax = GF_FALSE; | |
| } | |
| } | |
| ext = NULL; | |
| break; | |
| } | |
| else if (!strnicmp(ext+1, "asemode=", 8)){ | |
| char *mode = ext+9; | |
| if (!stricmp(mode, "v0-bs")) | |
| import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS; | |
| else if (!stricmp(mode, "v0-s")) | |
| import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_DEFAULT; | |
| else if (!stricmp(mode, "v0-2")) | |
| import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_2; | |
| else if (!stricmp(mode, "v1")) | |
| import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_MPEG; | |
| else if (!stricmp(mode, "v1-qt")) | |
| import->asemode = GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF; | |
| else | |
| M4_LOG(GF_LOG_ERROR, ("Unrecognized audio sample entry mode %s, ignoring\n", mode)); | |
| } | |
| else if (!strnicmp(ext+1, "audio_roll=", 11)) { roll_change = 3; roll = parse_s32(ext+12, "audio_roll"); } | |
| else if (!strnicmp(ext+1, "roll=", 5)) { roll_change = 1; roll = parse_s32(ext+6, "roll"); } | |
| else if (!strnicmp(ext+1, "proll=", 6)) { roll_change = 2; roll = parse_s32(ext+7, "proll"); } | |
| else if (!strcmp(ext+1, "stz2")) { | |
| use_stz2 = GF_TRUE; | |
| } else if (!strnicmp(ext+1, "bitdepth=", 9)) { | |
| bitdepth = parse_u32(ext+10, "bitdepth"); | |
| } | |
| else if (!strnicmp(ext+1, "hdr=", 4)) { | |
| hdr_file = gf_strdup(ext+5); | |
| } | |
| else if (!strnicmp(ext+1, "colr=", 5)) { | |
| char *cval = ext+6; | |
| if (!strcmp(cval, "none")) { | |
| clr_type = (u32) -1; | |
| } else if (strlen(cval)<6) { | |
| fmt_ok = GF_FALSE; | |
| } else { | |
| clr_type = GF_4CC(cval[0],cval[1],cval[2],cval[3]); | |
| cval+=4; | |
| if (cval[0] != ',') { | |
| fmt_ok = GF_FALSE; | |
| } | |
| else if ((clr_type==GF_ISOM_SUBTYPE_NCLX) || (clr_type==GF_ISOM_SUBTYPE_NCLC)) { | |
| fmt_ok = scan_color(cval+1, &clr_prim, &clr_tranf, &clr_mx, &clr_full_range); | |
| } | |
| else if ((clr_type==GF_ISOM_SUBTYPE_RICC) || (clr_type==GF_ISOM_SUBTYPE_PROF)) { | |
| FILE *f = gf_fopen(cval+1, "rb"); | |
| if (!f) { | |
| M4_LOG(GF_LOG_ERROR, ("Failed to open file %s\n", cval+1)); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } else { | |
| gf_fseek(f, 0, SEEK_END); | |
| icc_size = (u32) gf_ftell(f); | |
| icc_data = gf_malloc(sizeof(char)*icc_size); | |
| gf_fseek(f, 0, SEEK_SET); | |
| icc_size = (u32) gf_fread(icc_data, icc_size, f); | |
| gf_fclose(f); | |
| } | |
| } else { | |
| M4_LOG(GF_LOG_ERROR, ("Unrecognized colr profile %s\n", gf_4cc_to_str(clr_type) )); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| } | |
| if (!fmt_ok) { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("parsing colr option"); | |
| } | |
| } | |
| else if (!strnicmp(ext + 1, "dvp=", 4)) { | |
| gf_strcpy(dv_profile, ext + 5); | |
| } | |
| else if (!strnicmp(ext + 1, "dvmdc=", 6)) { | |
| dv_md_compression = parse_s32(ext+7, "dvmdc="); | |
| if (dv_md_compression == 2) { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("2 is reserved for Dolby Vision metadata compression") | |
| } | |
| if (dv_md_compression > 3) { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("Dolby Vision metadata compression valid value: 0, 1, 3. default = 0") | |
| } | |
| } | |
| else if (!strnicmp(ext + 1, "dvflags=", 8)) { | |
| dv_flags = (u16)strtol(ext+9, NULL, 16); | |
| if (dv_flags != 0 && dv_flags != 512) { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("Currently, only values 0x0 and 0x200 are defined for dvflags. default = 0") | |
| } | |
| } | |
| //old name | |
| else if (!strnicmp(ext + 1, "dv-profile=", 11)) { | |
| M4_LOG(GF_LOG_WARNING, ("Deprecated option name, use `:dvp=` instead\n")); | |
| gf_strcpy(dv_profile, ext + 12); | |
| } | |
| else if (!strnicmp(ext+1, "fullrange=", 10)) { | |
| if (!stricmp(ext+11, "off") || !stricmp(ext+11, "no")) fullrange = 0; | |
| else if (!stricmp(ext+11, "on") || !stricmp(ext+11, "yes")) fullrange = 1; | |
| else { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("invalid format for fullrange") | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "videofmt=", 9)) { | |
| u32 idx, count = GF_ARRAY_LENGTH(videofmt_names); | |
| for (idx=0; idx<count; idx++) { | |
| if (!strcmp(ext+10, videofmt_names[idx])) { | |
| videofmt = idx; | |
| break; | |
| } | |
| } | |
| if (videofmt==-1) { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("invalid format for videofmt") | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "colorprim=", 10)) { | |
| colorprim = gf_cicp_parse_color_primaries(ext+11); | |
| if (colorprim==-1) { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("invalid format for colorprim") | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "colortfc=", 9)) { | |
| colortfc = gf_cicp_parse_color_transfer(ext+10); | |
| if (colortfc==-1) { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("invalid format for colortfc") | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "colormx=", 8)) { | |
| colormx = gf_cicp_parse_color_matrix(ext+9); | |
| if (colormx==-1) { | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT("invalid format for colormx") | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "tc=", 3)) { | |
| char *tc_str = ext+4; | |
| if (tc_str[0] == 'd') { | |
| tc_drop_frame=GF_TRUE; | |
| tc_str+=1; | |
| } | |
| if (sscanf(tc_str, "%d/%d,%d,%d,%d,%d,%d", &tc_fps_num, &tc_fps_den, &tc_h, &tc_m, &tc_s, &tc_f, &tc_frames_per_tick) == 7) { | |
| } else if (sscanf(tc_str, "%d/%d,%d,%d,%d,%d", &tc_fps_num, &tc_fps_den, &tc_h, &tc_m, &tc_s, &tc_f) == 6) { | |
| } else if (sscanf(tc_str, "%d,%d,%d,%d,%d,%d", &tc_fps_num, &tc_h, &tc_m, &tc_s, &tc_f, &tc_frames_per_tick) == 6) { | |
| tc_fps_den = 1; | |
| } else if (sscanf(tc_str, "%d,%d,%d,%d,%d", &tc_fps_num, &tc_h, &tc_m, &tc_s, &tc_f) == 5) { | |
| tc_fps_den = 1; | |
| } else if (sscanf(tc_str, "%d/%d,%d,%d", &tc_fps_num, &tc_fps_den, &tc_f, &tc_frames_per_tick) == 4) { | |
| tc_force_counter = GF_TRUE; | |
| tc_h = tc_m = tc_s = 0; | |
| } else if (sscanf(tc_str, "%d/%d,%d", &tc_fps_num, &tc_fps_den, &tc_f) == 3) { | |
| tc_force_counter = GF_TRUE; | |
| tc_h = tc_m = tc_s = 0; | |
| } else if (sscanf(tc_str, "%d,%d,%d", &tc_fps_num, &tc_f, &tc_frames_per_tick) == 3) { | |
| tc_force_counter = GF_TRUE; | |
| tc_h = tc_m = tc_s = 0; | |
| tc_fps_den = 1; | |
| } else if (sscanf(tc_str, "%d,%d", &tc_fps_num, &tc_f) == 2) { | |
| tc_force_counter = GF_TRUE; | |
| tc_h = tc_m = tc_s = 0; | |
| tc_fps_den = 1; | |
| } else { | |
| M4_LOG(GF_LOG_ERROR, ("Bad format %s for timecode, ignoring\n", ext+1)); | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "edits=", 6)) { | |
| edits = gf_strdup(ext+7); | |
| } | |
| else if (!strnicmp(ext+1, "lastsampdur", 11)) { | |
| has_last_sample_dur = GF_TRUE; | |
| if (!strnicmp(ext+1, "lastsampdur=", 12)) { | |
| if (sscanf(ext+13, "%d/%u", &last_sample_dur.num, &last_sample_dur.den)==2) { | |
| } else { | |
| last_sample_dur.num = parse_s32(ext+13, "lastsampdur"); | |
| last_sample_dur.den = 1000; | |
| } | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "ID=", 3)) { | |
| import->target_trackID = (u32) parse_u32(ext+4, "ID"); | |
| } | |
| else if (!strnicmp(ext+1, "tkgp=", 5)) { | |
| e = GF_BAD_PARAM; | |
| char *sep=strchr(ext+1, ','); | |
| if (!sep) { | |
| GOTO_EXIT("invalid format for tkgp") | |
| } | |
| sep[0] = 0; | |
| tkgp_id = atoi(sep+1); | |
| if (strlen(ext+6)==4) tkgp_type=GF_4CC(ext[6], ext[7], ext[8], ext[9]); | |
| sep[0] = ','; | |
| if (!tkgp_type) { | |
| GOTO_EXIT("invalid format for tkgp") | |
| } | |
| e = GF_OK; | |
| if (tkgp_id<0) { | |
| tkgp_id = -tkgp_id; | |
| tkgp_add = GF_FALSE; | |
| } | |
| } | |
| else if (!strnicmp(ext+1, "tkidx=", 6)) { | |
| set_tk_idx = atoi(ext+7); | |
| } | |
| else if (!strnicmp(ext+1, "preselection=", 13)) { | |
| import->preselection = ext+14; | |
| } | |
| /*unrecognized, assume name has colon in it*/ | |
| else { | |
| M4_LOG(GF_LOG_ERROR, ("Unrecognized import option %s, ignoring\n", ext+1)); | |
| if (ext2) ext2[0] = ':'; | |
| ext = ext2; | |
| continue; | |
| } | |
| if (src_is_isom) { | |
| char *opt = ext+1; | |
| char *sep_eq = strchr(opt, '='); | |
| if (sep_eq) sep_eq[0] = 0; | |
| if (!mp4box_check_isom_fileopt(opt)) { | |
| M4_LOG(GF_LOG_ERROR, ("\t! Import option `%s` not available for ISOBMFF/QT sources, ignoring !\n", ext+1)); | |
| } | |
| if ((!has_non_extk_opt || (is_extk==2)) && mp4box_check_non_extk_fileopt(opt)) { | |
| has_non_extk_opt = GF_TRUE; | |
| if (is_extk==2) { | |
| M4_LOG(GF_LOG_ERROR, ("\t! Import option `%s` not allowed for external ISOBMFF tracks !\n", ext+1)); | |
| e = GF_BAD_PARAM; | |
| GOTO_EXIT(NULL) | |
| } | |
| } | |
| if (sep_eq) sep_eq[0] = '='; | |
| } | |
| if (ext2) ext2[0] = ':'; | |
| if (!first_ext) { | |
| first_ext = ext; | |
| ext[0] = 0; | |
| } | |
| /* restart from where we stopped | |
| * if we didn't stop (ext2 null) then the end has been reached | |
| * so we can stop the whole thing */ | |
| ext = ext2; | |
| } | |
| /*check duration import (old syntax)*/ | |
| ext = strrchr(final_name, '%'); | |
| if (ext && gf_sys_old_arch_compat()) { | |
| if (gf_parse_frac(ext+1, &import->duration)) | |
| ext[0] = 0; | |
| else | |
| ext = NULL; | |
| } | |
| /*select switches for av containers import*/ | |
| do_audio = do_video = do_auxv = do_pict = 0; | |
| track_id = prog_id = 0; | |
| do_all = 1; | |
| ext_start = gf_file_ext_start(final_name); | |
| ext = strrchr(ext_start ? ext_start : final_name, '#'); | |
| if (ext) ext[0] = 0; | |
| if (fake_import && ext) { | |
| ext++; | |
| if (!strnicmp(ext, "audio", 5)) do_audio = 1; | |
| else if (!strnicmp(ext, "video", 5)) do_video = 1; | |
| else if (!strnicmp(ext, "auxv", 4)) do_auxv = 1; | |
| else if (!strnicmp(ext, "pict", 4)) do_pict = 1; | |
| else if (!strnicmp(ext, "trackID=", 8)) track_id = parse_u32(&ext[8], "trackID"); | |
| else track_id = parse_u32(ext, "ID"); | |
| } | |
| else if (ext) { | |
| ext++; | |
| char *sep = gf_url_colon_suffix(ext, '='); | |
| if (sep) sep[0] = 0; | |
| //we have a fragment, we need to check if the track or the program is present in source | |
| import->in_name = final_name; | |
| import->flags = GF_IMPORT_PROBE_ONLY; | |
| e = gf_media_import(import); | |
| GOTO_EXIT("importing import"); | |
| if (!strnicmp(ext, "audio", 5)) do_audio = 1; | |
| else if (!strnicmp(ext, "video", 5)) do_video = 1; | |
| else if (!strnicmp(ext, "auxv", 4)) do_auxv = 1; | |
| else if (!strnicmp(ext, "pict", 4)) do_pict = 1; | |
| else if (!strnicmp(ext, "trackID=", 8)) track_id = parse_u32(&ext[8], "trackID"); | |
| else if (!strnicmp(ext, "PID=", 4)) track_id = parse_u32(&ext[4], "ID"); | |
| else if (!strnicmp(ext, "program=", 8)) { | |
| for (i=0; i<MIN(import->nb_progs, GF_IMPORT_MAX_TRACKS); i++) { | |
| if (!stricmp(import->pg_info[i].name, ext+8)) { | |
| prog_id = import->pg_info[i].number; | |
| do_all = 0; | |
| break; | |
| } | |
| } | |
| } | |
| else if (!strnicmp(ext, "prog_id=", 8)) { | |
| prog_id = parse_u32(ext+8, "prog_id"); | |
| do_all = 0; | |
| } | |
| else track_id = parse_u32(ext, "ID"); | |
| //figure out trackID | |
| if (do_audio || do_video || do_auxv || do_pict || track_id) { | |
| Bool found = track_id ? GF_FALSE : GF_TRUE; | |
| for (i=0; i<MIN(import->nb_tracks, GF_IMPORT_MAX_TRACKS); i++) { | |
| if (track_id && (import->tk_info[i].track_num==track_id)) { | |
| found=GF_TRUE; | |
| break; | |
| } | |
| if (do_audio && (import->tk_info[i].stream_type==GF_STREAM_AUDIO)) { | |
| track_id = import->tk_info[i].track_num; | |
| break; | |
| } | |
| else if (do_video && (import->tk_info[i].stream_type==GF_STREAM_VISUAL)) { | |
| track_id = import->tk_info[i].track_num; | |
| break; | |
| } | |
| else if (do_auxv && (import->tk_info[i].media_subtype==GF_ISOM_MEDIA_AUXV)) { | |
| track_id = import->tk_info[i].track_num; | |
| break; | |
| } | |
| else if (do_pict && (import->tk_info[i].media_subtype==GF_ISOM_MEDIA_PICT)) { | |
| track_id = import->tk_info[i].track_num; | |
| break; | |
| } | |
| } | |
| if (!track_id || !found) { | |
| M4_LOG(GF_LOG_ERROR, ("Cannot find track ID matching fragment #%s\n", ext)); | |
| if (sep) sep[0] = ':'; | |
| e = GF_NOT_FOUND; | |
| goto exit; | |
| } | |
| } | |
| if (sep) sep[0] = ':'; | |
| } | |
| if (do_audio || do_video || do_auxv || do_pict || track_id) do_all = 0; | |
| if (track_layout || is_chap) { | |
| u32 w, h, sw, sh, fw, fh; | |
| w = h = sw = sh = fw = fh = 0; | |
| chap_ref = 0; | |
| for (i=0; i<gf_isom_get_track_count(dest); i++) { | |
| switch (gf_isom_get_media_type(dest, i+1)) { | |
| case GF_ISOM_MEDIA_SCENE: | |
| case GF_ISOM_MEDIA_VISUAL: | |
| case GF_ISOM_MEDIA_AUXV: | |
| case GF_ISOM_MEDIA_PICT: | |
| if (!chap_ref && gf_isom_is_track_enabled(dest, i+1) ) chap_ref = i+1; | |
| gf_isom_get_visual_info(dest, i+1, 1, &sw, &sh); | |
| gf_isom_get_track_layout_info(dest, i+1, &fw, &fh, NULL, NULL, NULL); | |
| if (w<sw) w = sw; | |
| if (w<fw) w = fw; | |
| if (h<sh) h = sh; | |
| if (h<fh) h = fh; | |
| break; | |
| case GF_ISOM_MEDIA_AUDIO: | |
| if (!chap_ref && gf_isom_is_track_enabled(dest, i+1) ) chap_ref = i+1; | |
| break; | |
| } | |
| } | |
| if (track_layout) { | |
| if (!tw) tw = w; | |
| if (!th) th = h; | |
| if (ty==-1) ty = (h>(u32)th) ? h-th : 0; | |
| import->text_width = tw; | |
| import->text_height = th; | |
| } | |
| if (is_chap && chap_ref) import_flags |= GF_IMPORT_NO_TEXT_FLUSH; | |
| } | |
| if (text_layout && txtw && txth) { | |
| import->text_track_width = import->text_width ? import->text_width : txtw; | |
| import->text_track_height = import->text_height ? import->text_height : txth; | |
| import->text_width = txtw; | |
| import->text_height = txth; | |
| import->text_x = txtx; | |
| import->text_y = txty; | |
| } | |
| check_track_for_svc = check_track_for_lhvc = check_track_for_hevc = 0; | |
| source_magic = (u64) gf_crc_32((u8 *)inName, (u32) strlen(inName)); | |
| if (is_extk) { | |
| u32 ext_tk=0; | |
| GF_ISOFile *isom_in = gf_isom_open(final_name, GF_ISOM_OPEN_READ, NULL); | |
| if (!isom_in) { | |
| e = gf_isom_last_error(NULL); | |
| GOTO_EXIT("opening source") | |
| } | |
| if (!track_id) { | |
| if (gf_isom_get_track_count(isom_in)>1) { | |
| gf_isom_delete(isom_in); | |
| e = GF_BAD_PARAM; | |
| M4_LOG(GF_LOG_ERROR, ("More than one track in source and no trackID specified\n")); | |
| GOTO_EXIT("adding track"); | |
| } | |
| ext_tk = 1; | |
| } else { | |
| ext_tk = gf_isom_get_track_by_id(isom_in, track_id); | |
| if (!ext_tk) { | |
| gf_isom_delete(isom_in); | |
| e = GF_BAD_PARAM; | |
| M4_LOG(GF_LOG_ERROR, ("No track with ID %u in source %s\n", track_id, final_name)); | |
| GOTO_EXIT("adding track"); | |
| } | |
| } | |
| if (has_non_extk_opt) { | |
| is_extk=2; | |
| if (first_ext) first_ext[0] = ':'; | |
| goto reparse_opts; | |
| } | |
| u32 new_tk = 0; | |
| Bool src_is_extk = gf_isom_is_external_track(isom_in, ext_tk, NULL, NULL, NULL, NULL); | |
| if (src_is_extk) { | |
| e = gf_isom_clone_track(isom_in, ext_tk, dest, 0, &new_tk); | |
| GOTO_EXIT("cloning external track"); | |
| } else { | |
| u32 type = gf_isom_get_media_type(isom_in, ext_tk); | |
| u32 timescale = gf_isom_get_media_timescale(isom_in, ext_tk); | |
| new_tk = gf_isom_new_external_track(dest, 0, track_id, type, timescale, final_name); | |
| u32 mvscale = gf_isom_get_timescale(dest); | |
| if (new_tk) { | |
| u32 w, h; | |
| s32 tx, ty; | |
| s16 layer; | |
| if (gf_isom_get_track_layout_info(isom_in, ext_tk, &w, &h, &tx, &ty, &layer)==GF_OK) { | |
| gf_isom_set_track_layout_info(dest, new_tk, w<<16, h<<16, tx<<16, ty<<16, layer); | |
| } | |
| if (import->duration.num>=0) { | |
| u64 dur; | |
| if (import->duration.num) | |
| dur = gf_timestamp_rescale(import->duration.num, import->duration.den, mvscale); | |
| else | |
| dur = gf_timestamp_rescale(gf_isom_get_track_duration(isom_in, ext_tk), gf_isom_get_timescale(isom_in), mvscale); | |
| gf_isom_force_track_duration(dest, new_tk, dur); | |
| } | |
| } | |
| } | |
| track_id = gf_isom_get_track_id(dest, new_tk); | |
| fake_import = 1; | |
| gf_isom_delete(isom_in); | |
| } else if (!fake_import && (!fsess || mux_args_if_first_pass)) { | |
| import->in_name = final_name; | |
| import->dest = dest; | |
| import->video_fps = force_fps; | |
| import->frames_per_sample = frames_per_sample; | |
| import->flags = import_flags; | |
| import->keep_audelim = keep_audelim; | |
| import->print_stats_graph = print_stats_graph; | |
| import->xps_inband = xps_inband; | |
| import->prog_id = prog_id; | |
| import->trackID = track_id; | |
| import->source_magic = source_magic; | |
| import->track_index = tk_idx; | |
| //if moov timescale is <0 (auto mode) set it at import time | |
| if (moov_timescale<0) { | |
| import->moov_timescale = moov_timescale; | |
| } | |
| //otherwise force it now | |
| else if (moov_timescale>0) { | |
| e = gf_isom_set_timescale(dest, moov_timescale); | |
| GOTO_EXIT("changing timescale") | |
| } | |
| import->run_in_session = fsess; | |
| import->update_mux_args = NULL; | |
| if (do_all) | |
| import->flags |= GF_IMPORT_KEEP_REFS; | |
| e = gf_media_import(import); | |
| if (e) { | |
| if (import->update_mux_args) gf_free(import->update_mux_args); | |
| GOTO_EXIT("importing media"); | |
| } | |
| if (fsess) { | |
| *mux_args_if_first_pass = import->update_mux_args; | |
| import->update_mux_args = NULL; | |
| *mux_sid_if_first_pass = import->update_mux_sid; | |
| import->update_mux_sid = NULL; | |
| goto exit; | |
| } | |
| } | |
| nb_tracks = gf_isom_get_track_count(dest); | |
| for (i=0; i<nb_tracks; i++) { | |
| u32 media_type; | |
| track = i+1; | |
| media_type = gf_isom_get_media_type(dest, track); | |
| e = GF_OK; | |
| if (!fake_import) { | |
| u64 tk_source_magic; | |
| tk_source_magic = gf_isom_get_track_magic(dest, track); | |
| if ((tk_source_magic & 0xFFFFFFFFUL) != source_magic) | |
| continue; | |
| tk_source_magic>>=32; | |
| keep_handler = (tk_source_magic & 1) ? GF_TRUE : GF_FALSE; | |
| } else { | |
| keep_handler = GF_TRUE; | |
| if (do_audio && (media_type!=GF_ISOM_MEDIA_AUDIO)) continue; | |
| if (do_video && (media_type!=GF_ISOM_MEDIA_VISUAL)) continue; | |
| if (do_auxv && (media_type!=GF_ISOM_MEDIA_AUXV)) continue; | |
| if (do_pict && (media_type!=GF_ISOM_MEDIA_PICT)) continue; | |
| if (track_id && (gf_isom_get_track_id(dest, track) != track_id)) | |
| continue; | |
| } | |
| timescale = gf_isom_get_timescale(dest); | |
| if (szLan) { | |
| e = gf_isom_set_media_language(dest, track, (char *) szLan); | |
| GOTO_EXIT("changing language") | |
| } | |
| if (do_disable) { | |
| e = gf_isom_set_track_enabled(dest, track, (do_disable==2) ? GF_TRUE : GF_FALSE); | |
| GOTO_EXIT("disabling track") | |
| } | |
| if (track_flags_mode) { | |
| e = gf_isom_set_track_flags(dest, track, track_flags, track_flags_mode); | |
| GOTO_EXIT("disabling track") | |
| } | |
| if (import_flags & GF_IMPORT_NO_EDIT_LIST) { | |
| e = gf_isom_remove_edits(dest, track); | |
| GOTO_EXIT("removing edits") | |
| } | |
| if (delay.num && delay.den) { | |
| u64 tk_dur; | |
| e = gf_isom_remove_edits(dest, track); | |
| tk_dur = gf_isom_get_track_duration(dest, track); | |
| if (delay.num>0) { | |
| //cast to s64, timescale*delay could be quite large before /1000 | |
| e |= gf_isom_append_edit(dest, track, ((s64) delay.num) * timescale / delay.den, 0, GF_ISOM_EDIT_EMPTY); | |
| e |= gf_isom_append_edit(dest, track, tk_dur, 0, GF_ISOM_EDIT_NORMAL); | |
| } else { | |
| //cast to s64, timescale*delay could be quite large before /1000 | |
| u64 to_skip = ((s64) -delay.num) * timescale / delay.den; | |
| if (to_skip<tk_dur) { | |
| //cast to s64, timescale*delay could be quite large before /1000 | |
| u64 media_time = ((s64) -delay.num) * gf_isom_get_media_timescale(dest, track) / delay.den; | |
| e |= gf_isom_append_edit(dest, track, tk_dur-to_skip, media_time, GF_ISOM_EDIT_NORMAL); | |
| } else { | |
| M4_LOG(GF_LOG_ERROR, ("Warning: request negative delay longer than track duration - ignoring\n")); | |
| } | |
| } | |
| GOTO_EXIT("assigning delay") | |
| } | |
| if (gf_isom_is_video_handler_type(media_type)) { | |
| if (((par_n>=0) && (par_d>=0)) || force_par) { | |
| e = gf_media_change_par(dest, track, par_n, par_d, force_par, rewrite_bs); | |
| GOTO_EXIT("changing PAR") | |
| } | |
| if ((fullrange>=0) || (videofmt>=0) || (colorprim>=0) || (colortfc>=0) || (colormx>=0)) { | |
| e = gf_media_change_color(dest, i+1, fullrange, videofmt, colorprim, colortfc, colormx); | |
| GOTO_EXIT("changing color in bitstream") | |
| } | |
| if (has_clap) { | |
| e = gf_isom_set_clean_aperture(dest, track, 1, clap_wn, clap_wd, clap_hn, clap_hd, clap_hon, clap_hod, clap_von, clap_vod); | |
| GOTO_EXIT("changing clean aperture") | |
| } | |
| if (bitdepth) { | |
| e = gf_isom_set_visual_bit_depth(dest, track, 1, bitdepth); | |
| GOTO_EXIT("changing bit depth") | |
| } | |
| if (clr_type) { | |
| if (clr_type==(u32)-1) | |
| clr_type = 0; | |
| e = gf_isom_set_visual_color_info(dest, track, 1, clr_type, clr_prim, clr_tranf, clr_mx, clr_full_range, icc_data, icc_size); | |
| GOTO_EXIT("changing color info") | |
| } | |
| if (hdr_file) { | |
| e = apply_high_dynamc_range_xml_desc(dest, track, hdr_file); | |
| GOTO_EXIT("setting HDR info") | |
| } | |
| if (dv_profile[0]) { | |
| e = set_dv_profile(dest, track, dv_profile, dv_md_compression, dv_flags); | |
| GOTO_EXIT("setting DV profile") | |
| } | |
| if (set_ccst) { | |
| e = gf_isom_set_image_sequence_coding_constraints(dest, track, 1, GF_FALSE, GF_FALSE, GF_TRUE, 15); | |
| GOTO_EXIT("setting image sequence constraints") | |
| } | |
| } | |
| if (has_mx) { | |
| e = gf_isom_set_track_matrix(dest, track, mx); | |
| GOTO_EXIT("setting track matrix") | |
| } | |
| if (use_stz2) { | |
| e = gf_isom_use_compact_size(dest, track, GF_TRUE); | |
| GOTO_EXIT("setting compact size") | |
| } | |
| if (gf_isom_get_media_subtype(dest, track, 1) == GF_ISOM_MEDIA_TIMECODE) { | |
| tmcd_track = track; | |
| } | |
| if (rap_only || refs_only) { | |
| e = gf_media_remove_non_rap(dest, track, refs_only); | |
| GOTO_EXIT("removing non RAPs") | |
| } | |
| if (handler_name) { | |
| e = gf_isom_set_handler_name(dest, track, handler_name); | |
| GOTO_EXIT("setting handler name") | |
| } | |
| else if (!keep_handler) { | |
| char szHName[1024]; | |
| const char *fName = gf_url_get_resource_name((const char *)inName); | |
| fName = strchr(fName, '.'); | |
| if (fName) fName += 1; | |
| else fName = "?"; | |
| snprintf(szHName, sizeof(szHName), "%s@GPAC%s", fName, gf_gpac_version()); | |
| e = gf_isom_set_handler_name(dest, track, szHName); | |
| GOTO_EXIT("setting handler name") | |
| } | |
| if (handler) { | |
| e = gf_isom_set_media_type(dest, track, handler); | |
| GOTO_EXIT("setting media type") | |
| } | |
| if (group) { | |
| e = gf_isom_set_alternate_group_id(dest, track, group); | |
| GOTO_EXIT("setting alternate group") | |
| } | |
| if (tkgp_type) { | |
| e = gf_isom_set_track_group(dest, track, tkgp_id, tkgp_type, tkgp_add); | |
| GOTO_EXIT("setting trackGroup") | |
| } | |
| if (track_layout) { | |
| e = gf_isom_set_track_layout_info(dest, track, tw<<16, th<<16, tx<<16, ty<<16, tz); | |
| GOTO_EXIT("setting track layout") | |
| } | |
| if (stype) { | |
| e = gf_isom_set_media_subtype(dest, track, 1, stype); | |
| GOTO_EXIT("setting media subtype") | |
| } | |
| if (is_chap && chap_ref) { | |
| e = set_chapter_track(dest, track, chap_ref); | |
| GOTO_EXIT("setting chapter track") | |
| } | |
| for (j = 0; j < gf_list_count(kinds); j+=2) { | |
| char *kind_scheme = (char *)gf_list_get(kinds, j); | |
| char *kind_value = (char *)gf_list_get(kinds, j+1); | |
| e = gf_isom_add_track_kind(dest, i+1, kind_scheme, kind_value); | |
| GOTO_EXIT("setting track kind") | |
| } | |
| if (profile || compat || level) { | |
| e = gf_media_change_pl(dest, track, profile, compat, level); | |
| GOTO_EXIT("changing video PL") | |
| } | |
| if (gf_isom_get_mpeg4_subtype(dest, track, 1)) | |
| keep_sys_tracks = 1; | |
| //if moov timescale is <0 (auto mode) set it at import time | |
| if (fake_import) { | |
| if (import_flags & GF_IMPORT_NO_EDIT_LIST) | |
| gf_isom_remove_edits(dest, track); | |
| if (moov_timescale<0) { | |
| moov_timescale = gf_isom_get_media_timescale(dest, track); | |
| } | |
| if (moov_timescale>0) { | |
| e = gf_isom_set_timescale(dest, moov_timescale); | |
| GOTO_EXIT("changing timescale") | |
| } | |
| if (import->asemode && (media_type==GF_ISOM_MEDIA_AUDIO)) { | |
| u32 sr, ch, bps; | |
| gf_isom_get_audio_info(dest, track, 1, &sr, &ch, &bps); | |
| gf_isom_set_audio_info(dest, track, 1, sr, ch, bps, import->asemode); | |
| } | |
| } | |
| if (roll_change) { | |
| if ((roll_change!=3) || (media_type==GF_ISOM_MEDIA_AUDIO)) { | |
| e = gf_isom_set_sample_roll_group(dest, track, (u32) -1, (roll_change==2) ? GF_ISOM_SAMPLE_PREROLL : GF_ISOM_SAMPLE_ROLL, roll); | |
| GOTO_EXIT("assigning roll") | |
| } | |
| } | |
| if (new_timescale>1) { | |
| e = gf_isom_set_media_timescale(dest, track, new_timescale, 0, 0); | |
| GOTO_EXIT("setting media timescale") | |
| } | |
| if (rescale_num > 1) { | |
| switch (gf_isom_get_media_type(dest, track)) { | |
| case GF_ISOM_MEDIA_AUDIO: | |
| if (!rescale_override) { | |
| M4_LOG(GF_LOG_WARNING, ("Cannot force media timescale for audio media types - ignoring\n")); | |
| break; | |
| } | |
| default: | |
| e = gf_isom_set_media_timescale(dest, track, rescale_num, rescale_den, rescale_override ? 2 : 1); | |
| if (e==GF_EOS) { | |
| M4_LOG(GF_LOG_WARNING, ("Rescale ignored, same config in source file\n")); | |
| e = GF_OK; | |
| } | |
| GOTO_EXIT("rescaling media track") | |
| break; | |
| } | |
| } else if (src_is_isom && force_fps.den && force_fps.num) { | |
| if (gf_isom_is_video_handler_type(gf_isom_get_media_type(dest, track))) { | |
| e = gf_isom_set_media_timescale(dest, track, force_fps.num, force_fps.den, 2); | |
| if (e==GF_EOS) { | |
| M4_LOG(GF_LOG_WARNING, ("Rescale ignored, same config in source file\n")); | |
| e = GF_OK; | |
| } | |
| } else { | |
| M4_LOG(GF_LOG_WARNING, ("Cannot force FPS for media types %s - ignoring\n", gf_4cc_to_str( gf_isom_get_media_type(dest, track)) )); | |
| } | |
| } | |
| if (has_last_sample_dur) { | |
| e = gf_isom_set_last_sample_duration_ex(dest, track, last_sample_dur.num, last_sample_dur.den); | |
| GOTO_EXIT("setting last sample duration") | |
| } | |
| if (rvc_config) { | |
| #ifdef GPAC_DISABLE_ZLIB | |
| M4_LOG(GF_LOG_ERROR, ("Error: no zlib support - RVC not available\n")); | |
| e = GF_NOT_SUPPORTED; | |
| goto exit; | |
| #else | |
| u8 *data; | |
| u32 size; | |
| e = gf_file_load_data(rvc_config, (u8 **) &data, &size); | |
| GOTO_EXIT("loading RVC config file") | |
| gf_gz_compress_payload_ex (&data, size, &size, 0, GF_FALSE, NULL, GF_TRUE); | |
| e |= gf_isom_set_rvc_config(dest, track, 1, 0, "application/rvc-config+xml+gz", data, size); | |
| gf_free(data); | |
| GOTO_EXIT("compressing and assigning RVC config") | |
| #endif | |
| } else if (rvc_predefined>0) { | |
| e = gf_isom_set_rvc_config(dest, track, 1, rvc_predefined, NULL, NULL, 0); | |
| GOTO_EXIT("setting RVC predefined config") | |
| } | |
| if (neg_ctts_mode) { | |
| e = gf_isom_set_composition_offset_mode(dest, track, (neg_ctts_mode==1) ? GF_TRUE : GF_FALSE); | |
| GOTO_EXIT("setting composition offset mode") | |
| } | |
| if (gf_isom_get_avc_svc_type(dest, track, 1)>=GF_ISOM_AVCTYPE_AVC_SVC) | |
| check_track_for_svc = track; | |
| switch (gf_isom_get_hevc_lhvc_type(dest, track, 1)) { | |
| case GF_ISOM_HEVCTYPE_HEVC_LHVC: | |
| case GF_ISOM_HEVCTYPE_LHVC_ONLY: | |
| check_track_for_lhvc = i+1; | |
| break; | |
| case GF_ISOM_HEVCTYPE_HEVC_ONLY: | |
| check_track_for_hevc=1; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (txt_flags) { | |
| e = gf_isom_text_set_display_flags(dest, track, 0, txt_flags, txt_mode); | |
| GOTO_EXIT("setting text track display flags") | |
| } | |
| if (edits) { | |
| e = apply_edits(dest, track, edits); | |
| GOTO_EXIT("applying edits") | |
| } | |
| if (force_rate>=0) { | |
| e = gf_isom_update_bitrate(dest, i+1, 1, force_rate, force_rate, 0); | |
| GOTO_EXIT("updating bitrate") | |
| } | |
| if (split_tile_mode) { | |
| switch (gf_isom_get_media_subtype(dest, track, 1)) { | |
| case GF_ISOM_SUBTYPE_HVC1: | |
| case GF_ISOM_SUBTYPE_HEV1: | |
| case GF_ISOM_SUBTYPE_HVC2: | |
| case GF_ISOM_SUBTYPE_HEV2: | |
| break; | |
| default: | |
| split_tile_mode = 0; | |
| break; | |
| } | |
| } | |
| if ((dlb_mode>=-1) && (gf_isom_get_media_subtype(dest, track, 1)==GF_ISOM_SUBTYPE_EC3)) { | |
| GF_AC3Config *ac3c = gf_isom_ac3_config_get(dest, track, 1); | |
| if (ac3c) { | |
| if (dlb_mode==0) { | |
| ac3c->is_ec3 = GF_TRUE; | |
| ac3c->atmos_ec3_ext=0; | |
| ac3c->complexity_index_type=0; | |
| } else { | |
| #ifndef GPAC_DISABLE_AV_PARSERS | |
| u32 di; | |
| GF_ISOSample *samp = gf_isom_get_sample(dest, track, 1, &di); | |
| u32 pos; | |
| gf_eac3_parser(samp->data, samp->dataLength, &pos, ac3c, GF_TRUE); | |
| if (dlb_mode>0) { | |
| ac3c->atmos_ec3_ext = 1; | |
| ac3c->complexity_index_type = dlb_mode; | |
| } | |
| gf_isom_sample_del(&samp); | |
| #endif | |
| } | |
| gf_isom_ac3_config_update(dest, track, 1, ac3c); | |
| gf_free(ac3c); | |
| } | |
| } | |
| //if track order is required, gather track IDs - we cannot change directly track order since this impacts track number | |
| //and would break this loop | |
| if (set_tk_idx) { | |
| reorder_tk_ids = gf_realloc(reorder_tk_ids, sizeof(u32)*(reorder_tk_ids_count+1)); | |
| reorder_tk_ids[reorder_tk_ids_count] = gf_isom_get_track_id(dest, track); | |
| reorder_tk_ids_count++; | |
| } | |
| if (timestamp_source) { | |
| if (!track_id && gf_isom_get_track_count(dest)==1) | |
| track_id = gf_isom_get_track_id(dest, 1); | |
| if (!track_id) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Warning: `times=`cannot be used with multi-track import, ignoring\n")); | |
| } else { | |
| apply_timestamps(dest, track_id, timestamp_source); | |
| } | |
| } | |
| } | |
| if (reorder_tk_ids_count) { | |
| for (i=0; i<reorder_tk_ids_count; i++) { | |
| u32 tk = gf_isom_get_track_by_id(dest, reorder_tk_ids[i]); | |
| gf_isom_set_track_index(dest, tk, set_tk_idx); | |
| set_tk_idx++; | |
| } | |
| } | |
| if (chapter_name) { | |
| if (is_chap_file) { | |
| GF_Fraction a_fps = {0,0}; | |
| e = gf_media_import_chapters(dest, chapter_name, a_fps, GF_FALSE); | |
| } else { | |
| e = gf_isom_add_chapter(dest, 0, 0, chapter_name); | |
| } | |
| GOTO_EXIT("importing chapters") | |
| } | |
| if (tmcd_track) { | |
| u32 tmcd_id = gf_isom_get_track_id(dest, tmcd_track); | |
| for (i=0; i < gf_isom_get_track_count(dest); i++) { | |
| switch (gf_isom_get_media_type(dest, i+1)) { | |
| case GF_ISOM_MEDIA_VISUAL: | |
| case GF_ISOM_MEDIA_AUXV: | |
| case GF_ISOM_MEDIA_PICT: | |
| break; | |
| default: | |
| continue; | |
| } | |
| e = gf_isom_set_track_reference(dest, i+1, GF_ISOM_REF_TMCD, tmcd_id); | |
| GOTO_EXIT("assigning TMCD track references") | |
| } | |
| } | |
| /*force to rewrite all dependencies*/ | |
| for (i = 1; i <= gf_isom_get_track_count(dest); i++) | |
| { | |
| e = gf_isom_rewrite_track_dependencies(dest, i); | |
| if (e) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Warning: track ID %d has references to a track not imported\n", gf_isom_get_track_id(dest, i) )); | |
| e = GF_OK; | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_AV_PARSERS | |
| if (max_layer_id_plus_one || max_temporal_id_plus_one) { | |
| for (i = 1; i <= gf_isom_get_track_count(dest); i++) | |
| { | |
| e = gf_media_filter_hevc(dest, i, max_temporal_id_plus_one, max_layer_id_plus_one); | |
| if (e) { | |
| M4_LOG(GF_LOG_ERROR, ("Warning: track ID %d: error while filtering LHVC layers\n", gf_isom_get_track_id(dest, i))); | |
| e = GF_OK; | |
| } | |
| } | |
| } | |
| #endif | |
| if (check_track_for_svc) { | |
| if (svc_mode) { | |
| e = gf_media_split_svc(dest, check_track_for_svc, (svc_mode==2) ? 1 : 0); | |
| GOTO_EXIT("splitting SVC track") | |
| } else { | |
| e = gf_media_merge_svc(dest, check_track_for_svc, 1); | |
| GOTO_EXIT("merging SVC/SHVC track") | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_AV_PARSERS | |
| if (check_track_for_lhvc) { | |
| if (svc_mode) { | |
| GF_LHVCExtractoreMode xmode = GF_LHVC_EXTRACTORS_ON; | |
| if (svc_mode==3) xmode = GF_LHVC_EXTRACTORS_OFF; | |
| else if (svc_mode==4) xmode = GF_LHVC_EXTRACTORS_OFF_FORCE_INBAND; | |
| e = gf_media_split_lhvc(dest, check_track_for_lhvc, GF_FALSE, (svc_mode==1) ? 0 : 1, xmode ); | |
| GOTO_EXIT("splitting L-HEVC track") | |
| } else { | |
| //TODO - merge, temporal sublayers | |
| } | |
| } | |
| if (check_track_for_hevc) { | |
| if (split_tile_mode) { | |
| e = gf_media_split_hevc_tiles(dest, split_tile_mode - 1); | |
| GOTO_EXIT("splitting HEVC tiles") | |
| } | |
| if (temporal_mode) { | |
| GF_LHVCExtractoreMode xmode = (temporal_mode==3) ? GF_LHVC_EXTRACTORS_OFF : GF_LHVC_EXTRACTORS_ON; | |
| e = gf_media_split_lhvc(dest, check_track_for_hevc, GF_TRUE, (temporal_mode==1) ? GF_FALSE : GF_TRUE, xmode ); | |
| GOTO_EXIT("splitting HEVC temporal sublayers") | |
| } | |
| } | |
| if (tc_fps_num && !mux_args_if_first_pass) { | |
| u32 desc_index=0; | |
| u32 tmcd_tk, tmcd_id; | |
| u32 video_ref = 0; | |
| GF_BitStream *bs; | |
| GF_ISOSample *samp; | |
| for (i=0; i<gf_isom_get_track_count(dest); i++) { | |
| if (gf_isom_is_video_handler_type(gf_isom_get_media_type(dest, i+1))) { | |
| video_ref = i+1; | |
| break; | |
| } | |
| } | |
| tmcd_tk = gf_isom_new_track(dest, 0, GF_ISOM_MEDIA_TIMECODE, tc_fps_num); | |
| if (!tmcd_tk) { | |
| e = gf_isom_last_error(dest); | |
| GOTO_EXIT("creating TMCD track") | |
| } | |
| e = gf_isom_set_track_enabled(dest, tmcd_tk, 1); | |
| if (e != GF_OK) { | |
| GOTO_EXIT("enabling TMCD track") | |
| } | |
| if (!tc_frames_per_tick) { | |
| tc_frames_per_tick = tc_fps_num; | |
| tc_frames_per_tick /= (tc_fps_den ? tc_fps_den : 1); | |
| if (tc_frames_per_tick * tc_fps_den < tc_fps_num) | |
| tc_frames_per_tick++; | |
| } | |
| u32 tmcd_value = (tc_h * 3600 + tc_m*60 + tc_s)*tc_frames_per_tick+tc_f; | |
| tmcd_id = gf_isom_get_track_id(dest, tmcd_tk); | |
| e = gf_isom_tmcd_config_new(dest, tmcd_tk, tc_fps_num, tc_fps_den, tc_frames_per_tick, tc_drop_frame, tc_force_counter, &desc_index); | |
| GOTO_EXIT("configuring TMCD sample description") | |
| if (video_ref) { | |
| e = gf_isom_set_track_reference(dest, video_ref, GF_ISOM_REF_TMCD, tmcd_id); | |
| GOTO_EXIT("assigning TMCD track ref on video track") | |
| } | |
| bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| gf_bs_write_u32(bs, tmcd_value); | |
| samp = gf_isom_sample_new(); | |
| samp->IsRAP = SAP_TYPE_1; | |
| gf_bs_get_content(bs, &samp->data, &samp->dataLength); | |
| gf_bs_del(bs); | |
| e = gf_isom_add_sample(dest, tmcd_tk, desc_index, samp); | |
| gf_isom_sample_del(&samp); | |
| GOTO_EXIT("assigning TMCD sample") | |
| if (video_ref) { | |
| u64 video_ref_dur = gf_isom_get_media_duration(dest, video_ref); | |
| s64 ts_offset; | |
| //see #3551, tmcd is not in composition time but in presentation time, remove any skip time included in gf_isom_get_media_duration | |
| Bool has_edit_list = gf_isom_get_edit_list_type(dest, video_ref, &ts_offset) ? 1 : 0; | |
| if (!has_edit_list && (ts_offset<0)) { | |
| video_ref_dur -= -ts_offset; | |
| } | |
| video_ref_dur *= tc_fps_num; | |
| video_ref_dur /= gf_isom_get_media_timescale(dest, video_ref); | |
| e = gf_isom_set_last_sample_duration(dest, tmcd_tk, (u32) video_ref_dur); | |
| } else { | |
| e = gf_isom_set_last_sample_duration(dest, tmcd_tk, tc_fps_den ? tc_fps_den : 1); | |
| } | |
| GOTO_EXIT("setting TMCD sample dur") | |
| } | |
| #endif /*GPAC_DISABLE_AV_PARSERS*/ | |
| exit: | |
| while (gf_list_count(kinds)) { | |
| char *kind = (char *)gf_list_get(kinds, 0); | |
| gf_list_rem(kinds, 0); | |
| if (kind) gf_free(kind); | |
| } | |
| if (opt_src) opt_src[0] = ':'; | |
| if (opt_dst) opt_dst[0] = ':'; | |
| if (fchain) fchain[0] = ':'; | |
| if (hdr_file) gf_free(hdr_file); | |
| gf_list_del(kinds); | |
| if (handler_name) gf_free(handler_name); | |
| if (chapter_name ) gf_free(chapter_name); | |
| if (import->fontName) gf_free(import->fontName); | |
| if (import->streamFormat) gf_free(import->streamFormat); | |
| if (import->force_ext) gf_free(import->force_ext); | |
| if (rvc_config) gf_free(rvc_config); | |
| if (edits) gf_free(edits); | |
| if (szLan) gf_free((char *)szLan); | |
| if (icc_data) gf_free(icc_data); | |
| if (final_name) gf_free(final_name); | |
| if (reorder_tk_ids) gf_free(reorder_tk_ids); | |
| if (import) gf_free(import); | |
| if (timestamp_source) gf_free(timestamp_source); | |
| if (!e) return GF_OK; | |
| if (fail_msg) { | |
| M4_LOG(GF_LOG_ERROR, ("Failure while %s: %s\n", fail_msg, gf_error_to_string(e) )); | |
| } | |
| return e; | |
| } | |
| typedef struct | |
| { | |
| Double progress; | |
| u32 file_idx; | |
| } SplitInfo; | |
| static Bool on_split_event(void *_udta, GF_Event *evt) | |
| { | |
| Double progress; | |
| SplitInfo *sinfo = (SplitInfo *)_udta; | |
| if (!_udta) return GF_FALSE; | |
| if (evt->type != GF_EVENT_PROGRESS) return GF_FALSE; | |
| if (!evt->progress.total) return GF_FALSE; | |
| progress = (Double) (100*evt->progress.done) / evt->progress.total; | |
| if (progress <= sinfo->progress) | |
| return GF_FALSE; | |
| if (evt->progress.done == evt->progress.total) { | |
| if (sinfo->progress <= 0) | |
| return GF_FALSE; | |
| #ifndef GPAC_DISABLE_LOG | |
| GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("splitting: file %d done\n", sinfo->file_idx)); | |
| #else | |
| fprintf(stderr, "splitting: file %d done\n", sinfo->file_idx); | |
| #endif | |
| sinfo->file_idx++; | |
| sinfo->progress = -1; | |
| } else { | |
| sinfo->progress = progress; | |
| #ifndef GPAC_DISABLE_LOG | |
| GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("splitting: % 2.2f %%\r", progress)); | |
| #else | |
| fprintf(stderr, "splitting: % 2.2f %%\r", progress); | |
| #endif | |
| } | |
| return GF_FALSE; | |
| } | |
| extern u32 do_flat; | |
| extern Bool do_frag, use_mfra; | |
| extern Double interleaving_time; | |
| GF_Err split_isomedia_file(GF_ISOFile *mp4, Double split_dur, u64 split_size_kb, char *inName, Double InterleavingTime, Double chunk_start_time, u32 adjust_split_end, char *outName, Bool force_rap_split, const char *split_range_str, u32 fs_dump_flags) | |
| { | |
| Bool chunk_extraction, rap_split, split_until_end, use_splitrange=GF_TRUE; | |
| GF_Err e; | |
| char *ext, szName[GF_MAX_PATH], szFile[GF_MAX_PATH+100], szArgs[1000]; | |
| Double chunk_start = (Double) chunk_start_time; | |
| char *filter_args = NULL; | |
| GF_FilterSession *fs; | |
| GF_Filter *src, *reframe, *dst; | |
| SplitInfo sinfo; | |
| sinfo.progress = -1; | |
| sinfo.file_idx = 1; | |
| chunk_extraction = (chunk_start>=0) ? GF_TRUE : GF_FALSE; | |
| if (split_range_str) | |
| chunk_extraction = GF_TRUE; | |
| split_until_end = GF_FALSE; | |
| rap_split = GF_FALSE; | |
| if (split_size_kb == (u64)-1) rap_split = GF_TRUE; | |
| if (split_dur == -1) rap_split = GF_TRUE; | |
| else if (split_dur <= -2) { | |
| split_size_kb = 0; | |
| split_until_end = GF_TRUE; | |
| } | |
| else if (force_rap_split) | |
| rap_split = GF_TRUE; | |
| //split in same dir as source | |
| gf_strcpy(szName, inName); | |
| ext = strrchr(szName, '.'); | |
| if (ext) ext[0] = 0; | |
| fs = gf_fs_new_defaults(0); | |
| if (!fs) { | |
| M4_LOG(GF_LOG_ERROR, ("Failed to load filter session, aborting\n")); | |
| return GF_IO_ERR; | |
| } | |
| //load source with all tracks processing | |
| sprintf(szArgs, "mp4dmx:mov=%p:alltk", mp4); | |
| src = gf_fs_load_filter(fs, szArgs, &e); | |
| if (!src) { | |
| M4_LOG(GF_LOG_ERROR, ("Failed to load source filter: %s\n", gf_error_to_string(e) )); | |
| gf_fs_del(fs); | |
| return e; | |
| } | |
| //default output name formatting | |
| if (!outName) { | |
| gf_strcpy(szFile, szName); | |
| gf_strcat(szFile, "_$num%03d$"); | |
| } | |
| gf_dynstrcat(&filter_args, "reframer", NULL); | |
| if (rap_split) { | |
| gf_dynstrcat(&filter_args, ":xs=SAP", NULL); | |
| } else if (split_size_kb) { | |
| sprintf(szArgs, ":xs=S"LLU"k", split_size_kb); | |
| gf_dynstrcat(&filter_args, szArgs, NULL); | |
| } else if (chunk_extraction) { | |
| //we adjust end: start at the iframe at or after requested time and use xadjust (move end to next I-frame) | |
| //so that two calls with X:Y and Y:Z have the same Y boundary | |
| if (adjust_split_end==1) { | |
| gf_dynstrcat(&filter_args, ":xadjust:xround=after", NULL); | |
| } else if (adjust_split_end==2) { | |
| gf_dynstrcat(&filter_args, ":xadjust:xround=before", NULL); | |
| } else if (adjust_split_end==3) { | |
| gf_dynstrcat(&filter_args, ":xround=seek", NULL); | |
| } else if (!gf_sys_find_global_arg("xround")) { | |
| gf_dynstrcat(&filter_args, ":xround=closest", NULL); | |
| } | |
| if (!adjust_split_end || (adjust_split_end==3)) { | |
| if (!gf_sys_find_global_arg("probe_ref")) { | |
| gf_dynstrcat(&filter_args, ":probe_ref", NULL); | |
| } | |
| } | |
| if (split_range_str) { | |
| Bool first = GF_TRUE; | |
| char *xs_a=NULL, *xe_a=NULL; | |
| gf_dynstrcat(&xs_a, ":xs=", NULL); | |
| gf_dynstrcat(&xe_a, ":xe=", NULL); | |
| while (1) { | |
| char c; | |
| char *next_t = strchr(split_range_str, ','); | |
| if (next_t) next_t[0] = 0; | |
| Bool is_time = GF_FALSE; | |
| //S-E syntax | |
| char *end = (char *) strchr(split_range_str, '-'); | |
| if (!end) { | |
| //S:E syntax | |
| end = (char *) strchr(split_range_str, ':'); | |
| //if another `:` assume time format | |
| if (end && strchr(end+1, ':')) | |
| is_time = GF_TRUE; | |
| } else if (strchr(split_range_str, ':')) { | |
| is_time = GF_TRUE; | |
| } | |
| if (!end) { | |
| gf_free(filter_args); | |
| M4_LOG(GF_LOG_ERROR, ("Invalid range specifer %s, expecting START-END or START:END\n", split_range_str )); | |
| gf_fs_del(fs); | |
| if (next_t) next_t[0] = ','; | |
| return GF_BAD_PARAM; | |
| } | |
| if (!first) { | |
| gf_dynstrcat(&xs_a, ",", NULL); | |
| gf_dynstrcat(&xe_a, ",", NULL); | |
| } | |
| first = GF_FALSE; | |
| c = end[0]; | |
| end[0] = 0; | |
| if (is_time) { | |
| gf_dynstrcat(&xs_a, "T", NULL); | |
| gf_dynstrcat(&xe_a, "T", NULL); | |
| } | |
| gf_dynstrcat(&xs_a, split_range_str, NULL); | |
| gf_dynstrcat(&xe_a, end+1, NULL); | |
| end[0] = c; | |
| if (!next_t) break; | |
| next_t[0] = ','; | |
| split_range_str = next_t + 1; | |
| } | |
| if (xs_a) { | |
| gf_dynstrcat(&filter_args, xs_a, NULL); | |
| gf_free(xs_a); | |
| } | |
| if (xe_a) { | |
| gf_dynstrcat(&filter_args, xe_a, NULL); | |
| gf_free(xe_a); | |
| } | |
| szArgs[0]=0; | |
| //if output set and no template, don't use split range | |
| if (outName) { | |
| xs_a = strchr(outName, '$'); | |
| if (!xs_a || !strchr(xs_a+1, '$')) | |
| use_splitrange = GF_FALSE; | |
| } | |
| } else if (split_until_end) { | |
| Double end=0; | |
| if (split_dur<-2) { | |
| end = (Double) gf_isom_get_duration(mp4); | |
| end /= gf_isom_get_timescale(mp4); | |
| split_dur = -2 - split_dur; | |
| if (end > split_dur) end-=split_dur; | |
| else end = 0; | |
| } | |
| if (end>0) { | |
| sprintf(szArgs, ":xs=%u/1000:xe=%u/1000", (u32) (chunk_start*1000), (u32) (end * 1000) ); | |
| } else { | |
| sprintf(szArgs, ":xs=%u/1000", (u32) (chunk_start*1000)); | |
| } | |
| } else { | |
| sprintf(szArgs, ":xs=%u/1000:xe=%u/1000", (u32) (chunk_start*1000), (u32) ((chunk_start+split_dur) * 1000) ); | |
| } | |
| if (szArgs[0]) | |
| gf_dynstrcat(&filter_args, szArgs, NULL); | |
| if (!outName) { | |
| sprintf(szFile, "%s_$FS$", szName); | |
| } | |
| } else if (split_dur) { | |
| sprintf(szArgs, ":xs=D%g", split_dur); | |
| gf_dynstrcat(&filter_args, szArgs, NULL); | |
| if (adjust_split_end) { | |
| gf_dynstrcat(&filter_args, ":xadjust", NULL); | |
| } | |
| } else { | |
| gf_fs_del(fs); | |
| gf_free(filter_args); | |
| M4_LOG(GF_LOG_ERROR, ("Unrecognized split syntax\n")); | |
| return GF_BAD_PARAM; | |
| } | |
| if (use_splitrange) | |
| gf_dynstrcat(&filter_args, ":splitrange", NULL); | |
| reframe = gf_fs_load_filter(fs, filter_args, &e); | |
| gf_free(filter_args); | |
| filter_args = NULL; | |
| if (!reframe) { | |
| M4_LOG(GF_LOG_ERROR, ("Failed to load reframer filter: %s\n", gf_error_to_string(e) )); | |
| gf_fs_del(fs); | |
| return e; | |
| } | |
| if (!outName) { | |
| gf_strcat(szFile, ".mp4"); | |
| } else { | |
| gf_strcpy(szFile, outName); | |
| } | |
| if (gf_dir_exists(szFile)) { | |
| char c = szFile[strlen(szFile)-1]; | |
| if ((c!='/') && (c!='\\')) | |
| gf_strcat(szFile, "/"); | |
| gf_strcat(szFile, szName); | |
| gf_strcat(szFile, "_$num%03d$.mp4"); | |
| M4_LOG(GF_LOG_WARNING, ("Split output is a directory, will use template %s\n", szFile)); | |
| } | |
| else if (split_size_kb || split_dur) { | |
| if (!strchr(szFile, '$') && (stricmp(szFile, "null") || !strcmp(szFile, "/dev/null")) ) { | |
| char *sep = gf_file_ext_start(szFile); | |
| if (sep) sep[0] = 0; | |
| gf_strcat(szFile, "_$num$.mp4"); | |
| M4_LOG(GF_LOG_WARNING, ("Split by %s but output not a template, using %s as output\n", split_size_kb ? "size" : "duration", szFile)); | |
| } | |
| } | |
| if (do_frag) { | |
| sprintf(szArgs, ":cdur=%g", interleaving_time); | |
| gf_strcat(szFile, ":store=frag"); | |
| gf_strcat(szFile, szArgs); | |
| } | |
| else if (do_flat==1) { | |
| gf_strcat(szFile, ":store=flat"); | |
| } | |
| else if (do_flat || interleaving_time) { | |
| if (do_flat==3) { | |
| gf_strcat(szFile, ":store=fstart"); | |
| } | |
| sprintf(szArgs, ":cdur=%g", interleaving_time); | |
| gf_strcat(szFile, szArgs); | |
| } | |
| if (use_mfra) | |
| gf_strcat(szFile, ":mfra"); | |
| dst = gf_fs_load_destination(fs, szFile, NULL, NULL, &e); | |
| if (!dst) { | |
| M4_LOG(GF_LOG_ERROR, ("Failed to load destination filter: %s\n", gf_error_to_string(e) )); | |
| gf_fs_del(fs); | |
| return e; | |
| } | |
| //link reframer to dst | |
| gf_filter_set_source(dst, reframe, NULL); | |
| if (!gf_sys_is_test_mode() | |
| #ifndef GPAC_DISABLE_LOG | |
| && (gf_log_get_tool_level(GF_LOG_APP)!=GF_LOG_QUIET) | |
| #endif | |
| && !gf_sys_is_quiet() | |
| ) { | |
| gf_fs_enable_reporting(fs, GF_TRUE); | |
| gf_fs_set_ui_callback(fs, on_split_event, &sinfo); | |
| } | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| else if (gf_sys_is_cov_mode()) { | |
| on_split_event(NULL, NULL); | |
| } | |
| #endif //GPAC_ENABLE_COVERAGE | |
| e = gf_fs_run(fs); | |
| if (e>=GF_OK) { | |
| e = gf_fs_get_last_connect_error(fs); | |
| if (e>=GF_OK) | |
| e = gf_fs_get_last_process_error(fs); | |
| } | |
| gf_fs_print_non_connected(fs); | |
| if (fs_dump_flags & 1) gf_fs_print_stats(fs); | |
| if (fs_dump_flags & 2) gf_fs_print_connections(fs); | |
| gf_fs_del(fs); | |
| if (e<GF_OK) | |
| M4_LOG(GF_LOG_ERROR, ("Split failed: %s\n", gf_error_to_string(e) )); | |
| return e; | |
| } | |
| GF_Err cat_multiple_files(GF_ISOFile *dest, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command); | |
| static Bool merge_parameter_set(GF_List *src, GF_List *dst, const char *name) | |
| { | |
| u32 j, k; | |
| for (j=0; j<gf_list_count(src); j++) { | |
| Bool found = 0; | |
| GF_NALUFFParam *slc = gf_list_get(src, j); | |
| for (k=0; k<gf_list_count(dst); k++) { | |
| GF_NALUFFParam *slc_dst = gf_list_get(dst, k); | |
| if ( (slc->size==slc_dst->size) && !memcmp(slc->data, slc_dst->data, slc->size) ) { | |
| found = 1; | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| return GF_FALSE; | |
| } | |
| } | |
| return GF_TRUE; | |
| } | |
| static u32 merge_avc_config(GF_ISOFile *dest, u32 tk_id, GF_ISOFile **o_orig, u32 src_track, Bool force_cat, u32 *orig_nal_len, u32 *dst_nal_len) | |
| { | |
| GF_AVCConfig *avc_src, *avc_dst; | |
| GF_ISOFile *orig = *o_orig; | |
| u32 dst_tk = gf_isom_get_track_by_id(dest, tk_id); | |
| avc_src = gf_isom_avc_config_get(orig, src_track, 1); | |
| avc_dst = gf_isom_avc_config_get(dest, dst_tk, 1); | |
| if (avc_src && avc_dst) { | |
| if (!force_cat && (avc_src->AVCLevelIndication!=avc_dst->AVCLevelIndication)) { | |
| dst_tk = 0; | |
| } else if (!force_cat && (avc_src->AVCProfileIndication!=avc_dst->AVCProfileIndication)) { | |
| dst_tk = 0; | |
| } | |
| else { | |
| /*rewrite all samples if using different NALU size*/ | |
| if (avc_src->nal_unit_size > avc_dst->nal_unit_size) { | |
| gf_media_nal_rewrite_samples(dest, dst_tk, 8*avc_src->nal_unit_size); | |
| avc_dst->nal_unit_size = avc_src->nal_unit_size; | |
| } else if (avc_src->nal_unit_size < avc_dst->nal_unit_size) { | |
| *orig_nal_len = avc_src->nal_unit_size; | |
| *dst_nal_len = avc_dst->nal_unit_size; | |
| } | |
| /*merge PS*/ | |
| if (!merge_parameter_set(avc_src->sequenceParameterSets, avc_dst->sequenceParameterSets, "SPS")) | |
| dst_tk = 0; | |
| if (!merge_parameter_set(avc_src->pictureParameterSets, avc_dst->pictureParameterSets, "PPS")) | |
| dst_tk = 0; | |
| gf_isom_avc_config_update(dest, dst_tk, 1, avc_dst); | |
| } | |
| } | |
| gf_odf_avc_cfg_del(avc_src); | |
| gf_odf_avc_cfg_del(avc_dst); | |
| if (!dst_tk) { | |
| dst_tk = gf_isom_get_track_by_id(dest, tk_id); | |
| gf_isom_set_nalu_extract_mode(orig, src_track, GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG); | |
| if (!force_cat) { | |
| //set inband config but keep param sets | |
| gf_isom_avc_set_inband_config(dest, dst_tk, 1, GF_TRUE); | |
| } else { | |
| M4_LOG(GF_LOG_WARNING, ("WARNING: Concatenating track ID %d even though sample descriptions do not match\n", tk_id)); | |
| } | |
| } | |
| return dst_tk; | |
| } | |
| static u32 merge_hevc_config(GF_ISOFile *dest, u32 tk_id, GF_ISOFile **o_orig, u32 src_track, Bool force_cat, u32 *orig_nal_len, u32 *dst_nal_len) | |
| { | |
| u32 i; | |
| GF_HEVCConfig *hevc_src, *hevc_dst; | |
| GF_ISOFile *orig = *o_orig; | |
| u32 dst_tk = gf_isom_get_track_by_id(dest, tk_id); | |
| hevc_src = gf_isom_hevc_config_get(orig, src_track, 1); | |
| hevc_dst = gf_isom_hevc_config_get(dest, dst_tk, 1); | |
| if (hevc_src->profile_idc != hevc_dst->profile_idc) dst_tk = 0; | |
| else if (hevc_src->level_idc != hevc_dst->level_idc) dst_tk = 0; | |
| else if (hevc_src->general_profile_compatibility_flags != hevc_dst->general_profile_compatibility_flags ) dst_tk = 0; | |
| else { | |
| /*rewrite all samples if using different NALU size*/ | |
| if (hevc_src->nal_unit_size > hevc_dst->nal_unit_size) { | |
| gf_media_nal_rewrite_samples(dest, dst_tk, 8*hevc_src->nal_unit_size); | |
| hevc_dst->nal_unit_size = hevc_src->nal_unit_size; | |
| } else if (hevc_src->nal_unit_size < hevc_dst->nal_unit_size) { | |
| *orig_nal_len = hevc_src->nal_unit_size; | |
| *dst_nal_len = hevc_dst->nal_unit_size; | |
| } | |
| /*merge PS*/ | |
| for (i=0; i<gf_list_count(hevc_src->param_array); i++) { | |
| u32 k; | |
| GF_NALUFFParamArray *src_ar = gf_list_get(hevc_src->param_array, i); | |
| for (k=0; k<gf_list_count(hevc_dst->param_array); k++) { | |
| GF_NALUFFParamArray *dst_ar = gf_list_get(hevc_dst->param_array, k); | |
| if (dst_ar->type==src_ar->type) { | |
| if (!merge_parameter_set(src_ar->nalus, dst_ar->nalus, "SPS")) | |
| dst_tk = 0; | |
| break; | |
| } | |
| } | |
| } | |
| gf_isom_hevc_config_update(dest, dst_tk, 1, hevc_dst); | |
| } | |
| gf_odf_hevc_cfg_del(hevc_src); | |
| gf_odf_hevc_cfg_del(hevc_dst); | |
| if (!dst_tk) { | |
| dst_tk = gf_isom_get_track_by_id(dest, tk_id); | |
| gf_isom_set_nalu_extract_mode(orig, src_track, GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG); | |
| if (!force_cat) { | |
| gf_isom_hevc_set_inband_config(dest, dst_tk, 1, GF_TRUE); | |
| } else { | |
| M4_LOG(GF_LOG_WARNING, ("WARNING: Concatenating track ID %d even though sample descriptions do not match\n", tk_id)); | |
| } | |
| } | |
| return dst_tk; | |
| } | |
| static GF_Err rewrite_nal_size_field(GF_ISOSample *samp, u32 orig_nal_len, u32 dst_nal_len) | |
| { | |
| GF_Err e = GF_OK; | |
| u32 msize=0, remain = samp->dataLength; | |
| GF_BitStream *oldbs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ); | |
| GF_BitStream *newbs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| u8 *buffer = NULL; | |
| while (remain) { | |
| u32 size = gf_bs_read_int(oldbs, orig_nal_len*8); | |
| if (size > remain) { | |
| e = GF_ISOM_INVALID_FILE; | |
| goto exit; | |
| } | |
| gf_bs_write_int(newbs, size, dst_nal_len*8); | |
| remain -= orig_nal_len; | |
| if (size > msize) { | |
| msize = size; | |
| buffer = (u8*)gf_realloc(buffer, sizeof(u8)*msize); | |
| if (!buffer) { | |
| e = GF_OUT_OF_MEM; | |
| goto exit; | |
| } | |
| } | |
| gf_bs_read_data(oldbs, buffer, size); | |
| gf_bs_write_data(newbs, buffer, size); | |
| remain -= size; | |
| } | |
| gf_free(samp->data); | |
| samp->data = NULL; | |
| samp->dataLength = 0; | |
| gf_bs_get_content(newbs, &samp->data, &samp->dataLength); | |
| exit: | |
| gf_bs_del(newbs); | |
| gf_bs_del(oldbs); | |
| if (buffer) gf_free(buffer); | |
| return e; | |
| } | |
| GF_Err cat_playlist(GF_ISOFile *dest, char *playlistName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command); | |
| GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command, Bool is_pl) | |
| { | |
| u32 i, j, count, nb_tracks, nb_samp, nb_done; | |
| GF_ISOFile *orig; | |
| GF_Err e; | |
| char *opts, *multi_cat; | |
| Double ts_scale, media_ts_scale; | |
| Double dest_orig_dur; | |
| u32 dst_tk, tk_id, mtype; | |
| u64 insert_dts; | |
| Bool is_isom; | |
| GF_ISOSample *samp; | |
| GF_Fraction64 aligned_to_DTS_frac; | |
| if (is_pl) return cat_playlist(dest, fileName, import_flags, force_fps, frames_per_sample, force_cat, align_timelines, allow_add_in_command); | |
| if (strchr(fileName, '*') || (strchr(fileName, '@')) ) | |
| return cat_multiple_files(dest, fileName, import_flags, force_fps, frames_per_sample, force_cat, align_timelines, allow_add_in_command); | |
| multi_cat = allow_add_in_command ? strchr(fileName, '+') : NULL; | |
| if (multi_cat) { | |
| multi_cat[0] = 0; | |
| multi_cat = &multi_cat[1]; | |
| } | |
| opts = gf_url_colon_suffix(fileName, '='); | |
| e = GF_OK; | |
| /*if options are specified, re-import the file*/ | |
| if (opts) opts[0] = 0; | |
| is_isom = gf_isom_probe_file(fileName); | |
| if (!is_isom || multi_cat) { | |
| orig = gf_isom_open("temp", GF_ISOM_WRITE_EDIT, NULL); | |
| if (opts) opts[0] = ':'; | |
| e = import_file(orig, fileName, import_flags, force_fps, frames_per_sample, NULL, NULL, NULL, 0); | |
| if (e) { | |
| gf_isom_delete(orig); | |
| return e; | |
| } | |
| } else { | |
| //open read+edit mode to allow applying options on file | |
| orig = gf_isom_open(fileName, GF_ISOM_OPEN_READ_EDIT, NULL); | |
| if (opts) opts[0] = ':'; | |
| if (!orig) return gf_isom_last_error(NULL); | |
| if (opts) { | |
| e = import_file(orig, fileName, 0xFFFFFFFF, force_fps, frames_per_sample, NULL, NULL, NULL, 0); | |
| if (e) { | |
| gf_isom_delete(orig); | |
| return e; | |
| } | |
| } | |
| } | |
| while (multi_cat) { | |
| char *sep = strchr(multi_cat, '+'); | |
| if (sep) sep[0] = 0; | |
| e = import_file(orig, multi_cat, import_flags, force_fps, frames_per_sample, NULL, NULL, NULL, 0); | |
| if (e) { | |
| gf_isom_delete(orig); | |
| return e; | |
| } | |
| if (!sep) break; | |
| sep[0]=':'; | |
| multi_cat = sep+1; | |
| } | |
| nb_samp = 0; | |
| nb_tracks = gf_isom_get_track_count(orig); | |
| for (i=0; i<nb_tracks; i++) { | |
| mtype = gf_isom_get_media_type(orig, i+1); | |
| switch (mtype) { | |
| case GF_ISOM_MEDIA_HINT: | |
| case GF_ISOM_MEDIA_OD: | |
| case GF_ISOM_MEDIA_FLASH: | |
| M4_LOG(GF_LOG_WARNING, ("WARNING: Track ID %d (type %s) not handled by concatenation - removing from destination\n", gf_isom_get_track_id(orig, i+1), gf_4cc_to_str(mtype))); | |
| continue; | |
| case GF_ISOM_MEDIA_AUDIO: | |
| case GF_ISOM_MEDIA_TEXT: | |
| case GF_ISOM_MEDIA_SUBT: | |
| case GF_ISOM_MEDIA_MPEG_SUBT: | |
| case GF_ISOM_MEDIA_VISUAL: | |
| case GF_ISOM_MEDIA_AUXV: | |
| case GF_ISOM_MEDIA_PICT: | |
| case GF_ISOM_MEDIA_SCENE: | |
| case GF_ISOM_MEDIA_OCR: | |
| case GF_ISOM_MEDIA_OCI: | |
| case GF_ISOM_MEDIA_IPMP: | |
| case GF_ISOM_MEDIA_MPEGJ: | |
| case GF_ISOM_MEDIA_MPEG7: | |
| default: | |
| /*only cat self-contained files*/ | |
| if (gf_isom_is_self_contained(orig, i+1, 1)) { | |
| nb_samp+= gf_isom_get_sample_count(orig, i+1); | |
| break; | |
| } | |
| break; | |
| } | |
| } | |
| if (!nb_samp) { | |
| M4_LOG(GF_LOG_ERROR, ("No suitable media tracks to cat in %s - skipping\n", fileName)); | |
| goto err_exit; | |
| } | |
| dest_orig_dur = (Double) (s64) gf_isom_get_duration(dest); | |
| if (!gf_isom_get_timescale(dest)) { | |
| gf_isom_set_timescale(dest, gf_isom_get_timescale(orig)); | |
| } | |
| dest_orig_dur /= gf_isom_get_timescale(dest); | |
| aligned_to_DTS_frac.num = 0; | |
| aligned_to_DTS_frac.den = 1; | |
| for (i=0; i<gf_isom_get_track_count(dest); i++) { | |
| u32 last_samp = gf_isom_get_sample_count(dest, i+1); | |
| if (!last_samp) continue; | |
| //get next sample DTS - we do NOT use media duration as CTS offset could shift up everything | |
| u64 track_dur = gf_isom_get_sample_dts(dest, i+1, last_samp); | |
| track_dur += gf_isom_get_sample_duration(dest, i+1, last_samp); | |
| u32 track_ts = gf_isom_get_media_timescale(dest, i+1); | |
| if (gf_timestamp_less(aligned_to_DTS_frac.num, aligned_to_DTS_frac.den, track_dur, track_ts)) { | |
| aligned_to_DTS_frac.num = track_dur; | |
| aligned_to_DTS_frac.den = track_ts; | |
| } | |
| } | |
| M4_LOG(GF_LOG_INFO, ("Appending file %s\n", fileName)); | |
| nb_done = 0; | |
| for (i=0; i<nb_tracks; i++) { | |
| u64 last_DTS, dest_track_dur_before_cat; | |
| u32 nb_edits = 0; | |
| Bool merge_nal_video = 1; | |
| Bool skip_lang_test = 1; | |
| Bool use_ts_dur = 1; | |
| Bool merge_edits = 0; | |
| Bool new_track = 0; | |
| u32 dst_tk_sample_entry = 0; | |
| u32 orig_nal_len = 0; | |
| u32 dst_nal_len = 0; | |
| mtype = gf_isom_get_media_type(orig, i+1); | |
| switch (mtype) { | |
| case GF_ISOM_MEDIA_HINT: | |
| case GF_ISOM_MEDIA_OD: | |
| case GF_ISOM_MEDIA_FLASH: | |
| continue; | |
| case GF_ISOM_MEDIA_TEXT: | |
| case GF_ISOM_MEDIA_SUBT: | |
| case GF_ISOM_MEDIA_MPEG_SUBT: | |
| case GF_ISOM_MEDIA_SCENE: | |
| case GF_ISOM_MEDIA_TIMECODE: | |
| use_ts_dur = 0; | |
| case GF_ISOM_MEDIA_AUDIO: | |
| case GF_ISOM_MEDIA_VISUAL: | |
| case GF_ISOM_MEDIA_PICT: | |
| case GF_ISOM_MEDIA_OCR: | |
| case GF_ISOM_MEDIA_OCI: | |
| case GF_ISOM_MEDIA_IPMP: | |
| case GF_ISOM_MEDIA_MPEGJ: | |
| case GF_ISOM_MEDIA_MPEG7: | |
| default: | |
| if (!gf_isom_is_self_contained(orig, i+1, 1)) continue; | |
| break; | |
| } | |
| dst_tk = 0; | |
| /*if we had a temporary import of the file, check if the original track ID matches the dst one. If so, skip all language detection code*/ | |
| tk_id = gf_isom_get_track_original_id(orig, i+1); | |
| if (!tk_id) { | |
| tk_id = gf_isom_get_track_id(orig, i+1); | |
| skip_lang_test = 0; | |
| } | |
| dst_tk = gf_isom_get_track_by_id(dest, tk_id); | |
| if (dst_tk) { | |
| if (mtype != gf_isom_get_media_type(dest, dst_tk)) | |
| dst_tk = 0; | |
| else { | |
| u32 subtype_dst = gf_isom_get_media_subtype(dest, dst_tk, 1); | |
| u32 subtype_src = gf_isom_get_media_subtype(orig, i+1, 1); | |
| if (subtype_dst==GF_ISOM_SUBTYPE_AVC3_H264) | |
| subtype_dst=GF_ISOM_SUBTYPE_AVC_H264 ; | |
| if (subtype_src==GF_ISOM_SUBTYPE_AVC3_H264) | |
| subtype_src=GF_ISOM_SUBTYPE_AVC_H264; | |
| if (subtype_dst==GF_ISOM_SUBTYPE_HEV1) | |
| subtype_dst=GF_ISOM_SUBTYPE_HVC1; | |
| if (subtype_src==GF_ISOM_SUBTYPE_HEV1) | |
| subtype_src=GF_ISOM_SUBTYPE_HVC1; | |
| if (subtype_dst==GF_ISOM_SUBTYPE_VVI1) | |
| subtype_dst=GF_ISOM_SUBTYPE_VVC1; | |
| if (subtype_src==GF_ISOM_SUBTYPE_VVI1) | |
| subtype_src=GF_ISOM_SUBTYPE_VVC1; | |
| if (subtype_dst != subtype_src) { | |
| dst_tk_sample_entry = dst_tk; | |
| dst_tk = 0; | |
| merge_nal_video=0; | |
| } | |
| } | |
| } | |
| if (!dst_tk) { | |
| for (j=0; j<gf_isom_get_track_count(dest); j++) { | |
| if (mtype != gf_isom_get_media_type(dest, j+1)) continue; | |
| if (gf_isom_is_same_sample_description(orig, i+1, 0, dest, j+1, 0)) { | |
| if (gf_isom_is_video_handler_type(mtype) ) { | |
| u32 w, h, ow, oh; | |
| gf_isom_get_visual_info(orig, i+1, 1, &ow, &oh); | |
| gf_isom_get_visual_info(dest, j+1, 1, &w, &h); | |
| if ((ow==w) && (oh==h)) { | |
| dst_tk = j+1; | |
| dst_tk_sample_entry = 0; | |
| break; | |
| } | |
| } | |
| /*check language code*/ | |
| else if (!skip_lang_test && (mtype==GF_ISOM_MEDIA_AUDIO)) { | |
| u32 lang_src, lang_dst; | |
| char *lang = NULL; | |
| gf_isom_get_media_language(orig, i+1, &lang); | |
| if (lang) { | |
| lang_src = GF_4CC(lang[0], lang[1], lang[2], lang[3]); | |
| gf_free(lang); | |
| } else { | |
| lang_src = 0; | |
| } | |
| gf_isom_get_media_language(dest, j+1, &lang); | |
| if (lang) { | |
| lang_dst = GF_4CC(lang[0], lang[1], lang[2], lang[3]); | |
| gf_free(lang); | |
| } else { | |
| lang_dst = 0; | |
| } | |
| if (lang_dst==lang_src) { | |
| dst_tk = j+1; | |
| dst_tk_sample_entry = 0; | |
| break; | |
| } | |
| } else { | |
| dst_tk = j+1; | |
| dst_tk_sample_entry = 0; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if (dst_tk_sample_entry) | |
| dst_tk = dst_tk_sample_entry; | |
| if (dst_tk) { | |
| u32 found_dst_tk = dst_tk; | |
| u32 stype = gf_isom_get_media_subtype(dest, dst_tk, 1); | |
| /*we MUST have the same codec*/ | |
| if (gf_isom_get_media_subtype(orig, i+1, 1) != stype) dst_tk = 0; | |
| /*we only support cat with the same number of sample descriptions*/ | |
| if (gf_isom_get_sample_description_count(orig, i+1) != gf_isom_get_sample_description_count(dest, dst_tk)) dst_tk = 0; | |
| /*if not forcing cat, check the media codec config is the same*/ | |
| if (!gf_isom_is_same_sample_description(orig, i+1, 0, dest, dst_tk, 0)) { | |
| //we will need to merge the same descriptions | |
| if (!force_cat && !dst_tk_sample_entry) dst_tk_sample_entry = dst_tk; | |
| dst_tk = 0; | |
| } | |
| /*we force the same visual resolution*/ | |
| else if (gf_isom_is_video_handler_type(mtype) ) { | |
| u32 w, h, ow, oh; | |
| gf_isom_get_visual_info(orig, i+1, 1, &ow, &oh); | |
| gf_isom_get_visual_info(dest, dst_tk, 1, &w, &h); | |
| if ((ow!=w) || (oh!=h)) { | |
| dst_tk = 0; | |
| } | |
| } | |
| if (!dst_tk && merge_nal_video) { | |
| /*merge AVC config if possible*/ | |
| if ((stype == GF_ISOM_SUBTYPE_AVC_H264) | |
| || (stype == GF_ISOM_SUBTYPE_AVC2_H264) | |
| || (stype == GF_ISOM_SUBTYPE_AVC3_H264) | |
| || (stype == GF_ISOM_SUBTYPE_AVC4_H264) ) { | |
| dst_tk = merge_avc_config(dest, tk_id, &orig, i+1, force_cat, &orig_nal_len, &dst_nal_len); | |
| } | |
| /*merge HEVC config if possible*/ | |
| else if ((stype == GF_ISOM_SUBTYPE_HVC1) | |
| || (stype == GF_ISOM_SUBTYPE_HEV1) | |
| || (stype == GF_ISOM_SUBTYPE_HVC2) | |
| || (stype == GF_ISOM_SUBTYPE_HEV2)) { | |
| dst_tk = merge_hevc_config(dest, tk_id, &orig, i+1, force_cat, &orig_nal_len, &dst_nal_len); | |
| } | |
| else if (force_cat) { | |
| dst_tk = found_dst_tk; | |
| } | |
| } | |
| } | |
| if (dst_tk_sample_entry && !dst_tk) { | |
| u32 k, nb_sample_desc = gf_isom_get_sample_description_count(orig, i+1); | |
| dst_tk = dst_tk_sample_entry; | |
| M4_LOG(GF_LOG_INFO, ("Multiple sample entry required, merging\n")); | |
| for (k=0; k<nb_sample_desc; k++) { | |
| u32 sdesc_idx; | |
| e = gf_isom_clone_sample_description(dest, dst_tk, orig, i+1, k+1, NULL, NULL, &sdesc_idx); | |
| if (e) goto err_exit; | |
| //remember idx of first new added sample desc | |
| if (!k) | |
| dst_tk_sample_entry = sdesc_idx-1; | |
| } | |
| } else { | |
| dst_tk_sample_entry = 0; | |
| } | |
| /*looks like a new track*/ | |
| if (!dst_tk) { | |
| M4_LOG(GF_LOG_INFO, ("No suitable destination track found - creating new one (type %s)\n", gf_4cc_to_str(mtype))); | |
| e = gf_isom_clone_track(orig, i+1, dest, 0, &dst_tk); | |
| if (e) goto err_exit; | |
| gf_isom_clone_pl_indications(orig, dest); | |
| new_track = 1; | |
| if (align_timelines) { | |
| u32 max_timescale = 0; | |
| // u32 dst_timescale = 0; | |
| u32 idx; | |
| for (idx=0; idx<nb_tracks; idx++) { | |
| if (max_timescale < gf_isom_get_media_timescale(orig, idx+1)) | |
| max_timescale = gf_isom_get_media_timescale(orig, idx+1); | |
| } | |
| #if 0 | |
| if (dst_timescale < max_timescale) { | |
| dst_timescale = gf_isom_get_media_timescale(dest, dst_tk); | |
| idx = max_timescale / dst_timescale; | |
| if (dst_timescale * idx < max_timescale) idx ++; | |
| dst_timescale *= idx; | |
| gf_isom_set_media_timescale(dest, dst_tk, max_timescale, 0, 0); | |
| } | |
| #else | |
| gf_isom_set_media_timescale(dest, dst_tk, max_timescale, 0, 0); | |
| #endif | |
| } | |
| /*remove cloned edit list, as it will be rewritten after import*/ | |
| gf_isom_remove_edits(dest, dst_tk); | |
| } else { | |
| nb_edits = gf_isom_get_edits_count(orig, i+1); | |
| } | |
| dest_track_dur_before_cat = gf_isom_get_media_duration(dest, dst_tk); | |
| count = gf_isom_get_sample_count(dest, dst_tk); | |
| if (align_timelines) { | |
| insert_dts = (u64) (aligned_to_DTS_frac.num * gf_isom_get_media_timescale(dest, dst_tk)); | |
| insert_dts /= aligned_to_DTS_frac.den; | |
| } else if (use_ts_dur && (count>1)) { | |
| insert_dts = 2*gf_isom_get_sample_dts(dest, dst_tk, count) - gf_isom_get_sample_dts(dest, dst_tk, count-1); | |
| } else { | |
| insert_dts = dest_track_dur_before_cat; | |
| if (!count) insert_dts = 0; | |
| } | |
| ts_scale = gf_isom_get_media_timescale(dest, dst_tk); | |
| ts_scale /= gf_isom_get_media_timescale(orig, i+1); | |
| media_ts_scale = gf_isom_get_media_timescale(dest, dst_tk); | |
| media_ts_scale /= gf_isom_get_media_timescale(orig, i+1); | |
| /*if not a new track, see if we can merge the edit list - this is a crude test that only checks | |
| we have the same edit types*/ | |
| if (nb_edits && (nb_edits == gf_isom_get_edits_count(dest, dst_tk)) ) { | |
| u64 editTime, segmentDuration, mediaTime, dst_editTime, dst_segmentDuration, dst_mediaTime; | |
| GF_ISOEditType dst_editMode, editMode; | |
| merge_edits = 1; | |
| for (j=0; j<nb_edits; j++) { | |
| gf_isom_get_edit(orig, i+1, j+1, &editTime, &segmentDuration, &mediaTime, &editMode); | |
| gf_isom_get_edit(dest, dst_tk, j+1, &dst_editTime, &dst_segmentDuration, &dst_mediaTime, &dst_editMode); | |
| if (dst_editMode!=editMode) { | |
| merge_edits=0; | |
| break; | |
| } | |
| } | |
| } | |
| gf_isom_enable_raw_pack(orig, i+1, 2048); | |
| Bool adjust_first_sample=GF_FALSE; | |
| if (count && (gf_isom_get_sample_duration(dest, dst_tk, count)==0)) { | |
| adjust_first_sample = GF_TRUE; | |
| } | |
| last_DTS = 0; | |
| count = gf_isom_get_sample_count(orig, i+1); | |
| for (j=0; j<count; j++) { | |
| u32 di; | |
| samp = gf_isom_get_sample(orig, i+1, j+1, &di); | |
| if (!samp) { | |
| e = gf_isom_last_error(orig); | |
| if (!e) e = GF_OUT_OF_MEM; | |
| goto err_exit; | |
| } | |
| last_DTS = samp->DTS; | |
| samp->DTS = (u64) (ts_scale * samp->DTS + (new_track ? 0 : insert_dts)); | |
| samp->CTS_Offset = (u32) (samp->CTS_Offset * ts_scale); | |
| if (adjust_first_sample) { | |
| samp->DTS++; | |
| adjust_first_sample = 0; | |
| } | |
| if (gf_isom_is_self_contained(orig, i+1, di)) { | |
| if (orig_nal_len && dst_nal_len) { | |
| e = rewrite_nal_size_field(samp, orig_nal_len, dst_nal_len); | |
| if (e) { | |
| gf_isom_sample_del(&samp); | |
| goto err_exit; | |
| } | |
| } | |
| e = gf_isom_add_sample(dest, dst_tk, di + dst_tk_sample_entry, samp); | |
| } else { | |
| u64 offset; | |
| GF_ISOSample *s = gf_isom_get_sample_info(orig, i+1, j+1, &di, &offset); | |
| e = gf_isom_add_sample_reference(dest, dst_tk, di + dst_tk_sample_entry, samp, offset); | |
| gf_isom_sample_del(&s); | |
| } | |
| if (samp->nb_pack) | |
| j+= samp->nb_pack-1; | |
| if (samp->data && !samp->dataLength) { | |
| // force deletetion, see gf_isom_sample_del() | |
| samp->dataLength = samp->alloc_size; | |
| } | |
| gf_isom_sample_del(&samp); | |
| if (e) goto err_exit; | |
| e = gf_isom_copy_sample_info(dest, dst_tk, orig, i+1, j+1); | |
| if (e) goto err_exit; | |
| if (j+1==count) { | |
| u32 sdur = gf_isom_get_sample_duration(orig, i+1, j+1); | |
| gf_isom_set_last_sample_duration(dest, dst_tk, (u32) (ts_scale * sdur) ); | |
| } | |
| gf_set_progress("Appending", nb_done, nb_samp); | |
| nb_done++; | |
| } | |
| gf_isom_update_duration(dest); | |
| /*scene description and text: compute last sample duration based on original media duration*/ | |
| if (!use_ts_dur) { | |
| u64 extend_dur = gf_isom_get_media_duration(orig, i+1) - last_DTS; | |
| //extend the duration of the last sample, but don't insert any edit list entry | |
| gf_isom_set_last_sample_duration(dest, dst_tk, (u32) (ts_scale*extend_dur) ); | |
| } | |
| if (new_track && insert_dts) { | |
| u64 media_dur = gf_isom_get_media_duration(orig, i+1); | |
| /*convert from media time to track time*/ | |
| Double rescale = (Float) gf_isom_get_timescale(dest); | |
| rescale /= (Float) gf_isom_get_media_timescale(dest, dst_tk); | |
| /*convert from orig to dst time scale*/ | |
| rescale *= ts_scale; | |
| gf_isom_set_edit(dest, dst_tk, 0, (u64) (s64) (insert_dts*rescale), 0, GF_ISOM_EDIT_EMPTY); | |
| gf_isom_set_edit(dest, dst_tk, (u64) (s64) (insert_dts*rescale), (u64) (s64) (media_dur*rescale), 0, GF_ISOM_EDIT_NORMAL); | |
| } else if (merge_edits) { | |
| /*get the first edit normal mode and add the new track dur*/ | |
| for (j=nb_edits; j>0; j--) { | |
| u64 editTime, segmentDuration, mediaTime; | |
| GF_ISOEditType editMode; | |
| gf_isom_get_edit(dest, dst_tk, j, &editTime, &segmentDuration, &mediaTime, &editMode); | |
| if (editMode==GF_ISOM_EDIT_NORMAL) { | |
| u64 new_dur = gf_isom_get_media_duration(dest, dst_tk); | |
| if ((mediaTime>0) && (mediaTime<new_dur)) | |
| new_dur -= mediaTime; | |
| /*convert from media time to track/moov time*/ | |
| u32 movts_dst = gf_isom_get_timescale(dest); | |
| u32 trackts_dst = gf_isom_get_media_timescale(dest, dst_tk); | |
| new_dur = gf_timestamp_rescale(new_dur, trackts_dst, movts_dst); | |
| gf_isom_modify_edit(dest, dst_tk, j, new_dur, mediaTime, editMode); | |
| break; | |
| } | |
| } | |
| } else { | |
| u64 editTime, segmentDuration, mediaTime, edit_offset; | |
| Double t; | |
| GF_ISOEditType editMode; | |
| count = gf_isom_get_edits_count(dest, dst_tk); | |
| if (count) { | |
| e = gf_isom_get_edit(dest, dst_tk, count, &editTime, &segmentDuration, &mediaTime, &editMode); | |
| if (e) { | |
| M4_LOG(GF_LOG_ERROR, ("Error: edit segment error on destination track %u could not be retrieved.\n", dst_tk)); | |
| goto err_exit; | |
| } | |
| } else if (gf_isom_get_edits_count(orig, i+1)) { | |
| /*fake empty edit segment*/ | |
| /*convert from media time to track time*/ | |
| Double rescale = (Float) gf_isom_get_timescale(dest); | |
| rescale /= (Float) gf_isom_get_media_timescale(dest, dst_tk); | |
| segmentDuration = (u64) (dest_track_dur_before_cat * rescale); | |
| editTime = 0; | |
| mediaTime = 0; | |
| if (segmentDuration) | |
| gf_isom_set_edit(dest, dst_tk, editTime, segmentDuration, mediaTime, GF_ISOM_EDIT_NORMAL); | |
| } else { | |
| mediaTime = 0; | |
| editTime = 0; | |
| segmentDuration = 0; | |
| } | |
| /*convert to dst time scale*/ | |
| ts_scale = (Float) gf_isom_get_timescale(dest); | |
| ts_scale /= (Float) gf_isom_get_timescale(orig); | |
| edit_offset = editTime + segmentDuration; | |
| count = gf_isom_get_edits_count(orig, i+1); | |
| for (j=0; j<count; j++) { | |
| gf_isom_get_edit(orig, i+1, j+1, &editTime, &segmentDuration, &mediaTime, &editMode); | |
| t = (Double) (s64) editTime; | |
| t *= ts_scale; | |
| t += (s64) edit_offset; | |
| editTime = (s64) t; | |
| t = (Double) (s64) segmentDuration; | |
| t *= ts_scale; | |
| segmentDuration = (s64) t; | |
| t = (Double) (s64) mediaTime; | |
| //we are in media time, apply media ts scale, not file ts scale | |
| t *= media_ts_scale; | |
| t+= (s64) dest_track_dur_before_cat; | |
| mediaTime = (s64) t; | |
| if ((editMode == GF_ISOM_EDIT_EMPTY) && (mediaTime > 0)) { | |
| editMode = GF_ISOM_EDIT_NORMAL; | |
| } | |
| gf_isom_set_edit(dest, dst_tk, editTime, segmentDuration, mediaTime, editMode); | |
| } | |
| //no edit in added track, add duration | |
| if (!count && (segmentDuration || mediaTime) && gf_isom_get_edits_count(dest, dst_tk)) { | |
| t = (Double) (s64) gf_isom_get_media_duration(orig, i+1); | |
| t *= gf_isom_get_timescale(dest); | |
| t /= gf_isom_get_media_timescale(orig, i+1); | |
| gf_isom_set_edit(dest, dst_tk, editTime, segmentDuration+(u64) t, mediaTime, editMode); | |
| } | |
| } | |
| gf_media_update_bitrate(dest, dst_tk); | |
| } | |
| for (i = 0; i < gf_isom_get_track_count(dest); ++i) { | |
| if (gf_isom_get_media_type(dest, i+1) == GF_ISOM_MEDIA_TIMECODE) { | |
| u32 video_ref = 0; | |
| for (j = 0; j < gf_isom_get_track_count(dest); ++j) { | |
| if (gf_isom_is_video_handler_type(gf_isom_get_media_type(dest, j+1))) { | |
| video_ref = j+1; | |
| break; | |
| } | |
| } | |
| if (video_ref) { | |
| u64 video_ref_dur = gf_isom_get_media_duration(dest, video_ref); | |
| u32 last_sample_dur = gf_isom_get_sample_duration(dest, i+1, gf_isom_get_sample_count(dest, i+1)); | |
| u64 dur = video_ref_dur - gf_isom_get_media_duration(dest, i+1) + last_sample_dur; | |
| gf_isom_set_last_sample_duration(dest, i+1, (u32)dur); | |
| } | |
| } | |
| } | |
| gf_set_progress("Appending", nb_samp, nb_samp); | |
| /*check brands*/ | |
| gf_isom_get_brand_info(orig, NULL, NULL, &j); | |
| for (i=0; i<j; i++) { | |
| u32 brand; | |
| gf_isom_get_alternate_brand(orig, i+1, &brand); | |
| gf_isom_modify_alternate_brand(dest, brand, GF_TRUE); | |
| } | |
| /*check chapters*/ | |
| for (i=0; i<gf_isom_get_chapter_count(orig, 0); i++) { | |
| char *name; | |
| Double c_time; | |
| u64 chap_time; | |
| gf_isom_get_chapter(orig, 0, i+1, &chap_time, (const char **) &name); | |
| c_time = (Double) (s64) chap_time; | |
| c_time /= 1000; | |
| c_time += dest_orig_dur; | |
| /*check last file chapter*/ | |
| if (!i && gf_isom_get_chapter_count(dest, 0)) { | |
| const char *last_name; | |
| u64 last_chap_time; | |
| gf_isom_get_chapter(dest, 0, gf_isom_get_chapter_count(dest, 0), &last_chap_time, &last_name); | |
| /*last and first chapters are the same, don't duplicate*/ | |
| if (last_name && name && !stricmp(last_name, name)) continue; | |
| } | |
| chap_time = (u64) (c_time*1000); | |
| gf_isom_add_chapter(dest, 0, chap_time, name); | |
| } | |
| err_exit: | |
| gf_isom_delete(orig); | |
| return e; | |
| } | |
| typedef struct | |
| { | |
| char szPath[GF_MAX_PATH]; | |
| char szRad1[1024], szRad2[1024], szOpt[200]; | |
| GF_ISOFile *dest; | |
| u32 import_flags; | |
| GF_Fraction force_fps; | |
| u32 frames_per_sample; | |
| Bool force_cat, align_timelines, allow_add_in_command; | |
| } CATEnum; | |
| Bool cat_enumerate(void *cbk, char *szName, char *szPath, GF_FileEnumInfo *file_info) | |
| { | |
| GF_Err e; | |
| u32 len_rad1; | |
| char szFileName[GF_MAX_PATH]; | |
| CATEnum *cat_enum = (CATEnum *)cbk; | |
| len_rad1 = (u32) strlen(cat_enum->szRad1); | |
| if (strnicmp(szName, cat_enum->szRad1, len_rad1)) return 0; | |
| if (strlen(cat_enum->szRad2) && !strstr(szName + len_rad1, cat_enum->szRad2) ) return 0; | |
| gf_strcpy(szFileName, szPath); | |
| gf_strcat(szFileName, cat_enum->szOpt); | |
| e = cat_isomedia_file(cat_enum->dest, szFileName, cat_enum->import_flags, cat_enum->force_fps, cat_enum->frames_per_sample, cat_enum->force_cat, cat_enum->align_timelines, cat_enum->allow_add_in_command, GF_FALSE); | |
| if (e) return 1; | |
| return 0; | |
| } | |
| GF_Err cat_multiple_files(GF_ISOFile *dest, char *fileName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command) | |
| { | |
| CATEnum cat_enum; | |
| char *sep; | |
| cat_enum.dest = dest; | |
| cat_enum.import_flags = import_flags; | |
| cat_enum.force_fps = force_fps; | |
| cat_enum.frames_per_sample = frames_per_sample; | |
| cat_enum.force_cat = force_cat; | |
| cat_enum.align_timelines = align_timelines; | |
| cat_enum.allow_add_in_command = allow_add_in_command; | |
| if (strlen(fileName) >= sizeof(cat_enum.szPath)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", fileName)); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| gf_strcpy(cat_enum.szPath, fileName); | |
| sep = strrchr(cat_enum.szPath, GF_PATH_SEPARATOR); | |
| if (!sep) sep = strrchr(cat_enum.szPath, '/'); | |
| if (!sep) { | |
| gf_strcpy(cat_enum.szPath, "."); | |
| if (strlen(fileName) >= sizeof(cat_enum.szRad1)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", fileName)); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| gf_strcpy(cat_enum.szRad1, fileName); | |
| } else { | |
| if (strlen(sep + 1) >= sizeof(cat_enum.szRad1)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is too long.\n", (sep + 1))); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| gf_strcpy(cat_enum.szRad1, sep+1); | |
| sep[0] = 0; | |
| } | |
| sep = strchr(cat_enum.szRad1, '*'); | |
| if (!sep) sep = strchr(cat_enum.szRad1, '@'); | |
| if (!sep || strlen(sep + 1) >= sizeof(cat_enum.szRad2)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("File name %s is invald.\n", cat_enum.szRad1)); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| gf_strcpy(cat_enum.szRad2, sep+1); | |
| sep[0] = 0; | |
| sep = NULL; | |
| if (gf_sys_old_arch_compat()) sep = strchr(cat_enum.szRad2, '%'); | |
| if (!sep) sep = strchr(cat_enum.szRad2, '#'); | |
| if (!sep) sep = gf_url_colon_suffix(cat_enum.szRad2, '='); | |
| gf_strcpy(cat_enum.szOpt, ""); | |
| if (sep) { | |
| if (strlen(sep) >= sizeof(cat_enum.szOpt)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Invalid option: %s.\n", sep)); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| gf_strcpy(cat_enum.szOpt, sep); | |
| sep[0] = 0; | |
| } | |
| return gf_enum_directory(cat_enum.szPath, 0, cat_enumerate, &cat_enum, NULL); | |
| } | |
| GF_Err cat_playlist(GF_ISOFile *dest, char *playlistName, u32 import_flags, GF_Fraction force_fps, u32 frames_per_sample, Bool force_cat, Bool align_timelines, Bool allow_add_in_command) | |
| { | |
| GF_Err e; | |
| FILE *pl = gf_fopen(playlistName, "r"); | |
| if (!pl) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to open playlist file %s\n", playlistName)); | |
| return GF_URL_ERROR; | |
| } | |
| e = GF_OK; | |
| while (!feof(pl)) { | |
| char szLine[1000]; | |
| char *url; | |
| u32 len; | |
| szLine[0] = 0; | |
| if (gf_fgets(szLine, 1000, pl) == NULL) break; | |
| if (szLine[0]=='#') continue; | |
| len = (u32) strlen(szLine); | |
| while (len && strchr("\r\n \t", szLine[len-1])) { | |
| szLine[len-1] = 0; | |
| len--; | |
| } | |
| if (!len) continue; | |
| url = gf_url_concatenate(playlistName, szLine); | |
| if (!url) url = gf_strdup(szLine); | |
| e = cat_isomedia_file(dest, url, import_flags, force_fps, frames_per_sample, force_cat, align_timelines, allow_add_in_command, GF_FALSE); | |
| if (e) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to concatenate file %s\n", url)); | |
| gf_free(url); | |
| break; | |
| } | |
| gf_free(url); | |
| } | |
| gf_fclose(pl); | |
| return e; | |
| } | |
| #ifndef GPAC_DISABLE_SCENE_ENCODER | |
| /* | |
| MPEG-4 encoding | |
| */ | |
| GF_Err EncodeFile(char *in, GF_ISOFile *mp4, GF_SMEncodeOptions *opts, FILE *logs) | |
| { | |
| #ifdef GPAC_DISABLE_SMGR | |
| return GF_NOT_SUPPORTED; | |
| #else | |
| GF_Err e; | |
| GF_SceneLoader load; | |
| GF_SceneManager *ctx; | |
| GF_SceneGraph *sg; | |
| #ifndef GPAC_DISABLE_SCENE_STATS | |
| GF_StatManager *statsman = NULL; | |
| #endif | |
| sg = gf_sg_new(); | |
| ctx = gf_sm_new(sg); | |
| memset(&load, 0, sizeof(GF_SceneLoader)); | |
| load.fileName = in; | |
| load.ctx = ctx; | |
| #ifndef GPAC_DISABLE_SWF_IMPORT | |
| load.swf_import_flags = swf_flags; | |
| #endif | |
| load.swf_flatten_limit = (Float) swf_flatten_angle; | |
| /*since we're encoding we must get MPEG4 nodes only*/ | |
| load.flags = GF_SM_LOAD_MPEG4_STRICT; | |
| e = gf_sm_load_init(&load); | |
| if (e<0) { | |
| gf_sm_load_done(&load); | |
| M4_LOG(GF_LOG_ERROR, ("Cannot load context %s - %s\n", in, gf_error_to_string(e))); | |
| goto err_exit; | |
| } | |
| e = gf_sm_load_run(&load); | |
| gf_sm_load_done(&load); | |
| #ifndef GPAC_DISABLE_SCENE_STATS | |
| if (opts->auto_quant) { | |
| fprintf(stderr, "Analysing Scene for Automatic Quantization\n"); | |
| statsman = gf_sm_stats_new(); | |
| e = gf_sm_stats_for_scene(statsman, ctx); | |
| if (!e) { | |
| const GF_SceneStatistics *stats = gf_sm_stats_get(statsman); | |
| /*LASeR*/ | |
| if (opts->auto_quant==1) { | |
| if (opts->resolution > (s32)stats->frac_res_2d) { | |
| fprintf(stderr, " Given resolution %d is (unnecessarily) too high, using %d instead.\n", opts->resolution, stats->frac_res_2d); | |
| opts->resolution = stats->frac_res_2d; | |
| } else if (stats->int_res_2d + opts->resolution <= 0) { | |
| fprintf(stderr, " Given resolution %d is too low, using %d instead.\n", opts->resolution, stats->int_res_2d - 1); | |
| opts->resolution = 1 - stats->int_res_2d; | |
| } | |
| opts->coord_bits = stats->int_res_2d + opts->resolution; | |
| fprintf(stderr, " Coordinates & Lengths encoded using "); | |
| if (opts->resolution < 0) fprintf(stderr, "only the %d most significant bits (of %d).\n", opts->coord_bits, stats->int_res_2d); | |
| else fprintf(stderr, "a %d.%d representation\n", stats->int_res_2d, opts->resolution); | |
| fprintf(stderr, " Matrix Scale & Skew Coefficients "); | |
| if (opts->coord_bits - 8 < stats->scale_int_res_2d) { | |
| opts->scale_bits = stats->scale_int_res_2d - opts->coord_bits + 8; | |
| fprintf(stderr, "encoded using a %d.8 representation\n", stats->scale_int_res_2d); | |
| } else { | |
| opts->scale_bits = 0; | |
| fprintf(stderr, "encoded using a %d.8 representation\n", opts->coord_bits - 8); | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| /*BIFS*/ | |
| else if (stats->base_layer) { | |
| GF_AUContext *au; | |
| GF_CommandField *inf; | |
| M_QuantizationParameter *qp; | |
| GF_Command *com = gf_sg_command_new(ctx->scene_graph, GF_SG_GLOBAL_QUANTIZER); | |
| qp = (M_QuantizationParameter *) gf_node_new(ctx->scene_graph, TAG_MPEG4_QuantizationParameter); | |
| inf = gf_sg_command_field_new(com); | |
| inf->new_node = (GF_Node *)qp; | |
| inf->field_ptr = &inf->new_node; | |
| inf->fieldType = GF_SG_VRML_SFNODE; | |
| gf_node_register(inf->new_node, NULL); | |
| au = gf_list_get(stats->base_layer->AUs, 0); | |
| gf_list_insert(au->commands, com, 0); | |
| qp->useEfficientCoding = 1; | |
| qp->textureCoordinateQuant = 0; | |
| if ((stats->count_2f+stats->count_2d) && opts->resolution) { | |
| qp->position2DMin = stats->min_2d; | |
| qp->position2DMax = stats->max_2d; | |
| qp->position2DNbBits = opts->resolution; | |
| qp->position2DQuant = 1; | |
| } | |
| if ((stats->count_3f+stats->count_3d) && opts->resolution) { | |
| qp->position3DMin = stats->min_3d; | |
| qp->position3DMax = stats->max_3d; | |
| qp->position3DNbBits = opts->resolution; | |
| qp->position3DQuant = 1; | |
| qp->textureCoordinateQuant = 1; | |
| } | |
| //float quantif is disabled since 2008, check if we want to re-enable it | |
| #if 0 | |
| if (stats->count_float && opts->resolution) { | |
| qp->scaleMin = stats->min_fixed; | |
| qp->scaleMax = stats->max_fixed; | |
| qp->scaleNbBits = 2*opts->resolution; | |
| qp->scaleQuant = 1; | |
| } | |
| #endif | |
| } | |
| #endif | |
| } | |
| } | |
| #endif /*GPAC_DISABLE_SCENE_STATS*/ | |
| if (e<0) { | |
| M4_LOG(GF_LOG_ERROR, ("Error loading file %s\n", gf_error_to_string(e))); | |
| goto err_exit; | |
| } else { | |
| gf_log_cbk prev_logs = NULL; | |
| if (logs) { | |
| gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_DEBUG); | |
| prev_logs = gf_log_set_callback(logs, scene_coding_log); | |
| } | |
| opts->src_url = in; | |
| e = gf_sm_encode_to_file(ctx, mp4, opts); | |
| if (logs) { | |
| gf_log_set_tool_level(GF_LOG_CODING, GF_LOG_ERROR); | |
| gf_log_set_callback(NULL, prev_logs); | |
| } | |
| } | |
| gf_isom_set_brand_info(mp4, GF_ISOM_BRAND_MP42, 1); | |
| gf_isom_modify_alternate_brand(mp4, GF_ISOM_BRAND_ISOM, GF_TRUE); | |
| err_exit: | |
| #ifndef GPAC_DISABLE_SCENE_STATS | |
| if (statsman) gf_sm_stats_del(statsman); | |
| #endif | |
| gf_sm_del(ctx); | |
| gf_sg_del(sg); | |
| return e; | |
| #endif /*GPAC_DISABLE_SMGR*/ | |
| } | |
| #endif /*GPAC_DISABLE_SCENE_ENCODER*/ | |
| #ifndef GPAC_DISABLE_BIFS_ENC | |
| /* | |
| MPEG-4 chunk encoding | |
| */ | |
| static u32 GetNbBits(u32 MaxVal) | |
| { | |
| u32 k=0; | |
| while ((s32) MaxVal > ((1<<k)-1) ) k+=1; | |
| return k; | |
| } | |
| #ifndef GPAC_DISABLE_SMGR | |
| GF_Err EncodeBIFSChunk(GF_SceneManager *ctx, char *bifsOutputFile, GF_Err (*AUCallback)(GF_ISOSample *)) | |
| { | |
| GF_Err e; | |
| u8 *data; | |
| u32 data_len; | |
| GF_BifsEncoder *bifsenc; | |
| GF_InitialObjectDescriptor *iod; | |
| u32 i, j, count; | |
| Bool encode_names, delete_bcfg; | |
| GF_BIFSConfig *bcfg; | |
| GF_AUContext *au; | |
| char szRad[GF_MAX_PATH], *ext; | |
| char szName[GF_MAX_PATH+100]; | |
| FILE *f; | |
| gf_strcpy(szRad, bifsOutputFile); | |
| ext = strrchr(szRad, '.'); | |
| if (ext) ext[0] = 0; | |
| /* step3: encoding all AUs in ctx->streams starting at AU index 1 (0 is SceneReplace from previous context) */ | |
| bifsenc = gf_bifs_encoder_new(ctx->scene_graph); | |
| e = GF_OK; | |
| iod = (GF_InitialObjectDescriptor *) ctx->root_od; | |
| /*if no iod check we only have one bifs*/ | |
| if (!iod) { | |
| count = 0; | |
| for (i=0; i<gf_list_count(ctx->streams); i++) { | |
| GF_StreamContext *sc = gf_list_get(ctx->streams, i); | |
| if (sc->streamType == GF_STREAM_OD) count++; | |
| } | |
| if (count>1) return GF_NOT_SUPPORTED; | |
| } | |
| count = gf_list_count(ctx->streams); | |
| for (i=0; i<count; i++) { | |
| u32 nbb; | |
| Bool delete_esd = GF_FALSE; | |
| GF_ESD *esd = NULL; | |
| GF_StreamContext *sc = gf_list_get(ctx->streams, i); | |
| if (sc->streamType != GF_STREAM_SCENE) continue; | |
| if (iod) { | |
| for (j=0; j<gf_list_count(iod->ESDescriptors); j++) { | |
| esd = gf_list_get(iod->ESDescriptors, j); | |
| if (esd->decoderConfig && esd->decoderConfig->streamType == GF_STREAM_SCENE) { | |
| if (!sc->ESID) sc->ESID = esd->ESID; | |
| if (sc->ESID == esd->ESID) { | |
| break; | |
| } | |
| } | |
| /*special BIFS direct import from NHNT*/ | |
| else if (gf_list_count(iod->ESDescriptors)==1) { | |
| sc->ESID = esd->ESID; | |
| break; | |
| } | |
| esd = NULL; | |
| } | |
| } | |
| if (!esd) { | |
| esd = gf_odf_desc_esd_new(2); | |
| if (!esd) return GF_OUT_OF_MEM; | |
| gf_odf_desc_del((GF_Descriptor *) esd->decoderConfig->decoderSpecificInfo); | |
| esd->decoderConfig->decoderSpecificInfo = NULL; | |
| esd->ESID = sc->ESID; | |
| esd->decoderConfig->streamType = GF_STREAM_SCENE; | |
| delete_esd = GF_TRUE; | |
| } | |
| if (!esd->decoderConfig) return GF_OUT_OF_MEM; | |
| /*should NOT happen (means inputctx is not properly setup)*/ | |
| if (!esd->decoderConfig->decoderSpecificInfo) { | |
| bcfg = (GF_BIFSConfig*)gf_odf_desc_new(GF_ODF_BIFS_CFG_TAG); | |
| delete_bcfg = 1; | |
| } | |
| /*regular retrieve from ctx*/ | |
| else if (esd->decoderConfig->decoderSpecificInfo->tag == GF_ODF_BIFS_CFG_TAG) { | |
| bcfg = (GF_BIFSConfig *)esd->decoderConfig->decoderSpecificInfo; | |
| delete_bcfg = 0; | |
| } | |
| /*should not happen either (unless loading from MP4 in which case BIFSc is not decoded)*/ | |
| else { | |
| bcfg = gf_odf_get_bifs_config(esd->decoderConfig->decoderSpecificInfo, esd->decoderConfig->objectTypeIndication); | |
| delete_bcfg = 1; | |
| } | |
| /*NO CHANGE TO BIFSC otherwise the generated update will not match the input context*/ | |
| nbb = GetNbBits(ctx->max_node_id); | |
| if (!bcfg->nodeIDbits) bcfg->nodeIDbits=nbb; | |
| if (bcfg->nodeIDbits<nbb) M4_LOG(GF_LOG_WARNING, ("Warning: BIFSConfig.NodeIDBits TOO SMALL\n")); | |
| nbb = GetNbBits(ctx->max_route_id); | |
| if (!bcfg->routeIDbits) bcfg->routeIDbits = nbb; | |
| if (bcfg->routeIDbits<nbb) M4_LOG(GF_LOG_WARNING, ("Warning: BIFSConfig.RouteIDBits TOO SMALL\n")); | |
| nbb = GetNbBits(ctx->max_proto_id); | |
| if (!bcfg->protoIDbits) bcfg->protoIDbits=nbb; | |
| if (bcfg->protoIDbits<nbb) M4_LOG(GF_LOG_WARNING, ("Warning: BIFSConfig.ProtoIDBits TOO SMALL\n")); | |
| /*this is the real pb, not stored in cfg or file level, set at EACH replaceScene*/ | |
| encode_names = 0; | |
| /* The BIFS Config that is passed here should be the BIFSConfig from the IOD */ | |
| gf_bifs_encoder_new_stream(bifsenc, sc->ESID, bcfg, encode_names, 0); | |
| if (delete_bcfg) gf_odf_desc_del((GF_Descriptor *)bcfg); | |
| if (!esd->slConfig) esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG); | |
| if (sc->timeScale) esd->slConfig->timestampResolution = sc->timeScale; | |
| if (!esd->slConfig->timestampResolution) esd->slConfig->timestampResolution = 1000; | |
| esd->ESID = sc->ESID; | |
| gf_bifs_encoder_get_config(bifsenc, sc->ESID, &data, &data_len); | |
| if (esd->decoderConfig->decoderSpecificInfo) gf_odf_desc_del((GF_Descriptor *) esd->decoderConfig->decoderSpecificInfo); | |
| esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG); | |
| esd->decoderConfig->decoderSpecificInfo->data = data; | |
| esd->decoderConfig->decoderSpecificInfo->dataLength = data_len; | |
| esd->decoderConfig->objectTypeIndication = gf_bifs_encoder_get_version(bifsenc, sc->ESID); | |
| for (j=1; j<gf_list_count(sc->AUs); j++) { | |
| au = gf_list_get(sc->AUs, j); | |
| e = gf_bifs_encode_au(bifsenc, sc->ESID, au->commands, &data, &data_len); | |
| if (data) { | |
| sprintf(szName, "%s-%02d-%02d.bifs", szRad, sc->ESID, j); | |
| f = gf_fopen(szName, "wb"); | |
| if (gf_fwrite(data, data_len, f) != data_len) e = GF_IO_ERR; | |
| gf_fclose(f); | |
| gf_free(data); | |
| } | |
| if (e) break; | |
| } | |
| if (delete_esd) gf_odf_desc_del((GF_Descriptor*)esd); | |
| } | |
| gf_bifs_encoder_del(bifsenc); | |
| return e; | |
| } | |
| #endif /*GPAC_DISABLE_SMGR*/ | |
| #endif /*GPAC_DISABLE_BIFS_ENC*/ | |
| /** | |
| \param chunkFile BT chunk to be encoded | |
| \param bifs output file name for the BIFS data | |
| \param inputContext initial BT upon which the chunk is based (shall not be NULL) | |
| \param outputContext: file name to dump the context after applying the new chunk to the input context | |
| can be NULL, without .bt | |
| */ | |
| GF_Err EncodeFileChunk(char *chunkFile, char *bifs, char *inputContext, char *outputContext) | |
| { | |
| #if defined(GPAC_DISABLE_SMGR) || defined(GPAC_DISABLE_BIFS_ENC) || defined(GPAC_DISABLE_SCENE_ENCODER) || defined (GPAC_DISABLE_SCENE_DUMP) | |
| M4_LOG(GF_LOG_WARNING, ("BIFS encoding is not supported in this build of GPAC\n")); | |
| return GF_NOT_SUPPORTED; | |
| #else | |
| GF_Err e; | |
| GF_SceneGraph *sg; | |
| GF_SceneManager *ctx; | |
| GF_SceneLoader load; | |
| /*Step 1: create context and load input*/ | |
| sg = gf_sg_new(); | |
| ctx = gf_sm_new(sg); | |
| memset(&load, 0, sizeof(GF_SceneLoader)); | |
| load.fileName = inputContext; | |
| load.ctx = ctx; | |
| /*since we're encoding we must get MPEG4 nodes only*/ | |
| load.flags = GF_SM_LOAD_MPEG4_STRICT; | |
| e = gf_sm_load_init(&load); | |
| if (!e) e = gf_sm_load_run(&load); | |
| gf_sm_load_done(&load); | |
| if (e) { | |
| M4_LOG(GF_LOG_WARNING, ("Cannot load context %s: %s\n", inputContext, gf_error_to_string(e))); | |
| goto exit; | |
| } | |
| /* Step 2: make sure we have only ONE RAP for each stream*/ | |
| e = gf_sm_aggregate(ctx, 0); | |
| if (e) goto exit; | |
| /*Step 3: loading the chunk into the context*/ | |
| memset(&load, 0, sizeof(GF_SceneLoader)); | |
| load.fileName = chunkFile; | |
| load.ctx = ctx; | |
| load.flags = GF_SM_LOAD_MPEG4_STRICT | GF_SM_LOAD_CONTEXT_READY; | |
| e = gf_sm_load_init(&load); | |
| if (!e) e = gf_sm_load_run(&load); | |
| gf_sm_load_done(&load); | |
| if (e) { | |
| M4_LOG(GF_LOG_WARNING, ("Cannot load scene commands chunk %s: %s\n", chunkFile, gf_error_to_string(e))); | |
| goto exit; | |
| } | |
| M4_LOG(GF_LOG_INFO, ("Context and chunks loaded\n")); | |
| /* Assumes that the first AU contains only one command a SceneReplace and | |
| that is not part of the current chunk */ | |
| /* Last argument is a callback to pass the encoded AUs: not needed here | |
| Saving is not handled correctly */ | |
| e = EncodeBIFSChunk(ctx, bifs, NULL); | |
| if (e) goto exit; | |
| if (outputContext) { | |
| u32 d_mode, do_enc; | |
| char szF[GF_MAX_PATH], *ext; | |
| /*make random access for storage*/ | |
| e = gf_sm_aggregate(ctx, 0); | |
| if (e) goto exit; | |
| /*check if we dump to BT, XMT or encode to MP4*/ | |
| gf_strcpy(szF, outputContext); | |
| ext = strrchr(szF, '.'); | |
| d_mode = GF_SM_DUMP_BT; | |
| do_enc = 0; | |
| if (ext) { | |
| if (!stricmp(ext, ".xmt") || !stricmp(ext, ".xmta")) d_mode = GF_SM_DUMP_XMTA; | |
| else if (!stricmp(ext, ".mp4")) do_enc = 1; | |
| ext[0] = 0; | |
| } | |
| if (do_enc) { | |
| GF_ISOFile *mp4; | |
| gf_strcat(szF, ".mp4"); | |
| mp4 = gf_isom_open(szF, GF_ISOM_WRITE_EDIT, NULL); | |
| e = gf_sm_encode_to_file(ctx, mp4, NULL); | |
| if (e) gf_isom_delete(mp4); | |
| else gf_isom_close(mp4); | |
| } | |
| else e = gf_sm_dump(ctx, szF, GF_FALSE, d_mode); | |
| } | |
| exit: | |
| if (ctx) { | |
| sg = ctx->scene_graph; | |
| gf_sm_del(ctx); | |
| gf_sg_del(sg); | |
| } | |
| return e; | |
| #endif /*defined(GPAC_DISABLE_BIFS_ENC) || defined(GPAC_DISABLE_SCENE_ENCODER) || defined (GPAC_DISABLE_SCENE_DUMP)*/ | |
| } | |
| #endif /*GPAC_DISABLE_MEDIA_IMPORT*/ | |
| void sax_node_start(void *sax_cbck, const char *node_name, const char *name_space, const GF_XMLAttribute *attributes, u32 nb_attributes) | |
| { | |
| char szCheck[100]; | |
| GF_List *imports = sax_cbck; | |
| u32 i=0; | |
| /*do not process hyperlinks*/ | |
| if (!strcmp(node_name, "a") || !strcmp(node_name, "Anchor")) return; | |
| for (i=0; i<nb_attributes; i++) { | |
| GF_XMLAttribute *att = (GF_XMLAttribute *) &attributes[i]; | |
| if (stricmp(att->name, "xlink:href") && stricmp(att->name, "url")) continue; | |
| if (att->value[0]=='#') continue; | |
| if (!strnicmp(att->value, "od:", 3)) continue; | |
| sprintf(szCheck, "%d", atoi(att->value)); | |
| if (!strcmp(szCheck, att->value)) continue; | |
| gf_list_add(imports, gf_strdup(att->value) ); | |
| } | |
| } | |
| static Bool wgt_enum_files(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) | |
| { | |
| WGTEnum *wgt = (WGTEnum *)cbck; | |
| if (!strcmp(wgt->root_file, file_path)) return 0; | |
| //remove hidden files | |
| if (file_path[0] == '.') return 0; | |
| gf_list_add(wgt->imports, gf_strdup(file_path) ); | |
| return 0; | |
| } | |
| static Bool wgt_enum_dir(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) | |
| { | |
| if (!stricmp(file_name, "cvs") || !stricmp(file_name, ".svn") || !stricmp(file_name, ".git")) return GF_FALSE; | |
| gf_enum_directory(file_path, 0, wgt_enum_files, cbck, NULL); | |
| gf_enum_directory(file_path, 1, wgt_enum_dir, cbck, NULL); | |
| return GF_TRUE; | |
| } | |
| GF_ISOFile *package_file(char *file_name, char *fcc, Bool make_wgt) | |
| { | |
| GF_ISOFile *file = NULL; | |
| GF_Err e; | |
| GF_SAXParser *sax; | |
| GF_List *imports; | |
| Bool ascii; | |
| char root_dir[GF_MAX_PATH]; | |
| char *isom_src = NULL; | |
| u32 i, count, mtype, skip_chars; | |
| char *type; | |
| type = gf_xml_get_root_type(file_name, &e); | |
| if (!type) { | |
| M4_LOG(GF_LOG_ERROR, ("Cannot process XML file %s: %s\n", file_name, gf_error_to_string(e) )); | |
| return NULL; | |
| } | |
| if (make_wgt) { | |
| if (strcmp(type, "widget")) { | |
| M4_LOG(GF_LOG_ERROR, ("XML Root type %s differs from \"widget\" \n", type)); | |
| gf_free(type); | |
| return NULL; | |
| } | |
| gf_free(type); | |
| type = gf_strdup("application/mw-manifest+xml"); | |
| fcc = "mwgt"; | |
| } | |
| imports = gf_list_new(); | |
| root_dir[0] = 0; | |
| if (make_wgt) { | |
| WGTEnum wgt; | |
| char *sep = strrchr(file_name, '\\'); | |
| if (!sep) sep = strrchr(file_name, '/'); | |
| if (sep) { | |
| char c = sep[1]; | |
| sep[1]=0; | |
| gf_strcpy(root_dir, file_name); | |
| sep[1] = c; | |
| } else { | |
| gf_strcpy(root_dir, "./"); | |
| } | |
| wgt.dir = root_dir; | |
| wgt.root_file = file_name; | |
| wgt.imports = imports; | |
| gf_enum_directory(wgt.dir, 0, wgt_enum_files, &wgt, NULL); | |
| gf_enum_directory(wgt.dir, 1, wgt_enum_dir, &wgt, NULL); | |
| ascii = 1; | |
| } else { | |
| sax = gf_xml_sax_new(sax_node_start, NULL, NULL, imports); | |
| e = gf_xml_sax_parse_file(sax, file_name, NULL); | |
| ascii = !gf_xml_sax_binary_file(sax); | |
| gf_xml_sax_del(sax); | |
| if (e<0) goto exit; | |
| e = GF_OK; | |
| } | |
| if (fcc) { | |
| mtype = GF_4CC(fcc[0],fcc[1],fcc[2],fcc[3]); | |
| } else { | |
| mtype = 0; | |
| if (!stricmp(type, "svg")) mtype = ascii ? GF_META_TYPE_SVG : GF_META_TYPE_SVGZ; | |
| else if (!stricmp(type, "smil")) mtype = ascii ? GF_META_TYPE_SMIL : GF_META_TYPE_SMLZ; | |
| else if (!stricmp(type, "x3d")) mtype = ascii ? GF_META_TYPE_X3D : GF_META_TYPE_X3DZ ; | |
| else if (!stricmp(type, "xmt-a")) mtype = ascii ? GF_META_TYPE_XMTA : GF_META_TYPE_XMTZ; | |
| } | |
| if (!mtype) { | |
| M4_LOG(GF_LOG_ERROR, ("Missing 4CC code for meta name - please use ABCD:fileName\n")); | |
| e = GF_BAD_PARAM; | |
| goto exit; | |
| } | |
| if (!make_wgt) { | |
| count = gf_list_count(imports); | |
| for (i=0; i<count; i++) { | |
| char *item = gf_list_get(imports, i); | |
| char *res_url = NULL; | |
| FILE *test = gf_fopen(item, "rb"); | |
| if (!test) { | |
| res_url = gf_url_concatenate(file_name, item); | |
| test = gf_fopen(res_url, "rb"); | |
| } | |
| if (!test) { | |
| if (res_url) gf_free(res_url); | |
| gf_list_rem(imports, i); | |
| i--; | |
| count--; | |
| gf_free(item); | |
| continue; | |
| } | |
| gf_fclose(test); | |
| if (gf_isom_probe_file(res_url)) { | |
| if (res_url) gf_free(res_url); | |
| if (isom_src) { | |
| M4_LOG(GF_LOG_ERROR, ("Cannot package several IsoMedia files together\n")); | |
| e = GF_NOT_SUPPORTED; | |
| goto exit; | |
| } | |
| gf_list_rem(imports, i); | |
| i--; | |
| count--; | |
| isom_src = item; | |
| continue; | |
| } | |
| if (res_url) gf_free(res_url); | |
| } | |
| } | |
| if (isom_src) { | |
| file = gf_isom_open(isom_src, GF_ISOM_OPEN_EDIT, NULL); | |
| } else { | |
| file = gf_isom_open("package", GF_ISOM_WRITE_EDIT, NULL); | |
| } | |
| e = gf_isom_set_meta_type(file, 1, 0, mtype); | |
| if (e) goto exit; | |
| /*add self ref*/ | |
| if (isom_src) { | |
| e = gf_isom_add_meta_item(file, 1, 0, 1, NULL, isom_src, 0, 0, NULL, NULL, NULL, NULL, NULL); | |
| if (e) goto exit; | |
| } | |
| e = gf_isom_set_meta_xml(file, 1, 0, file_name, NULL, 0, !ascii); | |
| if (e) goto exit; | |
| skip_chars = (u32) strlen(root_dir); | |
| count = gf_list_count(imports); | |
| for (i=0; i<count; i++) { | |
| char *ext, *mime, *encoding, *name, *itemurl; | |
| char *item = gf_list_get(imports, i); | |
| name = gf_strdup(item + skip_chars); | |
| if (make_wgt) { | |
| while (1) { | |
| char *sep = strchr(name, '\\'); | |
| if (!sep) break; | |
| sep[0] = '/'; | |
| } | |
| } | |
| mime = encoding = NULL; | |
| ext = strrchr(item, '.'); | |
| if (!stricmp(ext, ".gz")) ext = strrchr(ext-1, '.'); | |
| if (!stricmp(ext, ".jpg") || !stricmp(ext, ".jpeg")) mime = "image/jpeg"; | |
| else if (!stricmp(ext, ".png")) mime = "image/png"; | |
| else if (!stricmp(ext, ".svg")) mime = "image/svg+xml"; | |
| else if (!stricmp(ext, ".x3d")) mime = "model/x3d+xml"; | |
| else if (!stricmp(ext, ".xmt")) mime = "application/x-xmt"; | |
| else if (!stricmp(ext, ".js")) { | |
| mime = "application/javascript"; | |
| } | |
| else if (!stricmp(ext, ".svgz") || !stricmp(ext, ".svg.gz")) { | |
| mime = "image/svg+xml"; | |
| encoding = "binary-gzip"; | |
| } | |
| else if (!stricmp(ext, ".x3dz") || !stricmp(ext, ".x3d.gz")) { | |
| mime = "model/x3d+xml"; | |
| encoding = "binary-gzip"; | |
| } | |
| else if (!stricmp(ext, ".xmtz") || !stricmp(ext, ".xmt.gz")) { | |
| mime = "application/x-xmt"; | |
| encoding = "binary-gzip"; | |
| } | |
| itemurl = gf_url_concatenate(file_name, item); | |
| e = gf_isom_add_meta_item(file, 1, 0, 0, itemurl ? itemurl : item, name, 0, GF_META_ITEM_TYPE_MIME, mime, encoding, NULL, NULL, NULL); | |
| gf_free(name); | |
| gf_free(itemurl); | |
| if (e) goto exit; | |
| } | |
| exit: | |
| while (gf_list_count(imports)) { | |
| char *item = gf_list_last(imports); | |
| gf_list_rem_last(imports); | |
| gf_free(item); | |
| } | |
| gf_list_del(imports); | |
| if (isom_src) gf_free(isom_src); | |
| if (type) gf_free(type); | |
| if (e) { | |
| if (file) gf_isom_delete(file); | |
| return NULL; | |
| } | |
| return file; | |
| } | |
| GF_Err apply_high_dynamc_range_xml_desc(GF_ISOFile *movie, u32 track, char *file_name) | |
| { | |
| GF_DOMParser *parser; | |
| GF_XMLNode *root, *stream; | |
| GF_Err e; | |
| u32 i; | |
| GF_MasteringDisplayColourVolumeInfo mdcv; | |
| GF_ContentLightLevelInfo clli; | |
| memset(&mdcv, 0, sizeof(GF_MasteringDisplayColourVolumeInfo)); | |
| memset(&clli, 0, sizeof(GF_ContentLightLevelInfo)); | |
| if (!strcmp(file_name, "none")) { | |
| for (i=0; i<gf_isom_get_track_count(movie); i++) { | |
| if (gf_isom_is_video_handler_type(gf_isom_get_media_type(movie, i+1))) { | |
| gf_isom_set_high_dynamic_range_info(movie, i+1, 1, NULL, NULL); | |
| } | |
| } | |
| return GF_OK; | |
| } | |
| parser = gf_xml_dom_new(); | |
| e = gf_xml_dom_parse(parser, file_name, NULL, NULL); | |
| if (e) { | |
| M4_LOG(GF_LOG_ERROR, ("Error parsing HDR XML file: Line %d - %s. Abort.\n", gf_xml_dom_get_line(parser), gf_xml_dom_get_error(parser))); | |
| gf_xml_dom_del(parser); | |
| return e; | |
| } | |
| root = gf_xml_dom_get_root(parser); | |
| if (!root) { | |
| M4_LOG(GF_LOG_ERROR, ("Error parsing HDR XML file: no \"root\" found. Abort.\n")); | |
| gf_xml_dom_del(parser); | |
| return e; | |
| } | |
| if (strcmp(root->name, "HDR")) { | |
| M4_LOG(GF_LOG_ERROR, ("Error parsing HDR XML file: root name is \"%s\", expecting \"HDR\"\n", root->name)); | |
| gf_xml_dom_del(parser); | |
| return GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| i = 0; | |
| while ((stream = gf_list_enum(root->content, &i))) { | |
| u32 j; | |
| GF_XMLAttribute* att = NULL; | |
| GF_XMLNode *box = NULL; | |
| if (stream->type != GF_XML_NODE_TYPE) continue; | |
| if (strcmp(stream->name, "Track")) continue; | |
| if (!track) { | |
| j = 0; | |
| while ((att = gf_list_enum(stream->attributes, &j))) { | |
| if (!strcmp(att->name, "id")) { | |
| u32 id = parse_u32(att->value, "id"); | |
| track = gf_isom_get_track_by_id(movie, id); | |
| if (!track) { | |
| fprintf(stderr, "HDR XML: no track with ID %d, ignoring attribute\n", id); | |
| } | |
| } | |
| else fprintf(stderr, "HDR XML: ignoring track attribute \"%s\"\n", att->name); | |
| } | |
| } | |
| j = 0; | |
| while ((box = gf_list_enum(stream->content, &j))) { | |
| u32 k; | |
| if (box->type != GF_XML_NODE_TYPE) continue; | |
| if (!strcmp(box->name, "mdcv")) { | |
| k = 0; | |
| while ((att = gf_list_enum(box->attributes, &k))) { | |
| if (!strcmp(att->name, "display_primaries_0_x")) mdcv.display_primaries[0].x = parse_u32(att->value, "display_primaries_0_x"); | |
| else if (!strcmp(att->name, "display_primaries_0_y")) mdcv.display_primaries[0].y = parse_u32(att->value, "display_primaries_0_y"); | |
| else if (!strcmp(att->name, "display_primaries_1_x")) mdcv.display_primaries[1].x = parse_u32(att->value, "display_primaries_1_x"); | |
| else if (!strcmp(att->name, "display_primaries_1_y")) mdcv.display_primaries[1].y = parse_u32(att->value, "display_primaries_1_y"); | |
| else if (!strcmp(att->name, "display_primaries_2_x")) mdcv.display_primaries[2].x = parse_u32(att->value, "display_primaries_2_x"); | |
| else if (!strcmp(att->name, "display_primaries_2_y")) mdcv.display_primaries[2].y = parse_u32(att->value, "display_primaries_2_y"); | |
| else if (!strcmp(att->name, "white_point_x")) mdcv.white_point_x = parse_u32(att->value, "white_point_x"); | |
| else if (!strcmp(att->name, "white_point_y")) mdcv.white_point_y = parse_u32(att->value, "white_point_y"); | |
| else if (!strcmp(att->name, "max_display_mastering_luminance")) mdcv.max_display_mastering_luminance = parse_u32(att->value, "max_display_mastering_luminance"); | |
| else if (!strcmp(att->name, "min_display_mastering_luminance")) mdcv.min_display_mastering_luminance = parse_u32(att->value, "min_display_mastering_luminance"); | |
| else fprintf(stderr, "HDR XML: ignoring box \"%s\" attribute \"%s\"\n", box->name, att->name); | |
| } | |
| } else if (!strcmp(box->name, "clli")) { | |
| k = 0; | |
| while ((att = gf_list_enum(box->attributes, &k))) { | |
| if (!strcmp(att->name, "max_content_light_level")) clli.max_content_light_level = parse_u32(att->value, "max_content_light_level"); | |
| else if (!strcmp(att->name, "max_pic_average_light_level")) clli.max_pic_average_light_level = parse_u32(att->value, "max_pic_average_light_level"); | |
| else fprintf(stderr, "HDR XML: ignoring box \"%s\" attribute \"%s\"\n", box->name, att->name); | |
| } | |
| } else { | |
| fprintf(stderr, "HDR XML: ignoring box element \"%s\"\n", box->name); | |
| continue; | |
| } | |
| } | |
| if (!track) { | |
| for (i=0; i<gf_isom_get_track_count(movie); i++) { | |
| if (gf_isom_is_video_handler_type(gf_isom_get_media_type(movie, i+1))) { | |
| e = gf_isom_set_high_dynamic_range_info(movie, i+1, 1, &mdcv, &clli); | |
| if (e) break; | |
| } | |
| } | |
| } else { | |
| e = gf_isom_set_high_dynamic_range_info(movie, track, 1, &mdcv, &clli); | |
| } | |
| if (e) { | |
| M4_LOG(GF_LOG_ERROR, ("HDR XML: error in gf_isom_set_high_dynamic_range_info()\n")); | |
| break; | |
| } | |
| } | |
| gf_xml_dom_del(parser); | |
| return e; | |
| } | |
| static GF_Err apply_timestamps(GF_ISOFile *file, GF_ISOTrackID trackID, const char *timestamp_source) | |
| { | |
| Bool use_dts = GF_FALSE; | |
| GF_Err e = GF_OK; | |
| GF_ISOSample samp; | |
| u32 di; | |
| u64 data_offset; | |
| u32 timescale=1000; | |
| FILE *src = gf_fopen(timestamp_source, "rb"); | |
| if (!src) return GF_URL_ERROR; | |
| u32 track = gf_isom_get_track_by_id(file, trackID); | |
| u32 sample_count = gf_isom_get_sample_count(file, track); | |
| u32 cur_sample = 0; | |
| u32 min_delta = 0; | |
| u64 prev_cts=0; | |
| u64 prev_dts=0; | |
| u64 last_dts_written=0; | |
| u32 nb_sample_in=0; | |
| gf_isom_set_media_timescale(file, track, timescale, 0, 1); | |
| memset(&samp, 0, sizeof(GF_ISOSample)); | |
| while (!gf_feof(src)) { | |
| char szLine[100]; | |
| szLine[0] = 0; | |
| gf_fgets(szLine, 99, src); | |
| szLine[99]=0; | |
| u32 len = (u32) strlen(szLine); | |
| while (len && (strchr(" \n\r", szLine[len-1]) != NULL)) { | |
| szLine[len-1] = 0; | |
| len--; | |
| } | |
| if (!szLine[0]) continue; | |
| if (szLine[0] == '#') { | |
| char *ts_sep=strstr(szLine, "timescale="); | |
| if (ts_sep) { | |
| char *sep = strchr(ts_sep+10, ' '); | |
| if (sep) sep[0]=0; | |
| timescale = atoi(ts_sep+10); | |
| gf_isom_set_media_timescale(file, track, timescale, 0, 1); | |
| } | |
| continue; | |
| } | |
| nb_sample_in++; | |
| if (cur_sample>=sample_count) | |
| continue; | |
| u64 dts, cts; | |
| char *sep = strchr(szLine, ' '); | |
| if (sep) { | |
| use_dts = GF_TRUE; | |
| sscanf(szLine, LLU" "LLU, &dts, &cts); | |
| } else { | |
| sscanf(szLine, LLU, &cts); | |
| dts = cts; | |
| } | |
| if (!cur_sample) { | |
| prev_dts = dts; | |
| prev_cts = cts; | |
| cur_sample++; | |
| continue; | |
| } | |
| if (!use_dts) { | |
| if (dts>=prev_dts) { | |
| if (dts==prev_dts) dts++; | |
| s32 delta = (dts - prev_dts); | |
| if (!min_delta || (min_delta>delta)) min_delta = delta; | |
| } else { | |
| //B-frame after ref | |
| prev_dts = last_dts_written + 1; | |
| last_dts_written += 1; | |
| } | |
| if (prev_cts<prev_dts) { | |
| prev_dts = last_dts_written + 1; | |
| last_dts_written += 1; | |
| } | |
| } | |
| gf_isom_get_sample_info_ex(file, track, cur_sample, &di, &data_offset, &samp); | |
| samp.DTS = prev_dts; | |
| if (cur_sample==1) samp.DTS = 0; | |
| samp.CTS_Offset = (s32)prev_cts - (s32) samp.DTS; | |
| e = gf_isom_update_sample(file, track, cur_sample, &samp, GF_FALSE); | |
| if (e) { | |
| gf_fclose(src); | |
| return e; | |
| } | |
| last_dts_written = prev_dts; | |
| cur_sample++; | |
| prev_cts = cts; | |
| prev_dts = dts; | |
| } | |
| //flush last | |
| gf_isom_get_sample_info_ex(file, track, cur_sample, &di, &data_offset, &samp); | |
| samp.DTS = prev_dts; | |
| samp.CTS_Offset = prev_cts; | |
| samp.CTS_Offset -= prev_dts; | |
| e = gf_isom_update_sample(file, track, cur_sample, &samp, GF_FALSE); | |
| if (e) { | |
| gf_fclose(src); | |
| return e; | |
| } | |
| if (nb_sample_in>sample_count) { | |
| M4_LOG(GF_LOG_WARNING, ("Too many inputs in timestamp file, ignoring last entries\n")); | |
| } else if (cur_sample < sample_count) { | |
| M4_LOG(GF_LOG_WARNING, ("Not enough samples in timestamp file, removing last %u samples\n", sample_count-cur_sample)); | |
| cur_sample++; | |
| for (di=cur_sample; di<=sample_count; di++) { | |
| gf_isom_remove_sample(file, track, cur_sample); | |
| } | |
| } | |
| gf_fclose(src); | |
| return GF_OK; | |
| } | |
| #endif /*GPAC_DISABLE_ISOM_WRITE*/ |