mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-01 05:29:41 +00:00
first iteration of parsing metadata and chapter names from ncc.html file
This commit is contained in:
parent
fe13456a2b
commit
6c9bf8c2bd
10 changed files with 394 additions and 6 deletions
99
server/scanner/DaisyFileScanner.js
Normal file
99
server/scanner/DaisyFileScanner.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
const { parseDaisyMetadata } = require('../utils/parsers/parseDaisyMetadata')
|
||||
const { readTextFile } = require('../utils/fileUtils')
|
||||
const Path = require('path')
|
||||
|
||||
class DaisyFileScanner {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Parse metadata from DAISY ncc.html file found in library scan and update bookMetadata
|
||||
*
|
||||
* @param {import('../models/LibraryItem').LibraryFileObject} daisyLibraryFileObj
|
||||
* @param {Object} bookMetadata
|
||||
*/
|
||||
async scanBookDaisyFile(daisyLibraryFileObj, bookMetadata, audioFiles = []) {
|
||||
const htmlText = await readTextFile(daisyLibraryFileObj.metadata.path)
|
||||
const daisyMetadata = htmlText ? parseDaisyMetadata(htmlText) : null
|
||||
if (daisyMetadata) {
|
||||
for (const key in daisyMetadata) {
|
||||
if (key === 'tags') {
|
||||
if (daisyMetadata.tags.length) {
|
||||
bookMetadata.tags = daisyMetadata.tags
|
||||
}
|
||||
} else if (key === 'genres') {
|
||||
if (daisyMetadata.genres.length) {
|
||||
bookMetadata.genres = daisyMetadata.genres
|
||||
}
|
||||
} else if (key === 'authors') {
|
||||
if (daisyMetadata.authors?.length) {
|
||||
bookMetadata.authors = daisyMetadata.authors
|
||||
}
|
||||
} else if (key === 'narrators') {
|
||||
if (daisyMetadata.narrators?.length) {
|
||||
bookMetadata.narrators = daisyMetadata.narrators
|
||||
}
|
||||
} else if (key === 'chapters') {
|
||||
if (!daisyMetadata.chapters?.length) continue
|
||||
|
||||
// DAISY ncc.html provides chapter names; preserve existing timings if available.
|
||||
if (bookMetadata.chapters?.length) {
|
||||
const updatedChapters = bookMetadata.chapters.map((chapter, index) => {
|
||||
const daisyChapter = daisyMetadata.chapters[index]
|
||||
if (!daisyChapter?.title) return chapter
|
||||
return {
|
||||
...chapter,
|
||||
id: chapter.id ?? index,
|
||||
title: daisyChapter.title
|
||||
}
|
||||
})
|
||||
bookMetadata.chapters = updatedChapters
|
||||
} else {
|
||||
const chaptersFromFiles = this.buildChaptersFromAudioFiles(audioFiles, daisyMetadata.chapters)
|
||||
if (chaptersFromFiles.length) {
|
||||
bookMetadata.chapters = chaptersFromFiles
|
||||
}
|
||||
}
|
||||
} else if (daisyMetadata[key]) {
|
||||
bookMetadata[key] = daisyMetadata[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build chapter timings from ordered audio files while applying DAISY chapter titles.
|
||||
* Falls back to file basenames if DAISY has fewer titles than files.
|
||||
*
|
||||
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
||||
* @param {{title:string}[]} daisyChapters
|
||||
* @returns {import('../models/Book').ChapterObject[]}
|
||||
*/
|
||||
buildChaptersFromAudioFiles(audioFiles, daisyChapters) {
|
||||
if (!audioFiles?.length) return []
|
||||
|
||||
const chapters = []
|
||||
let currentStartTime = 0
|
||||
let chapterId = 0
|
||||
|
||||
audioFiles.forEach((audioFile) => {
|
||||
if (!audioFile.duration) return
|
||||
|
||||
const fallbackTitle = audioFile.metadata?.filename
|
||||
? Path.basename(audioFile.metadata.filename, Path.extname(audioFile.metadata.filename))
|
||||
: `Chapter ${chapterId + 1}`
|
||||
const title = daisyChapters[chapterId]?.title || fallbackTitle
|
||||
|
||||
chapters.push({
|
||||
id: chapterId++,
|
||||
start: currentStartTime,
|
||||
end: currentStartTime + audioFile.duration,
|
||||
title
|
||||
})
|
||||
|
||||
currentStartTime += audioFile.duration
|
||||
})
|
||||
|
||||
return chapters
|
||||
}
|
||||
}
|
||||
module.exports = new DaisyFileScanner()
|
||||
Loading…
Add table
Add a link
Reference in a new issue