diff --git a/build.gradle b/build.gradle index 464949a9e..afe6c176e 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ spotless { cpp { target "*/src/**/cpp/*.cpp" - clangFormat("18.1.8"). + eclipseCdt() licenseHeaderFile("NOTICE") } } diff --git a/musikr/src/main/cpp/JVMInputStream.cpp b/musikr/src/main/cpp/JVMInputStream.cpp index 584fd0ab8..615944f45 100644 --- a/musikr/src/main/cpp/JVMInputStream.cpp +++ b/musikr/src/main/cpp/JVMInputStream.cpp @@ -22,106 +22,110 @@ // TODO: Handle stream exceptions -JVMInputStream::JVMInputStream(JNIEnv *env, jobject inputStream) - : env(env), inputStream(inputStream) { - if (!env->IsInstanceOf( - inputStream, - env->FindClass("org/oxycblt/musikr/metadata/NativeInputStream"))) { - throw std::runtime_error("oStream is not an instance of TagLibOStream"); - } - jclass inputStreamClass = - env->FindClass("org/oxycblt/musikr/metadata/NativeInputStream"); - inputStreamNameMethod = - env->GetMethodID(inputStreamClass, "name", "()Ljava/lang/String;"); - inputStreamReadBlockMethod = - env->GetMethodID(inputStreamClass, "readBlock", "(J)[B"); - inputStreamIsOpenMethod = env->GetMethodID(inputStreamClass, "isOpen", "()Z"); - inputStreamSeekFromBeginningMethod = - env->GetMethodID(inputStreamClass, "seekFromBeginning", "(J)V"); - inputStreamSeekFromCurrentMethod = - env->GetMethodID(inputStreamClass, "seekFromCurrent", "(J)V"); - inputStreamSeekFromEndMethod = - env->GetMethodID(inputStreamClass, "seekFromEnd", "(J)V"); - inputStreamClearMethod = env->GetMethodID(inputStreamClass, "clear", "()V"); - inputStreamTellMethod = env->GetMethodID(inputStreamClass, "tell", "()J"); - inputStreamLengthMethod = env->GetMethodID(inputStreamClass, "length", "()J"); - env->DeleteLocalRef(inputStreamClass); +JVMInputStream::JVMInputStream(JNIEnv *env, jobject inputStream) : env(env), inputStream( + inputStream) { + if (!env->IsInstanceOf(inputStream, + env->FindClass("org/oxycblt/musikr/metadata/NativeInputStream"))) { + throw std::runtime_error("oStream is not an instance of TagLibOStream"); + } + jclass inputStreamClass = env->FindClass( + "org/oxycblt/musikr/metadata/NativeInputStream"); + inputStreamNameMethod = env->GetMethodID(inputStreamClass, "name", + "()Ljava/lang/String;"); + inputStreamReadBlockMethod = env->GetMethodID(inputStreamClass, "readBlock", + "(J)[B"); + inputStreamIsOpenMethod = env->GetMethodID(inputStreamClass, "isOpen", + "()Z"); + inputStreamSeekFromBeginningMethod = env->GetMethodID(inputStreamClass, + "seekFromBeginning", "(J)V"); + inputStreamSeekFromCurrentMethod = env->GetMethodID(inputStreamClass, + "seekFromCurrent", "(J)V"); + inputStreamSeekFromEndMethod = env->GetMethodID(inputStreamClass, + "seekFromEnd", "(J)V"); + inputStreamClearMethod = env->GetMethodID(inputStreamClass, "clear", "()V"); + inputStreamTellMethod = env->GetMethodID(inputStreamClass, "tell", "()J"); + inputStreamLengthMethod = env->GetMethodID(inputStreamClass, "length", + "()J"); + env->DeleteLocalRef(inputStreamClass); } JVMInputStream::~JVMInputStream() { - // The implicit assumption is that inputStream is managed by the owner, - // so we don't need to delete any references here + // The implicit assumption is that inputStream is managed by the owner, + // so we don't need to delete any references here } TagLib::FileName JVMInputStream::name() const { - auto name = - (jstring)env->CallObjectMethod(inputStream, inputStreamNameMethod); - const char *nameChars = env->GetStringUTFChars(name, nullptr); - auto fileName = TagLib::FileName(nameChars); - env->ReleaseStringUTFChars(name, nameChars); - return fileName; + auto name = (jstring) env->CallObjectMethod(inputStream, + inputStreamNameMethod); + const char *nameChars = env->GetStringUTFChars(name, nullptr); + auto fileName = TagLib::FileName(nameChars); + env->ReleaseStringUTFChars(name, nameChars); + return fileName; } TagLib::ByteVector JVMInputStream::readBlock(size_t length) { - auto data = (jbyteArray)env->CallObjectMethod( - inputStream, inputStreamReadBlockMethod, length); - jsize dataLength = env->GetArrayLength(data); - auto dataBytes = env->GetByteArrayElements(data, nullptr); - TagLib::ByteVector byteVector(reinterpret_cast(dataBytes), - dataLength); - env->ReleaseByteArrayElements(data, dataBytes, JNI_ABORT); - return byteVector; + auto data = (jbyteArray) env->CallObjectMethod(inputStream, + inputStreamReadBlockMethod, length); + jsize dataLength = env->GetArrayLength(data); + auto dataBytes = env->GetByteArrayElements(data, nullptr); + TagLib::ByteVector byteVector(reinterpret_cast(dataBytes), + dataLength); + env->ReleaseByteArrayElements(data, dataBytes, JNI_ABORT); + return byteVector; } 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, - TagLib::offset_t start, size_t replace) { - throw std::runtime_error("Not implemented"); + TagLib::offset_t start, size_t replace) { + throw std::runtime_error("Not implemented"); } 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 { - return env->CallBooleanMethod(inputStream, inputStreamIsOpenMethod); + return env->CallBooleanMethod(inputStream, inputStreamIsOpenMethod); } void JVMInputStream::seek(TagLib::offset_t offset, Position p) { - auto joffset = static_cast(std::llround(offset)); - switch (p) { - case Beginning: - env->CallVoidMethod(inputStream, inputStreamSeekFromBeginningMethod, - joffset); - break; - case Current: - env->CallVoidMethod(inputStream, inputStreamSeekFromCurrentMethod, joffset); - break; - case End: - env->CallVoidMethod(inputStream, inputStreamSeekFromEndMethod, joffset); - break; - } + auto joffset = static_cast(std::llround(offset)); + switch (p) { + case Beginning: + env->CallVoidMethod(inputStream, inputStreamSeekFromBeginningMethod, + joffset); + break; + case Current: + env->CallVoidMethod(inputStream, inputStreamSeekFromCurrentMethod, + joffset); + break; + case End: + env->CallVoidMethod(inputStream, inputStreamSeekFromEndMethod, joffset); + break; + } } void JVMInputStream::clear() { - env->CallVoidMethod(inputStream, inputStreamClearMethod); + env->CallVoidMethod(inputStream, inputStreamClearMethod); } TagLib::offset_t JVMInputStream::tell() const { - jlong jposition = env->CallLongMethod(inputStream, inputStreamTellMethod); - return static_cast(jposition); + jlong jposition = env->CallLongMethod(inputStream, inputStreamTellMethod); + return static_cast(jposition); } TagLib::offset_t JVMInputStream::length() { - jlong jlength = env->CallLongMethod(inputStream, inputStreamLengthMethod); - return static_cast(jlength); + jlong jlength = env->CallLongMethod(inputStream, inputStreamLengthMethod); + return static_cast(jlength); } void JVMInputStream::truncate(TagLib::offset_t length) { - throw std::runtime_error("Not implemented"); -} \ No newline at end of file + throw std::runtime_error("Not implemented"); +} diff --git a/musikr/src/main/cpp/JVMMetadataBuilder.cpp b/musikr/src/main/cpp/JVMMetadataBuilder.cpp index 14e7adfc1..04f34e54b 100644 --- a/musikr/src/main/cpp/JVMMetadataBuilder.cpp +++ b/musikr/src/main/cpp/JVMMetadataBuilder.cpp @@ -21,142 +21,141 @@ #include #include -JVMMetadataBuilder::JVMMetadataBuilder(JNIEnv *env) - : env(env), id3v2(env), xiph(env), mp4(env), cover(), properties(nullptr) {} +JVMMetadataBuilder::JVMMetadataBuilder(JNIEnv *env) : env(env), id3v2(env), xiph( + env), mp4(env), cover(), properties(nullptr) { +} void JVMMetadataBuilder::setMimeType(const std::string_view type) { - this->mimeType = type; + this->mimeType = type; } void JVMMetadataBuilder::setId3v2(const TagLib::ID3v2::Tag &tag) { - for (auto frame : tag.frameList()) { - if (auto txxxFrame = - dynamic_cast(frame)) { - 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(); - frameText.erase(begin); - id3v2.add(key, frameText); - } else if (auto textFrame = - dynamic_cast( - frame)) { - TagLib::String key = frame->frameID(); - TagLib::StringList frameText = textFrame->fieldList(); - id3v2.add(key, frameText); - } else { - continue; - } - } + for (auto frame : tag.frameList()) { + if (auto txxxFrame = + dynamic_cast(frame)) { + 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(); + frameText.erase(begin); + id3v2.add(key, frameText); + } else if (auto textFrame = + dynamic_cast(frame)) { + TagLib::String key = frame->frameID(); + TagLib::StringList frameText = textFrame->fieldList(); + id3v2.add(key, frameText); + } else { + continue; + } + } } 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); - } + for (auto field : tag.fieldListMap()) { + auto key = field.first.upper(); + auto values = field.second; + xiph.add(key, values); + } } void JVMMetadataBuilder::setMp4(const TagLib::MP4::Tag &tag) { - for (auto item : tag.itemMap()) { - auto itemName = TagLib::String(item.first); - auto itemValue = item.second; - auto type = itemValue.type(); + for (auto item : tag.itemMap()) { + auto itemName = TagLib::String(item.first); + auto itemValue = item.second; + auto type = itemValue.type(); - // TODO: Handle internal atoms + // TODO: Handle internal atoms - // 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); - return; - } + // 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); + return; + } - // 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); - return; - } - if (type == TagLib::MP4::Item::Type::UInt) { - auto value = std::to_string(itemValue.toUInt()); - id3v2.add(itemName, value); - return; - } - if (type == TagLib::MP4::Item::Type::LongLong) { - auto value = std::to_string(itemValue.toLongLong()); - id3v2.add(itemName, value); - return; - } - 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); - return; - } - // Nothing else makes sense to handle as far as I can tell. - } + // 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); + return; + } + if (type == TagLib::MP4::Item::Type::UInt) { + auto value = std::to_string(itemValue.toUInt()); + id3v2.add(itemName, value); + return; + } + if (type == TagLib::MP4::Item::Type::LongLong) { + auto value = std::to_string(itemValue.toLongLong()); + id3v2.add(itemName, value); + return; + } + 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); + return; + } + // Nothing else makes sense to handle as far as I can tell. + } } void JVMMetadataBuilder::setCover( - const TagLib::List covers) { - if (covers.isEmpty()) { - return; - } - // Find the cover with a "front cover" type - for (auto cover : covers) { - auto type = cover["pictureType"].toString(); - if (type == "Front Cover") { - this->cover = cover["data"].toByteVector(); - return; - } - } - // No front cover, just pick first. - // TODO: Consider having cascading fallbacks to increasingly less - // relevant covers perhaps - this->cover = covers.front()["data"].toByteVector(); + const TagLib::List covers) { + if (covers.isEmpty()) { + return; + } + // Find the cover with a "front cover" type + for (auto cover : covers) { + auto type = cover["pictureType"].toString(); + if (type == "Front Cover") { + this->cover = cover["data"].toByteVector(); + return; + } + } + // No front cover, just pick first. + // TODO: Consider having cascading fallbacks to increasingly less + // relevant covers perhaps + this->cover = covers.front()["data"].toByteVector(); } void JVMMetadataBuilder::setProperties(TagLib::AudioProperties *properties) { - this->properties = properties; + this->properties = properties; } jobject JVMMetadataBuilder::build() { - jclass propertiesClass = - env->FindClass("org/oxycblt/musikr/metadata/Properties"); - jmethodID propertiesInit = - env->GetMethodID(propertiesClass, "", "(Ljava/lang/String;JII)V"); - jobject propertiesObj = env->NewObject( - propertiesClass, propertiesInit, env->NewStringUTF(mimeType.data()), - (jlong)properties->lengthInMilliseconds(), properties->bitrate(), - properties->sampleRate()); - env->DeleteLocalRef(propertiesClass); + jclass propertiesClass = env->FindClass( + "org/oxycblt/musikr/metadata/Properties"); + jmethodID propertiesInit = env->GetMethodID(propertiesClass, "", + "(Ljava/lang/String;JII)V"); + jobject propertiesObj = env->NewObject(propertiesClass, propertiesInit, + env->NewStringUTF(mimeType.data()), + (jlong) properties->lengthInMilliseconds(), properties->bitrate(), + properties->sampleRate()); + env->DeleteLocalRef(propertiesClass); - jclass metadataClass = env->FindClass("org/oxycblt/musikr/metadata/Metadata"); - jmethodID metadataInit = - env->GetMethodID(metadataClass, "", - "(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/" - "oxycblt/musikr/metadata/Properties;)V"); - jobject id3v2Map = id3v2.getObject(); - jobject xiphMap = xiph.getObject(); - jobject mp4Map = mp4.getObject(); - jbyteArray coverArray = nullptr; - if (cover.has_value()) { - auto coverSize = static_cast(cover->size()); - coverArray = env->NewByteArray(coverSize); - env->SetByteArrayRegion(coverArray, 0, coverSize, - reinterpret_cast(cover->data())); - } - jobject metadataObj = - env->NewObject(metadataClass, metadataInit, id3v2Map, xiphMap, mp4Map, - coverArray, propertiesObj); - env->DeleteLocalRef(metadataClass); - return metadataObj; -} \ No newline at end of file + jclass metadataClass = env->FindClass( + "org/oxycblt/musikr/metadata/Metadata"); + jmethodID metadataInit = env->GetMethodID(metadataClass, "", + "(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/" + "oxycblt/musikr/metadata/Properties;)V"); + jobject id3v2Map = id3v2.getObject(); + jobject xiphMap = xiph.getObject(); + jobject mp4Map = mp4.getObject(); + jbyteArray coverArray = nullptr; + if (cover.has_value()) { + auto coverSize = static_cast(cover->size()); + coverArray = env->NewByteArray(coverSize); + env->SetByteArrayRegion(coverArray, 0, coverSize, + reinterpret_cast(cover->data())); + } + jobject metadataObj = env->NewObject(metadataClass, metadataInit, id3v2Map, + xiphMap, mp4Map, coverArray, propertiesObj); + env->DeleteLocalRef(metadataClass); + return metadataObj; +} diff --git a/musikr/src/main/cpp/JVMTagMap.cpp b/musikr/src/main/cpp/JVMTagMap.cpp index f6357c98d..171a83b72 100644 --- a/musikr/src/main/cpp/JVMTagMap.cpp +++ b/musikr/src/main/cpp/JVMTagMap.cpp @@ -19,68 +19,71 @@ #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); + 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); - jclass arrayListClass = env->FindClass("java/util/ArrayList"); - arrayListInitMethod = env->GetMethodID(arrayListClass, "", "()V"); - arrayListAddMethod = - env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); - env->DeleteLocalRef(arrayListClass); + jclass 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); } +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()); + 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); - } + // 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) { - jstring jKey = env->NewStringUTF(key.toCString(true)); + 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"); - 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); - } + // 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); + } } -jobject JVMTagMap::getObject() { return hashMap; } \ No newline at end of file +jobject JVMTagMap::getObject() { + return hashMap; +} diff --git a/musikr/src/main/cpp/taglib_jni.cpp b/musikr/src/main/cpp/taglib_jni.cpp index 5f5e389d3..797a2029c 100644 --- a/musikr/src/main/cpp/taglib_jni.cpp +++ b/musikr/src/main/cpp/taglib_jni.cpp @@ -32,43 +32,43 @@ extern "C" JNIEXPORT jobject JNICALL Java_org_oxycblt_musikr_metadata_TagLibJNI_openNative(JNIEnv *env, - jobject /* this */, - jobject inputStream) { - JVMInputStream stream{env, inputStream}; - TagLib::FileRef fileRef{&stream}; - if (fileRef.isNull()) { - return nullptr; - } - TagLib::File *file = fileRef.file(); - JVMMetadataBuilder builder{env}; + jobject /* this */, + jobject inputStream) { + JVMInputStream stream {env, inputStream}; + TagLib::FileRef fileRef {&stream}; + if (fileRef.isNull()) { + return nullptr; + } + TagLib::File *file = fileRef.file(); + JVMMetadataBuilder builder {env}; - if (auto *mpegFile = dynamic_cast(file)) { - builder.setMimeType("audio/mpeg"); - builder.setId3v2(*mpegFile->ID3v2Tag()); - } else if (auto *mp4File = dynamic_cast(file)) { - builder.setMimeType("audio/mp4"); - builder.setMp4(*mp4File->tag()); - } else if (auto *flacFile = dynamic_cast(file)) { - builder.setMimeType("audio/flac"); - builder.setId3v2(*flacFile->ID3v2Tag()); - builder.setXiph(*flacFile->xiphComment()); - } else if (auto *opusFile = dynamic_cast(file)) { - builder.setMimeType("audio/opus"); - builder.setXiph(*opusFile->tag()); - } else if (auto *vorbisFile = - dynamic_cast(file)) { - builder.setMimeType("audio/vorbis"); - builder.setXiph(*vorbisFile->tag()); - } else if (auto *wavFile = dynamic_cast(file)) { - builder.setMimeType("audio/wav"); - builder.setId3v2(*wavFile->ID3v2Tag()); - } else { - // While taglib supports other formats, ExoPlayer does not. Ignore them. - return nullptr; - } + if (auto *mpegFile = dynamic_cast(file)) { + builder.setMimeType("audio/mpeg"); + builder.setId3v2(*mpegFile->ID3v2Tag()); + } else if (auto *mp4File = dynamic_cast(file)) { + builder.setMimeType("audio/mp4"); + builder.setMp4(*mp4File->tag()); + } else if (auto *flacFile = dynamic_cast(file)) { + builder.setMimeType("audio/flac"); + builder.setId3v2(*flacFile->ID3v2Tag()); + builder.setXiph(*flacFile->xiphComment()); + } else if (auto *opusFile = dynamic_cast(file)) { + builder.setMimeType("audio/opus"); + builder.setXiph(*opusFile->tag()); + } else if (auto *vorbisFile = + dynamic_cast(file)) { + builder.setMimeType("audio/vorbis"); + builder.setXiph(*vorbisFile->tag()); + } else if (auto *wavFile = dynamic_cast(file)) { + builder.setMimeType("audio/wav"); + builder.setId3v2(*wavFile->ID3v2Tag()); + } else { + // While taglib supports other formats, ExoPlayer does not. Ignore them. + return nullptr; + } - builder.setProperties(file->audioProperties()); - builder.setCover(file->tag()->complexProperties("PICTURE")); + builder.setProperties(file->audioProperties()); + builder.setCover(file->tag()->complexProperties("PICTURE")); - return builder.build(); + return builder.build(); } diff --git a/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt b/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt index ff93fb81e..015e94a54 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/graph/MusicGraph.kt @@ -19,8 +19,6 @@ package org.oxycblt.musikr.graph 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.interpret.PrePlaylist import org.oxycblt.musikr.tag.interpret.PreAlbum @@ -345,4 +343,4 @@ internal class PlaylistVertex(val prePlaylist: PrePlaylist) { val songVertices = mutableListOf() val pointerMap = mutableMapOf() val tag: Any? = null -} \ No newline at end of file +} diff --git a/musikr/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt b/musikr/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt index 501937bfe..41af9a0c6 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt @@ -41,7 +41,11 @@ internal interface 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 = graph.songVertex.mapTo(mutableSetOf()) { vertex -> SongImpl(SongVertexCore(vertex)).also { vertex.tag = it } @@ -105,8 +109,7 @@ private class LibraryFactoryImpl() : LibraryFactory { private class PlaylistVertexCore(vertex: PlaylistVertex) : PlaylistCore { override val prePlaylist = vertex.prePlaylist - override val songs = vertex.songVertices.mapNotNull { vertex -> - vertex?.let { it.tag as Song } - } + override val songs = + vertex.songVertices.mapNotNull { vertex -> vertex?.let { it.tag as Song } } } } diff --git a/musikr/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt b/musikr/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt index abed80959..1f4c14bb2 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt @@ -24,11 +24,7 @@ import org.oxycblt.musikr.MutableLibrary import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Song import org.oxycblt.musikr.Storage -import org.oxycblt.musikr.cover.StoredCovers 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( override val songs: Collection, diff --git a/musikr/src/main/java/org/oxycblt/musikr/model/PlaylistImpl.kt b/musikr/src/main/java/org/oxycblt/musikr/model/PlaylistImpl.kt index 1136c2895..0972fd6a3 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/model/PlaylistImpl.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/model/PlaylistImpl.kt @@ -21,7 +21,6 @@ package org.oxycblt.musikr.model import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Song import org.oxycblt.musikr.cover.Cover -import org.oxycblt.musikr.playlist.PlaylistHandle import org.oxycblt.musikr.playlist.interpret.PrePlaylistInfo import org.oxycblt.musikr.tag.Name diff --git a/musikr/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt b/musikr/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt index 08874ecec..e50711f4b 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -33,8 +32,6 @@ import org.oxycblt.musikr.MutableLibrary import org.oxycblt.musikr.Storage import org.oxycblt.musikr.graph.MusicGraph 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.tag.interpret.TagInterpreter @@ -46,7 +43,8 @@ internal interface EvaluateStep { ): MutableLibrary 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, extractedMusic: Flow ): MutableLibrary { - val filterFlow = extractedMusic.divert { - when (it) { - is ExtractedMusic.Song -> Divert.Right(it.song) - is ExtractedMusic.Playlist -> Divert.Left(it.file) + val filterFlow = + extractedMusic.divert { + when (it) { + is ExtractedMusic.Song -> Divert.Right(it.song) + is ExtractedMusic.Playlist -> Divert.Left(it.file) + } } - } val rawSongs = filterFlow.right val preSongs = rawSongs .map { tagInterpreter.interpret(it, interpretation) } .flowOn(Dispatchers.Main) .buffer(Channel.UNLIMITED) - val prePlaylists = filterFlow.left - .map { playlistInterpreter.interpret(it, interpretation) } - .flowOn(Dispatchers.Main) - .buffer(Channel.UNLIMITED) + val prePlaylists = + filterFlow.left + .map { playlistInterpreter.interpret(it, interpretation) } + .flowOn(Dispatchers.Main) + .buffer(Channel.UNLIMITED) val graphBuilder = MusicGraph.builder() - val graphBuild = merge( - preSongs.onEach { graphBuilder.add(it) }, - prePlaylists.onEach { graphBuilder.add(it) } - ) + val graphBuild = + merge( + preSongs.onEach { graphBuilder.add(it) }, + prePlaylists.onEach { graphBuilder.add(it) }) graphBuild.collect() val graph = graphBuilder.build() return libraryFactory.create(graph, storage, interpretation) diff --git a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt index 01d47a737..85f2acf78 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt @@ -23,7 +23,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.buffer -import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -52,12 +51,13 @@ private class ExtractStepImpl( private val tagParser: TagParser ) : ExtractStep { override fun extract(storage: Storage, nodes: Flow): Flow { - val filterFlow = nodes.divert { - when (it) { - is ExploreNode.Audio -> Divert.Right(it.file) - is ExploreNode.Playlist -> Divert.Left(it.file) + val filterFlow = + nodes.divert { + when (it) { + is ExploreNode.Audio -> Divert.Right(it.file) + is ExploreNode.Playlist -> Divert.Left(it.file) + } } - } val audioNodes = filterFlow.right val playlistNodes = filterFlow.left.map { ExtractedMusic.Playlist(it) } diff --git a/musikr/src/main/java/org/oxycblt/musikr/playlist/db/StoredPlaylists.kt b/musikr/src/main/java/org/oxycblt/musikr/playlist/db/StoredPlaylists.kt index 85d2cf143..e33e23dc8 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/playlist/db/StoredPlaylists.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/playlist/db/StoredPlaylists.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package org.oxycblt.musikr.playlist.db import org.oxycblt.musikr.Music @@ -26,6 +26,7 @@ import org.oxycblt.musikr.playlist.SongPointer interface StoredPlaylists { suspend fun new(name: String, songs: List): PlaylistHandle + suspend fun read(): List companion object { @@ -36,17 +37,8 @@ interface StoredPlaylists { private class StoredPlaylistsImpl(private val playlistDao: PlaylistDao) : StoredPlaylists { override suspend fun new(name: String, songs: List): PlaylistHandle { - val info = - PlaylistInfo( - Music.UID.auxio(Music.UID.Item.PLAYLIST), - name - ) - playlistDao.insertPlaylist( - RawPlaylist( - info, - songs.map { PlaylistSong(it.uid) } - ) - ) + val info = PlaylistInfo(Music.UID.auxio(Music.UID.Item.PLAYLIST), name) + playlistDao.insertPlaylist(RawPlaylist(info, songs.map { PlaylistSong(it.uid) })) return StoredPlaylistHandle(info, playlistDao) } @@ -55,7 +47,6 @@ private class StoredPlaylistsImpl(private val playlistDao: PlaylistDao) : Stored PlaylistFile( it.playlistInfo.name, it.songs.map { song -> SongPointer.UID(song.songUid) }, - StoredPlaylistHandle(it.playlistInfo, playlistDao) - ) + StoredPlaylistHandle(it.playlistInfo, playlistDao)) } } diff --git a/musikr/src/main/java/org/oxycblt/musikr/playlist/interpret/PlaylistInterpreter.kt b/musikr/src/main/java/org/oxycblt/musikr/playlist/interpret/PlaylistInterpreter.kt index be6fba530..917954c0e 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/playlist/interpret/PlaylistInterpreter.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/playlist/interpret/PlaylistInterpreter.kt @@ -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 . + */ + package org.oxycblt.musikr.playlist.interpret import org.oxycblt.musikr.Interpretation import org.oxycblt.musikr.playlist.PlaylistFile import org.oxycblt.musikr.playlist.PlaylistHandle -import org.oxycblt.musikr.playlist.SongPointer internal interface PlaylistInterpreter { fun interpret(file: PlaylistFile, interpretation: Interpretation): PrePlaylist @@ -25,18 +42,12 @@ private data object PlaylistInterpreterImpl : PlaylistInterpreter { name = interpretation.naming.name(file.name, null), rawName = file.name, handle = file.handle, - songPointers = file.songPointers - ) + songPointers = file.songPointers) override fun interpret( name: String, handle: PlaylistHandle, interpretation: Interpretation ): PostPlaylist = - PostPlaylist( - name = interpretation.naming.name(name, null), - rawName = name, - handle = handle - ) - -} \ No newline at end of file + PostPlaylist(name = interpretation.naming.name(name, null), rawName = name, handle = handle) +} diff --git a/musikr/src/main/java/org/oxycblt/musikr/playlist/interpret/PrePlaylist.kt b/musikr/src/main/java/org/oxycblt/musikr/playlist/interpret/PrePlaylist.kt index df6d014fb..7a16d23c4 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/playlist/interpret/PrePlaylist.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/playlist/interpret/PrePlaylist.kt @@ -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 . + */ + package org.oxycblt.musikr.playlist.interpret import org.oxycblt.musikr.playlist.PlaylistHandle @@ -21,4 +39,4 @@ internal data class PostPlaylist( override val name: Name.Known, override val rawName: String?, override val handle: PlaylistHandle, -) : PrePlaylistInfo \ No newline at end of file +) : PrePlaylistInfo diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/Name.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/Name.kt index 8d16ebffc..c20ee72af 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/Name.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/Name.kt @@ -67,12 +67,13 @@ sealed interface Name : Comparable { } /** An individual part of a name string that can be compared intelligently. */ -class Token -internal constructor(internal val collationKey: CollationKey, internal val type: Type) : +class Token internal constructor(internal val collationKey: CollationKey, internal val type: Type) : Comparable { override fun equals(other: Any?) = other is Token && collationKey == other.collationKey && type == other.type + override fun hashCode() = 31 * collationKey.hashCode() + type.hashCode() + val value: String get() = collationKey.sourceString diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt index b9c317891..75cf31920 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt @@ -24,7 +24,6 @@ import org.oxycblt.musikr.Music import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.fs.Format import org.oxycblt.musikr.fs.Path -import org.oxycblt.musikr.playlist.PlaylistHandle import org.oxycblt.musikr.tag.Date import org.oxycblt.musikr.tag.Disc import org.oxycblt.musikr.tag.Name diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt index dfc8fb361..551a31611 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/TagInterpreter.kt @@ -21,7 +21,6 @@ package org.oxycblt.musikr.tag.interpret import org.oxycblt.musikr.Interpretation import org.oxycblt.musikr.fs.Format import org.oxycblt.musikr.pipeline.RawSong -import org.oxycblt.musikr.playlist.PlaylistFile import org.oxycblt.musikr.tag.Disc import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.Placeholder