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