diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index a03b5d69a..6ec1e2611 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -22,12 +22,10 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.yield import org.oxycblt.auxio.R import org.oxycblt.auxio.detail.list.DiscDivider import org.oxycblt.auxio.detail.list.DiscHeader @@ -302,8 +300,8 @@ constructor( } /** - * Set a new [currentSong] from it's [Music.UID]. [currentSong] will - * be updated to align with the new [Song]. + * Set a new [currentSong] from it's [Music.UID]. [currentSong] will be updated to align with + * the new [Song]. * * @param uid The UID of the [Song] to load. Must be valid. */ @@ -504,9 +502,7 @@ constructor( }) } - private fun refreshAudioInfo(song: Song) { - - } + private fun refreshAudioInfo(song: Song) {} private inline fun refreshDetail( detail: Detail?, diff --git a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt index c558001f1..44e8e6fa2 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.detail import android.content.Context import android.os.Bundle -import android.text.format.Formatter import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import androidx.fragment.app.activityViewModels @@ -29,13 +28,8 @@ import androidx.navigation.fragment.navArgs import dagger.hilt.android.AndroidEntryPoint import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogSongDetailBinding -import org.oxycblt.auxio.detail.list.SongProperty import org.oxycblt.auxio.detail.list.SongPropertyAdapter -import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.music.resolve -import org.oxycblt.auxio.music.resolveNames -import org.oxycblt.auxio.playback.formatDurationMs -import org.oxycblt.auxio.playback.replaygain.formatDb import org.oxycblt.auxio.ui.ViewBindingMaterialDialogFragment import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.concatLocalized @@ -75,59 +69,70 @@ class SongDetailDialog : ViewBindingMaterialDialogFragment T.zipName(context: Context): String { diff --git a/musikr/src/main/cpp/JVMInputStream.cpp b/musikr/src/main/cpp/JVMInputStream.cpp index e8d7d2caa..eaf2e87e9 100644 --- a/musikr/src/main/cpp/JVMInputStream.cpp +++ b/musikr/src/main/cpp/JVMInputStream.cpp @@ -1,105 +1,127 @@ -// -// Created by oxycblt on 12/12/24. -// - +/* + * Copyright (c) 2024 Auxio Project + * JVMInputStream.cpp 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 . + */ + #include "JVMInputStream.h" #include // TODO: Handle stream exceptions -JVMInputStream::JVMInputStream(JNIEnv *env, jobject inputStream) : - env(env), inputStream(inputStream) { - if (!env->IsInstanceOf(inputStream, env->FindClass("org/oxycblt/ktaglib/NativeInputStream"))) { - throw std::runtime_error("oStream is not an instance of TagLibOStream"); - } - jclass inputStreamClass = env->FindClass("org/oxycblt/ktaglib/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/ktaglib/NativeInputStream"))) { + throw std::runtime_error("oStream is not an instance of TagLibOStream"); + } + jclass inputStreamClass = + env->FindClass("org/oxycblt/ktaglib/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"); + 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"); + throw std::runtime_error("Not implemented"); } \ No newline at end of file diff --git a/musikr/src/main/cpp/JVMMetadataBuilder.cpp b/musikr/src/main/cpp/JVMMetadataBuilder.cpp index 7ad3fae94..e4ebbe7a3 100644 --- a/musikr/src/main/cpp/JVMMetadataBuilder.cpp +++ b/musikr/src/main/cpp/JVMMetadataBuilder.cpp @@ -1,134 +1,161 @@ -// -// Created by oxycblt on 12/12/24. -// - +/* + * Copyright (c) 2024 Auxio Project + * JVMMetadataBuilder.cpp 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 . + */ + #include "JVMMetadataBuilder.h" #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; - } - - // 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. + // 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. + } } -void JVMMetadataBuilder::setCover(const TagLib::List covers) { - if (covers.isEmpty()) { - return; +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; } - // 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(); + } + // 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/ktaglib/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/ktaglib/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/ktaglib/Metadata"); - jmethodID metadataInit = env->GetMethodID(metadataClass, "", "(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/oxycblt/ktaglib/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; + jclass metadataClass = env->FindClass("org/oxycblt/ktaglib/Metadata"); + jmethodID metadataInit = + env->GetMethodID(metadataClass, "", + "(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/" + "oxycblt/ktaglib/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 diff --git a/musikr/src/main/cpp/JVMTagMap.cpp b/musikr/src/main/cpp/JVMTagMap.cpp index c8b3723e8..f6357c98d 100644 --- a/musikr/src/main/cpp/JVMTagMap.cpp +++ b/musikr/src/main/cpp/JVMTagMap.cpp @@ -1,70 +1,86 @@ -// -// Created by oxycblt on 12/12/24. -// - +/* + * Copyright (c) 2024 Auxio Project + * JVMTagMap.cpp 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 . + */ + #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; } \ No newline at end of file diff --git a/musikr/src/main/cpp/taglib_jni.cpp b/musikr/src/main/cpp/taglib_jni.cpp index 0fdec649b..5f5e389d3 100644 --- a/musikr/src/main/cpp/taglib_jni.cpp +++ b/musikr/src/main/cpp/taglib_jni.cpp @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2024 Auxio Project + * taglib_jni.cpp 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 . + */ + #include #include @@ -5,52 +23,52 @@ #include "JVMMetadataBuilder.h" #include "taglib/fileref.h" -#include "taglib/mpegfile.h" -#include "taglib/mp4file.h" #include "taglib/flacfile.h" +#include "taglib/mp4file.h" +#include "taglib/mpegfile.h" #include "taglib/opusfile.h" #include "taglib/vorbisfile.h" #include "taglib/wavfile.h" 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 }; +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}; - 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/cache/CacheDatabase.kt b/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt index afd17f288..4cb13d6d2 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt @@ -30,9 +30,9 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverter import androidx.room.TypeConverters -import org.oxycblt.musikr.metadata.Properties import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.fs.query.DeviceFile +import org.oxycblt.musikr.metadata.Properties import org.oxycblt.musikr.pipeline.RawSong import org.oxycblt.musikr.tag.Date import org.oxycblt.musikr.tag.parse.ParsedTags diff --git a/musikr/src/main/java/org/oxycblt/musikr/metadata/AndroidInputStream.kt b/musikr/src/main/java/org/oxycblt/musikr/metadata/AndroidInputStream.kt index a571bd57c..03c6d1c83 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/metadata/AndroidInputStream.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/metadata/AndroidInputStream.kt @@ -1,17 +1,33 @@ +/* + * Copyright (c) 2024 Auxio Project + * AndroidInputStream.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.metadata import android.content.Context import java.io.FileInputStream import java.nio.ByteBuffer -class AndroidInputStream( - context: Context, - fileRef: FileRef -) : NativeInputStream { +class AndroidInputStream(context: Context, fileRef: FileRef) : NativeInputStream { private val fileName = fileRef.fileName - private val fd = requireNotNull(context.contentResolver.openFileDescriptor(fileRef.uri, "r")) { - "Failed to open file descriptor for ${fileRef.fileName}" - } + private val fd = + requireNotNull(context.contentResolver.openFileDescriptor(fileRef.uri, "r")) { + "Failed to open file descriptor for ${fileRef.fileName}" + } private val fis = FileInputStream(fd.fileDescriptor) private val channel = fis.channel @@ -52,4 +68,4 @@ class AndroidInputStream( fis.close() fd.close() } -} \ No newline at end of file +} diff --git a/musikr/src/main/java/org/oxycblt/musikr/metadata/NativeInputStream.kt b/musikr/src/main/java/org/oxycblt/musikr/metadata/NativeInputStream.kt index b88c9b778..66e63c454 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/metadata/NativeInputStream.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/metadata/NativeInputStream.kt @@ -1,10 +1,27 @@ +/* + * Copyright (c) 2024 Auxio Project + * NativeInputStream.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.metadata /** * Java interface for the read-only methods in TagLib's IOStream API. * - * The vast majority of IO shim between Taglib/KTaglib should occur here - * to minimize JNI calls. + * The vast majority of IO shim between Taglib/KTaglib should occur here to minimize JNI calls. */ interface NativeInputStream { fun name(): String @@ -24,4 +41,4 @@ interface NativeInputStream { fun tell(): Long fun length(): Long -} \ No newline at end of file +} diff --git a/musikr/src/main/java/org/oxycblt/musikr/metadata/TagLibJNI.kt b/musikr/src/main/java/org/oxycblt/musikr/metadata/TagLibJNI.kt index 0980fb0cb..45ac2be9b 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/metadata/TagLibJNI.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/metadata/TagLibJNI.kt @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2024 Auxio Project + * TagLibJNI.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.metadata import android.content.Context @@ -11,8 +29,7 @@ object TagLibJNI { /** * Open a file and extract a tag. * - * Note: This method is blocking and should be handled as such if - * calling from a coroutine. + * Note: This method is blocking and should be handled as such if calling from a coroutine. */ fun open(context: Context, ref: FileRef): Metadata? { val inputStream = AndroidInputStream(context, ref) @@ -24,10 +41,7 @@ object TagLibJNI { private external fun openNative(ioStream: AndroidInputStream): Metadata? } -data class FileRef( - val fileName: String, - val uri: Uri -) +data class FileRef(val fileName: String, val uri: Uri) data class Metadata( val id3v2: Map>, 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 97e534159..f7a1f03ac 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt @@ -28,12 +28,12 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge -import org.oxycblt.musikr.metadata.Properties import org.oxycblt.musikr.Storage import org.oxycblt.musikr.cache.CacheResult import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.fs.query.DeviceFile import org.oxycblt.musikr.metadata.MetadataExtractor +import org.oxycblt.musikr.metadata.Properties import org.oxycblt.musikr.tag.parse.ParsedTags import org.oxycblt.musikr.tag.parse.TagParser diff --git a/musikr/src/main/java/org/oxycblt/musikr/playlist/m3u/M3U.kt b/musikr/src/main/java/org/oxycblt/musikr/playlist/m3u/M3U.kt index c11734db8..6dabfbaf3 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/playlist/m3u/M3U.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/playlist/m3u/M3U.kt @@ -77,9 +77,7 @@ interface M3U { } } -private class M3UImpl( - private val volumeManager: VolumeManager -) : M3U { +private class M3UImpl(private val volumeManager: VolumeManager) : M3U { override fun read(stream: InputStream, workingDirectory: Path): ImportedPlaylist? { val volumes = volumeManager.getVolumes() val reader = BufferedReader(InputStreamReader(stream)) diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/parse/TagParser.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/parse/TagParser.kt index d389a31a0..14ecaf570 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/parse/TagParser.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/parse/TagParser.kt @@ -18,8 +18,8 @@ package org.oxycblt.musikr.tag.parse -import org.oxycblt.musikr.metadata.Metadata import org.oxycblt.musikr.fs.query.DeviceFile +import org.oxycblt.musikr.metadata.Metadata interface TagParser { fun parse(file: DeviceFile, metadata: Metadata): ParsedTags