This commit is contained in:
advplyr 2021-12-05 19:22:24 -06:00
commit 04cbc49577
14 changed files with 76 additions and 44 deletions

View File

@ -1,6 +1,6 @@
<template>
<div class="w-full h-16 bg-primary relative">
<div id="appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-40">
<div id="appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-50">
<div class="flex h-full items-center">
<img v-if="!showBack" src="/Logo48.png" class="w-10 h-10 md:w-12 md:h-12 mr-4" />
<a v-if="showBack" @click="back" class="rounded-full h-12 w-12 flex items-center justify-center hover:bg-white hover:bg-opacity-10 mr-4 cursor-pointer">

View File

@ -11,7 +11,7 @@
<p>Series</p>
</nuxt-link>
</div>
<div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-20 flex items-center px-2 md:px-8">
<div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-30 flex items-center px-2 md:px-8">
<template v-if="page !== 'search' && !isHome">
<p v-if="!selectedSeries" class="font-book hidden md:block">{{ numShowing }} {{ entityName }}</p>
<div v-else class="items-center hidden md:flex">

View File

@ -1,5 +1,5 @@
<template>
<div class="w-20 bg-bg h-full relative box-shadow-side z-30" style="min-width: 80px">
<div class="w-20 bg-bg h-full relative box-shadow-side z-40" style="min-width: 80px">
<div class="absolute top-0 -right-4 w-4 bg-bg h-10 pointer-events-none" />
<nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">

View File

@ -7,7 +7,7 @@
</span>
</button>
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-56 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" role="listbox" aria-labelledby="listbox-label">
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-80 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" role="listbox" aria-labelledby="listbox-label">
<template v-for="item in items">
<li :key="item.value" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400" :class="item.value === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedOption(item.value)">
<div class="flex items-center">

View File

@ -10,6 +10,7 @@
<p class="text-xl font-book pl-4 hover:underline cursor-pointer" @click.stop="$emit('click', library)">{{ library.name }}</p>
<div class="flex-grow" />
<ui-btn v-show="isHovering && !libraryScan && canScan" small color="bg" @click.stop="scan">Scan</ui-btn>
<ui-btn v-show="isHovering && !libraryScan && canScan" small color="bg" class="ml-2" @click.stop="forceScan">Force Re-Scan</ui-btn>
<span v-show="isHovering && !libraryScan && showEdit && canEdit" class="material-icons text-xl text-gray-300 hover:text-gray-50 ml-4 cursor-pointer" @click.stop="editClick">edit</span>
<span v-show="!libraryScan && isHovering && showEdit && canDelete && !isDeleting" class="material-icons text-xl text-gray-300 ml-3" :class="isMain ? 'text-opacity-5 cursor-not-allowed' : 'hover:text-gray-50 cursor-pointer'" @click.stop="deleteClick">delete</span>
<div v-show="isDeleting" class="text-xl text-gray-300 ml-3 animate-spin">
@ -58,15 +59,15 @@ export default {
}
},
methods: {
itemClicked() {
// this.$emit('click', this.library)
},
editClick() {
this.$emit('edit', this.library)
},
scan() {
this.$root.socket.emit('scan', this.library.id)
},
forceScan() {
this.$root.socket.emit('scan', this.library.id, { forceRescan: true })
},
deleteClick() {
if (this.isMain) return
if (confirm(`Are you sure you want to permanently delete library "${this.library.name}"?`)) {

View File

@ -6,14 +6,16 @@
<span class="material-icons" style="font-size: 1.4rem">add</span>
</div>
</div>
<draggable v-model="libraryCopies" v-bind="dragOptions" class="list-group" draggable=".item" tag="div" @start="startDrag" @end="endDrag">
<!-- <transition-group type="transition" :name="!drag ? 'flip-list' : null"> -->
<draggable :list="libraryCopies" v-bind="dragOptions" class="list-group" draggable=".item" tag="div" @start="startDrag" @end="endDrag">
<template v-for="library in libraryCopies">
<modals-libraries-library-item :key="library.id" :library="library" :selected="currentLibraryId === library.id" :show-edit="true" :dragging="drag" @edit="editLibrary" @click="setLibrary" class="item" />
<div :key="library.id" class="item">
<modals-libraries-library-item :library="library" :selected="currentLibraryId === library.id" :show-edit="true" :dragging="drag" @edit="editLibrary" @sort="draggableSort" @click="setLibrary" />
</div>
</template>
<!-- </transition-group> -->
</draggable>
<modals-edit-library-modal v-model="showLibraryModal" :library="selectedLibrary" />
<p class="text-xs mt-4 text-gray-200">*<strong>Force Re-Scan</strong> will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be probed/parsed and used for book details.</p>
</div>
</template>
@ -48,6 +50,9 @@ export default {
},
libraries() {
return this.$store.getters['libraries/getSortedLibraries']()
},
libraryScans() {
return this.$store.state.scanners.libraryScans
}
},
methods: {
@ -55,10 +60,9 @@ export default {
this.drag = true
clearTimeout(this.orderTimeout)
},
endDrag() {
endDrag(e) {
this.drag = false
this.checkOrder()
console.log('DRAG END')
},
checkOrder() {
clearTimeout(this.orderTimeout)
@ -78,7 +82,7 @@ export default {
})
var newOrder = libraryOrderData.map((lib) => lib.id).join(',')
if (currOrder !== newOrder) {
this.$axios.$patch('/api/libraries/order', libraryOrderData).then((libraries) => {
this.$axios.$post('/api/libraries/order', libraryOrderData).then((libraries) => {
if (libraries && libraries.length) {
this.$toast.success('Library order saved', { timeout: 1500 })
this.$store.commit('libraries/set', libraries)

View File

@ -210,7 +210,7 @@ export const mutations = {
}
if (audiobook.book.genres && audiobook.book.genres.length) {
audiobook.book.genres.forEach((genre) => {
if (tag && !state.filterData.genres.includes(genre)) state.filterData.genres.push(genre)
if (genre && !state.filterData.genres.includes(genre)) state.filterData.genres.push(genre)
})
}
}

View File

@ -63,7 +63,8 @@ class ApiController {
this.router.get('/libraries/:id/search', LibraryController.middleware.bind(this), LibraryController.search.bind(this))
this.router.get('/libraries/:id/stats', LibraryController.middleware.bind(this), LibraryController.stats.bind(this))
this.router.get('/libraries/:id/authors', LibraryController.middleware.bind(this), LibraryController.getAuthors.bind(this))
this.router.patch('/libraries/order', LibraryController.reorder.bind(this))
this.router.post('/libraries/order', LibraryController.reorder.bind(this))
// TEMP: Support old syntax for mobile app
this.router.get('/library/:id/audiobooks', LibraryController.middleware.bind(this), LibraryController.getBooksForLibrary.bind(this))

View File

@ -316,9 +316,9 @@ class Server {
await this.scanner2.scanFilesChanged(fileUpdates)
}
async scan(libraryId) {
async scan(libraryId, options = {}) {
Logger.info('[Server] Starting Scan')
await this.scanner2.scan(libraryId)
await this.scanner2.scan(libraryId, options)
// await this.scanner.scan(libraryId)
Logger.info('[Server] Scan complete')
}

View File

@ -96,9 +96,9 @@ class AudioFile {
this.exclude = !!data.exclude
this.error = data.error || null
this.trackNumFromMeta = data.trackNumFromMeta || null
this.trackNumFromFilename = data.trackNumFromFilename || null
this.cdNumFromFilename = data.cdNumFromFilename || null
this.trackNumFromMeta = data.trackNumFromMeta
this.trackNumFromFilename = data.trackNumFromFilename
this.cdNumFromFilename = data.cdNumFromFilename
this.format = data.format
this.duration = data.duration
@ -130,9 +130,9 @@ class AudioFile {
this.fullPath = data.fullPath
this.addedAt = Date.now()
this.trackNumFromMeta = data.trackNumFromMeta || null
this.trackNumFromFilename = data.trackNumFromFilename || null
this.cdNumFromFilename = data.cdNumFromFilename || null
this.trackNumFromMeta = data.trackNumFromMeta
this.trackNumFromFilename = data.trackNumFromFilename
this.cdNumFromFilename = data.cdNumFromFilename
this.manuallyVerified = !!data.manuallyVerified
this.invalid = !!data.invalid
@ -302,9 +302,9 @@ class AudioFile {
hasUpdated = true
}
} else if (this[key] !== newjson[key]) {
// console.log(this.filename, 'key', key, 'updated', this[key], newjson[key])
this[key] = newjson[key]
hasUpdated = true
// console.log('key', key, 'updated', this[key], newjson[key])
}
}
return hasUpdated

View File

@ -542,10 +542,9 @@ class Audiobook {
var alreadyHasDescTxt = otherFilenamesAlreadyInBook.includes('desc.txt')
var alreadyHasReaderTxt = otherFilenamesAlreadyInBook.includes('reader.txt')
// Filter out other files no longer in directory
var newOtherFilePaths = newOtherFiles.map(f => f.path)
this.otherFiles = this.otherFiles.filter(f => newOtherFilePaths.includes(f.path))
// Some files are not there anymore and filtered out
if (currOtherFileNum !== this.otherFiles.length) {
Logger.debug(`[Audiobook] ${currOtherFileNum - this.otherFiles.length} other files were removed for "${this.title}"`)
hasUpdates = true
@ -937,6 +936,8 @@ class Audiobook {
var newAudioFileData = []
var newOtherFileData = []
var existingAudioFileData = []
var existingOtherFileData = []
dataFound.audioFiles.forEach((af) => {
var audioFileFoundCheck = this.checkFileFound(af, true)
@ -944,6 +945,9 @@ class Audiobook {
newAudioFileData.push(af)
} else if (audioFileFoundCheck) {
hasUpdated = true
existingAudioFileData.push(af)
} else {
existingAudioFileData.push(af)
}
})
@ -953,6 +957,9 @@ class Audiobook {
newOtherFileData.push(otherFileData)
} else if (fileFoundCheck) {
hasUpdated = true
existingOtherFileData.push(otherFileData)
} else {
existingOtherFileData.push(otherFileData)
}
})
@ -1010,7 +1017,9 @@ class Audiobook {
newAudioFileData,
newOtherFileData,
audioFilesRemoved,
otherFilesRemoved
otherFilesRemoved,
existingAudioFileData, // Existing file data may get re-scanned if forceRescan is set
existingOtherFileData
}
}
}

View File

@ -139,12 +139,10 @@ class AudioFileScanner {
}
if (existingAF) {
if (audiobook.updateAudioFile(newAF)) {
// console.log('update dauido file')
hasUpdated = true
}
} else {
audiobook.addAudioFile(newAF)
// console.log('added auido file')
hasUpdated = true
}
}

View File

@ -35,7 +35,7 @@ class AudioProbeData {
setData(data) {
var audioStream = this.getDefaultAudioStream(data.audio_streams)
this.embeddedCoverArt = data.video_stream ? this.getEmbeddedCoverArt(data.video_stream) : false
this.embeddedCoverArt = data.video_stream ? this.getEmbeddedCoverArt(data.video_stream) : null
this.format = data.format
this.duration = data.duration
this.size = data.size

View File

@ -221,7 +221,7 @@ class Scanner {
}
} else {
var checkRes = audiobook.checkScanData(dataFound, version)
if (checkRes.newAudioFileData.length || checkRes.newOtherFileData.length) { // Audiobook has new files
if (checkRes.newAudioFileData.length || checkRes.newOtherFileData.length || libraryScan.scanOptions.forceRescan) { // Audiobook has new files
checkRes.audiobook = audiobook
checkRes.bookScanData = dataFound
audiobookDataToRescan.push(checkRes)
@ -305,9 +305,11 @@ class Scanner {
return this.rescanAudiobook(abd, libraryScan)
}))
audiobooksUpdated = audiobooksUpdated.filter(ab => ab) // Filter out nulls
libraryScan.resultsUpdated += audiobooksUpdated.length
await this.db.updateEntities('audiobook', audiobooksUpdated)
this.emitter('audiobooks_updated', audiobooksUpdated.map(ab => ab.toJSONExpanded()))
if (audiobooksUpdated.length) {
libraryScan.resultsUpdated += audiobooksUpdated.length
await this.db.updateEntities('audiobook', audiobooksUpdated)
this.emitter('audiobooks_updated', audiobooksUpdated.map(ab => ab.toJSONExpanded()))
}
}
async scanNewAudiobookDataChunk(newAudiobookDataToScan, libraryScan) {
@ -321,24 +323,38 @@ class Scanner {
}
async rescanAudiobook(audiobookCheckData, libraryScan) {
const { newAudioFileData, newOtherFileData, audiobook, bookScanData } = audiobookCheckData
const { newAudioFileData, newOtherFileData, audiobook, bookScanData, updated, existingAudioFileData, existingOtherFileData } = audiobookCheckData
libraryScan.addLog(LogLevel.DEBUG, `Library "${libraryScan.libraryName}" Re-scanning "${audiobook.path}"`)
var hasUpdated = updated
// Sync other files first to use local images as cover before extracting audio file cover
if (newOtherFileData.length) {
if (newOtherFileData.length || libraryScan.scanOptions.forceRescan) {
// TODO: Cleanup other file sync
var allOtherFiles = newOtherFileData.concat(audiobook._otherFiles)
await audiobook.syncOtherFiles(allOtherFiles, this.MetadataPath, libraryScan.preferOpfMetadata)
var allOtherFiles = newOtherFileData.concat(existingOtherFileData)
if (await audiobook.syncOtherFiles(allOtherFiles, this.MetadataPath, libraryScan.preferOpfMetadata)) {
hasUpdated = true
}
}
// forceRescan all existing audio files - will probe and update ID3 tag metadata
if (libraryScan.scanOptions.forceRescan && existingAudioFileData.length) {
if (await AudioFileScanner.scanAudioFiles(existingAudioFileData, bookScanData, audiobook, libraryScan.preferAudioMetadata, libraryScan)) {
hasUpdated = true
}
}
// Scan new audio files
if (newAudioFileData.length) {
await AudioFileScanner.scanAudioFiles(newAudioFileData, bookScanData, audiobook, libraryScan.preferAudioMetadata, libraryScan)
// Extract embedded cover art if cover is not already in directory
if (await AudioFileScanner.scanAudioFiles(newAudioFileData, bookScanData, audiobook, libraryScan.preferAudioMetadata, libraryScan)) {
hasUpdated = true
}
}
// If an audio file has embedded cover art and no cover is set yet, extract & use it
if (newAudioFileData.length || libraryScan.scanOptions.forceRescan) {
if (audiobook.hasEmbeddedCoverArt && !audiobook.cover) {
var outputCoverDirs = this.getCoverDirectory(audiobook)
var relativeDir = await audiobook.saveEmbeddedCoverArt(outputCoverDirs.fullPath, outputCoverDirs.relPath)
if (relativeDir) {
hasUpdated = true
libraryScan.addLog(LogLevel.DEBUG, `Saved embedded cover art "${relativeDir}"`)
}
}
@ -346,17 +362,20 @@ class Scanner {
if (!audiobook.audioFilesToInclude.length && !audiobook.ebooks.length) { // Audiobook is invalid
audiobook.setInvalid()
hasUpdated = true
} else if (audiobook.isInvalid) {
audiobook.isInvalid = false
hasUpdated = true
}
// Scan for cover if enabled and has no cover
// Scan for cover if enabled and has no cover (and author or title has changed OR has been 7 days since last lookup)
if (audiobook && libraryScan.findCovers && !audiobook.cover && audiobook.book.shouldSearchForCover) {
var updatedCover = await this.searchForCover(audiobook, libraryScan)
audiobook.book.updateLastCoverSearch(updatedCover)
hasUpdated = true
}
return audiobook
return hasUpdated ? audiobook : null
}
async scanNewAudiobook(audiobookData, preferAudioMetadata, preferOpfMetadata, findCovers, libraryScan = null) {