Compare commits
81 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1dc33c1ae1 | ||
![]() |
e22f811c81 | ||
![]() |
47604724a4 | ||
![]() |
894e265a2c | ||
![]() |
774b2a90bb | ||
![]() |
2d7cafbdbf | ||
![]() |
89ab6ee336 | ||
![]() |
2b73be1995 | ||
![]() |
78a06a0430 | ||
![]() |
97bac886d0 | ||
![]() |
b60e3c5880 | ||
![]() |
313365d118 | ||
![]() |
03c596e03c | ||
![]() |
a3d853226f | ||
![]() |
a8dcb5de71 | ||
![]() |
f4a760e3b6 | ||
![]() |
ca169a9354 | ||
![]() |
11ab27df97 | ||
![]() |
c91286826f | ||
![]() |
269c593a7e | ||
![]() |
4655e314b9 | ||
![]() |
f50d9680a9 | ||
![]() |
e68242b8ee | ||
![]() |
57eb30701d | ||
![]() |
8415db30ff | ||
![]() |
de9a60d182 | ||
![]() |
fc5c49081f | ||
![]() |
12caac1f80 | ||
![]() |
da43ebda96 | ||
![]() |
a3f01f152b | ||
![]() |
f939e9d251 | ||
![]() |
d4890af780 | ||
![]() |
60e5614e63 | ||
![]() |
6385928150 | ||
![]() |
1cb2f5026f | ||
![]() |
98bf82ea15 | ||
![]() |
2c03cf8fed | ||
![]() |
608082a49f | ||
![]() |
c4f51b749a | ||
![]() |
65d8959bcf | ||
![]() |
6ef79a9aa5 | ||
![]() |
fa87ea09b8 | ||
![]() |
024cadf530 | ||
![]() |
0cb1b3a309 | ||
![]() |
c8d645c282 | ||
![]() |
b45e41bc3b | ||
![]() |
59f66978ff | ||
![]() |
f7d61cd1dc | ||
![]() |
0f3bed413d | ||
![]() |
f5de03dfee | ||
![]() |
3dfcf0f67a | ||
![]() |
61069bd4fe | ||
![]() |
03d8f70ecd | ||
![]() |
7906fcf5af | ||
![]() |
249915c3be | ||
![]() |
74edd1dbdf | ||
![]() |
3ecdbf289b | ||
![]() |
86b04eaead | ||
![]() |
42dfe4edcc | ||
![]() |
f3f349847a | ||
![]() |
520e52b100 | ||
![]() |
854164a523 | ||
![]() |
7f84349f2e | ||
![]() |
c115e34aac | ||
![]() |
20785300bb | ||
![]() |
5c4d0ab5f6 | ||
![]() |
289582964c | ||
![]() |
3aa39a7065 | ||
![]() |
013f25f46f | ||
![]() |
acee4ddedd | ||
![]() |
cf597cb98e | ||
![]() |
cd102369a0 | ||
![]() |
16fc14a4da | ||
![]() |
005898d776 | ||
![]() |
2ee9556564 | ||
![]() |
729a3c3273 | ||
![]() |
534f06d7e1 | ||
![]() |
ed0abb661c | ||
![]() |
6216e1d591 | ||
![]() |
d6cf484d61 | ||
![]() |
df68768842 |
72 changed files with 3838 additions and 1501 deletions
8
.github/workflows/android.yml
vendored
8
.github/workflows/android.yml
vendored
|
@ -13,6 +13,14 @@ jobs:
|
|||
steps:
|
||||
- name: Install ninja-build
|
||||
run: sudo apt-get install -y ninja-build
|
||||
- name: Setup arm rust target
|
||||
run: rustup target add armv7-linux-androideabi
|
||||
- name: Setup x86 rust target
|
||||
run: rustup target add i686-linux-android
|
||||
- name: Setup arm64 rust target
|
||||
run: rustup target add aarch64-linux-android
|
||||
- name: Setup x86_64 rust target
|
||||
run: rustup target add x86_64-linux-android
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Clone submodules
|
||||
|
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -2,7 +2,7 @@
|
|||
path = media
|
||||
url = https://github.com/OxygenCobalt/media.git
|
||||
|
||||
[submodule "musikr/src/main/cpp/taglib"]
|
||||
path = musikr/src/main/cpp/taglib
|
||||
[submodule "musikr/src/main/jni/taglib"]
|
||||
path = musikr/src/main/jni/taglib
|
||||
url = https://github.com/taglib/taglib.git
|
||||
tag = v2.0.2
|
||||
tag = v2.0.2
|
||||
|
|
|
@ -42,10 +42,6 @@ android {
|
|||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-DEBUG"
|
||||
|
||||
ndk {
|
||||
debugSymbolLevel 'FULL'
|
||||
}
|
||||
}
|
||||
|
||||
release {
|
||||
|
|
|
@ -33,6 +33,7 @@ plugins {
|
|||
id "com.google.devtools.ksp" version '2.0.21-1.0.25' apply false
|
||||
// We use spotless in the root build.gradle to apply to all modules.
|
||||
id "com.diffplug.spotless" version "6.25.0" apply true
|
||||
id "org.mozilla.rust-android-gradle.rust-android" version "0.9.6" apply false
|
||||
}
|
||||
|
||||
spotless {
|
||||
|
|
|
@ -25,3 +25,7 @@ android.nonFinalResIds=true
|
|||
org.gradle.parallel=true
|
||||
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
|
||||
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
|
||||
project.RUST_ANDROID_GRADLE_ARMV7_LINUX_ANDROIDEABI_NDK_PATH=android.ndkDirectory
|
||||
project.RUST_ANDROID_GRADLE_I686_LINUX_ANDROID_NDK_PATH=android.ndkDirectory
|
||||
project.RUST_ANDROID_GRADLE_AARCH64_LINUX_ANDROID_NDK_PATH=android.ndkDirectory
|
||||
project.RUST_ANDROID_GRADLE_X64_64_LINUX_ANDROID_NDK_PATH=android.ndkDirectory
|
||||
|
|
2
media
2
media
|
@ -1 +1 @@
|
|||
Subproject commit 9a0e432c08ee572056f99b9c26d9657753c87fe2
|
||||
Subproject commit 4b3084e1b63185eaeffa7cac9d7015040e0e2aa5
|
4
musikr/.gitignore
vendored
4
musikr/.gitignore
vendored
|
@ -1 +1,3 @@
|
|||
/build
|
||||
/build
|
||||
taglib/build/
|
||||
taglib/pkg/
|
|
@ -7,6 +7,7 @@ plugins {
|
|||
id "com.diffplug.spotless"
|
||||
id "kotlin-parcelize"
|
||||
id "org.jetbrains.dokka"
|
||||
id "org.mozilla.rust-android-gradle.rust-android"
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -20,27 +21,20 @@ android {
|
|||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags ""
|
||||
}
|
||||
|
||||
ndk {
|
||||
stl "c++_static"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// R8 flips out that random classes were stripped for unknown reasons
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/cpp/CMakeLists.txt"
|
||||
version "3.22.1"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
|
@ -57,6 +51,15 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
cargo {
|
||||
module = "./src/main/jni"
|
||||
libname = "metadatajni"
|
||||
targets = ["arm", "arm64", "x86", "x86_64"]
|
||||
prebuiltToolchains = true
|
||||
profile = "release"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Kotlin
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
@ -82,22 +85,12 @@ dependencies {
|
|||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
}
|
||||
|
||||
task assembleTaglib(type: Exec) {
|
||||
def jniDir = "$projectDir/src/main/cpp"
|
||||
def libs = new File("$jniDir/taglib/pkg")
|
||||
if (libs.exists()) {
|
||||
commandLine "true"
|
||||
return
|
||||
tasks.configureEach { task ->
|
||||
if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) {
|
||||
task.dependsOn 'cargoBuild'
|
||||
}
|
||||
commandLine "sh", "-c", "$jniDir/build_taglib.sh $jniDir $android.ndkDirectory"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
preDebugBuild.dependsOn assembleTaglib
|
||||
preReleaseBuild.dependsOn assembleTaglib
|
||||
}
|
||||
|
||||
clean {
|
||||
delete "$projectDir/src/main/cpp/taglib/pkg"
|
||||
delete "$projectDir/src/main/cpp/taglib/build"
|
||||
delete "$projectDir/build/rustJniLibs"
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
# For more information about using CMake with Android Studio, read the
|
||||
# documentation: https://d.android.com/studio/projects/add-native-code.html.
|
||||
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
|
||||
|
||||
# Sets the minimum CMake version required for this project.
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
|
||||
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
|
||||
# Since this is the top level CMakeLists.txt, the project name is also accessible
|
||||
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
|
||||
# build script scope).
|
||||
project("tagJNI") # becomes "libtagJNI.so"
|
||||
|
||||
# Creates and names a library, sets it as either STATIC
|
||||
# or SHARED, and provides the relative paths to its source code.
|
||||
# You can define multiple libraries, and CMake builds them for you.
|
||||
# Gradle automatically packages shared libraries with your APK.
|
||||
#
|
||||
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
|
||||
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
|
||||
# is preferred for the same purpose.
|
||||
#
|
||||
# In order to load a library into your app from Java/Kotlin, you must call
|
||||
# System.loadLibrary() and pass the name of the library defined here;
|
||||
# for GameActivity/NativeActivity derived applications, the same library name must be
|
||||
# used in the AndroidManifest.xml file.
|
||||
set(taglib_location "${CMAKE_CURRENT_SOURCE_DIR}/taglib")
|
||||
set(taglib_pkg "${taglib_location}/pkg/${ANDROID_ABI}")
|
||||
set(taglib_lib "${taglib_pkg}/lib")
|
||||
set(taglib_include "${taglib_pkg}/include")
|
||||
|
||||
set(taglib_file_name libtag.a)
|
||||
set(taglib_file_path ${taglib_lib}/${taglib_file_name})
|
||||
set(taglib_lib_name, "taglib")
|
||||
add_library(
|
||||
"taglib"
|
||||
STATIC
|
||||
IMPORTED)
|
||||
set_target_properties(
|
||||
"taglib" PROPERTIES
|
||||
IMPORTED_LOCATION
|
||||
${taglib_file_path}
|
||||
INTERFACE_INCLUDE_DIRECTORIES
|
||||
${taglib_include})
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||
taglib_jni.cpp
|
||||
JInputStream.cpp
|
||||
JTagMap.cpp
|
||||
JMetadataBuilder.cpp
|
||||
JClassRef.cpp
|
||||
JObjectRef.cpp
|
||||
JStringRef.cpp
|
||||
JByteArrayRef.cpp
|
||||
)
|
||||
target_link_options(${CMAKE_PROJECT_NAME}
|
||||
# @Tolriq found that these flags can reduce the size of the linked
|
||||
# taglib + jni shim shared library. Kudos to them.
|
||||
# https://github.com/taglib/taglib/issues/1212#issuecomment-2326456903
|
||||
# Additionally, enable 16kb page size. I believe taglib can support this fine,
|
||||
# as a cursory glance indicates that it doesn't hardcode any page sizes.
|
||||
PRIVATE "-Wl,--exclude-libs,ALL,-z,max-page-size=16384")
|
||||
|
||||
# Specifies libraries CMake should link to your target library. You
|
||||
# can link libraries from various origins, such as libraries defined in this
|
||||
# build script, prebuilt third-party libraries, or Android system libraries.
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}
|
||||
# List libraries link to the target library
|
||||
PRIVATE android
|
||||
PRIVATE log
|
||||
PRIVATE taglib)
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Auxio Project
|
||||
* JByteArrayRef.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "JByteArrayRef.h"
|
||||
|
||||
JByteArrayRef::JByteArrayRef(JNIEnv *env, TagLib::ByteVector &data) : env(env) {
|
||||
auto size = static_cast<jsize>(data.size());
|
||||
array = env->NewByteArray(size);
|
||||
env->SetByteArrayRegion(array, 0, static_cast<jsize>(size),
|
||||
reinterpret_cast<const jbyte*>(data.data()));
|
||||
}
|
||||
|
||||
JByteArrayRef::JByteArrayRef(JNIEnv *env, jbyteArray array) : env(env), array(
|
||||
array) {
|
||||
}
|
||||
|
||||
JByteArrayRef::~JByteArrayRef() {
|
||||
env->DeleteLocalRef(array);
|
||||
}
|
||||
|
||||
TagLib::ByteVector JByteArrayRef::copy() {
|
||||
jsize length = env->GetArrayLength(array);
|
||||
auto data = env->GetByteArrayElements(array, nullptr);
|
||||
TagLib::ByteVector byteVector(reinterpret_cast<const char*>(data), length);
|
||||
env->ReleaseByteArrayElements(array, data, JNI_ABORT);
|
||||
return byteVector;
|
||||
}
|
||||
|
||||
jbyteArray& JByteArrayRef::operator*() {
|
||||
return array;
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Auxio Project
|
||||
* JByteArrayRef.h 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/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_JBYTEARRAYREF_H
|
||||
#define AUXIO_JBYTEARRAYREF_H
|
||||
|
||||
#include <jni.h>
|
||||
#include <taglib/tbytevector.h>
|
||||
|
||||
class JByteArrayRef {
|
||||
public:
|
||||
JByteArrayRef(JNIEnv *env, TagLib::ByteVector &data);
|
||||
JByteArrayRef(JNIEnv *env, jbyteArray array);
|
||||
|
||||
~JByteArrayRef();
|
||||
|
||||
JByteArrayRef(const JByteArrayRef&) = delete;
|
||||
|
||||
JByteArrayRef& operator=(const JByteArrayRef&) = delete;
|
||||
|
||||
TagLib::ByteVector copy();
|
||||
|
||||
jbyteArray& operator*();
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
jbyteArray array;
|
||||
};
|
||||
|
||||
#endif //AUXIO_JBYTEARRAYREF_H
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Auxio Project
|
||||
* JClassRef.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "JClassRef.h"
|
||||
JClassRef::JClassRef(JNIEnv *env, const char *classpath) : env(env) {
|
||||
clazz = env->FindClass(classpath);
|
||||
}
|
||||
|
||||
JClassRef::~JClassRef() {
|
||||
env->DeleteLocalRef(clazz);
|
||||
}
|
||||
|
||||
jmethodID JClassRef::method(const char *name, const char *signature) {
|
||||
return env->GetMethodID(clazz, name, signature);
|
||||
}
|
||||
|
||||
jclass& JClassRef::operator*() {
|
||||
return clazz;
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Auxio Project
|
||||
* JClassRef.h 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/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_JCLASSREF_H
|
||||
#define AUXIO_JCLASSREF_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
class JClassRef {
|
||||
public:
|
||||
JClassRef(JNIEnv *env, const char *classpath);
|
||||
|
||||
~JClassRef();
|
||||
|
||||
JClassRef(const JClassRef&) = delete;
|
||||
|
||||
JClassRef& operator=(const JClassRef&) = delete;
|
||||
|
||||
// Only exists to work around a broken lint that doesn't
|
||||
// realize that this class is a smart pointer to jclass.
|
||||
jmethodID method(const char *name, const char *signature);
|
||||
|
||||
jclass& operator*();
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
jclass clazz;
|
||||
};
|
||||
|
||||
#endif //AUXIO_JCLASSREF_H
|
|
@ -1,145 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* JInputStream.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "JInputStream.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "JClassRef.h"
|
||||
#include "JByteArrayRef.h"
|
||||
#include "JStringRef.h"
|
||||
|
||||
JInputStream::JInputStream(JNIEnv *env, jobject jInputStream) : env(env), jInputStream(
|
||||
jInputStream) {
|
||||
JClassRef jInputStreamClass = { env,
|
||||
"org/oxycblt/musikr/metadata/NativeInputStream" };
|
||||
if (!env->IsInstanceOf(jInputStream, *jInputStreamClass)) {
|
||||
throw std::runtime_error("Object is not NativeInputStream");
|
||||
}
|
||||
jInputStreamNameMethod = jInputStreamClass.method("name",
|
||||
"()Ljava/lang/String;");
|
||||
jInputStreamReadBlockMethod = jInputStreamClass.method("readBlock",
|
||||
"(Ljava/nio/ByteBuffer;)Z");
|
||||
jInputStreamIsOpenMethod = jInputStreamClass.method("isOpen", "()Z");
|
||||
jInputStreamSeekFromBeginningMethod = jInputStreamClass.method(
|
||||
"seekFromBeginning", "(J)Z");
|
||||
jInputStreamSeekFromCurrentMethod = jInputStreamClass.method(
|
||||
"seekFromCurrent", "(J)Z");
|
||||
jInputStreamSeekFromEndMethod = jInputStreamClass.method("seekFromEnd",
|
||||
"(J)Z");
|
||||
jInputStreamTellMethod = jInputStreamClass.method("tell", "()J");
|
||||
jInputStreamLengthMethod = jInputStreamClass.method("length", "()J");
|
||||
}
|
||||
|
||||
JInputStream::~JInputStream() {
|
||||
// The implicit assumption is that inputStream is managed by the owner,
|
||||
// so we don't need to delete any references here
|
||||
}
|
||||
|
||||
TagLib::FileName JInputStream::name() const {
|
||||
// Not actually used except in FileRef, can safely ignore.
|
||||
JStringRef jName { env, reinterpret_cast<jstring>(env->CallObjectMethod(
|
||||
jInputStream, jInputStreamNameMethod)) };
|
||||
return jName.copy().toCString();
|
||||
}
|
||||
|
||||
TagLib::ByteVector JInputStream::readBlock(size_t length) {
|
||||
// We have to invert the buffer allocation here siits not a perfect system (vykeen instead of korvax0 but i warped all over the hub and i dont think its possible to find a "perfect" purple system like you would withnce the JVM ByteBuffer allocation system
|
||||
// uses a bugged caching mechanism that leaks memory if used in multithreaded contexts.
|
||||
TagLib::ByteVector buf { static_cast<unsigned int>(length), 0 };
|
||||
jobject wrappedByteBuffer = env->NewDirectByteBuffer(buf.data(),
|
||||
buf.size());
|
||||
if (wrappedByteBuffer == nullptr) {
|
||||
throw std::runtime_error("Failed to wrap ByteBuffer");
|
||||
}
|
||||
JObjectRef byteBuffer = { env, wrappedByteBuffer };
|
||||
jboolean result = env->CallBooleanMethod(jInputStream,
|
||||
jInputStreamReadBlockMethod, *byteBuffer);
|
||||
if (!result) {
|
||||
throw std::runtime_error("Failed to read block, see logs");
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
void JInputStream::writeBlock(const TagLib::ByteVector &data) {
|
||||
throw std::runtime_error("Not implemented");
|
||||
}
|
||||
|
||||
void JInputStream::insert(const TagLib::ByteVector &data,
|
||||
TagLib::offset_t start, size_t replace) {
|
||||
throw std::runtime_error("Not implemented");
|
||||
}
|
||||
|
||||
void JInputStream::removeBlock(TagLib::offset_t start, size_t length) {
|
||||
throw std::runtime_error("Not implemented");
|
||||
}
|
||||
|
||||
bool JInputStream::readOnly() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JInputStream::isOpen() const {
|
||||
return env->CallBooleanMethod(jInputStream, jInputStreamIsOpenMethod);
|
||||
}
|
||||
|
||||
void JInputStream::seek(TagLib::offset_t offset, Position p) {
|
||||
auto joffset = static_cast<jlong>(std::llround(offset));
|
||||
jboolean result;
|
||||
switch (p) {
|
||||
case Beginning:
|
||||
result = env->CallBooleanMethod(jInputStream,
|
||||
jInputStreamSeekFromBeginningMethod, joffset);
|
||||
break;
|
||||
case Current:
|
||||
result = env->CallBooleanMethod(jInputStream,
|
||||
jInputStreamSeekFromCurrentMethod, joffset);
|
||||
break;
|
||||
case End:
|
||||
result = env->CallBooleanMethod(jInputStream,
|
||||
jInputStreamSeekFromEndMethod, joffset);
|
||||
break;
|
||||
}
|
||||
if (!result) {
|
||||
throw std::runtime_error("Failed to seek, see logs");
|
||||
}
|
||||
}
|
||||
|
||||
void JInputStream::clear() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
TagLib::offset_t JInputStream::tell() const {
|
||||
jlong jposition = env->CallLongMethod(jInputStream, jInputStreamTellMethod);
|
||||
if (jposition == INT64_MIN) {
|
||||
throw std::runtime_error("Failed to get position, see logs");
|
||||
}
|
||||
return static_cast<TagLib::offset_t>(jposition);
|
||||
}
|
||||
|
||||
TagLib::offset_t JInputStream::length() {
|
||||
jlong jlength = env->CallLongMethod(jInputStream, jInputStreamLengthMethod);
|
||||
if (jlength == INT64_MIN) {
|
||||
throw std::runtime_error("Failed to get length, see logs");
|
||||
}
|
||||
return static_cast<TagLib::offset_t>(jlength);
|
||||
}
|
||||
|
||||
void JInputStream::truncate(TagLib::offset_t length) {
|
||||
throw std::runtime_error("Not implemented");
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* JInputStream.h 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/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_JINPUTSTREAM_H
|
||||
#define AUXIO_JINPUTSTREAM_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "JObjectRef.h"
|
||||
|
||||
#include "taglib/tiostream.h"
|
||||
|
||||
class JInputStream: public TagLib::IOStream {
|
||||
public:
|
||||
JInputStream(JNIEnv *env, jobject jInputStream);
|
||||
|
||||
~JInputStream();
|
||||
|
||||
JInputStream(const JInputStream&) = delete;
|
||||
JInputStream& operator=(const JInputStream&) = delete;
|
||||
|
||||
/*!
|
||||
* Returns the stream name in the local file system encoding.
|
||||
*/
|
||||
TagLib::FileName name() const override;
|
||||
|
||||
/*!
|
||||
* Reads a block of size \a length at the current get pointer.
|
||||
*/
|
||||
TagLib::ByteVector readBlock(size_t length) override;
|
||||
|
||||
/*!
|
||||
* Attempts to write the block \a data at the current get pointer. If the
|
||||
* file is currently only opened read only -- i.e. readOnly() returns \c true --
|
||||
* this attempts to reopen the file in read/write mode.
|
||||
*
|
||||
* \note This should be used instead of using the streaming output operator
|
||||
* for a ByteVector. And even this function is significantly slower than
|
||||
* doing output with a char[].
|
||||
*/
|
||||
void writeBlock(const TagLib::ByteVector &data) override;
|
||||
|
||||
/*!
|
||||
* Insert \a data at position \a start in the file overwriting \a replace
|
||||
* bytes of the original content.
|
||||
*
|
||||
* \note This method is slow since it requires rewriting all of the file
|
||||
* after the insertion point.
|
||||
*/
|
||||
void insert(const TagLib::ByteVector &data, TagLib::offset_t start = 0,
|
||||
size_t replace = 0) override;
|
||||
|
||||
/*!
|
||||
* Removes a block of the file starting a \a start and continuing for
|
||||
* \a length bytes.
|
||||
*
|
||||
* \note This method is slow since it involves rewriting all of the file
|
||||
* after the removed portion.
|
||||
*/
|
||||
void removeBlock(TagLib::offset_t start = 0, size_t length = 0) override;
|
||||
|
||||
/*!
|
||||
* Returns \c true if the file is read only (or if the file can not be opened).
|
||||
*/
|
||||
bool readOnly() const override;
|
||||
|
||||
/*!
|
||||
* Since the file can currently only be opened as an argument to the
|
||||
* constructor (sort-of by design), this returns if that open succeeded.
|
||||
*/
|
||||
bool isOpen() const override;
|
||||
|
||||
/*!
|
||||
* Move the I/O pointer to \a offset in the stream from position \a p. This
|
||||
* defaults to seeking from the beginning of the stream.
|
||||
*
|
||||
* \see Position
|
||||
*/
|
||||
void seek(TagLib::offset_t offset, Position p = Beginning) override;
|
||||
|
||||
/*!
|
||||
* Reset the end-of-stream and error flags on the stream.
|
||||
*/
|
||||
void clear() override;
|
||||
|
||||
/*!
|
||||
* Returns the current offset within the stream.
|
||||
*/
|
||||
TagLib::offset_t tell() const override;
|
||||
|
||||
/*!
|
||||
* Returns the length of the stream.
|
||||
*/
|
||||
TagLib::offset_t length() override;
|
||||
|
||||
/*!
|
||||
* Truncates the stream to a \a length.
|
||||
*/
|
||||
void truncate(TagLib::offset_t length) override;
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
jobject jInputStream;
|
||||
jmethodID jInputStreamNameMethod;
|
||||
jmethodID jInputStreamReadBlockMethod;
|
||||
jmethodID jInputStreamIsOpenMethod;
|
||||
jmethodID jInputStreamSeekFromBeginningMethod;
|
||||
jmethodID jInputStreamSeekFromCurrentMethod;
|
||||
jmethodID jInputStreamSeekFromEndMethod;
|
||||
jmethodID jInputStreamTellMethod;
|
||||
jmethodID jInputStreamLengthMethod;
|
||||
};
|
||||
|
||||
#endif //AUXIO_JINPUTSTREAM_H
|
|
@ -1,217 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* JMetadataBuilder.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "JMetadataBuilder.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#include <taglib/mp4tag.h>
|
||||
#include <taglib/textidentificationframe.h>
|
||||
#include <taglib/attachedpictureframe.h>
|
||||
|
||||
#include <taglib/tpropertymap.h>
|
||||
|
||||
#include "JObjectRef.h"
|
||||
#include "JClassRef.h"
|
||||
#include "JStringRef.h"
|
||||
#include "JByteArrayRef.h"
|
||||
|
||||
JMetadataBuilder::JMetadataBuilder(JNIEnv *env) : env(env), id3v2(env), xiph(
|
||||
env), mp4(env), cover(), properties(nullptr) {
|
||||
}
|
||||
|
||||
void JMetadataBuilder::setMimeType(TagLib::String type) {
|
||||
mimeType = type;
|
||||
}
|
||||
|
||||
void JMetadataBuilder::setId3v1(TagLib::ID3v1::Tag &tag) {
|
||||
id3v2.add_id("TIT2", tag.title());
|
||||
id3v2.add_id("TPE1", tag.artist());
|
||||
id3v2.add_id("TALB", tag.album());
|
||||
id3v2.add_id("TRCK", std::to_string(tag.track()));
|
||||
id3v2.add_id("TYER", std::to_string(tag.year()));
|
||||
const int genreNumber = tag.genreNumber();
|
||||
if (genreNumber != 255) {
|
||||
id3v2.add_id("TCON", std::to_string(genreNumber));
|
||||
}
|
||||
}
|
||||
|
||||
void JMetadataBuilder::setId3v2(TagLib::ID3v2::Tag &tag) {
|
||||
// We want to ideally find the front cover, fall back to the first picture otherwise.
|
||||
std::optional<TagLib::ID3v2::AttachedPictureFrame*> firstPic;
|
||||
std::optional<TagLib::ID3v2::AttachedPictureFrame*> frontCoverPic;
|
||||
for (auto frame : tag.frameList()) {
|
||||
if (auto txxxFrame =
|
||||
dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(frame)) {
|
||||
TagLib::String id = frame->frameID();
|
||||
TagLib::StringList frameText = txxxFrame->fieldList();
|
||||
if (frameText.isEmpty())
|
||||
continue;
|
||||
auto begin = frameText.begin();
|
||||
TagLib::String description = *begin;
|
||||
frameText.erase(begin);
|
||||
id3v2.add_combined(id, description, frameText);
|
||||
} else if (auto textFrame =
|
||||
dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(frame)) {
|
||||
TagLib::String key = frame->frameID();
|
||||
TagLib::StringList frameText = textFrame->fieldList();
|
||||
id3v2.add_id(key, frameText);
|
||||
} else if (auto pictureFrame =
|
||||
dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(frame)) {
|
||||
if (!firstPic) {
|
||||
firstPic = pictureFrame;
|
||||
}
|
||||
if (!frontCoverPic
|
||||
&& pictureFrame->type()
|
||||
== TagLib::ID3v2::AttachedPictureFrame::FrontCover) {
|
||||
frontCoverPic = pictureFrame;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (frontCoverPic) {
|
||||
auto pic = *frontCoverPic;
|
||||
cover = pic->picture();
|
||||
} else if (firstPic) {
|
||||
auto pic = *firstPic;
|
||||
cover = pic->picture();
|
||||
}
|
||||
}
|
||||
|
||||
void JMetadataBuilder::setXiph(TagLib::Ogg::XiphComment &tag) {
|
||||
for (auto field : tag.fieldListMap()) {
|
||||
auto key = field.first.upper();
|
||||
auto values = field.second;
|
||||
xiph.add_custom(key, values);
|
||||
}
|
||||
auto pics = tag.pictureList();
|
||||
setFlacPictures(pics);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void mp4AddImpl(JTagMap &map, TagLib::String &itemName, T itemValue) {
|
||||
if (itemName.startsWith("----")) {
|
||||
// Split this into it's atom name and description
|
||||
auto split = itemName.find(':');
|
||||
auto atomName = itemName.substr(0, split);
|
||||
auto atomDescription = itemName.substr(split + 1);
|
||||
map.add_combined(atomName, atomDescription, itemValue);
|
||||
} else {
|
||||
map.add_id(itemName, itemValue);
|
||||
}
|
||||
}
|
||||
|
||||
void JMetadataBuilder::setMp4(TagLib::MP4::Tag &tag) {
|
||||
auto map = tag.itemMap();
|
||||
std::optional < TagLib::MP4::CoverArt > firstCover;
|
||||
for (auto item : map) {
|
||||
auto itemName = item.first;
|
||||
auto itemValue = item.second;
|
||||
if (itemName == "covr") {
|
||||
// Special cover case.
|
||||
// MP4 has no types, so just prioritize easier to decode covers (PNG, JPEG)
|
||||
auto pics = itemValue.toCoverArtList();
|
||||
for (auto &pic : pics) {
|
||||
auto format = pic.format();
|
||||
if (format == TagLib::MP4::CoverArt::PNG
|
||||
|| format == TagLib::MP4::CoverArt::JPEG) {
|
||||
cover = pic.data();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
cover = pics.front().data();
|
||||
continue;
|
||||
}
|
||||
auto type = itemValue.type();
|
||||
std::string serializedValue;
|
||||
switch (type) {
|
||||
// Normal expected MP4 items
|
||||
case TagLib::MP4::Item::Type::StringList:
|
||||
mp4AddImpl(mp4, itemName, itemValue.toStringList());
|
||||
break;
|
||||
// Weird MP4 items I'm 90% sure I'll encounter.
|
||||
case TagLib::MP4::Item::Type::Int:
|
||||
serializedValue = std::to_string(itemValue.toInt());
|
||||
break;
|
||||
case TagLib::MP4::Item::Type::UInt:
|
||||
serializedValue = std::to_string(itemValue.toUInt());
|
||||
break;
|
||||
case TagLib::MP4::Item::Type::LongLong:
|
||||
serializedValue = std::to_string(itemValue.toLongLong());
|
||||
break;
|
||||
case TagLib::MP4::Item::Type::IntPair:
|
||||
// It's inefficient going from the integer representation back into
|
||||
// a string, but I fully expect taggers to just write "NN/TT" strings
|
||||
// anyway, and musikr doesn't have to do as much fiddly variant handling.
|
||||
serializedValue = std::to_string(itemValue.toIntPair().first) + "/"
|
||||
+ std::to_string(itemValue.toIntPair().second);
|
||||
break;
|
||||
default:
|
||||
// Don't care about the other types
|
||||
continue;
|
||||
}
|
||||
mp4AddImpl(mp4, itemName, TagLib::String(serializedValue));
|
||||
}
|
||||
}
|
||||
|
||||
void JMetadataBuilder::setFlacPictures(
|
||||
TagLib::List<TagLib::FLAC::Picture*> &pics) {
|
||||
// Find the front cover image. If it doesn't exist, fall back to the first image.
|
||||
for (auto pic : pics) {
|
||||
if (pic->type() == TagLib::FLAC::Picture::FrontCover) {
|
||||
cover = pic->data();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!pics.isEmpty()) {
|
||||
cover = pics.front()->data();
|
||||
}
|
||||
}
|
||||
|
||||
void JMetadataBuilder::setProperties(TagLib::AudioProperties *properties) {
|
||||
this->properties = properties;
|
||||
}
|
||||
|
||||
jobject JMetadataBuilder::build() {
|
||||
JClassRef jPropertiesClass { env, "org/oxycblt/musikr/metadata/Properties" };
|
||||
jmethodID jPropertiesInitMethod = jPropertiesClass.method("<init>",
|
||||
"(Ljava/lang/String;JII)V");
|
||||
JStringRef jMimeType { env, this->mimeType };
|
||||
|
||||
JObjectRef jProperties { env, env->NewObject(*jPropertiesClass,
|
||||
jPropertiesInitMethod, *jMimeType,
|
||||
(jlong) properties->lengthInMilliseconds(), properties->bitrate(),
|
||||
properties->sampleRate()) };
|
||||
|
||||
JClassRef jMetadataClass { env, "org/oxycblt/musikr/metadata/Metadata" };
|
||||
jmethodID jMetadataInitMethod = jMetadataClass.method("<init>",
|
||||
"(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/"
|
||||
"oxycblt/musikr/metadata/Properties;)V");
|
||||
auto jId3v2Map = id3v2.getObject();
|
||||
auto jXiphMap = xiph.getObject();
|
||||
auto jMp4Map = mp4.getObject();
|
||||
if (cover.has_value()) {
|
||||
JByteArrayRef jCoverArray { env, cover.value() };
|
||||
jobject result = env->NewObject(*jMetadataClass, jMetadataInitMethod,
|
||||
**jId3v2Map, **jXiphMap, **jMp4Map, *jCoverArray, *jProperties);
|
||||
return result;
|
||||
}
|
||||
return env->NewObject(*jMetadataClass, jMetadataInitMethod, **jId3v2Map,
|
||||
**jXiphMap, **jMp4Map, nullptr, *jProperties);
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* JMetadataBuilder.h 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/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_JMETADATABUILDER_H
|
||||
#define AUXIO_JMETADATABUILDER_H
|
||||
|
||||
#include <jni.h>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
|
||||
#include "taglib/id3v1tag.h"
|
||||
#include "taglib/id3v2tag.h"
|
||||
#include "taglib/xiphcomment.h"
|
||||
#include "taglib/mp4tag.h"
|
||||
#include "taglib/audioproperties.h"
|
||||
|
||||
#include "JTagMap.h"
|
||||
|
||||
class JMetadataBuilder {
|
||||
public:
|
||||
JMetadataBuilder(JNIEnv *env);
|
||||
|
||||
void setMimeType(TagLib::String type);
|
||||
void setId3v1(TagLib::ID3v1::Tag &tag);
|
||||
void setId3v2(TagLib::ID3v2::Tag &tag);
|
||||
void setXiph(TagLib::Ogg::XiphComment &tag);
|
||||
void setMp4(TagLib::MP4::Tag &tag);
|
||||
void setFlacPictures(TagLib::List<TagLib::FLAC::Picture*> &pics);
|
||||
void setProperties(TagLib::AudioProperties *properties);
|
||||
|
||||
jobject build();
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
|
||||
TagLib::String mimeType;
|
||||
|
||||
std::optional<TagLib::ByteVector> cover;
|
||||
TagLib::AudioProperties *properties;
|
||||
|
||||
JTagMap id3v2;
|
||||
JTagMap xiph;
|
||||
JTagMap mp4;
|
||||
};
|
||||
|
||||
#endif //AUXIO_JMETADATABUILDER_H
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Auxio Project
|
||||
* JObjectRef.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "JObjectRef.h"
|
||||
|
||||
JObjectRef::JObjectRef(JNIEnv *env, jobject object) : env(env), object(object) {
|
||||
}
|
||||
|
||||
JObjectRef::~JObjectRef() {
|
||||
env->DeleteLocalRef(object);
|
||||
}
|
||||
|
||||
jobject& JObjectRef::operator*() {
|
||||
return object;
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Auxio Project
|
||||
* JObjectRef.h 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/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_JOBJECTREF_H
|
||||
#define AUXIO_JOBJECTREF_H
|
||||
|
||||
#include <jni.h>
|
||||
#include <memory>
|
||||
#include <taglib/tstring.h>
|
||||
#include "JObjectRef.h"
|
||||
|
||||
class JObjectRef {
|
||||
public:
|
||||
JObjectRef(JNIEnv *env, jobject object);
|
||||
|
||||
~JObjectRef();
|
||||
|
||||
JObjectRef(const JObjectRef&) = delete;
|
||||
|
||||
JObjectRef& operator=(const JObjectRef&) = delete;
|
||||
|
||||
jobject& operator*();
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
jobject object;
|
||||
};
|
||||
|
||||
#endif //AUXIO_JOBJECTREF_H
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Auxio Project
|
||||
* JStringRef.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "JStringRef.h"
|
||||
#include "util.h"
|
||||
|
||||
JStringRef::JStringRef(JNIEnv *env, jstring jString) : env(env), string(jString) {
|
||||
}
|
||||
|
||||
JStringRef::JStringRef(JNIEnv *env, const TagLib::String string) {
|
||||
this->env = env;
|
||||
this->string = env->NewStringUTF(string.toCString(true));
|
||||
}
|
||||
|
||||
JStringRef::~JStringRef() {
|
||||
env->DeleteLocalRef(string);
|
||||
}
|
||||
|
||||
TagLib::String JStringRef::copy() {
|
||||
auto chars = env->GetStringUTFChars(string, nullptr);
|
||||
TagLib::String result = chars;
|
||||
env->ReleaseStringUTFChars(string, chars);
|
||||
return result;
|
||||
}
|
||||
|
||||
jstring& JStringRef::operator*() {
|
||||
return string;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Auxio Project
|
||||
* JStringRef.h 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/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_JSTRINGREF_H
|
||||
#define AUXIO_JSTRINGREF_H
|
||||
|
||||
#include <jni.h>
|
||||
#include <taglib/tstring.h>
|
||||
|
||||
class JStringRef {
|
||||
public:
|
||||
JStringRef(JNIEnv *env, jstring jString);
|
||||
|
||||
JStringRef(JNIEnv *env, TagLib::String string);
|
||||
|
||||
~JStringRef();
|
||||
|
||||
JStringRef(const JStringRef&) = delete;
|
||||
|
||||
JStringRef& operator=(const JStringRef&) = delete;
|
||||
|
||||
TagLib::String copy();
|
||||
|
||||
jstring& operator*();
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
jstring string;
|
||||
};
|
||||
|
||||
#endif //AUXIO_JSTRINGREF_H
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* JTagMap.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "JTagMap.h"
|
||||
|
||||
#include "JStringRef.h"
|
||||
|
||||
JTagMap::JTagMap(JNIEnv *env) : env(env) {
|
||||
auto jTagMapClass = std::make_unique < JClassRef
|
||||
> (env, "org/oxycblt/musikr/metadata/NativeTagMap");
|
||||
auto jTagMapInitMethod = jTagMapClass->method("<init>", "()V");
|
||||
jTagMap = std::move(
|
||||
std::make_unique < JObjectRef
|
||||
> (env, env->NewObject(**jTagMapClass, jTagMapInitMethod)));
|
||||
jTagMapAddIdSingleMethod = jTagMapClass->method("addID",
|
||||
"(Ljava/lang/String;Ljava/lang/String;)V");
|
||||
jTagMapAddIdListMethod = jTagMapClass->method("addID",
|
||||
"(Ljava/lang/String;Ljava/util/List;)V");
|
||||
jTagMapAddCustomSingleMethod = jTagMapClass->method("addCustom",
|
||||
"(Ljava/lang/String;Ljava/lang/String;)V");
|
||||
jTagMapAddCustomListMethod = jTagMapClass->method("addCustom",
|
||||
"(Ljava/lang/String;Ljava/util/List;)V");
|
||||
jTagMapAddCombinedSingleMethod = jTagMapClass->method("addCombined",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
jTagMapAddCombinedListMethod = jTagMapClass->method("addCombined",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V");
|
||||
jTagMapGetObjectMethod = jTagMapClass->method("getObject",
|
||||
"()Ljava/util/Map;");
|
||||
|
||||
jArrayListClass = std::make_unique < JClassRef
|
||||
> (env, "java/util/ArrayList");
|
||||
jArrayListInitMethod = jArrayListClass->method("<init>", "()V");
|
||||
jArrayListAddMethod = jArrayListClass->method("add",
|
||||
"(Ljava/lang/Object;)Z");
|
||||
}
|
||||
|
||||
void JTagMap::add_id(const TagLib::String id, const TagLib::String value) {
|
||||
JStringRef jId { env, id };
|
||||
JStringRef jValue { env, value };
|
||||
env->CallVoidMethod(**jTagMap, jTagMapAddIdSingleMethod, *jId, *jValue);
|
||||
}
|
||||
|
||||
void JTagMap::add_id(const TagLib::String id, const TagLib::StringList values) {
|
||||
JStringRef jId { env, id };
|
||||
JObjectRef jValues { env, env->NewObject(**jArrayListClass,
|
||||
jArrayListInitMethod) };
|
||||
for (auto &value : values) {
|
||||
JStringRef jValue { env, value };
|
||||
env->CallBooleanMethod(*jValues, jArrayListAddMethod, *jValue);
|
||||
}
|
||||
env->CallVoidMethod(**jTagMap, jTagMapAddIdListMethod, *jId, *jValues);
|
||||
}
|
||||
|
||||
void JTagMap::add_custom(const TagLib::String description,
|
||||
const TagLib::String value) {
|
||||
JStringRef jDescription { env, description };
|
||||
JStringRef jValue { env, value };
|
||||
env->CallVoidMethod(**jTagMap, jTagMapAddCustomSingleMethod, *jDescription,
|
||||
*jValue);
|
||||
}
|
||||
|
||||
void JTagMap::add_custom(const TagLib::String description,
|
||||
const TagLib::StringList values) {
|
||||
JStringRef jDescription { env, description };
|
||||
JObjectRef jValues { env, env->NewObject(**jArrayListClass,
|
||||
jArrayListInitMethod) };
|
||||
for (auto &value : values) {
|
||||
JStringRef jValue { env, value };
|
||||
env->CallBooleanMethod(*jValues, jArrayListAddMethod, *jValue);
|
||||
}
|
||||
env->CallVoidMethod(**jTagMap, jTagMapAddCustomListMethod, *jDescription,
|
||||
*jValues);
|
||||
}
|
||||
|
||||
void JTagMap::add_combined(const TagLib::String id,
|
||||
const TagLib::String description, const TagLib::String value) {
|
||||
JStringRef jId { env, id };
|
||||
JStringRef jDescription { env, description };
|
||||
JStringRef jValue { env, value };
|
||||
env->CallVoidMethod(**jTagMap, jTagMapAddCombinedSingleMethod, *jId,
|
||||
*jDescription, *jValue);
|
||||
}
|
||||
|
||||
void JTagMap::add_combined(const TagLib::String id,
|
||||
const TagLib::String description, const TagLib::StringList values) {
|
||||
JStringRef jId { env, id };
|
||||
JStringRef jDescription { env, description };
|
||||
JObjectRef jValues { env, env->NewObject(**jArrayListClass,
|
||||
jArrayListInitMethod) };
|
||||
for (auto &value : values) {
|
||||
JStringRef jValue { env, value };
|
||||
env->CallBooleanMethod(*jValues, jArrayListAddMethod, *jValue);
|
||||
}
|
||||
env->CallVoidMethod(**jTagMap, jTagMapAddCombinedListMethod, *jId,
|
||||
*jDescription, *jValues);
|
||||
}
|
||||
|
||||
std::unique_ptr<JObjectRef> JTagMap::getObject() {
|
||||
return std::move(
|
||||
std::make_unique < JObjectRef
|
||||
> (env, env->CallObjectMethod(**jTagMap,
|
||||
jTagMapGetObjectMethod)));
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* JTagMap.h 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/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_JTAGMAP_H
|
||||
#define AUXIO_JTAGMAP_H
|
||||
|
||||
#include <jni.h>
|
||||
#include <string_view>
|
||||
#include <taglib/tstring.h>
|
||||
#include <taglib/tstringlist.h>
|
||||
|
||||
#include "JObjectRef.h"
|
||||
#include "JClassRef.h"
|
||||
|
||||
class JTagMap {
|
||||
public:
|
||||
JTagMap(JNIEnv *env);
|
||||
|
||||
JTagMap(const JTagMap&) = delete;
|
||||
JTagMap& operator=(const JTagMap&) = delete;
|
||||
|
||||
void add_id(TagLib::String id, TagLib::String value);
|
||||
void add_id(TagLib::String id, TagLib::StringList values);
|
||||
|
||||
void add_custom(TagLib::String description, TagLib::String value);
|
||||
void add_custom(TagLib::String description, TagLib::StringList values);
|
||||
|
||||
void add_combined(TagLib::String id, TagLib::String description,
|
||||
TagLib::String value);
|
||||
void add_combined(TagLib::String id, TagLib::String description,
|
||||
TagLib::StringList values);
|
||||
|
||||
std::unique_ptr<JObjectRef> getObject();
|
||||
|
||||
private:
|
||||
JNIEnv *env;
|
||||
|
||||
std::unique_ptr<JObjectRef> jTagMap;
|
||||
jmethodID jTagMapAddIdSingleMethod;
|
||||
jmethodID jTagMapAddIdListMethod;
|
||||
jmethodID jTagMapAddCustomSingleMethod;
|
||||
jmethodID jTagMapAddCustomListMethod;
|
||||
jmethodID jTagMapAddCombinedSingleMethod;
|
||||
jmethodID jTagMapAddCombinedListMethod;
|
||||
jmethodID jTagMapGetObjectMethod;
|
||||
|
||||
std::unique_ptr<JClassRef> jArrayListClass;
|
||||
jmethodID jArrayListInitMethod;
|
||||
jmethodID jArrayListAddMethod;
|
||||
};
|
||||
|
||||
#endif //AUXIO_JTAGMAP_H
|
|
@ -1,42 +0,0 @@
|
|||
set -e
|
||||
WORKING_DIR=$1
|
||||
echo "Working directory is at $WORKING_DIR"
|
||||
cd "$WORKING_DIR"
|
||||
|
||||
TAGLIB_SRC_DIR=${WORKING_DIR}/taglib
|
||||
TAGLIB_DST_DIR=${WORKING_DIR}/taglib/build
|
||||
TAGLIB_PKG_DIR=${WORKING_DIR}/taglib/pkg
|
||||
NDK_TOOLCHAIN=${WORKING_DIR}/android.toolchain.cmake
|
||||
NDK_PATH=$2
|
||||
echo "Taglib source is at $TAGLIB_SRC_DIR"
|
||||
echo "Taglib build is at $TAGLIB_DST_DIR"
|
||||
echo "Taglib package is at $TAGLIB_PKG_DIR"
|
||||
echo "NDK toolchain is at $NDK_TOOLCHAIN"
|
||||
echo "NDK path is at $NDK_PATH"
|
||||
|
||||
X86_ARCH=x86
|
||||
X86_64_ARCH=x86_64
|
||||
ARMV7_ARCH=armeabi-v7a
|
||||
ARMV8_ARCH=arm64-v8a
|
||||
|
||||
build_for_arch() {
|
||||
local ARCH=$1
|
||||
local DST_DIR=$TAGLIB_DST_DIR/$ARCH
|
||||
local PKG_DIR=$TAGLIB_PKG_DIR/$ARCH
|
||||
|
||||
cd $TAGLIB_SRC_DIR
|
||||
cmake -B $DST_DIR -DANDROID_NDK_PATH=${NDK_PATH} -DCMAKE_TOOLCHAIN_FILE=${NDK_TOOLCHAIN} \
|
||||
-DANDROID_ABI=$ARCH -DBUILD_SHARED_LIBS=OFF -DVISIBILITY_HIDDEN=ON -DBUILD_TESTING=OFF \
|
||||
-DBUILD_EXAMPLES=OFF -DBUILD_BINDINGS=OFF -DWITH_ZLIB=OFF -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_FLAGS="-fPIC"
|
||||
# Try to parallelize the build
|
||||
cmake --build $DST_DIR --config Release -j$(nproc)
|
||||
cd $WORKING_DIR
|
||||
|
||||
cmake --install $DST_DIR --config Release --prefix $PKG_DIR --strip
|
||||
}
|
||||
|
||||
build_for_arch $X86_ARCH
|
||||
build_for_arch $X86_64_ARCH
|
||||
build_for_arch $ARMV7_ARCH
|
||||
build_for_arch $ARMV8_ARCH
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 648f5e588209464702e4955de614e391d3768ec8
|
|
@ -1,200 +0,0 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
#include "JInputStream.h"
|
||||
#include "JMetadataBuilder.h"
|
||||
#include "util.h"
|
||||
|
||||
#include "taglib/fileref.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"
|
||||
|
||||
bool parseMpeg(const char *name, TagLib::File *file,
|
||||
JMetadataBuilder &jBuilder) {
|
||||
auto *mpegFile = dynamic_cast<TagLib::MPEG::File*>(file);
|
||||
if (mpegFile == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto id3v1Tag = mpegFile->ID3v1Tag();
|
||||
if (id3v1Tag != nullptr) {
|
||||
try {
|
||||
jBuilder.setId3v1(*id3v1Tag);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse ID3v1 tag in %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
auto id3v2Tag = mpegFile->ID3v2Tag();
|
||||
if (id3v2Tag != nullptr) {
|
||||
try {
|
||||
jBuilder.setId3v2(*id3v2Tag);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse ID3v2 tag in %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseMp4(const char *name, TagLib::File *file,
|
||||
JMetadataBuilder &jBuilder) {
|
||||
auto *mp4File = dynamic_cast<TagLib::MP4::File*>(file);
|
||||
if (mp4File == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto tag = mp4File->tag();
|
||||
if (tag != nullptr) {
|
||||
try {
|
||||
jBuilder.setMp4(*tag);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse MP4 tag in %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseFlac(const char *name, TagLib::File *file,
|
||||
JMetadataBuilder &jBuilder) {
|
||||
auto *flacFile = dynamic_cast<TagLib::FLAC::File*>(file);
|
||||
if (flacFile == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto id3v1Tag = flacFile->ID3v1Tag();
|
||||
if (id3v1Tag != nullptr) {
|
||||
try {
|
||||
jBuilder.setId3v1(*id3v1Tag);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse ID3v1 tag in %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
auto id3v2Tag = flacFile->ID3v2Tag();
|
||||
if (id3v2Tag != nullptr) {
|
||||
try {
|
||||
jBuilder.setId3v2(*id3v2Tag);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse ID3v2 tag in %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
auto xiphComment = flacFile->xiphComment();
|
||||
if (xiphComment != nullptr) {
|
||||
try {
|
||||
jBuilder.setXiph(*xiphComment);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse Xiph comment in %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
auto pics = flacFile->pictureList();
|
||||
jBuilder.setFlacPictures(pics);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseOpus(const char *name, TagLib::File *file,
|
||||
JMetadataBuilder &jBuilder) {
|
||||
auto *opusFile = dynamic_cast<TagLib::Ogg::Opus::File*>(file);
|
||||
if (opusFile == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto tag = opusFile->tag();
|
||||
if (tag != nullptr) {
|
||||
try {
|
||||
jBuilder.setXiph(*tag);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse Xiph comment in %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseVorbis(const char *name, TagLib::File *file,
|
||||
JMetadataBuilder &jBuilder) {
|
||||
auto *vorbisFile = dynamic_cast<TagLib::Ogg::Vorbis::File*>(file);
|
||||
if (vorbisFile == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto tag = vorbisFile->tag();
|
||||
if (tag != nullptr) {
|
||||
try {
|
||||
jBuilder.setXiph(*tag);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse Xiph comment %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseWav(const char *name, TagLib::File *file,
|
||||
JMetadataBuilder &jBuilder) {
|
||||
auto *wavFile = dynamic_cast<TagLib::RIFF::WAV::File*>(file);
|
||||
if (wavFile == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto tag = wavFile->ID3v2Tag();
|
||||
if (tag != nullptr) {
|
||||
try {
|
||||
jBuilder.setId3v2(*tag);
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse ID3v2 tag in %s: %s", name, e.what());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
Java_org_oxycblt_musikr_metadata_TagLibJNI_openNative(JNIEnv *env,
|
||||
jobject /* this */,
|
||||
jobject inputStream) {
|
||||
const char *name = nullptr;
|
||||
try {
|
||||
JInputStream jStream {env, inputStream};
|
||||
name = jStream.name();
|
||||
TagLib::FileRef fileRef {&jStream};
|
||||
if (fileRef.isNull()) {
|
||||
throw std::runtime_error("Invalid file");
|
||||
}
|
||||
TagLib::File *file = fileRef.file();
|
||||
JMetadataBuilder jBuilder {env};
|
||||
jBuilder.setProperties(file->audioProperties());
|
||||
|
||||
// TODO: Make some type of composable logger so I don't
|
||||
// have to shoehorn this into the native code.
|
||||
if (parseMpeg(name, file, jBuilder)) {
|
||||
jBuilder.setMimeType("audio/mpeg");
|
||||
} else if (parseMp4(name, file, jBuilder)) {
|
||||
jBuilder.setMimeType("audio/mp4");
|
||||
} else if (parseFlac(name, file, jBuilder)) {
|
||||
jBuilder.setMimeType("audio/flac");
|
||||
} else if (parseOpus(name, file, jBuilder)) {
|
||||
jBuilder.setMimeType("audio/opus");
|
||||
} else if (parseVorbis(name, file, jBuilder)) {
|
||||
jBuilder.setMimeType("audio/vorbis");
|
||||
} else if (parseWav(name, file, jBuilder)) {
|
||||
jBuilder.setMimeType("audio/wav");
|
||||
} else {
|
||||
LOGE("File format in %s is not supported", name);
|
||||
return nullptr;
|
||||
}
|
||||
return jBuilder.build();
|
||||
} catch (std::exception &e) {
|
||||
LOGE("Unable to parse metadata in %s: %s", name != nullptr ? name : "unknown file", e.what());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
|
@ -24,34 +24,7 @@ internal data class Metadata(
|
|||
val mp4: Map<String, List<String>>,
|
||||
val cover: ByteArray?,
|
||||
val properties: Properties
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Metadata
|
||||
|
||||
if (id3v2 != other.id3v2) return false
|
||||
if (xiph != other.xiph) return false
|
||||
if (mp4 != other.mp4) return false
|
||||
if (cover != null) {
|
||||
if (other.cover == null) return false
|
||||
if (!cover.contentEquals(other.cover)) return false
|
||||
} else if (other.cover != null) return false
|
||||
if (properties != other.properties) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id3v2.hashCode()
|
||||
result = 31 * result + xiph.hashCode()
|
||||
result = 31 * result + mp4.hashCode()
|
||||
result = 31 * result + (cover?.contentHashCode() ?: 0)
|
||||
result = 31 * result + properties.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
internal data class Properties(
|
||||
val mimeType: String,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.oxycblt.musikr.metadata
|
||||
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import java.io.FileInputStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -36,6 +37,10 @@ private object MetadataExtractorImpl : MetadataExtractor {
|
|||
override suspend fun extract(deviceFile: DeviceFile, fd: ParcelFileDescriptor) =
|
||||
withContext(Dispatchers.IO) {
|
||||
val fis = FileInputStream(fd.fileDescriptor)
|
||||
TagLibJNI.open(deviceFile, fis).also { fis.close() }
|
||||
val input = NativeInputStream(deviceFile, fis)
|
||||
MetadataJNI.openFile(input).also {
|
||||
Log.d("MetadataExtractor", "Metadata: ${it?.id3v2}")
|
||||
fis.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Auxio Project
|
||||
* util.h is part of Auxio.
|
||||
* MetadataJNI.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
|
||||
|
@ -16,16 +16,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AUXIO_UTIL_H
|
||||
#define AUXIO_UTIL_H
|
||||
package org.oxycblt.musikr.metadata
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
internal object MetadataJNI {
|
||||
init {
|
||||
System.loadLibrary("metadatajni")
|
||||
}
|
||||
|
||||
#define LOG_TAG "taglib_jni"
|
||||
#define LOGE(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
|
||||
#define LOGD(...) \
|
||||
((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
|
||||
|
||||
#endif //AUXIO_UTIL_H
|
||||
// This is a rust function, Android Studio has no idea how to link to it
|
||||
external fun openFile(input: NativeInputStream): Metadata?
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.musikr.metadata
|
||||
|
||||
import java.io.FileInputStream
|
||||
import org.oxycblt.musikr.fs.DeviceFile
|
||||
|
||||
internal object TagLibJNI {
|
||||
init {
|
||||
System.loadLibrary("tagJNI")
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a file and extract a tag.
|
||||
*
|
||||
* Note: This method is blocking and should be handled as such if calling from a coroutine.
|
||||
*/
|
||||
fun open(deviceFile: DeviceFile, fis: FileInputStream): Metadata? {
|
||||
val inputStream = NativeInputStream(deviceFile, fis)
|
||||
val tag = openNative(inputStream)
|
||||
inputStream.close()
|
||||
return tag
|
||||
}
|
||||
|
||||
private external fun openNative(inputStream: NativeInputStream): Metadata?
|
||||
}
|
2
musikr/src/main/jni/.gitignore
vendored
Normal file
2
musikr/src/main/jni/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target/
|
||||
.vscode/
|
507
musikr/src/main/jni/Cargo.lock
generated
Normal file
507
musikr/src/main/jni/Cargo.lock
generated
Normal file
|
@ -0,0 +1,507 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "android_log-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
|
||||
|
||||
[[package]]
|
||||
name = "android_logger"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826"
|
||||
dependencies = [
|
||||
"android_log-sys",
|
||||
"env_filter",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cesu8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc49567e08c72902f4cbc7242ee8d874ec9cbe97fbabf77b4e0e1f447513e13a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-cmd",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"foldhash",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe46b5309c99e9775e7a338c98e4097455f52db5b684fd793ca22848fde6e371"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-cmd"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4315c4ce8d23c26d87f2f83698725fd5718d8e6ace4a9093da2664d23294d372"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55d69deb3a92f610a60ecc524a72c7374b6dc822f8fb7bb4e5d9473f10530c4"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bee7a1d9b5091462002c2b8de2a4ed0f0fde011d503cc272633f66075bd5141"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "metadatajni"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"bytemuck",
|
||||
"ctor",
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
"jni",
|
||||
"link-cplusplus",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
25
musikr/src/main/jni/Cargo.toml
Normal file
25
musikr/src/main/jni/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "metadatajni"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "metadatajni"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "1.21.0"
|
||||
cxx = "1.0.137"
|
||||
jni = "0.21.1"
|
||||
link-cplusplus = {version = "1.0.9", features = ["nothing"]}
|
||||
android_logger = { version = "0.14.1", default-features = false }
|
||||
log = "0.4.20"
|
||||
ctor = "0.2.6"
|
||||
|
||||
[build-dependencies]
|
||||
cxx-build = "1.0.137"
|
||||
link-cplusplus = {version = "1.0.9", features = ["nothing"]}
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
158
musikr/src/main/jni/build.rs
Normal file
158
musikr/src/main/jni/build.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
use cxx_build;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::error::Error;
|
||||
|
||||
fn build_taglib(target: &str, working_dir: &Path) -> Result<PathBuf, Box<dyn Error>> {
|
||||
let taglib_src_dir = working_dir.join("taglib");
|
||||
let taglib_build_dir = taglib_src_dir.join("build");
|
||||
let taglib_pkg_dir = taglib_src_dir.join("pkg");
|
||||
let arch_build_dir = taglib_build_dir.join(&target);
|
||||
let arch_pkg_dir = taglib_pkg_dir.join(&target);
|
||||
|
||||
// If lib/libtag.a exists, we don't need to build it
|
||||
if arch_pkg_dir.join("lib/libtag.a").exists() {
|
||||
return Ok(arch_pkg_dir);
|
||||
}
|
||||
|
||||
// Prepare basic cmake arguments
|
||||
let mut cmake_args = vec![
|
||||
"-B".to_string(),
|
||||
arch_build_dir.to_str().unwrap().to_string(),
|
||||
"-DBUILD_SHARED_LIBS=OFF".to_string(),
|
||||
"-DVISIBILITY_HIDDEN=ON".to_string(),
|
||||
"-DBUILD_TESTING=OFF".to_string(),
|
||||
"-DBUILD_EXAMPLES=OFF".to_string(),
|
||||
"-DBUILD_BINDINGS=OFF".to_string(),
|
||||
"-DWITH_ZLIB=OFF".to_string(),
|
||||
"-DCMAKE_BUILD_TYPE=Release".to_string(),
|
||||
"-DCMAKE_CXX_FLAGS=-fPIC".to_string(),
|
||||
];
|
||||
|
||||
if target.contains("android") {
|
||||
// Android target, we need to tack on the NDK toolchain file/args
|
||||
let arch = if target == "x86_64-linux-android" {
|
||||
"x86_64"
|
||||
} else if target.contains("i686-linux-android") {
|
||||
"x86"
|
||||
} else if target.contains("aarch64-linux-android") {
|
||||
"arm64-v8a"
|
||||
} else if target.contains("armv7-linux-androideabi") {
|
||||
"armeabi-v7a"
|
||||
} else {
|
||||
// should never happen
|
||||
return Err(format!("Unsupported Android target: {}", target).into());
|
||||
};
|
||||
|
||||
let clang_path = env::var("CLANG_PATH").expect("CLANG_PATH env var not set");
|
||||
let toolchains_marker = "/toolchains";
|
||||
let ndk_path = if let Some(pos) = clang_path.find(toolchains_marker) {
|
||||
&clang_path[..pos]
|
||||
} else {
|
||||
return Err(format!("CLANG_PATH does not contain '{}'", toolchains_marker).into());
|
||||
};
|
||||
|
||||
let ndk_toolchain = working_dir.join("android.toolchain.cmake");
|
||||
|
||||
cmake_args.extend(vec![
|
||||
format!("-DANDROID_NDK_PATH={}", ndk_path),
|
||||
format!("-DCMAKE_TOOLCHAIN_FILE={}", ndk_toolchain.to_str().unwrap()),
|
||||
format!("-DANDROID_ABI={}", arch),
|
||||
]);
|
||||
}
|
||||
|
||||
// Configure step
|
||||
let status = Command::new("cmake")
|
||||
.args(&cmake_args)
|
||||
.current_dir(&taglib_src_dir)
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
return Err(format!("cmake configure failed for target {}", target).into());
|
||||
}
|
||||
|
||||
// Build step
|
||||
let status = Command::new("cmake")
|
||||
.arg("--build")
|
||||
.arg(arch_build_dir.to_str().unwrap())
|
||||
.arg("--config")
|
||||
.arg("Release")
|
||||
.arg(format!(
|
||||
"-j{}",
|
||||
std::thread::available_parallelism().unwrap().get()
|
||||
))
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
return Err(format!("cmake build failed for target {}", target).into());
|
||||
}
|
||||
|
||||
// Install step
|
||||
let status = Command::new("cmake")
|
||||
.arg("--install")
|
||||
.arg(arch_build_dir.to_str().unwrap())
|
||||
.arg("--config")
|
||||
.arg("Release")
|
||||
.arg("--prefix")
|
||||
.arg(arch_pkg_dir.to_str().unwrap())
|
||||
.arg("--strip")
|
||||
.status()?;
|
||||
if !status.success() {
|
||||
return Err(format!("cmake install failed for arch {}", target).into());
|
||||
}
|
||||
|
||||
Ok(arch_pkg_dir)
|
||||
}
|
||||
|
||||
fn configure_cxx_bridge(target: &str) -> Result<(), Box<dyn Error>> {
|
||||
let mut builder = cxx_build::bridge("src/taglib/bridge.rs");
|
||||
builder
|
||||
.file("shim/iostream_shim.cpp")
|
||||
.file("shim/file_shim.cpp")
|
||||
.file("shim/tk_shim.cpp")
|
||||
.file("shim/picture_shim.cpp")
|
||||
.file("shim/xiph_shim.cpp")
|
||||
.file("shim/id3v1_shim.cpp")
|
||||
.file("shim/id3v2_shim.cpp")
|
||||
.file("shim/mp4_shim.cpp")
|
||||
.include(format!("taglib/pkg/{}/include", target))
|
||||
.include(".")
|
||||
.flag_if_supported("-std=c++14");
|
||||
|
||||
if target.contains("android") {
|
||||
builder
|
||||
.cpp_link_stdlib("c++_static")
|
||||
// Magic linker flags that statically link exception handling symbols
|
||||
// to the library.
|
||||
.flag("-fexceptions")
|
||||
.flag("-funwind-tables");
|
||||
}
|
||||
builder.compile("taglib_cxx_bindings");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let working_dir = env::current_dir()?;
|
||||
let target = env::var("TARGET")?;
|
||||
let working_dir = Path::new(&working_dir);
|
||||
|
||||
let arch_pkg_dir = build_taglib(&target, &working_dir)?;
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}/lib",
|
||||
arch_pkg_dir.display()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=tag");
|
||||
println!("cargo:rerun-if-changed=taglib/");
|
||||
|
||||
configure_cxx_bridge(&target)?;
|
||||
if target.contains("android") {
|
||||
// Magic linker flags that statically link the C++ runtime
|
||||
// and exception handling to the library.
|
||||
println!("cargo:rustc-link-lib=static=c++_static");
|
||||
println!("cargo:rustc-link-lib=static=c++abi");
|
||||
println!("cargo:rustc-link-lib=unwind");
|
||||
}
|
||||
println!("cargo:rerun-if-changed=shim/");
|
||||
|
||||
Ok(())
|
||||
}
|
39
musikr/src/main/jni/shim/file_shim.cpp
Normal file
39
musikr/src/main/jni/shim/file_shim.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#include "file_shim.hpp"
|
||||
|
||||
namespace taglib_shim
|
||||
{
|
||||
std::unique_ptr<TagLib::FileRef> new_FileRef(TagLib::IOStream *stream) {
|
||||
return std::make_unique<TagLib::FileRef>(stream);
|
||||
}
|
||||
|
||||
TagLib::Ogg::Vorbis::File *File_asVorbis(TagLib::File *file)
|
||||
{
|
||||
return dynamic_cast<TagLib::Ogg::Vorbis::File *>(file);
|
||||
}
|
||||
|
||||
TagLib::Ogg::Opus::File *File_asOpus(TagLib::File *file)
|
||||
{
|
||||
return dynamic_cast<TagLib::Ogg::Opus::File *>(file);
|
||||
}
|
||||
|
||||
TagLib::MPEG::File *File_asMPEG(TagLib::File *file)
|
||||
{
|
||||
return dynamic_cast<TagLib::MPEG::File *>(file);
|
||||
}
|
||||
|
||||
TagLib::FLAC::File *File_asFLAC(TagLib::File *file)
|
||||
{
|
||||
return dynamic_cast<TagLib::FLAC::File *>(file);
|
||||
}
|
||||
|
||||
TagLib::MP4::File *File_asMP4(TagLib::File *file)
|
||||
{
|
||||
return dynamic_cast<TagLib::MP4::File *>(file);
|
||||
}
|
||||
|
||||
TagLib::RIFF::WAV::File *File_asWAV(TagLib::File *file)
|
||||
{
|
||||
return dynamic_cast<TagLib::RIFF::WAV::File *>(file);
|
||||
}
|
||||
|
||||
} // namespace taglib_shim
|
27
musikr/src/main/jni/shim/file_shim.hpp
Normal file
27
musikr/src/main/jni/shim/file_shim.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <taglib/fileref.h>
|
||||
#include <taglib/tag.h>
|
||||
#include <taglib/tstring.h>
|
||||
#include <taglib/audioproperties.h>
|
||||
#include <taglib/mpegfile.h>
|
||||
#include <taglib/flacfile.h>
|
||||
#include <taglib/mp4file.h>
|
||||
#include <taglib/oggfile.h>
|
||||
#include <taglib/opusfile.h>
|
||||
#include <taglib/wavfile.h>
|
||||
#include <taglib/wavpackfile.h>
|
||||
#include <taglib/apefile.h>
|
||||
#include <taglib/vorbisfile.h>
|
||||
|
||||
namespace taglib_shim
|
||||
{
|
||||
std::unique_ptr<TagLib::FileRef> new_FileRef(TagLib::IOStream *stream);
|
||||
|
||||
TagLib::Ogg::Vorbis::File *File_asVorbis(TagLib::File *file);
|
||||
TagLib::Ogg::Opus::File *File_asOpus(TagLib::File *file);
|
||||
TagLib::MPEG::File *File_asMPEG(TagLib::File *file);
|
||||
TagLib::FLAC::File *File_asFLAC(TagLib::File *file);
|
||||
TagLib::MP4::File *File_asMP4(TagLib::File *file);
|
||||
TagLib::RIFF::WAV::File *File_asWAV(TagLib::File *file);
|
||||
} // namespace taglib_shim
|
31
musikr/src/main/jni/shim/id3v1_shim.cpp
Normal file
31
musikr/src/main/jni/shim/id3v1_shim.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include "id3v1_shim.hpp"
|
||||
|
||||
namespace taglib_shim {
|
||||
std::unique_ptr<TagLib::String> ID3v1Tag_title(const TagLib::ID3v1::Tag& tag) {
|
||||
return std::make_unique<TagLib::String>(tag.title());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::String> ID3v1Tag_artist(const TagLib::ID3v1::Tag& tag) {
|
||||
return std::make_unique<TagLib::String>(tag.artist());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::String> ID3v1Tag_album(const TagLib::ID3v1::Tag& tag) {
|
||||
return std::make_unique<TagLib::String>(tag.album());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::String> ID3v1Tag_comment(const TagLib::ID3v1::Tag& tag) {
|
||||
return std::make_unique<TagLib::String>(tag.comment());
|
||||
}
|
||||
|
||||
uint ID3v1Tag_genreIndex(const TagLib::ID3v1::Tag& tag) {
|
||||
return tag.genreNumber();
|
||||
}
|
||||
|
||||
uint ID3v1Tag_year(const TagLib::ID3v1::Tag& tag) {
|
||||
return tag.year();
|
||||
}
|
||||
|
||||
uint ID3v1Tag_track(const TagLib::ID3v1::Tag& tag) {
|
||||
return tag.track();
|
||||
}
|
||||
}
|
15
musikr/src/main/jni/shim/id3v1_shim.hpp
Normal file
15
musikr/src/main/jni/shim/id3v1_shim.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <taglib/id3v1tag.h>
|
||||
#include <taglib/tstring.h>
|
||||
|
||||
namespace taglib_shim {
|
||||
std::unique_ptr<TagLib::String> ID3v1Tag_title(const TagLib::ID3v1::Tag& tag);
|
||||
std::unique_ptr<TagLib::String> ID3v1Tag_artist(const TagLib::ID3v1::Tag& tag);
|
||||
std::unique_ptr<TagLib::String> ID3v1Tag_album(const TagLib::ID3v1::Tag& tag);
|
||||
std::unique_ptr<TagLib::String> ID3v1Tag_comment(const TagLib::ID3v1::Tag& tag);
|
||||
uint ID3v1Tag_genreIndex(const TagLib::ID3v1::Tag& tag);
|
||||
uint ID3v1Tag_year(const TagLib::ID3v1::Tag& tag);
|
||||
uint ID3v1Tag_track(const TagLib::ID3v1::Tag& tag);
|
||||
}
|
48
musikr/src/main/jni/shim/id3v2_shim.cpp
Normal file
48
musikr/src/main/jni/shim/id3v2_shim.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include "id3v2_shim.hpp"
|
||||
|
||||
namespace taglib_shim {
|
||||
std::unique_ptr<TagLib::ID3v2::FrameList> Tag_frameList(const TagLib::ID3v2::Tag& tag) {
|
||||
return std::make_unique<TagLib::ID3v2::FrameList>(tag.frameList());
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<FramePointer>> FrameList_to_vector(const TagLib::ID3v2::FrameList& list) {
|
||||
auto frames = std::make_unique<std::vector<FramePointer>>();
|
||||
for (const auto& frame : list) {
|
||||
frames->push_back(FramePointer{frame});
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
const TagLib::ID3v2::TextIdentificationFrame* Frame_asTextIdentification(const TagLib::ID3v2::Frame* frame) {
|
||||
return dynamic_cast<const TagLib::ID3v2::TextIdentificationFrame*>(frame);
|
||||
}
|
||||
|
||||
const TagLib::ID3v2::UserTextIdentificationFrame* Frame_asUserTextIdentification(const TagLib::ID3v2::Frame* frame) {
|
||||
return dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(frame);
|
||||
}
|
||||
|
||||
const TagLib::ID3v2::AttachedPictureFrame* Frame_asAttachedPicture(const TagLib::ID3v2::Frame* frame) {
|
||||
return dynamic_cast<const TagLib::ID3v2::AttachedPictureFrame*>(frame);
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::ByteVector> Frame_id(const TagLib::ID3v2::Frame& frame) {
|
||||
return std::make_unique<TagLib::ByteVector>(frame.frameID());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::ByteVector> AttachedPictureFrame_picture(const TagLib::ID3v2::AttachedPictureFrame& frame) {
|
||||
return std::make_unique<TagLib::ByteVector>(frame.picture());
|
||||
}
|
||||
|
||||
uint32_t AttachedPictureFrame_type(const TagLib::ID3v2::AttachedPictureFrame& frame) {
|
||||
return static_cast<uint32_t>(frame.type());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::StringList> TextIdentificationFrame_fieldList(const TagLib::ID3v2::TextIdentificationFrame& frame) {
|
||||
return std::make_unique<TagLib::StringList>(frame.fieldList());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::StringList> UserTextIdentificationFrame_fieldList(const TagLib::ID3v2::UserTextIdentificationFrame& frame) {
|
||||
return std::make_unique<TagLib::StringList>(frame.fieldList());
|
||||
}
|
||||
|
||||
}
|
36
musikr/src/main/jni/shim/id3v2_shim.hpp
Normal file
36
musikr/src/main/jni/shim/id3v2_shim.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "rust/cxx.h"
|
||||
#include "taglib/id3v2tag.h"
|
||||
#include "taglib/id3v2frame.h"
|
||||
#include "taglib/textidentificationframe.h"
|
||||
#include "taglib/unsynchronizedlyricsframe.h"
|
||||
#include "taglib/attachedpictureframe.h"
|
||||
#include "taglib/tbytevector.h"
|
||||
#include "taglib/mpegfile.h"
|
||||
|
||||
namespace taglib_shim {
|
||||
struct FramePointer {
|
||||
const TagLib::ID3v2::Frame* inner;
|
||||
const TagLib::ID3v2::Frame* get() const { return inner; }
|
||||
};
|
||||
|
||||
std::unique_ptr<TagLib::ID3v2::FrameList> Tag_frameList(const TagLib::ID3v2::Tag& tag);
|
||||
|
||||
std::unique_ptr<std::vector<FramePointer>> FrameList_to_vector(const TagLib::ID3v2::FrameList& list);
|
||||
|
||||
// Frame type checking and casting
|
||||
std::unique_ptr<TagLib::ByteVector> Frame_id(const TagLib::ID3v2::Frame &frame);
|
||||
const TagLib::ID3v2::TextIdentificationFrame* Frame_asTextIdentification(const TagLib::ID3v2::Frame* frame);
|
||||
const TagLib::ID3v2::UserTextIdentificationFrame* Frame_asUserTextIdentification(const TagLib::ID3v2::Frame* frame);
|
||||
const TagLib::ID3v2::AttachedPictureFrame* Frame_asAttachedPicture(const TagLib::ID3v2::Frame* frame);
|
||||
|
||||
// Frame data access
|
||||
std::unique_ptr<TagLib::ByteVector> AttachedPictureFrame_picture(const TagLib::ID3v2::AttachedPictureFrame& frame);
|
||||
uint32_t AttachedPictureFrame_type(const TagLib::ID3v2::AttachedPictureFrame& frame);
|
||||
std::unique_ptr<TagLib::StringList> TextIdentificationFrame_fieldList(const TagLib::ID3v2::TextIdentificationFrame& frame);
|
||||
std::unique_ptr<TagLib::StringList> UserTextIdentificationFrame_fieldList(const TagLib::ID3v2::UserTextIdentificationFrame& frame);
|
||||
|
||||
// ID3v2 tag access
|
||||
TagLib::ID3v2::Tag* File_ID3v2Tag(TagLib::MPEG::File* file, bool create);
|
||||
}
|
166
musikr/src/main/jni/shim/iostream_shim.cpp
Normal file
166
musikr/src/main/jni/shim/iostream_shim.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include "iostream_shim.hpp"
|
||||
#include <stdexcept>
|
||||
#include <rust/cxx.h>
|
||||
#include <vector>
|
||||
#include "metadatajni/src/taglib/bridge.rs.h"
|
||||
|
||||
namespace taglib_shim
|
||||
{
|
||||
|
||||
// C++ implementation of TagLib::IOStream that delegates to Rust
|
||||
class WrappedRsIOStream : public TagLib::IOStream
|
||||
{
|
||||
public:
|
||||
explicit WrappedRsIOStream(RsIOStream *stream);
|
||||
~WrappedRsIOStream() override;
|
||||
|
||||
// TagLib::IOStream interface implementation
|
||||
TagLib::FileName name() const override;
|
||||
TagLib::ByteVector readBlock(size_t length) override;
|
||||
void writeBlock(const TagLib::ByteVector &data) override;
|
||||
void insert(const TagLib::ByteVector &data, TagLib::offset_t start = 0, size_t replace = 0) override;
|
||||
void removeBlock(TagLib::offset_t start = 0, size_t length = 0) override;
|
||||
void seek(TagLib::offset_t offset, Position p = Beginning) override;
|
||||
void clear() override;
|
||||
void truncate(TagLib::offset_t length) override;
|
||||
TagLib::offset_t tell() const override;
|
||||
TagLib::offset_t length() override;
|
||||
bool readOnly() const override;
|
||||
bool isOpen() const override;
|
||||
|
||||
private:
|
||||
std::string _name;
|
||||
RsIOStream *rust_stream;
|
||||
};
|
||||
|
||||
WrappedRsIOStream::WrappedRsIOStream(RsIOStream *stream) : rust_stream(stream)
|
||||
{
|
||||
_name = std::move(std::string(rust_stream->name()));
|
||||
}
|
||||
|
||||
WrappedRsIOStream::~WrappedRsIOStream() = default;
|
||||
|
||||
TagLib::FileName WrappedRsIOStream::name() const
|
||||
{
|
||||
return _name.c_str();
|
||||
}
|
||||
|
||||
TagLib::ByteVector WrappedRsIOStream::readBlock(size_t length)
|
||||
{
|
||||
std::vector<uint8_t> buffer(length);
|
||||
size_t bytes_read = rust_stream->read(rust::Slice<uint8_t>(buffer.data(), length));
|
||||
return TagLib::ByteVector(reinterpret_cast<char *>(buffer.data()), bytes_read);
|
||||
}
|
||||
|
||||
void WrappedRsIOStream::writeBlock(const TagLib::ByteVector &data)
|
||||
{
|
||||
rust_stream->write(rust::Slice<const uint8_t>(
|
||||
reinterpret_cast<const uint8_t *>(data.data()), data.size()));
|
||||
}
|
||||
|
||||
void WrappedRsIOStream::insert(const TagLib::ByteVector &data, TagLib::offset_t start, size_t replace)
|
||||
{
|
||||
// Save current position
|
||||
auto current = tell();
|
||||
|
||||
// Seek to insert position
|
||||
seek(start);
|
||||
|
||||
// If replacing, remove that section first
|
||||
if (replace > 0)
|
||||
{
|
||||
removeBlock(start, replace);
|
||||
}
|
||||
|
||||
// Write new data
|
||||
writeBlock(data);
|
||||
|
||||
// Restore position
|
||||
seek(current);
|
||||
}
|
||||
|
||||
void WrappedRsIOStream::removeBlock(TagLib::offset_t start, size_t length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
// Save current position
|
||||
auto current = tell();
|
||||
|
||||
// Get file size
|
||||
auto file_length = this->length();
|
||||
|
||||
// Read everything after the removed section
|
||||
seek(start + length);
|
||||
auto remaining = readBlock(file_length - (start + length));
|
||||
|
||||
// Truncate to start position
|
||||
seek(start);
|
||||
truncate(start);
|
||||
|
||||
// Write remaining data
|
||||
writeBlock(remaining);
|
||||
|
||||
// Restore position
|
||||
seek(current);
|
||||
}
|
||||
|
||||
void WrappedRsIOStream::seek(TagLib::offset_t offset, Position p)
|
||||
{
|
||||
int32_t whence;
|
||||
switch (p)
|
||||
{
|
||||
case Beginning:
|
||||
whence = SEEK_SET;
|
||||
break;
|
||||
case Current:
|
||||
whence = SEEK_CUR;
|
||||
break;
|
||||
case End:
|
||||
whence = SEEK_END;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
break;
|
||||
}
|
||||
rust_stream->seek(offset, whence);
|
||||
}
|
||||
|
||||
void WrappedRsIOStream::clear()
|
||||
{
|
||||
truncate(0);
|
||||
seek(0);
|
||||
}
|
||||
|
||||
void WrappedRsIOStream::truncate(TagLib::offset_t length)
|
||||
{
|
||||
rust_stream->truncate(length);
|
||||
}
|
||||
|
||||
TagLib::offset_t WrappedRsIOStream::tell() const
|
||||
{
|
||||
return rust_stream->tell();
|
||||
}
|
||||
|
||||
TagLib::offset_t WrappedRsIOStream::length()
|
||||
{
|
||||
return rust_stream->length();
|
||||
}
|
||||
|
||||
bool WrappedRsIOStream::readOnly() const
|
||||
{
|
||||
return rust_stream->is_readonly();
|
||||
}
|
||||
|
||||
bool WrappedRsIOStream::isOpen() const
|
||||
{
|
||||
return true; // If we have a stream, it's open
|
||||
}
|
||||
|
||||
// Factory function to create a new RustIOStream
|
||||
std::unique_ptr<TagLib::IOStream> wrap_RsIOStream(RsIOStream *stream)
|
||||
{
|
||||
return std::unique_ptr<TagLib::IOStream>(new WrappedRsIOStream(stream));
|
||||
}
|
||||
|
||||
} // namespace taglib_shim
|
16
musikr/src/main/jni/shim/iostream_shim.hpp
Normal file
16
musikr/src/main/jni/shim/iostream_shim.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <taglib/tiostream.h>
|
||||
#include <taglib/fileref.h>
|
||||
#include "rust/cxx.h"
|
||||
|
||||
// Forward declare the bridge type
|
||||
struct RsIOStream;
|
||||
|
||||
namespace taglib_shim
|
||||
{
|
||||
// Factory functions with external linkage
|
||||
std::unique_ptr<TagLib::IOStream> wrap_RsIOStream(RsIOStream *stream);
|
||||
} // namespace taglib_shim
|
85
musikr/src/main/jni/shim/mp4_shim.cpp
Normal file
85
musikr/src/main/jni/shim/mp4_shim.cpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
#include "mp4_shim.hpp"
|
||||
#include <taglib/tstring.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace taglib_shim {
|
||||
ItemMapEntry::ItemMapEntry(TagLib::String key, TagLib::MP4::Item value)
|
||||
: key_(std::move(key)), value_(std::move(value)) {}
|
||||
|
||||
std::unique_ptr<TagLib::String> ItemMapEntry::key() const {
|
||||
return std::make_unique<TagLib::String>(key_);
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::MP4::Item> ItemMapEntry::value() const {
|
||||
return std::make_unique<TagLib::MP4::Item>(value_);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<ItemMapEntry>> ItemMap_to_entries(const TagLib::MP4::ItemMap& map) {
|
||||
auto entries = std::make_unique<std::vector<ItemMapEntry>>();
|
||||
|
||||
for (auto it = map.begin(); it != map.end(); ++it) {
|
||||
entries->emplace_back(it->first, it->second);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
IntPair::IntPair(int first, int second)
|
||||
: first_(first), second_(second) {}
|
||||
|
||||
int IntPair::first() const {
|
||||
return first_;
|
||||
}
|
||||
|
||||
int IntPair::second() const {
|
||||
return second_;
|
||||
}
|
||||
|
||||
CoverArt::CoverArt(TagLib::MP4::CoverArt::Format format, const TagLib::ByteVector& data)
|
||||
: art_(format, data) {}
|
||||
|
||||
uint32_t CoverArt::format() const {
|
||||
return static_cast<uint32_t>(art_.format());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::ByteVector> CoverArt::data() const {
|
||||
return std::make_unique<TagLib::ByteVector>(art_.data());
|
||||
}
|
||||
|
||||
CoverArtList::CoverArtList(const TagLib::MP4::CoverArtList& list)
|
||||
: list_(list) {}
|
||||
|
||||
std::unique_ptr<std::vector<CoverArt>> CoverArtList::to_vector() const {
|
||||
auto vec = std::make_unique<std::vector<CoverArt>>();
|
||||
for (const auto& item : list_) {
|
||||
vec->emplace_back(item.format(), item.data());
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
unsigned int Item_type(const TagLib::MP4::Item& item) {
|
||||
return static_cast<unsigned int>(item.type());
|
||||
}
|
||||
|
||||
std::unique_ptr<IntPair> Item_toIntPair(const TagLib::MP4::Item& item) {
|
||||
auto pair = item.toIntPair();
|
||||
return std::make_unique<IntPair>(pair.first, pair.second);
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::StringList> Item_toStringList(const TagLib::MP4::Item& item) {
|
||||
return std::make_unique<TagLib::StringList>(item.toStringList());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::ByteVectorList> Item_toByteVectorList(const TagLib::MP4::Item& item) {
|
||||
return std::make_unique<TagLib::ByteVectorList>(item.toByteVectorList());
|
||||
}
|
||||
|
||||
std::unique_ptr<CoverArtList> Item_toCoverArtList(const TagLib::MP4::Item& item) {
|
||||
return std::make_unique<CoverArtList>(item.toCoverArtList());
|
||||
}
|
||||
|
||||
int64_t Item_toLongLong(const TagLib::MP4::Item& item) {
|
||||
return item.toLongLong();
|
||||
}
|
||||
}
|
59
musikr/src/main/jni/shim/mp4_shim.hpp
Normal file
59
musikr/src/main/jni/shim/mp4_shim.hpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <taglib/mp4tag.h>
|
||||
#include <taglib/mp4item.h>
|
||||
#include <taglib/tstring.h>
|
||||
#include "rust/cxx.h"
|
||||
|
||||
namespace taglib_shim {
|
||||
class ItemMapEntry {
|
||||
public:
|
||||
ItemMapEntry(TagLib::String key, TagLib::MP4::Item value);
|
||||
std::unique_ptr<TagLib::String> key() const;
|
||||
std::unique_ptr<TagLib::MP4::Item> value() const;
|
||||
|
||||
private:
|
||||
TagLib::String key_;
|
||||
TagLib::MP4::Item value_;
|
||||
};
|
||||
|
||||
std::unique_ptr<std::vector<ItemMapEntry>> ItemMap_to_entries(const TagLib::MP4::ItemMap& map);
|
||||
|
||||
class IntPair {
|
||||
public:
|
||||
IntPair(int first, int second);
|
||||
int first() const;
|
||||
int second() const;
|
||||
|
||||
private:
|
||||
int first_;
|
||||
int second_;
|
||||
};
|
||||
|
||||
class CoverArt {
|
||||
public:
|
||||
CoverArt(TagLib::MP4::CoverArt::Format format, const TagLib::ByteVector& data);
|
||||
uint32_t format() const;
|
||||
std::unique_ptr<TagLib::ByteVector> data() const;
|
||||
|
||||
private:
|
||||
TagLib::MP4::CoverArt art_;
|
||||
};
|
||||
|
||||
class CoverArtList {
|
||||
public:
|
||||
CoverArtList(const TagLib::MP4::CoverArtList& list);
|
||||
std::unique_ptr<std::vector<CoverArt>> to_vector() const;
|
||||
|
||||
private:
|
||||
TagLib::MP4::CoverArtList list_;
|
||||
};
|
||||
|
||||
unsigned int Item_type(const TagLib::MP4::Item& item);
|
||||
std::unique_ptr<IntPair> Item_toIntPair(const TagLib::MP4::Item& item);
|
||||
std::unique_ptr<TagLib::StringList> Item_toStringList(const TagLib::MP4::Item& item);
|
||||
std::unique_ptr<TagLib::ByteVectorList> Item_toByteVectorList(const TagLib::MP4::Item& item);
|
||||
std::unique_ptr<CoverArtList> Item_toCoverArtList(const TagLib::MP4::Item& item);
|
||||
int64_t Item_toLongLong(const TagLib::MP4::Item& item);
|
||||
}
|
36
musikr/src/main/jni/shim/picture_shim.cpp
Normal file
36
musikr/src/main/jni/shim/picture_shim.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include "picture_shim.hpp"
|
||||
#include "taglib/flacfile.h"
|
||||
|
||||
namespace taglib_shim {
|
||||
std::unique_ptr<PictureList> XiphComment_pictureList(TagLib::Ogg::XiphComment& comment) {
|
||||
return std::make_unique<PictureList>(comment.pictureList());
|
||||
}
|
||||
|
||||
std::unique_ptr<PictureList> FLACFile_pictureList(TagLib::FLAC::File& file) {
|
||||
return std::make_unique<PictureList>(file.pictureList());
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<PicturePointer>> PictureList_to_vector(const PictureList& list) {
|
||||
auto result = std::make_unique<std::vector<PicturePointer>>();
|
||||
for (const auto* picture : list) {
|
||||
result->emplace_back(picture);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::String> Picture_mimeType(const TagLib::FLAC::Picture& picture) {
|
||||
return std::make_unique<TagLib::String>(picture.mimeType());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::String> Picture_description(const TagLib::FLAC::Picture& picture) {
|
||||
return std::make_unique<TagLib::String>(picture.description());
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::ByteVector> Picture_data(const TagLib::FLAC::Picture& picture) {
|
||||
return std::make_unique<TagLib::ByteVector>(picture.data());
|
||||
}
|
||||
|
||||
uint32_t Picture_type(const TagLib::FLAC::Picture& picture) {
|
||||
return static_cast<uint32_t>(picture.type());
|
||||
}
|
||||
}
|
28
musikr/src/main/jni/shim/picture_shim.hpp
Normal file
28
musikr/src/main/jni/shim/picture_shim.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "taglib/flacpicture.h"
|
||||
#include "taglib/tstring.h"
|
||||
#include "taglib/tbytevector.h"
|
||||
#include "taglib/tpicturetype.h"
|
||||
#include "tk_shim.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace taglib_shim {
|
||||
using PictureList = TagLib::List<TagLib::FLAC::Picture *>;
|
||||
|
||||
class PicturePointer {
|
||||
public:
|
||||
PicturePointer(const TagLib::FLAC::Picture* picture) : picture(picture) {}
|
||||
const TagLib::FLAC::Picture* get() const { return picture; }
|
||||
private:
|
||||
const TagLib::FLAC::Picture* picture;
|
||||
};
|
||||
std::unique_ptr<PictureList> FLACFile_pictureList(TagLib::FLAC::File& file);
|
||||
std::unique_ptr<PictureList> XiphComment_pictureList(TagLib::Ogg::XiphComment& comment);
|
||||
|
||||
std::unique_ptr<std::vector<PicturePointer>> PictureList_to_vector(const PictureList& list);
|
||||
|
||||
uint32_t Picture_type(const TagLib::FLAC::Picture& picture);
|
||||
std::unique_ptr<TagLib::ByteVector> Picture_data(const TagLib::FLAC::Picture& picture);
|
||||
}
|
45
musikr/src/main/jni/shim/tk_shim.cpp
Normal file
45
musikr/src/main/jni/shim/tk_shim.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "tk_shim.hpp"
|
||||
|
||||
namespace taglib_shim
|
||||
{
|
||||
Property::Property(TagLib::String key, TagLib::StringList value) : key_(key), value_(value) {}
|
||||
|
||||
const TagLib::String &Property::key() const
|
||||
{
|
||||
return key_;
|
||||
}
|
||||
|
||||
const TagLib::StringList &Property::value() const
|
||||
{
|
||||
return value_;
|
||||
}
|
||||
std::unique_ptr<std::vector<Property>> SimplePropertyMap_to_vector(const TagLib::SimplePropertyMap &map)
|
||||
{
|
||||
std::unique_ptr<std::vector<Property>> result = std::make_unique<std::vector<Property>>();
|
||||
for (const auto &pair : map)
|
||||
{
|
||||
result->push_back(Property(pair.first, pair.second));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<TagLib::String>> StringList_to_vector(const TagLib::StringList &list)
|
||||
{
|
||||
std::unique_ptr<std::vector<TagLib::String>> result = std::make_unique<std::vector<TagLib::String>>();
|
||||
for (const auto &str : list)
|
||||
{
|
||||
result->push_back(str);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<TagLib::ByteVector>> ByteVectorList_to_vector(const TagLib::ByteVectorList &list)
|
||||
{
|
||||
std::unique_ptr<std::vector<TagLib::ByteVector>> result = std::make_unique<std::vector<TagLib::ByteVector>>();
|
||||
for (const auto &vec : list)
|
||||
{
|
||||
result->push_back(vec);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
35
musikr/src/main/jni/shim/tk_shim.hpp
Normal file
35
musikr/src/main/jni/shim/tk_shim.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "taglib/tpropertymap.h"
|
||||
#include "taglib/xiphcomment.h"
|
||||
#include "taglib/tstring.h"
|
||||
#include "taglib/tstringlist.h"
|
||||
#include "taglib/flacpicture.h"
|
||||
#include "taglib/flacfile.h"
|
||||
#include "taglib/tbytevector.h"
|
||||
#include <memory>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "rust/cxx.h"
|
||||
|
||||
namespace taglib_shim
|
||||
{
|
||||
|
||||
struct Property
|
||||
{
|
||||
Property(TagLib::String key, TagLib::StringList value);
|
||||
const TagLib::String &key() const;
|
||||
const TagLib::StringList &value() const;
|
||||
|
||||
private:
|
||||
TagLib::String key_;
|
||||
TagLib::StringList value_;
|
||||
};
|
||||
|
||||
|
||||
std::unique_ptr<std::vector<Property>> SimplePropertyMap_to_vector(const TagLib::SimplePropertyMap &map);
|
||||
std::unique_ptr<std::vector<TagLib::String>> StringList_to_vector(const TagLib::StringList &list);
|
||||
|
||||
std::unique_ptr<std::vector<TagLib::ByteVector>> ByteVectorList_to_vector(const TagLib::ByteVectorList &list);
|
||||
}
|
26
musikr/src/main/jni/shim/xiph_shim.cpp
Normal file
26
musikr/src/main/jni/shim/xiph_shim.cpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#include "xiph_shim.hpp"
|
||||
|
||||
namespace taglib_shim
|
||||
{
|
||||
FieldListEntry::FieldListEntry(TagLib::String key, TagLib::StringList value) : key_(key), value_(value) {}
|
||||
|
||||
std::unique_ptr<TagLib::String> FieldListEntry::key() const
|
||||
{
|
||||
return std::make_unique<TagLib::String>(key_);
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::StringList> FieldListEntry::value() const
|
||||
{
|
||||
return std::make_unique<TagLib::StringList>(value_);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<FieldListEntry>> FieldListMap_to_entries(const TagLib::SimplePropertyMap &map)
|
||||
{
|
||||
std::unique_ptr<std::vector<FieldListEntry>> result = std::make_unique<std::vector<FieldListEntry>>();
|
||||
for (const auto &pair : map)
|
||||
{
|
||||
result->push_back(FieldListEntry(pair.first, pair.second));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
22
musikr/src/main/jni/shim/xiph_shim.hpp
Normal file
22
musikr/src/main/jni/shim/xiph_shim.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "taglib/tpropertymap.h"
|
||||
#include "taglib/xiphcomment.h"
|
||||
|
||||
namespace taglib_shim
|
||||
{
|
||||
using FieldListMap = TagLib::SimplePropertyMap;
|
||||
|
||||
struct FieldListEntry
|
||||
{
|
||||
FieldListEntry(TagLib::String key, TagLib::StringList value);
|
||||
std::unique_ptr<TagLib::String> key() const;
|
||||
std::unique_ptr<TagLib::StringList> value() const;
|
||||
|
||||
private:
|
||||
TagLib::String key_;
|
||||
TagLib::StringList value_;
|
||||
};
|
||||
|
||||
|
||||
std::unique_ptr<std::vector<FieldListEntry>> FieldListMap_to_entries(const FieldListMap &map);
|
||||
std::unique_ptr<std::vector<TagLib::String>> StringList_to_vector(const TagLib::StringList &list);
|
||||
}
|
249
musikr/src/main/jni/src/jbuilder.rs
Normal file
249
musikr/src/main/jni/src/jbuilder.rs
Normal file
|
@ -0,0 +1,249 @@
|
|||
use jni::{
|
||||
objects::{JObject, JValueGen},
|
||||
sys::jlong,
|
||||
JNIEnv,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::taglib::{
|
||||
audioproperties,
|
||||
flac::PictureType,
|
||||
id3v1, id3v2, mp4,
|
||||
xiph::{self, FLACPictureList},
|
||||
};
|
||||
|
||||
use crate::jtagmap::JTagMap;
|
||||
|
||||
pub struct JMetadataBuilder<'local, 'file_ref> {
|
||||
env: Rc<RefCell<JNIEnv<'local>>>,
|
||||
id3v2: JTagMap<'local>,
|
||||
xiph: JTagMap<'local>,
|
||||
mp4: JTagMap<'local>,
|
||||
cover: Option<Vec<u8>>,
|
||||
properties: Option<audioproperties::AudioProperties<'file_ref>>,
|
||||
mime_type: Option<String>,
|
||||
}
|
||||
|
||||
impl<'local, 'file_ref> JMetadataBuilder<'local, 'file_ref> {
|
||||
pub fn new(env: Rc<RefCell<JNIEnv<'local>>>) -> Self {
|
||||
Self {
|
||||
id3v2: JTagMap::new(env.clone()),
|
||||
xiph: JTagMap::new(env.clone()),
|
||||
mp4: JTagMap::new(env.clone()),
|
||||
env,
|
||||
cover: None,
|
||||
properties: None,
|
||||
mime_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mime_type(&mut self, mime_type: impl Into<String>) {
|
||||
self.mime_type = Some(mime_type.into());
|
||||
}
|
||||
|
||||
pub fn set_id3v1(&mut self, tag: &id3v1::ID3v1Tag) {
|
||||
self.id3v2.add_id("TIT2", tag.title().to_string());
|
||||
self.id3v2.add_id("TPE1", tag.artist().to_string());
|
||||
self.id3v2.add_id("TALB", tag.album().to_string());
|
||||
self.id3v2.add_id("TRCK", tag.track().to_string());
|
||||
self.id3v2.add_id("TYER", tag.year().to_string());
|
||||
|
||||
let genre = tag.genre_index();
|
||||
if genre != 255 {
|
||||
self.id3v2.add_id("TCON", genre.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_id3v2(&mut self, tag: &id3v2::ID3v2Tag) {
|
||||
let mut first_pic = None;
|
||||
let mut front_cover_pic = None;
|
||||
|
||||
for mut frame in tag.frames().to_vec() {
|
||||
if let Some(user_text_frame) = frame.as_user_text_identification() {
|
||||
let values = user_text_frame.values();
|
||||
let mut values = values.to_vec();
|
||||
if !values.is_empty() {
|
||||
let description = values.remove(0).to_string();
|
||||
let remaining: Vec<String> = values.into_iter().map(|s| s.to_string()).collect();
|
||||
self.id3v2.add_combined_list(frame.id().to_string_lossy(), description, remaining);
|
||||
}
|
||||
} else if let Some(text_frame) = frame.as_text_identification() {
|
||||
let field_list = text_frame.field_list();
|
||||
let values: Vec<String> = field_list
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
self.id3v2.add_id_list(frame.id().to_string_lossy(), values);
|
||||
} else if let Some(picture_frame) = frame.as_attached_picture() {
|
||||
if first_pic.is_none() {
|
||||
first_pic = Some(picture_frame.picture().to_vec());
|
||||
}
|
||||
if let (Some(PictureType::FrontCover), None) =
|
||||
(picture_frame.picture_type(), &front_cover_pic)
|
||||
{
|
||||
front_cover_pic = Some(picture_frame.picture().to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer front cover, fall back to first picture
|
||||
self.cover = front_cover_pic.or(first_pic);
|
||||
}
|
||||
|
||||
pub fn set_xiph(&mut self, tag: &mut xiph::XiphComment<'file_ref>) {
|
||||
for (key, values) in tag.field_list_map().to_hashmap() {
|
||||
let values: Vec<String> = values.to_vec().into_iter().map(|s| s.to_string()).collect();
|
||||
self.xiph.add_custom_list(key.to_uppercase(), values);
|
||||
}
|
||||
self.set_flac_pictures(&tag.picture_list());
|
||||
}
|
||||
|
||||
pub fn set_mp4(&mut self, tag: &mp4::MP4Tag) {
|
||||
let map = tag.item_map().to_hashmap();
|
||||
|
||||
for (key, item) in map {
|
||||
if key == "covr" {
|
||||
if let Some(mp4::MP4Data::CoverArtList(cover_list)) = item.data() {
|
||||
let covers = cover_list.to_vec();
|
||||
// Prefer PNG/JPEG covers
|
||||
let preferred_cover = covers.iter().find(|c| {
|
||||
matches!(
|
||||
c.format(),
|
||||
mp4::CoverArtFormat::PNG | mp4::CoverArtFormat::JPEG
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(cover) = preferred_cover {
|
||||
self.cover = Some(cover.data().to_vec());
|
||||
} else if let Some(first_cover) = covers.first() {
|
||||
self.cover = Some(first_cover.data().to_vec());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(data) = item.data() {
|
||||
match data {
|
||||
mp4::MP4Data::StringList(list) => {
|
||||
let values: Vec<String> =
|
||||
list.to_vec().into_iter().map(|s| s.to_string()).collect();
|
||||
if key.starts_with("----") {
|
||||
if let Some(split_idx) = key.find(':') {
|
||||
let (atom_name, atom_desc) = key.split_at(split_idx);
|
||||
let atom_desc = &atom_desc[1..]; // Skip the colon
|
||||
for value in values {
|
||||
self.mp4.add_combined(atom_name, atom_desc, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.mp4.add_id_list(key, values);
|
||||
}
|
||||
}
|
||||
mp4::MP4Data::Int(v) => self.mp4.add_id(key, v.to_string()),
|
||||
mp4::MP4Data::UInt(v) => self.mp4.add_id(key, v.to_string()),
|
||||
mp4::MP4Data::LongLong(v) => self.mp4.add_id(key, v.to_string()),
|
||||
mp4::MP4Data::IntPair(pair) => {
|
||||
if let Some((first, second)) = pair.to_tuple() {
|
||||
self.mp4.add_id(key, format!("{}/{}", first, second));
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_flac_pictures(&mut self, pictures: &FLACPictureList<'file_ref>) {
|
||||
for picture in pictures.to_vec().into_iter() {
|
||||
if let Some(PictureType::FrontCover) = picture.picture_type() {
|
||||
self.cover = Some(picture.data().to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_properties(&mut self, properties: audioproperties::AudioProperties<'file_ref>) {
|
||||
self.properties = Some(properties);
|
||||
}
|
||||
|
||||
pub fn build(&self) -> JObject {
|
||||
// Create Properties object
|
||||
let properties_class = self
|
||||
.env
|
||||
.borrow_mut()
|
||||
.find_class("org/oxycblt/musikr/metadata/Properties")
|
||||
.unwrap();
|
||||
let properties = if let Some(props) = &self.properties {
|
||||
let mime_type = self.mime_type.as_deref().unwrap_or("").to_string();
|
||||
let j_mime_type = self.env.borrow().new_string(mime_type).unwrap();
|
||||
|
||||
self.env
|
||||
.borrow_mut()
|
||||
.new_object(
|
||||
properties_class,
|
||||
"(Ljava/lang/String;JII)V",
|
||||
&[
|
||||
JValueGen::from(&j_mime_type),
|
||||
(props.length_in_milliseconds() as jlong).into(),
|
||||
(props.bitrate() as i32).into(),
|
||||
(props.sample_rate() as i32).into(),
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
let empty_mime = self.env.borrow().new_string("").unwrap();
|
||||
self.env
|
||||
.borrow_mut()
|
||||
.new_object(
|
||||
properties_class,
|
||||
"(Ljava/lang/String;JII)V",
|
||||
&[
|
||||
JValueGen::from(&empty_mime),
|
||||
0i64.into(),
|
||||
0i32.into(),
|
||||
0i32.into(),
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// Create cover byte array if present
|
||||
let cover_array = if let Some(cover_data) = &self.cover {
|
||||
let array = self
|
||||
.env
|
||||
.borrow()
|
||||
.new_byte_array(cover_data.len() as i32)
|
||||
.unwrap();
|
||||
self.env
|
||||
.borrow()
|
||||
.set_byte_array_region(&array, 0, bytemuck::cast_slice(cover_data))
|
||||
.unwrap();
|
||||
array.into()
|
||||
} else {
|
||||
JObject::null()
|
||||
};
|
||||
|
||||
// Create Metadata object
|
||||
let metadata_class = self
|
||||
.env
|
||||
.borrow_mut()
|
||||
.find_class("org/oxycblt/musikr/metadata/Metadata")
|
||||
.unwrap();
|
||||
// Get the objects first since they need the env borrow first
|
||||
let id3v2 = self.id3v2.get_object();
|
||||
let xiph = self.xiph.get_object();
|
||||
let mp4 = self.mp4.get_object();
|
||||
self.env.borrow_mut().new_object(
|
||||
metadata_class,
|
||||
"(Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;[BLorg/oxycblt/musikr/metadata/Properties;)V",
|
||||
&[
|
||||
JValueGen::from(&id3v2),
|
||||
JValueGen::from(&xiph),
|
||||
JValueGen::from(&mp4),
|
||||
JValueGen::from(&cover_array),
|
||||
JValueGen::from(&properties),
|
||||
],
|
||||
).unwrap()
|
||||
}
|
||||
}
|
117
musikr/src/main/jni/src/jstream.rs
Normal file
117
musikr/src/main/jni/src/jstream.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
use crate::taglib::iostream::IOStream;
|
||||
use crate::SharedEnv;
|
||||
use jni::objects::{JObject, JValue};
|
||||
use std::io::SeekFrom;
|
||||
|
||||
pub struct JInputStream<'local> {
|
||||
env: SharedEnv<'local>,
|
||||
input: &'local JObject<'local>,
|
||||
}
|
||||
|
||||
impl<'local, 'a> JInputStream<'local> {
|
||||
pub fn new(env: SharedEnv<'local>, input: &'local JObject<'local>) -> Self {
|
||||
Self { env, input }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'local> IOStream for JInputStream<'local> {
|
||||
fn read_block(&mut self, buf: &mut [u8]) -> usize {
|
||||
// Create a direct ByteBuffer from the Rust slice
|
||||
let byte_buffer = unsafe {
|
||||
self.env
|
||||
.borrow_mut()
|
||||
.new_direct_byte_buffer(buf.as_mut_ptr(), buf.len())
|
||||
.expect("Failed to create ByteBuffer")
|
||||
};
|
||||
|
||||
// Call readBlock safely
|
||||
let success = self
|
||||
.env
|
||||
.borrow_mut()
|
||||
.call_method(
|
||||
&self.input,
|
||||
"readBlock",
|
||||
"(Ljava/nio/ByteBuffer;)Z",
|
||||
&[JValue::Object(&byte_buffer)],
|
||||
)
|
||||
.and_then(|result| result.z())
|
||||
.expect("Failed to call readBlock");
|
||||
|
||||
if !success {
|
||||
return 0;
|
||||
}
|
||||
|
||||
buf.len()
|
||||
}
|
||||
|
||||
fn write_block(&mut self, _data: &[u8]) {
|
||||
panic!("JInputStream is read-only");
|
||||
}
|
||||
|
||||
fn seek(&mut self, pos: SeekFrom) {
|
||||
let (method, offset) = match pos {
|
||||
SeekFrom::Start(offset) => ("seekFromBeginning", offset as i64),
|
||||
SeekFrom::Current(offset) => ("seekFromCurrent", offset),
|
||||
SeekFrom::End(offset) => ("seekFromEnd", offset),
|
||||
};
|
||||
|
||||
// Call the appropriate seek method safely
|
||||
let success = self
|
||||
.env
|
||||
.borrow_mut()
|
||||
.call_method(&self.input, method, "(J)Z", &[JValue::Long(offset)])
|
||||
.and_then(|result| result.z())
|
||||
.expect("Failed to seek");
|
||||
|
||||
if !success {
|
||||
panic!("Failed to seek");
|
||||
}
|
||||
}
|
||||
|
||||
fn truncate(&mut self, _length: i64) {
|
||||
panic!("JInputStream is read-only");
|
||||
}
|
||||
|
||||
fn tell(&self) -> i64 {
|
||||
let position = self
|
||||
.env
|
||||
.borrow_mut()
|
||||
.call_method(&self.input, "tell", "()J", &[])
|
||||
.and_then(|result| result.j())
|
||||
.expect("Failed to get position");
|
||||
|
||||
if position == i64::MIN {
|
||||
panic!("Failed to get position");
|
||||
}
|
||||
|
||||
position
|
||||
}
|
||||
|
||||
fn length(&self) -> i64 {
|
||||
self.env
|
||||
.borrow_mut()
|
||||
.call_method(&self.input, "length", "()J", &[])
|
||||
.and_then(|result| result.j())
|
||||
.expect("Failed to get length")
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
// Call the Java name() method safely
|
||||
let name = self
|
||||
.env
|
||||
.borrow_mut()
|
||||
.call_method(&self.input, "name", "()Ljava/lang/String;", &[])
|
||||
.and_then(|result| result.l())
|
||||
.expect("Failed to call name() method");
|
||||
|
||||
self.env
|
||||
.borrow_mut()
|
||||
.get_string(&name.into())
|
||||
.expect("Failed to convert Java string")
|
||||
.into()
|
||||
}
|
||||
|
||||
fn is_readonly(&self) -> bool {
|
||||
true // JInputStream is always read-only
|
||||
}
|
||||
}
|
182
musikr/src/main/jni/src/jtagmap.rs
Normal file
182
musikr/src/main/jni/src/jtagmap.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
use jni::{
|
||||
objects::{JClass, JObject, JString, JValueGen},
|
||||
JNIEnv,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct JTagMap<'local> {
|
||||
env: Rc<RefCell<JNIEnv<'local>>>,
|
||||
tag_map: JObject<'local>,
|
||||
array_list_class: JClass<'local>,
|
||||
}
|
||||
|
||||
impl<'local> JTagMap<'local> {
|
||||
pub fn new(env: Rc<RefCell<JNIEnv<'local>>>) -> Self {
|
||||
// Get NativeTagMap class and create instance
|
||||
let tag_map_class = env
|
||||
.borrow_mut()
|
||||
.find_class("org/oxycblt/musikr/metadata/NativeTagMap")
|
||||
.unwrap();
|
||||
let tag_map = env
|
||||
.borrow_mut()
|
||||
.new_object(&tag_map_class, "()V", &[])
|
||||
.unwrap();
|
||||
|
||||
// Get ArrayList class
|
||||
let array_list_class = env.borrow_mut().find_class("java/util/ArrayList").unwrap();
|
||||
|
||||
Self {
|
||||
env,
|
||||
tag_map,
|
||||
array_list_class,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_array_list(&self, values: &[String]) -> JObject<'local> {
|
||||
let mut env = self.env.borrow_mut();
|
||||
let array_list = env.new_object(&self.array_list_class, "()V", &[]).unwrap();
|
||||
|
||||
// Create all JString values first
|
||||
let j_values: Vec<JString> = values
|
||||
.iter()
|
||||
.map(|value| env.new_string(value).unwrap())
|
||||
.collect();
|
||||
|
||||
// Then add them to the ArrayList
|
||||
for j_value in j_values {
|
||||
env.call_method(
|
||||
&array_list,
|
||||
"add",
|
||||
"(Ljava/lang/Object;)Z",
|
||||
&[JValueGen::Object(&j_value)],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
array_list
|
||||
}
|
||||
|
||||
pub fn add_id(&self, id: impl Into<String>, value: impl Into<String>) {
|
||||
let mut env = self.env.borrow_mut();
|
||||
let j_id = env.new_string(id.into()).unwrap();
|
||||
let j_value = env.new_string(value.into()).unwrap();
|
||||
|
||||
env.call_method(
|
||||
&self.tag_map,
|
||||
"addID",
|
||||
"(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
&[JValueGen::Object(&j_id), JValueGen::Object(&j_value)],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_id_list(&self, id: impl Into<String>, values: Vec<String>) {
|
||||
// Create array list first while holding the borrow
|
||||
let j_values = self.create_array_list(&values);
|
||||
|
||||
// Then create the id and make the call with a new borrow
|
||||
let mut env = self.env.borrow_mut();
|
||||
let j_id = env.new_string(id.into()).unwrap();
|
||||
|
||||
env.call_method(
|
||||
&self.tag_map,
|
||||
"addID",
|
||||
"(Ljava/lang/String;Ljava/util/List;)V",
|
||||
&[JValueGen::Object(&j_id), JValueGen::Object(&j_values)],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_custom(&self, description: impl Into<String>, value: impl Into<String>) {
|
||||
let mut env = self.env.borrow_mut();
|
||||
let j_description = env.new_string(description.into()).unwrap();
|
||||
let j_value = env.new_string(value.into()).unwrap();
|
||||
|
||||
env.call_method(
|
||||
&self.tag_map,
|
||||
"addCustom",
|
||||
"(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
&[
|
||||
JValueGen::Object(&j_description),
|
||||
JValueGen::Object(&j_value),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_custom_list(&self, description: impl Into<String>, values: Vec<String>) {
|
||||
let j_values = self.create_array_list(&values);
|
||||
|
||||
let mut env = self.env.borrow_mut();
|
||||
let j_description = env.new_string(description.into()).unwrap();
|
||||
|
||||
env.call_method(
|
||||
&self.tag_map,
|
||||
"addCustom",
|
||||
"(Ljava/lang/String;Ljava/util/List;)V",
|
||||
&[
|
||||
JValueGen::Object(&j_description),
|
||||
JValueGen::Object(&j_values),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_combined(
|
||||
&self,
|
||||
id: impl Into<String>,
|
||||
description: impl Into<String>,
|
||||
value: impl Into<String>,
|
||||
) {
|
||||
let mut env = self.env.borrow_mut();
|
||||
let j_id = env.new_string(id.into()).unwrap();
|
||||
let j_description = env.new_string(description.into()).unwrap();
|
||||
let j_value = env.new_string(value.into()).unwrap();
|
||||
|
||||
env.call_method(
|
||||
&self.tag_map,
|
||||
"addCombined",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
||||
&[
|
||||
JValueGen::Object(&j_id),
|
||||
JValueGen::Object(&j_description),
|
||||
JValueGen::Object(&j_value),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn add_combined_list(
|
||||
&self,
|
||||
id: impl Into<String>,
|
||||
description: impl Into<String>,
|
||||
values: Vec<String>,
|
||||
) {
|
||||
let j_values = self.create_array_list(&values);
|
||||
|
||||
let mut env = self.env.borrow_mut();
|
||||
let j_id = env.new_string(id.into()).unwrap();
|
||||
let j_description = env.new_string(description.into()).unwrap();
|
||||
|
||||
env.call_method(
|
||||
&self.tag_map,
|
||||
"addCombined",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V",
|
||||
&[
|
||||
JValueGen::Object(&j_id),
|
||||
JValueGen::Object(&j_description),
|
||||
JValueGen::Object(&j_values),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn get_object(&self) -> JObject<'local> {
|
||||
let mut env = self.env.borrow_mut();
|
||||
env.call_method(&self.tag_map, "getObject", "()Ljava/util/Map;", &[])
|
||||
.unwrap()
|
||||
.l()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
125
musikr/src/main/jni/src/lib.rs
Normal file
125
musikr/src/main/jni/src/lib.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
extern crate link_cplusplus;
|
||||
use android_logger::Config;
|
||||
use jni::objects::{JClass, JObject};
|
||||
use jni::sys::jobject;
|
||||
use jni::JNIEnv;
|
||||
use log::LevelFilter;
|
||||
use std::cell::RefCell;
|
||||
use std::panic;
|
||||
use std::rc::Rc;
|
||||
|
||||
mod jbuilder;
|
||||
mod jstream;
|
||||
mod jtagmap;
|
||||
mod taglib;
|
||||
|
||||
use jbuilder::JMetadataBuilder;
|
||||
use jstream::JInputStream;
|
||||
use taglib::file_ref::FileRef;
|
||||
type SharedEnv<'local> = Rc<RefCell<JNIEnv<'local>>>;
|
||||
|
||||
// Initialize the logger and panic hook when the library is loaded
|
||||
#[ctor::ctor]
|
||||
fn init() {
|
||||
// Initialize Android logger
|
||||
android_logger::init_once(
|
||||
Config::default()
|
||||
.with_max_level(LevelFilter::Error)
|
||||
.with_tag("musikr"),
|
||||
);
|
||||
|
||||
// Set custom panic hook
|
||||
panic::set_hook(Box::new(|panic_info| {
|
||||
let message = if let Some(s) = panic_info.payload().downcast_ref::<String>() {
|
||||
s.clone()
|
||||
} else if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
|
||||
s.to_string()
|
||||
} else {
|
||||
"Unknown panic message".to_string()
|
||||
};
|
||||
|
||||
let location = if let Some(location) = panic_info.location() {
|
||||
format!(
|
||||
"{}:{}:{}",
|
||||
location.file(),
|
||||
location.line(),
|
||||
location.column()
|
||||
)
|
||||
} else {
|
||||
"Unknown location".to_string()
|
||||
};
|
||||
|
||||
log::error!(target: "musikr", "Panic occurred at {}: {}", location, message);
|
||||
}));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_oxycblt_musikr_metadata_MetadataJNI_openFile<'local>(
|
||||
env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
input: JObject<'local>,
|
||||
) -> jobject {
|
||||
// Create JInputStream from the Java input stream
|
||||
let shared_env = Rc::new(RefCell::new(env));
|
||||
let stream = JInputStream::new(shared_env.clone(), &input);
|
||||
let file_ref = FileRef::new(stream);
|
||||
let file = file_ref.file();
|
||||
let mut jbuilder = JMetadataBuilder::new(shared_env.clone());
|
||||
match file {
|
||||
Some(mut file) => {
|
||||
if let Some(properties) = file.audio_properties() {
|
||||
jbuilder.set_properties(properties);
|
||||
}
|
||||
if let Some(vorbis) = file.as_vorbis() {
|
||||
jbuilder.set_mime_type("audio/ogg");
|
||||
if let Some(mut tag) = vorbis.xiph_comments() {
|
||||
jbuilder.set_xiph(&mut tag);
|
||||
}
|
||||
}
|
||||
if let Some(opus) = file.as_opus() {
|
||||
jbuilder.set_mime_type("audio/opus");
|
||||
if let Some(mut tag) = opus.xiph_comments() {
|
||||
jbuilder.set_xiph(&mut tag);
|
||||
}
|
||||
}
|
||||
if let Some(mut flac) = file.as_flac() {
|
||||
jbuilder.set_mime_type("audio/flac");
|
||||
if let Some(tag) = flac.id3v1_tag() {
|
||||
jbuilder.set_id3v1(&tag);
|
||||
}
|
||||
if let Some(tag) = flac.id3v2_tag() {
|
||||
jbuilder.set_id3v2(&tag);
|
||||
}
|
||||
if let Some(mut tag) = flac.xiph_comments() {
|
||||
jbuilder.set_xiph(&mut tag);
|
||||
}
|
||||
jbuilder.set_flac_pictures(&flac.picture_list());
|
||||
}
|
||||
if let Some(mut mpeg) = file.as_mpeg() {
|
||||
jbuilder.set_mime_type("audio/mpeg");
|
||||
if let Some(tag) = mpeg.id3v1_tag() {
|
||||
jbuilder.set_id3v1(&tag);
|
||||
}
|
||||
if let Some(tag) = mpeg.id3v2_tag() {
|
||||
jbuilder.set_id3v2(&tag);
|
||||
}
|
||||
}
|
||||
if let Some(mp4) = file.as_mp4() {
|
||||
jbuilder.set_mime_type("audio/mp4");
|
||||
if let Some(tag) = mp4.tag() {
|
||||
jbuilder.set_mp4(&tag);
|
||||
}
|
||||
}
|
||||
if let Some(mut wav) = file.as_wav() {
|
||||
jbuilder.set_mime_type("audio/wav");
|
||||
if let Some(tag) = wav.id3v2_tag() {
|
||||
jbuilder.set_id3v2(&tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
let metadata = jbuilder.build();
|
||||
metadata.into_raw()
|
||||
}
|
28
musikr/src/main/jni/src/taglib/audioproperties.rs
Normal file
28
musikr/src/main/jni/src/taglib/audioproperties.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use super::bridge::CppAudioProperties;
|
||||
use super::this::RefThis;
|
||||
|
||||
pub struct AudioProperties<'file_ref> {
|
||||
this: RefThis<'file_ref, CppAudioProperties>,
|
||||
}
|
||||
|
||||
impl<'file_ref> AudioProperties<'file_ref> {
|
||||
pub(super) fn new(this: RefThis<'file_ref, CppAudioProperties>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn length_in_milliseconds(&self) -> i32 {
|
||||
self.this.as_ref().lengthInMilliseconds()
|
||||
}
|
||||
|
||||
pub fn bitrate(&self) -> i32 {
|
||||
self.this.as_ref().bitrate()
|
||||
}
|
||||
|
||||
pub fn sample_rate(&self) -> i32 {
|
||||
self.this.as_ref().sampleRate()
|
||||
}
|
||||
|
||||
pub fn channels(&self) -> i32 {
|
||||
self.this.as_ref().channels()
|
||||
}
|
||||
}
|
497
musikr/src/main/jni/src/taglib/bridge.rs
Normal file
497
musikr/src/main/jni/src/taglib/bridge.rs
Normal file
|
@ -0,0 +1,497 @@
|
|||
use super::iostream::DynIOStream;
|
||||
|
||||
pub trait TagLibAllocated {}
|
||||
|
||||
pub trait TagLibRef {}
|
||||
|
||||
pub trait TagLibShared {}
|
||||
|
||||
#[cxx::bridge]
|
||||
mod bridge_impl {
|
||||
// Expose Rust IOStream to C++
|
||||
extern "Rust" {
|
||||
#[cxx_name = "RsIOStream"]
|
||||
type DynIOStream<'io_stream>;
|
||||
|
||||
fn name(self: &DynIOStream<'_>) -> String;
|
||||
fn read(self: &mut DynIOStream<'_>, buffer: &mut [u8]) -> usize;
|
||||
fn write(self: &mut DynIOStream<'_>, data: &[u8]);
|
||||
fn seek(self: &mut DynIOStream<'_>, offset: i64, whence: i32);
|
||||
fn truncate(self: &mut DynIOStream<'_>, length: i64);
|
||||
fn tell(self: &DynIOStream<'_>) -> i64;
|
||||
fn length(self: &DynIOStream<'_>) -> i64;
|
||||
fn is_readonly(self: &DynIOStream<'_>) -> bool;
|
||||
}
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe extern "C++" {
|
||||
include!("taglib/taglib.h");
|
||||
include!("taglib/tstring.h");
|
||||
include!("taglib/tstringlist.h");
|
||||
include!("taglib/vorbisfile.h");
|
||||
include!("taglib/xiphcomment.h");
|
||||
include!("taglib/tiostream.h");
|
||||
include!("taglib/flacpicture.h");
|
||||
include!("taglib/tbytevector.h");
|
||||
include!("taglib/mp4file.h");
|
||||
include!("taglib/mp4tag.h");
|
||||
include!("taglib/mp4item.h");
|
||||
include!("shim/iostream_shim.hpp");
|
||||
include!("shim/file_shim.hpp");
|
||||
include!("shim/tk_shim.hpp");
|
||||
include!("shim/picture_shim.hpp");
|
||||
include!("shim/xiph_shim.hpp");
|
||||
include!("shim/id3v2_shim.hpp");
|
||||
include!("shim/id3v1_shim.hpp");
|
||||
include!("shim/mp4_shim.hpp");
|
||||
include!("taglib/mpegfile.h");
|
||||
|
||||
// CORE
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "IOStream"]
|
||||
type CPPIOStream<'io_stream>;
|
||||
unsafe fn wrap_RsIOStream<'io_stream>(
|
||||
stream: *mut DynIOStream<'io_stream>,
|
||||
) -> UniquePtr<CPPIOStream<'io_stream>>;
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "FileRef"]
|
||||
type CPPFileRef;
|
||||
unsafe fn new_FileRef(stream: *mut CPPIOStream) -> UniquePtr<CPPFileRef>;
|
||||
fn isNull(self: &CPPFileRef) -> bool;
|
||||
fn file(self: &CPPFileRef) -> *mut CPPFile;
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "File"]
|
||||
type CPPFile;
|
||||
fn audioProperties(self: &CPPFile) -> *mut CppAudioProperties;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn File_asVorbis(file: *mut CPPFile) -> *mut CPPVorbisFile;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn File_asOpus(file: *mut CPPFile) -> *mut CPPOpusFile;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn File_asMPEG(file: *mut CPPFile) -> *mut CPPMPEGFile;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn File_asFLAC(file: *mut CPPFile) -> *mut CPPFLACFile;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn File_asMP4(file: *mut CPPFile) -> *mut CPPMP4File;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn File_asWAV(file: *mut CPPFile) -> *mut CPPWAVFile;
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "AudioProperties"]
|
||||
type CppAudioProperties;
|
||||
fn lengthInMilliseconds(self: &CppAudioProperties) -> i32;
|
||||
fn bitrate(self: &CppAudioProperties) -> i32;
|
||||
fn sampleRate(self: &CppAudioProperties) -> i32;
|
||||
fn channels(self: &CppAudioProperties) -> i32;
|
||||
|
||||
// XIPH
|
||||
|
||||
#[namespace = "TagLib::Ogg::Vorbis"]
|
||||
#[cxx_name = "File"]
|
||||
type CPPVorbisFile;
|
||||
#[cxx_name = "tag"]
|
||||
fn vorbisTag(self: &CPPVorbisFile) -> *mut CPPXiphComment;
|
||||
|
||||
#[namespace = "TagLib::Ogg::Opus"]
|
||||
#[cxx_name = "File"]
|
||||
type CPPOpusFile;
|
||||
#[cxx_name = "tag"]
|
||||
fn opusTag(self: &CPPOpusFile) -> *mut CPPXiphComment;
|
||||
|
||||
#[namespace = "TagLib::FLAC"]
|
||||
#[cxx_name = "File"]
|
||||
type CPPFLACFile;
|
||||
fn xiphComment(self: Pin<&mut CPPFLACFile>, create: bool) -> *mut CPPXiphComment;
|
||||
#[cxx_name = "ID3v1Tag"]
|
||||
fn FLACID3v1Tag(self: Pin<&mut CPPFLACFile>, create: bool) -> *mut CPPID3v1Tag;
|
||||
#[cxx_name = "ID3v2Tag"]
|
||||
fn FLACID3v2Tag(self: Pin<&mut CPPFLACFile>, create: bool) -> *mut CPPID3v2Tag;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn FLACFile_pictureList(file: Pin<&mut CPPFLACFile>) -> UniquePtr<CPPPictureList>;
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
#[cxx_name = "PictureList"]
|
||||
type CPPPictureList;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn PictureList_to_vector(
|
||||
list: &CPPPictureList,
|
||||
) -> UniquePtr<CxxVector<CPPFLACPicturePointer>>;
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
#[cxx_name = "PicturePointer"]
|
||||
type CPPFLACPicturePointer;
|
||||
fn get(self: &CPPFLACPicturePointer) -> *const CPPFLACPicture;
|
||||
|
||||
#[namespace = "TagLib::FLAC"]
|
||||
#[cxx_name = "Picture"]
|
||||
type CPPFLACPicture;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Picture_type(picture: &CPPFLACPicture) -> u32;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Picture_data(picture: &CPPFLACPicture) -> UniquePtr<CPPByteVector>;
|
||||
// XIPHComment
|
||||
|
||||
#[namespace = "TagLib::Ogg"]
|
||||
#[cxx_name = "XiphComment"]
|
||||
type CPPXiphComment;
|
||||
// Explicit lifecycle definition to state while the Pin is temporary, the CPPFieldListMap
|
||||
// ref returned actually has the same lifetime as the CPPXiphComment.
|
||||
fn fieldListMap(self: &CPPXiphComment) -> &CPPFieldListMap;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn XiphComment_pictureList(comment: Pin<&mut CPPXiphComment>) -> UniquePtr<CPPPictureList>;
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "SimplePropertyMap"]
|
||||
type CPPFieldListMap;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn FieldListMap_to_entries(
|
||||
field_list_map: &CPPFieldListMap,
|
||||
) -> UniquePtr<CxxVector<CPPFieldListEntry>>;
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
#[cxx_name = "FieldListEntry"]
|
||||
type CPPFieldListEntry;
|
||||
fn key(self: &CPPFieldListEntry) -> UniquePtr<CPPString>;
|
||||
fn value(self: &CPPFieldListEntry) -> UniquePtr<CPPStringList>;
|
||||
|
||||
// MPEG
|
||||
|
||||
#[namespace = "TagLib::MPEG"]
|
||||
#[cxx_name = "File"]
|
||||
type CPPMPEGFile;
|
||||
#[cxx_name = "ID3v1Tag"]
|
||||
fn MPEGID3v1Tag(self: Pin<&mut CPPMPEGFile>, create: bool) -> *mut CPPID3v1Tag;
|
||||
#[cxx_name = "ID3v2Tag"]
|
||||
fn MPEGID3v2Tag(self: Pin<&mut CPPMPEGFile>, create: bool) -> *mut CPPID3v2Tag;
|
||||
|
||||
// MP4
|
||||
|
||||
#[namespace = "TagLib::MP4"]
|
||||
#[cxx_name = "File"]
|
||||
type CPPMP4File;
|
||||
#[cxx_name = "tag"]
|
||||
fn MP4Tag(self: &CPPMP4File) -> *mut CPPMP4Tag;
|
||||
|
||||
#[namespace = "TagLib::MP4"]
|
||||
#[cxx_name = "Tag"]
|
||||
type CPPMP4Tag;
|
||||
|
||||
#[namespace = "TagLib::MP4"]
|
||||
#[cxx_name = "ItemMap"]
|
||||
type CPPItemMap;
|
||||
fn itemMap(self: &CPPMP4Tag) -> &CPPItemMap;
|
||||
fn ItemMap_to_entries(map: &CPPItemMap) -> UniquePtr<CxxVector<CPPItemMapEntry>>;
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
#[cxx_name = "ItemMapEntry"]
|
||||
type CPPItemMapEntry;
|
||||
fn key(self: &CPPItemMapEntry) -> UniquePtr<CPPString>;
|
||||
fn value(self: &CPPItemMapEntry) -> UniquePtr<CPPMP4Item>;
|
||||
|
||||
#[namespace = "TagLib::MP4"]
|
||||
#[cxx_name = "Item"]
|
||||
type CPPMP4Item;
|
||||
fn isValid(self: &CPPMP4Item) -> bool;
|
||||
fn toBool(self: &CPPMP4Item) -> bool;
|
||||
fn toInt(self: &CPPMP4Item) -> i32;
|
||||
fn toByte(self: &CPPMP4Item) -> u8;
|
||||
fn toUInt(self: &CPPMP4Item) -> u32;
|
||||
|
||||
fn Item_type(item: &CPPMP4Item) -> u32;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Item_toIntPair(item: &CPPMP4Item) -> UniquePtr<CPPIntPair>;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Item_toStringList(item: &CPPMP4Item) -> UniquePtr<CPPStringList>;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Item_toByteVectorList(item: &CPPMP4Item) -> UniquePtr<CPPByteVectorList>;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Item_toCoverArtList(item: &CPPMP4Item) -> UniquePtr<CPPCoverArtList>;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Item_toLongLong(item: &CPPMP4Item) -> i64;
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
#[cxx_name = "IntPair"]
|
||||
type CPPIntPair;
|
||||
fn first(self: &CPPIntPair) -> i32;
|
||||
fn second(self: &CPPIntPair) -> i32;
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
#[cxx_name = "CoverArtList"]
|
||||
type CPPCoverArtList;
|
||||
fn to_vector(self: &CPPCoverArtList) -> UniquePtr<CxxVector<CPPCoverArt>>;
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
#[cxx_name = "CoverArt"]
|
||||
type CPPCoverArt;
|
||||
fn format(self: &CPPCoverArt) -> u32;
|
||||
fn data(self: &CPPCoverArt) -> UniquePtr<CPPByteVector>;
|
||||
|
||||
#[namespace = "TagLib::RIFF::WAV"]
|
||||
#[cxx_name = "File"]
|
||||
type CPPWAVFile;
|
||||
#[cxx_name = "ID3v2Tag"]
|
||||
fn WAVID3v2Tag(self: &CPPWAVFile) -> *mut CPPID3v2Tag;
|
||||
|
||||
// ID3v1
|
||||
|
||||
#[namespace = "TagLib::ID3v1"]
|
||||
#[cxx_name = "Tag"]
|
||||
type CPPID3v1Tag;
|
||||
|
||||
fn ID3v1Tag_title(tag: &CPPID3v1Tag) -> UniquePtr<CPPString>;
|
||||
fn ID3v1Tag_artist(tag: &CPPID3v1Tag) -> UniquePtr<CPPString>;
|
||||
fn ID3v1Tag_album(tag: &CPPID3v1Tag) -> UniquePtr<CPPString>;
|
||||
fn ID3v1Tag_comment(tag: &CPPID3v1Tag) -> UniquePtr<CPPString>;
|
||||
fn ID3v1Tag_genreIndex(tag: &CPPID3v1Tag) -> u32;
|
||||
fn ID3v1Tag_year(tag: &CPPID3v1Tag) -> u32;
|
||||
fn ID3v1Tag_track(tag: &CPPID3v1Tag) -> u32;
|
||||
|
||||
// ID3v2
|
||||
|
||||
#[namespace = "TagLib::ID3v2"]
|
||||
#[cxx_name = "Tag"]
|
||||
type CPPID3v2Tag;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Tag_frameList(tag: &CPPID3v2Tag) -> UniquePtr<CPPID3v2FrameList>;
|
||||
|
||||
#[namespace = "TagLib::ID3v2"]
|
||||
#[cxx_name = "FrameList"]
|
||||
type CPPID3v2FrameList;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn FrameList_to_vector(list: &CPPID3v2FrameList) -> UniquePtr<CxxVector<CPPFramePointer>>;
|
||||
|
||||
#[namespace = "taglib_shim"]
|
||||
#[cxx_name = "FramePointer"]
|
||||
type CPPFramePointer;
|
||||
fn get(self: &CPPFramePointer) -> *const CPPID3v2Frame;
|
||||
|
||||
#[namespace = "TagLib::ID3v2"]
|
||||
#[cxx_name = "Frame"]
|
||||
type CPPID3v2Frame;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn Frame_id(frame: &CPPID3v2Frame) -> UniquePtr<CPPByteVector>;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn Frame_asTextIdentification(
|
||||
frame: *const CPPID3v2Frame,
|
||||
) -> *const CPPID3v2TextIdentificationFrame;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn Frame_asUserTextIdentification(
|
||||
frame: *const CPPID3v2Frame,
|
||||
) -> *const CPPID3v2UserTextIdentificationFrame;
|
||||
#[namespace = "taglib_shim"]
|
||||
unsafe fn Frame_asAttachedPicture(
|
||||
frame: *const CPPID3v2Frame,
|
||||
) -> *const CPPID3v2AttachedPictureFrame;
|
||||
|
||||
#[namespace = "TagLib::ID3v2"]
|
||||
#[cxx_name = "TextIdentificationFrame"]
|
||||
type CPPID3v2TextIdentificationFrame;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn TextIdentificationFrame_fieldList(
|
||||
frame: &CPPID3v2TextIdentificationFrame,
|
||||
) -> UniquePtr<CPPStringList>;
|
||||
|
||||
#[namespace = "TagLib::ID3v2"]
|
||||
#[cxx_name = "UserTextIdentificationFrame"]
|
||||
type CPPID3v2UserTextIdentificationFrame;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn UserTextIdentificationFrame_fieldList(
|
||||
frame: &CPPID3v2UserTextIdentificationFrame,
|
||||
) -> UniquePtr<CPPStringList>;
|
||||
|
||||
#[namespace = "TagLib::ID3v2"]
|
||||
#[cxx_name = "AttachedPictureFrame"]
|
||||
type CPPID3v2AttachedPictureFrame;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn AttachedPictureFrame_type(frame: &CPPID3v2AttachedPictureFrame) -> u32;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn AttachedPictureFrame_picture(
|
||||
frame: &CPPID3v2AttachedPictureFrame,
|
||||
) -> UniquePtr<CPPByteVector>;
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "String"]
|
||||
type CPPString;
|
||||
fn toCString(self: &CPPString, unicode: bool) -> *const c_char;
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "StringList"]
|
||||
type CPPStringList;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn StringList_to_vector(string_list: &CPPStringList) -> UniquePtr<CxxVector<CPPString>>;
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "ByteVector"]
|
||||
type CPPByteVector;
|
||||
fn size(self: &CPPByteVector) -> u32;
|
||||
fn data(self: &CPPByteVector) -> *const c_char;
|
||||
|
||||
#[namespace = "TagLib"]
|
||||
#[cxx_name = "ByteVectorList"]
|
||||
type CPPByteVectorList;
|
||||
#[namespace = "taglib_shim"]
|
||||
fn ByteVectorList_to_vector(
|
||||
list: &CPPByteVectorList,
|
||||
) -> UniquePtr<CxxVector<CPPByteVector>>;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum PictureType {
|
||||
Other,
|
||||
FileIcon,
|
||||
OtherFileIcon,
|
||||
FrontCover,
|
||||
BackCover,
|
||||
LeafletPage,
|
||||
Media,
|
||||
LeadArtist,
|
||||
Artist,
|
||||
Conductor,
|
||||
Band,
|
||||
Composer,
|
||||
Lyricist,
|
||||
RecordingLocation,
|
||||
DuringRecording,
|
||||
DuringPerformance,
|
||||
MovieScreenCapture,
|
||||
ColoredFish,
|
||||
Illustration,
|
||||
BandLogo,
|
||||
PublisherLogo,
|
||||
}
|
||||
|
||||
impl PictureType {
|
||||
pub fn from_u32(value: u32) -> Option<Self> {
|
||||
match value {
|
||||
0 => Some(Self::Other),
|
||||
1 => Some(Self::FileIcon),
|
||||
2 => Some(Self::OtherFileIcon),
|
||||
3 => Some(Self::FrontCover),
|
||||
4 => Some(Self::BackCover),
|
||||
5 => Some(Self::LeafletPage),
|
||||
6 => Some(Self::Media),
|
||||
7 => Some(Self::LeadArtist),
|
||||
8 => Some(Self::Artist),
|
||||
9 => Some(Self::Conductor),
|
||||
10 => Some(Self::Band),
|
||||
11 => Some(Self::Composer),
|
||||
12 => Some(Self::Lyricist),
|
||||
13 => Some(Self::RecordingLocation),
|
||||
14 => Some(Self::DuringRecording),
|
||||
15 => Some(Self::DuringPerformance),
|
||||
16 => Some(Self::MovieScreenCapture),
|
||||
17 => Some(Self::ColoredFish),
|
||||
18 => Some(Self::Illustration),
|
||||
19 => Some(Self::BandLogo),
|
||||
20 => Some(Self::PublisherLogo),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum MP4ItemType {
|
||||
Void,
|
||||
Bool,
|
||||
Int,
|
||||
IntPair,
|
||||
Byte,
|
||||
UInt,
|
||||
LongLong,
|
||||
StringList,
|
||||
ByteVectorList,
|
||||
CoverArtList,
|
||||
}
|
||||
|
||||
impl MP4ItemType {
|
||||
pub fn from_u32(value: u32) -> Option<Self> {
|
||||
match value {
|
||||
0 => Some(Self::Void),
|
||||
1 => Some(Self::Bool),
|
||||
2 => Some(Self::Int),
|
||||
3 => Some(Self::IntPair),
|
||||
4 => Some(Self::Byte),
|
||||
5 => Some(Self::UInt),
|
||||
6 => Some(Self::LongLong),
|
||||
7 => Some(Self::StringList),
|
||||
8 => Some(Self::ByteVectorList),
|
||||
9 => Some(Self::CoverArtList),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use bridge_impl::*;
|
||||
|
||||
impl TagLibAllocated for CPPFileRef {}
|
||||
|
||||
impl TagLibRef for CPPFile {}
|
||||
|
||||
impl TagLibRef for CppAudioProperties {}
|
||||
|
||||
// All of the File implementations are also TagLibRef and TagLibAllocated
|
||||
|
||||
impl TagLibRef for CPPVorbisFile {}
|
||||
|
||||
impl TagLibRef for CPPOpusFile {}
|
||||
|
||||
impl TagLibRef for CPPFLACFile {}
|
||||
|
||||
impl TagLibShared for CPPPictureList {}
|
||||
|
||||
impl TagLibShared for CPPFLACPicture {}
|
||||
|
||||
impl TagLibRef for CPPMPEGFile {}
|
||||
|
||||
impl TagLibRef for CPPMP4File {}
|
||||
|
||||
impl TagLibRef for CPPMP4Tag {}
|
||||
|
||||
impl TagLibRef for CPPItemMap {}
|
||||
|
||||
impl TagLibRef for CPPItemMapEntry {}
|
||||
|
||||
impl TagLibRef for CPPFieldListMap {}
|
||||
|
||||
impl TagLibRef for CPPFieldListEntry {}
|
||||
|
||||
impl TagLibRef for CPPWAVFile {}
|
||||
|
||||
impl TagLibRef for CPPXiphComment {}
|
||||
|
||||
impl TagLibRef for CPPID3v1Tag {}
|
||||
|
||||
impl TagLibRef for CPPID3v2Tag {}
|
||||
|
||||
impl TagLibShared for CPPID3v2FrameList {}
|
||||
|
||||
impl TagLibRef for CPPID3v2Frame {}
|
||||
|
||||
impl TagLibRef for CPPID3v2TextIdentificationFrame {}
|
||||
|
||||
impl TagLibRef for CPPID3v2UserTextIdentificationFrame {}
|
||||
|
||||
impl TagLibRef for CPPID3v2AttachedPictureFrame {}
|
||||
|
||||
impl TagLibShared for CPPMP4Item {}
|
||||
|
||||
impl TagLibShared for CPPCoverArt {}
|
||||
|
||||
impl TagLibShared for CPPIntPair {}
|
||||
|
||||
impl TagLibShared for CPPString {}
|
||||
|
||||
impl TagLibShared for CPPStringList {}
|
||||
|
||||
impl TagLibShared for CPPByteVector {}
|
||||
|
||||
impl TagLibShared for CPPByteVectorList {}
|
||||
|
||||
impl TagLibShared for CPPCoverArtList {}
|
||||
|
||||
impl<T: TagLibShared> TagLibRef for T {}
|
||||
impl<T: TagLibRef> TagLibAllocated for T {}
|
132
musikr/src/main/jni/src/taglib/file.rs
Normal file
132
musikr/src/main/jni/src/taglib/file.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use super::audioproperties::AudioProperties;
|
||||
use super::bridge::{self, CPPFile};
|
||||
use super::flac::FLACFile;
|
||||
use super::mp4::MP4File;
|
||||
use super::mpeg::MPEGFile;
|
||||
use super::ogg::OpusFile;
|
||||
use super::ogg::VorbisFile;
|
||||
use super::riff::WAVFile;
|
||||
use super::this::{RefThis, RefThisMut};
|
||||
|
||||
pub struct File<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPFile>,
|
||||
}
|
||||
|
||||
impl<'file_ref> File<'file_ref> {
|
||||
pub(super) fn new(this: RefThisMut<'file_ref, CPPFile>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn audio_properties(&self) -> Option<AudioProperties<'file_ref>> {
|
||||
let props_ptr = self.this.as_ref().audioProperties();
|
||||
let props_ref = unsafe {
|
||||
// SAFETY:
|
||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||
// - The null-safe version is being used.
|
||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||
// - There are no datapaths that will yield any mutable pointers or references
|
||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||
props_ptr.as_ref()
|
||||
};
|
||||
let props_this = props_ref.map(|props| RefThis::new(props));
|
||||
props_this.map(|this| AudioProperties::new(this))
|
||||
}
|
||||
|
||||
pub fn as_opus(&mut self) -> Option<OpusFile<'file_ref>> {
|
||||
let opus_file = unsafe {
|
||||
// SAFETY:
|
||||
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
||||
// the file can be cased down to an opus file. If the cast fails, a null
|
||||
// pointer is returned, which will be handled by as_ref's null checking.
|
||||
bridge::File_asOpus(self.this.ptr_mut())
|
||||
};
|
||||
let opus_ref = unsafe {
|
||||
// SAFETY:
|
||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||
// - The null-safe version is being used.
|
||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||
// - There are no datapaths that will yield any mutable pointers or references
|
||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||
opus_file.as_mut()
|
||||
};
|
||||
let opus_this = opus_ref.map(|opus| RefThisMut::new(opus));
|
||||
opus_this.map(|this| OpusFile::new(this))
|
||||
}
|
||||
|
||||
pub fn as_vorbis(&mut self) -> Option<VorbisFile<'file_ref>> {
|
||||
let vorbis_file = unsafe {
|
||||
// SAFETY:
|
||||
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
||||
// the file can be cased down to an opus file. If the cast fails, a null
|
||||
// pointer is returned, which will be handled by as_ref's null checking.
|
||||
bridge::File_asVorbis(self.this.ptr_mut())
|
||||
};
|
||||
let vorbis_ref = unsafe {
|
||||
// SAFETY:
|
||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||
// - The null-safe version is being used.
|
||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||
// - There are no datapaths that will yield any mutable pointers or references
|
||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||
vorbis_file.as_mut()
|
||||
};
|
||||
let vorbis_this = vorbis_ref.map(|vorbis| RefThisMut::new(vorbis));
|
||||
vorbis_this.map(|this| VorbisFile::new(this))
|
||||
}
|
||||
|
||||
pub fn as_flac(&mut self) -> Option<FLACFile<'file_ref>> {
|
||||
let flac_file = unsafe {
|
||||
// SAFETY:
|
||||
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
||||
// the file can be cased down to an opus file. If the cast fails, a null
|
||||
// pointer is returned, which will be handled by as_ref's null checking.
|
||||
bridge::File_asFLAC(self.this.ptr_mut())
|
||||
};
|
||||
let flac_ref = unsafe {
|
||||
// SAFETY:
|
||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||
// - The null-safe version is being used.
|
||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||
// - There are no datapaths that will yield any mutable pointers or references
|
||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||
flac_file.as_mut()
|
||||
};
|
||||
let flac_this = flac_ref.map(|flac| RefThisMut::new(flac));
|
||||
flac_this.map(|this| FLACFile::new(this))
|
||||
}
|
||||
|
||||
pub fn as_mpeg(&mut self) -> Option<MPEGFile<'file_ref>> {
|
||||
let mpeg_file = unsafe {
|
||||
// SAFETY:
|
||||
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
||||
// the file can be cased down to an MPEG file. If the cast fails, a null
|
||||
// pointer is returned, which will be handled by as_ref's null checking.
|
||||
bridge::File_asMPEG(self.this.ptr_mut())
|
||||
};
|
||||
let mpeg_ref = unsafe {
|
||||
// SAFETY:
|
||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||
// - The null-safe version is being used.
|
||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||
// - There are no datapaths that will yield any mutable pointers or references
|
||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||
mpeg_file.as_mut()
|
||||
};
|
||||
let mpeg_this = mpeg_ref.map(|mpeg| RefThisMut::new(mpeg));
|
||||
mpeg_this.map(|this| MPEGFile::new(this))
|
||||
}
|
||||
|
||||
pub fn as_mp4(&mut self) -> Option<MP4File<'file_ref>> {
|
||||
let mp4_file = unsafe { bridge::File_asMP4(self.this.ptr_mut()) };
|
||||
let mp4_ref = unsafe { mp4_file.as_mut() };
|
||||
let mp4_this = mp4_ref.map(|mp4| RefThisMut::new(mp4));
|
||||
mp4_this.map(|this| MP4File::new(this))
|
||||
}
|
||||
|
||||
pub fn as_wav(&mut self) -> Option<WAVFile<'file_ref>> {
|
||||
let wav_file = unsafe { bridge::File_asWAV(self.this.ptr_mut()) };
|
||||
let wav_ref = unsafe { wav_file.as_mut() };
|
||||
let wav_this = wav_ref.map(|wav| RefThisMut::new(wav));
|
||||
wav_this.map(|this| WAVFile::new(this))
|
||||
}
|
||||
}
|
54
musikr/src/main/jni/src/taglib/file_ref.rs
Normal file
54
musikr/src/main/jni/src/taglib/file_ref.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use super::bridge::{self, CPPFileRef};
|
||||
use super::file::File;
|
||||
use super::iostream::{BridgedIOStream, IOStream};
|
||||
use super::this::RefThisMut;
|
||||
use cxx::UniquePtr;
|
||||
use std::pin::Pin;
|
||||
|
||||
pub struct FileRef<'io> {
|
||||
stream: BridgedIOStream<'io>,
|
||||
this: UniquePtr<CPPFileRef>,
|
||||
}
|
||||
|
||||
impl<'io> FileRef<'io> {
|
||||
pub fn new<T: IOStream + 'io>(stream: T) -> FileRef<'io> {
|
||||
let stream = BridgedIOStream::new(stream);
|
||||
let cpp_stream = stream.cpp_stream().as_mut_ptr();
|
||||
let file_ref = unsafe { bridge::new_FileRef(cpp_stream) };
|
||||
FileRef {
|
||||
stream,
|
||||
this: file_ref,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file<'file_ref>(&'file_ref self) -> Option<File<'file_ref>> {
|
||||
let file_ptr = unsafe {
|
||||
// SAFETY:
|
||||
// - This pin is only used in this unsafe scope.
|
||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||
// not change address by C++ semantics.
|
||||
// - The file data is a pointer that does not depend on the
|
||||
// address of self.
|
||||
let this = Pin::new_unchecked(&*self.this);
|
||||
// Note: This is not the rust ptr "is_null", but a taglib isNull method
|
||||
// that checks for file validity. Without this check, we can get corrupted
|
||||
// file ptrs.
|
||||
if !this.isNull() {
|
||||
Some(this.file())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let file_ref = file_ptr.and_then(|file| unsafe {
|
||||
// SAFETY:
|
||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||
// - The null-safe version is being used.
|
||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||
// - There are no datapaths that will yield any mutable pointers or references
|
||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||
file.as_mut()
|
||||
});
|
||||
let file_this = file_ref.map(|file| RefThisMut::new(file));
|
||||
file_this.map(|this| File::new(this))
|
||||
}
|
||||
}
|
95
musikr/src/main/jni/src/taglib/flac.rs
Normal file
95
musikr/src/main/jni/src/taglib/flac.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
pub use super::bridge::CPPFLACFile;
|
||||
pub use super::bridge::CPPFLACPicture;
|
||||
pub use super::bridge::PictureType;
|
||||
use super::bridge::{
|
||||
CPPPictureList, FLACFile_pictureList, PictureList_to_vector, Picture_data, Picture_type,
|
||||
};
|
||||
use super::id3v1::ID3v1Tag;
|
||||
use super::id3v2::ID3v2Tag;
|
||||
use super::this::{OwnedThis, RefThis, RefThisMut, ThisMut};
|
||||
use super::tk::{ByteVector, OwnedByteVector};
|
||||
use super::xiph::XiphComment;
|
||||
|
||||
pub struct FLACFile<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPFLACFile>,
|
||||
}
|
||||
|
||||
impl<'file_ref> FLACFile<'file_ref> {
|
||||
pub(super) fn new(this: RefThisMut<'file_ref, CPPFLACFile>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn xiph_comments(&mut self) -> Option<XiphComment<'file_ref>> {
|
||||
let tag = self.this.pin_mut().xiphComment(false);
|
||||
let tag_ref = unsafe {
|
||||
// SAFETY: This pointer is a valid type, and can only used and accessed
|
||||
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
||||
tag.as_mut()
|
||||
};
|
||||
let tag_this = tag_ref.map(|tag| RefThisMut::new(tag));
|
||||
tag_this.map(|this| XiphComment::new(this))
|
||||
}
|
||||
|
||||
pub fn picture_list(&mut self) -> FLACPictureList<'file_ref> {
|
||||
let pictures = FLACFile_pictureList(self.this.pin_mut());
|
||||
let this = OwnedThis::new(pictures).unwrap();
|
||||
FLACPictureList::new(this)
|
||||
}
|
||||
|
||||
pub fn id3v1_tag(&mut self) -> Option<ID3v1Tag<'file_ref>> {
|
||||
let tag = self.this.pin_mut().FLACID3v1Tag(false);
|
||||
let tag_ref = unsafe { tag.as_mut() };
|
||||
let tag_this = tag_ref.map(|tag| RefThisMut::new(tag));
|
||||
tag_this.map(|this| ID3v1Tag::new(this))
|
||||
}
|
||||
|
||||
pub fn id3v2_tag(&mut self) -> Option<ID3v2Tag<'file_ref>> {
|
||||
let tag = self.this.pin_mut().FLACID3v2Tag(false);
|
||||
let tag_ref = unsafe { tag.as_mut() };
|
||||
let tag_this = tag_ref.map(|tag| RefThisMut::new(tag));
|
||||
tag_this.map(|this| ID3v2Tag::new(this))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FLACPictureList<'file_ref> {
|
||||
this: OwnedThis<'file_ref, CPPPictureList>,
|
||||
}
|
||||
|
||||
impl<'file_ref> FLACPictureList<'file_ref> {
|
||||
pub(super) fn new(this: OwnedThis<'file_ref, CPPPictureList>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<FLACPicture<'file_ref>> {
|
||||
let pictures = PictureList_to_vector(self.this.as_ref());
|
||||
let mut result = Vec::new();
|
||||
for picture_ptr in pictures.iter() {
|
||||
let picture_ptr = picture_ptr.get();
|
||||
let picture_ref = unsafe {
|
||||
// SAFETY: This pointer is a valid type, and can only used and accessed
|
||||
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
||||
picture_ptr.as_ref().unwrap()
|
||||
};
|
||||
let picture_this = RefThis::new(picture_ref);
|
||||
result.push(FLACPicture { this: picture_this });
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FLACPicture<'file_ref> {
|
||||
this: RefThis<'file_ref, CPPFLACPicture>,
|
||||
}
|
||||
|
||||
impl<'file_ref> FLACPicture<'file_ref> {
|
||||
pub fn picture_type(&self) -> Option<PictureType> {
|
||||
let picture_type = Picture_type(self.this.as_ref());
|
||||
PictureType::from_u32(picture_type)
|
||||
}
|
||||
|
||||
pub fn data(&self) -> OwnedByteVector<'file_ref> {
|
||||
let data = Picture_data(self.this.as_ref());
|
||||
let this = OwnedThis::new(data).unwrap();
|
||||
ByteVector::new(this)
|
||||
}
|
||||
}
|
49
musikr/src/main/jni/src/taglib/id3v1.rs
Normal file
49
musikr/src/main/jni/src/taglib/id3v1.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use super::bridge::{self, CPPID3v1Tag};
|
||||
use super::this::{OwnedThis, RefThisMut};
|
||||
use super::tk::{OwnedString, String};
|
||||
|
||||
pub struct ID3v1Tag<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPID3v1Tag>,
|
||||
}
|
||||
|
||||
impl<'file_ref> ID3v1Tag<'file_ref> {
|
||||
pub(super) fn new(this: RefThisMut<'file_ref, CPPID3v1Tag>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn title(&self) -> OwnedString<'file_ref> {
|
||||
let title = bridge::ID3v1Tag_title(self.this.as_ref());
|
||||
let this = OwnedThis::new(title).unwrap();
|
||||
String::new(this)
|
||||
}
|
||||
|
||||
pub fn artist(&self) -> OwnedString<'file_ref> {
|
||||
let artist = bridge::ID3v1Tag_artist(self.this.as_ref());
|
||||
let this = OwnedThis::new(artist).unwrap();
|
||||
String::new(this)
|
||||
}
|
||||
|
||||
pub fn album(&self) -> OwnedString<'file_ref> {
|
||||
let album = bridge::ID3v1Tag_album(self.this.as_ref());
|
||||
let this = OwnedThis::new(album).unwrap();
|
||||
String::new(this)
|
||||
}
|
||||
|
||||
pub fn comment(&self) -> OwnedString<'file_ref> {
|
||||
let comment = bridge::ID3v1Tag_comment(self.this.as_ref());
|
||||
let this = OwnedThis::new(comment).unwrap();
|
||||
String::new(this)
|
||||
}
|
||||
|
||||
pub fn genre_index(&self) -> u32 {
|
||||
bridge::ID3v1Tag_genreIndex(self.this.as_ref())
|
||||
}
|
||||
|
||||
pub fn year(&self) -> u32 {
|
||||
bridge::ID3v1Tag_year(self.this.as_ref())
|
||||
}
|
||||
|
||||
pub fn track(&self) -> u32 {
|
||||
bridge::ID3v1Tag_track(self.this.as_ref())
|
||||
}
|
||||
}
|
139
musikr/src/main/jni/src/taglib/id3v2.rs
Normal file
139
musikr/src/main/jni/src/taglib/id3v2.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use super::bridge::{
|
||||
self, CPPID3v2AttachedPictureFrame, CPPID3v2Frame, CPPID3v2FrameList, CPPID3v2Tag,
|
||||
CPPID3v2TextIdentificationFrame, CPPID3v2UserTextIdentificationFrame,
|
||||
};
|
||||
use super::this::{OwnedThis, RefThis, RefThisMut};
|
||||
use super::tk::{self, ByteVector, OwnedByteVector, OwnedStringList, StringList};
|
||||
|
||||
pub use super::bridge::PictureType;
|
||||
|
||||
pub struct ID3v2Tag<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPID3v2Tag>,
|
||||
}
|
||||
|
||||
impl<'file_ref> ID3v2Tag<'file_ref> {
|
||||
pub(super) fn new(this: RefThisMut<'file_ref, CPPID3v2Tag>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn frames(&self) -> FrameList<'file_ref> {
|
||||
let frames = bridge::Tag_frameList(self.this.as_ref());
|
||||
let this = OwnedThis::new(frames).unwrap();
|
||||
FrameList::new(this)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FrameList<'file_ref> {
|
||||
this: OwnedThis<'file_ref, CPPID3v2FrameList>,
|
||||
}
|
||||
|
||||
impl<'file_ref> FrameList<'file_ref> {
|
||||
pub(super) fn new(this: OwnedThis<'file_ref, CPPID3v2FrameList>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<Frame<'file_ref>> {
|
||||
let frames = bridge::FrameList_to_vector(self.this.as_ref());
|
||||
frames
|
||||
.iter()
|
||||
.map(|frame| {
|
||||
let frame_ptr = frame.get();
|
||||
let frame_ref = unsafe { frame_ptr.as_ref().unwrap() };
|
||||
let frame_this = RefThis::new(frame_ref);
|
||||
Frame::new(frame_this)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Frame<'file_ref> {
|
||||
this: RefThis<'file_ref, CPPID3v2Frame>,
|
||||
}
|
||||
|
||||
impl<'file_ref> Frame<'file_ref> {
|
||||
pub(super) fn new(this: RefThis<'file_ref, CPPID3v2Frame>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> tk::OwnedByteVector<'file_ref> {
|
||||
let id = bridge::Frame_id(self.this.as_ref());
|
||||
let this = OwnedThis::new(id).unwrap();
|
||||
ByteVector::new(this)
|
||||
}
|
||||
|
||||
pub fn as_text_identification(&mut self) -> Option<TextIdentificationFrame<'file_ref>> {
|
||||
let frame = unsafe { bridge::Frame_asTextIdentification(self.this.ptr()) };
|
||||
let frame_ref = unsafe { frame.as_ref() };
|
||||
let frame_this = frame_ref.map(|frame| RefThis::new(frame));
|
||||
frame_this.map(|this| TextIdentificationFrame::new(this))
|
||||
}
|
||||
|
||||
pub fn as_user_text_identification(
|
||||
&mut self,
|
||||
) -> Option<UserTextIdentificationFrame<'file_ref>> {
|
||||
let frame = unsafe { bridge::Frame_asUserTextIdentification(self.this.ptr()) };
|
||||
let frame_ref = unsafe { frame.as_ref() };
|
||||
let frame_this = frame_ref.map(|frame| RefThis::new(frame));
|
||||
frame_this.map(|this| UserTextIdentificationFrame::new(this))
|
||||
}
|
||||
|
||||
pub fn as_attached_picture(&mut self) -> Option<AttachedPictureFrame<'file_ref>> {
|
||||
let frame = unsafe { bridge::Frame_asAttachedPicture(self.this.ptr()) };
|
||||
let frame_ref = unsafe { frame.as_ref() };
|
||||
let frame_this = frame_ref.map(|frame| RefThis::new(frame));
|
||||
frame_this.map(|this| AttachedPictureFrame::new(this))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextIdentificationFrame<'file_ref> {
|
||||
this: RefThis<'file_ref, CPPID3v2TextIdentificationFrame>,
|
||||
}
|
||||
|
||||
impl<'file_ref> TextIdentificationFrame<'file_ref> {
|
||||
pub(super) fn new(this: RefThis<'file_ref, CPPID3v2TextIdentificationFrame>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn field_list(&self) -> OwnedStringList<'file_ref> {
|
||||
let field_list = bridge::TextIdentificationFrame_fieldList(self.this.as_ref());
|
||||
let this = OwnedThis::new(field_list).unwrap();
|
||||
StringList::new(this)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UserTextIdentificationFrame<'file_ref> {
|
||||
this: RefThis<'file_ref, CPPID3v2UserTextIdentificationFrame>,
|
||||
}
|
||||
|
||||
impl<'file_ref> UserTextIdentificationFrame<'file_ref> {
|
||||
pub(super) fn new(this: RefThis<'file_ref, CPPID3v2UserTextIdentificationFrame>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn values(&self) -> OwnedStringList<'file_ref> {
|
||||
let values = bridge::UserTextIdentificationFrame_fieldList(self.this.as_ref());
|
||||
let this = OwnedThis::new(values).unwrap();
|
||||
StringList::new(this)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AttachedPictureFrame<'file_ref> {
|
||||
this: RefThis<'file_ref, CPPID3v2AttachedPictureFrame>,
|
||||
}
|
||||
|
||||
impl<'file_ref> AttachedPictureFrame<'file_ref> {
|
||||
pub(super) fn new(this: RefThis<'file_ref, CPPID3v2AttachedPictureFrame>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn picture_type(&self) -> Option<PictureType> {
|
||||
let picture_type = bridge::AttachedPictureFrame_type(self.this.as_ref());
|
||||
PictureType::from_u32(picture_type)
|
||||
}
|
||||
|
||||
pub fn picture(&self) -> OwnedByteVector<'file_ref> {
|
||||
let picture = bridge::AttachedPictureFrame_picture(self.this.as_ref());
|
||||
let this = OwnedThis::new(picture).unwrap();
|
||||
ByteVector::new(this)
|
||||
}
|
||||
}
|
80
musikr/src/main/jni/src/taglib/iostream.rs
Normal file
80
musikr/src/main/jni/src/taglib/iostream.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use super::bridge::{self, CPPIOStream};
|
||||
use cxx::UniquePtr;
|
||||
use std::io::SeekFrom;
|
||||
|
||||
pub trait IOStream {
|
||||
fn read_block(&mut self, buffer: &mut [u8]) -> usize;
|
||||
fn write_block(&mut self, data: &[u8]);
|
||||
fn seek(&mut self, pos: SeekFrom);
|
||||
fn truncate(&mut self, length: i64);
|
||||
fn tell(&self) -> i64;
|
||||
fn length(&self) -> i64;
|
||||
fn name(&self) -> String;
|
||||
fn is_readonly(&self) -> bool;
|
||||
}
|
||||
|
||||
pub(super) struct BridgedIOStream<'io_stream> {
|
||||
stream: Box<DynIOStream<'io_stream>>,
|
||||
cpp_stream: UniquePtr<CPPIOStream<'io_stream>>,
|
||||
}
|
||||
|
||||
impl<'io_stream> BridgedIOStream<'io_stream> {
|
||||
pub fn new<T: IOStream + 'io_stream>(stream: T) -> Self {
|
||||
let mut rs_stream: Box<DynIOStream<'io_stream>> = Box::new(DynIOStream(Box::new(stream)));
|
||||
let cpp_stream: UniquePtr<CPPIOStream<'io_stream>> = unsafe {
|
||||
bridge::wrap_RsIOStream(rs_stream.as_mut() as *mut _)
|
||||
};
|
||||
BridgedIOStream {
|
||||
stream: rs_stream,
|
||||
cpp_stream,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cpp_stream(&self) -> &UniquePtr<CPPIOStream> {
|
||||
&self.cpp_stream
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub(super) struct DynIOStream<'io_stream>(Box<dyn IOStream + 'io_stream>);
|
||||
|
||||
impl<'io_stream> DynIOStream<'io_stream> {
|
||||
// Implement the exposed functions for cxx bridge
|
||||
pub fn name(&self) -> String {
|
||||
self.0.name()
|
||||
}
|
||||
|
||||
pub fn read(&mut self, buffer: &mut [u8]) -> usize {
|
||||
self.0.read_block(buffer)
|
||||
}
|
||||
|
||||
pub fn write(&mut self, data: &[u8]) {
|
||||
self.0.write_block(data);
|
||||
}
|
||||
|
||||
pub fn seek(&mut self, offset: i64, whence: i32) {
|
||||
let pos = match whence {
|
||||
0 => SeekFrom::Start(offset as u64),
|
||||
1 => SeekFrom::Current(offset),
|
||||
2 => SeekFrom::End(offset),
|
||||
_ => panic!("Invalid seek whence"),
|
||||
};
|
||||
self.0.seek(pos);
|
||||
}
|
||||
|
||||
pub fn truncate(&mut self, length: i64) {
|
||||
self.0.truncate(length);
|
||||
}
|
||||
|
||||
pub fn tell(&self) -> i64 {
|
||||
self.0.tell()
|
||||
}
|
||||
|
||||
pub fn length(&self) -> i64 {
|
||||
self.0.length()
|
||||
}
|
||||
|
||||
pub fn is_readonly(&self) -> bool {
|
||||
self.0.is_readonly()
|
||||
}
|
||||
}
|
15
musikr/src/main/jni/src/taglib/mod.rs
Normal file
15
musikr/src/main/jni/src/taglib/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
pub mod audioproperties;
|
||||
mod bridge;
|
||||
pub mod file;
|
||||
pub mod file_ref;
|
||||
pub mod flac;
|
||||
pub mod id3v1;
|
||||
pub mod id3v2;
|
||||
pub mod iostream;
|
||||
pub mod mp4;
|
||||
pub mod mpeg;
|
||||
pub mod ogg;
|
||||
pub mod riff;
|
||||
mod this;
|
||||
pub mod tk;
|
||||
pub mod xiph;
|
224
musikr/src/main/jni/src/taglib/mp4.rs
Normal file
224
musikr/src/main/jni/src/taglib/mp4.rs
Normal file
|
@ -0,0 +1,224 @@
|
|||
pub use super::bridge::CPPMP4Tag;
|
||||
use super::bridge::{
|
||||
CPPIntPair, CPPItemMap, CPPMP4File, CPPMP4Item, ItemMap_to_entries, MP4ItemType,
|
||||
};
|
||||
use super::this::{OwnedThis, RefThis, RefThisMut};
|
||||
use super::tk;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct MP4File<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPMP4File>,
|
||||
}
|
||||
|
||||
impl<'file_ref> MP4File<'file_ref> {
|
||||
pub fn new(this: RefThisMut<'file_ref, CPPMP4File>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn tag(&self) -> Option<MP4Tag<'file_ref>> {
|
||||
let this = self.this.as_ref();
|
||||
let tag = this.MP4Tag();
|
||||
let tag_ref = unsafe { tag.as_ref() };
|
||||
tag_ref.map(|tag| {
|
||||
// SAFETY: The tag pointer is guaranteed to be valid for the lifetime of self
|
||||
let tag_this = RefThis::new(tag);
|
||||
MP4Tag::new(tag_this)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MP4Tag<'file_ref> {
|
||||
this: RefThis<'file_ref, CPPMP4Tag>,
|
||||
}
|
||||
|
||||
impl<'file_ref> MP4Tag<'file_ref> {
|
||||
pub fn new(this: RefThis<'file_ref, CPPMP4Tag>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn item_map(&'file_ref self) -> ItemMap<'file_ref> {
|
||||
let map: &'file_ref CPPItemMap = self.this.as_ref().itemMap();
|
||||
let map_this = RefThis::new(map);
|
||||
ItemMap::new(map_this)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ItemMap<'file_ref> {
|
||||
this: RefThis<'file_ref, CPPItemMap>,
|
||||
}
|
||||
|
||||
impl<'file_ref> ItemMap<'file_ref> {
|
||||
pub fn new(this: RefThis<'file_ref, CPPItemMap>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn to_hashmap(&self) -> HashMap<String, MP4Item<'file_ref>> {
|
||||
let cxx_vec = ItemMap_to_entries(self.this.as_ref());
|
||||
let vec: Vec<(String, MP4Item<'file_ref>)> = cxx_vec
|
||||
.iter()
|
||||
.map(|property| {
|
||||
// SAFETY:
|
||||
// - This pin is only used in this unsafe scope.
|
||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||
// not change address by C++ semantics.
|
||||
// - The values returned are copied and thus not dependent on the address
|
||||
// of self.
|
||||
let key_ref = property.key();
|
||||
let key_this = OwnedThis::new(key_ref).unwrap();
|
||||
let key = tk::String::new(key_this).to_string();
|
||||
|
||||
let value_ref = property.value();
|
||||
let value_this = OwnedThis::new(value_ref).unwrap();
|
||||
let value = MP4Item::new(value_this);
|
||||
|
||||
(key, value)
|
||||
})
|
||||
.collect();
|
||||
|
||||
HashMap::from_iter(vec)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MP4Item<'file_ref> {
|
||||
this: OwnedThis<'file_ref, CPPMP4Item>,
|
||||
}
|
||||
|
||||
impl<'file_ref> MP4Item<'file_ref> {
|
||||
pub fn new(this: OwnedThis<'file_ref, CPPMP4Item>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn data(&self) -> Option<MP4Data<'file_ref>> {
|
||||
if !self.this.as_ref().isValid() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item_type = MP4ItemType::from_u32(super::bridge::Item_type(self.this.as_ref()));
|
||||
item_type.and_then(|item_type| match item_type {
|
||||
MP4ItemType::Void => Some(MP4Data::Void),
|
||||
MP4ItemType::Bool => Some(MP4Data::Bool(self.this.as_ref().toBool())),
|
||||
MP4ItemType::Int => Some(MP4Data::Int(self.this.as_ref().toInt())),
|
||||
MP4ItemType::IntPair => {
|
||||
let pair = super::bridge::Item_toIntPair(self.this.as_ref());
|
||||
let pair_this = OwnedThis::new(pair).unwrap();
|
||||
Some(MP4Data::IntPair(IntPair::new(pair_this)))
|
||||
}
|
||||
MP4ItemType::Byte => Some(MP4Data::Byte(self.this.as_ref().toByte())),
|
||||
MP4ItemType::UInt => Some(MP4Data::UInt(self.this.as_ref().toUInt())),
|
||||
MP4ItemType::LongLong => Some(MP4Data::LongLong(super::bridge::Item_toLongLong(
|
||||
self.this.as_ref(),
|
||||
))),
|
||||
MP4ItemType::StringList => {
|
||||
let string_list = super::bridge::Item_toStringList(self.this.as_ref());
|
||||
let string_list_this = OwnedThis::new(string_list).unwrap();
|
||||
Some(MP4Data::StringList(tk::StringList::new(string_list_this)))
|
||||
}
|
||||
MP4ItemType::ByteVectorList => {
|
||||
let byte_vector_list = super::bridge::Item_toByteVectorList(self.this.as_ref());
|
||||
let byte_vector_list_this = OwnedThis::new(byte_vector_list).unwrap();
|
||||
Some(MP4Data::ByteVectorList(tk::ByteVectorList::new(
|
||||
byte_vector_list_this,
|
||||
)))
|
||||
}
|
||||
MP4ItemType::CoverArtList => {
|
||||
let cover_art_list = super::bridge::Item_toCoverArtList(self.this.as_ref());
|
||||
let cover_art_list_this = OwnedThis::new(cover_art_list).unwrap();
|
||||
Some(MP4Data::CoverArtList(CoverArtList::new(
|
||||
cover_art_list_this,
|
||||
)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CoverArtList<'file_ref> {
|
||||
this: OwnedThis<'file_ref, super::bridge::CPPCoverArtList>,
|
||||
}
|
||||
|
||||
impl<'file_ref> CoverArtList<'file_ref> {
|
||||
pub fn new(this: OwnedThis<'file_ref, super::bridge::CPPCoverArtList>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<CoverArt> {
|
||||
let cover_arts = self.this.as_ref().to_vector();
|
||||
cover_arts
|
||||
.iter()
|
||||
.map(|ca| {
|
||||
let format = CoverArtFormat::from_u32(ca.format());
|
||||
let data = ca.data();
|
||||
let data_this = RefThis::new(&*data);
|
||||
let data = tk::ByteVector::new(data_this).to_vec();
|
||||
CoverArt { format, data }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IntPair<'file_ref> {
|
||||
this: OwnedThis<'file_ref, CPPIntPair>,
|
||||
}
|
||||
|
||||
impl<'file_ref> IntPair<'file_ref> {
|
||||
pub fn new(this: OwnedThis<'file_ref, CPPIntPair>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn to_tuple(&self) -> Option<(i32, i32)> {
|
||||
let this = self.this.as_ref();
|
||||
let first = this.first();
|
||||
let second = this.second();
|
||||
Some((first, second))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MP4Data<'file_ref> {
|
||||
Void,
|
||||
Bool(bool),
|
||||
Int(i32),
|
||||
Byte(u8),
|
||||
UInt(u32),
|
||||
LongLong(i64),
|
||||
IntPair(IntPair<'file_ref>),
|
||||
StringList(tk::OwnedStringList<'file_ref>),
|
||||
ByteVectorList(tk::OwnedByteVectorList<'file_ref>),
|
||||
CoverArtList(CoverArtList<'file_ref>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CoverArt {
|
||||
format: CoverArtFormat,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl CoverArt {
|
||||
pub fn format(&self) -> CoverArtFormat {
|
||||
self.format
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum CoverArtFormat {
|
||||
Unknown,
|
||||
BMP,
|
||||
JPEG,
|
||||
GIF,
|
||||
PNG,
|
||||
}
|
||||
|
||||
impl CoverArtFormat {
|
||||
fn from_u32(value: u32) -> Self {
|
||||
match value {
|
||||
0 => CoverArtFormat::Unknown,
|
||||
1 => CoverArtFormat::BMP,
|
||||
2 => CoverArtFormat::JPEG,
|
||||
3 => CoverArtFormat::GIF,
|
||||
4 => CoverArtFormat::PNG,
|
||||
_ => CoverArtFormat::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
28
musikr/src/main/jni/src/taglib/mpeg.rs
Normal file
28
musikr/src/main/jni/src/taglib/mpeg.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use super::bridge::CPPMPEGFile;
|
||||
use super::id3v1::ID3v1Tag;
|
||||
use super::id3v2::ID3v2Tag;
|
||||
use super::this::{RefThisMut, ThisMut};
|
||||
|
||||
pub struct MPEGFile<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPMPEGFile>,
|
||||
}
|
||||
|
||||
impl<'file_ref> MPEGFile<'file_ref> {
|
||||
pub(super) fn new(this: RefThisMut<'file_ref, CPPMPEGFile>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn id3v1_tag(&mut self) -> Option<ID3v1Tag<'file_ref>> {
|
||||
let tag = self.this.pin_mut().MPEGID3v1Tag(false);
|
||||
let tag_ref = unsafe { tag.as_mut() };
|
||||
let tag_this = tag_ref.map(|tag| RefThisMut::new(tag));
|
||||
tag_this.map(|this| ID3v1Tag::new(this))
|
||||
}
|
||||
|
||||
pub fn id3v2_tag(&mut self) -> Option<ID3v2Tag<'file_ref>> {
|
||||
let tag = self.this.pin_mut().MPEGID3v2Tag(false);
|
||||
let tag_ref = unsafe { tag.as_mut() };
|
||||
let tag_this = tag_ref.map(|tag| RefThisMut::new(tag));
|
||||
tag_this.map(|this| ID3v2Tag::new(this))
|
||||
}
|
||||
}
|
45
musikr/src/main/jni/src/taglib/ogg.rs
Normal file
45
musikr/src/main/jni/src/taglib/ogg.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
pub use super::bridge::{CPPOpusFile, CPPVorbisFile};
|
||||
use super::this::RefThisMut;
|
||||
use super::xiph::XiphComment;
|
||||
|
||||
pub struct VorbisFile<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPVorbisFile>,
|
||||
}
|
||||
|
||||
impl<'file_ref> VorbisFile<'file_ref> {
|
||||
pub(super) fn new(this: RefThisMut<'file_ref, CPPVorbisFile>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn xiph_comments(&self) -> Option<XiphComment<'file_ref>> {
|
||||
let tag = self.this.as_ref().vorbisTag();
|
||||
let tag_ref = unsafe {
|
||||
// SAFETY: This pointer is a valid type, and can only used and accessed
|
||||
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
||||
tag.as_mut()
|
||||
};
|
||||
let tag_this = tag_ref.map(|tag| RefThisMut::new(tag));
|
||||
tag_this.map(|this| XiphComment::new(this))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpusFile<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPOpusFile>,
|
||||
}
|
||||
|
||||
impl<'file_ref> OpusFile<'file_ref> {
|
||||
pub(super) fn new(this: RefThisMut<'file_ref, CPPOpusFile>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn xiph_comments(&self) -> Option<XiphComment<'file_ref>> {
|
||||
let tag = self.this.as_ref().opusTag();
|
||||
let tag_ref = unsafe {
|
||||
// SAFETY: This pointer is a valid type, and can only used and accessed
|
||||
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
||||
tag.as_mut()
|
||||
};
|
||||
let tag_this = tag_ref.map(|tag| RefThisMut::new(tag));
|
||||
tag_this.map(|this| XiphComment::new(this))
|
||||
}
|
||||
}
|
20
musikr/src/main/jni/src/taglib/riff.rs
Normal file
20
musikr/src/main/jni/src/taglib/riff.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use super::bridge::CPPWAVFile;
|
||||
use super::id3v2::ID3v2Tag;
|
||||
use super::this::RefThisMut;
|
||||
|
||||
pub struct WAVFile<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPWAVFile>,
|
||||
}
|
||||
|
||||
impl<'file_ref> WAVFile<'file_ref> {
|
||||
pub(super) fn new(this: RefThisMut<'file_ref, CPPWAVFile>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn id3v2_tag(&mut self) -> Option<ID3v2Tag<'file_ref>> {
|
||||
let tag = self.this.as_ref().WAVID3v2Tag();
|
||||
let tag_ref = unsafe { tag.as_mut() };
|
||||
let tag_this = tag_ref.map(|tag| RefThisMut::new(tag));
|
||||
tag_this.map(|this| ID3v2Tag::new(this))
|
||||
}
|
||||
}
|
98
musikr/src/main/jni/src/taglib/this.rs
Normal file
98
musikr/src/main/jni/src/taglib/this.rs
Normal file
|
@ -0,0 +1,98 @@
|
|||
use super::bridge::{TagLibAllocated, TagLibRef, TagLibShared};
|
||||
use cxx::{memory::UniquePtrTarget, UniquePtr};
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
|
||||
pub trait This<'file_ref, T: TagLibAllocated>: AsRef<T> {}
|
||||
|
||||
pub trait ThisMut<'file_ref, T: TagLibAllocated>: This<'file_ref, T> {
|
||||
fn pin_mut(&mut self) -> Pin<&mut T>;
|
||||
}
|
||||
|
||||
pub struct RefThis<'file_ref, T: TagLibRef> {
|
||||
this: &'file_ref T,
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibRef> RefThis<'file_ref, T> {
|
||||
pub fn new(this: &'file_ref T) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn ptr(&self) -> *const T {
|
||||
self.this as *const T
|
||||
}
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibRef> AsRef<T> for RefThis<'file_ref, T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
self.this
|
||||
}
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibRef> This<'file_ref, T> for RefThis<'file_ref, T> {}
|
||||
|
||||
pub struct RefThisMut<'file_ref, T: TagLibRef> {
|
||||
this: &'file_ref mut T,
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibRef> RefThisMut<'file_ref, T> {
|
||||
pub fn new(this: &'file_ref mut T) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn ptr(&self) -> *const T {
|
||||
self.this as *const T
|
||||
}
|
||||
|
||||
pub fn ptr_mut(&mut self) -> *mut T {
|
||||
self.this as *mut T
|
||||
}
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibRef> AsRef<T> for RefThisMut<'file_ref, T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
self.this
|
||||
}
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibRef> This<'file_ref, T> for RefThisMut<'file_ref, T> {}
|
||||
|
||||
impl<'file_ref, T: TagLibRef> ThisMut<'file_ref, T> for RefThisMut<'file_ref, T> {
|
||||
fn pin_mut(&mut self) -> Pin<&mut T> {
|
||||
unsafe { Pin::new_unchecked(self.this) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OwnedThis<'file_ref, T: TagLibShared + UniquePtrTarget> {
|
||||
_data: PhantomData<&'file_ref ()>,
|
||||
this: UniquePtr<T>,
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibShared + UniquePtrTarget> OwnedThis<'file_ref, T> {
|
||||
pub fn new(this: UniquePtr<T>) -> Option<Self> {
|
||||
if !this.is_null() {
|
||||
Some(Self {
|
||||
_data: PhantomData,
|
||||
this,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibShared + UniquePtrTarget> AsRef<T> for OwnedThis<'file_ref, T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
self.this.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'file_ref, T: TagLibShared + UniquePtrTarget> This<'file_ref, T> for OwnedThis<'file_ref, T> {}
|
||||
|
||||
impl<'file_ref, T: TagLibShared + UniquePtrTarget> ThisMut<'file_ref, T>
|
||||
for OwnedThis<'file_ref, T>
|
||||
{
|
||||
fn pin_mut(&mut self) -> Pin<&mut T> {
|
||||
self.this.as_mut().unwrap()
|
||||
}
|
||||
}
|
138
musikr/src/main/jni/src/taglib/tk.rs
Normal file
138
musikr/src/main/jni/src/taglib/tk.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use super::bridge;
|
||||
use super::this::{OwnedThis, RefThis, RefThisMut, This};
|
||||
use std::marker::PhantomData;
|
||||
use std::{ffi::CStr, string::ToString};
|
||||
|
||||
pub use bridge::CPPByteVector as InnerByteVector;
|
||||
pub use bridge::CPPByteVectorList as InnerByteVectorList;
|
||||
pub use bridge::CPPString as InnerString;
|
||||
pub use bridge::CPPStringList as InnerStringList;
|
||||
|
||||
pub struct String<'file_ref, T: This<'file_ref, InnerString>> {
|
||||
_data: PhantomData<&'file_ref ()>,
|
||||
this: T,
|
||||
}
|
||||
|
||||
impl<'file_ref, T: This<'file_ref, InnerString>> String<'file_ref, T> {
|
||||
pub(super) fn new(this: T) -> Self {
|
||||
Self {
|
||||
_data: PhantomData,
|
||||
this,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'file_ref, T: This<'file_ref, InnerString>> ToString for String<'file_ref, T> {
|
||||
fn to_string(&self) -> std::string::String {
|
||||
let c_str = self.this.as_ref().toCString(true);
|
||||
unsafe {
|
||||
// SAFETY:
|
||||
// - This is a C-string returned by a C++ method guaranteed to have
|
||||
// a null terminator.
|
||||
// - This C-string is fully allocated and owned by the TagString instance,
|
||||
// in a continous block from start to null terminator.
|
||||
// - This C-string will be non-null even if empty.
|
||||
// - This pointer will not be mutated before it's entirely copied into
|
||||
// rust.
|
||||
// - This C-string is copied to a rust string before TagString is destroyed.
|
||||
CStr::from_ptr(c_str)
|
||||
}
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub type OwnedString<'file_ref> = String<'file_ref, OwnedThis<'file_ref, InnerString>>;
|
||||
pub type RefString<'file_ref> = String<'file_ref, RefThis<'file_ref, InnerString>>;
|
||||
pub type RefStringMut<'file_ref> = String<'file_ref, RefThisMut<'file_ref, InnerString>>;
|
||||
pub struct StringList<'file_ref, T: This<'file_ref, InnerStringList>> {
|
||||
_data: PhantomData<&'file_ref ()>,
|
||||
this: T,
|
||||
}
|
||||
|
||||
impl<'file_ref, T: This<'file_ref, InnerStringList>> StringList<'file_ref, T> {
|
||||
pub(super) fn new(this: T) -> Self {
|
||||
Self {
|
||||
_data: PhantomData,
|
||||
this,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<std::string::String> {
|
||||
let cxx_values = bridge::StringList_to_vector(self.this.as_ref());
|
||||
cxx_values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
let this = RefThis::new(value);
|
||||
String::new(this).to_string()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub type OwnedStringList<'file_ref> = StringList<'file_ref, OwnedThis<'file_ref, InnerStringList>>;
|
||||
|
||||
pub struct ByteVector<'file_ref, T: This<'file_ref, InnerByteVector>> {
|
||||
_data: PhantomData<&'file_ref InnerByteVector>,
|
||||
this: T,
|
||||
}
|
||||
|
||||
impl<'file_ref, T: This<'file_ref, InnerByteVector>> ByteVector<'file_ref, T> {
|
||||
pub(super) fn new(this: T) -> Self {
|
||||
Self {
|
||||
_data: PhantomData,
|
||||
this,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
let this = self.this.as_ref();
|
||||
let size = this.size().try_into().unwrap();
|
||||
let data = this.data();
|
||||
// Re-cast to u8
|
||||
let data: *const u8 = data as *const u8;
|
||||
unsafe {
|
||||
// SAFETY:
|
||||
// - data points to a valid buffer of size 'size' owned by the C++ ByteVector
|
||||
// - we're creating a new Vec and copying the data, not taking ownership
|
||||
// - the source data won't be modified while we're reading from it
|
||||
std::slice::from_raw_parts(data, size).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string_lossy(&self) -> std::string::String {
|
||||
let this = self.this.as_ref();
|
||||
let size = this.size().try_into().unwrap();
|
||||
let data = this.data();
|
||||
let data: *const u8 = data as *const u8;
|
||||
let slice = unsafe { std::slice::from_raw_parts(data, size) };
|
||||
std::string::String::from_utf8_lossy(slice).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub type OwnedByteVector<'file_ref> = ByteVector<'file_ref, OwnedThis<'file_ref, InnerByteVector>>;
|
||||
|
||||
pub struct ByteVectorList<'file_ref, T: This<'file_ref, InnerByteVectorList>> {
|
||||
_data: PhantomData<&'file_ref InnerByteVectorList>,
|
||||
this: T,
|
||||
}
|
||||
|
||||
impl<'file_ref, T: This<'file_ref, InnerByteVectorList>> ByteVectorList<'file_ref, T> {
|
||||
pub(super) fn new(this: T) -> Self {
|
||||
Self {
|
||||
_data: PhantomData,
|
||||
this,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<Vec<u8>> {
|
||||
let cxx_values = bridge::ByteVectorList_to_vector(self.this.as_ref());
|
||||
cxx_values
|
||||
.iter()
|
||||
.map(|value| ByteVector::new(RefThis::new(value)).to_vec())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub type OwnedByteVectorList<'file_ref> =
|
||||
ByteVectorList<'file_ref, OwnedThis<'file_ref, InnerByteVectorList>>;
|
62
musikr/src/main/jni/src/taglib/xiph.rs
Normal file
62
musikr/src/main/jni/src/taglib/xiph.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
pub use super::bridge::CPPXiphComment;
|
||||
use super::bridge::{CPPFieldListMap, FieldListMap_to_entries, XiphComment_pictureList};
|
||||
pub use super::flac::FLACPictureList;
|
||||
use super::this::{OwnedThis, RefThis, RefThisMut, ThisMut};
|
||||
use super::tk;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct XiphComment<'file_ref> {
|
||||
this: RefThisMut<'file_ref, CPPXiphComment>,
|
||||
}
|
||||
|
||||
impl<'file_ref> XiphComment<'file_ref> {
|
||||
pub fn new(this: RefThisMut<'file_ref, CPPXiphComment>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
|
||||
pub fn field_list_map(&'file_ref self) -> FieldListMap<'file_ref> {
|
||||
let map: &'file_ref CPPFieldListMap = self.this.as_ref().fieldListMap();
|
||||
let map_this = RefThis::new(map);
|
||||
FieldListMap::new(map_this)
|
||||
}
|
||||
|
||||
pub fn picture_list(&mut self) -> FLACPictureList<'file_ref> {
|
||||
let pictures = XiphComment_pictureList(self.this.pin_mut());
|
||||
let pictures_this = OwnedThis::new(pictures).unwrap();
|
||||
FLACPictureList::new(pictures_this)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldListMap<'file_ref> {
|
||||
this: RefThis<'file_ref, CPPFieldListMap>,
|
||||
}
|
||||
|
||||
impl<'file_ref> FieldListMap<'file_ref> {
|
||||
pub fn new(this: RefThis<'file_ref, CPPFieldListMap>) -> Self {
|
||||
Self { this }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'file_ref> FieldListMap<'file_ref> {
|
||||
pub fn to_hashmap(&self) -> HashMap<String, tk::OwnedStringList<'file_ref>> {
|
||||
let cxx_vec = FieldListMap_to_entries(self.this.as_ref());
|
||||
cxx_vec
|
||||
.iter()
|
||||
.map(|property| {
|
||||
// SAFETY:
|
||||
// - This pin is only used in this unsafe scope.
|
||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||
// not change address by C++ semantics.
|
||||
// - The values returned are copied and thus not dependent on the address
|
||||
// of self.
|
||||
let key_ref = property.key();
|
||||
let key_this = OwnedThis::new(key_ref).unwrap();
|
||||
let key = tk::String::new(key_this).to_string();
|
||||
let value_ref = property.value();
|
||||
let value_this = OwnedThis::new(value_ref).unwrap();
|
||||
let value = tk::StringList::new(value_this);
|
||||
(key, value)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
1
musikr/src/main/jni/taglib
Submodule
1
musikr/src/main/jni/taglib
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit ee1931b81116cd0091c906896f6f4fb74850be51
|
Loading…
Reference in a new issue