/*
 * mpglen - A program to scan through a MPEG file and count the number of
 *          GOPs and frames.
 *
 * Copyright (C) 2003 Glen Harris
 * mpglen@iamnota.org
 * http://www.iamnota.net/mpglen
 *
 * 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.
 * http://www.gnu.org/copyleft/gpl.html
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/mman.h>

extern char *optarg;
extern int optind, opterr, optopt;
extern int errno;

void help(char *prog) {
	printf("usage: %s [-a] [-c size] [-d] [-e] [-f] [-g] [-h] [-p] [-t] file [file] ...\n", prog);
	printf("-a          show an total for all files processed as \"Total #frames #GOPs\\n\".\n");
	printf("-c size     process file(s) in chunks of <size> Mb. (Default 4)\n");
	printf("-d          emit debugging messages on stderr.\n");
	printf("-e          easily parseable output \"#frames #GOPs filename\\n\".\n");
	printf("-f          only print \"#frames\\n\".\n");
	printf("-g          only print \"#GOPs\\n\".\n");
	printf("-p          emit progress report as each chunk is processed.\n");
	printf("-t          terse output, only prints \"#frames #GOPs\\n\".\n");
	printf("-h          show this usage message.\n");
	printf("Setting -d as the first argument will show argument parsing, too.\n");
	printf("If you use -s, the exit code will report success(0) or failure(-1)\n");
	printf("Exit codes: 0=Success  -1=Parse error  -2=m[un]map error  -3=Other\n");
}

int main(int argc, char **argv) {
	int OPT_A, OPT_C, OPT_D, OPT_E, OPT_F, OPT_G, OPT_P, OPT_S, OPT_T;
	int optchar;
	int frames, gops;
	int cframes, cgops;
	int tframes, tgops;
	int chunksize;
	int pagesize;
	int fnum, fp;
	unsigned char *fmem;
	off64_t flen, fpos, fmemend, cpos;
	
	OPT_C = 4;
	OPT_A = OPT_D = OPT_E = OPT_F = OPT_G = OPT_P = OPT_T = 0;
	
	while((optchar = getopt(argc, argv, "ac:defghpt")) != -1) {
		switch (optchar) {
			case 'a':	OPT_A = 1;
						if (OPT_D) {
							fprintf(stderr, "Found (-a), printing total of all files at the end.\n");
						}
						break;
			case 'c':	OPT_C = atoi(optarg);
						if (OPT_C < 1) {
							fprintf(stderr, "Chunks of less than 1Mb are not supported. It would defeat the whole\n");
							fprintf(stderr, "purpose of mmap()ing the file in the first place.\n");
							exit(-1);
						}
						if (OPT_C > (512*1024)) {
							fprintf(stderr, "Systems capable of mmap64()ing this much memory have not been tested, so to\n");
							fprintf(stderr, "help me enhance this program's support for modern hardware, you may contact\n");
							fprintf(stderr, "me at astfgl@iamnota.org to arrange donations of suitable gear.\n");
						}
						if (OPT_D) {
							fprintf(stderr, "Found (-c), set chunk size to %d Mb.\n", OPT_C);
						}
						break;
			case 'd':	OPT_D = 1;
						if (OPT_D) {
							fprintf(stderr, "Found (-d), enabled debugging.\n");
						}
						break;
			case 'e':	OPT_E = 1;
						if (OPT_D) {
							fprintf(stderr, "Found (-e), printing totals in easily parsed format.\n");
						}
						break;
			case 'f':	OPT_F = 1;
						if (OPT_D) {
							fprintf(stderr, "Found (-f), only printing total frames.\n");
						}
						break;
			case 'g':	OPT_G = 1;
						if (OPT_D) {
							fprintf(stderr, "Found (-g), only printing total GOPs.\n");
						}
						break;
			case 'h':	help(argv[0]);
						exit(0);
			case 't':	OPT_T = 1;
						if (OPT_D) {
							fprintf(stderr, "Found (-t), printing final totals as \"%%d %%d\\n\".\n");
						}
						break;
			case 'p':	OPT_P = 1;
						if (OPT_D) {
							fprintf(stderr, "Found (-p), printing progress report for every chunk.\n");
						}
						break;
		}
	}
	
	chunksize = OPT_C * (1024*1024);
	if (OPT_D) {
		fprintf(stderr, "Chunksize is %d bytes.\n", chunksize);
	}
	
	pagesize = getpagesize();
	if (OPT_D) {
		fprintf(stderr, "This system has a pagesize of %d bytes. Aligning all mmap64()s.\n", pagesize);
	}
	
	fnum = 0;
	tframes = tgops = 0;
	while (optind < argc) {
		fnum++;
		fp = open64(argv[optind], O_RDONLY | O_LARGEFILE);
		if (!fp) {
			fprintf(stderr, "Couldn't open file %s\n",argv[optind]);
			continue;
		}

		/* We're looking ahead for a 4 byte signature, so back off three bytes */
		flen = lseek64(fp, 0, SEEK_END) - 3;
		lseek64(fp, 0, SEEK_SET);
		if (OPT_D) {
			fprintf(stderr, "File is %lld bytes long.\n", flen);
		}
		
		fmem = mmap64(0, chunksize+pagesize, PROT_READ, MAP_SHARED, fp, 0);
		if (fmem == (void *)-1) {
			perror("mmap64()");
			fprintf(stderr, "Cannot mmap64(0, %ld, PROT_READ, MAP_SHARED, fp, %lld)\n", chunksize+pagesize, fpos);
			exit(-2);
		}
		if (OPT_D) {
			fprintf(stderr, "mmap64(0, %ld, PROT_READ, MAP_SHARED, fp, %lld)\n", chunksize+pagesize, fpos);
		}
		fmemend = chunksize-1; /* Counting from zero, remember! */

		fpos = cpos = 0;
		frames = gops = 0;
		cframes = cgops = 0;

		while (fpos < flen) {
			if (fpos > fmemend) {
				if (munmap(fmem, chunksize+pagesize) == -1) {
					perror("munmap()");
					fprintf(stderr, "Cannot munmap %ld bytes\n", chunksize+pagesize);
					close(fp);
					exit(-2);
				}
				if (OPT_D) {
					fprintf(stderr, "munmap(fmem, %ld)\n", chunksize+pagesize);
				}
				fmem = mmap64(0, chunksize+pagesize, PROT_READ, MAP_SHARED, fp, fpos);
				if (fmem == (void *)-1) {
					perror("mmap64()");
					fprintf(stderr, "Cannot mmap64(0, %ld, PROT_READ, MAP_SHARED, fp, %lld)\n", chunksize+pagesize, fpos);
					exit(-2);
				}
				if (OPT_D) {
					fprintf(stderr, "mmap64(0, %ld, PROT_READ, MAP_SHARED, fp, %lld)\n", chunksize+pagesize, fpos);
				}
				fmemend += chunksize;
				if (OPT_P) {
					fprintf(stderr, "");
					fprintf(stderr, "File %d, %d Mb, ", fnum, (int)(fpos / (1024*1024)));
					fprintf(stderr, "%3d/%d", cframes, frames);
					if (OPT_A) {
						fprintf(stderr, "/%d", tframes);
					}
					fprintf(stderr, " frames, %3d/%d", cgops, gops);
					if (OPT_A) {
						fprintf(stderr, "/%d", tgops);
					}
					if (OPT_A) {
						fprintf(stderr, " gops (c/f/a)\n");
					} else {
						fprintf(stderr, " gops (c/f)\n");
					}
				}
				tframes += cframes;
				tgops += cgops;
				frames += cframes;
				gops += cgops;
				cframes = cgops = 0;
				cpos = 0;
			}
			if ((fmem[cpos]==0) &&
				(fmem[cpos+1]==0) &&
				(fmem[cpos+2]==1))
			{
				if (fmem[cpos+3]==0) {
					cframes++;
				}
				if (fmem[cpos+3]==0xB8) {
					cgops++;
				}
			}
			fpos++;
			cpos++;
		}
		munmap(fmem, chunksize+3);
		close(fp);
		tframes += cframes;
		tgops += cgops;
		frames += cframes;
		gops += cgops;
		if (OPT_T) {	/* Terse */
			printf("%d %d\n", frames, gops);
		} else {
			if (OPT_E) {	/* Easy to parse */
				printf("%d %d %s\n", frames, gops, argv[optind]);
			} else {
				if (OPT_F) {	/* Frames only */
					printf("%d\n", frames);
				} else {
					if (OPT_G) {	/* GOPs only */
						printf("%d\n", gops);
					} else {	/* Deafult */
						printf("Processed %s: total %ld frames in %ld GOPs\n", argv[optind], frames, gops);
					}
				}
			}
		}
		frames = gops = 0;
		optind++;
	}
	if (OPT_A) {
		if (OPT_F || OPT_G) {
			if (OPT_F) {
				printf("Total %d \n", tframes);
			}
			if (OPT_G) {
				printf("Total %d \n", tgops);
			}
		} else {
			printf("Total %d %d\n", tframes, tgops);
		}
	}
	return(0);
}
