musikr: do custom picture handling

TagLib's picture handling is inadequate for our use case.
This commit is contained in:
Alexander Capehart 2025-01-08 11:11:48 -07:00
parent b3f4fdfb4a
commit e94b74edd4
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 57 additions and 22 deletions

View file

@ -22,6 +22,7 @@
#include <taglib/mp4tag.h> #include <taglib/mp4tag.h>
#include <taglib/textidentificationframe.h> #include <taglib/textidentificationframe.h>
#include <taglib/attachedpictureframe.h>
#include <taglib/tpropertymap.h> #include <taglib/tpropertymap.h>
@ -33,7 +34,10 @@ void JVMMetadataBuilder::setMimeType(const std::string_view type) {
this->mimeType = type; this->mimeType = type;
} }
void JVMMetadataBuilder::setId3v2(const TagLib::ID3v2::Tag &tag) { void JVMMetadataBuilder::setId3v2(TagLib::ID3v2::Tag &tag) {
// We want to ideally find the front cover, fall back to the first picture otherwise.
std::optional<TagLib::ID3v2::AttachedPictureFrame*> firstPic;
std::optional<TagLib::ID3v2::AttachedPictureFrame*> frontCoverPic;
for (auto frame : tag.frameList()) { for (auto frame : tag.frameList()) {
if (auto txxxFrame = if (auto txxxFrame =
dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(frame)) { dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(frame)) {
@ -50,18 +54,37 @@ void JVMMetadataBuilder::setId3v2(const TagLib::ID3v2::Tag &tag) {
TagLib::String key = frame->frameID(); TagLib::String key = frame->frameID();
TagLib::StringList frameText = textFrame->fieldList(); TagLib::StringList frameText = textFrame->fieldList();
id3v2.add_id(key, frameText); id3v2.add_id(key, frameText);
} else if (auto pictureFrame =
dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(frame)) {
if (!firstPic) {
firstPic = pictureFrame;
}
if (!frontCoverPic
&& pictureFrame->type()
== TagLib::ID3v2::AttachedPictureFrame::FrontCover) {
frontCoverPic = pictureFrame;
}
} else { } else {
continue; continue;
} }
} }
if (frontCoverPic) {
auto pic = *frontCoverPic;
cover = pic->picture();
} else if (firstPic) {
auto pic = *firstPic;
cover = pic->picture();
}
} }
void JVMMetadataBuilder::setXiph(const TagLib::Ogg::XiphComment &tag) { void JVMMetadataBuilder::setXiph(TagLib::Ogg::XiphComment &tag) {
for (auto field : tag.fieldListMap()) { for (auto field : tag.fieldListMap()) {
auto key = field.first.upper(); auto key = field.first.upper();
auto values = field.second; auto values = field.second;
xiph.add_custom(key, values); xiph.add_custom(key, values);
} }
auto pics = tag.pictureList();
setFlacPictures(pics);
} }
template<typename T> template<typename T>
@ -77,11 +100,27 @@ void mp4AddImpl(JVMTagMap &map, TagLib::String &itemName, T itemValue) {
} }
} }
void JVMMetadataBuilder::setMp4(const TagLib::MP4::Tag &tag) { void JVMMetadataBuilder::setMp4(TagLib::MP4::Tag &tag) {
auto map = tag.itemMap(); auto map = tag.itemMap();
std::optional < TagLib::MP4::CoverArt > firstCover;
for (auto item : map) { for (auto item : map) {
auto itemName = item.first; auto itemName = item.first;
auto itemValue = item.second; auto itemValue = item.second;
if (itemName == "covr") {
// Special cover case.
// MP4 has no types, so just prioritize easier to decode covers (PNG, JPEG)
auto pics = itemValue.toCoverArtList();
for (auto &pic : pics) {
auto format = pic.format();
if (format == TagLib::MP4::CoverArt::PNG
|| format == TagLib::MP4::CoverArt::JPEG) {
cover = pic.data();
return;
}
}
cover = pics.front().data();
return;
}
auto type = itemValue.type(); auto type = itemValue.type();
std::string serializedValue; std::string serializedValue;
switch (type) { switch (type) {
@ -114,23 +153,18 @@ void JVMMetadataBuilder::setMp4(const TagLib::MP4::Tag &tag) {
} }
} }
void JVMMetadataBuilder::setCover( void JVMMetadataBuilder::setFlacPictures(
const TagLib::List<TagLib::VariantMap> covers) { TagLib::List<TagLib::FLAC::Picture*> &pics) {
if (covers.isEmpty()) { // Find the front cover image. If it doesn't exist, fall back to the first image.
return; for (auto pic : pics) {
} if (pic->type() == TagLib::FLAC::Picture::FrontCover) {
// Find the cover with a "front cover" type cover = pic->data();
for (auto cover : covers) {
auto type = cover["pictureType"].toString();
if (type == "Front Cover") {
this->cover = cover["data"].toByteVector();
return; return;
} }
} }
// No front cover, just pick first. if (!pics.isEmpty()) {
// TODO: Consider having cascading fallbacks to increasingly less cover = pics.front()->data();
// relevant covers perhaps }
this->cover = covers.front()["data"].toByteVector();
} }
void JVMMetadataBuilder::setProperties(TagLib::AudioProperties *properties) { void JVMMetadataBuilder::setProperties(TagLib::AudioProperties *properties) {

View file

@ -35,10 +35,10 @@ public:
JVMMetadataBuilder(JNIEnv *env); JVMMetadataBuilder(JNIEnv *env);
void setMimeType(const std::string_view type); void setMimeType(const std::string_view type);
void setId3v2(const TagLib::ID3v2::Tag &tag); void setId3v2(TagLib::ID3v2::Tag &tag);
void setXiph(const TagLib::Ogg::XiphComment &tag); void setXiph(TagLib::Ogg::XiphComment &tag);
void setMp4(const TagLib::MP4::Tag &tag); void setMp4(TagLib::MP4::Tag &tag);
void setCover(const TagLib::List<TagLib::VariantMap> covers); void setFlacPictures(TagLib::List<TagLib::FLAC::Picture*> &pics);
void setProperties(TagLib::AudioProperties *properties); void setProperties(TagLib::AudioProperties *properties);
jobject build(); jobject build();

View file

@ -66,6 +66,8 @@ Java_org_oxycblt_musikr_metadata_TagLibJNI_openNative(JNIEnv *env,
if (xiphComment != nullptr) { if (xiphComment != nullptr) {
builder.setXiph(*xiphComment); builder.setXiph(*xiphComment);
} }
auto pics = flacFile->pictureList();
builder.setFlacPictures(pics);
} else if (auto *opusFile = dynamic_cast<TagLib::Ogg::Opus::File *>(file)) { } else if (auto *opusFile = dynamic_cast<TagLib::Ogg::Opus::File *>(file)) {
builder.setMimeType("audio/opus"); builder.setMimeType("audio/opus");
auto tag = opusFile->tag(); auto tag = opusFile->tag();
@ -92,7 +94,6 @@ Java_org_oxycblt_musikr_metadata_TagLibJNI_openNative(JNIEnv *env,
} }
builder.setProperties(file->audioProperties()); builder.setProperties(file->audioProperties());
builder.setCover(file->tag()->complexProperties("PICTURE"));
return builder.build(); return builder.build();
} catch (std::runtime_error e) { } catch (std::runtime_error e) {
LOGE("Error opening file: %s", e.what()); LOGE("Error opening file: %s", e.what());