<commit_before>/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Copyright (c) <NAME> 2000-2005 
 *					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/mpegts.h>
#include <gpac/constants.h>

#ifndef GPAC_READ_ONLY

#include <gpac/internal/avilib.h>
#include <gpac/internal/ogg.h>
#include <gpac/internal/vobsub.h>

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_get_level() && (gf_log_get_tools() & GF_LOG_AUTHOR)) {
		va_list args;
		char szMsg[1024];
		va_start(args, format);
		vsprintf(szMsg, format, args);
		va_end(args);
		GF_LOG((u32) (e ? GF_LOG_ERROR : GF_LOG_WARNING), GF_LOG_AUTHOR, ("%s\n", szMsg) );
	}
#endif
	return e;
}

/*that's very very crude, we only support vorbis & theora in MP4 - this will need cleanup as soon as possible*/
static GF_Err gf_dump_to_ogg(GF_MediaExporter *dumper, char *szName, u32 track)
{
	FILE *out;
	ogg_stream_state os;
	ogg_packet op;
	ogg_page og;
	u32 count, i, di, theora_kgs, nb_i, nb_p;
	GF_BitStream *bs;
	GF_ISOSample *samp;
	GF_ESD *esd = gf_isom_get_esd(dumper->file, track, 1);


	gf_rand_init(1);
	ogg_stream_init(&os, gf_rand());

	op.granulepos = 0;
	op.packetno = 0;
	op.b_o_s = 1;
	op.e_o_s = 0;

	out = gf_f64_open(szName, "wb");
	if (!out) return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);

	theora_kgs = 0;
	bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ);
	while (gf_bs_available(bs)) {
		op.bytes = gf_bs_read_u16(bs);
		op.packet = (unsigned char*)malloc(sizeof(char) * op.bytes);
		gf_bs_read_data(bs, (char*)op.packet, op.bytes);
		ogg_stream_packetin(&os, &op);

		if (op.b_o_s) {
			ogg_stream_pageout(&os, &og);
			fwrite(og.header, 1, og.header_len, out);
			fwrite(og.body, 1, og.body_len, out);
			op.b_o_s = 0;

			if (esd->decoderConfig->objectTypeIndication==0xDF) {
				u32 kff;
				GF_BitStream *vbs = gf_bs_new((char*)op.packet, op.bytes, GF_BITSTREAM_READ);
				gf_bs_skip_bytes(vbs, 40);
				gf_bs_read_int(vbs, 6); /* quality */
				kff = 1 << gf_bs_read_int(vbs, 5);
				gf_bs_del(vbs);

				theora_kgs = 0;
				kff--;
				while (kff) {
					theora_kgs ++;
					kff >>= 1;
				}
			}
		}
		free(op.packet);
		op.packetno ++;
	}
	gf_bs_del(bs);
	gf_odf_desc_del((GF_Descriptor *)esd);

	while (ogg_stream_pageout(&os, &og)>0) {
		fwrite(og.header, 1, og.header_len, out);
		fwrite(og.body, 1, og.body_len, out);
	}
	
	op.granulepos = -1;

	count = gf_isom_get_sample_count(dumper->file, track);

	nb_i = nb_p = 0;
	samp = gf_isom_get_sample(dumper->file, track, 1, &di);
	for (i=0; i<count; i++) {
		GF_ISOSample *next_samp = gf_isom_get_sample(dumper->file, track, i+2, &di);
		if (!samp) break;
		op.bytes = samp->dataLength;
		op.packet = (unsigned char*)samp->data;
		op.packetno ++;

		if (theora_kgs) {
			if (samp->IsRAP) {
				if (i) nb_i+=nb_p+1;
				nb_p = 0;
			} else {
				nb_p++;
			}
			op.granulepos = nb_i;
			op.granulepos <<= theora_kgs;
			op.granulepos |= nb_p;
		} else {
			if (next_samp) op.granulepos = next_samp->DTS;
		}
		if (!next_samp) op.e_o_s = 1;

		ogg_stream_packetin(&os, &op);

		gf_isom_sample_del(&samp);
		samp = next_samp;
		next_samp = NULL;
		gf_set_progress("OGG Export", i+1, count);
		if (dumper->flags & GF_EXPORT_DO_ABORT) break;

		while (ogg_stream_pageout(&os, &og)>0) {
			fwrite(og.header, 1, og.header_len, out);
			fwrite(og.body, 1, og.body_len, out);
		}
	}
	if (samp) gf_isom_sample_del(&samp);

	while (ogg_stream_flush(&os, &og)>0) {
		fwrite(og.header, 1, og.header_len, out);
		fwrite(og.body, 1, og.body_len, out);
	}
    ogg_stream_clear(&os);
	fclose(out);
	return GF_OK;
}

GF_Err gf_export_hint(GF_MediaExporter *dumper)
{
	GF_Err e;
	char szName[1000], szType[5];
	char *pck;
	FILE *out;
	u32 track, i, size, m_stype, sn, count;

	track = gf_isom_get_track_by_id(dumper->file, dumper->trackID);
	m_stype = gf_isom_get_media_subtype(dumper->file, track, 1);

	e = gf_isom_reset_hint_reader(dumper->file, track, dumper->sample_num ? dumper->sample_num : 1, 0, 0, 0);
	if (e) return gf_export_message(dumper, e, "Error initializing hint reader");

	gf_export_message(dumper, GF_OK, "Extracting hint track samples - type %s", szType);

	count = gf_isom_get_sample_count(dumper->file, track);
	if (dumper->sample_num) count = 0;

	i = 1;
	while (1) {
		e = gf_isom_next_hint_packet(dumper->file, track, &pck, &size, NULL, NULL, NULL, &sn);
		if (e==GF_EOS) break;
		if (dumper->sample_num && (dumper->sample_num != sn)) {
			free(pck);
			break;
		}
		if (e) return gf_export_message(dumper, e, "Error fetching hint packet %d", i);
		sprintf(szName, "%s_pck_%04d.%s", dumper->out_name, i, gf_4cc_to_str(m_stype));
		out = fopen(szName, "wb");
		fwrite(pck, size, 1, out);
		fclose(out);
		free(pck);
		i++;
		if (count) gf_set_progress("Hint Export", sn, count);
	}
	if (count) gf_set_progress("Hint Export", count, count);

	return GF_OK;
}

static void write_jp2_file(GF_BitStream *bs, char *data, u32 data_size, char *dsi, u32 dsi_size)
{
	gf_bs_write_u32(bs, 12);
	gf_bs_write_u32(bs, GF_4CC('j','P',' ',' '));
	gf_bs_write_u32(bs, 0x0D0A870A);

	gf_bs_write_u32(bs, 20);
	gf_bs_write_u32(bs, GF_4CC('f','t','y','p'));
	gf_bs_write_u32(bs, GF_4CC('j','p','2',' '));
	gf_bs_write_u32(bs, 0);
	gf_bs_write_u32(bs, GF_4CC('j','p','2',' '));

	gf_bs_write_data(bs, dsi, dsi_size);
	gf_bs_write_data(bs, data, data_size);
}

GF_Err gf_media_export_samples(GF_MediaExporter *dumper)
{
	GF_DecoderConfig *dcfg;
	GF_GenericSampleDescription *udesc;
	char szName[1000], szEXT[10], szNum[1000], *dsi;
	FILE *out;
	GF_BitStream *bs;
	u32 track, i, di, count, m_type, m_stype, dsi_size, is_mj2k;

	track = gf_isom_get_track_by_id(dumper->file, dumper->trackID);
	m_type = gf_isom_get_media_type(dumper->file, track);
	m_stype = gf_isom_get_media_subtype(dumper->file, track, 1);
	dsi_size = 0;

	if (dumper->sample_num) sprintf(szNum, " %d", dumper->sample_num);
	else strcpy(szNum, "s");

	is_mj2k = 0;
	dsi = NULL;
	udesc = NULL;
	dcfg = NULL;
	if ((m_stype==GF_ISOM_SUBTYPE_MPEG4) || (m_stype==GF_ISOM_SUBTYPE_MPEG4_CRYP)) 
		dcfg = gf_isom_get_decoder_config(dumper->file, track, 1);

	strcpy(szName, dumper->out_name ? dumper->out_name : "");
	if (dcfg) {
		switch (dcfg->streamType) {
		case GF_STREAM_VISUAL:
			switch (dcfg->objectTypeIndication) {
			case 0x20:
				strcpy(szEXT, ".cmp");
				gf_export_message(dumper, GF_OK, "Dumping MPEG-4 Visual sample%s", szNum);
				break;
			case 0x21:
				strcpy(szEXT, ".h264");
				gf_export_message(dumper, GF_OK, "Dumping MPEG-4 AVC-H264 Visual sample%s", szNum);
				break;
			case 0x6C:
				strcpy(szEXT, ".jpg");
				gf_export_message(dumper, GF_OK, "Dumping JPEG image%s", szNum);
				break;
			case 0x6D:
				strcpy(szEXT, ".png");
				gf_export_message(dumper, GF_OK, "Dumping PNG image%s", szNum);
				break;
			case 0x6E:
				strcpy(szEXT, ".jp2");
				gf_export_message(dumper, GF_OK, "Dumping JPEG 2000 image%s", szNum);
				break;
			case GPAC_OGG_MEDIA_OTI:
				strcpy(szEXT, ".theo");
				gf_export_message(dumper, GF_OK, "Dumping Theora video sample%s", szNum);
				break;
			default:
				strcpy(szEXT, ".raw");
				gf_export_message(dumper, GF_OK, "Dumping Unknown video sample%s (OTI %d)", szNum, dcfg->objectTypeIndication);
				break;
			}
			break;
		case GF_STREAM_AUDIO:
			switch (dcfg->objectTypeIndication) {
			case 0x66:
			case 0x67:
			case 0x68:
			case 0x40:
				strcpy(szEXT, ".aac");
				gf_export_message(dumper, GF_OK, "Dumping MPEG-%d AAC sample%s", (dcfg->objectTypeIndication==0x40) ? 4 : 2, szNum);
				break;
			case 0x69:
			case 0x6B:
				strcpy(szEXT, ".mp3");
				gf_export_message(dumper, GF_OK, "Dumping MPEG-1/2 Audio (MP3) sample%s", szNum);
				break;
			case GPAC_OGG_MEDIA_OTI:
				strcpy(szEXT, ".vorb");
				gf_export_message(dumper, GF_OK, "Dumping Vorbis audio sample%s", szNum);
				break;
			default:
				strcpy(szEXT, ".raw");
				gf_export_message(dumper, GF_OK, "Dumping Unknown audio sample%s (OTI %d)", szNum, dcfg->objectTypeIndication);
				break;
			}
			break;
		case GF_STREAM_SCENE:
			strcpy(szEXT, ".bifs"); gf_export_message(dumper, GF_OK, "Dumping BIFS sample%s", szNum); 
			break;
		case GF_STREAM_OD:
			strcpy(szEXT, ".od"); gf_export_message(dumper, GF_OK, "Dumping OD sample%s", szNum); 
			break;
		case GF_STREAM_MPEGJ:
			strcpy(szEXT, ".mpj"); gf_export_message(dumper, GF_OK, "Dumping MPEG-J sample%s", szNum); 
			break;
		case GF_STREAM_OCI:
			strcpy(szEXT, ".oci"); gf_export_message(dumper, GF_OK, "Dumping OCI sample%s", szNum); 
			break;
		case GF_STREAM_MPEG7:
			strcpy(szEXT, ".mp7"); gf_export_message(dumper, GF_OK, "Dumping MPEG7 sample%s", szNum); 
			break;
		case GF_STREAM_IPMP:
			strcpy(szEXT, ".ipmp"); gf_export_message(dumper, GF_OK, "Dumping IPMP sample%s", szNum); 
			break;
		case GF_STREAM_TEXT:
			strcpy(szEXT, ".tx3g"); gf_export_message(dumper, GF_OK, "Dumping 3GP Text sample%s", szNum); 
			break;
		default:
			gf_odf_desc_del((GF_Descriptor *) dcfg);
			return gf_export_message(dumper, GF_NOT_SUPPORTED, "Cannot dump systems track ID %d sample%s - use NHNT", dumper->trackID, szNum);
		}
		gf_odf_desc_del((GF_Descriptor *) dcfg);
	} else if ((m_stype==GF_ISOM_SUBTYPE_3GP_AMR) || (m_stype==GF_ISOM_SUBTYPE_3GP_AMR_WB)) {
		strcpy(szEXT, ".amr");
		gf_export_message(dumper, GF_OK, "Extracting AMR Audio sample%s", szNum);
	} else if (m_stype==GF_ISOM_SUBTYPE_3GP_H263) {
		gf_export_message(dumper, GF_OK, "Extracting H263 Video sample%s", szNum);
		strcpy(szEXT, ".263");
	} else if (m_stype==GF_ISOM_SUBTYPE_AVC_H264) {
		strcpy(szEXT, ".h264");
		gf_export_message(dumper, GF_OK, "Dumping MPEG-4 AVC-H264 Visual sample%s", szNum);
	} else if (m_type==GF_ISOM_MEDIA_FLASH) {
		gf_export_message(dumper, GF_OK, "Extracting Macromedia Flash Movie sample%s", szNum);
		strcpy(szEXT, ".swf");
	} else if (m_type==GF_ISOM_MEDIA_HINT) {
		return gf_export_hint(dumper);
	} else if (m_stype==GF_4CC('m','j','p','2')) {
		strcpy(szEXT, ".jp2");
		gf_export_message(dumper, GF_OK, "Dumping JPEG 2000 sample%s", szNum);
		udesc = gf_isom_get_generic_sample_description(dumper->file, track, 1);
		dsi = udesc->extension_buf;
		dsi_size = udesc->extension_buf_size;
		free(udesc);
		is_mj2k = 1;
	} else {
		strcpy(szEXT, ".");
		strcat(szEXT, gf_4cc_to_str(m_stype));
		udesc = gf_isom_get_generic_sample_description(dumper->file, track, 1);
		switch (m_type) {
		case GF_ISOM_MEDIA_VISUAL: gf_export_message(dumper, GF_OK, "Extracting \'%s\' Video - Compressor %s", szEXT, udesc ? udesc->compressor_name: "Unknown"); break;
		case GF_ISOM_MEDIA_AUDIO: gf_export_message(dumper, GF_OK, "Extracting \'%s\' Audio - Compressor %s", szEXT, udesc ? udesc->compressor_name : "Unknown"); break;
		default:
			gf_export_message(dumper, GF_OK, "Extracting \'%s\' Track (type '%s') - Compressor %s sample%s", szEXT, gf_4cc_to_str(m_type), udesc ? udesc->compressor_name : "Unknown", szNum);
			break;
		}
		if (udesc->extension_buf) free(udesc->extension_buf);
		if (udesc) free(udesc);
	}
	if (dumper->flags & GF_EXPORT_PROBE_ONLY) return GF_OK;

	if (dumper->sample_num) {
		GF_ISOSample *samp = gf_isom_get_sample(dumper->file, track, dumper->sample_num, &di);
		if (!samp) return GF_BAD_PARAM;
		sprintf(szName, "%s_%d%s", dumper->out_name, dumper->sample_num, szEXT);
		out = fopen(szName, "wb");
		bs = gf_bs_from_file(out, GF_BITSTREAM_WRITE);
		if (is_mj2k) 
			write_jp2_file(bs, samp->data, samp->dataLength, dsi, dsi_size);
		else
			gf_bs_write_data(bs, samp->data, samp->dataLength);
		gf_isom_sample_del(&samp);
		gf_bs_del(bs);
		fclose(out);
		if (dsi) free(dsi);
		return GF_OK;
	}

	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 (count>=1000) {
			sprintf(szName, "%s_%08d%s", dumper->out_name, i+1, szEXT);
		} else {
			sprintf(szName, "%s_%03d%s", dumper->out_name, i+1, szEXT);
		}
		out = fopen(szName, "wb");
		bs = gf_bs_from_file(out, GF_BITSTREAM_WRITE);
		if (dsi) gf_bs_write_data(bs, dsi, dsi_size);
		if (is_mj2k) 
			write_jp2_file(bs, samp->data, samp->dataLength, dsi, dsi_size);
		else
			gf_bs_write_data(bs, samp->data, samp->dataLength);
		gf_isom_sample_del(&samp);
		gf_set_progress("Media Export", i+1, count);
		gf_bs_del(bs);
		fclose(out);
		if (dumper->flags & GF_EXPORT_DO_ABORT) break;
	}
	if (dsi) free(dsi);
	return GF_OK;
}

static GF_Err gf_dump_to_vobsub(GF_MediaExporter *dumper, char *szName, u32 track, char *dsi, u32 dsiSize)
{
	FILE			*fidx, *fsub;
	u32				 width, height, i, count, di;
	GF_ISOSample	*samp;
	char			 lang[] = "\0\0\0\0";

	/* Check decoder specific information (palette) size - should be 64 */
	if (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 */
	fidx = gf_f64_open(szName, "w");
	if (!fidx) {
		return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
	}

	/* Create a sub file */
	vobsub_trim_ext(szName);
	szName = strcat(szName, ".sub");
	fsub = gf_f64_open(szName, "wb");
	if (!fsub) {
		fclose(fidx);
		return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
	}

	/* Retrieve original subpicture resolution */
	gf_isom_get_track_layout_info(dumper->file, track, &width, &height, NULL, NULL, NULL);

	/* Write header */
	fputs("# VobSub index file, v7 (do not modify this line!)\n#\n", fidx);

	/* Write original subpicture resolution */
	fprintf(fidx, "size: %ux%u\n", width, height);

	/* Write palette */
	fputs("palette:", fidx);
	for (i = 0; i < 16; i++) {
		s32 y, u, v, r, g, b;

		y = (s32)dsi[(i<<2)+1] - 0x10;
		u = (s32)dsi[(i<<2)+3] - 0x80;
		v = (s32)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) fputc(',', fidx);

#define CLIP(x) (((x) >= 0) ? (((x) < 256) ? (x) : 255) : 0)
		fprintf(fidx, " %02x%02x%02x", CLIP(r), CLIP(g), CLIP(b));
#undef CLIP
	}
	fputc('\n', fidx);

	/* Write some other useful values */
	fputs("# ON: displays only forced subtitles, OFF: shows everything\n", fidx);
	fputs("forced subs: OFF\n\n", fidx);

	/* Write current language index */
	fputs("# Language index in use\nlangidx: 0\n", fidx);

	/* Write language header */
	gf_isom_get_media_language(dumper->file, track, lang);
	fprintf(fidx, "id: %s, index: 0\n", vobsub_lang_id(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+1, &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);
#if defined(WIN32)  && !defined(__GNUC__)
		fprintf(fidx, "timestamp: %02u:%02u:%02u:%03u, filepos: %09lx\n", hh, mm, ss, ms, gf_f64_tell(fsub));
#else
		fprintf(fidx, "timestamp: %02u:%02u:%02u:%03u, filepos: %09llx\n", hh, mm, ss, ms, gf_f64_tell(fsub));
#endif
		if (vobsub_packetize_subpicture(fsub, samp->DTS, samp->data, samp->dataLength) != GF_OK) {<commit_msg>no message<commit_after>/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Copyright (c) <NAME> 2000-2005 
 *					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/mpegts.h>
#include <gpac/constants.h>

#ifndef GPAC_READ_ONLY

#include <gpac/internal/avilib.h>
#include <gpac/internal/ogg.h>
#include <gpac/internal/vobsub.h>

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_get_level() && (gf_log_get_tools() & GF_LOG_AUTHOR)) {
		va_list args;
		char szMsg[1024];
		va_start(args, format);
		vsprintf(szMsg, format, args);
		va_end(args);
		GF_LOG((u32) (e ? GF_LOG_ERROR : GF_LOG_WARNING), GF_LOG_AUTHOR, ("%s\n", szMsg) );
	}
#endif
	return e;
}

/*that's very very crude, we only support vorbis & theora in MP4 - this will need cleanup as soon as possible*/
static GF_Err gf_dump_to_ogg(GF_MediaExporter *dumper, char *szName, u32 track)
{
	FILE *out;
	ogg_stream_state os;
	ogg_packet op;
	ogg_page og;
	u32 count, i, di, theora_kgs, nb_i, nb_p;
	GF_BitStream *bs;
	GF_ISOSample *samp;
	GF_ESD *esd = gf_isom_get_esd(dumper->file, track, 1);


	gf_rand_init(1);
	ogg_stream_init(&os, gf_rand());

	op.granulepos = 0;
	op.packetno = 0;
	op.b_o_s = 1;
	op.e_o_s = 0;

	out = gf_f64_open(szName, "wb");
	if (!out) return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);

	theora_kgs = 0;
	bs = gf_bs_new(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, GF_BITSTREAM_READ);
	while (gf_bs_available(bs)) {
		op.bytes = gf_bs_read_u16(bs);
		op.packet = (unsigned char*)malloc(sizeof(char) * op.bytes);
		gf_bs_read_data(bs, (char*)op.packet, op.bytes);
		ogg_stream_packetin(&os, &op);

		if (op.b_o_s) {
			ogg_stream_pageout(&os, &og);
			fwrite(og.header, 1, og.header_len, out);
			fwrite(og.body, 1, og.body_len, out);
			op.b_o_s = 0;

			if (esd->decoderConfig->objectTypeIndication==0xDF) {
				u32 kff;
				GF_BitStream *vbs = gf_bs_new((char*)op.packet, op.bytes, GF_BITSTREAM_READ);
				gf_bs_skip_bytes(vbs, 40);
				gf_bs_read_int(vbs, 6); /* quality */
				kff = 1 << gf_bs_read_int(vbs, 5);
				gf_bs_del(vbs);

				theora_kgs = 0;
				kff--;
				while (kff) {
					theora_kgs ++;
					kff >>= 1;
				}
			}
		}
		free(op.packet);
		op.packetno ++;
	}
	gf_bs_del(bs);
	gf_odf_desc_del((GF_Descriptor *)esd);

	while (ogg_stream_pageout(&os, &og)>0) {
		fwrite(og.header, 1, og.header_len, out);
		fwrite(og.body, 1, og.body_len, out);
	}
	
	op.granulepos = -1;

	count = gf_isom_get_sample_count(dumper->file, track);

	nb_i = nb_p = 0;
	samp = gf_isom_get_sample(dumper->file, track, 1, &di);
	for (i=0; i<count; i++) {
		GF_ISOSample *next_samp = gf_isom_get_sample(dumper->file, track, i+2, &di);
		if (!samp) break;
		op.bytes = samp->dataLength;
		op.packet = (unsigned char*)samp->data;
		op.packetno ++;

		if (theora_kgs) {
			if (samp->IsRAP) {
				if (i) nb_i+=nb_p+1;
				nb_p = 0;
			} else {
				nb_p++;
			}
			op.granulepos = nb_i;
			op.granulepos <<= theora_kgs;
			op.granulepos |= nb_p;
		} else {
			if (next_samp) op.granulepos = next_samp->DTS;
		}
		if (!next_samp) op.e_o_s = 1;

		ogg_stream_packetin(&os, &op);

		gf_isom_sample_del(&samp);
		samp = next_samp;
		next_samp = NULL;
		gf_set_progress("OGG Export", i+1, count);
		if (dumper->flags & GF_EXPORT_DO_ABORT) break;

		while (ogg_stream_pageout(&os, &og)>0) {
			fwrite(og.header, 1, og.header_len, out);
			fwrite(og.body, 1, og.body_len, out);
		}
	}
	if (samp) gf_isom_sample_del(&samp);

	while (ogg_stream_flush(&os, &og)>0) {
		fwrite(og.header, 1, og.header_len, out);
		fwrite(og.body, 1, og.body_len, out);
	}
    ogg_stream_clear(&os);
	fclose(out);
	return GF_OK;
}

GF_Err gf_export_hint(GF_MediaExporter *dumper)
{
	GF_Err e;
	char szName[1000], szType[5];
	char *pck;
	FILE *out;
	u32 track, i, size, m_stype, sn, count;

	track = gf_isom_get_track_by_id(dumper->file, dumper->trackID);
	m_stype = gf_isom_get_media_subtype(dumper->file, track, 1);

	e = gf_isom_reset_hint_reader(dumper->file, track, dumper->sample_num ? dumper->sample_num : 1, 0, 0, 0);
	if (e) return gf_export_message(dumper, e, "Error initializing hint reader");

	gf_export_message(dumper, GF_OK, "Extracting hint track samples - type %s", szType);

	count = gf_isom_get_sample_count(dumper->file, track);
	if (dumper->sample_num) count = 0;

	i = 1;
	while (1) {
		e = gf_isom_next_hint_packet(dumper->file, track, &pck, &size, NULL, NULL, NULL, &sn);
		if (e==GF_EOS) break;
		if (dumper->sample_num && (dumper->sample_num != sn)) {
			free(pck);
			break;
		}
		if (e) return gf_export_message(dumper, e, "Error fetching hint packet %d", i);
		sprintf(szName, "%s_pck_%04d.%s", dumper->out_name, i, gf_4cc_to_str(m_stype));
		out = fopen(szName, "wb");
		fwrite(pck, size, 1, out);
		fclose(out);
		free(pck);
		i++;
		if (count) gf_set_progress("Hint Export", sn, count);
	}
	if (count) gf_set_progress("Hint Export", count, count);

	return GF_OK;
}

static void write_jp2_file(GF_BitStream *bs, char *data, u32 data_size, char *dsi, u32 dsi_size)
{
	gf_bs_write_u32(bs, 12);
	gf_bs_write_u32(bs, GF_4CC('j','P',' ',' '));
	gf_bs_write_u32(bs, 0x0D0A870A);

	gf_bs_write_u32(bs, 20);
	gf_bs_write_u32(bs, GF_4CC('f','t','y','p'));
	gf_bs_write_u32(bs, GF_4CC('j','p','2',' '));
	gf_bs_write_u32(bs, 0);
	gf_bs_write_u32(bs, GF_4CC('j','p','2',' '));

	gf_bs_write_data(bs, dsi, dsi_size);
	gf_bs_write_data(bs, data, data_size);
}

GF_Err gf_media_export_samples(GF_MediaExporter *dumper)
{
	GF_DecoderConfig *dcfg;
	GF_GenericSampleDescription *udesc;
	char szName[1000], szEXT[10], szNum[1000], *dsi;
	FILE *out;
	GF_BitStream *bs;
	u32 track, i, di, count, m_type, m_stype, dsi_size, is_mj2k;

	track = gf_isom_get_track_by_id(dumper->file, dumper->trackID);
	m_type = gf_isom_get_media_type(dumper->file, track);
	m_stype = gf_isom_get_media_subtype(dumper->file, track, 1);
	dsi_size = 0;

	if (dumper->sample_num) sprintf(szNum, " %d", dumper->sample_num);
	else strcpy(szNum, "s");

	is_mj2k = 0;
	dsi = NULL;
	udesc = NULL;
	dcfg = NULL;
	if ((m_stype==GF_ISOM_SUBTYPE_MPEG4) || (m_stype==GF_ISOM_SUBTYPE_MPEG4_CRYP)) 
		dcfg = gf_isom_get_decoder_config(dumper->file, track, 1);

	strcpy(szName, dumper->out_name ? dumper->out_name : "");
	if (dcfg) {
		switch (dcfg->streamType) {
		case GF_STREAM_VISUAL:
			switch (dcfg->objectTypeIndication) {
			case 0x20:
				strcpy(szEXT, ".cmp");
				gf_export_message(dumper, GF_OK, "Dumping MPEG-4 Visual sample%s", szNum);
				break;
			case 0x21:
				strcpy(szEXT, ".h264");
				gf_export_message(dumper, GF_OK, "Dumping MPEG-4 AVC-H264 Visual sample%s", szNum);
				break;
			case 0x6C:
				strcpy(szEXT, ".jpg");
				gf_export_message(dumper, GF_OK, "Dumping JPEG image%s", szNum);
				break;
			case 0x6D:
				strcpy(szEXT, ".png");
				gf_export_message(dumper, GF_OK, "Dumping PNG image%s", szNum);
				break;
			case 0x6E:
				strcpy(szEXT, ".jp2");
				gf_export_message(dumper, GF_OK, "Dumping JPEG 2000 image%s", szNum);
				break;
			case GPAC_OGG_MEDIA_OTI:
				strcpy(szEXT, ".theo");
				gf_export_message(dumper, GF_OK, "Dumping Theora video sample%s", szNum);
				break;
			default:
				strcpy(szEXT, ".raw");
				gf_export_message(dumper, GF_OK, "Dumping Unknown video sample%s (OTI %d)", szNum, dcfg->objectTypeIndication);
				break;
			}
			break;
		case GF_STREAM_AUDIO:
			switch (dcfg->objectTypeIndication) {
			case 0x66:
			case 0x67:
			case 0x68:
			case 0x40:
				strcpy(szEXT, ".aac");
				gf_export_message(dumper, GF_OK, "Dumping MPEG-%d AAC sample%s", (dcfg->objectTypeIndication==0x40) ? 4 : 2, szNum);
				break;
			case 0x69:
			case 0x6B:
				strcpy(szEXT, ".mp3");
				gf_export_message(dumper, GF_OK, "Dumping MPEG-1/2 Audio (MP3) sample%s", szNum);
				break;
			case GPAC_OGG_MEDIA_OTI:
				strcpy(szEXT, ".vorb");
				gf_export_message(dumper, GF_OK, "Dumping Vorbis audio sample%s", szNum);
				break;
			default:
				strcpy(szEXT, ".raw");
				gf_export_message(dumper, GF_OK, "Dumping Unknown audio sample%s (OTI %d)", szNum, dcfg->objectTypeIndication);
				break;
			}
			break;
		case GF_STREAM_SCENE:
			strcpy(szEXT, ".bifs"); gf_export_message(dumper, GF_OK, "Dumping BIFS sample%s", szNum); 
			break;
		case GF_STREAM_OD:
			strcpy(szEXT, ".od"); gf_export_message(dumper, GF_OK, "Dumping OD sample%s", szNum); 
			break;
		case GF_STREAM_MPEGJ:
			strcpy(szEXT, ".mpj"); gf_export_message(dumper, GF_OK, "Dumping MPEG-J sample%s", szNum); 
			break;
		case GF_STREAM_OCI:
			strcpy(szEXT, ".oci"); gf_export_message(dumper, GF_OK, "Dumping OCI sample%s", szNum); 
			break;
		case GF_STREAM_MPEG7:
			strcpy(szEXT, ".mp7"); gf_export_message(dumper, GF_OK, "Dumping MPEG7 sample%s", szNum); 
			break;
		case GF_STREAM_IPMP:
			strcpy(szEXT, ".ipmp"); gf_export_message(dumper, GF_OK, "Dumping IPMP sample%s", szNum); 
			break;
		case GF_STREAM_TEXT:
			strcpy(szEXT, ".tx3g"); gf_export_message(dumper, GF_OK, "Dumping 3GP Text sample%s", szNum); 
			break;
		default:
			gf_odf_desc_del((GF_Descriptor *) dcfg);
			return gf_export_message(dumper, GF_NOT_SUPPORTED, "Cannot dump systems track ID %d sample%s - use NHNT", dumper->trackID, szNum);
		}
		gf_odf_desc_del((GF_Descriptor *) dcfg);
	} else if ((m_stype==GF_ISOM_SUBTYPE_3GP_AMR) || (m_stype==GF_ISOM_SUBTYPE_3GP_AMR_WB)) {
		strcpy(szEXT, ".amr");
		gf_export_message(dumper, GF_OK, "Extracting AMR Audio sample%s", szNum);
	} else if (m_stype==GF_ISOM_SUBTYPE_3GP_H263) {
		gf_export_message(dumper, GF_OK, "Extracting H263 Video sample%s", szNum);
		strcpy(szEXT, ".263");
	} else if (m_stype==GF_ISOM_SUBTYPE_AVC_H264) {
		strcpy(szEXT, ".h264");
		gf_export_message(dumper, GF_OK, "Dumping MPEG-4 AVC-H264 Visual sample%s", szNum);
	} else if (m_type==GF_ISOM_MEDIA_FLASH) {
		gf_export_message(dumper, GF_OK, "Extracting Macromedia Flash Movie sample%s", szNum);
		strcpy(szEXT, ".swf");
	} else if (m_type==GF_ISOM_MEDIA_HINT) {
		return gf_export_hint(dumper);
	} else if (m_stype==GF_4CC('m','j','p','2')) {
		strcpy(szEXT, ".jp2");
		gf_export_message(dumper, GF_OK, "Dumping JPEG 2000 sample%s", szNum);
		udesc = gf_isom_get_generic_sample_description(dumper->file, track, 1);
		dsi = udesc->extension_buf;
		dsi_size = udesc->extension_buf_size;
		free(udesc);
		is_mj2k = 1;
	} else {
		strcpy(szEXT, ".");
		strcat(szEXT, gf_4cc_to_str(m_stype));
		udesc = gf_isom_get_generic_sample_description(dumper->file, track, 1);
		switch (m_type) {
		case GF_ISOM_MEDIA_VISUAL: gf_export_message(dumper, GF_OK, "Extracting \'%s\' Video - Compressor %s", szEXT, udesc ? udesc->compressor_name: "Unknown"); break;
		case GF_ISOM_MEDIA_AUDIO: gf_export_message(dumper, GF_OK, "Extracting \'%s\' Audio - Compressor %s", szEXT, udesc ? udesc->compressor_name : "Unknown"); break;
		default:
			gf_export_message(dumper, GF_OK, "Extracting \'%s\' Track (type '%s') - Compressor %s sample%s", szEXT, gf_4cc_to_str(m_type), udesc ? udesc->compressor_name : "Unknown", szNum);
			break;
		}
		if (udesc->extension_buf) free(udesc->extension_buf);
		if (udesc) free(udesc);
	}
	if (dumper->flags & GF_EXPORT_PROBE_ONLY) return GF_OK;

	if (dumper->sample_num) {
		GF_ISOSample *samp = gf_isom_get_sample(dumper->file, track, dumper->sample_num, &di);
		if (!samp) return GF_BAD_PARAM;
		sprintf(szName, "%s_%d%s", dumper->out_name, dumper->sample_num, szEXT);
		out = fopen(szName, "wb");
		bs = gf_bs_from_file(out, GF_BITSTREAM_WRITE);
		if (is_mj2k) 
			write_jp2_file(bs, samp->data, samp->dataLength, dsi, dsi_size);
		else
			gf_bs_write_data(bs, samp->data, samp->dataLength);
		gf_isom_sample_del(&samp);
		gf_bs_del(bs);
		fclose(out);
		if (dsi) free(dsi);
		return GF_OK;
	}

	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 (count>=1000) {
			sprintf(szName, "%s_%08d%s", dumper->out_name, i+1, szEXT);
		} else {
			sprintf(szName, "%s_%03d%s", dumper->out_name, i+1, szEXT);
		}
		out = fopen(szName, "wb");
		bs = gf_bs_from_file(out, GF_BITSTREAM_WRITE);
		if (dsi) gf_bs_write_data(bs, dsi, dsi_size);
		if (is_mj2k) 
			write_jp2_file(bs, samp->data, samp->dataLength, dsi, dsi_size);
		else
			gf_bs_write_data(bs, samp->data, samp->dataLength);
		gf_isom_sample_del(&samp);
		gf_set_progress("Media Export", i+1, count);
		gf_bs_del(bs);
		fclose(out);
		if (dumper->flags & GF_EXPORT_DO_ABORT) break;
	}
	if (dsi) free(dsi);
	return GF_OK;
}

static GF_Err gf_dump_to_vobsub(GF_MediaExporter *dumper, char *szName, u32 track, char *dsi, u32 dsiSize)
{
	FILE			*fidx, *fsub;
	u32				 width, height, i, count, di;
	GF_ISOSample	*samp;
	char			 lang[] = "\0\0\0\0";

	/* Check decoder specific information (palette) size - should be 64 */
	if (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 */
	fidx = gf_f64_open(szName, "w");
	if (!fidx) {
		return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
	}

	/* Create a sub file */
	vobsub_trim_ext(szName);
	szName = strcat(szName, ".sub");
	fsub = gf_f64_open(szName, "wb");
	if (!fsub) {
		fclose(fidx);
		return gf_export_message(dumper, GF_IO_ERR, "Error opening %s for writing - check disk access & permissions", szName);
	}

	/* Retrieve original subpicture resolution */
	gf_isom_get_track_layout_info(dumper->file, track, &width, &height, NULL, NULL, NULL);

	/* Write header */
	fputs("# VobSub index file, v7 (do not modify this line!)\n#\n", fidx);

	/* Write original subpicture resolution */
	fprintf(fidx, "size: %ux%u\n", width, height);

	/* Write palette */
	fputs("palette:", fidx);
	for (i = 0; i < 16; i++) {
		s32 y, u, v, r, g, b;

		y = (s32)dsi[(i<<2)+1] - 0x10;
		u = (s32)dsi[(i<<2)+3] - 0x80;
		v = (s32)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) fputc(',', fidx);

#define CLIP(x) (((x) >= 0) ? (((x) < 256) ? (x) : 255) : 0)
		fprintf(fidx, " %02x%02x%02x", CLIP(r), CLIP(g), CLIP(b));
#undef CLIP
	}
	fputc('\n', fidx);

	/* Write some other useful values */
	fputs("# ON: displays only forced subtitles, OFF: shows everything\n", fidx);
	fputs("forced subs: OFF\n\n", fidx);

	/* Write current language index */
	fputs("# Language index in use\nlangidx: 0\n", fidx);

	/* Write language header */
	gf_isom_get_media_language(dumper->file, track, lang);
	fprintf(fidx, "id: %s, index: 0\n", vobsub_lang_id(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);
#if defined(WIN32)  && !defined(__GNUC__)
		fprintf(fidx, "timestamp: %02u:%02u:%02u:%03u, filepos: %09lx\n", hh, mm, ss, ms, gf_f64_tell(fsub));
#else
		fprintf(fidx, "timestamp: %02u:%02u:%02u:%03u, filepos: %09llx\n", hh, mm, ss, ms, gf_f64_tell(fsub));
#endif
		if (vobsub_packetize_subpicture(fsub, samp->DTS, samp->data, samp->dataLength) != GF_OK) {
{'fraction_non_alphanumeric': 0.1559862266508212, 'fraction_numerical': 0.024895633360758142, 'mean_word_length': 4.371194762684125, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 207, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2206024', 'n_tokens_mistral': 15620, 'n_tokens_neox': 13977, 'n_words': 3027}