1 /*******************************************************************************
2 *                         Goggles Music Manager                                *
3 ********************************************************************************
4 *           Copyright (C) 2006-2021 by Sander Jansen. All Rights Reserved      *
5 *                               ---                                            *
6 * This program is free software: you can redistribute it and/or modify         *
7 * it under the terms of the GNU General Public License as published by         *
8 * the Free Software Foundation, either version 3 of the License, or            *
9 * (at your option) any later version.                                          *
10 *                                                                              *
11 * This program 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, see http://www.gnu.org/licenses.           *
18 ********************************************************************************/
19 #include <math.h>
20 #include <errno.h>
21 
22 #include "gmdefs.h"
23 #include "gmutils.h"
24 #include "GMTrack.h"
25 #include "GMCover.h"
26 #include "GMTag.h"
27 #include "GMAudioPlayer.h"
28 
29 /// TagLib
30 
31 
32 #include <fileref.h>
33 #include <tstring.h>
34 #include <id3v1tag.h>
35 #include <id3v1genres.h>
36 #include <id3v2tag.h>
37 #include <id3v2framefactory.h>
38 #include <mpegfile.h>
39 #include <vorbisfile.h>
40 #include <opusfile.h>
41 #include <tdebuglistener.h>
42 #include <flacfile.h>
43 #include <apetag.h>
44 #include <textidentificationframe.h>
45 #include <attachedpictureframe.h>
46 #include <unsynchronizedlyricsframe.h>
47 #include <vorbisproperties.h>
48 #include <flacproperties.h>
49 #include <mp4file.h>
50 #include <mp4tag.h>
51 #include <mp4coverart.h>
52 #include <mp4properties.h>
53 
54 
55 #define TAGLIB_VERSION ((TAGLIB_PATCH_VERSION) + (TAGLIB_MINOR_VERSION*1000) + (TAGLIB_MAJOR_VERSION*100000))
56 #define TAGVERSION(major,minor,release) ((release)+(minor*1000)+(major*100000))
57 
58 #if TAGLIB_VERSION >= TAGVERSION(1,10,0)
59 #include <synchronizedlyricsframe.h>
60 #endif
61 
62 
63 
64 #include "FXPNGImage.h"
65 #include "FXJPGImage.h"
66 
67 
to_int(const FXString & str,FXint & val)68 static FXbool to_int(const FXString & str,FXint & val){
69   char * endptr=nullptr;
70   errno=0;
71   val=strtol(str.text(),&endptr,10);
72   if (errno==0) {
73     if (endptr==str.text())
74       return false;
75     return true;
76     }
77   return false;
78   }
79 
80 
81 /// FlacPictureBlock structure.
82 struct FlacPictureBlock{
83   FXString    mimetype;
84   FXString    description;
85   FXuint      type;
86   FXuint      width;
87   FXuint      height;
88   FXuint      bps;
89   FXuint      ncolors;
90   FXuint      data_size;
91   FXuchar*    data;
92   };
93 
94 #if TAGLIB_VERSION < TAGVERSION(1,11,0)
95 
gm_uint32_be(const FXuchar * block,FXuint & v)96 static FXbool gm_uint32_be(const FXuchar * block,FXuint & v) {
97 #if FOX_BIGENDIAN == 0
98   ((FXuchar*)&v)[3]=block[0];
99   ((FXuchar*)&v)[2]=block[1];
100   ((FXuchar*)&v)[1]=block[2];
101   ((FXuchar*)&v)[0]=block[3];
102 #else
103   ((FXuchar*)&v)[3]=block[3];
104   ((FXuchar*)&v)[2]=block[2];
105   ((FXuchar*)&v)[1]=block[1];
106   ((FXuchar*)&v)[0]=block[0];
107 #endif
108   return true;
109   }
110 
111 
xiph_decode_bytevector(const TagLib::ByteVector & bytevector,FXuchar * & buffer,FXint & length)112 static FXbool xiph_decode_bytevector(const TagLib::ByteVector & bytevector,FXuchar *& buffer,FXint & length) {
113   if (bytevector.size()) {
114     if (length==0)
115       length = bytevector.size();
116 
117     allocElms(buffer,length);
118     memcpy(buffer,bytevector.data(),length);
119     if (gm_decode_base64(buffer,length))
120       return true;
121 
122     freeElms(buffer);
123     }
124   return false;
125   }
126 
xiph_decode_picture(const FXuchar * buffer,FXint len,FlacPictureBlock & picture,FXbool full=true)127 static FXbool xiph_decode_picture(const FXuchar * buffer,FXint len,FlacPictureBlock & picture,FXbool full=true){
128   const FXuchar * p = buffer;
129   const FXuchar * end = buffer+len;
130   FXuint sz;
131 
132   gm_uint32_be(p,picture.type);
133 
134   if (!full)
135     return true;
136 
137   p+=4;
138 
139   gm_uint32_be(p,sz);
140   if (sz) {
141     if (p+4+sz>end)
142       return false;
143     picture.mimetype.length(sz);
144     picture.mimetype.assign((const FXchar*)p+4,sz);
145     }
146   p+=(4+sz);
147 
148   gm_uint32_be(p,sz);
149   if (sz) {
150     if (p+4+sz>end)
151       return false;
152     picture.description.length(sz);
153     picture.description.assign((const FXchar*)p+4,sz);
154     }
155   p+=(4+sz);
156 
157   gm_uint32_be(p+0,picture.width);
158   gm_uint32_be(p+4,picture.height);
159   gm_uint32_be(p+8,picture.bps);
160   gm_uint32_be(p+12,picture.ncolors);
161   gm_uint32_be(p+16,picture.data_size);
162 
163   if (picture.data_size>0 && (p+20+picture.data_size)==end) {
164     picture.data = (FXuchar*) p+20;
165     return true;
166     }
167   return false;
168   }
169 
170 
xiph_check_cover(const TagLib::ByteVector & bytevector)171 static FXint xiph_check_cover(const TagLib::ByteVector & bytevector){
172   FlacPictureBlock picture;
173   FXint     covertype = -1;
174   FXuchar * buffer = nullptr;
175   FXint     length = 8; // decode only 8 bytes
176   if (xiph_decode_bytevector(bytevector,buffer,length)) {
177     if (xiph_decode_picture(buffer,length,picture,false)) {
178       covertype = picture.type;
179       }
180     freeElms(buffer);
181     }
182   return covertype;
183   }
184 
xiph_load_cover(const TagLib::ByteVector & bytevector)185 static GMCover * xiph_load_cover(const TagLib::ByteVector & bytevector) {
186   FlacPictureBlock picture;
187   GMCover * cover  = nullptr;
188   FXuchar * buffer = nullptr;
189   FXint     length = 0;
190   if (xiph_decode_bytevector(bytevector,buffer,length)){
191     if (xiph_decode_picture(buffer,length,picture)) {
192       cover = new GMCover(picture.data,picture.data_size,picture.type,picture.description);
193       }
194     freeElms(buffer);
195     }
196   return cover;
197   }
198 
199 #endif
200 
id3v2_load_cover(TagLib::ID3v2::AttachedPictureFrame * frame)201 static GMCover * id3v2_load_cover(TagLib::ID3v2::AttachedPictureFrame * frame) {
202   FXString mime = frame->mimeType().toCString(true);
203   /// Skip File Icon
204   if (frame->picture().size()==0 ||
205       frame->type()==TagLib::ID3v2::AttachedPictureFrame::FileIcon ||
206       frame->type()==TagLib::ID3v2::AttachedPictureFrame::OtherFileIcon ||
207       frame->type()==TagLib::ID3v2::AttachedPictureFrame::ColouredFish) {
208     return nullptr;
209     }
210   return new GMCover(frame->picture().data(),frame->picture().size(),frame->type());
211   }
212 
id3v2_is_front_cover(TagLib::ID3v2::AttachedPictureFrame * frame)213 static FXbool id3v2_is_front_cover(TagLib::ID3v2::AttachedPictureFrame * frame){
214   if (frame->type()==TagLib::ID3v2::AttachedPictureFrame::FrontCover)
215     return true;
216   else
217     return false;
218   }
219 
220 
flac_load_cover_from_taglib(const TagLib::FLAC::Picture * picture)221 GMCover* flac_load_cover_from_taglib(const TagLib::FLAC::Picture * picture) {
222   if (picture && picture->data().size()>0) {
223     if (picture->type()==TagLib::FLAC::Picture::FileIcon ||
224         picture->type()==TagLib::FLAC::Picture::OtherFileIcon ||
225         picture->type()==TagLib::FLAC::Picture::ColouredFish) {
226         return nullptr;
227         }
228     return new GMCover(picture->data().data(),picture->data().size(),picture->type(),picture->description().toCString(true));
229     }
230   return nullptr;
231   }
232 
flac_load_frontcover_from_taglib(const TagLib::FLAC::Picture * picture)233 GMCover* flac_load_frontcover_from_taglib(const TagLib::FLAC::Picture * picture) {
234   if (picture && picture->type()==TagLib::FLAC::Picture::FrontCover && picture->data().size()>0) {
235     return new GMCover(picture->data().data(),picture->data().size(),picture->type(),picture->description().toCString(true));
236     }
237   return nullptr;
238   }
239 
240 
241 
242 // For conversion from UTF16 to UTF32
243 static const FXint SURROGATE_OFFSET=0x10000-(0xD800<<10)-0xDC00;
244 
245 
gm_taglib_string(const TagLib::String & src,FXString & dst)246 static FXbool gm_taglib_string(const TagLib::String & src,FXString & dst) {
247   // Method 1 - Uses 1 extra buffer
248   // const TagLib::ByteVector b = src.data(TagLib::String::UTF8);
249   // dst.assign(b.data(),b.size());
250   // dst.trim();
251 
252   // Method 2 - Use 2 extra buffers (ByteVector -> std::string -> c_str)
253   // dst = src.toCString(true);
254   // dst.trim();
255 
256   // Method 3 - Use FOX's utf16->utf8 and copy directly into FXString
257   const int slen = src.size();
258 
259   /* Determine Number of Bytes Needed */
260   FXint p=0;
261   FXint q=0;
262   FXwchar w;
263 
264   while(q<slen){
265     w=src[q++];
266     if(__likely(w<0x80)){ p+=1; continue; }
267     if(__likely(w<0x800)){ p+=2; continue; }
268     if(__likely(!FXISSEQUTF16(w))){ p+=3; continue; }
269     if(__likely(FXISLEADUTF16(w) && q<slen && FXISFOLLOWUTF16(src[q++]))){ p+=4; continue; }
270     break;
271     }
272 
273   dst.length(p);
274   const FXint dlen = dst.length();
275 
276   /* Copy Bytes */
277   p=q=0;
278   while(q<slen){
279     w=src[q++];
280     if(__likely(w<0x80)){
281       if(__unlikely(p>=dlen)) break;
282       dst[p++]=w;
283       continue;
284       }
285     if(__likely(w<0x800)){
286       if(__unlikely(p+1>=dlen)) break;
287       dst[p++]=(w>>6)|0xC0;
288       dst[p++]=(w&0x3F)|0x80;
289       continue;
290       }
291     if(__likely(!FXISSEQUTF16(w))){
292       if(__unlikely(p+2>=dlen)) break;
293       dst[p++]=(w>>12)|0xE0;
294       dst[p++]=((w>>6)&0x3F)|0x80;
295       dst[p++]=(w&0x3F)|0x80;
296       continue;
297       }
298     if(__likely(FXISLEADUTF16(w) && q<slen && FXISFOLLOWUTF16(src[q]))){
299       if(__unlikely(p+3>=dlen)) break;
300       w=SURROGATE_OFFSET+(w<<10)+src[q++];
301       dst[p++]=(w>>18)|0xF0;
302       dst[p++]=((w>>12)&0x3F)|0x80;
303       dst[p++]=((w>>6)&0x3F)|0x80;
304       dst[p++]=(w&0x3F)|0x80;
305       continue;
306       }
307     break;
308     }
309   dst.trim();
310 
311   return !dst.empty();
312   }
313 
314 
315 
316 
317 
318  #if 0
319 
320 static void gm_strip_tags(TagLib::File * file,FXuint opts) {
321   TagLib::MPEG::File * mpeg = dynamic_cast<TagLib::MPEG::File*>(file);
322   if (mpeg) {
323     int tags = TagLib::MPEG::NoTags;
324 
325     if (opts&TAG_STRIP_ID3v1)
326       tags|=TagLib::MPEG::ID3v1;
327     if (opts&TAG_STRIP_ID3v2)
328       tags|=TagLib::MPEG::ID3v2;
329     if (opts&TAG_STRIP_APE)
330       tags|=TagLib::MPEG::APE;
331 
332     if (tags)
333       mpeg->strip(tags);
334     }
335   }
336 #endif
337 
338 
339 /******************************************************************************/
340 
GMFileTag()341 GMFileTag::GMFileTag() :
342     file(nullptr),
343     tag(nullptr),
344     mp4(nullptr),
345     xiph(nullptr),
346     id3v2(nullptr),
347     ape(nullptr) {
348   /// TODO
349   }
350 
~GMFileTag()351 GMFileTag::~GMFileTag() {
352   if (file) delete file;
353   }
354 
355 
open(const FXString & filename,FXuint opts)356 FXbool GMFileTag::open(const FXString & filename,FXuint opts) {
357 
358   file = TagLib::FileRef::create(filename.text(),(opts&FILETAG_AUDIOPROPERTIES));
359   if (file==nullptr || !file->isValid() || file->tag()==nullptr) {
360     if (file) {
361       delete file;
362       file=nullptr;
363       }
364     return false;
365     }
366 
367   TagLib::MPEG::File        * mpgfile   = nullptr;
368   TagLib::Ogg::Vorbis::File * oggfile   = nullptr;
369   TagLib::Ogg::Opus::File   * opusfile  = nullptr;
370   TagLib::FLAC::File        * flacfile  = nullptr;
371   TagLib::MP4::File         * mp4file   = nullptr;
372 
373   tag = file->tag();
374 
375   if ((oggfile = dynamic_cast<TagLib::Ogg::Vorbis::File*>(file))!=nullptr) {
376     xiph=oggfile->tag();
377     }
378   else if ((flacfile = dynamic_cast<TagLib::FLAC::File*>(file))!=nullptr){
379     xiph=flacfile->xiphComment();
380     id3v2=flacfile->ID3v2Tag();
381     }
382   else if ((mpgfile = dynamic_cast<TagLib::MPEG::File*>(file))!=nullptr){
383     id3v2=mpgfile->ID3v2Tag();
384     ape=mpgfile->APETag();
385     }
386   else if ((mp4file = dynamic_cast<TagLib::MP4::File*>(file))!=nullptr){
387     mp4=mp4file->tag();
388     }
389   else if ((opusfile = dynamic_cast<TagLib::Ogg::Opus::File*>(file))!=nullptr){
390     xiph=opusfile->tag();
391     }
392   return true;
393   }
394 
395 
save()396 FXbool GMFileTag::save() {
397   return file->save();
398   }
399 
400 
getFileType() const401 FXuchar GMFileTag::getFileType() const {
402   TagLib::MP4::File       * mp4file    = nullptr;
403   TagLib::AudioProperties * properties = file->audioProperties();
404   if (properties) {
405     if ((dynamic_cast<TagLib::Ogg::File*>(file))) {
406       if (dynamic_cast<TagLib::Vorbis::Properties*>(properties))
407         return FILETYPE_OGG_VORBIS;
408       else if (dynamic_cast<TagLib::Ogg::Opus::Properties*>(properties))
409         return FILETYPE_OGG_OPUS;
410       }
411     else if (dynamic_cast<TagLib::FLAC::File*>(file)){
412       if (dynamic_cast<TagLib::FLAC::Properties*>(properties))
413         return FILETYPE_FLAC;
414       }
415     else if (dynamic_cast<TagLib::MPEG::File*>(file)){
416       return FILETYPE_MP3;
417       }
418     else if ((mp4file = dynamic_cast<TagLib::MP4::File*>(file))){
419       TagLib::MP4::Properties * mp4prop = dynamic_cast<TagLib::MP4::Properties*>(properties);
420       if (mp4prop) {
421         switch(mp4prop->codec()) {
422           case TagLib::MP4::Properties::AAC : return FILETYPE_MP4_AAC; break;
423           case TagLib::MP4::Properties::ALAC: return FILETYPE_MP4_ALAC; break;
424           default: break;
425           }
426         }
427       }
428     }
429   return FILETYPE_UNKNOWN;
430   }
431 
432 
trimList(FXStringList & list) const433 FXbool GMFileTag::trimList(FXStringList & list) const {
434   for (FXint i=list.no()-1;i>=0;i--){
435     list[i].trim();
436     if (list[i].empty())
437       list.erase(i);
438     }
439   return list.no()>0;
440   }
441 
442 
xiph_add_field(const FXchar * field,const FXString & value)443 void GMFileTag::xiph_add_field(const FXchar * field,const FXString & value) {
444   FXASSERT(field);
445   FXASSERT(xiph);
446   if (!value.empty())
447     xiph->addField(field,TagLib::String(value.text(),TagLib::String::UTF8),false);
448   }
449 
xiph_update_field(const FXchar * field,const FXString & value)450 void GMFileTag::xiph_update_field(const FXchar * field,const FXString & value) {
451   FXASSERT(field);
452   FXASSERT(xiph);
453   if (!value.empty())
454     xiph->addField(field,TagLib::String(value.text(),TagLib::String::UTF8),true);
455   else
456 #if TAGLIB_VERSION >= TAGVERSION(1,11,0)
457     xiph->removeFields(field);
458 #else
459     xiph->removeField(field); // deprecated
460 #endif
461   }
462 
463 
xiph_update_field(const FXchar * field,const FXStringList & list)464 void GMFileTag::xiph_update_field(const FXchar * field,const FXStringList & list) {
465   FXASSERT(field);
466   FXASSERT(xiph);
467 #if TAGLIB_VERSION >= TAGVERSION(1,11,0)
468   xiph->removeFields(field);
469 #else
470   xiph->removeField(field); // deprecated
471 #endif
472   for (FXint i=0;i<list.no();i++) {
473     xiph->addField(field,TagLib::String(list[i].text(),TagLib::String::UTF8),false);
474     }
475   }
476 
477 
xiph_get_field(const FXchar * field,FXString & value) const478 FXbool GMFileTag::xiph_get_field(const FXchar * field,FXString & value) const {
479   FXASSERT(field);
480   FXASSERT(xiph);
481   if (xiph->contains(field)) {
482     return gm_taglib_string(xiph->fieldListMap()[field].front(),value);
483     }
484   else {
485     value.clear();
486     return false;
487     }
488   }
489 
xiph_get_field(const FXchar * field,FXStringList & list) const490 FXbool GMFileTag::xiph_get_field(const FXchar * field,FXStringList & list)  const{
491   FXASSERT(field);
492   FXASSERT(xiph);
493   if (xiph->contains(field)) {
494     const TagLib::StringList & fieldlist = xiph->fieldListMap()[field];
495     list.no(fieldlist.size());
496     FXint item=0;
497     for(TagLib::StringList::ConstIterator it = fieldlist.begin(); it != fieldlist.end(); it++) {
498       gm_taglib_string(*it,list[item++]);
499       }
500     return trimList(list);
501     }
502   else {
503     list.clear();
504     return false;
505     }
506   }
507 
ape_get_field(const FXchar * field,FXString & value) const508 FXbool GMFileTag::ape_get_field(const FXchar * field,FXString & value)  const{
509   FXASSERT(field);
510   FXASSERT(ape);
511   if (ape->itemListMap().contains(field) && !ape->itemListMap()[field].isEmpty()){
512     return gm_taglib_string(ape->itemListMap()[field].toString(),value);
513     }
514   else {
515     value.clear();
516     return false;
517     }
518   }
519 
ape_get_field(const FXchar * field,FXStringList & list) const520 FXbool GMFileTag::ape_get_field(const FXchar * field,FXStringList & list)  const{
521   FXASSERT(field);
522   FXASSERT(ape);
523   if (ape->itemListMap().contains(field)) {
524     const TagLib::StringList fieldlist = ape->itemListMap()[field].toStringList();
525     list.no(fieldlist.size());
526     FXint item=0;
527     for(TagLib::StringList::ConstIterator it = fieldlist.begin(); it != fieldlist.end(); it++) {
528       gm_taglib_string(*it,list[item++]);
529       }
530     return trimList(list);
531     }
532   else {
533     list.clear();
534     return false;
535     }
536   }
537 
ape_update_field(const FXchar * field,const FXString & value)538 void GMFileTag::ape_update_field(const FXchar * field,const FXString & value) {
539   FXASSERT(field);
540   FXASSERT(ape);
541   if (!value.empty())
542     ape->addValue(field,TagLib::String(value.text(),TagLib::String::UTF8),true);
543   else
544     ape->removeItem(field);
545   }
546 
547 
ape_update_field(const FXchar * field,const FXStringList & list)548 void GMFileTag::ape_update_field(const FXchar * field,const FXStringList & list) {
549   FXASSERT(field);
550   FXASSERT(ape);
551   ape->removeItem(field);
552 
553   TagLib::StringList values;
554   for (FXint i=0;i<list.no();i++) {
555     values.append(TagLib::String(list[i].text(),TagLib::String::UTF8));
556     }
557 
558   ape->setItem(field,TagLib::APE::Item(field,values));
559   }
560 
561 
id3v2_update_field(const FXchar * field,const FXString & value)562 void GMFileTag::id3v2_update_field(const FXchar * field,const FXString & value) {
563   FXASSERT(field);
564   FXASSERT(id3v2);
565   if (value.empty()) {
566     id3v2->removeFrames(field);
567     }
568   else if (id3v2->frameListMap().contains(field) && !id3v2->frameListMap()[field].isEmpty()) {
569     id3v2->frameListMap()[field].front()->setText(TagLib::String(value.text(),TagLib::String::UTF8));
570     }
571   else {
572     TagLib::ID3v2::TextIdentificationFrame *frame = new TagLib::ID3v2::TextIdentificationFrame(field,TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding());
573     frame->setText(TagLib::String(value.text(),TagLib::String::UTF8) );
574     id3v2->addFrame(frame);
575     }
576   }
577 
id3v2_update_field(const FXchar * field,const FXStringList & list)578 void GMFileTag::id3v2_update_field(const FXchar * field,const FXStringList & list) {
579   FXASSERT(field);
580   FXASSERT(id3v2);
581   if (list.no()==0) {
582     id3v2->removeFrames(field);
583     }
584   else {
585     TagLib::ID3v2::TextIdentificationFrame * frame = nullptr;
586     if (id3v2->frameListMap().contains(field) && !id3v2->frameListMap()[field].isEmpty()) {
587       frame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(id3v2->frameListMap()[field].front());
588       }
589     else {
590       frame = new TagLib::ID3v2::TextIdentificationFrame(field,TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding());
591       id3v2->addFrame(frame);
592       }
593     FXASSERT(frame);
594 
595     TagLib::StringList values;
596     for (FXint i=0;i<list.no();i++) {
597       values.append(TagLib::String(list[i].text(),TagLib::String::UTF8));
598       }
599     frame->setText(values);
600     }
601   }
602 
id3v2_get_field(const FXchar * field,FXString & value) const603 FXbool  GMFileTag::id3v2_get_field(const FXchar * field,FXString & value) const{
604   FXASSERT(field);
605   FXASSERT(id3v2);
606   if (id3v2->frameListMap().contains(field) && !id3v2->frameListMap()[field].isEmpty() ){
607     return gm_taglib_string(id3v2->frameListMap()[field].front()->toString(),value);
608     }
609   else {
610     value.clear();
611     return false;
612     }
613   }
614 
id3v2_get_field(const FXchar * field,FXStringList & list) const615 FXbool  GMFileTag::id3v2_get_field(const FXchar * field,FXStringList & list) const {
616   FXASSERT(field);
617   FXASSERT(id3v2);
618   if (id3v2->frameListMap().contains(field) && !id3v2->frameListMap()[field].isEmpty() ) {
619     TagLib::ID3v2::TextIdentificationFrame * frame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(id3v2->frameListMap()[field].front());
620     const TagLib::StringList fieldlist = frame->fieldList();
621     list.no(fieldlist.size());
622     FXint item=0;
623     for(TagLib::StringList::ConstIterator it = fieldlist.begin(); it != fieldlist.end(); it++) {
624       gm_taglib_string(*it,list[item++]);
625       }
626     return trimList(list);
627     }
628   else {
629     list.clear();
630     return false;
631     }
632   }
633 
634 
mp4_update_field(const FXchar * field,const FXString & value)635 void GMFileTag::mp4_update_field(const FXchar * field,const FXString & value) {
636   FXASSERT(field);
637   FXASSERT(mp4);
638   if (!value.empty())
639     mp4->itemListMap().insert(field,TagLib::StringList(TagLib::String(value.text(),TagLib::String::UTF8)));
640   else
641     mp4->itemListMap().erase(field);
642   }
643 
644 
mp4_update_field(const FXchar * field,const FXStringList & list)645 void GMFileTag::mp4_update_field(const FXchar * field,const FXStringList & list) {
646   FXASSERT(field);
647   FXASSERT(mp4);
648   if (list.no()==0) {
649     mp4->itemListMap().erase(field);
650     }
651   else {
652     TagLib::StringList values;
653     for (FXint i=0;i<list.no();i++) {
654       values.append(TagLib::String(list[i].text(),TagLib::String::UTF8));
655       }
656     mp4->itemListMap().insert(field,values);
657     }
658   }
659 
660 
mp4_get_field(const FXchar * field,FXString & value) const661 FXbool GMFileTag::mp4_get_field(const FXchar * field,FXString & value) const {
662   FXASSERT(field);
663   FXASSERT(mp4);
664   if (mp4->itemListMap().contains(field)) {
665     value=mp4->itemListMap()[field].toStringList().toString(", ").toCString(true);
666     value.trim();
667     return !value.empty();
668     }
669   else {
670     value.clear();
671     return false;
672     }
673   }
674 
675 
mp4_get_field(const FXchar * field,FXStringList & list) const676 FXbool GMFileTag::mp4_get_field(const FXchar * field,FXStringList & list) const{
677   FXASSERT(field);
678   FXASSERT(mp4);
679   if (mp4->itemListMap().contains(field)) {
680     const TagLib::StringList fieldlist = mp4->itemListMap()[field].toStringList();
681     list.no(fieldlist.size());
682     FXint item=0;
683     for(TagLib::StringList::ConstIterator it = fieldlist.begin(); it != fieldlist.end(); it++) {
684       gm_taglib_string(*it,list[item++]);
685       }
686     return trimList(list);
687     }
688   else {
689     list.clear();
690     return false;
691     }
692   }
693 
694 
695 
696 /******************************************************************************/
697 
setComposer(const FXString & composer)698 void GMFileTag::setComposer(const FXString & composer) {
699   if (xiph)
700     xiph_update_field("COMPOSER",composer);
701   if (id3v2)
702     id3v2_update_field("TCOM",composer);
703   if (mp4)
704     mp4_update_field("\251wrt",composer);
705   if (ape)
706     ape_update_field("Composer",composer);
707   }
708 
getComposer(FXString & composer) const709 void GMFileTag::getComposer(FXString & composer) const{
710   if (xiph && xiph_get_field("COMPOSER",composer))
711     return;
712   else if (id3v2 && id3v2_get_field("TCOM",composer))
713     return;
714   else if (mp4 && mp4_get_field("\251wrt",composer))
715     return;
716   else if (ape && ape_get_field("Composer",composer))
717     return;
718   else
719     composer.clear();
720   }
721 
setConductor(const FXString & conductor)722 void GMFileTag::setConductor(const FXString & conductor) {
723   if (xiph)
724     xiph_update_field("CONDUCTOR",conductor);
725   if (id3v2)
726     id3v2_update_field("TPE3",conductor);
727   if (mp4)
728     mp4_update_field("----:com.apple.iTunes:CONDUCTOR",conductor);
729   if (ape)
730     ape_update_field("Conductor",conductor);
731   }
732 
getConductor(FXString & conductor) const733 void GMFileTag::getConductor(FXString & conductor) const{
734   if (xiph && xiph_get_field("CONDUCTOR",conductor))
735     return;
736   else if (id3v2 && id3v2_get_field("TPE3",conductor))
737     return;
738   else if (mp4 && mp4_get_field("----:com.apple.iTunes:CONDUCTOR",conductor))
739     return;
740   else if (ape && ape_get_field("Conductor",conductor))
741     return;
742   else
743     conductor.clear();
744   }
745 
746 
setAlbumArtist(const FXString & albumartist)747 void GMFileTag::setAlbumArtist(const FXString & albumartist) {
748   if (xiph)
749     xiph_update_field("ALBUMARTIST",albumartist);
750   if (id3v2)
751     id3v2_update_field("TPE2",albumartist);
752   if (mp4)
753     mp4_update_field("aART",albumartist);
754   //FIXME ape?
755   }
756 
757 
getAlbumArtist(FXString & albumartist) const758 void GMFileTag::getAlbumArtist(FXString & albumartist) const{
759   if (xiph && xiph_get_field("ALBUMARTIST",albumartist))
760     return;
761   else if (id3v2 && id3v2_get_field("TPE2",albumartist))
762     return;
763   else if (mp4 && mp4_get_field("aART",albumartist))
764     return;
765   else
766     albumartist.clear();
767   }
768 
769 
setLyrics(const FXString & lyrics)770 void GMFileTag::setLyrics(const FXString & lyrics) {
771   if (xiph) {
772     xiph_update_field("LYRICS",lyrics);
773     }
774   else if (id3v2) {
775 
776     // Always store as Unsynchronized Lyrics Frame
777     TagLib::ID3v2::FrameList framelist = id3v2->frameListMap()["USLT"];
778     if (!framelist.isEmpty()) {
779       framelist.front()->setText(TagLib::String(lyrics.text(),TagLib::String::UTF8));
780       }
781     else {
782       TagLib::ID3v2::UnsynchronizedLyricsFrame *frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding());
783       frame->setText(TagLib::String(lyrics.text(),TagLib::String::UTF8));
784       id3v2->addFrame(frame);
785       }
786     }
787   else if (mp4) {
788     mp4_update_field("\251lyr",lyrics);
789     }
790   }
791 
792 
getLyrics(FXString & lyrics) const793 void GMFileTag::getLyrics(FXString & lyrics) const{
794   if (xiph && xiph_get_field("LYRICS",lyrics))
795     return;
796   else if (id3v2) {
797 
798     {
799       const TagLib::ID3v2::FrameList framelist = id3v2->frameListMap()["USLT"];
800       for(auto it = framelist.begin(); it != framelist.end(); it++) {
801         TagLib::ID3v2::UnsynchronizedLyricsFrame * frame = dynamic_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame*>(*it);
802         gm_taglib_string(frame->text(),lyrics);
803         if (!lyrics.empty()) return;
804         }
805     }
806 #if TAGLIB_VERSION >= TAGVERSION(1,10,0)
807     {
808       const TagLib::ID3v2::FrameList framelist = id3v2->frameListMap()["SYLT"];
809       for(auto it = framelist.begin(); it != framelist.end(); it++) {
810         TagLib::ID3v2::SynchronizedLyricsFrame * frame = dynamic_cast<TagLib::ID3v2::SynchronizedLyricsFrame*>(*it);
811         if (frame->type()==TagLib::ID3v2::SynchronizedLyricsFrame::Lyrics) {
812           auto textlist = frame->synchedText();
813           for(auto it = textlist.begin(); it != textlist.end(); it++) {
814             FXString line;
815             gm_taglib_string((*it).text,line);
816             lyrics.append(line);
817             }
818           if (!lyrics.empty()) return;
819           }
820         }
821     }
822 #endif
823 
824     }
825   else if (mp4 && mp4_get_field("\251lyr",lyrics)) {
826     return;
827     }
828   else
829     lyrics.clear();
830   }
831 
832 
833 
setTags(const FXStringList & tags)834 void GMFileTag::setTags(const FXStringList & tags){
835   if (xiph)
836     xiph_update_field("GENRE",tags);
837   if (id3v2)
838     id3v2_update_field("TCON",tags);
839   if (mp4)
840     mp4_update_field("\251gen",tags);
841   if (ape)
842     ape_update_field("GENRE",tags);
843 
844   if (xiph==nullptr && id3v2==nullptr && mp4==nullptr && ape==nullptr) {
845     if (tags.no())
846       tag->setGenre(TagLib::String(tags[0].text(),TagLib::String::UTF8));
847     else
848       tag->setGenre(TagLib::String("",TagLib::String::UTF8));
849     }
850   }
851 
getTags(FXStringList & tags) const852 void GMFileTag::getTags(FXStringList & tags) const {
853   tags.clear();
854   if (xiph && xiph_get_field("GENRE",tags))
855     return;
856   else if (id3v2 && id3v2_get_field("TCON",tags)) {
857     parse_tagids(tags);
858     }
859   else if (mp4 && mp4_get_field("\251gen",tags))
860     return;
861   else if (ape && ape_get_field("GENRE",tags))
862     return;
863   else {
864     FXString genre = tag->genre().toCString(true);
865     genre.trim();
866     if (!genre.empty()) tags.append(genre);
867     }
868   }
869 
setComment(const FXString & value)870 void GMFileTag::setComment(const FXString & value) {
871   tag->setComment(TagLib::String(value.text(),TagLib::String::UTF8));
872   }
873 
getComment(FXString & value) const874 void GMFileTag::getComment(FXString & value) const {
875   gm_taglib_string(tag->comment(),value);
876   }
877 
setArtist(const FXString & value)878 void GMFileTag::setArtist(const FXString & value){
879   tag->setArtist(TagLib::String(value.text(),TagLib::String::UTF8));
880   }
881 
getArtist(FXString & value) const882 void GMFileTag::getArtist(FXString& value) const{
883   gm_taglib_string(tag->artist(),value);
884   }
885 
setAlbum(const FXString & value)886 void GMFileTag::setAlbum(const FXString & value){
887   tag->setAlbum(TagLib::String(value.text(),TagLib::String::UTF8));
888   }
889 
getAlbum(FXString & value) const890 void GMFileTag::getAlbum(FXString& value) const{
891   gm_taglib_string(tag->album(),value);
892   }
893 
setTitle(const FXString & value)894 void GMFileTag::setTitle(const FXString & value){
895   tag->setTitle(TagLib::String(value.text(),TagLib::String::UTF8));
896   }
897 
getTitle(FXString & value) const898 void GMFileTag::getTitle(FXString& value) const {
899   FXStringList vals;
900   if (xiph && xiph_get_field("TITLE",vals)) {
901     value.clear();
902     if (vals.no()) {
903       value=vals[0];
904       for (FXint i=1;i<vals.no();i++) {
905         value+=" - ";
906         value+=vals[i];
907         }
908       }
909     }
910   else {
911     gm_taglib_string(tag->title(),value);
912     }
913   }
914 
parse_tagids(FXStringList & tags) const915 void GMFileTag::parse_tagids(FXStringList & tags)const{
916   FXint id;
917   for (FXint i=tags.no()-1;i>=0;i--){
918     if (to_int(tags[i],id)) {
919       tags[i]=TagLib::ID3v1::genre(id).toCString(true);
920       }
921     }
922   trimList(tags);
923   }
924 
925 
setDiscNumber(FXushort disc)926 void GMFileTag::setDiscNumber(FXushort disc) {
927   if (xiph) {
928     if (disc>0)
929       xiph_update_field("DISCNUMBER",FXString::value("%d",disc));
930     else
931       xiph_update_field("DISCNUMBER",FXString::null);
932     }
933   if (id3v2) {
934     if (disc>0)
935       id3v2_update_field("TPOS",FXString::value("%d",disc));
936     else
937       id3v2_update_field("TPOS",FXString::null);
938     }
939   if (mp4) {
940     if (disc>0)
941       mp4->itemListMap().insert("disk",TagLib::MP4::Item(disc,0));
942     else
943       mp4->itemListMap().erase("disk");
944     }
945   }
946 
947 
string_to_disc_number(const FXString & disc)948 static FXushort string_to_disc_number(const FXString & disc) {
949   if (disc.empty())
950     return 0;
951   return FXMIN(disc.before('/').toUInt(),0xFFFF);
952   }
953 
getDiscNumber() const954 FXushort GMFileTag::getDiscNumber() const{
955   FXString disc;
956   if (xiph && xiph_get_field("DISCNUMBER",disc)) {
957     return string_to_disc_number(disc);
958     }
959   else if (id3v2 && id3v2_get_field("TPOS",disc)) {
960     return string_to_disc_number(disc);
961     }
962   else if (mp4 && mp4->itemListMap().contains("disk")) {
963     return FXMIN(mp4->itemListMap()["disk"].toIntPair().first,0xFFFF);
964     }
965   return 0;
966   }
967 
getTime() const968 FXint GMFileTag::getTime() const{
969   FXASSERT(file);
970   TagLib::AudioProperties * properties = file->audioProperties();
971   if (properties)
972     return properties->length();
973   else
974     return 0;
975   }
976 
getBitRate() const977 FXint GMFileTag::getBitRate() const{
978   FXASSERT(file);
979   TagLib::AudioProperties * properties = file->audioProperties();
980   if (properties)
981     return properties->bitrate();
982   else
983     return 0;
984   }
985 
getSampleRate() const986 FXint GMFileTag::getSampleRate() const{
987   FXASSERT(file);
988   TagLib::AudioProperties * properties = file->audioProperties();
989   if (properties)
990     return properties->sampleRate();
991   else
992     return 0;
993   }
994 
995 
getChannels() const996 FXint GMFileTag::getChannels() const{
997   FXASSERT(file);
998   TagLib::AudioProperties * properties = file->audioProperties();
999   if (properties)
1000     return properties->channels();
1001   else
1002     return 0;
1003   }
1004 
1005 
getSampleSize() const1006 FXint GMFileTag::getSampleSize() const{
1007   FXASSERT(file);
1008   TagLib::FLAC::File * flacfile = dynamic_cast<TagLib::FLAC::File*>(file);
1009   if (flacfile && flacfile->audioProperties()) {
1010     return flacfile->audioProperties()->sampleWidth();
1011     }
1012   else
1013     return 0;
1014   }
1015 
1016 
1017 
setTrackNumber(FXushort track)1018 void GMFileTag::setTrackNumber(FXushort track) {
1019   tag->setTrack(track);
1020   }
1021 
getTrackNumber() const1022 FXushort GMFileTag::getTrackNumber() const{
1023   return FXMIN(tag->track(),0xFFFF);
1024   }
1025 
setYear(FXint year)1026 void GMFileTag::setYear(FXint year) {
1027   tag->setYear(year);
1028   }
1029 
getYear() const1030 FXint GMFileTag::getYear()const {
1031   return tag->year();
1032   }
1033 
getFrontCover() const1034 GMCover * GMFileTag::getFrontCover() const {
1035   TagLib::FLAC::File * flacfile = dynamic_cast<TagLib::FLAC::File*>(file);
1036   if (flacfile) {
1037     const TagLib::List<TagLib::FLAC::Picture*> picturelist = flacfile->pictureList();
1038     for(TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end(); it++) {
1039       GMCover * cover = flac_load_frontcover_from_taglib((*it));
1040       if (cover) return cover;
1041       }
1042     }
1043   else if (id3v2) {
1044     TagLib::ID3v2::FrameList framelist = id3v2->frameListMap()["APIC"];
1045     if(!framelist.isEmpty()){
1046       /// First Try Front Cover
1047       for(TagLib::ID3v2::FrameList::Iterator it = framelist.begin(); it != framelist.end(); it++) {
1048         TagLib::ID3v2::AttachedPictureFrame * frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
1049         FXASSERT(frame);
1050         if (id3v2_is_front_cover(frame)) {
1051           GMCover * cover = id3v2_load_cover(frame);
1052           if (cover) return cover;
1053           }
1054         }
1055       for(TagLib::ID3v2::FrameList::Iterator it = framelist.begin(); it != framelist.end(); it++) {
1056         TagLib::ID3v2::AttachedPictureFrame * frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
1057         FXASSERT(frame);
1058         GMCover * cover = id3v2_load_cover(frame);
1059         if (cover) return cover;
1060         }
1061       }
1062     }
1063   else if (xiph) {
1064 #if TAGLIB_VERSION >= TAGVERSION(1,11,0)
1065     const TagLib::List<TagLib::FLAC::Picture*> picturelist = xiph->pictureList();
1066     for(TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end(); it++) {
1067       GMCover * cover = flac_load_frontcover_from_taglib((*it));
1068       if (cover) return cover;
1069       }
1070 #else
1071     if (xiph->contains("METADATA_BLOCK_PICTURE")) {
1072       const TagLib::StringList & coverlist = xiph->fieldListMap()["METADATA_BLOCK_PICTURE"];
1073       for(TagLib::StringList::ConstIterator it = coverlist.begin(); it != coverlist.end(); it++) {
1074         const TagLib::ByteVector & bytevector = (*it).data(TagLib::String::Latin1);
1075         if (xiph_check_cover(bytevector)==GMCover::FrontCover){
1076           GMCover* cover = xiph_load_cover(bytevector);
1077           if (cover) return cover;
1078          }
1079         }
1080       }
1081 #endif
1082     }
1083   else if (mp4) { /// MP4
1084     if (mp4->itemListMap().contains("covr")) {
1085       TagLib::MP4::CoverArtList coverlist = mp4->itemListMap()["covr"].toCoverArtList();
1086       for(TagLib::MP4::CoverArtList::Iterator it = coverlist.begin(); it != coverlist.end(); it++) {
1087         if (it->data().size())
1088           return new GMCover(it->data().data(),it->data().size());
1089         }
1090       }
1091     }
1092   return nullptr;
1093   }
1094 
getCovers(GMCoverList & covers) const1095 FXint GMFileTag::getCovers(GMCoverList & covers) const {
1096   TagLib::FLAC::File * flacfile = dynamic_cast<TagLib::FLAC::File*>(file);
1097   if (flacfile) {
1098     const TagLib::List<TagLib::FLAC::Picture*> picturelist = flacfile->pictureList();
1099     for(TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end(); it++) {
1100       GMCover * cover = flac_load_cover_from_taglib((*it));
1101       if (cover) covers.append(cover);
1102       }
1103     if (covers.no()) return covers.no();
1104     }
1105   else if (xiph) {
1106 #if TAGLIB_VERSION >= TAGVERSION(1,11,0)
1107     const TagLib::List<TagLib::FLAC::Picture*> picturelist = xiph->pictureList();
1108     for(TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end(); it++) {
1109       GMCover * cover = flac_load_cover_from_taglib((*it));
1110       if (cover) covers.append(cover);
1111       }
1112 #else
1113     if (xiph->contains("METADATA_BLOCK_PICTURE")) {
1114       const TagLib::StringList & coverlist = xiph->fieldListMap()["METADATA_BLOCK_PICTURE"];
1115       for(TagLib::StringList::ConstIterator it = coverlist.begin(); it != coverlist.end(); it++) {
1116         const TagLib::ByteVector & bytevector = (*it).data(TagLib::String::Latin1);
1117         FXint type = xiph_check_cover(bytevector);
1118         if (type>=0 && type!=GMCover::FileIcon && type!=GMCover::OtherFileIcon && type!=GMCover::Fish){
1119           GMCover * cover = xiph_load_cover((*it).data(TagLib::String::Latin1));
1120           if (cover) covers.append(cover);
1121           }
1122         }
1123       }
1124 #endif
1125     }
1126   else if (id3v2) {
1127     TagLib::ID3v2::FrameList framelist = id3v2->frameListMap()["APIC"];
1128     if(!framelist.isEmpty()){
1129       for(TagLib::ID3v2::FrameList::Iterator it = framelist.begin(); it != framelist.end(); it++) {
1130         TagLib::ID3v2::AttachedPictureFrame * frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
1131         GMCover * cover = id3v2_load_cover(frame);
1132         if (cover) covers.append(cover);
1133         }
1134       }
1135     }
1136   else if (mp4) {
1137     if (mp4->itemListMap().contains("covr")) {
1138       TagLib::MP4::CoverArtList coverlist = mp4->itemListMap()["covr"].toCoverArtList();
1139       for(TagLib::MP4::CoverArtList::Iterator it = coverlist.begin(); it != coverlist.end(); it++) {
1140         if (it->data().size())
1141           covers.append(new GMCover(it->data().data(),it->data().size(),0));
1142         }
1143       }
1144     }
1145   return covers.no();
1146   }
1147 
1148 
replaceCover(GMCover * cover,FXuint mode)1149 void GMFileTag::replaceCover(GMCover*cover,FXuint mode){
1150   TagLib::FLAC::File * flacfile = dynamic_cast<TagLib::FLAC::File*>(file);
1151   if (mode==COVER_REPLACE_TYPE) {
1152     if (flacfile) {
1153       const TagLib::List<TagLib::FLAC::Picture*> picturelist = flacfile->pictureList();
1154       for(TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end();it++){
1155         if (cover->type==(*it)->type()) {
1156           flacfile->removePicture((*it));
1157           }
1158         }
1159       }
1160     else if (id3v2) {
1161       TagLib::ID3v2::FrameList framelist = id3v2->frameListMap()["APIC"];
1162       if(!framelist.isEmpty()){
1163         for(TagLib::ID3v2::FrameList::Iterator it = framelist.begin(); it != framelist.end(); it++) {
1164           TagLib::ID3v2::AttachedPictureFrame * frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
1165           if (frame->type()==cover->type) {
1166             id3v2->removeFrame(frame);
1167             }
1168           }
1169         }
1170       }
1171     else if (xiph) {
1172 #if TAGLIB_VERSION >= TAGVERSION(1,11,0)
1173       const TagLib::List<TagLib::FLAC::Picture*> picturelist = xiph->pictureList();
1174       for(TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end();it++){
1175         if (cover->type==(*it)->type()) {
1176           xiph->removePicture((*it));
1177           }
1178         }
1179 #else
1180       TagLib::StringList & coverlist = const_cast<TagLib::StringList&>(xiph->fieldListMap()["METADATA_BLOCK_PICTURE"]);
1181       TagLib::StringList::Iterator it = coverlist.begin();
1182       while(it!=coverlist.end()){
1183         const TagLib::ByteVector & bytevector = (*it).data(TagLib::String::Latin1);
1184         FXint type = xiph_check_cover(bytevector);
1185         if (type==cover->type)
1186           it=coverlist.erase(it);
1187         else
1188           it++;
1189         }
1190 #endif
1191       }
1192     else if (mp4) {
1193       // mp4 has no type information so we erase all
1194       mp4->itemListMap().erase("covr");
1195       }
1196     }
1197   else { // COVER_REPLACE_ALL
1198     clearCovers();
1199     }
1200   appendCover(cover);
1201   }
1202 
clearCovers()1203 void GMFileTag::clearCovers() {
1204   TagLib::FLAC::File * flacfile = dynamic_cast<TagLib::FLAC::File*>(file);
1205   if (flacfile) {
1206     flacfile->removePictures();
1207     }
1208   else if (id3v2) {
1209     id3v2->removeFrames("APIC");
1210     }
1211   else if (xiph) {
1212 #if TAGLIB_VERSION >= TAGVERSION(1,11,0)
1213     xiph->removeAllPictures();
1214 #else
1215     xiph->removeField("METADATA_BLOCK_PICTURE");
1216 #endif
1217     }
1218   else if (mp4) {
1219     mp4->itemListMap().erase("covr");
1220     }
1221   }
1222 
1223 
appendCover(GMCover * cover)1224 void GMFileTag::appendCover(GMCover* cover){
1225   GMImageInfo info;
1226 
1227   TagLib::FLAC::File * flacfile = dynamic_cast<TagLib::FLAC::File*>(file);
1228   if (flacfile) {
1229     if (cover->getImageInfo(info)) {
1230       TagLib::FLAC::Picture * picture = new TagLib::FLAC::Picture();
1231       picture->setWidth(info.width);
1232       picture->setHeight(info.height);
1233       picture->setColorDepth(info.bps);
1234       picture->setNumColors(info.colors);
1235       picture->setMimeType(TagLib::String(cover->mimeType().text(),TagLib::String::UTF8));
1236       picture->setDescription(TagLib::String(cover->description.text(),TagLib::String::UTF8));
1237       picture->setType(static_cast<TagLib::FLAC::Picture::Type>(cover->type));
1238       picture->setData(TagLib::ByteVector((const FXchar*)cover->data,cover->size));
1239       flacfile->addPicture(picture);
1240       }
1241     }
1242   else if (xiph) {
1243     if (cover->getImageInfo(info)) {
1244 #if TAGLIB_VERSION >= TAGVERSION(1,11,0)
1245       TagLib::FLAC::Picture * picture = new TagLib::FLAC::Picture();
1246       picture->setWidth(info.width);
1247       picture->setHeight(info.height);
1248       picture->setColorDepth(info.bps);
1249       picture->setNumColors(info.colors);
1250       picture->setMimeType(TagLib::String(cover->mimeType().text(),TagLib::String::UTF8));
1251       picture->setDescription(TagLib::String(cover->description.text(),TagLib::String::UTF8));
1252       picture->setType(static_cast<TagLib::FLAC::Picture::Type>(cover->type));
1253       picture->setData(TagLib::ByteVector((const FXchar*)cover->data,cover->size));
1254       xiph->addPicture(picture);
1255 #else
1256       FXString mimetype = cover->mimeType();
1257       FXint nbytes = 32 + cover->description.length() + mimetype.length() + cover->size;
1258       Base64Encoder base64(nbytes);
1259 #if FOX_BIGENDIAN == 0
1260       base64.encode(swap32(cover->type));
1261       base64.encode(swap32(mimetype.length()));
1262       base64.encode((const FXuchar*)mimetype.text(),mimetype.length());
1263       base64.encode(swap32(cover->description.length()));
1264       base64.encode((const FXuchar*)cover->description.text(),cover->description.length());
1265       base64.encode(swap32(info.width));
1266       base64.encode(swap32(info.height));
1267       base64.encode(swap32(info.bps));
1268       base64.encode(swap32(info.colors));
1269       base64.encode(swap32(cover->size));
1270 #else
1271       base64.encode(cover->type);
1272       base64.encode(mimetype.length());
1273       base64.encode((const FXuchar*)mimetype.text(),mimetype.length());
1274       base64.encode(cover->description.length());
1275       base64.encode(cover->description.text(),cover->description.length());
1276       base64.encode(info.width);
1277       base64.encode(info.height);
1278       base64.encode(info.bps);
1279       base64.encode(info.colors);
1280       base64.encode(cover->size);
1281 #endif
1282       base64.encode(cover->data,cover->size);
1283       base64.finish();
1284       xiph_add_field("METADATA_BLOCK_PICTURE",base64.getOutput());
1285 #endif
1286       }
1287     }
1288   else if (id3v2) {
1289     TagLib::ID3v2::AttachedPictureFrame * frame = new TagLib::ID3v2::AttachedPictureFrame();
1290     frame->setPicture(TagLib::ByteVector((const FXchar*)cover->data,cover->size));
1291     frame->setType(static_cast<TagLib::ID3v2::AttachedPictureFrame::Type>(cover->type));
1292     frame->setMimeType(TagLib::String(cover->mimeType().text(),TagLib::String::UTF8));
1293     frame->setDescription(TagLib::String(cover->description.text(),TagLib::String::UTF8));
1294     frame->setTextEncoding(TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding());
1295     id3v2->addFrame(frame);
1296     }
1297   else if (mp4) {
1298     TagLib::MP4::CoverArt::Format format;
1299     switch(cover->fileType()){
1300       case FILETYPE_PNG: format = TagLib::MP4::CoverArt::PNG; break;
1301       case FILETYPE_JPG: format = TagLib::MP4::CoverArt::JPEG; break;
1302       case FILETYPE_BMP: format = TagLib::MP4::CoverArt::BMP; break;
1303       case FILETYPE_GIF: format = TagLib::MP4::CoverArt::GIF; break;
1304       default: return; break;
1305       }
1306     if (!mp4->itemListMap().contains("covr")) {
1307       TagLib::MP4::CoverArtList list;
1308       list.append(TagLib::MP4::CoverArt(format,TagLib::ByteVector((const FXchar*)cover->data,cover->size)));
1309       mp4->itemListMap().insert("covr",list);
1310       }
1311     else {
1312       TagLib::MP4::CoverArtList list = mp4->itemListMap()["covr"].toCoverArtList();
1313       list.append(TagLib::MP4::CoverArt(format,TagLib::ByteVector((const FXchar*)cover->data,cover->size)));
1314       mp4->itemListMap().insert("covr",list);
1315       }
1316     }
1317 }
1318 
1319 
1320 
1321 
1322 
1323 
1324 
GMAudioProperties()1325 GMAudioProperties::GMAudioProperties() :
1326   bitrate(0),
1327   samplerate(0),
1328   channels(0),
1329   samplesize(0),
1330   filetype(FILETYPE_UNKNOWN) {
1331   }
1332 
1333 
load(const FXString & filename)1334 FXbool GMAudioProperties::load(const FXString & filename){
1335   GMFileTag tags;
1336   if (tags.open(filename,FILETAG_AUDIOPROPERTIES)){
1337     bitrate    = tags.getBitRate();
1338     samplerate = tags.getSampleRate();
1339     channels   = tags.getChannels();
1340     samplesize = tags.getSampleSize();
1341     filetype   = tags.getFileType();
1342     return true;
1343     }
1344   return false;
1345   }
1346 
1347 
1348 class GMTagLibDebugListener : public TagLib::DebugListener {
1349 private:
1350   GMTagLibDebugListener(const GMTagLibDebugListener &);
1351   GMTagLibDebugListener &operator=(const GMTagLibDebugListener &);
1352 public:
GMTagLibDebugListener()1353   GMTagLibDebugListener(){}
1354 
1355 #ifdef DEBUG
printMessage(const TagLib::String & msg)1356   virtual void printMessage(const TagLib::String &msg){
1357     fxmessage("%s\n",msg.toCString(true));
1358     }
1359 #else
printMessage(const TagLib::String &)1360   virtual void printMessage(const TagLib::String&){}
1361 #endif
1362 
1363   };
1364 
1365 
1366 class GMStringHandler : public TagLib::ID3v1::StringHandler {
1367   public:
1368     static GMStringHandler * instance;
1369   protected:
1370     const FXTextCodec * codec;
1371   public:
GMStringHandler(const FXTextCodec * c)1372     GMStringHandler(const FXTextCodec *c) : codec(c) {
1373       FXASSERT(codec!=nullptr);
1374       FXASSERT(instance==nullptr);
1375       GM_DEBUG_PRINT("[tag] id3v1 string handler: %s\n",codec->name());
1376       instance=this;
1377       }
1378 
1379       /*!
1380        * Decode a string from \a data.  The default implementation assumes that
1381        * \a data is an ISO-8859-1 (Latin1) character array.
1382        */
parse(const TagLib::ByteVector & in) const1383     virtual TagLib::String parse(const TagLib::ByteVector &in) const {
1384        TagLib::ByteVector utf;
1385        FXint n = codec->mb2utflen(in.data(),in.size());
1386        utf.resize(n);
1387        codec->mb2utf(utf.data(),utf.size(),in.data(),in.size());
1388        return TagLib::String(utf,TagLib::String::UTF8).stripWhiteSpace();
1389       }
1390 
1391       /*!
1392        * Encode a ByteVector with the data from \a s.  The default implementation
1393        * assumes that \a s is an ISO-8859-1 (Latin1) string.  If the string is
1394        * does not conform to ISO-8859-1, no value is written.
1395        *
1396        * \warning It is recommended that you <b>not</b> override this method, but
1397        * instead do not write an ID3v1 tag in the case that the data is not
1398        * ISO-8859-1.
1399        */
1400       //virtual ByteVector render(const String &s) const;
1401 
~GMStringHandler()1402     virtual ~GMStringHandler() {
1403       instance=nullptr;
1404       }
1405     };
1406 
1407 
1408 
1409 static GMTagLibDebugListener debuglistener;
1410 GMStringHandler* GMStringHandler::instance = nullptr;
1411 
1412 namespace GMTag {
1413 
init()1414 void init(){
1415   TagLib::ID3v2::FrameFactory::instance()->setDefaultTextEncoding(TagLib::String::UTF16);
1416   TagLib::setDebugListener(&debuglistener);
1417   }
1418 
setID3v1Encoding(const FXTextCodec * codec)1419 void setID3v1Encoding(const FXTextCodec * codec){
1420   if (codec) {
1421     FXASSERT(GMStringHandler::instance==nullptr);
1422     TagLib::ID3v1::Tag::setStringHandler(new GMStringHandler(codec));
1423     }
1424   else {
1425     TagLib::ID3v1::Tag::setStringHandler(nullptr);
1426     if (GMStringHandler::instance) {
1427       delete GMStringHandler::instance;
1428       }
1429     }
1430   }
1431 
1432 
length(GMTrack & info)1433 FXbool length(GMTrack & info) {
1434   GMFileTag tags;
1435   if (!tags.open(info.url,FILETAG_AUDIOPROPERTIES))
1436     return false;
1437   info.time = tags.getTime();
1438   return true;
1439   }
1440 
1441 
1442 
1443 
1444 }
1445 
1446 
1447 
1448 
1449 
1450 
1451 
1452 
1453