GF_Err gf_media_export_webvtt_metadata(GF_MediaExporter *dumper)
{
	GF_ESD *esd;
	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);
		gf_fprintf(vtt, "language:%s\n", lang);
		gf_free(lang);
	}
	{
		const char *handler;
		gf_isom_get_handler_name(dumper->file, track, &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) {
			gf_fwrite(samp->data, samp->dataLength, med);
		} 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;
				char *b64;
				b64 = (char *)gf_malloc(samp->dataLength*3);
				b64_size = gf_base64_encode(samp->data, samp->dataLength, b64, samp->dataLength*3);
				if (b64_size != (u32)-1) {
					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 GF_OK;
}