|
1 /* |
|
2 * MP3/MPlayer plugin to VDR (C++) |
|
3 * |
|
4 * (C) 2001-2006 Stefan Huelswitt <s.huelswitt@gmx.de> |
|
5 * |
|
6 * This code is free software; you can redistribute it and/or |
|
7 * modify it under the terms of the GNU General Public License |
|
8 * as published by the Free Software Foundation; either version 2 |
|
9 * of the License, or (at your option) any later version. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, |
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 * GNU General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU General Public License |
|
17 * along with this program; if not, write to the Free Software |
|
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
19 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html |
|
20 */ |
|
21 |
|
22 #include <stdlib.h> |
|
23 #include <stdio.h> |
|
24 #include <unistd.h> |
|
25 |
|
26 #include "common.h" |
|
27 #include "data-mp3.h" |
|
28 #include "data.h" |
|
29 #include "decoder.h" |
|
30 |
|
31 #define DEBUG_IMAGE |
|
32 |
|
33 #ifdef DEBUG_IMAGE |
|
34 #define di(x) { (x); } |
|
35 #else |
|
36 #define di(x) ; |
|
37 #endif |
|
38 |
|
39 const char *imagecache = "/var/cache/images/mp3"; |
|
40 const char *imageconv = "image_convert.sh"; |
|
41 |
|
42 // image suffixes to search |
|
43 const char *img_suff[] = { "jpg","png","gif",0 }; |
|
44 // exclude list for instant playlist creation |
|
45 const char *excl_pl[] = { "*"PLAYLISTEXT,"*.jpg","*.gif","*.png",0 }; |
|
46 // exclude list for song browser |
|
47 const char *excl_br[] = { ".*","*.jpg","*.gif","*.png",0 }; |
|
48 |
|
49 // --- cImageConvert ----------------------------------------------------------- |
|
50 |
|
51 class cImageConvert : private cThread { |
|
52 private: |
|
53 char *image; |
|
54 enum eStatus { stNone, stRun, stFin }; |
|
55 eStatus status; |
|
56 protected: |
|
57 virtual void Action(void); |
|
58 public: |
|
59 cImageConvert(void); |
|
60 ~cImageConvert(); |
|
61 bool Convert(const char *Image); |
|
62 bool Status(void); |
|
63 }; |
|
64 |
|
65 cImageConvert::cImageConvert(void) |
|
66 { |
|
67 image=0; status=stNone; |
|
68 } |
|
69 |
|
70 cImageConvert::~cImageConvert() |
|
71 { |
|
72 if(status==stRun) Cancel(10); |
|
73 free(image); |
|
74 } |
|
75 |
|
76 bool cImageConvert::Convert(const char *Image) |
|
77 { |
|
78 if(status==stNone) { |
|
79 image=strdup(Image); |
|
80 status=stRun; |
|
81 Start(); |
|
82 return true; |
|
83 } |
|
84 return false; |
|
85 } |
|
86 |
|
87 bool cImageConvert::Status(void) |
|
88 { |
|
89 if(status==stRun && !Active()) status=stFin; |
|
90 return status==stFin; |
|
91 } |
|
92 |
|
93 void cImageConvert::Action(void) |
|
94 { |
|
95 nice(3); |
|
96 char *m, *cmd, *qp, *qm; |
|
97 asprintf(&m,"%s%s.mpg",imagecache,image); |
|
98 di(printf("image: convert started %s -> %s\n",image,m)) |
|
99 asprintf(&cmd,"%s \"%s\" \"%s\"",imageconv,qp=Quote(image),qm=Quote(m)); |
|
100 int r=system(cmd); |
|
101 if(r!=0) di(printf("image: convert returned with code %d. Failed?\n",r)) |
|
102 free(cmd); free(qp); free(qm); free(m); |
|
103 di(printf("image: convert finished\n")) |
|
104 status=stFin; |
|
105 } |
|
106 |
|
107 // --- cSong ------------------------------------------------------------------- |
|
108 |
|
109 cSong::cSong(cFileObj *Obj) |
|
110 { |
|
111 obj=new cFileObj(Obj); |
|
112 Init(); |
|
113 } |
|
114 |
|
115 cSong::cSong(cFileSource *Source, const char *Subdir, const char *Name) |
|
116 { |
|
117 obj=new cFileObj(Source,Subdir,Name,otFile); |
|
118 Init(); |
|
119 } |
|
120 |
|
121 cSong::cSong(cSong *Song) |
|
122 { |
|
123 obj=new cFileObj(Song->obj); |
|
124 Init(); |
|
125 } |
|
126 |
|
127 cSong::~cSong() |
|
128 { |
|
129 delete conv; |
|
130 delete decoder; |
|
131 obj->Source()->Unblock(); |
|
132 delete obj; |
|
133 free((char *)image); |
|
134 } |
|
135 |
|
136 void cSong::Init(void) |
|
137 { |
|
138 decoder=0; user=0; image=0; conv=0; queueStat=0; |
|
139 fromDOS=decoderFailed=false; |
|
140 obj->Source()->Block(); |
|
141 } |
|
142 |
|
143 #if APIVERSNUM >= 10315 |
|
144 int cSong::Compare(const cListObject &ListObject) const |
|
145 #else |
|
146 bool cSong::operator<(const cListObject &ListObject) |
|
147 #endif |
|
148 { |
|
149 cSong *song=(cSong *)&ListObject; |
|
150 #if APIVERSNUM >= 10315 |
|
151 return strcasecmp(obj->Path(),song->obj->Path()); |
|
152 #else |
|
153 return strcasecmp(obj->Path(),song->obj->Path())<0; |
|
154 #endif |
|
155 } |
|
156 |
|
157 cSongInfo *cSong::Info(bool get) |
|
158 { |
|
159 Decoder(); |
|
160 cSongInfo *si=0; |
|
161 if(decoder) si=decoder->SongInfo(get); |
|
162 return si; |
|
163 } |
|
164 |
|
165 cDecoder *cSong::Decoder(void) |
|
166 { |
|
167 decLock.Lock(); |
|
168 if(!decoder && !decoderFailed) { |
|
169 decoder=cDecoders::FindDecoder(obj); |
|
170 if(!decoder) decoderFailed=true; |
|
171 } |
|
172 decLock.Unlock(); |
|
173 return decoder; |
|
174 } |
|
175 |
|
176 void cSong::Convert(void) |
|
177 { |
|
178 char *Name=Convert2Unix(obj->Name()); |
|
179 obj->SetName(Name); |
|
180 fromDOS=true; |
|
181 free(Name); |
|
182 } |
|
183 |
|
184 char *cSong::Convert2Unix(const char *name) const |
|
185 { |
|
186 char *Name=strdup(name); |
|
187 char *p=Name; |
|
188 while(*p) { |
|
189 if(*p=='/') *p='?'; |
|
190 if(*p=='\\') *p='/'; |
|
191 p++; |
|
192 } |
|
193 return Name; |
|
194 } |
|
195 |
|
196 /* |
|
197 char *cSong::Convert2Dos(const char *name) |
|
198 { |
|
199 char *Name=strdup(name); |
|
200 char *p=Name; |
|
201 while(*p) { |
|
202 if(*p=='\\') *p='?'; |
|
203 if(*p=='/') *p='\\'; |
|
204 p++; |
|
205 } |
|
206 return Name; |
|
207 } |
|
208 */ |
|
209 |
|
210 bool cSong::Parse(char *s, const char *reldir) const |
|
211 { |
|
212 s=skipspace(stripspace(s)); |
|
213 if(*s) { |
|
214 if(s[0]=='/' || !reldir) |
|
215 obj->SplitAndSet(s); |
|
216 else { |
|
217 s=AddPath(reldir,s); |
|
218 obj->SplitAndSet(s); |
|
219 free(s); |
|
220 } |
|
221 return true; |
|
222 } |
|
223 return false; |
|
224 } |
|
225 |
|
226 bool cSong::Save(FILE *f, const char *reldir) const |
|
227 { |
|
228 const char *path=obj->Path(); |
|
229 if(reldir) { |
|
230 int l=strlen(reldir); |
|
231 if(!strncasecmp(path,reldir,l)) path+=l+1; |
|
232 } |
|
233 return fprintf(f,"%s\n",path)>0; |
|
234 } |
|
235 |
|
236 bool cSong::FindImage(void) |
|
237 { |
|
238 if(image) return true; |
|
239 |
|
240 char base[strlen(obj->Path())+32]; |
|
241 strcpy(base,obj->Path()); |
|
242 di(printf("image: checking image for %s\n",obj->Path())) |
|
243 |
|
244 // song specific image |
|
245 char *m=rindex(base,'.'); |
|
246 if(m) *m=0; |
|
247 if((image=CheckImage(base))) return true; |
|
248 |
|
249 // album specific image in song directory |
|
250 if(!(m=rindex(base,'/'))) m=base-1; |
|
251 strcpy(m+1,"cover"); |
|
252 if((image=CheckImage(base))) return true; |
|
253 |
|
254 // artist specific image in parent directory |
|
255 if((m=rindex(base,'/'))) { |
|
256 *m=0; |
|
257 if(!(m=rindex(base,'/'))) m=base-1; |
|
258 strcpy(m+1,"artist"); |
|
259 if((image=CheckImage(base))) return true; |
|
260 } |
|
261 |
|
262 // default image in source basedir |
|
263 if((image=CheckImage("background"))) return true; |
|
264 |
|
265 di(printf("image: no image for %s\n",obj->Path())) |
|
266 return false; |
|
267 } |
|
268 |
|
269 const char *cSong::CheckImage(const char *base) const |
|
270 { |
|
271 char *p; |
|
272 int n; |
|
273 asprintf(&p,"%s/%s.%n ",obj->Source()->BaseDir(),base,&n); |
|
274 for(const char **s=img_suff; *s; s++) { |
|
275 #ifdef DEBUG |
|
276 if(strlen(*s)>5) printf("ERROR: buffer overflow in CheckImage ext=%s\n",*s); |
|
277 #endif |
|
278 strcpy(&p[n],*s); |
|
279 di(printf("image: check %s\n",p)) |
|
280 if(!access(p,R_OK)) { |
|
281 di(printf("image: found\n")) |
|
282 return p; |
|
283 } |
|
284 } |
|
285 free(p); |
|
286 return 0; |
|
287 } |
|
288 |
|
289 #include "data-mp3-image.c" |
|
290 extern void PropagateImage(const char *image); |
|
291 |
|
292 bool cSong::Image(unsigned char * &mem, int &len) |
|
293 { |
|
294 mem=0; |
|
295 if(queueStat>0) { |
|
296 if(!conv->Status()) { |
|
297 di(printf("image: still queued\n")) |
|
298 return false; |
|
299 } |
|
300 queueStat=-1; |
|
301 delete conv; conv=0; |
|
302 } |
|
303 |
|
304 int res=0; |
|
305 if(image || FindImage()) { |
|
306 di(printf("image: loading image %s\n",image)) |
|
307 char *m; |
|
308 asprintf(&m,"%s%s.mpg",imagecache,image); |
|
309 if(access(m,R_OK)) { |
|
310 di(printf("image: not cached\n")) |
|
311 if(queueStat<0) { |
|
312 di(printf("image: obviously convert failed...\n")) |
|
313 } |
|
314 else { |
|
315 if(!conv) conv=new cImageConvert; |
|
316 if(conv && conv->Convert(image)) { |
|
317 di(printf("image: convert queued\n")) |
|
318 queueStat=1; |
|
319 res=-1; |
|
320 } |
|
321 else { |
|
322 di(printf("image: queueing failed\n")) |
|
323 queueStat=-1; |
|
324 } |
|
325 } |
|
326 } |
|
327 else { |
|
328 di(printf("image: cached\n")) |
|
329 int f=open(m,O_RDONLY); |
|
330 if(f>=0) { |
|
331 struct stat64 st; |
|
332 fstat64(f,&st); |
|
333 len=st.st_size; |
|
334 mem=MALLOC(unsigned char,len); |
|
335 if(mem) { |
|
336 if(read(f,mem,len)==len) res=1; |
|
337 else free(mem); |
|
338 } |
|
339 close(f); |
|
340 } |
|
341 } |
|
342 free(m); |
|
343 } |
|
344 |
|
345 PropagateImage(res==1 ? image : 0); |
|
346 |
|
347 if(res<=0) { |
|
348 di(printf("image: using static default image\n")) |
|
349 len=sizeof(defaultImage); |
|
350 mem=MALLOC(unsigned char,len); |
|
351 if(mem) { |
|
352 memcpy(mem,defaultImage,len); |
|
353 } |
|
354 } |
|
355 return res>=0; |
|
356 } |
|
357 |
|
358 // -- cPlayList -------------------------------------------------------------- |
|
359 |
|
360 cPlayList::cPlayList(cFileObj *Obj) |
|
361 { |
|
362 obj=new cFileObj(Obj); |
|
363 Init(); |
|
364 } |
|
365 |
|
366 cPlayList::cPlayList(cFileSource *Source, const char *Subdir, const char *Name) |
|
367 { |
|
368 obj=new cFileObj(Source,Subdir,Name,otFile); |
|
369 Init(); |
|
370 } |
|
371 |
|
372 cPlayList::cPlayList(cPlayList *List) |
|
373 { |
|
374 obj=new cFileObj(List->obj); |
|
375 Init(); |
|
376 } |
|
377 |
|
378 cPlayList::~cPlayList() |
|
379 { |
|
380 free(basename); |
|
381 free(extbuffer); |
|
382 obj->Source()->Unblock(); |
|
383 delete obj; |
|
384 } |
|
385 |
|
386 void cPlayList::Init(void) |
|
387 { |
|
388 extbuffer=basename=0; |
|
389 isWinAmp=false; |
|
390 obj->Source()->Block(); |
|
391 Set(); |
|
392 } |
|
393 |
|
394 void cPlayList::Set(void) |
|
395 { |
|
396 free(basename); basename=0; |
|
397 if(obj->Name()) { |
|
398 basename=strdup(obj->Name()); |
|
399 int l=strlen(basename)-strlen(PLAYLISTEXT); |
|
400 if(l>0 && !strcasecmp(basename+l,PLAYLISTEXT)) basename[l]=0; |
|
401 } |
|
402 } |
|
403 |
|
404 #if APIVERSNUM >= 10315 |
|
405 int cPlayList::Compare(const cListObject &ListObject) const |
|
406 #else |
|
407 bool cPlayList::operator<(const cListObject &ListObject) |
|
408 #endif |
|
409 { |
|
410 cPlayList *list=(cPlayList *)&ListObject; |
|
411 #if APIVERSNUM >= 10315 |
|
412 return strcasecmp(obj->Name(),list->obj->Name()); |
|
413 #else |
|
414 return strcasecmp(obj->Name(),list->obj->Name())<0; |
|
415 #endif |
|
416 } |
|
417 |
|
418 bool cPlayList::Load(void) |
|
419 { |
|
420 Clear(); |
|
421 bool result=false; |
|
422 FILE *f=fopen(obj->FullPath(),"r"); |
|
423 if(f) { |
|
424 char buffer[512]; |
|
425 result=true; |
|
426 while(fgets(buffer,sizeof(buffer),f)>0) { |
|
427 if(buffer[0]=='#') { |
|
428 if(!strncmp(buffer,WINAMPEXT,strlen(WINAMPEXT))) { |
|
429 d(printf("mp3: detected WinAmp style playlist\n")) |
|
430 isWinAmp=true; |
|
431 } |
|
432 continue; |
|
433 } |
|
434 if(!isempty(buffer)) { |
|
435 cSong *song=new cSong(obj->Source(),0,0); |
|
436 if(song->Parse(buffer,obj->Subdir())) Add(song); |
|
437 else { |
|
438 esyslog("error loading playlist %s\n",obj->FullPath()); |
|
439 delete song; |
|
440 result=false; |
|
441 break; |
|
442 } |
|
443 } |
|
444 } |
|
445 fclose(f); |
|
446 } |
|
447 else LOG_ERROR_STR(obj->FullPath()); |
|
448 |
|
449 if(result && isWinAmp) { |
|
450 cSong *song=First(); |
|
451 while(song) { // if this is a WinAmp playlist, convert \ to / |
|
452 song->Convert(); |
|
453 song=cList<cSong>::Next(song); |
|
454 } |
|
455 } |
|
456 return result; |
|
457 } |
|
458 |
|
459 bool cPlayList::Save(void) |
|
460 { |
|
461 bool result=true; |
|
462 cSafeFile f(obj->FullPath()); |
|
463 if(f.Open()) { |
|
464 cSong *song=First(); |
|
465 while(song) { |
|
466 if(!song->Save(f,obj->Subdir())) { |
|
467 result=false; |
|
468 break; |
|
469 } |
|
470 song=cList<cSong>::Next(song); |
|
471 } |
|
472 if(!f.Close()) result=false; |
|
473 } |
|
474 else result=false; |
|
475 return result; |
|
476 } |
|
477 |
|
478 bool cPlayList::Exists(void) |
|
479 { |
|
480 return obj->Exists(); |
|
481 } |
|
482 |
|
483 bool cPlayList::TestName(const char *newName) |
|
484 { |
|
485 return obj->TestName(AddExt(newName,PLAYLISTEXT)); |
|
486 } |
|
487 |
|
488 bool cPlayList::Rename(const char *newName) |
|
489 { |
|
490 bool r=obj->Rename(AddExt(newName,PLAYLISTEXT)); |
|
491 if(r) Set(); |
|
492 return r; |
|
493 } |
|
494 |
|
495 bool cPlayList::Create(const char *newName) |
|
496 { |
|
497 bool r=obj->Create(AddExt(newName,PLAYLISTEXT)); |
|
498 if(r) { |
|
499 Set(); |
|
500 r=Load(); |
|
501 } |
|
502 return r; |
|
503 } |
|
504 |
|
505 bool cPlayList::Delete(void) |
|
506 { |
|
507 return obj->Delete(); |
|
508 } |
|
509 |
|
510 const char *cPlayList::AddExt(const char *FileName, const char *Ext) |
|
511 { |
|
512 free(extbuffer); extbuffer=0; |
|
513 asprintf(&extbuffer,"%s%s",FileName,Ext); |
|
514 return extbuffer; |
|
515 } |
|
516 |
|
517 // -- cInstantPlayList ------------------------------------------------------ |
|
518 |
|
519 cInstantPlayList::cInstantPlayList(cFileObj *Obj) |
|
520 :cPlayList(Obj) |
|
521 { |
|
522 if(!Obj->Name()) Obj->SetName("instant"); |
|
523 } |
|
524 |
|
525 bool cInstantPlayList::Load(void) |
|
526 { |
|
527 bool res=false; |
|
528 Clear(); |
|
529 switch(obj->Type()) { |
|
530 case otFile: |
|
531 d(printf("instant: file %s\n",obj->Name())) |
|
532 if(strcasecmp(obj->Name(),basename)) { |
|
533 d(printf("instant: detected as playlist\n")) |
|
534 res=cPlayList::Load(); |
|
535 } |
|
536 else { |
|
537 Add(new cSong(obj)); |
|
538 res=true; |
|
539 } |
|
540 break; |
|
541 case otDir: |
|
542 { |
|
543 d(printf("instant: dir %s\n",obj->Name())) |
|
544 res=ScanDir(obj->Source(),obj->Path(),stFile,obj->Source()->Include(),excl_pl,true); |
|
545 Sort(); |
|
546 break; |
|
547 } |
|
548 case otBase: |
|
549 d(printf("instant: base\n")) |
|
550 res=ScanDir(obj->Source(),0,stFile,obj->Source()->Include(),excl_pl,true); |
|
551 Sort(); |
|
552 break; |
|
553 default: break; |
|
554 } |
|
555 return res; |
|
556 } |
|
557 |
|
558 void cInstantPlayList::DoItem(cFileSource *src, const char *subdir, const char *name) |
|
559 { |
|
560 Add(new cSong(src,subdir,name)); |
|
561 } |
|
562 |
|
563 // -- cPlayLists -------------------------------------------------------------- |
|
564 |
|
565 bool cPlayLists::Load(cFileSource *Source) |
|
566 { |
|
567 static const char *spec[] = { "*"PLAYLISTEXT,0 }; |
|
568 Clear(); |
|
569 bool res=ScanDir(Source,0,stFile,spec,0,false); |
|
570 Sort(); |
|
571 return res; |
|
572 } |
|
573 |
|
574 void cPlayLists::DoItem(cFileSource *src, const char *subdir, const char *name) |
|
575 { |
|
576 Add(new cPlayList(src,subdir,name)); |
|
577 } |