mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-07-29 07:58:43 +02:00
Add local library items to bookshelf and landing page
This commit is contained in:
@@ -141,9 +141,9 @@ class AudioDownloader : Plugin() {
|
||||
var downloadItem = DownloadItem(libraryItem.id, localFolder, bookTitle, mutableListOf())
|
||||
var itemFolderPath = localFolder.absolutePath + "/" + bookTitle
|
||||
tracks.forEach { audioFile ->
|
||||
var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioFile.metadata.relPath)}"
|
||||
var destinationFilename = getFilenameFromRelPath(audioFile.metadata.relPath)
|
||||
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioFile.metadata.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
||||
var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioFile.relPath)}"
|
||||
var destinationFilename = getFilenameFromRelPath(audioFile.relPath)
|
||||
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioFile.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
||||
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
||||
var destinationUri = Uri.fromFile(destinationFile)
|
||||
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}")
|
||||
|
||||
@@ -78,34 +78,26 @@ class MyNativeAudio : Plugin() {
|
||||
var libraryItemId = call.getString("libraryItemId", "").toString()
|
||||
var playWhenReady = call.getBoolean("playWhenReady") == true
|
||||
|
||||
apiHandler.playLibraryItem(libraryItemId, false) {
|
||||
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}")
|
||||
playerNotificationService.preparePlayer(it, playWhenReady)
|
||||
if (libraryItemId.startsWith("local")) { // Play local media item
|
||||
DeviceManager.dbManager.getLocalMediaItem(libraryItemId)?.let {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
Log.d(tag, "Preparing Local Media item ${jacksonObjectMapper().writeValueAsString(it)}")
|
||||
var playbackSession = it.getPlaybackSession()
|
||||
playerNotificationService.preparePlayer(playbackSession, playWhenReady)
|
||||
}
|
||||
return call.resolve(JSObject())
|
||||
}
|
||||
} else { // Play library item from server
|
||||
apiHandler.playLibraryItem(libraryItemId, false) {
|
||||
|
||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(it)))
|
||||
}
|
||||
}
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}")
|
||||
playerNotificationService.preparePlayer(it, playWhenReady)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun playLocalLibraryItem(call:PluginCall) {
|
||||
var localMediaItemId = call.getString("localMediaItemId", "").toString()
|
||||
var playWhenReady = call.getBoolean("playWhenReady") == true
|
||||
Log.d(tag, "playLocalLibraryItem $playWhenReady")
|
||||
|
||||
DeviceManager.dbManager.loadLocalMediaItem(localMediaItemId)?.let {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
Log.d(tag, "Preparing Local Media item ${jacksonObjectMapper().writeValueAsString(it)}")
|
||||
var playbackSession = it.getPlaybackSession()
|
||||
playerNotificationService.preparePlayer(playbackSession, playWhenReady)
|
||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(it)))
|
||||
}
|
||||
return call.resolve(JSObject())
|
||||
}
|
||||
var errObj = JSObject()
|
||||
errObj.put("error", "Item Not Found")
|
||||
call.resolve(errObj)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.audiobookshelf.app.data
|
||||
|
||||
import android.net.Uri
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@@ -24,8 +24,16 @@ data class AudioProbeChapter(
|
||||
val id:Int,
|
||||
val start:Int,
|
||||
val end:Int,
|
||||
val tags:AudioProbeChapterTags
|
||||
)
|
||||
val tags:AudioProbeChapterTags?
|
||||
) {
|
||||
@JsonIgnore
|
||||
fun getBookChapter():BookChapter {
|
||||
var startS = start / 1000.0
|
||||
var endS = end / 1000.0
|
||||
var title = tags?.title ?: "Chapter $id"
|
||||
return BookChapter(id, startS, endS, title)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class AudioProbeFormatTags(
|
||||
@@ -57,4 +65,10 @@ class AudioProbeResult (
|
||||
val size get() = format.size
|
||||
val title get() = format.tags.title ?: format.filename.split("/").last()
|
||||
val artist get() = format.tags.artist ?: ""
|
||||
|
||||
@JsonIgnore
|
||||
fun getBookChapters(): List<BookChapter> {
|
||||
if (chapters.isEmpty()) return mutableListOf()
|
||||
return chapters.map { it.getBookChapter() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ data class Podcast(
|
||||
data class Book(
|
||||
var metadata:BookMetadata,
|
||||
var coverPath:String?,
|
||||
var tags:MutableList<String>,
|
||||
var audioFiles:MutableList<AudioFile>,
|
||||
var chapters:MutableList<BookChapter>,
|
||||
var tracks:MutableList<AudioFile>?,
|
||||
var tags:List<String>,
|
||||
var audioFiles:List<AudioFile>,
|
||||
var chapters:List<BookChapter>,
|
||||
var tracks:List<AudioTrack>?,
|
||||
var size:Long?,
|
||||
var duration:Double?
|
||||
) : MediaType()
|
||||
@@ -154,9 +154,10 @@ data class AudioTrack(
|
||||
var title:String,
|
||||
var contentUrl:String,
|
||||
var mimeType:String,
|
||||
var metadata:FileMetadata?,
|
||||
var isLocal:Boolean,
|
||||
var localFileId:String?,
|
||||
var audioProbeResult:AudioProbeResult?
|
||||
var audioProbeResult:AudioProbeResult?,
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
@@ -165,6 +166,13 @@ data class AudioTrack(
|
||||
val durationMs get() = (duration * 1000L).toLong()
|
||||
@get:JsonIgnore
|
||||
val endOffsetMs get() = startOffsetMs + durationMs
|
||||
@get:JsonIgnore
|
||||
val relPath get() = metadata?.relPath ?: ""
|
||||
|
||||
@JsonIgnore
|
||||
fun getBookChapter():BookChapter {
|
||||
return BookChapter(index + 1,startOffset, startOffset + duration, title)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
||||
@@ -56,7 +56,7 @@ class DbManager : Plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadLocalMediaItem(localMediaItemId:String):LocalMediaItem? {
|
||||
fun getLocalMediaItem(localMediaItemId:String):LocalMediaItem? {
|
||||
return Paper.book("localMediaItems").read(localMediaItemId)
|
||||
}
|
||||
|
||||
@@ -153,6 +153,32 @@ class DbManager : Plugin() {
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun getLocalLibraryItems_WV(call:PluginCall) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
var localLibraryItems = getLocalMediaItems().map {
|
||||
it.getLocalLibraryItem()
|
||||
}
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("localLibraryItems", jacksonObjectMapper().writeValueAsString(localLibraryItems))
|
||||
call.resolve(jsobj)
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun getLocalLibraryItem_WV(call:PluginCall) {
|
||||
var id = call.getString("id", "").toString()
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
var mediaItem = getLocalMediaItem(id)
|
||||
var localLibraryItem = mediaItem?.getLocalLibraryItem()
|
||||
if (localLibraryItem == null) {
|
||||
call.resolve()
|
||||
} else {
|
||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localLibraryItem)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun setCurrentServerConnectionConfig_WV(call:PluginCall) {
|
||||
var serverConnectionConfigId = call.getString("id", "").toString()
|
||||
|
||||
@@ -18,6 +18,18 @@ data class DeviceData(
|
||||
var lastServerConnectionConfigId:String?
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class LocalLibraryItem(
|
||||
var id:String,
|
||||
var folderId:String,
|
||||
var absolutePath:String,
|
||||
var isInvalid:Boolean,
|
||||
var mediaType:String,
|
||||
var media:MediaType,
|
||||
var localFiles:MutableList<LocalFile>,
|
||||
var isLocal:Boolean
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class LocalMediaItem(
|
||||
var id:String,
|
||||
@@ -40,6 +52,13 @@ data class LocalMediaItem(
|
||||
return total
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getTotalSize():Long {
|
||||
var total = 0L
|
||||
localFiles.forEach { total += it.size }
|
||||
return total
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getMediaMetadata():MediaTypeMetadata {
|
||||
return if (mediaType == "book") {
|
||||
@@ -54,7 +73,36 @@ data class LocalMediaItem(
|
||||
var sessionId = "play-${UUID.randomUUID()}"
|
||||
|
||||
var mediaMetadata = getMediaMetadata()
|
||||
return PlaybackSession(sessionId,null,null,null, mediaType, mediaMetadata, mutableListOf(), name, "author name here",null,getDuration(),PLAYMETHOD_LOCAL,audioTracks,0.0,null,this,null,null)
|
||||
var chapters = getAudiobookChapters()
|
||||
var authorName = "Unknown"
|
||||
if (mediaType == "book") {
|
||||
var bookMetadata = mediaMetadata as BookMetadata
|
||||
authorName = bookMetadata?.authorName ?: "Unknown"
|
||||
}
|
||||
return PlaybackSession(sessionId,null,null,null, mediaType, mediaMetadata, chapters, name, authorName,null,getDuration(),PLAYMETHOD_LOCAL,audioTracks,0.0,null,this,null,null)
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getAudiobookChapters():List<BookChapter> {
|
||||
if (mediaType != "book" || audioTracks.isEmpty()) return mutableListOf()
|
||||
if (audioTracks.size == 1) { // Single track audiobook look for chapters from ffprobe
|
||||
return audioTracks[0].audioProbeResult?.getBookChapters() ?: mutableListOf()
|
||||
}
|
||||
// Multi-track make chapters from tracks
|
||||
return audioTracks.map { it.getBookChapter() }
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun getLocalLibraryItem():LocalLibraryItem {
|
||||
var mediaMetadata = getMediaMetadata()
|
||||
if (mediaType == "book") {
|
||||
var chapters = getAudiobookChapters()
|
||||
var book = Book(mediaMetadata as BookMetadata, coverContentUrl, mutableListOf(), mutableListOf(), chapters,audioTracks,getTotalSize(),getDuration())
|
||||
return LocalLibraryItem(id, folderId, absolutePath, false,mediaType, book, localFiles, true)
|
||||
} else {
|
||||
var podcast = Podcast(mediaMetadata as PodcastMetadata, coverContentUrl, mutableListOf(), mutableListOf(), false)
|
||||
return LocalLibraryItem(id, folderId, absolutePath, false, mediaType, podcast,localFiles,true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class PlaybackSession(
|
||||
var episodeId:String?,
|
||||
var mediaType:String,
|
||||
var mediaMetadata:MediaTypeMetadata,
|
||||
var chapters:MutableList<BookChapter>,
|
||||
var chapters:List<BookChapter>,
|
||||
var displayTitle: String?,
|
||||
var displayAuthor: String?,
|
||||
var coverPath:String?,
|
||||
|
||||
@@ -11,9 +11,6 @@ import com.arthenica.ffmpegkit.Level
|
||||
import com.audiobookshelf.app.data.*
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class FolderScanner(var ctx: Context) {
|
||||
private val tag = "FolderScanner"
|
||||
@@ -61,7 +58,7 @@ class FolderScanner(var ctx: Context) {
|
||||
Log.d(tag, "Iterating over Folder Found ${it.name} | ${it.getSimplePath(ctx)} | URI: ${it.uri}")
|
||||
|
||||
var itemFolderName = it.name ?: ""
|
||||
var itemId = DeviceManager.getBase64Id(it.id)
|
||||
var itemId = "local_" + DeviceManager.getBase64Id(it.id)
|
||||
|
||||
var existingMediaItem = existingMediaItems.find { emi -> emi.id == itemId }
|
||||
var existingLocalFiles = existingMediaItem?.localFiles ?: mutableListOf()
|
||||
@@ -130,7 +127,7 @@ class FolderScanner(var ctx: Context) {
|
||||
audioTrackToAdd = existingAudioTrack
|
||||
} else {
|
||||
// Create new audio track
|
||||
var track = AudioTrack(index, startOffset, audioProbeResult.duration, filename, localFile.contentUrl, mimeType, true, localFileId, audioProbeResult)
|
||||
var track = AudioTrack(index, startOffset, audioProbeResult.duration, filename, localFile.contentUrl, mimeType, null, true, localFileId, audioProbeResult)
|
||||
audioTrackToAdd = track
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user