ktaglib: scaffold jni impl

This commit is contained in:
Alexander Capehart 2024-12-12 13:16:41 -07:00
parent 7640292d7a
commit 55e77707ea
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
6 changed files with 234 additions and 8 deletions

View file

@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import org.oxycblt.ktaglib.KTagLib
import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cache.CachedSong
import org.oxycblt.musikr.cover.Cover
@ -35,6 +36,7 @@ import org.oxycblt.musikr.fs.query.DeviceFile
import org.oxycblt.musikr.metadata.MetadataExtractor
import org.oxycblt.musikr.tag.parse.ParsedTags
import org.oxycblt.musikr.tag.parse.TagParser
import timber.log.Timber
interface ExtractStep {
fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic>

View file

@ -0,0 +1,65 @@
//
// Created by oxycblt on 12/12/24.
//
#include "AndroidIOStream.h"
AndroidIOStream::AndroidIOStream(JNIEnv *env, jobject &fileRef) : env(env), fileRef(fileRef) {}
AndroidIOStream::~AndroidIOStream() {}
/*!
* Returns the stream name in the local file system encoding.
*/
TagLib::FileName AndroidIOStream::name() const {
return TagLib::FileName();
};
/*!
* Reads a block of size \a length at the current get pointer.
*/
TagLib::ByteVector AndroidIOStream::readBlock(size_t length) {
return {};
};
void AndroidIOStream::writeBlock(const TagLib::ByteVector &data) {
throw std::runtime_error("Not implemented");
};
void AndroidIOStream::insert(const TagLib::ByteVector &data,
TagLib::offset_t start, size_t replace) {
throw std::runtime_error("Not implemented");
};
void AndroidIOStream::removeBlock(TagLib::offset_t start, size_t length) {
throw std::runtime_error("Not implemented");
};
bool AndroidIOStream::readOnly() const {
return true;
};
bool AndroidIOStream::isOpen() const {
};
void AndroidIOStream::seek(TagLib::offset_t offset, Position p) {
};
void AndroidIOStream::clear() {
}
TagLib::offset_t AndroidIOStream::tell() const {
return 0;
};
TagLib::offset_t AndroidIOStream::length() {
return 0;
};
void AndroidIOStream::truncate(TagLib::offset_t length) {
};

View file

@ -0,0 +1,106 @@
//
// Created by oxycblt on 12/12/24.
//
#ifndef AUXIO_ANDROIDIOSTREAM_H
#define AUXIO_ANDROIDIOSTREAM_H
#include <jni.h>
#include "taglib/tiostream.h"
class AndroidIOStream : public TagLib::IOStream {
public:
AndroidIOStream(JNIEnv *env, jobject &fileRef);
~AndroidIOStream();
AndroidIOStream(const AndroidIOStream &) = delete;
AndroidIOStream &operator=(const AndroidIOStream &) = 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();
/*!
* 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 &fileRef;
};
#endif //AUXIO_ANDROIDIOSTREAM_H

View file

@ -45,7 +45,8 @@ set_target_properties(
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
ktaglib.cpp)
ktaglib.cpp
AndroidIOStream.cpp)
# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this

View file

@ -1,12 +1,33 @@
#include <jni.h>
#include <string>
#include "taglib/tag.h"
#include "AndroidIOStream.h"
#include <taglib/fileref.h>
#include <taglib/tag.h>
extern "C" JNIEXPORT jstring JNICALL
extern "C" JNIEXPORT jobject JNICALL
Java_org_oxycblt_ktaglib_KTagLib_load(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
jobject /* this */,
jobject fileRef) {
AndroidIOStream stream { env, fileRef };
TagLib::FileRef file { &stream };
if (file.isNull()) {
return nullptr;
}
TagLib::Tag* tag = file.tag();
if (tag == nullptr) {
return nullptr;
}
jclass mapClass = env->FindClass("java/util/HashMap");
jmethodID init = env->GetMethodID(mapClass, "<init>", "()V");
jobject id3v2 = env->NewObject(mapClass, init);
jobject vorbis = env->NewObject(mapClass, init);
jbyteArray coverData = env->NewByteArray(0);
jclass tagClass = env->FindClass("org/oxycblt/ktaglib/Tag");
jmethodID tagInit = env->GetMethodID(tagClass, "<init>", "(Ljava/util/Map;Ljava/util/Map;[B)V");
// Create tag
jobject tagObj = env->NewObject(tagClass, tagInit, id3v2, vorbis, coverData);
return tagObj;
}

View file

@ -12,5 +12,36 @@ object KTagLib {
* A native method that is implemented by the 'ktaglib' native library,
* which is packaged with this application.
*/
external fun load(): String
}
external fun load(fileRef: FileRef): Tag?
}
data class FileRef(
val fileName: String,
val inputStream: InputStream
)
data class Tag(
val id3v2: Map<String, String>,
val vorbis: Map<String, String>,
val coverData: ByteArray
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Tag
if (id3v2 != other.id3v2) return false
if (vorbis != other.vorbis) return false
if (!coverData.contentEquals(other.coverData)) return false
return true
}
override fun hashCode(): Int {
var result = id3v2.hashCode()
result = 31 * result + vorbis.hashCode()
result = 31 * result + coverData.contentHashCode()
return result
}
}