nathan@0: /* nathan@0: * MP3/MPlayer plugin to VDR (C++) nathan@0: * nathan@0: * (C) 2001-2005 Stefan Huelswitt nathan@0: * nathan@0: * This code is free software; you can redistribute it and/or nathan@0: * modify it under the terms of the GNU General Public License nathan@0: * as published by the Free Software Foundation; either version 2 nathan@0: * of the License, or (at your option) any later version. nathan@0: * nathan@0: * This code is distributed in the hope that it will be useful, nathan@0: * but WITHOUT ANY WARRANTY; without even the implied warranty of nathan@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nathan@0: * GNU General Public License for more details. nathan@0: * nathan@0: * You should have received a copy of the GNU General Public License nathan@0: * along with this program; if not, write to the Free Software nathan@0: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. nathan@0: * Or, point your browser to http://www.gnu.org/copyleft/gpl.html nathan@0: */ nathan@0: nathan@0: #ifdef TEST_MAIN nathan@0: #define HAVE_SNDFILE nathan@0: #define REMOTE_LIRC nathan@0: #define _GNU_SOURCE nathan@0: #define DEBUG nathan@0: #endif nathan@0: nathan@0: #ifdef HAVE_SNDFILE nathan@0: nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: nathan@0: #include "common.h" nathan@0: #include "setup-mp3.h" nathan@0: #include "decoder-snd.h" nathan@0: #include "data.h" nathan@0: #include "network.h" nathan@0: #include "menu-async.h" nathan@0: #include "i18n.h" nathan@0: #include "version.h" nathan@0: nathan@0: #ifndef SNDFILE_1 nathan@0: #error You must use libsndfile version 1.x.x nathan@0: #endif nathan@0: nathan@0: #define CDFS_TRACK_OFF 150 nathan@0: #define CDFS_PROC "/proc/cdfs" nathan@0: #define CDFS_MARK_ID "CD (discid=%x) contains %d tracks:" nathan@0: #define CDFS_MARK_TR "%*[^[][ %d - %d" nathan@0: #define CDFS_TRACK "track-" nathan@0: nathan@0: #define CDDB_PROTO 5 // used protocol level nathan@0: #define CDDB_TOUT 30*1000 // connection timeout (ms) nathan@0: nathan@0: const char *cddbpath="/var/lib/cddb"; // default local cddb path nathan@0: nathan@0: #define CDDB_DEBUG // debug cddb queries nathan@0: //#define DEBUG_CDFS // debug cdfs parsing nathan@0: //#define GUARD_DEBUG // enable framebuffer guard nathan@0: nathan@0: #if !defined(NO_DEBUG) && defined(DEBUG_CDFS) nathan@0: #define dc(x) { (x); } nathan@0: #else nathan@0: #define dc(x) ; nathan@0: #endif nathan@0: nathan@0: #ifndef TEST_MAIN nathan@0: nathan@0: // --- cSndDecoder ------------------------------------------------------------- nathan@0: nathan@0: #define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t)) nathan@0: nathan@0: cSndDecoder::cSndDecoder(const char *Filename) nathan@0: :cDecoder(Filename) nathan@0: ,file(Filename) nathan@0: ,info(&file) nathan@0: { nathan@0: pcm=0; framebuff=0; playing=ready=false; nathan@0: } nathan@0: nathan@0: cSndDecoder::~cSndDecoder() nathan@0: { nathan@0: Clean(); nathan@0: } nathan@0: nathan@0: bool cSndDecoder::Valid(void) nathan@0: { nathan@0: bool res=false; nathan@0: if(TryLock()) { nathan@0: if(file.Open(false)) res=true; nathan@0: cDecoder::Unlock(); nathan@0: } nathan@0: return res; nathan@0: } nathan@0: nathan@0: cFileInfo *cSndDecoder::FileInfo(void) nathan@0: { nathan@0: cFileInfo *fi=0; nathan@0: if(file.HasInfo()) fi=&file; nathan@0: else if(TryLock()){ nathan@0: if(file.Open()) { fi=&file; file.Close(); } nathan@0: cDecoder::Unlock(); nathan@0: } nathan@0: return fi; nathan@0: } nathan@0: nathan@0: cSongInfo *cSndDecoder::SongInfo(bool get) nathan@0: { nathan@0: cSongInfo *si=0; nathan@0: if(info.HasInfo()) si=&info; nathan@0: else if(get && TryLock()) { nathan@0: if(info.DoScan(false)) si=&info; nathan@0: cDecoder::Unlock(); nathan@0: } nathan@0: return si; nathan@0: } nathan@0: nathan@0: cPlayInfo *cSndDecoder::PlayInfo(void) nathan@0: { nathan@0: if(playing) { nathan@0: pi.Index=index/info.SampleFreq; nathan@0: pi.Total=info.Total; nathan@0: return π nathan@0: } nathan@0: return 0; nathan@0: } nathan@0: nathan@0: void cSndDecoder::Init(void) nathan@0: { nathan@0: Clean(); nathan@0: pcm=new struct mad_pcm; nathan@0: framebuff=MALLOC(int,2*SF_SAMPLES+8); nathan@0: #ifdef GUARD_DEBUG nathan@0: for(int i=0; i<8; i++) framebuff[i+(SF_SAMPLES*2)-4]=0xdeadbeaf; nathan@0: #endif nathan@0: index=0; nathan@0: } nathan@0: nathan@0: bool cSndDecoder::Clean(void) nathan@0: { nathan@0: playing=false; nathan@0: nathan@0: buffMutex.Lock(); nathan@0: run=false; bgCond.Broadcast(); nathan@0: buffMutex.Unlock(); nathan@0: cThread::Cancel(3); nathan@0: nathan@0: buffMutex.Lock(); nathan@0: if(!ready) { deferedN=-1; ready=true; } nathan@0: fgCond.Broadcast(); nathan@0: buffMutex.Unlock(); nathan@0: nathan@0: delete pcm; pcm=0; nathan@0: #ifdef GUARD_DEBUG nathan@0: if(framebuff) { nathan@0: printf("snd: bufferguard"); nathan@0: for(int i=0; i<8; i++) printf(" %08x",framebuff[i+(SF_SAMPLES*2)-4]); nathan@0: printf("\n"); nathan@0: } nathan@0: #endif nathan@0: free(framebuff); framebuff=0; nathan@0: file.Close(); nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cSndDecoder::Start(void) nathan@0: { nathan@0: cDecoder::Lock(true); nathan@0: Init(); playing=true; nathan@0: if(file.Open() && info.DoScan(true)) { nathan@0: d(printf("snd: open rate=%d frames=%lld channels=%d format=0x%x seek=%d\n", nathan@0: file.sfi.samplerate,file.sfi.frames,file.sfi.channels,file.sfi.format,file.sfi.seekable)) nathan@0: if(file.sfi.channels<=2) { nathan@0: ready=false; run=true; softCount=0; nathan@0: cThread::Start(); nathan@0: cDecoder::Unlock(); nathan@0: return true; nathan@0: } nathan@0: else esyslog("ERROR: cannot play sound file %s: more than 2 channels",filename); nathan@0: } nathan@0: cDecoder::Unlock(); nathan@0: return Clean(); nathan@0: } nathan@0: nathan@0: bool cSndDecoder::Stop(void) nathan@0: { nathan@0: cDecoder::Lock(); nathan@0: if(playing) Clean(); nathan@0: cDecoder::Unlock(); nathan@0: return true; nathan@0: } nathan@0: nathan@0: void cSndDecoder::Action(void) nathan@0: { nathan@0: buffMutex.Lock(); nathan@0: while(run) { nathan@0: if(ready) bgCond.Wait(buffMutex); nathan@0: if(!ready) { nathan@0: buffMutex.Unlock(); nathan@0: deferedN=file.Stream(framebuff,SF_SAMPLES); nathan@0: buffMutex.Lock(); nathan@0: ready=true; fgCond.Broadcast(); nathan@0: } nathan@0: } nathan@0: buffMutex.Unlock(); nathan@0: } nathan@0: nathan@0: struct Decode *cSndDecoder::Done(eDecodeStatus status) nathan@0: { nathan@0: ds.status=status; nathan@0: ds.index=index*1000/info.SampleFreq; nathan@0: ds.pcm=pcm; nathan@0: cDecoder::Unlock(); // release the lock from Decode() nathan@0: return &ds; nathan@0: } nathan@0: nathan@0: struct Decode *cSndDecoder::Decode(void) nathan@0: { nathan@0: cDecoder::Lock(); // this is released in Done() nathan@0: if(playing) { nathan@0: cMutexLock lock(&buffMutex); nathan@0: while(!ready) nathan@0: if(!softCount || !fgCond.TimedWait(buffMutex,softCount*5)) { nathan@0: if(softCount<20) softCount++; nathan@0: return Done(dsSoftError); nathan@0: } nathan@0: softCount=0; nathan@0: ready=false; bgCond.Broadcast(); nathan@0: nathan@0: int n=deferedN; nathan@0: if(n<0) return Done(dsError); nathan@0: if(n==0) return Done(dsEof); nathan@0: nathan@0: pcm->samplerate=file.sfi.samplerate; nathan@0: pcm->channels=file.sfi.channels; nathan@0: pcm->length=n; nathan@0: index+=n; nathan@0: nathan@0: int *data=framebuff; nathan@0: mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1]; nathan@0: const int s=(sizeof(int)*8)-1-MAD_F_FRACBITS; // shift value for mad_fixed conversion nathan@0: if(pcm->channels>1) { nathan@0: for(; n>0 ; n--) { nathan@0: *sam0++=(*data++) >> s; nathan@0: *sam1++=(*data++) >> s; nathan@0: } nathan@0: } nathan@0: else { nathan@0: for(; n>0 ; n--) nathan@0: *sam0++=(*data++) >> s; nathan@0: } nathan@0: return Done(dsPlay); nathan@0: } nathan@0: return Done(dsError); nathan@0: } nathan@0: nathan@0: bool cSndDecoder::Skip(int Seconds, float bsecs) nathan@0: { nathan@0: cDecoder::Lock(); nathan@0: bool res=false; nathan@0: if(playing && file.sfi.seekable) { nathan@0: float fsecs=(float)Seconds-bsecs; nathan@0: sf_count_t frames=(sf_count_t)(fsecs*(float)file.sfi.samplerate); nathan@0: sf_count_t newpos=file.Seek(0,true)+frames; nathan@0: if(newpos>file.sfi.frames) newpos=file.sfi.frames-1; nathan@0: if(newpos<0) newpos=0; nathan@0: d(printf("snd: skip: secs=%d fsecs=%f frames=%lld current=%lld new=%lld\n",Seconds,fsecs,frames,file.Seek(0,true),newpos)) nathan@0: nathan@0: buffMutex.Lock(); nathan@0: frames=file.Seek(newpos,false); nathan@0: ready=false; bgCond.Broadcast(); nathan@0: buffMutex.Unlock(); nathan@0: if(frames>=0) { nathan@0: index=frames; nathan@0: #ifdef DEBUG nathan@0: int i=frames/file.sfi.samplerate; nathan@0: printf("snd: skipping to %02d:%02d (frame %lld)\n",i/60,i%60,frames); nathan@0: #endif nathan@0: res=true; nathan@0: } nathan@0: } nathan@0: cDecoder::Unlock(); nathan@0: return res; nathan@0: } nathan@0: nathan@0: #endif //TEST_MAIN nathan@0: nathan@0: // --- cDiscID ------------------------------------------------------------------- nathan@0: nathan@0: class cDiscID { nathan@0: public: nathan@0: int discid, ntrks, nsecs; nathan@0: int *offsets; nathan@0: // nathan@0: cDiscID(void); nathan@0: ~cDiscID(); nathan@0: bool Get(void); nathan@0: }; nathan@0: nathan@0: cDiscID::cDiscID(void) nathan@0: { nathan@0: offsets=0; discid=ntrks=0; nathan@0: } nathan@0: nathan@0: cDiscID::~cDiscID() nathan@0: { nathan@0: delete offsets; nathan@0: } nathan@0: nathan@0: bool cDiscID::Get(void) nathan@0: { nathan@0: bool res=false; nathan@0: FILE *f=fopen(CDFS_PROC,"r"); nathan@0: if(f) { nathan@0: char line[256]; nathan@0: bool state=false; nathan@0: int tr=0; nathan@0: while(fgets(line,sizeof(line),f)) { nathan@0: if(!state) { nathan@0: int id, n; nathan@0: if(sscanf(line,CDFS_MARK_ID,&id,&n)==2) { nathan@0: d(printf("discid: found id=%08x n=%d\n",id,n)) nathan@0: if(discid==id && ntrks==n) { nathan@0: res=true; nathan@0: break; nathan@0: } nathan@0: else { nathan@0: discid=id; ntrks=n; nathan@0: delete offsets; offsets=new int[ntrks]; nathan@0: state=true; nathan@0: } nathan@0: } nathan@0: } nathan@0: else { nathan@0: int off, end; nathan@0: if(sscanf(line,CDFS_MARK_TR,&off,&end)==2) { nathan@0: dc(printf("discid: found offset=%d end=%d for track %d\n",off,end,tr+1)) nathan@0: offsets[tr]=off+CDFS_TRACK_OFF; nathan@0: if(++tr==ntrks) { nathan@0: nsecs=(end+1)/75; nathan@0: dc(printf("discid: nsecs=%d / 0x%x\n",nsecs,nsecs)) nathan@0: res=true; nathan@0: break; nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: fclose(f); nathan@0: } nathan@0: return res; nathan@0: } nathan@0: nathan@0: // --- cCDDBSong --------------------------------------------------------------- nathan@0: nathan@0: // The CDDB code is loosely based on the implementation in mp3c 0.27 which is nathan@0: // (C) 1999-2001 WSPse, Matthias Hensler, nathan@0: nathan@0: class cCDDBSong : public cListObject { nathan@0: public: nathan@0: cCDDBSong(void); nathan@0: ~cCDDBSong(); nathan@0: // nathan@0: int Track; nathan@0: char *TTitle, *ExtT; nathan@0: char *Title, *Artist; nathan@0: }; nathan@0: nathan@0: cCDDBSong::cCDDBSong(void) nathan@0: { nathan@0: Title=Artist=0; TTitle=ExtT=0; nathan@0: } nathan@0: nathan@0: cCDDBSong::~cCDDBSong() nathan@0: { nathan@0: free(Title); nathan@0: free(Artist); nathan@0: free(TTitle); nathan@0: free(ExtT); nathan@0: } nathan@0: nathan@0: // --- cCDDBDisc --------------------------------------------------------------- nathan@0: nathan@0: const char *sampler[] = { // some artist names to identify sampler discs nathan@0: "various", nathan@0: "varios", nathan@0: "variété", nathan@0: "compilation", nathan@0: "sampler", nathan@0: "mixed", nathan@0: "divers", nathan@0: "v.a.", nathan@0: "VA", nathan@0: "misc", nathan@0: "none", nathan@0: 0 }; nathan@0: nathan@0: class cCDDBDisc : public cList { nathan@0: private: nathan@0: int DiscID; nathan@0: // nathan@0: bool isSampler; nathan@0: char *DTitle, *ExtD; nathan@0: // nathan@0: cCDDBSong *GetTrack(const char *name, unsigned int pos); nathan@0: cCDDBSong *FindTrack(int tr); nathan@0: void Strcat(char * &store, char *value); nathan@0: bool Split(const char *source, char div, char * &first, char * &second, bool only3=false); nathan@0: void Put(const char *from, char * &to); nathan@0: void Clean(void); nathan@0: public: nathan@0: cCDDBDisc(void); nathan@0: ~cCDDBDisc(); nathan@0: bool Load(cDiscID *id, const char *filename); nathan@0: bool Cached(cDiscID *id) { return DiscID==id->discid; } nathan@0: bool TrackInfo(int tr, cSongInfo *si); nathan@0: // nathan@0: char *Album, *Artist; nathan@0: int Year; nathan@0: }; nathan@0: nathan@0: cCDDBDisc::cCDDBDisc(void) nathan@0: { nathan@0: Album=Artist=0; DTitle=ExtD=0; DiscID=0; nathan@0: } nathan@0: nathan@0: cCDDBDisc::~cCDDBDisc() nathan@0: { nathan@0: Clean(); nathan@0: } nathan@0: nathan@0: void cCDDBDisc::Clean(void) nathan@0: { nathan@0: free(DTitle); DTitle=0; nathan@0: free(ExtD); ExtD=0; nathan@0: free(Artist); Artist=0; nathan@0: free(Album); Album=0; nathan@0: Year=-1; DiscID=0; isSampler=false; nathan@0: } nathan@0: nathan@0: bool cCDDBDisc::TrackInfo(int tr, cSongInfo *si) nathan@0: { nathan@0: cCDDBSong *s=FindTrack(tr); nathan@0: if(s) { nathan@0: Put(s->Title,si->Title); nathan@0: if(s->Artist) Put(s->Artist,si->Artist); else Put(Artist,si->Artist); nathan@0: Put(Album,si->Album); nathan@0: if(Year>0) si->Year=Year; nathan@0: return true; nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: void cCDDBDisc::Put(const char *from, char * &to) nathan@0: { nathan@0: free(to); nathan@0: to=from ? strdup(from):0; nathan@0: } nathan@0: nathan@0: bool cCDDBDisc::Load(cDiscID *id, const char *filename) nathan@0: { nathan@0: char *p; nathan@0: Clean(); Clear(); nathan@0: nathan@0: d(printf("cddb: loading discid %08x from %s\n",id->discid,filename)) nathan@0: DiscID=id->discid; nathan@0: FILE *f=fopen(filename,"r"); nathan@0: if(f) { nathan@0: char buff[1024]; nathan@0: while(fgets(buff,sizeof(buff),f)) { nathan@0: int i=strlen(buff); nathan@0: while(i && (buff[i-1]=='\n' || buff[i-1]=='\r')) buff[--i]=0; nathan@0: nathan@0: if(buff[0]=='#') { // special comment line handling nathan@0: } nathan@0: else { nathan@0: p=strchr(buff,'='); nathan@0: if(p) { nathan@0: *p=0; nathan@0: char *name =compactspace(buff); nathan@0: char *value=compactspace(p+1); nathan@0: if(*name && *value) { nathan@0: if(!strcasecmp(name,"DTITLE")) Strcat(DTitle,value); nathan@0: else if(!strcasecmp(name,"EXTD")) Strcat(ExtD,value); nathan@0: else if(!strcasecmp(name,"DYEAR")) Year=atoi(value); nathan@0: else if(!strncasecmp(name,"TTITLE",6)) { nathan@0: cCDDBSong *s=GetTrack(name,6); nathan@0: if(s) Strcat(s->TTitle,value); nathan@0: } nathan@0: else if(!strncasecmp(name,"EXTT",4)) { nathan@0: cCDDBSong *s=GetTrack(name,4); nathan@0: if(s) Strcat(s->ExtT,value); nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: } nathan@0: fclose(f); nathan@0: nathan@0: // read all data, now post-processing nathan@0: if(Count()>0) { nathan@0: if(DTitle) { nathan@0: if(Split(DTitle,'/',Artist,Album)) { nathan@0: for(int n=0 ; sampler[n] ; n++) nathan@0: if(!strncasecmp(Artist,sampler[n],strlen(sampler[n]))) { nathan@0: isSampler=true; nathan@0: break; nathan@0: } nathan@0: } nathan@0: else { nathan@0: Album=strdup(DTitle); nathan@0: isSampler=true; nathan@0: } nathan@0: } nathan@0: d(printf("cddb: found artist='%s' album='%s' isSampler=%d\n",Artist,Album,isSampler)) nathan@0: free(DTitle); DTitle=0; nathan@0: nathan@0: if(!isSampler && Artist && Album && !strncmp(Album,Artist,strlen(Artist))) { nathan@0: d(printf("cddb: detecting sampler from Artist==Album\n")) nathan@0: isSampler=true; nathan@0: } nathan@0: nathan@0: if(!isSampler) { nathan@0: int nofail1=0, nofail2=0; nathan@0: cCDDBSong *s=First(); nathan@0: while(s) { nathan@0: if(s->TTitle) { nathan@0: if(strstr(s->TTitle," / ")) nofail1++; nathan@0: //if(strstr(s->TTitle," - ")) nofail2++; nathan@0: } nathan@0: s=Next(s); nathan@0: } nathan@0: if(nofail1==Count() || nofail2==Count()) { nathan@0: d(printf("cddb: detecting sampler from nofail\n")) nathan@0: isSampler=true; nathan@0: } nathan@0: } nathan@0: nathan@0: if(Year<0 && ExtD && (p=strstr(ExtD,"YEAR:"))) Year=atoi(p+5); nathan@0: free(ExtD); ExtD=0; nathan@0: d(printf("cddb: found year=%d\n",Year)) nathan@0: nathan@0: cCDDBSong *s=First(); nathan@0: while(s) { nathan@0: if(s->TTitle) { nathan@0: if(isSampler) { nathan@0: if(!Split(s->TTitle,'/',s->Artist,s->Title) && nathan@0: !Split(s->TTitle,'-',s->Artist,s->Title,true)) { nathan@0: s->Title=compactspace(strdup(s->TTitle)); nathan@0: if(s->ExtT) s->Artist=compactspace(strdup(s->ExtT)); nathan@0: } nathan@0: } nathan@0: else { nathan@0: s->Title=compactspace(strdup(s->TTitle)); nathan@0: if(Artist) s->Artist=strdup(Artist); nathan@0: } nathan@0: } nathan@0: else s->Title=strdup(tr("unknown")); nathan@0: nathan@0: free(s->TTitle); s->TTitle=0; nathan@0: free(s->ExtT); s->ExtT=0; nathan@0: d(printf("cddb: found track %d title='%s' artist='%s'\n",s->Track,s->Title,s->Artist)) nathan@0: s=Next(s); nathan@0: } nathan@0: return true; nathan@0: } nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cCDDBDisc::Split(const char *source, char div, char * &first, char * &second, bool only3) nathan@0: { nathan@0: int pos=-1, n=0; nathan@0: char *p, l[4]={ ' ',div,' ',0 }; nathan@0: if ((p=strstr(source,l))) { pos=p-source; n=3; } nathan@0: else if(!only3 && (p=strchr(source,div))) { pos=p-source; n=1; } nathan@0: if(pos>=0) { nathan@0: free(first); first=strdup(source); first[pos]=0; compactspace(first); nathan@0: free(second); second=strdup(source+pos+n); compactspace(second); nathan@0: return true; nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: void cCDDBDisc::Strcat(char * &store, char *value) nathan@0: { nathan@0: if(store) { nathan@0: char *n=MALLOC(char,strlen(store)+strlen(value)+1); nathan@0: if(n) { nathan@0: strcpy(n,store); nathan@0: strcat(n,value); nathan@0: free(store); store=n; nathan@0: } nathan@0: } nathan@0: else store=strdup(value); nathan@0: } nathan@0: nathan@0: cCDDBSong *cCDDBDisc::GetTrack(const char *name, unsigned int pos) nathan@0: { nathan@0: cCDDBSong *s=0; nathan@0: if(strlen(name)>pos) { nathan@0: int tr=atoi(&name[pos]); nathan@0: s=FindTrack(tr); nathan@0: if(!s) { nathan@0: s=new cCDDBSong; nathan@0: Add(s); nathan@0: s->Track=tr; nathan@0: } nathan@0: } nathan@0: return s; nathan@0: } nathan@0: nathan@0: cCDDBSong *cCDDBDisc::FindTrack(int tr) nathan@0: { nathan@0: cCDDBSong *s=First(); nathan@0: while(s) { nathan@0: if(s->Track==tr) break; nathan@0: s=Next(s); nathan@0: } nathan@0: return s; nathan@0: } nathan@0: nathan@0: #ifndef TEST_MAIN nathan@0: nathan@0: // --- cCDDB ------------------------------------------------------------------- nathan@0: nathan@0: class cCDDB : public cScanDir, cMutex { nathan@0: private: nathan@0: cCDDBDisc cache; nathan@0: cFileSource *src; nathan@0: cFileObj *file; nathan@0: cNet *net; nathan@0: char searchID[10], cddbstr[256]; nathan@0: // nathan@0: virtual void DoItem(cFileSource *src, const char *subdir, const char *name); nathan@0: bool LocalQuery(cDiscID *id); nathan@0: bool RemoteGet(cDiscID *id); nathan@0: bool GetLine(char *buff, int size, bool log=true); nathan@0: int GetCddbResponse(void); nathan@6: int DoCddbCmd(const char *format, ...); nathan@0: public: nathan@0: cCDDB(void); nathan@0: virtual ~cCDDB(); nathan@0: bool Lookup(cDiscID *id, int track, cSongInfo *si); nathan@0: }; nathan@0: nathan@0: cCDDB cddb; nathan@0: nathan@0: cCDDB::cCDDB(void) nathan@0: { nathan@0: src=0; file=0; net=0; nathan@0: } nathan@0: nathan@0: cCDDB::~cCDDB() nathan@0: { nathan@0: delete file; nathan@0: delete src; nathan@0: delete net; nathan@0: } nathan@0: nathan@0: bool cCDDB::Lookup(cDiscID *id, int track, cSongInfo *si) nathan@0: { nathan@0: bool res=false; nathan@0: Lock(); nathan@0: if(!cache.Cached(id)) { nathan@0: if(LocalQuery(id) || (MP3Setup.UseCddb>1 && RemoteGet(id) && LocalQuery(id))) nathan@0: cache.Load(id,file->FullPath()); nathan@0: } nathan@0: if(cache.Cached(id) && cache.TrackInfo(track,si)) res=true; nathan@0: Unlock(); nathan@0: return res; nathan@0: } nathan@0: nathan@0: bool cCDDB::LocalQuery(cDiscID *id) nathan@0: { nathan@0: bool res=false; nathan@0: delete file; file=0; nathan@0: if(!src) src=new cFileSource(cddbpath,"CDDB database",false); nathan@0: if(src) { nathan@0: snprintf(searchID,sizeof(searchID),"%08x",id->discid); nathan@0: if(ScanDir(src,0,stDir,0,0,false) && file) res=true; nathan@0: } nathan@0: return res; nathan@0: } nathan@0: nathan@0: void cCDDB::DoItem(cFileSource *src, const char *subdir, const char *name) nathan@0: { nathan@0: if(!file) { nathan@0: file=new cFileObj(src,name,searchID,otFile); nathan@0: if(access(file->FullPath(),R_OK)) { delete file; file=0; } nathan@0: } nathan@0: } nathan@0: nathan@0: bool cCDDB::RemoteGet(cDiscID *id) nathan@0: { nathan@0: bool res=false; nathan@0: asyncStatus.Set(tr("Remote CDDB lookup...")); nathan@0: nathan@0: delete net; net=new cNet(16*1024,CDDB_TOUT,CDDB_TOUT); nathan@0: if(net->Connect(MP3Setup.CddbHost,MP3Setup.CddbPort)) { nathan@0: int code=GetCddbResponse(); nathan@0: if(code/100==2) { nathan@6: const char *host=getenv("HOSTNAME"); if(!host) host="unknown"; nathan@6: const char *user=getenv("USER"); if(!user) user="nobody"; nathan@0: code=DoCddbCmd("cddb hello %s %s %s %s\n",user,host,PLUGIN_NAME,PLUGIN_VERSION); nathan@0: if(code/100==2) { nathan@0: code=DoCddbCmd("proto %d\n",CDDB_PROTO); nathan@0: if(code>0) { nathan@0: char off[1024]; nathan@0: off[0]=0; for(int i=0 ; intrks ; i++) sprintf(&off[strlen(off)]," %d",id->offsets[i]); nathan@0: nathan@0: code=DoCddbCmd("cddb query %08x %d %s %d\n",id->discid,id->ntrks,off,id->nsecs); nathan@0: if(code/100==2) { nathan@0: char *cat=0; nathan@0: if(code==200) cat=strdup(cddbstr); nathan@0: else if(code==210) { nathan@0: if(GetLine(off,sizeof(off))) { nathan@0: cat=strdup(off); nathan@0: while(GetLine(off,sizeof(off)) && off[0]!='.'); nathan@0: } nathan@0: } nathan@0: nathan@0: if(cat) { nathan@0: char *s=index(cat,' '); if(s) *s=0; nathan@0: code=DoCddbCmd("cddb read %s %08x\n",cat,id->discid); nathan@0: if(code==210) { nathan@0: char *name=0; nathan@0: asprintf(&name,"%s/%s/%08x",cddbpath,cat,id->discid); nathan@0: if(MakeDirs(name,false)) { nathan@0: FILE *out=fopen(name,"w"); nathan@0: if(out) { nathan@0: while(GetLine(off,sizeof(off),false) && off[0]!='.') fputs(off,out); nathan@0: fclose(out); nathan@0: res=true; nathan@0: } nathan@0: else esyslog("fopen() failed: %s",strerror(errno)); nathan@0: } nathan@0: free(name); nathan@0: } nathan@0: else if(code>0) esyslog("server read error: %d %s",code,cddbstr); nathan@0: free(cat); nathan@0: } nathan@0: } nathan@0: else if(code>0) esyslog("server query error: %d %s",code,cddbstr); nathan@0: } nathan@0: else esyslog("server proto error: %d %s",code,cddbstr); nathan@0: } nathan@0: else if(code>0) esyslog("server hello error: %d %s",code,cddbstr); nathan@0: } nathan@0: else if(code>0) esyslog("server sign-on error: %d %s",code,cddbstr); nathan@0: } nathan@0: nathan@0: delete net; net=0; nathan@0: asyncStatus.Set(0); nathan@0: return res; nathan@0: } nathan@0: nathan@0: bool cCDDB::GetLine(char *buff, int size, bool log) nathan@0: { nathan@0: if(net->Gets(buff,size)>0) { nathan@0: #ifdef CDDB_DEBUG nathan@0: if(log) printf("cddb: <- %s",buff); nathan@0: #endif nathan@0: return true; nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: int cCDDB::GetCddbResponse(void) nathan@0: { nathan@0: char buf[1024]; nathan@0: if(GetLine(buf,sizeof(buf))) { nathan@0: int code; nathan@0: if(sscanf(buf,"%d %255[^\n]",&code,cddbstr)==2) return code; nathan@0: else esyslog("Unexpected server response: %s",buf); nathan@0: } nathan@0: return -1; nathan@0: } nathan@0: nathan@6: int cCDDB::DoCddbCmd(const char *format, ...) nathan@0: { nathan@0: va_list ap; nathan@0: va_start(ap,format); nathan@0: char *buff=0; nathan@0: vasprintf(&buff,format,ap); nathan@0: va_end(ap); nathan@0: #ifdef CDDB_DEBUG nathan@0: printf("cddb: -> %s",buff); nathan@0: #endif nathan@0: int r=net->Puts(buff); nathan@0: free(buff); nathan@0: if(r<0) return -1; nathan@0: return GetCddbResponse(); nathan@0: } nathan@0: nathan@0: // --- cSndInfo ---------------------------------------------------------------- nathan@0: nathan@0: cSndInfo::cSndInfo(cSndFile *File) nathan@0: { nathan@0: file=File; nathan@0: id=new cDiscID; nathan@0: } nathan@0: nathan@0: cSndInfo::~cSndInfo() nathan@0: { nathan@0: delete id; nathan@0: } nathan@0: nathan@0: bool cSndInfo::Abort(bool result) nathan@0: { nathan@0: if(!keepOpen) file->Close(); nathan@0: return result; nathan@0: } nathan@0: nathan@0: bool cSndInfo::DoScan(bool KeepOpen) nathan@0: { nathan@0: keepOpen=KeepOpen; nathan@0: if(!file->Open()) return Abort(false); nathan@0: if(HasInfo()) return Abort(true); nathan@0: nathan@0: // check the infocache nathan@0: cCacheData *dat=InfoCache.Search(file); nathan@0: if(dat) { nathan@0: Set(dat); dat->Unlock(); nathan@0: if(!DecoderID) { nathan@0: DecoderID=DEC_SND; nathan@0: InfoCache.Cache(this,file); nathan@0: } nathan@0: return Abort(true); nathan@0: } nathan@0: nathan@0: Clear(); nathan@0: nathan@0: if(file->FsType!=CDFS_MAGIC || !MP3Setup.UseCddb || !CDDBLookup(file->Filename)) nathan@0: FakeTitle(file->Filename); nathan@0: nathan@0: Frames=file->sfi.frames; nathan@0: SampleFreq=file->sfi.samplerate; nathan@0: Channels=file->sfi.channels; nathan@0: ChMode=Channels>1 ? 3:0; nathan@0: Total=Frames/SampleFreq; nathan@0: Bitrate=file->Filesize*8/Total; //XXX SampleFreq*Channels*file->sfi.pcmbitwidth; nathan@0: DecoderID=DEC_SND; nathan@0: nathan@0: InfoDone(); nathan@0: InfoCache.Cache(this,file); nathan@0: return Abort(true); nathan@0: } nathan@0: nathan@0: bool cSndInfo::CDDBLookup(const char *filename) nathan@0: { nathan@0: if(id->Get()) { nathan@0: int tr; nathan@0: char *s=strstr(filename,CDFS_TRACK); nathan@0: if(s && sscanf(s+strlen(CDFS_TRACK),"%d",&tr)==1) { nathan@0: d(printf("snd: looking up disc id %08x track %d\n",id->discid,tr)) nathan@0: return cddb.Lookup(id,tr-1,this); nathan@0: } nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: // --- cSndFile ---------------------------------------------------------------- nathan@0: nathan@0: cSndFile::cSndFile(const char *Filename) nathan@0: :cFileInfo(Filename) nathan@0: { nathan@0: sf=0; nathan@0: } nathan@0: nathan@0: cSndFile::~cSndFile() nathan@0: { nathan@0: Close(); nathan@0: } nathan@0: nathan@0: bool cSndFile::Open(bool log) nathan@0: { nathan@0: if(sf) return (Seek()>=0); nathan@0: nathan@0: if(FileInfo(log)) { nathan@0: sf=sf_open(Filename,SFM_READ,&sfi); nathan@0: if(!sf && log) Error("open"); nathan@0: } nathan@0: return (sf!=0); nathan@0: } nathan@0: nathan@0: void cSndFile::Close(void) nathan@0: { nathan@0: if(sf) { sf_close(sf); sf=0; } nathan@0: } nathan@0: nathan@0: void cSndFile::Error(const char *action) nathan@0: { nathan@0: char buff[128]; nathan@0: sf_error_str(sf,buff,sizeof(buff)); nathan@0: esyslog("ERROR: sndfile %s failed on %s: %s",action,Filename,buff); nathan@0: } nathan@0: nathan@0: sf_count_t cSndFile::Seek(sf_count_t frames, bool relativ) nathan@0: { nathan@0: int dir=SEEK_CUR; nathan@0: if(!relativ) dir=SEEK_SET; nathan@0: int n=sf_seek(sf,frames,dir); nathan@0: if(n<0) Error("seek"); nathan@0: return n; nathan@0: } nathan@0: nathan@0: sf_count_t cSndFile::Stream(int *buffer, sf_count_t frames) nathan@0: { nathan@0: sf_count_t n=sf_readf_int(sf,buffer,frames); nathan@0: if(n<0) Error("read"); nathan@0: return n; nathan@0: } nathan@0: nathan@0: #endif //TEST_MAIN nathan@0: #endif //HAVE_SNDFILE nathan@0: nathan@0: #ifdef TEST_MAIN nathan@0: // nathan@0: // to compile: nathan@0: // g++ -g -DTEST_MAIN -o test mp3-decoder-snd.c tools.o thread.o -lpthread nathan@0: // nathan@0: // calling: nathan@0: // test nathan@0: // nathan@0: nathan@0: extern const char *tr(const char *test) nathan@0: { nathan@0: return test; nathan@0: } nathan@0: nathan@0: int main (int argc, char *argv[]) nathan@0: { nathan@0: cCDDBDisc cddb; nathan@0: nathan@0: cddb.Load(1,argv[1]); nathan@0: return 0; nathan@0: } nathan@0: #endif