mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-02-03 16:59:41 +00:00
Add:Audiobooks only library settings, supplementary ebooks #1664
This commit is contained in:
parent
4b4fb33d8f
commit
014fc45c15
39 changed files with 624 additions and 122 deletions
|
|
@ -3,7 +3,7 @@ const fs = require('../libs/fsExtra')
|
|||
const date = require('../libs/dateAndTime')
|
||||
|
||||
const Logger = require('../Logger')
|
||||
const Folder = require('../objects/Folder')
|
||||
const Library = require('../objects/Library')
|
||||
const { LogLevel } = require('../utils/constants')
|
||||
const filePerms = require('../utils/filePerms')
|
||||
const { getId, secondsToTimestamp } = require('../utils/index')
|
||||
|
|
@ -12,10 +12,7 @@ class LibraryScan {
|
|||
constructor() {
|
||||
this.id = null
|
||||
this.type = null
|
||||
this.libraryId = null
|
||||
this.libraryName = null
|
||||
this.libraryMediaType = null
|
||||
this.folders = null
|
||||
this.library = null
|
||||
this.verbose = false
|
||||
|
||||
this.scanOptions = null
|
||||
|
|
@ -31,6 +28,11 @@ class LibraryScan {
|
|||
this.logs = []
|
||||
}
|
||||
|
||||
get libraryId() { return this.library.id }
|
||||
get libraryName() { return this.library.name }
|
||||
get libraryMediaType() { return this.library.mediaType }
|
||||
get folders() { return this.library.folders }
|
||||
|
||||
get _scanOptions() { return this.scanOptions || {} }
|
||||
get forceRescan() { return !!this._scanOptions.forceRescan }
|
||||
get preferAudioMetadata() { return !!this._scanOptions.preferAudioMetadata }
|
||||
|
|
@ -70,10 +72,7 @@ class LibraryScan {
|
|||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
libraryId: this.libraryId,
|
||||
libraryName: this.libraryName,
|
||||
libraryMediaType: this.libraryMediaType,
|
||||
folders: this.folders.map(f => f.toJSON()),
|
||||
library: this.library.toJSON(),
|
||||
scanOptions: this.scanOptions ? this.scanOptions.toJSON() : null,
|
||||
startedAt: this.startedAt,
|
||||
finishedAt: this.finishedAt,
|
||||
|
|
@ -87,10 +86,7 @@ class LibraryScan {
|
|||
setData(library, scanOptions, type = 'scan') {
|
||||
this.id = getId('lscan')
|
||||
this.type = type
|
||||
this.libraryId = library.id
|
||||
this.libraryName = library.name
|
||||
this.libraryMediaType = library.mediaType
|
||||
this.folders = library.folders.map(folder => new Folder(folder.toJSON()))
|
||||
this.library = new Library(library.toJSON()) // clone library
|
||||
|
||||
this.scanOptions = scanOptions
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
class ScanOptions {
|
||||
constructor(options) {
|
||||
constructor() {
|
||||
this.forceRescan = false
|
||||
|
||||
// Server settings
|
||||
|
|
@ -10,26 +10,11 @@ class ScanOptions {
|
|||
this.preferOpfMetadata = false
|
||||
this.preferMatchedMetadata = false
|
||||
this.preferOverdriveMediaMarker = false
|
||||
|
||||
if (options) {
|
||||
this.construct(options)
|
||||
}
|
||||
}
|
||||
|
||||
construct(options) {
|
||||
for (const key in options) {
|
||||
if (key === 'metadataPrecedence' && options[key].length) {
|
||||
this.metadataPrecedence = [...options[key]]
|
||||
} else if (this[key] !== undefined) {
|
||||
this[key] = options[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
forceRescan: this.forceRescan,
|
||||
metadataPrecedence: this.metadataPrecedence,
|
||||
parseSubtitles: this.parseSubtitles,
|
||||
findCovers: this.findCovers,
|
||||
storeCoverWithItem: this.storeCoverWithItem,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const Logger = require('../Logger')
|
|||
const SocketAuthority = require('../SocketAuthority')
|
||||
|
||||
// Utils
|
||||
const { groupFilesIntoLibraryItemPaths, getLibraryItemFileData, scanFolder } = require('../utils/scandir')
|
||||
const { groupFilesIntoLibraryItemPaths, getLibraryItemFileData, scanFolder, checkFilepathIsAudioFile } = require('../utils/scandir')
|
||||
const { comparePaths } = require('../utils/index')
|
||||
const { getIno, filePathToPOSIX } = require('../utils/fileUtils')
|
||||
const { ScanResult, LogLevel } = require('../utils/constants')
|
||||
|
|
@ -86,7 +86,7 @@ class Scanner {
|
|||
})
|
||||
this.taskManager.addTask(task)
|
||||
|
||||
const result = await this.scanLibraryItem(library.mediaType, folder, libraryItem)
|
||||
const result = await this.scanLibraryItem(library, folder, libraryItem)
|
||||
|
||||
task.setFinished(this.getScanResultDescription(result))
|
||||
this.taskManager.taskFinished(task)
|
||||
|
|
@ -94,7 +94,9 @@ class Scanner {
|
|||
return result
|
||||
}
|
||||
|
||||
async scanLibraryItem(libraryMediaType, folder, libraryItem) {
|
||||
async scanLibraryItem(library, folder, libraryItem) {
|
||||
const libraryMediaType = library.mediaType
|
||||
|
||||
// TODO: Support for single media item
|
||||
const libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, libraryItem.path, false)
|
||||
if (!libraryItemData) {
|
||||
|
|
@ -106,7 +108,7 @@ class Scanner {
|
|||
if (checkRes.updated) hasUpdated = true
|
||||
|
||||
// Sync other files first so that local images are used as cover art
|
||||
if (await libraryItem.syncFiles(this.db.serverSettings.scannerPreferOpfMetadata)) {
|
||||
if (await libraryItem.syncFiles(this.db.serverSettings.scannerPreferOpfMetadata, library.settings)) {
|
||||
hasUpdated = true
|
||||
}
|
||||
|
||||
|
|
@ -157,10 +159,10 @@ class Scanner {
|
|||
return
|
||||
}
|
||||
|
||||
var scanOptions = new ScanOptions()
|
||||
const scanOptions = new ScanOptions()
|
||||
scanOptions.setData(options, this.db.serverSettings)
|
||||
|
||||
var libraryScan = new LibraryScan()
|
||||
const libraryScan = new LibraryScan()
|
||||
libraryScan.setData(library, scanOptions)
|
||||
libraryScan.verbose = false
|
||||
this.librariesScanning.push(libraryScan.getScanEmitData)
|
||||
|
|
@ -169,7 +171,7 @@ class Scanner {
|
|||
|
||||
Logger.info(`[Scanner] Starting library scan ${libraryScan.id} for ${libraryScan.libraryName}`)
|
||||
|
||||
var canceled = await this.scanLibrary(libraryScan)
|
||||
const canceled = await this.scanLibrary(libraryScan)
|
||||
|
||||
if (canceled) {
|
||||
Logger.info(`[Scanner] Library scan canceled for "${libraryScan.libraryName}"`)
|
||||
|
|
@ -182,7 +184,7 @@ class Scanner {
|
|||
this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id)
|
||||
|
||||
if (canceled && !libraryScan.totalResults) {
|
||||
var emitData = libraryScan.getScanEmitData
|
||||
const emitData = libraryScan.getScanEmitData
|
||||
emitData.results = null
|
||||
SocketAuthority.emitter('scan_complete', emitData)
|
||||
return
|
||||
|
|
@ -201,7 +203,7 @@ class Scanner {
|
|||
// Scan each library
|
||||
for (let i = 0; i < libraryScan.folders.length; i++) {
|
||||
const folder = libraryScan.folders[i]
|
||||
const itemDataFoundInFolder = await scanFolder(libraryScan.libraryMediaType, folder)
|
||||
const itemDataFoundInFolder = await scanFolder(libraryScan.library, folder)
|
||||
libraryScan.addLog(LogLevel.INFO, `${itemDataFoundInFolder.length} item data found in folder "${folder.fullPath}"`)
|
||||
libraryItemDataFound = libraryItemDataFound.concat(itemDataFoundInFolder)
|
||||
}
|
||||
|
|
@ -356,7 +358,7 @@ class Scanner {
|
|||
|
||||
async scanNewLibraryItemDataChunk(newLibraryItemsData, libraryScan) {
|
||||
let newLibraryItems = await Promise.all(newLibraryItemsData.map((lid) => {
|
||||
return this.scanNewLibraryItem(lid, libraryScan.libraryMediaType, libraryScan)
|
||||
return this.scanNewLibraryItem(lid, libraryScan.library, libraryScan)
|
||||
}))
|
||||
newLibraryItems = newLibraryItems.filter(li => li) // Filter out nulls
|
||||
|
||||
|
|
@ -376,7 +378,7 @@ class Scanner {
|
|||
let hasUpdated = updated
|
||||
|
||||
// Sync other files first to use local images as cover before extracting audio file cover
|
||||
if (await libraryItem.syncFiles(libraryScan.preferOpfMetadata)) {
|
||||
if (await libraryItem.syncFiles(libraryScan.preferOpfMetadata, libraryScan.library.settings)) {
|
||||
hasUpdated = true
|
||||
}
|
||||
|
||||
|
|
@ -425,7 +427,7 @@ class Scanner {
|
|||
return hasUpdated ? libraryItem : null
|
||||
}
|
||||
|
||||
async scanNewLibraryItem(libraryItemData, libraryMediaType, libraryScan = null) {
|
||||
async scanNewLibraryItem(libraryItemData, library, libraryScan = null) {
|
||||
if (libraryScan) libraryScan.addLog(LogLevel.DEBUG, `Scanning new library item "${libraryItemData.path}"`)
|
||||
else Logger.debug(`[Scanner] Scanning new item "${libraryItemData.path}"`)
|
||||
|
||||
|
|
@ -433,14 +435,14 @@ class Scanner {
|
|||
const findCovers = libraryScan ? !!libraryScan.findCovers : !!global.ServerSettings.scannerFindCovers
|
||||
|
||||
const libraryItem = new LibraryItem()
|
||||
libraryItem.setData(libraryMediaType, libraryItemData)
|
||||
libraryItem.setData(library.mediaType, libraryItemData)
|
||||
|
||||
const mediaFiles = libraryItemData.libraryFiles.filter(lf => lf.fileType === 'audio' || lf.fileType === 'video')
|
||||
if (mediaFiles.length) {
|
||||
await MediaFileScanner.scanMediaFiles(mediaFiles, libraryItem, libraryScan)
|
||||
}
|
||||
|
||||
await libraryItem.syncFiles(preferOpfMetadata)
|
||||
await libraryItem.syncFiles(preferOpfMetadata, library.settings)
|
||||
|
||||
if (!libraryItem.hasMediaEntities) {
|
||||
Logger.warn(`[Scanner] Library item has no media files "${libraryItemData.path}"`)
|
||||
|
|
@ -457,7 +459,7 @@ class Scanner {
|
|||
}
|
||||
|
||||
// Scan for cover if enabled and has no cover
|
||||
if (libraryMediaType === 'book') {
|
||||
if (library.isBook) {
|
||||
if (libraryItem && findCovers && !libraryItem.media.coverPath && libraryItem.media.shouldSearchForCover) {
|
||||
const updatedCover = await this.searchForCover(libraryItem, libraryScan)
|
||||
libraryItem.media.updateLastCoverSearch(updatedCover)
|
||||
|
|
@ -534,7 +536,7 @@ class Scanner {
|
|||
}
|
||||
|
||||
async scanFilesChanged(fileUpdates) {
|
||||
if (!fileUpdates || !fileUpdates.length) return
|
||||
if (!fileUpdates?.length) return
|
||||
|
||||
// If already scanning files from watcher then add these updates to queue
|
||||
if (this.scanningFilesChanged) {
|
||||
|
|
@ -545,28 +547,28 @@ class Scanner {
|
|||
this.scanningFilesChanged = true
|
||||
|
||||
// files grouped by folder
|
||||
var folderGroups = this.getFileUpdatesGrouped(fileUpdates)
|
||||
const folderGroups = this.getFileUpdatesGrouped(fileUpdates)
|
||||
|
||||
for (const folderId in folderGroups) {
|
||||
var libraryId = folderGroups[folderId].libraryId
|
||||
var library = this.db.libraries.find(lib => lib.id === libraryId)
|
||||
const libraryId = folderGroups[folderId].libraryId
|
||||
const library = this.db.libraries.find(lib => lib.id === libraryId)
|
||||
if (!library) {
|
||||
Logger.error(`[Scanner] Library not found in files changed ${libraryId}`)
|
||||
continue;
|
||||
}
|
||||
var folder = library.getFolderById(folderId)
|
||||
const folder = library.getFolderById(folderId)
|
||||
if (!folder) {
|
||||
Logger.error(`[Scanner] Folder is not in library in files changed "${folderId}", Library "${library.name}"`)
|
||||
continue;
|
||||
}
|
||||
var relFilePaths = folderGroups[folderId].fileUpdates.map(fileUpdate => fileUpdate.relPath)
|
||||
var fileUpdateGroup = groupFilesIntoLibraryItemPaths(library.mediaType, relFilePaths)
|
||||
const relFilePaths = folderGroups[folderId].fileUpdates.map(fileUpdate => fileUpdate.relPath)
|
||||
const fileUpdateGroup = groupFilesIntoLibraryItemPaths(library.mediaType, relFilePaths, false)
|
||||
|
||||
if (!Object.keys(fileUpdateGroup).length) {
|
||||
Logger.info(`[Scanner] No important changes to scan for in folder "${folderId}"`)
|
||||
continue;
|
||||
}
|
||||
var folderScanResults = await this.scanFolderUpdates(library, folder, fileUpdateGroup)
|
||||
const folderScanResults = await this.scanFolderUpdates(library, folder, fileUpdateGroup)
|
||||
Logger.debug(`[Scanner] Folder scan results`, folderScanResults)
|
||||
}
|
||||
|
||||
|
|
@ -584,25 +586,25 @@ class Scanner {
|
|||
|
||||
// First pass - Remove files in parent dirs of items and remap the fileupdate group
|
||||
// Test Case: Moving audio files from library item folder to author folder should trigger a re-scan of the item
|
||||
var updateGroup = { ...fileUpdateGroup }
|
||||
const updateGroup = { ...fileUpdateGroup }
|
||||
for (const itemDir in updateGroup) {
|
||||
if (itemDir == fileUpdateGroup[itemDir]) continue; // Media in root path
|
||||
|
||||
var itemDirNestedFiles = fileUpdateGroup[itemDir].filter(b => b.includes('/'))
|
||||
const itemDirNestedFiles = fileUpdateGroup[itemDir].filter(b => b.includes('/'))
|
||||
if (!itemDirNestedFiles.length) continue;
|
||||
|
||||
var firstNest = itemDirNestedFiles[0].split('/').shift()
|
||||
var altDir = `${itemDir}/${firstNest}`
|
||||
const firstNest = itemDirNestedFiles[0].split('/').shift()
|
||||
const altDir = `${itemDir}/${firstNest}`
|
||||
|
||||
var fullPath = Path.posix.join(filePathToPOSIX(folder.fullPath), itemDir)
|
||||
var childLibraryItem = this.db.libraryItems.find(li => li.path !== fullPath && li.path.startsWith(fullPath))
|
||||
const fullPath = Path.posix.join(filePathToPOSIX(folder.fullPath), itemDir)
|
||||
const childLibraryItem = this.db.libraryItems.find(li => li.path !== fullPath && li.path.startsWith(fullPath))
|
||||
if (!childLibraryItem) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
var altFullPath = Path.posix.join(filePathToPOSIX(folder.fullPath), altDir)
|
||||
var altChildLibraryItem = this.db.libraryItems.find(li => li.path !== altFullPath && li.path.startsWith(altFullPath))
|
||||
const altFullPath = Path.posix.join(filePathToPOSIX(folder.fullPath), altDir)
|
||||
const altChildLibraryItem = this.db.libraryItems.find(li => li.path !== altFullPath && li.path.startsWith(altFullPath))
|
||||
if (altChildLibraryItem) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
delete fileUpdateGroup[itemDir]
|
||||
|
|
@ -638,14 +640,17 @@ class Scanner {
|
|||
SocketAuthority.emitter('item_updated', existingLibraryItem.toJSONExpanded())
|
||||
|
||||
itemGroupingResults[itemDir] = ScanResult.REMOVED
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Scan library item for updates
|
||||
Logger.debug(`[Scanner] Folder update for relative path "${itemDir}" is in library item "${existingLibraryItem.media.metadata.title}" - scan for updates`)
|
||||
itemGroupingResults[itemDir] = await this.scanLibraryItem(library.mediaType, folder, existingLibraryItem)
|
||||
continue;
|
||||
itemGroupingResults[itemDir] = await this.scanLibraryItem(library, folder, existingLibraryItem)
|
||||
continue
|
||||
} else if (library.settings.audiobooksOnly && !fileUpdateGroup[itemDir].some(checkFilepathIsAudioFile)) {
|
||||
Logger.debug(`[Scanner] Folder update for relative path "${itemDir}" has no audio files`)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if a library item is a subdirectory of this dir
|
||||
|
|
@ -653,12 +658,12 @@ class Scanner {
|
|||
if (childItem) {
|
||||
Logger.warn(`[Scanner] Files were modified in a parent directory of a library item "${childItem.media.metadata.title}" - ignoring`)
|
||||
itemGroupingResults[itemDir] = ScanResult.NOTHING
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
Logger.debug(`[Scanner] Folder update group must be a new item "${itemDir}" in library "${library.name}"`)
|
||||
var isSingleMediaItem = itemDir === fileUpdateGroup[itemDir]
|
||||
var newLibraryItem = await this.scanPotentialNewLibraryItem(library.mediaType, folder, fullPath, isSingleMediaItem)
|
||||
var newLibraryItem = await this.scanPotentialNewLibraryItem(library, folder, fullPath, isSingleMediaItem)
|
||||
if (newLibraryItem) {
|
||||
await this.createNewAuthorsAndSeries(newLibraryItem)
|
||||
await this.db.insertLibraryItem(newLibraryItem)
|
||||
|
|
@ -670,10 +675,10 @@ class Scanner {
|
|||
return itemGroupingResults
|
||||
}
|
||||
|
||||
async scanPotentialNewLibraryItem(libraryMediaType, folder, fullPath, isSingleMediaItem = false) {
|
||||
const libraryItemData = await getLibraryItemFileData(libraryMediaType, folder, fullPath, isSingleMediaItem)
|
||||
async scanPotentialNewLibraryItem(library, folder, fullPath, isSingleMediaItem = false) {
|
||||
const libraryItemData = await getLibraryItemFileData(library.mediaType, folder, fullPath, isSingleMediaItem)
|
||||
if (!libraryItemData) return null
|
||||
return this.scanNewLibraryItem(libraryItemData, libraryMediaType)
|
||||
return this.scanNewLibraryItem(libraryItemData, library)
|
||||
}
|
||||
|
||||
async searchForCover(libraryItem, libraryScan = null) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue