MythTV master
metaioflacvorbis.cpp
Go to the documentation of this file.
1
2// libmythbase
5
6// qt
7#include <QBuffer>
8
9// Taglib
10#include <taglib/xiphcomment.h>
11
12// libmythmetadata
13#include "metaioflacvorbis.h"
14#include "musicmetadata.h"
15#include "musicutils.h"
16
23TagLib::FLAC::File *MetaIOFLACVorbis::OpenFile(const QString &filename)
24{
25 QByteArray fname = filename.toLocal8Bit();
26 auto *flacfile = new TagLib::FLAC::File(fname.constData());
27
28 if (!flacfile->isOpen())
29 {
30 delete flacfile;
31 flacfile = nullptr;
32 }
33
34 return flacfile;
35}
36
37
42{
43 if (!mdata)
44 return false;
45
46 if (filename.isEmpty())
47 return false;
48
50
51 TagLib::FLAC::File *flacfile = OpenFile(m_filename);
52
53 if (!flacfile)
54 return false;
55
56 TagLib::Ogg::XiphComment *tag = flacfile->xiphComment(true);
57
58 if (!tag)
59 {
60 delete flacfile;
61 return false;
62 }
63
64 WriteGenericMetadata(tag, mdata);
65
66 // Compilation
67 if (mdata->Compilation())
68 {
69 tag->addField("MUSICBRAINZ_ALBUMARTISTID",
71 tag->addField("COMPILATION_ARTIST",
72 QStringToTString(mdata->CompilationArtist()), true);
73 }
74 else
75 {
76 // Don't remove the musicbrainz field unless it indicated a compilation
77 if (tag->contains("MUSICBRAINZ_ALBUMARTISTID") &&
78 (tag->fieldListMap()["MUSICBRAINZ_ALBUMARTISTID"].toString() ==
80 {
81 tag->removeFields("MUSICBRAINZ_ALBUMARTISTID");
82 }
83 tag->removeFields("COMPILATION_ARTIST");
84 }
85
87 bool result = flacfile->save();
89
90 delete flacfile;
91
92 return (result);
93}
94
99{
100 TagLib::FLAC::File *flacfile = OpenFile(filename);
101
102 if (!flacfile)
103 return nullptr;
104
105 TagLib::Ogg::XiphComment *tag = flacfile->xiphComment();
106
107 if (!tag)
108 {
109 delete flacfile;
110 return nullptr;
111 }
112
113 auto *metadata = new MusicMetadata(filename);
114
115 ReadGenericMetadata(tag, metadata);
116
117 bool compilation = false;
118
119 if (tag->contains("COMPILATION_ARTIST"))
120 {
121 QString compilation_artist = TStringToQString(
122 tag->fieldListMap()["COMPILATION_ARTIST"].toString()).trimmed();
123 if (compilation_artist != metadata->Artist())
124 {
125 metadata->setCompilationArtist(compilation_artist);
126 compilation = true;
127 }
128 }
129 else if (tag->contains("ALBUMARTIST"))
130 {
131 QString compilation_artist = TStringToQString(
132 tag->fieldListMap()["ALBUMARTIST"].toString()).trimmed();
133 if (compilation_artist != metadata->Artist())
134 {
135 metadata->setCompilationArtist(compilation_artist);
136 compilation = true;
137 }
138 }
139
140 if (!compilation && tag->contains("MUSICBRAINZ_ALBUMARTISTID"))
141 {
142 QString musicbrainzcode = TStringToQString(
143 tag->fieldListMap()["MUSICBRAINZ_ALBUMARTISTID"].toString()).trimmed();
144 if (musicbrainzcode == MYTH_MUSICBRAINZ_ALBUMARTIST_UUID)
145 compilation = true;
146 }
147
148 metadata->setCompilation(compilation);
149
150 if (metadata->Length() <= 0ms)
151 metadata->setLength(getTrackLength(flacfile));
152
153 if (tag->contains("DISCNUMBER"))
154 {
155 bool valid = false;
156 int n = tag->fieldListMap()["DISCNUMBER"].toString().toInt(&valid);
157 if (valid)
158 metadata->setDiscNumber(n);
159 }
160
161 if (tag->contains("TOTALTRACKS"))
162 {
163 bool valid = false;
164 int n = tag->fieldListMap()["TOTALTRACKS"].toString().toInt(&valid);
165 if (valid)
166 metadata->setTrackCount(n);
167 }
168
169 if (tag->contains("TOTALDISCS"))
170 {
171 bool valid = false;
172 int n = tag->fieldListMap()["TOTALDISCS"].toString().toInt(&valid);
173 if (valid)
174 metadata->setDiscCount(n);
175 }
176
177 delete flacfile;
178
179 return metadata;
180}
181
190{
191 auto *picture = new QImage();
192 TagLib::FLAC::File * flacfile = OpenFile(filename);
193
194 if (flacfile)
195 {
196 TagLib::FLAC::Picture* pic = getPictureFromFile(flacfile, type);
197 if (pic)
198 {
199 picture->loadFromData((const uchar *)pic->data().data(),
200 pic->data().size());
201 }
202 else
203 {
204 delete picture;
205 return nullptr;
206 }
207
208 delete flacfile;
209 }
210
211 return picture;
212}
213
215 TagLib::FLAC::File *flacfile,
217{
218 TagLib::FLAC::Picture *pic = nullptr;
219
220 if (flacfile)
221 {
222 TagLib::FLAC::Picture::Type artType = PictureTypeFromImageType(type);
223
224 // From what I can tell, FLAC::File maintains ownership of the Picture pointers, so no need to delete
225 const TagLib::List<TagLib::FLAC::Picture *>& picList = flacfile->pictureList();
226
227 for (auto *entry : picList)
228 {
229 if (entry->type() == artType)
230 {
231 //found the type we were looking for
232 pic = entry;
233 break;
234 }
235 }
236 }
237
238 return pic;
239}
240
242 ImageType itype) {
243 TagLib::FLAC::Picture::Type artType = TagLib::FLAC::Picture::Other;
244 switch (itype)
245 {
246 case IT_UNKNOWN :
247 artType = TagLib::FLAC::Picture::Other;
248 break;
249 case IT_FRONTCOVER :
250 artType = TagLib::FLAC::Picture::FrontCover;
251 break;
252 case IT_BACKCOVER :
253 artType = TagLib::FLAC::Picture::BackCover;
254 break;
255 case IT_CD :
256 artType = TagLib::FLAC::Picture::Media;
257 break;
258 case IT_INLAY :
259 artType = TagLib::FLAC::Picture::LeafletPage;
260 break;
261 case IT_ARTIST :
262 artType = TagLib::FLAC::Picture::Artist;
263 break;
264 default:
265 return TagLib::FLAC::Picture::Other;
266 }
267
268 return artType;
269}
270
277{
278 AlbumArtList artlist;
279 TagLib::FLAC::File * flacfile = OpenFile(filename);
280
281 if (flacfile)
282 {
283 const TagLib::List<TagLib::FLAC::Picture *>& picList = flacfile->pictureList();
284
285 for (auto *pic : picList)
286 {
287 // Assume a valid image would have at least
288 // 100 bytes of data (1x1 indexed gif is 35 bytes)
289 if (pic->data().size() < 100)
290 {
291 LOG(VB_GENERAL, LOG_NOTICE,
292 "Music Scanner - Discarding picture "
293 "with size less than 100 bytes");
294 continue;
295 }
296
297 auto *art = new AlbumArtImage();
298
299 if (pic->description().isEmpty())
300 art->m_description.clear();
301 else
302 art->m_description = TStringToQString(pic->description());
303
304 art->m_embedded = true;
305 art->m_hostname = gCoreContext->GetHostName();
306
307 QString ext = getExtFromMimeType(
308 TStringToQString(pic->mimeType()).toLower());
309
310 switch (pic->type())
311 {
312 case TagLib::FLAC::Picture::FrontCover :
313 art->m_imageType = IT_FRONTCOVER;
314 art->m_filename = QString("front") + ext;
315 break;
316 case TagLib::FLAC::Picture::BackCover :
317 art->m_imageType = IT_BACKCOVER;
318 art->m_filename = QString("back") + ext;
319 break;
320 case TagLib::FLAC::Picture::Media :
321 art->m_imageType = IT_CD;
322 art->m_filename = QString("cd") + ext;
323 break;
324 case TagLib::FLAC::Picture::LeafletPage :
325 art->m_imageType = IT_INLAY;
326 art->m_filename = QString("inlay") + ext;
327 break;
328 case TagLib::FLAC::Picture::Artist :
329 art->m_imageType = IT_ARTIST;
330 art->m_filename = QString("artist") + ext;
331 break;
332 case TagLib::FLAC::Picture::Other :
333 art->m_imageType = IT_UNKNOWN;
334 art->m_filename = QString("unknown") + ext;
335 break;
336 default:
337 LOG(VB_GENERAL, LOG_ERR, "Music Scanner - picture found "
338 "with unsupported type");
339 delete art;
340 continue;
341 }
342
343 artlist.append(art);
344 }
345 }
346
347 delete flacfile;
348 return artlist;
349}
350
361 const AlbumArtImage *albumart)
362{
363#if TAGLIB_MAJOR_VERSION == 1 && TAGLIB_MINOR_VERSION >= 8
364 if (filename.isEmpty() || !albumart)
365 return false;
366
368
369 bool retval = false;
370
371 // load the image into a QByteArray
372 QImage image(albumart->m_filename);
373 QByteArray imageData;
374 QBuffer buffer(&imageData);
375 buffer.open(QIODevice::WriteOnly);
376 // Write the image data to a file
377 image.save(&buffer, "JPEG");
378
379 TagLib::FLAC::File * flacfile = OpenFile(filename);
380
381 // This presumes that there is only one art item of each type
382 if (flacfile)
383 {
384 // Now see if the art is in the FLAC file
385 TagLib::FLAC::Picture *pic = getPictureFromFile(flacfile, albumart->m_imageType);
386
387 if (pic)
388 {
389 // Remove the embedded image of the matching type
390 flacfile->removePicture(pic, false);
391 }
392 else
393 {
394 // Create a new image of the correct type
395 pic = new TagLib::FLAC::Picture();
396 pic->setType(PictureTypeFromImageType(albumart->m_imageType));
397 }
398
399 TagLib::ByteVector bytevector;
400 bytevector.setData(imageData.data(), imageData.size());
401
402 pic->setData(bytevector);
403 QString mimetype = "image/jpeg";
404
405 pic->setMimeType(QStringToTString(mimetype));
406 pic->setDescription(QStringToTString(albumart->m_description));
407
408 flacfile->addPicture(pic);
409
411 retval = flacfile->save();
413
414 delete flacfile;
415 }
416 else
417 {
418 retval = false;
419 }
420
421 return retval;
422#else
423 LOG(VB_GENERAL, LOG_WARNING,
424 "TagLib 1.8.0 or later is required to write albumart to flac xiphComment tags");
425 return false;
426#endif
427}
428
437 const AlbumArtImage *albumart)
438{
439#if TAGLIB_MAJOR_VERSION == 1 && TAGLIB_MINOR_VERSION >= 8
440
441 if (filename.isEmpty() || !albumart)
442 return false;
443
444 bool retval = false;
445
446 TagLib::FLAC::File * flacfile = OpenFile(filename);
447
448 // This presumes that there is only one art item of each type
449 if (flacfile)
450 {
451 // Now see if the art is in the FLAC file
452 TagLib::FLAC::Picture *pic = getPictureFromFile(flacfile, albumart->m_imageType);
453
454 if (pic)
455 {
456 // Remove the embedded image of the matching type
457 flacfile->removePicture(pic, false);
458 flacfile->save();
459 retval = true;
460 }
461 else
462 {
463 retval = false;
464 }
465
466 delete flacfile;
467 }
468 else
469 {
470 retval = false;
471 }
472
473 return retval;
474#else
475 LOG(VB_GENERAL, LOG_WARNING,
476 "TagLib 1.8.0 or later is required to remove albumart from flac xiphComment tags");
477 return false;
478#endif
479}
480
482 const AlbumArtImage* albumart,
483 ImageType newType)
484{
485 if (filename.isEmpty() || !albumart)
486 return false;
487
488 if (albumart->m_imageType == newType)
489 return true;
490
491 bool retval = false;
492
493 TagLib::FLAC::File * flacfile = OpenFile(filename);
494
495 // This presumes that there is only one art item of each type
496 if (flacfile)
497 {
498 // Now see if the art is in the FLAC file
499 TagLib::FLAC::Picture *pic = getPictureFromFile(flacfile, albumart->m_imageType);
500
501 if (pic)
502 {
503 pic->setType(PictureTypeFromImageType(newType));
504 flacfile->save();
505 retval = true;
506 }
507 else
508 {
509 retval = false;
510 }
511
512 delete flacfile;
513 }
514 else
515 {
516 retval = false;
517 }
518
519 return retval;
520}
521
523{
524 TagLib::FLAC::File *flacfile = OpenFile(filename);
525
526 if (!flacfile)
527 return false;
528
529 TagLib::Ogg::XiphComment *tag = flacfile->xiphComment(false);
530
531 bool retval = false;
532 if (tag && !tag->isEmpty())
533 retval = true;
534
535 delete flacfile;
536
537 return retval;
538}
539
540QString MetaIOFLACVorbis::getExtFromMimeType(const QString &mimeType)
541{
542 if (mimeType == "image/png")
543 return {".png"};
544 if (mimeType == "image/jpeg" || mimeType == "image/jpg")
545 return {".jpg"};
546 if (mimeType == "image/gif")
547 return {".gif"};
548 if (mimeType == "image/bmp")
549 return {".bmp"};
550
551 LOG(VB_GENERAL, LOG_ERR,
552 "Music Scanner - Unknown image mimetype found - " + mimeType);
553
554 return {};
555}
QString m_filename
Definition: musicmetadata.h:49
QString m_description
Definition: musicmetadata.h:52
ImageType m_imageType
Definition: musicmetadata.h:51
static QString getExtFromMimeType(const QString &mimeType)
bool TagExists(const QString &filename) override
static TagLib::FLAC::File * OpenFile(const QString &filename)
Open the file to read the tag.
QImage * getAlbumArt(const QString &filename, ImageType type) override
Read the albumart image from the file.
static TagLib::FLAC::Picture::Type PictureTypeFromImageType(ImageType itype)
static TagLib::FLAC::Picture * getPictureFromFile(TagLib::FLAC::File *flacfile, ImageType type)
bool changeImageType(const QString &filename, const AlbumArtImage *albumart, ImageType newType) override
bool write(const QString &filename, MusicMetadata *mdata) override
Writes all metadata back to a file.
bool writeAlbumArt(const QString &filename, const AlbumArtImage *albumart) override
Write the albumart image to the file.
bool removeAlbumArt(const QString &filename, const AlbumArtImage *albumart) override
Remove the albumart image from the file.
AlbumArtList getAlbumArtList(const QString &filename) override
Read the albumart images from the file.
MusicMetadata * read(const QString &filename) override
Reads MusicMetadata from a file.
static void WriteGenericMetadata(TagLib::Tag *tag, const MusicMetadata *metadata)
Writes metadata common to all tag formats to the tag.
static std::chrono::milliseconds getTrackLength(TagLib::File *file)
Find the length of the track (in milliseconds)
void ReadGenericMetadata(TagLib::Tag *tag, MusicMetadata *metadata)
Writes metadata common to all tag formats to the tag.
void restoreTimeStamps(void)
Definition: metaio.cpp:232
void saveTimeStamps(void)
Definition: metaio.cpp:223
QString m_filename
Definition: metaio.h:168
QString CompilationArtist() const
bool Compilation() const
QString GetHostName(void)
static constexpr const char * MYTH_MUSICBRAINZ_ALBUMARTIST_UUID
Definition: metaio.h:15
ImageType
Definition: musicmetadata.h:29
@ IT_INLAY
Definition: musicmetadata.h:34
@ IT_BACKCOVER
Definition: musicmetadata.h:32
@ IT_UNKNOWN
Definition: musicmetadata.h:30
@ IT_FRONTCOVER
Definition: musicmetadata.h:31
@ IT_ARTIST
Definition: musicmetadata.h:35
@ IT_CD
Definition: musicmetadata.h:33
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:56
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39