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