/*
 * NcdT -- directory tree printer with extended capabilities
 * (C) 1999-2001 by Pawel Wiecek <coven@vmh.net>
 * See Copying file for licence.
 * 
 * MP3 handler
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include "ncdt.h"

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

void mp3info(char *fname, struct TREE *p) {
 char buf[1024], *ptr;
 int ri, id, fd, vbr, vbroff;
 unsigned char head[4], th2[3], vbh[4];
 unsigned char mpeg25, lsf, layer, errp, bitrate, freq, pad, ext, mode,
               modext, copy, orig, emph, freq_;
 long fs, sfd, secs, off, len;
 unsigned vflags, vframes=0, vbytes=0;

 fd=open(fname,O_RDONLY);
 off=id=0;
 if(fd>=0) {
  read(fd,head,4);
  ri=!strncmp(head,"RIFF",4);
  if(ri) {	/* This is messy... RIFF headers might have various length (!)
		   and we of course don't know how to figure it out (actually
		   this shouldn't be very hard to do I guess -- RIFF is a
		   fairly simple format. Unless the difference is in the
		   format of the last chunk, which is of course entirely
		   possible). So all we can do is to scan the file until we
		   find its end... Hopefully it's within the first 1024 bytes
		   (1032 actually). Anyway, mpg123 does this in the same way
		   as we do. */
   read(fd,buf,1024);
   for(ptr=buf;(ptr<buf+1020)&&strncmp(ptr,"data",4);ptr++);
   if(!strncmp(ptr,"data",4)) {
    off=lseek(fd,ptr-buf+12,SEEK_SET);
    read(fd,head,4);
   }
  }
  if(!strncmp(head,"ID3\3",4)) {
   id=2;
   lseek(fd,2,SEEK_CUR);
   read(fd,head,4);
   /* what a messy way to encode tag length! */
   off=lseek(fd,(head[0]<<21)|(head[1]<<14)|(head[2]<<7)|(head[3]),SEEK_CUR);
   read(fd,head,4);
  }
  ri=!strncmp(head,"RIFF",4);
  if(ri) {	/* Might also happen here */
   read(fd,buf,1024);
   for(ptr=buf;(ptr<buf+1020)&&strncmp(ptr,"data",4);ptr++);
   if(!strncmp(ptr,"data",4)) {
    off=lseek(fd,off+ptr-buf+12,SEEK_SET);
    read(fd,head,4);
   }
  }
  if((head[0]==0xff)&&((head[1]&0xe0)==0xe0)) {
   mpeg25=(head[1]>>4)&1;
   if(mpeg25) lsf=(head[1]>>3)&1;
         else lsf=0;
   layer=(head[1]>>1)&3;
   errp=head[1]&1;
   bitrate=(head[2]>>4)&15;
   freq=(head[2]>>2)&3;
   pad=(head[2]>>1)&1;
   ext=head[2]&1;
   mode=(head[3]>>6)&3;
   modext=(head[3]>>4)&3;
   copy=(head[3]>>3)&1;
   orig=(head[3]>>2)&1;
   emph=head[3]&3;
   vbroff=((head[1]>>3)&1)?((mode==3)?17:32):((mode==3)?9:17);
   lseek(fd,vbroff,SEEK_CUR);
   read(fd,vbh,4);
   if(!strncmp(vbh,"Xing",4)) {
    vbr=1;
    read(fd,vbh,4);
    vflags=(vbh[0]<<24)|(vbh[1]<<16)|(vbh[2])<<8|vbh[3];
    if(vflags&1) {
     read(fd,vbh,4);
     vframes=(vbh[0]<<24)|(vbh[1]<<16)|(vbh[2])<<8|vbh[3];
    } else {
     vframes=1;
    }
    if(vflags&2) {
     read(fd,vbh,4);
     vbytes=(vbh[0]<<24)|(vbh[1]<<16)|(vbh[2])<<8|vbh[3];
    } else {
     vbytes=0;
    }
   } else {
    vbr=0;
   }
   len=lseek(fd,-128,SEEK_END)-off;
   read(fd,th2,3);
   if(!strncmp(th2,"TAG",3)) {
    if(!id) id=1;
   } else {
    len+=128;
   }
   freq_=mpeg25?(freq+3*!lsf):(6+freq);
   if(vbr) {
    p->brmin=(vbytes?vbytes:len)/vframes*freqs[freq_]/144000;
    sfd=freqs[freq_]*(2-lsf);
    secs=((vframes)*((layer==2)?384:1152)+sfd/2)/sfd;
   } else {
    p->brmin=tabsel_123[!lsf][3-layer][bitrate];
    fs=((((long)p->brmin*((layer==2)?12000:144000))/
	 (freqs[freq_]<<((!lsf)&&(layer==1)))+pad)<<(2*(layer==2)));
    sfd=freqs[freq_]*(2-lsf);
    secs=((len/fs)*((layer==2)?384:1152)+sfd/2)/sfd;
   }
   p->name=realloc(p->name,strlen(p->name)+24);
   sprintf(p->name+strlen(p->name),"  <%s %s%d%c%s%s>",nicetime(secs),
	   vbr?"v":"",p->brmin,modes[mode],ri?"R":"",id?((id==2)?"+":""):"-");
   p->time=secs;
   p->brmax=p->brmin;
  }
  close(fd);
 }
}
