mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-02 09:14:40 +02:00
Migrate to new library item in scanner
This commit is contained in:
parent
fdbca4feb6
commit
d5ce7b4939
21 changed files with 435 additions and 845 deletions
|
@ -30,14 +30,14 @@ class Scanner {
|
|||
/**
|
||||
*
|
||||
* @param {import('../routers/ApiRouter')} apiRouterCtx
|
||||
* @param {import('../objects/LibraryItem')} libraryItem
|
||||
* @param {import('../models/LibraryItem')} libraryItem
|
||||
* @param {QuickMatchOptions} options
|
||||
* @returns {Promise<{updated: boolean, libraryItem: import('../objects/LibraryItem')}>}
|
||||
*/
|
||||
async quickMatchLibraryItem(apiRouterCtx, libraryItem, options = {}) {
|
||||
const provider = options.provider || 'google'
|
||||
const searchTitle = options.title || libraryItem.media.metadata.title
|
||||
const searchAuthor = options.author || libraryItem.media.metadata.authorName
|
||||
const searchTitle = options.title || libraryItem.media.title
|
||||
const searchAuthor = options.author || libraryItem.media.authorName
|
||||
|
||||
// If overrideCover and overrideDetails is not sent in options than use the server setting to determine if we should override
|
||||
if (options.overrideCover === undefined && options.overrideDetails === undefined && Database.serverSettings.scannerPreferMatchedMetadata) {
|
||||
|
@ -52,11 +52,11 @@ class Scanner {
|
|||
let existingSeries = []
|
||||
|
||||
if (libraryItem.isBook) {
|
||||
existingAuthors = libraryItem.media.metadata.authors.map((a) => a.id)
|
||||
existingSeries = libraryItem.media.metadata.series.map((s) => s.id)
|
||||
existingAuthors = libraryItem.media.authors.map((a) => a.id)
|
||||
existingSeries = libraryItem.media.series.map((s) => s.id)
|
||||
|
||||
const searchISBN = options.isbn || libraryItem.media.metadata.isbn
|
||||
const searchASIN = options.asin || libraryItem.media.metadata.asin
|
||||
const searchISBN = options.isbn || libraryItem.media.isbn
|
||||
const searchASIN = options.asin || libraryItem.media.asin
|
||||
|
||||
const results = await BookFinder.search(libraryItem, provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 })
|
||||
if (!results.length) {
|
||||
|
@ -69,15 +69,21 @@ class Scanner {
|
|||
// Update cover if not set OR overrideCover flag
|
||||
if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) {
|
||||
Logger.debug(`[Scanner] Updating cover "${matchData.cover}"`)
|
||||
var coverResult = await CoverManager.downloadCoverFromUrl(libraryItem, matchData.cover)
|
||||
if (!coverResult || coverResult.error || !coverResult.cover) {
|
||||
Logger.warn(`[Scanner] Match cover "${matchData.cover}" failed to use: ${coverResult ? coverResult.error : 'Unknown Error'}`)
|
||||
const coverResult = await CoverManager.downloadCoverFromUrlNew(matchData.cover, libraryItem.id, libraryItem.isFile ? null : libraryItem.path)
|
||||
if (coverResult.error) {
|
||||
Logger.warn(`[Scanner] Match cover "${matchData.cover}" failed to use: ${coverResult.error}`)
|
||||
} else {
|
||||
libraryItem.media.coverPath = coverResult.cover
|
||||
libraryItem.media.changed('coverPath', true) // Cover path may be the same but this forces the update
|
||||
hasUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
updatePayload = await this.quickMatchBookBuildUpdatePayload(libraryItem, matchData, options)
|
||||
const bookBuildUpdateData = await this.quickMatchBookBuildUpdatePayload(apiRouterCtx, libraryItem, matchData, options)
|
||||
updatePayload = bookBuildUpdateData.updatePayload
|
||||
if (bookBuildUpdateData.hasSeriesUpdates || bookBuildUpdateData.hasAuthorUpdates) {
|
||||
hasUpdated = true
|
||||
}
|
||||
} else if (libraryItem.isPodcast) {
|
||||
// Podcast quick match
|
||||
const results = await PodcastFinder.search(searchTitle)
|
||||
|
@ -91,10 +97,12 @@ class Scanner {
|
|||
// Update cover if not set OR overrideCover flag
|
||||
if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) {
|
||||
Logger.debug(`[Scanner] Updating cover "${matchData.cover}"`)
|
||||
var coverResult = await CoverManager.downloadCoverFromUrl(libraryItem, matchData.cover)
|
||||
if (!coverResult || coverResult.error || !coverResult.cover) {
|
||||
Logger.warn(`[Scanner] Match cover "${matchData.cover}" failed to use: ${coverResult ? coverResult.error : 'Unknown Error'}`)
|
||||
const coverResult = await CoverManager.downloadCoverFromUrlNew(matchData.cover, libraryItem.id, libraryItem.path)
|
||||
if (coverResult.error) {
|
||||
Logger.warn(`[Scanner] Match cover "${matchData.cover}" failed to use: ${coverResult.error}`)
|
||||
} else {
|
||||
libraryItem.media.coverPath = coverResult.cover
|
||||
libraryItem.media.changed('coverPath', true) // Cover path may be the same but this forces the update
|
||||
hasUpdated = true
|
||||
}
|
||||
}
|
||||
|
@ -103,44 +111,45 @@ class Scanner {
|
|||
}
|
||||
|
||||
if (Object.keys(updatePayload).length) {
|
||||
Logger.debug('[Scanner] Updating details', updatePayload)
|
||||
if (libraryItem.media.update(updatePayload)) {
|
||||
Logger.debug('[Scanner] Updating details with payload', updatePayload)
|
||||
libraryItem.media.set(updatePayload)
|
||||
if (libraryItem.media.changed()) {
|
||||
Logger.debug(`[Scanner] Updating library item "${libraryItem.media.title}" keys`, libraryItem.media.changed())
|
||||
hasUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUpdated) {
|
||||
if (libraryItem.isPodcast && libraryItem.media.metadata.feedUrl) {
|
||||
if (libraryItem.isPodcast && libraryItem.media.feedURL) {
|
||||
// Quick match all unmatched podcast episodes
|
||||
await this.quickMatchPodcastEpisodes(libraryItem, options)
|
||||
}
|
||||
|
||||
await Database.updateLibraryItem(libraryItem)
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded())
|
||||
await libraryItem.media.save()
|
||||
|
||||
// Check if any authors or series are now empty and should be removed
|
||||
if (libraryItem.isBook) {
|
||||
const authorsRemoved = existingAuthors.filter((aid) => !libraryItem.media.metadata.authors.find((au) => au.id === aid))
|
||||
const seriesRemoved = existingSeries.filter((sid) => !libraryItem.media.metadata.series.find((se) => se.id === sid))
|
||||
libraryItem.changed('updatedAt', true)
|
||||
await libraryItem.save()
|
||||
|
||||
if (authorsRemoved.length) {
|
||||
await apiRouterCtx.checkRemoveAuthorsWithNoBooks(authorsRemoved)
|
||||
}
|
||||
if (seriesRemoved.length) {
|
||||
await apiRouterCtx.checkRemoveEmptySeries(seriesRemoved)
|
||||
}
|
||||
}
|
||||
await libraryItem.saveMetadataFile()
|
||||
|
||||
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
|
||||
}
|
||||
|
||||
return {
|
||||
updated: hasUpdated,
|
||||
libraryItem: libraryItem.toJSONExpanded()
|
||||
libraryItem: libraryItem.toOldJSONExpanded()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/LibraryItem')} libraryItem
|
||||
* @param {*} matchData
|
||||
* @param {QuickMatchOptions} options
|
||||
* @returns {Map<string, any>} - Update payload
|
||||
*/
|
||||
quickMatchPodcastBuildUpdatePayload(libraryItem, matchData, options) {
|
||||
const updatePayload = {}
|
||||
updatePayload.metadata = {}
|
||||
|
||||
const matchDataTransformed = {
|
||||
title: matchData.title || null,
|
||||
|
@ -158,7 +167,7 @@ class Scanner {
|
|||
for (const key in matchDataTransformed) {
|
||||
if (matchDataTransformed[key]) {
|
||||
if (key === 'genres') {
|
||||
if (!libraryItem.media.metadata.genres.length || options.overrideDetails) {
|
||||
if (!libraryItem.media.genres.length || options.overrideDetails) {
|
||||
var genresArray = []
|
||||
if (Array.isArray(matchDataTransformed[key])) genresArray = [...matchDataTransformed[key]]
|
||||
else {
|
||||
|
@ -169,46 +178,42 @@ class Scanner {
|
|||
.map((v) => v.trim())
|
||||
.filter((v) => !!v)
|
||||
}
|
||||
updatePayload.metadata[key] = genresArray
|
||||
updatePayload[key] = genresArray
|
||||
}
|
||||
} else if (libraryItem.media.metadata[key] !== matchDataTransformed[key] && (!libraryItem.media.metadata[key] || options.overrideDetails)) {
|
||||
updatePayload.metadata[key] = matchDataTransformed[key]
|
||||
} else if (libraryItem.media[key] !== matchDataTransformed[key] && (!libraryItem.media[key] || options.overrideDetails)) {
|
||||
updatePayload[key] = matchDataTransformed[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.keys(updatePayload.metadata).length) {
|
||||
delete updatePayload.metadata
|
||||
}
|
||||
|
||||
return updatePayload
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../objects/LibraryItem')} libraryItem
|
||||
* @param {import('../routers/ApiRouter')} apiRouterCtx
|
||||
* @param {import('../models/LibraryItem')} libraryItem
|
||||
* @param {*} matchData
|
||||
* @param {QuickMatchOptions} options
|
||||
* @returns
|
||||
* @returns {Promise<{updatePayload: Map<string, any>, seriesIdsRemoved: string[], hasSeriesUpdates: boolean, authorIdsRemoved: string[], hasAuthorUpdates: boolean}>}
|
||||
*/
|
||||
async quickMatchBookBuildUpdatePayload(libraryItem, matchData, options) {
|
||||
async quickMatchBookBuildUpdatePayload(apiRouterCtx, libraryItem, matchData, options) {
|
||||
// Update media metadata if not set OR overrideDetails flag
|
||||
const detailKeysToUpdate = ['title', 'subtitle', 'description', 'narrator', 'publisher', 'publishedYear', 'genres', 'tags', 'language', 'explicit', 'abridged', 'asin', 'isbn']
|
||||
const updatePayload = {}
|
||||
updatePayload.metadata = {}
|
||||
|
||||
for (const key in matchData) {
|
||||
if (matchData[key] && detailKeysToUpdate.includes(key)) {
|
||||
if (key === 'narrator') {
|
||||
if (!libraryItem.media.metadata.narratorName || options.overrideDetails) {
|
||||
updatePayload.metadata.narrators = matchData[key]
|
||||
if (!libraryItem.media.narrators?.length || options.overrideDetails) {
|
||||
updatePayload.narrators = matchData[key]
|
||||
.split(',')
|
||||
.map((v) => v.trim())
|
||||
.filter((v) => !!v)
|
||||
}
|
||||
} else if (key === 'genres') {
|
||||
if (!libraryItem.media.metadata.genres.length || options.overrideDetails) {
|
||||
var genresArray = []
|
||||
if (!libraryItem.media.genres.length || options.overrideDetails) {
|
||||
let genresArray = []
|
||||
if (Array.isArray(matchData[key])) genresArray = [...matchData[key]]
|
||||
else {
|
||||
// Genres should always be passed in as an array but just incase handle a string
|
||||
|
@ -218,11 +223,11 @@ class Scanner {
|
|||
.map((v) => v.trim())
|
||||
.filter((v) => !!v)
|
||||
}
|
||||
updatePayload.metadata[key] = genresArray
|
||||
updatePayload[key] = genresArray
|
||||
}
|
||||
} else if (key === 'tags') {
|
||||
if (!libraryItem.media.tags.length || options.overrideDetails) {
|
||||
var tagsArray = []
|
||||
let tagsArray = []
|
||||
if (Array.isArray(matchData[key])) tagsArray = [...matchData[key]]
|
||||
else
|
||||
tagsArray = matchData[key]
|
||||
|
@ -231,94 +236,174 @@ class Scanner {
|
|||
.filter((v) => !!v)
|
||||
updatePayload[key] = tagsArray
|
||||
}
|
||||
} else if (!libraryItem.media.metadata[key] || options.overrideDetails) {
|
||||
updatePayload.metadata[key] = matchData[key]
|
||||
} else if (!libraryItem.media[key] || options.overrideDetails) {
|
||||
updatePayload[key] = matchData[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add or set author if not set
|
||||
if (matchData.author && (!libraryItem.media.metadata.authorName || options.overrideDetails)) {
|
||||
let hasAuthorUpdates = false
|
||||
if (matchData.author && (!libraryItem.media.authorName || options.overrideDetails)) {
|
||||
if (!Array.isArray(matchData.author)) {
|
||||
matchData.author = matchData.author
|
||||
.split(',')
|
||||
.map((au) => au.trim())
|
||||
.filter((au) => !!au)
|
||||
}
|
||||
const authorPayload = []
|
||||
const authorIdsRemoved = []
|
||||
for (const authorName of matchData.author) {
|
||||
let author = await Database.authorModel.getByNameAndLibrary(authorName, libraryItem.libraryId)
|
||||
if (!author) {
|
||||
author = await Database.authorModel.create({
|
||||
name: authorName,
|
||||
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||
libraryId: libraryItem.libraryId
|
||||
})
|
||||
SocketAuthority.emitter('author_added', author.toOldJSON())
|
||||
// Update filter data
|
||||
Database.addAuthorToFilterData(libraryItem.libraryId, author.name, author.id)
|
||||
const existingAuthor = libraryItem.media.authors.find((a) => a.name.toLowerCase() === authorName.toLowerCase())
|
||||
if (!existingAuthor) {
|
||||
let author = await Database.authorModel.getByNameAndLibrary(authorName, libraryItem.libraryId)
|
||||
if (!author) {
|
||||
author = await Database.authorModel.create({
|
||||
name: authorName,
|
||||
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||
libraryId: libraryItem.libraryId
|
||||
})
|
||||
SocketAuthority.emitter('author_added', author.toOldJSON())
|
||||
// Update filter data
|
||||
Database.addAuthorToFilterData(libraryItem.libraryId, author.name, author.id)
|
||||
|
||||
await Database.bookAuthorModel
|
||||
.create({
|
||||
authorId: author.id,
|
||||
bookId: libraryItem.media.id
|
||||
})
|
||||
.then(() => {
|
||||
Logger.info(`[Scanner] quickMatchBookBuildUpdatePayload: Added author "${author.name}" to "${libraryItem.media.title}"`)
|
||||
libraryItem.media.authors.push(author)
|
||||
hasAuthorUpdates = true
|
||||
})
|
||||
}
|
||||
}
|
||||
const authorsRemoved = libraryItem.media.authors.filter((a) => !matchData.author.find((ma) => ma.toLowerCase() === a.name.toLowerCase()))
|
||||
if (authorsRemoved.length) {
|
||||
for (const author of authorsRemoved) {
|
||||
await Database.bookAuthorModel.destroy({ where: { authorId: author.id, bookId: libraryItem.media.id } })
|
||||
libraryItem.media.authors = libraryItem.media.authors.filter((a) => a.id !== author.id)
|
||||
authorIdsRemoved.push(author.id)
|
||||
Logger.info(`[Scanner] quickMatchBookBuildUpdatePayload: Removed author "${author.name}" from "${libraryItem.media.title}"`)
|
||||
}
|
||||
hasAuthorUpdates = true
|
||||
}
|
||||
authorPayload.push(author.toJSONMinimal())
|
||||
}
|
||||
updatePayload.metadata.authors = authorPayload
|
||||
|
||||
// For all authors removed from book, check if they are empty now and should be removed
|
||||
if (authorIdsRemoved.length) {
|
||||
await apiRouterCtx.checkRemoveAuthorsWithNoBooks(authorIdsRemoved)
|
||||
}
|
||||
}
|
||||
|
||||
// Add or set series if not set
|
||||
if (matchData.series && (!libraryItem.media.metadata.seriesName || options.overrideDetails)) {
|
||||
let hasSeriesUpdates = false
|
||||
if (matchData.series && (!libraryItem.media.seriesName || options.overrideDetails)) {
|
||||
if (!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, sequence: matchData.sequence }]
|
||||
const seriesPayload = []
|
||||
const seriesIdsRemoved = []
|
||||
for (const seriesMatchItem of matchData.series) {
|
||||
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
|
||||
if (!seriesItem) {
|
||||
seriesItem = await Database.seriesModel.create({
|
||||
name: seriesMatchItem.series,
|
||||
nameIgnorePrefix: getTitleIgnorePrefix(seriesMatchItem.series),
|
||||
libraryId: libraryItem.libraryId
|
||||
const existingSeries = libraryItem.media.series.find((s) => s.name.toLowerCase() === seriesMatchItem.series.toLowerCase())
|
||||
if (existingSeries) {
|
||||
if (existingSeries.bookSeries.sequence !== seriesMatchItem.sequence) {
|
||||
existingSeries.bookSeries.sequence = seriesMatchItem.sequence
|
||||
await existingSeries.bookSeries.save()
|
||||
Logger.info(`[Scanner] quickMatchBookBuildUpdatePayload: Updated series sequence for "${existingSeries.name}" to ${seriesMatchItem.sequence} in "${libraryItem.media.title}"`)
|
||||
hasSeriesUpdates = true
|
||||
}
|
||||
} else {
|
||||
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
|
||||
if (!seriesItem) {
|
||||
seriesItem = await Database.seriesModel.create({
|
||||
name: seriesMatchItem.series,
|
||||
nameIgnorePrefix: getTitleIgnorePrefix(seriesMatchItem.series),
|
||||
libraryId: libraryItem.libraryId
|
||||
})
|
||||
// Update filter data
|
||||
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
|
||||
SocketAuthority.emitter('series_added', seriesItem.toOldJSON())
|
||||
}
|
||||
const bookSeries = await Database.bookSeriesModel.create({
|
||||
seriesId: seriesItem.id,
|
||||
bookId: libraryItem.media.id,
|
||||
sequence: seriesMatchItem.sequence
|
||||
})
|
||||
// Update filter data
|
||||
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
|
||||
SocketAuthority.emitter('series_added', seriesItem.toOldJSON())
|
||||
seriesItem.bookSeries = bookSeries
|
||||
libraryItem.media.series.push(seriesItem)
|
||||
Logger.info(`[Scanner] quickMatchBookBuildUpdatePayload: Added series "${seriesItem.name}" to "${libraryItem.media.title}"`)
|
||||
hasSeriesUpdates = true
|
||||
}
|
||||
const seriesRemoved = libraryItem.media.series.filter((s) => !matchData.series.find((ms) => ms.series.toLowerCase() === s.name.toLowerCase()))
|
||||
if (seriesRemoved.length) {
|
||||
for (const series of seriesRemoved) {
|
||||
await series.bookSeries.destroy()
|
||||
libraryItem.media.series = libraryItem.media.series.filter((s) => s.id !== series.id)
|
||||
seriesIdsRemoved.push(series.id)
|
||||
Logger.info(`[Scanner] quickMatchBookBuildUpdatePayload: Removed series "${series.name}" from "${libraryItem.media.title}"`)
|
||||
}
|
||||
hasSeriesUpdates = true
|
||||
}
|
||||
seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))
|
||||
}
|
||||
updatePayload.metadata.series = seriesPayload
|
||||
|
||||
// For all series removed from book, check if it is empty now and should be removed
|
||||
if (seriesIdsRemoved.length) {
|
||||
await apiRouterCtx.checkRemoveEmptySeries(seriesIdsRemoved)
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.keys(updatePayload.metadata).length) {
|
||||
delete updatePayload.metadata
|
||||
return {
|
||||
updatePayload,
|
||||
hasSeriesUpdates,
|
||||
hasAuthorUpdates
|
||||
}
|
||||
|
||||
return updatePayload
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/LibraryItem')} libraryItem
|
||||
* @param {QuickMatchOptions} options
|
||||
* @returns {Promise<number>} - Number of episodes updated
|
||||
*/
|
||||
async quickMatchPodcastEpisodes(libraryItem, options = {}) {
|
||||
const episodesToQuickMatch = libraryItem.media.episodes.filter((ep) => !ep.enclosureUrl) // Only quick match episodes without enclosure
|
||||
if (!episodesToQuickMatch.length) return false
|
||||
/** @type {import('../models/PodcastEpisode')[]} */
|
||||
const episodesToQuickMatch = libraryItem.media.podcastEpisodes.filter((ep) => !ep.enclosureURL) // Only quick match episodes that are not already matched
|
||||
if (!episodesToQuickMatch.length) return 0
|
||||
|
||||
const feed = await getPodcastFeed(libraryItem.media.metadata.feedUrl)
|
||||
const feed = await getPodcastFeed(libraryItem.media.feedURL)
|
||||
if (!feed) {
|
||||
Logger.error(`[Scanner] quickMatchPodcastEpisodes: Unable to quick match episodes feed not found for "${libraryItem.media.metadata.feedUrl}"`)
|
||||
return false
|
||||
Logger.error(`[Scanner] quickMatchPodcastEpisodes: Unable to quick match episodes feed not found for "${libraryItem.media.feedURL}"`)
|
||||
return 0
|
||||
}
|
||||
|
||||
let numEpisodesUpdated = 0
|
||||
for (const episode of episodesToQuickMatch) {
|
||||
const episodeMatches = findMatchingEpisodesInFeed(feed, episode.title)
|
||||
if (episodeMatches && episodeMatches.length) {
|
||||
const wasUpdated = this.updateEpisodeWithMatch(libraryItem, episode, episodeMatches[0].episode, options)
|
||||
if (episodeMatches?.length) {
|
||||
const wasUpdated = await this.updateEpisodeWithMatch(episode, episodeMatches[0].episode, options)
|
||||
if (wasUpdated) numEpisodesUpdated++
|
||||
}
|
||||
}
|
||||
if (numEpisodesUpdated) {
|
||||
Logger.info(`[Scanner] quickMatchPodcastEpisodes: Updated ${numEpisodesUpdated} episodes for "${libraryItem.media.title}"`)
|
||||
}
|
||||
return numEpisodesUpdated
|
||||
}
|
||||
|
||||
updateEpisodeWithMatch(libraryItem, episode, episodeToMatch, options = {}) {
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/PodcastEpisode')} episode
|
||||
* @param {import('../utils/podcastUtils').RssPodcastEpisode} episodeToMatch
|
||||
* @param {QuickMatchOptions} options
|
||||
* @returns {Promise<boolean>} - true if episode was updated
|
||||
*/
|
||||
async updateEpisodeWithMatch(episode, episodeToMatch, options = {}) {
|
||||
Logger.debug(`[Scanner] quickMatchPodcastEpisodes: Found episode match for "${episode.title}" => ${episodeToMatch.title}`)
|
||||
const matchDataTransformed = {
|
||||
title: episodeToMatch.title || '',
|
||||
subtitle: episodeToMatch.subtitle || '',
|
||||
description: episodeToMatch.description || '',
|
||||
enclosure: episodeToMatch.enclosure || null,
|
||||
enclosureURL: episodeToMatch.enclosure?.url || null,
|
||||
enclosureSize: episodeToMatch.enclosure?.length || null,
|
||||
enclosureType: episodeToMatch.enclosure?.type || null,
|
||||
episode: episodeToMatch.episode || '',
|
||||
episodeType: episodeToMatch.episodeType || 'full',
|
||||
season: episodeToMatch.season || '',
|
||||
|
@ -328,20 +413,19 @@ class Scanner {
|
|||
const updatePayload = {}
|
||||
for (const key in matchDataTransformed) {
|
||||
if (matchDataTransformed[key]) {
|
||||
if (key === 'enclosure') {
|
||||
if (!episode.enclosure || JSON.stringify(episode.enclosure) !== JSON.stringify(matchDataTransformed.enclosure)) {
|
||||
updatePayload[key] = {
|
||||
...matchDataTransformed.enclosure
|
||||
}
|
||||
}
|
||||
} else if (episode[key] !== matchDataTransformed[key] && (!episode[key] || options.overrideDetails)) {
|
||||
if (episode[key] !== matchDataTransformed[key] && (!episode[key] || options.overrideDetails)) {
|
||||
updatePayload[key] = matchDataTransformed[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updatePayload).length) {
|
||||
return libraryItem.media.updateEpisode(episode.id, updatePayload)
|
||||
episode.set(updatePayload)
|
||||
if (episode.changed()) {
|
||||
Logger.debug(`[Scanner] quickMatchPodcastEpisodes: Updating episode "${episode.title}" keys`, episode.changed())
|
||||
await episode.save()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -351,7 +435,7 @@ class Scanner {
|
|||
*
|
||||
* @param {import('../routers/ApiRouter')} apiRouterCtx
|
||||
* @param {import('../models/Library')} library
|
||||
* @param {import('../objects/LibraryItem')[]} libraryItems
|
||||
* @param {import('../models/LibraryItem')[]} libraryItems
|
||||
* @param {LibraryScan} libraryScan
|
||||
* @returns {Promise<boolean>} false if scan canceled
|
||||
*/
|
||||
|
@ -359,20 +443,20 @@ class Scanner {
|
|||
for (let i = 0; i < libraryItems.length; i++) {
|
||||
const libraryItem = libraryItems[i]
|
||||
|
||||
if (libraryItem.media.metadata.asin && library.settings.skipMatchingMediaWithAsin) {
|
||||
Logger.debug(`[Scanner] matchLibraryItems: Skipping "${libraryItem.media.metadata.title}" because it already has an ASIN (${i + 1} of ${libraryItems.length})`)
|
||||
if (libraryItem.media.asin && library.settings.skipMatchingMediaWithAsin) {
|
||||
Logger.debug(`[Scanner] matchLibraryItems: Skipping "${libraryItem.media.title}" because it already has an ASIN (${i + 1} of ${libraryItems.length})`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (libraryItem.media.metadata.isbn && library.settings.skipMatchingMediaWithIsbn) {
|
||||
Logger.debug(`[Scanner] matchLibraryItems: Skipping "${libraryItem.media.metadata.title}" because it already has an ISBN (${i + 1} of ${libraryItems.length})`)
|
||||
if (libraryItem.media.isbn && library.settings.skipMatchingMediaWithIsbn) {
|
||||
Logger.debug(`[Scanner] matchLibraryItems: Skipping "${libraryItem.media.title}" because it already has an ISBN (${i + 1} of ${libraryItems.length})`)
|
||||
continue
|
||||
}
|
||||
|
||||
Logger.debug(`[Scanner] matchLibraryItems: Quick matching "${libraryItem.media.metadata.title}" (${i + 1} of ${libraryItems.length})`)
|
||||
Logger.debug(`[Scanner] matchLibraryItems: Quick matching "${libraryItem.media.title}" (${i + 1} of ${libraryItems.length})`)
|
||||
const result = await this.quickMatchLibraryItem(apiRouterCtx, libraryItem, { provider: library.provider })
|
||||
if (result.warning) {
|
||||
Logger.warn(`[Scanner] matchLibraryItems: Match warning ${result.warning} for library item "${libraryItem.media.metadata.title}"`)
|
||||
Logger.warn(`[Scanner] matchLibraryItems: Match warning ${result.warning} for library item "${libraryItem.media.title}"`)
|
||||
} else if (result.updated) {
|
||||
libraryScan.resultsUpdated++
|
||||
}
|
||||
|
@ -430,9 +514,8 @@ class Scanner {
|
|||
|
||||
offset += limit
|
||||
hasMoreChunks = libraryItems.length === limit
|
||||
let oldLibraryItems = libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li))
|
||||
|
||||
const shouldContinue = await this.matchLibraryItemsChunk(apiRouterCtx, library, oldLibraryItems, libraryScan)
|
||||
const shouldContinue = await this.matchLibraryItemsChunk(apiRouterCtx, library, libraryItems, libraryScan)
|
||||
if (!shouldContinue) {
|
||||
isCanceled = true
|
||||
break
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue