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