MythTV  master
metaioid3.cpp
Go to the documentation of this file.
1 #include <set>
2 
3 // C++ headers
4 #include <cmath>
5 
6 // qt
7 #include <QBuffer>
8 
9 // Libmythbase
12 
13 // Taglib
14 #include <flacfile.h>
15 #include <mpegfile.h>
16 
17 // libmythmetadata
18 #include "metaioid3.h"
19 #include "musicmetadata.h"
20 #include "musicutils.h"
21 
22 const String email = "music@mythtv.org"; // TODO username/ip/hostname?
23 
31 bool MetaIOID3::OpenFile(const QString &filename, bool forWriting)
32 {
33  // Check if file is already opened
34  if (m_file && (m_filename == filename) &&
35  (!forWriting || !m_file->readOnly()))
36  return true;
37 
38  if (m_file)
39  {
40  LOG(VB_FILE, LOG_DEBUG,
41  QString("MetaIO switch file: %1 New File: %2 Type: %3")
42  .arg(m_filename,
43  filename,
44  QString::number(m_fileType)));
45  }
46 
47  // If a file is open but it's not the requested file then close it first
48  if (m_file)
49  CloseFile();
50 
52 
53  QString extension = m_filename.section('.', -1);
54 
55  if (extension.toLower() == "flac")
56  m_fileType = kFLAC;
57  else if (extension.toLower() == "mp3" || extension.toLower() == "mp2")
58  m_fileType = kMPEG;
59  else
60  return false;
61 
62  QByteArray fname = m_filename.toLocal8Bit();
63  // Open the file
64  switch (m_fileType)
65  {
66  case kMPEG :
67  m_file = new TagLib::MPEG::File(fname.constData());
68  break;
69  case kFLAC :
70  m_file = new TagLib::FLAC::File(fname.constData());
71  break;
72  }
73 
74  // If the requested file could not be opened then close it and return false
75  if (!m_file->isOpen() || (forWriting && m_file->readOnly()))
76  {
77  if (m_file->isOpen())
78  {
79  LOG(VB_FILE, LOG_NOTICE,
80  QString("Could not open file for writing: %1").arg(m_filename));
81  }
82  else
83  {
84  LOG(VB_FILE, LOG_ERR,
85  QString("Could not open file: %1").arg(m_filename));
86  }
87 
88  CloseFile();
89  return false;
90  }
91 
92  return true;
93 }
94 
96 {
97  if (!m_file)
98  return false;
99 
100  saveTimeStamps();
101  bool retval = m_file->save();
103 
104  return retval;
105 }
106 
108 {
109  LOG(VB_FILE, LOG_DEBUG, QString("MetaIO Close file: %1") .arg(m_filename));
110  delete m_file;
111  m_file = nullptr;
112  m_fileType = kMPEG;
113  m_filename.clear();
114 }
115 
116 TagLib::ID3v2::Tag* MetaIOID3::GetID3v2Tag(bool create)
117 {
118  if (!m_file)
119  return nullptr;
120 
121  if (m_fileType == kMPEG)
122  {
123  auto *file = dynamic_cast<TagLib::MPEG::File*>(m_file);
124  return (file != nullptr) ? file->ID3v2Tag(create) : nullptr;
125  }
126 
127  if (m_fileType == kFLAC)
128  {
129  auto *file = dynamic_cast<TagLib::FLAC::File*>(m_file);
130  return (file != nullptr) ? file->ID3v2Tag(create) : nullptr;
131  }
132 
133  return nullptr;
134 }
135 
136 TagLib::ID3v1::Tag* MetaIOID3::GetID3v1Tag(bool create)
137 {
138  if (!m_file)
139  return nullptr;
140 
141  if (m_fileType == kMPEG)
142  {
143  auto *file = dynamic_cast<TagLib::MPEG::File*>(m_file);
144  return (file != nullptr) ? file->ID3v1Tag(create) : nullptr;
145  }
146 
147  return nullptr;
148 }
149 
153 bool MetaIOID3::write(const QString &filename, MusicMetadata* mdata)
154 {
155  if (filename.isEmpty())
156  return false;
157 
158  if (!OpenFile(filename, true))
159  return false;
160 
161  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
162 
163  if (!tag)
164  return false;
165 
166  WriteGenericMetadata(tag, mdata);
167 
168  // MythTV rating and playcount, stored in POPM frame
169  writeRating(tag, mdata->Rating());
170  writePlayCount(tag, mdata->PlayCount());
171  writeLastPlay(tag, mdata->LastPlay());
172 
173  // MusicBrainz ID
174  UserTextIdentificationFrame *musicbrainz = nullptr;
175  musicbrainz = find(tag, "MusicBrainz Album Artist Id");
176 
177  if (mdata->Compilation())
178  {
179 
180  if (!musicbrainz)
181  {
182  musicbrainz = new UserTextIdentificationFrame(TagLib::String::UTF8);
183  tag->addFrame(musicbrainz);
184  musicbrainz->setDescription("MusicBrainz Album Artist Id");
185  }
186 
187  musicbrainz->setText(MYTH_MUSICBRAINZ_ALBUMARTIST_UUID);
188  }
189  else if (musicbrainz)
190  {
191  tag->removeFrame(musicbrainz);
192  }
193 
194  // Compilation Artist Frame (TPE4/2)
195  if (!mdata->CompilationArtist().isEmpty())
196  {
197  TextIdentificationFrame *tpe4frame = nullptr;
198  TagLib::ID3v2::FrameList tpelist = tag->frameListMap()["TPE4"];
199  if (!tpelist.isEmpty())
200  tpe4frame = dynamic_cast<TextIdentificationFrame *>(tpelist.front());
201 
202  if (!tpe4frame)
203  {
204  tpe4frame = new TextIdentificationFrame(TagLib::ByteVector("TPE4"),
205  TagLib::String::UTF8);
206  tag->addFrame(tpe4frame);
207  }
208  tpe4frame->setText(QStringToTString(mdata->CompilationArtist()));
209 
210 
211  TextIdentificationFrame *tpe2frame = nullptr;
212  tpelist = tag->frameListMap()["TPE2"];
213  if (!tpelist.isEmpty())
214  tpe2frame = dynamic_cast<TextIdentificationFrame *>(tpelist.front());
215 
216  if (!tpe2frame)
217  {
218  tpe2frame = new TextIdentificationFrame(TagLib::ByteVector("TPE2"),
219  TagLib::String::UTF8);
220  tag->addFrame(tpe2frame);
221  }
222  tpe2frame->setText(QStringToTString(mdata->CompilationArtist()));
223  }
224 
225  return SaveFile();
226 }
227 
232 {
233  if (!OpenFile(filename))
234  return nullptr;
235 
236  TagLib::ID3v2::Tag *tag = GetID3v2Tag(true); // Create tag if none are found
237 
238  // if there is no ID3v2 tag, try to read the ID3v1 tag and copy it to
239  // the ID3v2 tag structure
240  if (tag->isEmpty())
241  {
242  TagLib::ID3v1::Tag *tag_v1 = GetID3v1Tag();
243 
244  if (!tag_v1)
245  return nullptr;
246 
247  if (!tag_v1->isEmpty())
248  {
249  tag->setTitle(tag_v1->title());
250  tag->setArtist(tag_v1->artist());
251  tag->setAlbum(tag_v1->album());
252  tag->setTrack(tag_v1->track());
253  tag->setYear(tag_v1->year());
254  tag->setGenre(tag_v1->genre());
255  }
256  }
257 
258  auto *metadata = new MusicMetadata(filename);
259 
260  ReadGenericMetadata(tag, metadata);
261 
262  bool compilation = false;
263 
264  // Compilation Artist (TPE4 Remix) or fallback to (TPE2 Band)
265  // N.B. The existance of a either frame is NOT an indication that this
266  // is a compilation, but if it is then one of them will probably hold
267  // the compilation artist.
268  TextIdentificationFrame *tpeframe = nullptr;
269  TagLib::ID3v2::FrameList tpelist = tag->frameListMap()["TPE4"];
270  if (tpelist.isEmpty() || tpelist.front()->toString().isEmpty())
271  tpelist = tag->frameListMap()["TPE2"];
272  if (!tpelist.isEmpty())
273  tpeframe = dynamic_cast<TextIdentificationFrame *>(tpelist.front());
274 
275  if (tpeframe && !tpeframe->toString().isEmpty())
276  {
277  QString compilation_artist = TStringToQString(tpeframe->toString())
278  .trimmed();
279  metadata->setCompilationArtist(compilation_artist);
280  }
281 
282  // Rating and playcount, stored in POPM frame
283  PopularimeterFrame *popm = findPOPM(tag, ""); // Global (all apps) tag
284 
285  // If no 'global' tag exists, look for the MythTV specific one
286  if (!popm)
287  {
288  popm = findPOPM(tag, email);
289  }
290 
291  // Fallback to using any POPM tag we can find
292  if (!popm)
293  {
294  if (!tag->frameListMap()["POPM"].isEmpty())
295  popm = dynamic_cast<PopularimeterFrame *>
296  (tag->frameListMap()["POPM"].front());
297  }
298 
299  if (popm)
300  {
301  int rating = popm->rating();
302  rating = lroundf(static_cast<float>(rating) / 255.0F * 10.0F);
303  metadata->setRating(rating);
304  metadata->setPlaycount(popm->counter());
305  }
306 
307  // Look for MusicBrainz Album+Artist ID in TXXX Frame
308  UserTextIdentificationFrame *musicbrainz = find(tag,
309  "MusicBrainz Album Artist Id");
310 
311  if (musicbrainz)
312  {
313  // If the MusicBrainz ID is the special "Various Artists" ID
314  // then compilation is TRUE
315  if (!compilation && !musicbrainz->fieldList().isEmpty())
316  {
317  for (auto & field : musicbrainz->fieldList())
318  {
319  QString ID = TStringToQString(field);
320 
322  {
323  compilation = true;
324  break;
325  }
326  }
327  }
328  }
329 
330  // TLEN - Ignored intentionally, some encoders write bad values
331  // e.g. Lame under certain circumstances will always write a length of
332  // 27 hours
333 
334  // Length
335  if (!tag->frameListMap()["TLEN"].isEmpty())
336  {
337  int length = tag->frameListMap()["TLEN"].front()->toString().toInt();
338  LOG(VB_FILE, LOG_DEBUG,
339  QString("MetaIOID3::read: Length for '%1' from tag is '%2'\n").arg(filename).arg(length));
340  }
341 
342  metadata->setCompilation(compilation);
343 
344  metadata->setLength(getTrackLength(m_file));
345 
346  // The number of tracks on the album, if supplied
347  if (!tag->frameListMap()["TRCK"].isEmpty())
348  {
349  QString trackFrame = TStringToQString(
350  tag->frameListMap()["TRCK"].front()->toString())
351  .trimmed();
352  int trackCount = trackFrame.section('/', -1).toInt();
353  if (trackCount > 0)
354  metadata->setTrackCount(trackCount);
355  }
356 
357  LOG(VB_FILE, LOG_DEBUG,
358  QString("MetaIOID3::read: Length for '%1' from properties is '%2'\n")
359  .arg(filename).arg(metadata->Length().count()));
360 
361  // Look for MythTVLastPlayed in TXXX Frame
362  UserTextIdentificationFrame *lastplayed = find(tag, "MythTVLastPlayed");
363  if (lastplayed)
364  {
365  QString lastPlayStr = TStringToQString(lastplayed->toString());
366  metadata->setLastPlay(QDateTime::fromString(lastPlayStr, Qt::ISODate));
367  }
368 
369  // Part of a set
370  if (!tag->frameListMap()["TPOS"].isEmpty())
371  {
372  QString pos = TStringToQString(
373  tag->frameListMap()["TPOS"].front()->toString()).trimmed();
374 
375  int discNumber = pos.section('/', 0, 0).toInt();
376  int discCount = pos.section('/', -1).toInt();
377 
378  if (discNumber > 0)
379  metadata->setDiscNumber(discNumber);
380  if (discCount > 0)
381  metadata->setDiscCount(discCount);
382  }
383 
384  return metadata;
385 }
386 
395 {
396  auto *picture = new QImage();
397 
398  AttachedPictureFrame::Type apicType
399  = AttachedPictureFrame::FrontCover;
400 
401  switch (type)
402  {
403  case IT_UNKNOWN :
404  apicType = AttachedPictureFrame::Other;
405  break;
406  case IT_FRONTCOVER :
407  apicType = AttachedPictureFrame::FrontCover;
408  break;
409  case IT_BACKCOVER :
410  apicType = AttachedPictureFrame::BackCover;
411  break;
412  case IT_CD :
413  apicType = AttachedPictureFrame::Media;
414  break;
415  case IT_INLAY :
416  apicType = AttachedPictureFrame::LeafletPage;
417  break;
418  case IT_ARTIST :
419  apicType = AttachedPictureFrame::Artist;
420  break;
421  default:
422  return picture;
423  }
424 
425  if (OpenFile(filename))
426  {
427  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
428  if (tag && !tag->frameListMap()["APIC"].isEmpty())
429  {
430  TagLib::ID3v2::FrameList apicframes = tag->frameListMap()["APIC"];
431 
432  for (auto & apicframe : apicframes)
433  {
434  auto *frame = dynamic_cast<AttachedPictureFrame *>(apicframe);
435  if (frame && frame->type() == apicType)
436  {
437  picture->loadFromData((const uchar *)frame->picture().data(),
438  frame->picture().size());
439  return picture;
440  }
441  }
442  }
443  }
444 
445  delete picture;
446 
447  return nullptr;
448 }
449 
450 
457 {
458  AlbumArtList imageList;
459  if (OpenFile(filename))
460  {
461  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
462 
463  if (!tag)
464  return imageList;
465 
466  imageList = readAlbumArt(tag);
467  }
468 
469  return imageList;
470 }
471 
479 AlbumArtList MetaIOID3::readAlbumArt(TagLib::ID3v2::Tag *tag)
480 {
481  AlbumArtList artlist;
482 
483  if (!tag->frameListMap()["APIC"].isEmpty())
484  {
485  TagLib::ID3v2::FrameList apicframes = tag->frameListMap()["APIC"];
486 
487  for (auto & apicframe : apicframes)
488  {
489  auto *frame = dynamic_cast<AttachedPictureFrame *>(apicframe);
490  if (frame == nullptr)
491  {
492  LOG(VB_GENERAL, LOG_DEBUG,
493  "Music Scanner - Cannot convert APIC frame");
494  continue;
495  }
496 
497  // Assume a valid image would have at least
498  // 100 bytes of data (1x1 indexed gif is 35 bytes)
499  if (frame->picture().size() < 100)
500  {
501  LOG(VB_GENERAL, LOG_NOTICE,
502  "Music Scanner - Discarding APIC frame "
503  "with size less than 100 bytes");
504  continue;
505  }
506 
507  auto *art = new AlbumArtImage();
508 
509  if (frame->description().isEmpty())
510  art->m_description.clear();
511  else
512  art->m_description = TStringToQString(frame->description());
513 
514  art->m_embedded = true;
515  art->m_hostname = gCoreContext->GetHostName();
516 
517  QString ext = getExtFromMimeType(
518  TStringToQString(frame->mimeType()).toLower());
519 
520  switch (frame->type())
521  {
522  case AttachedPictureFrame::FrontCover :
523  art->m_imageType = IT_FRONTCOVER;
524  art->m_filename = QString("front") + ext;
525  break;
526  case AttachedPictureFrame::BackCover :
527  art->m_imageType = IT_BACKCOVER;
528  art->m_filename = QString("back") + ext;
529  break;
530  case AttachedPictureFrame::Media :
531  art->m_imageType = IT_CD;
532  art->m_filename = QString("cd") + ext;
533  break;
534  case AttachedPictureFrame::LeafletPage :
535  art->m_imageType = IT_INLAY;
536  art->m_filename = QString("inlay") + ext;
537  break;
538  case AttachedPictureFrame::Artist :
539  art->m_imageType = IT_ARTIST;
540  art->m_filename = QString("artist") + ext;
541  break;
542  case AttachedPictureFrame::Other :
543  art->m_imageType = IT_UNKNOWN;
544  art->m_filename = QString("unknown") + ext;
545  break;
546  default:
547  LOG(VB_GENERAL, LOG_ERR, "Music Scanner - APIC tag found "
548  "with unsupported type");
549  delete art;
550  continue;
551  }
552 
553  artlist.append(art);
554  }
555  }
556 
557  return artlist;
558 }
559 
560 QString MetaIOID3::getExtFromMimeType(const QString &mimeType)
561 {
562  if (mimeType == "image/png")
563  return {".png"};
564  if (mimeType == "image/jpeg" || mimeType == "image/jpg")
565  return {".jpg"};
566  if (mimeType == "image/gif")
567  return {".gif"};
568  if (mimeType == "image/bmp")
569  return {".bmp"};
570 
571  LOG(VB_GENERAL, LOG_ERR,
572  "Music Scanner - Unknown image mimetype found - " + mimeType);
573 
574  return {};
575 }
576 
585 AttachedPictureFrame* MetaIOID3::findAPIC(TagLib::ID3v2::Tag *tag,
586  AttachedPictureFrame::Type type,
587  const String &description)
588 {
589  TagLib::ID3v2::FrameList l = tag->frameList("APIC");
590  for (auto & frame : l)
591  {
592  auto *f = dynamic_cast<AttachedPictureFrame *>(frame);
593  if (f && f->type() == type &&
594  (description.isEmpty() || f->description() == description))
595  return f;
596  }
597  return nullptr;
598 }
599 
609 bool MetaIOID3::writeAlbumArt(const QString &filename,
610  const AlbumArtImage *albumart)
611 {
612  if (filename.isEmpty() || !albumart)
613  return false;
614 
615  // load the image into a QByteArray
616  QImage image(albumart->m_filename);
617  QByteArray imageData;
618  QBuffer buffer(&imageData);
619  buffer.open(QIODevice::WriteOnly);
620  image.save(&buffer, "JPEG");
621 
622  AttachedPictureFrame::Type type = AttachedPictureFrame::Other;
623  switch (albumart->m_imageType)
624  {
625  case IT_FRONTCOVER:
626  type = AttachedPictureFrame::FrontCover;
627  break;
628  case IT_BACKCOVER:
629  type = AttachedPictureFrame::BackCover;
630  break;
631  case IT_CD:
632  type = AttachedPictureFrame::Media;
633  break;
634  case IT_INLAY:
635  type = AttachedPictureFrame::LeafletPage;
636  break;
637  case IT_ARTIST:
638  type = AttachedPictureFrame::Artist;
639  break;
640  default:
641  type = AttachedPictureFrame::Other;
642  break;
643  }
644 
645  if (!OpenFile(filename, true))
646  return false;
647 
648  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
649 
650  if (!tag)
651  return false;
652 
653  AttachedPictureFrame *apic = findAPIC(tag, type,
654  QStringToTString(albumart->m_description));
655 
656  if (!apic)
657  {
658  apic = new AttachedPictureFrame();
659  tag->addFrame(apic);
660  apic->setType(type);
661  }
662 
663  QString mimetype = "image/jpeg";
664 
665  TagLib::ByteVector bytevector;
666  bytevector.setData(imageData.data(), imageData.size());
667 
668  apic->setMimeType(QStringToTString(mimetype));
669  apic->setPicture(bytevector);
670  apic->setDescription(QStringToTString(albumart->m_description));
671 
672  return SaveFile();
673 }
674 
683  const AlbumArtImage *albumart)
684 {
685  if (filename.isEmpty() || !albumart)
686  return false;
687 
688  AttachedPictureFrame::Type type = AttachedPictureFrame::Other;
689  switch (albumart->m_imageType)
690  {
691  case IT_FRONTCOVER:
692  type = AttachedPictureFrame::FrontCover;
693  break;
694  case IT_BACKCOVER:
695  type = AttachedPictureFrame::BackCover;
696  break;
697  case IT_CD:
698  type = AttachedPictureFrame::Media;
699  break;
700  case IT_INLAY:
701  type = AttachedPictureFrame::LeafletPage;
702  break;
703  case IT_ARTIST:
704  type = AttachedPictureFrame::Artist;
705  break;
706  default:
707  type = AttachedPictureFrame::Other;
708  break;
709  }
710 
711  if (!OpenFile(filename, true))
712  return false;
713 
714  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
715 
716  if (!tag)
717  return false;
718 
719  AttachedPictureFrame *apic = findAPIC(tag, type,
720  QStringToTString(albumart->m_description));
721  if (!apic)
722  return false;
723 
724  tag->removeFrame(apic);
725 
726  return SaveFile();
727 }
728 
730  const AlbumArtImage* albumart,
731  ImageType newType)
732 {
733  if (!albumart)
734  return false;
735 
736  if (albumart->m_imageType == newType)
737  return true;
738 
739  AttachedPictureFrame::Type type = AttachedPictureFrame::Other;
740  switch (albumart->m_imageType)
741  {
742  case IT_FRONTCOVER:
743  type = AttachedPictureFrame::FrontCover;
744  break;
745  case IT_BACKCOVER:
746  type = AttachedPictureFrame::BackCover;
747  break;
748  case IT_CD:
749  type = AttachedPictureFrame::Media;
750  break;
751  case IT_INLAY:
752  type = AttachedPictureFrame::LeafletPage;
753  break;
754  case IT_ARTIST:
755  type = AttachedPictureFrame::Artist;
756  break;
757  default:
758  type = AttachedPictureFrame::Other;
759  break;
760  }
761 
762  if (!OpenFile(filename, true))
763  return false;
764 
765  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
766 
767  if (!tag)
768  return false;
769 
770  AttachedPictureFrame *apic = findAPIC(tag, type,
771  QStringToTString(albumart->m_description));
772  if (!apic)
773  return false;
774 
775  // set the new image type
776  switch (newType)
777  {
778  case IT_FRONTCOVER:
779  apic->setType(AttachedPictureFrame::FrontCover);
780  break;
781  case IT_BACKCOVER:
782  apic->setType(AttachedPictureFrame::BackCover);
783  break;
784  case IT_CD:
785  apic->setType(AttachedPictureFrame::Media);
786  break;
787  case IT_INLAY:
788  apic->setType(AttachedPictureFrame::LeafletPage);
789  break;
790  case IT_ARTIST:
791  apic->setType(AttachedPictureFrame::Artist);
792  break;
793  default:
794  apic->setType(AttachedPictureFrame::Other);
795  break;
796  }
797 
798  return SaveFile();
799 }
800 
811 UserTextIdentificationFrame* MetaIOID3::find(TagLib::ID3v2::Tag *tag,
812  const String &description)
813 {
814  TagLib::ID3v2::FrameList l = tag->frameList("TXXX");
815  for (auto & frame : l)
816  {
817  auto *f = dynamic_cast<UserTextIdentificationFrame *>(frame);
818  if (f && f->description() == description)
819  return f;
820  }
821  return nullptr;
822 }
823 
831 PopularimeterFrame* MetaIOID3::findPOPM(TagLib::ID3v2::Tag *tag,
832  const String &_email)
833 {
834  TagLib::ID3v2::FrameList l = tag->frameList("POPM");
835  for (auto & frame : l)
836  {
837  auto *f = dynamic_cast<PopularimeterFrame *>(frame);
838  if (f && f->email() == _email)
839  return f;
840  }
841  return nullptr;
842 }
843 
844 bool MetaIOID3::writePlayCount(TagLib::ID3v2::Tag *tag, int playcount)
845 {
846  if (!tag)
847  return false;
848 
849  // MythTV Specific playcount Tag
850  PopularimeterFrame *popm = findPOPM(tag, email);
851 
852  if (!popm)
853  {
854  popm = new PopularimeterFrame();
855  tag->addFrame(popm);
856  popm->setEmail(email);
857  }
858 
859  int prevCount = popm->counter();
860  int countDiff = playcount - prevCount;
861  // Allow for situations where the user has rolled back to an old DB backup
862  if (countDiff > 0)
863  {
864  popm->setCounter(playcount);
865 
866  // Global playcount Tag - Updated by all apps/hardware that support it
867  PopularimeterFrame *gpopm = findPOPM(tag, "");
868  if (!gpopm)
869  {
870  gpopm = new PopularimeterFrame();
871  tag->addFrame(gpopm);
872  gpopm->setEmail("");
873  }
874  gpopm->setCounter((gpopm->counter() > 0) ? gpopm->counter() + countDiff : playcount);
875  }
876 
877  return true;
878 }
879 
881 {
882  if (filename.isEmpty())
883  return false;
884 
885  int rating = mdata->Rating();
886  int playcount = mdata->PlayCount();
887  QDateTime lastPlay = mdata->LastPlay();
888 
889  if (!OpenFile(filename, true))
890  return false;
891 
892  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
893 
894  if (!tag)
895  return false;
896 
897  bool result = (writeRating(tag, rating) && writePlayCount(tag, playcount) &&
898  writeLastPlay(tag, lastPlay));
899 
900  if (!SaveFile())
901  return false;
902 
903  return result;
904 }
905 
906 bool MetaIOID3::writeRating(TagLib::ID3v2::Tag *tag, int rating)
907 {
908  if (!tag)
909  return false;
910 
911  int popmrating = lroundf(static_cast<float>(rating) / 10.0F * 255.0F);
912 
913  // MythTV Specific Rating Tag
914  PopularimeterFrame *popm = findPOPM(tag, email);
915 
916  if (!popm)
917  {
918  popm = new PopularimeterFrame();
919  tag->addFrame(popm);
920  popm->setEmail(email);
921  }
922  popm->setRating(popmrating);
923 
924  // Global Rating Tag
925  PopularimeterFrame *gpopm = findPOPM(tag, "");
926  if (!gpopm)
927  {
928  gpopm = new PopularimeterFrame();
929  tag->addFrame(gpopm);
930  gpopm->setEmail("");
931  }
932  gpopm->setRating(popmrating);
933 
934  return true;
935 }
936 
937 bool MetaIOID3::writeLastPlay(TagLib::ID3v2::Tag *tag, QDateTime lastPlay)
938 {
939  if (!tag)
940  return false;
941 
942  // MythTV Specific Rating Tag
943  UserTextIdentificationFrame *txxx = find(tag, "MythTVLastPlayed");
944 
945  if (!txxx)
946  {
947  txxx = new UserTextIdentificationFrame();
948  tag->addFrame(txxx);
949  txxx->setDescription("MythTVLastPlayed");
950  }
951 #if QT_VERSION < QT_VERSION_CHECK(6,5,0)
952  lastPlay.setTimeSpec(Qt::UTC);
953 #else
954  lastPlay.setTimeZone(QTimeZone(QTimeZone::UTC));
955 #endif
956  txxx->setText(QStringToTString(lastPlay.toString(Qt::ISODate)));
957 
958  return true;
959 }
960 
961 bool MetaIOID3::TagExists(const QString &filename)
962 {
963  if (!OpenFile(filename))
964  return false;
965 
966  TagLib::ID3v1::Tag *v1_tag = GetID3v1Tag();
967  TagLib::ID3v2::Tag *v2_tag = GetID3v2Tag();
968 
969  bool retval = false;
970 
971  if ((v2_tag && !v2_tag->isEmpty()) ||
972  (v1_tag && !v1_tag->isEmpty()))
973  retval = true;
974 
975  return retval;
976 }
AlbumArtImage::m_imageType
ImageType m_imageType
Definition: musicmetadata.h:52
MetaIOTagLib::WriteGenericMetadata
static void WriteGenericMetadata(TagLib::Tag *tag, const MusicMetadata *metadata)
Writes metadata common to all tag formats to the tag.
Definition: metaiotaglib.cpp:24
MetaIOID3::GetID3v1Tag
TagLib::ID3v1::Tag * GetID3v1Tag(bool create=false)
Definition: metaioid3.cpp:136
MusicMetadata::CompilationArtist
QString CompilationArtist() const
Definition: musicmetadata.h:138
MetaIOID3::writeAlbumArt
bool writeAlbumArt(const QString &filename, const AlbumArtImage *albumart) override
Write the albumart image to the file.
Definition: metaioid3.cpp:609
IT_UNKNOWN
@ IT_UNKNOWN
Definition: musicmetadata.h:31
FrameList
QList< MPEG2frame * > FrameList
Definition: mpeg2fix.h:118
MetaIOID3::kMPEG
@ kMPEG
Definition: metaioid3.h:84
MetaIO::m_filename
QString m_filename
Definition: metaio.h:169
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MusicMetadata
Definition: musicmetadata.h:81
IT_CD
@ IT_CD
Definition: musicmetadata.h:34
IT_FRONTCOVER
@ IT_FRONTCOVER
Definition: musicmetadata.h:32
build_compdb.file
file
Definition: build_compdb.py:55
MetaIOID3::find
static UserTextIdentificationFrame * find(TagLib::ID3v2::Tag *tag, const String &description)
Find the a custom comment tag by description. This is a copy of the same function in the TagLib::ID3v...
Definition: metaioid3.cpp:811
MetaIOID3::write
bool write(const QString &filename, MusicMetadata *mdata) override
Writes all metadata back to a file.
Definition: metaioid3.cpp:153
musicutils.h
MetaIOID3::getAlbumArt
QImage * getAlbumArt(const QString &filename, ImageType type) override
Read the albumart image from the file.
Definition: metaioid3.cpp:394
MetaIOID3::writeLastPlay
static bool writeLastPlay(TagLib::ID3v2::Tag *tag, QDateTime lastPlay)
Definition: metaioid3.cpp:937
MetaIOTagLib::getTrackLength
static std::chrono::milliseconds getTrackLength(TagLib::File *file)
Find the length of the track (in milliseconds)
Definition: metaiotaglib.cpp:89
MetaIOID3::readAlbumArt
static AlbumArtList readAlbumArt(TagLib::ID3v2::Tag *tag)
Read the albumart images from the file.
Definition: metaioid3.cpp:479
MetaIOTagLib::ReadGenericMetadata
void ReadGenericMetadata(TagLib::Tag *tag, MusicMetadata *metadata)
Writes metadata common to all tag formats to the tag.
Definition: metaiotaglib.cpp:54
MetaIO::restoreTimeStamps
void restoreTimeStamps(void)
Definition: metaio.cpp:226
mythlogging.h
IT_INLAY
@ IT_INLAY
Definition: musicmetadata.h:35
hardwareprofile.scan.rating
def rating(profile, smoonURL, gate)
Definition: scan.py:37
MetaIOID3::removeAlbumArt
bool removeAlbumArt(const QString &filename, const AlbumArtImage *albumart) override
Remove the albumart image from the file.
Definition: metaioid3.cpp:682
MetaIOID3::TagExists
bool TagExists(const QString &filename) override
Definition: metaioid3.cpp:961
MetaIOID3::findPOPM
static PopularimeterFrame * findPOPM(TagLib::ID3v2::Tag *tag, const String &email)
Find the POPM tag associated with MythTV.
Definition: metaioid3.cpp:831
MetaIOID3::read
MusicMetadata * read(const QString &filename) override
Reads MusicMetadata from a file.
Definition: metaioid3.cpp:231
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
MetaIOID3::getExtFromMimeType
static QString getExtFromMimeType(const QString &mimeType)
Definition: metaioid3.cpp:560
MetaIOID3::writeVolatileMetadata
bool writeVolatileMetadata(const QString &filename, MusicMetadata *mdata) override
Definition: metaioid3.cpp:880
MetaIOID3::getAlbumArtList
AlbumArtList getAlbumArtList(const QString &filename) override
Read the albumart images from the file.
Definition: metaioid3.cpp:456
MythDate::fromString
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
MetaIOID3::m_file
TagLib::File * m_file
Definition: metaioid3.h:82
MetaIOID3::writePlayCount
static bool writePlayCount(TagLib::ID3v2::Tag *tag, int playcount)
Definition: metaioid3.cpp:844
mythcorecontext.h
AlbumArtList
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:57
IT_BACKCOVER
@ IT_BACKCOVER
Definition: musicmetadata.h:33
MetaIOID3::GetID3v2Tag
TagLib::ID3v2::Tag * GetID3v2Tag(bool create=false)
Definition: metaioid3.cpp:116
MythDate::ISODate
@ ISODate
Default UTC.
Definition: mythdate.h:17
MYTH_MUSICBRAINZ_ALBUMARTIST_UUID
static constexpr const char * MYTH_MUSICBRAINZ_ALBUMARTIST_UUID
Definition: metaio.h:15
MusicMetadata::LastPlay
QDateTime LastPlay() const
Definition: musicmetadata.h:244
MetaIOID3::writeRating
static bool writeRating(TagLib::ID3v2::Tag *tag, int rating)
Definition: metaioid3.cpp:906
AlbumArtImage
Definition: musicmetadata.h:40
MusicMetadata::Compilation
bool Compilation() const
Definition: musicmetadata.h:252
MetaIOID3::m_fileType
TagType m_fileType
Definition: metaioid3.h:85
MetaIOID3::kFLAC
@ kFLAC
Definition: metaioid3.h:84
MusicMetadata::PlayCount
int PlayCount() const
Definition: musicmetadata.h:248
metaioid3.h
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:842
MusicMetadata::Rating
int Rating() const
Definition: musicmetadata.h:239
MetaIOID3::findAPIC
static AttachedPictureFrame * findAPIC(TagLib::ID3v2::Tag *tag, AttachedPictureFrame::Type type, const String &description=String())
Find an APIC tag by type and optionally description.
Definition: metaioid3.cpp:585
MetaIOID3::CloseFile
void CloseFile()
Definition: metaioid3.cpp:107
MetaIOID3::changeImageType
bool changeImageType(const QString &filename, const AlbumArtImage *albumart, ImageType newType) override
Definition: metaioid3.cpp:729
email
const String email
Definition: metaioid3.cpp:22
AlbumArtImage::m_description
QString m_description
Definition: musicmetadata.h:53
IT_ARTIST
@ IT_ARTIST
Definition: musicmetadata.h:36
build_compdb.filename
filename
Definition: build_compdb.py:21
MetaIOID3::SaveFile
bool SaveFile()
Definition: metaioid3.cpp:95
ImageType
ImageType
Definition: musicmetadata.h:29
musicmetadata.h
MetaIOID3::OpenFile
bool OpenFile(const QString &filename, bool forWriting=false)
Open the file to read the tag.
Definition: metaioid3.cpp:31
MetaIO::saveTimeStamps
void saveTimeStamps(void)
Definition: metaio.cpp:217
AlbumArtImage::m_filename
QString m_filename
Definition: musicmetadata.h:50