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.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>
|
||||
|
|
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
|
||||
# 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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue