mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-22 11:49:37 +00:00
Support for libraries and folder mapping, updating static cover path, detect reader.txt
This commit is contained in:
parent
a590e795e3
commit
577f3bead9
43 changed files with 2548 additions and 768 deletions
|
|
@ -34,9 +34,6 @@ class AudioFile {
|
|||
this.exclude = false
|
||||
this.error = null
|
||||
|
||||
// TEMP: For forcing rescan
|
||||
this.isOldAudioFile = false
|
||||
|
||||
if (data) {
|
||||
this.construct(data)
|
||||
}
|
||||
|
|
@ -103,7 +100,6 @@ class AudioFile {
|
|||
// Old version of AudioFile used `tagAlbum` etc.
|
||||
var isOldVersion = Object.keys(data).find(key => key.startsWith('tag'))
|
||||
if (isOldVersion) {
|
||||
this.isOldAudioFile = true
|
||||
this.metadata = new AudioFileMetadata(data)
|
||||
} else {
|
||||
this.metadata = new AudioFileMetadata(data.metadata || {})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const Path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const { bytesPretty, elapsedPretty, readTextFile } = require('../utils/fileUtils')
|
||||
const { comparePaths, getIno } = require('../utils/index')
|
||||
const { extractCoverArt } = require('../utils/ffmpegHelpers')
|
||||
|
|
@ -14,11 +15,15 @@ class Audiobook {
|
|||
this.id = null
|
||||
this.ino = null // Inode
|
||||
|
||||
this.libraryId = null
|
||||
this.folderId = null
|
||||
|
||||
this.path = null
|
||||
this.fullPath = null
|
||||
|
||||
this.addedAt = null
|
||||
this.lastUpdate = null
|
||||
this.lastScan = null
|
||||
this.scanVersion = null
|
||||
|
||||
this.tracks = []
|
||||
this.missingParts = []
|
||||
|
|
@ -41,11 +46,14 @@ class Audiobook {
|
|||
construct(audiobook) {
|
||||
this.id = audiobook.id
|
||||
this.ino = audiobook.ino || null
|
||||
|
||||
this.libraryId = audiobook.libraryId || 'main'
|
||||
this.folderId = audiobook.folderId || 'audiobooks'
|
||||
this.path = audiobook.path
|
||||
this.fullPath = audiobook.fullPath
|
||||
this.addedAt = audiobook.addedAt
|
||||
this.lastUpdate = audiobook.lastUpdate || this.addedAt
|
||||
this.lastScan = audiobook.lastScan || null
|
||||
this.scanVersion = audiobook.scanVersion || null
|
||||
|
||||
this.tracks = audiobook.tracks.map(track => new AudioTrack(track))
|
||||
this.missingParts = audiobook.missingParts
|
||||
|
|
@ -127,10 +135,6 @@ class Audiobook {
|
|||
return !!this._audioFiles.find(af => af.embeddedCoverArt)
|
||||
}
|
||||
|
||||
get hasDescriptionTextFile() {
|
||||
return !!this._otherFiles.find(of => of.filename === 'desc.txt')
|
||||
}
|
||||
|
||||
bookToJSON() {
|
||||
return this.book ? this.book.toJSON() : null
|
||||
}
|
||||
|
|
@ -144,6 +148,8 @@ class Audiobook {
|
|||
return {
|
||||
id: this.id,
|
||||
ino: this.ino,
|
||||
libraryId: this.libraryId,
|
||||
folderId: this.folderId,
|
||||
title: this.title,
|
||||
author: this.author,
|
||||
cover: this.cover,
|
||||
|
|
@ -151,6 +157,8 @@ class Audiobook {
|
|||
fullPath: this.fullPath,
|
||||
addedAt: this.addedAt,
|
||||
lastUpdate: this.lastUpdate,
|
||||
lastScan: this.lastScan,
|
||||
scanVersion: this.scanVersion,
|
||||
missingParts: this.missingParts,
|
||||
tags: this.tags,
|
||||
book: this.bookToJSON(),
|
||||
|
|
@ -166,6 +174,8 @@ class Audiobook {
|
|||
return {
|
||||
id: this.id,
|
||||
ino: this.ino,
|
||||
libraryId: this.libraryId,
|
||||
folderId: this.folderId,
|
||||
book: this.bookToJSON(),
|
||||
tags: this.tags,
|
||||
path: this.path,
|
||||
|
|
@ -188,6 +198,9 @@ class Audiobook {
|
|||
toJSONExpanded() {
|
||||
return {
|
||||
id: this.id,
|
||||
ino: this.ino,
|
||||
libraryId: this.libraryId,
|
||||
folderId: this.folderId,
|
||||
path: this.path,
|
||||
fullPath: this.fullPath,
|
||||
addedAt: this.addedAt,
|
||||
|
|
@ -284,13 +297,10 @@ class Audiobook {
|
|||
return hasUpdates
|
||||
}
|
||||
|
||||
// Scans in v1.3.0 or lower will need to rescan audiofiles to pickup metadata and embedded cover
|
||||
checkNeedsAudioFileRescan() {
|
||||
return !!(this.audioFiles || []).find(af => af.isOldAudioFile || af.codec === null)
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.id = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
|
||||
this.libraryId = data.libraryId || 'main'
|
||||
this.folderId = data.folderId || 'audiobooks'
|
||||
this.ino = data.ino || null
|
||||
|
||||
this.path = data.path
|
||||
|
|
@ -307,7 +317,26 @@ class Audiobook {
|
|||
this.setBook(data)
|
||||
}
|
||||
|
||||
checkHasOldCoverPath() {
|
||||
return this.book.cover && !this.book.coverFullPath
|
||||
}
|
||||
|
||||
setLastScan(version) {
|
||||
this.lastScan = Date.now()
|
||||
this.lastUpdate = Date.now()
|
||||
this.scanVersion = version
|
||||
}
|
||||
|
||||
setBook(data) {
|
||||
// Use first image file as cover
|
||||
if (this.otherFiles && this.otherFiles.length) {
|
||||
var imageFile = this.otherFiles.find(f => f.filetype === 'image')
|
||||
if (imageFile) {
|
||||
data.coverFullPath = imageFile.fullPath
|
||||
data.cover = Path.normalize(Path.join(`/s/book/${this.id}`, imageFile.path))
|
||||
}
|
||||
}
|
||||
|
||||
this.book = new Book()
|
||||
this.book.setData(data)
|
||||
}
|
||||
|
|
@ -432,12 +461,13 @@ class Audiobook {
|
|||
}
|
||||
|
||||
// On scan check other files found with other files saved
|
||||
async syncOtherFiles(newOtherFiles, forceRescan = false) {
|
||||
async syncOtherFiles(newOtherFiles, metadataPath, forceRescan = false) {
|
||||
var hasUpdates = false
|
||||
|
||||
var currOtherFileNum = this.otherFiles.length
|
||||
|
||||
var alreadyHadDescTxt = this.otherFiles.find(of => of.filename === 'desc.txt')
|
||||
var alreadyHasDescTxt = this.otherFiles.find(of => of.filename === 'desc.txt')
|
||||
var alreadyHasReaderTxt = this.otherFiles.find(of => of.filename === 'reader.txt')
|
||||
|
||||
var newOtherFilePaths = newOtherFiles.map(f => f.path)
|
||||
this.otherFiles = this.otherFiles.filter(f => newOtherFilePaths.includes(f.path))
|
||||
|
|
@ -448,9 +478,9 @@ class Audiobook {
|
|||
hasUpdates = true
|
||||
}
|
||||
|
||||
// If desc.txt is new or forcing rescan then read it and update description if empty
|
||||
var descriptionTxt = newOtherFiles.find(file => file.filename === 'desc.txt')
|
||||
if (descriptionTxt && (!alreadyHadDescTxt || forceRescan)) {
|
||||
// If desc.txt is new or forcing rescan then read it and update description (will overwrite)
|
||||
var descriptionTxt = this.otherFiles.find(file => file.filename === 'desc.txt')
|
||||
if (descriptionTxt && (!alreadyHasDescTxt || forceRescan)) {
|
||||
var newDescription = await readTextFile(descriptionTxt.fullPath)
|
||||
if (newDescription) {
|
||||
Logger.debug(`[Audiobook] Sync Other File desc.txt: ${newDescription}`)
|
||||
|
|
@ -458,10 +488,19 @@ class Audiobook {
|
|||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
// If reader.txt is new or forcing rescan then read it and update narrarator (will overwrite)
|
||||
var readerTxt = this.otherFiles.find(file => file.filename === 'reader.txt')
|
||||
if (readerTxt && (!alreadyHasReaderTxt || forceRescan)) {
|
||||
var newReader = await readTextFile(readerTxt.fullPath)
|
||||
if (newReader) {
|
||||
Logger.debug(`[Audiobook] Sync Other File reader.txt: ${newReader}`)
|
||||
this.update({ book: { narrarator: newReader } })
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should use inode
|
||||
newOtherFiles.forEach((file) => {
|
||||
var existingOtherFile = this.otherFiles.find(f => f.path === file.path)
|
||||
var existingOtherFile = this.otherFiles.find(f => f.ino === file.ino)
|
||||
if (!existingOtherFile) {
|
||||
Logger.debug(`[Audiobook] New other file found on sync ${file.filename} | "${this.title}"`)
|
||||
this.addOtherFile(file)
|
||||
|
|
@ -469,21 +508,76 @@ class Audiobook {
|
|||
}
|
||||
})
|
||||
|
||||
// Check if cover was a local image and that it still exists
|
||||
|
||||
var imageFiles = this.otherFiles.filter(f => f.filetype === 'image')
|
||||
|
||||
// OLD Path Check if cover was a local image and that it still exists
|
||||
if (this.book.cover && this.book.cover.substr(1).startsWith('local')) {
|
||||
var coverStillExists = imageFiles.find(f => comparePaths(f.path, this.book.cover.substr('/local/'.length)))
|
||||
var coverStripped = this.book.cover.substr('/local/'.length)
|
||||
// Check if was removed first
|
||||
var coverStillExists = imageFiles.find(f => comparePaths(f.path, coverStripped))
|
||||
if (!coverStillExists) {
|
||||
Logger.info(`[Audiobook] Local cover was removed | "${this.title}"`)
|
||||
this.book.cover = null
|
||||
this.book.removeCover()
|
||||
} else {
|
||||
var oldFormat = this.book.cover
|
||||
|
||||
// Update book cover path to new format
|
||||
this.book.fullCoverPath = Path.join(this.fullPath, this.book.cover.substr(7))
|
||||
this.book.cover = Path.normalize(coverStripped.replace(this.path, `/s/book/${this.id}`))
|
||||
Logger.debug(`[Audiobook] updated book cover to new format "${oldFormat}" => "${this.book.cover}"`)
|
||||
}
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
// Check if book was removed from book dir
|
||||
if (this.book.cover && this.book.cover.substr(1).startsWith('s/book/')) {
|
||||
// Fixing old cover paths
|
||||
if (!this.book.coverFullPath) {
|
||||
this.book.coverFullPath = Path.join(this.fullPath, this.book.cover.substr(`/s/book/${this.id}`.length))
|
||||
Logger.debug(`[Audiobook] Metadata cover full path set "${this.book.coverFullPath}" for "${this.title}"`)
|
||||
hasUpdates = true
|
||||
}
|
||||
|
||||
var coverStillExists = imageFiles.find(f => f.fullPath === this.book.coverFullPath)
|
||||
if (!coverStillExists) {
|
||||
Logger.info(`[Audiobook] Local cover "${this.book.cover}" was removed | "${this.title}"`)
|
||||
this.book.removeCover()
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
if (this.book.cover && this.book.cover.substr(1).startsWith('metadata')) {
|
||||
// Fixing old cover paths
|
||||
if (!this.book.coverFullPath) {
|
||||
this.book.coverFullPath = Path.join(metadataPath, this.book.cover.substr('/metadata/'.length))
|
||||
Logger.debug(`[Audiobook] Metadata cover full path set "${this.book.coverFullPath}" for "${this.title}"`)
|
||||
hasUpdates = true
|
||||
}
|
||||
var coverStillExists = imageFiles.find(f => f.fullPath === this.book.coverFullPath)
|
||||
if (!coverStillExists) {
|
||||
Logger.info(`[Audiobook] Metadata cover "${this.book.cover}" was removed | "${this.title}"`)
|
||||
this.book.removeCover()
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
|
||||
if (this.book.cover && !this.book.coverFullPath) {
|
||||
if (this.book.cover.startsWith('http')) {
|
||||
Logger.debug(`[Audiobook] Still using http path for cover "${this.book.cover}" - should update to local`)
|
||||
this.book.coverFullPath = this.book.cover
|
||||
hasUpdates = true
|
||||
} else {
|
||||
Logger.warn(`[Audiobook] Full cover path still not set "${this.book.cover}"`)
|
||||
}
|
||||
}
|
||||
|
||||
// If no cover set and image file exists then use it
|
||||
if (!this.book.cover && imageFiles.length) {
|
||||
this.book.cover = Path.join('/local', imageFiles[0].path)
|
||||
Logger.info(`[Audiobook] Local cover was set | "${this.title}"`)
|
||||
var imagePathRelativeToBook = imageFiles[0].path.replace(this.path, '')
|
||||
this.book.cover = Path.join(`/s/book/${this.id}`, imagePathRelativeToBook)
|
||||
this.book.coverFullPath = imageFiles[0].fullPath
|
||||
Logger.info(`[Audiobook] Local cover was set to "${this.book.cover}" | "${this.title}"`)
|
||||
hasUpdates = true
|
||||
}
|
||||
return hasUpdates
|
||||
|
|
@ -582,6 +676,12 @@ class Audiobook {
|
|||
var coverFilename = audioFileWithCover.embeddedCoverArt === 'png' ? 'cover.png' : 'cover.jpg'
|
||||
var coverFilePath = Path.join(coverDirFullPath, coverFilename)
|
||||
|
||||
var coverAlreadyExists = await fs.pathExists(coverFilePath)
|
||||
if (coverAlreadyExists) {
|
||||
Logger.warn(`[Audiobook] Extract embedded cover art but cover already exists for "${this.title}" - bail`)
|
||||
return false
|
||||
}
|
||||
|
||||
var success = await extractCoverArt(audioFileWithCover.fullPath, coverFilePath)
|
||||
if (success) {
|
||||
var coverRelPath = Path.join(coverDirRelPath, coverFilename)
|
||||
|
|
@ -591,16 +691,32 @@ class Audiobook {
|
|||
return false
|
||||
}
|
||||
|
||||
// If desc.txt exists then use it as description
|
||||
async saveDescriptionFromTextFile() {
|
||||
var descriptionTextFile = this.otherFiles.find(file => file.filename === 'desc.txt')
|
||||
if (!descriptionTextFile) return false
|
||||
var newDescription = await readTextFile(descriptionTextFile.fullPath)
|
||||
if (!newDescription) return false
|
||||
return this.update({ book: { description: newDescription } })
|
||||
// Look for desc.txt and reader.txt and update details if found
|
||||
async saveDataFromTextFiles() {
|
||||
var bookUpdatePayload = {}
|
||||
var descriptionText = await this.fetchTextFromTextFile('desc.txt')
|
||||
if (descriptionText) {
|
||||
Logger.debug(`[Audiobook] "${this.title}" found desc.txt updating description with "${descriptionText.slice(0, 20)}..."`)
|
||||
bookUpdatePayload.description = descriptionText
|
||||
}
|
||||
var readerText = await this.fetchTextFromTextFile('reader.txt')
|
||||
if (readerText) {
|
||||
Logger.debug(`[Audiobook] "${this.title}" found reader.txt updating narrarator with "${readerText}"`)
|
||||
bookUpdatePayload.narrarator = readerText
|
||||
}
|
||||
if (Object.keys(bookUpdatePayload).length) {
|
||||
return this.update({ book: bookUpdatePayload })
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Audio file metadata tags map to EMPTY book details
|
||||
fetchTextFromTextFile(textfileName) {
|
||||
var textFile = this.otherFiles.find(file => file.filename === textfileName)
|
||||
if (!textFile) return false
|
||||
return readTextFile(textFile.fullPath)
|
||||
}
|
||||
|
||||
// Audio file metadata tags map to book details (will not overwrite)
|
||||
setDetailsFromFileMetadata() {
|
||||
if (!this.audioFiles.length) return false
|
||||
var audioFile = this.audioFiles[0]
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class Book {
|
|||
this.publisher = null
|
||||
this.description = null
|
||||
this.cover = null
|
||||
this.coverFullPath = null
|
||||
this.genres = []
|
||||
this.lastUpdate = null
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ class Book {
|
|||
this.publisher = book.publisher
|
||||
this.description = book.description
|
||||
this.cover = book.cover
|
||||
this.coverFullPath = book.coverFullPath || null
|
||||
this.genres = book.genres
|
||||
this.lastUpdate = book.lastUpdate || Date.now()
|
||||
}
|
||||
|
|
@ -65,6 +67,7 @@ class Book {
|
|||
publisher: this.publisher,
|
||||
description: this.description,
|
||||
cover: this.cover,
|
||||
coverFullPath: this.coverFullPath,
|
||||
genres: this.genres,
|
||||
lastUpdate: this.lastUpdate
|
||||
}
|
||||
|
|
@ -100,20 +103,13 @@ class Book {
|
|||
this.publishYear = data.publishYear || null
|
||||
this.description = data.description || null
|
||||
this.cover = data.cover || null
|
||||
this.coverFullPath = data.coverFullPath || null
|
||||
this.genres = data.genres || []
|
||||
this.lastUpdate = Date.now()
|
||||
|
||||
if (data.author) {
|
||||
this.setParseAuthor(this.author)
|
||||
}
|
||||
|
||||
// Use first image file as cover
|
||||
if (data.otherFiles && data.otherFiles.length) {
|
||||
var imageFile = data.otherFiles.find(f => f.filetype === 'image')
|
||||
if (imageFile) {
|
||||
this.cover = Path.normalize(Path.join('/local', imageFile.path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
|
|
@ -168,6 +164,12 @@ class Book {
|
|||
return true
|
||||
}
|
||||
|
||||
removeCover() {
|
||||
this.cover = null
|
||||
this.coverFullPath = null
|
||||
this.lastUpdate = Date.now()
|
||||
}
|
||||
|
||||
// If audiobook directory path was changed, check and update properties set from dirnames
|
||||
// May be worthwhile checking if these were manually updated and not override manual updates
|
||||
syncPathsUpdated(audiobookData) {
|
||||
|
|
|
|||
36
server/objects/Folder.js
Normal file
36
server/objects/Folder.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
class Folder {
|
||||
constructor(folder = null) {
|
||||
this.id = null
|
||||
this.fullPath = null
|
||||
this.libraryId = null
|
||||
this.addedAt = null
|
||||
|
||||
if (folder) {
|
||||
this.construct(folder)
|
||||
}
|
||||
}
|
||||
|
||||
construct(folder) {
|
||||
this.id = folder.id
|
||||
this.fullPath = folder.fullPath
|
||||
this.libraryId = folder.libraryId
|
||||
this.addedAt = folder.addedAt
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
fullPath: this.fullPath,
|
||||
libraryId: this.libraryId,
|
||||
addedAt: this.addedAt
|
||||
}
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.id = data.id ? data.id : 'fol' + (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
|
||||
this.fullPath = data.fullPath
|
||||
this.libraryId = data.libraryId
|
||||
this.addedAt = Date.now()
|
||||
}
|
||||
}
|
||||
module.exports = Folder
|
||||
95
server/objects/Library.js
Normal file
95
server/objects/Library.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
const Folder = require('./Folder')
|
||||
|
||||
class Library {
|
||||
constructor(library = null) {
|
||||
this.id = null
|
||||
this.name = null
|
||||
this.folders = []
|
||||
|
||||
this.createdAt = null
|
||||
this.lastUpdate = null
|
||||
|
||||
if (library) {
|
||||
this.construct(library)
|
||||
}
|
||||
}
|
||||
|
||||
get folderPaths() {
|
||||
return this.folders.map(f => f.fullPath)
|
||||
}
|
||||
|
||||
construct(library) {
|
||||
this.id = library.id
|
||||
this.name = library.name
|
||||
this.folders = (library.folders || []).map(f => new Folder(f))
|
||||
this.createdAt = library.createdAt
|
||||
this.lastUpdate = library.lastUpdate
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
folders: (this.folders || []).map(f => f.toJSON()),
|
||||
createdAt: this.createdAt,
|
||||
lastUpdate: this.lastUpdate
|
||||
}
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.id = data.id ? data.id : 'lib' + (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
|
||||
this.name = data.name
|
||||
if (data.folder) {
|
||||
this.folders = [
|
||||
new Folder(data.folder)
|
||||
]
|
||||
} else if (data.folders) {
|
||||
this.folders = data.folders.map(folder => {
|
||||
var newFolder = new Folder()
|
||||
newFolder.setData({
|
||||
fullPath: folder.fullPath,
|
||||
libraryId: this.id
|
||||
})
|
||||
return newFolder
|
||||
})
|
||||
}
|
||||
this.createdAt = Date.now()
|
||||
this.lastUpdate = Date.now()
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
var hasUpdates = false
|
||||
if (payload.name && payload.name !== this.name) {
|
||||
this.name = payload.name
|
||||
hasUpdates = true
|
||||
}
|
||||
if (payload.folders) {
|
||||
var newFolders = payload.folders.filter(f => !f.id)
|
||||
var removedFolders = this.folders.filter(f => !payload.folders.find(_f => _f.id === f.id))
|
||||
|
||||
if (removedFolders.length) {
|
||||
var removedFolderIds = removedFolders.map(f => f.id)
|
||||
this.folders = this.folders.filter(f => !removedFolderIds.includes(f.id))
|
||||
}
|
||||
|
||||
if (newFolders.length) {
|
||||
newFolders.forEach((folderData) => {
|
||||
var newFolder = new Folder()
|
||||
newFolder.setData(folderData)
|
||||
this.folders.push(newFolder)
|
||||
})
|
||||
}
|
||||
|
||||
hasUpdates = newFolders.length || removedFolders.length
|
||||
}
|
||||
if (hasUpdates) {
|
||||
this.lastUpdate = Date.now()
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
checkFullPathInLibrary(fullPath) {
|
||||
return this.folders.find(folder => fullPath.startsWith(folder.fullPath))
|
||||
}
|
||||
}
|
||||
module.exports = Library
|
||||
|
|
@ -8,6 +8,7 @@ class ServerSettings {
|
|||
this.autoTagNew = false
|
||||
this.newTagExpireDays = 15
|
||||
this.scannerParseSubtitle = false
|
||||
this.scannerFindCovers = false
|
||||
this.coverDestination = CoverDestination.METADATA
|
||||
this.saveMetadataFile = false
|
||||
this.rateLimitLoginRequests = 10
|
||||
|
|
@ -22,6 +23,7 @@ class ServerSettings {
|
|||
construct(settings) {
|
||||
this.autoTagNew = settings.autoTagNew
|
||||
this.newTagExpireDays = settings.newTagExpireDays
|
||||
this.scannerFindCovers = !!settings.scannerFindCovers
|
||||
this.scannerParseSubtitle = settings.scannerParseSubtitle
|
||||
this.coverDestination = settings.coverDestination || CoverDestination.METADATA
|
||||
this.saveMetadataFile = !!settings.saveMetadataFile
|
||||
|
|
@ -39,6 +41,7 @@ class ServerSettings {
|
|||
id: this.id,
|
||||
autoTagNew: this.autoTagNew,
|
||||
newTagExpireDays: this.newTagExpireDays,
|
||||
scannerFindCovers: this.scannerFindCovers,
|
||||
scannerParseSubtitle: this.scannerParseSubtitle,
|
||||
coverDestination: this.coverDestination,
|
||||
saveMetadataFile: !!this.saveMetadataFile,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue