bool MetaIOID3::writeVolatileMetadata(const QString &filename, MusicMetadata* mdata) { if (filename.isEmpty()) return false; int rating = mdata->Rating(); int playcount = mdata->PlayCount(); QDateTime lastPlay = mdata->LastPlay(); if (!OpenFile(filename, true)) return false; TagLib::ID3v2::Tag *tag = GetID3v2Tag(); if (!tag) return false; bool result = (writeRating(tag, rating) && writePlayCount(tag, playcount) && writeLastPlay(tag, lastPlay)); if (!SaveFile()) return false; return result; }
/*! * \brief Read the albumart image from the file * * \param filename The filename for which we want to find the albumart. * \param type The type of image we want - front/back etc * \returns A pointer to a QImage owned by the caller or nullptr if not found. */ QImage* MetaIOID3::getAlbumArt(const QString &filename, ImageType type) { QImage *picture = new QImage(); AttachedPictureFrame::Type apicType = AttachedPictureFrame::FrontCover; switch (type) { case IT_UNKNOWN : apicType = AttachedPictureFrame::Other; break; case IT_FRONTCOVER : apicType = AttachedPictureFrame::FrontCover; break; case IT_BACKCOVER : apicType = AttachedPictureFrame::BackCover; break; case IT_CD : apicType = AttachedPictureFrame::Media; break; case IT_INLAY : apicType = AttachedPictureFrame::LeafletPage; break; case IT_ARTIST : apicType = AttachedPictureFrame::Artist; break; default: return picture; } if (OpenFile(filename)) { TagLib::ID3v2::Tag *tag = GetID3v2Tag(); if (tag && !tag->frameListMap()["APIC"].isEmpty()) { TagLib::ID3v2::FrameList apicframes = tag->frameListMap()["APIC"]; for(TagLib::ID3v2::FrameList::Iterator it = apicframes.begin(); it != apicframes.end(); ++it) { AttachedPictureFrame *frame = static_cast<AttachedPictureFrame *>(*it); if (frame && frame->type() == apicType) { picture->loadFromData((const uchar *)frame->picture().data(), frame->picture().size()); return picture; } } } } delete picture; return nullptr; }
/*! * \brief Remove the albumart image from the file * * \param filename The music file to remove the albumart * \param albumart The Album Art image to remove * \returns True if successful */ bool MetaIOID3::removeAlbumArt(const QString &filename, const AlbumArtImage *albumart) { if (filename.isEmpty() || !albumart) return false; AttachedPictureFrame::Type type = AttachedPictureFrame::Other; switch (albumart->m_imageType) { case IT_FRONTCOVER: type = AttachedPictureFrame::FrontCover; break; case IT_BACKCOVER: type = AttachedPictureFrame::BackCover; break; case IT_CD: type = AttachedPictureFrame::Media; break; case IT_INLAY: type = AttachedPictureFrame::LeafletPage; break; case IT_ARTIST: type = AttachedPictureFrame::Artist; break; default: type = AttachedPictureFrame::Other; break; } if (!OpenFile(filename, true)) return false; TagLib::ID3v2::Tag *tag = GetID3v2Tag(); if (!tag) return false; AttachedPictureFrame *apic = findAPIC(tag, type, QStringToTString(albumart->m_description)); if (!apic) return false; tag->removeFrame(apic); if (!SaveFile()) return false; return true; }
/*! * \brief Read the albumart images from the file * * \param filename The filename for which we want to find the images. */ AlbumArtList MetaIOID3::getAlbumArtList(const QString &filename) { AlbumArtList imageList; if (OpenFile(filename)) { TagLib::ID3v2::Tag *tag = GetID3v2Tag(); if (!tag) return imageList; imageList = readAlbumArt(tag); } return imageList; }
bool MetaIOID3::TagExists(const QString &filename) { if (!OpenFile(filename)) return false; TagLib::ID3v1::Tag *v1_tag = GetID3v1Tag(); TagLib::ID3v2::Tag *v2_tag = GetID3v2Tag(); bool retval = false; if ((v2_tag && !v2_tag->isEmpty()) || (v1_tag && !v1_tag->isEmpty())) retval = true; return retval; }
bool MetaIOID3::writeVolatileMetadata(const Metadata* mdata) { QString filename = mdata->Filename(); int rating = mdata->Rating(); int playcount = mdata->PlayCount(); if (!OpenFile(filename, true)) return false; TagLib::ID3v2::Tag *tag = GetID3v2Tag(); if (!tag) return false; bool result = (writeRating(tag, rating) && writePlayCount(tag, playcount)); if (!SaveFile()) return false; return result; }
bool MetaIOID3::changeImageType(const QString &filename, const AlbumArtImage* albumart, ImageType newType) { if (!albumart) return false; if (albumart->m_imageType == newType) return true; AttachedPictureFrame::Type type = AttachedPictureFrame::Other; switch (albumart->m_imageType) { case IT_FRONTCOVER: type = AttachedPictureFrame::FrontCover; break; case IT_BACKCOVER: type = AttachedPictureFrame::BackCover; break; case IT_CD: type = AttachedPictureFrame::Media; break; case IT_INLAY: type = AttachedPictureFrame::LeafletPage; break; case IT_ARTIST: type = AttachedPictureFrame::Artist; break; default: type = AttachedPictureFrame::Other; break; } if (!OpenFile(filename, true)) return false; TagLib::ID3v2::Tag *tag = GetID3v2Tag(); if (!tag) return false; AttachedPictureFrame *apic = findAPIC(tag, type, QStringToTString(albumart->m_description)); if (!apic) return false; // set the new image type switch (newType) { case IT_FRONTCOVER: apic->setType(AttachedPictureFrame::FrontCover); break; case IT_BACKCOVER: apic->setType(AttachedPictureFrame::BackCover); break; case IT_CD: apic->setType(AttachedPictureFrame::Media); break; case IT_INLAY: apic->setType(AttachedPictureFrame::LeafletPage); break; case IT_ARTIST: apic->setType(AttachedPictureFrame::Artist); break; default: apic->setType(AttachedPictureFrame::Other); break; } if (!SaveFile()) return false; return true; }
/*! * \brief Write the albumart image to the file * * \param filename The music file to add the albumart * \param albumart The Album Art image to write * \returns True if successful * * \note We always save the image in JPEG format */ bool MetaIOID3::writeAlbumArt(const QString &filename, const AlbumArtImage *albumart) { if (filename.isEmpty() || !albumart) return false; // load the image into a QByteArray QImage image(albumart->m_filename); QByteArray imageData; QBuffer buffer(&imageData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPEG"); AttachedPictureFrame::Type type = AttachedPictureFrame::Other; switch (albumart->m_imageType) { case IT_FRONTCOVER: type = AttachedPictureFrame::FrontCover; break; case IT_BACKCOVER: type = AttachedPictureFrame::BackCover; break; case IT_CD: type = AttachedPictureFrame::Media; break; case IT_INLAY: type = AttachedPictureFrame::LeafletPage; break; case IT_ARTIST: type = AttachedPictureFrame::Artist; break; default: type = AttachedPictureFrame::Other; break; } if (!OpenFile(filename, true)) return false; TagLib::ID3v2::Tag *tag = GetID3v2Tag(); if (!tag) return false; AttachedPictureFrame *apic = findAPIC(tag, type, QStringToTString(albumart->m_description)); if (!apic) { apic = new AttachedPictureFrame(); tag->addFrame(apic); apic->setType(type); } QString mimetype = "image/jpeg"; TagLib::ByteVector bytevector; bytevector.setData(imageData.data(), imageData.size()); apic->setMimeType(QStringToTString(mimetype)); apic->setPicture(bytevector); apic->setDescription(QStringToTString(albumart->m_description)); if (!SaveFile()) return false; return true; }
/*! * \copydoc MetaIO::read() */ MusicMetadata *MetaIOID3::read(const QString &filename) { if (!OpenFile(filename)) return nullptr; TagLib::ID3v2::Tag *tag = GetID3v2Tag(true); // Create tag if none are found // if there is no ID3v2 tag, try to read the ID3v1 tag and copy it to // the ID3v2 tag structure if (tag->isEmpty()) { TagLib::ID3v1::Tag *tag_v1 = GetID3v1Tag(); if (!tag_v1) return nullptr; if (!tag_v1->isEmpty()) { tag->setTitle(tag_v1->title()); tag->setArtist(tag_v1->artist()); tag->setAlbum(tag_v1->album()); tag->setTrack(tag_v1->track()); tag->setYear(tag_v1->year()); tag->setGenre(tag_v1->genre()); } } MusicMetadata *metadata = new MusicMetadata(filename); ReadGenericMetadata(tag, metadata); bool compilation = false; // Compilation Artist (TPE4 Remix) or fallback to (TPE2 Band) // N.B. The existance of a either frame is NOT an indication that this // is a compilation, but if it is then one of them will probably hold // the compilation artist. TextIdentificationFrame *tpeframe = nullptr; TagLib::ID3v2::FrameList tpelist = tag->frameListMap()["TPE4"]; if (tpelist.isEmpty() || tpelist.front()->toString().isEmpty()) tpelist = tag->frameListMap()["TPE2"]; if (!tpelist.isEmpty()) tpeframe = (TextIdentificationFrame *)tpelist.front(); if (tpeframe && !tpeframe->toString().isEmpty()) { QString compilation_artist = TStringToQString(tpeframe->toString()) .trimmed(); metadata->setCompilationArtist(compilation_artist); } // Rating and playcount, stored in POPM frame PopularimeterFrame *popm = findPOPM(tag, ""); // Global (all apps) tag // If no 'global' tag exists, look for the MythTV specific one if (!popm) { popm = findPOPM(tag, email); } // Fallback to using any POPM tag we can find if (!popm) { if (!tag->frameListMap()["POPM"].isEmpty()) popm = dynamic_cast<PopularimeterFrame *> (tag->frameListMap()["POPM"].front()); } if (popm) { int rating = popm->rating(); rating = lroundf(static_cast<float>(rating) / 255.0f * 10.0f); metadata->setRating(rating); metadata->setPlaycount(popm->counter()); } // Look for MusicBrainz Album+Artist ID in TXXX Frame UserTextIdentificationFrame *musicbrainz = find(tag, "MusicBrainz Album Artist Id"); if (musicbrainz) { // If the MusicBrainz ID is the special "Various Artists" ID // then compilation is TRUE if (!compilation && !musicbrainz->fieldList().isEmpty()) { TagLib::StringList l = musicbrainz->fieldList(); for (TagLib::StringList::ConstIterator it = l.begin(); it != l.end(); it++) { QString ID = TStringToQString((*it)); if (ID == MYTH_MUSICBRAINZ_ALBUMARTIST_UUID) { compilation = true; break; } } } } // TLEN - Ignored intentionally, some encoders write bad values // e.g. Lame under certain circumstances will always write a length of // 27 hours // Length if (!tag->frameListMap()["TLEN"].isEmpty()) { int length = tag->frameListMap()["TLEN"].front()->toString().toInt(); LOG(VB_FILE, LOG_DEBUG, QString("MetaIOID3::read: Length for '%1' from tag is '%2'\n").arg(filename).arg(length)); } metadata->setCompilation(compilation); metadata->setLength(getTrackLength(m_file)); // The number of tracks on the album, if supplied if (!tag->frameListMap()["TRCK"].isEmpty()) { QString trackFrame = TStringToQString( tag->frameListMap()["TRCK"].front()->toString()) .trimmed(); int trackCount = trackFrame.section('/', -1).toInt(); if (trackCount > 0) metadata->setTrackCount(trackCount); } LOG(VB_FILE, LOG_DEBUG, QString("MetaIOID3::read: Length for '%1' from properties is '%2'\n").arg(filename).arg(metadata->Length())); // Look for MythTVLastPlayed in TXXX Frame UserTextIdentificationFrame *lastplayed = find(tag, "MythTVLastPlayed"); if (lastplayed) { QString lastPlayStr = TStringToQString(lastplayed->toString()); metadata->setLastPlay(QDateTime::fromString(lastPlayStr, Qt::ISODate)); } // Part of a set if (!tag->frameListMap()["TPOS"].isEmpty()) { QString pos = TStringToQString( tag->frameListMap()["TPOS"].front()->toString()).trimmed(); int discNumber = pos.section('/', 0, 0).toInt(); int discCount = pos.section('/', -1).toInt(); if (discNumber > 0) metadata->setDiscNumber(discNumber); if (discCount > 0) metadata->setDiscCount(discCount); } return metadata; }
/*! * \copydoc MetaIO::write() */ bool MetaIOID3::write(const QString &filename, MusicMetadata* mdata) { if (filename.isEmpty()) return false; if (!OpenFile(filename, true)) return false; TagLib::ID3v2::Tag *tag = GetID3v2Tag(); if (!tag) return false; WriteGenericMetadata(tag, mdata); // MythTV rating and playcount, stored in POPM frame writeRating(tag, mdata->Rating()); writePlayCount(tag, mdata->PlayCount()); writeLastPlay(tag, mdata->LastPlay()); // MusicBrainz ID UserTextIdentificationFrame *musicbrainz = nullptr; musicbrainz = find(tag, "MusicBrainz Album Artist Id"); if (mdata->Compilation()) { if (!musicbrainz) { musicbrainz = new UserTextIdentificationFrame(TagLib::String::UTF8); tag->addFrame(musicbrainz); musicbrainz->setDescription("MusicBrainz Album Artist Id"); } musicbrainz->setText(MYTH_MUSICBRAINZ_ALBUMARTIST_UUID); } else if (musicbrainz) tag->removeFrame(musicbrainz); // Compilation Artist Frame (TPE4/2) if (!mdata->CompilationArtist().isEmpty()) { TextIdentificationFrame *tpe4frame = nullptr; TagLib::ID3v2::FrameList tpelist = tag->frameListMap()["TPE4"]; if (!tpelist.isEmpty()) tpe4frame = (TextIdentificationFrame *)tpelist.front(); if (!tpe4frame) { tpe4frame = new TextIdentificationFrame(TagLib::ByteVector("TPE4"), TagLib::String::UTF8); tag->addFrame(tpe4frame); } tpe4frame->setText(QStringToTString(mdata->CompilationArtist())); TextIdentificationFrame *tpe2frame = nullptr; tpelist = tag->frameListMap()["TPE2"]; if (!tpelist.isEmpty()) tpe2frame = (TextIdentificationFrame *)tpelist.front(); if (!tpe2frame) { tpe2frame = new TextIdentificationFrame(TagLib::ByteVector("TPE2"), TagLib::String::UTF8); tag->addFrame(tpe2frame); } tpe2frame->setText(QStringToTString(mdata->CompilationArtist())); } if (!SaveFile()) return false; return true; }
/*! * \copydoc MetaIO::read() */ Metadata *MetaIOID3::read(const QString &filename) { if (!OpenFile(filename)) return NULL; TagLib::ID3v2::Tag *tag = GetID3v2Tag(true); // Create tag if none are found // if there is no ID3v2 tag, try to read the ID3v1 tag and copy it to // the ID3v2 tag structure if (tag->isEmpty()) { TagLib::ID3v1::Tag *tag_v1 = GetID3v1Tag(); if (!tag_v1) return NULL; if (!tag_v1->isEmpty()) { tag->setTitle(tag_v1->title()); tag->setArtist(tag_v1->artist()); tag->setAlbum(tag_v1->album()); tag->setTrack(tag_v1->track()); tag->setYear(tag_v1->year()); tag->setGenre(tag_v1->genre()); } } Metadata *metadata = new Metadata(filename); ReadGenericMetadata(tag, metadata); bool compilation = false; // Compilation Artist (TPE4 Remix) or fallback to (TPE2 Band) // N.B. The existance of a either frame is NOT an indication that this // is a compilation, but if it is then one of them will probably hold // the compilation artist. TextIdentificationFrame *tpeframe = NULL; TagLib::ID3v2::FrameList tpelist = tag->frameListMap()["TPE4"]; if (tpelist.isEmpty() || tpelist.front()->toString().isEmpty()) tpelist = tag->frameListMap()["TPE2"]; if (!tpelist.isEmpty()) tpeframe = (TextIdentificationFrame *)tpelist.front(); if (tpeframe && !tpeframe->toString().isEmpty()) { QString compilation_artist = TStringToQString(tpeframe->toString()) .trimmed(); metadata->setCompilationArtist(compilation_artist); } // MythTV rating and playcount, stored in POPM frame PopularimeterFrame *popm = findPOPM(tag, email); if (!popm) { if (!tag->frameListMap()["POPM"].isEmpty()) popm = dynamic_cast<PopularimeterFrame *> (tag->frameListMap()["POPM"].front()); } if (popm) { int rating = popm->rating(); rating = static_cast<int>(((static_cast<float>(rating)/255.0) * 10.0) + 0.5); metadata->setRating(rating); metadata->setPlaycount(popm->counter()); } // Look for MusicBrainz Album+Artist ID in TXXX Frame UserTextIdentificationFrame *musicbrainz = find(tag, "MusicBrainz Album Artist Id"); if (musicbrainz) { // If the MusicBrainz ID is the special "Various Artists" ID // then compilation is TRUE if (!compilation && !musicbrainz->fieldList().isEmpty()) compilation = (MYTH_MUSICBRAINZ_ALBUMARTIST_UUID == TStringToQString(musicbrainz->fieldList().front())); } // TLEN - Ignored intentionally, some encoders write bad values // e.g. Lame under certain circumstances will always write a length of // 27 hours // Length if (!tag->frameListMap()["TLEN"].isEmpty()) { int length = tag->frameListMap()["TLEN"].front()->toString().toInt(); LOG(VB_FILE, LOG_DEBUG, QString("MetaIOID3::read: Length for '%1' from tag is '%2'\n").arg(filename).arg(length)); } metadata->setCompilation(compilation); metadata->setLength(getTrackLength(m_file)); // The number of tracks on the album, if supplied if (!tag->frameListMap()["TRCK"].isEmpty()) { QString trackFrame = TStringToQString( tag->frameListMap()["TRCK"].front()->toString()) .trimmed(); int trackCount = trackFrame.section('/', -1).toInt(); if (trackCount > 0) metadata->setTrackCount(trackCount); } LOG(VB_FILE, LOG_DEBUG, QString("MetaIOID3::read: Length for '%1' from properties is '%2'\n").arg(filename).arg(metadata->Length())); return metadata; }