/* 
 *  dvbackup.c
 *
 *     Copyright (C) Peter Schlaile - Jan - Mai 2001
 *
 *  dvbackup 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.
 *   
 *  libdv 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. 
 *
 *  The dvbackup homepage is http://dvbackup.sourceforge.net/.  
 */

/* FIXME:
TODO: - Testmodus, der 123456 und so weiter aufs Band schreibt 
        und beim Rücklesen meldet, was falsch ist.
      - Modus, der Audiodaten mit gueltigem Header schreibt.
      - Man kann sowohl Video als auch Audio-Daten als ungültig kennzeichnen
        (nach Standard!!!) => Wenn man den Pinguin einspart kriegt man noch
	mehr drauf...
      - Man kann noch beliebige Binärdaten in den AUX-Bereichen unterbringen.

*/

#include <time.h>
#include <math.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>
#include <popt.h>

#include "minilogo.c"

#define DVBACKUP_VERSION 0x01

/*
  4 bytes dvbackup magic      ("DV-B")
  2 bytes header size  
  1 byte  dvbackup version    (0x01 for now...)
  8 bytes logical byte offset
  1 byte  flagbyte :     0x01 - EOF
  3 byte  bytes used in this frame
  4 byte  adler 32 checksum over block
  n bytes backup-id (ASCII without Z!)
*/

#define HEADER_SIZE   (4+2+1+8+1+3+4)

/* We only use the DC coefficients! */
#define DV_WIDTH       (720/8)
#define DV_PAL_HEIGHT  (576/8)
#define DV_NTSC_HEIGHT (480/8)

int     dv_super_map_vertical[5] = { 2, 6, 8, 0, 4 };
int     dv_super_map_horizontal[5] = { 2, 1, 3, 0, 4 };

int    dv_parse_bit_start[6] = { 4,  18, 32, 46, 60, 70 };
int    dv_parse_bit_end[6]   = { 18, 32, 46, 60, 70, 80 };

typedef struct {
	int       i,j;   /* superblock row/column, */
	int       k;     /* macroblock no. within superblock */
	int       x, y;  /* top-left corner position */
	short     b[6];  /* DC coefficients */
} dv_macroblock_t;

typedef struct {
	int       i, k;
	dv_macroblock_t mb[5]; 
	int       isPAL;
} dv_videosegment_t;

static inline void
dv_place_411_macroblock(dv_macroblock_t *mb) 
{
	int mb_num; /* mb number withing the 6 x 5 zig-zag pattern  */
	int mb_num_mod_6, mb_num_div_6; /* temporaries */
	int mb_row;    /* mb row within sb (de-zigzag) */
	int mb_col;    /* mb col within sb (de-zigzag) */
	/* Column offset of superblocks in macroblocks. */
	static const unsigned int column_offset[] = {0, 4, 9, 13, 18};  
	
	/* Consider the area spanned super block as 30 element macroblock
	   grid (6 rows x 5 columns).  The macroblocks are laid out in a in
	   a zig-zag down and up the columns of the grid.  Of course,
	   superblocks are not perfect rectangles, since there are only 27
	   blocks.  The missing three macroblocks are either at the start
	   or end depending on the superblock column.
	
	   Within a superblock, the macroblocks start at the topleft corner
	   for even-column superblocks, and 3 down for odd-column
	   superblocks. */

	mb_num = ((mb->j % 2) == 1) ? mb->k + 3: mb->k;  
	mb_num_mod_6 = mb_num % 6;
	mb_num_div_6 = mb_num / 6;

	/* Compute superblock-relative row position (de-zigzag) */
	mb_row = ((mb_num_div_6 % 2) == 0) ? 
		mb_num_mod_6 : (5 - mb_num_mod_6); 
	/* Compute macroblock's frame-relative column position (in blocks) */
	mb_col = (mb_num_div_6 + column_offset[mb->j]) * 4;
	/* Compute frame-relative byte offset of macroblock's top-left corner
	   with special case for right-edge macroblocks */
	if(mb_col < (22 * 4)) {
		/* Convert from superblock-relative row position 
		   to frame relative (in blocks). */
		mb_row += (mb->i * 6); /* each superblock is 6 blocks high */
		/* Normal case */
	} else { 
		/* Convert from superblock-relative row position to 
		   frame relative (in blocks). */
		mb_row = mb_row * 2 + mb->i * 6; 
                /* each right-edge macroblock is 2 blocks high, 
		   and each superblock is 6 blocks high */
	}
	mb->x = mb_col;
	mb->y = mb_row;
} /* dv_place_411_macroblock */

static inline void 
dv_place_420_macroblock(dv_macroblock_t *mb) 
{
	int mb_num; /* mb number withing the 6 x 5 zig-zag pattern */
	int mb_num_mod_3, mb_num_div_3; /* temporaries */
	int mb_row;    /* mb row within sb (de-zigzag) */
	int mb_col;    /* mb col within sb (de-zigzag) */
	/* Column offset of superblocks in macroblocks. */
	static const int column_offset[] = {0, 9, 18, 27, 36};  
	
	/* Consider the area spanned super block as 30 element macroblock
	   grid (6 rows x 5 columns).  The macroblocks are laid out in a in
	   a zig-zag down and up the columns of the grid.  Of course,
	   superblocks are not perfect rectangles, since there are only 27
	   blocks.  The missing three macroblocks are either at the start
	   or end depending on the superblock column. */
	
	/* Within a superblock, the macroblocks start at the topleft corner
	   for even-column superblocks, and 3 down for odd-column
	   superblocks. */
	mb_num = mb->k;  
	mb_num_mod_3 = mb_num % 3;
	mb_num_div_3 = mb_num / 3;
	/* Compute superblock-relative row position (de-zigzag) */
	mb_row = ((mb_num_div_3 % 2) == 0) ? mb_num_mod_3 : (2- mb_num_mod_3); 
	/* Compute macroblock's frame-relative column position (in blocks) */
	mb_col = mb_num_div_3 + column_offset[mb->j];
	/* Compute frame-relative byte offset of macroblock's top-left corner
	   Convert from superblock-relative row position to frame relative 
	   (in blocks). */
	mb_row += (mb->i * 3); /* each right-edge macroblock is 
				  2 blocks high, and each superblock is 
				  6 blocks high */
	mb->x = mb_col * 2;
	mb->y = mb_row * 2;
} /* dv_place_420_macroblock */

inline int f2b(float f)
{
	int b = rint(f);
	if (b < 0)
		b = 0;
	if (b > 255)
		b = 255;
	
	return b;
}


inline int f2sb(float f)
{
	int b = rint(f);
	
	return b;
}

	
	
static void convert_to_yuv(unsigned char* img_rgb, int height,
			   short* img_y, short* img_cr, short* img_cb)
{
	int x,y;
	/* This is the RGB -> YUV color matrix */
	static const double cm[3][3] = {
		{.299 * 219.0/255.0,.587* 219.0/255.0,.114* 219.0/255.0},
		{.5* 224.0/255.0,-.419* 224.0/255.0,-.081* 224.0/255.0},
		{-.169 * 224.0/255.0,-.331* 224.0/255.0,.5* 224.0/255.0}
	};

	double tmp_cr[DV_PAL_HEIGHT][DV_WIDTH];
	double tmp_cb[DV_PAL_HEIGHT][DV_WIDTH];

	for (y = 0; y < height; y++) {
		for (x = 0; x < DV_WIDTH; x++) {
			register double cy, cr, cb,val;
			register int r = img_rgb[(y * DV_WIDTH + x)*3 + 0];
			register int g = img_rgb[(y * DV_WIDTH + x)*3 + 1];
			register int b = img_rgb[(y * DV_WIDTH + x)*3 + 2];
			cy =    (cm[0][0] * r) + (cm[0][1] * g) +
				(cm[0][2] * b);
			cr =    (cm[1][0] * r) + (cm[1][1] * g) +
				(cm[1][2] * b);
			cb =    (cm[2][0] * r) + (cm[2][1] * g) +
				(cm[2][2] * b);
			
			val = cy - 128 + 16;

			img_y[y * DV_WIDTH + x] = f2sb(val * 2); 
			tmp_cr[y][x] = cr;
			tmp_cb[y][x] = cb;
		}
	}
	for (y = 0; y < height; y++) {
		for (x = 0; x < DV_WIDTH / 2; x++) {
			img_cr[y * DV_WIDTH / 2  + x] = 
				f2sb((tmp_cr[y][2*x] +
				      tmp_cr[y][2*x+1]) / 2.0);
			img_cb[y * DV_WIDTH / 2 + x] = 
				f2sb((tmp_cb[y][2*x] +
				      tmp_cb[y][2*x+1]) / 2.0);
		}
	}
}

void build_coeff(short* img_y, short* img_cr, short* img_cb,
		 dv_macroblock_t *mb, int isPAL)
{
	int y = mb->y;
	int x = mb->x;

        if (isPAL || mb->x == DV_WIDTH - 2) { /* PAL or rightmost NTSC block */
		mb->b[0] = img_y[y * DV_WIDTH +  x ];
		mb->b[1] = img_y[y * DV_WIDTH +  x + 1];
		mb->b[2] = img_y[(y + 1) * DV_WIDTH + x];
		mb->b[3] = img_y[(y + 1) * DV_WIDTH + x + 1];
		mb->b[4] = (img_cr[y * DV_WIDTH/2 + x / 2]
			    + img_cr[(y + 1) * DV_WIDTH/2 + x / 2]) >> 1;
		mb->b[5] = (img_cb[y * DV_WIDTH/2 + x / 2]
			    + img_cb[(y + 1) * DV_WIDTH/2 + x / 2]) >> 1;
	} else {
		mb->b[0] = img_y[y * DV_WIDTH + x ];
		mb->b[1] = img_y[y * DV_WIDTH + x + 1];
		mb->b[2] = img_y[y * DV_WIDTH + x + 2];
		mb->b[3] = img_y[y * DV_WIDTH + x + 3];
		mb->b[4] = (img_cr[y * DV_WIDTH/2 + x / 2]
			    + img_cr[y * DV_WIDTH/2 + x / 2 + 1]) >> 1;
		mb->b[5] = (img_cb[y * DV_WIDTH/2 + x / 2]
			    + img_cb[y * DV_WIDTH/2 + x / 2 + 1]) >> 1;
	}
}

static void process_videosegment(
	short* img_y, short* img_cr, short* img_cb,
	dv_videosegment_t* videoseg,
	unsigned char * vsbuffer)
{
	dv_macroblock_t *mb;
	int m;

	for (m = 0, mb = videoseg->mb; m < 5; m++, mb++) {
		unsigned int b;
		
		mb->i = (videoseg->i+ dv_super_map_vertical[m]) 
			% (videoseg->isPAL ? 12 : 10);
		mb->j = dv_super_map_horizontal[m];
		mb->k = videoseg->k;
		
                if (videoseg->isPAL) {
                        dv_place_420_macroblock(mb);
                } else {
                        dv_place_411_macroblock(mb);
                }
		build_coeff(img_y, img_cr, img_cb, mb, videoseg->isPAL);
		
		for (b = 0; b < 6; b++) {
			int ofs = (80 * m) + dv_parse_bit_start[b];
			
			vsbuffer[ofs] = (mb->b[b] >> 1) & 0xff;
			vsbuffer[ofs+1] = ((mb->b[b] << 7) & 0xff) | 0x6;
		}
	}
}

static void encode(short* img_y, short* img_cr, short* img_cb, 
		   int isPAL, unsigned char* target)
{
	static dv_videosegment_t videoseg;

	int numDIFseq;
	int ds;
	int v;
	unsigned int dif;
	unsigned int offset;

	memset(target, 0, 144000);

	dif = 0;
	offset = dif * 80;
	if (isPAL) {
		target[offset + 3] |= 0x80;
	}

	numDIFseq = isPAL ? 12 : 10;

	for (ds = 0; ds < numDIFseq; ds++) { 
		/* Each DIF segment conists of 150 dif blocks, 
		   135 of which are video blocks */
		dif += 6; /* skip the first 6 dif blocks in a dif sequence */
		/* A video segment consists of 5 video blocks, where each video
		   block contains one compressed macroblock.  DV bit allocation
		   for the VLC stage can spill bits between blocks in the same
		   video segment.  So parsing needs the whole segment to decode
		   the VLC data */
		/* Loop through video segments */
		for (v = 0; v < 27; v++) {
			/* skip audio block - 
			   interleaved before every 3rd video segment
			*/

			if(!(v % 3)) dif++; 

			offset = dif * 80;
			
			videoseg.i = ds;
			videoseg.k = v;
			videoseg.isPAL = isPAL;

			process_videosegment(img_y, img_cr, img_cb, 
					     &videoseg, target + offset);
			
			dif += 5;
		} 
	} 
}

void write_header_block(unsigned char* target, int ds, int isPAL)
{
	target[0] = 0x1f; /* header magic */
	target[1] = 0x07 | (ds << 4);
	target[2] = 0x00;

	target[3] = ( isPAL ? 0x80 : 0); /* FIXME: 0x3f */
	target[4] = 0x68; /* FIXME ? */
	target[5] = 0x78; /* FIXME ? */
	target[6] = 0x78; /* FIXME ? */
	target[7] = 0x78; /* FIXME ? */

	memset(target + 8, 0xff, 80 - 8);
}

inline void write_bcd(unsigned char* target, int val)
{
	*target = ((val / 10) << 4) + (val % 10);
}

void write_timecode_13(unsigned char* target, struct tm * now, int frame,
		       int isPAL)
{
	target[0] = 0x13;
	write_bcd(target+1, frame % (isPAL ? 25 : 30));
	write_bcd(target+2, now->tm_sec);
	write_bcd(target+3, now->tm_min);
	write_bcd(target+4, now->tm_hour);
}

/*
  60 ff ff 20 ff 61 33 c8 fd ff 62 ff d0 e1 01 63 ff b8 c6 c9
*/

void write_timecode_60(unsigned char* target, struct tm * now, int isPAL)
{
	target[0] = 0x60;
	target[1] = 0xff;
	target[2] = 0xff;
	target[3] = isPAL ? 0x20 : 0x00;
	target[4] = 0xff;
}

void write_timecode_61(unsigned char* target, struct tm * now)
{
	target[0] = 0x61; 

	target[1] = 0x33; /* scrambling off, compression 1, 
			     input-source no-info, copy-control off */
	target[2] = 0xc8; /* 4x3 fullscreen, record: original */
	target[3] = 0xfd; /* not record start, bcsys type 1, not still-picture
			     field-time-offset > 0, interlaced yes,
			     same-frame no, both-fields-used yes */

	target[4] = 0xff; /* category: no info (backup ;-) */
}

void write_timecode_62(unsigned char* target, struct tm * now)
{
	target[0] = 0x62;
	target[1] = 0xff;
	write_bcd(target + 2, now->tm_mday);
	write_bcd(target + 3, now->tm_mon);
	write_bcd(target + 4, now->tm_year % 100);
}
void write_timecode_63(unsigned char* target, struct tm * now)
{
	target[0] = 0x63;
	target[1] = 0xff;
	write_bcd(target + 2, now->tm_sec);
	write_bcd(target + 3, now->tm_min);
	write_bcd(target + 4, now->tm_hour);
}

void write_subcode_blocks(unsigned char* target, int ds, int frame, 
			  struct tm * now, int isPAL)
{
	static int block_count = 0;

	memset(target, 0xff, 2*80);

	target[0] = 0x3f; /* subcode magic */
	target[1] = 0x07 | (ds << 4);
	target[2] = 0x00;

	target[80 + 0] = 0x3f; /* subcode magic */
	target[80 + 1] = 0x07 | (ds << 4);
	target[80 + 2] = 0x01;
	
	target[5] = target[80 + 5] = 0xff;

	if (ds >= 6) {
		target[3] = 0x80 | (block_count >> 8);
		target[4] = block_count;

		target[80 + 3] = 0x80 | (block_count >> 8);
		target[80 + 4] = block_count + 6;

		write_timecode_13(target + 6, now, frame, isPAL);
		write_timecode_13(target + 80 + 6, now, frame, isPAL);

		write_timecode_62(target + 6 + 8, now);
		write_timecode_62(target + 80 + 6 + 8, now);

		write_timecode_63(target + 6 + 2*8, now);
		write_timecode_63(target + 80 + 6 + 2*8, now);

		write_timecode_13(target + 6 + 3*8, now, frame, isPAL);
		write_timecode_13(target + 80 + 6 + 3*8, now, frame, isPAL);

		write_timecode_62(target + 6 + 4*8, now);
		write_timecode_62(target + 80 + 6 + 4*8, now);

		write_timecode_63(target + 6 + 5*8, now);
		write_timecode_63(target + 80 + 6 + 5*8, now);
	} else {
		target[3] = (block_count >> 8);
		target[4] = block_count;

		target[80 + 3] = (block_count >> 8);
		target[80 + 4] = block_count + 6;
		
	}
	block_count += 0x20;
	block_count &= 0xfff;
}

void write_vaux_blocks(unsigned char* target, int ds, struct tm* now, 
		       int isPAL)
{
	memset(target, 0xff, 3*80);

	target[0] = 0x5f; /* vaux magic */
	target[1] = 0x07 | (ds << 4);
	target[2] = 0x00;

	target[0 + 80] = 0x5f; /* vaux magic */
	target[1 + 80] = 0x07 | (ds << 4);
	target[2 + 80] = 0x01;

	target[0 + 2*80] = 0x5f; /* vaux magic */
	target[1 + 2*80] = 0x07 | (ds << 4);
	target[2 + 2*80] = 0x02;


	if ((ds & 1) == 0) {
		if (ds == 0) {
			target[3] = 0x70; /* home camcorder 1 */
			target[4] = 0xc5;  /* blende */
			target[5] = 0x41;  /* automatic gain control */
			target[6] = 0x20;  /* white balance */
			target[7] = 0xff;  /* focus */
			target[8] = 0x71;  /* home camcorder 2 */
			target[9] = 0xff;
			target[10] = 0x7f; /* picture-stabilisator is on ;-) */
			target[11] = 0xff;
			target[12] = 0xff;
			target[13] = 0x7f; /* shutter ;-) */
			target[14] = 0xff;
			target[15] = 0xff;
			target[16] = 0x38;
			target[17] = 0x81;
		}
	} else {
		write_timecode_60(target + 3, now, isPAL);
		write_timecode_61(target + 3 + 5, now);
		write_timecode_62(target + 3 + 2*5, now);
		write_timecode_63(target + 3 + 3*5, now);
	}
	write_timecode_60(target + 2*80+ 48, now, isPAL);
	write_timecode_61(target + 2*80+ 48 + 5, now);
	write_timecode_62(target + 2*80+ 48 + 2*5, now);
	write_timecode_63(target + 2*80+ 48 + 3*5, now);
}

void write_video_headers(unsigned char* target, int frame, int ds)
{
	int i,j, z;
	z = 0;
	for(i = 0; i < 9; i++) {
		target += 80;
		for (j = 1; j < 16; j++) { /* j = 0: audio blocks */
			target[0] = 0x90 | ((frame + 0xb) % 12);
			target[1] = 0x07 | (ds << 4);
			target[2] = z++;
			target += 80;
		}
	}
}

void write_audio_headers(unsigned char* target, int frame, int ds)
{
	int i, z;
	unsigned char* p;

	if ((ds & 1) == 0) {
		p = target + 3 * 16 * 80 + 3;
	} else {
		p = target + 3;
	}

	z = 0;
	for(i = 0; i < 9; i++) {
		memset(target, 0xff, 80);

		target[0] = 0x70 | ((frame + 0xb) % 12);
		target[1] = 0x07 | (ds << 4);
		target[2] = z++;

		target += 16 * 80;
	}

	p[0] = 0x51; /* Source control AAUX 1 */
	p[1] = 0x33; /* copy without restrictions       (00b)
			no information for input source (11b)
			compression 1 time              (00b)
			no information for scrambling   (11b) */
	p[2] = 0xff; /* not a record start              (1b)
			not a record end                (1b)
			rec mode: invalid audio         (111b)
			insert channel: no info         (111b) */
	p[3] = 0xa0; /* normal speed */
	p[4] = 0xff; /* unknown category */
}

int write_full_audio_headers(unsigned char * frame_buf, int channels,
			     int frequency, int isPAL)
{
	int dif_seg;
	int dif_seg_max = isPAL ? 12 : 10;
	int samplesperframe = frequency / (isPAL ? 25 : 30);
	
	int bits_per_sample = 16;

	unsigned char head_50[5];
	unsigned char head_51[5];
	unsigned char head_52[5];
	unsigned char head_53[5];

	head_50[0] = 0x50;

	if (isPAL) {
		head_50[3] = /* stype = */ 0 | (/* isPAL */ 1 << 5)
			| (/* ml */ 1 << 6) | (/* res */ 1 << 7);
		switch(frequency) {
		case 32000:
			if (channels == 2) {
				head_50[1] = (samplesperframe - 1264) 
					| (1 << 6) | (/* unlocked = */ 1 << 7);
				head_50[2] = /* audio_mode= */ 0 
					| (/* pa = */ 0 << 4) 
					| (/* chn = */ 0 << 5)
					| (/* sm = */ 0 << 7);
				head_50[4] = /* 12 Bits */0 
					| (/* 32000 kHz */ 2 << 3)
					| (/* tc = */ 0 << 6) 
					| (/* ef = */ 0 << 7);
			} else {
				head_50[1] = (samplesperframe - 1264) 
					| (1 << 6) | (/* unlocked = */ 1 << 7);
				head_50[2] = /* audio_mode= */ 0 
					| (/* pa = */ 1 << 4) 
					| (/* chn = */ 1 << 5)
					| (/* sm = */ 0 << 7);
				head_50[4] = /* 12 Bits */1 
					| (/* 32000 kHz */ 2 << 3)
					| (/* tc = */ 0 << 6) 
					| (/* ef = */ 0 << 7);
				bits_per_sample = 12;
			}
			break;
		case 44100:
			head_50[1] = (samplesperframe - 1742) | (1 << 6)
				| (/* unlocked = */ 1 << 7);
			head_50[2] = /* audio_mode= */ 0 | (/* pa = */ 0 << 4) 
				| (/* chn = */ 0 << 5)
				| (/* sm = */ 0 << 7);
			head_50[4] = /* 16 Bits */0 | (/* 44100 kHz */ 1 << 3)
				| (/* tc = */ 0 << 6) | (/* ef = */ 0 << 7);
			break;
		case 48000:
			head_50[1] = (samplesperframe - 1896) | (1 << 6)
				| (/* unlocked = */ 1 << 7);
			head_50[2] = /* audio_mode= */ 0 | (/* pa = */ 0 << 4) 
				| (/* chn = */ 0 << 5);
			head_50[4] = /* 16 Bits */0 | (/* 48000 kHz */ 0 << 3)
				| (/* tc = */ 0 << 6) | (/* ef = */ 0 << 7);
			break;
		default:
			fprintf(stderr, "Impossible frequency??\n");
			return(-1);
		}
	} else {
		head_50[3] = /* stype = */ 0 | (/* isPAL */ 0 << 5)
			| (/* ml */ 1 << 6) | (/* res */ 1 << 7);
		switch(frequency) {
		case 32000:
			if (channels == 2) {
				head_50[1] = (samplesperframe - 1053) 
					| (1 << 6) | (/* unlocked = */ 1 << 7);
				head_50[2] = /* audio_mode= */ 0 
					| (/* pa = */ 0 << 4) 
					| (/* chn = */ 0 << 5)
					| (/* sm = */ 0 << 7);
				head_50[4] = /* 12 Bits */0 
					| (/* 32000 kHz */ 2 << 3)
					| (/* tc = */ 0 << 6) 
					| (/* ef = */ 0 << 7);
			} else {
				head_50[1] = (samplesperframe - 1053) 
					| (1 << 6) | (/* unlocked = */ 1 << 7);
				head_50[2] = /* audio_mode= */ 0 
					| (/* pa = */ 1 << 4) 
					| (/* chn = */ 1 << 5)
					| (/* sm = */ 0 << 7);
				head_50[4] = /* 12 Bits */1 
					| (/* 32000 kHz */ 2 << 3)
					| (/* tc = */ 0 << 6) 
					| (/* ef = */ 0 << 7);
				bits_per_sample = 12;
			}
			break;
		case 44100:
			head_50[1] = (samplesperframe - 1452) | (1 << 6)
				| (/* unlocked = */ 1 << 7);
			head_50[2] = /* audio_mode= */ 0 | (/* pa = */ 0 << 4) 
				| (/* chn = */ 0 << 5)
				| (/* sm = */ 0 << 7);
			head_50[4] = /* 16 Bits */0 | (/* 44100 kHz */ 1 << 3)
				| (/* tc = */ 0 << 6) | (/* ef = */ 0 << 7);
			break;
		case 48000:
			head_50[1] = (samplesperframe - 1580) | (1 << 6)
				| (/* unlocked = */ 1 << 7);
			head_50[2] = /* audio_mode= */ 0 | (/* pa = */ 0 << 4) 
				| (/* chn = */ 0 << 5);
			head_50[4] = /* 16 Bits */0 | (/* 48000 kHz */ 0 << 3)
				| (/* tc = */ 0 << 6) | (/* ef = */ 0 << 7);
			break;
		default:
			fprintf(stderr, "Impossible frequency??\n");
			return(-1);
		}
	}

	head_51[0] = 0x51; /* FIXME: What's this? */ 
	head_51[1] = 0x33;
	head_51[2] = 0xcf;
	head_51[3] = 0xa0;

	head_52[0] = 0x52;
	head_52[1] = frame_buf[5 * 80 + 48 + 2 * 5 + 1]; /* steal video */
	head_52[2] = frame_buf[5 * 80 + 48 + 2 * 5 + 2]; /* timestamp */
	head_52[3] = frame_buf[5 * 80 + 48 + 2 * 5 + 3]; /* this gets us an */
	head_52[4] = frame_buf[5 * 80 + 48 + 2 * 5 + 4]; /* off by one error!*/
	                                                   
	head_53[0] = 0x53; 
	head_53[1] = frame_buf[5 * 80 + 48 + 3 * 5 + 1];
	head_53[2] = frame_buf[5 * 80 + 48 + 3 * 5 + 2];
	head_53[3] = frame_buf[5 * 80 + 48 + 3 * 5 + 3];
	head_53[4] = frame_buf[5 * 80 + 48 + 3 * 5 + 4];

	for (dif_seg = 0; dif_seg < dif_seg_max; dif_seg++) {
		unsigned char* target= frame_buf + dif_seg * 150 * 80 + 6 * 80;

		unsigned char* p;

		if ((dif_seg & 1) == 0) {
			p = target + 3 * 16 * 80 + 3;
		} else {
			p = target + 3;
		}

		/* Timestamp it! */
		memcpy(p + 0*16*80, head_50, 5);
		memcpy(p + 1*16*80, head_51, 5);
		memcpy(p + 2*16*80, head_52, 5);
		memcpy(p + 3*16*80, head_53, 5);

		if (dif_seg >= dif_seg_max/2) {
			p[2] |= 1;
		}
	}
	return 0;
}



void write_info_blocks(unsigned char* target, int frame, int isPAL,
		       time_t * now, int enable_audio)
{
	int numDIFseq;
	int ds;
	struct tm * now_t = NULL;
	unsigned char* frame_buf = target;

	numDIFseq = isPAL ? 12 : 10;

	if (frame % (isPAL ? 25 : 30) == 0) {
		(*now)++;
	}

	now_t = localtime(now);

	for (ds = 0; ds < numDIFseq; ds++) { 
		write_header_block(target, ds, isPAL);
		target +=   1 * 80;
		write_subcode_blocks(target, ds, frame, now_t, isPAL);
		target +=   2 * 80;
		write_vaux_blocks(target, ds, now_t, isPAL);
		target +=   3 * 80;
		write_video_headers(target, frame, ds);
		write_audio_headers(target, frame, ds);
		target += 144 * 80;
	}
	if (enable_audio) {
		write_full_audio_headers(frame_buf, 2, 48000, isPAL);
	}
}

void encode_picture(unsigned char* readbuf, int isPAL, unsigned char* target)
{
	short img_y[DV_PAL_HEIGHT * DV_WIDTH];
	short img_cr[DV_PAL_HEIGHT * DV_WIDTH / 2];
	short img_cb[DV_PAL_HEIGHT * DV_WIDTH / 2];

	convert_to_yuv(readbuf, isPAL ? 72 : 60, img_y, img_cr, img_cb);
	encode(img_y, img_cr, img_cb, isPAL, target);

}

static int read_ppm(const char* filename, int isPAL, char* readbuf)
{
        int height, width;
        char line[200];
	int binary = 0;
	int counter = 0;
	FILE* f = fopen(filename, "rb");
	if (!f) {
		perror("read_ppm fopen:");
		return -1;
	}

        fgets(line, sizeof(line), f);
        if (feof(f)) {
                return -1;
        }
	if (line[0] == 'P' && line[1] == '3' && line[2] == '\n') {
		binary = 0;
	} else if (line[0] == 'P' && line[1] == '6' && line[2] == '\n') {
		binary = 1;
	} else {
		fprintf(stderr, "read_ppm: unknown file format\n");
		return -1;
	}
        do {
                fgets(line, sizeof(line), f); /* P6 */
        } while ((line[0] == '#'||(line[0] == '\n')) && !feof(f));
        if (sscanf(line, "%d %d\n", &width, &height) != 2) {
                fprintf(stderr, "Bad PPM file!\n");
                return -1;
        }
        if (width != DV_WIDTH || (isPAL && height != DV_PAL_HEIGHT)
	    || (!isPAL && height != DV_NTSC_HEIGHT)) {
                fprintf(stderr, "Invalid picture size! (%d, %d)\n"
                        "Allowed sizes are %d x %d for PAL and "
                        "%d x %d for NTSC\n"
                        "Probably you should try ppmqscale...\n", 
                        width, height, DV_WIDTH, DV_PAL_HEIGHT,
			DV_WIDTH, DV_NTSC_HEIGHT);
                return -1;
        }
	if (binary) {
		fgets(line, sizeof(line), f);   /* 255 */
		fread(readbuf, 1, 3 * DV_WIDTH * height, f);
	} else {
		fgets(line, sizeof(line), f);   /* 255 */
		for (counter = 0; counter < 3 * DV_WIDTH * height; counter++) {
			fgets(line, sizeof(line), f);
			readbuf[counter] = atoi(line);
		}
	}
	fclose(f);	
	return 0;
}


void add_info_blocks(int isPAL, unsigned char* target, time_t* now,
		     int enable_audio)
{
	static int frame_counter = 0;
	write_info_blocks(target, frame_counter, isPAL, now,
			  enable_audio);

	frame_counter++;
}

long get_chunk_size(int isPAL)
{
	unsigned long numbytes = 9 * ((80 - 3 - 5) + (12 * 4 + 2*8) * 15);
	
	return numbytes * (isPAL ? 12 : 10);
}

void insert_data(unsigned char* src, int isPAL, unsigned char* target)
{
	int numDIFseq;
	int ds,i,j,b;

	numDIFseq = isPAL ? 12 : 10;

	for (ds = 0; ds < numDIFseq; ds++) { 
		target += 6 * 80;
		for(i = 0; i < 9; i++) {
			/* first use audio */
			memcpy(target+3+5, src, 80 - 3 - 5);
			target += 80;
			src += 80 - 3 - 5;
			for (j = 1; j < 16; j++) { /* j = 0: audio blocks */
				for (b = 0; b < 4; b ++) {
					memcpy(target 
					       + dv_parse_bit_start[b] + 2,
					       src, 12);
					src += 12;
				}
				for (b = 4; b < 6; b ++) {
					memcpy(target 
					       + dv_parse_bit_start[b] + 2,
					       src, 8);
					src += 8;
				}
				target += 80;
			}
		}
	}
}

void extract_data(unsigned char* src, unsigned char* target)
{
	int numDIFseq;
	int ds,i,j,b;
	int isPAL = src[3] & 0x80;

	numDIFseq = isPAL ? 12 : 10;

	for (ds = 0; ds < numDIFseq; ds++) { 
		src += 6 * 80;
		for(i = 0; i < 9; i++) {
			/* first use audio */
			memcpy(target, src + 3 + 5, 80 - 3 - 5);
			src += 80;
			target += 80 - 3 - 5;
			for (j = 1; j < 16; j++) { /* j = 0: audio blocks */
				for (b = 0; b < 4; b ++) {
					memcpy(target, 
					       src + dv_parse_bit_start[b] + 2,
					       12);
					target += 12;
				}
				for (b = 4; b < 6; b ++) {
					memcpy(target, 
					       src + dv_parse_bit_start[b] + 2,
					       8);
				        target += 8;
				}
				src += 80;
			}
		}
	}
}

void write_short(unsigned char* dst, unsigned short val)
{
	dst[0] = val >> 8;
	dst[1] = val;
}

void write_long(unsigned char* dst, unsigned long val)
{
	dst[0] = val >> 24;
	dst[1] = val >> 16;
	dst[2] = val >> 8;
	dst[3] = val;
}

void write_longlong(unsigned char* dst, unsigned long long val)
{
	dst[0] = val >> 56;
	dst[1] = val >> 48;
	dst[2] = val >> 40;
	dst[3] = val >> 32;
	dst[4] = val >> 24;
	dst[5] = val >> 16;
	dst[6] = val >> 8;
	dst[7] = val;
}

long get_header_size(const char* backup_title)
{
	if (!backup_title) {
		return HEADER_SIZE;
	} else {
		return HEADER_SIZE + strlen(backup_title);
	}
}

void build_header(unsigned char* databuffer, 
		  unsigned long long current_address, 
		  unsigned long got,
		  unsigned long header_size,
		  int eof, const char* backup_title)
{
	unsigned long cksum = adler32(0L, Z_NULL, 0);

	databuffer[0] = 'D';
	databuffer[1] = 'V';
	databuffer[2] = '-';
	databuffer[3] = 'B';

	write_short(databuffer + 4, header_size);
	databuffer[6] = DVBACKUP_VERSION;   /* Version code */
	write_longlong(databuffer + 7, current_address);
	write_long(databuffer+15, got | (eof ? 0x01000000 : 0) );
	write_long(databuffer+19, 0);
	if (backup_title) {
		memcpy(databuffer+23, backup_title, strlen(backup_title));
	} 
	write_long(databuffer+19, adler32(cksum, databuffer,header_size+got));
}

unsigned short read_short(unsigned char* src)
{
	return (src[0] << 8) | src[1];
}

unsigned long read_long(unsigned char* src)
{
	return (src[0] << 24) | (src[1] << 16) | (src[2] << 8)	| src[3];
}

unsigned long long read_longlong(unsigned char* src)
{
	return    ((unsigned long long) src[0] << 56)
		| ((unsigned long long) src[1] << 48)
		| ((unsigned long long) src[2] << 40)
		| ((unsigned long long) src[3] << 32)
		| ((unsigned long long) src[4] << 24)
		| ((unsigned long long) src[5] << 16) 
		| ((unsigned long long) src[6] << 8)	
		| (unsigned long long) src[7];
}


static unsigned long long next_address = 0;

int verify_eof(int verbose)
{
	if (next_address != 0) {
		if (verbose) {
			fprintf(stderr, "\nUnexpected EOF at address %lld\n",
				next_address);
		}
		return -1;
	}
	return 0;
}


int verify_data(unsigned char* databuffer, int verbose)
{
	static unsigned short old_header_size = 0;
	static unsigned char backup_title[65536];
	unsigned long long addr;
	unsigned long got;
	unsigned short header_size;
	int flag_byte;
	unsigned long cksum = read_long(databuffer + 19);
	unsigned long calc_cksum = adler32(0L, Z_NULL, 0);
	int rval = 0;

	write_long(databuffer + 19, 0);

	if (databuffer[0] != 'D' || databuffer[1] != 'V'
	    || databuffer[2] != '-' || databuffer[3] != 'B') {
		if (verbose) {
			fprintf(stderr, "Missing Header!\r");
		}
		return -1;
	}

	header_size = read_short(databuffer + 4);
	if (databuffer[6] != DVBACKUP_VERSION) {
		if (verbose) {
			fprintf(stderr, 
				"\nUnsupported dvbackup version: %d!\n",
				databuffer[6]);
		}
		return -1;
	}

	if (header_size < HEADER_SIZE) {
		if (verbose) {
			fprintf(stderr, "\nInvalid header size: %d!\n",
				header_size);
		}
		return -1;
	}

	addr = read_longlong(databuffer + 7);
	if (addr != next_address) {
		if (verbose) {
			fprintf(stderr, "\nSkipped blocks! (%lld != %lld)\n",
				addr, next_address);
		}
	}

	got = read_long(databuffer + 15);
	flag_byte = got >> 24;
	got &= 0x00ffffff;

	if (next_address == 0) {
		memcpy(backup_title, databuffer + 23, 
		       header_size - HEADER_SIZE);
		backup_title[header_size - HEADER_SIZE] = 0;
		old_header_size = header_size;
		if (verbose) {
			if (got == 0) {
				fprintf(stderr, 
					"Found prefix zone of backup: '%s'\r",
					backup_title);
			} else {
				fprintf(stderr, 
					"\nBeginning new backup: '%s'\n", 
					backup_title);
			}
		}
	} else {
		if (header_size != old_header_size || 
		    memcmp(backup_title, databuffer+ 23,
			   header_size - HEADER_SIZE) != 0) {
			if (verbose) {
				fprintf(stderr, 
					"\nBackup title changed! '%s' -> ",
					backup_title);
			}
			memcpy(backup_title, databuffer + 23, 
			       header_size - HEADER_SIZE);
			backup_title[header_size - HEADER_SIZE] = 0;
			old_header_size = header_size;

			if (verbose) {
				fprintf(stderr, "'%s'\n", backup_title);
			}
		}
		if (verbose) {
			fprintf(stderr, 
				"Processing address %lld of backup '%s'\r",
				addr, backup_title);
		}
	}


	calc_cksum = adler32(calc_cksum, databuffer, header_size + got);
	if (cksum != calc_cksum) {
		if (verbose) {
			fprintf(stderr, "\nChecksum failed: %lu != %lu\n", 
				cksum, calc_cksum);
		}
	}

	next_address = addr + got;

	if (flag_byte & 0x01) {
		if (verbose) {
			fprintf(stderr, "\nReached EOF: Total length: %lld!\n",
				next_address);
		}
		next_address = 0;
		rval = 1;
	}
	return rval;
}

void write_extracted_data(unsigned char* databuffer, int do_recover)
{
	unsigned short header_size = read_short(databuffer + 4);
	unsigned long got = read_long(databuffer + 15) & 0x00ffffff;

	static unsigned short last_header_size = 0;
	static unsigned long last_got = 0;

	if (do_recover) {
		header_size = last_header_size;
		got = last_got;
	}
	fwrite(databuffer + header_size, 1, got, stdout);

	last_header_size = header_size;
	last_got = got;
}

void build_test_data(unsigned long long seed,
		     int isPAL, unsigned char* buffer)
{
	/* FIXME: build simple RNG... */

	if (isPAL) {
		

	} else {

	}

}

void insert_test_data(unsigned long long current_address, 
		      int isPAL, unsigned char* target)
{
	

}

void verify_test_data(unsigned char * databuffer)
{
	/* unsigned long long addr = read_longlong(databuffer + 7); */

	


}


#define OPT_VERSION         0
#define OPT_NTSC            1
#define OPT_DECODE          2
#define OPT_VERIFY          3
#define OPT_TITLE           4
#define OPT_PICTURE         5
#define OPT_VERBOSE         6
#define OPT_PREFIX          7
#define OPT_TEST            8
#define OPT_RECOVER         9
#define OPT_ENABLE_AUDIO    10
#define OPT_AUTOHELP        11
#define NUM_OPTS            12

int main(int argc, char *argv[])
{
	time_t now;
	int do_ntsc = 0;
	int isPAL = 0;
	int decode = 0;
	int verify = 0;
	int verbose = 0;
	int prefix = 0;
	int testmode = -1;
	int recover = 0;
	int enable_audio = 0;

	char* backup_title = NULL;
	char* picture_name = NULL;
	unsigned char piccy[(DV_PAL_HEIGHT+1)* DV_WIDTH * 3];
	unsigned char framebuffer[144000];
	unsigned char databuffer[144000];
	unsigned long chunk_size;
	unsigned long header_size;
	unsigned long long current_address;

	struct poptOption option_table[NUM_OPTS+1]; 
	int rc;             /* return code from popt */
	poptContext optCon; /* context for parsing command-line options */
	option_table[OPT_VERSION] = (struct poptOption) {
		longName: "version", 
		val: 'v',
		descrip: "show dvbackup version number"
	}; /* version */

	option_table[OPT_NTSC] = (struct poptOption) {
		longName:   "ntsc-mode", 
		shortName:  'n', 
		arg:        &do_ntsc,
		descrip:    "generate ntsc frames"
	}; /* ntsc-mode */

	option_table[OPT_DECODE] = (struct poptOption) {
		longName:   "decode", 
		shortName:  'd', 
		arg:        &decode,
		descrip:    "decode file"
	}; /* decode */

	option_table[OPT_VERIFY] = (struct poptOption) {
		longName:   "verify", 
		shortName:  't', 
		arg:        &verify,
		descrip:    "verify data"
	}; /* verify */
	
	option_table[OPT_TITLE] = (struct poptOption) {
		longName:   "set-backup-title", 
		shortName:  'b', 
                argInfo:    POPT_ARG_STRING, 
		arg:        &backup_title,
                argDescrip: "TITLE",
		descrip:    "set title of backup"
	}; /* title */

	option_table[OPT_PICTURE] = (struct poptOption) {
		longName:   "set-picture", 
                argInfo:    POPT_ARG_STRING, 
		arg:        &picture_name,
                argDescrip: "PPM-FILE",
		descrip:    "set picture of backup"
	}; /* picture */

	option_table[OPT_VERBOSE] = (struct poptOption) {
		longName:   "verbose",
		shortName:  'v', 
		arg:        &verbose,
		descrip:    "verbose mode"
	}; /* verbose */

	option_table[OPT_PREFIX] = (struct poptOption) {
		longName:   "prefix", 
		shortName:  'p', 
                argInfo:    POPT_ARG_INT, 
		arg:        &prefix,
                argDescrip: "count",
		descrip:    "prefix data with 'count' empty frames"
	}; /* picture */

	option_table[OPT_TEST] = (struct poptOption) {
		longName:   "test", 
                argInfo:    POPT_ARG_INT, 
		arg:        &testmode,
                argDescrip: "count",
		descrip:    "write count test frames - use -t -d to verify"
	}; /* test */

	option_table[OPT_RECOVER] = (struct poptOption) {
		longName:   "recover",
		shortName:  'r', 
		arg:        &recover,
		descrip:    "recover mode"
	}; /* recover */


	option_table[OPT_PREFIX] = (struct poptOption) {
		longName:   "prefix", 
		shortName:  'p', 
                argInfo:    POPT_ARG_INT, 
		arg:        &prefix,
                argDescrip: "count",
		descrip:    "prefix data with 'count' empty frames"
	}; /* picture */

	option_table[OPT_ENABLE_AUDIO] = (struct poptOption) {
		longName:   "enable-audio",
		arg:        &enable_audio,
		descrip:    "enable full audio headers (read noise mode ;-)"
		            " helps with buggy camcorder firmware."
	}; /* enable audio */

	option_table[OPT_AUTOHELP] = (struct poptOption) {
		argInfo: POPT_ARG_INCLUDE_TABLE,
		arg:     poptHelpOptions,
		descrip: "Help options",
	}; /* autohelp */

	option_table[NUM_OPTS] = (struct poptOption) { 
		NULL, 0, 0, NULL, 0 };

	optCon = poptGetContext(NULL, argc, 
				(const char **)argv, option_table, 0);

	while ((rc = poptGetNextOpt(optCon)) > 0) {
		switch (rc) {
		case 'v':
			fprintf(stderr,"dvbackup: version 0.0.4 "
				"http://dvbackup.sourceforge.net/\n");
			exit(0);
			break;
		default:
			break;
		} /* switch */
	} /* while */

	if (rc < -1) {
		/* an error occurred during option processing */
		fprintf(stderr, "%s: %s\n",
			poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
			poptStrerror(rc));
		exit(-1);
	}

	poptFreeContext(optCon);

	now = time(NULL);
	isPAL = !do_ntsc;

	if (!picture_name || read_ppm(picture_name, isPAL, piccy)) {
		memcpy(piccy, standard_logo.pixel_data, 90 * 72 * 3);
	}

	encode_picture(piccy, isPAL, framebuffer);

	chunk_size = get_chunk_size(isPAL);
	header_size = get_header_size(backup_title);
	current_address = 0;

	if (decode) {
		if (testmode < 0) {
			int eof = 0;
			while (!feof(stdin) && !eof) {
				long got = fread(framebuffer, 
						 1, 120000, stdin);
				int isPAL = framebuffer[3] & 0x80;

				if (isPAL) {
					got += fread(framebuffer + 120000,
						     1, 
						     144000 - 120000, stdin);
				}
				if (got > 0) {
					int rval;
					extract_data(framebuffer, databuffer);
					rval = verify_data(databuffer,verbose);
					if (rval >= 0 || recover) {
						write_extracted_data(
							databuffer, recover);
					}
					if (rval == 1) {
						eof = 1;
					}
				}
			}
			verify_eof(verbose);
		} else {
			while (!feof(stdin)) {
				long got = fread(framebuffer, 
						 1, 120000, stdin);
				int isPAL = framebuffer[3] & 0x80;

				if (isPAL) {
					got += fread(framebuffer + 120000,
						     1, 
						     144000 - 120000, stdin);
				}
				if (got > 0) {
					extract_data(framebuffer, databuffer);
					verify_test_data(databuffer);
				}
			}
		}
	} else if (verify) {
		while (!feof(stdin)) {
			long got = fread(framebuffer, 1, 120000, stdin);
			int isPAL = framebuffer[3] & 0x80;

			if (isPAL) {
				got += fread(framebuffer + 120000,
					     1, 144000 - 120000, stdin);
			}
			if (got > 0) {
				extract_data(framebuffer, databuffer);
				verify_data(databuffer, 1);
			}
		}
		verify_eof(1);
	} else {
		int i;
		for (i = 0; i < prefix; i++) {
			build_header(databuffer, 0, 0,
				     header_size, 0, backup_title);
			add_info_blocks(isPAL, framebuffer, &now, 
					enable_audio);
			insert_data(databuffer, isPAL, framebuffer);
			fwrite(framebuffer, 1,
			       isPAL ? 144000 : 120000, stdout);
		}
		if (testmode < 0) {
			while (!feof(stdin)) {
				long got = fread(databuffer + header_size, 1, 
						 chunk_size - header_size, 
						 stdin);
				int eof = (got < chunk_size - header_size);
				
				build_header(databuffer, current_address, got,
					     header_size, eof, backup_title);

				add_info_blocks(isPAL, framebuffer, &now,
						enable_audio);
				insert_data(databuffer, isPAL, framebuffer);
				fwrite(framebuffer, 1, 
				       isPAL ? 144000 : 120000, stdout);
				
				current_address += got;
			}
		} else {
			for (i = 0; i < testmode; i++) {
				build_header(databuffer, current_address, 
					     chunk_size - header_size,
					     header_size, 0, backup_title);
				
				add_info_blocks(isPAL, framebuffer, &now,
						enable_audio);
				insert_test_data(current_address,
						 isPAL, framebuffer);

				fwrite(framebuffer, 1, 
				       isPAL ? 144000 : 120000, stdout);
				
				current_address += chunk_size - header_size;
			}
		}
	}

	return 0;
}





