nathan@0: /* nathan@0: * MP3/MPlayer plugin to VDR (C++) nathan@0: * nathan@0: * (C) 2001-2006 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: #include nathan@0: #include nathan@0: #include nathan@0: nathan@0: #include "common.h" nathan@0: #include "data-mp3.h" nathan@0: #include "data.h" nathan@0: #include "decoder.h" nathan@0: nathan@0: #define DEBUG_IMAGE nathan@0: nathan@0: #ifdef DEBUG_IMAGE nathan@0: #define di(x) { (x); } nathan@0: #else nathan@0: #define di(x) ; nathan@0: #endif nathan@0: nathan@0: const char *imagecache = "/var/cache/images/mp3"; nathan@0: const char *imageconv = "image_convert.sh"; nathan@0: nathan@0: // image suffixes to search nathan@0: const char *img_suff[] = { "jpg","png","gif",0 }; nathan@0: // exclude list for instant playlist creation nathan@0: const char *excl_pl[] = { "*"PLAYLISTEXT,"*.jpg","*.gif","*.png",0 }; nathan@0: // exclude list for song browser nathan@0: const char *excl_br[] = { ".*","*.jpg","*.gif","*.png",0 }; nathan@0: nathan@0: // --- cImageConvert ----------------------------------------------------------- nathan@0: nathan@0: class cImageConvert : private cThread { nathan@0: private: nathan@0: char *image; nathan@0: enum eStatus { stNone, stRun, stFin }; nathan@0: eStatus status; nathan@0: protected: nathan@0: virtual void Action(void); nathan@0: public: nathan@0: cImageConvert(void); nathan@0: ~cImageConvert(); nathan@0: bool Convert(const char *Image); nathan@0: bool Status(void); nathan@0: }; nathan@0: nathan@0: cImageConvert::cImageConvert(void) nathan@0: { nathan@0: image=0; status=stNone; nathan@0: } nathan@0: nathan@0: cImageConvert::~cImageConvert() nathan@0: { nathan@0: if(status==stRun) Cancel(10); nathan@0: free(image); nathan@0: } nathan@0: nathan@0: bool cImageConvert::Convert(const char *Image) nathan@0: { nathan@0: if(status==stNone) { nathan@0: image=strdup(Image); nathan@0: status=stRun; nathan@0: Start(); nathan@0: return true; nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cImageConvert::Status(void) nathan@0: { nathan@0: if(status==stRun && !Active()) status=stFin; nathan@0: return status==stFin; nathan@0: } nathan@0: nathan@0: void cImageConvert::Action(void) nathan@0: { nathan@0: nice(3); nathan@0: char *m, *cmd, *qp, *qm; nathan@0: asprintf(&m,"%s%s.mpg",imagecache,image); nathan@0: di(printf("image: convert started %s -> %s\n",image,m)) nathan@0: asprintf(&cmd,"%s \"%s\" \"%s\"",imageconv,qp=Quote(image),qm=Quote(m)); nathan@0: int r=system(cmd); nathan@0: if(r!=0) di(printf("image: convert returned with code %d. Failed?\n",r)) nathan@0: free(cmd); free(qp); free(qm); free(m); nathan@0: di(printf("image: convert finished\n")) nathan@0: status=stFin; nathan@0: } nathan@0: nathan@0: // --- cSong ------------------------------------------------------------------- nathan@0: nathan@0: cSong::cSong(cFileObj *Obj) nathan@0: { nathan@0: obj=new cFileObj(Obj); nathan@0: Init(); nathan@0: } nathan@0: nathan@0: cSong::cSong(cFileSource *Source, const char *Subdir, const char *Name) nathan@0: { nathan@0: obj=new cFileObj(Source,Subdir,Name,otFile); nathan@0: Init(); nathan@0: } nathan@0: nathan@0: cSong::cSong(cSong *Song) nathan@0: { nathan@0: obj=new cFileObj(Song->obj); nathan@0: Init(); nathan@0: } nathan@0: nathan@0: cSong::~cSong() nathan@0: { nathan@0: delete conv; nathan@0: delete decoder; nathan@0: obj->Source()->Unblock(); nathan@0: delete obj; nathan@0: free((char *)image); nathan@0: } nathan@0: nathan@0: void cSong::Init(void) nathan@0: { nathan@0: decoder=0; user=0; image=0; conv=0; queueStat=0; nathan@0: fromDOS=decoderFailed=false; nathan@0: obj->Source()->Block(); nathan@0: } nathan@0: nathan@0: #if APIVERSNUM >= 10315 nathan@0: int cSong::Compare(const cListObject &ListObject) const nathan@0: #else nathan@0: bool cSong::operator<(const cListObject &ListObject) nathan@0: #endif nathan@0: { nathan@0: cSong *song=(cSong *)&ListObject; nathan@0: #if APIVERSNUM >= 10315 nathan@0: return strcasecmp(obj->Path(),song->obj->Path()); nathan@0: #else nathan@0: return strcasecmp(obj->Path(),song->obj->Path())<0; nathan@0: #endif nathan@0: } nathan@0: nathan@0: cSongInfo *cSong::Info(bool get) nathan@0: { nathan@0: Decoder(); nathan@0: cSongInfo *si=0; nathan@0: if(decoder) si=decoder->SongInfo(get); nathan@0: return si; nathan@0: } nathan@0: nathan@0: cDecoder *cSong::Decoder(void) nathan@0: { nathan@0: decLock.Lock(); nathan@0: if(!decoder && !decoderFailed) { nathan@0: decoder=cDecoders::FindDecoder(obj); nathan@0: if(!decoder) decoderFailed=true; nathan@0: } nathan@0: decLock.Unlock(); nathan@0: return decoder; nathan@0: } nathan@0: nathan@0: void cSong::Convert(void) nathan@0: { nathan@0: char *Name=Convert2Unix(obj->Name()); nathan@0: obj->SetName(Name); nathan@0: fromDOS=true; nathan@0: free(Name); nathan@0: } nathan@0: nathan@0: char *cSong::Convert2Unix(const char *name) const nathan@0: { nathan@0: char *Name=strdup(name); nathan@0: char *p=Name; nathan@0: while(*p) { nathan@0: if(*p=='/') *p='?'; nathan@0: if(*p=='\\') *p='/'; nathan@0: p++; nathan@0: } nathan@0: return Name; nathan@0: } nathan@0: nathan@0: /* nathan@0: char *cSong::Convert2Dos(const char *name) nathan@0: { nathan@0: char *Name=strdup(name); nathan@0: char *p=Name; nathan@0: while(*p) { nathan@0: if(*p=='\\') *p='?'; nathan@0: if(*p=='/') *p='\\'; nathan@0: p++; nathan@0: } nathan@0: return Name; nathan@0: } nathan@0: */ nathan@0: nathan@0: bool cSong::Parse(char *s, const char *reldir) const nathan@0: { nathan@0: s=skipspace(stripspace(s)); nathan@0: if(*s) { nathan@0: if(s[0]=='/' || !reldir) nathan@0: obj->SplitAndSet(s); nathan@0: else { nathan@0: s=AddPath(reldir,s); nathan@0: obj->SplitAndSet(s); nathan@0: free(s); nathan@0: } nathan@0: return true; nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cSong::Save(FILE *f, const char *reldir) const nathan@0: { nathan@0: const char *path=obj->Path(); nathan@0: if(reldir) { nathan@0: int l=strlen(reldir); nathan@0: if(!strncasecmp(path,reldir,l)) path+=l+1; nathan@0: } nathan@0: return fprintf(f,"%s\n",path)>0; nathan@0: } nathan@0: nathan@0: bool cSong::FindImage(void) nathan@0: { nathan@0: if(image) return true; nathan@0: nathan@0: char base[strlen(obj->Path())+32]; nathan@0: strcpy(base,obj->Path()); nathan@0: di(printf("image: checking image for %s\n",obj->Path())) nathan@0: nathan@0: // song specific image nathan@0: char *m=rindex(base,'.'); nathan@0: if(m) *m=0; nathan@0: if((image=CheckImage(base))) return true; nathan@0: nathan@0: // album specific image in song directory nathan@0: if(!(m=rindex(base,'/'))) m=base-1; nathan@0: strcpy(m+1,"cover"); nathan@0: if((image=CheckImage(base))) return true; nathan@0: nathan@0: // artist specific image in parent directory nathan@0: if((m=rindex(base,'/'))) { nathan@0: *m=0; nathan@0: if(!(m=rindex(base,'/'))) m=base-1; nathan@0: strcpy(m+1,"artist"); nathan@0: if((image=CheckImage(base))) return true; nathan@0: } nathan@0: nathan@0: // default image in source basedir nathan@0: if((image=CheckImage("background"))) return true; nathan@0: nathan@0: di(printf("image: no image for %s\n",obj->Path())) nathan@0: return false; nathan@0: } nathan@0: nathan@0: const char *cSong::CheckImage(const char *base) const nathan@0: { nathan@0: char *p; nathan@0: int n; nathan@0: asprintf(&p,"%s/%s.%n ",obj->Source()->BaseDir(),base,&n); nathan@0: for(const char **s=img_suff; *s; s++) { nathan@0: #ifdef DEBUG nathan@0: if(strlen(*s)>5) printf("ERROR: buffer overflow in CheckImage ext=%s\n",*s); nathan@0: #endif nathan@0: strcpy(&p[n],*s); nathan@0: di(printf("image: check %s\n",p)) nathan@0: if(!access(p,R_OK)) { nathan@0: di(printf("image: found\n")) nathan@0: return p; nathan@0: } nathan@0: } nathan@0: free(p); nathan@0: return 0; nathan@0: } nathan@0: nathan@0: #include "data-mp3-image.c" nathan@0: extern void PropagateImage(const char *image); nathan@0: nathan@0: bool cSong::Image(unsigned char * &mem, int &len) nathan@0: { nathan@0: mem=0; nathan@0: if(queueStat>0) { nathan@0: if(!conv->Status()) { nathan@0: di(printf("image: still queued\n")) nathan@0: return false; nathan@0: } nathan@0: queueStat=-1; nathan@0: delete conv; conv=0; nathan@0: } nathan@0: nathan@0: int res=0; nathan@0: if(image || FindImage()) { nathan@0: di(printf("image: loading image %s\n",image)) nathan@0: char *m; nathan@0: asprintf(&m,"%s%s.mpg",imagecache,image); nathan@0: if(access(m,R_OK)) { nathan@0: di(printf("image: not cached\n")) nathan@0: if(queueStat<0) { nathan@0: di(printf("image: obviously convert failed...\n")) nathan@0: } nathan@0: else { nathan@0: if(!conv) conv=new cImageConvert; nathan@0: if(conv && conv->Convert(image)) { nathan@0: di(printf("image: convert queued\n")) nathan@0: queueStat=1; nathan@0: res=-1; nathan@0: } nathan@0: else { nathan@0: di(printf("image: queueing failed\n")) nathan@0: queueStat=-1; nathan@0: } nathan@0: } nathan@0: } nathan@0: else { nathan@0: di(printf("image: cached\n")) nathan@0: int f=open(m,O_RDONLY); nathan@0: if(f>=0) { nathan@0: struct stat64 st; nathan@0: fstat64(f,&st); nathan@0: len=st.st_size; nathan@0: mem=MALLOC(unsigned char,len); nathan@0: if(mem) { nathan@0: if(read(f,mem,len)==len) res=1; nathan@0: else free(mem); nathan@0: } nathan@0: close(f); nathan@0: } nathan@0: } nathan@0: free(m); nathan@0: } nathan@0: nathan@0: PropagateImage(res==1 ? image : 0); nathan@0: nathan@0: if(res<=0) { nathan@0: di(printf("image: using static default image\n")) nathan@0: len=sizeof(defaultImage); nathan@0: mem=MALLOC(unsigned char,len); nathan@0: if(mem) { nathan@0: memcpy(mem,defaultImage,len); nathan@0: } nathan@0: } nathan@0: return res>=0; nathan@0: } nathan@0: nathan@0: // -- cPlayList -------------------------------------------------------------- nathan@0: nathan@0: cPlayList::cPlayList(cFileObj *Obj) nathan@0: { nathan@0: obj=new cFileObj(Obj); nathan@0: Init(); nathan@0: } nathan@0: nathan@0: cPlayList::cPlayList(cFileSource *Source, const char *Subdir, const char *Name) nathan@0: { nathan@0: obj=new cFileObj(Source,Subdir,Name,otFile); nathan@0: Init(); nathan@0: } nathan@0: nathan@0: cPlayList::cPlayList(cPlayList *List) nathan@0: { nathan@0: obj=new cFileObj(List->obj); nathan@0: Init(); nathan@0: } nathan@0: nathan@0: cPlayList::~cPlayList() nathan@0: { nathan@0: free(basename); nathan@0: free(extbuffer); nathan@0: obj->Source()->Unblock(); nathan@0: delete obj; nathan@0: } nathan@0: nathan@0: void cPlayList::Init(void) nathan@0: { nathan@0: extbuffer=basename=0; nathan@0: isWinAmp=false; nathan@0: obj->Source()->Block(); nathan@0: Set(); nathan@0: } nathan@0: nathan@0: void cPlayList::Set(void) nathan@0: { nathan@0: free(basename); basename=0; nathan@0: if(obj->Name()) { nathan@0: basename=strdup(obj->Name()); nathan@0: int l=strlen(basename)-strlen(PLAYLISTEXT); nathan@0: if(l>0 && !strcasecmp(basename+l,PLAYLISTEXT)) basename[l]=0; nathan@0: } nathan@0: } nathan@0: nathan@0: #if APIVERSNUM >= 10315 nathan@0: int cPlayList::Compare(const cListObject &ListObject) const nathan@0: #else nathan@0: bool cPlayList::operator<(const cListObject &ListObject) nathan@0: #endif nathan@0: { nathan@0: cPlayList *list=(cPlayList *)&ListObject; nathan@0: #if APIVERSNUM >= 10315 nathan@0: return strcasecmp(obj->Name(),list->obj->Name()); nathan@0: #else nathan@0: return strcasecmp(obj->Name(),list->obj->Name())<0; nathan@0: #endif nathan@0: } nathan@0: nathan@0: bool cPlayList::Load(void) nathan@0: { nathan@0: Clear(); nathan@0: bool result=false; nathan@0: FILE *f=fopen(obj->FullPath(),"r"); nathan@0: if(f) { nathan@0: char buffer[512]; nathan@0: result=true; nathan@0: while(fgets(buffer,sizeof(buffer),f)>0) { nathan@0: if(buffer[0]=='#') { nathan@0: if(!strncmp(buffer,WINAMPEXT,strlen(WINAMPEXT))) { nathan@0: d(printf("mp3: detected WinAmp style playlist\n")) nathan@0: isWinAmp=true; nathan@0: } nathan@0: continue; nathan@0: } nathan@0: if(!isempty(buffer)) { nathan@0: cSong *song=new cSong(obj->Source(),0,0); nathan@0: if(song->Parse(buffer,obj->Subdir())) Add(song); nathan@0: else { nathan@0: esyslog("error loading playlist %s\n",obj->FullPath()); nathan@0: delete song; nathan@0: result=false; nathan@0: break; nathan@0: } nathan@0: } nathan@0: } nathan@0: fclose(f); nathan@0: } nathan@0: else LOG_ERROR_STR(obj->FullPath()); nathan@0: nathan@0: if(result && isWinAmp) { nathan@0: cSong *song=First(); nathan@0: while(song) { // if this is a WinAmp playlist, convert \ to / nathan@0: song->Convert(); nathan@0: song=cList::Next(song); nathan@0: } nathan@0: } nathan@0: return result; nathan@0: } nathan@0: nathan@0: bool cPlayList::Save(void) nathan@0: { nathan@0: bool result=true; nathan@0: cSafeFile f(obj->FullPath()); nathan@0: if(f.Open()) { nathan@0: cSong *song=First(); nathan@0: while(song) { nathan@0: if(!song->Save(f,obj->Subdir())) { nathan@0: result=false; nathan@0: break; nathan@0: } nathan@0: song=cList::Next(song); nathan@0: } nathan@0: if(!f.Close()) result=false; nathan@0: } nathan@0: else result=false; nathan@0: return result; nathan@0: } nathan@0: nathan@0: bool cPlayList::Exists(void) nathan@0: { nathan@0: return obj->Exists(); nathan@0: } nathan@0: nathan@0: bool cPlayList::TestName(const char *newName) nathan@0: { nathan@0: return obj->TestName(AddExt(newName,PLAYLISTEXT)); nathan@0: } nathan@0: nathan@0: bool cPlayList::Rename(const char *newName) nathan@0: { nathan@0: bool r=obj->Rename(AddExt(newName,PLAYLISTEXT)); nathan@0: if(r) Set(); nathan@0: return r; nathan@0: } nathan@0: nathan@0: bool cPlayList::Create(const char *newName) nathan@0: { nathan@0: bool r=obj->Create(AddExt(newName,PLAYLISTEXT)); nathan@0: if(r) { nathan@0: Set(); nathan@0: r=Load(); nathan@0: } nathan@0: return r; nathan@0: } nathan@0: nathan@0: bool cPlayList::Delete(void) nathan@0: { nathan@0: return obj->Delete(); nathan@0: } nathan@0: nathan@0: const char *cPlayList::AddExt(const char *FileName, const char *Ext) nathan@0: { nathan@0: free(extbuffer); extbuffer=0; nathan@0: asprintf(&extbuffer,"%s%s",FileName,Ext); nathan@0: return extbuffer; nathan@0: } nathan@0: nathan@0: // -- cInstantPlayList ------------------------------------------------------ nathan@0: nathan@0: cInstantPlayList::cInstantPlayList(cFileObj *Obj) nathan@0: :cPlayList(Obj) nathan@0: { nathan@0: if(!Obj->Name()) Obj->SetName("instant"); nathan@0: } nathan@0: nathan@0: bool cInstantPlayList::Load(void) nathan@0: { nathan@0: bool res=false; nathan@0: Clear(); nathan@0: switch(obj->Type()) { nathan@0: case otFile: nathan@0: d(printf("instant: file %s\n",obj->Name())) nathan@0: if(strcasecmp(obj->Name(),basename)) { nathan@0: d(printf("instant: detected as playlist\n")) nathan@0: res=cPlayList::Load(); nathan@0: } nathan@0: else { nathan@0: Add(new cSong(obj)); nathan@0: res=true; nathan@0: } nathan@0: break; nathan@0: case otDir: nathan@0: { nathan@0: d(printf("instant: dir %s\n",obj->Name())) nathan@0: res=ScanDir(obj->Source(),obj->Path(),stFile,obj->Source()->Include(),excl_pl,true); nathan@0: Sort(); nathan@0: break; nathan@0: } nathan@0: case otBase: nathan@0: d(printf("instant: base\n")) nathan@0: res=ScanDir(obj->Source(),0,stFile,obj->Source()->Include(),excl_pl,true); nathan@0: Sort(); nathan@0: break; nathan@0: default: break; nathan@0: } nathan@0: return res; nathan@0: } nathan@0: nathan@0: void cInstantPlayList::DoItem(cFileSource *src, const char *subdir, const char *name) nathan@0: { nathan@0: Add(new cSong(src,subdir,name)); nathan@0: } nathan@0: nathan@0: // -- cPlayLists -------------------------------------------------------------- nathan@0: nathan@0: bool cPlayLists::Load(cFileSource *Source) nathan@0: { nathan@0: static const char *spec[] = { "*"PLAYLISTEXT,0 }; nathan@0: Clear(); nathan@0: bool res=ScanDir(Source,0,stFile,spec,0,false); nathan@0: Sort(); nathan@0: return res; nathan@0: } nathan@0: nathan@0: void cPlayLists::DoItem(cFileSource *src, const char *subdir, const char *name) nathan@0: { nathan@0: Add(new cPlayList(src,subdir,name)); nathan@0: }