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