Add NFO metadata source

This commit is contained in:
mikiher 2023-11-12 13:30:23 +00:00
parent d3a55c8b1a
commit d990e5b909
7 changed files with 165 additions and 4 deletions

View File

@ -127,7 +127,7 @@ export default {
skipMatchingMediaWithIsbn: false,
autoScanCronExpression: null,
hideSingleBookSeries: false,
metadataPrecedence: ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
}
}
},

View File

@ -64,6 +64,11 @@ export default {
name: 'Audio file meta tags',
include: true
},
nfoFile: {
id: 'nfoFile',
name: 'NFO file',
include: true
},
txtFiles: {
id: 'txtFiles',
name: 'desc.txt & reader.txt files',

View File

@ -9,7 +9,7 @@ class LibrarySettings {
this.autoScanCronExpression = null
this.audiobooksOnly = false
this.hideSingleBookSeries = false // Do not show series that only have 1 book
this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
if (settings) {
this.construct(settings)
@ -28,7 +28,7 @@ class LibrarySettings {
this.metadataPrecedence = [...settings.metadataPrecedence]
} else {
// Added in v2.4.5
this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
}
}

View File

@ -18,6 +18,7 @@ const BookFinder = require('../finders/BookFinder')
const LibraryScan = require("./LibraryScan")
const OpfFileScanner = require('./OpfFileScanner')
const NfoFileScanner = require('./NfoFileScanner')
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
/**
@ -593,7 +594,7 @@ class BookScanner {
}
const bookMetadataSourceHandler = new BookScanner.BookMetadataSourceHandler(bookMetadata, audioFiles, libraryItemData, libraryScan, existingLibraryItemId)
const metadataPrecedence = librarySettings.metadataPrecedence || ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
const metadataPrecedence = librarySettings.metadataPrecedence || ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
libraryScan.addLog(LogLevel.DEBUG, `"${bookMetadata.title}" Getting metadata with precedence [${metadataPrecedence.join(', ')}]`)
for (const metadataSource of metadataPrecedence) {
if (bookMetadataSourceHandler[metadataSource]) {
@ -649,6 +650,14 @@ class BookScanner {
AudioFileScanner.setBookMetadataFromAudioMetaTags(bookTitle, this.audioFiles, this.bookMetadata, this.libraryScan)
}
/**
* Metadata from .nfo file
*/
async nfoFile() {
if (!this.libraryItemData.metadataNfoLibraryFile) return
await NfoFileScanner.scanBookNfoFile(this.libraryItemData.metadataNfoLibraryFile, this.bookMetadata)
}
/**
* Description from desc.txt and narrator from reader.txt
*/

View File

@ -132,6 +132,11 @@ class LibraryItemScanData {
return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.opf')
}
/** @type {LibraryItem.LibraryFileObject} */
get metadataNfoLibraryFile() {
return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.nfo')
}
/**
*
* @param {LibraryItem} existingLibraryItem

View File

@ -0,0 +1,48 @@
const { parseNfoMetadata } = require('../utils/parsers/parseNfoMetadata')
const { readTextFile } = require('../utils/fileUtils')
class NfoFileScanner {
constructor() { }
/**
* Parse metadata from .nfo file found in library scan and update bookMetadata
*
* @param {import('../models/LibraryItem').LibraryFileObject} nfoLibraryFileObj
* @param {Object} bookMetadata
*/
async scanBookNfoFile(nfoLibraryFileObj, bookMetadata) {
const nfoText = await readTextFile(nfoLibraryFileObj.metadata.path)
const nfoMetadata = nfoText ? await parseNfoMetadata(nfoText) : null
if (nfoMetadata) {
for (const key in nfoMetadata) {
if (key === 'tags') { // Add tags only if tags are empty
if (nfoMetadata.tags.length) {
bookMetadata.tags = nfoMetadata.tags
}
} else if (key === 'genres') { // Add genres only if genres are empty
if (nfoMetadata.genres.length) {
bookMetadata.genres = nfoMetadata.genres
}
} else if (key === 'authors') {
if (nfoMetadata.authors?.length) {
bookMetadata.authors = nfoMetadata.authors
}
} else if (key === 'narrators') {
if (nfoMetadata.narrators?.length) {
bookMetadata.narrators = nfoMetadata.narrators
}
} else if (key === 'series') {
if (nfoMetadata.series) {
bookMetadata.series = [{
name: nfoMetadata.series,
sequence: nfoMetadata.sequence || null
}]
}
} else if (nfoMetadata[key] && key !== 'sequence') {
bookMetadata[key] = nfoMetadata[key]
}
}
}
}
}
module.exports = new NfoFileScanner()

View File

@ -0,0 +1,94 @@
function parseNfoMetadata(nfoText) {
if (!nfoText) return null
const lines = nfoText.split(/\r?\n/)
const metadata = {}
let insideBookDescription = false
lines.forEach(line => {
if (line.search(/^\s*book description\s*$/i) !== -1) {
insideBookDescription = true
return
}
if (insideBookDescription) {
if (line.search(/^\s*=+\s*$/i) !== -1) return
metadata.description = metadata.description || ''
metadata.description += line + '\n'
return
}
const match = line.match(/^(.*?):(.*)$/)
if (match) {
const key = match[1].toLowerCase().trim()
const value = match[2].trim()
if (!value) return
switch (key) {
case 'title':
{
const titleMatch = value.match(/^(.*?):(.*)$/)
if (titleMatch) {
metadata.title = titleMatch[1].trim()
metadata.subtitle = titleMatch[2].trim()
} else {
metadata.title = value
}
}
break
case 'author':
metadata.authors = value.split(/\s*,\s*/)
break
case 'narrator':
case 'read by':
metadata.narrators = value.split(/\s*,\s*/)
break
case 'series name':
metadata.series = value
break
case 'genre':
metadata.genres = value.split(/\s*,\s*/)
break
case 'tags':
metadata.tags = value.split(/\s*,\s*/)
break
case 'copyright':
case 'audible.com release':
case 'audiobook copyright':
case 'book copyright':
case 'recording copyright':
case 'release date':
case 'date':
{
const year = extractYear(value)
if (year) {
metadata.publishedYear = year
}
}
break;
case 'position in series':
metadata.sequence = value
break
case 'unabridged':
metadata.abridged = value.toLowerCase() === 'yes' ? false : true
break
case 'abridged':
metadata.abridged = value.toLowerCase() === 'no' ? false : true
break
case 'publisher':
metadata.publisher = value
break
case 'asin':
metadata.asin = value
break
case 'isbn':
case 'isbn-10':
case 'isbn-13':
metadata.isbn = value
break
}
}
})
return metadata
}
module.exports = { parseNfoMetadata }
function extractYear(str) {
const match = str.match(/\d{4}/g)
return match ? match[match.length-1] : null
}