mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-27 16:29:30 +01:00
Add NFO metadata source
This commit is contained in:
parent
d3a55c8b1a
commit
d990e5b909
@ -127,7 +127,7 @@ export default {
|
|||||||
skipMatchingMediaWithIsbn: false,
|
skipMatchingMediaWithIsbn: false,
|
||||||
autoScanCronExpression: null,
|
autoScanCronExpression: null,
|
||||||
hideSingleBookSeries: false,
|
hideSingleBookSeries: false,
|
||||||
metadataPrecedence: ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
|
metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -64,6 +64,11 @@ export default {
|
|||||||
name: 'Audio file meta tags',
|
name: 'Audio file meta tags',
|
||||||
include: true
|
include: true
|
||||||
},
|
},
|
||||||
|
nfoFile: {
|
||||||
|
id: 'nfoFile',
|
||||||
|
name: 'NFO file',
|
||||||
|
include: true
|
||||||
|
},
|
||||||
txtFiles: {
|
txtFiles: {
|
||||||
id: 'txtFiles',
|
id: 'txtFiles',
|
||||||
name: 'desc.txt & reader.txt files',
|
name: 'desc.txt & reader.txt files',
|
||||||
|
@ -9,7 +9,7 @@ class LibrarySettings {
|
|||||||
this.autoScanCronExpression = null
|
this.autoScanCronExpression = null
|
||||||
this.audiobooksOnly = false
|
this.audiobooksOnly = false
|
||||||
this.hideSingleBookSeries = false // Do not show series that only have 1 book
|
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) {
|
if (settings) {
|
||||||
this.construct(settings)
|
this.construct(settings)
|
||||||
@ -28,7 +28,7 @@ class LibrarySettings {
|
|||||||
this.metadataPrecedence = [...settings.metadataPrecedence]
|
this.metadataPrecedence = [...settings.metadataPrecedence]
|
||||||
} else {
|
} else {
|
||||||
// Added in v2.4.5
|
// Added in v2.4.5
|
||||||
this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata']
|
this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ const BookFinder = require('../finders/BookFinder')
|
|||||||
|
|
||||||
const LibraryScan = require("./LibraryScan")
|
const LibraryScan = require("./LibraryScan")
|
||||||
const OpfFileScanner = require('./OpfFileScanner')
|
const OpfFileScanner = require('./OpfFileScanner')
|
||||||
|
const NfoFileScanner = require('./NfoFileScanner')
|
||||||
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -593,7 +594,7 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bookMetadataSourceHandler = new BookScanner.BookMetadataSourceHandler(bookMetadata, audioFiles, libraryItemData, libraryScan, existingLibraryItemId)
|
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(', ')}]`)
|
libraryScan.addLog(LogLevel.DEBUG, `"${bookMetadata.title}" Getting metadata with precedence [${metadataPrecedence.join(', ')}]`)
|
||||||
for (const metadataSource of metadataPrecedence) {
|
for (const metadataSource of metadataPrecedence) {
|
||||||
if (bookMetadataSourceHandler[metadataSource]) {
|
if (bookMetadataSourceHandler[metadataSource]) {
|
||||||
@ -649,6 +650,14 @@ class BookScanner {
|
|||||||
AudioFileScanner.setBookMetadataFromAudioMetaTags(bookTitle, this.audioFiles, this.bookMetadata, this.libraryScan)
|
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
|
* Description from desc.txt and narrator from reader.txt
|
||||||
*/
|
*/
|
||||||
|
@ -132,6 +132,11 @@ class LibraryItemScanData {
|
|||||||
return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.opf')
|
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
|
* @param {LibraryItem} existingLibraryItem
|
||||||
|
48
server/scanner/NfoFileScanner.js
Normal file
48
server/scanner/NfoFileScanner.js
Normal 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()
|
94
server/utils/parsers/parseNfoMetadata.js
Normal file
94
server/utils/parsers/parseNfoMetadata.js
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user