ktaglib: scaffold jni impl
This commit is contained in:
parent
7640292d7a
commit
55e77707ea
6 changed files with 234 additions and 8 deletions
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.filterIsInstance
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
|
import org.oxycblt.ktaglib.KTagLib
|
||||||
import org.oxycblt.musikr.Storage
|
import org.oxycblt.musikr.Storage
|
||||||
import org.oxycblt.musikr.cache.CachedSong
|
import org.oxycblt.musikr.cache.CachedSong
|
||||||
import org.oxycblt.musikr.cover.Cover
|
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.metadata.MetadataExtractor
|
||||||
import org.oxycblt.musikr.tag.parse.ParsedTags
|
import org.oxycblt.musikr.tag.parse.ParsedTags
|
||||||
import org.oxycblt.musikr.tag.parse.TagParser
|
import org.oxycblt.musikr.tag.parse.TagParser
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
interface ExtractStep {
|
interface ExtractStep {
|
||||||
fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic>
|
fun extract(storage: Storage, nodes: Flow<ExploreNode>): Flow<ExtractedMusic>
|
||||||
|
|
65
ktaglib/src/main/cpp/AndroidIOStream.cpp
Normal file
65
ktaglib/src/main/cpp/AndroidIOStream.cpp
Normal 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) {
|
||||||
|
|
||||||
|
};
|
106
ktaglib/src/main/cpp/AndroidIOStream.h
Normal file
106
ktaglib/src/main/cpp/AndroidIOStream.h
Normal 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
|
|
@ -45,7 +45,8 @@ set_target_properties(
|
||||||
|
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
# 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
|
# Specifies libraries CMake should link to your target library. You
|
||||||
# can link libraries from various origins, such as libraries defined in this
|
# can link libraries from various origins, such as libraries defined in this
|
||||||
|
|
|
@ -1,12 +1,33 @@
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <string>
|
#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(
|
Java_org_oxycblt_ktaglib_KTagLib_load(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
jobject /* this */) {
|
jobject /* this */,
|
||||||
std::string hello = "Hello from C++";
|
jobject fileRef) {
|
||||||
return env->NewStringUTF(hello.c_str());
|
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;
|
||||||
}
|
}
|
|
@ -12,5 +12,36 @@ object KTagLib {
|
||||||
* A native method that is implemented by the 'ktaglib' native library,
|
* A native method that is implemented by the 'ktaglib' native library,
|
||||||
* which is packaged with this application.
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue