From 3c406c12b459120b36120587a4d4ab7415769471 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 16 May 2023 18:58:01 -0500 Subject: [PATCH] Updates to metadata file format changing, use chapters from metadata file --- client/pages/config/index.vue | 9 ++++--- server/objects/LibraryItem.js | 30 +++++++++++++++++++---- server/objects/files/LibraryFile.js | 6 ++--- server/objects/mediaTypes/Book.js | 32 ++++++++++++++++--------- server/scanner/Scanner.js | 2 +- server/utils/abmetadataGenerator.js | 37 +++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 23 deletions(-) diff --git a/client/pages/config/index.vue b/client/pages/config/index.vue index 93a4f803..2ff59422 100644 --- a/client/pages/config/index.vue +++ b/client/pages/config/index.vue @@ -45,7 +45,7 @@
- +
@@ -355,6 +355,10 @@ export default { updateServerLanguage(val) { this.updateSettingsKey('language', val) }, + updateMetadataFileFormat(val) { + if (this.serverSettings.metadataFileFormat === val) return + this.updateSettingsKey('metadataFileFormat', val) + }, updateSettingsKey(key, val) { this.updateServerSettings({ [key]: val @@ -364,8 +368,7 @@ export default { this.updatingServerSettings = true this.$store .dispatch('updateServerSettings', payload) - .then((success) => { - console.log('Updated Server Settings', success) + .then(() => { this.updatingServerSettings = false this.$toast.success('Server settings updated') diff --git a/server/objects/LibraryItem.js b/server/objects/LibraryItem.js index 0d001799..b95559f8 100644 --- a/server/objects/LibraryItem.js +++ b/server/objects/LibraryItem.js @@ -9,6 +9,7 @@ const Podcast = require('./mediaTypes/Podcast') const Video = require('./mediaTypes/Video') const Music = require('./mediaTypes/Music') const { areEquivalent, copyValue, getId, cleanStringForSearch } = require('../utils/index') +const { filePathToPOSIX } = require('../utils/fileUtils') class LibraryItem { constructor(libraryItem = null) { @@ -368,7 +369,7 @@ class LibraryItem { const fileFoundCheck = this.checkFileFound(lf, true) if (fileFoundCheck === null) { newLibraryFiles.push(lf) - } else if (fileFoundCheck && lf.metadata.format !== 'abs') { // Ignore abs file updates + } else if (fileFoundCheck && lf.metadata.format !== 'abs' && lf.metadata.filename !== 'metadata.json') { // Ignore abs file updates hasUpdated = true existingLibraryFiles.push(lf) } else { @@ -502,17 +503,26 @@ class LibraryItem { const metadataFileFormat = global.ServerSettings.metadataFileFormat const metadataFilePath = Path.join(metadataPath, `metadata.${metadataFileFormat}`) - if (metadataFileFormat === 'json') { // Remove metadata.abs if it exists if (await fs.pathExists(Path.join(metadataPath, `metadata.abs`))) { Logger.debug(`[LibraryItem] Removing metadata.abs for item "${this.media.metadata.title}"`) await fs.remove(Path.join(metadataPath, `metadata.abs`)) + this.libraryFiles = this.libraryFiles.filter(lf => lf.metadata.path !== filePathToPOSIX(Path.join(metadataPath, `metadata.abs`))) } - return fs.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2)).then(() => { + return fs.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2)).then(async () => { + this.isSavingMetadata = false + // Add metadata.json to libraryFiles array if it is new + if (global.ServerSettings.storeMetadataWithItem && !this.libraryFiles.some(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))) { + const newLibraryFile = new LibraryFile() + await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`) + this.libraryFiles.push(newLibraryFile) + } + return true }).catch((error) => { + this.isSavingMetadata = false Logger.error(`[LibraryItem] Failed to save json file at "${metadataFilePath}"`, error) return false }) @@ -521,12 +531,22 @@ class LibraryItem { if (await fs.pathExists(Path.join(metadataPath, `metadata.json`))) { Logger.debug(`[LibraryItem] Removing metadata.json for item "${this.media.metadata.title}"`) await fs.remove(Path.join(metadataPath, `metadata.json`)) + this.libraryFiles = this.libraryFiles.filter(lf => lf.metadata.path !== filePathToPOSIX(Path.join(metadataPath, `metadata.json`))) } - return abmetadataGenerator.generate(this, metadataFilePath).then((success) => { + return abmetadataGenerator.generate(this, metadataFilePath).then(async (success) => { this.isSavingMetadata = false if (!success) Logger.error(`[LibraryItem] Failed saving abmetadata to "${metadataFilePath}"`) - else Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`) + else { + // Add metadata.abs to libraryFiles array if it is new + if (global.ServerSettings.storeMetadataWithItem && !this.libraryFiles.some(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))) { + const newLibraryFile = new LibraryFile() + await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.abs`) + this.libraryFiles.push(newLibraryFile) + } + + Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`) + } return success }) } diff --git a/server/objects/files/LibraryFile.js b/server/objects/files/LibraryFile.js index 72c39c37..d832a135 100644 --- a/server/objects/files/LibraryFile.js +++ b/server/objects/files/LibraryFile.js @@ -1,5 +1,5 @@ const Path = require('path') -const { getFileTimestampsWithIno } = require('../../utils/fileUtils') +const { getFileTimestampsWithIno, filePathToPOSIX } = require('../../utils/fileUtils') const globals = require('../../utils/globals') const FileMetadata = require('../metadata/FileMetadata') @@ -59,8 +59,8 @@ class LibraryFile { var fileMetadata = new FileMetadata() fileMetadata.setData(fileTsData) fileMetadata.filename = Path.basename(relPath) - fileMetadata.path = path - fileMetadata.relPath = relPath + fileMetadata.path = filePathToPOSIX(path) + fileMetadata.relPath = filePathToPOSIX(relPath) fileMetadata.ext = Path.extname(relPath) this.ino = fileTsData.ino this.metadata = fileMetadata diff --git a/server/objects/mediaTypes/Book.js b/server/objects/mediaTypes/Book.js index d0cfab93..5d549970 100644 --- a/server/objects/mediaTypes/Book.js +++ b/server/objects/mediaTypes/Book.js @@ -237,7 +237,7 @@ class Book { // Look for desc.txt, reader.txt, metadata.abs and opf file then update details if found async syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) { let metadataUpdatePayload = {} - let tagsUpdated = false + let hasUpdated = false const descTxt = textMetadataFiles.find(lf => lf.metadata.filename === 'desc.txt') if (descTxt) { @@ -256,18 +256,25 @@ class Book { } } - const metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs' || lf.metadata.filename === 'metadata.json') - if (metadataAbs) { - const isJSON = metadataAbs.metadata.filename === 'metadata.json' - Logger.debug(`[Book] Found ${metadataAbs.metadata.filename} file for "${this.metadata.title}"`) - const metadataText = await readTextFile(metadataAbs.metadata.path) - const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'book', isJSON) + const metadataIsJSON = global.ServerSettings.metadataFileFormat === 'json' + const metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs') + const metadataJson = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.json') + + const metadataFile = metadataIsJSON ? metadataJson : metadataAbs + if (metadataFile) { + Logger.debug(`[Book] Found ${metadataFile.metadata.filename} file for "${this.metadata.title}"`) + const metadataText = await readTextFile(metadataFile.metadata.path) + const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'book', metadataIsJSON) if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) { Logger.debug(`[Book] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates) if (abmetadataUpdates.tags) { // Set media tags if updated this.tags = abmetadataUpdates.tags - tagsUpdated = true + hasUpdated = true + } + if (abmetadataUpdates.chapters) { // Set chapters if updated + this.chapters = abmetadataUpdates.chapters + hasUpdated = true } if (abmetadataUpdates.metadata) { metadataUpdatePayload = { @@ -276,6 +283,9 @@ class Book { } } } + } else if (metadataAbs || metadataJson) { // Has different metadata file format so mark as updated + Logger.debug(`[Book] Found different format metadata file ${(metadataAbs || metadataJson).metadata.filename}, expecting .${global.ServerSettings.metadataFileFormat} for "${this.metadata.title}"`) + hasUpdated = true } const metadataOpf = textMetadataFiles.find(lf => lf.isOPFFile || lf.metadata.filename === 'metadata.xml') @@ -289,7 +299,7 @@ class Book { if (key === 'tags') { // Add tags only if tags are empty if (opfMetadata.tags.length && (!this.tags.length || opfMetadataOverrideDetails)) { this.tags = opfMetadata.tags - tagsUpdated = true + hasUpdated = true } } else if (key === 'genres') { // Add genres only if genres are empty if (opfMetadata.genres.length && (!this.metadata.genres.length || opfMetadataOverrideDetails)) { @@ -321,9 +331,9 @@ class Book { } if (Object.keys(metadataUpdatePayload).length) { - return this.metadata.update(metadataUpdatePayload) || tagsUpdated + return this.metadata.update(metadataUpdatePayload) || hasUpdated } - return tagsUpdated + return hasUpdated } searchQuery(query) { diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index d6f20930..3a0f75f1 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -111,8 +111,8 @@ class Scanner { } if (hasUpdated) { - SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) await this.db.updateLibraryItem(libraryItem) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) return ScanResult.UPDATED } return ScanResult.UPTODATE diff --git a/server/utils/abmetadataGenerator.js b/server/utils/abmetadataGenerator.js index 26cffbae..b0415aea 100644 --- a/server/utils/abmetadataGenerator.js +++ b/server/utils/abmetadataGenerator.js @@ -3,6 +3,7 @@ const filePerms = require('./filePerms') const package = require('../../package.json') const Logger = require('../Logger') const { getId } = require('./index') +const areEquivalent = require('../utils/areEquivalent') const CurrentAbMetadataVersion = 2 @@ -424,6 +425,33 @@ function parseJsonMetadataText(text) { } } +function cleanChaptersArray(chaptersArray, mediaTitle) { + const chapters = [] + let index = 0 + for (const chap of chaptersArray) { + if (chap.start === null || isNaN(chap.start)) { + Logger.error(`[abmetadataGenerator] Invalid chapter start time ${chap.start} for "${mediaTitle}" metadata file`) + return null + } + if (chap.end === null || isNaN(chap.end)) { + Logger.error(`[abmetadataGenerator] Invalid chapter end time ${chap.end} for "${mediaTitle}" metadata file`) + return null + } + if (!chap.title || typeof chap.title !== 'string') { + Logger.error(`[abmetadataGenerator] Invalid chapter title ${chap.title} for "${mediaTitle}" metadata file`) + return null + } + + chapters.push({ + id: index++, + start: chap.start, + end: chap.end, + title: chap.title + }) + } + return chapters +} + // Input text from abmetadata file and return object of media changes // only returns object of changes. empty object means no changes function parseAndCheckForUpdates(text, media, mediaType, isJSON) { @@ -477,6 +505,15 @@ function parseAndCheckForUpdates(text, media, mediaType, isJSON) { } } + if (abmetadataData.chapters && mediaType === 'book') { + const abmetadataChaptersCleaned = cleanChaptersArray(abmetadataData.chapters) + if (abmetadataChaptersCleaned) { + if (!areEquivalent(abmetadataChaptersCleaned, media.chapters)) { + updatePayload.chapters = abmetadataChaptersCleaned + } + } + } + if (Object.keys(metadataUpdatePayload).length) { updatePayload.metadata = metadataUpdatePayload }