/*************************************************************************
 *
 * ogmnav.c - Sample tutorial OGM bitstream navigation program
 *
 * 
 * Author: George Shuklin, gs@shounen.ru
 *
 * No licence, PUBLIC DOMAIN
 *
 * used as a sample for ogm file format description 
 * (http://shounen.ru/docs/ogm/)
 *
 *************************************************************************/

 /** Russian, DOS-866 codepage *******************************************
 *
 * ogmnav.c - Учебная программа для навигации по OGM-файлам
 *
 * 
 * Автор: Георгий Шуклин, gs@shounen.ru
 *
 * Без лицензии, свободное использование (public domain)
 *
 * является примером к описанию формата файлов ogm 
 * (http://shounen.ru/docs/ogm/)
 *
 *************************************************************************/


#include <stdio.h>
#include <stdlib.h>


#define PART_HEADER_SIZE 0x1B


#define CONTINUED_FLAG	0x1
#define BoS_FLAG		0x2
#define EoS_FLAG		0x4

#define CharsToInt32(table,offset)  (table[offset]+(table[offset+1]<<8)+(table[offset+2]<<16)+(table[offset+3]<<24))
#define CharsToInt64H(table,offset) (CharsToInt32(table,offset+4))
#define CharsToInt64L(table,offset) (CharsToInt32(table,offset))


char* VectorToString(unsigned char* buffer, int Vector_size){
/*Функция ковертирует вектор в ASCIIZ строку*/
	char *temp;
	temp=malloc(Vector_size+1);
	if(!temp){
		 printf("!!! !!! --- Error allocating %d bytes.\n",Vector_size+1);
		 exit(-1);
	}
	memcpy(temp,buffer,Vector_size);
	temp[Vector_size]=0;
	return temp;
}

void page_data_process(unsigned char* buf, int size, char flags,int str_num){ 
/*функция обрабатывает страницы заголовков и комментариев, выводя их hex-dump и расшифровку.
Все остальные страницы (не заголовки и не комментарии) игнорируются*/
    int c=0; /* используются для печати 16ричного дампа*/
    int lc=0;
	unsigned char *temp; /*используется для вывода комментариев*/

	int offset=0;/*используется для сохранения текущего смещения при выводе комментариев*/
	int base=0;

	if(!buf||size<8||flags&CONTINUED_FLAG) return; /*игнорировать возможные ошибки при передаче параметров*/

	if(buf[0]==0x1||buf[0]==0x3){/*если заголовок или комментарий, печать HEX-dump*/
		while(lc<size){
			printf(". 0x%04X ",lc);
			c=0;
			while(c+lc<size&&c<16){ 
				printf("%02X ",buf[lc+c]);
				c++;
			}
			printf(" ");
			if(c!=16) for(;c<16;c++) printf ("   ");
			c=0;
			while(c+lc<size&&c<16){
				if(buf[lc+c]>=32) printf("%c",buf[lc+c]); else printf(".");
				c++;
			}
			lc+=16;
			printf("\n");
		}
	}
	/*обработка заголовков (расшифровка) и вывод комментариев в текстовом виде*/
	switch(buf[0]){ 
		case 0x1: /*заголовок*/
			printf(".. This page contain the header of the stream #%d (%s).\n",str_num,buf+1);
			if(!strcmp(buf+1,"video")){
			    if(size<53){
			    	printf("!!! --- bad video header (too small, must be at least 53 bytes, is %d)\n",size);
			    	return;
				}
				printf(".. Codec: %c%c%c%c\n",buf[9],buf[10],buf[11],buf[12]);
				printf(".. fps: %f\n", (double)10000000/((double)CharsToInt64H(buf,17)*(double)4294967296+(double)CharsToInt64L(buf,17)));
				printf(".. color depth: %d\n",buf[41]+buf[42]<<8);
				printf(".. Width:%d\n",CharsToInt32(buf,0x2D));
				printf(".. Height:%d\n",CharsToInt32(buf,0x31));
			}
			if(!strcmp(buf+1,"vorbis")){
				printf(".. channels: %d\n",(int)buf[0xB]);
				printf(".. frequency: %d Hz\n",CharsToInt32(buf,0xC));
				printf(".. avg. bitrate: %d Kb\n",CharsToInt32(buf,0x14)/1000);
			}
			if(!strcmp(buf+1,"text")){
			}
			break;
		case 0x3: /*комментарий*/
			printf(".. This page contain a comment for stream #%d\n",str_num);
			temp=VectorToString(buf+11,CharsToInt32(buf,7));
			printf(".. Created using %s\n",temp);
			free(temp);
			base=11+CharsToInt32(buf,7);
			offset=4;
			for(c=0;c<CharsToInt32(buf,base);c++){
				temp=VectorToString(buf+base+offset+4,CharsToInt32(buf,base+offset));
				offset+=4+CharsToInt32(buf,base+offset);
				printf(".. %s\n",temp);
				free(temp);
			}
	}
}

int main( int argc, char* argv[] ){

	FILE* file;
	unsigned char part_header[PART_HEADER_SIZE]; /*используется для чтения заголовка (не включая segment_table)*/
	unsigned char *segment_table=NULL; /*используется для хранения segment_table, размер зависит от page_segments*/
	const unsigned char capture_pattern[4]={0x4f, 0x67, 0x67, 0x53}; /*сигнатура заголовка страницы, 'OggS'*/
	int page_start=0; /*используется для хранения начала "текущей" (обрабатываемой) страницы*/
	int	page_data_size; /*используется для хранения размера данных страницы*/
	int c; /*счётчик в циклах обработки segment_table*/
	int stream_counter=0; /*подсчёт общего числа логических потоков в файле*/
	int page_counter=0; /*подсчёт общего числа страниц в файле*/
	unsigned char *databuf=0; /*буффер для данных страницы*/
	if( argc<2 ){
		printf( "ogmnav - sample navigation in OGM files\n\nUsage: ogmnav filename.ogm\n" );
		return 0;
	}
	file = fopen( argv[1], "rb" );
	if( !file ){
		printf( "Error opening file: %s\n", argv[1] );
		return -1;
	}
	while( fread( part_header, 1, sizeof(part_header), file ) ){
		if( memcmp( part_header, capture_pattern, 4 ) != 0 ){/*проверка на совпадение с сигнатурой, в случае несовпадения, завершение работы*/
			printf( "!!! --- sync lost at offset %d (0x%X), must be 'OggS', is '%c%c%c%c' ---\n",
				page_start, page_start, part_header[0], part_header[1], part_header[2], part_header[3] );
			if( segment_table ) free( segment_table );
			fclose( file );
			return -1;
		}
		if( part_header[4] != 0 ){
			printf( "!!! --- unknown version of page header structure, must be 0, is %d ---\n", part_header[5] );
			if( segment_table ) free( segment_table );
			fclose( file );
			return -1;
		}
		/*ok, заголовок страницы найден, версия проверена*/
		page_counter++;
		printf( "\n....... page #%d of stream #%d, at %d (0x%X)\n", CharsToInt32(part_header,18), CharsToInt32(part_header,14), page_start, page_start );
		if( part_header[5] != 0 ){
			printf( "... This page: " );
			if( part_header[5] & CONTINUED_FLAG ) printf( "contain a continued packet " );
			if( part_header[5] & BoS_FLAG       ) printf( "is the Begin of Stream #%d", stream_counter++ );
			if( part_header[5] & EoS_FLAG       ) printf( "is the End of Stream" );
			printf( "\n" );
		}
		printf("... header_type_flag          = 0x%X\n", (int)part_header[5] );
		printf("... absolute_granule_position = 0x%X%X\n", CharsToInt64H( part_header, 6 ), CharsToInt64L( part_header, 6 ) );
		printf("... page_checksum             = 0x%X\n", CharsToInt32( part_header, 22 ) );
        printf("... page_segments             = %d\n", (int)part_header[26] );
        
        /*теперь читаем segment_table*/
        if( part_header[26] == 0 ){
        	printf( "....... this page do not contain segment_table and data\n" );
	        continue;
        }
        segment_table =malloc( (int)part_header[26] );
        if( !segment_table ){
			printf( "!!! !!! Error allocating %d bytes of memory. Program terminating\n", part_header[26] );
			fclose( file );
			return -1;
		}
		if( !fread( segment_table, 1, (int)part_header[26], file ) ){
			if( segment_table ) free( segment_table );
			fclose( file );
			printf( "!!! --- Error reading segment_table (%d bytes), offset:%d (0x%d) ---\n", part_header[26], (int)ftell(file), (int)ftell(file) );
			return -1;
		}
		/*обрабатываем segment_table*/
		page_data_size = 0;
		for( c=0; c<part_header[26]; c++ ){
			page_data_size += segment_table[c];
		}
        printf( "... page_data_size            = %d\n",page_data_size);
        printf("... common_segment_size       = %d\n", page_data_size+part_header[26]+PART_HEADER_SIZE );
		/*пропускаем данные сегмента, так, чтобы прочитать (в следующей итерации цикла) заголовок следующего сегмента*/
        free( segment_table );
		segment_table = 0;
		databuf = malloc( page_data_size );
		if( !databuf ){
			printf("!!! !!! Error allocating %d bytes of memory. Program terminating\n", page_data_size);
			if(segment_table) free(segment_table);
			fclose(file);
			return -1;
		}
		if(!fread(databuf,1,page_data_size,file)){
			printf("!!! --- Error reading page content\n");
			if(segment_table) free(segment_table);
			fclose(file);
			return -1;
		}
		page_data_process ( databuf, page_data_size, part_header[5],CharsToInt32(part_header,14));
		free(databuf);
		databuf=NULL;
		page_start = ftell( file );        
	}
	fclose( file );
	printf("\n    --- EoF ---\n");
	printf(" \n\nStatistic: %d streams, %d pages\n", stream_counter, page_counter );
	return 0;
}
