mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-06-25 20:22:32 +02:00
Format PodcastScanner (Pretteier-only changes)
This commit is contained in:
parent
0169bf5518
commit
bacefb5f6f
@ -1,4 +1,4 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const { LogLevel } = require('../utils/constants')
|
const { LogLevel } = require('../utils/constants')
|
||||||
const { getTitleIgnorePrefix } = require('../utils/index')
|
const { getTitleIgnorePrefix } = require('../utils/index')
|
||||||
@ -8,9 +8,9 @@ const { filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtil
|
|||||||
const AudioFile = require('../objects/files/AudioFile')
|
const AudioFile = require('../objects/files/AudioFile')
|
||||||
const CoverManager = require('../managers/CoverManager')
|
const CoverManager = require('../managers/CoverManager')
|
||||||
const LibraryFile = require('../objects/files/LibraryFile')
|
const LibraryFile = require('../objects/files/LibraryFile')
|
||||||
const fsExtra = require("../libs/fsExtra")
|
const fsExtra = require('../libs/fsExtra')
|
||||||
const PodcastEpisode = require("../models/PodcastEpisode")
|
const PodcastEpisode = require('../models/PodcastEpisode')
|
||||||
const AbsMetadataFileScanner = require("./AbsMetadataFileScanner")
|
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata for podcasts pulled from files
|
* Metadata for podcasts pulled from files
|
||||||
@ -32,13 +32,13 @@ const AbsMetadataFileScanner = require("./AbsMetadataFileScanner")
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class PodcastScanner {
|
class PodcastScanner {
|
||||||
constructor() { }
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('../models/LibraryItem')} existingLibraryItem
|
* @param {import('../models/LibraryItem')} existingLibraryItem
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
||||||
* @param {import('./LibraryScan')} libraryScan
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
* @returns {Promise<{libraryItem:import('../models/LibraryItem'), wasUpdated:boolean}>}
|
* @returns {Promise<{libraryItem:import('../models/LibraryItem'), wasUpdated:boolean}>}
|
||||||
*/
|
*/
|
||||||
async rescanExistingPodcastLibraryItem(existingLibraryItem, libraryItemData, librarySettings, libraryScan) {
|
async rescanExistingPodcastLibraryItem(existingLibraryItem, libraryItemData, librarySettings, libraryScan) {
|
||||||
@ -59,28 +59,34 @@ class PodcastScanner {
|
|||||||
|
|
||||||
if (libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== existingPodcastEpisodes.length) {
|
if (libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== existingPodcastEpisodes.length) {
|
||||||
// Filter out and destroy episodes that were removed
|
// Filter out and destroy episodes that were removed
|
||||||
existingPodcastEpisodes = await Promise.all(existingPodcastEpisodes.filter(async ep => {
|
existingPodcastEpisodes = await Promise.all(
|
||||||
if (libraryItemData.checkAudioFileRemoved(ep.audioFile)) {
|
existingPodcastEpisodes.filter(async (ep) => {
|
||||||
libraryScan.addLog(LogLevel.INFO, `Podcast episode "${ep.title}" audio file was removed`)
|
if (libraryItemData.checkAudioFileRemoved(ep.audioFile)) {
|
||||||
// TODO: Should clean up other data linked to this episode
|
libraryScan.addLog(LogLevel.INFO, `Podcast episode "${ep.title}" audio file was removed`)
|
||||||
await ep.destroy()
|
// TODO: Should clean up other data linked to this episode
|
||||||
return false
|
await ep.destroy()
|
||||||
}
|
return false
|
||||||
return true
|
}
|
||||||
}))
|
return true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// Update audio files that were modified
|
// Update audio files that were modified
|
||||||
if (libraryItemData.audioLibraryFilesModified.length) {
|
if (libraryItemData.audioLibraryFilesModified.length) {
|
||||||
let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(existingLibraryItem.mediaType, libraryItemData, libraryItemData.audioLibraryFilesModified.map(lf => lf.new))
|
let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(
|
||||||
|
existingLibraryItem.mediaType,
|
||||||
|
libraryItemData,
|
||||||
|
libraryItemData.audioLibraryFilesModified.map((lf) => lf.new)
|
||||||
|
)
|
||||||
|
|
||||||
for (const podcastEpisode of existingPodcastEpisodes) {
|
for (const podcastEpisode of existingPodcastEpisodes) {
|
||||||
let matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.metadata.path === podcastEpisode.audioFile.metadata.path)
|
let matchedScannedAudioFile = scannedAudioFiles.find((saf) => saf.metadata.path === podcastEpisode.audioFile.metadata.path)
|
||||||
if (!matchedScannedAudioFile) {
|
if (!matchedScannedAudioFile) {
|
||||||
matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.ino === podcastEpisode.audioFile.ino)
|
matchedScannedAudioFile = scannedAudioFiles.find((saf) => saf.ino === podcastEpisode.audioFile.ino)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedScannedAudioFile) {
|
if (matchedScannedAudioFile) {
|
||||||
scannedAudioFiles = scannedAudioFiles.filter(saf => saf !== matchedScannedAudioFile)
|
scannedAudioFiles = scannedAudioFiles.filter((saf) => saf !== matchedScannedAudioFile)
|
||||||
const audioFile = new AudioFile(podcastEpisode.audioFile)
|
const audioFile = new AudioFile(podcastEpisode.audioFile)
|
||||||
audioFile.updateFromScan(matchedScannedAudioFile)
|
audioFile.updateFromScan(matchedScannedAudioFile)
|
||||||
podcastEpisode.audioFile = audioFile.toJSON()
|
podcastEpisode.audioFile = audioFile.toJSON()
|
||||||
@ -137,14 +143,14 @@ class PodcastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if cover was removed
|
// Check if cover was removed
|
||||||
if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some(lf => lf.metadata.path === media.coverPath)) {
|
if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some((lf) => lf.metadata.path === media.coverPath)) {
|
||||||
media.coverPath = null
|
media.coverPath = null
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cover if it was modified
|
// Update cover if it was modified
|
||||||
if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) {
|
if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) {
|
||||||
let coverMatch = libraryItemData.imageLibraryFilesModified.find(iFile => iFile.old.metadata.path === media.coverPath)
|
let coverMatch = libraryItemData.imageLibraryFilesModified.find((iFile) => iFile.old.metadata.path === media.coverPath)
|
||||||
if (coverMatch) {
|
if (coverMatch) {
|
||||||
const coverPath = coverMatch.new.metadata.path
|
const coverPath = coverMatch.new.metadata.path
|
||||||
if (coverPath !== media.coverPath) {
|
if (coverPath !== media.coverPath) {
|
||||||
@ -159,7 +165,7 @@ class PodcastScanner {
|
|||||||
// Check if cover is not set and image files were found
|
// Check if cover is not set and image files were found
|
||||||
if (!media.coverPath && libraryItemData.imageLibraryFiles.length) {
|
if (!media.coverPath && libraryItemData.imageLibraryFiles.length) {
|
||||||
// Prefer using a cover image with the name "cover" otherwise use the first image
|
// Prefer using a cover image with the name "cover" otherwise use the first image
|
||||||
const coverMatch = libraryItemData.imageLibraryFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = libraryItemData.imageLibraryFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
media.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
media.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
@ -172,7 +178,7 @@ class PodcastScanner {
|
|||||||
|
|
||||||
if (key === 'genres') {
|
if (key === 'genres') {
|
||||||
const existingGenres = media.genres || []
|
const existingGenres = media.genres || []
|
||||||
if (podcastMetadata.genres.some(g => !existingGenres.includes(g)) || existingGenres.some(g => !podcastMetadata.genres.includes(g))) {
|
if (podcastMetadata.genres.some((g) => !existingGenres.includes(g)) || existingGenres.some((g) => !podcastMetadata.genres.includes(g))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating podcast genres "${existingGenres.join(',')}" => "${podcastMetadata.genres.join(',')}" for podcast "${podcastMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating podcast genres "${existingGenres.join(',')}" => "${podcastMetadata.genres.join(',')}" for podcast "${podcastMetadata.title}"`)
|
||||||
media.genres = podcastMetadata.genres
|
media.genres = podcastMetadata.genres
|
||||||
media.changed('genres', true)
|
media.changed('genres', true)
|
||||||
@ -180,7 +186,7 @@ class PodcastScanner {
|
|||||||
}
|
}
|
||||||
} else if (key === 'tags') {
|
} else if (key === 'tags') {
|
||||||
const existingTags = media.tags || []
|
const existingTags = media.tags || []
|
||||||
if (podcastMetadata.tags.some(t => !existingTags.includes(t)) || existingTags.some(t => !podcastMetadata.tags.includes(t))) {
|
if (podcastMetadata.tags.some((t) => !existingTags.includes(t)) || existingTags.some((t) => !podcastMetadata.tags.includes(t))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating podcast tags "${existingTags.join(',')}" => "${podcastMetadata.tags.join(',')}" for podcast "${podcastMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating podcast tags "${existingTags.join(',')}" => "${podcastMetadata.tags.join(',')}" for podcast "${podcastMetadata.title}"`)
|
||||||
media.tags = podcastMetadata.tags
|
media.tags = podcastMetadata.tags
|
||||||
media.changed('tags', true)
|
media.changed('tags', true)
|
||||||
@ -195,7 +201,7 @@ class PodcastScanner {
|
|||||||
|
|
||||||
// If no cover then extract cover from audio file if available
|
// If no cover then extract cover from audio file if available
|
||||||
if (!media.coverPath && existingPodcastEpisodes.length) {
|
if (!media.coverPath && existingPodcastEpisodes.length) {
|
||||||
const audioFiles = existingPodcastEpisodes.map(ep => ep.audioFile)
|
const audioFiles = existingPodcastEpisodes.map((ep) => ep.audioFile)
|
||||||
const extractedCoverPath = await CoverManager.saveEmbeddedCoverArt(audioFiles, existingLibraryItem.id, existingLibraryItem.path)
|
const extractedCoverPath = await CoverManager.saveEmbeddedCoverArt(audioFiles, existingLibraryItem.id, existingLibraryItem.path)
|
||||||
if (extractedCoverPath) {
|
if (extractedCoverPath) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating podcast "${podcastMetadata.title}" extracted embedded cover art from audio file to path "${extractedCoverPath}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating podcast "${podcastMetadata.title}" extracted embedded cover art from audio file to path "${extractedCoverPath}"`)
|
||||||
@ -227,10 +233,10 @@ class PodcastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
||||||
* @param {import('./LibraryScan')} libraryScan
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
* @returns {Promise<import('../models/LibraryItem')>}
|
* @returns {Promise<import('../models/LibraryItem')>}
|
||||||
*/
|
*/
|
||||||
async scanNewPodcastLibraryItem(libraryItemData, librarySettings, libraryScan) {
|
async scanNewPodcastLibraryItem(libraryItemData, librarySettings, libraryScan) {
|
||||||
@ -272,7 +278,7 @@ class PodcastScanner {
|
|||||||
// Set cover image from library file
|
// Set cover image from library file
|
||||||
if (libraryItemData.imageLibraryFiles.length) {
|
if (libraryItemData.imageLibraryFiles.length) {
|
||||||
// Prefer using a cover image with the name "cover" otherwise use the first image
|
// Prefer using a cover image with the name "cover" otherwise use the first image
|
||||||
const coverMatch = libraryItemData.imageLibraryFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = libraryItemData.imageLibraryFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
podcastMetadata.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
podcastMetadata.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,10 +336,10 @@ class PodcastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {PodcastEpisode[]} podcastEpisodes Not the models for new podcasts
|
* @param {PodcastEpisode[]} podcastEpisodes Not the models for new podcasts
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {import('./LibraryScan')} libraryScan
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
* @param {string} [existingLibraryItemId]
|
* @param {string} [existingLibraryItemId]
|
||||||
* @returns {Promise<PodcastMetadataObject>}
|
* @returns {Promise<PodcastMetadataObject>}
|
||||||
*/
|
*/
|
||||||
@ -370,8 +376,8 @@ class PodcastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('../models/LibraryItem')} libraryItem
|
* @param {import('../models/LibraryItem')} libraryItem
|
||||||
* @param {import('./LibraryScan')} libraryScan
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
@ -405,41 +411,44 @@ class PodcastScanner {
|
|||||||
explicit: !!libraryItem.media.explicit,
|
explicit: !!libraryItem.media.explicit,
|
||||||
podcastType: libraryItem.media.podcastType
|
podcastType: libraryItem.media.podcastType
|
||||||
}
|
}
|
||||||
return fsExtra.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2)).then(async () => {
|
return fsExtra
|
||||||
// Add metadata.json to libraryFiles array if it is new
|
.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2))
|
||||||
let metadataLibraryFile = libraryItem.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
.then(async () => {
|
||||||
if (storeMetadataWithItem) {
|
// Add metadata.json to libraryFiles array if it is new
|
||||||
if (!metadataLibraryFile) {
|
let metadataLibraryFile = libraryItem.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
||||||
const newLibraryFile = new LibraryFile()
|
if (storeMetadataWithItem) {
|
||||||
await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
if (!metadataLibraryFile) {
|
||||||
metadataLibraryFile = newLibraryFile.toJSON()
|
const newLibraryFile = new LibraryFile()
|
||||||
libraryItem.libraryFiles.push(metadataLibraryFile)
|
await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
||||||
} else {
|
metadataLibraryFile = newLibraryFile.toJSON()
|
||||||
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
libraryItem.libraryFiles.push(metadataLibraryFile)
|
||||||
if (fileTimestamps) {
|
} else {
|
||||||
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
||||||
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
if (fileTimestamps) {
|
||||||
metadataLibraryFile.metadata.size = fileTimestamps.size
|
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
||||||
metadataLibraryFile.ino = fileTimestamps.ino
|
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
||||||
|
metadataLibraryFile.metadata.size = fileTimestamps.size
|
||||||
|
metadataLibraryFile.ino = fileTimestamps.ino
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const libraryItemDirTimestamps = await getFileTimestampsWithIno(libraryItem.path)
|
||||||
|
if (libraryItemDirTimestamps) {
|
||||||
|
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
|
||||||
|
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
|
||||||
|
let size = 0
|
||||||
|
libraryItem.libraryFiles.forEach((lf) => (size += !isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
||||||
|
libraryItem.size = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const libraryItemDirTimestamps = await getFileTimestampsWithIno(libraryItem.path)
|
|
||||||
if (libraryItemDirTimestamps) {
|
|
||||||
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
|
|
||||||
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
|
|
||||||
let size = 0
|
|
||||||
libraryItem.libraryFiles.forEach((lf) => size += (!isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
|
||||||
libraryItem.size = size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
|
||||||
return metadataLibraryFile
|
return metadataLibraryFile
|
||||||
}).catch((error) => {
|
})
|
||||||
libraryScan.addLog(LogLevel.ERROR, `Failed to save json file at "${metadataFilePath}"`, error)
|
.catch((error) => {
|
||||||
return null
|
libraryScan.addLog(LogLevel.ERROR, `Failed to save json file at "${metadataFilePath}"`, error)
|
||||||
})
|
return null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = new PodcastScanner()
|
module.exports = new PodcastScanner()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user