mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-27 09:08:57 +01:00
This commit is contained in:
parent
c7b0e1e2a2
commit
fa8d02c729
@ -36,9 +36,15 @@
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2 -mx-1">
|
||||
<div class="w-1/2 px-1">
|
||||
<div class="w-1/3 px-1">
|
||||
<ui-text-input-with-label v-model="details.narrator" label="Narrator" />
|
||||
</div>
|
||||
<div class="w-1/3 px-1">
|
||||
<ui-text-input-with-label v-model="details.publisher" label="Publisher" />
|
||||
</div>
|
||||
<div class="flex-grow px-1">
|
||||
<ui-text-input-with-label v-model="details.isbn" label="ISBN" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -83,6 +89,8 @@ export default {
|
||||
series: null,
|
||||
volumeNumber: null,
|
||||
publishYear: null,
|
||||
publisher: null,
|
||||
isbn: null,
|
||||
genres: []
|
||||
},
|
||||
newTags: [],
|
||||
@ -207,6 +215,8 @@ export default {
|
||||
this.details.series = this.book.series
|
||||
this.details.volumeNumber = this.book.volumeNumber
|
||||
this.details.publishYear = this.book.publishYear
|
||||
this.details.publisher = this.book.publisher || null
|
||||
this.details.isbn = this.book.isbn || null
|
||||
|
||||
this.newTags = this.audiobook.tags || []
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "1.6.12",
|
||||
"version": "1.6.13",
|
||||
"description": "Audiobook manager and player",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -86,7 +86,7 @@ export default {
|
||||
return Object.values(this.$store.state.user.user.audiobooks || {})
|
||||
},
|
||||
userAudiobooksRead() {
|
||||
return this.userAudiobooks.map((ab) => !!ab.isRead)
|
||||
return this.userAudiobooks.filter((ab) => !!ab.isRead)
|
||||
},
|
||||
genresWithCount() {
|
||||
var genresMap = {}
|
||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "1.6.8",
|
||||
"version": "1.6.12",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "1.6.12",
|
||||
"version": "1.6.13",
|
||||
"description": "Self-hosted audiobook server for managing and playing audiobooks",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@ -45,7 +45,8 @@
|
||||
"read-chunk": "^3.1.0",
|
||||
"recursive-readdir-async": "^1.1.8",
|
||||
"socket.io": "^4.1.3",
|
||||
"watcher": "^1.2.0"
|
||||
"watcher": "^1.2.0",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
@ -2,6 +2,7 @@ const Path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const { bytesPretty, elapsedPretty, readTextFile } = require('../utils/fileUtils')
|
||||
const { comparePaths, getIno } = require('../utils/index')
|
||||
const { parseOpfMetadataXML } = require('../utils/parseOpfMetadata')
|
||||
const { extractCoverArt } = require('../utils/ffmpegHelpers')
|
||||
const nfoGenerator = require('../utils/nfoGenerator')
|
||||
const Logger = require('../Logger')
|
||||
@ -501,6 +502,7 @@ class Audiobook {
|
||||
|
||||
var alreadyHasDescTxt = this.otherFiles.find(of => of.filename === 'desc.txt')
|
||||
var alreadyHasReaderTxt = this.otherFiles.find(of => of.filename === 'reader.txt')
|
||||
var alreadyHasMetadataOpf = this.otherFiles.find(of => of.filename === 'metadata.opf')
|
||||
|
||||
var newOtherFilePaths = newOtherFiles.map(f => f.path)
|
||||
this.otherFiles = this.otherFiles.filter(f => newOtherFilePaths.includes(f.path))
|
||||
@ -531,6 +533,27 @@ class Audiobook {
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
var metadataOpf = newOtherFiles.find(file => file.filename === 'metadata.opf' || file.filename === 'metadata.xml')
|
||||
if (metadataOpf && (!alreadyHasMetadataOpf || forceRescan)) {
|
||||
var xmlText = await readTextFile(metadataOpf.fullPath)
|
||||
if (xmlText) {
|
||||
var opfMetadata = await parseOpfMetadataXML(xmlText)
|
||||
Logger.debug(`[Audiobook] Sync Other File ${metadataOpf.filename} parsed:`, opfMetadata)
|
||||
if (opfMetadata) {
|
||||
const bookUpdatePayload = {}
|
||||
for (const key in opfMetadata) {
|
||||
if (opfMetadata[key] && !this.book[key]) {
|
||||
bookUpdatePayload[key] = opfMetadata[key]
|
||||
}
|
||||
}
|
||||
if (Object.keys(bookUpdatePayload).length) {
|
||||
Logger.debug(`[Audiobook] Using data found in metadata opf/xml`, bookUpdatePayload)
|
||||
this.update({ book: bookUpdatePayload })
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newOtherFiles.forEach((file) => {
|
||||
var existingOtherFile = this.otherFiles.find(f => f.ino === file.ino)
|
||||
@ -754,6 +777,23 @@ class Audiobook {
|
||||
Logger.debug(`[Audiobook] "${this.title}" found reader.txt updating narrator with "${readerText}"`)
|
||||
bookUpdatePayload.narrator = readerText
|
||||
}
|
||||
|
||||
var metadataOpf = this.otherFiles.find(file => file.filename === 'metadata.opf' || file.filename === 'metadata.xml')
|
||||
if (metadataOpf) {
|
||||
var xmlText = await readTextFile(metadataOpf.fullPath)
|
||||
if (xmlText) {
|
||||
var opfMetadata = await parseOpfMetadataXML(xmlText)
|
||||
Logger.debug(`[Audiobook] "${this.title}" found ${metadataOpf.filename} parsed:`, opfMetadata)
|
||||
if (opfMetadata) {
|
||||
for (const key in opfMetadata) {
|
||||
if (opfMetadata[key] && !this.book[key] && !bookUpdatePayload[key]) {
|
||||
bookUpdatePayload[key] = opfMetadata[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(bookUpdatePayload).length) {
|
||||
return this.update({ book: bookUpdatePayload })
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
const Path = require('path')
|
||||
const fs = require('fs')
|
||||
const Logger = require('../Logger')
|
||||
const { parseString } = require("xml2js")
|
||||
|
||||
const levenshteinDistance = (str1, str2, caseSensitive = false) => {
|
||||
if (!caseSensitive) {
|
||||
@ -43,3 +44,17 @@ module.exports.getIno = (path) => {
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
const xmlToJSON = (xml) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
parseString(xml, (err, results) => {
|
||||
if (err) {
|
||||
Logger.error(`[xmlToJSON] Error`, err)
|
||||
resolve(null)
|
||||
} else {
|
||||
resolve(results)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
module.exports.xmlToJSON = xmlToJSON
|
||||
|
78
server/utils/parseOpfMetadata.js
Normal file
78
server/utils/parseOpfMetadata.js
Normal file
@ -0,0 +1,78 @@
|
||||
const { xmlToJSON } = require('./index')
|
||||
|
||||
function parseCreators(metadata) {
|
||||
if (!metadata['dc:creator']) return null
|
||||
var creators = metadata['dc:creator']
|
||||
if (!creators.length) return null
|
||||
return creators.map(c => {
|
||||
if (typeof c !== 'object' || !c['$'] || !c['_']) return false
|
||||
return {
|
||||
value: c['_'],
|
||||
role: c['$']['opf:role'] || null,
|
||||
fileAs: c['$']['opf:file-as'] || null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function fetchCreator(creators, role) {
|
||||
if (!creators || !creators.length) return null
|
||||
var creator = creators.find(c => c.role === role)
|
||||
return creator ? creator.value : null
|
||||
}
|
||||
|
||||
function fetchDate(metadata) {
|
||||
if (!metadata['dc:date']) return null
|
||||
var dates = metadata['dc:date']
|
||||
if (!dates.length || typeof dates[0] !== 'string') return null
|
||||
var dateSplit = dates[0].split('-')
|
||||
if (!dateSplit.length || dateSplit[0].length !== 4 || isNaN(dateSplit[0])) return null
|
||||
return dateSplit[0]
|
||||
}
|
||||
|
||||
function fetchPublisher(metadata) {
|
||||
if (!metadata['dc:publisher']) return null
|
||||
var publishers = metadata['dc:publisher']
|
||||
if (!publishers.length || typeof publishers[0] !== 'string') return null
|
||||
return publishers[0]
|
||||
}
|
||||
|
||||
function fetchISBN(metadata) {
|
||||
if (!metadata['dc:identifier'] || !metadata['dc:identifier'].length) return null
|
||||
var identifiers = metadata['dc:identifier']
|
||||
var isbnObj = identifiers.find(i => i['$'] && i['$']['opf:scheme'] === 'ISBN')
|
||||
return isbnObj ? isbnObj['_'] || null : null
|
||||
}
|
||||
|
||||
function fetchTitle(metadata) {
|
||||
if (!metadata['dc:title']) return null
|
||||
var titles = metadata['dc:title']
|
||||
if (!titles.length) return null
|
||||
if (typeof titles[0] === 'string') {
|
||||
return titles[0]
|
||||
}
|
||||
if (titles[0]['_']) {
|
||||
return titles[0]['_']
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
module.exports.parseOpfMetadataXML = async (xml) => {
|
||||
var json = await xmlToJSON(xml)
|
||||
if (!json || !json.package || !json.package.metadata) return null
|
||||
var metadata = json.package.metadata
|
||||
if (Array.isArray(metadata)) {
|
||||
if (!metadata.length) return null
|
||||
metadata = metadata[0]
|
||||
}
|
||||
|
||||
var creators = parseCreators(metadata)
|
||||
var data = {
|
||||
title: fetchTitle(metadata),
|
||||
author: fetchCreator(creators, 'aut'),
|
||||
narrator: fetchCreator(creators, 'nrt'),
|
||||
publishYear: fetchDate(metadata),
|
||||
publisher: fetchPublisher(metadata),
|
||||
isbn: fetchISBN(metadata)
|
||||
}
|
||||
return data
|
||||
}
|
Loading…
Reference in New Issue
Block a user