/*
 *  import_mpeg2.c
 *
 *  Copyright (C) Thomas Oestreich - June 2001
 *
 *  This file is part of transcode, a video stream  processing tool
 *
 *  transcode 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, or (at your option)
 *  any later version.
 *
 *  transcode 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define MOD_NAME    "import_mpeg2.so"
#define MOD_VERSION "v0.4.0 (2003-10-02)"
#define MOD_CODEC   "(video) MPEG2"

#include "transcode.h"

static int verbose_flag = TC_QUIET;
static int capability_flag = TC_CAP_RGB | TC_CAP_YUV | TC_CAP_VID;

#define MOD_PRE mpeg2
#include "import_def.h"


char import_cmd_buf[TC_BUF_MAX];

typedef struct tbuf_t {
	int off;
	int len;
	char *d;
} tbuf_t;

// m2v passthru
static int can_read = 1;
static tbuf_t tbuf;
static int m2v_passthru=0;
static FILE *f; // video fd


/* ------------------------------------------------------------
 *
 * open stream
 *
 * ------------------------------------------------------------*/

MOD_open
{

  char requant_buf[256];
  long sret;

  if(param->flag != TC_VIDEO) return(TC_IMPORT_ERROR);

  if(vob->ts_pid1==0) { // no transport stream

    switch(vob->im_v_codec) {

    case CODEC_RGB:

      sret = tc_snprintf(import_cmd_buf, TC_BUF_MAX,
			 "tcextract -x mpeg2 -i \"%s\" -d %d |"
			 " tcdecode -x mpeg2 -d %d",
			 vob->video_in_file, vob->verbose, vob->verbose);
      if (sret < 0)
	return(TC_IMPORT_ERROR);

      break;

    case CODEC_YUV:

      sret = tc_snprintf(import_cmd_buf, TC_BUF_MAX,
			 "tcextract -x mpeg2 -i \"%s\" -d %d |"
			 " tcdecode -x mpeg2 -d %d -y yuv420p",
			 vob->video_in_file, vob->verbose, vob->verbose);
      if (sret < 0)
	return(TC_IMPORT_ERROR);

      break;

    case CODEC_RAW:
    case CODEC_RAW_YUV:

	memset(requant_buf, 0, sizeof (requant_buf));
	if (vob->m2v_requant > M2V_REQUANT_FACTOR) {
	  tc_snprintf (requant_buf, 256, " | tcrequant -d %d -f %f ",
                    vob->verbose, vob->m2v_requant);
	}
	m2v_passthru=1;

        sret = tc_snprintf(import_cmd_buf, TC_BUF_MAX,
			   "tcextract -x mpeg2 -i \"%s\" -d %d%s",
			   vob->video_in_file, vob->verbose, requant_buf);
        if (sret < 0)
	  return(TC_IMPORT_ERROR);

	break;
    }

  } else {

    switch(vob->im_v_codec) {

    case CODEC_RGB:

      sret = tc_snprintf(import_cmd_buf, TC_BUF_MAX,
			 "tccat -i \"%s\" -d %d -n 0x%x |"
			 " tcextract -x mpeg2 -t m2v -d %d |"
			 " tcdecode -x mpeg2 -d %d",
			 vob->video_in_file, vob->verbose, vob->ts_pid1,
			 vob->verbose, vob->verbose);
      if (sret < 0)
	return(TC_IMPORT_ERROR);

      break;

    case CODEC_YUV:

      sret = tc_snprintf(import_cmd_buf, TC_BUF_MAX,
			 "tccat -i \"%s\" -d %d -n 0x%x |"
			 " tcextract -x mpeg2 -t m2v -d %d |"
			 " tcdecode -x mpeg2 -d %d -y yuv420p",
			 vob->video_in_file, vob->verbose,vob->ts_pid1,
			 vob->verbose, vob->verbose);
      if (sret < 0)
	return(TC_IMPORT_ERROR);

      break;
    }
  }

  // print out
  if(verbose_flag) tc_log_info(MOD_NAME, "%s", import_cmd_buf);

  param->fd = NULL;

  // popen
  if((param->fd = popen(import_cmd_buf, "r"))== NULL) {
    tc_log_perror(MOD_NAME, "popen RGB stream");
    return(TC_IMPORT_ERROR);
  }

  // we handle the read;
  if (m2v_passthru) {
    f = param->fd;
    param->fd = NULL;

    tbuf.d = tc_malloc (SIZE_RGB_FRAME);
    tbuf.len = SIZE_RGB_FRAME;
    tbuf.off = 0;

    if ((tbuf.len = fread(tbuf.d, 1, tbuf.len, f)) < 0)
      return(TC_IMPORT_ERROR);

    // find a sync word
    while (tbuf.off+4<tbuf.len) {
      if (tbuf.d[tbuf.off+0]==0x0 && tbuf.d[tbuf.off+1]==0x0 &&
	  tbuf.d[tbuf.off+2]==0x1 &&
	  (unsigned char)tbuf.d[tbuf.off+3]==0xb3) break;
      else tbuf.off++;
    }
    if (tbuf.off+4>=tbuf.len)  {
      tc_log_warn(MOD_NAME, "Internal Error. No sync word");
      return (TC_IMPORT_ERROR);
    }

  }

  return(TC_IMPORT_OK);
}

/* ------------------------------------------------------------
 *
 * decode  stream
 *
 * ------------------------------------------------------------*/

MOD_decode{

  if(param->flag == TC_VIDEO && m2v_passthru) {

    // ---------------------------------------------------
    // This code splits the MPEG2 elementary stream
    // into packets. It sets the type of the packet
    // as an frame attribute.
    // I frames (== Key frames) are not only I frames,
    // they also carry the sequence headers in the packet.
    // ---------------------------------------------------

    int ID, start_seq, start_pic, pic_type;

    ID = tbuf.d[tbuf.off+3]&0xff;

    switch (ID) {
      case 0xb3: // sequence
	start_seq = tbuf.off;

	// look for pic header
	while (tbuf.off+6<tbuf.len) {

	  if (tbuf.d[tbuf.off+0]==0x0 && tbuf.d[tbuf.off+1]==0x0 &&
	      tbuf.d[tbuf.off+2]==0x1 && tbuf.d[tbuf.off+3]==0x0 &&
	      ((tbuf.d[tbuf.off+5]>>3)&0x7)>1 &&
	      ((tbuf.d[tbuf.off+5]>>3)&0x7)<4) {
	    if (verbose & TC_DEBUG)
              tc_log_warn(MOD_NAME, "Completed a sequence + I frame from %d -> %d",
		      start_seq, tbuf.off);

	    param->attributes |= TC_FRAME_IS_KEYFRAME;
	    param->size = tbuf.off-start_seq;

	    // spit frame out
	    ac_memcpy(param->buffer, tbuf.d+start_seq, param->size);
	    memmove(tbuf.d, tbuf.d+param->size, tbuf.len-param->size);
	    tbuf.off = 0;
	    tbuf.len -= param->size;

	    if (verbose & TC_DEBUG)
              tc_log_info(MOD_NAME, "%02x %02x %02x %02x",
			      tbuf.d[0]&0xff, tbuf.d[1]&0xff,
                              tbuf.d[2]&0xff, tbuf.d[3]&0xff);
	    return TC_IMPORT_OK;
	  }
	  else tbuf.off++;
	}

	// not enough data.
	if (tbuf.off+6 >= tbuf.len) {

	  if (verbose & TC_DEBUG) tc_log_info(MOD_NAME, "Fetching in Sequence");
	  memmove (tbuf.d, tbuf.d+start_seq, tbuf.len - start_seq);
	  tbuf.len -= start_seq;
	  tbuf.off = 0;

	  if (can_read>0) {
	    can_read = fread (tbuf.d+tbuf.len, SIZE_RGB_FRAME-tbuf.len, 1, f);
	    tbuf.len += (SIZE_RGB_FRAME-tbuf.len);
	  } else {
	    tc_log_info(MOD_NAME, "No 1 Read %d", can_read);
	    /* XXX: Flush buffers */
	    return TC_IMPORT_ERROR;
	  }
	}
	break;

      case 0x00: // pic header

	start_pic = tbuf.off;
	pic_type = (tbuf.d[start_pic+5] >> 3) & 0x7;
	tbuf.off++;

	while (tbuf.off+6<tbuf.len) {
	  if (tbuf.d[tbuf.off+0]==0x0 && tbuf.d[tbuf.off+1]==0x0 &&
	      tbuf.d[tbuf.off+2]==0x1 &&
	      (unsigned char)tbuf.d[tbuf.off+3]==0xb3) {
	    if (verbose & TC_DEBUG)
               tc_log_info(MOD_NAME, "found a last P or B frame %d -> %d",
                       start_pic, tbuf.off);

	    param->size = tbuf.off - start_pic;

	    ac_memcpy(param->buffer, tbuf.d+start_pic, param->size);
	    memmove(tbuf.d, tbuf.d+param->size, tbuf.len-param->size);
	    tbuf.off = 0;
	    tbuf.len -= param->size;

	    return TC_IMPORT_OK;

	  } else if // P or B frame
	    (tbuf.d[tbuf.off+0]==0x0 && tbuf.d[tbuf.off+1]==0x0 &&
	     tbuf.d[tbuf.off+2]==0x1 && tbuf.d[tbuf.off+3]==0x0 &&
	     ((tbuf.d[tbuf.off+5]>>3)&0x7)>1 &&
	     ((tbuf.d[tbuf.off+5]>>3)&0x7)<4) {
	      if (verbose & TC_DEBUG)
                tc_log_info(MOD_NAME, "found a P or B frame from %d -> %d",
		        start_pic, tbuf.off);

	      param->size = tbuf.off - start_pic;

	      ac_memcpy(param->buffer, tbuf.d+start_pic, param->size);
	      memmove(tbuf.d, tbuf.d+param->size, tbuf.len-param->size);
	      tbuf.off = 0;
	      tbuf.len -= param->size;

	      return TC_IMPORT_OK;

	    } else tbuf.off++;

	  // not enough data.
	  if (tbuf.off+6 >= tbuf.len) {

	    memmove (tbuf.d, tbuf.d+start_pic, tbuf.len - start_pic);
	    tbuf.len -= start_pic;
	    tbuf.off = 0;

	    if (can_read>0) {
	      can_read = fread (tbuf.d+tbuf.len, SIZE_RGB_FRAME-tbuf.len, 1, f);
	      tbuf.len += (SIZE_RGB_FRAME-tbuf.len);
	    } else {
	      tc_log_info(MOD_NAME, "No 1 Read %d", can_read);
	      /* XXX: Flush buffers */
	      return TC_IMPORT_ERROR;
	    }
	  }
	}
	break;
      default:
	// should not get here
	tc_log_warn(MOD_NAME, "Default case");
	tbuf.off++;
	break;
    }


  }
  return(TC_IMPORT_OK);
}

/* ------------------------------------------------------------
 *
 * close stream
 *
 * ------------------------------------------------------------*/

MOD_close
{

    if(param->fd != NULL) pclose(param->fd);
    if(f != NULL) pclose(f);
    param->fd = f = NULL;

    return(TC_IMPORT_OK);
}
