audiobookshelf/server/utils/parsers/parseComicMetadata.js

109 lines
3.2 KiB
JavaScript

const Path = require('path')
const globals = require('../globals')
const fs = require('../../libs/fsExtra')
const Logger = require('../../Logger')
const Archive = require('../../libs/libarchive/archive')
const { xmlToJSON } = require('../index')
const parseComicInfoMetadata = require('./parseComicInfoMetadata')
/**
*
* @param {string} filepath
* @returns {Promise<Buffer>}
*/
async function getComicFileBuffer(filepath) {
if (!await fs.pathExists(filepath)) {
Logger.error(`Comic path does not exist "${filepath}"`)
return null
}
try {
return fs.readFile(filepath)
} catch (error) {
Logger.error(`Failed to read comic at "${filepath}"`, error)
return null
}
}
/**
* Extract cover image from comic return true if success
*
* @param {string} comicPath
* @param {string} comicImageFilepath
* @param {string} outputCoverPath
* @returns {Promise<boolean>}
*/
async function extractCoverImage(comicPath, comicImageFilepath, outputCoverPath) {
const comicFileBuffer = await getComicFileBuffer(comicPath)
if (!comicFileBuffer) return null
const archive = await Archive.open(comicFileBuffer)
const fileEntry = await archive.extractSingleFile(comicImageFilepath)
if (!fileEntry?.fileData) {
Logger.error(`[parseComicMetadata] Invalid file entry data for comicPath "${comicPath}"/${comicImageFilepath}`)
return false
}
try {
await fs.writeFile(outputCoverPath, fileEntry.fileData)
return true
} catch (error) {
Logger.error(`[parseComicMetadata] Failed to extract image from comicPath "${comicPath}"`, error)
return false
}
}
module.exports.extractCoverImage = extractCoverImage
/**
* Parse metadata from comic
*
* @param {import('../../models/Book').EBookFileObject} ebookFile
* @returns {Promise<import('./parseEbookMetadata').EBookFileScanData>}
*/
async function parse(ebookFile) {
const comicPath = ebookFile.metadata.path
Logger.debug(`Parsing metadata from comic at "${comicPath}"`)
const comicFileBuffer = await getComicFileBuffer(comicPath)
if (!comicFileBuffer) return null
const archive = await Archive.open(comicFileBuffer)
const fileObjects = await archive.getFilesArray()
fileObjects.sort((a, b) => {
return a.file.name.localeCompare(b.file.name, undefined, {
numeric: true,
sensitivity: 'base'
})
})
let metadata = null
const comicInfo = fileObjects.find(fo => fo.file.name === 'ComicInfo.xml')
if (comicInfo) {
const comicInfoEntry = await comicInfo.file.extract()
if (comicInfoEntry?.fileData) {
const comicInfoStr = new TextDecoder().decode(comicInfoEntry.fileData)
const comicInfoJson = await xmlToJSON(comicInfoStr)
if (comicInfoJson) {
metadata = parseComicInfoMetadata.parse(comicInfoJson)
}
}
}
const payload = {
path: comicPath,
ebookFormat: ebookFile.ebookFormat,
metadata
}
const firstImage = fileObjects.find(fo => globals.SupportedImageTypes.includes(Path.extname(fo.file.name).toLowerCase().slice(1)))
if (firstImage?.file?._path) {
payload.ebookCoverPath = firstImage.file._path
} else {
Logger.warn(`Cover image not found in comic at "${comicPath}"`)
}
return payload
}
module.exports.parse = parse