From ef2b9a0415c1d71e84ca96578a284a77b6638c9d Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 1 Oct 2021 20:29:00 -0500 Subject: [PATCH] Scan for covers now saves covers, server settings to save covers in audiobook folder --- client/components/ui/ToggleSwitch.vue | 2 +- client/package.json | 2 +- client/pages/config/index.vue | 54 ++++++++++++++++++++++++--- client/plugins/constants.js | 8 +++- package.json | 2 +- server/CoverController.js | 19 +++------- server/Scanner.js | 18 ++++++--- server/Server.js | 4 +- server/objects/Audiobook.js | 5 ++- 9 files changed, 83 insertions(+), 31 deletions(-) diff --git a/client/components/ui/ToggleSwitch.vue b/client/components/ui/ToggleSwitch.vue index 73892a51..9292d59d 100644 --- a/client/components/ui/ToggleSwitch.vue +++ b/client/components/ui/ToggleSwitch.vue @@ -30,7 +30,7 @@ export default { } }, className() { - if (this.disabled) return 'bg-bg cursor-not-allowed' + if (this.disabled) return this.toggleValue ? `bg-${this.onColor} cursor-not-allowed` : `bg-${this.offColor} cursor-not-allowed` return this.toggleValue ? `bg-${this.onColor}` : `bg-${this.offColor}` }, switchClassName() { diff --git a/client/package.json b/client/package.json index 91940d35..a9d7b142 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "1.3.3", + "version": "1.3.4", "description": "Audiobook manager and player", "main": "index.js", "scripts": { diff --git a/client/pages/config/index.vue b/client/pages/config/index.vue index a0bb7efa..c25ef8b6 100644 --- a/client/pages/config/index.vue +++ b/client/pages/config/index.vue @@ -42,9 +42,9 @@
- + -

Parse Subtitles info_outlined

+

Parse subtitles info_outlined

@@ -53,12 +53,30 @@ Scan
- + Scan for Covers
+
+ + - +
+

Metadata

+
+
+
+ + +

Store covers with audiobook info_outlined

+
+
+
+
+
+ + Save Metadata +
@@ -101,18 +119,21 @@ export default { }, data() { return { + storeCoversInAudiobookDir: false, isResettingAudiobooks: false, users: [], selectedAccount: null, showAccountModal: false, isDeletingUser: false, - newServerSettings: {} + newServerSettings: {}, + updatingServerSettings: false } }, watch: { serverSettings(newVal, oldVal) { if (newVal && !oldVal) { this.newServerSettings = { ...this.serverSettings } + this.storeCoversInAudiobookDir = this.newServerSettings.coverDestination === this.$constants.CoverDestination.AUDIOBOOK } } }, @@ -120,6 +141,12 @@ export default { parseSubtitleTooltip() { return 'Extract subtitles from audiobook directory names.
Subtitle must be seperated by " - "
i.e. "Book Title - A Subtitle Here" has the subtitle "A Subtitle Here"' }, + coverDestinationTooltip() { + return 'By default covers are stored in /metadata/books, enabling this setting will store covers inside your audiobooks directory. Only one file named "cover" will be kept.' + }, + saveMetadataTooltip() { + return 'This will write a "metadata.nfo" file in all of your audiobook directories.' + }, serverSettings() { return this.$store.state.serverSettings }, @@ -134,6 +161,12 @@ export default { } }, methods: { + updateCoverStorageDestination(val) { + this.newServerSettings.coverDestination = val ? this.$constants.CoverDestination.AUDIOBOOK : this.$constants.CoverDestination.METADATA + this.updateServerSettings({ + coverDestination: this.newServerSettings.coverDestination + }) + }, updateScannerParseSubtitle(val) { var payload = { scannerParseSubtitle: val @@ -141,13 +174,16 @@ export default { this.updateServerSettings(payload) }, updateServerSettings(payload) { + this.updatingServerSettings = true this.$store .dispatch('updateServerSettings', payload) .then((success) => { console.log('Updated Server Settings', success) + this.updatingServerSettings = false }) .catch((error) => { console.error('Failed to update server settings', error) + this.updatingServerSettings = false }) }, setDeveloperMode() { @@ -161,7 +197,14 @@ export default { scanCovers() { this.$root.socket.emit('scan_covers') }, + saveMetadataComplete(result) { + this.savingMetadata = false + if (!result) return + this.$toast.success(`Metadata saved for ${result.success} audiobooks`) + }, saveMetadataFiles() { + this.savingMetadata = true + this.$root.socket.once('save_metadata_complete', this.saveMetadataComplete) this.$root.socket.emit('save_metadata') }, loadUsers() { @@ -247,6 +290,7 @@ export default { this.$root.socket.on('user_removed', this.userRemoved) this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {} + this.storeCoversInAudiobookDir = this.newServerSettings.coverDestination === this.$constants.CoverDestination.AUDIOBOOK } }, mounted() { diff --git a/client/plugins/constants.js b/client/plugins/constants.js index 35dc5e08..dbd71632 100644 --- a/client/plugins/constants.js +++ b/client/plugins/constants.js @@ -5,8 +5,14 @@ const DownloadStatus = { FAILED: 3 } +const CoverDestination = { + METADATA: 0, + AUDIOBOOK: 1 +} + const Constants = { - DownloadStatus + DownloadStatus, + CoverDestination } export default ({ app }, inject) => { diff --git a/package.json b/package.json index 592e2f4a..fd4e75d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "1.3.3", + "version": "1.3.4", "description": "Self-hosted audiobook server for managing and playing audiobooks", "main": "index.js", "scripts": { diff --git a/server/CoverController.js b/server/CoverController.js index fba0a791..0aa5aedd 100644 --- a/server/CoverController.js +++ b/server/CoverController.js @@ -8,7 +8,6 @@ const imageType = require('image-type') const globals = require('./utils/globals') const { CoverDestination } = require('./utils/constants') - class CoverController { constructor(db, MetadataPath, AudiobookPath) { this.db = db @@ -52,8 +51,8 @@ class CoverController { } } - // Remove covers in metadata/books/{ID} that dont have the same filename as the new cover - async checkBookMetadataCovers(dirpath, newCoverExt) { + // Remove covers that dont have the same filename as the new cover + async removeOldCovers(dirpath, newCoverExt) { var filesInDir = await this.getFilesInDirectory(dirpath) for (let i = 0; i < filesInDir.length; i++) { @@ -97,17 +96,11 @@ class CoverController { var { fullPath, relPath } = this.getCoverDirectory(audiobook) await fs.ensureDir(fullPath) - var isStoringInMetadata = relPath.slice(1).startsWith('metadata') var coverFilename = `cover${extname}` var coverFullPath = Path.join(fullPath, coverFilename) var coverPath = Path.join(relPath, coverFilename) - - if (isStoringInMetadata) { - await this.checkBookMetadataCovers(fullPath, extname) - } - // Move cover from temp upload dir to destination var success = await coverFile.mv(coverFullPath).then(() => true).catch((error) => { Logger.error('[CoverController] Failed to move cover file', path, error) @@ -115,12 +108,13 @@ class CoverController { }) if (!success) { - // return res.status(500).send('Failed to move cover into destination') return { error: 'Failed to move cover into destination' } } + await this.removeOldCovers(fullPath, extname) + Logger.info(`[CoverController] Uploaded audiobook cover "${coverPath}" for "${audiobook.title}"`) audiobook.updateBookCover(coverPath) @@ -171,10 +165,7 @@ class CoverController { var coverFullPath = Path.join(fullPath, coverFilename) await fs.rename(temppath, coverFullPath) - var isStoringInMetadata = relPath.slice(1).startsWith('metadata') - if (isStoringInMetadata) { - await this.checkBookMetadataCovers(fullPath, '.' + imgtype.ext) - } + await this.removeOldCovers(fullPath, '.' + imgtype.ext) Logger.info(`[CoverController] Downloaded audiobook cover "${coverPath}" from url "${url}" for "${audiobook.title}"`) diff --git a/server/Scanner.js b/server/Scanner.js index 44a7b78a..e3881c9c 100644 --- a/server/Scanner.js +++ b/server/Scanner.js @@ -10,12 +10,13 @@ const { secondsToTimestamp } = require('./utils/fileUtils') const { ScanResult, CoverDestination } = require('./utils/constants') class Scanner { - constructor(AUDIOBOOK_PATH, METADATA_PATH, db, emitter) { + constructor(AUDIOBOOK_PATH, METADATA_PATH, db, coverController, emitter) { this.AudiobookPath = AUDIOBOOK_PATH this.MetadataPath = METADATA_PATH this.BookMetadataPath = Path.join(this.MetadataPath, 'books') this.db = db + this.coverController = coverController this.emitter = emitter this.cancelScan = false @@ -453,6 +454,8 @@ class Scanner { var audiobooksNeedingCover = this.audiobooks.filter(ab => !ab.cover && ab.author) var found = 0 var notFound = 0 + var failed = 0 + for (let i = 0; i < audiobooksNeedingCover.length; i++) { var audiobook = audiobooksNeedingCover[i] var options = { @@ -462,10 +465,15 @@ class Scanner { var results = await this.bookFinder.findCovers('openlibrary', audiobook.title, audiobook.author, options) if (results.length) { Logger.debug(`[Scanner] Found best cover for "${audiobook.title}"`) - audiobook.book.cover = results[0] - await this.db.updateAudiobook(audiobook) - found++ - this.emitter('audiobook_updated', audiobook.toJSONMinified()) + var coverUrl = results[0] + var result = await this.coverController.downloadCoverFromUrl(audiobook, coverUrl) + if (result.error) { + failed++ + } else { + found++ + await this.db.updateAudiobook(audiobook) + this.emitter('audiobook_updated', audiobook.toJSONMinified()) + } } else { notFound++ } diff --git a/server/Server.js b/server/Server.js index b47e5ab1..c3ee1fe4 100644 --- a/server/Server.js +++ b/server/Server.js @@ -36,10 +36,10 @@ class Server { this.db = new Db(this.ConfigPath) this.auth = new Auth(this.db) this.watcher = new Watcher(this.AudiobookPath) - this.scanner = new Scanner(this.AudiobookPath, this.MetadataPath, this.db, this.emitter.bind(this)) + this.coverController = new CoverController(this.db, this.MetadataPath, this.AudiobookPath) + this.scanner = new Scanner(this.AudiobookPath, this.MetadataPath, this.db, this.coverController, this.emitter.bind(this)) this.streamManager = new StreamManager(this.db, this.MetadataPath) this.rssFeeds = new RssFeeds(this.Port, this.db) - this.coverController = new CoverController(this.db, this.MetadataPath, this.AudiobookPath) this.downloadManager = new DownloadManager(this.db, this.MetadataPath, this.AudiobookPath, this.emitter.bind(this)) this.apiController = new ApiController(this.MetadataPath, this.db, this.scanner, this.auth, this.streamManager, this.rssFeeds, this.downloadManager, this.coverController, this.emitter.bind(this), this.clientEmitter.bind(this)) this.hlsController = new HlsController(this.db, this.scanner, this.auth, this.streamManager, this.emitter.bind(this), this.streamManager.StreamsPath) diff --git a/server/objects/Audiobook.js b/server/objects/Audiobook.js index 2fec1e86..a4b777df 100644 --- a/server/objects/Audiobook.js +++ b/server/objects/Audiobook.js @@ -437,7 +437,10 @@ class Audiobook { this.otherFiles = this.otherFiles.filter(f => newOtherFilePaths.includes(f.path)) // Some files are not there anymore and filtered out - if (currOtherFileNum !== this.otherFiles.length) hasUpdates = true + if (currOtherFileNum !== this.otherFiles.length) { + Logger.debug(`[Audiobook] ${currOtherFileNum - this.otherFiles.length} other files were removed for "${this.title}"`) + hasUpdates = true + } // If desc.txt is new or forcing rescan then read it and update description if empty var descriptionTxt = newOtherFiles.find(file => file.filename === 'desc.txt')