all: fix formatting

This commit is contained in:
Alexander Capehart 2024-12-16 20:47:17 -05:00
parent 7fab7f7eeb
commit 880967f8be
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
17 changed files with 356 additions and 335 deletions

View file

@ -41,7 +41,7 @@ spotless {
cpp { cpp {
target "*/src/**/cpp/*.cpp" target "*/src/**/cpp/*.cpp"
clangFormat("18.1.8"). eclipseCdt()
licenseHeaderFile("NOTICE") licenseHeaderFile("NOTICE")
} }
} }

View file

@ -22,106 +22,110 @@
// TODO: Handle stream exceptions // TODO: Handle stream exceptions
JVMInputStream::JVMInputStream(JNIEnv *env, jobject inputStream) JVMInputStream::JVMInputStream(JNIEnv *env, jobject inputStream) : env(env), inputStream(
: env(env), inputStream(inputStream) { inputStream) {
if (!env->IsInstanceOf( if (!env->IsInstanceOf(inputStream,
inputStream, env->FindClass("org/oxycblt/musikr/metadata/NativeInputStream"))) {
env->FindClass("org/oxycblt/musikr/metadata/NativeInputStream"))) { throw std::runtime_error("oStream is not an instance of TagLibOStream");
throw std::runtime_error("oStream is not an instance of TagLibOStream"); }
} jclass inputStreamClass = env->FindClass(
jclass inputStreamClass = "org/oxycblt/musikr/metadata/NativeInputStream");
env->FindClass("org/oxycblt/musikr/metadata/NativeInputStream"); inputStreamNameMethod = env->GetMethodID(inputStreamClass, "name",
inputStreamNameMethod = "()Ljava/lang/String;");
env->GetMethodID(inputStreamClass, "name", "()Ljava/lang/String;"); inputStreamReadBlockMethod = env->GetMethodID(inputStreamClass, "readBlock",
inputStreamReadBlockMethod = "(J)[B");
env->GetMethodID(inputStreamClass, "readBlock", "(J)[B"); inputStreamIsOpenMethod = env->GetMethodID(inputStreamClass, "isOpen",
inputStreamIsOpenMethod = env->GetMethodID(inputStreamClass, "isOpen", "()Z"); "()Z");
inputStreamSeekFromBeginningMethod = inputStreamSeekFromBeginningMethod = env->GetMethodID(inputStreamClass,
env->GetMethodID(inputStreamClass, "seekFromBeginning", "(J)V"); "seekFromBeginning", "(J)V");
inputStreamSeekFromCurrentMethod = inputStreamSeekFromCurrentMethod = env->GetMethodID(inputStreamClass,
env->GetMethodID(inputStreamClass, "seekFromCurrent", "(J)V"); "seekFromCurrent", "(J)V");
inputStreamSeekFromEndMethod = inputStreamSeekFromEndMethod = env->GetMethodID(inputStreamClass,
env->GetMethodID(inputStreamClass, "seekFromEnd", "(J)V"); "seekFromEnd", "(J)V");
inputStreamClearMethod = env->GetMethodID(inputStreamClass, "clear", "()V"); inputStreamClearMethod = env->GetMethodID(inputStreamClass, "clear", "()V");
inputStreamTellMethod = env->GetMethodID(inputStreamClass, "tell", "()J"); inputStreamTellMethod = env->GetMethodID(inputStreamClass, "tell", "()J");
inputStreamLengthMethod = env->GetMethodID(inputStreamClass, "length", "()J"); inputStreamLengthMethod = env->GetMethodID(inputStreamClass, "length",
env->DeleteLocalRef(inputStreamClass); "()J");
env->DeleteLocalRef(inputStreamClass);
} }
JVMInputStream::~JVMInputStream() { JVMInputStream::~JVMInputStream() {
// The implicit assumption is that inputStream is managed by the owner, // The implicit assumption is that inputStream is managed by the owner,
// so we don't need to delete any references here // so we don't need to delete any references here
} }
TagLib::FileName JVMInputStream::name() const { TagLib::FileName JVMInputStream::name() const {
auto name = auto name = (jstring) env->CallObjectMethod(inputStream,
(jstring)env->CallObjectMethod(inputStream, inputStreamNameMethod); inputStreamNameMethod);
const char *nameChars = env->GetStringUTFChars(name, nullptr); const char *nameChars = env->GetStringUTFChars(name, nullptr);
auto fileName = TagLib::FileName(nameChars); auto fileName = TagLib::FileName(nameChars);
env->ReleaseStringUTFChars(name, nameChars); env->ReleaseStringUTFChars(name, nameChars);
return fileName; return fileName;
} }
TagLib::ByteVector JVMInputStream::readBlock(size_t length) { TagLib::ByteVector JVMInputStream::readBlock(size_t length) {
auto data = (jbyteArray)env->CallObjectMethod( auto data = (jbyteArray) env->CallObjectMethod(inputStream,
inputStream, inputStreamReadBlockMethod, length); inputStreamReadBlockMethod, length);
jsize dataLength = env->GetArrayLength(data); jsize dataLength = env->GetArrayLength(data);
auto dataBytes = env->GetByteArrayElements(data, nullptr); auto dataBytes = env->GetByteArrayElements(data, nullptr);
TagLib::ByteVector byteVector(reinterpret_cast<const char *>(dataBytes), TagLib::ByteVector byteVector(reinterpret_cast<const char*>(dataBytes),
dataLength); dataLength);
env->ReleaseByteArrayElements(data, dataBytes, JNI_ABORT); env->ReleaseByteArrayElements(data, dataBytes, JNI_ABORT);
return byteVector; return byteVector;
} }
void JVMInputStream::writeBlock(const TagLib::ByteVector &data) { void JVMInputStream::writeBlock(const TagLib::ByteVector &data) {
throw std::runtime_error("Not implemented"); throw std::runtime_error("Not implemented");
} }
void JVMInputStream::insert(const TagLib::ByteVector &data, void JVMInputStream::insert(const TagLib::ByteVector &data,
TagLib::offset_t start, size_t replace) { TagLib::offset_t start, size_t replace) {
throw std::runtime_error("Not implemented"); throw std::runtime_error("Not implemented");
} }
void JVMInputStream::removeBlock(TagLib::offset_t start, size_t length) { void JVMInputStream::removeBlock(TagLib::offset_t start, size_t length) {
throw std::runtime_error("Not implemented"); throw std::runtime_error("Not implemented");
} }
bool JVMInputStream::readOnly() const { return true; } bool JVMInputStream::readOnly() const {
return true;
}
bool JVMInputStream::isOpen() const { bool JVMInputStream::isOpen() const {
return env->CallBooleanMethod(inputStream, inputStreamIsOpenMethod); return env->CallBooleanMethod(inputStream, inputStreamIsOpenMethod);
} }
void JVMInputStream::seek(TagLib::offset_t offset, Position p) { void JVMInputStream::seek(TagLib::offset_t offset, Position p) {
auto joffset = static_cast<jlong>(std::llround(offset)); auto joffset = static_cast<jlong>(std::llround(offset));
switch (p) { switch (p) {
case Beginning: case Beginning:
env->CallVoidMethod(inputStream, inputStreamSeekFromBeginningMethod, env->CallVoidMethod(inputStream, inputStreamSeekFromBeginningMethod,
joffset); joffset);
break; break;
case Current: case Current:
env->CallVoidMethod(inputStream, inputStreamSeekFromCurrentMethod, joffset); env->CallVoidMethod(inputStream, inputStreamSeekFromCurrentMethod,
break; joffset);
case End: break;
env->CallVoidMethod(inputStream, inputStreamSeekFromEndMethod, joffset); case End:
break; env->CallVoidMethod(inputStream, inputStreamSeekFromEndMethod, joffset);
} break;
}
} }
void JVMInputStream::clear() { void JVMInputStream::clear() {
env->CallVoidMethod(inputStream, inputStreamClearMethod); env->CallVoidMethod(inputStream, inputStreamClearMethod);
} }
TagLib::offset_t JVMInputStream::tell() const { TagLib::offset_t JVMInputStream::tell() const {
jlong jposition = env->CallLongMethod(inputStream, inputStreamTellMethod); jlong jposition = env->CallLongMethod(inputStream, inputStreamTellMethod);
return static_cast<TagLib::offset_t>(jposition); return static_cast<TagLib::offset_t>(jposition);
} }
TagLib::offset_t JVMInputStream::length() { TagLib::offset_t JVMInputStream::length() {
jlong jlength = env->CallLongMethod(inputStream, inputStreamLengthMethod); jlong jlength = env->CallLongMethod(inputStream, inputStreamLengthMethod);
return static_cast<TagLib::offset_t>(jlength); return static_cast<TagLib::offset_t>(jlength);
} }
void JVMInputStream::truncate(TagLib::offset_t length) { void JVMInputStream::truncate(TagLib::offset_t length) {
throw std::runtime_error("Not implemented"); throw std::runtime_error("Not implemented");
} }

View file

@ -21,142 +21,141 @@
#include <taglib/mp4tag.h> #include <taglib/mp4tag.h>
#include <taglib/textidentificationframe.h> #include <taglib/textidentificationframe.h>
JVMMetadataBuilder::JVMMetadataBuilder(JNIEnv *env) JVMMetadataBuilder::JVMMetadataBuilder(JNIEnv *env) : env(env), id3v2(env), xiph(
: env(env), id3v2(env), xiph(env), mp4(env), cover(), properties(nullptr) {} env), mp4(env), cover(), properties(nullptr) {
}
void JVMMetadataBuilder::setMimeType(const std::string_view type) { 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(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::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 key = TagLib::String(frame->frameID()) + ":"
TagLib::String(frame->frameID()) + ":" + begin->upper(); + begin->upper();
frameText.erase(begin); frameText.erase(begin);
id3v2.add(key, frameText); id3v2.add(key, frameText);
} else if (auto textFrame = } else if (auto textFrame =
dynamic_cast<TagLib::ID3v2::TextIdentificationFrame *>( dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(frame)) {
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(key, frameText); } else {
} else { continue;
continue; }
} }
}
} }
void JVMMetadataBuilder::setXiph(const TagLib::Ogg::XiphComment &tag) { 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(key, values);
} }
} }
void JVMMetadataBuilder::setMp4(const TagLib::MP4::Tag &tag) { void JVMMetadataBuilder::setMp4(const TagLib::MP4::Tag &tag) {
for (auto item : tag.itemMap()) { for (auto item : tag.itemMap()) {
auto itemName = TagLib::String(item.first); auto itemName = TagLib::String(item.first);
auto itemValue = item.second; auto itemValue = item.second;
auto type = itemValue.type(); auto type = itemValue.type();
// TODO: Handle internal atoms // TODO: Handle internal atoms
// Only read out the atoms for the reasonable tags we are expecting. // Only read out the atoms for the reasonable tags we are expecting.
// None of the crazy binary atoms. // None of the crazy binary atoms.
if (type == TagLib::MP4::Item::Type::StringList) { if (type == TagLib::MP4::Item::Type::StringList) {
auto value = itemValue.toStringList(); auto value = itemValue.toStringList();
mp4.add(itemName, value); mp4.add(itemName, value);
return; return;
} }
// Assume that taggers will be unhinged and store track numbers // Assume that taggers will be unhinged and store track numbers
// as ints, uints, or longs. // as ints, uints, or longs.
if (type == TagLib::MP4::Item::Type::Int) { if (type == TagLib::MP4::Item::Type::Int) {
auto value = std::to_string(itemValue.toInt()); auto value = std::to_string(itemValue.toInt());
id3v2.add(itemName, value); id3v2.add(itemName, value);
return; return;
} }
if (type == TagLib::MP4::Item::Type::UInt) { if (type == TagLib::MP4::Item::Type::UInt) {
auto value = std::to_string(itemValue.toUInt()); auto value = std::to_string(itemValue.toUInt());
id3v2.add(itemName, value); id3v2.add(itemName, value);
return; return;
} }
if (type == TagLib::MP4::Item::Type::LongLong) { if (type == TagLib::MP4::Item::Type::LongLong) {
auto value = std::to_string(itemValue.toLongLong()); auto value = std::to_string(itemValue.toLongLong());
id3v2.add(itemName, value); id3v2.add(itemName, value);
return; return;
} }
if (type == TagLib::MP4::Item::Type::IntPair) { if (type == TagLib::MP4::Item::Type::IntPair) {
// It's inefficient going from the integer representation back into // It's inefficient going from the integer representation back into
// a string, but I fully expect taggers to just write "NN/TT" strings // 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. // anyway, and musikr doesn't have to do as much fiddly variant handling.
auto value = std::to_string(itemValue.toIntPair().first) + "/" + auto value = std::to_string(itemValue.toIntPair().first) + "/"
std::to_string(itemValue.toIntPair().second); + std::to_string(itemValue.toIntPair().second);
id3v2.add(itemName, value); id3v2.add(itemName, value);
return; return;
} }
// Nothing else makes sense to handle as far as I can tell. // Nothing else makes sense to handle as far as I can tell.
} }
} }
void JVMMetadataBuilder::setCover( void JVMMetadataBuilder::setCover(
const TagLib::List<TagLib::VariantMap> covers) { const TagLib::List<TagLib::VariantMap> covers) {
if (covers.isEmpty()) { if (covers.isEmpty()) {
return; return;
} }
// Find the cover with a "front cover" type // Find the cover with a "front cover" type
for (auto cover : covers) { for (auto cover : covers) {
auto type = cover["pictureType"].toString(); auto type = cover["pictureType"].toString();
if (type == "Front Cover") { if (type == "Front Cover") {
this->cover = cover["data"].toByteVector(); this->cover = cover["data"].toByteVector();
return; return;
} }
} }
// No front cover, just pick first. // No front cover, just pick first.
// TODO: Consider having cascading fallbacks to increasingly less // TODO: Consider having cascading fallbacks to increasingly less
// relevant covers perhaps // relevant covers perhaps
this->cover = covers.front()["data"].toByteVector(); this->cover = covers.front()["data"].toByteVector();
} }
void JVMMetadataBuilder::setProperties(TagLib::AudioProperties *properties) { void JVMMetadataBuilder::setProperties(TagLib::AudioProperties *properties) {
this->properties = properties; this->properties = properties;
} }
jobject JVMMetadataBuilder::build() { jobject JVMMetadataBuilder::build() {
jclass propertiesClass = jclass propertiesClass = env->FindClass(
env->FindClass("org/oxycblt/musikr/metadata/Properties"); "org/oxycblt/musikr/metadata/Properties");
jmethodID propertiesInit = jmethodID propertiesInit = env->GetMethodID(propertiesClass, "<init>",
env->GetMethodID(propertiesClass, "<init>", "(Ljava/lang/String;JII)V"); "(Ljava/lang/String;JII)V");
jobject propertiesObj = env->NewObject( jobject propertiesObj = env->NewObject(propertiesClass, propertiesInit,
propertiesClass, propertiesInit, env->NewStringUTF(mimeType.data()), env->NewStringUTF(mimeType.data()),
(jlong)properties->lengthInMilliseconds(), properties->bitrate(), (jlong) properties->lengthInMilliseconds(), properties->bitrate(),
properties->sampleRate()); properties->sampleRate());
env->DeleteLocalRef(propertiesClass); env->DeleteLocalRef(propertiesClass);
jclass metadataClass = env->FindClass("org/oxycblt/musikr/metadata/Metadata"); jclass metadataClass = env->FindClass(
jmethodID metadataInit = "org/oxycblt/musikr/metadata/Metadata");
env->GetMethodID(metadataClass, "<init>", jmethodID metadataInit = env->GetMethodID(metadataClass, "<init>",
"(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/" "(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/"
"oxycblt/musikr/metadata/Properties;)V"); "oxycblt/musikr/metadata/Properties;)V");
jobject id3v2Map = id3v2.getObject(); jobject id3v2Map = id3v2.getObject();
jobject xiphMap = xiph.getObject(); jobject xiphMap = xiph.getObject();
jobject mp4Map = mp4.getObject(); jobject mp4Map = mp4.getObject();
jbyteArray coverArray = nullptr; jbyteArray coverArray = nullptr;
if (cover.has_value()) { if (cover.has_value()) {
auto coverSize = static_cast<jsize>(cover->size()); auto coverSize = static_cast<jsize>(cover->size());
coverArray = env->NewByteArray(coverSize); coverArray = env->NewByteArray(coverSize);
env->SetByteArrayRegion(coverArray, 0, coverSize, env->SetByteArrayRegion(coverArray, 0, coverSize,
reinterpret_cast<const jbyte *>(cover->data())); reinterpret_cast<const jbyte*>(cover->data()));
} }
jobject metadataObj = jobject metadataObj = env->NewObject(metadataClass, metadataInit, id3v2Map,
env->NewObject(metadataClass, metadataInit, id3v2Map, xiphMap, mp4Map, xiphMap, mp4Map, coverArray, propertiesObj);
coverArray, propertiesObj); env->DeleteLocalRef(metadataClass);
env->DeleteLocalRef(metadataClass); return metadataObj;
return metadataObj;
} }

View file

@ -19,68 +19,71 @@
#include "JVMTagMap.h" #include "JVMTagMap.h"
JVMTagMap::JVMTagMap(JNIEnv *env) : env(env) { JVMTagMap::JVMTagMap(JNIEnv *env) : env(env) {
jclass hashMapClass = env->FindClass("java/util/HashMap"); jclass hashMapClass = env->FindClass("java/util/HashMap");
jmethodID init = env->GetMethodID(hashMapClass, "<init>", "()V"); jmethodID init = env->GetMethodID(hashMapClass, "<init>", "()V");
hashMap = env->NewObject(hashMapClass, init); hashMap = env->NewObject(hashMapClass, init);
hashMapGetMethod = env->GetMethodID(hashMapClass, "get", hashMapGetMethod = env->GetMethodID(hashMapClass, "get",
"(Ljava/lang/Object;)Ljava/lang/Object;"); "(Ljava/lang/Object;)Ljava/lang/Object;");
hashMapPutMethod = env->GetMethodID( hashMapPutMethod = env->GetMethodID(hashMapClass, "put",
hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); env->DeleteLocalRef(hashMapClass);
env->DeleteLocalRef(hashMapClass);
jclass arrayListClass = env->FindClass("java/util/ArrayList"); jclass arrayListClass = env->FindClass("java/util/ArrayList");
arrayListInitMethod = env->GetMethodID(arrayListClass, "<init>", "()V"); arrayListInitMethod = env->GetMethodID(arrayListClass, "<init>", "()V");
arrayListAddMethod = arrayListAddMethod = env->GetMethodID(arrayListClass, "add",
env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); "(Ljava/lang/Object;)Z");
env->DeleteLocalRef(arrayListClass); env->DeleteLocalRef(arrayListClass);
} }
JVMTagMap::~JVMTagMap() { env->DeleteLocalRef(hashMap); } JVMTagMap::~JVMTagMap() {
env->DeleteLocalRef(hashMap);
}
void JVMTagMap::add(TagLib::String &key, std::string_view value) { void JVMTagMap::add(TagLib::String &key, std::string_view value) {
jstring jKey = env->NewStringUTF(key.toCString(true)); jstring jKey = env->NewStringUTF(key.toCString(true));
jstring jValue = env->NewStringUTF(value.data()); jstring jValue = env->NewStringUTF(value.data());
// check if theres already a value arraylist in the map // check if theres already a value arraylist in the map
jobject existingValue = jobject existingValue = env->CallObjectMethod(hashMap, hashMapGetMethod,
env->CallObjectMethod(hashMap, hashMapGetMethod, jKey); jKey);
// if there is, add to the value to the existing arraylist // if there is, add to the value to the existing arraylist
if (existingValue != nullptr) { if (existingValue != nullptr) {
env->CallBooleanMethod(existingValue, arrayListAddMethod, jValue); env->CallBooleanMethod(existingValue, arrayListAddMethod, jValue);
} else { } else {
// if there isn't, create a new arraylist and add the value to it // if there isn't, create a new arraylist and add the value to it
jclass arrayListClass = env->FindClass("java/util/ArrayList"); jclass arrayListClass = env->FindClass("java/util/ArrayList");
jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod); jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod);
env->CallBooleanMethod(arrayList, arrayListAddMethod, jValue); env->CallBooleanMethod(arrayList, arrayListAddMethod, jValue);
env->CallObjectMethod(hashMap, hashMapPutMethod, jKey, arrayList); env->CallObjectMethod(hashMap, hashMapPutMethod, jKey, arrayList);
env->DeleteLocalRef(arrayListClass); env->DeleteLocalRef(arrayListClass);
} }
} }
void JVMTagMap::add(TagLib::String &key, TagLib::StringList &value) { void JVMTagMap::add(TagLib::String &key, TagLib::StringList &value) {
jstring jKey = env->NewStringUTF(key.toCString(true)); jstring jKey = env->NewStringUTF(key.toCString(true));
// check if theres already a value arraylist in the map // check if theres already a value arraylist in the map
jobject existingValue = jobject existingValue = env->CallObjectMethod(hashMap, hashMapGetMethod,
env->CallObjectMethod(hashMap, hashMapGetMethod, jKey); jKey);
// if there is, add to the value to the existing arraylist // if there is, add to the value to the existing arraylist
if (existingValue != nullptr) { if (existingValue != nullptr) {
for (auto &val : value) { for (auto &val : value) {
jstring jValue = env->NewStringUTF(val.toCString(true)); jstring jValue = env->NewStringUTF(val.toCString(true));
env->CallBooleanMethod(existingValue, arrayListAddMethod, jValue); env->CallBooleanMethod(existingValue, arrayListAddMethod, jValue);
} }
} else { } else {
// if there isn't, create a new arraylist and add the value to it // if there isn't, create a new arraylist and add the value to it
jclass arrayListClass = env->FindClass("java/util/ArrayList"); jclass arrayListClass = env->FindClass("java/util/ArrayList");
jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod); jobject arrayList = env->NewObject(arrayListClass, arrayListInitMethod);
for (auto &val : value) { for (auto &val : value) {
jstring jValue = env->NewStringUTF(val.toCString(true)); jstring jValue = env->NewStringUTF(val.toCString(true));
env->CallBooleanMethod(arrayList, arrayListAddMethod, jValue); env->CallBooleanMethod(arrayList, arrayListAddMethod, jValue);
} }
env->CallObjectMethod(hashMap, hashMapPutMethod, jKey, arrayList); env->CallObjectMethod(hashMap, hashMapPutMethod, jKey, arrayList);
env->DeleteLocalRef(arrayListClass); env->DeleteLocalRef(arrayListClass);
} }
} }
jobject JVMTagMap::getObject() { return hashMap; } jobject JVMTagMap::getObject() {
return hashMap;
}

View file

@ -32,43 +32,43 @@
extern "C" JNIEXPORT jobject JNICALL extern "C" JNIEXPORT jobject JNICALL
Java_org_oxycblt_musikr_metadata_TagLibJNI_openNative(JNIEnv *env, Java_org_oxycblt_musikr_metadata_TagLibJNI_openNative(JNIEnv *env,
jobject /* this */, jobject /* this */,
jobject inputStream) { jobject inputStream) {
JVMInputStream stream{env, inputStream}; JVMInputStream stream {env, inputStream};
TagLib::FileRef fileRef{&stream}; TagLib::FileRef fileRef {&stream};
if (fileRef.isNull()) { if (fileRef.isNull()) {
return nullptr; return nullptr;
} }
TagLib::File *file = fileRef.file(); TagLib::File *file = fileRef.file();
JVMMetadataBuilder builder{env}; JVMMetadataBuilder builder {env};
if (auto *mpegFile = dynamic_cast<TagLib::MPEG::File *>(file)) { if (auto *mpegFile = dynamic_cast<TagLib::MPEG::File *>(file)) {
builder.setMimeType("audio/mpeg"); builder.setMimeType("audio/mpeg");
builder.setId3v2(*mpegFile->ID3v2Tag()); builder.setId3v2(*mpegFile->ID3v2Tag());
} else if (auto *mp4File = dynamic_cast<TagLib::MP4::File *>(file)) { } else if (auto *mp4File = dynamic_cast<TagLib::MP4::File *>(file)) {
builder.setMimeType("audio/mp4"); builder.setMimeType("audio/mp4");
builder.setMp4(*mp4File->tag()); builder.setMp4(*mp4File->tag());
} else if (auto *flacFile = dynamic_cast<TagLib::FLAC::File *>(file)) { } else if (auto *flacFile = dynamic_cast<TagLib::FLAC::File *>(file)) {
builder.setMimeType("audio/flac"); builder.setMimeType("audio/flac");
builder.setId3v2(*flacFile->ID3v2Tag()); builder.setId3v2(*flacFile->ID3v2Tag());
builder.setXiph(*flacFile->xiphComment()); builder.setXiph(*flacFile->xiphComment());
} 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");
builder.setXiph(*opusFile->tag()); builder.setXiph(*opusFile->tag());
} else if (auto *vorbisFile = } else if (auto *vorbisFile =
dynamic_cast<TagLib::Ogg::Vorbis::File *>(file)) { dynamic_cast<TagLib::Ogg::Vorbis::File *>(file)) {
builder.setMimeType("audio/vorbis"); builder.setMimeType("audio/vorbis");
builder.setXiph(*vorbisFile->tag()); builder.setXiph(*vorbisFile->tag());
} else if (auto *wavFile = dynamic_cast<TagLib::RIFF::WAV::File *>(file)) { } else if (auto *wavFile = dynamic_cast<TagLib::RIFF::WAV::File *>(file)) {
builder.setMimeType("audio/wav"); builder.setMimeType("audio/wav");
builder.setId3v2(*wavFile->ID3v2Tag()); builder.setId3v2(*wavFile->ID3v2Tag());
} else { } else {
// While taglib supports other formats, ExoPlayer does not. Ignore them. // While taglib supports other formats, ExoPlayer does not. Ignore them.
return nullptr; return nullptr;
} }
builder.setProperties(file->audioProperties()); builder.setProperties(file->audioProperties());
builder.setCover(file->tag()->complexProperties("PICTURE")); builder.setCover(file->tag()->complexProperties("PICTURE"));
return builder.build(); return builder.build();
} }

View file

@ -19,8 +19,6 @@
package org.oxycblt.musikr.graph package org.oxycblt.musikr.graph
import org.oxycblt.musikr.Music import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.playlist.SongPointer import org.oxycblt.musikr.playlist.SongPointer
import org.oxycblt.musikr.playlist.interpret.PrePlaylist import org.oxycblt.musikr.playlist.interpret.PrePlaylist
import org.oxycblt.musikr.tag.interpret.PreAlbum import org.oxycblt.musikr.tag.interpret.PreAlbum

View file

@ -41,7 +41,11 @@ internal interface LibraryFactory {
} }
private class LibraryFactoryImpl() : LibraryFactory { private class LibraryFactoryImpl() : LibraryFactory {
override fun create(graph: MusicGraph, storage: Storage, interpretation: Interpretation): MutableLibrary { override fun create(
graph: MusicGraph,
storage: Storage,
interpretation: Interpretation
): MutableLibrary {
val songs = val songs =
graph.songVertex.mapTo(mutableSetOf()) { vertex -> graph.songVertex.mapTo(mutableSetOf()) { vertex ->
SongImpl(SongVertexCore(vertex)).also { vertex.tag = it } SongImpl(SongVertexCore(vertex)).also { vertex.tag = it }
@ -105,8 +109,7 @@ private class LibraryFactoryImpl() : LibraryFactory {
private class PlaylistVertexCore(vertex: PlaylistVertex) : PlaylistCore { private class PlaylistVertexCore(vertex: PlaylistVertex) : PlaylistCore {
override val prePlaylist = vertex.prePlaylist override val prePlaylist = vertex.prePlaylist
override val songs = vertex.songVertices.mapNotNull { vertex -> override val songs =
vertex?.let { it.tag as Song } vertex.songVertices.mapNotNull { vertex -> vertex?.let { it.tag as Song } }
}
} }
} }

View file

@ -24,11 +24,7 @@ import org.oxycblt.musikr.MutableLibrary
import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song import org.oxycblt.musikr.Song
import org.oxycblt.musikr.Storage import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cover.StoredCovers
import org.oxycblt.musikr.fs.Path import org.oxycblt.musikr.fs.Path
import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.playlist.db.PlaylistInfo
import org.oxycblt.musikr.playlist.db.StoredPlaylists
internal data class LibraryImpl( internal data class LibraryImpl(
override val songs: Collection<SongImpl>, override val songs: Collection<SongImpl>,

View file

@ -21,7 +21,6 @@ package org.oxycblt.musikr.model
import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.playlist.interpret.PrePlaylistInfo import org.oxycblt.musikr.playlist.interpret.PrePlaylistInfo
import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.Name

View file

@ -23,7 +23,6 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
@ -33,8 +32,6 @@ import org.oxycblt.musikr.MutableLibrary
import org.oxycblt.musikr.Storage import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.graph.MusicGraph import org.oxycblt.musikr.graph.MusicGraph
import org.oxycblt.musikr.model.LibraryFactory import org.oxycblt.musikr.model.LibraryFactory
import org.oxycblt.musikr.model.PlaylistImpl
import org.oxycblt.musikr.playlist.SongPointer
import org.oxycblt.musikr.playlist.interpret.PlaylistInterpreter import org.oxycblt.musikr.playlist.interpret.PlaylistInterpreter
import org.oxycblt.musikr.tag.interpret.TagInterpreter import org.oxycblt.musikr.tag.interpret.TagInterpreter
@ -46,7 +43,8 @@ internal interface EvaluateStep {
): MutableLibrary ): MutableLibrary
companion object { companion object {
fun new(): EvaluateStep = EvaluateStepImpl(TagInterpreter.new(), PlaylistInterpreter.new(), LibraryFactory.new()) fun new(): EvaluateStep =
EvaluateStepImpl(TagInterpreter.new(), PlaylistInterpreter.new(), LibraryFactory.new())
} }
} }
@ -60,27 +58,29 @@ private class EvaluateStepImpl(
interpretation: Interpretation, interpretation: Interpretation,
extractedMusic: Flow<ExtractedMusic> extractedMusic: Flow<ExtractedMusic>
): MutableLibrary { ): MutableLibrary {
val filterFlow = extractedMusic.divert { val filterFlow =
when (it) { extractedMusic.divert {
is ExtractedMusic.Song -> Divert.Right(it.song) when (it) {
is ExtractedMusic.Playlist -> Divert.Left(it.file) is ExtractedMusic.Song -> Divert.Right(it.song)
is ExtractedMusic.Playlist -> Divert.Left(it.file)
}
} }
}
val rawSongs = filterFlow.right val rawSongs = filterFlow.right
val preSongs = val preSongs =
rawSongs rawSongs
.map { tagInterpreter.interpret(it, interpretation) } .map { tagInterpreter.interpret(it, interpretation) }
.flowOn(Dispatchers.Main) .flowOn(Dispatchers.Main)
.buffer(Channel.UNLIMITED) .buffer(Channel.UNLIMITED)
val prePlaylists = filterFlow.left val prePlaylists =
.map { playlistInterpreter.interpret(it, interpretation) } filterFlow.left
.flowOn(Dispatchers.Main) .map { playlistInterpreter.interpret(it, interpretation) }
.buffer(Channel.UNLIMITED) .flowOn(Dispatchers.Main)
.buffer(Channel.UNLIMITED)
val graphBuilder = MusicGraph.builder() val graphBuilder = MusicGraph.builder()
val graphBuild = merge( val graphBuild =
preSongs.onEach { graphBuilder.add(it) }, merge(
prePlaylists.onEach { graphBuilder.add(it) } preSongs.onEach { graphBuilder.add(it) },
) prePlaylists.onEach { graphBuilder.add(it) })
graphBuild.collect() graphBuild.collect()
val graph = graphBuilder.build() val graph = graphBuilder.build()
return libraryFactory.create(graph, storage, interpretation) return libraryFactory.create(graph, storage, interpretation)

View file

@ -23,7 +23,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
@ -52,12 +51,13 @@ private class ExtractStepImpl(
private val tagParser: TagParser private val tagParser: TagParser
) : ExtractStep { ) : ExtractStep {
override fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic> { override fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic> {
val filterFlow = nodes.divert { val filterFlow =
when (it) { nodes.divert {
is ExploreNode.Audio -> Divert.Right(it.file) when (it) {
is ExploreNode.Playlist -> Divert.Left(it.file) is ExploreNode.Audio -> Divert.Right(it.file)
is ExploreNode.Playlist -> Divert.Left(it.file)
}
} }
}
val audioNodes = filterFlow.right val audioNodes = filterFlow.right
val playlistNodes = filterFlow.left.map { ExtractedMusic.Playlist(it) } val playlistNodes = filterFlow.left.map { ExtractedMusic.Playlist(it) }

View file

@ -26,6 +26,7 @@ import org.oxycblt.musikr.playlist.SongPointer
interface StoredPlaylists { interface StoredPlaylists {
suspend fun new(name: String, songs: List<Song>): PlaylistHandle suspend fun new(name: String, songs: List<Song>): PlaylistHandle
suspend fun read(): List<PlaylistFile> suspend fun read(): List<PlaylistFile>
companion object { companion object {
@ -36,17 +37,8 @@ interface StoredPlaylists {
private class StoredPlaylistsImpl(private val playlistDao: PlaylistDao) : StoredPlaylists { private class StoredPlaylistsImpl(private val playlistDao: PlaylistDao) : StoredPlaylists {
override suspend fun new(name: String, songs: List<Song>): PlaylistHandle { override suspend fun new(name: String, songs: List<Song>): PlaylistHandle {
val info = val info = PlaylistInfo(Music.UID.auxio(Music.UID.Item.PLAYLIST), name)
PlaylistInfo( playlistDao.insertPlaylist(RawPlaylist(info, songs.map { PlaylistSong(it.uid) }))
Music.UID.auxio(Music.UID.Item.PLAYLIST),
name
)
playlistDao.insertPlaylist(
RawPlaylist(
info,
songs.map { PlaylistSong(it.uid) }
)
)
return StoredPlaylistHandle(info, playlistDao) return StoredPlaylistHandle(info, playlistDao)
} }
@ -55,7 +47,6 @@ private class StoredPlaylistsImpl(private val playlistDao: PlaylistDao) : Stored
PlaylistFile( PlaylistFile(
it.playlistInfo.name, it.playlistInfo.name,
it.songs.map { song -> SongPointer.UID(song.songUid) }, it.songs.map { song -> SongPointer.UID(song.songUid) },
StoredPlaylistHandle(it.playlistInfo, playlistDao) StoredPlaylistHandle(it.playlistInfo, playlistDao))
)
} }
} }

View file

@ -1,9 +1,26 @@
/*
* Copyright (c) 2024 Auxio Project
* PlaylistInterpreter.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.playlist.interpret package org.oxycblt.musikr.playlist.interpret
import org.oxycblt.musikr.Interpretation import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.playlist.PlaylistFile import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.playlist.PlaylistHandle import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.playlist.SongPointer
internal interface PlaylistInterpreter { internal interface PlaylistInterpreter {
fun interpret(file: PlaylistFile, interpretation: Interpretation): PrePlaylist fun interpret(file: PlaylistFile, interpretation: Interpretation): PrePlaylist
@ -25,18 +42,12 @@ private data object PlaylistInterpreterImpl : PlaylistInterpreter {
name = interpretation.naming.name(file.name, null), name = interpretation.naming.name(file.name, null),
rawName = file.name, rawName = file.name,
handle = file.handle, handle = file.handle,
songPointers = file.songPointers songPointers = file.songPointers)
)
override fun interpret( override fun interpret(
name: String, name: String,
handle: PlaylistHandle, handle: PlaylistHandle,
interpretation: Interpretation interpretation: Interpretation
): PostPlaylist = ): PostPlaylist =
PostPlaylist( PostPlaylist(name = interpretation.naming.name(name, null), rawName = name, handle = handle)
name = interpretation.naming.name(name, null),
rawName = name,
handle = handle
)
} }

View file

@ -1,3 +1,21 @@
/*
* Copyright (c) 2024 Auxio Project
* PrePlaylist.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.playlist.interpret package org.oxycblt.musikr.playlist.interpret
import org.oxycblt.musikr.playlist.PlaylistHandle import org.oxycblt.musikr.playlist.PlaylistHandle

View file

@ -67,12 +67,13 @@ sealed interface Name : Comparable<Name> {
} }
/** An individual part of a name string that can be compared intelligently. */ /** An individual part of a name string that can be compared intelligently. */
class Token class Token internal constructor(internal val collationKey: CollationKey, internal val type: Type) :
internal constructor(internal val collationKey: CollationKey, internal val type: Type) :
Comparable<Token> { Comparable<Token> {
override fun equals(other: Any?) = override fun equals(other: Any?) =
other is Token && collationKey == other.collationKey && type == other.type other is Token && collationKey == other.collationKey && type == other.type
override fun hashCode() = 31 * collationKey.hashCode() + type.hashCode() override fun hashCode() = 31 * collationKey.hashCode() + type.hashCode()
val value: String val value: String
get() = collationKey.sourceString get() = collationKey.sourceString

View file

@ -24,7 +24,6 @@ import org.oxycblt.musikr.Music
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.fs.Format import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.fs.Path import org.oxycblt.musikr.fs.Path
import org.oxycblt.musikr.playlist.PlaylistHandle
import org.oxycblt.musikr.tag.Date import org.oxycblt.musikr.tag.Date
import org.oxycblt.musikr.tag.Disc import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.Name

View file

@ -21,7 +21,6 @@ package org.oxycblt.musikr.tag.interpret
import org.oxycblt.musikr.Interpretation import org.oxycblt.musikr.Interpretation
import org.oxycblt.musikr.fs.Format import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.pipeline.RawSong import org.oxycblt.musikr.pipeline.RawSong
import org.oxycblt.musikr.playlist.PlaylistFile
import org.oxycblt.musikr.tag.Disc import org.oxycblt.musikr.tag.Disc
import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.Name
import org.oxycblt.musikr.tag.Placeholder import org.oxycblt.musikr.tag.Placeholder