/* GET ALBUM COVER */ const char * tag_data_get_cover (TagDatac * data, uint * len) { TagLib::ID3v2::AttachedPictureFrame * pic = NULL; if (!(reinterpret_cast<TagData *>(data))->id3v2 || (reinterpret_cast<TagData *>(data))->id3v2->frameListMap()["APIC"].isEmpty()) { *len = 0; return NULL; } TagLib::ID3v2::FrameList::ConstIterator it; TagLib::ID3v2::FrameList l = (reinterpret_cast<TagData *>(data))->id3v2->frameListMap()["APIC"]; for(it=l.begin(); it!=l.end(); ++it) { TagLib::ID3v2::AttachedPictureFrame * t = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it); if (pic == NULL) pic = t; if (t->type () == TagLib::ID3v2::AttachedPictureFrame::FrontCover) { pic = t; break; } } if (pic == NULL) { *len = 0; return NULL; } *len = pic->picture ().size (); const char * out = (const char *) malloc (*len); memcpy ((void *) out, pic->picture ().data(), *len); return out; }
bool MetaDSF::exportPictures(const char *prefix) const { std::map<std::string, unsigned int> counter; TagLib::ID3v2::FrameList l = _i->_file.ID3v2Tag()->frameList("APIC"); TagLib::ID3v2::FrameList::ConstIterator it; for (it = l.begin(); it != l.end(); ++it) { TagLib::ID3v2::AttachedPictureFrame *f = static_cast<TagLib::ID3v2::AttachedPictureFrame *>(*it); std::string fname = prefix; std::string tname = picTypeDesc[f->type()].toCString(); std::string ext = MIMETypeToExtMap[f->mimeType()].toCString(); if (counter.find(tname) == counter.end()) counter[tname] = 1; else counter[tname] += 1; fname += "_"; fname += tname; fname += "_"; fname += std::to_string(counter[tname]); fname += "."; fname += ext; //std::cout << "fname = " << fname << std::endl; writeFileFromVector(fname.c_str(), f->picture()); } return true; }
bool MetaDSF::attachPicture(const TagLib::String &path, const TagLib::String &ptype) { // Determine MIME type TagLib::String mimeType; TagLib::String ext = path.substr(path.rfind(".") + 1); transform(ext.begin(), ext.end(), ext.begin(), ::tolower); if (extToMIMETypeMap.find(ext) == extToMIMETypeMap.end()) { std::cerr << "Unknown image format " << ext << std::endl; return false; } mimeType = extToMIMETypeMap[ext]; // Load file into memory TagLib::ByteVector v; if (loadFileIntoVector(path.toCString(), v) <= 0) { return false; } std::string pt = ptype.toCString(); TagLib::ID3v2::AttachedPictureFrame *apic = new TagLib::ID3v2::AttachedPictureFrame(); TagLib::ID3v2::AttachedPictureFrame::Type t = static_cast<TagLib::ID3v2::AttachedPictureFrame::Type>(std::stoi(pt, 0, 16)); apic->setPicture(v); apic->setMimeType(mimeType); apic->setType(t); _i->_file.ID3v2Tag()->addFrame(apic); _i->_changed = true; return true; }
Tag::Tag(const QString &filename) : m_filename(filename) { TagLib::MPEG::File mpegfile(filename.toLocal8Bit().constData()); TagLib::ID3v2::Tag* id3v2 = mpegfile.ID3v2Tag(); if (id3v2 && !id3v2->isEmpty()) { readRegularTag(id3v2, m_data); int picnum = 0; TagLib::ID3v2::FrameList frames = id3v2->frameListMap()["APIC"]; // attached picture TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); while (it != frames.end()) { TagLib::ID3v2::AttachedPictureFrame* apic = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it); TagLib::ByteVector bytes = apic->picture(); QImage img = QImage::fromData(reinterpret_cast<const uchar*>(bytes.data()), bytes.size()); if (!img.isNull()) { m_data[QLatin1String("picture") + QString::number(picnum++)] = QVariant(img); } ++it; } } else { TagLib::FileRef fileref(filename.toLocal8Bit().constData()); if (fileref.isNull()) return; TagLib::Tag* tag = fileref.tag(); if (!tag || tag->isEmpty()) return; readRegularTag(tag, m_data); } }
/** Check if file has an inner picture. */ bool FileHelper::hasCover() const { bool atLeastOnePicture = false; switch (_fileType) { case EXT_MP3: { TagLib::MPEG::File *mpegFile = static_cast<TagLib::MPEG::File*>(_file); if (mpegFile && mpegFile->hasID3v2Tag()) { // Look for picture frames only TagLib::ID3v2::FrameList listOfMp3Frames = mpegFile->ID3v2Tag()->frameListMap()["APIC"]; // It's possible to have more than one picture per file! if (!listOfMp3Frames.isEmpty()) { for (TagLib::ID3v2::FrameList::ConstIterator it = listOfMp3Frames.begin(); it != listOfMp3Frames.end() ; it++) { // Cast a Frame* to AttachedPictureFrame* TagLib::ID3v2::AttachedPictureFrame *pictureFrame = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it); atLeastOnePicture = atLeastOnePicture || (pictureFrame != nullptr && !pictureFrame->picture().isEmpty()); } } } break; } case EXT_FLAC: { if (TagLib::FLAC::File *flacFile = static_cast<TagLib::FLAC::File*>(_file)) { atLeastOnePicture = !flacFile->pictureList().isEmpty(); } } default: break; } return atLeastOnePicture; }
bool CTagBase::exportImage(const wchar_t* s) { if (!s) return false; if (_tagFile) { //get picture TagLib::MPEG::File *f = (TagLib::MPEG::File *)_tagFile.get(); if (!f->hasID3v2Tag() || f->ID3v2Tag()->isEmpty()) return false; /* TagLib::ID3v2::FrameList::ConstIterator it = f->ID3v2Tag()->frameList().begin(); for (; it != f->ID3v2Tag()->frameList().end(); it++) { if ((*it)->frameID().operator == (TagLib::ByteVector("APIC"))) { HANDLE hFile = CreateFile(s, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) return false; int nWriteBytes = 0; size_t size = (*it)->size(); ::WriteFile(hFile, (*it)->render().data(), (*it)->size(), (LPDWORD)&nWriteBytes, NULL); ::CloseHandle(hFile); if (nWriteBytes != size) return false; return true; } }*/ if (f->ID3v2Tag()->frameListMap().size() == 0) return false; if (f->ID3v2Tag()->frameListMap().find("APIC") == f->ID3v2Tag()->frameListMap().end()) return false; TagLib::ID3v2::FrameList Flist = f->ID3v2Tag()->frameListMap()["APIC"]; if (Flist.isEmpty()) return false; TagLib::ID3v2::AttachedPictureFrame *p = static_cast<TagLib::ID3v2::AttachedPictureFrame *>(Flist.front()); size_t size = p->picture().size(); CString strPicType = p->mimeType().toCString(true); int nPos = strPicType.Find('/'); CString strTemp = strPicType.Right(strPicType.GetLength() - nPos - 1); //CString strPicPath = s; //if (strTemp == _T("png")) // strPicPath.Append(_T(".png")); //else // strPicPath.Append(_T(".jpg")); HANDLE hFile = CreateFile(s, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) return false; int nWriteBytes = 0; ::WriteFile(hFile, p->picture().data(), size, (LPDWORD)&nWriteBytes, NULL); ::CloseHandle(hFile); if (nWriteBytes != size) return false; return true; } return false; }
/* ** Extracts cover art embedded in ID3v2 tags. ** */ bool CCover::ExtractID3(TagLib::ID3v2::Tag* tag, const std::wstring& target) { const TagLib::ID3v2::FrameList& frameList = tag->frameList("APIC"); if (!frameList.isEmpty()) { // Grab the first image TagLib::ID3v2::AttachedPictureFrame* frame = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(frameList.front()); return WriteCover(frame->picture(), target); } return false; }
/* ** Extracts cover art embedded in ID3v2 tags. ** */ bool QCoverArt::ExtractID3(TagLib::ID3v2::Tag* tag) { const TagLib::ID3v2::FrameList& frameList = tag->frameList("APIC"); if (!frameList.isEmpty()) { // Grab the first image TagLib::ID3v2::AttachedPictureFrame* frame = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(frameList.front()); img.loadFromData((const unsigned char*)frame->picture().data(), (int)frame->picture().size()); return true; } return false; }
bool CTagBase::addImage(wchar_t* s) { if (!_tagFile) return false; TagLib::MPEG::File *pAudioFile = (TagLib::MPEG::File *)_tagFile.get(); CString strFileName = s; ImageFile imageFile((CW2A)strFileName.GetBuffer()); if (!pAudioFile->isValid() || !imageFile.isValid()) return false; TagLib::ID3v2::Tag *tag = pAudioFile->ID3v2Tag(true); TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame; frame->setMimeType(imageFile.mimeType()); frame->setPicture(imageFile.data()); tag->addFrame(frame); return pAudioFile->save(); }
Cover* FileHelper::extractCover() { Cover *cover = nullptr; switch (_fileType) { case EXT_MP3: { TagLib::MPEG::File *mpegFile = static_cast<TagLib::MPEG::File*>(_file); if (mpegFile && mpegFile->hasID3v2Tag()) { // Look for picture frames only TagLib::ID3v2::FrameList listOfMp3Frames = mpegFile->ID3v2Tag()->frameListMap()["APIC"]; // It's possible to have more than one picture per file! if (!listOfMp3Frames.isEmpty()) { for (TagLib::ID3v2::FrameList::ConstIterator it = listOfMp3Frames.begin(); it != listOfMp3Frames.end() ; it++) { // Cast a Frame* to AttachedPictureFrame* TagLib::ID3v2::AttachedPictureFrame *pictureFrame = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it); if (pictureFrame) { // Performs a deep copy of the cover QByteArray b = QByteArray(pictureFrame->picture().data(), pictureFrame->picture().size()); cover = new Cover(b, QString(pictureFrame->mimeType().toCString(true))); } } } } else if (mpegFile && mpegFile->hasID3v1Tag()) { qDebug() << Q_FUNC_INFO << "Not implemented for ID3v1Tag"; } break; } case EXT_FLAC: { if (TagLib::FLAC::File *flacFile = static_cast<TagLib::FLAC::File*>(_file)) { auto list = flacFile->pictureList(); for (auto it = list.begin(); it != list.end() ; it++) { TagLib::FLAC::Picture *p = *it; if (p->type() == TagLib::FLAC::Picture::FrontCover) { // Performs a deep copy of the cover QByteArray b = QByteArray(p->data().data(), p->data().size()); cover = new Cover(b, QString(p->mimeType().toCString(true))); break; } } } break; } default: qDebug() << Q_FUNC_INFO << "Not implemented for this file type" << _fileType << _file << _fileInfo.absoluteFilePath(); break; } return cover; }
/** Sets the inner picture. */ void FileHelper::setCover(Cover *cover) { switch (_fileType) { case EXT_MP3: { TagLib::MPEG::File *mpegFile = static_cast<TagLib::MPEG::File*>(_file); if (mpegFile->hasID3v2Tag()) { // Look for picture frames only TagLib::ID3v2::FrameList mp3Frames = mpegFile->ID3v2Tag()->frameListMap()["APIC"]; if (!mp3Frames.isEmpty()) { for (TagLib::ID3v2::FrameList::Iterator it = mp3Frames.begin(); it != mp3Frames.end() ; it++) { // Removing a frame will invalidate any pointers on the list mpegFile->ID3v2Tag()->removeFrame(*it); break; } } if (cover != nullptr) { TagLib::ByteVector bv(cover->byteArray().data(), cover->byteArray().length()); TagLib::ID3v2::AttachedPictureFrame *pictureFrame = new TagLib::ID3v2::AttachedPictureFrame(); pictureFrame->setMimeType(cover->mimeType()); pictureFrame->setPicture(bv); pictureFrame->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover); mpegFile->ID3v2Tag()->addFrame(pictureFrame); } } else if (mpegFile->hasID3v1Tag()) { qDebug() << Q_FUNC_INFO << "Not implemented for ID3v1Tag"; } break; } case EXT_FLAC: { TagLib::FLAC::File *flacFile = static_cast<TagLib::FLAC::File*>(_file); flacFile->removePictures(); if (cover != nullptr) { TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture; picture->setType(TagLib::FLAC::Picture::FrontCover); TagLib::ByteVector bv(cover->byteArray().data(), cover->byteArray().length()); picture->setData(bv); flacFile->addPicture(picture); } break; } default: qDebug() << Q_FUNC_INFO << "Not implemented for" << _fileType; break; } }
QImage ID3v2TagHelper::embeddedCover() const { TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[fieldName( Meta::valHasCover ).toCString()]; TagLib::ID3v2::AttachedPictureFrame *cover = NULL; TagLib::ID3v2::AttachedPictureFrame *otherCover = NULL; for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it ) { TagLib::ID3v2::AttachedPictureFrame *currFrame = dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it ); if( currFrame->picture().size() < MIN_COVER_SIZE ) continue; if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover ) { cover = currFrame; } else if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::Other ) { otherCover = currFrame; } } if( !cover && otherCover ) cover = otherCover; if( !cover ) return QImage(); return QImage::fromData( ( uchar * )( cover->picture().data() ), cover->picture().size() ); }
bool CoverUtils::coverFromMPEGTags(TagLib::ID3v2::Tag *tag, Album *album) { if (!tag) return false; TagLib::ID3v2::FrameList list = tag->frameList("APIC"); if (list.isEmpty()) return false; TagLib::ID3v2::AttachedPictureFrame *frame = static_cast<TagLib::ID3v2::AttachedPictureFrame *>(list.front()); if (!frame) return false; const int frameSize = frame->picture().size(); if (frameSize <= 0) return false; QImage image; image.loadFromData((const uchar *) frame->picture().data(), frame->picture().size()); if (!isAcceptableImage(image)) return false; return saveImage(image, album); }
bool ID3v2TagHelper::hasEmbeddedCover() const { TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[fieldName( Meta::valHasCover ).toCString()]; for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it ) { TagLib::ID3v2::AttachedPictureFrame *currFrame = dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it ); if( currFrame->picture().size() < MIN_COVER_SIZE ) continue; if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover || currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::Other ) return true; } return false; }
bool ID3v2TagHelper::setEmbeddedCover( const QImage &cover ) { QByteArray bytes; QBuffer buffer( &bytes ); buffer.open( QIODevice::WriteOnly ); if( !cover.save( &buffer, "JPEG" ) ) { buffer.close(); return false; } buffer.close(); TagLib::ByteVector field = fieldName( Meta::valHasCover ).toCString(); TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[field]; TagLib::ID3v2::AttachedPictureFrame *frontCover = NULL; // remove covers TagLib::List<TagLib::ID3v2::AttachedPictureFrame*> backedUpPictures; for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it ) { TagLib::ID3v2::AttachedPictureFrame *currFrame = dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it ); m_tag->removeFrame( currFrame, false ); } // add new cover frontCover = new TagLib::ID3v2::AttachedPictureFrame( field ); frontCover->setMimeType( "image/jpeg" ); frontCover->setPicture( TagLib::ByteVector( bytes.data(), bytes.count() ) ); frontCover->setType( TagLib::ID3v2::AttachedPictureFrame::FrontCover ); m_tag->addFrame( frontCover ); return true; }
int MetaDSF::deletePictures(const TagLib::String &ptype) { TagLib::ID3v2::FrameList l = _i->_file.ID3v2Tag()->frameList("APIC"); TagLib::ID3v2::FrameList dl; // a list of frames to be deleted TagLib::ID3v2::FrameList::ConstIterator it; std::string pt = ptype.toCString(); // retrieve a list of frames that matches the given type for (it = l.begin(); it != l.end(); it++) { TagLib::ID3v2::AttachedPictureFrame *f = static_cast<TagLib::ID3v2::AttachedPictureFrame *>(*it); TagLib::ID3v2::AttachedPictureFrame::Type t = static_cast<TagLib::ID3v2::AttachedPictureFrame::Type>(std::stoi(pt, 0, 16)); if (f->type() == t) { dl.append(f); } } _i->deleteFrames(dl); _i->_changed = true; return dl.size(); }
void saveFile(string stdPath, DataEditors* editors) { const char* charPath = stdPath.c_str(); ifstream fileCheck(charPath); if(!fileCheck.good()) { QWidget *w = new QWidget(); QLabel *l = new QLabel("Invalid file!", w); QPushButton *b = new QPushButton("OK", w); QVBoxLayout *lay = new QVBoxLayout(); lay->addWidget(l); lay->addWidget(b); w->setLayout(lay); QWidget::connect(b, SIGNAL(clicked()), w, SLOT(close())); w->show(); return; } TagLib::FileName name = charPath; TagLib::FileRef file(name); if(editors->artist->isEnabled()) file.tag()->setArtist(editors->artist->text().toStdString()); if(editors->album->isEnabled()) file.tag()->setAlbum(editors->album->text().toStdString()); if(editors->track->isEnabled()) file.tag()->setTrack(editors->track->text().toInt()); if(editors->title->isEnabled()) file.tag()->setTitle(editors->title->text().toStdString()); if(editors->year->isEnabled()) file.tag()->setYear(editors->year->text().toInt()); if(editors->genre->isEnabled()) file.tag()->setGenre(editors->genre->currentText().toStdString()); if(editors->comment->isEnabled()) file.tag()->setComment(editors->comment->toPlainText().toStdString()); file.save(); if(editors->picture->isEnabled()) { TagLib::MPEG::File mpegFile(name); TagLib::ID3v2::Tag *tag = mpegFile.ID3v2Tag(true); TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame; QString picture = editors->picture->text(); if(picture == "<Attached picture>") { TagLib::ID3v2::AttachedPictureFrame *f = static_cast<TagLib::ID3v2::AttachedPictureFrame *>(editors->mpegFile->ID3v2Tag(true)->frameList("APIC").front()); frame->setMimeType(f->mimeType()); frame->setPicture(f->picture()); tag->removeFrames("APIC"); tag->addFrame(frame); mpegFile.save(); } else if(picture == "") { tag->removeFrames("APIC"); mpegFile.save(); } else { frame->setMimeType("image/jpeg"); string stdPic = picture.toStdString(); const char* charPicture = stdPic.c_str(); ifstream fileCheck(charPicture); if(!fileCheck.good()) { QWidget *w = new QWidget(); QLabel *l = new QLabel("Invalid picture!", w); QPushButton *b = new QPushButton("OK", w); QVBoxLayout *lay = new QVBoxLayout(); lay->addWidget(l); lay->addWidget(b); w->setLayout(lay); QWidget::connect(b, SIGNAL(clicked()), w, SLOT(close())); w->show(); delete w; return; } ImageFile imageTagLibFile(charPicture); frame->setPicture(imageTagLibFile.data()); tag->removeFrames("APIC"); tag->addFrame(frame); mpegFile.save(); } } }
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; }
/// <summary> /// Tries to set the cover based on the Tags of the specified file. /// </summary> /// <param name="filePath">Path to the file to get the cover from.</param> bool CoverArt::SetCoverFromTag(LPCWSTR filePath) { LPCWSTR extension = wcsrchr(filePath, L'.'); if (extension == nullptr) { return false; } auto ParseImage = [this] (const BYTE * data, UINT size) { IWICImagingFactory *factory = nullptr; IWICBitmapDecoder *decoder = nullptr; IWICBitmapFrameDecode *source = nullptr; HRESULT hr = E_FAIL; IStream *stream = SHCreateMemStream(data, size); if (stream) { hr = Factories::GetWICFactory(reinterpret_cast<LPVOID*>(&factory)); if (SUCCEEDED(hr)) { hr = factory->CreateDecoderFromStream(stream, nullptr, WICDecodeMetadataCacheOnDemand, &decoder); } if (SUCCEEDED(hr)) { hr = decoder->GetFrame(0, &source); } if (SUCCEEDED(hr)) { SendMessage(gLSModule.GetMessageWindow(), WM_COVERARTUPDATE, (WPARAM)this, (LPARAM)source); } SAFERELEASE(decoder); SAFERELEASE(stream); } return hr == S_OK; }; ++extension; if (_wcsicmp(extension, L"mp3") == 0) { TagLib::ID3v2::AttachedPictureFrame *pictureFrame = nullptr; BYTE picturePriority = 0xFF; TagLib::MPEG::File mp3File(filePath); auto tag = mp3File.ID3v2Tag(); if (tag && tag->frameListMap().contains("APIC")) { for (auto frame : mp3File.ID3v2Tag()->frameListMap()["APIC"]) { auto picFrame = (TagLib::ID3v2::AttachedPictureFrame *)frame; BYTE priority = mID3CoverTypePriority[picFrame->type()]; if (priority < picturePriority) { pictureFrame = picFrame; picturePriority = priority; } } if (pictureFrame != nullptr) { return ParseImage((const BYTE *)pictureFrame->picture().data(), pictureFrame->picture().size()); } } } else if (_wcsicmp(extension, L"flac") == 0) { TagLib::FLAC::File flacFile(filePath); for (auto &picture : flacFile.pictureList()) { if (picture->type() == TagLib::FLAC::Picture::FrontCover) { return ParseImage((const BYTE *)picture->data().data(), picture->data().size()); } } } else if (_wcsicmp(extension, L"mp4") == 0 || _wcsicmp(extension, L"m4a") == 0) { TagLib::MP4::File mp4File(filePath); if (mp4File.tag()->itemListMap().contains("covr")) { auto map = mp4File.tag()->itemListMap()["covr"]; auto list = map.toCoverArtList(); auto cover = list.front(); return ParseImage((const BYTE *)cover.data().data(), cover.data().size()); } } return false; }
bool SFB::Audio::SetID3v2TagFromMetadata(const Metadata& metadata, TagLib::ID3v2::Tag *tag, bool setAlbumArt) { if(nullptr == tag) return false; // Use UTF-8 as the default encoding (TagLib::ID3v2::FrameFactory::instance())->setDefaultTextEncoding(TagLib::String::UTF8); // Album title tag->setAlbum(TagLib::StringFromCFString(metadata.GetAlbumTitle())); // Artist tag->setArtist(TagLib::StringFromCFString(metadata.GetArtist())); // Composer tag->removeFrames("TCOM"); if(metadata.GetComposer()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TCOM", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(metadata.GetComposer())); tag->addFrame(frame); } // Genre tag->setGenre(TagLib::StringFromCFString(metadata.GetGenre())); // Date #if 1 int year = 0; if(metadata.GetReleaseDate()) year = CFStringGetIntValue(metadata.GetReleaseDate()); tag->setYear((TagLib::uint)year); #else // TODO: Parse the release date into components and set the frame appropriately tag->removeFrames("TDRC"); if(metadata.GetReleaseDate()) { /* 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. */ // year = CFStringGetIntValue(metadata.GetReleaseDate()); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TDRC", TagLib::String::Latin1); frame->setText(""); tag->addFrame(frame); } #endif // Comment tag->setComment(TagLib::StringFromCFString(metadata.GetComment())); // Album artist tag->removeFrames("TPE2"); if(metadata.GetAlbumArtist()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPE2", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(metadata.GetAlbumArtist())); tag->addFrame(frame); } // Track title tag->setTitle(TagLib::StringFromCFString(metadata.GetTitle())); // BPM tag->removeFrames("TBPM"); if(metadata.GetBPM()) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), metadata.GetBPM()); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TBPM", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } // Rating tag->removeFrames("POPM"); CFNumberRef rating = metadata.GetRating(); if(rating) { TagLib::ID3v2::PopularimeterFrame *frame = new TagLib::ID3v2::PopularimeterFrame(); int i; if(CFNumberGetValue(rating, kCFNumberIntType, &i)) { frame->setRating(i); tag->addFrame(frame); } } // Track number and total tracks tag->removeFrames("TRCK"); CFNumberRef trackNumber = metadata.GetTrackNumber(); CFNumberRef trackTotal = metadata.GetTrackTotal(); if(trackNumber && trackTotal) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@/%@"), trackNumber, trackTotal); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } else if(trackNumber) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), trackNumber); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } else if(trackTotal) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("/%@"), trackTotal); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TRCK", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } // Compilation // iTunes uses the TCMP frame for this, which isn't in the standard, but we'll use it for compatibility tag->removeFrames("TCMP"); if(metadata.GetCompilation()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TCMP", TagLib::String::Latin1); frame->setText(CFBooleanGetValue(metadata.GetCompilation()) ? "1" : "0"); tag->addFrame(frame); } // Disc number and total discs tag->removeFrames("TPOS"); CFNumberRef discNumber = metadata.GetDiscNumber(); CFNumberRef discTotal = metadata.GetDiscTotal(); if(discNumber && discTotal) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@/%@"), discNumber, discTotal); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } else if(discNumber) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("%@"), discNumber); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } else if(discTotal) { SFB::CFString str = CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("/%@"), discTotal); auto frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } // Lyrics tag->removeFrames("USLT"); if(metadata.GetLyrics()) { auto frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetLyrics())); tag->addFrame(frame); } tag->removeFrames("TSRC"); if(metadata.GetISRC()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSRC", TagLib::String::Latin1); frame->setText(TagLib::StringFromCFString(metadata.GetISRC())); tag->addFrame(frame); } // MusicBrainz auto musicBrainzReleaseIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "MusicBrainz Album Id"); if(nullptr != musicBrainzReleaseIDFrame) tag->removeFrame(musicBrainzReleaseIDFrame); CFStringRef musicBrainzReleaseID = metadata.GetMusicBrainzReleaseID(); if(musicBrainzReleaseID) { auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("MusicBrainz Album Id"); frame->setText(TagLib::StringFromCFString(musicBrainzReleaseID)); tag->addFrame(frame); } auto musicBrainzRecordingIDFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(const_cast<TagLib::ID3v2::Tag *>(tag), "MusicBrainz Track Id"); if(nullptr != musicBrainzRecordingIDFrame) tag->removeFrame(musicBrainzRecordingIDFrame); CFStringRef musicBrainzRecordingID = metadata.GetMusicBrainzRecordingID(); if(musicBrainzRecordingID) { auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("MusicBrainz Track Id"); frame->setText(TagLib::StringFromCFString(musicBrainzRecordingID)); tag->addFrame(frame); } // Sorting and grouping tag->removeFrames("TSOT"); if(metadata.GetTitleSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOT", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetTitleSortOrder())); tag->addFrame(frame); } tag->removeFrames("TSOA"); if(metadata.GetAlbumTitleSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOA", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetAlbumTitleSortOrder())); tag->addFrame(frame); } tag->removeFrames("TSOP"); if(metadata.GetArtistSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOP", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetArtistSortOrder())); tag->addFrame(frame); } tag->removeFrames("TSO2"); if(metadata.GetAlbumArtistSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSO2", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetAlbumArtistSortOrder())); tag->addFrame(frame); } tag->removeFrames("TSOC"); if(metadata.GetComposerSortOrder()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TSOC", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetComposerSortOrder())); tag->addFrame(frame); } tag->removeFrames("TIT1"); if(metadata.GetGrouping()) { auto frame = new TagLib::ID3v2::TextIdentificationFrame("TIT1", TagLib::String::UTF8); frame->setText(TagLib::StringFromCFString(metadata.GetGrouping())); tag->addFrame(frame); } // ReplayGain CFNumberRef trackGain = metadata.GetReplayGainTrackGain(); CFNumberRef trackPeak = metadata.GetReplayGainTrackPeak(); CFNumberRef albumGain = metadata.GetReplayGainAlbumGain(); CFNumberRef albumPeak = metadata.GetReplayGainAlbumPeak(); // Write TXXX frames auto trackGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_track_gain"); auto trackPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_track_peak"); auto albumGainFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_album_gain"); auto albumPeakFrame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, "replaygain_album_peak"); if(nullptr != trackGainFrame) tag->removeFrame(trackGainFrame); if(nullptr != trackPeakFrame) tag->removeFrame(trackPeakFrame); if(nullptr != albumGainFrame) tag->removeFrame(albumGainFrame); if(nullptr != albumPeakFrame) tag->removeFrame(albumPeakFrame); if(trackGain) { SFB::CFString str = CreateStringFromNumberWithFormat(trackGain, kCFNumberDoubleType, CFSTR("%+2.2f dB")); auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("replaygain_track_gain"); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } if(trackPeak) { SFB::CFString str = CreateStringFromNumberWithFormat(trackPeak, kCFNumberDoubleType, CFSTR("%1.8f dB")); auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("replaygain_track_peak"); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } if(albumGain) { SFB::CFString str = CreateStringFromNumberWithFormat(albumGain, kCFNumberDoubleType, CFSTR("%+2.2f dB")); auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("replaygain_album_gain"); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } if(albumPeak) { SFB::CFString str = CreateStringFromNumberWithFormat(albumPeak, kCFNumberDoubleType, CFSTR("%1.8f dB")); auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription("replaygain_album_peak"); frame->setText(TagLib::StringFromCFString(str)); tag->addFrame(frame); } // Also write the RVA2 frames tag->removeFrames("RVA2"); if(trackGain) { auto relativeVolume = new TagLib::ID3v2::RelativeVolumeFrame(); float f; CFNumberGetValue(trackGain, kCFNumberFloatType, &f); relativeVolume->setIdentification(TagLib::String("track", TagLib::String::Latin1)); relativeVolume->setVolumeAdjustment(f, TagLib::ID3v2::RelativeVolumeFrame::MasterVolume); tag->addFrame(relativeVolume); } if(albumGain) { auto relativeVolume = new TagLib::ID3v2::RelativeVolumeFrame(); float f; CFNumberGetValue(albumGain, kCFNumberFloatType, &f); relativeVolume->setIdentification(TagLib::String("album", TagLib::String::Latin1)); relativeVolume->setVolumeAdjustment(f, TagLib::ID3v2::RelativeVolumeFrame::MasterVolume); tag->addFrame(relativeVolume); } // Album art if(setAlbumArt) { tag->removeFrames("APIC"); for(auto attachedPicture : metadata.GetAttachedPictures()) { SFB::CGImageSource imageSource = CGImageSourceCreateWithData(attachedPicture->GetData(), nullptr); if(!imageSource) continue; TagLib::ID3v2::AttachedPictureFrame *frame = new TagLib::ID3v2::AttachedPictureFrame; // Convert the image's UTI into a MIME type SFB::CFString mimeType = UTTypeCopyPreferredTagWithClass(CGImageSourceGetType(imageSource), kUTTagClassMIMEType); if(mimeType) frame->setMimeType(TagLib::StringFromCFString(mimeType)); frame->setPicture(TagLib::ByteVector((const char *)CFDataGetBytePtr(attachedPicture->GetData()), (TagLib::uint)CFDataGetLength(attachedPicture->GetData()))); frame->setType((TagLib::ID3v2::AttachedPictureFrame::Type)attachedPicture->GetType()); if(attachedPicture->GetDescription()) frame->setDescription(TagLib::StringFromCFString(attachedPicture->GetDescription())); tag->addFrame(frame); } } return true; }
bool mediaMP3(Artwork *art, const char *filePath) { TagLib::MPEG::File f(filePath); if (!f.tag()) { return false; } if (!mediaTag(art, &f)) { return false; } art->filetype = FILETYPE_MP3; TagLib::ID3v2::Tag *id3v2tag = f.ID3v2Tag(); if (!id3v2tag) { return true; } // Don't know if this is the right way to interact with // TagLib, but for now it work!!! TagLib::ID3v2::FrameList::ConstIterator it; TagLib::ByteVector id; QString str; for(it = id3v2tag->frameList().begin(); it != id3v2tag->frameList().end(); it ++) { id = (*it)->frameID(); str = TStringToQString((*it)->toString()); if (id == "APIC") // Album cover { if (!art->hasCover()) { TagLib::ID3v2::AttachedPictureFrame *f = (TagLib::ID3v2::AttachedPictureFrame*) (*it); saveImage(art, (const char *) f->picture().data(), f->picture().size()); } } else if (id == "TCOM") // Composer { // composer } else if (id == "TPE2") // Album Artist { art->albumArtist = str; } else if (id == "TCMP" || id == "TCP") // iTunes, compilation { art->type = (str != "" && str != "0") ? ARTWORK_COMPILATION : ARTWORK_NORMAL; } else if (id == "TRCK") // iTunes, track # / total { iTunesSet(str, art->trackNo, art->trackCount); } else if (id == "TPOS") // iTunes, disk # / total { iTunesSet(str, art->discNo, art->discCount); } else if (id == "TSOA") // iTunes, SORT album { art->sAlbum = str; } else if (id == "TSPO") // iTunes, SORT artist { art->sArtist = str; } else if (id == "TSO2") // iTunes, SORT album artist { // art->sAlbumArtist = str; } else if (id == "TSOC") // iTunes, SORT composer { art->sComposer = str; } else if (id == "TDAT") // iTunes, ?????? { // ... what is this?? } else if (id == "TSIZ") // iTunes, ?????? { // ... what is this?? } } return true; }
Meta::FieldHash ID3v2TagHelper::tags() const { Meta::FieldHash data = TagHelper::tags(); TagLib::ID3v2::FrameList list = m_tag->frameList(); for( TagLib::ID3v2::FrameList::ConstIterator it = list.begin(); it != list.end(); ++it ) { qint64 field; TagLib::String frameName = TagLib::String( ( *it )->frameID() ); if( ( field = fieldName( frameName ) ) ) { if( field == Meta::valUniqueId ) { TagLib::ID3v2::UniqueFileIdentifierFrame *frame = dynamic_cast< TagLib::ID3v2::UniqueFileIdentifierFrame * >( *it ); if( !frame ) continue; QString identifier = TStringToQString( TagLib::String( frame->identifier() ) ); if( identifier.isEmpty() ) continue; if( frame->owner() == uidFieldName( UIDAFT ) && isValidUID( identifier, UIDAFT ) ) data.insert( Meta::valUniqueId, identifier ); continue; } else if( field == Meta::valHasCover ) { TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it ); if( !frame ) continue; if( ( frame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover || frame->type() == TagLib::ID3v2::AttachedPictureFrame::Other ) && frame->picture().size() > MIN_COVER_SIZE ) // must be at least 1kb { data.insert( Meta::valHasCover, true ); } continue; } TagLib::ID3v2::TextIdentificationFrame *frame = dynamic_cast< TagLib::ID3v2::TextIdentificationFrame * >( *it ); if( !frame ) continue; QString value = TStringToQString( frame->fieldList().toString( '\n' ) ); if( field == Meta::valDiscNr ) { int disc; if( ( disc = splitDiscNr( value ).first ) ) data.insert( field, disc ); } else if( field == Meta::valBpm ) data.insert( field, value.toFloat() ); else data.insert( field, value ); } else if( frameName == POPM_Frame ) { TagLib::ID3v2::PopularimeterFrame *frame = dynamic_cast< TagLib::ID3v2::PopularimeterFrame * >( *it ); if( !frame ) continue; if( TStringToQString( frame->email() ).isEmpty() ) // only read anonymous ratings { // FMPS tags have precedence if( !data.contains( Meta::valRating ) && frame->rating() != 0 ) data.insert( Meta::valRating, qRound( frame->rating() / 256.0 * 10.0 ) ); if( !data.contains( Meta::valPlaycount ) && frame->counter() < 10000 ) data.insert( Meta::valPlaycount, frame->counter() ); } } else if( frameName == TXXX_Frame ) { TagLib::ID3v2::UserTextIdentificationFrame *frame = dynamic_cast< TagLib::ID3v2::UserTextIdentificationFrame * >( *it ); if( !frame ) continue; // the value of the user text frame is stored in the // second and following fields. TagLib::StringList fields = frame->fieldList(); if( fields.size() >= 2 ) { QString value = TStringToQString( fields[1] ); if( fields[0] == fmpsFieldName( FMPSRating ) ) data.insert( Meta::valRating, qRound( value.toFloat() * 10.0 ) ); else if( fields[0] == fmpsFieldName( FMPSScore ) ) data.insert( Meta::valScore, value.toFloat() * 100.0 ); else if( fields[0] == fmpsFieldName( FMPSPlayCount ) ) data.insert( Meta::valPlaycount, value.toFloat() ); } } } return data; }
QByteArray TagReader::LoadEmbeddedArt(const QString& filename) const { if (filename.isEmpty()) return QByteArray(); qLog(Debug) << "Loading art from" << filename; #ifdef Q_OS_WIN32 TagLib::FileRef ref(filename.toStdWString().c_str()); #else TagLib::FileRef ref(QFile::encodeName(filename).constData()); #endif if (ref.isNull() || !ref.file()) return QByteArray(); // MP3 TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(ref.file()); if (file && file->ID3v2Tag()) { TagLib::ID3v2::FrameList apic_frames = file->ID3v2Tag()->frameListMap()["APIC"]; if (apic_frames.isEmpty()) return QByteArray(); TagLib::ID3v2::AttachedPictureFrame* pic = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front()); return QByteArray((const char*)pic->picture().data(), pic->picture().size()); } // Ogg vorbis/speex TagLib::Ogg::XiphComment* xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(ref.file()->tag()); if (xiph_comment) { TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap(); // Other than the below mentioned non-standard COVERART, // METADATA_BLOCK_PICTURE // is the proposed tag for cover pictures. // (see http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE) if (map.contains("METADATA_BLOCK_PICTURE")) { TagLib::StringList pict_list = map["METADATA_BLOCK_PICTURE"]; for (std::list<TagLib::String>::iterator it = pict_list.begin(); it != pict_list.end(); ++it) { QByteArray data(QByteArray::fromBase64(it->toCString())); TagLib::ByteVector tdata(data.data(), data.size()); TagLib::FLAC::Picture p(tdata); if (p.type() == TagLib::FLAC::Picture::FrontCover) return QByteArray(p.data().data(), p.data().size()); } // If there was no specific front cover, just take the first picture QByteArray data(QByteArray::fromBase64( map["METADATA_BLOCK_PICTURE"].front().toCString())); TagLib::ByteVector tdata(data.data(), data.size()); TagLib::FLAC::Picture p(tdata); return QByteArray(p.data().data(), p.data().size()); } // Ogg lacks a definitive standard for embedding cover art, but it seems // b64 encoding a field called COVERART is the general convention if (!map.contains("COVERART")) return QByteArray(); return QByteArray::fromBase64(map["COVERART"].toString().toCString()); } #ifdef TAGLIB_HAS_FLAC_PICTURELIST // Flac TagLib::FLAC::File* flac_file = dynamic_cast<TagLib::FLAC::File*>(ref.file()); if (flac_file && flac_file->xiphComment()) { TagLib::List<TagLib::FLAC::Picture*> pics = flac_file->pictureList(); if (!pics.isEmpty()) { // Use the first picture in the file - this could be made cleverer and // pick the front cover if it's present. std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin(); TagLib::FLAC::Picture* picture = *it; return QByteArray(picture->data().data(), picture->data().size()); } } #endif // MP4/AAC TagLib::MP4::File* aac_file = dynamic_cast<TagLib::MP4::File*>(ref.file()); if (aac_file) { TagLib::MP4::Tag* tag = aac_file->tag(); const TagLib::MP4::ItemListMap& items = tag->itemListMap(); TagLib::MP4::ItemListMap::ConstIterator it = items.find("covr"); if (it != items.end()) { const TagLib::MP4::CoverArtList& art_list = it->second.toCoverArtList(); if (!art_list.isEmpty()) { // Just take the first one for now const TagLib::MP4::CoverArt& art = art_list.front(); return QByteArray(art.data().data(), art.data().size()); } } } return QByteArray(); }
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); } } }