mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-31 02:17:01 -04:00 
			
		
		
		
	Add:Support for openaudible folder structure (subject to change), add support for treating single audio files in the root directory as library items #401
This commit is contained in:
		
							parent
							
								
									49bef2c641
								
							
						
					
					
						commit
						33dfb764fa
					
				| @ -150,6 +150,10 @@ export default { | |||||||
|     _libraryItem() { |     _libraryItem() { | ||||||
|       return this.libraryItem || {} |       return this.libraryItem || {} | ||||||
|     }, |     }, | ||||||
|  |     isFile() { | ||||||
|  |       // Library item is not in a folder | ||||||
|  |       return this._libraryItem.isFile | ||||||
|  |     }, | ||||||
|     media() { |     media() { | ||||||
|       return this._libraryItem.media || {} |       return this._libraryItem.media || {} | ||||||
|     }, |     }, | ||||||
| @ -365,7 +369,7 @@ export default { | |||||||
|           text: 'Match' |           text: 'Match' | ||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
|       if (this.userIsRoot) { |       if (this.userIsRoot && !this.isFile) { | ||||||
|         items.push({ |         items.push({ | ||||||
|           func: 'rescan', |           func: 'rescan', | ||||||
|           text: 'Re-Scan' |           text: 'Re-Scan' | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
|         </ui-tooltip> |         </ui-tooltip> | ||||||
| 
 | 
 | ||||||
|         <ui-tooltip :disabled="!!libraryScan" text="(Root User Only) Rescan audiobook including metadata" direction="bottom" class="mr-4"> |         <ui-tooltip :disabled="!!libraryScan" text="(Root User Only) Rescan audiobook including metadata" direction="bottom" class="mr-4"> | ||||||
|           <ui-btn v-if="isRootUser" :loading="rescanning" :disabled="!!libraryScan" color="bg" type="button" class="h-full" small @click.stop.prevent="rescan">Re-Scan</ui-btn> |           <ui-btn v-if="isRootUser && !isFile" :loading="rescanning" :disabled="!!libraryScan" color="bg" type="button" class="h-full" small @click.stop.prevent="rescan">Re-Scan</ui-btn> | ||||||
|         </ui-tooltip> |         </ui-tooltip> | ||||||
| 
 | 
 | ||||||
|         <ui-btn @click="submitForm">Submit</ui-btn> |         <ui-btn @click="submitForm">Submit</ui-btn> | ||||||
| @ -49,6 +49,9 @@ export default { | |||||||
|         this.$emit('update:processing', val) |         this.$emit('update:processing', val) | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     isFile() { | ||||||
|  |       return !!this.libraryItem && this.libraryItem.isFile | ||||||
|  |     }, | ||||||
|     isRootUser() { |     isRootUser() { | ||||||
|       return this.$store.getters['user/getIsRoot'] |       return this.$store.getters['user/getIsRoot'] | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -1,9 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6"> |   <div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6"> | ||||||
|     <template v-for="audiobook in audiobooks"> |  | ||||||
|       <tables-tracks-table :key="audiobook.id" :title="`Audiobook Tracks (${audiobook.name})`" :audiobook-id="audiobook.id" :tracks="audiobook.tracks" class="mb-4" /> |  | ||||||
|     </template> |  | ||||||
| 
 |  | ||||||
|     <tables-library-files-table expanded :files="libraryFiles" :library-item-id="libraryItem.id" :is-missing="isMissing" /> |     <tables-library-files-table expanded :files="libraryFiles" :library-item-id="libraryItem.id" :is-missing="isMissing" /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| @ -51,12 +47,6 @@ export default { | |||||||
|     }, |     }, | ||||||
|     showDownload() { |     showDownload() { | ||||||
|       return this.userCanDownload && !this.isMissing |       return this.userCanDownload && !this.isMissing | ||||||
|     }, |  | ||||||
|     audiobooks() { |  | ||||||
|       return this.media.audiobooks || [] |  | ||||||
|     }, |  | ||||||
|     ebooks() { |  | ||||||
|       return this.media.ebooks || [] |  | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ | |||||||
|       <!-- <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span> --> |       <!-- <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span> --> | ||||||
|       <div class="flex-grow" /> |       <div class="flex-grow" /> | ||||||
|       <ui-btn small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">Full Path</ui-btn> |       <ui-btn small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">Full Path</ui-btn> | ||||||
|       <nuxt-link v-if="userCanUpdate" :to="`/audiobook/${libraryItemId}/edit`" class="mr-2 md:mr-4" @mousedown.prevent> |       <nuxt-link v-if="userCanUpdate && !isFile" :to="`/audiobook/${libraryItemId}/edit`" class="mr-2 md:mr-4" @mousedown.prevent> | ||||||
|         <ui-btn small color="primary">Manage Tracks</ui-btn> |         <ui-btn small color="primary">Manage Tracks</ui-btn> | ||||||
|       </nuxt-link> |       </nuxt-link> | ||||||
|       <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''"> |       <div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''"> | ||||||
| @ -59,7 +59,8 @@ export default { | |||||||
|       type: Array, |       type: Array, | ||||||
|       default: () => [] |       default: () => [] | ||||||
|     }, |     }, | ||||||
|     libraryItemId: String |     libraryItemId: String, | ||||||
|  |     isFile: Boolean | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ | |||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <tables-tracks-table :title="`Audiobook Tracks`" :tracks="media.tracks" :library-item-id="libraryItemId" class="mt-6" /> |     <tables-tracks-table :title="`Audiobook Tracks`" :tracks="media.tracks" :is-file="isFile" :library-item-id="libraryItemId" class="mt-6" /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| @ -27,7 +27,8 @@ export default { | |||||||
|     media: { |     media: { | ||||||
|       type: Object, |       type: Object, | ||||||
|       default: () => {} |       default: () => {} | ||||||
|     } |     }, | ||||||
|  |     isFile: Boolean | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return {} |     return {} | ||||||
|  | |||||||
| @ -107,6 +107,10 @@ export default { | |||||||
|       console.error('Invalid media type') |       console.error('Invalid media type') | ||||||
|       return redirect('/') |       return redirect('/') | ||||||
|     } |     } | ||||||
|  |     if (libraryItem.isFile) { | ||||||
|  |       console.error('No need to edit library item that is 1 file...') | ||||||
|  |       return redirect('/') | ||||||
|  |     } | ||||||
|     return { |     return { | ||||||
|       libraryItem, |       libraryItem, | ||||||
|       files: libraryItem.media.audioFiles ? libraryItem.media.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : [] |       files: libraryItem.media.audioFiles ? libraryItem.media.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : [] | ||||||
|  | |||||||
| @ -165,7 +165,7 @@ | |||||||
|             <p v-for="audioFile in invalidAudioFiles" :key="audioFile.id" class="text-xs pl-2">- {{ audioFile.metadata.filename }} ({{ audioFile.error }})</p> |             <p v-for="audioFile in invalidAudioFiles" :key="audioFile.id" class="text-xs pl-2">- {{ audioFile.metadata.filename }} ({{ audioFile.error }})</p> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|           <widgets-audiobook-data v-if="tracks.length" :library-item-id="libraryItemId" :media="media" /> |           <widgets-audiobook-data v-if="tracks.length" :library-item-id="libraryItemId" :is-file="isFile" :media="media" /> | ||||||
| 
 | 
 | ||||||
|           <tables-podcast-episodes-table v-if="isPodcast" :library-item="libraryItem" /> |           <tables-podcast-episodes-table v-if="isPodcast" :library-item="libraryItem" /> | ||||||
| 
 | 
 | ||||||
| @ -210,6 +210,9 @@ export default { | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|  |     isFile() { | ||||||
|  |       return this.libraryItem.isFile | ||||||
|  |     }, | ||||||
|     coverAspectRatio() { |     coverAspectRatio() { | ||||||
|       return this.$store.getters['getServerSetting']('coverAspectRatio') |       return this.$store.getters['getServerSetting']('coverAspectRatio') | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -411,7 +411,9 @@ class Db { | |||||||
| 
 | 
 | ||||||
|   removeEntity(entityName, entityId) { |   removeEntity(entityName, entityId) { | ||||||
|     var entityDb = this.getEntityDb(entityName) |     var entityDb = this.getEntityDb(entityName) | ||||||
|     return entityDb.delete((record) => record.id === entityId).then((results) => { |     return entityDb.delete((record) => { | ||||||
|  |       return record.id === entityId | ||||||
|  |     }).then((results) => { | ||||||
|       Logger.debug(`[DB] Deleted entity ${entityName}: ${results.deleted}`) |       Logger.debug(`[DB] Deleted entity ${entityName}: ${results.deleted}`) | ||||||
|       var arrayKey = this.getEntityArrayKey(entityName) |       var arrayKey = this.getEntityArrayKey(entityName) | ||||||
|       if (this[arrayKey]) { |       if (this[arrayKey]) { | ||||||
|  | |||||||
| @ -342,6 +342,12 @@ class LibraryItemController { | |||||||
|       Logger.error(`[LibraryItemController] Non-root user attempted to scan library item`, req.user) |       Logger.error(`[LibraryItemController] Non-root user attempted to scan library item`, req.user) | ||||||
|       return res.sendStatus(403) |       return res.sendStatus(403) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     if (req.libraryItem.isFile) { | ||||||
|  |       Logger.error(`[LibraryItemController] Re-scanning file library items not yet supported`) | ||||||
|  |       return res.sendStatus(500) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     var result = await this.scanner.scanLibraryItemById(req.libraryItem.id) |     var result = await this.scanner.scanLibraryItemById(req.libraryItem.id) | ||||||
|     res.json({ |     res.json({ | ||||||
|       result: Object.keys(ScanResult).find(key => ScanResult[key] == result) |       result: Object.keys(ScanResult).find(key => ScanResult[key] == result) | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ class CoverManager { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getCoverDirectory(libraryItem) { |   getCoverDirectory(libraryItem) { | ||||||
|     if (this.db.serverSettings.storeCoverWithItem) { |     if (this.db.serverSettings.storeCoverWithItem && !libraryItem.isFile) { | ||||||
|       return libraryItem.path |       return libraryItem.path | ||||||
|     } else { |     } else { | ||||||
|       return Path.posix.join(this.ItemMetadataPath, libraryItem.id) |       return Path.posix.join(this.ItemMetadataPath, libraryItem.id) | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ class LibraryItem { | |||||||
| 
 | 
 | ||||||
|     this.path = null |     this.path = null | ||||||
|     this.relPath = null |     this.relPath = null | ||||||
|  |     this.isFile = false | ||||||
|     this.mtimeMs = null |     this.mtimeMs = null | ||||||
|     this.ctimeMs = null |     this.ctimeMs = null | ||||||
|     this.birthtimeMs = null |     this.birthtimeMs = null | ||||||
| @ -51,6 +52,7 @@ class LibraryItem { | |||||||
|     this.folderId = libraryItem.folderId |     this.folderId = libraryItem.folderId | ||||||
|     this.path = libraryItem.path |     this.path = libraryItem.path | ||||||
|     this.relPath = libraryItem.relPath |     this.relPath = libraryItem.relPath | ||||||
|  |     this.isFile = !!libraryItem.isFile | ||||||
|     this.mtimeMs = libraryItem.mtimeMs || 0 |     this.mtimeMs = libraryItem.mtimeMs || 0 | ||||||
|     this.ctimeMs = libraryItem.ctimeMs || 0 |     this.ctimeMs = libraryItem.ctimeMs || 0 | ||||||
|     this.birthtimeMs = libraryItem.birthtimeMs || 0 |     this.birthtimeMs = libraryItem.birthtimeMs || 0 | ||||||
| @ -82,6 +84,7 @@ class LibraryItem { | |||||||
|       folderId: this.folderId, |       folderId: this.folderId, | ||||||
|       path: this.path, |       path: this.path, | ||||||
|       relPath: this.relPath, |       relPath: this.relPath, | ||||||
|  |       isFile: this.isFile, | ||||||
|       mtimeMs: this.mtimeMs, |       mtimeMs: this.mtimeMs, | ||||||
|       ctimeMs: this.ctimeMs, |       ctimeMs: this.ctimeMs, | ||||||
|       birthtimeMs: this.birthtimeMs, |       birthtimeMs: this.birthtimeMs, | ||||||
| @ -105,6 +108,7 @@ class LibraryItem { | |||||||
|       folderId: this.folderId, |       folderId: this.folderId, | ||||||
|       path: this.path, |       path: this.path, | ||||||
|       relPath: this.relPath, |       relPath: this.relPath, | ||||||
|  |       isFile: this.isFile, | ||||||
|       mtimeMs: this.mtimeMs, |       mtimeMs: this.mtimeMs, | ||||||
|       ctimeMs: this.ctimeMs, |       ctimeMs: this.ctimeMs, | ||||||
|       birthtimeMs: this.birthtimeMs, |       birthtimeMs: this.birthtimeMs, | ||||||
| @ -128,6 +132,7 @@ class LibraryItem { | |||||||
|       folderId: this.folderId, |       folderId: this.folderId, | ||||||
|       path: this.path, |       path: this.path, | ||||||
|       relPath: this.relPath, |       relPath: this.relPath, | ||||||
|  |       isFile: this.isFile, | ||||||
|       mtimeMs: this.mtimeMs, |       mtimeMs: this.mtimeMs, | ||||||
|       ctimeMs: this.ctimeMs, |       ctimeMs: this.ctimeMs, | ||||||
|       birthtimeMs: this.birthtimeMs, |       birthtimeMs: this.birthtimeMs, | ||||||
| @ -460,7 +465,7 @@ class LibraryItem { | |||||||
|     this.isSavingMetadata = true |     this.isSavingMetadata = true | ||||||
| 
 | 
 | ||||||
|     var metadataPath = Path.join(global.MetadataPath, 'items', this.id) |     var metadataPath = Path.join(global.MetadataPath, 'items', this.id) | ||||||
|     if (global.ServerSettings.storeMetadataWithItem) { |     if (global.ServerSettings.storeMetadataWithItem && !this.isFile) { | ||||||
|       metadataPath = this.path |       metadataPath = this.path | ||||||
|     } else { |     } else { | ||||||
|       // Make sure metadata book dir exists
 |       // Make sure metadata book dir exists
 | ||||||
|  | |||||||
| @ -235,7 +235,7 @@ class Scanner { | |||||||
| 
 | 
 | ||||||
|       var hasMediaFile = dataFound.libraryFiles.some(lf => lf.isMediaFile) |       var hasMediaFile = dataFound.libraryFiles.some(lf => lf.isMediaFile) | ||||||
|       if (!hasMediaFile) { |       if (!hasMediaFile) { | ||||||
|         libraryScan.addLog(LogLevel.WARN, `Directory found "${libraryItemDataFound.path}" has no media files`) |         libraryScan.addLog(LogLevel.WARN, `Item found "${libraryItemDataFound.path}" has no media files`) | ||||||
|       } else { |       } else { | ||||||
|         var audioFileSize = 0 |         var audioFileSize = 0 | ||||||
|         dataFound.libraryFiles.filter(lf => lf.fileType == 'audio').forEach(lf => audioFileSize += lf.metadata.size) |         dataFound.libraryFiles.filter(lf => lf.fileType == 'audio').forEach(lf => audioFileSize += lf.metadata.size) | ||||||
|  | |||||||
| @ -115,6 +115,7 @@ async function recurseFiles(path, relPathToReplace = null) { | |||||||
| 
 | 
 | ||||||
|     var relpath = item.fullname.replace(relPathToReplace, '') |     var relpath = item.fullname.replace(relPathToReplace, '') | ||||||
|     var reldirname = Path.dirname(relpath) |     var reldirname = Path.dirname(relpath) | ||||||
|  |     if (reldirname === '.') reldirname = '' | ||||||
|     var dirname = Path.dirname(item.fullname) |     var dirname = Path.dirname(item.fullname) | ||||||
| 
 | 
 | ||||||
|     // Directory has a file named ".ignore" flag directory and ignore
 |     // Directory has a file named ".ignore" flag directory and ignore
 | ||||||
| @ -139,15 +140,18 @@ async function recurseFiles(path, relPathToReplace = null) { | |||||||
|       return false |       return false | ||||||
|     } |     } | ||||||
|     return true |     return true | ||||||
|   }).map((item) => ({ |   }).map((item) => { | ||||||
|  |     var isInRoot = (item.path + '/' === relPathToReplace) | ||||||
|  |     return { | ||||||
|       name: item.name, |       name: item.name, | ||||||
|       path: item.fullname.replace(relPathToReplace, ''), |       path: item.fullname.replace(relPathToReplace, ''), | ||||||
|       dirpath: item.path, |       dirpath: item.path, | ||||||
|     reldirpath: item.path.replace(relPathToReplace, ''), |       reldirpath: isInRoot ? '' : item.path.replace(relPathToReplace, ''), | ||||||
|       fullpath: item.fullname, |       fullpath: item.fullname, | ||||||
|       extension: item.extension, |       extension: item.extension, | ||||||
|       deep: item.deep |       deep: item.deep | ||||||
|   })) |     } | ||||||
|  |   }) | ||||||
| 
 | 
 | ||||||
|   // Sort from least deep to most
 |   // Sort from least deep to most
 | ||||||
|   list.sort((a, b) => a.deep - b.deep) |   list.sort((a, b) => a.deep - b.deep) | ||||||
|  | |||||||
| @ -5,9 +5,9 @@ const { recurseFiles, getFileTimestampsWithIno } = require('./fileUtils') | |||||||
| const globals = require('./globals') | const globals = require('./globals') | ||||||
| const LibraryFile = require('../objects/files/LibraryFile') | const LibraryFile = require('../objects/files/LibraryFile') | ||||||
| 
 | 
 | ||||||
| function isMediaFile(mediaType, path) { | function isMediaFile(mediaType, ext) { | ||||||
|   if (!path) return false |   // if (!path) return false
 | ||||||
|   var ext = Path.extname(path) |   // var ext = Path.extname(path)
 | ||||||
|   if (!ext) return false |   if (!ext) return false | ||||||
|   var extclean = ext.slice(1).toLowerCase() |   var extclean = ext.slice(1).toLowerCase() | ||||||
|   if (mediaType === 'podcast') return globals.SupportedAudioTypes.includes(extclean) |   if (mediaType === 'podcast') return globals.SupportedAudioTypes.includes(extclean) | ||||||
| @ -62,25 +62,31 @@ module.exports.groupFilesIntoLibraryItemPaths = groupFilesIntoLibraryItemPaths | |||||||
| // Input: array of relative file items (see recurseFiles)
 | // Input: array of relative file items (see recurseFiles)
 | ||||||
| // Output: map of files grouped into potential libarary item dirs
 | // Output: map of files grouped into potential libarary item dirs
 | ||||||
| function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems) { | function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems) { | ||||||
|   // Step 1: Filter out files in root dir (with depth of 0)
 |   // Step 1: Filter out non-media files in root dir (with depth of 0)
 | ||||||
|   var itemsFiltered = fileItems.filter(i => i.deep > 0) |   var itemsFiltered = fileItems.filter(i => { | ||||||
|  |     return i.deep > 0 || isMediaFile(mediaType, i.extension) | ||||||
|  |   }) | ||||||
| 
 | 
 | ||||||
|   // Step 2: Seperate media files and other files
 |   // Step 2: Seperate media files and other files
 | ||||||
|   //     - Directories without a media file will not be included
 |   //     - Directories without a media file will not be included
 | ||||||
|   var mediaFileItems = [] |   var mediaFileItems = [] | ||||||
|   var otherFileItems = [] |   var otherFileItems = [] | ||||||
|   itemsFiltered.forEach(item => { |   itemsFiltered.forEach(item => { | ||||||
|     if (isMediaFile(mediaType, item.fullpath)) mediaFileItems.push(item) |     if (isMediaFile(mediaType, item.extension)) mediaFileItems.push(item) | ||||||
|     else otherFileItems.push(item) |     else otherFileItems.push(item) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   // Step 3: Group audio files in library items
 |   // Step 3: Group audio files in library items
 | ||||||
|   var libraryItemGroup = {} |   var libraryItemGroup = {} | ||||||
|   mediaFileItems.forEach((item) => { |   mediaFileItems.forEach((item) => { | ||||||
|     var dirparts = item.reldirpath.split('/') |     var dirparts = item.reldirpath.split('/').filter(p => !!p) | ||||||
|     var numparts = dirparts.length |     var numparts = dirparts.length | ||||||
|     var _path = '' |     var _path = '' | ||||||
| 
 | 
 | ||||||
|  |     if (!dirparts.length) { | ||||||
|  |       // Media file in root
 | ||||||
|  |       libraryItemGroup[item.name] = item.name | ||||||
|  |     } else { | ||||||
|       // Iterate over directories in path
 |       // Iterate over directories in path
 | ||||||
|       for (let i = 0; i < numparts; i++) { |       for (let i = 0; i < numparts; i++) { | ||||||
|         var dirpart = dirparts.shift() |         var dirpart = dirparts.shift() | ||||||
| @ -98,6 +104,7 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems) { | |||||||
|           return |           return | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   // Step 4: Add other files into library item groups
 |   // Step 4: Add other files into library item groups
 | ||||||
| @ -140,6 +147,15 @@ async function scanFolder(libraryMediaType, folder, serverSettings = {}) { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   var fileItems = await recurseFiles(folderPath) |   var fileItems = await recurseFiles(folderPath) | ||||||
|  |   var basePath = folderPath | ||||||
|  | 
 | ||||||
|  |   const isOpenAudibleFolder = fileItems.find(fi => fi.deep === 0 && fi.name === 'books.json') | ||||||
|  |   if (isOpenAudibleFolder) { | ||||||
|  |     Logger.info(`[scandir] Detected Open Audible Folder, looking in books folder`) | ||||||
|  |     basePath = Path.posix.join(folderPath, 'books') | ||||||
|  |     fileItems = await recurseFiles(basePath) | ||||||
|  |     Logger.debug(`[scandir] ${fileItems.length} files found in books folder`) | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   var libraryItemGrouping = groupFileItemsIntoLibraryItemDirs(libraryMediaType, fileItems) |   var libraryItemGrouping = groupFileItemsIntoLibraryItemDirs(libraryMediaType, fileItems) | ||||||
| 
 | 
 | ||||||
| @ -148,11 +164,27 @@ async function scanFolder(libraryMediaType, folder, serverSettings = {}) { | |||||||
|     return [] |     return [] | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   var isFile = false // item is not in a folder
 | ||||||
|   var items = [] |   var items = [] | ||||||
|   for (const libraryItemPath in libraryItemGrouping) { |   for (const libraryItemPath in libraryItemGrouping) { | ||||||
|     var libraryItemData = getDataFromMediaDir(libraryMediaType, folderPath, libraryItemPath, serverSettings) |     var libraryItemData = null | ||||||
|  |     var fileObjs = [] | ||||||
|  |     if (libraryItemPath === libraryItemGrouping[libraryItemPath]) { | ||||||
|  |       // Media file in root only get title
 | ||||||
|  |       libraryItemData = { | ||||||
|  |         mediaMetadata: { | ||||||
|  |           title: Path.basename(libraryItemPath, Path.extname(libraryItemPath)) | ||||||
|  |         }, | ||||||
|  |         path: Path.posix.join(basePath, libraryItemPath), | ||||||
|  |         relPath: libraryItemPath | ||||||
|  |       } | ||||||
|  |       fileObjs = await cleanFileObjects(basePath, [libraryItemPath]) | ||||||
|  |       isFile = true | ||||||
|  |     } else { | ||||||
|  |       libraryItemData = getDataFromMediaDir(libraryMediaType, folderPath, libraryItemPath, serverSettings) | ||||||
|  |       fileObjs = await cleanFileObjects(libraryItemData.path, libraryItemGrouping[libraryItemPath]) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     var fileObjs = await cleanFileObjects(libraryItemData.path, libraryItemGrouping[libraryItemPath]) |  | ||||||
|     var libraryItemFolderStats = await getFileTimestampsWithIno(libraryItemData.path) |     var libraryItemFolderStats = await getFileTimestampsWithIno(libraryItemData.path) | ||||||
|     items.push({ |     items.push({ | ||||||
|       folderId: folder.id, |       folderId: folder.id, | ||||||
| @ -163,6 +195,7 @@ async function scanFolder(libraryMediaType, folder, serverSettings = {}) { | |||||||
|       birthtimeMs: libraryItemFolderStats.birthtimeMs || 0, |       birthtimeMs: libraryItemFolderStats.birthtimeMs || 0, | ||||||
|       path: libraryItemData.path, |       path: libraryItemData.path, | ||||||
|       relPath: libraryItemData.relPath, |       relPath: libraryItemData.relPath, | ||||||
|  |       isFile, | ||||||
|       media: { |       media: { | ||||||
|         metadata: libraryItemData.mediaMetadata || null |         metadata: libraryItemData.mediaMetadata || null | ||||||
|       }, |       }, | ||||||
| @ -242,7 +275,6 @@ function getBookDataFromDir(folderPath, relPath, parseSubtitle = false) { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|   // Subtitle can be parsed from the title if user enabled
 |   // Subtitle can be parsed from the title if user enabled
 | ||||||
|   // Subtitle is everything after " - "
 |   // Subtitle is everything after " - "
 | ||||||
|   var subtitle = null |   var subtitle = null | ||||||
| @ -290,7 +322,7 @@ function getDataFromMediaDir(libraryMediaType, folderPath, relPath, serverSettin | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | // Called from Scanner.js
 | ||||||
| async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath, serverSettings = {}) { | async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath, serverSettings = {}) { | ||||||
|   var fileItems = await recurseFiles(libraryItemPath) |   var fileItems = await recurseFiles(libraryItemPath) | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user