mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-05-13 06:51:29 +00:00
update
This commit is contained in:
parent
f4ce4a4bde
commit
e5261d137f
3 changed files with 111 additions and 39 deletions
|
|
@ -321,8 +321,12 @@ class LibraryScanner {
|
|||
let isFile = false // item is not in a folder
|
||||
let libraryItemData = null
|
||||
let fileObjs = []
|
||||
if (libraryItemPath === libraryItemGrouping[libraryItemPath]) {
|
||||
// Media file in root only get title
|
||||
const groupedFiles = libraryItemGrouping[libraryItemPath]
|
||||
const isSingleFileGroup =
|
||||
libraryItemPath === groupedFiles || (Array.isArray(groupedFiles) && groupedFiles.includes(libraryItemPath))
|
||||
|
||||
if (isSingleFileGroup) {
|
||||
// Media file item may exist in the library root or inside a poorly-structured parent folder.
|
||||
libraryItemData = {
|
||||
mediaMetadata: {
|
||||
title: Path.basename(libraryItemPath, Path.extname(libraryItemPath))
|
||||
|
|
@ -330,11 +334,11 @@ class LibraryScanner {
|
|||
path: Path.posix.join(folderPath, libraryItemPath),
|
||||
relPath: libraryItemPath
|
||||
}
|
||||
fileObjs = await scanUtils.buildLibraryFile(folderPath, [libraryItemPath])
|
||||
fileObjs = await scanUtils.buildLibraryFile(folderPath, Array.isArray(groupedFiles) ? groupedFiles : [libraryItemPath])
|
||||
isFile = true
|
||||
} else {
|
||||
libraryItemData = scanUtils.getDataFromMediaDir(library.mediaType, folderPath, libraryItemPath)
|
||||
fileObjs = await scanUtils.buildLibraryFile(libraryItemData.path, libraryItemGrouping[libraryItemPath])
|
||||
fileObjs = await scanUtils.buildLibraryFile(libraryItemData.path, groupedFiles)
|
||||
}
|
||||
|
||||
const libraryItemFolderStats = await fileUtils.getFileTimestampsWithIno(libraryItemData.path)
|
||||
|
|
@ -651,11 +655,11 @@ function ItemToItemInoMatch(libraryItem1, libraryItem2) {
|
|||
}
|
||||
|
||||
function hasAudioFiles(fileUpdateGroup, itemDir) {
|
||||
return isSingleMediaFile(fileUpdateGroup, itemDir) ? scanUtils.checkFilepathIsAudioFile(fileUpdateGroup[itemDir]) : fileUpdateGroup[itemDir].some(scanUtils.checkFilepathIsAudioFile)
|
||||
return isSingleMediaFile(fileUpdateGroup, itemDir) ? scanUtils.checkFilepathIsAudioFile(itemDir) : fileUpdateGroup[itemDir].some(scanUtils.checkFilepathIsAudioFile)
|
||||
}
|
||||
|
||||
function isSingleMediaFile(fileUpdateGroup, itemDir) {
|
||||
return itemDir === fileUpdateGroup[itemDir]
|
||||
return itemDir === fileUpdateGroup[itemDir] || (Array.isArray(fileUpdateGroup[itemDir]) && fileUpdateGroup[itemDir].includes(itemDir))
|
||||
}
|
||||
|
||||
async function findLibraryItemByItemToItemInoMatch(libraryId, fullPath) {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ function isScannableNonMediaFile(ext) {
|
|||
return globals.TextFileTypes.includes(extclean) || globals.MetadataFileTypes.includes(extclean) || globals.SupportedImageTypes.includes(extclean)
|
||||
}
|
||||
|
||||
function isDiscDirectoryName(name) {
|
||||
return /^(cd|dis[ck])\s*\d{1,3}$/i.test(name || '')
|
||||
}
|
||||
|
||||
function checkFilepathIsAudioFile(filepath) {
|
||||
const ext = Path.extname(filepath)
|
||||
if (!ext) return false
|
||||
|
|
@ -67,52 +71,76 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly,
|
|||
|
||||
// Step 3: Group media files (or non-media files if includeNonMediaFiles is true) in library items
|
||||
const libraryItemGroup = {}
|
||||
const directMediaFileCountByDir = {}
|
||||
const hasNestedNonDiscMediaByDir = {}
|
||||
|
||||
mediaFileItems.forEach((item) => {
|
||||
const dirPath = item.reldirpath || ''
|
||||
directMediaFileCountByDir[dirPath] = (directMediaFileCountByDir[dirPath] || 0) + 1
|
||||
|
||||
const dirParts = dirPath.split('/').filter(Boolean)
|
||||
for (let i = 0; i < dirParts.length - 1; i++) {
|
||||
const ancestorPath = dirParts.slice(0, i + 1).join('/')
|
||||
const nextSegment = dirParts[i + 1]
|
||||
if (!isDiscDirectoryName(nextSegment)) {
|
||||
hasNestedNonDiscMediaByDir[ancestorPath] = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mediaFileItems.forEach((item) => {
|
||||
const dirparts = item.reldirpath.split('/').filter((p) => !!p)
|
||||
const numparts = dirparts.length
|
||||
let _path = ''
|
||||
|
||||
if (!dirparts.length) {
|
||||
// Media file in root
|
||||
libraryItemGroup[item.name] = item.name
|
||||
libraryItemGroup[item.path] = item.path
|
||||
} else {
|
||||
// Iterate over directories in path
|
||||
for (let i = 0; i < numparts; i++) {
|
||||
const dirpart = dirparts.shift()
|
||||
_path = Path.posix.join(_path, dirpart)
|
||||
const dirPath = dirparts.join('/')
|
||||
const lastDir = dirparts[dirparts.length - 1]
|
||||
const shouldUseFileAsLibraryItem = directMediaFileCountByDir[dirPath] === 1 && hasNestedNonDiscMediaByDir[dirPath]
|
||||
|
||||
if (libraryItemGroup[_path]) {
|
||||
// Directory already has files, add file
|
||||
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
||||
libraryItemGroup[_path].push(relpath)
|
||||
return
|
||||
} else if (!dirparts.length) {
|
||||
// This is the last directory, create group
|
||||
libraryItemGroup[_path] = [item.name]
|
||||
return
|
||||
} else if (dirparts.length === 1 && /^(cd|dis[ck])\s*\d{1,3}$/i.test(dirparts[0])) {
|
||||
// Next directory is the last and is a CD dir, create group
|
||||
libraryItemGroup[_path] = [Path.posix.join(dirparts[0], item.name)]
|
||||
return
|
||||
}
|
||||
if (shouldUseFileAsLibraryItem) {
|
||||
libraryItemGroup[item.path] = [item.path]
|
||||
return
|
||||
}
|
||||
|
||||
const groupPath = isDiscDirectoryName(lastDir) && dirparts.length > 1 ? dirparts.slice(0, -1).join('/') : dirPath
|
||||
const relpath = Path.posix.relative(groupPath, item.path)
|
||||
if (!libraryItemGroup[groupPath]) {
|
||||
libraryItemGroup[groupPath] = []
|
||||
}
|
||||
libraryItemGroup[groupPath].push(relpath)
|
||||
}
|
||||
})
|
||||
|
||||
// Step 4: Add other files into library item groups
|
||||
otherFileItems.forEach((item) => {
|
||||
const dirparts = item.reldirpath.split('/')
|
||||
const numparts = dirparts.length
|
||||
let _path = ''
|
||||
const dirPath = item.reldirpath || ''
|
||||
const itemPath = item.path
|
||||
const sameDirFileGroups = Object.keys(libraryItemGroup).filter((groupPath) => {
|
||||
if (!groupPath || !Path.posix.extname(groupPath)) return false
|
||||
return Path.posix.dirname(groupPath) === dirPath
|
||||
})
|
||||
|
||||
// Iterate over directories in path
|
||||
for (let i = 0; i < numparts; i++) {
|
||||
const dirpart = dirparts.shift()
|
||||
_path = Path.posix.join(_path, dirpart)
|
||||
if (libraryItemGroup[_path]) {
|
||||
// Directory is audiobook group
|
||||
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
||||
libraryItemGroup[_path].push(relpath)
|
||||
if (sameDirFileGroups.length) {
|
||||
const itemStem = Path.basename(item.name, item.extension)
|
||||
const matchingFileGroup =
|
||||
sameDirFileGroups.find((groupPath) => Path.basename(groupPath, Path.extname(groupPath)) === itemStem) ||
|
||||
(sameDirFileGroups.length === 1 ? sameDirFileGroups[0] : null)
|
||||
|
||||
if (matchingFileGroup) {
|
||||
if (Array.isArray(libraryItemGroup[matchingFileGroup])) {
|
||||
libraryItemGroup[matchingFileGroup].push(itemPath)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const dirparts = dirPath.split('/').filter(Boolean)
|
||||
for (let i = dirparts.length; i >= 1; i--) {
|
||||
const groupPath = dirparts.slice(0, i).join('/')
|
||||
if (Array.isArray(libraryItemGroup[groupPath])) {
|
||||
const relpath = Path.posix.relative(groupPath, itemPath)
|
||||
libraryItemGroup[groupPath].push(relpath)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ describe('scanUtils', async () => {
|
|||
const dirname = Path.dirname(filePath)
|
||||
fileItems.push({
|
||||
name: Path.basename(filePath),
|
||||
path: filePath,
|
||||
reldirpath: dirname === '.' ? '' : dirname,
|
||||
extension: Path.extname(filePath),
|
||||
deep: filePath.split('/').length - 1
|
||||
|
|
@ -49,4 +50,43 @@ describe('scanUtils', async () => {
|
|||
'Author/Series2/Book5/deeply/nested': ['cd 01/audiofile.mp3', 'cd 02/audiofile.mp3']
|
||||
})
|
||||
})
|
||||
|
||||
it('should keep nested book folders separate when a parent folder also contains a direct media file', async () => {
|
||||
const filePaths = [
|
||||
'Series Alpha/Standalone Side Story.m4b',
|
||||
'Series Alpha/Standalone Side Story.nfo',
|
||||
'Series Alpha/Author Example - Book One Main Saga, Book 1/Book One Main Saga, Book 1.m4b',
|
||||
'Series Alpha/Author Example - Book One Main Saga, Book 1/Book One Main Saga, Book 1.cue',
|
||||
'Series Alpha/Author Example - Book Two Main Saga, Book 2/Book Two Main Saga, Book 2.m4b',
|
||||
'Series Alpha/Author Example - Book Two Main Saga, Book 2/Book Two Main Saga, Book 2.nfo'
|
||||
]
|
||||
|
||||
const fileItems = filePaths.map((filePath) => {
|
||||
const dirname = Path.dirname(filePath)
|
||||
return {
|
||||
name: Path.basename(filePath),
|
||||
path: filePath,
|
||||
reldirpath: dirname === '.' ? '' : dirname,
|
||||
extension: Path.extname(filePath),
|
||||
deep: filePath.split('/').length - 1
|
||||
}
|
||||
})
|
||||
|
||||
const libraryItemGrouping = scanUtils.groupFileItemsIntoLibraryItemDirs('book', fileItems, false)
|
||||
|
||||
expect(libraryItemGrouping).to.deep.equal({
|
||||
'Series Alpha/Standalone Side Story.m4b': [
|
||||
'Series Alpha/Standalone Side Story.m4b',
|
||||
'Series Alpha/Standalone Side Story.nfo'
|
||||
],
|
||||
'Series Alpha/Author Example - Book One Main Saga, Book 1': [
|
||||
'Book One Main Saga, Book 1.m4b',
|
||||
'Book One Main Saga, Book 1.cue'
|
||||
],
|
||||
'Series Alpha/Author Example - Book Two Main Saga, Book 2': [
|
||||
'Book Two Main Saga, Book 2.m4b',
|
||||
'Book Two Main Saga, Book 2.nfo'
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue