bool SFB::Audio::AddID3v2TagToDictionary(CFMutableDictionaryRef dictionary, std::vector<std::shared_ptr<AttachedPicture>>& attachedPictures, const TagLib::ID3v2::Tag *tag) { if(nullptr == dictionary || nullptr == tag) return false; // Add the basic tags not specific to ID3v2 AddTagToDictionary(dictionary, tag); // Release date auto frameList = tag->frameListMap()["TDRC"]; if(!frameList.isEmpty()) { /* The timestamp fields are based on a subset of ISO 8601. When being as precise as possible the format of a time string is yyyy-MM-ddTHH:mm:ss (year, "-", month, "-", day, "T", hour (out of 24), ":", minutes, ":", seconds), but the precision may be reduced by removing as many time indicators as wanted. Hence valid timestamps are yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm and yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use the slash character as described in 8601, and for multiple non- contiguous dates, use multiple strings, if allowed by the frame definition. */ TagLib::AddStringToCFDictionary(dictionary, Metadata::kReleaseDateKey, frameList.front()->toString()); } // Extract composer if present frameList = tag->frameListMap()["TCOM"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kComposerKey, frameList.front()->toString()); // Extract album artist frameList = tag->frameListMap()["TPE2"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumArtistKey, frameList.front()->toString()); // BPM frameList = tag->frameListMap()["TBPM"]; if(!frameList.isEmpty()) { bool ok = false; int BPM = frameList.front()->toString().toInt(&ok); if(ok) AddIntToDictionary(dictionary, Metadata::kBPMKey, BPM); } // Rating TagLib::ID3v2::PopularimeterFrame *popularimeter = nullptr; frameList = tag->frameListMap()["POPM"]; if(!frameList.isEmpty() && nullptr != (popularimeter = dynamic_cast<TagLib::ID3v2::PopularimeterFrame *>(frameList.front()))) AddIntToDictionary(dictionary, Metadata::kRatingKey, popularimeter->rating()); // Extract total tracks if present frameList = tag->frameListMap()["TRCK"]; if(!frameList.isEmpty()) { // Split the tracks at '/' TagLib::String s = frameList.front()->toString(); bool ok; size_t pos = s.find("/", 0); if(TagLib::String::npos() != pos) { int trackNum = s.substr(0, pos).toInt(&ok); if(ok) AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, trackNum); int trackTotal = s.substr(pos + 1).toInt(&ok); if(ok) AddIntToDictionary(dictionary, Metadata::kTrackTotalKey, trackTotal); } else if(s.length()) { int trackNum = s.toInt(&ok); if(ok) AddIntToDictionary(dictionary, Metadata::kTrackNumberKey, trackNum); } } // Extract disc number and total discs frameList = tag->frameListMap()["TPOS"]; if(!frameList.isEmpty()) { // Split the tracks at '/' TagLib::String s = frameList.front()->toString(); bool ok; size_t pos = s.find("/", 0); if(TagLib::String::npos() != pos) { int discNum = s.substr(0, pos).toInt(&ok); if(ok) AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, discNum); int discTotal = s.substr(pos + 1).toInt(&ok); if(ok) AddIntToDictionary(dictionary, Metadata::kDiscTotalKey, discTotal); } else if(s.length()) { int discNum = s.toInt(&ok); if(ok) AddIntToDictionary(dictionary, Metadata::kDiscNumberKey, discNum); } } // Lyrics frameList = tag->frameListMap()["USLT"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kLyricsKey, frameList.front()->toString()); // Extract compilation if present (iTunes TCMP tag) frameList = tag->frameListMap()["TCMP"]; if(!frameList.isEmpty()) // It seems that the presence of this frame indicates a compilation CFDictionarySetValue(dictionary, Metadata::kCompilationKey, kCFBooleanTrue); frameList = tag->frameListMap()["TSRC"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kISRCKey, frameList.front()->toString()); // MusicBrainz auto musicBrainzReleaseIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Album Id"); if(musicBrainzReleaseIDFrame) TagLib::AddStringToCFDictionary(dictionary, Metadata::kMusicBrainzReleaseIDKey, musicBrainzReleaseIDFrame->fieldList().back()); auto musicBrainzRecordingIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Track Id"); if(musicBrainzRecordingIDFrame) TagLib::AddStringToCFDictionary(dictionary, Metadata::kMusicBrainzRecordingIDKey, musicBrainzRecordingIDFrame->fieldList().back()); // Sorting and grouping frameList = tag->frameListMap()["TSOT"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kTitleSortOrderKey, frameList.front()->toString()); frameList = tag->frameListMap()["TSOA"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumTitleSortOrderKey, frameList.front()->toString()); frameList = tag->frameListMap()["TSOP"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kArtistSortOrderKey, frameList.front()->toString()); frameList = tag->frameListMap()["TSO2"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kAlbumArtistSortOrderKey, frameList.front()->toString()); frameList = tag->frameListMap()["TSOC"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kComposerSortOrderKey, frameList.front()->toString()); frameList = tag->frameListMap()["TIT1"]; if(!frameList.isEmpty()) TagLib::AddStringToCFDictionary(dictionary, Metadata::kGroupingKey, frameList.front()->toString()); // ReplayGain bool foundReplayGain = false; // Preference is TXXX frames, RVA2 frame, then LAME header auto trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_TRACK_GAIN"); auto trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_TRACK_PEAK"); auto albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_ALBUM_GAIN"); auto albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "REPLAYGAIN_ALBUM_PEAK"); if(!trackGainFrame) trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_track_gain"); if(trackGainFrame) { SFB::CFString str(trackGainFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8); double num = CFStringGetDoubleValue(str); AddDoubleToDictionary(dictionary, Metadata::kTrackGainKey, num); AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, 89.0); foundReplayGain = true; } if(!trackPeakFrame) trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_track_peak"); if(trackPeakFrame) { SFB::CFString str(trackPeakFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8); double num = CFStringGetDoubleValue(str); AddDoubleToDictionary(dictionary, Metadata::kTrackPeakKey, num); } if(!albumGainFrame) albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_album_gain"); if(albumGainFrame) { SFB::CFString str(albumGainFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8); double num = CFStringGetDoubleValue(str); AddDoubleToDictionary(dictionary, Metadata::kAlbumGainKey, num); AddDoubleToDictionary(dictionary, Metadata::kReferenceLoudnessKey, 89.0); foundReplayGain = true; } if(!albumPeakFrame) albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "replaygain_album_peak"); if(albumPeakFrame) { SFB::CFString str(albumPeakFrame->fieldList().back().toCString(true), kCFStringEncodingUTF8); double num = CFStringGetDoubleValue(str); AddDoubleToDictionary(dictionary, Metadata::kAlbumPeakKey, num); } // If nothing found check for RVA2 frame if(!foundReplayGain) { frameList = tag->frameListMap()["RVA2"]; for(auto frameIterator : tag->frameListMap()["RVA2"]) { TagLib::ID3v2::RelativeVolumeFrame *relativeVolume = dynamic_cast<TagLib::ID3v2::RelativeVolumeFrame *>(frameIterator); if(!relativeVolume) continue; // Attempt to use the master volume if present auto channels = relativeVolume->channels(); auto channelType = TagLib::ID3v2::RelativeVolumeFrame::MasterVolume; // Fall back on whatever else exists in the frame if(!channels.contains(TagLib::ID3v2::RelativeVolumeFrame::MasterVolume)) channelType = channels.front(); float volumeAdjustment = relativeVolume->volumeAdjustment(channelType); if(TagLib::String("track", TagLib::String::Latin1) == relativeVolume->identification()) { if((int)volumeAdjustment) AddFloatToDictionary(dictionary, Metadata::kTrackGainKey, volumeAdjustment); } else if(TagLib::String("album", TagLib::String::Latin1) == relativeVolume->identification()) { if((int)volumeAdjustment) AddFloatToDictionary(dictionary, Metadata::kAlbumGainKey, volumeAdjustment); } // Fall back to track gain if identification is not specified else { if((int)volumeAdjustment) AddFloatToDictionary(dictionary, Metadata::kTrackGainKey, volumeAdjustment); } } } // Extract album art if present for(auto it : tag->frameListMap()["APIC"]) { TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(it); if(frame) { SFB::CFData data((const UInt8 *)frame->picture().data(), (CFIndex)frame->picture().size()); SFB::CFString description; if(!frame->description().isEmpty()) description = CFString(frame->description().toCString(true), kCFStringEncodingUTF8); attachedPictures.push_back(std::make_shared<AttachedPicture>(data, (AttachedPicture::Type)frame->type(), description)); } } return true; }
void cMediaInfo::readTagV2(ID3v2::Tag* lpTag) { m_iID3v2Version = lpTag->header()->majorVersion(); m_iID3v2Revision = lpTag->header()->revisionNumber(); m_iID3v2Size = lpTag->header()->tagSize(); for(ID3v2::FrameList::ConstIterator it = lpTag->frameList().begin();it != lpTag->frameList().end();it++) { QString szID = QString("%1").arg((*it)->frameID().data()).left(4); if(!szID.compare("TIT1")) m_szContentGroupDescription = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TIT2")) m_szTitle = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TIT3")) m_szSubTitle = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TALB")) m_szAlbum = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TOAL")) m_szOriginalAlbum = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TRCK")) m_szTrackNumber = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TPOS")) m_szPartOfSet = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TSST")) m_szSubTitleOfSet = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TSRC")) m_szInternationalStandardRecordingCode = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TPE1")) m_szLeadArtist = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TPE2")) m_szBand = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TPE3")) m_szConductor = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TPE4")) m_szInterpret = QString::fromStdWString((*it)->toString().toWString()).split("\r\n"); else if(!szID.compare("TOPE")) m_szOriginalArtist = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TEXT")) m_szTextWriter = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TOLY")) m_szOriginalTextWriter = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TCOM")) m_szComposer = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TENC")) m_szEncodedBy = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TBPM")) m_iBeatsPerMinute = QString::fromStdWString((*it)->toString().toWString()).toInt(); else if(!szID.compare("TLEN") && m_iLength == 0) m_iLength = QString::fromStdWString((*it)->toString().toWString()).toInt(); else if(!szID.compare("TLAN")) m_szLanguage = QString::fromStdWString((*it)->toString().toWString()).split("\r\n"); else if(!szID.compare("TCON")) m_szContentType = QString::fromStdWString((*it)->toString().toWString()).split("\r\n"); else if(!szID.compare("TFLT")) m_szFileType = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TMED")) m_szMediaType = QString::fromStdWString((*it)->toString().toWString()).split("\r\n"); else if(!szID.compare("TMOO")) m_szMood = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TCOP")) m_szCopyright = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TPRO")) m_szProducedNotice = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TPUB")) m_szPublisher = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TOWN")) m_szFileOwner = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TRSN")) m_szInternetRadioStationName = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TRSO")) m_szInternetRadioStationOwner = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TOFN")) m_szOriginalFilename = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TDLY")) m_iPlaylistDelay = QString::fromStdWString((*it)->toString().toWString()).toInt(); else if(!szID.compare("TDEN")) m_encodingTime = str2TS(QString::fromStdWString((*it)->toString().toWString())); else if(!szID.compare("TDOR")) m_originalReleaseTime = str2TS(QString::fromStdWString((*it)->toString().toWString())); else if(!szID.compare("TDRC")) m_recordingTime = str2TS(QString::fromStdWString((*it)->toString().toWString())); else if(!szID.compare("TDRL")) m_releaseTime = str2TS(QString::fromStdWString((*it)->toString().toWString())); else if(!szID.compare("TDTG")) m_taggingTime = str2TS(QString::fromStdWString((*it)->toString().toWString())); else if(!szID.compare("TSSE")) m_szswhwSettings = QString(QString::fromStdWString((*it)->toString().toWString())).split("\r\n"); else if(!szID.compare("TSOA")) m_szAlbumSortOrder = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TSOP")) m_szPerformerSortOrder = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("TSOT")) m_szTitleSortOrder = QString::fromStdWString((*it)->toString().toWString()); else if(!szID.compare("SYLT")) { TagLib::ID3v2::SynchronizedLyricsFrame* lpLyrics = static_cast<TagLib::ID3v2::SynchronizedLyricsFrame *> (*it); m_szSynchronizedLyricsLanguage = QString(lpLyrics->language().data()).left(3); String::Type type = lpLyrics->textEncoding(); m_szSynchronizedLyricsDescription = QString::fromStdWString(lpLyrics->description().toWString()); TagLib::ID3v2::SynchronizedLyricsFrame::SynchedTextList list = lpLyrics->synchedText(); for(ID3v2::SynchronizedLyricsFrame::SynchedTextList::ConstIterator it1 = list.begin(); it1 != list.end();it1++) { ID3v2::SynchronizedLyricsFrame::SynchedText t = *(it1); m_szSynchronizedLyrics.add(t.time, QString::fromStdWString(t.text.toWString())); } } else if(!szID.compare("USLT")) { TagLib::ID3v2::UnsynchronizedLyricsFrame* lpLyrics = static_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame *> (*it); m_szUnsynchronizedLyricsLanguage = QString(lpLyrics->language().data()).left(3); String::Type type = lpLyrics->textEncoding(); m_szUnsynchronizedLyricsDescription = QString::fromStdWString(lpLyrics->description().toWString()); QString szText = QString::fromStdWString(lpLyrics->text().toWString()); if(szText.contains("\r\n")) m_szUnsynchronizedLyrics = szText.split("\r\n"); else if(szText.contains("\r")) m_szUnsynchronizedLyrics = szText.split("\r"); else if(szText.contains("\n")) m_szUnsynchronizedLyrics = szText.split("\n"); else m_szUnsynchronizedLyrics.append(szText); } else if(!szID.compare("APIC")) { TagLib::ID3v2::AttachedPictureFrame* lpPicture = static_cast<TagLib::ID3v2::AttachedPictureFrame *> (*it); TagLib::ID3v2::AttachedPictureFrame::Type t = lpPicture->type(); QString szDescription; szDescription = QString::fromStdWString(lpPicture->description().toWString()); QByteArray pictureData = QByteArray(lpPicture->picture().data(), lpPicture->picture().size()); m_pixmapList.add(pictureData, m_szFileName, (cPixmap::ImageType)t, szDescription); } } }