mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-01 00:18:14 +01:00
Fix listener for audiobook updates in edit modal, add remove cover button
This commit is contained in:
parent
cb40e063da
commit
1dc01615dd
@ -25,18 +25,21 @@ export default {
|
||||
return {
|
||||
selectedTab: 'details',
|
||||
processing: false,
|
||||
audiobook: null
|
||||
audiobook: null,
|
||||
fetchOnShow: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
if (this.audiobook && this.audiobook.id === this.selectedAudiobookId) return
|
||||
if (this.audiobook && this.audiobook.id === this.selectedAudiobookId) {
|
||||
if (this.fetchOnShow) this.fetchFull()
|
||||
return
|
||||
}
|
||||
this.fetchOnShow = false
|
||||
this.audiobook = null
|
||||
this.init()
|
||||
} else {
|
||||
this.$store.commit('audiobooks/removeListener', 'edit-modal')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -75,7 +78,10 @@ export default {
|
||||
this.selectedTab = tab
|
||||
},
|
||||
audiobookUpdated() {
|
||||
this.fetchFull()
|
||||
if (!this.show) this.fetchOnShow = true
|
||||
else {
|
||||
this.fetchFull()
|
||||
}
|
||||
},
|
||||
init() {
|
||||
this.$store.commit('audiobooks/addListener', { meth: this.audiobookUpdated, id: 'edit-modal', audiobookId: this.selectedAudiobookId })
|
||||
|
@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<div class="w-full h-full overflow-hidden overflow-y-auto px-1">
|
||||
<div class="flex">
|
||||
<cards-book-cover :audiobook="audiobook" />
|
||||
<div class="relative">
|
||||
<cards-book-cover :audiobook="audiobook" />
|
||||
<!-- book cover overlay -->
|
||||
<div v-if="book.cover" class="absolute top-0 left-0 w-full h-full z-10 opacity-0 hover:opacity-100 transition-opacity duration-100">
|
||||
<div class="absolute top-0 left-0 w-full h-16 bg-gradient-to-b from-black-600 to-transparent" />
|
||||
<div class="p-1 absolute top-1 right-1 text-red-500 rounded-full w-8 h-8 cursor-pointer hover:text-red-400 shadow-sm" @click="removeCover">
|
||||
<span class="material-icons">delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow pl-6 pr-2">
|
||||
<form @submit.prevent="submitForm">
|
||||
<div class="flex items-center">
|
||||
@ -19,7 +28,6 @@
|
||||
|
||||
<div v-if="showLocalCovers" class="flex items-center justify-center">
|
||||
<template v-for="cover in localCovers">
|
||||
<!-- <img :src="`/local/${cover.path}`" :key="cover.path" class="w-20 h-32 object-cover m-0.5" /> -->
|
||||
<div :key="cover.path" class="m-0.5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover.localPath === imageUrl ? 'border-yellow-300' : ''" @click="setCover(cover.localPath)">
|
||||
<img :src="cover.localPath" class="h-24 object-cover" style="width: 60px" />
|
||||
</div>
|
||||
@ -43,9 +51,11 @@
|
||||
<div v-if="hasSearched" class="flex items-center flex-wrap justify-center max-h-60 overflow-y-scroll mt-2 max-w-full">
|
||||
<p v-if="!coversFound.length">No Covers Found</p>
|
||||
<template v-for="cover in coversFound">
|
||||
<div :key="cover" class="m-0.5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover === imageUrl ? 'border-yellow-300' : ''" @click="setCover(cover)">
|
||||
<img :src="cover" class="h-24 object-cover" style="width: 60px" />
|
||||
</div>
|
||||
<ui-tooltip :key="cover" direction="bottom" :text="cover">
|
||||
<div class="m-0.5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover === imageUrl ? 'border-yellow-300' : ''" @click="setCover(cover)">
|
||||
<img :src="cover" class="h-24 object-cover" style="width: 60px" />
|
||||
</div>
|
||||
</ui-tooltip>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@ -78,7 +88,9 @@ export default {
|
||||
audiobook: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) this.init()
|
||||
if (newVal) {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -118,10 +130,22 @@ export default {
|
||||
this.searchTitle = this.book.title || ''
|
||||
this.searchAuthor = this.book.author || ''
|
||||
},
|
||||
removeCover() {
|
||||
if (!this.book.cover) {
|
||||
this.imageUrl = ''
|
||||
return
|
||||
}
|
||||
this.updateCover('')
|
||||
},
|
||||
submitForm() {
|
||||
this.updateCover(this.imageUrl)
|
||||
},
|
||||
async updateCover(cover) {
|
||||
if (cover === this.book.cover) {
|
||||
console.warn('Cover has not changed..', cover)
|
||||
return
|
||||
}
|
||||
|
||||
this.isProcessing = true
|
||||
const updatePayload = {
|
||||
book: {
|
||||
@ -134,7 +158,6 @@ export default {
|
||||
})
|
||||
this.isProcessing = false
|
||||
if (updatedAudiobook) {
|
||||
console.log('Update Successful', updatedAudiobook)
|
||||
this.$toast.success('Update Successful')
|
||||
this.$emit('close')
|
||||
}
|
||||
|
@ -130,7 +130,6 @@ export default {
|
||||
})
|
||||
this.isProcessing = false
|
||||
if (updatedAudiobook) {
|
||||
console.log('Update Successful', updatedAudiobook)
|
||||
this.$toast.success('Update Successful')
|
||||
this.$emit('close')
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getSearchQuery() {
|
||||
var searchQuery = `provider=${this.provider}&title=${this.searchTitle}`
|
||||
var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${this.searchTitle}`
|
||||
if (this.searchAuthor) searchQuery += `&author=${this.searchAuthor}`
|
||||
return searchQuery
|
||||
},
|
||||
@ -129,7 +129,6 @@ export default {
|
||||
})
|
||||
this.isProcessing = false
|
||||
if (updatedAudiobook) {
|
||||
console.log('Update Successful', updatedAudiobook)
|
||||
this.$toast.success('Update Successful')
|
||||
this.$emit('close')
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "0.9.77-beta",
|
||||
"version": "0.9.78-beta",
|
||||
"description": "Audiobook manager and player",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "0.9.77-beta",
|
||||
"version": "0.9.78-beta",
|
||||
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -95,13 +95,21 @@ class Book {
|
||||
if (data.otherFiles && data.otherFiles.length) {
|
||||
var imageFile = data.otherFiles.find(f => f.filetype === 'image')
|
||||
if (imageFile) {
|
||||
this.cover = Path.join('/local', imageFile.path)
|
||||
this.cover = Path.normalize(Path.join('/local', imageFile.path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
var hasUpdates = false
|
||||
|
||||
if (payload.cover) {
|
||||
// If updating to local cover then normalize path
|
||||
if (!payload.cover.startsWith('http:') && !payload.cover.startsWith('https:')) {
|
||||
payload.cover = Path.normalize(payload.cover)
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in payload) {
|
||||
if (payload[key] === undefined) continue;
|
||||
|
||||
|
@ -12,7 +12,7 @@ class BookFinder {
|
||||
async findByISBN(isbn) {
|
||||
var book = await this.openLibrary.isbnLookup(isbn)
|
||||
if (book.errorCode) {
|
||||
console.error('Book not found')
|
||||
Logger.error('Book not found')
|
||||
}
|
||||
return book
|
||||
}
|
||||
@ -61,73 +61,82 @@ class BookFinder {
|
||||
return books.map(b => {
|
||||
b.cleanedTitle = this.cleanTitleForCompares(b.title)
|
||||
b.titleDistance = levenshteinDistance(b.cleanedTitle, title)
|
||||
|
||||
// Total length of search (title or both title & author)
|
||||
b.totalPossibleDistance = b.title.length
|
||||
|
||||
if (author) {
|
||||
if (!b.author) {
|
||||
b.authorDistance = author.length
|
||||
} else {
|
||||
b.totalPossibleDistance += b.author.length
|
||||
b.cleanedAuthor = this.cleanAuthorForCompares(b.author)
|
||||
|
||||
var cleanedAuthorDistance = levenshteinDistance(b.cleanedAuthor, searchAuthor)
|
||||
var authorDistance = levenshteinDistance(b.author || '', author)
|
||||
|
||||
// Use best distance
|
||||
if (cleanedAuthorDistance > authorDistance) b.authorDistance = authorDistance
|
||||
else b.authorDistance = cleanedAuthorDistance
|
||||
b.authorDistance = Math.min(cleanedAuthorDistance, authorDistance)
|
||||
|
||||
// Check book author contains searchAuthor
|
||||
if (searchAuthor.length > 4 && b.cleanedAuthor.includes(searchAuthor)) b.includesAuthor = searchAuthor
|
||||
else if (author.length > 4 && b.author.includes(author)) b.includesAuthor = author
|
||||
}
|
||||
}
|
||||
b.totalDistance = b.titleDistance + (b.authorDistance || 0)
|
||||
b.totalPossibleDistance = b.title.length
|
||||
|
||||
if (b.cleanedTitle.includes(searchTitle) && searchTitle.length > 4) {
|
||||
b.includesSearch = searchTitle
|
||||
} else if (b.title.includes(searchTitle) && searchTitle.length > 4) {
|
||||
b.includesSearch = searchTitle
|
||||
}
|
||||
|
||||
if (author && b.author) b.totalPossibleDistance += b.author.length
|
||||
// Check book title contains the searchTitle
|
||||
if (searchTitle.length > 4 && b.cleanedTitle.includes(searchTitle)) b.includesTitle = searchTitle
|
||||
else if (title.length > 4 && b.title.includes(title)) b.includesTitle = title
|
||||
|
||||
return b
|
||||
}).filter(b => {
|
||||
if (b.includesSearch) { // If search was found in result title exactly then skip over leven distance check
|
||||
Logger.debug(`Exact search was found inside title ${b.cleanedTitle}/${b.includesSearch}`)
|
||||
if (b.includesTitle) { // If search title was found in result title then skip over leven distance check
|
||||
Logger.debug(`Exact title was included in "${b.title}", Search: "${b.includesTitle}"`)
|
||||
} else if (b.titleDistance > maxTitleDistance) {
|
||||
Logger.debug(`Filtering out search result title distance = ${b.titleDistance}: "${b.cleanedTitle}"/"${searchTitle}"`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (author && b.authorDistance > maxAuthorDistance) {
|
||||
Logger.debug(`Filtering out search result "${b.title}", author distance = ${b.authorDistance}: "${b.author}"/"${author}"`)
|
||||
return false
|
||||
if (author) {
|
||||
if (b.includesAuthor) { // If search author was found in result author then skip over leven distance check
|
||||
Logger.debug(`Exact author was included in "${b.author}", Search: "${b.includesAuthor}"`)
|
||||
} else if (b.authorDistance > maxAuthorDistance) {
|
||||
Logger.debug(`Filtering out search result "${b.author}", author distance = ${b.authorDistance}: "${b.author}"/"${author}"`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (b.totalPossibleDistance < 4 && b.totalDistance > 0) return false
|
||||
// If book total search length < 5 and was not exact match, then filter out
|
||||
if (b.totalPossibleDistance < 5 && b.totalDistance > 0) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
async getLibGenResults(title, author, maxTitleDistance, maxAuthorDistance) {
|
||||
var books = await this.libGen.search(title)
|
||||
Logger.info(`LibGen Book Search Results: ${books.length || 0}`)
|
||||
Logger.debug(`LibGen Book Search Results: ${books.length || 0}`)
|
||||
if (books.errorCode) {
|
||||
Logger.error(`LibGen Search Error ${books.errorCode}`)
|
||||
return []
|
||||
}
|
||||
var booksFiltered = this.filterSearchResults(books, title, author, maxTitleDistance, maxAuthorDistance)
|
||||
if (!booksFiltered.length && books.length) {
|
||||
Logger.info(`Search has ${books.length} matches, but no close title matches`)
|
||||
Logger.debug(`Search has ${books.length} matches, but no close title matches`)
|
||||
}
|
||||
return booksFiltered
|
||||
}
|
||||
|
||||
async getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance) {
|
||||
var books = await this.openLibrary.searchTitle(title)
|
||||
Logger.info(`OpenLib Book Search Results: ${books.length || 0}`)
|
||||
Logger.debug(`OpenLib Book Search Results: ${books.length || 0}`)
|
||||
if (books.errorCode) {
|
||||
Logger.error(`OpenLib Search Error ${books.errorCode}`)
|
||||
return []
|
||||
}
|
||||
var booksFiltered = this.filterSearchResults(books, title, author, maxTitleDistance, maxAuthorDistance)
|
||||
if (!booksFiltered.length && books.length) {
|
||||
Logger.info(`Search has ${books.length} matches, but no close title matches`)
|
||||
Logger.debug(`Search has ${books.length} matches, but no close title matches`)
|
||||
}
|
||||
return booksFiltered
|
||||
}
|
||||
@ -136,7 +145,7 @@ class BookFinder {
|
||||
var books = []
|
||||
var maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4
|
||||
var maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4
|
||||
Logger.info(`Book Search, title: "${title}", author: "${author}", provider: ${provider}`)
|
||||
Logger.debug(`Book Search, title: "${title}", author: "${author}", provider: ${provider}`)
|
||||
|
||||
if (provider === 'libgen') {
|
||||
books = await this.getLibGenResults(title, author, maxTitleDistance, maxAuthorDistance)
|
||||
@ -147,18 +156,16 @@ class BookFinder {
|
||||
var olBooks = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
|
||||
books = books.concat(lbBooks, olBooks)
|
||||
} else {
|
||||
var olBooks = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
|
||||
var hasCloseMatch = olBooks.find(b => (b.totalDistance < 4 && b.totalPossibleDistance > 4))
|
||||
if (hasCloseMatch) {
|
||||
books = olBooks
|
||||
} else {
|
||||
Logger.info(`Book Search, LibGen has no close matches - get openlib results also`)
|
||||
books = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
|
||||
var hasCloseMatch = books.find(b => (b.totalDistance < 2 && b.totalPossibleDistance > 6))
|
||||
if (!hasCloseMatch) {
|
||||
Logger.debug(`Book Search, openlib has no super close matches - get libgen results also`)
|
||||
var lbBooks = await this.getLibGenResults(title, author, maxTitleDistance, maxAuthorDistance)
|
||||
books = books.concat(lbBooks)
|
||||
}
|
||||
|
||||
if (!books.length && author) {
|
||||
Logger.info(`Book Search, no matches for title and author.. check title only`)
|
||||
if (!books.length && author && options.fallbackTitleOnly) {
|
||||
Logger.debug(`Book Search, no matches for title and author.. check title only`)
|
||||
return this.search(provider, title, null, options)
|
||||
}
|
||||
}
|
||||
@ -170,7 +177,7 @@ class BookFinder {
|
||||
|
||||
async findCovers(provider, title, author, options = {}) {
|
||||
var searchResults = await this.search(provider, title, author, options)
|
||||
Logger.info(`[BookFinder] FindCovers search results: ${searchResults.length}`)
|
||||
Logger.debug(`[BookFinder] FindCovers search results: ${searchResults.length}`)
|
||||
|
||||
var covers = []
|
||||
searchResults.forEach((result) => {
|
||||
|
@ -247,7 +247,7 @@ class Scanner {
|
||||
}
|
||||
var results = await this.bookFinder.findCovers('openlibrary', audiobook.title, audiobook.author, options)
|
||||
if (results.length) {
|
||||
Logger.info(`[Scanner] Found best cover for "${audiobook.title}"`)
|
||||
Logger.debug(`[Scanner] Found best cover for "${audiobook.title}"`)
|
||||
audiobook.book.cover = results[0]
|
||||
await this.db.updateAudiobook(audiobook)
|
||||
found++
|
||||
@ -294,7 +294,10 @@ class Scanner {
|
||||
|
||||
async findCovers(req, res) {
|
||||
var query = req.query
|
||||
var result = await this.bookFinder.findCovers(query.provider, query.title, query.author || null)
|
||||
var options = {
|
||||
fallbackTitleOnly: !!query.fallbackTitleOnly
|
||||
}
|
||||
var result = await this.bookFinder.findCovers(query.provider, query.title, query.author || null, options)
|
||||
res.json(result)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user