in applications/mp4box/fileimport.c [1624:2076]
GF_Err cat_isomedia_file(GF_ISOFile *dest, char *fileName, u32 import_flags, Double force_fps, u32 frames_per_sample, char *tmp_dir, Bool force_cat, Bool align_timelines, Bool allow_add_in_command)
{
u32 i, j, count, nb_tracks, nb_samp, nb_done;
GF_ISOFile *orig;
GF_Err e;
char *opts, *multi_cat;
Double ts_scale;
Double dest_orig_dur;
u32 dst_tk, tk_id, mtype;
u64 insert_dts;
Bool is_isom;
GF_ISOSample *samp;
Double aligned_to_DTS = 0;
if (strchr(fileName, '*')) return cat_multiple_files(dest, fileName, import_flags, force_fps, frames_per_sample, tmp_dir, 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 = strchr(fileName, ':');
if (opts && (opts[1]=='\\'))
opts = strchr(fileName, ':');
e = GF_OK;
/*if options are specified, reimport the file*/
is_isom = opts ? 0 : gf_isom_probe_file(fileName);
if (!is_isom || opts) {
orig = gf_isom_open("temp", GF_ISOM_WRITE_EDIT, tmp_dir);
e = import_file(orig, fileName, import_flags, force_fps, frames_per_sample);
if (e) return e;
} else {
/*we open the original file in edit mode since we may have to rewrite AVC samples*/
orig = gf_isom_open(fileName, GF_ISOM_OPEN_EDIT, tmp_dir);
}
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);
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++) {
u32 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:
fprintf(stderr, "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_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) {
fprintf(stderr, "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 = 0;
for (i=0; i<gf_isom_get_track_count(dest); i++) {
Double track_dur = (Double) gf_isom_get_media_duration(dest, i+1);
track_dur /= gf_isom_get_media_timescale(dest, i+1);
if (aligned_to_DTS < track_dur) {
aligned_to_DTS = track_dur;
}
}
fprintf(stderr, "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 skip_lang_test = 1;
Bool use_ts_dur = 1;
Bool merge_edits = 0;
Bool new_track = 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:
use_ts_dur = 0;
case GF_ISOM_MEDIA_AUDIO:
case GF_ISOM_MEDIA_VISUAL:
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 if (gf_isom_get_media_subtype(dest, dst_tk, 1) != gf_isom_get_media_subtype(orig, i+1, 1))
dst_tk = 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 (mtype==GF_ISOM_MEDIA_VISUAL) {
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;
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;
break;
}
} else {
dst_tk = j+1;
break;
}
}
}
}
if (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)) {
dst_tk = 0;
}
/*we force the same visual resolution*/
else if (mtype==GF_ISOM_MEDIA_VISUAL) {
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 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);
}
/*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);
}
}
}
/*looks like a new track*/
if (!dst_tk) {
fprintf(stderr, "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, 1, &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 (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);
}
}
/*remove cloned edit list, as it will be rewritten after import*/
gf_isom_remove_edit_segments(dest, dst_tk);
} else {
nb_edits = gf_isom_get_edit_segment_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 * gf_isom_get_media_timescale(dest, dst_tk));
} 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);
/*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_edit_segment_count(dest, dst_tk)) ) {
u64 editTime, segmentDuration, mediaTime, dst_editTime, dst_segmentDuration, dst_mediaTime;
u8 dst_editMode, editMode;
u32 j;
merge_edits = 1;
for (j=0; j<nb_edits; j++) {
gf_isom_get_edit_segment(orig, i+1, j+1, &editTime, &segmentDuration, &mediaTime, &editMode);
gf_isom_get_edit_segment(dest, dst_tk, j+1, &dst_editTime, &dst_segmentDuration, &dst_mediaTime, &dst_editMode);
if (dst_editMode!=editMode) {
merge_edits=0;
break;
}
}
}
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);
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 (gf_isom_is_self_contained(orig, i+1, di)) {
e = gf_isom_add_sample(dest, dst_tk, di, 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, samp, offset);
gf_isom_sample_del(&s);
}
gf_isom_sample_del(&samp);
if (e)
goto err_exit;
gf_set_progress("Appending", nb_done, nb_samp);
nb_done++;
}
/*scene description and text: compute last sample duration based on original media duration*/
if (!use_ts_dur) {
insert_dts = gf_isom_get_media_duration(orig, i+1) - last_DTS;
gf_isom_set_last_sample_duration(dest, dst_tk, (u32) insert_dts);
}
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_segment(dest, dst_tk, 0, (u64) (s64) (insert_dts*rescale), 0, GF_ISOM_EDIT_EMPTY);
gf_isom_set_edit_segment(dest, dst_tk, (u64) (s64) (insert_dts*rescale), (u64) (s64) (media_dur*rescale), 0, GF_ISOM_EDIT_NORMAL);
} else if (merge_edits) {
/*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;
/*get the first edit normal mode and add the new track dur*/
for (j=nb_edits; j>0; j--) {
u64 editTime, segmentDuration, mediaTime;
u8 editMode;
gf_isom_get_edit_segment(dest, dst_tk, j, &editTime, &segmentDuration, &mediaTime, &editMode);
if (editMode==GF_ISOM_EDIT_NORMAL) {
Double prev_dur = (Double) (s64) dest_track_dur_before_cat;
Double dur = (Double) (s64) gf_isom_get_media_duration(orig, i+1);
dur *= rescale;
prev_dur *= rescale;
/*safety test: some files have broken edit lists. If no more than 2 entries, check that the segment duration
is less or equal to the movie duration*/
if (prev_dur < segmentDuration) {
fprintf(stderr, "Warning: suspicious edit list entry found: duration %g sec but longest track duration before cat is %g - fixing it\n", (Double) (s64) segmentDuration/1000.0, prev_dur/1000);
segmentDuration = (u64) (s64) ( (Double) (s64) (dest_track_dur_before_cat - mediaTime) * rescale );
}
segmentDuration += (u64) (s64) dur;
gf_isom_modify_edit_segment(dest, dst_tk, j, segmentDuration, mediaTime, editMode);
break;
}
}
} else {
u64 editTime, segmentDuration, mediaTime, edit_offset;
Double t;
u8 editMode;
u32 j, count;
count = gf_isom_get_edit_segment_count(dest, dst_tk);
if (count) {
e = gf_isom_get_edit_segment(dest, dst_tk, count, &editTime, &segmentDuration, &mediaTime, &editMode);
if (e) {
fprintf(stderr, "Error: edit segment error on destination track %u could not be retrieved.\n", dst_tk);
goto err_exit;
}
} else if (gf_isom_get_edit_segment_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;
gf_isom_set_edit_segment(dest, dst_tk, editTime, segmentDuration, mediaTime, GF_ISOM_EDIT_NORMAL);
} else {
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_edit_segment_count(orig, i+1);
for (j=0; j<count; j++) {
gf_isom_get_edit_segment(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;
t *= 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_segment(dest, dst_tk, editTime, segmentDuration, mediaTime, editMode);
}
}
}
gf_set_progress("Appending", nb_samp, nb_samp);
/*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;
}