Skip to content

Commit

Permalink
scanners: Move FFmpeg extractor to external app
Browse files Browse the repository at this point in the history
* ffMetadataEx is now at https://github.com/mikooomich/ffMetadataEx and
  is an optional external app.
* This commit was forged with my tears.
  • Loading branch information
mikooomich committed Jan 4, 2025
1 parent d3d37f8 commit b154538
Show file tree
Hide file tree
Showing 17 changed files with 94 additions and 272 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
22 changes: 0 additions & 22 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<br/><br/>

Expand Down
10 changes: 0 additions & 10 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Binary file removed app/prebuilt/ffMetadataEx-release.aar
Binary file not shown.
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
<intent>
<action android:name="android.media.action.DISPLAY_AUDIO_EFFECT_CONTROL_PANEL" />
</intent>

<package android:name="wah.mikooomich.ffMetadataEx" />
<intent>
<action android:name="wah.mikooomich.ffMetadataEx.ACTION_EXTRACT_METADATA"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent>
</queries>
<application
android:name=".App"
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/dd3boh/outertune/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ import com.dd3boh.outertune.ui.utils.appBarScrollBehavior
import com.dd3boh.outertune.ui.utils.cacheDirectoryTree
import com.dd3boh.outertune.ui.utils.getLocalThumbnail
import com.dd3boh.outertune.ui.utils.resetHeightOffset
import com.dd3boh.outertune.utils.ActivityLauncherHelper
import com.dd3boh.outertune.utils.NetworkConnectivityObserver
import com.dd3boh.outertune.utils.SyncUtils
import com.dd3boh.outertune.utils.dataStore
Expand Down Expand Up @@ -240,6 +241,8 @@ class MainActivity : ComponentActivity() {
@Inject
lateinit var syncUtils: SyncUtils

lateinit var activityLauncher: ActivityLauncherHelper

private var playerConnection by mutableStateOf<PlayerConnection?>(null)
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Expand Down Expand Up @@ -307,6 +310,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)

activityLauncher = ActivityLauncherHelper(this)

setContent {
val connectivityObserver = NetworkConnectivityObserver(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ fun LibraryFoldersScreen(

// initialize with first directory
if (folderStack.isEmpty()) {
println("wtrf reinti")
viewModel.getLocalSongs(database)

folderStack.push(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, ((ActivityResult) -> 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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -981,4 +981,5 @@ class LocalMediaScanner(val context: Context, val scannerImpl: ScannerImpl) {
}

class InvalidAudioFileException(message: String) : Throwable(message)
class ScannerAbortException(message: String) : Throwable(message)
class ScannerAbortException(message: String) : Throwable(message)
class ScannerCriticalFailureException(message: String) : Throwable(message)
3 changes: 0 additions & 3 deletions ffMetadataEx/.gitignore

This file was deleted.

52 changes: 0 additions & 52 deletions ffMetadataEx/build.gradle.kts

This file was deleted.

51 changes: 0 additions & 51 deletions ffMetadataEx/src/main/cpp/CMakeLists.txt

This file was deleted.

Loading

0 comments on commit b154538

Please sign in to comment.