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")