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  {
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  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 = dynamic_cast<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 = dynamic_cast<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 
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  auto *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 = dynamic_cast<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 
393 {
394  auto *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 (auto & apicframe : apicframes)
431  {
432  auto *frame = dynamic_cast<AttachedPictureFrame *>(apicframe);
433  if (frame && frame->type() == apicType)
434  {
435  picture->loadFromData((const uchar *)frame->picture().data(),
436  frame->picture().size());
437  return picture;
438  }
439  }
440  }
441  }
442 
443  delete picture;
444 
445  return nullptr;
446 }
447 
448 
455 {
456  AlbumArtList imageList;
457  if (OpenFile(filename))
458  {
459  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
460 
461  if (!tag)
462  return imageList;
463 
464  imageList = readAlbumArt(tag);
465  }
466 
467  return imageList;
468 }
469 
477 AlbumArtList MetaIOID3::readAlbumArt(TagLib::ID3v2::Tag *tag)
478 {
479  AlbumArtList artlist;
480 
481  if (!tag->frameListMap()["APIC"].isEmpty())
482  {
483  TagLib::ID3v2::FrameList apicframes = tag->frameListMap()["APIC"];
484 
485  for (auto & apicframe : apicframes)
486  {
487  auto *frame = dynamic_cast<AttachedPictureFrame *>(apicframe);
488  if (frame == nullptr)
489  {
490  LOG(VB_GENERAL, LOG_DEBUG,
491  "Music Scanner - Cannot convert APIC frame");
492  continue;
493  }
494 
495  // Assume a valid image would have at least
496  // 100 bytes of data (1x1 indexed gif is 35 bytes)
497  if (frame->picture().size() < 100)
498  {
499  LOG(VB_GENERAL, LOG_NOTICE,
500  "Music Scanner - Discarding APIC frame "
501  "with size less than 100 bytes");
502  continue;
503  }
504 
505  auto *art = new AlbumArtImage();
506 
507  if (frame->description().isEmpty())
508  art->m_description.clear();
509  else
510  art->m_description = TStringToQString(frame->description());
511 
512  art->m_embedded = true;
513  art->m_hostname = gCoreContext->GetHostName();
514 
515  QString ext = getExtFromMimeType(
516  TStringToQString(frame->mimeType()).toLower());
517 
518  switch (frame->type())
519  {
520  case AttachedPictureFrame::FrontCover :
521  art->m_imageType = IT_FRONTCOVER;
522  art->m_filename = QString("front") + ext;
523  break;
524  case AttachedPictureFrame::BackCover :
525  art->m_imageType = IT_BACKCOVER;
526  art->m_filename = QString("back") + ext;
527  break;
528  case AttachedPictureFrame::Media :
529  art->m_imageType = IT_CD;
530  art->m_filename = QString("cd") + ext;
531  break;
532  case AttachedPictureFrame::LeafletPage :
533  art->m_imageType = IT_INLAY;
534  art->m_filename = QString("inlay") + ext;
535  break;
536  case AttachedPictureFrame::Artist :
537  art->m_imageType = IT_ARTIST;
538  art->m_filename = QString("artist") + ext;
539  break;
540  case AttachedPictureFrame::Other :
541  art->m_imageType = IT_UNKNOWN;
542  art->m_filename = QString("unknown") + ext;
543  break;
544  default:
545  LOG(VB_GENERAL, LOG_ERR, "Music Scanner - APIC tag found "
546  "with unsupported type");
547  delete art;
548  continue;
549  }
550 
551  artlist.append(art);
552  }
553  }
554 
555  return artlist;
556 }
557 
558 QString MetaIOID3::getExtFromMimeType(const QString &mimeType)
559 {
560  if (mimeType == "image/png")
561  return QString(".png");
562  if (mimeType == "image/jpeg" || mimeType == "image/jpg")
563  return QString(".jpg");
564  if (mimeType == "image/gif")
565  return QString(".gif");
566  if (mimeType == "image/bmp")
567  return QString(".bmp");
568 
569  LOG(VB_GENERAL, LOG_ERR,
570  "Music Scanner - Unknown image mimetype found - " + mimeType);
571 
572  return QString();
573 }
574 
583 AttachedPictureFrame* MetaIOID3::findAPIC(TagLib::ID3v2::Tag *tag,
584  const AttachedPictureFrame::Type &type,
585  const String &description)
586 {
587  TagLib::ID3v2::FrameList l = tag->frameList("APIC");
588  for (auto & frame : l)
589  {
590  auto *f = dynamic_cast<AttachedPictureFrame *>(frame);
591  if (f && f->type() == type &&
592  (description.isNull() || f->description() == description))
593  return f;
594  }
595  return nullptr;
596 }
597 
607 bool MetaIOID3::writeAlbumArt(const QString &filename,
608  const AlbumArtImage *albumart)
609 {
610  if (filename.isEmpty() || !albumart)
611  return false;
612 
613  // load the image into a QByteArray
614  QImage image(albumart->m_filename);
615  QByteArray imageData;
616  QBuffer buffer(&imageData);
617  buffer.open(QIODevice::WriteOnly);
618  image.save(&buffer, "JPEG");
619 
620  AttachedPictureFrame::Type type = AttachedPictureFrame::Other;
621  switch (albumart->m_imageType)
622  {
623  case IT_FRONTCOVER:
624  type = AttachedPictureFrame::FrontCover;
625  break;
626  case IT_BACKCOVER:
627  type = AttachedPictureFrame::BackCover;
628  break;
629  case IT_CD:
630  type = AttachedPictureFrame::Media;
631  break;
632  case IT_INLAY:
633  type = AttachedPictureFrame::LeafletPage;
634  break;
635  case IT_ARTIST:
636  type = AttachedPictureFrame::Artist;
637  break;
638  default:
639  type = AttachedPictureFrame::Other;
640  break;
641  }
642 
643  if (!OpenFile(filename, true))
644  return false;
645 
646  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
647 
648  if (!tag)
649  return false;
650 
651  AttachedPictureFrame *apic = findAPIC(tag, type,
652  QStringToTString(albumart->m_description));
653 
654  if (!apic)
655  {
656  apic = new AttachedPictureFrame();
657  tag->addFrame(apic);
658  apic->setType(type);
659  }
660 
661  QString mimetype = "image/jpeg";
662 
663  TagLib::ByteVector bytevector;
664  bytevector.setData(imageData.data(), imageData.size());
665 
666  apic->setMimeType(QStringToTString(mimetype));
667  apic->setPicture(bytevector);
668  apic->setDescription(QStringToTString(albumart->m_description));
669 
670  return SaveFile();
671 }
672 
681  const AlbumArtImage *albumart)
682 {
683  if (filename.isEmpty() || !albumart)
684  return false;
685 
686  AttachedPictureFrame::Type type = AttachedPictureFrame::Other;
687  switch (albumart->m_imageType)
688  {
689  case IT_FRONTCOVER:
690  type = AttachedPictureFrame::FrontCover;
691  break;
692  case IT_BACKCOVER:
693  type = AttachedPictureFrame::BackCover;
694  break;
695  case IT_CD:
696  type = AttachedPictureFrame::Media;
697  break;
698  case IT_INLAY:
699  type = AttachedPictureFrame::LeafletPage;
700  break;
701  case IT_ARTIST:
702  type = AttachedPictureFrame::Artist;
703  break;
704  default:
705  type = AttachedPictureFrame::Other;
706  break;
707  }
708 
709  if (!OpenFile(filename, true))
710  return false;
711 
712  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
713 
714  if (!tag)
715  return false;
716 
717  AttachedPictureFrame *apic = findAPIC(tag, type,
718  QStringToTString(albumart->m_description));
719  if (!apic)
720  return false;
721 
722  tag->removeFrame(apic);
723 
724  return SaveFile();
725 }
726 
728  const AlbumArtImage* albumart,
729  ImageType newType)
730 {
731  if (!albumart)
732  return false;
733 
734  if (albumart->m_imageType == newType)
735  return true;
736 
737  AttachedPictureFrame::Type type = AttachedPictureFrame::Other;
738  switch (albumart->m_imageType)
739  {
740  case IT_FRONTCOVER:
741  type = AttachedPictureFrame::FrontCover;
742  break;
743  case IT_BACKCOVER:
744  type = AttachedPictureFrame::BackCover;
745  break;
746  case IT_CD:
747  type = AttachedPictureFrame::Media;
748  break;
749  case IT_INLAY:
750  type = AttachedPictureFrame::LeafletPage;
751  break;
752  case IT_ARTIST:
753  type = AttachedPictureFrame::Artist;
754  break;
755  default:
756  type = AttachedPictureFrame::Other;
757  break;
758  }
759 
760  if (!OpenFile(filename, true))
761  return false;
762 
763  TagLib::ID3v2::Tag *tag = GetID3v2Tag();
764 
765  if (!tag)
766  return false;
767 
768  AttachedPictureFrame *apic = findAPIC(tag, type,
769  QStringToTString(albumart->m_description));
770  if (!apic)
771  return false;
772 
773  // set the new image type
774  switch (newType)
775  {
776  case IT_FRONTCOVER:
777  apic->setType(AttachedPictureFrame::FrontCover);
778  break;
779  case IT_BACKCOVER:
780  apic->setType(AttachedPictureFrame::BackCover);
781  break;
782  case IT_CD:
783  apic->setType(AttachedPictureFrame::Media);
784  break;
785  case IT_INLAY:
786  apic->setType(AttachedPictureFrame::LeafletPage);
787  break;
788  case IT_ARTIST:
789  apic->setType(AttachedPictureFrame::Artist);
790  break;
791  default:
792  apic->setType(AttachedPictureFrame::Other);
793  break;
794  }
795 
796  return SaveFile();
797 }
798 
809 UserTextIdentificationFrame* MetaIOID3::find(TagLib::ID3v2::Tag *tag,
810  const String &description)
811 {
812  TagLib::ID3v2::FrameList l = tag->frameList("TXXX");
813  for (auto & frame : l)
814  {
815  auto *f = dynamic_cast<UserTextIdentificationFrame *>(frame);
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 (auto & frame : l)
834  {
835  auto *f = dynamic_cast<PopularimeterFrame *>(frame);
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 
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 }
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:56
bool removeAlbumArt(const QString &filename, const AlbumArtImage *albumart) override
Remove the albumart image from the file.
Definition: metaioid3.cpp:680
void CloseFile()
Definition: metaioid3.cpp:107
bool TagExists(const QString &filename) override
Definition: metaioid3.cpp:955
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
static bool writeLastPlay(TagLib::ID3v2::Tag *tag, QDateTime lastPlay)
Definition: metaioid3.cpp:935
QString m_description
Definition: musicmetadata.h:52
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:809
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:904
int Rating() const
int PlayCount() const
ImageType m_imageType
Definition: musicmetadata.h:51
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:607
def rating(profile, smoonURL, gate)
Definition: scan.py:39
TagLib::ID3v1::Tag * GetID3v1Tag(bool create=false)
Definition: metaioid3.cpp:136
QString m_filename
Definition: musicmetadata.h:49
TagLib::ID3v2::Tag * GetID3v2Tag(bool create=false)
Definition: metaioid3.cpp:116
void saveTimeStamps(void)
Definition: metaio.cpp:222
bool changeImageType(const QString &filename, const AlbumArtImage *albumart, ImageType newType) override
Definition: metaioid3.cpp:727
bool write(const QString &filename, MusicMetadata *mdata) override
Writes all metadata back to a file.
Definition: metaioid3.cpp:153
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.
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:583
static AlbumArtList readAlbumArt(TagLib::ID3v2::Tag *tag)
Read the albumart images from the file.
Definition: metaioid3.cpp:477
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:558
QImage * getAlbumArt(const QString &filename, ImageType type) override
Read the albumart image from the file.
Definition: metaioid3.cpp:392
bool SaveFile()
Definition: metaioid3.cpp:95
QString m_filename
Definition: metaio.h:181
QList< MPEG2frame * > FrameList
Definition: mpeg2fix.h:113
QDateTime LastPlay() const
ImageType
Definition: musicmetadata.h:28
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:454
bool writeVolatileMetadata(const QString &filename, MusicMetadata *mdata) override
Writes rating and playcount back to a file.
Definition: metaioid3.cpp:878
static PopularimeterFrame * findPOPM(TagLib::ID3v2::Tag *tag, const String &email)
Find the POPM tag associated with MythTV.
Definition: metaioid3.cpp:829
QString GetHostName(void)
#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:842