/*
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * Author:
 *         Thomas Fuhrmann <fuhrmann@ira.uka.de>
 *         Stefan Denk <denk@ira.uka.de>
 *
 * $Id: parsefile.c,v 1.14 2006/02/02 18:11:53 kutzner Exp $
 *
 */

/*************************************************************
 *
 * MPEG parser - DVDR project
 *
 * Author: Thomas Fuhrmann
 * Date: March 2005
 *
 * Copyright (c) 2005 by 
 * P2P Research Group of the Systems Architecture Group
 * University of Karlsruhe (TH), Germany.
 *
 * All rights reserved.
 *
 * This code performs a task similar to the VDR project's 
 * 'genindex' program. It shares a few definitions with that
 * respective source code.
 *
 *************************************************************/

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdlib.h>
#include "parsefile.h"
#include "sha1.h"

/*************************************************************
 * MPEG header types:
 *************************************************************/

#define PICTURE_START   0x00

#define USER_DATA       0xb2
#define SEQUENCE_HEADER 0xb3
#define SEQUENCE_ERROR  0xb4
#define EXTENSION_HDR   0xb5
#define SEQUENCE_END    0xb7
#define GROUP_START     0xb8
#define MPEG_END_CODE   0xb9
#define PACK_HEADER     0xba
#define SYSTEM_HEADER   0xbb

#define PROG_STREAM_MAP	0xBC
#define PRIVATE_STREAM1	0xBD
#define PADDING_STREAM	0xBE
#define PRIVATE_STREAM2	0xBF

#define AUDIO_STREAM_S	0xC0
#define AUDIO_STREAM_E	0xDF
#define VIDEO_STREAM_S	0xE0
#define VIDEO_STREAM_E	0xEF

#define ECM_STREAM	0xF0
#define EMM_STREAM	0xF1
#define DSM_CC_STREAM	0xF2
#define ISO13522_STREAM	0xF3
#define PROG_STREAM_DIR	0xFF

/*************************************************************
 * MPEG picture types:
 *************************************************************/

#define NO_PICTURE	0
#define I_FRAME		1
#define P_FRAME		2
#define B_FRAME		3

/*************************************************************
 * File buffer for reading the MPEG file:
 *************************************************************/

#define BUFFER_SIZE	4096

unsigned char
fget (fget_buf_t * fbuffer)
{
  //ASSERT( fbuffer->pos <= fbuffer->len, "We read bytewise, but have somehow missed the buffer's end. Panic!");
  if (fbuffer->pos == fbuffer->len)
    {
      fbuffer->offset += fbuffer->len;
      fbuffer->len = fread (fbuffer->buffer, 1, BUFFER_SIZE, fbuffer->fp);
      fbuffer->pos = 0;
      /*
       * Test whether we have bytes at all. If not, we return zero. 
       * We should throw an exception, but that is future work ...
       */
      if ( fbuffer->len == 0 ) return 0;
    }
  return fbuffer->buffer[fbuffer->pos++];
}

/*************************************************************
 * 
 * Parse a video file and report the frames' timestamps and offsets
 * in the video file.
 *
 * Note: VDR has its own mechanism to detect I-Frame offsets.
 * This mechanism here is similar.
 *
 * Warning: Timestamps in the video stream might not be correct!
 * Do not rely on them!
 *
 *************************************************************/

int parsefile (char *videofile, char *videodatafile)
{
  int zeros = 0;

  /*--------------------------------------------------------
   * Data parsed from the MPEG file:
   *--------------------------------------------------------*/

  unsigned long filesize = 0;
  
  unsigned char hours = 0;
  unsigned char minutes = 0;
  unsigned char seconds = 0;
  unsigned char pictures = 0;

  unsigned long offset = 0;
  unsigned long roffset = 0;
  unsigned int size = 0;
  unsigned int hash = 0;
  unsigned int pic_count = 0;
  
  unsigned int gopcounter = 0;	// to write to the fitting (nth) position in viddata[n]
  
  FILE *savefp, *fp;
  void *fittedsize;
  vid *viddata;

  fp = fopen(videofile, "r");
  fget_buf_t fbuffer = { 0, 0, 0, fp, 0};
  
  fseek (fp, 0, SEEK_END);
  filesize = ftell (fp);
  fseek (fp, 0, SEEK_SET);

  printf("parsing %s to %s\n", videofile, videodatafile);

  if ((viddata = (vid *) malloc (filesize / 5000)) == NULL)
    printf ("error mallocating memory!\n");
  // 1 mb of viddata contains information of roughly 9000-16000 mb of mpeg2-data - the longer the clip, the worse the ratio, as it seems
  // funny enough VOX produces worse ratios than VIVA
  // the number of pictures per GoP averages out to exactly 12 over a longer record duration
  
  do
    {
      unsigned char byte = fget (&fbuffer);
      switch (byte)
	{
	case 0:
	  zeros++;
	  break;
	case 1:
	  if (zeros >= 2)
	    {
	      unsigned char type = fget (&fbuffer);
	      switch (type)
		{

		case VIDEO_STREAM_S...VIDEO_STREAM_E:
		  {
		    roffset = fbuffer.offset + fbuffer.pos - 4;
		    if(roffset == filesize)
		    {
		    size = roffset - offset;
		    vidtrans_SHA1_CTX context;
		    unsigned char full_hash[20];
		    void *data =
		      mmap (NULL, size, PROT_READ, MAP_SHARED, fileno (fp),
			    offset);
		    vidtrans_SHA1Init (&context);
		    vidtrans_SHA1Update (&context, (const unsigned char*)data, size);
		    vidtrans_SHA1Final (full_hash, &context);
		    munmap (data, size);
		    hash = ntohl (*((unsigned int *) full_hash));

	    /*--------------------------------------------------------
	     * also store the last block (but only if its size is >250000 - XXX - nasty hack, as I so far do not know how to distinguish a truncated file
	     * from a not truncated file
	     *--------------------------------------------------------*/

		    if (size > 250000)	
		      {
			viddata[gopcounter].hash = hash;
			viddata[gopcounter].offset = offset;
			viddata[gopcounter].length = size;
			viddata[gopcounter].picno = viddata[gopcounter - 1].picno + pic_count;
			gopcounter++;
		      }
		    }
		    break;
		  }

		case GROUP_START:
		  {
		    size = roffset - offset;

		if (size != 0)
		{

		    vidtrans_SHA1_CTX context;
		    unsigned char full_hash[20];

		    unsigned long offset_to_page = offset & 0xFFF;

		    void *data =
		      mmap (NULL, size + offset_to_page, PROT_READ, MAP_SHARED, fileno (fp),
			    offset - offset_to_page);
		    vidtrans_SHA1Init (&context);
		    vidtrans_SHA1Update (&context, (const unsigned char *)data + offset_to_page, size);
		    vidtrans_SHA1Final (full_hash, &context);
		    munmap (data, size);
		    hash = ntohl (*((unsigned int *) full_hash));

	    /*--------------------------------------------------------
	     * store the parsed information in struct viddata (if there is information inside)
	     *--------------------------------------------------------*/

	 		viddata[gopcounter].hash = hash;
			viddata[gopcounter].offset = offset;
			viddata[gopcounter].length = size;
			if (gopcounter == 0)
			  {
			    viddata[gopcounter].picno = pic_count;
			  }	
			else
			  {
			    viddata[gopcounter].picno = viddata[gopcounter - 1].picno + pic_count;
			  }
			gopcounter++;
		}

	    /*--------------------------------------------------------
	     *
	     *--------------------------------------------------------*/

		    offset = roffset;
		    pic_count = 0;

		    unsigned int timecode =
		      (((((fget (&fbuffer) << 8) | fget (&fbuffer)) << 8) |
			fget (&fbuffer)) << 8) | fget (&fbuffer);
		    pictures = (timecode >>= 7) & 0x3F;
		    seconds = (timecode >>= 6) & 0x3F;
		    minutes = (timecode >>= 7) & 0x3F;
		    hours = (timecode >>= 6) & 0x1F;
#ifdef DEBUG
		    printf ("New GOP with timecode: %02d:%02d:%02d:%02d\n",
			    hours, minutes, seconds, pictures);			// values not useful for cutting :(
#endif
		    break;
		  }

		case PICTURE_START:
		  {
#ifdef DEBUG
		    fget (&fbuffer);
		    char frametype = (fget (&fbuffer) >> 3) & 0x07;
		    switch (frametype)
		      {
		      case I_FRAME:
			printf ("I-Frame found at %ld\n", offset);
			break;
#ifdef DEBUG_2
		      case P_FRAME:
			printf ("P-Frame found at %ld\n", offset);
			break;
		      case B_FRAME:
			printf ("B-Frame found at %ld\n", offset);
			break;
		      default:
			printf ("\n");
#endif
		      }
#endif
		    pic_count++;
		    break;
		  }
		}
	    }
	default:
	  zeros = 0;
	}
    if (roffset == filesize) break;	// if the end of an already truncated file is reached, jump out (otherwise SIGSEGV happens in fget)
    }
  while ((fbuffer.len == BUFFER_SIZE) || (fbuffer.pos != fbuffer.len));
  
  fittedsize = (void *) realloc (viddata, gopcounter * sizeof (vid));
  if (fittedsize == NULL)
    printf ("reallocation failed!!\n");

  savefp = fopen (videodatafile, "w");
  if (savefp == NULL)
    printf ("error opening outfile!\n");
  if (gopcounter != (fwrite (fittedsize, sizeof (vid), gopcounter, savefp)))
    printf ("saving %s failed!\n", videodatafile);

  fclose(savefp);
  fclose(fp);

  free (fittedsize);
  return 1; 
}

