Add local library items to bookshelf and landing page

This commit is contained in:
advplyr
2022-04-03 17:07:26 -05:00
parent 4f8b13b23d
commit 9fd3dc6978
15 changed files with 279 additions and 125 deletions
@@ -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
}