musikr: add back tag whitespace fixes

Requires me to rejig the JNI integration, but it's overall good since
it allows me to strip away a lot of the logic.
This commit is contained in:
Alexander Capehart 2025-01-04 15:55:59 -07:00
parent fddd527975
commit a4d7b54db7
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 173 additions and 109 deletions

View file

@ -23,3 +23,4 @@
-keep class org.oxycblt.musikr.metadata.NativeInputStream { *; } -keep class org.oxycblt.musikr.metadata.NativeInputStream { *; }
-keep class org.oxycblt.musikr.metadata.Metadata { *; } -keep class org.oxycblt.musikr.metadata.Metadata { *; }
-keep class org.oxycblt.musikr.metadata.Properties { *; } -keep class org.oxycblt.musikr.metadata.Properties { *; }
-keep class org.oxycblt.musikr.metadata.NativeTagMap { *; }

View file

@ -18,7 +18,7 @@
#include "JVMMetadataBuilder.h" #include "JVMMetadataBuilder.h"
#include "log.h" #include "util.h"
#include <taglib/mp4tag.h> #include <taglib/mp4tag.h>
#include <taglib/textidentificationframe.h> #include <taglib/textidentificationframe.h>
@ -37,18 +37,18 @@ void JVMMetadataBuilder::setId3v2(const TagLib::ID3v2::Tag &tag) {
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)) {
TagLib::String id = frame->frameID();
TagLib::StringList frameText = txxxFrame->fieldList(); TagLib::StringList frameText = txxxFrame->fieldList();
// Frame text starts with the description then the remaining values // Frame text starts with the description then the remaining values
auto begin = frameText.begin(); auto begin = frameText.begin();
TagLib::String key = TagLib::String(frame->frameID()) + ":" TagLib::String description = *begin;
+ begin->upper();
frameText.erase(begin); frameText.erase(begin);
id3v2.add(key, frameText); id3v2.add_combined(id, description, frameText);
} else if (auto textFrame = } else if (auto textFrame =
dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(frame)) { dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(frame)) {
TagLib::String key = frame->frameID(); TagLib::String key = frame->frameID();
TagLib::StringList frameText = textFrame->fieldList(); TagLib::StringList frameText = textFrame->fieldList();
id3v2.add(key, frameText); id3v2.add_id(key, frameText);
} else { } else {
continue; continue;
} }
@ -59,7 +59,23 @@ void JVMMetadataBuilder::setXiph(const 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(key, values); xiph.add_custom(key, values);
}
}
template<typename T>
void mp4AddImpl(JVMTagMap &map, TagLib::String &itemName, T itemValue) {
if (itemName.startsWith("----")) {
// Split this into it's atom name and description
auto split = itemName.split(":");
if (split.size() != 2) {
throw std::runtime_error("Invalid atom name");
}
auto atomName = split[0];
auto atomDescription = split[1];
map.add_combined(atomName, atomDescription, itemValue);
} else {
map.add_id(itemName, itemValue);
} }
} }
@ -67,47 +83,36 @@ void JVMMetadataBuilder::setMp4(const TagLib::MP4::Tag &tag) {
auto map = tag.itemMap(); auto map = tag.itemMap();
for (auto item : map) { for (auto item : map) {
auto itemName = item.first; auto itemName = item.first;
if (itemName.startsWith("----")) {
// Capitalize description atoms only
// Other standard atoms are cased so we want to avoid collissions there.
itemName = itemName.upper();
}
auto itemValue = item.second; auto itemValue = item.second;
auto type = itemValue.type(); auto type = itemValue.type();
// Only read out the atoms for the reasonable tags we are expecting. std::string serializedValue;
// None of the crazy binary atoms. switch (type) {
if (type == TagLib::MP4::Item::Type::StringList) { // Normal expected MP4 items
auto value = itemValue.toStringList(); case TagLib::MP4::Item::Type::StringList:
mp4.add(itemName, value); mp4AddImpl(mp4, itemName, itemValue.toStringList());
continue; break;
} // Weird MP4 items I'm 90% sure I'll encounter.
case TagLib::MP4::Item::Type::Int:
// Assume that taggers will be unhinged and store track numbers serializedValue = std::to_string(itemValue.toInt());
// as ints, uints, or longs. break;
if (type == TagLib::MP4::Item::Type::Int) { case TagLib::MP4::Item::Type::UInt:
auto value = std::to_string(itemValue.toInt()); serializedValue = std::to_string(itemValue.toUInt());
id3v2.add(itemName, value); break;
continue; case TagLib::MP4::Item::Type::LongLong:
} serializedValue = std::to_string(itemValue.toLongLong());
if (type == TagLib::MP4::Item::Type::UInt) { break;
auto value = std::to_string(itemValue.toUInt()); case TagLib::MP4::Item::Type::IntPair:
id3v2.add(itemName, value); // It's inefficient going from the integer representation back into
continue; // a string, but I fully expect taggers to just write "NN/TT" strings
} // anyway, and musikr doesn't have to do as much fiddly variant handling.
if (type == TagLib::MP4::Item::Type::LongLong) { serializedValue = std::to_string(itemValue.toIntPair().first) + "/"
auto value = std::to_string(itemValue.toLongLong()); + std::to_string(itemValue.toIntPair().second);
id3v2.add(itemName, value); break;
continue; default:
} // Don't care about the other types
if (type == TagLib::MP4::Item::Type::IntPair) { continue;
// It's inefficient going from the integer representation back into
// a string, but I fully expect taggers to just write "NN/TT" strings
// anyway, and musikr doesn't have to do as much fiddly variant handling.
auto value = std::to_string(itemValue.toIntPair().first) + "/"
+ std::to_string(itemValue.toIntPair().second);
id3v2.add(itemName, value);
continue;
} }
mp4AddImpl(mp4, itemName, TagLib::String(serializedValue));
} }
} }

View file

@ -18,76 +18,86 @@
#include "JVMTagMap.h" #include "JVMTagMap.h"
JVMTagMap::JVMTagMap(JNIEnv *env) : env(env) { #include "util.h"
jclass hashMapClass = env->FindClass("java/util/HashMap");
jmethodID init = env->GetMethodID(hashMapClass, "<init>", "()V");
hashMap = env->NewObject(hashMapClass, init);
hashMapGetMethod = env->GetMethodID(hashMapClass, "get",
"(Ljava/lang/Object;)Ljava/lang/Object;");
hashMapPutMethod = env->GetMethodID(hashMapClass, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
env->DeleteLocalRef(hashMapClass);
jclass arrayListClass = env->FindClass("java/util/ArrayList"); JVMTagMap::JVMTagMap(JNIEnv *env) : env(env) {
jclass tagMapClass = env->FindClass("org/oxycblt/musikr/metadata/NativeTagMap");
jmethodID init = env->GetMethodID(tagMapClass, "<init>", "()V");
tagMap = env->NewObject(tagMapClass, init);
tagMapAddIdSingleMethod = env->GetMethodID(tagMapClass, "addID",
"(Ljava/lang/String;Ljava/lang/String;)V");
tagMapAddIdListMethod = env->GetMethodID(tagMapClass, "addID",
"(Ljava/lang/String;Ljava/util/List;)V");
tagMapAddCustomSingleMethod = env->GetMethodID(tagMapClass, "addCustom",
"(Ljava/lang/String;Ljava/lang/String;)V");
tagMapAddCustomListMethod = env->GetMethodID(tagMapClass, "addCustom",
"(Ljava/lang/String;Ljava/util/List;)V");
tagMapAddCombinedSingleMethod = env->GetMethodID(tagMapClass, "addCombined",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
tagMapAddCombinedListMethod = env->GetMethodID(tagMapClass, "addCombined",
"(Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V");
tagMapGetObjectMethod = env->GetMethodID(tagMapClass, "getObject",
"()Ljava/util/Map;");
env->DeleteLocalRef(tagMapClass);
arrayListClass = env->FindClass("java/util/ArrayList");
arrayListInitMethod = env->GetMethodID(arrayListClass, "<init>", "()V"); arrayListInitMethod = env->GetMethodID(arrayListClass, "<init>", "()V");
arrayListAddMethod = env->GetMethodID(arrayListClass, "add", arrayListAddMethod = env->GetMethodID(arrayListClass, "add",
"(Ljava/lang/Object;)Z"); "(Ljava/lang/Object;)Z");
env->DeleteLocalRef(arrayListClass);
} }
JVMTagMap::~JVMTagMap() { JVMTagMap::~JVMTagMap() {
env->DeleteLocalRef(hashMap); env->DeleteLocalRef(tagMap);
env->DeleteLocalRef(arrayListClass);
} }
void JVMTagMap::add(TagLib::String &key, std::string_view value) { void JVMTagMap::add_id(TagLib::String &id, TagLib::String &value) {
jstring jKey = env->NewStringUTF(key.toCString(true)); env->CallVoidMethod(tagMap, tagMapAddIdSingleMethod,
jstring jValue = env->NewStringUTF(value.data()); env->NewStringUTF(id.toCString(true)), env->NewStringUTF(value.toCString(true)));
// check if theres already a value arraylist in the map
jobject existingValue = env->CallObjectMethod(hashMap, hashMapGetMethod,
jKey);
// if there is, add to the value to the existing arraylist
if (existingValue != nullptr) {
env->CallBooleanMethod(existingValue, arrayListAddMethod, jValue);
} else {
// if there isn't, create a new arraylist and add the value to it
jclass arrayListClass = env->FindClass("java/util/ArrayList");
jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod);
env->CallBooleanMethod(arrayList, arrayListAddMethod, jValue);
env->CallObjectMethod(hashMap, hashMapPutMethod, jKey, arrayList);
env->DeleteLocalRef(arrayListClass);
}
} }
void JVMTagMap::add(TagLib::String &key, TagLib::StringList &value) { void JVMTagMap::add_id(TagLib::String &id, TagLib::StringList &value) {
if (value.isEmpty()) { jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod);
// Nothing to add for (auto &item : value) {
return; env->CallBooleanMethod(arrayList, arrayListAddMethod,
env->NewStringUTF(item.toCString(true)));
} }
jstring jKey = env->NewStringUTF(key.toCString(true)); env->CallVoidMethod(tagMap, tagMapAddIdListMethod,
env->NewStringUTF(id.toCString(true)), arrayList);
}
// check if theres already a value arraylist in the map void JVMTagMap::add_custom(TagLib::String &description, TagLib::String &value) {
jobject existingValue = env->CallObjectMethod(hashMap, hashMapGetMethod, env->CallVoidMethod(tagMap, tagMapAddCustomSingleMethod,
jKey); env->NewStringUTF(description.toCString(true)), env->NewStringUTF(value.toCString(true)));
// if there is, add to the value to the existing arraylist }
if (existingValue != nullptr) {
for (auto &val : value) { void JVMTagMap::add_custom(TagLib::String &description, TagLib::StringList &value) {
jstring jValue = env->NewStringUTF(val.toCString(true)); jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod);
env->CallBooleanMethod(existingValue, arrayListAddMethod, jValue); for (auto &item : value) {
} env->CallBooleanMethod(arrayList, arrayListAddMethod,
} else { env->NewStringUTF(item.toCString(true)));
// if there isn't, create a new arraylist and add the value to it
jclass arrayListClass = env->FindClass("java/util/ArrayList");
jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod);
for (auto &val : value) {
jstring jValue = env->NewStringUTF(val.toCString(true));
env->CallBooleanMethod(arrayList, arrayListAddMethod, jValue);
}
env->CallObjectMethod(hashMap, hashMapPutMethod, jKey, arrayList);
env->DeleteLocalRef(arrayListClass);
} }
env->CallVoidMethod(tagMap, tagMapAddCustomListMethod,
env->NewStringUTF(description.toCString(true)), arrayList);
}
void JVMTagMap::add_combined(TagLib::String &id, TagLib::String &description, TagLib::String &value) {
env->CallVoidMethod(tagMap, tagMapAddCombinedSingleMethod,
env->NewStringUTF(id.toCString(true)), env->NewStringUTF(description.toCString(true)),
env->NewStringUTF(value.toCString(true)));
}
void JVMTagMap::add_combined(TagLib::String &id, TagLib::String &description, TagLib::StringList &value) {
jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod);
for (auto &item : value) {
env->CallBooleanMethod(arrayList, arrayListAddMethod,
env->NewStringUTF(item.toCString(true)));
}
env->CallVoidMethod(tagMap, tagMapAddCombinedListMethod,
env->NewStringUTF(id.toCString(true)), env->NewStringUTF(description.toCString(true)),
arrayList);
} }
jobject JVMTagMap::getObject() { jobject JVMTagMap::getObject() {
return hashMap; return env->CallObjectMethod(tagMap, tagMapGetObjectMethod);
} }

View file

@ -32,16 +32,28 @@ public:
JVMTagMap(const JVMTagMap&) = delete; JVMTagMap(const JVMTagMap&) = delete;
JVMTagMap& operator=(const JVMTagMap&) = delete; JVMTagMap& operator=(const JVMTagMap&) = delete;
void add(TagLib::String &key, std::string_view value); void add_id(TagLib::String &id, TagLib::String &value);
void add(TagLib::String &key, TagLib::StringList &value); void add_id(TagLib::String &id, TagLib::StringList &value);
void add_custom(TagLib::String &description, TagLib::String &value);
void add_custom(TagLib::String &description, TagLib::StringList &value);
void add_combined(TagLib::String &id, TagLib::String &description, TagLib::String &value);
void add_combined(TagLib::String &id, TagLib::String &description, TagLib::StringList &value);
jobject getObject(); jobject getObject();
private: private:
JNIEnv *env; JNIEnv *env;
jobject hashMap; jobject tagMap;
jmethodID hashMapGetMethod; jmethodID tagMapAddIdSingleMethod;
jmethodID hashMapPutMethod; jmethodID tagMapAddIdListMethod;
jmethodID tagMapAddCustomSingleMethod;
jmethodID tagMapAddCustomListMethod;
jmethodID tagMapAddCombinedSingleMethod;
jmethodID tagMapAddCombinedListMethod;
jmethodID tagMapGetObjectMethod;
jclass arrayListClass;
jmethodID arrayListInitMethod; jmethodID arrayListInitMethod;
jmethodID arrayListAddMethod; jmethodID arrayListAddMethod;
}; };

View file

@ -20,7 +20,7 @@
#include <string> #include <string>
#include "JVMInputStream.h" #include "JVMInputStream.h"
#include "JVMMetadataBuilder.h" #include "JVMMetadataBuilder.h"
#include "log.h" #include "util.h"
#include "taglib/fileref.h" #include "taglib/fileref.h"
#include "taglib/flacfile.h" #include "taglib/flacfile.h"

View file

@ -16,9 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#ifndef AUXIO_LOG_H #ifndef AUXIO_UTIL_H
#define AUXIO_LOG_H #define AUXIO_UTIL_H
#include <jni.h>
#include <android/log.h> #include <android/log.h>
#define LOG_TAG "taglib_jni" #define LOG_TAG "taglib_jni"
@ -27,4 +28,4 @@
#define LOGD(...) \ #define LOGD(...) \
((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif //AUXIO_LOG_H #endif //AUXIO_UTIL_H

View file

@ -0,0 +1,35 @@
package org.oxycblt.musikr.metadata
import org.oxycblt.musikr.util.correctWhitespace
class NativeTagMap {
private val map = mutableMapOf<String, List<String>>()
fun addID(id: String, value: String) {
addID(id, listOf(value))
}
fun addID(id: String, values: List<String>) {
map[id] = values.mapNotNull { it.correctWhitespace() }
}
fun addCustom(description: String, value: String) {
addCustom(description, listOf(value))
}
fun addCustom(description: String, values: List<String>) {
map[description.uppercase()] = values.mapNotNull { it.correctWhitespace() }
}
fun addCombined(id: String, description: String, value: String) {
addCombined(id, description, listOf(value))
}
fun addCombined(id: String, description: String, values: List<String>) {
map["$id:${description.uppercase()}"] = values.mapNotNull { it.correctWhitespace() }
}
fun getObject(): Map<String, List<String>> {
return map
}
}