diff --git a/musikr/proguard-rules.pro b/musikr/proguard-rules.pro index 3f066b310..8cc776ba8 100644 --- a/musikr/proguard-rules.pro +++ b/musikr/proguard-rules.pro @@ -22,4 +22,5 @@ -keep class org.oxycblt.musikr.metadata.NativeInputStream { *; } -keep class org.oxycblt.musikr.metadata.Metadata { *; } --keep class org.oxycblt.musikr.metadata.Properties { *; } \ No newline at end of file +-keep class org.oxycblt.musikr.metadata.Properties { *; } +-keep class org.oxycblt.musikr.metadata.NativeTagMap { *; } \ No newline at end of file diff --git a/musikr/src/main/cpp/JVMMetadataBuilder.cpp b/musikr/src/main/cpp/JVMMetadataBuilder.cpp index 1db08a59d..afb92a43b 100644 --- a/musikr/src/main/cpp/JVMMetadataBuilder.cpp +++ b/musikr/src/main/cpp/JVMMetadataBuilder.cpp @@ -18,7 +18,7 @@ #include "JVMMetadataBuilder.h" -#include "log.h" +#include "util.h" #include #include @@ -37,18 +37,18 @@ void JVMMetadataBuilder::setId3v2(const TagLib::ID3v2::Tag &tag) { for (auto frame : tag.frameList()) { if (auto txxxFrame = dynamic_cast(frame)) { + TagLib::String id = frame->frameID(); TagLib::StringList frameText = txxxFrame->fieldList(); // Frame text starts with the description then the remaining values auto begin = frameText.begin(); - TagLib::String key = TagLib::String(frame->frameID()) + ":" - + begin->upper(); + TagLib::String description = *begin; frameText.erase(begin); - id3v2.add(key, frameText); + id3v2.add_combined(id, description, frameText); } else if (auto textFrame = dynamic_cast(frame)) { TagLib::String key = frame->frameID(); TagLib::StringList frameText = textFrame->fieldList(); - id3v2.add(key, frameText); + id3v2.add_id(key, frameText); } else { continue; } @@ -59,7 +59,23 @@ void JVMMetadataBuilder::setXiph(const TagLib::Ogg::XiphComment &tag) { for (auto field : tag.fieldListMap()) { auto key = field.first.upper(); auto values = field.second; - xiph.add(key, values); + xiph.add_custom(key, values); + } +} + +template +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(); for (auto item : map) { 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 type = itemValue.type(); - // Only read out the atoms for the reasonable tags we are expecting. - // None of the crazy binary atoms. - if (type == TagLib::MP4::Item::Type::StringList) { - auto value = itemValue.toStringList(); - mp4.add(itemName, value); - continue; - } - - // Assume that taggers will be unhinged and store track numbers - // as ints, uints, or longs. - if (type == TagLib::MP4::Item::Type::Int) { - auto value = std::to_string(itemValue.toInt()); - id3v2.add(itemName, value); - continue; - } - if (type == TagLib::MP4::Item::Type::UInt) { - auto value = std::to_string(itemValue.toUInt()); - id3v2.add(itemName, value); - continue; - } - if (type == TagLib::MP4::Item::Type::LongLong) { - auto value = std::to_string(itemValue.toLongLong()); - id3v2.add(itemName, value); - continue; - } - if (type == TagLib::MP4::Item::Type::IntPair) { - // 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; + std::string serializedValue; + switch (type) { + // Normal expected MP4 items + case TagLib::MP4::Item::Type::StringList: + mp4AddImpl(mp4, itemName, itemValue.toStringList()); + break; + // Weird MP4 items I'm 90% sure I'll encounter. + case TagLib::MP4::Item::Type::Int: + serializedValue = std::to_string(itemValue.toInt()); + break; + case TagLib::MP4::Item::Type::UInt: + serializedValue = std::to_string(itemValue.toUInt()); + break; + case TagLib::MP4::Item::Type::LongLong: + serializedValue = std::to_string(itemValue.toLongLong()); + break; + case TagLib::MP4::Item::Type::IntPair: + // 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. + serializedValue = std::to_string(itemValue.toIntPair().first) + "/" + + std::to_string(itemValue.toIntPair().second); + break; + default: + // Don't care about the other types + continue; } + mp4AddImpl(mp4, itemName, TagLib::String(serializedValue)); } } diff --git a/musikr/src/main/cpp/JVMTagMap.cpp b/musikr/src/main/cpp/JVMTagMap.cpp index 70bfe342d..272b33aec 100644 --- a/musikr/src/main/cpp/JVMTagMap.cpp +++ b/musikr/src/main/cpp/JVMTagMap.cpp @@ -18,76 +18,86 @@ #include "JVMTagMap.h" -JVMTagMap::JVMTagMap(JNIEnv *env) : env(env) { - jclass hashMapClass = env->FindClass("java/util/HashMap"); - jmethodID init = env->GetMethodID(hashMapClass, "", "()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); +#include "util.h" - 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, "", "()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, "", "()V"); arrayListAddMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); - env->DeleteLocalRef(arrayListClass); } JVMTagMap::~JVMTagMap() { - env->DeleteLocalRef(hashMap); + env->DeleteLocalRef(tagMap); + env->DeleteLocalRef(arrayListClass); } -void JVMTagMap::add(TagLib::String &key, std::string_view value) { - jstring jKey = env->NewStringUTF(key.toCString(true)); - jstring jValue = env->NewStringUTF(value.data()); - - // 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_id(TagLib::String &id, TagLib::String &value) { + env->CallVoidMethod(tagMap, tagMapAddIdSingleMethod, + env->NewStringUTF(id.toCString(true)), env->NewStringUTF(value.toCString(true))); } -void JVMTagMap::add(TagLib::String &key, TagLib::StringList &value) { - if (value.isEmpty()) { - // Nothing to add - return; +void JVMTagMap::add_id(TagLib::String &id, TagLib::StringList &value) { + jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod); + for (auto &item : value) { + 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 - jobject existingValue = env->CallObjectMethod(hashMap, hashMapGetMethod, - jKey); - // if there is, add to the value to the existing arraylist - if (existingValue != nullptr) { - for (auto &val : value) { - jstring jValue = env->NewStringUTF(val.toCString(true)); - 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); - 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); +void JVMTagMap::add_custom(TagLib::String &description, TagLib::String &value) { + env->CallVoidMethod(tagMap, tagMapAddCustomSingleMethod, + env->NewStringUTF(description.toCString(true)), env->NewStringUTF(value.toCString(true))); +} + +void JVMTagMap::add_custom(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, 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() { - return hashMap; + return env->CallObjectMethod(tagMap, tagMapGetObjectMethod); } diff --git a/musikr/src/main/cpp/JVMTagMap.h b/musikr/src/main/cpp/JVMTagMap.h index 3090730bb..c75067ef1 100644 --- a/musikr/src/main/cpp/JVMTagMap.h +++ b/musikr/src/main/cpp/JVMTagMap.h @@ -32,16 +32,28 @@ public: JVMTagMap(const JVMTagMap&) = delete; JVMTagMap& operator=(const JVMTagMap&) = delete; - void add(TagLib::String &key, std::string_view value); - void add(TagLib::String &key, TagLib::StringList &value); + void add_id(TagLib::String &id, TagLib::String &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(); private: JNIEnv *env; - jobject hashMap; - jmethodID hashMapGetMethod; - jmethodID hashMapPutMethod; + jobject tagMap; + jmethodID tagMapAddIdSingleMethod; + jmethodID tagMapAddIdListMethod; + jmethodID tagMapAddCustomSingleMethod; + jmethodID tagMapAddCustomListMethod; + jmethodID tagMapAddCombinedSingleMethod; + jmethodID tagMapAddCombinedListMethod; + jmethodID tagMapGetObjectMethod; + jclass arrayListClass; jmethodID arrayListInitMethod; jmethodID arrayListAddMethod; }; diff --git a/musikr/src/main/cpp/taglib_jni.cpp b/musikr/src/main/cpp/taglib_jni.cpp index e8fca822c..c5d72bf6e 100644 --- a/musikr/src/main/cpp/taglib_jni.cpp +++ b/musikr/src/main/cpp/taglib_jni.cpp @@ -20,7 +20,7 @@ #include #include "JVMInputStream.h" #include "JVMMetadataBuilder.h" -#include "log.h" +#include "util.h" #include "taglib/fileref.h" #include "taglib/flacfile.h" diff --git a/musikr/src/main/cpp/log.h b/musikr/src/main/cpp/util.h similarity index 92% rename from musikr/src/main/cpp/log.h rename to musikr/src/main/cpp/util.h index 2be505c6a..1c223a142 100644 --- a/musikr/src/main/cpp/log.h +++ b/musikr/src/main/cpp/util.h @@ -16,9 +16,10 @@ * along with this program. If not, see . */ -#ifndef AUXIO_LOG_H -#define AUXIO_LOG_H +#ifndef AUXIO_UTIL_H +#define AUXIO_UTIL_H +#include #include #define LOG_TAG "taglib_jni" @@ -27,4 +28,4 @@ #define LOGD(...) \ ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) -#endif //AUXIO_LOG_H +#endif //AUXIO_UTIL_H diff --git a/musikr/src/main/java/org/oxycblt/musikr/metadata/NativeTagMap.kt b/musikr/src/main/java/org/oxycblt/musikr/metadata/NativeTagMap.kt new file mode 100644 index 000000000..7740fea51 --- /dev/null +++ b/musikr/src/main/java/org/oxycblt/musikr/metadata/NativeTagMap.kt @@ -0,0 +1,35 @@ +package org.oxycblt.musikr.metadata + +import org.oxycblt.musikr.util.correctWhitespace + +class NativeTagMap { + private val map = mutableMapOf>() + + fun addID(id: String, value: String) { + addID(id, listOf(value)) + } + + fun addID(id: String, values: List) { + map[id] = values.mapNotNull { it.correctWhitespace() } + } + + fun addCustom(description: String, value: String) { + addCustom(description, listOf(value)) + } + + fun addCustom(description: String, values: List) { + 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) { + map["$id:${description.uppercase()}"] = values.mapNotNull { it.correctWhitespace() } + } + + fun getObject(): Map> { + return map + } +} \ No newline at end of file