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 <taglib/flacfile.h>
15#include <taglib/mpegfile.h>
16
17// libmythmetadata
18#include "metaioid3.h"
19#include "musicmetadata.h"
20#include "musicutils.h"
21
22const TagLib::String email = "music@mythtv.org"; // TODO username/ip/hostname?
23
31bool 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,
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")
57 else if (extension.toLower() == "mp3" || extension.toLower() == "mp2")
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
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;
113 m_filename.clear();
114}
115
116TagLib::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
136TagLib::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
153bool 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 TagLib::ID3v2::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 TagLib::ID3v2::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 TagLib::ID3v2::TextIdentificationFrame *tpe4frame = nullptr;
198 TagLib::ID3v2::FrameList tpelist = tag->frameListMap()["TPE4"];
199 if (!tpelist.isEmpty())
200 tpe4frame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>(tpelist.front());
201
202 if (!tpe4frame)
203 {
204 tpe4frame = new TagLib::ID3v2::TextIdentificationFrame(TagLib::ByteVector("TPE4"),
205 TagLib::String::UTF8);
206 tag->addFrame(tpe4frame);
207 }
208 tpe4frame->setText(QStringToTString(mdata->CompilationArtist()));
209
210
211 TagLib::ID3v2::TextIdentificationFrame *tpe2frame = nullptr;
212 tpelist = tag->frameListMap()["TPE2"];
213 if (!tpelist.isEmpty())
214 tpe2frame = dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>(tpelist.front());
215
216 if (!tpe2frame)
217 {
218 tpe2frame = new TagLib::ID3v2::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 TagLib::ID3v2::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<TagLib::ID3v2::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 TagLib::ID3v2::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<TagLib::ID3v2::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 TagLib::ID3v2::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 TagLib::ID3v2::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 TagLib::ID3v2::AttachedPictureFrame::Type apicType
399 = TagLib::ID3v2::AttachedPictureFrame::FrontCover;
400
401 switch (type)
402 {
403 case IT_UNKNOWN :
404 apicType = TagLib::ID3v2::AttachedPictureFrame::Other;
405 break;
406 case IT_FRONTCOVER :
407 apicType = TagLib::ID3v2::AttachedPictureFrame::FrontCover;
408 break;
409 case IT_BACKCOVER :
410 apicType = TagLib::ID3v2::AttachedPictureFrame::BackCover;
411 break;
412 case IT_CD :
413 apicType = TagLib::ID3v2::AttachedPictureFrame::Media;
414 break;
415 case IT_INLAY :
416 apicType = TagLib::ID3v2::AttachedPictureFrame::LeafletPage;
417 break;
418 case IT_ARTIST :
419 apicType = TagLib::ID3v2::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<TagLib::ID3v2::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
479AlbumArtList 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<TagLib::ID3v2::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 TagLib::ID3v2::AttachedPictureFrame::FrontCover :
523 art->m_imageType = IT_FRONTCOVER;
524 art->m_filename = QString("front") + ext;
525 break;
526 case TagLib::ID3v2::AttachedPictureFrame::BackCover :
527 art->m_imageType = IT_BACKCOVER;
528 art->m_filename = QString("back") + ext;
529 break;
530 case TagLib::ID3v2::AttachedPictureFrame::Media :
531 art->m_imageType = IT_CD;
532 art->m_filename = QString("cd") + ext;
533 break;
534 case TagLib::ID3v2::AttachedPictureFrame::LeafletPage :
535 art->m_imageType = IT_INLAY;
536 art->m_filename = QString("inlay") + ext;
537 break;
538 case TagLib::ID3v2::AttachedPictureFrame::Artist :
539 art->m_imageType = IT_ARTIST;
540 art->m_filename = QString("artist") + ext;
541 break;
542 case TagLib::ID3v2::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
560QString 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
585TagLib::ID3v2::AttachedPictureFrame* MetaIOID3::findAPIC(TagLib::ID3v2::Tag *tag,
586 TagLib::ID3v2::AttachedPictureFrame::Type type,
587 const TagLib::String &description)
588{
589 TagLib::ID3v2::FrameList l = tag->frameList("APIC");
590 for (auto & frame : l)
591 {
592 auto *f = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(frame);
593 if (f && f->type() == type &&
594 (description.isEmpty() || f->description() == description))
595 return f;
596 }
597 return nullptr;
598}
599
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 TagLib::ID3v2::AttachedPictureFrame::Type type = TagLib::ID3v2::AttachedPictureFrame::Other;
623 switch (albumart->m_imageType)
624 {
625 case IT_FRONTCOVER:
626 type = TagLib::ID3v2::AttachedPictureFrame::FrontCover;
627 break;
628 case IT_BACKCOVER:
629 type = TagLib::ID3v2::AttachedPictureFrame::BackCover;
630 break;
631 case IT_CD:
632 type = TagLib::ID3v2::AttachedPictureFrame::Media;
633 break;
634 case IT_INLAY:
635 type = TagLib::ID3v2::AttachedPictureFrame::LeafletPage;
636 break;
637 case IT_ARTIST:
638 type = TagLib::ID3v2::AttachedPictureFrame::Artist;
639 break;
640 default:
641 type = TagLib::ID3v2::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 TagLib::ID3v2::AttachedPictureFrame *apic = findAPIC(tag, type,
654 QStringToTString(albumart->m_description));
655
656 if (!apic)
657 {
658 apic = new TagLib::ID3v2::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 TagLib::ID3v2::AttachedPictureFrame::Type type = TagLib::ID3v2::AttachedPictureFrame::Other;
689 switch (albumart->m_imageType)
690 {
691 case IT_FRONTCOVER:
692 type = TagLib::ID3v2::AttachedPictureFrame::FrontCover;
693 break;
694 case IT_BACKCOVER:
695 type = TagLib::ID3v2::AttachedPictureFrame::BackCover;
696 break;
697 case IT_CD:
698 type = TagLib::ID3v2::AttachedPictureFrame::Media;
699 break;
700 case IT_INLAY:
701 type = TagLib::ID3v2::AttachedPictureFrame::LeafletPage;
702 break;
703 case IT_ARTIST:
704 type = TagLib::ID3v2::AttachedPictureFrame::Artist;
705 break;
706 default:
707 type = TagLib::ID3v2::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 TagLib::ID3v2::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 TagLib::ID3v2::AttachedPictureFrame::Type type = TagLib::ID3v2::AttachedPictureFrame::Other;
740 switch (albumart->m_imageType)
741 {
742 case IT_FRONTCOVER:
743 type = TagLib::ID3v2::AttachedPictureFrame::FrontCover;
744 break;
745 case IT_BACKCOVER:
746 type = TagLib::ID3v2::AttachedPictureFrame::BackCover;
747 break;
748 case IT_CD:
749 type = TagLib::ID3v2::AttachedPictureFrame::Media;
750 break;
751 case IT_INLAY:
752 type = TagLib::ID3v2::AttachedPictureFrame::LeafletPage;
753 break;
754 case IT_ARTIST:
755 type = TagLib::ID3v2::AttachedPictureFrame::Artist;
756 break;
757 default:
758 type = TagLib::ID3v2::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 TagLib::ID3v2::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(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
780 break;
781 case IT_BACKCOVER:
782 apic->setType(TagLib::ID3v2::AttachedPictureFrame::BackCover);
783 break;
784 case IT_CD:
785 apic->setType(TagLib::ID3v2::AttachedPictureFrame::Media);
786 break;
787 case IT_INLAY:
788 apic->setType(TagLib::ID3v2::AttachedPictureFrame::LeafletPage);
789 break;
790 case IT_ARTIST:
791 apic->setType(TagLib::ID3v2::AttachedPictureFrame::Artist);
792 break;
793 default:
794 apic->setType(TagLib::ID3v2::AttachedPictureFrame::Other);
795 break;
796 }
797
798 return SaveFile();
799}
800
811TagLib::ID3v2::UserTextIdentificationFrame* MetaIOID3::find(TagLib::ID3v2::Tag *tag,
812 const TagLib::String &description)
813{
814 TagLib::ID3v2::FrameList l = tag->frameList("TXXX");
815 for (auto & frame : l)
816 {
817 auto *f = dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame *>(frame);
818 if (f && f->description() == description)
819 return f;
820 }
821 return nullptr;
822}
823
831TagLib::ID3v2::PopularimeterFrame* MetaIOID3::findPOPM(TagLib::ID3v2::Tag *tag,
832 const TagLib::String &_email)
833{
834 TagLib::ID3v2::FrameList l = tag->frameList("POPM");
835 for (auto & frame : l)
836 {
837 auto *f = dynamic_cast<TagLib::ID3v2::PopularimeterFrame *>(frame);
838 if (f && f->email() == _email)
839 return f;
840 }
841 return nullptr;
842}
843
844bool MetaIOID3::writePlayCount(TagLib::ID3v2::Tag *tag, int playcount)
845{
846 if (!tag)
847 return false;
848
849 // MythTV Specific playcount Tag
850 TagLib::ID3v2::PopularimeterFrame *popm = findPOPM(tag, email);
851
852 if (!popm)
853 {
854 popm = new TagLib::ID3v2::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 TagLib::ID3v2::PopularimeterFrame *gpopm = findPOPM(tag, "");
868 if (!gpopm)
869 {
870 gpopm = new TagLib::ID3v2::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
906bool 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 TagLib::ID3v2::PopularimeterFrame *popm = findPOPM(tag, email);
915
916 if (!popm)
917 {
918 popm = new TagLib::ID3v2::PopularimeterFrame();
919 tag->addFrame(popm);
920 popm->setEmail(email);
921 }
922 popm->setRating(popmrating);
923
924 // Global Rating Tag
925 TagLib::ID3v2::PopularimeterFrame *gpopm = findPOPM(tag, "");
926 if (!gpopm)
927 {
928 gpopm = new TagLib::ID3v2::PopularimeterFrame();
929 tag->addFrame(gpopm);
930 gpopm->setEmail("");
931 }
932 gpopm->setRating(popmrating);
933
934 return true;
935}
936
937bool MetaIOID3::writeLastPlay(TagLib::ID3v2::Tag *tag, QDateTime lastPlay)
938{
939 if (!tag)
940 return false;
941
942 // MythTV Specific Rating Tag
943 TagLib::ID3v2::UserTextIdentificationFrame *txxx = find(tag, "MythTVLastPlayed");
944
945 if (!txxx)
946 {
947 txxx = new TagLib::ID3v2::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
961bool 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}
QString m_filename
Definition: musicmetadata.h:55
QString m_description
Definition: musicmetadata.h:58
ImageType m_imageType
Definition: musicmetadata.h:57
bool write(const QString &filename, MusicMetadata *mdata) override
Writes all metadata back to a file.
Definition: metaioid3.cpp:153
static bool writeLastPlay(TagLib::ID3v2::Tag *tag, QDateTime lastPlay)
Definition: metaioid3.cpp:937
void CloseFile()
Definition: metaioid3.cpp:107
static TagLib::ID3v2::PopularimeterFrame * findPOPM(TagLib::ID3v2::Tag *tag, const TagLib::String &email)
Find the POPM tag associated with MythTV.
Definition: metaioid3.cpp:831
static bool writeRating(TagLib::ID3v2::Tag *tag, int rating)
Definition: metaioid3.cpp:906
MusicMetadata * read(const QString &filename) override
Reads MusicMetadata from a file.
Definition: metaioid3.cpp:231
AlbumArtList getAlbumArtList(const QString &filename) override
Read the albumart images from the file.
Definition: metaioid3.cpp:456
bool writeVolatileMetadata(const QString &filename, MusicMetadata *mdata) override
Writes rating and playcount back to a file.
Definition: metaioid3.cpp:880
bool SaveFile()
Definition: metaioid3.cpp:95
static AlbumArtList readAlbumArt(TagLib::ID3v2::Tag *tag)
Read the albumart images from the file.
Definition: metaioid3.cpp:479
static TagLib::ID3v2::UserTextIdentificationFrame * find(TagLib::ID3v2::Tag *tag, const TagLib::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
QImage * getAlbumArt(const QString &filename, ImageType type) override
Read the albumart image from the file.
Definition: metaioid3.cpp:394
TagType m_fileType
Definition: metaioid3.h:80
bool changeImageType(const QString &filename, const AlbumArtImage *albumart, ImageType newType) override
Definition: metaioid3.cpp:729
static bool writePlayCount(TagLib::ID3v2::Tag *tag, int playcount)
Definition: metaioid3.cpp:844
static QString getExtFromMimeType(const QString &mimeType)
Definition: metaioid3.cpp:560
bool writeAlbumArt(const QString &filename, const AlbumArtImage *albumart) override
Write the albumart image to the file.
Definition: metaioid3.cpp:609
TagLib::File * m_file
Definition: metaioid3.h:77
bool OpenFile(const QString &filename, bool forWriting=false)
Open the file to read the tag.
Definition: metaioid3.cpp:31
TagLib::ID3v1::Tag * GetID3v1Tag(bool create=false)
Definition: metaioid3.cpp:136
bool TagExists(const QString &filename) override
Definition: metaioid3.cpp:961
TagLib::ID3v2::Tag * GetID3v2Tag(bool create=false)
Definition: metaioid3.cpp:116
static TagLib::ID3v2::AttachedPictureFrame * findAPIC(TagLib::ID3v2::Tag *tag, TagLib::ID3v2::AttachedPictureFrame::Type type, const TagLib::String &description=TagLib::String())
Find an APIC tag by type and optionally description.
Definition: metaioid3.cpp:585
bool removeAlbumArt(const QString &filename, const AlbumArtImage *albumart) override
Remove the albumart image from the file.
Definition: metaioid3.cpp:682
static void WriteGenericMetadata(TagLib::Tag *tag, const MusicMetadata *metadata)
Writes metadata common to all tag formats to the tag.
static std::chrono::milliseconds getTrackLength(TagLib::File *file)
Find the length of the track (in milliseconds)
void ReadGenericMetadata(TagLib::Tag *tag, MusicMetadata *metadata)
Writes metadata common to all tag formats to the tag.
void restoreTimeStamps(void)
Definition: metaio.cpp:232
void saveTimeStamps(void)
Definition: metaio.cpp:223
QString m_filename
Definition: metaio.h:168
QDateTime LastPlay() const
QString CompilationArtist() const
int Rating() const
bool Compilation() const
int PlayCount() const
QString GetHostName(void)
static constexpr const char * MYTH_MUSICBRAINZ_ALBUMARTIST_UUID
Definition: metaio.h:15
const TagLib::String email
Definition: metaioid3.cpp:22
QList< MPEG2frame * > FrameList
Definition: mpeg2fix.h:116
ImageType
Definition: musicmetadata.h:35
@ IT_INLAY
Definition: musicmetadata.h:40
@ IT_BACKCOVER
Definition: musicmetadata.h:38
@ IT_UNKNOWN
Definition: musicmetadata.h:36
@ IT_FRONTCOVER
Definition: musicmetadata.h:37
@ IT_ARTIST
Definition: musicmetadata.h:41
@ IT_CD
Definition: musicmetadata.h:39
QList< AlbumArtImage * > AlbumArtList
Definition: musicmetadata.h:62
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ ISODate
Default UTC.
Definition: mythdate.h:17
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
def rating(profile, smoonURL, gate)
Definition: scan.py:36