diff --git a/client/components/modals/libraries/EditModal.vue b/client/components/modals/libraries/EditModal.vue index 8fe4c2ec1..5b81b6d7b 100644 --- a/client/components/modals/libraries/EditModal.vue +++ b/client/components/modals/libraries/EditModal.vue @@ -126,6 +126,7 @@ export default { skipMatchingMediaWithIsbn: false, autoScanCronExpression: null, matchAfterScan: false, + matchMinConfidence: 0, hideSingleBookSeries: false, onlyShowLaterBooksInContinueSeries: false, metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'], diff --git a/client/components/modals/libraries/ScheduleScan.vue b/client/components/modals/libraries/ScheduleScan.vue index 25a6b298e..5d46d0d47 100644 --- a/client/components/modals/libraries/ScheduleScan.vue +++ b/client/components/modals/libraries/ScheduleScan.vue @@ -9,6 +9,10 @@
{{ $strings.MessageScheduleLibraryScanNote }}
@@ -29,10 +33,21 @@ export default { return { cronExpression: null, enableAutoScan: false, - matchAfterScan: false + matchAfterScan: false, + matchMinConfidence: 0 + } + }, + computed: { + matchMinConfidencePercentage: { + get() { + return this.matchMinConfidence * 100 + }, + set(val) { + this.matchMinConfidence = val / 100 + this.updateMatchMinConfidence() + } } }, - computed: {}, methods: { checkBlurExpressionInput() { // returns true if advanced cron input is focused @@ -60,10 +75,18 @@ export default { } }) }, + updateMatchMinConfidence() { + this.$emit('update', { + settings: { + matchMinConfidence: this.matchMinConfidence + } + }) + }, init() { this.cronExpression = this.library.settings.autoScanCronExpression this.enableAutoScan = !!this.cronExpression this.matchAfterScan = this.library.settings.matchAfterScan + this.matchMinConfidence = this.library.settings.matchMinConfidence || 0 } }, mounted() { diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 06866ced3..c81a9b94c 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -449,6 +449,7 @@ "LabelLowestPriority": "Lowest Priority", "LabelMatchConfidence": "Confidence", "LabelMatchAfterScan": "Run 'Match Books' after scan", + "LabelMatchMinConfidence": "Minimum match confidence (0-100)", "LabelMatchExistingUsersBy": "Match existing users by", "LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider", "LabelMaxEpisodesToDownload": "Max # of episodes to download. Use 0 for unlimited.", diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 8daeb6a89..c8a36b3a9 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -354,6 +354,19 @@ class LibraryController { updatedSettings[key] = req.body.settings[key] === null ? null : Number(req.body.settings[key]) Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`) } + } else if (key === 'matchMinConfidence') { + if (req.body.settings[key] !== null && isNaN(req.body.settings[key])) { + Logger.error(`[LibraryController] Invalid request. Setting "${key}" must be a number`) + return res.status(400).send(`Invalid request. Setting "${key}" must be a number`) + } else if (req.body.settings[key] !== null && (Number(req.body.settings[key]) < 0 || Number(req.body.settings[key]) > 1)) { + Logger.error(`[LibraryController] Invalid request. Setting "${key}" must be between 0 and 1`) + return res.status(400).send(`Invalid request. Setting "${key}" must be between 0 and 1`) + } + if (req.body.settings[key] !== updatedSettings[key]) { + hasUpdates = true + updatedSettings[key] = req.body.settings[key] === null ? null : Number(req.body.settings[key]) + Logger.debug(`[LibraryController] Library "${req.library.name}" updating setting "${key}" to "${updatedSettings[key]}"`) + } } else if (key === 'matchAfterScan') { if (typeof req.body.settings[key] !== 'boolean') { return res.status(400).send('Invalid request. Setting "matchAfterScan" must be a boolean') diff --git a/server/managers/CronManager.js b/server/managers/CronManager.js index 8849d8491..33ddd87b0 100644 --- a/server/managers/CronManager.js +++ b/server/managers/CronManager.js @@ -84,7 +84,9 @@ class CronManager { checkRemoveEmptySeries, checkRemoveAuthorsWithNoBooks } - Scanner.matchLibraryItems(apiRouterCtx, library) + Scanner.matchLibraryItems(apiRouterCtx, library, { + minConfidence: library.settings.matchMinConfidence + }) } } }) diff --git a/server/models/Library.js b/server/models/Library.js index e10f442dd..b647c030c 100644 --- a/server/models/Library.js +++ b/server/models/Library.js @@ -69,6 +69,7 @@ class Library extends Model { disableWatcher: false, autoScanCronExpression: null, matchAfterScan: false, + matchMinConfidence: 0, skipMatchingMediaWithAsin: false, skipMatchingMediaWithIsbn: false, audiobooksOnly: false, diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 206068cc4..78b41f42e 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -60,6 +60,12 @@ class Scanner { } const matchData = results[0] + if (options.minConfidence && matchData.matchConfidence < options.minConfidence) { + return { + warning: `Match confidence ${matchData.matchConfidence} is below the minimum of ${options.minConfidence}` + } + } + // Update cover if not set OR overrideCover flag if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) { Logger.debug(`[Scanner] Updating cover "${matchData.cover}"`) @@ -433,7 +439,7 @@ class Scanner { * @param {LibraryScan} libraryScan * @returns {Promise