blob: b5e1d8a226c004733009ae9878a35895887c6f31 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2026
* All rights reserved
*
* This file is part of GPAC / Media Tools sub-project
*
* 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 <gpac/internal/media_dev.h>
#include <gpac/internal/isomedia_dev.h>
#include <gpac/mpegts.h>
#include <gpac/constants.h>
#include <gpac/filters.h>
#ifndef GPAC_DISABLE_MEDIA_EXPORT
#ifndef GPAC_DISABLE_AVILIB
#include <gpac/internal/avilib.h>
#endif
#ifndef GPAC_DISABLE_OGG
#include <gpac/internal/ogg.h>
#endif
#ifndef GPAC_DISABLE_VOBSUB
#include <gpac/internal/vobsub.h>
#endif
#ifndef GPAC_DISABLE_ZLIB
#include <zlib.h>
#endif
static GF_Err gf_export_message(GF_MediaExporter *dumper, GF_Err e, char *format, ...)
{
if (dumper->flags & GF_EXPORT_PROBE_ONLY) return e;
#ifndef GPAC_DISABLE_LOG
if (gf_log_tool_level_on(GF_LOG_MEDIA, e ? GF_LOG_ERROR : GF_LOG_WARNING)) {
va_list args;
char szMsg[1024];
va_start(args, format);
vsnprintf(szMsg, 1024, format, args);
va_end(args);
GF_LOG((u32) (e ? GF_LOG_ERROR : GF_LOG_WARNING), GF_LOG_MEDIA, ("%s\n", szMsg) );
}
#endif
return e;
}
#ifndef GPAC_DISABLE_AV_PARSERS
static GF_Err gf_dump_to_vobsub(GF_MediaExporter *dumper, char *szName, u32 track, char *dsi, u32 dsiSize)
{
#ifndef GPAC_DISABLE_VOBSUB
FILE *fidx, *fsub;
u32 width, height, i, count, di;
GF_ISOSample *samp;
char *lang = NULL;
if (!szName) {
szName = gf_file_basename(gf_isom_get_filename(dumper->file));
if (!szName) return GF_BAD_PARAM;
}
/* Check decoder specific information (palette) size - should be 64 */
if (!dsi || (dsiSize != 64)) {
return gf_export_message(dumper, GF_CORRUPTED_DATA, "Invalid decoder specific info size - must be 64 but is %d", dsiSize);
}
/* Create an idx file */
if (!gf_file_ext_start(szName)) {
char szPath[GF_MAX_PATH];
gf_strcpy(szPath, szName);
gf_strcat(szPath, ".idx");
fidx = gf_fopen(szPath, "wb");
} else {
fidx = gf_fopen(szName, "wb");
}
if (!fidx) {
return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
}
/* Create a sub file */
char subName[GF_MAX_PATH];
gf_strcpy(subName, szName);
char *ext = gf_file_ext_start(subName);
if (ext && (!stricmp(ext, ".idx") || !stricmp(ext, ".sub")) ) {
ext[0] = 0;
}
gf_strcat(subName, ".sub");
fsub = gf_fopen(subName, "wb");
if (!fsub) {
gf_fclose(fidx);
return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", subName);
}
/* Retrieve original subpicture resolution */
gf_isom_get_track_layout_info(dumper->file, track, &width, &height, NULL, NULL, NULL);
/* Write header */
gf_fputs("# VobSub index file, v7 (do not modify this line!)\n#\n", fidx);
/* Write original subpicture resolution */
gf_fprintf(fidx, "size: %ux%u\n", width, height);
/* Write palette */
gf_fputs("palette:", fidx);
for (i = 0; i < 16; i++) {
s32 y, u, v, r, g, b;
y = (s32)(u8)dsi[(i<<2)+1] - 0x10;
u = (s32)(u8)dsi[(i<<2)+3] - 0x80;
v = (s32)(u8)dsi[(i<<2)+2] - 0x80;
r = (298 * y + 409 * v + 128) >> 8;
g = (298 * y - 100 * u - 208 * v + 128) >> 8;
b = (298 * y + 516 * u + 128) >> 8;
if (i) gf_fputc(',', fidx);
#define CLIP(x) (((x) >= 0) ? (((x) < 256) ? (x) : 255) : 0)
gf_fprintf(fidx, " %02x%02x%02x", CLIP(r), CLIP(g), CLIP(b));
#undef CLIP
}
gf_fputc('\n', fidx);
/* Write some other useful values */
gf_fputs("# ON: displays only forced subtitles, OFF: shows everything\n", fidx);
gf_fputs("forced subs: OFF\n\n", fidx);
/* Write current language index */
gf_fputs("# Language index in use\nlangidx: 0\n", fidx);
/* Write language header */
gf_isom_get_media_language(dumper->file, track, &lang);
gf_fprintf(fidx, "id: %s, index: 0\n", vobsub_lang_id(lang));
gf_free(lang);
/* Retrieve sample count */
count = gf_isom_get_sample_count(dumper->file, track);
/* Process samples (skip first - because it is special) */
for (i = 2; i <= count; i++)
{
u64 dts;
u32 hh, mm, ss, ms;
samp = gf_isom_get_sample(dumper->file, track, i, &di);
if (!samp) {
break;
}
dts = samp->DTS / 90;
ms = (u32)(dts % 1000);
dts = dts / 1000;
ss = (u32)(dts % 60);
dts = dts / 60;
mm = (u32)(dts % 60);
hh = (u32)(dts / 60);
gf_fprintf(fidx, "timestamp: %02u:%02u:%02u:%03u, filepos: %09"LLX_SUF"\n", hh, mm, ss, ms, gf_ftell(fsub));
if (vobsub_packetize_subpicture(fsub, samp->DTS, samp->data, samp->dataLength) != GF_OK) {
gf_isom_sample_del(&samp);
gf_fclose(fsub);
gf_fclose(fidx);
return gf_export_message(dumper, GF_IO_ERR, "Unable packetize subpicture into file %s\n", szName);
}
gf_isom_sample_del(&samp);
gf_set_progress("VobSub Export", i + 1, count);
if (dumper->flags & GF_EXPORT_DO_ABORT) {
break;
}
}
/* Delete sample if any */
if (samp) {
gf_isom_sample_del(&samp);
}
gf_fclose(fsub);
gf_fclose(fidx);
return GF_OK;
#else
return GF_NOT_SUPPORTED;
#endif
}
#endif // GPAC_DISABLE_AV_PARSERS
#ifndef GPAC_DISABLE_ISOM_WRITE
static GF_Err gf_export_isom_copy_track(GF_MediaExporter *dumper, GF_ISOFile *infile, u32 inTrackNum, GF_ISOFile *outfile, Bool ResetDependencies, Bool AddToIOD)
{
GF_ESD *esd;
GF_InitialObjectDescriptor *iod;
GF_ISOTrackID TrackID;
u32 newTk, descIndex, i, ts, rate, pos, di, count, msubtype;
u64 dur;
GF_ISOSample *samp;
if (!inTrackNum) {
if (gf_isom_get_track_count(infile) != 1) return gf_export_message(dumper, GF_BAD_PARAM, "Please specify trackID to export");
inTrackNum = 1;
}
//check the ID is available
TrackID = gf_isom_get_track_id(infile, inTrackNum);
newTk = gf_isom_get_track_by_id(outfile, TrackID);
if (newTk) TrackID = 0;
//get the ESD and remove dependencies
esd = NULL;
msubtype = gf_isom_get_media_subtype(infile, inTrackNum, 1);
if (msubtype == GF_ISOM_SUBTYPE_MPEG4) {
esd = gf_isom_get_esd(infile, inTrackNum, 1);
if (esd && ResetDependencies) {
esd->dependsOnESID = 0;
esd->OCRESID = 0;
}
}
newTk = gf_isom_new_track(outfile, TrackID, gf_isom_get_media_type(infile, inTrackNum), gf_isom_get_media_timescale(infile, inTrackNum));
gf_isom_set_track_enabled(outfile, newTk, GF_TRUE);
if (gf_isom_has_keep_utc_times(infile)) {
u64 cdate, mdate;
gf_isom_get_track_creation_time(infile, inTrackNum, &cdate, &mdate);
gf_isom_set_track_creation_time(outfile, newTk, cdate, mdate);
}
if (esd) {
gf_isom_new_mpeg4_description(outfile, newTk, esd, NULL, NULL, &descIndex);
} else {
gf_isom_clone_sample_description(outfile, newTk, infile, inTrackNum, 1, NULL, NULL, &descIndex);
}
if (esd && esd->decoderConfig) {
if ((esd->decoderConfig->streamType == GF_STREAM_VISUAL) || (esd->decoderConfig->streamType == GF_STREAM_SCENE)) {
u32 w, h;
gf_isom_get_visual_info(infile, inTrackNum, 1, &w, &h);
#ifndef GPAC_DISABLE_AV_PARSERS
/*this is because so many files have reserved values of 320x240 from v1 ... */
if ((esd->decoderConfig->objectTypeIndication == GF_CODECID_MPEG4_PART2)
&& esd->decoderConfig->decoderSpecificInfo
) {
GF_M4VDecSpecInfo dsi;
gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
w = dsi.width;
h = dsi.height;
}
#endif
gf_isom_set_visual_info(outfile, newTk, 1, w, h);
}
else if ((esd->decoderConfig->streamType == GF_STREAM_TEXT) && (esd->decoderConfig->objectTypeIndication == GF_CODECID_SUBPIC)) {
u32 w, h;
s32 trans_x, trans_y;
s16 layer;
gf_isom_get_track_layout_info(infile, inTrackNum, &w, &h, &trans_x, &trans_y, &layer);
gf_isom_set_track_layout_info(outfile, newTk, w << 16, h << 16, trans_x, trans_y, layer);
}
esd->decoderConfig->avgBitrate = 0;
esd->decoderConfig->maxBitrate = 0;
}
pos = 0;
rate = 0;
ts = gf_isom_get_media_timescale(infile, inTrackNum);
count = gf_isom_get_sample_count(infile, inTrackNum);
for (i=0; i<count; i++) {
samp = gf_isom_get_sample(infile, inTrackNum, i+1, &di);
gf_isom_add_sample(outfile, newTk, descIndex, samp);
if (esd && esd->decoderConfig) {
rate += samp->dataLength;
esd->decoderConfig->avgBitrate += samp->dataLength;
if (esd->decoderConfig->bufferSizeDB<samp->dataLength) esd->decoderConfig->bufferSizeDB = samp->dataLength;
if (samp->DTS - pos > ts) {
if (esd->decoderConfig->maxBitrate<rate) esd->decoderConfig->maxBitrate = rate;
rate = 0;
pos = 0;
}
}
gf_isom_sample_del(&samp);
gf_set_progress("ISO File Export", i, count);
}
gf_set_progress("ISO File Export", count, count);
if (msubtype == GF_ISOM_SUBTYPE_MPEG4_CRYP) {
esd = gf_isom_get_esd(infile, inTrackNum, 1);
} else if ((msubtype == GF_ISOM_SUBTYPE_AVC_H264)
|| (msubtype == GF_ISOM_SUBTYPE_AVC2_H264)
|| (msubtype == GF_ISOM_SUBTYPE_AVC3_H264)
|| (msubtype == GF_ISOM_SUBTYPE_AVC4_H264)
) {
return gf_isom_set_pl_indication(outfile, GF_ISOM_PL_VISUAL, 0x0F);
}
/*likely 3gp or any non-MPEG-4 isomedia file*/
if (!esd)
return gf_isom_remove_root_od(outfile);
//broken stream description, do not check for profiles & levels and remove from root
if (!esd->decoderConfig) {
gf_odf_desc_del((GF_Descriptor *)esd);
return gf_isom_remove_root_od(outfile);
}
dur = gf_isom_get_media_duration(outfile, newTk);
if (!dur) dur = ts;
esd->decoderConfig->maxBitrate *= 8;
esd->decoderConfig->avgBitrate = (u32) (esd->decoderConfig->avgBitrate * 8 * ts / dur);
gf_isom_change_mpeg4_description(outfile, newTk, 1, esd);
iod = (GF_InitialObjectDescriptor *) gf_isom_get_root_od(infile);
switch (esd->decoderConfig->streamType) {
case GF_STREAM_SCENE:
if (iod && (iod->tag==GF_ODF_IOD_TAG)) {
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_SCENE, iod->scene_profileAndLevel);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_GRAPHICS, iod->graphics_profileAndLevel);
} else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2) {
gf_export_message(dumper, GF_OK, "Warning: Scene PLs not found in original MP4 - defaulting to No Profile Specified");
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_SCENE, 0xFE);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_GRAPHICS, 0xFE);
}
break;
case GF_STREAM_VISUAL:
if (iod && (iod->tag==GF_ODF_IOD_TAG)) {
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_VISUAL, iod->visual_profileAndLevel);
}
#ifndef GPAC_DISABLE_AV_PARSERS
else if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2)
&& esd->decoderConfig->decoderSpecificInfo
) {
GF_M4VDecSpecInfo dsi;
gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_VISUAL, dsi.VideoPL);
}
#endif
else {
gf_export_message(dumper, GF_OK, "Warning: Visual PLs not found in original MP4 - defaulting to No Profile Specified");
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_VISUAL, 0xFE);
}
break;
case GF_STREAM_AUDIO:
if (iod && (iod->tag==GF_ODF_IOD_TAG)) {
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_AUDIO, iod->audio_profileAndLevel);
}
#ifndef GPAC_DISABLE_AV_PARSERS
else if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_AAC_MPEG4)
&& esd->decoderConfig->decoderSpecificInfo
) {
GF_M4ADecSpecInfo cfg;
gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &cfg);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_AUDIO, cfg.audioPL);
}
#endif
else {
gf_export_message(dumper, GF_OK, "Warning: Audio PLs not found in original MP4 - defaulting to No Profile Specified");
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_AUDIO, 0xFE);
}
default:
break;
}
if (iod) gf_odf_desc_del((GF_Descriptor *) iod);
gf_odf_desc_del((GF_Descriptor *)esd);
if (AddToIOD) gf_isom_add_track_to_root_od(outfile, newTk);
return GF_OK;
}
GF_Err gf_media_export_isom(GF_MediaExporter *dumper)
{
GF_ISOFile *outfile;
GF_Err e;
Bool add_to_iod, is_stdout;
u32 track;
GF_ISOOpenMode mode;
if (!(track = gf_isom_get_track_by_id(dumper->file, dumper->trackID))) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Wrong track ID %d for file %s \n", dumper->trackID, gf_isom_get_filename(dumper->file)));
return GF_BAD_PARAM;
}
if (gf_isom_get_media_type(dumper->file, dumper->trackID)==GF_ISOM_MEDIA_OD) {
return gf_export_message(dumper, GF_BAD_PARAM, "Cannot extract OD track, result is meaningless");
}
if (dumper->flags & GF_EXPORT_PROBE_ONLY) {
dumper->flags |= GF_EXPORT_MERGE;
return GF_OK;
}
char *in_name = NULL;
if (dumper->out_name && gf_file_ext_start(dumper->out_name)) {
gf_dynstrcat(&in_name, dumper->out_name, NULL);
} else {
char *ext = (char *) gf_isom_get_filename(dumper->file);
if (ext) ext = gf_file_ext_start(ext);
gf_dynstrcat(&in_name, dumper->out_name, NULL);
gf_dynstrcat(&in_name, ext ? ext : ".mp4", NULL);
}
is_stdout = (dumper->out_name && !strcmp(dumper->out_name, "std")) ? 1 : 0;
add_to_iod = 1;
mode = GF_ISOM_WRITE_EDIT;
if (!is_stdout && (dumper->flags & GF_EXPORT_MERGE)) {
FILE *t = gf_fopen(in_name, "rb");
if (t) {
add_to_iod = 0;
mode = GF_ISOM_OPEN_EDIT;
gf_fclose(t);
}
}
outfile = gf_isom_open(is_stdout ? "std" : in_name, mode, NULL);
if (mode == GF_ISOM_WRITE_EDIT) {
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_AUDIO, 0xFF);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_VISUAL, 0xFF);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_GRAPHICS, 0xFF);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_SCENE, 0xFF);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_OD, 0xFF);
gf_isom_set_pl_indication(outfile, GF_ISOM_PL_MPEGJ, 0xFF);
}
if (gf_isom_has_keep_utc_times(dumper->file)) {
u64 cdate, mdate;
gf_isom_get_creation_time(dumper->file, &cdate, &mdate);
gf_isom_set_creation_time(outfile, cdate, mdate);
}
e = gf_export_isom_copy_track(dumper, dumper->file, track, outfile, 1, add_to_iod);
if (!add_to_iod) {
u32 i;
for (i=0; i<gf_isom_get_track_count(outfile); i++) {
gf_isom_remove_track_from_root_od(outfile, i+1);
}
}
if (gf_isom_has_keep_utc_times(dumper->file))
gf_isom_keep_utc_times(outfile, GF_TRUE);
if (e) gf_isom_delete(outfile);
else e = gf_isom_close(outfile);
gf_free(in_name);
return e;
}
#endif /*GPAC_DISABLE_ISOM_WRITE*/
/* Required for base64 encoding of DecoderSpecificInfo */
#include <gpac/base_coding.h>
#ifndef GPAC_DISABLE_VTT
/* Required for timestamp generation */
#include <gpac/webvtt.h>
GF_Err gf_media_export_webvtt_metadata(GF_MediaExporter *dumper)
{
GF_ESD *esd;
GF_Err e = GF_OK;
char szName[1000], szMedia[1000];
FILE *med, *vtt;
u32 w, h;
u32 track, i, di, count, pos;
u32 mtype, mstype;
Bool isText;
char *mime = NULL;
Bool useBase64 = GF_FALSE;
u32 headerLength = 0;
if (!(track = gf_isom_get_track_by_id(dumper->file, dumper->trackID))) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Wrong track ID %d for file %s \n", dumper->trackID, gf_isom_get_filename(dumper->file)));
return GF_BAD_PARAM;
}
if (!track) return gf_export_message(dumper, GF_BAD_PARAM, "Invalid track ID %d", dumper->trackID);
if (dumper->flags & GF_EXPORT_PROBE_ONLY) {
return GF_OK;
}
esd = gf_isom_get_esd(dumper->file, track, 1);
med = NULL;
if (dumper->flags & GF_EXPORT_WEBVTT_META_EMBEDDED) {
} else {
sprintf(szMedia, "%s.media", dumper->out_name);
med = gf_fopen(szMedia, "wb");
if (!med) {
if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szMedia);
}
}
sprintf(szName, "%s.vtt", dumper->out_name);
vtt = gf_fopen(szName, "wt");
if (!vtt) {
gf_fclose(med);
if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
}
mtype = gf_isom_get_media_type(dumper->file, track);
if (mtype==GF_ISOM_MEDIA_TEXT || mtype == GF_ISOM_MEDIA_MPEG_SUBT || mtype == GF_ISOM_MEDIA_SUBT) {
isText = GF_TRUE;
} else {
isText = GF_FALSE;
}
mstype = gf_isom_get_media_subtype(dumper->file, track, 1);
/*write header*/
gf_fprintf(vtt, "WEBVTT Metadata track generated by GPAC MP4Box %s\n", gf_sys_is_test_mode() ? "" : gf_gpac_version());
gf_fprintf(vtt, "kind:metadata\n");
char *lang;
gf_isom_get_media_language(dumper->file, track, &lang);
if (lang) {
gf_fprintf(vtt, "language:%s\n", lang);
gf_free(lang);
}
const char *handler;
gf_isom_get_handler_name(dumper->file, track, &handler);
if (handler)
gf_fprintf(vtt, "label: %s\n", handler);
if (gf_isom_is_track_in_root_od(dumper->file, track)) gf_fprintf(vtt, "inRootOD: yes\n");
gf_fprintf(vtt, "trackID: %d\n", dumper->trackID);
if (med) {
gf_fprintf(vtt, "baseMediaFile: %s\n", gf_file_basename(szMedia));
}
if (esd) {
if (esd->decoderConfig) {
/* TODO: export the MPEG-4 Stream type only if it is not a GPAC internal value */
gf_fprintf(vtt, "MPEG-4-streamType: %d\n", esd->decoderConfig->streamType);
/* TODO: export the MPEG-4 Object Type Indication only if it is not a GPAC internal value */
gf_fprintf(vtt, "MPEG-4-objectTypeIndication: %d\n", esd->decoderConfig->objectTypeIndication);
}
if (gf_isom_is_video_handler_type(mtype) ) {
gf_isom_get_visual_info(dumper->file, track, 1, &w, &h);
gf_fprintf(vtt, "width:%d\n", w);
gf_fprintf(vtt, "height:%d\n", h);
}
else if (mtype==GF_ISOM_MEDIA_AUDIO) {
u32 sr, nb_ch, bps;
gf_isom_get_audio_info(dumper->file, track, 1, &sr, &nb_ch, &bps);
gf_fprintf(vtt, "sampleRate: %d\n", sr);
gf_fprintf(vtt, "numChannels: %d\n", nb_ch);
} else if (isText) {
s32 tx, ty;
s16 layer;
gf_isom_get_track_layout_info(dumper->file, track, &w, &h, &tx, &ty, &layer);
gf_fprintf(vtt, "width:%d\n", w);
gf_fprintf(vtt, "height:%d\n", h);
if (tx || ty) gf_fprintf(vtt, "translation:%d,%d\n", tx, ty);
if (layer) gf_fprintf(vtt, "layer:%d\n", layer);
}
if (esd->decoderConfig && esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) {
if (isText) {
if (mstype == GF_ISOM_SUBTYPE_WVTT) {
/* Warning: Just use -raw export */
mime = "text/vtt";
} else if (mstype == GF_ISOM_SUBTYPE_STXT) {
/* TODO: find the mime type from the ESD, assume SVG for now */
mime = "image/svg+xml";
} else if (mstype == GF_ISOM_SUBTYPE_STPP) {
/* TODO: find the mime type from the ESD, assume TTML for now */
mime = "application/ttml+xml";
}
if (dumper->flags & GF_EXPORT_WEBVTT_META_EMBEDDED) {
if (mstype == GF_ISOM_SUBTYPE_STXT) {
if (esd->decoderConfig->decoderSpecificInfo->dataLength) {
gf_fprintf(vtt, "text-header: \n");
gf_webvtt_dump_header_boxed(vtt, esd->decoderConfig->decoderSpecificInfo->data+4, esd->decoderConfig->decoderSpecificInfo->dataLength, &headerLength);
}
}
} else {
gf_webvtt_dump_header_boxed(med, esd->decoderConfig->decoderSpecificInfo->data+4, esd->decoderConfig->decoderSpecificInfo->dataLength, &headerLength);
gf_fprintf(vtt, "text-header-length: %d\n", headerLength);
}
} else {
char b64[200];
u32 size = gf_base64_encode(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, b64, 200);
useBase64 = GF_TRUE;
if (size != (u32)-1 && size != 0) {
b64[size] = 0;
gf_fprintf(vtt, "MPEG-4-DecoderSpecificInfo: %s\n", b64);
}
}
}
gf_odf_desc_del((GF_Descriptor *) esd);
} else {
GF_GenericSampleDescription *sdesc = gf_isom_get_generic_sample_description(dumper->file, track, 1);
gf_fprintf(vtt, "mediaType: %s\n", gf_4cc_to_str(mtype));
gf_fprintf(vtt, "mediaSubType: %s\n", gf_4cc_to_str(mstype ));
if (sdesc) {
if (gf_isom_is_video_handler_type(mtype) ) {
gf_fprintf(vtt, "codecVendor: %s\n", gf_4cc_to_str(sdesc->vendor_code));
gf_fprintf(vtt, "codecVersion: %d\n", sdesc->version);
gf_fprintf(vtt, "codecRevision: %d\n", sdesc->revision);
gf_fprintf(vtt, "width: %d\n", sdesc->width);
gf_fprintf(vtt, "height: %d\n", sdesc->height);
gf_fprintf(vtt, "compressorName: %s\n", sdesc->compressor_name);
gf_fprintf(vtt, "temporalQuality: %d\n", sdesc->temporal_quality);
gf_fprintf(vtt, "spatialQuality: %d\n", sdesc->spatial_quality);
gf_fprintf(vtt, "horizontalResolution: %d\n", sdesc->h_res);
gf_fprintf(vtt, "verticalResolution: %d\n", sdesc->v_res);
gf_fprintf(vtt, "bitDepth: %d\n", sdesc->depth);
} else if (mtype==GF_ISOM_MEDIA_AUDIO) {
gf_fprintf(vtt, "codecVendor: %s\n", gf_4cc_to_str(sdesc->vendor_code));
gf_fprintf(vtt, "codecVersion: %d\n", sdesc->version);
gf_fprintf(vtt, "codecRevision: %d\n", sdesc->revision);
gf_fprintf(vtt, "sampleRate: %d\n", sdesc->samplerate);
gf_fprintf(vtt, "numChannels: %d\n", sdesc->nb_channels);
gf_fprintf(vtt, "bitsPerSample: %d\n", sdesc->bits_per_sample);
}
if (sdesc->extension_buf) {
char b64[200];
u32 size = gf_base64_encode(sdesc->extension_buf, sdesc->extension_buf_size, b64, 200);
useBase64 = GF_TRUE;
if (size != (u32)-1) {
b64[size] = 0;
gf_fprintf(vtt, "specificInfo: %s\n", b64);
gf_free(sdesc->extension_buf);
}
}
gf_free(sdesc);
}
}
gf_fprintf(vtt, "inBandMetadataTrackDispatchType: %s\n", (mime ? mime : (isText? "text/plain" : "application/octet-stream")));
if (useBase64) gf_fprintf(vtt, "encoding: base64\n");
gf_fprintf(vtt, "\n");
pos = 0;
count = gf_isom_get_sample_count(dumper->file, track);
for (i=0; i<count; i++) {
GF_ISOSample *samp = gf_isom_get_sample(dumper->file, track, i+1, &di);
if (!samp) break;
{
GF_WebVTTTimestamp start, end;
u64 dur = gf_isom_get_sample_duration(dumper->file, track, i+1);
gf_webvtt_timestamp_set(&start, samp->DTS);
gf_webvtt_timestamp_set(&end, samp->DTS+dur);
gf_webvtt_timestamp_dump(&start, vtt, GF_TRUE);
gf_fprintf(vtt, " --> ");
gf_webvtt_timestamp_dump(&end, vtt, GF_TRUE);
gf_fprintf(vtt, " ");
if (med) {
gf_fprintf(vtt, "mediaOffset:%d ", pos+headerLength);
gf_fprintf(vtt, "dataLength:%d ", samp->dataLength);
}
if (samp->CTS_Offset) gf_fprintf(vtt, "CTS: "LLD"", samp->DTS+samp->CTS_Offset);
if (samp->IsRAP==RAP) gf_fprintf(vtt, "isRAP:true ");
else if (samp->IsRAP==RAP_REDUNDANT) gf_fprintf(vtt, "isSyncShadow: true ");
else gf_fprintf(vtt, "isRAP:false ");
gf_fprintf(vtt, "\n");
}
if (med) {
if (gf_fwrite(samp->data, samp->dataLength, med) != samp->dataLength)
e = GF_IO_ERR;
} else if (dumper->flags & GF_EXPORT_WEBVTT_META_EMBEDDED) {
if (isText) {
samp->data = (char *)gf_realloc(samp->data, samp->dataLength+1);
samp->data[samp->dataLength] = 0;
gf_fprintf(vtt, "%s\n", samp->data);
} else {
u32 b64_size = samp->dataLength*2 + 3;
char *b64;
b64 = (char *)gf_malloc(sizeof(char)*b64_size);
if (b64) {
b64_size = gf_base64_encode(samp->data, samp->dataLength, b64, b64_size);
b64[b64_size] = 0;
gf_fprintf(vtt, "%s\n", b64);
gf_free(b64);
}
}
}
gf_fprintf(vtt, "\n");
pos += samp->dataLength;
gf_isom_sample_del(&samp);
gf_set_progress("WebVTT metadata Export", i+1, count);
if (dumper->flags & GF_EXPORT_DO_ABORT) break;
}
if (med) gf_fclose(med);
gf_fclose(vtt);
return e;
}
#endif /*GPAC_DISABLE_VTT*/
/* Experimental Streaming Instructions XML export */
GF_Err gf_media_export_six(GF_MediaExporter *dumper)
{
GF_ESD *esd;
GF_Err e=GF_OK;
char szName[1000], szMedia[1000];
FILE *media, *six;
u32 track, i, di, count, pos, header_size;
//u32 mtype;
#if !defined(GPAC_DISABLE_TTXT) && !defined(GPAC_DISABLE_VTT)
u32 mstype;
#endif
const char *szRootName;
//Bool isText;
if (!(track = gf_isom_get_track_by_id(dumper->file, dumper->trackID))) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Wrong track ID %d for file %s \n", dumper->trackID, gf_isom_get_filename(dumper->file)));
return GF_BAD_PARAM;
}
if (!track) return gf_export_message(dumper, GF_BAD_PARAM, "Invalid track ID %d", dumper->trackID);
if (dumper->flags & GF_EXPORT_PROBE_ONLY) {
dumper->flags |= GF_EXPORT_NHML_FULL;
return GF_OK;
}
esd = gf_isom_get_esd(dumper->file, track, 1);
sprintf(szMedia, "%s.media", dumper->out_name);
media = gf_fopen(szMedia, "wb");
if (!media) {
if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szMedia);
}
sprintf(szName, "%s.six", dumper->out_name);
szRootName = "stream";
six = gf_fopen(szName, "wt");
if (!six) {
gf_fclose(media);
if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
}
/*
mtype = gf_isom_get_media_type(dumper->file, track);
if (mtype==GF_ISOM_MEDIA_TEXT || mtype == GF_ISOM_MEDIA_SUBM || mtype == GF_ISOM_MEDIA_SUBT) {
isText = GF_TRUE;
} else {
isText = GF_FALSE;
}
*/
#if !defined(GPAC_DISABLE_TTXT) && !defined(GPAC_DISABLE_VTT)
mstype = gf_isom_get_media_subtype(dumper->file, track, 1);
#endif
/*write header*/
gf_fprintf(six, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
gf_fprintf(six, "<%s timescale=\"%d\" ", szRootName, gf_isom_get_media_timescale(dumper->file, track) );
gf_fprintf(six, "file=\"%s\" ", szMedia);
gf_fprintf(six, ">\n");
header_size = 0;
if (esd) {
if (esd->decoderConfig && esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) {
#if !defined(GPAC_DISABLE_TTXT) && !defined(GPAC_DISABLE_VTT)
if (mstype == GF_ISOM_SUBTYPE_WVTT || mstype == GF_ISOM_SUBTYPE_STXT) {
gf_webvtt_dump_header_boxed(media,
esd->decoderConfig->decoderSpecificInfo->data+4,
esd->decoderConfig->decoderSpecificInfo->dataLength,
&header_size);
} else
#endif
{
if (gf_fwrite(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, media) != esd->decoderConfig->decoderSpecificInfo->dataLength)
e = GF_IO_ERR;
header_size = esd->decoderConfig->decoderSpecificInfo->dataLength;
}
}
gf_odf_desc_del((GF_Descriptor *) esd);
} else {
GF_GenericSampleDescription *sdesc = gf_isom_get_generic_sample_description(dumper->file, track, 1);
if (sdesc) {
header_size = sdesc->extension_buf_size;
gf_free(sdesc);
}
}
gf_fprintf(six, "<header range-begin=\"0\" range-end=\"%d\"/>\n", header_size-1);
pos = header_size;
count = gf_isom_get_sample_count(dumper->file, track);
for (i=0; i<count; i++) {
GF_ISOSample *samp = gf_isom_get_sample(dumper->file, track, i+1, &di);
if (!samp) break;
if (media) {
if (gf_fwrite(samp->data, samp->dataLength, media)!=samp->dataLength)
e = GF_IO_ERR;
}
gf_fprintf(six, "<unit time=\""LLU"\" ", samp->DTS);
if (samp->IsRAP==RAP) gf_fprintf(six, "rap=\"1\" ");
else if (samp->IsRAP==RAP_NO) gf_fprintf(six, "rap=\"0\" ");
gf_fprintf(six, "range-begin=\"%d\" ", pos);
gf_fprintf(six, "range-end=\"%d\" ", pos+samp->dataLength-1);
gf_fprintf(six, "/>\n");
pos += samp->dataLength;
gf_isom_sample_del(&samp);
gf_set_progress("SIX Export", i+1, count);
if (dumper->flags & GF_EXPORT_DO_ABORT) break;
}
gf_fprintf(six, "</%s>\n", szRootName);
if (media) gf_fclose(media);
gf_fclose(six);
return e;
}
typedef struct
{
u32 track_num, stream_id, last_sample, nb_samp;
} SAFInfo;
GF_Err gf_media_export_saf(GF_MediaExporter *dumper)
{
#ifndef GPAC_DISABLE_SAF
u32 count, i, s_count, di, tot_samp, samp_done;
char out_file[GF_MAX_PATH];
GF_SAFMuxer *mux;
u8 *data;
u32 size;
Bool is_stdout = 0;
FILE *saf_f;
GF_Err e=GF_OK;
if (dumper->flags & GF_EXPORT_PROBE_ONLY) return GF_OK;
s_count = tot_samp = 0;
mux = gf_saf_mux_new();
count = gf_isom_get_track_count(dumper->file);
SAFInfo *safs;
safs = gf_malloc(sizeof(SAFInfo) * count);
if (!safs) return GF_OUT_OF_MEM;
memset(safs, 0, sizeof(SAFInfo) * count);
for (i=0; i<count; i++) {
u32 time_scale, mtype, stream_id = 0;
GF_ESD *esd;
mtype = gf_isom_get_media_type(dumper->file, i+1);
if (mtype==GF_ISOM_MEDIA_OD) continue;
if (mtype==GF_ISOM_MEDIA_HINT) continue;
time_scale = gf_isom_get_media_timescale(dumper->file, i+1);
esd = gf_isom_get_esd(dumper->file, i+1, 1);
if (esd && esd->decoderConfig) {
stream_id = gf_isom_find_od_id_for_track(dumper->file, i+1);
if (!stream_id) stream_id = esd->ESID;
/*translate OD IDs to ESIDs !!*/
if (esd->decoderConfig->decoderSpecificInfo) {
gf_saf_mux_stream_add(mux, stream_id, time_scale, esd->decoderConfig->bufferSizeDB, esd->decoderConfig->streamType, esd->decoderConfig->objectTypeIndication, NULL, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, esd->URLString);
} else {
gf_saf_mux_stream_add(mux, stream_id, time_scale, esd->decoderConfig->bufferSizeDB, esd->decoderConfig->streamType, esd->decoderConfig->objectTypeIndication, NULL, NULL, 0, esd->URLString);
}
}
if (esd)
gf_odf_desc_del((GF_Descriptor *)esd);
if (!stream_id) {
char *mime = NULL;
switch (gf_isom_get_media_subtype(dumper->file, i+1, 1)) {
case GF_ISOM_SUBTYPE_3GP_H263:
mime = "video/h263";
break;
case GF_ISOM_SUBTYPE_3GP_AMR:
mime = "audio/amr";
break;
case GF_ISOM_SUBTYPE_3GP_AMR_WB:
mime = "audio/amr-wb";
break;
case GF_ISOM_SUBTYPE_3GP_EVRC:
mime = "audio/x-evrc-es";
break;
case GF_ISOM_SUBTYPE_3GP_QCELP:
mime = "audio/x-qcelp-es";
break;
case GF_ISOM_SUBTYPE_3GP_SMV:
mime = "audio/x-smv-es";
break;
}
if (!mime) continue;
stream_id = gf_isom_get_track_id(dumper->file, i+1);
gf_saf_mux_stream_add(mux, stream_id, time_scale, 0, 0xFF, 0xFF, mime, NULL, 0, NULL);
}
safs[s_count].track_num = i+1;
safs[s_count].stream_id = stream_id;
safs[s_count].nb_samp = gf_isom_get_sample_count(dumper->file, i+1);
safs[s_count].last_sample = 0;
tot_samp += safs[s_count].nb_samp;
s_count++;
}
if (!s_count) {
gf_export_message(dumper, GF_OK, "No tracks available for SAF muxing");
gf_saf_mux_del(mux);
gf_free(safs);
return GF_OK;
}
gf_export_message(dumper, GF_OK, "SAF: Multiplexing %d tracks", s_count);
if (dumper->out_name && !strcmp(dumper->out_name, "std"))
is_stdout = 1;
gf_strcpy(out_file, dumper->out_name ? dumper->out_name : "");
gf_strcat(out_file, ".saf");
saf_f = is_stdout ? stdout : gf_fopen(out_file, "wb");
if (!saf_f) {
gf_saf_mux_del(mux);
gf_free(safs);
return GF_BAD_PARAM;
}
samp_done = 0;
while (samp_done<tot_samp) {
for (i=0; i<s_count; i++) {
GF_ISOSample *samp;
if (safs[i].last_sample==safs[i].nb_samp) continue;
samp = gf_isom_get_sample(dumper->file, safs[i].track_num, safs[i].last_sample + 1, &di);
if (!samp) {
gf_saf_mux_del(mux);
gf_free(safs);
return gf_isom_last_error(dumper->file);
}
gf_saf_mux_add_au(mux, safs[i].stream_id, (u32) (samp->DTS+samp->CTS_Offset), samp->data, samp->dataLength, (samp->IsRAP==RAP) ? 1 : 0);
/*data is kept by muxer!!*/
gf_free(samp);
safs[i].last_sample++;
samp_done ++;
}
while (1) {
gf_saf_mux_for_time(mux, (u32) -1, 0, &data, &size);
if (!data) break;
if (gf_fwrite(data, size, saf_f) != size) e = GF_IO_ERR;
gf_free(data);
}
gf_set_progress("SAF Export", samp_done, tot_samp);
if (dumper->flags & GF_EXPORT_DO_ABORT) break;
}
gf_saf_mux_for_time(mux, (u32) -1, 1, &data, &size);
if (data) {
if (gf_fwrite(data, size, saf_f)!=size) e = GF_IO_ERR;
gf_free(data);
}
if (!is_stdout)
gf_fclose(saf_f);
gf_saf_mux_del(mux);
gf_free(safs);
return e;
#else
return GF_NOT_SUPPORTED;
#endif
}
static GF_Err gf_media_export_filters(GF_MediaExporter *dumper)
{
char *args, szSubArgs[1024], szExt[30];
GF_Filter *file_out, *reframer, *remux=NULL, *src_filter;
GF_FilterSession *fsess;
GF_Err e = GF_OK;
u32 codec_id=0;
u32 sample_count=0;
Bool skip_write_filter = GF_FALSE;
Bool ext_forced = GF_FALSE;
Bool use_dynext = GF_FALSE;
args = NULL;
gf_strcpy(szExt, "");
if (dumper->trackID && dumper->file) {
u32 msubtype = 0;
u32 mtype = 0;
u32 afmt = 0;
GF_PixelFormat pfmt = 0;
GF_ESD *esd;
const char *export_ext = dumper->out_name ? gf_file_ext_start(dumper->out_name) : NULL;
u32 track_num = gf_isom_get_track_by_id(dumper->file, dumper->trackID);
if (!track_num) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] No tracks with ID %d in file\n", dumper->trackID));
return GF_BAD_PARAM;
}
esd = gf_media_map_esd(dumper->file, track_num, 0);
sample_count = gf_isom_get_sample_count(dumper->file, dumper->trackID);
if (esd && esd->decoderConfig) {
if (esd->decoderConfig->objectTypeIndication<GF_CODECID_LAST_MPEG4_MAPPING) {
codec_id = gf_codecid_from_oti(esd->decoderConfig->streamType, esd->decoderConfig->objectTypeIndication);
#ifndef GPAC_DISABLE_AV_PARSERS
if (esd->decoderConfig->decoderSpecificInfo && (codec_id==GF_CODECID_AAC_MPEG4)) {
GF_M4ADecSpecInfo acfg;
gf_m4a_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &acfg);
if (acfg.base_object_type == GF_M4A_USAC)
codec_id = GF_CODECID_USAC;
}
#endif
} else {
codec_id = esd->decoderConfig->objectTypeIndication;
}
}
if (!codec_id) {
msubtype = gf_isom_get_media_subtype(dumper->file, track_num, 1);
codec_id = gf_codec_id_from_isobmf(msubtype);
}
mtype = gf_isom_get_media_type(dumper->file, track_num);
if (!codec_id) {
pfmt = gf_pixel_fmt_from_qt_type(msubtype);
if (pfmt) codec_id = GF_CODECID_RAW;
}
if (!codec_id) {
gf_strcpy(szExt, gf_4cc_to_str(msubtype));
ext_forced = GF_TRUE;
} else if (codec_id==GF_CODECID_RAW) {
switch (mtype) {
case GF_ISOM_MEDIA_VISUAL:
case GF_ISOM_MEDIA_AUXV:
case GF_ISOM_MEDIA_PICT:
if (pfmt)
gf_strcpy(szExt, gf_pixel_fmt_sname(pfmt));
break;
case GF_ISOM_MEDIA_AUDIO:
afmt = gf_audio_fmt_from_isobmf(msubtype);
if (afmt)
gf_strcpy(szExt, gf_audio_fmt_name(afmt));
break;
default:
gf_strcpy(szExt, gf_4cc_to_str(msubtype));
break;
}
} else {
const char *sname = gf_codecid_file_ext(codec_id);
if (export_ext && strstr(sname, export_ext+1)) {
szExt[0]=0;
} else {
char *sep;
gf_strcpy(szExt, sname);
sep = strchr(szExt, '|');
if (sep) sep[0] = 0;
}
}
switch (mtype) {
case GF_ISOM_MEDIA_VISUAL:
case GF_ISOM_MEDIA_AUXV:
case GF_ISOM_MEDIA_PICT:
case GF_ISOM_MEDIA_AUDIO:
skip_write_filter = codec_id ? GF_TRUE : GF_FALSE;
break;
default:
switch (codec_id) {
case GF_CODECID_WEBVTT:
skip_write_filter = GF_TRUE;
break;
case GF_CODECID_META_TEXT:
case GF_CODECID_META_XML:
case GF_CODECID_SUBS_TEXT:
case GF_CODECID_SUBS_XML:
case GF_CODECID_SIMPLE_TEXT:
//use dynamic extension
szExt[0] = 0;
use_dynext = GF_TRUE;
break;
}
break;
}
if ((codec_id==GF_CODECID_VORBIS) || (codec_id==GF_CODECID_THEORA) || (codec_id==GF_CODECID_SPEEX))
gf_strcpy(szExt, "ogg");
else if (codec_id==GF_CODECID_OPUS)
gf_strcpy(szExt, "opus");
if (codec_id==GF_CODECID_SUBPIC) {
#ifndef GPAC_DISABLE_AV_PARSERS
char *dsi = NULL;
u32 dsi_size = 0;
if (esd && esd->decoderConfig && esd->decoderConfig->decoderSpecificInfo) {
dsi = esd->decoderConfig->decoderSpecificInfo->data;
dsi_size = esd->decoderConfig->decoderSpecificInfo->dataLength;
}
e = gf_dump_to_vobsub(dumper, dumper->out_name, track_num, dsi, dsi_size);
#else
e = GF_NOT_SUPPORTED;
#endif
if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
return e;
}
if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
} else {
const char *export_ext = dumper->out_name ? gf_file_ext_start(dumper->out_name) : NULL;
if (!export_ext) {
use_dynext = GF_TRUE;
if (dumper->in_name) {
#ifdef GPAC_DISABLE_MEDIA_IMPORT
return GF_NOT_SUPPORTED;
#else
GF_MediaImporter *import;
GF_SAFEALLOC(import, GF_MediaImporter);
if (!import) return GF_OUT_OF_MEM;
import->flags = GF_IMPORT_PROBE_ONLY;
import->in_name = dumper->in_name;
e = gf_media_import(import);
if (e) {
gf_free(import);
return e;
}
Bool found = GF_FALSE;
u32 i;
for (i=0; i<import->nb_tracks; i++) {
struct __track_import_info *tki = &import->tk_info[i];
if (!tki->codecid) continue;
if (dumper->trackID) {
if (dumper->trackID != tki->track_num) continue;
} else if (dumper->track_type) {
if ((dumper->track_type==1) && (tki->stream_type!=GF_STREAM_VISUAL)) continue;
if ((dumper->track_type==2) && (tki->stream_type!=GF_STREAM_AUDIO)) continue;
if ((dumper->track_type==3) && (tki->stream_type!=GF_STREAM_TEXT)) continue;
}
found = GF_TRUE;
skip_write_filter = GF_TRUE;
const char *sname = gf_codecid_file_ext(tki->codecid);
if (export_ext && strstr(sname, export_ext+1)) {
szExt[0]=0;
} else {
char *sep;
gf_strcpy(szExt, sname);
sep = strchr(szExt, '|');
if (sep) sep[0] = 0;
use_dynext = GF_FALSE;
}
dumper->trackID = tki->track_num;
dumper->track_type = 0;
break;
}
gf_free(import);
if (!found) return GF_NOT_FOUND;
#endif
}
} else {
skip_write_filter = GF_TRUE;
}
}
fsess = gf_fs_new_defaults(0);
if (!fsess) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Failed to create filter session\n"));
return GF_OUT_OF_MEM;
}
file_out = NULL;
args = NULL;
Bool load_dest = GF_FALSE;
if (dumper->flags & GF_EXPORT_REMUX) {
file_out = gf_fs_load_destination(fsess, dumper->out_name, NULL, NULL, &e);
if (!file_out) {
gf_fs_del(fsess);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot open destination %s\n", dumper->out_name));
return e;
}
}
//except in nhml inband file dump, create a sink filter
else if (!dumper->dump_file) {
Bool no_ext = (dumper->flags & GF_EXPORT_NO_FILE_EXT) ? GF_TRUE : GF_FALSE;
char *ext = gf_file_ext_start(dumper->out_name);
//mux args, for now we only dump to file
e = gf_dynstrcat(&args, "fout:dst=", NULL);
e |= gf_dynstrcat(&args, dumper->out_name, NULL);
if (dumper->flags & GF_EXPORT_NHNT) {
gf_strcpy(szExt, "nhnt");
e |= gf_dynstrcat(&args, ":clone", NULL);
no_ext = GF_TRUE;
if (!ext)
e |= gf_dynstrcat(&args, ":dynext", NULL);
} else if (dumper->flags & GF_EXPORT_NHML) {
gf_strcpy(szExt, "nhml");
e |= gf_dynstrcat(&args, ":clone", NULL);
no_ext = GF_TRUE;
if (!ext)
e |= gf_dynstrcat(&args, ":dynext", NULL);
}
if (dumper->flags & GF_EXPORT_RAW_SAMPLES) {
if (!dumper->sample_num) {
ext = gf_file_ext_start(args);
if (ext) ext[0] = 0;
if (sample_count>=1000) {
e |= gf_dynstrcat(&args, "_$num%08d$", NULL);
} else if (sample_count) {
e |= gf_dynstrcat(&args, "_$num%03d$", NULL);
} else {
e |= gf_dynstrcat(&args, "_$num$", NULL);
}
ext = gf_file_ext_start(dumper->out_name);
if (ext) e |= gf_dynstrcat(&args, ext, NULL);
}
e |= gf_dynstrcat(&args, ":dynext", NULL);
} else if (dumper->trackID && strlen(szExt) ) {
if (!no_ext && !gf_file_ext_start(dumper->out_name)) {
if (args) gf_free(args);
args=NULL;
e = gf_dynstrcat(&args, "fout:dst=", NULL);
e |= gf_dynstrcat(&args, dumper->out_name, NULL);
e |= gf_dynstrcat(&args, szExt, ".");
} else {
e |= gf_dynstrcat(&args, ":ext=", NULL);
e |= gf_dynstrcat(&args, szExt, NULL);
}
load_dest = GF_TRUE;
} else if ((dumper->trackID || dumper->track_type) && use_dynext) {
e |= gf_dynstrcat(&args, ":dynext", NULL);
}
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot load arguments for output file dumper\n"));
if (args) gf_free(args);
gf_fs_del(fsess);
return e;
}
if (load_dest) {
//skip fout:dst= whenever we have an extension specified, to allow using meta filters (ffmx)
file_out = gf_fs_load_destination(fsess, args+9, NULL, NULL, &e);
} else {
file_out = gf_fs_load_filter(fsess, args, &e);
}
if (!file_out) {
gf_fs_del(fsess);
if (args) gf_free(args);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot load output file dumper\n"));
return e;
}
}
if (args) gf_free(args);
args = NULL;
//raw sample frame, force loading filter generic write in frame mode
if (dumper->flags & GF_EXPORT_RAW_SAMPLES) {
e = gf_dynstrcat(&args, "writegen:frame", NULL);
if (dumper->sample_num) {
sprintf(szSubArgs, ":sstart=%d:send=%d", dumper->sample_num, dumper->sample_num);
e |= gf_dynstrcat(&args, szSubArgs, NULL);
}
remux = e ? NULL : gf_fs_load_filter(fsess, args, &e);
if (!remux || e) {
gf_fs_del(fsess);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot load stream->file filter\n"));
if (args) gf_free(args);
return e ? e : GF_FILTER_NOT_FOUND;
}
}
else if (dumper->flags & GF_EXPORT_NHNT) {
remux = gf_fs_load_filter(fsess, "nhntw:exporter", &e);
if (!remux) {
gf_fs_del(fsess);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot load NHNT write filter\n"));
return e;
}
}
else if (dumper->flags & GF_EXPORT_NHML) {
e = gf_dynstrcat(&args, "nhmlw:exporter:name=", NULL);
e |= gf_dynstrcat(&args, dumper->out_name, NULL);
if (dumper->flags & GF_EXPORT_NHML_FULL)
e |= gf_dynstrcat(&args, ":pckp", NULL);
if (dumper->dump_file) {
sprintf(szSubArgs, ":nhmlonly:payload:filep=%p", dumper->dump_file);
e |= gf_dynstrcat(&args, szSubArgs, NULL);
}
remux = e ? NULL : gf_fs_load_filter(fsess, args, &e);
if (!remux || e) {
gf_fs_del(fsess);
if (args) gf_free(args);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot load NHML write filter\n"));
return e ? e : GF_FILTER_NOT_FOUND;
}
} else if (!skip_write_filter) {
e = gf_dynstrcat(&args, "writegen:exporter", NULL);
//extension has been forced, override ext at output of writegen
if (ext_forced) {
e |= gf_dynstrcat(&args, ":#Extension=", NULL);
e |= gf_dynstrcat(&args, szExt, NULL);
}
remux = e ? NULL : gf_fs_load_filter(fsess, args, &e);
if (!remux) {
gf_fs_del(fsess);
if (args) gf_free(args);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot load stream->file filter\n"));
return e;
}
}
if (args) gf_free(args);
args = NULL;
//force a reframer filter, connected to our input
e = gf_dynstrcat(&args, "reframer:SID=1", NULL);
if (dumper->trackID) {
sprintf(szSubArgs, "#PID=%d", dumper->trackID);
e |= gf_dynstrcat(&args, szSubArgs, NULL);
}
e |= gf_dynstrcat(&args, ":exporter", NULL);
if (dumper->flags & GF_EXPORT_SVC_LAYER)
e |= gf_dynstrcat(&args, ":extract=layer", NULL);
if (dumper->flags & GF_EXPORT_WEBVTT_NOMERGE)
e |= gf_dynstrcat(&args, ":merge", NULL);
reframer = gf_fs_load_filter(fsess, args, &e);
if (!reframer || e) {
gf_fs_del(fsess);
if (args) gf_free(args);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot load reframer filter\n"));
return e ? e : GF_FILTER_NOT_FOUND;
}
if (args) gf_free(args);
args = NULL;
//we already have the file loaded, directly load the mp4dmx filter with this file
if (dumper->file) {
//we want to expose every track
e = gf_dynstrcat(&args, "mp4dmx:FID=1:edits=no:alltk:allt", NULL);
if (!e) {
sprintf(szSubArgs, ":mov=%p", dumper->file);
e = gf_dynstrcat(&args, szSubArgs, NULL);
}
//we want to expose every track
src_filter = gf_fs_load_filter(fsess, args, &e);
gf_free(args);
args = NULL;
} else {
//we want to expose every track
src_filter = gf_fs_load_source(fsess, dumper->in_name, "FID=1:edits=no:alltk:allt", NULL, &e);
}
if (!src_filter || e) {
gf_fs_del(fsess);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Exporter] Cannot load filter for input file \"%s\": %s\n", dumper->in_name, gf_error_to_string(e) ));
return e;
}
if (dumper->track_type) {
const char *mtype = (dumper->track_type==1) ? "video" : ((dumper->track_type==2) ? "audio" : "text");
if (dumper->trackID) {
sprintf(szSubArgs, "%s%d", mtype, dumper->trackID);
} else {
sprintf(szSubArgs, "%s", mtype);
}
}
else if (dumper->trackID) {
sprintf(szSubArgs, "PID=%d", dumper->trackID);
}
if (remux) {
gf_filter_set_source(file_out, remux, (dumper->trackID || dumper->track_type) ? szSubArgs : NULL);
gf_filter_set_source(remux, reframer, (dumper->trackID || dumper->track_type) ? szSubArgs : NULL);
} else {
gf_filter_set_source(file_out, reframer, (dumper->trackID || dumper->track_type) ? szSubArgs : NULL);
}
e = gf_fs_run(fsess);
if (e>GF_OK) e = GF_OK;
if (!e) e = gf_fs_get_last_connect_error(fsess);
if (!e) e = gf_fs_get_last_process_error(fsess);
if (!e) {
if (dumper->file)
gf_fs_print_unused_args(fsess, NULL);
else
gf_fs_print_unused_args(fsess, "alltk,allt,edits");
}
gf_fs_print_non_connected(fsess);
if (dumper->print_stats_graph & 1) gf_fs_print_stats(fsess);
if (dumper->print_stats_graph & 2) gf_fs_print_connections(fsess);
gf_fs_del(fsess);
return e;
}
GF_EXPORT
GF_Err gf_media_export(GF_MediaExporter *dumper)
{
if (!dumper) return GF_BAD_PARAM;
if (!dumper->out_name && !(dumper->flags & GF_EXPORT_PROBE_ONLY) && !dumper->dump_file) return GF_BAD_PARAM;
//internal export not using filters
#ifndef GPAC_DISABLE_ISOM_WRITE
if (dumper->flags & GF_EXPORT_MP4) return gf_media_export_isom(dumper);
#endif /*GPAC_DISABLE_ISOM_WRITE*/
#ifndef GPAC_DISABLE_VTT
else if (dumper->flags & GF_EXPORT_WEBVTT_META) return gf_media_export_webvtt_metadata(dumper);
#endif
else if (dumper->flags & GF_EXPORT_SIX) return gf_media_export_six(dumper);
//the following ones should be moved to muxing filters
else if (dumper->flags & GF_EXPORT_SAF) return gf_media_export_saf(dumper);
//the rest is handled by the generic exporter
return gf_media_export_filters(dumper);
}
#endif /*GPAC_DISABLE_MEDIA_EXPORT*/