mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-05 04:51:09 +01:00
parent
32bdae31a8
commit
81d4ac3ed2
@ -39,11 +39,15 @@
|
|||||||
<ui-multi-select v-model="newServerSettings.sortingPrefixes" small :items="newServerSettings.sortingPrefixes" :label="$strings.LabelPrefixesToIgnore" @input="updateSortingPrefixes" :disabled="updatingServerSettings" />
|
<ui-multi-select v-model="newServerSettings.sortingPrefixes" small :items="newServerSettings.sortingPrefixes" :label="$strings.LabelPrefixesToIgnore" @input="updateSortingPrefixes" :disabled="updatingServerSettings" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div class="flex items-center py-2 mb-2">
|
||||||
<ui-toggle-switch labeledBy="settings-chromecast-support" v-model="newServerSettings.chromecastEnabled" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('chromecastEnabled', val)" />
|
<ui-toggle-switch labeledBy="settings-chromecast-support" v-model="newServerSettings.chromecastEnabled" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('chromecastEnabled', val)" />
|
||||||
<p class="pl-4" id="settings-chromecast-support">{{ $strings.LabelSettingsChromecastSupport }}</p>
|
<p class="pl-4" id="settings-chromecast-support">{{ $strings.LabelSettingsChromecastSupport }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="w-44 mb-2">
|
||||||
|
<ui-dropdown v-model="newServerSettings.metadataFileFormat" small :items="metadataFileFormats" label="Metadata File Format" @input="(val) => updateSettingsKey('metadataFileFormat', val)" :disabled="updatingServerSettings" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
<h2 class="font-semibold">{{ $strings.HeaderSettingsDisplay }}</h2>
|
<h2 class="font-semibold">{{ $strings.HeaderSettingsDisplay }}</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -272,7 +276,17 @@ export default {
|
|||||||
useBookshelfView: false,
|
useBookshelfView: false,
|
||||||
isPurgingCache: false,
|
isPurgingCache: false,
|
||||||
newServerSettings: {},
|
newServerSettings: {},
|
||||||
showConfirmPurgeCache: false
|
showConfirmPurgeCache: false,
|
||||||
|
metadataFileFormats: [
|
||||||
|
{
|
||||||
|
text: '.json',
|
||||||
|
value: 'json'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '.abs',
|
||||||
|
value: 'abs'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -499,14 +499,37 @@ class LibraryItem {
|
|||||||
// Make sure metadata book dir exists
|
// Make sure metadata book dir exists
|
||||||
await fs.ensureDir(metadataPath)
|
await fs.ensureDir(metadataPath)
|
||||||
}
|
}
|
||||||
metadataPath = Path.join(metadataPath, 'metadata.abs')
|
|
||||||
|
|
||||||
return abmetadataGenerator.generate(this, metadataPath).then((success) => {
|
const metadataFileFormat = global.ServerSettings.metadataFileFormat
|
||||||
this.isSavingMetadata = false
|
const metadataFilePath = Path.join(metadataPath, `metadata.${metadataFileFormat}`)
|
||||||
if (!success) Logger.error(`[LibraryItem] Failed saving abmetadata to "${metadataPath}"`)
|
|
||||||
else Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataPath}"`)
|
if (metadataFileFormat === 'json') {
|
||||||
return success
|
// Remove metadata.abs if it exists
|
||||||
})
|
if (await fs.pathExists(Path.join(metadataPath, `metadata.abs`))) {
|
||||||
|
Logger.debug(`[LibraryItem] Removing metadata.abs for item "${this.media.metadata.title}"`)
|
||||||
|
await fs.remove(Path.join(metadataPath, `metadata.abs`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2)).then(() => {
|
||||||
|
return true
|
||||||
|
}).catch((error) => {
|
||||||
|
Logger.error(`[LibraryItem] Failed to save json file at "${metadataFilePath}"`, error)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Remove metadata.json if it exists
|
||||||
|
if (await fs.pathExists(Path.join(metadataPath, `metadata.json`))) {
|
||||||
|
Logger.debug(`[LibraryItem] Removing metadata.json for item "${this.media.metadata.title}"`)
|
||||||
|
await fs.remove(Path.join(metadataPath, `metadata.json`))
|
||||||
|
}
|
||||||
|
|
||||||
|
return abmetadataGenerator.generate(this, metadataFilePath).then((success) => {
|
||||||
|
this.isSavingMetadata = false
|
||||||
|
if (!success) Logger.error(`[LibraryItem] Failed saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
else Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
return success
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLibraryFile(ino) {
|
removeLibraryFile(ino) {
|
||||||
|
@ -89,6 +89,14 @@ class Book {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSONForMetadataFile() {
|
||||||
|
return {
|
||||||
|
tags: [...this.tags],
|
||||||
|
chapters: this.chapters.map(c => ({ ...c })),
|
||||||
|
metadata: this.metadata.toJSONForMetadataFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get size() {
|
get size() {
|
||||||
var total = 0
|
var total = 0
|
||||||
this.audioFiles.forEach((af) => total += af.metadata.size)
|
this.audioFiles.forEach((af) => total += af.metadata.size)
|
||||||
@ -248,11 +256,12 @@ class Book {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs')
|
const metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs' || lf.metadata.filename === 'metadata.json')
|
||||||
if (metadataAbs) {
|
if (metadataAbs) {
|
||||||
Logger.debug(`[Book] Found metadata.abs file for "${this.metadata.title}"`)
|
const isJSON = metadataAbs.metadata.filename === 'metadata.json'
|
||||||
|
Logger.debug(`[Book] Found ${metadataAbs.metadata.filename} file for "${this.metadata.title}"`)
|
||||||
const metadataText = await readTextFile(metadataAbs.metadata.path)
|
const metadataText = await readTextFile(metadataAbs.metadata.path)
|
||||||
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'book')
|
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'book', isJSON)
|
||||||
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
|
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
|
||||||
Logger.debug(`[Book] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
|
Logger.debug(`[Book] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
|
||||||
|
|
||||||
|
@ -94,6 +94,13 @@ class Podcast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSONForMetadataFile() {
|
||||||
|
return {
|
||||||
|
tags: [...this.tags],
|
||||||
|
metadata: this.metadata.toJSON()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get size() {
|
get size() {
|
||||||
var total = 0
|
var total = 0
|
||||||
this.episodes.forEach((ep) => total += ep.size)
|
this.episodes.forEach((ep) => total += ep.size)
|
||||||
@ -199,10 +206,11 @@ class Podcast {
|
|||||||
let metadataUpdatePayload = {}
|
let metadataUpdatePayload = {}
|
||||||
let tagsUpdated = false
|
let tagsUpdated = false
|
||||||
|
|
||||||
const metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs')
|
const metadataAbs = textMetadataFiles.find(lf => lf.metadata.filename === 'metadata.abs' || lf.metadata.filename === 'metadata.json')
|
||||||
if (metadataAbs) {
|
if (metadataAbs) {
|
||||||
|
const isJSON = metadataAbs.metadata.filename === 'metadata.json'
|
||||||
const metadataText = await readTextFile(metadataAbs.metadata.path)
|
const metadataText = await readTextFile(metadataAbs.metadata.path)
|
||||||
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'podcast')
|
const abmetadataUpdates = abmetadataGenerator.parseAndCheckForUpdates(metadataText, this, 'podcast', isJSON)
|
||||||
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
|
if (abmetadataUpdates && Object.keys(abmetadataUpdates).length) {
|
||||||
Logger.debug(`[Podcast] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
|
Logger.debug(`[Podcast] "${this.metadata.title}" changes found in metadata.abs file`, abmetadataUpdates)
|
||||||
|
|
||||||
|
@ -109,6 +109,16 @@ class BookMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toJSONForMetadataFile() {
|
||||||
|
const json = this.toJSON()
|
||||||
|
json.authors = json.authors.map(au => au.name)
|
||||||
|
json.series = json.series.map(se => {
|
||||||
|
if (!se.sequence) return se.name
|
||||||
|
return `${se.name} #${se.sequence}`
|
||||||
|
})
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
return new BookMetadata(this.toJSON())
|
return new BookMetadata(this.toJSON())
|
||||||
}
|
}
|
||||||
@ -191,8 +201,9 @@ class BookMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(payload) {
|
update(payload) {
|
||||||
var json = this.toJSON()
|
const json = this.toJSON()
|
||||||
var hasUpdates = false
|
let hasUpdates = false
|
||||||
|
|
||||||
for (const key in json) {
|
for (const key in json) {
|
||||||
if (payload[key] !== undefined) {
|
if (payload[key] !== undefined) {
|
||||||
if (!areEquivalent(payload[key], json[key])) {
|
if (!areEquivalent(payload[key], json[key])) {
|
||||||
@ -373,8 +384,10 @@ class BookMetadata {
|
|||||||
const parsed = parseNameString.parse(authorsTag)
|
const parsed = parseNameString.parse(authorsTag)
|
||||||
if (!parsed) return []
|
if (!parsed) return []
|
||||||
return (parsed.names || []).map((au) => {
|
return (parsed.names || []).map((au) => {
|
||||||
|
const findAuthor = this.authors.find(_au => _au.name == au)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `new-${Math.floor(Math.random() * 1000000)}`,
|
id: findAuthor?.id || `new-${Math.floor(Math.random() * 1000000)}`,
|
||||||
name: au
|
name: au
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
const { BookshelfView } = require('../../utils/constants')
|
const { BookshelfView } = require('../../utils/constants')
|
||||||
const { isNullOrNaN } = require('../../utils')
|
|
||||||
const Logger = require('../../Logger')
|
const Logger = require('../../Logger')
|
||||||
|
|
||||||
class ServerSettings {
|
class ServerSettings {
|
||||||
@ -21,6 +20,7 @@ class ServerSettings {
|
|||||||
// Metadata - choose to store inside users library item folder
|
// Metadata - choose to store inside users library item folder
|
||||||
this.storeCoverWithItem = false
|
this.storeCoverWithItem = false
|
||||||
this.storeMetadataWithItem = false
|
this.storeMetadataWithItem = false
|
||||||
|
this.metadataFileFormat = 'json'
|
||||||
|
|
||||||
// Security/Rate limits
|
// Security/Rate limits
|
||||||
this.rateLimitLoginRequests = 10
|
this.rateLimitLoginRequests = 10
|
||||||
@ -77,6 +77,7 @@ class ServerSettings {
|
|||||||
|
|
||||||
this.storeCoverWithItem = !!settings.storeCoverWithItem
|
this.storeCoverWithItem = !!settings.storeCoverWithItem
|
||||||
this.storeMetadataWithItem = !!settings.storeMetadataWithItem
|
this.storeMetadataWithItem = !!settings.storeMetadataWithItem
|
||||||
|
this.metadataFileFormat = settings.metadataFileFormat || 'json'
|
||||||
|
|
||||||
this.rateLimitLoginRequests = !isNaN(settings.rateLimitLoginRequests) ? Number(settings.rateLimitLoginRequests) : 10
|
this.rateLimitLoginRequests = !isNaN(settings.rateLimitLoginRequests) ? Number(settings.rateLimitLoginRequests) : 10
|
||||||
this.rateLimitLoginWindow = !isNaN(settings.rateLimitLoginWindow) ? Number(settings.rateLimitLoginWindow) : 10 * 60 * 1000 // 10 Minutes
|
this.rateLimitLoginWindow = !isNaN(settings.rateLimitLoginWindow) ? Number(settings.rateLimitLoginWindow) : 10 * 60 * 1000 // 10 Minutes
|
||||||
@ -112,6 +113,16 @@ class ServerSettings {
|
|||||||
if (settings.homeBookshelfView == undefined) { // homeBookshelfView was added in 2.1.3
|
if (settings.homeBookshelfView == undefined) { // homeBookshelfView was added in 2.1.3
|
||||||
this.homeBookshelfView = settings.bookshelfView
|
this.homeBookshelfView = settings.bookshelfView
|
||||||
}
|
}
|
||||||
|
if (settings.metadataFileFormat == undefined) { // metadataFileFormat was added in 2.2.21
|
||||||
|
// All users using old settings will stay abs until changed
|
||||||
|
this.metadataFileFormat = 'abs'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!['abs', 'json'].includes(this.metadataFileFormat)) {
|
||||||
|
Logger.error(`[ServerSettings] construct: Invalid metadataFileFormat ${this.metadataFileFormat}`)
|
||||||
|
this.metadataFileFormat = 'json'
|
||||||
|
}
|
||||||
|
|
||||||
if (this.logLevel !== Logger.logLevel) {
|
if (this.logLevel !== Logger.logLevel) {
|
||||||
Logger.setLogLevel(this.logLevel)
|
Logger.setLogLevel(this.logLevel)
|
||||||
@ -133,6 +144,7 @@ class ServerSettings {
|
|||||||
scannerUseTone: this.scannerUseTone,
|
scannerUseTone: this.scannerUseTone,
|
||||||
storeCoverWithItem: this.storeCoverWithItem,
|
storeCoverWithItem: this.storeCoverWithItem,
|
||||||
storeMetadataWithItem: this.storeMetadataWithItem,
|
storeMetadataWithItem: this.storeMetadataWithItem,
|
||||||
|
metadataFileFormat: this.metadataFileFormat,
|
||||||
rateLimitLoginRequests: this.rateLimitLoginRequests,
|
rateLimitLoginRequests: this.rateLimitLoginRequests,
|
||||||
rateLimitLoginWindow: this.rateLimitLoginWindow,
|
rateLimitLoginWindow: this.rateLimitLoginWindow,
|
||||||
backupSchedule: this.backupSchedule,
|
backupSchedule: this.backupSchedule,
|
||||||
|
@ -2,7 +2,7 @@ const fs = require('../libs/fsExtra')
|
|||||||
const filePerms = require('./filePerms')
|
const filePerms = require('./filePerms')
|
||||||
const package = require('../../package.json')
|
const package = require('../../package.json')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const { getId, copyValue } = require('./index')
|
const { getId } = require('./index')
|
||||||
|
|
||||||
|
|
||||||
const CurrentAbMetadataVersion = 2
|
const CurrentAbMetadataVersion = 2
|
||||||
@ -328,11 +328,11 @@ function parseAbMetadataText(text, mediaType) {
|
|||||||
module.exports.parse = parseAbMetadataText
|
module.exports.parse = parseAbMetadataText
|
||||||
|
|
||||||
function checkUpdatedBookAuthors(abmetadataAuthors, authors) {
|
function checkUpdatedBookAuthors(abmetadataAuthors, authors) {
|
||||||
var finalAuthors = []
|
const finalAuthors = []
|
||||||
var hasUpdates = false
|
let hasUpdates = false
|
||||||
|
|
||||||
abmetadataAuthors.forEach((authorName) => {
|
abmetadataAuthors.forEach((authorName) => {
|
||||||
var findAuthor = authors.find(au => au.name.toLowerCase() == authorName.toLowerCase())
|
const findAuthor = authors.find(au => au.name.toLowerCase() == authorName.toLowerCase())
|
||||||
if (!findAuthor) {
|
if (!findAuthor) {
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
finalAuthors.push({
|
finalAuthors.push({
|
||||||
@ -397,18 +397,54 @@ function checkArraysChanged(abmetadataArray, mediaArray) {
|
|||||||
return abmetadataArray.join(',') != mediaArray.join(',')
|
return abmetadataArray.join(',') != mediaArray.join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseJsonMetadataText(text) {
|
||||||
|
try {
|
||||||
|
const abmetadataData = JSON.parse(text)
|
||||||
|
if (abmetadataData.metadata?.series?.length) {
|
||||||
|
abmetadataData.metadata.series = abmetadataData.metadata.series.map(series => {
|
||||||
|
let sequence = null
|
||||||
|
let name = series
|
||||||
|
// Series sequence match any characters after " #" other than whitespace and another #
|
||||||
|
// e.g. "Name #1a" is valid. "Name #1#a" or "Name #1 a" is not valid.
|
||||||
|
const matchResults = series.match(/ #([^#\s]+)$/) // Pull out sequence #
|
||||||
|
if (matchResults && matchResults.length && matchResults.length > 1) {
|
||||||
|
sequence = matchResults[1] // Group 1
|
||||||
|
name = series.replace(matchResults[0], '')
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
sequence
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return abmetadataData
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`[abmetadataGenerator] Invalid metadata.json JSON`, error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Input text from abmetadata file and return object of media changes
|
// Input text from abmetadata file and return object of media changes
|
||||||
// only returns object of changes. empty object means no changes
|
// only returns object of changes. empty object means no changes
|
||||||
function parseAndCheckForUpdates(text, media, mediaType) {
|
function parseAndCheckForUpdates(text, media, mediaType, isJSON) {
|
||||||
if (!text || !media || !media.metadata || !mediaType) {
|
if (!text || !media || !media.metadata || !mediaType) {
|
||||||
Logger.error(`Invalid inputs to parseAndCheckForUpdates`)
|
Logger.error(`Invalid inputs to parseAndCheckForUpdates`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaMetadata = media.metadata
|
const mediaMetadata = media.metadata
|
||||||
const metadataUpdatePayload = {} // Only updated key/values
|
const metadataUpdatePayload = {} // Only updated key/values
|
||||||
|
|
||||||
const abmetadataData = parseAbMetadataText(text, mediaType)
|
let abmetadataData = null
|
||||||
|
|
||||||
|
if (isJSON) {
|
||||||
|
abmetadataData = parseJsonMetadataText(text)
|
||||||
|
} else {
|
||||||
|
abmetadataData = parseAbMetadataText(text, mediaType)
|
||||||
|
}
|
||||||
|
|
||||||
if (!abmetadataData || !abmetadataData.metadata) {
|
if (!abmetadataData || !abmetadataData.metadata) {
|
||||||
|
Logger.error(`[abmetadataGenerator] Invalid metadata file`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const globals = {
|
|||||||
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||||
SupportedVideoTypes: ['mp4'],
|
SupportedVideoTypes: ['mp4'],
|
||||||
TextFileTypes: ['txt', 'nfo'],
|
TextFileTypes: ['txt', 'nfo'],
|
||||||
MetadataFileTypes: ['opf', 'abs', 'xml']
|
MetadataFileTypes: ['opf', 'abs', 'xml', 'json']
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = globals
|
module.exports = globals
|
||||||
|
Loading…
Reference in New Issue
Block a user