diff --git a/app/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt b/app/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt index 8334df78d..ca487dde3 100644 --- a/app/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt +++ b/app/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt @@ -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): Flow diff --git a/ktaglib/src/main/cpp/AndroidIOStream.cpp b/ktaglib/src/main/cpp/AndroidIOStream.cpp new file mode 100644 index 000000000..61ae43b90 --- /dev/null +++ b/ktaglib/src/main/cpp/AndroidIOStream.cpp @@ -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) { + +}; \ No newline at end of file diff --git a/ktaglib/src/main/cpp/AndroidIOStream.h b/ktaglib/src/main/cpp/AndroidIOStream.h new file mode 100644 index 000000000..d85717e54 --- /dev/null +++ b/ktaglib/src/main/cpp/AndroidIOStream.h @@ -0,0 +1,106 @@ +// +// Created by oxycblt on 12/12/24. +// + +#ifndef AUXIO_ANDROIDIOSTREAM_H +#define AUXIO_ANDROIDIOSTREAM_H + +#include + +#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 diff --git a/ktaglib/src/main/cpp/CMakeLists.txt b/ktaglib/src/main/cpp/CMakeLists.txt index 712ecd476..5aeb06623 100644 --- a/ktaglib/src/main/cpp/CMakeLists.txt +++ b/ktaglib/src/main/cpp/CMakeLists.txt @@ -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 diff --git a/ktaglib/src/main/cpp/ktaglib.cpp b/ktaglib/src/main/cpp/ktaglib.cpp index eb05a5164..6041f1153 100644 --- a/ktaglib/src/main/cpp/ktaglib.cpp +++ b/ktaglib/src/main/cpp/ktaglib.cpp @@ -1,12 +1,33 @@ #include #include -#include "taglib/tag.h" +#include "AndroidIOStream.h" +#include +#include -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, "", "()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, "", "(Ljava/util/Map;Ljava/util/Map;[B)V"); + // Create tag + jobject tagObj = env->NewObject(tagClass, tagInit, id3v2, vorbis, coverData); + return tagObj; } \ No newline at end of file diff --git a/ktaglib/src/main/java/org/oxycblt/ktaglib/KTagLib.kt b/ktaglib/src/main/java/org/oxycblt/ktaglib/KTagLib.kt index eb54a74aa..e80950a58 100644 --- a/ktaglib/src/main/java/org/oxycblt/ktaglib/KTagLib.kt +++ b/ktaglib/src/main/java/org/oxycblt/ktaglib/KTagLib.kt @@ -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 -} \ No newline at end of file + external fun load(fileRef: FileRef): Tag? +} + +data class FileRef( + val fileName: String, + val inputStream: InputStream +) + +data class Tag( + val id3v2: Map, + val vorbis: Map, + 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 + } +}