/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

/*
	main.c   Cut MP3s framewise, VBR supported!
	MP2 files should work since 2.0.4, as well!
	The code is somewhat ugly and unsafe, sorry! At least it is fast :-)
	(c) Jochen Puchalla 2003-2015  <mail@puchalla-online.de>
*/

#include "cutmp3.h"

/* general buffersize */
#define BUFFER 32000
#define FN_LEN 8191
#define FS_BLOCK_SIZE 4096

/* 10 MB for VBR scanning */
#define SCANAREA 10485759

#ifdef DEBUG
	int debug=0; // 1=volume, 2=operation, 5=ID3tags 7=saving+cutting 9=seeksilence
#endif

	unsigned int silencelength=1000, silvol=1;

	FILE *mp3file, *timefile;
	unsigned char begin[BUFFER];
	long filesize, dataend, inpoint, outpoint;
	unsigned int audiobegin=0;
	unsigned int startmins, endmins;
	real startsecs, endsecs;
	int negstart=1, negend=1; /* start- or endpoint counted from end? */
	unsigned int totalmins; /* minutes of total time */
	real totalsecs; /* seconds of total time (<60) */
	int howlong=1; /* play how many seconds? */
	unsigned int fix_secondbyte=0, fix_sampfreq=0, fix_channelmode=7, fix_mpeg=7, fix_frsize=1;
	real fix_frametime=0;
	int vbr=0, a_b_used=0;
	real avbr, msec; /* average bitrate and one millisecond in relation to avbr */
	long totalframes;
	int usefilename=0;

	char *filename;
	char *forcedname;
	char *userin;
	char *userout;

	int livetime=1;
	unsigned int mute=0, forced_file=0, overwrite=0, forced_prefix=0;
	int copytags=0, nonint=0;
	long silfactor=15; /* silence already longer than wanted by this factor */
	int stdoutwrite=0;
	long framenumber=0, framecount=0;
	real intime=0;   /* time of inpoint */
	real outtime=0;  /* time of outpoint */
	real endtime=0;  /* total time of MP3 in milliseconds */
	int no_tags=0;

	unsigned int card=1;
	signed int genre=-1;

	long *framepos1000 = NULL;	// byte position of every 1000th frame
	unsigned char *id3v2 = NULL;	// space to copy id3v2 tag
	unsigned char id3v1[128];	// space to copy id3v1 tag
	unsigned int minorv=0;		// id3v2 minor version, mostly 3, rarely 4, 2 is obsolete

	char artist[255]="";
	char album[255]="";
	char year[255]="";
	char track[255]="";
	char comment[255]="";
	/* title is used to determine whether we name the file "result000x.mp3"
	or "artist - title.mp3", so leave it empty here! */
	char title[255]="";

	unsigned char ttable[BUFFER];
	FILE *tblfile;
	char tblname[17] = "/tmp/tableXXXXXX";

	FILE *logfile;
	char logname[21] = "/tmp/mpg123logXXXXXX";
	int fdlog=-1;

void exitseq(int foobar)
{
	free(framepos1000);
	free(id3v2);
	remove(logname);

	exit(foobar);  /* now really exit */

	(void)ht;
	(void)htc;
}

void usage(char *text)
{
	printf("\ncutmp3  version %s  \n", VERSION);
	printf("\nUsage:  cutmp3 -i file.mp3 [-a inpoint] [-b outpoint] [-f timetable]");
	printf("\n               [-o outputprefix] [-c] [-C] [-k] [-q]\n");
	printf("\n  cutmp3 -i file.mp3 -a 0:37 -b 3:57  copies valid data from 0:37 to 3:57");
	printf("\n  cutmp3 -i file.mp3 -f timetable     copies valid data described in timetable");
	printf("\n  cutmp3 -i file.mp3 -o song          writes the output to song0001.mp3, song0002.mp3,...");
	printf("\n  cutmp3 -i file.mp3 -k               appends 0001.mp3, etc. to original filename");
	printf("\n  cutmp3 -i file.mp3 -O song.mp3      writes the output to song.mp3");
	printf("\n  cutmp3 -i file.mp3 -O -             writes the output to STDOUT");
	printf("\n  cutmp3 -i file.mp3 -d 2             use second sound card");
	printf("\n  cutmp3 -i file.mp3 -s 0             no maximum silence length");
	printf("\n  cutmp3 -I file.mp3 [-F]             prints file information [in raw mode]\n\n");
#ifdef DEBUG
	printf("  cutmp3 -D <n>                       Show debug prints:  0:none (default)\n");
	printf("                                      1:volume, 2:operation, 5:ID3tags, 7:saving+cutting, 9:seeksilence\n\n");
#endif
	if (text && text[0]!='\0') printf("%s\n\n",text);
}

void help(void)
{
	printf("\n\ncutmp3  version %s  \n", VERSION);
	printf("\n        Seek with 1234567890,.\n");
	printf("        Mark beginning with 'a', go there with 'A'\n");
	printf("        Mark       end with 'b', go there with 'B'\n");
	printf("        Save selection with 's'.\n");
	printf("        Save selection with ID3-tag with 't'.\n\n");
	printf("        Seek to next ID3-tag with 'T'\n");
	printf("        Seek to end of next silence with 'p'\n");
	printf("        Seek to beginning of next silence with 'P'\n");
	printf("        increase volume of silence with '+'.\n");
	printf("        decrease volume of silence with '-'.\n");
	printf("        increase length of silence with 'm'.\n");
	printf("        decrease length of silence with 'n'.\n");
	printf("        increase playback length with 'M'.\n");
	printf("        decrease playback length with 'N'.\n");
	printf("        'o' turns on overwriting for next time.\n");
	printf("        'i' shows file info.\n\n");
	printf("        Replay with 'r'.\n");
	printf("        Toggle playing up to / from location with 'R'.\n");
	printf("        Toggle sound on/off with '#'.\n");
	printf("        Toggle live time on/off with 'l'.\n");
	printf("        Write configuration file with 'S'.\n\n");
	printf("        Press 'q' to quit.\n\n");
	printf("        HINTS: 'w' is a shortcut for 'bsa'.\n");
	printf("               'W' is a shortcut for 'bta'.\n");
	return;
}


/* Which MPEG-Type ? */
/* Type is in bits nr. 11&12 */
unsigned int mpeg(unsigned char mpegnumber)
{
	switch (mpegnumber & (0x3 << 3))
	{
		case (0<<3): return 3;
		case (3<<3): return 1;
		case (2<<3): return 2;
	}
	DBGPRINT(2, "%s(0x%02hhX) Warning: reserved value\n", __func__, mpegnumber);
	return 0;
}


unsigned int crc(unsigned char crcbyte)
{
	if (crcbyte & 0x1)
		return 0;
	else
		return 1;
}


unsigned int sampfreq(unsigned char secondbyte, unsigned char thirdbyte)
{
	switch (mpeg(secondbyte))
	{
		case 1:
			switch(thirdbyte & (0x3<<2))
			{
				case (0<<2): return 44100;
				case (1<<2): return 48000;
				case (2<<2): return 32000;
			}
		case 2:
			switch(thirdbyte & (0x3<<2))
			{
				case (0<<2): return 22050;
				case (1<<2): return 24000;
				case (2<<2): return 16000;
			}
		case 3: // MPEG2.5
			switch(thirdbyte & (0x3<<2))
			{
				case (0<<2): return 11025;
				case (1<<2): return 12000;
				case (2<<2): return 8000;
			}
	}

	/* In case of an error, sampfreq=1 is returned */
	DBGPRINT(2, "%s(0x%02hhX, 0x%02hhX) Warning: reserved value\n", __func__, secondbyte, thirdbyte);
	return 1;
}


unsigned int channelmode(unsigned char fourthbyte)
{
//	printf("  channelmode()");

	return fourthbyte>>6;
/* result: 0=Stereo
           1=Joint-Stereo
           2=Dual-Channel
           3=Mono */
}


unsigned int channels(unsigned char fourthbyte)
{
	if (channelmode(fourthbyte)==3) return 1;
	else return 2;
}


#define INVALID_HDR { 0x00, 0x08, 0xFC, 0x02 }

inline unsigned int is_hdr(unsigned char *buf)
{
    /* http://www.mp3-tech.org/programmer/frame_header.html */
	if (buf[0] == 255
	&& (buf[1] & (0x7<<5)) == (7<<5)
	&& (buf[1] & (0x3<<1)) != 0
#if 0	/* Strict sample rate check */
	&& sampfreq(buf[1], buf[2]) == fix_sampfreq
#else
	&& (buf[1] & (0x3<<3)) != (1<<3)
	&& (buf[2] & (0x3<<2)) != (3<<2)
#endif
	&& (buf[2] & (0xF<<4)) != (0xF<<4)
	&& (buf[3] & 0x3) != 2) return 1;
		else return 0;
}


unsigned int bitrate(unsigned char secondbyte, unsigned char thirdbyte)
{
	unsigned char bitratenumber;

	bitratenumber = thirdbyte >> 4;

	switch( layer(secondbyte) ) {

	case 3: /* MPEG Layer3 */
		switch (mpeg(secondbyte)) {
		case 1: /* MPEG1 */
			switch (bitratenumber) {
				case 1: return 32;
				case 2: return 40;
				case 3: return 48;
				case 4: return 56;
				case 5: return 64;
				case 6: return 80;
				case 7: return 96;
				case 8: return 112;
				case 9: return 128;
				case 10: return 160;
				case 11: return 192;
				case 12: return 224;
				case 13: return 256;
				case 14: return 320;
			}
			break;
		case 2: /* MPEG2 */
		case 3: /* MPEG2.5 */
			switch (bitratenumber) {
				case 1: return 8;
				case 2: return 16;
				case 3: return 24;
				case 4: return 32;
				case 5: return 40;
				case 6: return 48;
				case 7: return 56;
				case 8: return 64;
				case 9: return 80;
				case 10: return 96;
				case 11: return 112;
				case 12: return 128;
				case 13: return 144;
				case 14: return 160;
			}
			break;
		}
		break;

	case 2: /* MPEG Layer2 */
		switch (mpeg(secondbyte)) {
		case 1: /* MPEG1 */
			switch (bitratenumber) {
				case 1: return 32;
				case 2: return 48;
				case 3: return 56;
				case 4: return 64;
				case 5: return 80;
				case 6: return 96;
				case 7: return 112;
				case 8: return 128;
				case 9: return 160;
				case 10: return 192;
				case 11: return 224;
				case 12: return 256;
				case 13: return 320;
				case 14: return 384;
			}
			break;
		case 2: /* MPEG2 */
		case 3: /* MPEG2.5 */
			switch (bitratenumber) {
				case 1: return 8;
				case 2: return 16;
				case 3: return 24;
				case 4: return 32;
				case 5: return 40;
				case 6: return 48;
				case 7: return 56;
				case 8: return 64;
				case 9: return 80;
				case 10: return 96;
				case 11: return 112;
				case 12: return 128;
				case 13: return 144;
				case 14: return 160;
			}
			break;
		}
		break;

	case 1: /* MPEG Layer1 */
		if (mpeg(secondbyte)==1) /* MPEG1 */
			switch (bitratenumber)
			{
				case 1: return 32;
				case 2: return 64;
				case 3: return 96;
				case 4: return 128;
				case 5: return 160;
				case 6: return 192;
				case 7: return 224;
				case 8: return 256;
				case 9: return 288;
				case 10: return 320;
				case 11: return 352;
				case 12: return 384;
				case 13: return 416;
				case 14: return 448;
			}
		break;
	}

	/* In case of an error, bitrate=1 is returned */
	DBGPRINT(2, "%s(0x%02hhX, 0x%02hhX) Warning: reserved value\n", __func__, secondbyte, thirdbyte);
	return 1;
}


unsigned int layer(unsigned char secondbyte)
{
	switch (secondbyte & (0x3<<1))
	{
		case (1<<1): return 3;
		case (2<<1): return 2;
// 		case (2<<1): {printf("\nFile is an MP2, not am MP3.\n"); exitseq(99);}
		case (3<<1): return 1;
	}

	/* In case of an error, layer=0 is returned */
	DBGPRINT(2, "%s(0x%02hhX) Warning: reserved value\n", __func__, secondbyte);
	return 0;
}


unsigned int paddingbit(unsigned char thirdbyte)
{
	if (thirdbyte & 0x1<<1)
		return 1;
	else
		return 0;
}


unsigned int frame_sz(unsigned char *buf)
{
	unsigned int br, sf, mfactor;

	if ((br =  bitrate(buf[1], buf[2])) == 1) return 1;
	if ((sf = sampfreq(buf[1], buf[2])) == 1) return 1;

	if (mpeg(buf[1]) == 1)
		mfactor = 2;
	else
		mfactor = 1;

	return (mfactor * 72000 * br / sf) + paddingbit(buf[2]);
}


unsigned int framesize(unsigned char secondbyte,unsigned char thirdbyte,unsigned char fourthbyte)
{
	int br=bitrate(secondbyte,thirdbyte);
	int mfactor=2;
	int sf=sampfreq(secondbyte,thirdbyte);

	if (mpeg(secondbyte)!=1) mfactor=1;
	if ((br!=1) && (sf!=1)) return (mfactor*72000*br/sf)+paddingbit(thirdbyte);
	else return 1;
}


/* find next frame at seekpos */
long nextframe(long seekpos)
{
	unsigned char hdrbuf[4] = INVALID_HDR;
	unsigned int framesz;

	if (seekpos >= filesize) return filesize;

	fseek(mp3file, seekpos, SEEK_SET);

	/* if seekpos is a header and framesize is valid, jump to next header via framesize, else move on one byte */
	if(fread(hdrbuf, 1, 4, mp3file) < 4) return seekpos;
	if (is_hdr(hdrbuf))
		seekpos = seekpos + frame_sz(hdrbuf);
	else
		seekpos = seekpos + 1;

	/* find next possible header, start right at seekpos */
	if (seekpos >= filesize) return filesize;
	fseek(mp3file, seekpos, SEEK_SET);

	while(seekpos < filesize)
	{
		hdrbuf[0] = fgetc(mp3file);  /* get next byte */
		if (hdrbuf[0] == 255)
		{
			if ((framesz = fread(&hdrbuf[1], 1, 3, mp3file)) == 3 && is_hdr(hdrbuf))
				break;
			else
				fseek(mp3file, -1l * framesz, SEEK_CUR);
		}
		seekpos++;
	}

	/* also check next frame if possible */
	if (seekpos + (framesz = frame_sz(hdrbuf)) + 4 < filesize)
	{
		fseek(mp3file, seekpos + framesz, SEEK_SET);
		if(fread(hdrbuf, 1, 4, mp3file) < 4) return seekpos;
		if (!is_hdr(hdrbuf))
			seekpos = nextframe(seekpos + 1);
	}

	// TODO do we guarantee file's at seekpos? If so, why not at the beginning too
//	fseek(mp3file, seekpos, SEEK_SET);
	return seekpos;
}


/* find previous frame at seekpos */
long prevframe(long seekpos)
{
	unsigned char hdrbuf[4] = INVALID_HDR;
	long nextheaderpos;

	if (seekpos <= audiobegin + 1) return audiobegin;

	/* find next header, start right at seekpos */
//	nextheaderpos = nextframe(seekpos);
	fseek(mp3file, seekpos, SEEK_SET);
	if(fread(hdrbuf, 1, 4, mp3file) < 4) return seekpos;
	if (!is_hdr(hdrbuf))
		nextheaderpos = nextframe(seekpos + 1);
	else
		nextheaderpos = seekpos;

	/* rewind to previous header and check if nextheader is more than framesize bytes further */
	while(--seekpos > audiobegin)		// if seekpos == audiobegin we'll just return that
	{
		fseek(mp3file, seekpos, SEEK_SET);
		hdrbuf[0] = fgetc(mp3file);
		if (hdrbuf[0] == 255)
		{
			if (fread(&hdrbuf[1], 1, 3, mp3file) == 3 && is_hdr(hdrbuf) \
			 && seekpos + frame_sz(hdrbuf) <= nextheaderpos)     /* previous frame found */
				break;
		}
	}

	// TODO do we guarantee file's at seekpos? If so, why not at the beginning too
//	fseek(mp3file, seekpos, SEEK_SET);
	return seekpos;
}


void zaptitle (void)
{
	title[0]='\0';
}


/*
time of frame in milliseconds is: framesize*8/bitrate == 1152000/sampfreq !
because bitrate=128 means 128000bits/s
*/


/* scan whole file, can take seconds */
void scanframes()
{
	long pos;
	unsigned char hdrbuf[4] = INVALID_HDR;
	unsigned int count, ii;

	framepos1000[0] = audiobegin;
	pos = nextframe(audiobegin);
	count = 1, ii = 1;

	while (pos < filesize) {
		fseek(mp3file, pos, SEEK_SET);
		if (fread(hdrbuf, 1, 4, mp3file) < 4 || !is_hdr(hdrbuf))
			break;
		pos += frame_sz(hdrbuf);
		count++;
		if (!(count % 1000))
			framepos1000[ii++] = pos;
	}

	if (pos < filesize) {
		fseek(mp3file, -4l, SEEK_CUR);
		if (fgetc(mp3file)!='T' || fgetc(mp3file)!='A' || fgetc(mp3file)!='G' ) {
			printf("\n%s(): rerunning scan with paranoia due to error at %ld\n", __func__, pos);
			count = 0, ii = 1;

			while (pos < filesize)
			{
				pos = nextframe(pos);
				count++;
				if (!(count % 1000))
					framepos1000[ii++] = pos;
			}
		}
	}

	totalframes = count;
	framepos1000[ii] = filesize + 1;	// TODO why?
	return;
}


/* get frame number from position, quite slow */
long pos2frame (long seekpos)
{
	long pos;
	long count=0,i=0;

	if (seekpos<=audiobegin) return 0;
	if (seekpos>dataend) return totalframes;

	pos=audiobegin;

	while (pos>0 && pos<seekpos) // go forward until too far
	{
		i++;
		pos=framepos1000[i];
	}

	while (pos>=seekpos) // go back to one before
	{
		i--;
		pos=framepos1000[i];
	}

	count=1000*i; // we know at how many thousands of frames we are

	while (pos<seekpos) // find exact frame
	{
		pos=nextframe(pos);
// 		printf(".");
		count++;
	}

	return count;
}


/* get time from position, quite slow */
real pos2time (long seekpos)
{
	long framenr;

	framenr=pos2frame(seekpos);
	return (real)framenr*fix_frametime;
}


/* get whole minutes from position, quite slow */
unsigned int pos2mins (long seekpos)
{
	long framenr;

	framenr=pos2frame(seekpos);
	return (real)framenr*fix_frametime/60000;
}


/* returns seconds without minutes to display mm:ss.ss, quite slow */
real pos2secs (long seekpos)
{
	long framenr;
	real msecs,temp;
	int mins;

	framenr=pos2frame(seekpos);
	msecs=(real)framenr*fix_frametime;
	mins=msecs/60000;
	temp=(msecs/1000-(mins*60));
	if (temp>59.99) return 59.99; /* 59.997 would be displayed as 60:00 without this */
	return temp;
}


/* time in milliseconds from frame number, fast! */
real frame2time (long framenr)
{
	if (framenr>totalframes) framenr=totalframes;

	return (real)framenr*fix_frametime;
}


/* whole minutes from frame number, fast! */
unsigned int frame2mins (long framenr)
{
	return frame2time(framenr)/(real)60000;
}


/* seconds over whole minutes from frame number, fast! */
real frame2secs (long framenr)
{
	real temp;

	temp=frame2time(framenr)/1000 - frame2mins(framenr)*60;
	if (temp>59.99) return 59.99; /* 59.997 would be displayed as 60:00 without this */
	return temp;
}


/* position from frame number, quite fast */
long frame2pos (long framenr)
{
	long pos,i;
	int tsd;

	tsd=framenr/1000; /* start search at 1000s frames */
// 	printf(" tsd=%i \n",tsd);
	pos=framepos1000[tsd];
	for (i=0;i<framenr-tsd*1000;i++) pos=nextframe(pos);

	return pos;
}


unsigned long fforward (long seekpos,long skiptime)
{
	long skipframes=0;

	if (frame2time(framenumber)+skiptime>=endtime)
	{
		framenumber=totalframes;
		return filesize;
	}

// 	go forward skiptime/fix_frametime +1 headers
	skipframes=(real)skiptime/(real)fix_frametime+(real)1;
	framenumber=framenumber+skipframes;

	return frame2pos(framenumber);
}


unsigned long frewind (long seekpos,long rewtime)
{
	long rewframes=0;

	if (rewtime>=frame2time(framenumber))
	{
		framenumber=0;
		return audiobegin;
	}

// 	go back rewtime/fix_frametime +1 headers
	rewframes=(real)rewtime/(real)fix_frametime+(real)1;
	framenumber=framenumber-rewframes;

	return frame2pos(framenumber);
}


void copyfile(FILE *outfile, FILE *infile, size_t nbyte)
{
	char *buf = malloc(FS_BLOCK_SIZE);
	ssize_t read_sz, write_sz;

	while(nbyte) {
		read_sz = fread(buf, 1, nbyte < FS_BLOCK_SIZE ? nbyte : FS_BLOCK_SIZE, infile);
		if(read_sz < 0) {perror("copyfile read()"); exitseq(10);}
		nbyte -= read_sz;
		while(read_sz>0) {
			write_sz = fwrite(buf, 1, read_sz, outfile);
			if(write_sz < 0) {perror("copyfile write()"); exitseq(10);}
			read_sz -= write_sz;
		}
	}
	free(buf);
}


/* Determination of average VBR bitrate */
real avbitrate(void)
{
	real frmcount=0, bitratesum=0;
	long pos=audiobegin;
	unsigned char hdrbuf[4] = INVALID_HDR;
	real thisfrsize=1, thisbitrate=0;

//	printf("  avbitrate");
	fseek(mp3file, audiobegin, SEEK_SET);

	while(pos < filesize-4 && pos < SCANAREA)
	{
//		printf("\navbitrate in loop, pos=%ld, filesize=%ld\n",pos,filesize);
		if (pos>=filesize-1 || pos>SCANAREA) break;
		hdrbuf[0]=fgetc(mp3file);
//		printf("\na=%u\n",a);
		if (hdrbuf[0]==255)
		{
			if(fread(&hdrbuf[1], 1, 3, mp3file) < 3) break;
			thisfrsize = frame_sz(hdrbuf);                                     /* cache framesize(b,c,d) */
//			if (isheader(b,c,d)==1 && thisfrsize!=1 && sampfreq(b,c)!=1)     /* next frame found */
			if (thisfrsize!=1 && sampfreq(hdrbuf[1], hdrbuf[2])==fix_sampfreq)                /* next frame found */
			{
				pos += thisfrsize;							/* adjust pos to actual SEEK_SET */
				fseek(mp3file, pos, SEEK_SET);				/* skip framesize bytes */
				frmcount++;									/* one more frame */
				thisbitrate=bitrate(hdrbuf[1], hdrbuf[2]);	/* cache bitrate(b,c,d) */
				bitratesum += thisbitrate;
//				printf("\nbitratesum:%ld frmcount:%ld ",bitratesum,frmcount);
				if (bitratesum/frmcount!=thisbitrate && vbr==0)    /* VBR check */
				{
					vbr=1;     /* if file is VBR */
//					printf("\nnoVBR: bitrate=%.0f at %ld Bytes \n",thisbitrate,pos);
				}
			}
			else fseek(mp3file, ++pos, SEEK_SET);    /* rewind to next byte for if (a==255) */
		}
	}

	if (bitratesum==0 || frmcount==0) return 0;
	return bitratesum/frmcount;
}


/* This function shall return the volume (energy level)
   of one frame. Taken from mpglib and mpcut. */
unsigned int volume(long playpos)
{
	unsigned char a,b,c,d;
	unsigned int level, framenum=4;
	unsigned int i, frsize;
	long pos;

	FILE *volfile;
	static char volname[] = "/dev/shm/volXXXXXX";
	int fd=-1;

	DBGPRINT(1, "\nvolume(): before prevframe(playpos)");

	for (i=0; i<framenum ; i++) playpos=prevframe(playpos);  /* step back framenum frames */

	DBGPRINT(1, "\nvolume(): after %ux prevframe %ld \n",framenum,playpos);

	if ((fd = mkstemp(volname)) == -1 || (volfile = fdopen(fd, "w+b")) == NULL)
	{
		DBGPRINT(1, "\nvolume(): /dev/shm not writable\n");
		strcpy(volname, "/tmp/volXXXXXX");
		if ((fd = mkstemp(volname)) == -1 || (volfile = fdopen(fd, "w+b")) == NULL)
		{
			/* open temp.vol.file read-writable zero length */
			perror("\ncutmp3: failed writing temporary volume mp3 in /tmp");
			exitseq(6);
		}
	}

	frsize=1;
	pos=playpos-1;

	/* Copy 2*framenum valid frames to volfile */
	for (framecount=0; framecount < 2*framenum ; framecount++)
	{
		while (frsize==1)
		{
			/* seek next frame */
			pos=nextframe(pos);
			DBGPRINT(1, "\nvolume(): pos=%ld\n",pos);
			fseek(mp3file, pos, SEEK_SET);
			a=fgetc(mp3file);
			if(a==255) {
				b=fgetc(mp3file);
				c=fgetc(mp3file);
				d=fgetc(mp3file);
				frsize=framesize(b,c,d);
			}
		}
		DBGPRINT(1, "\nvolume(): frsize=%u\n",frsize);
		/* write this frame */
		fseek(mp3file, pos, SEEK_SET);
		for (i=0 ; i<frsize && pos+frsize<filesize ; i++)
		{
			fputc(fgetc(mp3file),volfile);
			pos++;
		}
	}

	/* get energy level */
	fseek(volfile, 0, SEEK_SET);
	DBGPRINT(1, "volume(): before InitMP3() \n");
	InitMP3();
	DBGPRINT(1, "volume(): before get_framelevel \n");
	level = get_framelevel(volfile,framenum);
	DBGPRINT(1, "volume(): after get_framelevel \n");
	if (level==MP3_ERR) level=65535;
//	if (level<0) level=(-1)*level;
	if (level>65535) level=65535;
	ExitMP3();
	fclose(volfile);
	remove(volname);
	return level;
}


/* This function returns the position of the end of the next silence */
long seeksilence(long seekpos)
{
	unsigned int vol;
	long silend, silfirstfound;

//	printf("  seeksilence()");

	seekpos=fforward(seekpos,silencelength); /* move on */
	if (seekpos>=dataend) /* dataend reached? */
	{
		printf("  seeking at        ");
		framenumber=totalframes;
		printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber));
		return filesize;
	}

	printf("  seeking at        ");
	printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber));

	while (seekpos<dataend)
	{
		while ((vol=volume(seekpos)) >= silvol)
		{
			/* look for possible silence every silencelength milliseconds: */
			DBGPRINT(9, "\ncandidate seek at %ld   volume is %u\n",seekpos,vol);
			printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber));
			seekpos=fforward(seekpos,silencelength);
 			framenumber=pos2frame(seekpos);
			if (seekpos>=dataend)
			{
				return filesize; /* dataend reached? */
			}
		}

		/* we found a candidate! */
		DBGPRINT(9, "\ncandidate found at %ld   volume is %u.\n",seekpos,vol);
// 		printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber)); unnötig;
		silfirstfound=seekpos; /* remember first found silent frame */
		framenumber=pos2frame(seekpos);

		/* forward to last silent frame */
		while ((vol=volume(seekpos)) < silvol && seekpos<dataend)
		{
			seekpos=nextframe(seekpos); framenumber++;
			printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber));
			DBGPRINT(9, "\nforward seek1 at %ld   volume is %u\n",seekpos,vol);
		}
		silend=seekpos;
		DBGPRINT(9, "\nsilend at %ld   \n",silend);

		/* quick check if silence is already long enough */
		if (pos2time(silend)-pos2time(silfirstfound) >= silencelength)
		{
			framenumber--;
			return frame2pos(framenumber);
		}

		/* rewind silencelength ms */
		seekpos=frewind(seekpos,silencelength);
		framenumber=pos2frame(seekpos);
		DBGPRINT(9, "\nrewind to %ld   \n",seekpos);

		/* forward to last silent frame again and see if position is silend */
		while ((vol=volume(seekpos)) < silvol && seekpos<dataend)
		{
			seekpos=nextframe(seekpos); framenumber++;
			DBGPRINT(9, "\nforward seek2 at %ld   volume is %u\n",seekpos,vol);
		}
		DBGPRINT(9, "\nafter seek2 at %ld   volume is %u\n",seekpos,vol);
		if (seekpos==silend) /* we found a silence */
		{
			framenumber--;
			return frame2pos(framenumber);
		}
		else
		{
			framenumber=pos2frame(silend);
			seekpos=fforward(silend,silencelength);
			DBGPRINT(9, "\nfforward to %ld   \n",seekpos);
			DBGPRINT(9, "\nsilend at %ld   \n",silend);
		}
	}
	framenumber=totalframes;
	return filesize; /* EOF */
}


/* This function returns the position of the beginning of the next silence */
long seeksilstart(long seekpos)
{
	unsigned int vol;
	long silstart, silend, silfirstfound;
	long silfirstfoundframe;

	seekpos=fforward(seekpos,silencelength); /* move on */
	if (seekpos>=dataend) /* dataend reached? */
	{
		printf("  seeking at        ");
		framenumber=totalframes;
		printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber));
		return filesize;
	}

	printf("  seeking at        ");
	printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber));

	while (seekpos<dataend)
	{
		while ((vol=volume(seekpos)) >= silvol)
		{
			/* look for possible silence every silencelength milliseconds: */
			DBGPRINT(9, "\ncandidate seek at %ld   volume is %u\n",seekpos,vol);
			printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber));
			seekpos=fforward(seekpos,silencelength);
 			framenumber=pos2frame(seekpos);
			if (seekpos>=dataend)
			{
				return filesize; /* dataend reached? */
			}
		}

		/* we found a candidate! */
		DBGPRINT(9, "\ncandidate found at %ld   volume is %u.\n",seekpos,vol);
// 		printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber)); unnötig;
		silfirstfound=seekpos; /* remember first found silent frame */
		framenumber=pos2frame(seekpos);
		silfirstfoundframe=framenumber; /* remember first found silent frame number */

		/* forward to last silent frame */
		while ((vol=volume(seekpos)) < silvol && seekpos<dataend)
		{
			seekpos=nextframe(seekpos); framenumber++;
			DBGPRINT(9, "\nforward seek at %ld   volume is %u\n",seekpos,vol);
			/* quick check if silence is already long enough */
			if (framenumber-silfirstfoundframe >= silencelength/fix_frametime)
			{
				framenumber=silfirstfoundframe+1;
				return frame2pos(framenumber);
			}
		}
		silend=seekpos;
		DBGPRINT(9, "\nsilend at %ld   \n",silend);

		/* rewind to first silent frame from first found silent frame */
		seekpos=silfirstfound;
		framenumber=pos2frame(silfirstfound);
		DBGPRINT(9, "\nrewind to %ld   \n",seekpos);
		while ((vol=volume(seekpos)) < silvol && seekpos>audiobegin)
		{
			seekpos=prevframe(seekpos); framenumber--;
			printf("\b\b\b\b\b\b\b%4u:%02.0f",frame2mins(framenumber),frame2secs(framenumber));
			DBGPRINT(9, "\nbackward seek at %ld   volume is %u\n",seekpos,vol);
		}
		silstart=seekpos;
		DBGPRINT(9, "\nsilend at %ld   \n",silend);

		if (pos2time(silend)-pos2time(silstart) >= silencelength)
		{
			DBGPRINT(9, "\nseeksilstart(): silend=%ld silstart=%ld\n",silend,silstart);
			framenumber++;
			return frame2pos(framenumber);
		}
		/* silence was not long enough! */

		/* Do not find the same silence again! Jump to end of silence and seek on */
		seekpos=nextframe(silend);
		framenumber=pos2frame(seekpos);
		DBGPRINT(9, "\nseekpos=%ld dataend=%ld\n",seekpos,dataend);
	}
	framenumber=totalframes;
	return filesize; /* EOF */
}


/* This function prints the reached ID3 V2 TAG */
void print_id3v2(long showpos)
{
	if (importid3v2(showpos)==0) return;
	printf("\n\nFound ID3 V2 tag of this next track:");
	printf("\nTitle:   %s",title);
	printf("\nArtist:  %s",artist);
	printf("\nAlbum:   %s",album);
	printf("\nYear:    %s",year);
	printf("\nComment: %s\n",comment);
	return;
}


/* This function prints the reached ID3 V1 TAG */
void print_id3v1(long seekpos)
{
	if (importid3v1(seekpos)==0) return;

	printf("\nID3 V1 tag:");
	printf("\nTitle:   %s",title);
	printf("\nArtist:  %s",artist);
	printf("\nAlbum:   %s",album);
	printf("\nYear:    %s",year);
	printf("\nComment: %s\n",comment);
	return;
}

/* This function prints the reached ID3 V1 TAG,
   argument is next byte after tag */
void showtag(long seekpos)
{
	if (importid3v1(seekpos)==0) return;

	printf("\n\nReached end of this track:   ");
	printf("\nTitle:   %s",title);
	printf("\nArtist:  %s",artist);
	printf("\nAlbum:   %s",album);
	printf("\nYear:    %s",year);
	printf("\nComment: %s",comment);
	return;
}


/*
ID3 V1 Implementation

   The Information is stored in the last 128 bytes of an MP3. The Tag
   has got the following fields, and the offsets given here, are from
   0-127.

     Field      Length    Offsets
     Tag        3           0-2
     Songname   30          3-32
     Artist     30         33-62
     Album      30         63-92
     Year       4          93-96
     Comment    30         97-126
     Genre      1           127

   The string-fields contain ASCII-data, coded in ISO-Latin 1 codepage.
   Strings which are smaller than the field length are padded with zero-
   bytes.

     Tag: The tag is valid if this field contains the string "TAG". This
        has to be uppercase!

     Songname: This field contains the title of the MP3 (string as
        above).

     Artist: This field contains the artist of the MP3 (string as above).

     Album: this field contains the album where the MP3 comes from
        (string as above).

     Year: this field contains the year when this song has originally
        been released (string as above).

     Comment: this field contains a comment for the MP3 (string as
        above). In version 1.1 Offset 126 is binarily used for the track number.

     Genre: this byte contains the offset of a genre in a predefined
        list the byte is treated as an unsigned byte. The offset is
        starting from 0.
*/


/*
Textual frames are marked with an encoding byte.[7]

$00 – ISO-8859-1 (LATIN-1, Identical to ASCII for values smaller than 0x80).
$01 – UCS-2 (UTF-16 encoded Unicode with BOM), in ID3v2.2 and ID3v2.3.
$02 – UTF-16BE encoded Unicode without BOM, in ID3v2.4.
$03 – UTF-8 encoded Unicode, in ID3v2.4.
(BOM= byte order mark: 255 254 or 254 255)
*/


/* This function gets data from ID3 V2 and returns its length,
   argument is start of tag */
long importid3v2(long seekpos)
{
	long length=0, pos=0, i=0, j=0;
	int size=0, hasexthdr=0, exthdrsize=0, footerlength=0;
	unsigned char a,b,c,d;
	unsigned int encoding=0;
	int ssf=1; // synch safe factor

	/* delete old stuff in case they are not overwritten: */
	title[0]='\0';
	year[0]='\0';
	album[0]='\0';
	artist[0]='\0';
	comment[0]='\0';

	DBGPRINT(5, "\n *debug* importid3v2 at %ld\n",seekpos);

	fseek(mp3file, seekpos, SEEK_SET);
	length=skipid3v2(seekpos)-seekpos; /* skipid3v2 checks if there is ID3 V2 and allocates id3v2[] buffer */
	DBGPRINT(5, " *debug* id3v2 length %ld \n",length);
	if (length==0) return 0; /* is not ID3 V2 */

	/* copy whole tag to buffer */
	fseek(mp3file, seekpos, SEEK_SET);
	if(fread(id3v2, 1, length, mp3file) < length) return 0;

	minorv=id3v2[3]; if (minorv==3) ssf=2;
	DBGPRINT(5, "\n *debug* found ID3 version 2.%u.%u\n", minorv, id3v2[4]);
	a=b=id3v2[5]; /* flags byte */
	DBGPRINT(5, " *debug* flags byte=%u ",a);
/*
	if (minorv>=3) hasexthdr=(a<<1)>>7;			// has extended header?
	if (minorv==4) footerlength=((b<<3)>>7)*10;		// has footer?
*/
	if (minorv>=3 && (a & 0x40)) hasexthdr=1;		// has extended header?
	if (minorv==4 && (b & 0x10)) footerlength=10; 	// has footer?
											// skip 4 bytes tag length info
	if (hasexthdr)
	{
		/* skip ext header */
		a=id3v2[10];
		b=id3v2[11];
		c=id3v2[12];
		d=id3v2[13];
		pos=pos+4;
		exthdrsize=((a * 128 + b) * 128 + c) *128 + d;
//		exthdrsize=a*128*128*128+b*128*128+c*128+d;
		DBGPRINT(5, "\n *debug* hasexthdr of size=%i\n",exthdrsize);
	}
	pos=10+exthdrsize;

	if (minorv==3 || minorv==4) /* ID3V2.3 & ID3V2.4 */
	{
		while (pos<length)
		{
			DBGPRINT(5, "\n *debug* loopstart pos=%ld length=%ld footerlength=%i\n",pos,length,footerlength);
			if (pos+4>=length-footerlength) break;

			while ((id3v2[pos]<48 || id3v2[pos]>90) && pos<length)
			{
				pos++;
			}
			DBGPRINT(5, " *debug* after alphanum pos=%ld\n",pos);

			a=id3v2[pos];
			b=id3v2[pos+1];
			c=id3v2[pos+2];
			d=id3v2[pos+3];
			pos=pos+4;

			DBGPRINT(5, "\n *debug* loopstart: %c%c%c%c\n",a,b,c,d);
			DBGPRINT(5, "\n *debug* loopstart: a=%u b=%u c=%u d=%u\n",a,b,c,d);


			if (a=='T' && b=='I' && c=='T' && d=='2') /* read title */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				d=id3v2[pos+3];
				pos=pos+6; /* also skip 2 flag bytes */
				size=a*128*ssf*128*ssf*128*ssf+b*128*ssf*128*ssf+c*128*ssf+d;
				encoding=id3v2[pos]; pos++; size=size-1;
				if (encoding==1){pos=pos+2; size=size-2;}
				DBGPRINT(5, " *debug* frame size %d pos=%ld\n",size,pos);
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					title[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				title[i]='\0';
				pos=pos+size;
				DBGPRINT(5, " *debug* Title: %s\n",title);
			}
			else if (a=='T' && b=='P' && c=='E' && d=='1') /* read artist */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				d=id3v2[pos+3];
				pos=pos+6; /* also skip 2 flag bytes */
				size=a*128*ssf*128*ssf*128*ssf+b*128*ssf*128*ssf+c*128*ssf+d;
				encoding=id3v2[pos]; pos++; size=size-1;
				if (encoding==1){pos=pos+2; size=size-2;}
				DBGPRINT(5, " *debug* frame size %d pos=%ld\n",size,pos);
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					artist[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				artist[i]='\0';
				pos=pos+size;
				DBGPRINT(5, " *debug* Artist: %s\n",artist);
			}
			else if (a=='T' && b=='Y' && c=='E' && d=='R') /* read year */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				d=id3v2[pos+3];
				pos=pos+6; /* also skip 2 flag bytes */
				size=a*128*ssf*128*ssf*128*ssf+b*128*ssf*128*ssf+c*128*ssf+d;
				encoding=id3v2[pos]; pos++; size=size-1;
				if (encoding==1){pos=pos+2; size=size-2;}
				DBGPRINT(5, " *debug* frame size %d pos=%ld\n",size,pos);
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					year[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				year[i]='\0';
				pos=pos+size;
				DBGPRINT(5, " *debug* Year: %s\n",year);
			}
			else if (a=='T' && b=='D' && c=='R' && d=='C') /* read year in 2.4.0 */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				d=id3v2[pos+3];
				pos=pos+6; /* also skip 2 flag bytes */
				size=a*128*ssf*128*ssf*128*ssf+b*128*ssf*128*ssf+c*128*ssf+d;
				encoding=id3v2[pos]; pos++; size=size-1;
				if (encoding==1){pos=pos+2; size=size-2;}
				DBGPRINT(5, " *debug* frame size %d pos=%ld\n",size,pos);
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					year[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				year[i]='\0';
				pos=pos+size;
				DBGPRINT(5, " *debug* Year: %s\n",year);
			}
			else if (a=='T' && b=='A' && c=='L' && d=='B') /* read album */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				d=id3v2[pos+3];
				pos=pos+6; /* also skip 2 flag bytes */
				size=a*128*ssf*128*ssf*128*ssf+b*128*ssf*128*ssf+c*128*ssf+d;
				encoding=id3v2[pos]; pos++; size=size-1;
				if (encoding==1){pos=pos+2; size=size-2;}
				DBGPRINT(5, " *debug* frame size %d pos=%ld\n",size,pos);
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					album[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				album[i]='\0';
				pos=pos+size;
				DBGPRINT(5, " *debug* Album: %s\n",album);
			}
			else if (a=='T' && b=='R' && c=='C' && d=='K') /* read track */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				d=id3v2[pos+3];
				pos=pos+6; /* also skip 2 flag bytes */
				size=a*128*ssf*128*ssf*128*ssf+b*128*ssf*128*ssf+c*128*ssf+d;
				encoding=id3v2[pos]; pos++; size=size-1;
				if (encoding==1){pos=pos+2; size=size-2;}
				DBGPRINT(5, " *debug* frame size %d pos=%ld\n",size,pos);
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					track[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				track[i]='\0';
				pos=pos+size;
				DBGPRINT(5, " *debug* Track: %s\n",track);
			}
			else if (a=='C' && b=='O' && c=='M' && d=='M') /* read comment */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				d=id3v2[pos+3];
				pos=pos+6; /* also skip 2 flag bytes */
				size=a*128*ssf*128*ssf*128*ssf+b*128*ssf*128*ssf+c*128*ssf+d;
				DBGPRINT(5, " *debug* frame size %d pos=%ld\n",size,pos);
				encoding=id3v2[pos]; pos++; size=size-1;
				pos=pos+3; size=size-3;// skip language info
				while ((id3v2[pos]<32 || id3v2[pos]>253) && pos<length){pos++; size--;} // skip no-text-chars
				DBGPRINT(5, " *debug* frame size %d pos=%ld\n",size,pos);
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					comment[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				comment[i]='\0';
				pos=pos+size;
				DBGPRINT(5, " *debug* Comment: %s\n",comment);
			}
			else if (a>=48 && a<=90 && b>=48 && b<=90 && c>=48 && c<=90 && d>=48 && d<=90)
			{
				/* frame not important, so skip it */
				DBGPRINT(5, "\n *debug* skipped %c%c%c%c\n",a,b,c,d);
// 				{printf("\n* ignored ID3V2 frame named %c%c%c%c",a,b,c,d);}
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				d=id3v2[pos+3];
				pos=pos+6; /* skip 2 flag bytes */
				size=a*128*ssf*128*ssf*128*ssf+b*128*ssf*128*ssf+c*128*ssf+d;
				DBGPRINT(5, "\n *debug* skip size=%d length=%ld\n",size,length);
				if (pos+size>length) break;
				pos=pos+size;
				DBGPRINT(5, "\n *debug* next pos=%ld \n",pos);
			}
		}
	}

	if (minorv==2) /* ID3V2.2 */
	{
		while (pos<length)
		{
			if (pos+3>=length) break;
			a=id3v2[pos];
			b=id3v2[pos+1];
			c=id3v2[pos+2];
			pos=pos+3;

			if (a==0 && b==0 && c==0) break; /* is padding at the end of the tag */

			else if (a=='T' && b=='T' && c=='2') /* read title */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				pos=pos+3;
				size=a*256*256+b*256+c;
				if (id3v2[pos]==0){encoding=0;}
				if (id3v2[pos]==1){encoding=1;}
				while ((id3v2[pos]<32 || id3v2[pos]>253) && pos<length){pos++; size--;} // skip no-text-chars
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					title[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				pos=pos+size;
			}
			else if (a=='T' && b=='P' && c=='1') /* read artist */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				pos=pos+3;
				size=a*256*256+b*256+c;
				if (id3v2[pos]==0){encoding=0;}
				if (id3v2[pos]==1){encoding=1;}
				while ((id3v2[pos]<32 || id3v2[pos]>253) && pos<length){pos++; size--;} // skip no-text-chars
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					artist[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				pos=pos+size;
			}
			else if (a=='T' && b=='Y' && c=='E') /* read year */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				pos=pos+3;
				size=a*256*256+b*256+c;
				if (id3v2[pos]==0){encoding=0;}
				if (id3v2[pos]==1){encoding=1;}
				while ((id3v2[pos]<32 || id3v2[pos]>253) && pos<length){pos++; size--;} // skip no-text-chars
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					year[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				pos=pos+size;
			}
			else if (a=='C' && b=='O' && c=='M') /* read comment */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				pos=pos+3;
				size=a*256*256+b*256+c;
				if (id3v2[pos]==0){encoding=0;}
				if (id3v2[pos]==1){encoding=1;}
				while ((id3v2[pos]<32 || id3v2[pos]>253) && pos<length){pos++; size--;} // skip no-text-chars
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					comment[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				pos=pos+size;
			}
			else if (a=='T' && b=='A' && c=='L') /* read album */
			{
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				pos=pos+3;
				size=a*256*256+b*256+c;
				if (id3v2[pos]==0){encoding=0;}
				if (id3v2[pos]==1){encoding=1;}
				while ((id3v2[pos]<32 || id3v2[pos]>253) && pos<length){pos++; size--;} // skip no-text-chars
				i=0;
				for (j=pos; j<pos+size && i<255; j++)
				{
					album[i]=id3v2[j]; i++;
					if (encoding==1){j++;}
				}
				pos=pos+size;
			}
//			else if (a>=48 && a<=90 && b>=48 && b<=90 && c>=48 && c<=90 && d>=48 && d<=90)
			else if (a>=48 && a<=90 && b>=48 && b<=90 && c>=48 && c<=90)
			{
				/* frame not important, so skip it */
				a=id3v2[pos];
				b=id3v2[pos+1];
				c=id3v2[pos+2];
				pos=pos+3;
				size=a*256*256+b*256+c;
				if (pos+size>length) break;
				pos=pos+size;
			}
		}
	}

	/* in case there was no info about them: */
	if (strlen(title)==0) snprintf(title,31,"%s","unknown title");
	if (strlen(artist)==0) snprintf(artist,31,"%s","unknown artist");

	return length;

	/*
	Main info in V2.4:

	Structure:
	header 10 bytes
	optional ext. header
	frames
	optional padding (var. length) or footer (10 bytes)

	Header:
	3 Bytes ID3
	2 Bytes version (04 00 for 2.4.0)
	1 Byte flags
	4 Bytes length (0 + 7 bits, each)

	Frames:
	TIT2=title
	TPE1=artist
	TYER=year
	COMM=comment
	TALB=album
	*/

	/*
	Main info in V2.2:

	Structure:
	header 10 bytes
	frames

	Frames:
	ABCXXX with XXX=size, ABC as follows:

	TT2=title
	TP1=artist
	TYE=year
	COM=comment
	TAL=album
	*/
}


/* This function gets data from ID3 V1 and returns its length,
   argument is end of tag */
int importid3v1(long seekpos)
{
	int j=0;

//	printf("\nimportid3v1 at %ld\n",seekpos);

	if (seekpos < 0) seekpos=0;
	if (seekpos > filesize) seekpos=filesize;

	fseek(mp3file, seekpos-128, SEEK_SET); /* set to right position */
	if (fgetc(mp3file)!='T' || fgetc(mp3file)!='A' || fgetc(mp3file)!='G' )
	return 0; /* if TAG is not there */

	if(!fgets(title,31,mp3file))return 0; /* save title */
	for (j=29; title[j]==' ' ; j--) title[j]='\0'; /* erase trailing spaces */
	if(!fgets(artist,31,mp3file))return 0; /* save artist */
	for (j=29; artist[j]==' ' ; j--) artist[j]='\0'; /* erase trailing spaces */
	if(!fgets(album,31,mp3file))return 0; /* save album */
	for (j=29; album[j]==' ' ; j--) album[j]='\0'; /* erase trailing spaces */
	if(!fgets(year,5,mp3file))return 0; /* save year */
	if(!fgets(comment,31,mp3file))return 0; /* save comment */
	for (j=29; comment[j]==' ' ; j--) comment[j]='\0'; /* erase trailing spaces */
	genre=fgetc(mp3file);

	/* copy tag to buffer */
	fseek(mp3file, seekpos-128, SEEK_SET); /* reset to right position */
	for (j=0;j<128;j++) {id3v1[j]=fgetc(mp3file);}

	/* in case there was no info about them: */
	if (strlen(title)==0) snprintf(title,31,"%s","unknown title");
	if (strlen(artist)==0) snprintf(artist,31,"%s","unknown artist");

	return 128;
}


/* This function skips ID3 V1 tag, if any.
   Returns position of next byte after tag. */
long skipid3v1(long seekpos)
{
	unsigned char a,b,c,d;

	if (seekpos+4>filesize) return seekpos;

	fseek(mp3file, seekpos, SEEK_SET);
	a=fgetc(mp3file);
	b=fgetc(mp3file);
	c=fgetc(mp3file);
	d=fgetc(mp3file);

	/* check for ID3 V1, this is quite safe */
	if (a=='T' && b=='A' && c=='G') return seekpos+128;

	else /* check for ID3 V1 after this frame */
	{
		if (seekpos+framesize(b,c,d)+4>filesize) return seekpos;
		fseek(mp3file, seekpos+framesize(b,c,d), SEEK_SET);
		a=fgetc(mp3file);
		b=fgetc(mp3file);
		c=fgetc(mp3file);
		if (a=='T' && b=='A' && c=='G')	return seekpos+framesize(b,c,d)+128;
	}
	return seekpos;
}


/* This function rewinds to before ID3V1 tag, if any.
   Returns position of last byte before tag. */
long rewind3v1(long seekpos)
{
	unsigned char a,b,c;

	if (seekpos-128<0) return seekpos;

	fseek(mp3file, seekpos-128, SEEK_SET);
	a=fgetc(mp3file);
	b=fgetc(mp3file);
	c=fgetc(mp3file);

	/* check for ID3 V1, this is quite safe */
	if (a=='T' && b=='A' && c=='G')
	{
// 		printf("\nID3 V1 found at %ld\n",seekpos);
		seekpos=seekpos-128;
	}
	return seekpos;
}


/* This function rewinds max 100kB to before ID3V2 tag, if any.
   Returns position of first byte of tag. */
long rewind3v2(long seekpos)
{
	long oldseekpos=seekpos;   /* remember seekpos */
	unsigned char a,b,c;
	long id3pos,skippedpos;

	while (seekpos>0)
	{
		fseek(mp3file, seekpos, SEEK_SET); /* (re)set to right position */
		while ((a=fgetc(mp3file))!='I' && seekpos>oldseekpos-1E5)
		 {seekpos--; fseek(mp3file, seekpos, SEEK_SET);} /* look for 'I' */
		b=fgetc(mp3file);
		c=fgetc(mp3file);
		if (a=='I' && b=='D' && c=='3' ) /* ID3 is there */
		{
			id3pos=seekpos;
			if ( (skippedpos=skipid3v2(id3pos)) != id3pos )  /* ID3 is valid */
			{
				return id3pos; /* return position of ID3 */
			}
		}
		else seekpos--;
	}
	return oldseekpos;
}


/* This function skips ID3 V2 tag, if any.
   Useful also for getting the length of the ID3 V2 tag.
   Returns position of next byte after tag. */
long skipid3v2(long seekpos)
{
	long oldseekpos=seekpos;   /* remember seekpos */
	unsigned char buffer[10] = {0};
	int pos, footer, length;

	if (seekpos+10>filesize) return seekpos;

	fseek(mp3file, seekpos, SEEK_SET);

	/* Load 10 Bytes into unsigned buffer[] */
	for (pos=0 ; pos<10 && pos<=filesize ; pos++) buffer[pos]=fgetc(mp3file);

	/* check for ID3 V2.(<5).(<10), this is quite safe */
	if (buffer[0]=='I' && buffer[1]=='D' && buffer[2]=='3' && buffer[3]<5 && buffer[4]<10 && (buffer[5]<<4)==0 && buffer[6]<128 && buffer[7]<128 && buffer[8]<128 && buffer[9]<128)
	{
// 		printf("\nID3 V2 found at %ld\n",seekpos);
		footer=buffer[5]; footer=footer<<3; footer=footer>>7; footer=footer*10; /* check for footer presence */
		length=((buffer[6]*128+buffer[7])*128+buffer[8])*128+buffer[9];
		id3v2 = (unsigned char*)realloc(id3v2, 10+length+footer);
		if (!id3v2) exit(2);
		seekpos=seekpos+10+length+footer;
	}
	fseek(mp3file, oldseekpos, SEEK_SET); /* really necessary? */
// 	printf("\nID3 V2 length is %ld\n",seekpos);
	return seekpos;

	/*
	buffer[3] = v2 minor version (2.X.0)
	buffer[4] = v2 revision number (2.4.X)
	buffer[5] = flags byte, 4 lowest bits must be zero
	buffer[6-9] = synchsafe, highest bit is zero
	*/
}


/* This function returns the position of the next ID3 TAG,
   next byte after V1 and first byte of prepended V2 */
long seektag(long seekpos)
{
	unsigned char a,b,c,tag=0;
	long skippedpos, id3pos;

//	printf("%ld\n",seekpos);
	printf("  seeking ...       ");

	seekpos++; /* do not find ID3 again */

	while (seekpos<filesize)
	{
		fseek(mp3file, seekpos, SEEK_SET); /* (re)set to right position */
		while ((a=fgetc(mp3file))!='T' && a!='I' && seekpos<filesize) seekpos++; /* look for 'T' or 'I' */
		seekpos++;
// 		printf("\b\b\b\b\b\b\b%4u:%02.0f",pos2mins(seekpos),pos2secs(seekpos)); too slow!
		b=fgetc(mp3file);
		c=fgetc(mp3file);
		if (a=='T' && b=='A' && c=='G' ) /* TAG is there */
		{
			if (nextframe(seekpos) == seekpos+127) tag=1; /* TAG is valid, next frame is OK */
			if (skipid3v2(seekpos+127) != seekpos+127) tag=2; /* TAG is valid, ID3 is next */
			if (tag>0)
			{
				/* skip TAG and return position */
				showtag(seekpos+127);
				if (tag==2) print_id3v2(seekpos+127); /* Also show next track info */
				printf("\n\n[1..0,r,a,b,s,q] >                      ");
				framenumber=pos2frame(seekpos-1);
				return seekpos+127;
			}
		}
		if (a=='I' && b=='D' && c=='3' ) /* ID3 is there */
		{
			id3pos=seekpos-1;
			if ( (skippedpos=skipid3v2(id3pos)) != id3pos )  /* ID3 is valid */
			{
				tag=1;
				fseek(mp3file, skippedpos, SEEK_SET); /* Now check if TAG is next */
				a=fgetc(mp3file);
				b=fgetc(mp3file);
				c=fgetc(mp3file);
				if (a=='T' && b=='A' && c=='G' ) /* ID3 was an appended tag, because TAG is next. */
				{
					showtag(skippedpos+128);
					printf("\n\n[1..0,r,a,b,s,q] >                      ");
					/* Skip TAG and return position */
					framenumber=pos2frame(seekpos);
					return seekpos+128;
				}
				else /* ID3 is a prepended tag */
				{
					print_id3v2(id3pos);
					printf("\n[1..0,r,a,b,s,q] >                      ");
					framenumber=pos2frame(id3pos);
					return id3pos; /* return position of ID3 */
				}
			}
		}
	}
	framenumber=totalframes;
	return filesize; /* no more tags */
}


/* This function writes the configuration file */
void writeconf(void)
{
	FILE *conffile;
	char filename1[FN_LEN];

	snprintf(filename1, FN_LEN, "%s/%s", getenv("HOME"), ".cutmp3rc");
	/* check for conffile */
	if (NULL== (conffile = fopen(filename1,"wb")))
	{
		printf("  configuration file could NOT be written!     \n");
		return;
	}

	fprintf(conffile, "leng=");
	fprintf(conffile, "%u",silencelength);
	fprintf(conffile, "\n");
	fprintf(conffile, "volu=");
	fprintf(conffile, "%u",silvol);
	fprintf(conffile, "\n");
	fprintf(conffile, "card=");
	fprintf(conffile, "%u",card);
	fprintf(conffile, "\n");
	fprintf(conffile, "mute=");
	fprintf(conffile, "%u",mute);
	fprintf(conffile, "\n");
	fclose(conffile);
	printf("\n\n     length of silence is %u milliseconds",silencelength);
	printf("\n     maximum volume during silence is %u ",silvol);
	printf("\n     using soundcard #%u ",card);
	printf("\n     sound is ");
	if (mute) printf("OFF \n");
	else printf("ON \n");
	printf("\n     This configuration has been saved.     \n");
	return;
}


/* This function plays sound at the current position */
void playsel(long playpos)
{
	long playsize;
	int skipframes=(real)howlong*(real)1000/fix_frametime; /* real time */
	long pos;
	FILE *outfile;
	char command[1023];
	char playname[16] = "/tmp/playXXXXXX";
	static char prev_playname[16] = "";
	int fd=-1;
	int i=0;
	long tempfrnr=pos2frame(playpos);
	unsigned int mins,secs;

	if (prev_playname[0] != 0) {
		// printf("remove file %s\n", prev_playname); /* debug */
		remove(prev_playname); /* delete tempfile */
	}

	/* Determine playsize: */
	pos=playpos;
	for (i=0;i<skipframes;i++){pos=nextframe(pos);}
	playsize=pos-playpos;

	if (!mute)
	{
		if (playsize < 0) playsize=0;
		if (playpos < audiobegin)
		{
// 			playsize=playsize-(audiobegin-playpos);
			playpos=audiobegin;
		}
		if (playpos > filesize) playpos=filesize;
		if ((fd = mkstemp(playname)) == -1 || (NULL== (outfile = fdopen(fd, "w+b"))) )
		{
			perror("\ncutmp3: failed writing temporary mp3 in /tmp"); exitseq(9);
		} else {
			strncpy(prev_playname, playname, 16);
			fseek(mp3file, playpos, SEEK_SET);
			copyfile(outfile, mp3file, playsize);
			fclose(outfile);
		}
	}

	pos=playpos;

	if (!mute)
	{
 		if (card > 1)
			snprintf(command,1022," %s %s %s%u %s %s %s","mpg123",playname,"-a /dev/dsp",card-1,">",logname,"2>&1 &");
		else
			snprintf(command,1022," %s %s %s %s %s","mpg123",playname,">",logname,"2>&1 &");

		if(system(command)) printf("Error executing %s\n", command); /* play mp3 excerpt */

		printf("  playing at %4u:%05.2f",pos2mins(pos),pos2secs(pos));

		/* show live time? */
		if (livetime==1)
		{
			i=0; while (i<howlong*20)
			{
				printf("\b\b\b\b\b\b\b\b\b\b\b %4u:%05.2f",frame2mins(tempfrnr),frame2secs(tempfrnr));
				usleep(50000);
				tempfrnr=(real)tempfrnr+(real)50/(real)fix_frametime+1;
				if (tempfrnr>totalframes) break;
				i++;
			}
		}
		printf(" / %u:%05.2f  ",totalmins,totalsecs);
	}
	else
	{
		printf("  position is %4u:%05.2f / ",frame2mins(framenumber),frame2secs(framenumber));
		printf("%u:%05.2f  ",totalmins,totalsecs);
	}

	secs=abs(pos2time(inpoint)-frame2time(framenumber))/1000;
	mins=secs/60;
	secs=secs%60;

	if (playpos >= inpoint) printf("(%u:%02u after startpoint) \n",mins,secs);
	else                    printf("(%u:%02u before startpoint) \n",mins,secs);
}


/* This function plays sound up to the current position */
void playtoend(long playpos)
{
	long pos;
	unsigned int mins,secs;
	long framenr,rewframes;

	secs=abs(pos2time(inpoint)-pos2time(playpos))/1000;
	mins=secs/60;
	secs=secs%60;

	/* go rewframes frames back! */
	rewframes=(real)howlong*1000/(real)fix_frametime;
	framenr=framenumber-rewframes;
	if (framenr<0) framenr=0;

	pos=frame2pos(framenr);

	if (mute)
	{
		if (playpos >= inpoint)
		printf("  position is %4u:%05.2f / %u:%02.0f  (%u:%02u after startpoint) \n",frame2mins(framenumber),frame2secs(framenumber),totalmins,totalsecs,mins,secs);
		else
		printf("  position is %4u:%05.2f / %u:%02.0f  (%u:%02u before startpoint) \n",frame2mins(framenumber),frame2secs(framenumber),totalmins,totalsecs,mins,secs);
	}
	else
	{
		printf("\n  Now playing up to location.  ");
		playsel(pos);
	}
	return;
}


/* This function reads the configuration file */
void readconf(void)
{
	FILE *conffile;
	long confsize=0,pos=0;
	unsigned char conf[BUFFER];
	char filename1[FN_LEN];

	snprintf(filename1, FN_LEN, "%s/%s", getenv("HOME"), ".cutmp3rc");	/* get name of conffile */
	if (NULL == (conffile = fopen(filename1, "rb"))) return;			/* check for conffile */
	fseek(conffile, 0, SEEK_END);
	confsize=ftell(conffile);         /* size of conffile */

	/* read conffile into buffer: */
	fseek(conffile, 0, SEEK_SET);
	for (pos=0 ; pos<BUFFER && pos<confsize ; pos++) conf[pos]=fgetc(conffile);
	fclose(conffile);

	pos=0;        /* reset pos */
	while(1)                              /* loop till break */
	{
		if (pos==confsize || pos==BUFFER) break;
		if (conf[pos]=='l')
		{
			pos++;
			if (conf[pos]=='e' && conf[pos+1]=='n' && conf[pos+2]=='g' && conf[pos+3]=='=')
			{
				pos=pos+4;
				silencelength=0;
				while(isdigit(conf[pos]))
				{
					silencelength = silencelength*10 + conf[pos]-48;
					pos++;
				}
			}
		}
		if (conf[pos]=='v')
		{
			pos++;
			if (conf[pos]=='o' && conf[pos+1]=='l' && conf[pos+2]=='u' && conf[pos+3]=='=')
			{
				pos=pos+4;
				silvol=0;
				while(isdigit(conf[pos]))
				{
					silvol = silvol*10 + conf[pos]-48;
					pos++;
				}
			}
		}
		if (conf[pos]=='c')
		{
			pos++;
			if (conf[pos]=='a' && conf[pos+1]=='r' && conf[pos+2]=='d' && conf[pos+3]=='=')
			{
				pos=pos+4;
				if (isdigit(conf[pos]))
				{
					card = conf[pos]-48;
					pos++;
				}
			}
		}
		if (conf[pos]=='m')
		{
			pos++;
			if (conf[pos]=='u' && conf[pos+1]=='t' && conf[pos+2]=='e' && conf[pos+3]=='=')
			{
				pos=pos+4;
				if (isdigit(conf[pos]))
				{
					mute = conf[pos]-48;
					pos++;
				}
			}
		}
		pos++;
	}
	return;
}


/* This function plays the file at program start only */
void playfirst(long playpos)
{
	printf("\n1 <- 5 seeks backward");
	printf("\n6 -> 0 seeks forward");
	printf("\n ,  .  rewinds/skips one frame");
	printf("\n     a sets startpoint, A goes there");
	printf("\n     b sets endpoint, B goes there");
	printf("\n     s saves");
	printf("\n     p seeks to next silence");
	printf("\n\n     length of silence is %u milliseconds",silencelength);
	printf("\n     maximum volume during silence is %u ",silvol);
	printf("\n     using soundcard #%u ",card);
	printf("\n     sound is ");
	if (mute) printf("OFF \n");
	else printf("ON \n");
	printf("\n     Press 'h' for more help\n");
	printf("\n   0:00.00 [1..0,r,a,b,s,q] >  ");
	playsel(playpos);
}


/* This function saves the selection non-interactively */
void savesel(char *prefix)
{
	char *outname=malloc(FN_LEN);
	unsigned int number=1;
	long endm, startm;
	real ends, starts;
//	int a;
	FILE *fp;
	FILE *outfile=stdout;
	long i;

	DBGPRINT(7, "savesel(): ss=%.2f es=%.2f ip=%ld op=%ld \n",startsecs,endsecs,inpoint,outpoint);

	/* correct mins and secs */
	if (startsecs>59.999){ do {startmins++; startsecs=startsecs-60;} while (startsecs>59.999);}
	if (endsecs>59.999) { do {endmins++; endsecs=endsecs-60;} while (endsecs>59.999);}

	/* Import title from tags. Prefer V1 over V2!
	   No user interaction in this function */
	zaptitle();
	importid3v2(inpoint);
	importid3v1(outpoint);

	/* title known? */
	if (!usefilename && !no_tags && !forced_prefix && strlen(title)>0)
	{
		snprintf(outname, FN_LEN, "%s - %s.mp3", artist, title);
		fp = fopen(outname, "r");
		if (fp != NULL) /* file exists? */
		{
			while(fp != NULL) /* test if file exists */
			{
				number++;
				fclose(fp);
				if (number>99)
				{
					printf("\n\nFilename already exists 99 times.\nPlease save with tag (press t) and choose another title. \n");
					goto savesel_ret;
				}
				snprintf(outname, FN_LEN, "%s - %s_%02u.mp3", artist, title, number);
				fp = fopen(outname,"r");
			}
			number=number-overwrite; /* step one number back in case of overwrite mode */
			if (number==1) snprintf(outname, FN_LEN, "%s - %s.mp3", artist, title); /* overwrite file without number */
			else snprintf(outname, FN_LEN, "%s - %s_%02u.mp3", artist, title, number);
		}
	}
	else
	/* title not known */
	{
		/* outname = prefix number . suffix */
		snprintf(outname, FN_LEN, "%s%04u.mp3", prefix, number);

		if (inpoint > filesize)
		{
			if (nonint==1)
			{
				fprintf(stdout,"  cutmp3: startpoint (%u:%05.2f) must be before end of file (%u:%05.2f)!  \n",pos2mins(inpoint),pos2secs(inpoint),pos2mins(filesize),pos2secs(filesize));
				goto savesel_ret;
			}
			else
			{
				printf("  ERROR: startpoint (%u:%05.2f) must be before end of file (%u:%05.2f)!  \n",pos2mins(inpoint),pos2secs(inpoint),pos2mins(filesize),pos2secs(filesize));
				goto savesel_ret;
			}
		}
		if (inpoint < audiobegin)
		{
			if (nonint==1)
			{
				fprintf(stdout,"  cutmp3: setting startpoint (%u:%05.2f) to beginning of file (%u:%05.2f)!  \n",pos2mins(inpoint),pos2secs(inpoint),pos2mins(audiobegin),pos2secs(audiobegin));
				inpoint=audiobegin;
				goto savesel_ret;
			}
			else
			{
				printf("  WARNING: setting startpoint (%u:%05.2f) to beginning of file (%u:%05.2f)!  \n",pos2mins(inpoint),pos2secs(inpoint),pos2mins(audiobegin),pos2secs(audiobegin));
				inpoint=audiobegin;
			}
		}
		if (outpoint > filesize)
		{
			if (nonint==1)
			{
	// 			printf("%ld %ld",outpoint,filesize);
				fprintf(stdout,"  cutmp3: setting endpoint (%u:%05.2f) to end of file (%u:%05.2f)!  \n",pos2mins(outpoint),pos2secs(outpoint),pos2mins(filesize),pos2secs(filesize));
	// 			goto savesel_ret;
			}
			else
			{
				printf("  WARNING: setting endpoint (%u:%05.2f) to end of file (%u:%05.2f)!  \n",pos2mins(outpoint),pos2secs(outpoint),pos2mins(filesize),pos2secs(filesize));
			}
			outpoint=filesize;
		}
		if (outpoint <= inpoint)
		{
			if (nonint==1)
			{
				fprintf(stdout,"  cutmp3: endpoint (%u:%05.2f) must be after startpoint (%u:%05.2f)!  \n",pos2mins(outpoint),pos2secs(outpoint),pos2mins(inpoint),pos2secs(inpoint));
				goto savesel_ret;
			}
			else
			{
				printf("  ERROR: endpoint (%u:%05.2f) must be after startpoint (%u:%05.2f)!  \n",pos2mins(outpoint),pos2secs(outpoint),pos2mins(inpoint),pos2secs(inpoint));
				goto savesel_ret;
			}
		}

		/* if file exists, increment number */
		fp = fopen(outname,"r");
		while(fp != NULL) /* test if file exists */
		{
			number++;
			fclose(fp);
			if (number>9999) usage("9999 files written. Please choose another prefix via '-o prefix.'");
			snprintf(outname, FN_LEN, "%s%04u.mp3", prefix, number);
			fp = fopen(outname,"r");
		}
		number=number-overwrite; /* step one number back in case of overwrite mode */
		if (number==0) number=1;
		snprintf(outname, FN_LEN, "%s%04u.mp3", prefix, number);
	}

	/* forced output file name used? */
	if (forced_file==1) strncpy (outname, forcedname, FN_LEN-1);

	/* open outfile */
// 	if (NULL== (outfile = fopen(outname,"wb"))){perror("\ncutmp3: cannot not write output file! read-only filesystem?");exitseq(10);}
	if (stdoutwrite!=1 && NULL== (outfile = fopen(outname,"wb"))){perror("\ncutmp3: cannot not write output file! read-only filesystem?");exitseq(10);}

	/* copy id3 in case of -c switch */
	if (copytags==1){inpoint=rewind3v2(inpoint); outpoint=skipid3v1(outpoint);}

	/* do not copy id3 in case of -C switch */
	if (no_tags){inpoint=skipid3v2(inpoint); outpoint=rewind3v1(outpoint);}

	/* COPY DATA HERE */
	fseek(mp3file, inpoint, SEEK_SET);
	copyfile(outfile, mp3file, outpoint-inpoint);

	/* copy tag in case of -c switch */
	if (copytags==1 && importid3v1(filesize)>0)
	{
		for (i=0;i<128;i++)
		{
			if (stdoutwrite==1) putchar(id3v1[i]);
			else fputc(id3v1[i],outfile);
		}
	}

	/* close outfile */
	if (stdoutwrite!=1) fclose(outfile);

 	if (stdoutwrite==1) /* write to STDOUT, return without printing messages */
 	{
		overwrite=0;
		zaptitle(); /* erase title name after saving */
		goto savesel_ret;
	}

	/* noninteractive cutting: */
	if (nonint==1 && negstart>0 && negend>0) printf("  saved %u:%05.2f - %u:%05.2f to '%s'.  \n",startmins,startsecs,endmins,endsecs,outname);
	else
	/* negative offset: */
	if (nonint==1)
	{
		if (negend<0)
		{
			endm=totalmins-endmins;
			ends=totalsecs-endsecs;
			if (ends<0){ends=ends+60; endm--;}
		}
		else {endm=endmins; ends=endsecs;}
		if (negstart<0)
		{
			startm=totalmins-startmins;
			starts=totalsecs-startsecs;
			if (starts<0){starts=starts+60; startm--;}
		}
		else {startm=startmins; starts=startsecs;}
		printf("  saved %u:%05.2f - %u:%05.2f to '%s'.  \n",pos2mins(inpoint),pos2secs(inpoint),pos2mins(outpoint),pos2secs(outpoint),outname);
	}
	else
	/* interactive cutting: */
	printf("  saved %u:%05.2f - %u:%05.2f to '%s'.  \n",pos2mins(inpoint),pos2secs(inpoint),pos2mins(outpoint),pos2secs(outpoint),outname);

	overwrite=0;
	zaptitle(); /* erase title name after saving */
savesel_ret:
	free(outname);
	return;
}


/* This function saves the selection interactively with ID3 tags */
void savewithtag(void)
{

#define QRY_AND_UPD(tempvar_, len_, querystr_) if(1) { \
	char *tmp = NULL; \
	printf("\n\nPress <ENTER> for '%s'\n", (tempvar_)); \
	tmp = readline(querystr_); \
	if(tmp) { \
		if(tmp[0]) { \
			register unsigned ii; \
			for(ii=0; ii<(len_) && tmp[ii]; ii++) (tempvar_)[ii] = tmp[ii]; \
			/*for(ii=0; ii<(len_)           ; ii++) (tempvar_)[ii] = ' ';*/ \
			(tempvar_)[ii] = 0; \
		} \
		free(tmp); \
	} \
	fprintf(outfile, "%-" #len_ "s", (tempvar_)); \
}

	static char tempartist[31]  = {0};
	static char temptitle[31]   = {0};
	static char tempalbum[31]   = {0};
	static char tempyear[5]     = {0};
	static char tempcomment[31] = {0};
	int i = 1, tags = 0, tagver = 0; //, hasid3=0, hastag=0;
	unsigned int number=1;
	char outname[FN_LEN]="cutmp3.tmp";
	char *newname;
	FILE *fp;
	FILE *outfile;
	long oldinpoint=inpoint;
	long oldoutpoint=outpoint;
	long length = 0;

	if (inpoint > filesize)
	{
		printf("  ERROR: startpoint (%u:%05.2f) must be before end of file (%u:%05.2f)!  \n",pos2mins(inpoint),pos2secs(inpoint),pos2mins(filesize),pos2secs(filesize));
		return;
	}
	if (inpoint < audiobegin)
	{
		printf("  WARNING: setting startpoint (%u:%05.2f) to beginning of file (%u:%05.2f)!  \n",pos2mins(inpoint),pos2secs(inpoint),pos2mins(audiobegin),pos2secs(audiobegin));
		inpoint=audiobegin;
	}
	if (outpoint > filesize)
	{
		printf("  WARNING: setting endpoint (%u:%05.2f) to end of file (%u:%05.2f)!  \n",pos2mins(outpoint),pos2secs(outpoint),pos2mins(filesize),pos2secs(filesize));
		outpoint=filesize;
	}
	if (outpoint <= inpoint)
	{
		printf("  ERROR: endpoint (%u:%05.2f) must be after startpoint (%u:%05.2f)!  \n",pos2mins(outpoint),pos2secs(outpoint),pos2mins(inpoint),pos2secs(inpoint));
		return;
	}
	newname = (char*)malloc(FN_LEN);

	/*********************************/
	/* Choose ID3 V2 or V1 or custom */
	/*********************************/

	/* check if selection has tag v1 */
	if(outpoint < filesize) {
		if (rewind3v1(outpoint) != outpoint) {
			importid3v1(outpoint);
			printf("\n\nImported from selection:");
			printf("\n  Press 1 to copy ID3v1 with this title: %s", title);
			tags |= 0x1;
			i++;
		}
	}

	/* check if selection has tag v2 */
	if(inpoint) {
		if (skipid3v2(inpoint) != inpoint) {
			length = importid3v2(inpoint);
			if(!tags) printf("\n\nImported from selection:");
			printf("\n  Press %d to copy ID3v2 with this title: %s", i, title);
			tags |= 0x2;
			i++;
		}
	}

	/* check if file has tag v1 */
	if (rewind3v1(filesize) != filesize) {
		importid3v1(filesize);
		if(!tags) printf("\n");
		printf("\nImported from file %s:", filename);
		printf("\n  Press %d to copy ID3v1 with this title: %s", i, title);
		tags |= 0x4;
		i++;
	}

	/* check if file has tag v2 */
	if (skipid3v2(0) != 0) {
		length = importid3v2(0);
		if(!tags) printf("\n");
		if(!(tags & 0x04)) printf("\nImported from file %s:", filename);
		printf("\n  Press %d to copy ID3v2 with this title: %s", i, title);
		tags |= 0x8;
		i++;
	}

	if (tags) {
		printf("\n  Or press any other key for a custom tag  ");
		tagver = getchar();
		if(tagver < '1' || tagver >= i + '0') {
			tagver = 0;
		} else {
			tagver -= '0';
			for (i = 0; i < 3; i++) if(!(tags & (1<<i)) && i < tagver) tagver++;
		}
	}

	/* forced output file name used? */
	if (forced_file==1)
	{
		strncpy (outname, forcedname, FN_LEN-1);
	}

	/*******************/
	/* file write part */
	/*******************/
	if (NULL== (outfile = fopen(outname,"wb"))){perror("\ncutmp3: cannot not write output file! read-only filesystem?");exitseq(10);}

	printf("\nwriting audio data...");

	if (tagver == 2 || tagver == 4) { /* copy ID3v2 */
		if(tagver == 2 && (tags & 0x8)) length = importid3v2(inpoint); /* Not the last tag we read */
		fwrite(id3v2, 1, length, outfile);
	}

	if(tagver == 1 && (tags & 0xE)) importid3v1(outpoint); /* Not the last tag we read */
	if(tagver == 3 && (tags & 0x8)) importid3v1(filesize);

	inpoint = skipid3v2(inpoint); /* remove possible id3 tags */
	outpoint = rewind3v1(outpoint);

	fseek(mp3file, inpoint, SEEK_SET);
	copyfile(outfile, mp3file, outpoint-inpoint);

	printf(" finished.");

	if (tagver == 1 || tagver == 3) {
		/* copy ID3v1 */
		fwrite(id3v1, 1, 128, outfile);

	} else if (tagver == 0) {
		/* custom tag */
		fputs("TAG",outfile);

		QRY_AND_UPD(temptitle,   30, "Title? ")
		QRY_AND_UPD(tempartist,  30, "Artist? ")
		QRY_AND_UPD(tempalbum,   30, "Album? ")
		QRY_AND_UPD(tempyear,     4, "Year? ")
		QRY_AND_UPD(tempcomment, 30, "Comment? ")

		fputc(genre,outfile);

		strncpy(title, temptitle, 31);
		strncpy(artist, tempartist, 31);
	}

	fclose(outfile);

	/* restore them: */
	inpoint=oldinpoint;
	outpoint=oldoutpoint;

	/********************/
	/* file rename part */
	/********************/
	snprintf(newname, FN_LEN, "%s - %s.mp3", artist, title);
	fp = fopen(newname, "r");
	if (fp != NULL) /* file exists? */
	{
		while(fp != NULL) /* test if file exists */
		{
			number++;
			fclose(fp);
			if (number>99)
			{
				printf("\n  File NOT written. Please choose another title.  \n");
				return;
			}
			snprintf(newname, FN_LEN, "%s - %s_%02u.mp3", artist, title, number);
			fp = fopen(newname, "r");
		}
		number=number-overwrite; /* step one number back in case of overwrite mode */
		if (number==1) snprintf(newname, FN_LEN, "%s - %s.mp3", artist, title); /* overwrite file without number */
		else snprintf(newname, FN_LEN, "%s - %s_%02u.mp3", artist, title, number);
	}

	/* forced output file name used? Then show correct file name in summary. */
	if (forced_file==1) strncpy (newname, forcedname, FN_LEN-1);
	/* rename file only if not forced name: **
	** Only after writing ID3 tag we know the name, so it must be renamed after writing. */
	else rename(outname, newname);

	printf("\n  saved %u:%02.0f - %u:%02.0f with ID3v%c tag to '%s'.  \n", pos2mins(inpoint),pos2secs(inpoint), pos2mins(outpoint),pos2secs(outpoint), (tagver==2||tagver==4)?'2':'1', newname);

	overwrite=0;
	free(newname);
	return;
}


/* ################### KEY PRESS ######################## */

static struct termios  current,  /* new terminal settings             */
                       initial;  /* initial state for restoring later */

/* Restore term-settings to those saved when term_init was called */
void  term_restore  (void)  {
  tcsetattr(0, TCSANOW, &initial);
}  /* term_restore */

/* Clean up terminal; called on exit */
void  term_exit  (int)  {
	term_restore();
	printf("\n\nbugreports to mail@puchalla-online.de\n\n");
	exitseq(0);
}	/* term_exit */

/* Will be called when ctrl-z is pressed, this correctly handles the terminal */
void  term_ctrlz  (int)  {
	signal(SIGTSTP, term_ctrlz);
	term_restore();
	kill(getpid(), SIGSTOP);
}  /* term_ctrlz */

/* Will be called when application is continued after having been stopped */
void  term_cont  (int)  {
	signal(SIGCONT, term_cont);
	tcsetattr(0, TCSANOW, &current);
}  /* term_cont */

/* Needs to be called to initialize the terminal */
void  term_init  (void)  {
	/* if stdin isn't a terminal this fails. But    */
	/* then so does tcsetattr so it doesn't matter. */

	tcgetattr(0, &initial);
	current = initial;                      /* Save a copy to work with later  */
	signal(SIGINT,  term_exit);             /* We _must_ clean up when we exit */
	signal(SIGQUIT, term_exit);
	signal(SIGTSTP, term_ctrlz);            /* Ctrl-Z must also be handled     */
	signal(SIGCONT, term_cont);
	//atexit(term_exit);
}	/* term_init */

/* Set character-by-character input mode */

void  term_character  (void)  {
	/* One or more characters are sufficient to cause a read to return */
	current.c_cc[VMIN]   = 1;
	current.c_cc[VTIME]  = 0;  /* No timeout; read forever until ready */
	current.c_lflag     &= ~ICANON;           /* Line-by-line mode off */
	tcsetattr(0, TCSANOW, &current);
}  /* term_character */

/* Return to line-by-line input mode */
void  term_line  (void)  {
	current.c_cc[VEOF]  = 4;
	current.c_lflag    |= ICANON;
	tcsetattr(0, TCSANOW, &current);
}  /* term_line */

/* Key pressed ? */
int  kbhit  (void)  {
	struct timeval  tv;
	fd_set          read_fd;

	/* Do not wait at all, not even a microsecond */
	tv.tv_sec  = 0;
	tv.tv_usec = 0;

	/* Must be done first to initialize read_fd */
	FD_ZERO(&read_fd);

	/* Makes select() ask if input is ready;  0 is file descriptor for stdin */
	FD_SET(0, &read_fd);

	/* The first parameter is the number of the largest fd to check + 1 */
	if (select(1, &read_fd,
		NULL,         /* No writes        */
		NULL,         /* No exceptions    */
		&tv)  == -1)
	return(0);                 /* an error occured */

	/* read_fd now holds a bit map of files that are readable. */
	/* We test the entry for the standard input (file 0).      */
	return(FD_ISSET(0, &read_fd)  ?  1  :  0);
}

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


void cutexact(char *prefix)
{
	real seektime;
	long framenr;

	DBGPRINT(7, "cutexact()\n");

	DBGPRINT(7, " %i %u %lf %i %u %lf \n",negstart,startmins,startsecs,negend,endmins,endsecs);

	/* first look for inpoint */

	DBGPRINT(7, " sm=%u ss=%lf \n",startmins,startsecs);
	if (negstart<0)
	{
		seektime=endtime-startmins*60000-startsecs*1000;
		framenr=seektime/fix_frametime;
		DBGPRINT(7, " st=%lf fn=%ld\n",seektime,framenr);
		if (framenr<=0) inpoint=0;
		else inpoint=frame2pos(framenr);
		DBGPRINT(7, " ss=%.2f es=%.2f ip=%ld op=%ld fn=%ld\n",startsecs,endsecs,inpoint,outpoint,framenr);
	}
	else
	{
		seektime=startmins*60000+startsecs*1000;
		framenr=seektime/fix_frametime;
		DBGPRINT(7, " st=%lf fn=%ld\n",seektime,framenr);
		if (framenr>=totalframes) inpoint=filesize;
		else inpoint=frame2pos(framenr);
		DBGPRINT(7, " ss=%.2f es=%.2f ip=%ld op=%ld fn=%ld\n",startsecs,endsecs,inpoint,outpoint,framenr);
	}
// 	printf("  inpoint=%ld",inpoint);

	/* inpoint reached, now search for outpoint */

//	printf("  seeking endpoint");
	if (negend<0)
	{
		seektime=endtime-endmins*60000-endsecs*1000;
		framenr=seektime/fix_frametime;
		DBGPRINT(7, " st=%lf fn=%ld\n",seektime,framenr);
		if (framenr<=0) outpoint=0;
		else outpoint=frame2pos(framenr);
		DBGPRINT(7, " ss=%.2f es=%.2f ip=%ld op=%ld fn=%ld\n",startsecs,endsecs,inpoint,outpoint,framenr);
	}
	else
	{
		seektime=endmins*60000+endsecs*1000;
		framenr=seektime/fix_frametime;
		DBGPRINT(7, " st=%lf fn=%ld\n",seektime,framenr);
		if (framenr>=totalframes) outpoint=filesize;
		else outpoint=frame2pos(framenr);
		DBGPRINT(7, " ss=%.2f es=%.2f ip=%ld op=%ld fn=%ld\n",startsecs,endsecs,inpoint,outpoint,framenr);
	}

	/* outpoint reached, now write file */

//	printf("  saving selection...\n");
	DBGPRINT(7, " ss=%.2f es=%.2f ip=%ld op=%ld fn=%ld\n",startsecs,endsecs,inpoint,outpoint,framenr);
	savesel(prefix);
	DBGPRINT(7, " ss=%.2f es=%.2f ip=%ld op=%ld fn=%ld\n",startsecs,endsecs,inpoint,outpoint,framenr);
	startsecs=startmins=endsecs=endmins=0;
	return;
}


/* This function writes a timetable when -a and -b is used */
void writetable()
{
	int fd=-1;

	if ((fd = mkstemp(tblname)) == -1 || (NULL== (tblfile = fdopen(fd, "w+b"))) )
	{
		perror("\ncutmp3: failed writing temporary timetable in /tmp");exitseq(11);
	}
	fprintf(tblfile, "%s %s\n", userin, userout);
	fclose(tblfile);
	a_b_used=1;
	return;
}


/* This function copies selections when using a timetable */
void cutfromtable(char *tablename, char *prefix)
{
	unsigned int pos2, position=1;
//	int linenumber=1;
	real number=0;

	startsecs=endsecs=0.0; /* seconds _may_ be given */
	startmins=endmins=0; /* minutes _must_ be given! */
	negstart=negend=1;

	DBGPRINT(7, " cutfromtable()\n");

	timefile = fopen(tablename,"rb");
	/* Load BUFFER Bytes into unsigned buffer[] */
	for (pos2=0 ; pos2<BUFFER ; pos2++) ttable[pos2]=fgetc(timefile);
	fclose(timefile);

	pos2=0;
	do
	{
		/* position: startmins=1 startsek=2 startten=3 starthun=4 */
		/*             endmins=5   endsek=6   endten=7   endhun=8 */
		number=ttable[pos2]-48;
		if (isdigit(ttable[pos2])) /* char is a digit */
		{
			switch (position)
			{	case 1: startmins=startmins*10+ttable[pos2]-48; break;
				case 2: startsecs=startsecs*10+ttable[pos2]-48; break;
				case 3: startsecs=startsecs+number/10; position++; break;
				case 4: startsecs=startsecs+number/100; do pos2++; while (!isspace(ttable[pos2])); break;
				case 5: endmins=endmins*10+ttable[pos2]-48; break;
				case 6: endsecs=endsecs*10+ttable[pos2]-48; break;
				case 7: endsecs=endsecs+number/10; position++; break;
				case 8: endsecs=endsecs+number/100; position++; break;
				default: break;
			}
		}

		/* '-' is offset from end */
		if (ttable[pos2]==45 && position < 5) negstart=-1;
		if (ttable[pos2]==45 && position > 4) negend=-1;
		/* ':' and '.' increase position */
		if (ttable[pos2]==58 && position < 5) {position=2;} /* : */
		if (ttable[pos2]==58 && position > 4) {position=6;} /* : */
		if (ttable[pos2]==46 && position==1) {startsecs=startmins; startmins=0; position=3;} /* . */
		if (ttable[pos2]==46 && position==2) {position=3;} /* . */
		if (ttable[pos2]==46 && position==5) {endsecs=endmins; endmins=0; position=7;} /* . */
		if (ttable[pos2]==46 && position==6) {position=7;} /* . */

		DBGPRINT(7, " %i %u %lf %i %u %lf \n",negstart,startmins,startsecs,negend,endmins,endsecs);

		/* space switches to read end or to cutexact() */
		if (isspace(ttable[pos2]))  /* char is a whitespace */
		{
		DBGPRINT(7, "whitespace %i %u %lf %i %u %lf \n",negstart,startmins,startsecs,negend,endmins,endsecs);
			if (position==5 && endmins+endsecs > 0)  /* only minutes given, finish */
			{
				position=9;
			}
			if (position < 5) position=5;  /* read end now */
			if ((position > 4 && endmins+endsecs > 0) || (position > 4 && endmins+endsecs==0 && negend==-1) ) /* cut exact now */
			{
				/* correct mins and secs */
				if (startsecs>59.999){ do {startmins++; startsecs=startsecs-60;} while (startsecs>59.999);}
				if (endsecs>59.999) { do {endmins++; endsecs=endsecs-60;} while (endsecs>59.999);}

//				if (a_b_used!=1) printf(" using timetable \"%s\" line %i:\n",tablename,linenumber);linenumber++;
				if (a_b_used!=1) printf(" using timetable \"%s\"\n",tablename);
				if (endmins*negend>totalmins)
				{
					printf("  WARNING: setting endpoint (%u:%05.2f) to end of file (%u:%05.2f)!  \n",endmins,endsecs,pos2mins(filesize),pos2secs(filesize));
					endmins=totalmins;
					endsecs=totalsecs;
				}
				if (endmins*negend==totalmins && endsecs>totalsecs)
				{
// 					printf("%ld %ld",outpoint,filesize);
					printf("  WARNING: setting endpoint (%u:%05.2f)  to end of file (%u:%05.2f)!  \n",endmins,endsecs,pos2mins(filesize),pos2secs(filesize));
					endsecs=totalsecs;
				}
				if (startmins*negstart>totalmins)
				{
					printf("  ERROR: startpoint (%u:%05.2f) is after end of file (%u:%05.2f)!  \n",startmins,startsecs,pos2mins(filesize),pos2secs(filesize));
				}
				if (startmins*negstart==totalmins && startsecs>totalsecs)
				{
					printf("  ERROR: startpoint (%u:%05.2f)  is after end of file (%u:%05.2f)!  \n",startmins,startsecs,pos2mins(filesize),pos2secs(filesize));
				}
				/* end after start? -> check in cutexact() */
				else cutexact(prefix);
				position=1;
				startsecs=endsecs=0; /* seconds _may_ be given */
				startmins=endmins=0; /* minutes _must_ be given! */
				negstart=negend=1;
				number=0;
			}
		}

		if (ttable[pos2]==255) pos2=BUFFER;
		DBGPRINT(7, "pos2=%u %u:%02.2f %u:%02.2f position=%u\n",pos2,startmins,startsecs,endmins,endsecs,position);
		pos2++;
	}
	while (pos2<BUFFER);
}


void showfileinfo(int rawmode, int fix_channels)
{
	unsigned char a,b,c;

	if (rawmode==1)
	{
		printf("%ld %u %i %.0f %u %.0lf\n",totalframes,fix_sampfreq,fix_channels,avbr,fix_mpeg,fix_frametime*totalframes);
		exitseq(0);
	}

	printf("\n\n* Properties of \"%s\":\n* \n",filename);
	if (audiobegin != 0) printf("* Audio data starts at %u Bytes\n",audiobegin);
	printf("* Size of first frame is %u Bytes\n",frame_sz(begin));
	if (mpeg(begin[1])==3) printf("* File is MPEG2.5-Layer%u\n",layer(begin[1]));
	else printf("*\n* Format is MPEG%u-Layer%u\n",mpeg(begin[1]),layer(begin[1]));
	if (vbr==0) printf("*           %.0f kBit/sec\n",avbr);
	else printf("*           %.2f kBit/sec (VBR average)\n",avbr);
	printf("*           %u Hz ",sampfreq(begin[1],begin[2]));
	if (channelmode(begin[3])==0) printf("Stereo\n");
	if (channelmode(begin[3])==1) printf("Joint-Stereo\n");
	if (channelmode(begin[3])==2) printf("Dual-Channel\n");
	if (channelmode(begin[3])==3) printf("Mono\n");
	printf("*\n* Filelength is %u minute(s) and %4.2f second(s)\n",totalmins,totalsecs);

	/* show tag V1 if any: */
	fseek(mp3file, filesize-128, SEEK_SET); /* set to right position */
	a=fgetc(mp3file);
	b=fgetc(mp3file);
	c=fgetc(mp3file);
	if (a=='T' && b=='A' && c=='G' ) print_id3v1(filesize); /* TAG is there */
	else printf("\nno ID3 V1 tag.\n");

	/* show tag V2 if any: */
	zaptitle(); importid3v2(0);
	if (strlen(title)>0)
	{
		printf("\nID3 V2.%u tag:\nTitle:   %s",minorv,title);
		printf("\nArtist:  %s",artist);
		printf("\nAlbum:   %s",album);
		printf("\nYear:    %s",year);
		printf("\nTrack:   %s",track);
		printf("\nComment: %s\n",comment);
	}
	else printf("\nno ID3 V2 tag.\n");
	return;

}


void showdebug(long seekpos, char *prefix)
{
//	int a,b,c,d;
	int b,c,d;

	printf("\n\nfile name is %s\n",filename);
	printf("audiobegin is at      %10u\n",audiobegin);
	printf("I am at offset        %10li <-\n",seekpos);
// 	printf("frame2pos             %10li\n",frame2pos(framenumber));
	printf("last frame, last byte %10li\n",dataend);
	printf("file size is          %10li\n",filesize);
	printf("inpoint is            %10li\n",inpoint);
	printf("outpoint is           %10li\n",outpoint);
	fseek(mp3file, seekpos, SEEK_SET);
//	a=fgetc(mp3file);
	fgetc(mp3file);
	b=fgetc(mp3file);
	c=fgetc(mp3file);
	d=fgetc(mp3file);
	printf("current framesize is %u bytes\n",framesize(b,c,d));
	printf("pos2time is %lf\n",pos2time(seekpos));
	printf("current framenumber is %10li\n",framenumber);
	printf("pos2frame is           %10li\n",pos2frame(seekpos));
	printf("total # of frames is   %10li\n",totalframes);
// 	printf("framenumber(filesize) is %ld\n",pos2frame(filesize));
// 	printf("framenumber(dataend) is %ld\n",pos2frame(dataend));
	printf("fix framesize is %u bytes\n",fix_frsize);
	printf("fix frametime is %.5lf ms\n",fix_frametime);
// 	printf("framepos1000[0] is %ld\n",framepos1000[0]);
// 	printf("framepos1000[1] is %ld\n",framepos1000[1]);
	printf("silencelength is %u ms\n",silencelength);
	printf("silvol is %u\n",silvol);
	printf("volume is %u\n",volume(seekpos));
	printf("msec is %lf bytes\n",msec);
	printf("Prefix is '%s'\n",prefix);
	print_id3v1(seekpos);
	print_id3v2(seekpos);
}


/* ------------------------------------------------------------------------- */
/*      */
/* MAIN */
/*      */
/* ------------------------------------------------------------------------- */

int main(int argc, char *argv[])
{
//	int a;
	int b,c,d,char1,showinfo=0,rawmode=0,fix_channels=0;
	unsigned long pos, startpos;
	real ft_factor = 2;
	char *tablename = NULL;
	char *prefix = malloc(FN_LEN);
	void (*play)(long playpos) = playsel;
	prefix[0] = 0;

/* ------------------------------------------------------------------------- */
/*                   */
/* parse commandline */
/*                   */
/* ------------------------------------------------------------------------- */

	if ((fdlog = mkstemp(logname)) == -1 || (NULL== (logfile = fdopen(fdlog, "w+b"))) )
	{
		perror("\ncutmp3: failed writing temporary logfile in /tmp");exitseq(11);
	}

	readconf();       /* read configuration file first! */

	if (argc<2) {usage("ERROR: missing filename");exitseq(1);}
	else
	{
		while ((char1 = getopt(argc, argv, "cCkheqFf:a:b:i:o:O:I:d:D:s:")) != -1)
		{
			switch (char1)
			{
				case 'a':
					if (optarg!=0) userin=optarg;
//					else usage("Error: missing time argument");
					break;
				case 'b':
					if (optarg!=0) userout=optarg;
//					else usage("Error: missing time argument");
					break;
				case 'c':
					copytags=1;
					break;
				case 'C':
					no_tags=1;
					break;
				case 'd':
					if (optarg!=0) card=atoi(optarg);
					break;
#ifdef DEBUG
				case 'D':
					if (optarg!=0) debug=atoi(optarg);
					break;
#endif
				case 'e':
// 					exactmode=1;
					break;
				case 'f':
					if (NULL == (timefile = fopen(optarg,"rb") ) ) {
						perror(optarg);
						exitseq(3);
					}
					else tablename = optarg;
					break;
				case 'h':
					usage("");
					exit(0);
				case 'q':
					mute=1;
					break;
				case 'i':
//					if (optarg==0) usage("Error: option requires an argument");
					if (NULL == (mp3file = fopen(optarg,"rb") ) )
					{
						perror(optarg);
						exitseq(2);
					}
					else filename=optarg;
					break;
				case 'I':
//					if (optarg==0) usage("Error: option requires an argument");
					if (NULL == (mp3file = fopen(optarg,"rb") ) )
					{
						perror(optarg);
						exitseq(2);
					}
					else { filename=optarg; showinfo=1; }
					break;
				case 'F':
					rawmode=1;
					break;
				case 'o':
					if (optarg!=0) strncpy(prefix, optarg, FN_LEN-1);
//					else usage("Error: missing outputprefix");
					forced_prefix=1;
					break;
/* ThOr: let user define exactly one output file */
				case 'O':
					if (optarg!=0)
					{
						forcedname=optarg;
						forced_file=1;
						if (optarg[0]=='-') stdoutwrite=1;
					}
					break;
				case 's':
					if (optarg!=0) silfactor=atoi(optarg); /* changes maximum silence length */
					break;
				case 'k':
					usefilename=1;
					break;
				default:
					exitseq(1);
					break;
			}
		}
	}

	if (usefilename) {
		strncpy(prefix,filename,FN_LEN-1);
		prefix[strlen(filename)-4]=0;
	}
	if (prefix[0]==0) strcpy(prefix,"result");

	if (userin==0 && userout!=0) usage("ERROR: missing inpoint");
	if (userin!=0 && userout==0) usage("ERROR: missing outpoint");
	if (filename==0) {usage("ERROR: missing filename, use 'cutmp3 -i file.mp3 [options]'");exitseq(1);}

	if (stdoutwrite==0) setvbuf(stdout,NULL,_IONBF,0); /* switch off buffered output to stdout when writing to file */

	mp3file = fopen(filename, "rb");

	/* get filesize and set outpoint to it */
	fseek(mp3file, 0, SEEK_END);
	outpoint=filesize=ftell(mp3file);
	framepos1000 = (long*)malloc((filesize/384 + 1999) / 1000 * sizeof(long));	// Framenum with minimum theoretical size / 1000 + 1

	/* Check beginning of file: set audiobegin */
	if ((audiobegin = skipid3v2(0)) == 0) {
		fseek(mp3file, 0, SEEK_SET);
//		a=fgetc(mp3file);
		fgetc(mp3file);
		b=fgetc(mp3file);
		c=fgetc(mp3file);
		d=fgetc(mp3file);
		inpoint=nextframe(0);
		if (framesize(b,c,d)==inpoint) audiobegin=0;
		else audiobegin=inpoint;
	}
	if(audiobegin) audiobegin=nextframe(audiobegin-1); /* in case there is invalid data after an ID3 V2 tag */
	audiobegin=nextframe(audiobegin);	// Skip first frame containing original file length

	startpos=inpoint=audiobegin;

	fseek(mp3file, audiobegin, SEEK_SET);
	/* Load BUFFER Bytes into unsigned begin[]
	   begin[] starts at audiobegin, so contents should be fine */
	for (pos=0 ; pos<BUFFER && audiobegin+pos<=filesize ; pos++) begin[pos]=fgetc(mp3file);

/********** global values: **********/

	fix_secondbyte=begin[1];
	fix_sampfreq=sampfreq(begin[1],begin[2]);
	if (fix_sampfreq==1) usage("ERROR: Could not determine sampling frequency.");
	fix_channelmode=channelmode(begin[3]);
	fix_channels=channels(begin[3]);
	fix_mpeg=mpeg(begin[1]);
	if (fix_mpeg==1) ft_factor=1;
	else ft_factor=2;

	fix_frametime=(real)1152000/(real)fix_sampfreq/(real)ft_factor;  /* in msecs, half the time if MPEG>1 */

	avbr=avbitrate(); /* average bitrate during SCANAREA (10 MB) */
	if (avbr==0) {usage("ERROR: File has less than two valid frames.");exitseq(4);}
// 	msec=avbr/8;  /* one millisecond in bytes */
	if (!vbr) fix_frsize=frame_sz(begin);
	scanframes();
	msec=frame2time(totalframes)*1000/filesize;  /* one millisecond in bytes */

	dataend=prevframe(filesize); /* Beginning of last frame */
	fseek(mp3file, dataend, SEEK_SET);
	fgetc(mp3file);
	b=fgetc(mp3file);
	c=fgetc(mp3file);
	d=fgetc(mp3file);
	dataend=dataend+framesize(b,c,d)-1; /* last byte of last frame */
//	printf("dataend=%ld  filesize=%ld  nextframe(dataend)=%ld",dataend,filesize,nextframe(dataend));

	/* get total mins:secs */
	endtime=totalframes*fix_frametime;
	totalmins=endtime/60000;
	totalsecs=(endtime-(real)totalmins*(real)60000)/1000;

/********** End of global part, now do specific parts **********/

	/* Show file info only? */
	if (showinfo==1)
	{
		showfileinfo(rawmode,fix_channels);
		printf("\n");
		exitseq(0);
	}

	/* -a and -b used? */
	if (userin!=0 && userout!=0)
	{
		nonint=1;
		writetable();
		cutfromtable(tblname,prefix);
		remove(tblname);
		exitseq(0);
	}

	/* timetable used? */
	if (tablename)
	{
		nonint=1;
		cutfromtable(tablename,prefix);
		exitseq(0);
	}

	/*** now handle interactive mode ***/

	if (stdoutwrite==1)
	{
		usage("ERROR: Writing to STDOUT in interactive mode is a bad idea.");exitseq(5);
	}

	copytags=0;  /* -c is only for noninteractive mode */

	term_init();
	term_character();

	playfirst(audiobegin);

	while (1)
	{
 		printf("\n%4u:%05.2f [1..0,r,a,b,s,q] > ", pos2mins(startpos), pos2secs(startpos));
		c = getchar();
		if (c == 'q') {printf("\n\n");exitseq(0);}
		if (c == '1') {startpos=frewind(startpos,1000*600); play(startpos);}
		if (c == '2') {startpos=frewind(startpos,1000*60); play(startpos);}
		if (c == '3') {startpos=frewind(startpos,1000*10); play(startpos);}
		if (c == '4') {startpos=frewind(startpos,1000); play(startpos);}
		if (c == '5') {startpos=frewind(startpos,100); play(startpos);}
		/* previous frame, when at EOF */
		if (c == ',') {startpos=frewind(startpos,1); play(startpos);}
		if (c == '.') {startpos=fforward(startpos,1); play(startpos);}    /* next frame */
		if (c == '6') {startpos=fforward(startpos,100);play(startpos);}
		if (c == '7') {startpos=fforward(startpos,1000);play(startpos);}
		if (c == '8') {startpos=fforward(startpos,1000*10);play(startpos);}
		if (c == '9') {startpos=fforward(startpos,1000*60);play(startpos);}
		if (c == '0') {startpos=fforward(startpos,1000*600);play(startpos);}
		if (c == 'r') play(startpos);
		if (c == 'R') { play = play == &playsel ? playtoend : playsel; play(startpos);}
		if (c == 'v') printf("  volume=%u",volume(startpos));
		if (c == 's') {savesel(prefix);play=playsel;}
		if (c == 'S') writeconf();
		if (c == 't') savewithtag();
		if (c == 'i') showfileinfo(rawmode,fix_channels);
		if (c == 'h') help();
		if (c == 'N') {howlong=howlong-1;if (howlong<1)howlong=1;printf("  playing %i second(s) now\n",howlong);}
		if (c == 'M') {howlong=howlong+1;printf("  playing %i second(s) now\n",howlong);}
		if (c == 'z') showdebug(startpos,prefix);
		if (c == 'a')
		{
			inpoint=startpos;
			intime=frame2time(framenumber);
			printf("  startpoint set to %u:%05.2f  ",pos2mins(inpoint),pos2secs(inpoint));
// 			play=playtoend;
		}
		if (c == 'b')
		{
			outpoint=startpos;
			outtime=frame2time(framenumber);
			printf("  endpoint set to %u:%05.2f  ",pos2mins(outpoint),pos2secs(outpoint));
// 			play=playsel;
		}
		if (c == '#')
		{
			mute=1-mute;
			if (mute) printf("  sound off\n");
			else printf("  sound on\n");
		}
		if (c == 'l')
		{
			livetime=1-livetime;
			if (livetime) printf("  live time on\n");
			else printf("  live time off\n");
		}
		if (c == 'o')
		{
			overwrite=1-overwrite;
			if (1-overwrite) printf("  overwrite off\n");
			else printf("  overwrite on (for next time only)\n");
		}
		if (c == 'p')
		{
			startpos=seeksilence(startpos);
			printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
			if (startpos>=dataend) printf("  End of file reached.     \n");
			else
			{
				printf("\n  End of silence reached.       \n");
				play(startpos);
			}
		}
		if (c == 'P')
		{
			startpos=seeksilstart(startpos);
			printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
			if (startpos>=dataend) printf("  End of file reached.     \n");
			else
			{
				printf("\n  Beginning of silence reached.\n");
// 				play=playtoend;
				play(startpos);
			}
		}
		if (c == 'T')
		{
			startpos=seektag(startpos);
			printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
			if (startpos>=dataend) printf("  End of file reached.     \n");
			else play(startpos);
		}
		if (c == 'W')
		{
			outpoint=startpos;printf("  endpoint set to %u:%05.2f  ",pos2mins(outpoint),pos2secs(outpoint));
			savewithtag();
			inpoint=startpos;printf("  startpoint set to %u:%05.2f  \n",pos2mins(inpoint),pos2secs(inpoint));
// 			play=playtoend;
		}
		if (c == 'w')
		{
			outpoint=startpos;printf("  endpoint set to %u:%05.2f  \n",pos2mins(outpoint),pos2secs(outpoint));
			savesel(prefix);
			inpoint=startpos;printf("  startpoint set to %u:%05.2f  \n",pos2mins(inpoint),pos2secs(inpoint));
// 			play=playtoend;
		}
		if (c == 'A')
		{
			startpos=inpoint;
			framenumber=pos2frame(inpoint);
			play(startpos);
		}
		if (c == 'B')
		{
			startpos=outpoint;
			framenumber=pos2frame(outpoint);
			play(startpos);
		}
		if (c == '+')
		{
			silvol=silvol*1.1+1;
			if (silvol>10000) silvol=10000;
			printf("  maximum volume during silence is %u  \n",silvol);
		}
		if (c == '-')
		{
			silvol=silvol/1.1; if (silvol<1) silvol=1;
			printf("  maximum volume during silence is %u  \n",silvol);
		}
		if (c == 'm')
		{
			silencelength=silencelength*1.101;
			if (silencelength>10000 && silfactor !=0) silencelength=10000;
			printf("  length of silence is %u milliseconds \n",silencelength);
		}
		if (c == 'n')
		{
			silencelength=silencelength/1.1;
			if (silencelength<100) silencelength=100;
			printf("  length of silence is %u milliseconds \n",silencelength);
		}
	}
	exitseq(0);
}


/*

MP3-Frame-Header: 4 Bytes

########
0 - 7   Alles 1 für Sync
########
8 -10   Alles 1 für Sync
11-12   MPEG-Version: 10=MPEG2 11=MPEG1 01=MPEG2.5
13-14   0 1 für Layer3
        1 0 für Layer2
        1 1 für Layer1
   15   Protection Bit, bei 0 soll nach Header Error-Check kommen (CRC)
########
16-19   bitrate encoding
20-21   Sampling Frequency
   22   Padding Bit, if set, frame is 1 byte bigger
   23   Private Bit
########
24-25   Mono/Stereo Modus
26-27   Mode Extension für Joint Stereo
   28   Copyright Bit
   29   Original?
30-31   Emphasis
########


------------------------------------------------------------------------------


What you call "volume coefficients" are actually called "global gain
factors". These are stored logarithmically as 8 bit per granule per
channel. A granule is usually half an MP3 frame (in case of 32-48
kHz). These global gain factors are stored in a so-called "side info
block" with a granularity of 1.5 dB.
(global gain factor +/- 4 --> 200% / 50% amplitude)

MP3-Frame
---------
32 Bit Header
16 Bit CRC (optional)
xx Bytes Side Info Block
yy Bytes Main Data

where xx =
9  in case of MPEG2/2.5 mono
17 in case of MPEG2/2.5 stereo or MPEG1 mono
32 in case of MPEG1 stereo

For more details see
www.mp3-tech.org -> programmer's corner -> technical audio papers
-> ISO 11172 Coding of [...] Part 3: Audio
This is a draft of the MPEG-1 audio Layer 1/2/3 specification.

Note: These global gain factors are NOT 8bit-aligned.
*/
