mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-03-19 00:18:34 +01:00
Podcast scanner refactor/cleanup
This commit is contained in:
parent
347b49f564
commit
89821b91b0
@ -61,5 +61,65 @@ class AbsMetadataFileScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for metadata.json or metadata.abs file and set podcast metadata
|
||||||
|
*
|
||||||
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
|
* @param {Object} podcastMetadata
|
||||||
|
* @param {string} [existingLibraryItemId]
|
||||||
|
*/
|
||||||
|
async scanPodcastMetadataFile(libraryScan, libraryItemData, podcastMetadata, existingLibraryItemId = null) {
|
||||||
|
const metadataLibraryFile = libraryItemData.metadataJsonLibraryFile || libraryItemData.metadataAbsLibraryFile
|
||||||
|
let metadataText = metadataLibraryFile ? await readTextFile(metadataLibraryFile.metadata.path) : null
|
||||||
|
let metadataFilePath = metadataLibraryFile?.metadata.path
|
||||||
|
let metadataFileFormat = libraryItemData.metadataJsonLibraryFile ? 'json' : 'abs'
|
||||||
|
|
||||||
|
// When metadata file is not stored with library item then check in the /metadata/items folder for it
|
||||||
|
if (!metadataText && existingLibraryItemId) {
|
||||||
|
let metadataPath = Path.join(global.MetadataPath, 'items', existingLibraryItemId)
|
||||||
|
|
||||||
|
let altFormat = global.ServerSettings.metadataFileFormat === 'json' ? 'abs' : 'json'
|
||||||
|
// First check the metadata format set in server settings, fallback to the alternate
|
||||||
|
metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
|
||||||
|
metadataFileFormat = global.ServerSettings.metadataFileFormat
|
||||||
|
if (await fsExtra.pathExists(metadataFilePath)) {
|
||||||
|
metadataText = await readTextFile(metadataFilePath)
|
||||||
|
} else if (await fsExtra.pathExists(Path.join(metadataPath, `metadata.${altFormat}`))) {
|
||||||
|
metadataFilePath = Path.join(metadataPath, `metadata.${altFormat}`)
|
||||||
|
metadataFileFormat = altFormat
|
||||||
|
metadataText = await readTextFile(metadataFilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadataText) {
|
||||||
|
libraryScan.addLog(LogLevel.INFO, `Found metadata file "${metadataFilePath}" - preferring`)
|
||||||
|
let abMetadata = null
|
||||||
|
if (metadataFileFormat === 'json') {
|
||||||
|
abMetadata = abmetadataGenerator.parseJson(metadataText)
|
||||||
|
} else {
|
||||||
|
abMetadata = abmetadataGenerator.parse(metadataText, 'podcast')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abMetadata) {
|
||||||
|
if (abMetadata.tags?.length) {
|
||||||
|
podcastMetadata.tags = abMetadata.tags
|
||||||
|
}
|
||||||
|
for (const key in abMetadata.metadata) {
|
||||||
|
if (abMetadata.metadata[key] === undefined) continue
|
||||||
|
|
||||||
|
// TODO: New podcast model changed some keys, need to update the abmetadataGenerator
|
||||||
|
let newModelKey = key
|
||||||
|
if (key === 'feedUrl') newModelKey = 'feedURL'
|
||||||
|
else if (key === 'imageUrl') newModelKey = 'imageURL'
|
||||||
|
else if (key === 'itunesPageUrl') newModelKey = 'itunesPageURL'
|
||||||
|
else if (key === 'type') newModelKey = 'podcastType'
|
||||||
|
|
||||||
|
podcastMetadata[newModelKey] = abMetadata.metadata[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = new AbsMetadataFileScanner()
|
module.exports = new AbsMetadataFileScanner()
|
@ -215,7 +215,7 @@ class AudioFileScanner {
|
|||||||
* @param {string} bookTitle
|
* @param {string} bookTitle
|
||||||
* @param {import('../models/Book').AudioFileObject} audioFile
|
* @param {import('../models/Book').AudioFileObject} audioFile
|
||||||
* @param {Object} bookMetadata
|
* @param {Object} bookMetadata
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
*/
|
*/
|
||||||
setBookMetadataFromAudioMetaTags(bookTitle, audioFiles, bookMetadata, libraryScan) {
|
setBookMetadataFromAudioMetaTags(bookTitle, audioFiles, bookMetadata, libraryScan) {
|
||||||
const MetadataMapArray = [
|
const MetadataMapArray = [
|
||||||
@ -309,10 +309,145 @@ class AudioFileScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set podcast metadata from first audio file
|
||||||
|
*
|
||||||
|
* @param {import('../models/Book').AudioFileObject} audioFile
|
||||||
|
* @param {Object} podcastMetadata
|
||||||
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
|
*/
|
||||||
|
setPodcastMetadataFromAudioMetaTags(audioFile, podcastMetadata, libraryScan) {
|
||||||
|
const audioFileMetaTags = audioFile.metaTags
|
||||||
|
|
||||||
|
const MetadataMapArray = [
|
||||||
|
{
|
||||||
|
tag: 'tagAlbum',
|
||||||
|
altTag: 'tagSeries',
|
||||||
|
key: 'title'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagArtist',
|
||||||
|
key: 'author'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagGenre',
|
||||||
|
key: 'genres'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagLanguage',
|
||||||
|
key: 'language'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagItunesId',
|
||||||
|
key: 'itunesId'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagPodcastType',
|
||||||
|
key: 'podcastType',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
MetadataMapArray.forEach((mapping) => {
|
||||||
|
let value = audioFileMetaTags[mapping.tag]
|
||||||
|
let tagToUse = mapping.tag
|
||||||
|
if (!value && mapping.altTag) {
|
||||||
|
value = audioFileMetaTags[mapping.altTag]
|
||||||
|
tagToUse = mapping.altTag
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && typeof value === 'string') {
|
||||||
|
value = value.trim() // Trim whitespace
|
||||||
|
|
||||||
|
if (mapping.key === 'genres') {
|
||||||
|
podcastMetadata.genres = this.parseGenresString(value)
|
||||||
|
libraryScan.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastMetadata.genres.join(', ')}`)
|
||||||
|
} else {
|
||||||
|
podcastMetadata[mapping.key] = value
|
||||||
|
libraryScan.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastMetadata[mapping.key]}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import('../models/PodcastEpisode')} podcastEpisode Not the model when creating new podcast
|
||||||
|
* @param {import('./ScanLogger')} scanLogger
|
||||||
|
*/
|
||||||
|
setPodcastEpisodeMetadataFromAudioMetaTags(podcastEpisode, scanLogger) {
|
||||||
|
const MetadataMapArray = [
|
||||||
|
{
|
||||||
|
tag: 'tagComment',
|
||||||
|
altTag: 'tagSubtitle',
|
||||||
|
key: 'description'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagSubtitle',
|
||||||
|
key: 'subtitle'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagDate',
|
||||||
|
key: 'pubDate'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagDisc',
|
||||||
|
key: 'season',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagTrack',
|
||||||
|
altTag: 'tagSeriesPart',
|
||||||
|
key: 'episode'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagTitle',
|
||||||
|
key: 'title'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: 'tagEpisodeType',
|
||||||
|
key: 'episodeType'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const audioFileMetaTags = podcastEpisode.audioFile.metaTags
|
||||||
|
MetadataMapArray.forEach((mapping) => {
|
||||||
|
let value = audioFileMetaTags[mapping.tag]
|
||||||
|
let tagToUse = mapping.tag
|
||||||
|
if (!value && mapping.altTag) {
|
||||||
|
tagToUse = mapping.altTag
|
||||||
|
value = audioFileMetaTags[mapping.altTag]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && typeof value === 'string') {
|
||||||
|
value = value.trim() // Trim whitespace
|
||||||
|
|
||||||
|
if (mapping.key === 'pubDate') {
|
||||||
|
const pubJsDate = new Date(value)
|
||||||
|
if (pubJsDate && !isNaN(pubJsDate)) {
|
||||||
|
podcastEpisode.publishedAt = pubJsDate.valueOf()
|
||||||
|
podcastEpisode.pubDate = value
|
||||||
|
scanLogger.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastEpisode[mapping.key]}`)
|
||||||
|
} else {
|
||||||
|
scanLogger.addLog(LogLevel.WARN, `Mapping pubDate with tag ${tagToUse} has invalid date "${value}"`)
|
||||||
|
}
|
||||||
|
} else if (mapping.key === 'episodeType') {
|
||||||
|
if (['full', 'trailer', 'bonus'].includes(value)) {
|
||||||
|
podcastEpisode.episodeType = value
|
||||||
|
scanLogger.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastEpisode[mapping.key]}`)
|
||||||
|
} else {
|
||||||
|
scanLogger.addLog(LogLevel.WARN, `Mapping episodeType with invalid value "${value}". Must be one of [full, trailer, bonus].`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
podcastEpisode[mapping.key] = value
|
||||||
|
scanLogger.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastEpisode[mapping.key]}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} bookTitle
|
* @param {string} bookTitle
|
||||||
* @param {AudioFile[]} audioFiles
|
* @param {AudioFile[]} audioFiles
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
* @returns {import('../models/Book').ChapterObject[]}
|
* @returns {import('../models/Book').ChapterObject[]}
|
||||||
*/
|
*/
|
||||||
getBookChaptersFromAudioFiles(bookTitle, audioFiles, libraryScan) {
|
getBookChaptersFromAudioFiles(bookTitle, audioFiles, libraryScan) {
|
||||||
|
@ -580,9 +580,10 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bookMetadataSourceHandler = new BookScanner.BookMetadataSourceHandler(bookMetadata, audioFiles, libraryItemData, libraryScan, existingLibraryItemId)
|
const bookMetadataSourceHandler = new BookScanner.BookMetadataSourceHandler(bookMetadata, audioFiles, libraryItemData, libraryScan, existingLibraryItemId)
|
||||||
for (const metadataSource of librarySettings.metadataPrecedence) {
|
const metadataPrecedence = librarySettings.metadataPrecedence || ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
|
||||||
|
libraryScan.addLog(LogLevel.DEBUG, `"${bookMetadata.title}" Getting metadata with precedence [${metadataPrecedence.join(', ')}]`)
|
||||||
|
for (const metadataSource of metadataPrecedence) {
|
||||||
if (bookMetadataSourceHandler[metadataSource]) {
|
if (bookMetadataSourceHandler[metadataSource]) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Getting metadata from source "${metadataSource}"`)
|
|
||||||
await bookMetadataSourceHandler[metadataSource]()
|
await bookMetadataSourceHandler[metadataSource]()
|
||||||
} else {
|
} else {
|
||||||
libraryScan.addLog(LogLevel.ERROR, `Invalid metadata source "${metadataSource}"`)
|
libraryScan.addLog(LogLevel.ERROR, `Invalid metadata source "${metadataSource}"`)
|
||||||
|
@ -50,28 +50,25 @@ class LibraryItemScanner {
|
|||||||
|
|
||||||
const scanLogger = new ScanLogger()
|
const scanLogger = new ScanLogger()
|
||||||
scanLogger.verbose = true
|
scanLogger.verbose = true
|
||||||
scanLogger.setData('libraryItem', libraryItemId)
|
scanLogger.setData('libraryItem', libraryItem.relPath)
|
||||||
|
|
||||||
const libraryItemPath = fileUtils.filePathToPOSIX(libraryItem.path)
|
const libraryItemPath = fileUtils.filePathToPOSIX(libraryItem.path)
|
||||||
const folder = library.libraryFolders[0]
|
const folder = library.libraryFolders[0]
|
||||||
const libraryItemScanData = await this.getLibraryItemScanData(libraryItemPath, library, folder, false)
|
const libraryItemScanData = await this.getLibraryItemScanData(libraryItemPath, library, folder, false)
|
||||||
|
|
||||||
if (await libraryItemScanData.checkLibraryItemData(libraryItem, scanLogger)) {
|
let libraryItemDataUpdated = await libraryItemScanData.checkLibraryItemData(libraryItem, scanLogger)
|
||||||
if (libraryItemScanData.hasLibraryFileChanges || libraryItemScanData.hasPathChange) {
|
|
||||||
const { libraryItem: expandedLibraryItem } = await this.rescanLibraryItemMedia(libraryItem, libraryItemScanData, library.settings, scanLogger)
|
const { libraryItem: expandedLibraryItem, wasUpdated } = await this.rescanLibraryItemMedia(libraryItem, libraryItemScanData, library.settings, scanLogger)
|
||||||
|
if (libraryItemDataUpdated || wasUpdated) {
|
||||||
const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(expandedLibraryItem)
|
const oldLibraryItem = Database.libraryItemModel.getOldLibraryItem(expandedLibraryItem)
|
||||||
SocketAuthority.emitter('item_updated', oldLibraryItem.toJSONExpanded())
|
SocketAuthority.emitter('item_updated', oldLibraryItem.toJSONExpanded())
|
||||||
|
|
||||||
await this.checkAuthorsAndSeriesRemovedFromBooks(library.id, scanLogger)
|
await this.checkAuthorsAndSeriesRemovedFromBooks(library.id, scanLogger)
|
||||||
} else {
|
|
||||||
// TODO: Temporary while using old model to socket emit
|
|
||||||
const oldLibraryItem = await Database.libraryItemModel.getOldById(libraryItem.id)
|
|
||||||
SocketAuthority.emitter('item_updated', oldLibraryItem.toJSONExpanded())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ScanResult.UPDATED
|
return ScanResult.UPDATED
|
||||||
}
|
}
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Library item "${libraryItem.relPath}" is up-to-date`)
|
|
||||||
|
scanLogger.addLog(LogLevel.DEBUG, `Library item is up-to-date`)
|
||||||
return ScanResult.UPTODATE
|
return ScanResult.UPTODATE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,13 @@ const { getTitleIgnorePrefix } = require('../utils/index')
|
|||||||
const abmetadataGenerator = require('../utils/generators/abmetadataGenerator')
|
const abmetadataGenerator = require('../utils/generators/abmetadataGenerator')
|
||||||
const AudioFileScanner = require('./AudioFileScanner')
|
const AudioFileScanner = require('./AudioFileScanner')
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
const { readTextFile, filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtils')
|
const { filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtils')
|
||||||
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")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata for podcasts pulled from files
|
* Metadata for podcasts pulled from files
|
||||||
@ -87,7 +88,7 @@ class PodcastScanner {
|
|||||||
podcastEpisode.changed('audioFile', true)
|
podcastEpisode.changed('audioFile', true)
|
||||||
|
|
||||||
// Set metadata and save episode
|
// Set metadata and save episode
|
||||||
this.setPodcastEpisodeMetadataFromAudioFile(podcastEpisode, libraryScan)
|
AudioFileScanner.setPodcastEpisodeMetadataFromAudioMetaTags(podcastEpisode, libraryScan)
|
||||||
libraryScan.addLog(LogLevel.INFO, `Podcast episode "${podcastEpisode.title}" keys changed [${podcastEpisode.changed()?.join(', ')}]`)
|
libraryScan.addLog(LogLevel.INFO, `Podcast episode "${podcastEpisode.title}" keys changed [${podcastEpisode.changed()?.join(', ')}]`)
|
||||||
await podcastEpisode.save()
|
await podcastEpisode.save()
|
||||||
}
|
}
|
||||||
@ -122,7 +123,7 @@ class PodcastScanner {
|
|||||||
}
|
}
|
||||||
const newPodcastEpisode = Database.podcastEpisodeModel.build(newEpisode)
|
const newPodcastEpisode = Database.podcastEpisodeModel.build(newEpisode)
|
||||||
// Set metadata and save new episode
|
// Set metadata and save new episode
|
||||||
this.setPodcastEpisodeMetadataFromAudioFile(newPodcastEpisode, libraryScan)
|
AudioFileScanner.setPodcastEpisodeMetadataFromAudioMetaTags(newPodcastEpisode, libraryScan)
|
||||||
libraryScan.addLog(LogLevel.INFO, `New Podcast episode "${newPodcastEpisode.title}" added`)
|
libraryScan.addLog(LogLevel.INFO, `New Podcast episode "${newPodcastEpisode.title}" added`)
|
||||||
await newPodcastEpisode.save()
|
await newPodcastEpisode.save()
|
||||||
existingPodcastEpisodes.push(newPodcastEpisode)
|
existingPodcastEpisodes.push(newPodcastEpisode)
|
||||||
@ -242,7 +243,7 @@ class PodcastScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set metadata and save new episode
|
// Set metadata and save new episode
|
||||||
this.setPodcastEpisodeMetadataFromAudioFile(newEpisode, libraryScan)
|
AudioFileScanner.setPodcastEpisodeMetadataFromAudioMetaTags(newEpisode, libraryScan)
|
||||||
libraryScan.addLog(LogLevel.INFO, `New Podcast episode "${newEpisode.title}" found`)
|
libraryScan.addLog(LogLevel.INFO, `New Podcast episode "${newEpisode.title}" found`)
|
||||||
newPodcastEpisodes.push(newEpisode)
|
newPodcastEpisodes.push(newEpisode)
|
||||||
}
|
}
|
||||||
@ -320,7 +321,7 @@ class PodcastScanner {
|
|||||||
async getPodcastMetadataFromScanData(podcastEpisodes, libraryItemData, libraryScan, existingLibraryItemId = null) {
|
async getPodcastMetadataFromScanData(podcastEpisodes, libraryItemData, libraryScan, existingLibraryItemId = null) {
|
||||||
const podcastMetadata = {
|
const podcastMetadata = {
|
||||||
title: libraryItemData.mediaMetadata.title,
|
title: libraryItemData.mediaMetadata.title,
|
||||||
titleIgnorePrefix: getTitleIgnorePrefix(libraryItemData.mediaMetadata.title),
|
titleIgnorePrefix: undefined,
|
||||||
author: undefined,
|
author: undefined,
|
||||||
releaseDate: undefined,
|
releaseDate: undefined,
|
||||||
feedURL: undefined,
|
feedURL: undefined,
|
||||||
@ -336,132 +337,19 @@ class PodcastScanner {
|
|||||||
genres: []
|
genres: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use audio meta tags
|
||||||
if (podcastEpisodes.length) {
|
if (podcastEpisodes.length) {
|
||||||
const audioFileMetaTags = podcastEpisodes[0].audioFile.metaTags
|
AudioFileScanner.setPodcastMetadataFromAudioMetaTags(podcastEpisodes[0].audioFile, podcastMetadata, libraryScan)
|
||||||
|
|
||||||
const MetadataMapArray = [
|
|
||||||
{
|
|
||||||
tag: 'tagAlbum',
|
|
||||||
altTag: 'tagSeries',
|
|
||||||
key: 'title'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagArtist',
|
|
||||||
key: 'author'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagGenre',
|
|
||||||
key: 'genres'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagLanguage',
|
|
||||||
key: 'language'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagItunesId',
|
|
||||||
key: 'itunesId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagPodcastType',
|
|
||||||
key: 'podcastType',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
MetadataMapArray.forEach((mapping) => {
|
|
||||||
let value = audioFileMetaTags[mapping.tag]
|
|
||||||
let tagToUse = mapping.tag
|
|
||||||
if (!value && mapping.altTag) {
|
|
||||||
value = audioFileMetaTags[mapping.altTag]
|
|
||||||
tagToUse = mapping.altTag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value && typeof value === 'string') {
|
// Use metadata.json or metadata.abs file
|
||||||
value = value.trim() // Trim whitespace
|
await AbsMetadataFileScanner.scanPodcastMetadataFile(libraryScan, libraryItemData, podcastMetadata, existingLibraryItemId)
|
||||||
|
|
||||||
if (mapping.key === 'genres') {
|
|
||||||
podcastMetadata.genres = this.parseGenresString(value)
|
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastMetadata.genres.join(', ')}`)
|
|
||||||
} else {
|
|
||||||
podcastMetadata[mapping.key] = value
|
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastMetadata[mapping.key]}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// If metadata.json or metadata.abs use this for metadata
|
|
||||||
const metadataLibraryFile = libraryItemData.metadataJsonLibraryFile || libraryItemData.metadataAbsLibraryFile
|
|
||||||
let metadataText = metadataLibraryFile ? await readTextFile(metadataLibraryFile.metadata.path) : null
|
|
||||||
let metadataFilePath = metadataLibraryFile?.metadata.path
|
|
||||||
let metadataFileFormat = libraryItemData.metadataJsonLibraryFile ? 'json' : 'abs'
|
|
||||||
|
|
||||||
// When metadata file is not stored with library item then check in the /metadata/items folder for it
|
|
||||||
if (!metadataText && existingLibraryItemId) {
|
|
||||||
let metadataPath = Path.join(global.MetadataPath, 'items', existingLibraryItemId)
|
|
||||||
|
|
||||||
let altFormat = global.ServerSettings.metadataFileFormat === 'json' ? 'abs' : 'json'
|
|
||||||
// First check the metadata format set in server settings, fallback to the alternate
|
|
||||||
metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
|
|
||||||
metadataFileFormat = global.ServerSettings.metadataFileFormat
|
|
||||||
if (await fsExtra.pathExists(metadataFilePath)) {
|
|
||||||
metadataText = await readTextFile(metadataFilePath)
|
|
||||||
} else if (await fsExtra.pathExists(Path.join(metadataPath, `metadata.${altFormat}`))) {
|
|
||||||
metadataFilePath = Path.join(metadataPath, `metadata.${altFormat}`)
|
|
||||||
metadataFileFormat = altFormat
|
|
||||||
metadataText = await readTextFile(metadataFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metadataText) {
|
|
||||||
libraryScan.addLog(LogLevel.INFO, `Found metadata file "${metadataFilePath}" - preferring`)
|
|
||||||
let abMetadata = null
|
|
||||||
if (metadataFileFormat === 'json') {
|
|
||||||
abMetadata = abmetadataGenerator.parseJson(metadataText)
|
|
||||||
} else {
|
|
||||||
abMetadata = abmetadataGenerator.parse(metadataText, 'podcast')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (abMetadata) {
|
|
||||||
if (abMetadata.tags?.length) {
|
|
||||||
podcastMetadata.tags = abMetadata.tags
|
|
||||||
}
|
|
||||||
for (const key in abMetadata.metadata) {
|
|
||||||
if (abMetadata.metadata[key] === undefined) continue
|
|
||||||
|
|
||||||
// TODO: New podcast model changed some keys, need to update the abmetadataGenerator
|
|
||||||
let newModelKey = key
|
|
||||||
if (key === 'feedUrl') newModelKey = 'feedURL'
|
|
||||||
else if (key === 'imageUrl') newModelKey = 'imageURL'
|
|
||||||
else if (key === 'itunesPageUrl') newModelKey = 'itunesPageURL'
|
|
||||||
else if (key === 'type') newModelKey = 'podcastType'
|
|
||||||
|
|
||||||
podcastMetadata[newModelKey] = abMetadata.metadata[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
podcastMetadata.titleIgnorePrefix = getTitleIgnorePrefix(podcastMetadata.title)
|
podcastMetadata.titleIgnorePrefix = getTitleIgnorePrefix(podcastMetadata.title)
|
||||||
|
|
||||||
return podcastMetadata
|
return podcastMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a genre string into multiple genres
|
|
||||||
* @example "Fantasy;Sci-Fi;History" => ["Fantasy", "Sci-Fi", "History"]
|
|
||||||
* @param {string} genreTag
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
parseGenresString(genreTag) {
|
|
||||||
if (!genreTag?.length) return []
|
|
||||||
const separators = ['/', '//', ';']
|
|
||||||
for (let i = 0; i < separators.length; i++) {
|
|
||||||
if (genreTag.includes(separators[i])) {
|
|
||||||
return genreTag.split(separators[i]).map(genre => genre.trim()).filter(g => !!g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [genreTag]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('../models/LibraryItem')} libraryItem
|
* @param {import('../models/LibraryItem')} libraryItem
|
||||||
@ -589,80 +477,5 @@ class PodcastScanner {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {PodcastEpisode} podcastEpisode Not the model when creating new podcast
|
|
||||||
* @param {import('./ScanLogger')} scanLogger
|
|
||||||
*/
|
|
||||||
setPodcastEpisodeMetadataFromAudioFile(podcastEpisode, scanLogger) {
|
|
||||||
const MetadataMapArray = [
|
|
||||||
{
|
|
||||||
tag: 'tagComment',
|
|
||||||
altTag: 'tagSubtitle',
|
|
||||||
key: 'description'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagSubtitle',
|
|
||||||
key: 'subtitle'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagDate',
|
|
||||||
key: 'pubDate'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagDisc',
|
|
||||||
key: 'season',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagTrack',
|
|
||||||
altTag: 'tagSeriesPart',
|
|
||||||
key: 'episode'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagTitle',
|
|
||||||
key: 'title'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagEpisodeType',
|
|
||||||
key: 'episodeType'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const audioFileMetaTags = podcastEpisode.audioFile.metaTags
|
|
||||||
MetadataMapArray.forEach((mapping) => {
|
|
||||||
let value = audioFileMetaTags[mapping.tag]
|
|
||||||
let tagToUse = mapping.tag
|
|
||||||
if (!value && mapping.altTag) {
|
|
||||||
tagToUse = mapping.altTag
|
|
||||||
value = audioFileMetaTags[mapping.altTag]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value && typeof value === 'string') {
|
|
||||||
value = value.trim() // Trim whitespace
|
|
||||||
|
|
||||||
if (mapping.key === 'pubDate') {
|
|
||||||
const pubJsDate = new Date(value)
|
|
||||||
if (pubJsDate && !isNaN(pubJsDate)) {
|
|
||||||
podcastEpisode.publishedAt = pubJsDate.valueOf()
|
|
||||||
podcastEpisode.pubDate = value
|
|
||||||
scanLogger.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastEpisode[mapping.key]}`)
|
|
||||||
} else {
|
|
||||||
scanLogger.addLog(LogLevel.WARN, `Mapping pubDate with tag ${tagToUse} has invalid date "${value}"`)
|
|
||||||
}
|
|
||||||
} else if (mapping.key === 'episodeType') {
|
|
||||||
if (['full', 'trailer', 'bonus'].includes(value)) {
|
|
||||||
podcastEpisode.episodeType = value
|
|
||||||
scanLogger.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastEpisode[mapping.key]}`)
|
|
||||||
} else {
|
|
||||||
scanLogger.addLog(LogLevel.WARN, `Mapping episodeType with invalid value "${value}". Must be one of [full, trailer, bonus].`)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
podcastEpisode[mapping.key] = value
|
|
||||||
scanLogger.addLog(LogLevel.DEBUG, `Mapping metadata to key ${tagToUse} => ${mapping.key}: ${podcastEpisode[mapping.key]}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
module.exports = new PodcastScanner()
|
module.exports = new PodcastScanner()
|
Loading…
Reference in New Issue
Block a user