/*
 * InfoMPEG version 1.0
 * Copyright (C) 1993 Dennis Lee
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "infompeg.h"

#ifdef BORLAND
 #include <dir.h>
 #include <dos.h>
#endif

/*
 * Usage of the program.
 */
void
usage() {
    fprintf(stderr,"\nUsage: InfoMPEG [-123] filename.mpg [filename.mpg ...]\n\n");
    fprintf(stderr,"       1 - Reports resolution and frame types(IPB) present(very fast)\n");
    fprintf(stderr,"       2 - More detail than 1, including # of each frame type\n");
    fprintf(stderr,"           present and overall compression\n");
    fprintf(stderr,"       3 - (Default) Reports the most information\n\n");
    fprintf(stderr,"InfoMPEG by Dennis Lee\n");
    exit(0);
    }

#ifdef BORLAND
/*
 * Strips the name of the file specified leaving only the path to it.
 */
char *
get_path (char *name) {
    int i;
    for(i = strlen(name)-1; name[i] != '\\' && name[i] != '/' && name[i] != ':'; i--)
	name[i] = 0;
    return name;
    }

/*
 * Expands '*.MPG' expressions specified on the command line.
 */
void
expand_cmd_line (short *argc, char **argv[]) {
    yes_or_no files_left, do_expand;
    struct ffblk tmp_file_info;
    char file_mask[80], tmp_path[80];
    short i, j, new_argc=0;
    char **argv_buffer = (char **)malloc(MAX_FILES * sizeof(char *));

    for(i=0; i < *argc; i++) {
	do_expand = NO;    /* Assume there is no wildcard '*' until one is found */
	for(j = strlen((*argv)[i])-1; j >= 0; j--) {
	    if ((*argv)[i][j] == '*') {
		do_expand = YES;
		break;
		}
	    }
	if (do_expand) {
	    strcpy(file_mask, (*argv)[i]);
	    strcpy(tmp_path, get_path((*argv)[i]));
	    files_left = !findfirst(file_mask, &tmp_file_info, FA_RDONLY);
	    while (files_left) {
		argv_buffer[new_argc] = (char *)malloc(80 * sizeof(char));
		strcpy(argv_buffer[new_argc], tmp_path);
		strcat(argv_buffer[new_argc++], tmp_file_info.ff_name);
		files_left = !findnext(&tmp_file_info);
		}
	    }
	else {
	    argv_buffer[new_argc] = (char *)malloc(80 * sizeof(char));
	    strcpy(argv_buffer[new_argc++], (*argv)[i]);
	    }
	}
    *argc = new_argc;
    *argv = argv_buffer;
    }
#endif

/*
 * Strips the full path of the file specified leaving only the filename.
 */
void
get_name (char *name) {
    int i, j, pos=0;
    for (i=strlen(name)-1;i>=0;i--)
	if (name[i]=='\\' || name[i]=='/' || name[i]==':')
	    break;
    if (i==0) i--;
    for (j=i+1; j <= strlen(name); j++)
	name[pos++]=toupper(name[j]);
    }

/*
 * Reads the next 24 bits in the stream for the pixel resolution
 * of the MPEG file. 12 bits each for height and width, respectively.
 */
void
get_stream_res(FILE *stream) {
    long byte1, byte2, byte3;
    byte1 = getc(stream);
    byte2 = getc(stream);
    byte3 = getc(stream);
    width = (byte1 << 4) | (byte2 >> 4);
    height = ((byte2 & 0x0f) << 8) | byte3;
    }

/*
 * This procedure is called after a picture start code has just been
 * read, and it proceeds to read info on the current frame, such as frame
 * type(IPB), and length.  The procedure also has code to find a recursive
 * frame sequence if one exists.
 */
void
get_frame_info (yes_or_no get_all_data, FILE *stream) {
    frame_info cur_frame;
    short cur_byte;

    cur_frame.frame_len = ftell(stream) - file_offset;
    file_offset = ftell(stream);

    if (previous_frame != 0) {      /* if a previous frame exists */
	switch (previous_frame) {
	    case I: frame_sums[0].sum_frame_len += cur_frame.frame_len;  break;
	    case P: frame_sums[1].sum_frame_len += cur_frame.frame_len;  break;
	    case B: frame_sums[2].sum_frame_len += cur_frame.frame_len;
	    }
	}

    if (get_all_data) {
	cur_byte=getc(stream);
	cur_byte=getc(stream);
	cur_frame.frame_type = (cur_byte >> 3) & 7;
	previous_frame = cur_frame.frame_type;
	switch (cur_frame.frame_type) {
	    case I: frame_sums[0].sum_frames++;  break;
	    case P: frame_sums[1].sum_frames++;  break;
	    case B: frame_sums[2].sum_frames++;
	    }
	if (cur_frame.frame_type==I && frame_sums[0].sum_frames==3) {
	    frame_cycle[cycle_len]=0;
	    cycle_len=0;
	    }
	if (frame_sums[0].sum_frames==2)
	    frame_cycle[cycle_len++] = cur_frame.frame_type;
	else if (frame_sums[0].sum_frames==3) {
	    if (frame_cycle[cycle_len++] != cur_frame.frame_type)
		cycle_exists=FALSE;
	    }
	total_frames++;
	if (report_info_type != 1) {
	    printf("%c", frame_types[cur_frame.frame_type]);
	    if ((total_frames % 64) == 0)
		printf("\n");
	    fflush(stdout);
	    }
	}
    }

/*
 * Parses the stream sequentially until a start code is found.
 * (a start code begins with 0x000001)
 * The last byte of the start code is left in the stream for an external
 * procedure to use (it specifies the type of start code).
 */
void
next_start_code (FILE *stream) {
    long counter=0;
    while(1) {
	switch(getc(stream)) {
	    case EOF:  return;
	    case 0x00: counter++; break;
	    case 0x01: if (counter >= 2) return;
	    default:   counter=0;
	    }
	}
    }

/*
 * Initializes all global variables for every new MPEG stream to be parsed.
 */
void
init_vars() {
    int i;
    file_offset=0;
    total_frames=0;
    previous_frame=0;
    cycle_exists=TRUE;
    cycle_len=0;
    for(i=0; i < 3; i++) {
	frame_sums[i].sum_frames=0;
	frame_sums[i].sum_frame_len=0;
	}
    }

/*
 * Collects info on the MPEG stream specified and presents them.
 */
void
report_info (char *filename, FILE *stream) {
    boolean error_exit=FALSE, finish=FALSE;
    static first_call=TRUE;
    char tmp_string[15];

    init_vars();
    get_name(filename);
    get_stream_res(stream);
    if (report_info_type==1) {
	if (first_call) {
	    first_call=FALSE;
	    printf("\nInfoMPEG version 1.0\n");
	    printf("--------------------\n");
	    printf("Filename                  Frame Resolution     Frame Types Present\n");
	    printf("--------                  ----------------     -------------------\n");
	    }
	while(!finish) {
	    next_start_code(stream);
	  /*
	   *  The last byte of a start code is read.  If the byte is
	   *  0x00 - data to reconstruct a frame follows
	   *  0xB7 - the MPEG stream ends (0x000001B7 is the sequence end code)
	   *  EOF  - a premature EOF has been encountered indicating the
	   *         bitstream is missing a sequence end code
	   */
	    switch (getc(stream)) {
		case 0x00: get_frame_info(YES, stream);     break;
		case 0xB7: error_exit=FALSE;  finish=TRUE;  break;
		case EOF:  error_exit=TRUE;   finish=TRUE;
		}
	    /* 
	     *  Finishes when at least 1 of each frame type is found or 3
	     *  I frames have been encountered.  It is assumed that some sort
	     *  of recursive frame sequence exists, in which case all frame
	     *  types in the stream have already been found.
	     */
	    if (frame_sums[0].sum_frames==3 || (frame_sums[1].sum_frames != 0
					    && frame_sums[2].sum_frames != 0))
		finish=TRUE;
	    }
	sprintf(tmp_string,"(%ldx%ld)", width, height);
	printf("%-29s%-26s",filename,tmp_string);
	/* Assumes an I frame is always present */
	printf("I");
	if (frame_sums[1].sum_frames != 0)
	    printf("P");
	if (frame_sums[2].sum_frames != 0)
	    printf("B");
	printf("\n");
	}
    else {
	short tmp_compression;
	long filesize, uncomp_len, i;
	printf("\nInfoMPEG version 1.0\n");
	printf("--------------------\n");
	printf("%s is (%ldx%ld)\n\n", filename, width, height);
	printf("Sequence of Frames (Decoder's Viewpoint)\n");
	printf("----------------------------------------\n");
	while(!finish) {
	    next_start_code(stream);
	    switch (getc(stream)) {
		case 0x00: get_frame_info(YES, stream);     break;
		case 0xB7: error_exit=FALSE;  finish=TRUE;  break;
		case EOF:  error_exit=TRUE;   finish=TRUE;
		}
	    }
	get_frame_info(NO, stream);

	printf("\n\nRecursive frame type sequence : ");
	if (cycle_exists && (frame_sums[0].sum_frames > 3)) {
	    short i;
	    for(i=0; i < cycle_len; i++)
		printf("%c", frame_types[frame_cycle[i]]);
	    printf("\n                       Length : %d\n\n",cycle_len);
	    }
	else
	    printf("None found.\n\n");

	filesize = ftell(stream);
	uncomp_len = width*height*3*total_frames;
	if (report_info_type==3) {
	    for(i=0; i < 3; i++) {
	    if (frame_sums[i].sum_frames != 0) {
		printf("# of %c Frames : %d\n", frame_types[i+1], frame_sums[i].sum_frames);
		printf("Average Size  : %ld Bytes\n", frame_sums[i].sum_frame_len/
						      frame_sums[i].sum_frames);
		printf("Compression   : %ld:%ld  or\n", width*height*3*frame_sums[i].sum_frames,
							frame_sums[i].sum_frame_len);
		printf("                %d to 1\n\n", width*height*3*frame_sums[i].sum_frames/
						      frame_sums[i].sum_frame_len);
		}
	    }
	    printf("Total Frames        : %d\n", total_frames);
	    printf("Overall Avg Size    : %ld Bytes\n", filesize/total_frames);
	    printf("Overall Compression : %ld:%ld  or\n", uncomp_len, filesize);
	    tmp_compression = uncomp_len/filesize;
	    printf("                      %d to 1\n\n", tmp_compression);
	    }
	else {
	    for(i=0; i < 3; i++) {
		if (frame_sums[i].sum_frames != 0)
		    printf("# of %c Frames : %d\n", frame_types[i+1], frame_sums[i].sum_frames);
		}
	    printf("Total Frames  : %d\n", total_frames);
	    printf("Overall Compression : %ld:%ld  or\n", uncomp_len, filesize);
	    tmp_compression = uncomp_len/filesize;
	    printf("                      %d to 1\n\n", tmp_compression);
	    }
	if (num_files < MAX_FILES) {
	    strcpy(files[num_files].name, filename);
	    files[num_files].size = filesize;
	    tot_len += uncomp_len;
	    sprintf(tmp_string,"(%ldx%ld)", width, height);
	    strcpy(files[num_files].resolution, tmp_string);
	    files[num_files].frames = total_frames;
	    strcpy(tmp_string,"I");
	    if (frame_sums[1].sum_frames != 0)
		strcat(tmp_string,"P");
	    if (frame_sums[2].sum_frames != 0)
		strcat(tmp_string,"B");
	    strcpy(files[num_files].frame_types_found, tmp_string);
	    files[num_files++].compression = tmp_compression;
	    }
	}
    if (error_exit)
	fprintf(stderr,"Error: Premature EOF in %s.\n",filename);
    }

/*
 * Sorts the MPEG streams in order of compression(greatest to least).
 */
void
sort_files(short start, short end) {
    file_info tmp;
    int i, j, largest_left;
    for(i = start; i < end; i++) {
	tmp = files[i];
	largest_left=i;
	for(j = i+1; j <= end; j++) {
	    if (files[j].compression > tmp.compression) {
		tmp = files[j];
		largest_left = j;
		}
	    }
	tmp = files[i];
	files[i] = files[largest_left];
	files[largest_left] = tmp;
	}
    }

/*
 * If more than one MPEG stream is specified on the command line, with
 * 'report_info_type' not equal to 1, a chart listing the streams in order
 * of compression(greatest to least) is displayed allowing comparisons
 * between the streams.
 */
void
report_comparison() {
    ULONG i, tot_file_len=0;
    printf("\n*********************************************************\n\n");
    printf("MPEG Files (Ordered by Compression)\n");
    printf("-----------------------------------\n");
    printf("Filename        Filesize   Resolution  #Frames  Types  Compression\n");
    printf("--------        --------   ----------  -------  -----  -----------\n");
    sort_files(0, num_files-1);
    for(i=0; i < num_files; i++) {
	printf("%-17s%-10ld%-14s%-8d%-10s%d\n", files[i].name,
						files[i].size,
						files[i].resolution,
						files[i].frames,
						files[i].frame_types_found,
						files[i].compression);
	tot_file_len += files[i].size;
	}
    printf("------------------------------------------------------------------\n");
    printf("Total Filesizes: %-10ldTotal Compression: %ld:%ld  or\n", tot_file_len,
								      tot_len,
								      tot_file_len);
    printf("                                              %d to 1\n", tot_len/
								      tot_file_len);
    }

main(short argc, char *argv[]) {
    FILE *mpeg_stream;
    char filename[100];
    short file_start=1;

    #ifdef BORLAND
     expand_cmd_line(&argc, &argv);
    #endif

    if (argc < 2)
	usage();
    else {
	report_info_type = 3;
	if (argv[1][0] == '-') {
	    file_start++;
	    switch (argv[1][1]) {
		case '1': report_info_type = 1; break;
		case '2': report_info_type = 2; break;
		case '3': report_info_type = 3; break;
		default : usage();
		}
	    }
	num_files=0;
	for(;file_start < argc; file_start++) {
	    strcpy(filename,argv[file_start]);
	    if ((mpeg_stream = fopen(filename,"rb")) == NULL) {
		fprintf(stderr,"Error: Cannot open file %s.\n",filename);
		continue;
		}
	    next_start_code(mpeg_stream);
	    if (getc(mpeg_stream) != 0xB3) {  /* 0xB3 is the last byte of the sequence start code */
		fprintf(stderr,"Error: %s is not a valid MPEG video stream.\n",filename);
		fclose(mpeg_stream);
		continue;
		}
	    report_info(filename, mpeg_stream);
	    fclose(mpeg_stream);
	    }
	if (num_files > 1)
	    report_comparison();
	}
    return(0);
    }
