
/*
 * CINELERRA
 * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 */

#include "asset.h"
#include "bcsignals.h"
#include "byteorder.h"
#include "clip.h"
#include "cmodel_permutation.h"
#include "edit.h"
#include "file.h"
#include "fileogg.h"
#include "guicast.h"
#include "language.h"
#include "mutex.h"
#include "mwindow.inc"
#include "quicktime.h"
#include "vframe.h"
#include "videodevice.inc"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#define READ_SIZE 66000

/* This code was aspired by ffmpeg2theora */
/* Special thanks for help on this code goes out to j@v2v.cc */


FileOGG::FileOGG(Asset *asset, File *file)
 : FileBase(asset, file)
{
	if(asset->format == FILE_UNKNOWN)
		asset->format = FILE_OGG;
	asset->byte_order = 0;
	reset_parameters();
}

FileOGG::~FileOGG()
{
	if (tf) 
	{

		if (tf->videosync) 
		{
			ogg_sync_clear(&tf->videosync->sync);
			delete tf->videosync;
			theora_info_clear(&tf->ti);
			theora_comment_clear(&tf->tc);
		}
		if (tf->audiosync) 
		{
			ogg_sync_clear(&tf->audiosync->sync);
			delete tf->audiosync;
			vorbis_info_clear(&tf->vi);
			vorbis_comment_clear(&tf->vc);
		}
		delete tf;
	}
	if (temp_frame) delete temp_frame;
	if (stream) close_file();
	if(pcm_history)
	{
		for(int i = 0; i < asset->channels; i++)
			delete [] pcm_history[i];
		delete [] pcm_history;
	}

	if (flush_lock) delete flush_lock;
}

void FileOGG::get_parameters(BC_WindowBase *parent_window,
	Asset *asset,
	BC_WindowBase* &format_window,
	int audio_options,
	int video_options)
{
	if(audio_options)
	{
		OGGConfigAudio *window = new OGGConfigAudio(parent_window, asset);
		format_window = window;
		window->create_objects();
		window->run_window();
		delete window;
	}
	else
	if(video_options)
	{
		OGGConfigVideo *window = new OGGConfigVideo(parent_window, asset);
		format_window = window;
		window->create_objects();
		window->run_window();
		delete window;
	}
}

int FileOGG::reset_parameters_derived()
{
	tf = 0;
	temp_frame = 0;
	stream = 0;
	flush_lock = 0;
	pcm_history = 0;

}

static int read_buffer(FILE *in, sync_window_t *sw, int buflen)
{
	char *buffer = ogg_sync_buffer(&sw->sync, buflen);
//	printf("reading range: %lli - %lli\n", sw->file_bufpos, sw->file_bufpos + buflen);
	sw->wlen = fread(buffer, 1, buflen, in);
	ogg_sync_wrote(&sw->sync, sw->wlen);
//	printf("XX data: %c %c %c %c\n", sw->sync.data[0], sw->sync.data[1], sw->sync.data[2], sw->sync.data[3]);
	sw->file_bufpos += sw->wlen;
//	printf("sb: %i\n",buffer);
	return (sw->wlen);
}

static int read_buffer_at(FILE *in, sync_window_t *sw, int buflen, off_t filepos)
{
//	printf("seeking to %lli %lli\n", filepos, sw->file_bufpos);
	fseeko(in, filepos, SEEK_SET);
//	if (sw->file_bufpos != filepos)
//	{
		sw->file_bufpos = filepos;
		sw->file_pagepos = filepos; // this one is not valid until sync_pageseek!
		ogg_sync_reset(&sw->sync);
			
//	}
	return read_buffer(in, sw, buflen);
}

static int take_page_out_autoadvance(FILE *in, sync_window_t *sw, ogg_page *og)
{
	while (1)
	{
		int ret = ogg_sync_pageout(&sw->sync, og);
		if (ret > 0)
		{
//		printf("fpa: %lli\n", sw->file_pagepos);
// advance 'virtual' position
			sw->file_pagepos += og->header_len + og->body_len; 
//		printf("ret2: %i %i\n",ret, og->header_len + og->body_len); 
			return ret;
		}
		else if (ret < 0)
		{
			printf("FileOGG: Taking page out on nonsynced stream!\n");
			return ret;
			
		} else
		{
			// need more data for page
			if ((ret = read_buffer(in, sw, READ_SIZE)) == 0) 
			{
				printf("FileOGG: There is no more data in the file we are reading from\n");
				return 0;  // No more data
			}
		}
	}
	return 1;
}


// we never need to autoadvance when syncing, since our read chunks are larger than 
// maximum page size
static int sync_and_take_page_out(sync_window_t *sw, ogg_page *page)
{
	page->header_len = 0;
	page->body_len = 0;
	page->header = 0;
	page->body = 0;
	int ret = ogg_sync_pageseek(&sw->sync, page);
	if (ret < 0)
	{
		sw->file_pagepos -= ret;
	}
	else if (ret > 0)
	{
		sw->file_pagepos += ret;
//		printf("ret: %i %i\n",ret, page->header_len + page->body_len); 
	}
	return ret;
}


int FileOGG::open_file(int rd, int wr)
{
	if (!tf)
	{
		tf = new theoraframes_info_t;
		tf->audiosync = 0;
		tf->videosync = 0;
	}


	if(wr)
	{


		if((stream = fopen(asset->path, "w+b")) == 0)
		{
			perror(_("FileOGG::open_file rdwr"));
			return 1;
		}

		tf->audio_bytesout = 0;
		tf->video_bytesout = 0;
		tf->videotime = 0;
		tf->audiotime = 0;

		tf->vpage_valid = 0;
		tf->apage_valid = 0;
		tf->apage_buffer_length = 0;
		tf->vpage_buffer_length = 0;
		tf->apage = NULL;
		tf->vpage = NULL;
	    tf->v_pkg=0;
    	tf->a_pkg=0;


		/* yayness.  Set up Ogg output stream */
		srand (time (NULL));

		if(asset->video_data)
		{
			ogg_stream_init (&tf->to, rand ());    /* oops, add one ot the above */
		
			theora_info_init (&tf->ti);
			
			tf->ti.frame_width = asset->width; 
			tf->ti.frame_height = asset->height;
			
			tf->ti.width = ((asset->width + 15) >>4)<<4; // round up to the nearest multiple of 16 
			tf->ti.height = ((asset->height + 15) >>4)<<4; // round up to the nearest multiple of 16
			if (tf->ti.width != tf->ti.frame_width || tf->ti.height != tf->ti.frame_height)
				printf("FileOGG: WARNING: Encoding theora when width or height are not dividable by 16 is suboptimal\n");
			
			tf->ti.offset_x = 0;
			tf->ti.offset_y = tf->ti.height - tf->ti.frame_height;
			tf->ti.fps_numerator = (unsigned int)(asset->frame_rate * 1000000);
			tf->ti.fps_denominator = 1000000;
			
			if (asset->aspect_ratio > 0)
			{
				// Cinelerra uses frame aspect ratio, theora uses pixel aspect ratio
				float pixel_aspect = asset->aspect_ratio / asset->width * asset->height;
				tf->ti.aspect_numerator = (unsigned int)(pixel_aspect * 1000000);
				tf->ti.aspect_denominator = 1000000;
			} else
			{
				tf->ti.aspect_numerator = 1000000;
				tf->ti.aspect_denominator = 1000000;
			}
			if(EQUIV(asset->frame_rate, 25) || EQUIV(asset->frame_rate, 50))
				tf->ti.colorspace = OC_CS_ITU_REC_470BG;
			else if((asset->frame_rate > 29 && asset->frame_rate < 31) || (asset->frame_rate > 59 && asset->frame_rate < 61) )
				tf->ti.colorspace = OC_CS_ITU_REC_470M;
			else
				tf->ti.colorspace = OC_CS_UNSPECIFIED;

			if (asset->theora_fix_bitrate)
			{
				tf->ti.target_bitrate = asset->theora_bitrate; 
				tf->ti.quality = 0;
			} else
			{
				tf->ti.target_bitrate = 0;
				tf->ti.quality = asset->theora_quality;     // video quality 0-63
			}
			tf->ti.dropframes_p = 0;
			tf->ti.quick_p = 1;
			tf->ti.keyframe_auto_p = 1;
			tf->ti.keyframe_frequency = asset->theora_keyframe_frequency;
			tf->ti.keyframe_frequency_force = asset->theora_keyframe_force_frequency;
			tf->ti.keyframe_data_target_bitrate = (unsigned int) (tf->ti.target_bitrate * 1.5) ;
			tf->ti.keyframe_auto_threshold = 80;
			tf->ti.keyframe_mindistance = 8;
			tf->ti.noise_sensitivity = 1;		
			tf->ti.sharpness = 2;
			
					
			if (theora_encode_init (&tf->td, &tf->ti))
			{
				printf("FileOGG: initialization of theora codec failed\n");
			}
		}
		/* init theora done */

		/* initialize Vorbis too, if we have audio. */
		if(asset->audio_data)
		{
			ogg_stream_init (&tf->vo, rand ());    
			vorbis_info_init (&tf->vi);
			/* Encoding using a VBR quality mode.  */
			int ret;
			if(!asset->vorbis_vbr) 
			{
				ret = vorbis_encode_init(&tf->vi, 
			    				asset->channels, 
			    				asset->sample_rate, 
			    				asset->vorbis_max_bitrate, 
			    				asset->vorbis_bitrate,
			    				asset->vorbis_min_bitrate); 
			} else
			{
				// Set true VBR as demonstrated by http://svn.xiph.org/trunk/vorbis/doc/vorbisenc/examples.html
				ret = vorbis_encode_setup_managed(&tf->vi,
					asset->channels, 
					asset->sample_rate, 
					-1, 
					asset->vorbis_bitrate, 
					-1);
				ret |= vorbis_encode_ctl(&tf->vi, OV_ECTL_RATEMANAGE_AVG, NULL);
				ret |= vorbis_encode_setup_init(&tf->vi);
			}

			if (ret)
			{
				fprintf (stderr,
					"The Vorbis encoder could not set up a mode according to\n"
					"the requested quality or bitrate.\n\n");
				return 1;
			}

			vorbis_comment_init (&tf->vc); // comment is cleared lateron 
			vorbis_comment_add_tag (&tf->vc, (char*)"ENCODER", (char*)PACKAGE_STRING);
			/* set up the analysis state and auxiliary encoding storage */
			vorbis_analysis_init (&tf->vd, &tf->vi);
			vorbis_block_init (&tf->vd, &tf->vb);

		}
		/* audio init done */

		/* write the bitstream header packets with proper page interleave */

		/* first packet will get its own page automatically */
		if(asset->video_data)
		{
			theora_encode_header (&tf->td, &tf->op);
			ogg_stream_packetin (&tf->to, &tf->op);
			if (ogg_stream_pageout (&tf->to, &tf->og) != 1)
			{
				fprintf (stderr, "Internal Ogg library error.\n");
				return 1;
			}
			fwrite (tf->og.header, 1, tf->og.header_len, stream);
			fwrite (tf->og.body, 1, tf->og.body_len, stream);

			/* create the remaining theora headers */
			theora_comment_init (&tf->tc);
			theora_comment_add_tag (&tf->tc, (char*)"ENCODER", (char*)PACKAGE_STRING);
			theora_encode_comment (&tf->tc, &tf->op);
			ogg_stream_packetin (&tf->to, &tf->op);
			theora_comment_clear(&tf->tc);
			theora_encode_tables (&tf->td, &tf->op);
			ogg_stream_packetin (&tf->to, &tf->op);
		}
		if(asset->audio_data)
		{
			ogg_packet header;
			ogg_packet header_comm;
			ogg_packet header_code;

			vorbis_analysis_headerout (&tf->vd, &tf->vc, &header,
				       &header_comm, &header_code);
			ogg_stream_packetin (&tf->vo, &header);    /* automatically placed in its own page */
			vorbis_comment_clear(&tf->vc);
			if (ogg_stream_pageout (&tf->vo, &tf->og) != 1)
			{
				fprintf (stderr, "Internal Ogg library error.\n");
				return 1;
			}
			fwrite (tf->og.header, 1, tf->og.header_len, stream);
			fwrite (tf->og.body, 1, tf->og.body_len, stream);

			/* remaining vorbis header packets */
			ogg_stream_packetin (&tf->vo, &header_comm);
			ogg_stream_packetin (&tf->vo, &header_code);
		}

		/* Flush the rest of our headers. This ensures
		 * the actual data in each stream will start
		 * on a new page, as per spec. */
		while (1 && asset->video_data)
		{
			int result = ogg_stream_flush (&tf->to, &tf->og);
			if (result < 0)
			{
				/* can't get here */
				fprintf (stderr, "Internal Ogg library error.\n");
				return 1;
			}
			if (result == 0)
				break;
			fwrite (tf->og.header, 1, tf->og.header_len, stream);
			fwrite (tf->og.body, 1, tf->og.body_len, stream);
		}
		while (1 && asset->audio_data)
		{
			int result = ogg_stream_flush (&tf->vo, &tf->og);
			if (result < 0)
			{
				/* can't get here */
				fprintf (stderr, "Internal Ogg library error.\n");
				return 1;
			}
			if (result == 0)
				break;
			fwrite (tf->og.header, 1, tf->og.header_len, stream);
			fwrite (tf->og.body, 1, tf->og.body_len, stream);
		}
		flush_lock = new Mutex("OGGFile::Flush lock");
//		printf("End of headers at position: %lli\n", ftello(stream));
	} else
	if (rd)
	{

		if((stream = fopen(asset->path, "rb")) == 0)
		{
			perror(_("FileOGG::open_file rdwr"));
			return 1;
		}

		/* get file length */
		struct stat file_stat;
		stat(asset->path, &file_stat);
		file_length = file_stat.st_size;

		/* start up Ogg stream synchronization layer */
		/* oy is used just here to parse header, we use separate syncs for video and audio*/
		sync_window_t oy;
		ogg_sync_init(&oy.sync);
		// make sure we init the position structures to zero
		read_buffer_at(stream, &oy, READ_SIZE, 0);


		/* init supporting Vorbis structures needed in header parsing */
		vorbis_info_init(&tf->vi);
		vorbis_comment_init(&tf->vc);


		/* init supporting Theora structures needed in header parsing */
		theora_comment_init(&tf->tc);
		theora_info_init(&tf->ti);



		/* Ogg file open; parse the headers */
		/* Only interested in Vorbis/Theora streams */
		int stateflag = 0;
		int theora_p = 0;
		int vorbis_p = 0;
		while(!stateflag)
		{


//			int ret = read_buffer(stream, &oy, 4096);
			TRACE("FileOGG::open_file 60")
//			if(ret == 0)
//				break;
			
			while(take_page_out_autoadvance(stream, &oy, &tf->og) > 0)
			{
				ogg_stream_state test;

				/* is this a mandated initial header? If not, stop parsing */
				if(!ogg_page_bos(&tf->og))
				{
					/* don't leak the page; get it into the appropriate stream */
				//	queue_page(&tf->og);
					if(theora_p)ogg_stream_pagein(&tf->to, &tf->og);
					if(vorbis_p)ogg_stream_pagein(&tf->vo, &tf->og);
				
					stateflag = 1;
					break;
				}

				ogg_stream_init(&test, ogg_page_serialno(&tf->og));
				ogg_stream_pagein(&test, &tf->og);
				ogg_stream_packetout(&test, &tf->op);

				/* identify the codec: try theora */
				if(!theora_p && theora_decode_header(&tf->ti, &tf->tc, &tf->op)>=0)
				{
					/* it is theora */
					memcpy(&tf->to, &test, sizeof(test));
					theora_p = 1;
	// find out granule shift - from liboggz's oggz_auto.c
					unsigned char * header = tf->op.packet;
					theora_keyframe_granule_shift = (char) ((header[40] & 0x03) << 3);
					theora_keyframe_granule_shift |= (header[41] & 0xe0) >> 5;

				} else if(!vorbis_p && vorbis_synthesis_headerin(&tf->vi, &tf->vc, &tf->op)>=0)
				{
					/* it is vorbis */
					memcpy(&tf->vo, &test, sizeof(test));
					vorbis_p = 1;
				} else 
				{
					/* whatever it is, we don't care about it */
					ogg_stream_clear(&test);
				}
			}
		/* fall through to non-bos page parsing */
		}


		/* we're expecting more header packets. */
		while((theora_p && theora_p < 3) || (vorbis_p && vorbis_p < 3))
		{
			int ret;

			/* look for further theora headers */
			while(theora_p && (theora_p < 3) && (ret = ogg_stream_packetout(&tf->to, &tf->op)))
			{
				if(ret < 0)
				{
					fprintf(stderr,"FileOGG: Error parsing Theora stream headers; corrupt stream?\n");
					return 1;
				}
				if(theora_decode_header(&tf->ti, &tf->tc, &tf->op))
				{
					printf("FileOGG: Error parsing Theora stream headers; corrupt stream?\n");
					return 1;
				}
				theora_p++;
				if(theora_p == 3) 
					break;
			}

			/* look for more vorbis header packets */
			while(vorbis_p && (vorbis_p < 3) && (ret = ogg_stream_packetout(&tf->vo, &tf->op)))
			{
				if(ret<0)
				{
					fprintf(stderr,"FileOGG: Error parsing Vorbis stream headers; corrupt stream?\n");
					return 1;
				}
				if (vorbis_synthesis_headerin(&tf->vi, &tf->vc, &tf->op))
				{
					fprintf(stderr,"FileOGG: Error parsing Vorbis stream headers; corrupt stream?\n");
					return 1;
				}
				vorbis_p++;
				if (vorbis_p == 3)
					break;
			}

			if (vorbis_p == 3 && theora_p == 3) 
				break;
			/* The header pages/packets will arrive before anything else we
			    care about, or the stream is not obeying spec */

			if(take_page_out_autoadvance(stream, &oy, &tf->og) > 0)
			{
//				queue_page(&tf->og); /* demux into the appropriate stream */
				if(theora_p) ogg_stream_pagein(&tf->to, &tf->og);
				if(vorbis_p) ogg_stream_pagein(&tf->vo, &tf->og);

			} else
			{
				fprintf(stderr,"FileOGG: End of file while searching for codec headers.\n");
				return 1;
			}
		}
		// Remember where the real data begins for later seeking purposes
		filedata_begin = oy.file_pagepos; 



		/* and now we have it all.  initialize decoders */
		if(theora_p)
		{
			int ret;

// WORKAROUND for bug in alpha4 version of theora:
			tf->td.internal_encode = 0;

			ret = theora_decode_init(&tf->td, &tf->ti);
			double fps = (double)tf->ti.fps_numerator/tf->ti.fps_denominator;
/*			printf("FileOGG: Ogg logical stream %x is Theora %dx%d %.02f fps\n",
				(unsigned int)tf->to.serialno, tf->ti.width, tf->ti.height, 
				fps);
*/
/*
Not yet available in alpha4, we assume 420 for now

			switch(tf->ti.pixelformat)
			{
				case OC_PF_420: printf(" 4:2:0 video\n"); break;
				case OC_PF_422: printf(" 4:2:2 video\n"); break;
				case OC_PF_444: printf(" 4:4:4 video\n"); break;
				case OC_PF_RSVD:
				default:
					printf(" video\n  (UNKNOWN Chroma sampling!)\n");
				break;
			}
*/

			theora_cmodel = BC_YUV420P;
			
			if(tf->ti.width!=tf->ti.frame_width || tf->ti.height!=tf->ti.frame_height)
				printf("FileOGG::  Frame content is %dx%d with offset (%d,%d), We do not support this yet.\n",
				tf->ti.frame_width, tf->ti.frame_height, tf->ti.offset_x, tf->ti.offset_y);

			tf->videosync = new sync_window_t;
			ogg_sync_init(&tf->videosync->sync);
			tf->videosync->wlen = 0;

			ret = ogg_get_first_page(tf->videosync, tf->to.serialno, &tf->videopage);
			ogg_packet op;

			// we have headers already decoded, so just skip them
			ogg_stream_reset(&tf->to);
			ogg_stream_pagein(&tf->to, &tf->videopage);
			while (1)
			{
				while (ogg_stream_packetpeek(&tf->to, NULL) != 1)
				{
					if (!ogg_get_next_page(tf->videosync, tf->to.serialno, &tf->videopage))
					{
						printf("FileOGG: Cannot find next page while looking for first non-header packet\n");
						return 1;
					}
					ogg_stream_pagein(&tf->to, &tf->videopage);
				}
				ogg_stream_packetout(&tf->to, &op);
				if (!theora_packet_isheader(&op))
					break;
			}
			// now get to the page of the finish of the first packet
			while (ogg_page_packets(&tf->videopage) == 0) 
			{
				if (ogg_page_granulepos(&tf->videopage) != -1)
				{
					printf("FileOGG: Broken ogg file - broken page: ogg_page_packets == 0 and granulepos != -1\n");
					return 1;
				}
				ogg_get_next_page(tf->videosync, tf->to.serialno, &tf->videopage);
			}
			// FIXME - LOW PRIORITY - start counting at first decodable keyframe!
			// but then we have to fix a/v sync somehow
			start_frame = (int64_t) (theora_granule_frame (&tf->td, ogg_page_granulepos(&tf->videopage)) - ogg_page_packets(&tf->videopage)) + 1;

			ret = ogg_get_last_page(tf->videosync, tf->to.serialno, &tf->videopage);
			last_frame = (int64_t) (theora_granule_frame (&tf->td, ogg_page_granulepos(&tf->videopage)));
			asset->video_length = last_frame - start_frame + 1;
//			printf("FileOGG:: first frame: %lli, last frame: %lli, video length: %lli\n", start_frame, last_frame, asset->video_length);
			
			asset->layers = 1;
			// FIXME - LOW PRIORITY Cinelerra does not honor the frame_width and frame_height
			asset->width = tf->ti.width;
			asset->height = tf->ti.height;
// Don't want a user configured frame rate to get destroyed
			if(!asset->frame_rate)
				asset->frame_rate = fps;

	/*		ogg_get_page_of_frame(tf->videosync, tf->to.serialno, &og, 0 +start_frame);
			ogg_get_page_of_frame(tf->videosync, tf->to.serialno, &og, 1 +start_frame);
			ogg_get_page_of_frame(tf->videosync, tf->to.serialno, &og, 5 +start_frame);
			ogg_get_page_of_frame(tf->videosync, tf->to.serialno, &og, 206 +start_frame);
			ogg_get_page_of_frame(tf->videosync, tf->to.serialno, &og, 207 +start_frame);
			ogg_get_page_of_frame(tf->videosync, tf->to.serialno, &og, 208 +start_frame);

			int64_t kf;
			ogg_seek_to_keyframe(tf->videosync, tf->to.serialno, 0 + start_frame, &kf);
			ogg_seek_to_keyframe(tf->videosync, tf->to.serialno, 1 + start_frame, &kf);
			ogg_seek_to_keyframe(tf->videosync, tf->to.serialno, 5 + start_frame, &kf);
			ogg_seek_to_keyframe(tf->videosync, tf->to.serialno, 66 + start_frame, &kf);
			//printf("Keyframe: %lli\n", kf);
			ogg_seek_to_keyframe(tf->videosync, tf->to.serialno, 1274 + start_frame, &kf);
*/
	
			set_video_position(0); // make sure seeking is done to the first sample
			ogg_frame_position = -10;
			asset->video_data = 1;
			strncpy(asset->vcodec, "theo", 4);


//		    report_colorspace(&ti);
//		    dump_comments(&tc);
		} else 
		{
			/* tear down the partial theora setup */
			theora_info_clear(&tf->ti);
			theora_comment_clear(&tf->tc);
		}


		if(vorbis_p)
		{
			vorbis_synthesis_init(&tf->vd, &tf->vi);
			vorbis_block_init(&tf->vd, &tf->vb);
/*			fprintf(stderr,"FileOGG: Ogg logical stream %x is Vorbis %d channel %d Hz audio.\n",
				(unsigned int)tf->vo.serialno, tf->vi.channels, (int)tf->vi.rate);
*/			
			/* init audio_sync structure */
			tf->audiosync = new sync_window_t;
			ogg_sync_init(&tf->audiosync->sync);
			tf->audiosync->wlen = 0;

			int ret2 = ogg_get_first_page(tf->audiosync, tf->vo.serialno, &tf->audiopage);
			int result;
			ogg_packet op;
			ogg_stream_reset(&tf->vo);
			// FIXME - LOW PRIORITY should be getting pages until one has granulepos, probably never happens in pracitce
			ogg_stream_pagein(&tf->vo, &tf->audiopage);
			ogg_int64_t accumulated = 0;
			long        lastblock = -1;
			while((result = ogg_stream_packetout(&tf->vo, &op)))
			{
				if(result > 0)
				{ // ignore holes 
					long thisblock =  vorbis_packet_blocksize(&tf->vi, &op);
					if(lastblock != -1)
						accumulated += (lastblock + thisblock) >> 2;
					lastblock = thisblock;
				}
			}
			start_sample = ogg_page_granulepos(&tf->audiopage) - accumulated;
/*
			printf("Begin: %lli, To byte: %lli,  granule: %lli, serialno: %x\n", 
					tf->audiosync->file_pagepos_found,
					tf->audiosync->file_pagepos_found + tf->audiopage.body_len + tf->audiopage.header_len,
					ogg_page_granulepos(&tf->audiopage), 
					ogg_page_serialno(&tf->audiopage));
			while (ogg_get_next_page(tf->audiosync, tf->vo.serialno, &tf->audiopage))
				printf("At byte: %lli, To byte: %lli,  granule: %lli, serialno: %x\n", 
					tf->audiosync->file_pagepos_found,
					tf->audiosync->file_pagepos_found + tf->audiopage.body_len + tf->audiopage.header_len,
					ogg_page_granulepos(&tf->audiopage), 
					ogg_page_serialno(&tf->audiopage));
*/

			int ret = ogg_get_last_page(tf->audiosync, tf->vo.serialno, &tf->audiopage);
			last_sample = ogg_page_granulepos(&tf->audiopage);
			asset->audio_length = last_sample - start_sample;
						
/*			printf("FileOGG:: First sample: %lli, last_sample: %lli\n", start_sample, last_sample);
			printf("FileOGG:: audio length: samples: %lli, playing time: %f\n", asset->audio_length, 1.0 * asset->audio_length / tf->vi.rate);
*/			
/*			printf("End: %lli, To byte: %lli,  granule: %lli, serialno: %x\n", 
					tf->audiosync->file_pagepos_found,
					tf->audiosync->file_pagepos_found + tf->audiopage.body_len + tf->audiopage.header_len,
					ogg_page_granulepos(&tf->audiopage), 
					ogg_page_serialno(&tf->audiopage));
			while (ogg_get_prev_page(tf->audiosync, tf->vo.serialno, &tf->audiopage))
				printf("At byte: %lli, To byte: %lli,  granule: %lli, serialno: %x\n", 
					tf->audiosync->file_pagepos_found,
					tf->audiosync->file_pagepos_found + tf->audiopage.body_len + tf->audiopage.header_len,
					ogg_page_granulepos(&tf->audiopage), 
					ogg_page_serialno(&tf->audiopage));
*/
			
/*			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 0);
			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 1);
			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 50);
			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 5000);
			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 30000);
			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 50000);
			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 90000);
			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 95999);
			ogg_get_page_of_sample(tf->audiosync, tf->vo.serialno, &tf->audiopage, 96000);
			ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, 0);
			ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, 1);
			ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, 5000);
			ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, 30000);
			ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, 50000);
			ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, 90000);
			ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, 95999);
			ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, 96000);
*/			
			asset->channels = tf->vi.channels;
			if(!asset->sample_rate)
				asset->sample_rate = tf->vi.rate;
			asset->audio_data = 1;
			

			ogg_sample_position = -10;
			set_audio_position(0); // make sure seeking is done to the first sample
			strncpy(asset->acodec, "vorb", 4);


		} else 
		{
			/* tear down the partial vorbis setup */
			vorbis_info_clear(&tf->vi);
			vorbis_comment_clear(&tf->vc);
		}

		ogg_sync_clear(&oy.sync);

	}
	return 0;
}

int FileOGG::ogg_get_prev_page(sync_window_t *sw, long serialno, ogg_page *og)
{
	char *buffer;
	ogg_page page;
	off_t filepos = sw->file_pagepos_found - READ_SIZE;
	int first_page_offset = 0;
	int done = 0;	
	int read_len = READ_SIZE;

//	printf("fp: %lli pagepos found: %lli\n", filepos, sw->file_pagepos_found);
	while (!done)
	{
		if (filepos < 0)
		{
//			read_len = read_len - (filedata_begin - filepos);
			read_len = read_len + filepos;
			filepos = 0;
		}
		if (read_len <= 0) 
			return 0;
		int have_read = read_buffer_at(stream, sw, read_len, filepos);
		
//		printf("reading at %lli, len: %i, read: %i, pagepos: %lli, pageposfound: %lli\n", filepos, read_len, have_read, sw->file_pagepos, sw->file_pagepos_found);
//		printf("Buffer position: %lli\n", sw->file_bufpos);
//		printf("SS: storage: %i, fill: %i, returned: %i\n", sw->sync.storage, sw->sync.fill, sw->sync.returned);
//		printf("US: unsynced%i, headrebytes: %i, bodybyes: %i\n", sw->sync.unsynced, sw->sync.headerbytes, sw->sync.bodybytes);
//		printf("data: %c %c %c %c\n", sw->sync.data[0], sw->sync.data[1], sw->sync.data[2], sw->sync.data[3]);
		if (!have_read) 
			return 0;
		
// read all pages in the buffer
		int page_offset = 0;
		int page_length = 0;
		int first_page = 1;
		while (first_page || page_length) 
		{
			// if negative, skip bytes
			while ((page_length = sync_and_take_page_out(sw, &page)) < 0)
			{
				page_offset -= page_length;
						
//				if (filepos == 0)
//					printf("BBBb page_len: %i\n", page_length);
			}
//			if (filepos == 0 && page_length)
//			{
//				printf("AAAAAAAAAAAAAAAAAAAAAAAAAaaa\n");
//				printf("pp: %lli %x\n", sw->file_pagepos, ogg_page_serialno(&page));
//			}
			if (first_page)
				first_page_offset = page_offset;
			first_page = 0;
//			printf("pl: %i, serial: %x iscem: %x\n", page_length, ogg_page_serialno(&page), serialno);
			if (page_length && ogg_page_serialno(&page) == serialno)
			{
				// we will copy every page until last page in this buffer
				done = 1;
				
				sw->file_pagepos_found = sw->file_pagepos - page.header_len - page.body_len;
//				printf("got it : %lli %i %i\n", sw->file_pagepos, page.header_len, page.body_len);
				memcpy(og, &page, sizeof(page));
			}
		}
		off_t old_filepos = filepos;
//		printf("fpo: %i\n", first_page_offset);
		filepos = filepos + first_page_offset - READ_SIZE;
	}
	
//	printf("finished\n");
	if (done) 
		return 1;
	else 
		return 0;
}

int FileOGG::ogg_get_last_page(sync_window_t *sw, long serialno, ogg_page *og)
{
	char *buffer;
	ogg_page page;
	off_t filepos = file_length - READ_SIZE;
	if (filepos < 0) 
		filepos = 0;

	int first_page_offset = 0;
	int done = 0;	
	while (!done && filepos >= 0)
	{
		int readlen;
		readlen = read_buffer_at(stream, sw, READ_SIZE, filepos);

// read all pages in the buffer
		int page_offset = 0;
		int page_length = 0;
		int first_page = 1;
		while (first_page || page_length) 
		{
			// if negative, skip bytes
			while ((page_length = sync_and_take_page_out(sw, &page)) < 0)
					page_offset -= page_length;
			if (first_page)
				first_page_offset = page_offset;
			first_page = 0;
			if (page_length && ogg_page_serialno(&page) == serialno)
			{
				// we will copy every page until last page in this buffer
				done = 1;
				sw->file_pagepos_found = sw->file_pagepos - page.header_len - page.body_len;
				memcpy(og, &page, sizeof(page));
			}
		}
		filepos = filepos + first_page_offset - READ_SIZE;		
	}
	
	if (done) 
		return 1;
	else 
		return 0;
}

int FileOGG::ogg_get_first_page(sync_window_t *sw, long serialno, ogg_page *og)
{
//	printf("FileOGG:: Seeking to first page at %lli\n", filedata_begin);
	read_buffer_at(stream, sw, READ_SIZE, 0);
// we don't even need to sync since we _know_ it is right
	return (ogg_get_next_page(sw, serialno, og));
}

int FileOGG::ogg_get_next_page(sync_window_t *sw, long serialno, ogg_page *og)
{
	while (take_page_out_autoadvance(stream, sw, og) > 0)
	{ 
		if (ogg_page_serialno(og) == serialno)
		{
			sw->file_pagepos_found = sw->file_pagepos - og->header_len - og->body_len;
			return 1;
		}
	}
	return 0;
}

int FileOGG::ogg_sync_and_get_next_page(sync_window_t *sw, long serialno, ogg_page *og)
{
// TODO: Put better error reporting inhere
	int ret;
	while ((ret = sync_and_take_page_out(sw, og)) < 0)
	{
		// do nothing;
	}
	if (ret == 0) 
		return 0;
	if (ogg_page_serialno(og) == serialno)
	{
		sw->file_pagepos_found = sw->file_pagepos - og->header_len - og->body_len;
		return 1;
	}
	while (ogg_get_next_page(sw, serialno, og))
		if (ogg_page_serialno(og) == serialno)
		{
			sw->file_pagepos_found = sw->file_pagepos - og->header_len - og->body_len;
			return 1;
		}
	
	return 0;
}
// Returns:
// >= 0, number of sample within a page
// <  0 error
int FileOGG::ogg_get_page_of_sample(sync_window_t *sw, long serialno, ogg_page *og, int64_t sample)
{
// First make an educated guess about position
	if (sample >= asset->audio_length + start_sample)
	{
		printf("FileOGG: Illegal seek beyond end of samples\n");
		return 0;
	}
	off_t educated_guess = filedata_begin + (file_length - filedata_begin) * (sample - start_sample) / asset->audio_length - READ_SIZE;
	if (educated_guess < 0) 
		educated_guess = 0;
//	printf("My educated guess: %lli\n", educated_guess); 
// now see if we won
	read_buffer_at(stream, sw, READ_SIZE, educated_guess);
	ogg_sync_and_get_next_page(sw, serialno, og);
	int64_t end_sample = ogg_page_granulepos(og);
	// linear seek to the sample
	int64_t start_sample = 0;
// TODO: Use bisection also
//	printf("Next page granulepos: %lli, pagepos: %lli\n", end_sample, sw->file_pagepos_found);
	if (end_sample <= sample)
	{
	// scan forward
		while (end_sample <= sample)
		{
			ogg_get_next_page(sw, serialno, og); 
			start_sample = end_sample;
			end_sample = ogg_page_granulepos(og);
		}
		ogg_get_prev_page(sw, serialno, og);
		while (ogg_page_continued(og) && ogg_page_packets(og) == 1)
			ogg_get_prev_page(sw, serialno, og);
	} else
	{
	// scan backward
		start_sample = end_sample;
		while (start_sample > sample || (ogg_page_continued(og) && 
			ogg_page_packets(og) == 1))
		{
//			printf("get prev page: %lli pagepos:%lli\n", ogg_page_granulepos(og), sw->file_pagepos_found);
			ogg_get_prev_page(sw, serialno, og);
			end_sample = start_sample;
			start_sample = ogg_page_granulepos(og);
		}
	// go forward one page at the end

	}
	
//	printf("For sample %lli we need to start decoding on page with granulepos: %lli\n", sample, ogg_page_granulepos(og));
	return 1;
}

// seeks, so next sample returned will be the correct one
// sample here is still the vorbis sample number (= cinelerra sample number + start_sample)
// reinits the decoding engine

int FileOGG::ogg_seek_to_sample(sync_window_t *sw, long serialno, int64_t sample)
{
	// MAYBE FIXME - find out if we really are decoding previous two packets or not
	// get to the sample
	ogg_page og;
	ogg_packet op;
//	printf("Calling get page of sample\n");
	if (!ogg_get_page_of_sample(sw, serialno, &og, sample))
	{
		printf("FileOGG: Seeking to sample's page failed\n");
		return 0;
	}
//	printf("Pagepos: %lli\n", sw->file_pagepos);
	vorbis_synthesis_restart(&tf->vd);
	ogg_stream_reset(&tf->vo);
	ogg_stream_pagein(&tf->vo, &og);
	int sync = 0;
//	printf("seeking to sample : %lli , starting at page with gpos: %lli\n", sample, ogg_page_granulepos(&og));
	
	int64_t current_comming_sample = -1;
	while (1) 
	{
	
		// make sure we have a packet ready
		while (ogg_stream_packetpeek(&tf->vo, NULL) != 1)
		{
			if (!ogg_get_next_page(sw, serialno, &og))
			{
				printf("FileOGG: Cannot find next page while seeking\n");
				return 0;
			}
			ogg_stream_pagein(&tf->vo, &og);
		}
		ogg_stream_packetout(&tf->vo, &op);
		if (sync)
		{
			
			if(!vorbis_synthesis(&tf->vb, &op))
			{
				int ret= vorbis_synthesis_blockin(&tf->vd, &tf->vb);
				int64_t previous_comming_sample = current_comming_sample;
				current_comming_sample += vorbis_synthesis_pcmout(&tf->vd, NULL);
				if (current_comming_sample > sample)
				{
					vorbis_synthesis_read(&tf->vd, (sample - previous_comming_sample));
//					printf("WE GOT IT, samples already decoded: %li\n", vorbis_synthesis_pcmout(&tf->vd,NULL));
					return 1; // YAY next sample read is going to be ours, sexy!
				} else
				{
					// discard decoded data before current sample
					vorbis_synthesis_read(&tf->vd, (current_comming_sample - previous_comming_sample));
					
				}
 			}
		}
		if (!sync && op.granulepos >= 0)
		{
			sync = 1;
			current_comming_sample = op.granulepos;
			if(!vorbis_synthesis(&tf->vb, &op))
			{
				vorbis_synthesis_blockin(&tf->vd, &tf->vb);
				if (vorbis_synthesis_pcmout(&tf->vd, NULL) != 0)
				{
					printf("FileOGG: Something wrong while trying to seek\n");
					return 0;
				}
			
			}
			
		}
	}
	
	
	return 0;
}

int FileOGG::ogg_get_page_of_frame(sync_window_t *sw, long serialno, ogg_page *og, int64_t frame)
{
	if (frame >= asset->video_length + start_frame)
	{
		printf("FileOGG: Illegal seek beyond end of frames\n");
		return 0;
	}
//	printf("frame: %lli start frame: %lli\n", frame, start_frame);
//	printf("file_length: %lli filedata_begin: %lli\n", file_length, filedata_begin);
	off_t educated_guess = filedata_begin + (file_length - filedata_begin) * (frame - start_frame) / asset->video_length - READ_SIZE/2;
//	educated_guess += 100000;
	if (educated_guess > file_length - READ_SIZE)
		educated_guess = file_length - READ_SIZE;
	if (educated_guess < filedata_begin) 
		educated_guess = filedata_begin;
//	printf("My educated guess: %lli\n", educated_guess); 
// now see if we won
	read_buffer_at(stream, sw, READ_SIZE, educated_guess);
	ogg_sync_and_get_next_page(sw, serialno, og);
	int64_t pageend_frame;
	int read_back = 0;
	// find the page with "real" ending
	while ((pageend_frame = ogg_page_granulepos(og)) == -1)
	{
		if (ogg_get_next_page(sw, serialno, og) == 0) 
		{
			read_back = 1;
			break;
		} 
	}
	pageend_frame = theora_granule_frame(&tf->td, ogg_page_granulepos(og));

	// FIXME - MEDIUM PRIORITY: read back if we've gone too far and no page of our serialno at all can be found

	
	// linear seek to the sample
	int64_t start_frame = 0;
// TODO: Use bisection also
//	printf("Next page granulepos: %lli, pagepos: %lli\n", end_sample, sw->file_pagepos_found);
	int discard_packets = 0;
	int missp = 0;
	int missm = 0;
	if (pageend_frame <= frame)
	{
	// scan forward
		while (pageend_frame < frame)
		{
			do {
				ogg_get_next_page(sw, serialno, og); 
			} while (ogg_page_packets(og) == 0);
			pageend_frame = theora_granule_frame(&tf->td, ogg_page_granulepos(og));
			missp++;
		}
		// go back if this frame starts on previous page
		if (ogg_page_continued(og) && pageend_frame - ogg_page_packets(og) == frame - 1)
		{
			do {
				ogg_get_prev_page(sw, serialno, og); 
			} while (ogg_page_packets(og) == 0 && ogg_page_continued(og));		
		}
		pageend_frame = theora_granule_frame(&tf->td, ogg_page_granulepos(og));
	} else
	{
	// scan backward
		int64_t first_frame_on_page = theora_granule_frame(&tf->td, ogg_page_granulepos(og)) - ogg_page_packets(og) + 2;
		if (!ogg_page_continued(og))
			first_frame_on_page--;
		while (first_frame_on_page > frame)
		{
//			printf("get prev page: %lli pagepos:%lli\n", ogg_page_granulepos(og), sw->file_pagepos_found);
			do {
				ogg_get_prev_page(sw, serialno, og); 
			} while (ogg_page_packets(og) == 0 && ogg_page_continued(og));		
			missm++;
//			pageend_frame = theora_granule_frame(&tf->td, ogg_page_granulepos(og));
			first_frame_on_page = theora_granule_frame(&tf->td, ogg_page_granulepos(og)) - ogg_page_packets(og) + 2;
			if (!ogg_page_continued(og))
				first_frame_on_page--;
		}
	}
//	printf("Miss plus: %i, miss minus: %i\n", missp, missm);
//	printf("last frame of page with frame : %lli\n", pageend_frame);
	return 1;			
}


int FileOGG::ogg_seek_to_keyframe(sync_window_t *sw, long serialno, int64_t frame, int64_t *keyframe_number)
{
	ogg_page og;
	ogg_packet op;
//	printf("Searching for the proper position to start decoding frame %lli\n", frame);
	if (!ogg_get_page_of_frame(sw, serialno, &og, frame))
	{
		printf("FileOGG: Seeking to frame failed\n");
		return 0;
	}
	// TODO: if the frame we are looking for continoues on the next page, we don't need to do this
	// Now go to previous page in order to find out the granulepos
	// Don't do it in case this is the first page.
//	printf("GP: %lli\n", ogg_page_granulepos(&og));
	int64_t granulepos, iframe, pframe;
	granulepos = ogg_page_granulepos(&og);
	iframe = granulepos >> theora_keyframe_granule_shift;
	pframe = granulepos - (iframe << theora_keyframe_granule_shift);
	// check if maybe iframe is known from this page already
	if (granulepos != -1 && iframe <= frame)
	{
		// optimisation, iframe is already known from this page
	} else
	{
		// get previous page so we will get the iframe number 
		do {
			ogg_get_prev_page(sw, serialno, &og); 
		} while (ogg_page_packets(&og) == 0);		

		granulepos = ogg_page_granulepos(&og);
		iframe = granulepos >> theora_keyframe_granule_shift;
		pframe = granulepos - (iframe << theora_keyframe_granule_shift);
	}
	int64_t first_frame_on_page = theora_granule_frame(&tf->td, ogg_page_granulepos(&og)) - ogg_page_packets(&og) + 2;
	if (!ogg_page_continued(&og))
		first_frame_on_page--;
	if (first_frame_on_page <= iframe)
	{
		// optimisation, happens mainly in low-bitrate streams, it spares us one seek
	} else
	{
		// get the page where keyframe starts
		if (!ogg_get_page_of_frame(sw, serialno, &og, iframe))
		{
			printf("FileOGG: Seeking to keyframe failed\n");
			return 0;
		}
	}		
//	printf("looking for frame: %lli, last frame of the page: %lli, last keyframe: %lli\n", frame, pframe+iframe, iframe);
	ogg_stream_reset(&tf->to);
	ogg_stream_pagein(&tf->to, &og);
	// Read until one frame before keyframe
//	printf("c: %i\n", ogg_page_continued(&og));
	int numread = iframe - (theora_granule_frame(&tf->td, ogg_page_granulepos(&og)) - ogg_page_packets(&og)) - 1;
	if (ogg_page_continued(&og))
		numread--;
//	printf("numread: %i\n", numread);
//	printf("FileOGG:: Proper position: %lli\n", theora_granule_frame(&tf->td, ogg_page_granulepos(&og)) + numread - ogg_page_packets(&og));
	while (numread > 0)
	{
		while (ogg_stream_packetpeek(&tf->to, NULL) != 1)
		{
			if (!ogg_get_next_page(sw, serialno, &og))
			{
				printf("FileOGG: Cannot find next page while seeking\n");
				return 0;
			}
			ogg_stream_pagein(&tf->to, &og);
		}
		ogg_stream_packetout(&tf->to, &op);
		numread --;
	}
	*keyframe_number = iframe;
	return 1;
}


int FileOGG::check_sig(Asset *asset)
{

	FILE *fd = fopen(asset->path, "rb");

// Test for "OggS"
	fseek(fd, 0, SEEK_SET);
	char data[4];

	int temp = fread(data, 4, 1, fd);

	if(data[0] == 'O' &&
		data[1] == 'g' &&
		data[2] == 'g' &&
		data[3] == 'S')
	{

		fclose(fd);
		printf("Yay, we have an ogg file\n");

		return 1;
	}

	fclose(fd);

	return 0;
	
}

int FileOGG::close_file()
{

	if (file->wr)
	{
		if (asset->audio_data)
			write_samples_vorbis(0, 0, 1); // set eos
		if (asset->video_data)
			write_frames_theora(0, 1, 1); // set eos

		flush_ogg(1); // flush all
	
		if (asset->audio_data)
		{
			vorbis_block_clear (&tf->vb);
			vorbis_dsp_clear (&tf->vd);
			vorbis_info_clear (&tf->vi);
			ogg_stream_clear (&tf->vo);
		}
		if (asset->video_data)
		{
			theora_info_clear (&tf->ti);
			ogg_stream_clear (&tf->to);
			theora_clear (&tf->td);
		}
		
		if (stream) fclose(stream);
		stream = 0;
	} 
	else 
	if (file->rd) 
	{	
		if (asset->audio_data)
		{
			vorbis_block_clear (&tf->vb);
			vorbis_dsp_clear (&tf->vd);
			vorbis_comment_clear (&tf->vc);
			vorbis_info_clear (&tf->vi);
			ogg_stream_clear (&tf->vo);
		}
		theora_comment_clear(&tf->tc);
		if (asset->video_data)
		{
			theora_info_clear (&tf->ti);
			theora_comment_clear (&tf->tc);
			theora_clear (&tf->td);
			ogg_stream_clear (&tf->to);
		}
		
			
		if (stream) fclose(stream);
		stream = 0;

	}
}

int FileOGG::close_file_derived()
{
//printf("FileOGG::close_file_derived(): 1\n");
	if (stream) fclose(stream);
	stream = 0;
}

int64_t FileOGG::get_video_position()
{
//	printf("GVP\n");
	return next_frame_position - start_frame;
}

int64_t FileOGG::get_audio_position()
{
	return next_sample_position - start_sample;
}

int FileOGG::set_video_position(int64_t x)
{
//	x=0;
//	printf("SVP: %lli\n", x);
	
	next_frame_position = x + start_frame;
	return 1;
}


int FileOGG::colormodel_supported(int colormodel)
{
//	printf("CMS\n");

	if (colormodel == BC_YUV420P)
		return BC_YUV420P;
	else
		return colormodel;
}
int FileOGG::get_best_colormodel(Asset *asset, int driver)
{

	return BC_YUV420P;
}


int FileOGG::read_frame(VFrame *frame)
{

	if(!stream) return 1;

	
	// skip is cheaper than seek, do it...
	int decode_frames = 0;
	int expect_keyframe = 0;
	if (ogg_frame_position >= 0 && 
	    next_frame_position >= ogg_frame_position && 
	    next_frame_position - ogg_frame_position < 32)
	{
		decode_frames = next_frame_position - ogg_frame_position;
	} else
	if (next_frame_position != ogg_frame_position)
	{
		if (!ogg_seek_to_keyframe(tf->videosync, tf->to.serialno, next_frame_position, &ogg_frame_position))
		{
			printf("FileOGG:: Error while seeking to frame's keyframe (frame: %lli, keyframe: %lli)\n", (long long)next_frame_position, (long long)ogg_frame_position);
			return 1;
		}
//		printf("For frame: %lli, keyframe is: %lli\n", next_frame_position,ogg_frame_position);
		// skip frames must be > 0 here
		decode_frames = next_frame_position - ogg_frame_position + 1; 
		ogg_frame_position --; // ogg_frame_position is at last decoded frame, so it will point right 
		if (decode_frames <= 0) 
		{
			printf("FileOGG:: Error while seeking to keyframe, wrong keyframe number (frame: %lli, keyframe: %lli)\n", (long long)next_frame_position, (long long)ogg_frame_position);
			return 1;
			
		}
		expect_keyframe = 1;
	}

//	printf("Frames to decode: %i\n", decode_frames);

// THIS IS WHAT CAUSES SLOWNESS OF SEEKING, but we can do nothing about it.
	while (decode_frames > 0)
	{
		ogg_page og;
		ogg_packet op;
		while (ogg_stream_packetpeek(&tf->to, NULL) != 1)
		{
			if (!ogg_get_next_page(tf->videosync, tf->to.serialno, &og))
			{
				printf("FileOGG: Cannot find next page while seeking\n");
				return 1;
			}
			ogg_stream_pagein(&tf->to, &og);
		}
		ogg_stream_packetout(&tf->to, &op);
		if (expect_keyframe && !theora_packet_iskeyframe(&op))
		{
				printf("FileOGG: Expecting keyframe, but didn't get it\n");
			//	return 1; this is generally not a fatal error
		}
		expect_keyframe = 0;
		
		// decode
		theora_decode_packetin(&tf->td, &op);

		decode_frames --;
		ogg_frame_position ++;
	}
	{
		yuv_buffer yuv;
		int ret = theora_decode_YUVout (&tf->td, &yuv);
		if (ret)
		{
			printf("FileOGG: theora_decode_YUVout failed with code %i\n", ret);
		}

// Dirty magic 
/*		yuv.y += yuv.y_stride * (yuv.y_height - 1);
		yuv.u += yuv.uv_stride * (yuv.uv_height - 1);
		yuv.v += yuv.uv_stride * (yuv.uv_height - 1);
		yuv.y_stride = - yuv.y_stride;
		yuv.uv_stride = - yuv.uv_stride;*/
		VFrame *temp_frame = new VFrame(yuv.y, 
						-1,
						0,
						yuv.u - yuv.y,
						yuv.v - yuv.y,
						- yuv.y_stride,
						yuv.y_height,
						BC_YUV420P,
						- yuv.y_stride);
		// copy into temp frame...
		
		cmodel_transfer(frame->get_rows(),
			temp_frame->get_rows(),
			frame->get_y(),
			frame->get_u(),
			frame->get_v(),
			temp_frame->get_y(),
			temp_frame->get_u(),
			temp_frame->get_v(),
			0,
			0,
			yuv.y_width,
			yuv.y_height,
			0,
			0,
			yuv.y_width,  // temp_frame can be larger than frame if width not dividable by 16
			yuv.y_height,	
			BC_YUV420P,
			frame->get_color_model(),
			0,
			-temp_frame->get_w(),
			frame->get_w());
		delete temp_frame;
	}

	next_frame_position ++;
	
	return 0;		
}



int FileOGG::ogg_decode_more_samples(sync_window_t *sw, long serialno)
{
	ogg_page og;
	ogg_packet op;
	int done = 0;
	while (!done)
	{
		while (ogg_stream_packetpeek(&tf->vo, NULL) != 1)
		{
			if (!ogg_get_next_page(sw, serialno, &og))
			{
				printf("FileOGG: Cannot find next page while trying to decode more samples\n");
				return 0;
			}
			ogg_stream_pagein(&tf->vo, &og);
		}
		ogg_stream_packetout(&tf->vo, &op);
		if(!vorbis_synthesis(&tf->vb, &op))
		{
			done = 1;	
			vorbis_synthesis_blockin(&tf->vd, &tf->vb);
		}
	}
	return 1;
}

int FileOGG::set_audio_position(int64_t x)
{
	next_sample_position = x + start_sample;
	return 0;
}

int FileOGG::move_history(int from, int to, int len)
{
	for(int i = 0; i < asset->channels; i++)
		memmove(pcm_history[i] + to, pcm_history[i] + from, sizeof(float) * len);
	history_start = history_start + from - to;
	return 0;
} 

int FileOGG::read_samples(double *buffer, int64_t len)
{
	float **vorbis_buffer;
	if (len <= 0) 
		return 0;
//	printf("Reading samples: Channel: %i, number of samples: %lli, reading at :%lli\n", file->current_channel, len, next_sample_position);
//		printf("\tnext_sample_position: %lli, length: %i\n", next_sample_position, len);
//		printf("\thistory_start: %lli, length: %i\n", history_start, history_size);

	if(len > HISTORY_MAX)
	{
		printf("FileOGG::read_samples max samples=%d\n", HISTORY_MAX);
		return 1;
	}

	if(!pcm_history)
	{
		pcm_history = new float*[asset->channels];
		for(int i = 0; i < asset->channels; i++)
			pcm_history[i] = new float[HISTORY_MAX];
		history_start = -100000000; // insane value to trigger reload
		history_size = 0;
	}

	int64_t hole_start = -1;
	int64_t hole_len = -1;
	int64_t hole_absstart = -1;
	int64_t hole_fill = 0;

	if (history_start < next_sample_position && history_start + history_size > next_sample_position && history_start + history_size < next_sample_position + len) 
	{
//		printf("a\n");
		hole_fill = 1;
		hole_start = history_start + history_size - next_sample_position;
		hole_len = history_size - hole_start;
		hole_absstart = next_sample_position + hole_start;
		move_history(next_sample_position - history_start,
				0,
				hole_start); //	
		
	} else
	if (next_sample_position < history_start && history_start < next_sample_position + len)
	{
//		printf("b\n");
		hole_fill = 1;
		hole_start = 0;
		hole_len = history_start - next_sample_position;
		hole_absstart = next_sample_position;
//		printf("hs: %lli, histstart: %lli, next_sample_position: %lli\n", history_size, history_start, next_sample_position);
//		printf("to: 0, from: %lli, size: %lli\n", 
 //				history_start - next_sample_position,
//				history_size - history_start + next_sample_position);
		move_history(0, 
				history_start - next_sample_position,
				history_size - history_start + next_sample_position);
			
	} else 
	if (next_sample_position >= history_start + history_size || next_sample_position + len <= history_start)
	{
//		printf("c\n");
		hole_fill = 1;
		hole_start = 0;
		hole_len = HISTORY_MAX;
		hole_absstart = next_sample_position;
		history_start = hole_absstart;
		history_size = hole_len;
	}
	
	if (hole_fill)
	{
		if (hole_start < 0 || hole_len <= 0 || hole_absstart < 0)
		{
			printf("FileOGG: Error at finding out what to read from file\n");
			return 1;
		}
		
		if (hole_absstart + hole_len > asset->audio_length + start_sample)
		{
			hole_len = asset->audio_length + start_sample - hole_absstart;
			history_size = asset->audio_length + start_sample - history_start;
		} else
		{
			history_size = HISTORY_MAX;
		}
		
		
//		printf("Decode samples at position: %lli, samples to read: %lli\n", hole_absstart, hole_len);
	
		int64_t samples_read = 0;	 
		if (ogg_sample_position != hole_absstart)
		{
			ogg_sample_position = hole_absstart;
			if (!ogg_seek_to_sample(tf->audiosync, tf->vo.serialno, ogg_sample_position))
			{
				printf("FileOGG:: Error while seeking to sample\n");
				return 1;
			}
		}
		// now we have ogg_sample_positon aligned
		int64_t samples_to_read = hole_len;
		while (samples_read < hole_len)
		{
			int64_t waiting_samples = vorbis_synthesis_pcmout(&tf->vd, &vorbis_buffer);
			int64_t takeout_samples;
			if (waiting_samples > samples_to_read - samples_read)
				takeout_samples = samples_to_read - samples_read;
			else 
				takeout_samples = waiting_samples;

			int i, j;
//			printf("takeout samples: %lli, samples_read: %lli\n", takeout_samples, samples_read);

			if(waiting_samples)
			{
				for(int i = 0; i < asset->channels; i++)
				{
					float *input = vorbis_buffer[i];
					float *output = pcm_history[i] + hole_start;
					// TODO: use memcpy
					for(int j = 0; j < takeout_samples ; j++)
					{
						output[j] = input[j];
					}
				}                                                                   
			}

			vorbis_synthesis_read(&tf->vd, takeout_samples);
			samples_read += takeout_samples;
			ogg_sample_position += takeout_samples;
			hole_start += takeout_samples;
			
			if (samples_read < hole_len)
				if (!ogg_decode_more_samples(tf->audiosync, tf->vo.serialno))
				{
					ogg_sample_position = -1;
					return 1;
				}


		}
	}	
	
	// now we can be sure our history is correct, just copy it out
	if (next_sample_position < history_start || next_sample_position + len > history_start + history_size)
	{
		printf("FileOGG:: History not aligned properly \n");
		printf("\tnext_sample_position: %lli, length: %i\n", (long long)next_sample_position, (int)len);
		printf("\thistory_start: %lli, length: %i\n", (long long)history_start, (int)history_size);
		
		return 1;
	}
	float *input = pcm_history[file->current_channel] + next_sample_position - history_start;
	for (int i = 0; i < len; i++)
		buffer[i] = input[i];
	 
	next_sample_position += len;
	return 0;
}


int FileOGG::write_audio_page()
{
  int ret;

  ret = fwrite(tf->apage, 1, tf->apage_len, stream);
  if(ret < tf->apage_len) {
    fprintf(stderr,"error writing audio page\n"); 
  }
  tf->apage_valid = 0;
  tf->a_pkg -= ogg_page_packets((ogg_page *)&tf->apage);
  return ret;
}

int FileOGG::write_video_page()
{
  int ret;

  ret = fwrite(tf->vpage, 1, tf->vpage_len, stream);
  if(ret < tf->vpage_len) {
    fprintf(stderr,"error writing video page\n");
  }
  tf->vpage_valid = 0;
  tf->v_pkg -= ogg_page_packets((ogg_page *)&tf->vpage);
  return ret;
}

void FileOGG::flush_ogg (int e_o_s)
{
    int len;
    ogg_page og;

	flush_lock->lock();
    /* flush out the ogg pages  */
    while(1) {
      /* Get pages for both streams, if not already present, and if available.*/
      if(asset->video_data && !tf->vpage_valid) {
        // this way seeking is much better,
        // not sure if 23 packets  is a good value. it works though
        int v_next=0;
        if(tf->v_pkg>22 && ogg_stream_flush(&tf->to, &og) > 0) {
          v_next=1;
        }
        else if(ogg_stream_pageout(&tf->to, &og) > 0) {
          v_next=1;
        }
        if(v_next) {
          len = og.header_len + og.body_len;
          if(tf->vpage_buffer_length < len) {
            tf->vpage = (unsigned char *)realloc(tf->vpage, len);
            tf->vpage_buffer_length = len;
          }
          tf->vpage_len = len;
          memcpy(tf->vpage, og.header, og.header_len);
          memcpy(tf->vpage+og.header_len , og.body, og.body_len);

          tf->vpage_valid = 1;
          tf->videotime = theora_granule_time (&tf->td,
                  ogg_page_granulepos(&og));
        }
      }
      if(asset->audio_data && !tf->apage_valid) {
        // this way seeking is much better,
        // not sure if 23 packets  is a good value. it works though
        int a_next=0;
        if(tf->a_pkg>22 && ogg_stream_flush(&tf->vo, &og) > 0) {
          a_next=1;
        }
        else if(ogg_stream_pageout(&tf->vo, &og) > 0) {
          a_next=1;
        }
        if(a_next) {
          len = og.header_len + og.body_len;
          if(tf->apage_buffer_length < len) {
            tf->apage = (unsigned char *)realloc(tf->apage, len);
            tf->apage_buffer_length = len;
          }
          tf->apage_len = len;
          memcpy(tf->apage, og.header, og.header_len);
          memcpy(tf->apage+og.header_len , og.body, og.body_len);

          tf->apage_valid = 1;
          tf->audiotime= vorbis_granule_time (&tf->vd, 
                  ogg_page_granulepos(&og));
        }
      }

      if(!asset->audio_data && tf->vpage_valid) {
        write_video_page();
      }
      else if(!asset->video_data && tf->apage_valid) {
        write_audio_page();
      }
      /* We're using both. We can output only:
       *  a) If we have valid pages for both
       *  b) At EOS, for the remaining stream.
       */
      else if(tf->vpage_valid && tf->apage_valid) {
        /* Make sure they're in the right order. */
        if(tf->videotime <= tf->audiotime)
          write_video_page();
        else
          write_audio_page();
      } 
      else if(e_o_s && tf->vpage_valid) {
          write_video_page();
      }
      else if(e_o_s && tf->apage_valid) {
          write_audio_page();
      }
      else {
        break; /* Nothing more writable at the moment */
      }
    }
	flush_lock->unlock();
}


int FileOGG::write_samples_vorbis(double **buffer, int64_t len, int e_o_s)
{
	int i,j, count = 0;
	float **vorbis_buffer;
	static int samples = 0;
	samples += len;
	if(e_o_s)
	{
		vorbis_analysis_wrote (&tf->vd, 0);
	} else
	{
		vorbis_buffer = vorbis_analysis_buffer (&tf->vd, len);
		/* double to float conversion */
		for(i = 0; i<asset->channels; i++)
		{
			for (j = 0; j < len; j++)
			{
				vorbis_buffer[i][j] = buffer[i][j];
			}
		}
		vorbis_analysis_wrote (&tf->vd, len);
	}
	while(vorbis_analysis_blockout (&tf->vd, &tf->vb) == 1)
	{
	    /* analysis, assume we want to use bitrate management */
	    vorbis_analysis (&tf->vb, NULL);
	    vorbis_bitrate_addblock (&tf->vb);
	    
	    /* weld packets into the bitstream */
	    while (vorbis_bitrate_flushpacket (&tf->vd, &tf->op))
	    {
		flush_lock->lock();
		ogg_stream_packetin (&tf->vo, &tf->op);
		tf->a_pkg++;
		flush_lock->unlock();
	    }

	}
	flush_ogg(0);
	return 0;


}

int FileOGG::write_samples(double **buffer, int64_t len)
{
	if (len > 0)
		return write_samples_vorbis(buffer, len, 0);
	return 0;
}

int FileOGG::write_frames_theora(VFrame ***frames, int len, int e_o_s)
{
	// due to clumsy theora's design we need to delay writing out by one frame
	// always stay one frame behind, so we can correctly encode e_o_s

	int i, j, result = 0;

	if(!stream) return 0;

	
	for(j = 0; j < len && !result; j++)
	{
		if (temp_frame) // encode previous frame if available
		{
			yuv_buffer yuv;
			yuv.y_width = tf->ti.width;
			yuv.y_height = tf->ti.height;
			yuv.y_stride = temp_frame->get_bytes_per_line();

			yuv.uv_width = tf->ti.width / 2;
			yuv.uv_height = tf->ti.height / 2;
			yuv.uv_stride = temp_frame->get_bytes_per_line() /2;

			yuv.y = temp_frame->get_y();
			yuv.u = temp_frame->get_u();
			yuv.v = temp_frame->get_v();
			int ret = theora_encode_YUVin (&tf->td, &yuv);
			if (ret)
			{
				printf("FileOGG: theora_encode_YUVin failed with code %i\n", ret);
				printf("yuv_buffer: y_width: %i, y_height: %i, y_stride: %i, uv_width: %i, uv_height: %i, uv_stride: %i\n",
					yuv.y_width,
					yuv.y_height,
					yuv.y_stride,
					yuv.uv_width,
					yuv.uv_height,
					yuv.uv_stride);
			}
			while(theora_encode_packetout (&tf->td, e_o_s, &tf->op)) {
				flush_lock->lock();
				ogg_stream_packetin (&tf->to, &tf->op);
				tf->v_pkg++;
				flush_lock->unlock();
            }
			flush_ogg(0);  // eos flush is done later at close_file
		}
// If we have e_o_s, don't encode any new frames
		if (e_o_s) 
			break;

		if (!temp_frame)
		{
			temp_frame = new VFrame (0, 
						-1,
						tf->ti.width, 
						tf->ti.height,
						BC_YUV420P,
						-1);
		} 
		VFrame *frame = frames[0][j];
		int in_color_model = frame->get_color_model();
		if (in_color_model == BC_YUV422P &&
		    temp_frame->get_w() == frame->get_w() &&
		    temp_frame->get_h() == frame->get_h() &&
		    temp_frame->get_bytes_per_line() == frame->get_bytes_per_line())
		{
			temp_frame->copy_from(frame);
		} else
		{

			cmodel_transfer(temp_frame->get_rows(),
				frame->get_rows(),
				temp_frame->get_y(),
				temp_frame->get_u(),
				temp_frame->get_v(),
				frame->get_y(),
				frame->get_u(),
				frame->get_v(),
				0,
				0,
				frame->get_w(),
				frame->get_h(),
				0,
				0,
				frame->get_w(),  // temp_frame can be larger than frame if width not dividable by 16
				frame->get_h(),	
				frame->get_color_model(),
				BC_YUV420P,
				0,
				frame->get_w(),
				temp_frame->get_w());

		}
	}						
				
	return 0;
}


int FileOGG::write_frames(VFrame ***frames, int len)
{
	
	return write_frames_theora(frames, len, 0);
}

OGGConfigAudio::OGGConfigAudio(BC_WindowBase *parent_window, Asset *asset)
 : BC_Window(PROGRAM_NAME ": Audio Compression",
	parent_window->get_abs_cursor_x(1),
	parent_window->get_abs_cursor_y(1),
	DP(350),
	DP(250))
{
	this->parent_window = parent_window;
	this->asset = asset;
}

OGGConfigAudio::~OGGConfigAudio()
{

}

void OGGConfigAudio::create_objects()
{
//	add_tool(new BC_Title(10, 10, _("There are no audio options for this format")));

	int x = DP(10), y = DP(10);
	int x1 = DP(150);
	char string[BCTEXTLEN];

	lock_window("OGGConfigAudio::create_objects");
	add_tool(fixed_bitrate = new OGGVorbisFixedBitrate(x, y, this));
	add_tool(variable_bitrate = new OGGVorbisVariableBitrate(x + fixed_bitrate->get_w() + DP(5), 
		y, 
		this));

	y += DP(30);
	sprintf(string, "%d", asset->vorbis_min_bitrate);
	add_tool(new BC_Title(x, y, _("Min bitrate:")));
	add_tool(new OGGVorbisMinBitrate(x1, y, this, string));

	y += DP(30);
	add_tool(new BC_Title(x, y, _("Avg bitrate:")));
	sprintf(string, "%d", asset->vorbis_bitrate);
	add_tool(new OGGVorbisAvgBitrate(x1, y, this, string));

	y += DP(30);
	add_tool(new BC_Title(x, y, _("Max bitrate:")));
	sprintf(string, "%d", asset->vorbis_max_bitrate);
	add_tool(new OGGVorbisMaxBitrate(x1, y, this, string));


	add_subwindow(new BC_OKButton(this));
	show_window(1);
	unlock_window();
}

int OGGConfigAudio::close_event()
{
	set_done(0);
	return 1;
}

OGGVorbisFixedBitrate::OGGVorbisFixedBitrate(int x, int y, OGGConfigAudio *gui)
 : BC_Radial(x, y, !gui->asset->vorbis_vbr, _("Average bitrate"))
{
	this->gui = gui;
}
int OGGVorbisFixedBitrate::handle_event()
{
	gui->asset->vorbis_vbr = 0;
	gui->variable_bitrate->update(0);
	return 1;
}

OGGVorbisVariableBitrate::OGGVorbisVariableBitrate(int x, int y, OGGConfigAudio *gui)
 : BC_Radial(x, y, gui->asset->vorbis_vbr, _("Variable bitrate"))
{
	this->gui = gui;
}
int OGGVorbisVariableBitrate::handle_event()
{
	gui->asset->vorbis_vbr = 1;
	gui->fixed_bitrate->update(0);
	return 1;
}


OGGVorbisMinBitrate::OGGVorbisMinBitrate(int x, 
	int y, 
	OGGConfigAudio *gui, 
	char *text)
 : BC_TextBox(x, y, DP(180), 1, text)
{
	this->gui = gui;
}
int OGGVorbisMinBitrate::handle_event()
{
	gui->asset->vorbis_min_bitrate = atol(get_text());
	return 1;
}



OGGVorbisMaxBitrate::OGGVorbisMaxBitrate(int x, 
	int y, 
	OGGConfigAudio *gui,
	char *text)
 : BC_TextBox(x, y, DP(180), 1, text)
{
	this->gui = gui;
}
int OGGVorbisMaxBitrate::handle_event()
{
	gui->asset->vorbis_max_bitrate = atol(get_text());
	return 1;
}



OGGVorbisAvgBitrate::OGGVorbisAvgBitrate(int x, int y, OGGConfigAudio *gui, char *text)
 : BC_TextBox(x, y, DP(180), 1, text)
{
	this->gui = gui;
}
int OGGVorbisAvgBitrate::handle_event()
{
	gui->asset->vorbis_bitrate = atol(get_text());
	return 1;
}





OGGConfigVideo::OGGConfigVideo(BC_WindowBase *parent_window, Asset *asset)
 : BC_Window(PROGRAM_NAME ": Video Compression",
	parent_window->get_abs_cursor_x(1),
	parent_window->get_abs_cursor_y(1),
	DP(450),
	DP(220))
{
	this->parent_window = parent_window;
	this->asset = asset;
}

OGGConfigVideo::~OGGConfigVideo()
{

}

void OGGConfigVideo::create_objects()
{
//	add_tool(new BC_Title(10, 10, _("There are no video options for this format")));
	int x = DP(10), y = DP(10);
	int x1 = x + DP(150);
	int x2 = x + DP(300);

	lock_window("OGGConfigVideo::create_objects");
	BC_Title *title;
	add_subwindow(title = new BC_Title(x, y, _("Bitrate:")));
	add_subwindow(new OGGTheoraBitrate(x + title->get_w() + DP(5), y, this));
	add_subwindow(fixed_bitrate = new OGGTheoraFixedBitrate(x2, y, this));
	y += DP(30);

	add_subwindow(new BC_Title(x, y, _("Quality:")));
	add_subwindow(new BC_ISlider(x + DP(80), 
		y,
		0,
		DP(200),
		DP(200),
		0,
		63,
		asset->theora_quality,
		0,
		0,
		&asset->theora_quality));

	
	add_subwindow(fixed_quality = new OGGTheoraFixedQuality(x2, y, this));
	y += DP(30);

	add_subwindow(new BC_Title(x, y, _("Keyframe frequency:")));
	OGGTheoraKeyframeFrequency *keyframe_frequency = 
		new OGGTheoraKeyframeFrequency(x1 + DP(60), y, this);
	keyframe_frequency->create_objects();
	y += DP(30);
	
	add_subwindow(new BC_Title(x, y, _("Keyframe force frequency:")));
	OGGTheoraKeyframeForceFrequency *keyframe_force_frequency = 
		new OGGTheoraKeyframeForceFrequency(x1 + DP(60), y, this);
	keyframe_force_frequency->create_objects();
	y += DP(30);

	add_subwindow(new BC_Title(x, y, _("Sharpness:")));
	OGGTheoraSharpness *sharpness = 
		new OGGTheoraSharpness(x1 + DP(60), y, this);
	sharpness->create_objects();
	y += DP(30);
	

	add_subwindow(new BC_OKButton(this));
	show_window(1);
	unlock_window();
}




int OGGConfigVideo::close_event()
{
	set_done(0);
	return 1;
}

OGGTheoraBitrate::OGGTheoraBitrate(int x, int y, OGGConfigVideo *gui)
 : BC_TextBox(x, y, DP(100), 1, gui->asset->theora_bitrate)
{
	this->gui = gui;
}


int OGGTheoraBitrate::handle_event()
{
	// TODO: MIN / MAX check
	gui->asset->theora_bitrate = atol(get_text());
	return 1;
};




OGGTheoraFixedBitrate::OGGTheoraFixedBitrate(int x, int y, OGGConfigVideo *gui)
 : BC_Radial(x, y, gui->asset->theora_fix_bitrate, _("Fixed bitrate"))
{
	this->gui = gui;
}

int OGGTheoraFixedBitrate::handle_event()
{
	update(1);
	gui->asset->theora_fix_bitrate = 1;
	gui->fixed_quality->update(0);
	return 1;
};

OGGTheoraFixedQuality::OGGTheoraFixedQuality(int x, int y, OGGConfigVideo *gui)
 : BC_Radial(x, y, !gui->asset->theora_fix_bitrate, _("Fixed quality"))
{
	this->gui = gui;
}

int OGGTheoraFixedQuality::handle_event()
{
	update(1);
	gui->asset->theora_fix_bitrate = 0;
	gui->fixed_bitrate->update(0);
	return 1;
};

OGGTheoraKeyframeFrequency::OGGTheoraKeyframeFrequency(int x, int y, OGGConfigVideo *gui)
 : BC_TumbleTextBox(gui, 
 	(int64_t)gui->asset->theora_keyframe_frequency, 
	(int64_t)1,
	(int64_t)500,
	x, 
	y,
	DP(40))
{
	this->gui = gui;
}

int OGGTheoraKeyframeFrequency::handle_event()
{
	gui->asset->theora_keyframe_frequency = atol(get_text());
	return 1;
}

OGGTheoraKeyframeForceFrequency::OGGTheoraKeyframeForceFrequency(int x, int y, OGGConfigVideo *gui)
 : BC_TumbleTextBox(gui, 
 	(int64_t)gui->asset->theora_keyframe_frequency, 
	(int64_t)1,
	(int64_t)500,
	x, 
	y,
	DP(40))
{
	this->gui = gui;
}

int OGGTheoraKeyframeForceFrequency::handle_event()
{
	gui->asset->theora_keyframe_frequency = atol(get_text());
	return 1;
}


OGGTheoraSharpness::OGGTheoraSharpness(int x, int y, OGGConfigVideo *gui)
 : BC_TumbleTextBox(gui, 
 	(int64_t)gui->asset->theora_sharpness, 
	(int64_t)0,
	(int64_t)2,
	x, 
	y,
	DP(40))
{
	this->gui = gui;
}

int OGGTheoraSharpness::handle_event()
{
	gui->asset->theora_sharpness = atol(get_text());
	return 1;
}


