mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01: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) => {
 | 
				
			||||||
    name: item.name,
 | 
					    var isInRoot = (item.path + '/' === relPathToReplace)
 | 
				
			||||||
    path: item.fullname.replace(relPathToReplace, ''),
 | 
					    return {
 | 
				
			||||||
    dirpath: item.path,
 | 
					      name: item.name,
 | 
				
			||||||
    reldirpath: item.path.replace(relPathToReplace, ''),
 | 
					      path: item.fullname.replace(relPathToReplace, ''),
 | 
				
			||||||
    fullpath: item.fullname,
 | 
					      dirpath: item.path,
 | 
				
			||||||
    extension: item.extension,
 | 
					      reldirpath: isInRoot ? '' : item.path.replace(relPathToReplace, ''),
 | 
				
			||||||
    deep: item.deep
 | 
					      fullpath: item.fullname,
 | 
				
			||||||
  }))
 | 
					      extension: item.extension,
 | 
				
			||||||
 | 
					      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,40 +62,47 @@ 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 = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Iterate over directories in path
 | 
					    if (!dirparts.length) {
 | 
				
			||||||
    for (let i = 0; i < numparts; i++) {
 | 
					      // Media file in root
 | 
				
			||||||
      var dirpart = dirparts.shift()
 | 
					      libraryItemGroup[item.name] = item.name
 | 
				
			||||||
      _path = Path.posix.join(_path, dirpart)
 | 
					    } else {
 | 
				
			||||||
 | 
					      // Iterate over directories in path
 | 
				
			||||||
 | 
					      for (let i = 0; i < numparts; i++) {
 | 
				
			||||||
 | 
					        var dirpart = dirparts.shift()
 | 
				
			||||||
 | 
					        _path = Path.posix.join(_path, dirpart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (libraryItemGroup[_path]) { // Directory already has files, add file
 | 
					        if (libraryItemGroup[_path]) { // Directory already has files, add file
 | 
				
			||||||
        var relpath = Path.posix.join(dirparts.join('/'), item.name)
 | 
					          var relpath = Path.posix.join(dirparts.join('/'), item.name)
 | 
				
			||||||
        libraryItemGroup[_path].push(relpath)
 | 
					          libraryItemGroup[_path].push(relpath)
 | 
				
			||||||
        return
 | 
					          return
 | 
				
			||||||
      } else if (!dirparts.length) { // This is the last directory, create group
 | 
					        } else if (!dirparts.length) { // This is the last directory, create group
 | 
				
			||||||
        libraryItemGroup[_path] = [item.name]
 | 
					          libraryItemGroup[_path] = [item.name]
 | 
				
			||||||
        return
 | 
					          return
 | 
				
			||||||
      } else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) { // Next directory is the last and is a CD dir, create group
 | 
					        } else if (dirparts.length === 1 && /^cd\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)]
 | 
					          libraryItemGroup[_path] = [Path.posix.join(dirparts[0], item.name)]
 | 
				
			||||||
        return
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
@ -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…
	
		Reference in New Issue
	
	Block a user