mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-01 19:49:26 +01:00
Update parsing and using tags from abmetadata file
This commit is contained in:
parent
639b600570
commit
08f765fa51
@ -255,9 +255,16 @@ class Book {
|
|||||||
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'book')
|
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'book')
|
||||||
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
|
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
|
||||||
Logger.debug(`[Book] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
|
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
|
||||||
|
}
|
||||||
|
if (abmetadataUpdates.metadata) {
|
||||||
metadataUpdatePayload = {
|
metadataUpdatePayload = {
|
||||||
...metadataUpdatePayload,
|
...metadataUpdatePayload,
|
||||||
...abmetadataUpdates
|
...abmetadataUpdates.metadata
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,22 +188,33 @@ class Podcast {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) {
|
async syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) {
|
||||||
var metadataUpdatePayload = {}
|
let metadataUpdatePayload = {}
|
||||||
|
let tagsUpdated = false
|
||||||
|
|
||||||
var metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs')
|
const metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs')
|
||||||
if (metadataAbs) {
|
if (metadataAbs) {
|
||||||
var metadataText = await readTextFile(metadataAbs.metadata.path)
|
const metadataText = await readTextFile(metadataAbs.metadata.path)
|
||||||
var abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'podcast')
|
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'podcast')
|
||||||
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
|
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
|
||||||
Logger.debug(`[Podcast] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
|
Logger.debug(`[Podcast] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
|
||||||
metadataUpdatePayload = abmetadataUpdates
|
|
||||||
|
if (abmetadataUpdates.tags) { // Set media tags if updated
|
||||||
|
this.tags = abmetadataUpdates.tags
|
||||||
|
tagsUpdated = true
|
||||||
|
}
|
||||||
|
if (abmetadataUpdates.metadata) {
|
||||||
|
metadataUpdatePayload = {
|
||||||
|
...metadataUpdatePayload,
|
||||||
|
...abmetadataUpdates.metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(metadataUpdatePayload).length) {
|
if (Object.keys(metadataUpdatePayload).length) {
|
||||||
return this.metadata.update(metadataUpdatePayload)
|
return this.metadata.update(metadataUpdatePayload) || tagsUpdated
|
||||||
}
|
}
|
||||||
return false
|
return tagsUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
searchEpisodes(query) {
|
searchEpisodes(query) {
|
||||||
|
@ -130,13 +130,13 @@ const metadataMappers = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generate(libraryItem, outputPath) {
|
function generate(libraryItem, outputPath) {
|
||||||
var fileString = `;ABMETADATA${CurrentAbMetadataVersion}\n`
|
let fileString = `;ABMETADATA${CurrentAbMetadataVersion}\n`
|
||||||
fileString += `#audiobookshelf v${package.version}\n\n`
|
fileString += `#audiobookshelf v${package.version}\n\n`
|
||||||
|
|
||||||
const mediaType = libraryItem.mediaType
|
const mediaType = libraryItem.mediaType
|
||||||
|
|
||||||
fileString += `media=${mediaType}\n`
|
fileString += `media=${mediaType}\n`
|
||||||
fileString += `tags=`+JSON.stringify(libraryItem.media.tags)+`\n\n`
|
fileString += `tags=${JSON.stringify(libraryItem.media.tags)}\n`
|
||||||
|
|
||||||
const metadataMapper = metadataMappers[mediaType]
|
const metadataMapper = metadataMappers[mediaType]
|
||||||
var mediaMetadata = libraryItem.media.metadata
|
var mediaMetadata = libraryItem.media.metadata
|
||||||
@ -223,17 +223,31 @@ function parseChapterLines(lines) {
|
|||||||
return chapter
|
return chapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseTags(value) {
|
||||||
|
if (!value) return null
|
||||||
|
try {
|
||||||
|
const parsedTags = []
|
||||||
|
JSON.parse(value).forEach((loadedTag) => {
|
||||||
|
if (loadedTag.trim()) parsedTags.push(loadedTag) // Only push tags that are non-empty
|
||||||
|
})
|
||||||
|
return parsedTags
|
||||||
|
} catch (err) {
|
||||||
|
Logger.error(`[abmetadataGenerator] Error parsing TAGS "${value}":`, err.message)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function parseAbMetadataText(text, mediaType) {
|
function parseAbMetadataText(text, mediaType) {
|
||||||
if (!text) return null
|
if (!text) return null
|
||||||
var lines = text.split(/\r?\n/)
|
let lines = text.split(/\r?\n/)
|
||||||
|
|
||||||
// Check first line and get abmetadata version number
|
// Check first line and get abmetadata version number
|
||||||
var firstLine = lines.shift().toLowerCase()
|
const firstLine = lines.shift().toLowerCase()
|
||||||
if (!firstLine.startsWith(';abmetadata')) {
|
if (!firstLine.startsWith(';abmetadata')) {
|
||||||
Logger.error(`Invalid abmetadata file first line is not ;abmetadata "${firstLine}"`)
|
Logger.error(`Invalid abmetadata file first line is not ;abmetadata "${firstLine}"`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
var abmetadataVersion = Number(firstLine.replace(';abmetadata', '').trim())
|
const abmetadataVersion = Number(firstLine.replace(';abmetadata', '').trim())
|
||||||
if (isNaN(abmetadataVersion) || abmetadataVersion != CurrentAbMetadataVersion) {
|
if (isNaN(abmetadataVersion) || abmetadataVersion != CurrentAbMetadataVersion) {
|
||||||
Logger.warn(`Invalid abmetadata version ${abmetadataVersion} - must use version ${CurrentAbMetadataVersion}`)
|
Logger.warn(`Invalid abmetadata version ${abmetadataVersion} - must use version ${CurrentAbMetadataVersion}`)
|
||||||
return null
|
return null
|
||||||
@ -244,9 +258,9 @@ function parseAbMetadataText(text, mediaType) {
|
|||||||
lines = lines.filter(line => !!line.trim() && !ignoreFirstChars.includes(line[0]))
|
lines = lines.filter(line => !!line.trim() && !ignoreFirstChars.includes(line[0]))
|
||||||
|
|
||||||
// Get lines that map to book details (all lines before the first chapter or description section)
|
// Get lines that map to book details (all lines before the first chapter or description section)
|
||||||
var firstSectionLine = lines.findIndex(l => l.startsWith('['))
|
const firstSectionLine = lines.findIndex(l => l.startsWith('['))
|
||||||
var detailLines = firstSectionLine > 0 ? lines.slice(0, firstSectionLine) : lines
|
const detailLines = firstSectionLine > 0 ? lines.slice(0, firstSectionLine) : lines
|
||||||
var remainingLines = firstSectionLine > 0 ? lines.slice(firstSectionLine) : []
|
const remainingLines = firstSectionLine > 0 ? lines.slice(firstSectionLine) : []
|
||||||
|
|
||||||
if (!detailLines.length) {
|
if (!detailLines.length) {
|
||||||
Logger.error(`Invalid abmetadata file no detail lines`)
|
Logger.error(`Invalid abmetadata file no detail lines`)
|
||||||
@ -255,64 +269,57 @@ function parseAbMetadataText(text, mediaType) {
|
|||||||
|
|
||||||
// Check the media type saved for this abmetadata file show warning if not matching expected
|
// Check the media type saved for this abmetadata file show warning if not matching expected
|
||||||
if (detailLines[0].toLowerCase().startsWith('media=')) {
|
if (detailLines[0].toLowerCase().startsWith('media=')) {
|
||||||
var mediaLine = detailLines.shift() // Remove media line
|
const mediaLine = detailLines.shift() // Remove media line
|
||||||
var abMediaType = mediaLine.toLowerCase().split('=')[1].trim()
|
const abMediaType = mediaLine.toLowerCase().split('=')[1].trim()
|
||||||
if (abMediaType != mediaType) {
|
if (abMediaType != mediaType) {
|
||||||
Logger.warn(`Invalid media type in abmetadata file ${abMediaType} expecting ${mediaType}`)
|
Logger.warn(`Invalid media type in abmetadata file ${abMediaType} expecting ${mediaType}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.warn(`No media type found in abmetadata file - expecting ${mediaType}`)
|
Logger.warn(`No media type found in abmetadata file - expecting ${mediaType}`)
|
||||||
}
|
}
|
||||||
const abTags = [];
|
|
||||||
try{
|
|
||||||
if (detailLines[0].toLowerCase().startsWith('tags=')) {
|
|
||||||
var tagLine = detailLines.shift()
|
|
||||||
var tagsStr = tagLine.substring(5, tagLine.len)
|
|
||||||
JSON.parse(tagsStr).forEach((loadedTag) => { abTags.push(loadedTag) })
|
|
||||||
}
|
|
||||||
}catch(err){
|
|
||||||
Logger.error("Error parsing TAGS:", err.message)
|
|
||||||
}
|
|
||||||
const metadataMapper = metadataMappers[mediaType]
|
const metadataMapper = metadataMappers[mediaType]
|
||||||
// Put valid book detail values into map
|
// Put valid book detail values into map
|
||||||
const mediaMetadataDetails = {}
|
const mediaDetails = {
|
||||||
|
metadata: {},
|
||||||
|
chapters: [],
|
||||||
|
tags: null // When tags are null it will not be used
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < detailLines.length; i++) {
|
for (let i = 0; i < detailLines.length; i++) {
|
||||||
var line = detailLines[i]
|
const line = detailLines[i]
|
||||||
var keyValue = line.split('=')
|
const keyValue = line.split('=')
|
||||||
if (keyValue.length < 2) {
|
if (keyValue.length < 2) {
|
||||||
Logger.warn('abmetadata invalid line has no =', line)
|
Logger.warn('abmetadata invalid line has no =', line)
|
||||||
} else if (!metadataMapper[keyValue[0].trim()]) {
|
} else if (keyValue[0].trim() === 'tags') { // Parse tags
|
||||||
|
const value = keyValue.slice(1).join('=').trim() // Everything after "tags="
|
||||||
|
mediaDetails.tags = parseTags(value)
|
||||||
|
} else if (!metadataMapper[keyValue[0].trim()]) { // Ensure valid media metadata key
|
||||||
Logger.warn(`abmetadata key "${keyValue[0].trim()}" is not a valid ${mediaType} metadata key`)
|
Logger.warn(`abmetadata key "${keyValue[0].trim()}" is not a valid ${mediaType} metadata key`)
|
||||||
} else {
|
} else {
|
||||||
var key = keyValue.shift().trim()
|
const key = keyValue.shift().trim()
|
||||||
var value = keyValue.join('=').trim()
|
const value = keyValue.join('=').trim()
|
||||||
mediaMetadataDetails[key] = metadataMapper[key].from(value)
|
mediaDetails.metadata[key] = metadataMapper[key].from(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const chapters = []
|
|
||||||
|
|
||||||
// Parse sections for description and chapters
|
// Parse sections for description and chapters
|
||||||
var sections = parseSections(remainingLines)
|
const sections = parseSections(remainingLines)
|
||||||
sections.forEach((section) => {
|
sections.forEach((section) => {
|
||||||
var sectionHeader = section.shift()
|
const sectionHeader = section.shift()
|
||||||
if (sectionHeader.toLowerCase().startsWith('[description]')) {
|
if (sectionHeader.toLowerCase().startsWith('[description]')) {
|
||||||
mediaMetadataDetails.description = section.join('\n')
|
mediaDetails.metadata.description = section.join('\n')
|
||||||
} else if (sectionHeader.toLowerCase().startsWith('[chapter]')) {
|
} else if (sectionHeader.toLowerCase().startsWith('[chapter]')) {
|
||||||
var chapter = parseChapterLines(section)
|
const chapter = parseChapterLines(section)
|
||||||
if (chapter) {
|
if (chapter) {
|
||||||
chapters.push(chapter)
|
mediaDetails.chapters.push(chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
chapters.sort((a, b) => a.start - b.start)
|
mediaDetails.chapters.sort((a, b) => a.start - b.start)
|
||||||
|
|
||||||
return {
|
return mediaDetails
|
||||||
metadata: mediaMetadataDetails,
|
|
||||||
chapters,
|
|
||||||
tags: abTags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
module.exports.parse = parseAbMetadataText
|
module.exports.parse = parseAbMetadataText
|
||||||
|
|
||||||
@ -386,53 +393,54 @@ function checkArraysChanged(abmetadataArray, mediaArray) {
|
|||||||
return abmetadataArray.join(',') != mediaArray.join(',')
|
return abmetadataArray.join(',') != mediaArray.join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input text from abmetadata file and return object of metadata changes from media metadata
|
// 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) {
|
function parseAndCheckForUpdates(text, media, mediaType) {
|
||||||
if (!text || !media || !media.metadata || !mediaType) {
|
if (!text || !media || !media.metadata || !mediaType) {
|
||||||
Logger.error(`Invalid inputs to parseAndCheckForUpdates`)
|
Logger.error(`Invalid inputs to parseAndCheckForUpdates`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const mediaMetadata = media.metadata
|
const mediaMetadata = media.metadata
|
||||||
var updatePayload = {} // Only updated key/values
|
const metadataUpdatePayload = {} // Only updated key/values
|
||||||
|
|
||||||
var abmetadataData = parseAbMetadataText(text, mediaType)
|
const abmetadataData = parseAbMetadataText(text, mediaType)
|
||||||
if (!abmetadataData || !abmetadataData.metadata) {
|
if (!abmetadataData || !abmetadataData.metadata) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
var abMetadata = abmetadataData.metadata // Metadata from abmetadata file
|
const abMetadata = abmetadataData.metadata // Metadata from abmetadata file
|
||||||
|
|
||||||
for (const key in abMetadata) {
|
for (const key in abMetadata) {
|
||||||
if (mediaMetadata[key] !== undefined) {
|
if (mediaMetadata[key] !== undefined) {
|
||||||
if (key === 'authors') {
|
if (key === 'authors') {
|
||||||
var authorUpdatePayload = checkUpdatedBookAuthors(abMetadata[key], mediaMetadata[key])
|
const authorUpdatePayload = checkUpdatedBookAuthors(abMetadata[key], mediaMetadata[key])
|
||||||
if (authorUpdatePayload.hasUpdates) updatePayload.authors = authorUpdatePayload.authors
|
if (authorUpdatePayload.hasUpdates) metadataUpdatePayload.authors = authorUpdatePayload.authors
|
||||||
} else if (key === 'series') {
|
} else if (key === 'series') {
|
||||||
var seriesUpdatePayload = checkUpdatedBookSeries(abMetadata[key], mediaMetadata[key])
|
const seriesUpdatePayload = checkUpdatedBookSeries(abMetadata[key], mediaMetadata[key])
|
||||||
if (seriesUpdatePayload.hasUpdates) updatePayload.series = seriesUpdatePayload.series
|
if (seriesUpdatePayload.hasUpdates) metadataUpdatePayload.series = seriesUpdatePayload.series
|
||||||
} else if (key === 'genres' || key === 'narrators') { // Compare array differences
|
} else if (key === 'genres' || key === 'narrators') { // Compare array differences
|
||||||
if (checkArraysChanged(abMetadata[key], mediaMetadata[key])) {
|
if (checkArraysChanged(abMetadata[key], mediaMetadata[key])) {
|
||||||
updatePayload[key] = abMetadata[key]
|
metadataUpdatePayload[key] = abMetadata[key]
|
||||||
}
|
}
|
||||||
} else if (abMetadata[key] !== mediaMetadata[key]) {
|
} else if (abMetadata[key] !== mediaMetadata[key]) {
|
||||||
updatePayload[key] = abMetadata[key]
|
metadataUpdatePayload[key] = abMetadata[key]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.warn('[abmetadataGenerator] Invalid key', key)
|
Logger.warn('[abmetadataGenerator] Invalid key', key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try{
|
|
||||||
if(abmetadataData.tags.length > 0){
|
const updatePayload = {} // Only updated key/values
|
||||||
abmetadataData.tags.forEach((tag) => {
|
// Check update tags
|
||||||
if(media.tags.includes(tag) == false){
|
if (abmetadataData.tags) {
|
||||||
media.tags.push(copyValue(tag))
|
if (checkArraysChanged(abmetadataData.tags, media.tags)) {
|
||||||
}
|
updatePayload.tags = abmetadataData.tags
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(err){
|
|
||||||
Logger.error("[abmetadataGenerator] Error parsing tags", err.message)
|
if (Object.keys(metadataUpdatePayload).length) {
|
||||||
|
updatePayload.metadata = metadataUpdatePayload
|
||||||
}
|
}
|
||||||
|
|
||||||
return updatePayload
|
return updatePayload
|
||||||
}
|
}
|
||||||
module.exports.parseAndCheckForUpdates = parseAndCheckForUpdates
|
module.exports.parseAndCheckForUpdates = parseAndCheckForUpdates
|
||||||
|
Loading…
Reference in New Issue
Block a user