/****************************************************************************
                             Hyper's CD Catalog 
		A multiplatform qt and xml based catalog program
  
 Author    : Peter Deak (hyperr@freemail.hu)
 License   : GPL
 Copyright : (C) 2003 Peter Deak
****************************************************************************/

#include "mp3tag.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>

#define TITLE  0
#define ARTIST 1 
#define ALBUM  2 
#define TRACK  3 
#define GENRE  4 
#define COMM   5 
#define YEAR   8 

#define FREE_AND_ALLOC(a,b) { if(a != NULL) { delete [] a; a=NULL; } a = new char[b]; strcpy(a,""); }

#define MAX    9

char markers[][MAX] = {"TIT2","TPE1","TALB","TRCK","TCON","COMM","TCOP","TENC","TYER"};

unsigned char found_error;


ReadMp3Tag::ReadMp3Tag(void)
    {
     mp3      = NULL;
     techinfo = NULL;
     Year     = NULL;
     Album    = NULL;
     Artist   = NULL;
     Title    = NULL;
     Comment  = NULL;
 
     bTAGreaded = false;
     techinfo   = NULL;
     v1_over_v2 = false;
    }

ReadMp3Tag::ReadMp3Tag(const char *fn,bool v1_over_v2p)
    {
     mp3      = NULL;
     techinfo = NULL;
     Year     = NULL;
     Album    = NULL;
     Artist   = NULL;
     Title    = NULL;
     Comment  = NULL;
     v1_over_v2 = v1_over_v2p;
     bTAGreaded = false;
     techinfo   = NULL;
 
     Work(fn);
    }
																			 
ReadMp3Tag::~ReadMp3Tag(void)
    {
     if(techinfo != NULL) { delete [] techinfo; techinfo= NULL; }
     if(Year     != NULL) { delete [] Year;     Year    = NULL; }
     if(Album    != NULL) { delete [] Album;    Album   = NULL; }
     if(Artist   != NULL) { delete [] Artist;   Artist  = NULL; }
     if(Title    != NULL) { delete [] Title;    Title   = NULL; }
     if(Comment  != NULL) { delete [] Comment;  Comment = NULL; }
    }
    
char *mmstr(const char *imp)
 {
  char *x;
  if(imp == NULL) return NULL;
  x = new char[strlen(imp)+1];
  strcpy(x,imp);
  return x;
 }
	        
char *ReadMp3Tag::year   (void) const  
 {
   return (Year    == NULL ? mmstr(" ") : mmstr(Year));   
 }
 
char *ReadMp3Tag::album  (void) const  
 {
   return (Album   == NULL ? mmstr(" ") : mmstr(Album)); 
 }
 
char *ReadMp3Tag::artist (void) const  
 {
   return (Artist  == NULL ? mmstr(" ") : mmstr(Artist));
 }
 
char *ReadMp3Tag::title  (void) const  
 {
   return (Title   == NULL ? mmstr(" ") : mmstr(Title)); 
 }
 
char *ReadMp3Tag::comment(void) const  
 {
   return (Comment == NULL ? mmstr(" ") : mmstr(Comment));
 }

char *ReadMp3Tag::gettechinfo(void) const  
 {
   return mmstr(techinfo);
 }



 
int         ReadMp3Tag::tnum    (void) const 
 {
   return tnumber; 
 }

						 
char * ReadMp3Tag::strbcut(char *a) //Oly used by A ReadTAG function
 {
  for(int i=strlen(a)-1;i>0;i--)
   if(a[i] != ' ')
    {
     a[i+1]='\0';
     return a;
    }
   a[1]='\0';
   return a;
 }

int ReadMp3Tag::Work(const char *filename)
 {
  //cerr<<"Load file: "<<filename<<" ...";
  bTAGreaded = 1;
  bTAGexist  = 0;
  if( (mp3 = fopen(filename,"rb"))==NULL)
   {
     bTAGexist  = 0;
     found_error = 1;
     fprintf(stderr,"I can't open a .mp3 file: %s\n",filename);
     return -1;
   }
  if(v1_over_v2)
   {
     bTAGexist = ReadTAGv1();
     if(bTAGexist == 0)
        bTAGexist = ReadTAGv2();
   }
  else
   {
     bTAGexist = ReadTAGv2();
     if(bTAGexist == 0)
        bTAGexist = ReadTAGv1();
   } 

  technicalInfo();
  fclose(mp3);  
  return 0;
 }

/*********************************************************************
*  Read ID3 tag version 1 
**********************************************************************/
int ReadMp3Tag::ReadTAGv1(void)
 {
   char TAG[4];

   //         X AL AR TI TAG
   fseek(mp3,-128,SEEK_END);
   strcpy(TAG,"");
   fread(TAG,sizeof(char),3,mp3);   
   if(strncmp(TAG,"TAG",3))
      return 0; //Id3 tag not exists
   else
    {

     FREE_AND_ALLOC(Title  ,31);
     FREE_AND_ALLOC(Artist ,31);
     FREE_AND_ALLOC(Album  ,31);    
     FREE_AND_ALLOC(Year   ,5 );     
     FREE_AND_ALLOC(Comment,31);     
    
     fread(Title ,sizeof(char),30,mp3);  Title  [30]='\0';
     fread(Artist,sizeof(char),30,mp3);  Artist [30]='\0';
     fread(Album ,sizeof(char),30,mp3);  Album  [30]='\0';
     fread(Year ,sizeof(char),4,mp3);    Year   [4 ]='\0';
     fread(Comment,sizeof(char),30,mp3); Comment[30]='\0';

     tnumber = 0;
     if(Comment[28] == 0) 
      {
       tnumber = Comment[29];
       Comment[28] = '\0'; 
      }

     strbcut(Artist);
     strbcut(Title);
     strbcut(Year);
     strbcut(Album);
     strbcut(Comment);
    }
    return 1;
 }

/*********************************************************************
*  Read ID3 tag version 2 
**********************************************************************/
int ReadMp3Tag::ReadTAGv2(void)
{

  char *tag = NULL;
  unsigned long len;
  unsigned long flen;
  unsigned char c1,c2,c3,c4,c5;

  unsigned int pos=0;
  unsigned int framelen;
  char id[5];
  id[5] ='\0';

  fseek(mp3,0,SEEK_END);
  flen = ftell(mp3);
  fseek(mp3,0,SEEK_SET);

  c1 = getc(mp3);
 //I
  c2 = getc(mp3);
 //D
  c3 = getc(mp3);
 //3
  c4 = getc(mp3);
 //ver
  c5 = getc(mp3);
 //ver

  if(!(c1 == 'I' && c2 == 'D' && c3 == '3' && (c4 == 2 || c4 == 3))) 
    return 0; //tag doesn't exist

  fseek(mp3,6,SEEK_SET);
  c1 = getc(mp3);
  c2 = getc(mp3);
  c3 = getc(mp3);
  c4 = getc(mp3);
  len = c1*2097152+c2*16384+c3*128+c4;


  if(len>flen)
    return 0; //tag corrupt

  // read tag: 
  tag=new char[len];

  fread(tag,sizeof(char),len,mp3);

  for(pos=0;pos<len;)
    {
       id[0] = *(tag+pos++);
       id[1] = *(tag+pos++);      
       id[2] = *(tag+pos++);
       id[3] = *(tag+pos++);

       framelen = 0;
       framelen += (*(tag+pos++))*2097152;
       framelen += (*(tag+pos++))*16384;
       framelen += (*(tag+pos++))*128;
       framelen += (*(tag+pos++));

       pos +=2; //skip 2 bytes

       if(framelen>len || pos+framelen > len) //Corrupt frame (There is a lot time this need to run :-( )
         break;  

       if(!strncmp(id,"",4)) //End of potential tags
         break;  

       if(!strncmp(id,markers[TITLE],4))  
        {
	   if(framelen>1)
	    { 
	      FREE_AND_ALLOC(Title,framelen);     
	      memcpy(Title,tag+pos+1,framelen-1); 
	      Title[framelen-1] = '\0';
	    }    
        }	      
       if(!strncmp(id,markers[ARTIST],4))  
        {
	   if(framelen>1)
	    { 
	      FREE_AND_ALLOC(Artist,framelen);     
	      memcpy(Artist,tag+pos+1,framelen-1); 
	      Artist[framelen-1] = '\0';
	    }    	   
        }	      
       if(!strncmp(id,markers[ALBUM],4))   
        {
	   if(framelen>1)
	    { 
	      FREE_AND_ALLOC(Album,framelen);     
	      memcpy(Album,tag+pos+1,framelen-1); 
	      Album[framelen-1] = '\0';
	    }    
        }	      
       if(!strncmp(id,markers[YEAR],4))    
        {
	   if(framelen>1)
	    { 
	      FREE_AND_ALLOC(Year,framelen);     
	      memcpy(Year,tag+pos+1,framelen-1); 
	      Year[framelen-1] = '\0';
	    }    
        }
       if(!strncmp(id,markers[COMM],4))    
        {
	   if(framelen>5)
	    { 
	      FREE_AND_ALLOC(Comment,framelen);     
	      memcpy(Comment,tag+pos+5,framelen-5); 
	      Comment[framelen-5] = '\0';
	    }    
        }	      
       pos += framelen;	
    } //end frame cycle
  delete [] tag;    
  return 1;     
}
 
/*********************************************************************
 *
 * Get some technical info from mp3
 * 
 * I used some code from:
 *   mp3guessenc version 0.2 by Naoki Shibata 
(shibatch.sf.net)

 *********************************************************************/
#define NUMTOCENTRIES 100

int GetVbrTag( unsigned char *buf);

#define MPG_MD_STEREO       0
#define MPG_MD_JOINT_STEREO 1
#define MPG_MD_DUAL_CHANNEL 2
#define MPG_MD_MONO         3

static long freqs[9] = { 44100, 48000, 32000,
			 22050, 24000, 16000 ,
			 11025 , 12000 , 8000 };
  
static int tabsel_123[2][3][16] = {
  { {128,32,64,96,128,160,192,224,256,288,320,352,384,416,448,},
    {128,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,},
    {128,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,} },

  { {128,32,48,56,64,80,96,112,128,144,160,176,192,224,256,},
    {128,8,16,24,32,40,48,56,64,80,96,112,128,144,160,},
    {128,8,16,24,32,40,48,56,64,80,96,112,128,144,160,} }
};

#define VBRHEADERSIZE (NUMTOCENTRIES+4+4+4+4+4+20)

int Gmode=0,Gbitrate=0,Gfreq=0,Gfsize=0,Glsf=0;

static const char VBRTag[]={"Xing"};

int GetVbrTag(unsigned char *buf)
{
	int  h_id, h_mode, h_sr_index;

	h_id       = (buf[1] >> 3) & 1;
	h_sr_index = (buf[2] >> 2) & 3;
	h_mode     = (buf[3] >> 6) & 3;

	if( h_id ) 
	{
	   if( h_mode != 3 )
	     buf+=(32+4);
	   else	
	     buf+=(17+4);
	}
	else
	{
	   if( h_mode != 3 ) 
	     buf+=(17+4);
	   else
	     buf+=(9+4);
	}
	
	if( buf[0] != VBRTag[0] ) return 0;
	if( buf[1] != VBRTag[1] ) return 0;
	if( buf[2] != VBRTag[2] ) return 0;
	if( buf[3] != VBRTag[3] ) return 0;
        return 1;
}

int head_check2(unsigned long head)
{
  int lsf,srate,nch,lay,mpeg25,freq,mode;
  int padding,bitrate_index,framesize;

  if ((head & 0xffe00000) != 0xffe00000) return 0;
  if (!((head>>17)&3)) return 0;
  if (((head>>12)&0xf) == 0xf) return 0;
  if (((head>>10)&0x3) == 0x3 ) return 0;
  //if ((head & 0xffff0000) == 0xfffe0000) return 0;

  if( head & (1<<20) ) 
   {
    lsf = (head & (1<<19)) ? 0x0 : 0x1;
    mpeg25 = 0;
   }
  else 
   {
    lsf = 1;
    mpeg25 = 1;
   }

   if(mpeg25) srate = 6 + ((head>>10)&0x3);
   else srate = ((head>>10)&0x3) + (lsf*3);

   bitrate_index = ((head>>12)&0xf);
   padding       = ((head>>9)&0x1);
   mode          = ((head>>6)&0x3);
   freq          = freqs[srate];
   nch           = (mode == MPG_MD_MONO) ? 1 : 2;
   lay           = 4-((head>>17)&3);

   Gmode   = mode;
   Gbitrate= tabsel_123[lsf][lay-1][bitrate_index];
   Gfreq   = freq;
   Glsf    = lsf;
  
  if (lay != 3) return 0;
  if (bitrate_index == 0) return -1; // free format bitstream
  framesize = tabsel_123[lsf][lay-1][bitrate_index]*144000/(freqs[srate]<<lsf)+padding;
  return framesize;
}


int resync_mp3(FILE *fp,int pos)
{
  int i;
  unsigned long head;

  for(i=0;i<65536;i++)
    {
      fseek(fp,pos+i,SEEK_SET);
      head = 0;
      head |= getc(fp); head = head << 8;
      head |= getc(fp); head = head << 8;
      head |= getc(fp); head = head << 8;
      head |= getc(fp);

      if (feof(fp)) return -1;
      if ((Gfsize=head_check2(head))) return pos+i;
    }

  return -1;
}

int ReadMp3Tag::technicalInfo(void)
{
  char retvalue[512];
  int  pos,time;
  long flength;

  fseek(mp3,0,SEEK_END);
  flength = ftell(mp3);
   
  fseek(mp3,0,SEEK_SET);
  pos = 0;

  {
    unsigned char c1,c2,c3,c4;
    c1 = getc(mp3);
    c2 = getc(mp3);
    c3 = getc(mp3);
    c4 = getc(mp3);
    if (c1 == 'I' && c2 == 'D' && c3 == '3' && c4 == 2) {
      fseek(mp3,6,SEEK_SET);
      c1 = getc(mp3);
      c2 = getc(mp3);
      c3 = getc(mp3);
      c4 = getc(mp3);
      pos = c1*2097152+c2*16384+c3*128+c4;
    }
  }

  // find first frame
  pos = resync_mp3(mp3,pos);

  if (pos == -1)
      return -1;

  {
    unsigned char buf[VBRHEADERSIZE+36];
    fseek(mp3,pos,SEEK_SET);
    fread(&buf,1,VBRHEADERSIZE+36,mp3);
    if (GetVbrTag(buf)) 
    {
       Gbitrate = 0;// VBR
    }
  }

  time = ((flength - pos) / Gfsize) * 576 * (Glsf?1:2) / Gfreq;

  if(Gbitrate == 0)
    sprintf(retvalue,"VBR,%dHz#%s",
                    Gfreq,
		    Gmode == 3 ? "Mono" : Gmode == 0 ? "Simple stereo" : Gmode == 1 ? "Joint stereo" : "Dual channel");
  else
    sprintf(retvalue,"%d:%d, %d kbps#%dHz, %s",
                    (int)(time/60),
		    time%60,
                    Gbitrate,
		    Gfreq,
		    Gmode == 3 ? "Mono" : Gmode == 0 ? "Simple stereo" : Gmode == 1 ? "Joint stereo" : "Dual channel");
		    
  techinfo=mmstr(retvalue);
  return 0;
}
/*end of file*/
