Add new folder scanner for media items with ffprober

This commit is contained in:
advplyr
2022-03-30 19:41:04 -05:00
parent 4fc70cd3dd
commit a8de03b82d
6 changed files with 113 additions and 46 deletions
@@ -1,20 +1,17 @@
package com.audiobookshelf.app
import android.Manifest
import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.SimpleStorage
import com.anggrayudi.storage.callback.FolderPickerCallback
import com.anggrayudi.storage.callback.StorageAccessCallback
import com.anggrayudi.storage.file.*
import com.audiobookshelf.app.device.FolderScanner
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.getcapacitor.*
import com.getcapacitor.annotation.CapacitorPlugin
@@ -159,12 +156,19 @@ class StorageManager : Plugin() {
@PluginMethod
fun searchFolder(call: PluginCall) {
var folderUrl = call.data.getString("folderUrl", "").toString()
var mediaType = call.data.getString("mediaType", "book").toString()
Log.d(TAG, "Searching folder $folderUrl")
var folderScanner = FolderScanner(context)
var data = folderScanner.scanForAudiobooks(folderUrl)
Log.d(TAG, "Scan DATA $data")
call.resolve(JSObject())
var folderScanResult = folderScanner.scanForMediaItems(folderUrl, mediaType)
if (folderScanResult == null) {
Log.d(TAG, "NO Scan DATA")
call.resolve(JSObject())
} else {
Log.d(TAG, "Scan DATA ${jacksonObjectMapper().writeValueAsString(folderScanResult)}")
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(folderScanResult)))
}
//
// var df: DocumentFile? = DocumentFileCompat.fromUri(context, Uri.parse(folderUrl))
//
@@ -150,5 +150,6 @@ data class AudioTrack(
var title:String,
var contentUrl:String,
var mimeType:String,
var isLocal:Boolean
var isLocal:Boolean,
var audioProbeResult:AudioProbeResult?
)
@@ -1,6 +1,7 @@
package com.audiobookshelf.app.data
import android.net.Uri
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.getcapacitor.JSObject
data class ServerConfig(
@@ -17,8 +18,24 @@ data class DeviceData(
var lastServerConfigId:String?
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class LocalMediaItem(
val name: String,
val simplePath: String,
val audioTracks:MutableList<AudioTrack>
var name: String,
var contentUrl:String,
var simplePath: String,
var absolutePath:String,
var audioTracks:MutableList<AudioTrack>,
var localFiles:MutableList<LocalFile>,
var coverPath:String?
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class LocalFile(
var id:String,
var filename:String?,
var contentUrl:String,
var absolutePath:String,
var simplePath:String,
var mimeType:String?,
var size:Long
)
@@ -0,0 +1,9 @@
package com.audiobookshelf.app.data
data class FolderScanResult(
val name:String?,
val absolutePath:String,
val mediaType:String,
val contentUrl:String,
val localMediaItems:MutableList<LocalMediaItem>,
)
@@ -5,39 +5,35 @@ import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.file.*
import com.arthenica.ffmpegkit.FFmpegKitConfig
import com.arthenica.ffmpegkit.FFprobeKit
import com.arthenica.ffmpegkit.FFprobeSession
import com.arthenica.ffmpegkit.Level
import com.audiobookshelf.app.data.AudioProbeResult
import com.audiobookshelf.app.data.AudioTrack
import com.audiobookshelf.app.data.LocalMediaItem
import com.audiobookshelf.app.data.PlaybackSession
import com.audiobookshelf.app.data.*
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
class FolderScanner(var ctx: Context) {
private val tag = "FolderScanner"
fun scanForAudiobooks(folderUrl: String):MutableList<LocalMediaItem> {
fun scanForMediaItems(folderUrl: String, mediaType:String):FolderScanResult? {
var df: DocumentFile? = DocumentFileCompat.fromUri(ctx, Uri.parse(folderUrl))
if (df == null) {
Log.e(tag, "Folder Doc File Invalid $folderUrl")
return mutableListOf()
return null
}
var mediaFolders = mutableListOf<LocalMediaItem>()
var foldersFound = df.search(false, DocumentFileType.FOLDER)
var mediaItems = mutableListOf<LocalMediaItem>()
foldersFound.forEach {
Log.d(tag, "Iterating over Folder Found ${it.name} | ${it.getSimplePath(ctx)} | URI: ${it.uri}")
var folderName = it.name ?: ""
var mediaFiles = mutableListOf<LocalMediaItem>()
var audioTracks = mutableListOf<AudioTrack>()
var localFiles = mutableListOf<LocalFile>()
var index = 1
var startOffset = 0.0
var coverPath:String? = null
var filesInFolder = it.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
filesInFolder.forEach { it2 ->
@@ -46,12 +42,16 @@ class FolderScanner(var ctx: Context) {
var isAudio = mimeType.startsWith("audio")
Log.d(tag, "Found $mimeType file $filename in folder $folderName")
var localFile = LocalFile(it2.id,it2.name,it2.uri.toString(),it2.getAbsolutePath(ctx),it2.getSimplePath(ctx),it2.mimeType,it2.length())
localFiles.add(localFile)
Log.d(tag, "File attributes Id:${it2.id}|ContentUrl:${localFile.contentUrl}|isDownloadsDocument:${it2.isDownloadsDocument}")
if (isAudio) {
var absolutePath = it2.getAbsolutePath(ctx)
Log.d(tag, "Audio File Path $absolutePath")
Log.d(tag, "Scanning Audio File Path ${localFile.absolutePath}")
// TODO: Make asynchronous
var session = FFprobeKit.execute("-i \"$absolutePath\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet")
var session = FFprobeKit.execute("-i \"${localFile.absolutePath}\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet")
var sessionData = session.output
Log.d(tag, "AFTER FFPROBE STRING $sessionData")
@@ -59,19 +59,29 @@ class FolderScanner(var ctx: Context) {
val audioProbeResult = mapper.readValue<AudioProbeResult>(sessionData)
Log.d(tag, "Probe Result DATA ${audioProbeResult.duration} | ${audioProbeResult.size} | ${audioProbeResult.title} | ${audioProbeResult.artist}")
var track = AudioTrack(index, 0.0, 0.0, filename, absolutePath, mimeType, true)
var track = AudioTrack(index, startOffset, audioProbeResult.duration, filename, localFile.contentUrl, mimeType, true, audioProbeResult)
audioTracks.add(track)
startOffset += audioProbeResult.duration
} else {
Log.d(tag, "Found non audio file $filename")
// First image file use as cover path
if (coverPath == null) {
coverPath = localFile.absolutePath
}
}
// var imageFile = StorageManager.MediaFile(it2.uri, filename, it2.getSimplePath(context), it2.length(), mimeType, isAudio)
// mediaFiles.add(imageFile)
}
if (mediaFiles.size > 0) {
if (audioTracks.size > 0) {
Log.d(tag, "Found local media item named $folderName with ${audioTracks.size} tracks")
var localMediaItem = LocalMediaItem(folderName, it.uri.toString(), it.getSimplePath(ctx), it.getAbsolutePath(ctx),audioTracks,localFiles,coverPath)
mediaItems.add(localMediaItem)
}
}
return mediaFolders
return if (mediaItems.size > 0) {
Log.d(tag, "Found ${mediaItems.size} Media Items")
FolderScanResult(df.name, df.getAbsolutePath(ctx), mediaType, df.uri.toString(), mediaItems)
} else {
Log.d(tag, "No Media Items Found")
null
}
}
}