diff --git a/.gitignore b/.gitignore
index 7e85fead6..ac0e8844f 100755
--- a/.gitignore
+++ b/.gitignore
@@ -97,5 +97,3 @@ lint/tmp/
# misc
.DS_Store
-app/src/main/java/com/dd3boh/outertune/utils/scanners/jni/ffmpeg-android-maker
-app/src/main/java/com/dd3boh/outertune/utils/scanners/jni/src/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ecd2e1a36..19de0e6fb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -16,28 +16,6 @@ x86_64
**For most users, the `universal` variant is sufficient.** The other build variants may reduce file size, however at the
cost of compatibility.
-## Building with FFmpeg binaries
-
-By default, we ship a prebuilt library (`/app/prebuilt/ffMetadataEx.arr`), and you *DO NOT* need to care about this.
-However, should you choose to opt for self built libraries and/or work on the extractor itself, keep reading:
-
-1. First you will need to setup the [Android NDK](https://developer.android.com/studio/projects/install-ndk)
-
-2. We use FFMpeg to extract metadata from local files. The FFMpeg binaries must be resolved in one of two ways:
-
- - a) Build libraries yourself. Clone [ffmpeg-android-maker](https://github.com/Javernaut/ffmpeg-android-maker) into
- `/ffMetadataEx/src/main/cpp/ffmpeg-android-maker`, run the build script. Note: It may be helpful to modify the
- FFmpeg build script disable uneeded FFmpeg fetaures to reduce app size,
- see [here](https://github.com/mikooomich/ffmpeg-android-maker/blob/master/scripts/ffmpeg/build.sh) for an example.
-
- - b) Use prebuilt FFmpeg libraries.
- Clone [prebuilt ffmpeg-android-maker](https://github.com/mikooomich/ffmpeg-android-maker-prebuilt) into
- `/ffMetadataEx/src/main/cpp/ffmpeg-android-maker`.
-
-3. Modify `app/build.gradle.kts` and `settings.gradle.kts` to switch to the self built version, with the instructions
- being in both of the files.
-
-4. Start the build are you normally would.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index df843e717..b0bb8367f 100755
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -173,15 +173,5 @@ dependencies {
implementation(libs.timber)
- /**
- * Custom FFmpeg metadata extractor
- *
- * My boss has requested prebuilt libraries by default. Shall you choose
- * to work on the scanner itself, switch the implementation below AND
- * include the project (uncomment the include line) in /settings.gradle.kts
- */
- implementation(files("prebuilt/ffMetadataEx-release.aar")) // prebuilt
-// implementation(project(":ffMetadataEx")) // self built
-
implementation(libs.taglib)
}
\ No newline at end of file
diff --git a/app/prebuilt/ffMetadataEx-release.aar b/app/prebuilt/ffMetadataEx-release.aar
deleted file mode 100644
index 1181501e6..000000000
Binary files a/app/prebuilt/ffMetadataEx-release.aar and /dev/null differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 459e48d79..d95c7ca61 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,6 +19,12 @@
+
+
+
+
+
+
(null)
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
@@ -307,6 +310,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
+ activityLauncher = ActivityLauncherHelper(this)
setContent {
val connectivityObserver = NetworkConnectivityObserver(this)
diff --git a/app/src/main/java/com/dd3boh/outertune/ui/screens/library/LibraryFoldersScreen.kt b/app/src/main/java/com/dd3boh/outertune/ui/screens/library/LibraryFoldersScreen.kt
index 8d2eb1813..33db00816 100644
--- a/app/src/main/java/com/dd3boh/outertune/ui/screens/library/LibraryFoldersScreen.kt
+++ b/app/src/main/java/com/dd3boh/outertune/ui/screens/library/LibraryFoldersScreen.kt
@@ -123,7 +123,6 @@ fun LibraryFoldersScreen(
// initialize with first directory
if (folderStack.isEmpty()) {
- println("wtrf reinti")
viewModel.getLocalSongs(database)
folderStack.push(
diff --git a/app/src/main/java/com/dd3boh/outertune/ui/utils/LocalMediaUtils.kt b/app/src/main/java/com/dd3boh/outertune/ui/utils/LocalMediaUtils.kt
index 490390e40..69570e4a3 100644
--- a/app/src/main/java/com/dd3boh/outertune/ui/utils/LocalMediaUtils.kt
+++ b/app/src/main/java/com/dd3boh/outertune/ui/utils/LocalMediaUtils.kt
@@ -19,7 +19,7 @@ const val SYNC_SCANNER = false // true will not use multithreading for scanner
const val MAX_CONCURRENT_JOBS = 4
const val SCANNER_DEBUG = false
-const val EXTRACTOR_DEBUG = true
+const val EXTRACTOR_DEBUG = false
const val DEBUG_SAVE_OUTPUT = false // ignored (will be false) when EXTRACTOR_DEBUG IS false
const val EXTRACTOR_TAG = "MetadataExtractor"
diff --git a/app/src/main/java/com/dd3boh/outertune/utils/ActivityLaunchHelper.kt b/app/src/main/java/com/dd3boh/outertune/utils/ActivityLaunchHelper.kt
new file mode 100644
index 000000000..cd14fb8ce
--- /dev/null
+++ b/app/src/main/java/com/dd3boh/outertune/utils/ActivityLaunchHelper.kt
@@ -0,0 +1,30 @@
+package com.dd3boh.outertune.utils
+
+import android.content.Intent
+import android.util.ArrayMap
+import androidx.activity.ComponentActivity
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+
+class ActivityLauncherHelper(
+ private val activity: ComponentActivity
+) {
+ private var consumers = ArrayMap Unit)?>()
+
+ private val launcher = activity.registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ val id = result.data?.getStringExtra("id")
+ val consumer = consumers.get(id)
+ consumer?.invoke(result)
+ consumers.remove(id)
+ }
+
+ fun launchActivityForResult(intent: Intent, onResult: (ActivityResult) -> Unit) {
+ val id = intent.getStringExtra("filePath")
+ if (id != null) {
+ consumers.put(id, onResult)
+ launcher.launch(intent)
+ }
+ }
+}
diff --git a/app/src/main/java/com/dd3boh/outertune/utils/scanners/FFMpegScanner.kt b/app/src/main/java/com/dd3boh/outertune/utils/scanners/FFMpegScanner.kt
index b1bbdc11d..0e6cfb742 100644
--- a/app/src/main/java/com/dd3boh/outertune/utils/scanners/FFMpegScanner.kt
+++ b/app/src/main/java/com/dd3boh/outertune/utils/scanners/FFMpegScanner.kt
@@ -1,6 +1,10 @@
package com.dd3boh.outertune.utils.scanners
-import com.dd3boh.ffMetadataEx.FFMpegWrapper
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Context
+import android.content.Intent
+import com.dd3boh.outertune.MainActivity
import com.dd3boh.outertune.db.entities.AlbumEntity
import com.dd3boh.outertune.db.entities.ArtistEntity
import com.dd3boh.outertune.db.entities.FormatEntity
@@ -12,6 +16,10 @@ import com.dd3boh.outertune.ui.utils.ARTIST_SEPARATORS
import com.dd3boh.outertune.ui.utils.DEBUG_SAVE_OUTPUT
import com.dd3boh.outertune.ui.utils.EXTRACTOR_DEBUG
import com.dd3boh.outertune.ui.utils.EXTRACTOR_TAG
+import com.dd3boh.outertune.utils.reportException
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.sync.Mutex
import timber.log.Timber
import java.io.File
import java.lang.Integer.parseInt
@@ -24,18 +32,8 @@ import kotlin.math.roundToLong
const val toSeconds = 1000 * 60 * 16.7 // convert FFmpeg duration to seconds
-class FFMpegScanner : MetadataScanner {
- // load advanced scanner libs
- init {
- System.loadLibrary("avcodec")
- System.loadLibrary("avdevice")
- System.loadLibrary("ffmetaexjni")
- System.loadLibrary("avfilter")
- System.loadLibrary("avformat")
- System.loadLibrary("avutil")
- System.loadLibrary("swresample")
- System.loadLibrary("swscale")
- }
+class FFMpegScanner(context: Context) : MetadataScanner {
+ val ctx = context
/**
* Given a path to a file, extract all necessary metadata
@@ -45,8 +43,45 @@ class FFMpegScanner : MetadataScanner {
override fun getAllMetadataFromPath(path: String): SongTempData {
if (EXTRACTOR_DEBUG)
Timber.tag(EXTRACTOR_TAG).d("Starting Full Extractor session on: $path")
- val ffmpeg = FFMpegWrapper()
- val data = ffmpeg.getFullAudioMetadata(path)
+
+ var data: String = ""
+ val mutex = Mutex(true)
+ val intent = Intent("wah.mikooomich.ffMetadataEx.ACTION_EXTRACT_METADATA").apply {
+ putExtra("filePath", path)
+ }
+
+ try {
+ (ctx as MainActivity).activityLauncher.launchActivityForResult(intent) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val metadata = result.data?.getStringExtra("rawExtractorData")
+ if (metadata != null) {
+ data = metadata
+ mutex.unlock()
+ } else {
+ data = "No metadata received"
+ }
+ } else {
+ data = "Metadata extraction failed"
+ }
+ }
+ } catch (e: ActivityNotFoundException) {
+ throw ScannerCriticalFailureException("ffMetaDataEx extractor app not found: ${e.message}")
+ }
+
+ // wait until scanner finishes
+ runBlocking {
+ var delays = 0
+
+ // TODO: make this less cursed
+ while (mutex.isLocked) {
+ delay(100)
+ delays++
+ if (delays > 100) {
+ reportException(Exception("Took too long to extract metadata from ffMetadataEx. Bailing. $path"))
+ mutex.unlock()
+ }
+ }
+ }
if (EXTRACTOR_DEBUG && DEBUG_SAVE_OUTPUT) {
Timber.tag(EXTRACTOR_TAG).d("Full output for: $path \n $data")
diff --git a/app/src/main/java/com/dd3boh/outertune/utils/scanners/LocalMediaScanner.kt b/app/src/main/java/com/dd3boh/outertune/utils/scanners/LocalMediaScanner.kt
index dc7f6b6ca..a5833c2a0 100644
--- a/app/src/main/java/com/dd3boh/outertune/utils/scanners/LocalMediaScanner.kt
+++ b/app/src/main/java/com/dd3boh/outertune/utils/scanners/LocalMediaScanner.kt
@@ -44,7 +44,7 @@ import java.util.Locale
class LocalMediaScanner(val context: Context, val scannerImpl: ScannerImpl) {
private var advancedScannerImpl: MetadataScanner = when (scannerImpl) {
ScannerImpl.TAGLIB -> TagLibScanner()
- ScannerImpl.FFMPEG_EXT -> FFMpegScanner()
+ ScannerImpl.FFMPEG_EXT -> FFMpegScanner(context)
}
init {
@@ -981,4 +981,5 @@ class LocalMediaScanner(val context: Context, val scannerImpl: ScannerImpl) {
}
class InvalidAudioFileException(message: String) : Throwable(message)
-class ScannerAbortException(message: String) : Throwable(message)
\ No newline at end of file
+class ScannerAbortException(message: String) : Throwable(message)
+class ScannerCriticalFailureException(message: String) : Throwable(message)
\ No newline at end of file
diff --git a/ffMetadataEx/.gitignore b/ffMetadataEx/.gitignore
deleted file mode 100644
index 9e3ffeef5..000000000
--- a/ffMetadataEx/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/build
-/src/main/cpp/ffmpeg-android-maker
-/src/main/cpp/src/
\ No newline at end of file
diff --git a/ffMetadataEx/build.gradle.kts b/ffMetadataEx/build.gradle.kts
deleted file mode 100644
index 5c717e66e..000000000
--- a/ffMetadataEx/build.gradle.kts
+++ /dev/null
@@ -1,52 +0,0 @@
-plugins {
- id("com.android.library")
- kotlin("android")
-}
-
-android {
- namespace = "com.dd3boh.ffMetadataEx"
- compileSdk = 35
-
- defaultConfig {
- minSdk = 24
-
- externalNativeBuild {
- cmake {
- arguments += listOf("-DCMAKE_SHARED_LINKER_FLAGS=-Wl,--build-id=none")
- }
- }
- }
-
- buildTypes {
- release {
- isMinifyEnabled = false // proguard or whatever isn't set up
- }
- }
-
- sourceSets {
- getByName("main") {
- jniLibs.srcDirs("src/main/cpp/ffmpeg-android-maker/output/lib/")
- }
- }
-
- externalNativeBuild {
- cmake {
- path = file("src/main/cpp/CMakeLists.txt")
- version = "3.22.1"
- }
- }
-
- ndkVersion = "27.0.11718014"
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
- kotlinOptions {
- jvmTarget = "17"
- }
-}
-
-dependencies {
- implementation(libs.timber)
-}
\ No newline at end of file
diff --git a/ffMetadataEx/src/main/cpp/CMakeLists.txt b/ffMetadataEx/src/main/cpp/CMakeLists.txt
deleted file mode 100644
index aad5b14dc..000000000
--- a/ffMetadataEx/src/main/cpp/CMakeLists.txt
+++ /dev/null
@@ -1,51 +0,0 @@
-# CMakeLists.txt
-cmake_minimum_required(VERSION 3.10.2)
-project(ffMetadataEx)
-
-
-add_library(avformat SHARED IMPORTED)
-set_target_properties( # Specifies the target library.
- avformat
-
- # Specifies the parameter you want to define.
- PROPERTIES IMPORTED_LOCATION
-
- # Provides the path to the library you want to import.
- ${CMAKE_SOURCE_DIR}/ffmpeg-android-maker/output/lib/${ANDROID_ABI}/libavformat.so )
-
-
-add_library(avutil SHARED IMPORTED)
-set_target_properties( # Specifies the target library.
- avutil
-
- # Specifies the parameter you want to define.
- PROPERTIES IMPORTED_LOCATION
-
- # Provides the path to the library you want to import.
- ${CMAKE_SOURCE_DIR}/ffmpeg-android-maker/output/lib/${ANDROID_ABI}/libavutil.so )
-
-add_library(avcodec SHARED IMPORTED)
-set_target_properties( # Specifies the target library.
- avcodec
-
- # Specifies the parameter you want to define.
- PROPERTIES IMPORTED_LOCATION
-
- # Provides the path to the library you want to import.
- ${CMAKE_SOURCE_DIR}/ffmpeg-android-maker/output/lib/${ANDROID_ABI}/libavcodec.so )
-
-
-# Include FFmpeg headers
-include_directories(${CMAKE_SOURCE_DIR}/ffmpeg-android-maker/output/include/${ANDROID_ABI})
-
-add_library(ffmetaexjni SHARED ffMetaExJni.cpp)
-
-# Link FFmpeg libraries
-target_link_libraries(ffmetaexjni
- avformat
- avutil
- avcodec
-)
-
-# Set the output directory for the .so file
-set_target_properties(ffmetaexjni PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
\ No newline at end of file
diff --git a/ffMetadataEx/src/main/cpp/ffMetaExJni.cpp b/ffMetadataEx/src/main/cpp/ffMetaExJni.cpp
deleted file mode 100644
index 60d4104e6..000000000
--- a/ffMetadataEx/src/main/cpp/ffMetaExJni.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
-#include
-#include
-
-extern "C" {
-#include
-#include
-#include
-}
-
-extern "C" JNIEXPORT jstring JNICALL
-Java_com_dd3boh_ffMetadataEx_FFMpegWrapper_getFullAudioMetadata(JNIEnv* env, jobject obj, jstring filePath) {
- const char* file_path = env->GetStringUTFChars(filePath, nullptr);
- if (!file_path) {
- return env->NewStringUTF("Error getting file path");
- }
-
- AVFormatContext* format_context = nullptr;
- if (avformat_open_input(&format_context, file_path, nullptr, nullptr) != 0) {
- env->ReleaseStringUTFChars(filePath, file_path);
- return env->NewStringUTF("Error opening file");
- }
-
- // Retrieve stream information
- if (avformat_find_stream_info(format_context, nullptr) < 0) {
- avformat_close_input(&format_context);
- env->ReleaseStringUTFChars(filePath, file_path);
- return env->NewStringUTF("Error finding stream information");
- }
-
- // get audio stream
- int audio_stream_index = -1;
- for (int i = 0; i < format_context->nb_streams; i++) {
- if (format_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
- audio_stream_index = i;
- break;
- }
- }
-
- std::string result;
-
- // container tags (audio containers e.g. flac, mp3)
- AVDictionaryEntry* tag = nullptr;
- while ((tag = av_dict_get(format_context->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
- result += tag->key;
- result += ": ";
- result += tag->value;
- result += "\n";
- }
-
- // bitrate
- result += "\nbitrate: " + std::to_string(format_context->bit_rate);
-
- // audio stream tags (for mixed containers e.g. ogg)
- if (audio_stream_index >= 0) {
- AVStream* audio_stream = format_context->streams[audio_stream_index];
- AVCodecParameters* codecpar = audio_stream->codecpar;
-
- // add codec information
- const char* codec_type = av_get_media_type_string(codecpar->codec_type);
- if (codec_type != nullptr) {
- result += "\ntype: ";
- result += codec_type;
- }
-
- const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);
- if (codec != nullptr) {
- result += "\ncodec: ";
- result += codec->long_name;
- } else {
- result += "\ncodec: Unknown";
- }
-
- // misc stream data
- result += "\nduration: " + std::to_string(format_context->duration);
- result += "\nsampleRate: " + std::to_string(codecpar->sample_rate);
- result += "\nchannels: " + std::to_string(codecpar->ch_layout.nb_channels);
-
- // these show up as 0
- /*
- * codecpar->bits_per_raw_sample
- * codecpar->bits_per_coded_sample
- * codecpar->frame_size
- * codecpar->bit_rate (use container bitrate instead
- */
- result += "\n";
-
- // add audio stream tags (ID3 result)
- tag = nullptr;
- while ((tag = av_dict_get(audio_stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
- result += tag->key;
- result += ": ";
- result += tag->value;
- result += "\n";
- }
- }
-
- avformat_close_input(&format_context);
- env->ReleaseStringUTFChars(filePath, file_path);
-
- return env->NewStringUTF(result.c_str());
-}
diff --git a/ffMetadataEx/src/main/java/com/dd3boh/ffMetadataEx/FFMpegWrapper.kt b/ffMetadataEx/src/main/java/com/dd3boh/ffMetadataEx/FFMpegWrapper.kt
deleted file mode 100644
index fb502c7d7..000000000
--- a/ffMetadataEx/src/main/java/com/dd3boh/ffMetadataEx/FFMpegWrapper.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.dd3boh.ffMetadataEx
-
-/**
- * Pain and suffering.
- */
-class FFMpegWrapper {
-
- external fun getFullAudioMetadata(filePath: String): String
-}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 7f4ed74a8..7c04ffe39 100755
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -16,6 +16,3 @@ include(":innertube")
include(":kugou")
include(":lrclib")
include(":material-color-utilities")
-
-// you must enable self built in \app\build.gradle.kts should you choose to uncomment this
-//include(":ffMetadataEx")