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:
parent
fddd527975
commit
a4d7b54db7
7 changed files with 173 additions and 109 deletions
1
musikr/proguard-rules.pro
vendored
1
musikr/proguard-rules.pro
vendored
|
@ -23,3 +23,4 @@
|
|||
-keep class org.oxycblt.musikr.metadata.NativeInputStream { *; }
|
||||
-keep class org.oxycblt.musikr.metadata.Metadata { *; }
|
||||
-keep class org.oxycblt.musikr.metadata.Properties { *; }
|
||||
-keep class org.oxycblt.musikr.metadata.NativeTagMap { *; }
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
#include "JVMMetadataBuilder.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <taglib/mp4tag.h>
|
||||
#include <taglib/textidentificationframe.h>
|
||||
|
@ -37,18 +37,18 @@ void JVMMetadataBuilder::setId3v2(const TagLib::ID3v2::Tag &tag) {
|
|||
for (auto frame : tag.frameList()) {
|
||||
if (auto txxxFrame =
|
||||
dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(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<TagLib::ID3v2::TextIdentificationFrame*>(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<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();
|
||||
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) {
|
||||
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.
|
||||
auto value = std::to_string(itemValue.toIntPair().first) + "/"
|
||||
serializedValue = std::to_string(itemValue.toIntPair().first) + "/"
|
||||
+ std::to_string(itemValue.toIntPair().second);
|
||||
id3v2.add(itemName, value);
|
||||
break;
|
||||
default:
|
||||
// Don't care about the other types
|
||||
continue;
|
||||
}
|
||||
mp4AddImpl(mp4, itemName, TagLib::String(serializedValue));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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, "<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);
|
||||
#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, "<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");
|
||||
arrayListAddMethod = env->GetMethodID(arrayListClass, "add",
|
||||
"(Ljava/lang/Object;)Z");
|
||||
env->DeleteLocalRef(arrayListClass);
|
||||
}
|
||||
|
||||
JVMTagMap::~JVMTagMap() {
|
||||
env->DeleteLocalRef(hashMap);
|
||||
}
|
||||
|
||||
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(tagMap);
|
||||
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;
|
||||
}
|
||||
jstring jKey = env->NewStringUTF(key.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) {
|
||||
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");
|
||||
void JVMTagMap::add_id(TagLib::String &id, TagLib::StringList &value) {
|
||||
jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod);
|
||||
for (auto &val : value) {
|
||||
jstring jValue = env->NewStringUTF(val.toCString(true));
|
||||
env->CallBooleanMethod(arrayList, arrayListAddMethod, jValue);
|
||||
for (auto &item : value) {
|
||||
env->CallBooleanMethod(arrayList, arrayListAddMethod,
|
||||
env->NewStringUTF(item.toCString(true)));
|
||||
}
|
||||
env->CallObjectMethod(hashMap, hashMapPutMethod, jKey, arrayList);
|
||||
env->DeleteLocalRef(arrayListClass);
|
||||
env->CallVoidMethod(tagMap, tagMapAddIdListMethod,
|
||||
env->NewStringUTF(id.toCString(true)), arrayList);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include <string>
|
||||
#include "JVMInputStream.h"
|
||||
#include "JVMMetadataBuilder.h"
|
||||
#include "log.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "taglib/fileref.h"
|
||||
#include "taglib/flacfile.h"
|
||||
|
|
|
@ -16,9 +16,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_LOG_H
|
||||
#define AUXIO_LOG_H
|
||||
#ifndef AUXIO_UTIL_H
|
||||
#define AUXIO_UTIL_H
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#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
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue