Add:Audiobooks only library settings, supplementary ebooks #1664

This commit is contained in:
advplyr 2023-06-10 12:46:57 -05:00
parent 4b4fb33d8f
commit 014fc45c15
39 changed files with 624 additions and 122 deletions

View file

@ -611,13 +611,26 @@ class LibraryItemController {
}
/**
* GET api/items/:id/ebook
* GET api/items/:id/ebook/:fileid?
* fileid is the inode value stored in LibraryFile.ino or EBookFile.ino
* fileid is only required when reading a supplementary ebook
* when no fileid is passed in the primary ebook will be returned
*
* @param {express.Request} req
* @param {express.Response} res
*/
async getEBookFile(req, res) {
const ebookFile = req.libraryItem.media.ebookFile
let ebookFile = null
if (req.params.fileid) {
ebookFile = req.libraryItem.libraryFiles.find(lf => lf.ino === req.params.fileid)
if (!ebookFile?.isEBookFile) {
Logger.error(`[LibraryItemController] Invalid ebook file id "${req.params.fileid}"`)
return res.status(400).send('Invalid ebook file id')
}
} else {
ebookFile = req.libraryItem.media.ebookFile
}
if (!ebookFile) {
Logger.error(`[LibraryItemController] No ebookFile for library item "${req.libraryItem.media.metadata.title}"`)
return res.sendStatus(404)
@ -632,6 +645,37 @@ class LibraryItemController {
res.sendFile(ebookFilePath)
}
/**
* PATCH api/items/:id/ebook/:fileid/status
* toggle the status of an ebook file.
* if an ebook file is the primary ebook, then it will be changed to supplementary
* if an ebook file is supplementary, then it will be changed to primary
*
* @param {express.Request} req
* @param {express.Response} res
*/
async updateEbookFileStatus(req, res) {
const ebookLibraryFile = req.libraryItem.libraryFiles.find(lf => lf.ino === req.params.fileid)
if (!ebookLibraryFile?.isEBookFile) {
Logger.error(`[LibraryItemController] Invalid ebook file id "${req.params.fileid}"`)
return res.status(400).send('Invalid ebook file id')
}
if (ebookLibraryFile.isSupplementary) {
Logger.info(`[LibraryItemController] Updating ebook file "${ebookLibraryFile.metadata.filename}" to primary`)
req.libraryItem.setPrimaryEbook(ebookLibraryFile)
} else {
Logger.info(`[LibraryItemController] Updating ebook file "${ebookLibraryFile.metadata.filename}" to supplementary`)
ebookLibraryFile.isSupplementary = true
req.libraryItem.setPrimaryEbook(null)
}
req.libraryItem.updatedAt = Date.now()
await this.db.updateLibraryItem(req.libraryItem)
SocketAuthority.emitter('item_updated', req.libraryItem.toJSONExpanded())
res.sendStatus(200)
}
middleware(req, res, next) {
req.libraryItem = this.db.libraryItems.find(li => li.id === req.params.id)
if (!req.libraryItem?.media) return res.sendStatus(404)

View file

@ -33,6 +33,9 @@ class Library {
get isMusic() {
return this.mediaType === 'music'
}
get isBook() {
return this.mediaType === 'book'
}
construct(library) {
this.id = library.id

View file

@ -80,6 +80,16 @@ class LibraryItem {
this.media.libraryItemId = this.id
this.libraryFiles = libraryItem.libraryFiles.map(f => new LibraryFile(f))
// Migration for v2.2.23 to set ebook library files as supplementary
if (this.isBook && this.media.ebookFile) {
for (const libraryFile of this.libraryFiles) {
if (libraryFile.isEBookFile && libraryFile.isSupplementary === null) {
libraryFile.isSupplementary = this.media.ebookFile.ino !== libraryFile.ino
}
}
}
}
toJSON() {
@ -432,21 +442,30 @@ class LibraryItem {
}
// Set metadata from files
async syncFiles(preferOpfMetadata) {
async syncFiles(preferOpfMetadata, librarySettings) {
let hasUpdated = false
if (this.mediaType === 'book') {
// Add/update ebook file (ebooks that were removed are removed in checkScanData)
if (this.media.ebookFile) {
if (this.isBook) {
// Add/update ebook files (ebooks that were removed are removed in checkScanData)
if (librarySettings.audiobooksOnly) {
hasUpdated = this.media.ebookFile
if (hasUpdated) {
// If library was set to audiobooks only then set primary ebook as supplementary
Logger.info(`[LibraryItem] Library is audiobooks only so setting ebook "${this.media.ebookFile.metadata.filename}" as supplementary`)
}
this.setPrimaryEbook(null)
} else if (this.media.ebookFile) {
const matchingLibraryFile = this.libraryFiles.find(lf => lf.ino === this.media.ebookFile.ino)
if (matchingLibraryFile && this.media.ebookFile.updateFromLibraryFile(matchingLibraryFile)) {
hasUpdated = true
}
} else {
const ebookLibraryFiles = this.libraryFiles.filter(lf => lf.isEBookFile && !lf.isSupplementary)
// Prefer epub ebook then fallback to first other ebook file
const ebookLibraryFile = this.libraryFiles.find(lf => lf.isEBookFile && lf.metadata.format === 'epub') || this.libraryFiles.find(lf => lf.isEBookFile)
const ebookLibraryFile = ebookLibraryFiles.find(lf => lf.metadata.format === 'epub') || ebookLibraryFiles[0]
if (ebookLibraryFile) {
this.media.setEbookFile(ebookLibraryFile)
this.setPrimaryEbook(ebookLibraryFile)
hasUpdated = true
}
}
@ -565,5 +584,20 @@ class LibraryItem {
}
return false
}
/**
* Set the EBookFile from a LibraryFile
* If null then ebookFile will be removed from the book
* all ebook library files that are not primary are marked as supplementary
*
* @param {LibraryFile} [libraryFile]
*/
setPrimaryEbook(ebookLibraryFile = null) {
const ebookLibraryFiles = this.libraryFiles.filter(lf => lf.isEBookFile)
for (const libraryFile of ebookLibraryFiles) {
libraryFile.isSupplementary = ebookLibraryFile?.ino !== libraryFile.ino
}
this.media.setEbookFile(ebookLibraryFile)
}
}
module.exports = LibraryItem

View file

@ -7,6 +7,7 @@ class LibraryFile {
constructor(file) {
this.ino = null
this.metadata = null
this.isSupplementary = null
this.addedAt = null
this.updatedAt = null
@ -18,6 +19,7 @@ class LibraryFile {
construct(file) {
this.ino = file.ino
this.metadata = new FileMetadata(file.metadata)
this.isSupplementary = file.isSupplementary === undefined ? null : file.isSupplementary
this.addedAt = file.addedAt
this.updatedAt = file.updatedAt
}
@ -26,6 +28,7 @@ class LibraryFile {
return {
ino: this.ino,
metadata: this.metadata.toJSON(),
isSupplementary: this.isSupplementary,
addedAt: this.addedAt,
updatedAt: this.updatedAt,
fileType: this.fileType

View file

@ -370,10 +370,20 @@ class Book {
return payload
}
setEbookFile(libraryFile) {
var ebookFile = new EBookFile()
ebookFile.setData(libraryFile)
this.ebookFile = ebookFile
/**
* Set the EBookFile from a LibraryFile
* If null then ebookFile will be removed from the book
*
* @param {LibraryFile} [libraryFile]
*/
setEbookFile(libraryFile = null) {
if (!libraryFile) {
this.ebookFile = null
} else {
const ebookFile = new EBookFile()
ebookFile.setData(libraryFile)
this.ebookFile = ebookFile
}
}
addAudioFile(audioFile) {

View file

@ -7,6 +7,7 @@ class LibrarySettings {
this.skipMatchingMediaWithAsin = false
this.skipMatchingMediaWithIsbn = false
this.autoScanCronExpression = null
this.audiobooksOnly = false
if (settings) {
this.construct(settings)
@ -19,6 +20,7 @@ class LibrarySettings {
this.skipMatchingMediaWithAsin = !!settings.skipMatchingMediaWithAsin
this.skipMatchingMediaWithIsbn = !!settings.skipMatchingMediaWithIsbn
this.autoScanCronExpression = settings.autoScanCronExpression || null
this.audiobooksOnly = !!settings.audiobooksOnly
}
toJSON() {
@ -27,12 +29,13 @@ class LibrarySettings {
disableWatcher: this.disableWatcher,
skipMatchingMediaWithAsin: this.skipMatchingMediaWithAsin,
skipMatchingMediaWithIsbn: this.skipMatchingMediaWithIsbn,
autoScanCronExpression: this.autoScanCronExpression
autoScanCronExpression: this.autoScanCronExpression,
audiobooksOnly: this.audiobooksOnly
}
}
update(payload) {
var hasUpdates = false
let hasUpdates = false
for (const key in payload) {
if (this[key] !== payload[key]) {
this[key] = payload[key]

View file

@ -125,7 +125,8 @@ class ApiRouter {
this.router.get('/items/:id/file/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.getLibraryFile.bind(this))
this.router.delete('/items/:id/file/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.deleteLibraryFile.bind(this))
this.router.get('/items/:id/file/:fileid/download', LibraryItemController.middleware.bind(this), LibraryItemController.downloadLibraryFile.bind(this))
this.router.get('/items/:id/ebook', LibraryItemController.middleware.bind(this), LibraryItemController.getEBookFile.bind(this))
this.router.get('/items/:id/ebook/:fileid?', LibraryItemController.middleware.bind(this), LibraryItemController.getEBookFile.bind(this))
this.router.patch('/items/:id/ebook/:fileid/status', LibraryItemController.middleware.bind(this), LibraryItemController.updateEbookFileStatus.bind(this))
//
// User Routes

View file

@ -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

View file

@ -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,

View file

@ -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) {

View file

@ -5,14 +5,23 @@ const { recurseFiles, getFileTimestampsWithIno, filePathToPOSIX } = require('./f
const globals = require('./globals')
const LibraryFile = require('../objects/files/LibraryFile')
function isMediaFile(mediaType, ext) {
function isMediaFile(mediaType, ext, audiobooksOnly = false) {
if (!ext) return false
var extclean = ext.slice(1).toLowerCase()
const extclean = ext.slice(1).toLowerCase()
if (mediaType === 'podcast' || mediaType === 'music') return globals.SupportedAudioTypes.includes(extclean)
else if (mediaType === 'video') return globals.SupportedVideoTypes.includes(extclean)
else if (audiobooksOnly) return globals.SupportedAudioTypes.includes(extclean)
return globals.SupportedAudioTypes.includes(extclean) || globals.SupportedEbookTypes.includes(extclean)
}
function checkFilepathIsAudioFile(filepath) {
const ext = Path.extname(filepath)
if (!ext) return false
const extclean = ext.slice(1).toLowerCase()
return globals.SupportedAudioTypes.includes(extclean)
}
module.exports.checkFilepathIsAudioFile = checkFilepathIsAudioFile
// TODO: Function needs to be re-done
// Input: array of relative file paths
// Output: map of files grouped into potential item dirs
@ -25,12 +34,12 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
let parsedPath = Path.parse(path)
// Is not in root dir OR is a book media file
if (parsedPath.dir) {
if (!isMediaFile(mediaType, parsedPath.ext)) { // Seperate out non-media files
if (!isMediaFile(mediaType, parsedPath.ext, false)) { // Seperate out non-media files
nonMediaFilePaths.push(path)
return false
}
return true
} else if (mediaType === 'book' && isMediaFile(mediaType, parsedPath.ext)) { // (book media type supports single file audiobooks/ebooks in root dir)
} else if (mediaType === 'book' && isMediaFile(mediaType, parsedPath.ext, false)) { // (book media type supports single file audiobooks/ebooks in root dir)
return true
}
return false
@ -90,11 +99,11 @@ module.exports.groupFilesIntoLibraryItemPaths = groupFilesIntoLibraryItemPaths
// Input: array of relative file items (see recurseFiles)
// Output: map of files grouped into potential libarary item dirs
function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems) {
function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly = false) {
// Handle music where every audio file is a library item
if (mediaType === 'music') {
const audioFileGroup = {}
fileItems.filter(i => isMediaFile(mediaType, i.extension)).forEach((item) => {
fileItems.filter(i => isMediaFile(mediaType, i.extension, audiobooksOnly)).forEach((item) => {
audioFileGroup[item.path] = item.path
})
return audioFileGroup
@ -102,7 +111,7 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems) {
// Step 1: Filter out non-book-media files in root dir (with depth of 0)
const itemsFiltered = fileItems.filter(i => {
return i.deep > 0 || ((mediaType === 'book' || mediaType === 'video' || mediaType === 'music') && isMediaFile(mediaType, i.extension))
return i.deep > 0 || ((mediaType === 'book' || mediaType === 'video' || mediaType === 'music') && isMediaFile(mediaType, i.extension, audiobooksOnly))
})
// Step 2: Seperate media files and other files
@ -110,7 +119,7 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems) {
const mediaFileItems = []
const otherFileItems = []
itemsFiltered.forEach(item => {
if (isMediaFile(mediaType, item.extension)) mediaFileItems.push(item)
if (isMediaFile(mediaType, item.extension, audiobooksOnly)) mediaFileItems.push(item)
else otherFileItems.push(item)
})
@ -175,7 +184,7 @@ function cleanFileObjects(libraryItemPath, files) {
}
// Scan folder
async function scanFolder(libraryMediaType, folder) {
async function scanFolder(library, folder) {
const folderPath = filePathToPOSIX(folder.fullPath)
const pathExists = await fs.pathExists(folderPath)
@ -185,7 +194,7 @@ async function scanFolder(libraryMediaType, folder) {
}
const fileItems = await recurseFiles(folderPath)
const libraryItemGrouping = groupFileItemsIntoLibraryItemDirs(libraryMediaType, fileItems)
const libraryItemGrouping = groupFileItemsIntoLibraryItemDirs(library.mediaType, fileItems, library.settings.audiobooksOnly)
if (!Object.keys(libraryItemGrouping).length) {
Logger.error(`Root path has no media folders: ${folderPath}`)
@ -197,7 +206,7 @@ async function scanFolder(libraryMediaType, folder) {
let isFile = false // item is not in a folder
let libraryItemData = null
let fileObjs = []
if (libraryMediaType === 'music') {
if (library.mediaType === 'music') {
libraryItemData = {
path: Path.posix.join(folderPath, libraryItemPath),
relPath: libraryItemPath
@ -216,7 +225,7 @@ async function scanFolder(libraryMediaType, folder) {
fileObjs = await cleanFileObjects(folderPath, [libraryItemPath])
isFile = true
} else {
libraryItemData = getDataFromMediaDir(libraryMediaType, folderPath, libraryItemPath)
libraryItemData = getDataFromMediaDir(library.mediaType, folderPath, libraryItemPath)
fileObjs = await cleanFileObjects(libraryItemData.path, libraryItemGrouping[libraryItemPath])
}