mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Support volumes with decimal #196, Change:Time remaining adjusted for current playback rate, Change:Series bookshelf shows shelf label with series name, Fix:Search bookshelf UI, Add:Show current chapter under audio track, Change: Highlight colors for chapters modal
This commit is contained in:
		
							parent
							
								
									24d2e09724
								
							
						
					
					
						commit
						ad8670aeb4
					
				| @ -1,12 +1,12 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="w-full -mt-4"> |   <div class="w-full -mt-6"> | ||||||
|     <div class="w-full relative mb-2"> |     <div class="w-full relative mb-1"> | ||||||
|       <div class="absolute top-0 left-0 w-full h-full bg-red flex items-end pointer-events-none"> |       <!-- <div class="absolute top-0 left-0 w-full h-full bg-red flex items-end pointer-events-none"> | ||||||
|         <p ref="currentTimestamp" class="font-mono text-sm text-gray-100 pointer-events-auto">00:00:00</p> |         <p ref="currentTimestamp" class="font-mono text-sm text-gray-100 pointer-events-auto">00:00:00</p> | ||||||
|         <p class="font-mono text-sm text-gray-100 pointer-events-auto"> / {{ progressPercent }}%</p> |         <p class="font-mono text-sm text-gray-100 pointer-events-auto"> / {{ progressPercent }}%</p> | ||||||
|         <div class="flex-grow" /> |         <div class="flex-grow" /> | ||||||
|         <p class="font-mono text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p> |         <p class="font-mono text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p> | ||||||
|       </div> |       </div> --> | ||||||
| 
 | 
 | ||||||
|       <div v-if="chapters.length" class="hidden md:flex absolute right-20 top-0 bottom-0 h-full items-end"> |       <div v-if="chapters.length" class="hidden md:flex absolute right-20 top-0 bottom-0 h-full items-end"> | ||||||
|         <div class="cursor-pointer text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="showChapters"> |         <div class="cursor-pointer text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="showChapters"> | ||||||
| @ -59,7 +59,7 @@ | |||||||
|       </div> |       </div> | ||||||
|       <div ref="track" class="w-full h-2 relative overflow-hidden"> |       <div ref="track" class="w-full h-2 relative overflow-hidden"> | ||||||
|         <template v-for="(tick, index) in chapterTicks"> |         <template v-for="(tick, index) in chapterTicks"> | ||||||
|           <div :key="index" :style="{ left: tick.left + 'px' }" class="absolute top-0 w-px bg-white bg-opacity-50 h-1 pointer-events-none" /> |           <div :key="index" :style="{ left: tick.left + 'px' }" class="absolute top-0 w-px bg-white bg-opacity-30 h-1 pointer-events-none" /> | ||||||
|         </template> |         </template> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
| @ -73,6 +73,14 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="flex"> | ||||||
|  |       <p ref="currentTimestamp" class="font-mono text-sm text-gray-100 pointer-events-auto">00:00:00</p> | ||||||
|  |       <p class="font-mono text-sm text-gray-100 pointer-events-auto"> / {{ progressPercent }}%</p> | ||||||
|  |       <div class="flex-grow" /> | ||||||
|  |       <p class="text-sm text-gray-300 pt-0.5">{{ currentChapterName }}</p> | ||||||
|  |       <div class="flex-grow" /> | ||||||
|  |       <p class="font-mono text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p> | ||||||
|  |     </div> | ||||||
| 
 | 
 | ||||||
|     <audio ref="audio" @progress="progress" @timeupdate="timeupdate" @loadedmetadata="audioLoadedMetadata" @loadeddata="audioLoadedData" @play="audioPlayed" @pause="audioPaused" @error="audioError" @ended="audioEnded" @stalled="audioStalled" @suspend="audioSuspended" /> |     <audio ref="audio" @progress="progress" @timeupdate="timeupdate" @loadedmetadata="audioLoadedMetadata" @loadeddata="audioLoadedData" @play="audioPlayed" @pause="audioPaused" @error="audioError" @ended="audioEnded" @stalled="audioStalled" @suspend="audioSuspended" /> | ||||||
| 
 | 
 | ||||||
| @ -131,7 +139,7 @@ export default { | |||||||
|     }, |     }, | ||||||
|     timeRemaining() { |     timeRemaining() { | ||||||
|       if (!this.audioEl) return 0 |       if (!this.audioEl) return 0 | ||||||
|       return this.totalDuration - this.currentTime |       return (this.totalDuration - this.currentTime) / this.playbackRate | ||||||
|     }, |     }, | ||||||
|     timeRemainingPretty() { |     timeRemainingPretty() { | ||||||
|       if (this.timeRemaining < 0) { |       if (this.timeRemaining < 0) { | ||||||
| @ -156,6 +164,9 @@ export default { | |||||||
|     currentChapter() { |     currentChapter() { | ||||||
|       return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end) |       return this.chapters.find((chapter) => chapter.start <= this.currentTime && this.currentTime < chapter.end) | ||||||
|     }, |     }, | ||||||
|  |     currentChapterName() { | ||||||
|  |       return this.currentChapter ? this.currentChapter.title : '' | ||||||
|  |     }, | ||||||
|     showExperimentalFeatures() { |     showExperimentalFeatures() { | ||||||
|       return this.$store.state.showExperimentalFeatures |       return this.$store.state.showExperimentalFeatures | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <div id="bookshelf" class="bookshelf overflow-hidden relative block max-h-full"> |   <div id="bookshelf" class="overflow-hidden relative block max-h-full"> | ||||||
|     <div ref="wrapper" class="h-full w-full relative" :class="isGridMode ? 'overflow-y-scroll' : 'overflow-hidden'"> |     <div ref="wrapper" class="h-full w-full relative" :class="isGridMode ? 'overflow-y-scroll' : 'overflow-hidden'"> | ||||||
|       <!-- Cover size widget --> |       <!-- Cover size widget --> | ||||||
|       <div v-show="!isSelectionMode && isGridMode" class="fixed bottom-2 right-4 z-30"> |       <div v-show="!isSelectionMode && isGridMode" class="fixed bottom-2 right-4 z-30"> | ||||||
| @ -36,10 +36,10 @@ | |||||||
|                     <cards-group-card v-else-if="showGroups" :key="entity.id" :width="bookCoverWidth" :group="entity" @click="clickGroup" /> |                     <cards-group-card v-else-if="showGroups" :key="entity.id" :width="bookCoverWidth" :group="entity" @click="clickGroup" /> | ||||||
| 
 | 
 | ||||||
|                     <!-- <cards-book-3d :key="entity.id" v-else :width="100" :src="$store.getters['audiobooks/getBookCoverSrc'](entity.book)" /> --> |                     <!-- <cards-book-3d :key="entity.id" v-else :width="100" :src="$store.getters['audiobooks/getBookCoverSrc'](entity.book)" /> --> | ||||||
|                     <cards-book-card v-else :ref="`book-card-${entity.id}`" :key="entity.id" is-bookshelf-book :show-volume-number="!!selectedSeries" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" @edit="editBook" @hook:mounted="mountedBookCard(entity)" /> |                     <cards-book-card v-else :ref="`book-card-${entity.id}`" :key="entity.id" :is-bookshelf-book="!isSeries" :show-volume-number="!!selectedSeries" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" @edit="editBook" @hook:mounted="mountedBookCard(entity)" /> | ||||||
|                   </template> |                   </template> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-10" :class="isCollections ? 'h-6' : 'h-4'" /> |                 <div class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-10" :class="isCollections || isSeriesGroups ? 'h-6' : 'h-4'" /> | ||||||
|               </div> |               </div> | ||||||
|             </template> |             </template> | ||||||
|           </div> |           </div> | ||||||
| @ -159,6 +159,12 @@ export default { | |||||||
|     isCollections() { |     isCollections() { | ||||||
|       return this.page === 'collections' |       return this.page === 'collections' | ||||||
|     }, |     }, | ||||||
|  |     isSeries() { | ||||||
|  |       return this.page === 'series' | ||||||
|  |     }, | ||||||
|  |     isSeriesGroups() { | ||||||
|  |       return this.isSeries && !this.selectedSeries | ||||||
|  |     }, | ||||||
|     categorizedShelves() { |     categorizedShelves() { | ||||||
|       if (this.page !== 'search') return [] |       if (this.page !== 'search') return [] | ||||||
|       var audiobookSearchResults = this.searchResults ? this.searchResults.audiobooks || [] : [] |       var audiobookSearchResults = this.searchResults ? this.searchResults.audiobooks || [] : [] | ||||||
| @ -448,17 +454,6 @@ export default { | |||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
| .bookshelf { |  | ||||||
|   /* height: calc(100% - 40px); */ |  | ||||||
|   width: calc(100vw - 80px); |  | ||||||
| } |  | ||||||
| @media (max-width: 768px) { |  | ||||||
|   .bookshelf { |  | ||||||
|     /* height: calc(100% - 80px); */ |  | ||||||
|     width: 100vw; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .bookshelfRow { | .bookshelfRow { | ||||||
|   background-image: url(/wood_panels.jpg); |   background-image: url(/wood_panels.jpg); | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,13 +9,13 @@ | |||||||
|         </div> |         </div> | ||||||
|         <div v-else-if="shelf.series" class="flex items-center -mb-2"> |         <div v-else-if="shelf.series" class="flex items-center -mb-2"> | ||||||
|           <template v-for="entity in shelf.series"> |           <template v-for="entity in shelf.series"> | ||||||
|             <cards-group-card :key="entity.name" :width="bookCoverWidth" :group="entity" @click="$emit('clickSeries', entity)" /> |             <cards-group-card is-search :key="entity.name" :width="bookCoverWidth" :group="entity" @click="$emit('clickSeries', entity)" /> | ||||||
|           </template> |           </template> | ||||||
|         </div> |         </div> | ||||||
|         <div v-else-if="shelf.tags" class="flex items-center -mb-2"> |         <div v-else-if="shelf.tags" class="flex items-center -mb-2"> | ||||||
|           <template v-for="entity in shelf.tags"> |           <template v-for="entity in shelf.tags"> | ||||||
|             <nuxt-link :key="entity.name" :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(entity.name)}`"> |             <nuxt-link :key="entity.name" :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(entity.name)}`"> | ||||||
|               <cards-group-card :width="bookCoverWidth" :group="entity" /> |               <cards-group-card is-search :width="bookCoverWidth" :group="entity" /> | ||||||
|             </nuxt-link> |             </nuxt-link> | ||||||
|           </template> |           </template> | ||||||
|         </div> |         </div> | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="relative"> |   <div class="relative"> | ||||||
|     <div class="rounded-sm h-full relative" :style="{ padding: `16px ${paddingX}px` }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard"> |     <div class="rounded-sm h-full relative" :style="{ padding: `${paddingY}px ${paddingX}px` }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard"> | ||||||
|       <nuxt-link :to="groupTo" class="cursor-pointer"> |       <nuxt-link :to="groupTo" class="cursor-pointer"> | ||||||
|         <div class="w-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }"> |         <div class="w-full h-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }"> | ||||||
|           <covers-group-cover ref="groupcover" :name="groupName" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" /> |           <covers-group-cover ref="groupcover" :name="groupName" :is-search="isSearch" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" /> | ||||||
| 
 | 
 | ||||||
|           <div v-if="hasValidCovers && !showExperimentalFeatures" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity z-30" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }"> |           <div v-if="hasValidCovers && (!showExperimentalFeatures || isSearch)" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity z-30" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }"> | ||||||
|             <p class="font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p> |             <p class="font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
| @ -18,6 +18,12 @@ | |||||||
|         </div> |         </div> | ||||||
|       </nuxt-link> |       </nuxt-link> | ||||||
|     </div> |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="categoryPlacard absolute z-30 left-0 right-0 mx-auto bottom-0 h-6 rounded-md font-book text-center" :style="{ width: Math.min(160, coverWidth) + 'px' }"> | ||||||
|  |       <div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${1 * sizeMultiplier}rem` }"> | ||||||
|  |         <p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ groupName }}</p> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| @ -31,7 +37,12 @@ export default { | |||||||
|     width: { |     width: { | ||||||
|       type: Number, |       type: Number, | ||||||
|       default: 120 |       default: 120 | ||||||
|     } |     }, | ||||||
|  |     paddingY: { | ||||||
|  |       type: Number, | ||||||
|  |       default: 24 | ||||||
|  |     }, | ||||||
|  |     isSearch: Boolean | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
| @ -48,6 +59,10 @@ export default { | |||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|  |     labelFontSize() { | ||||||
|  |       if (this.coverWidth < 160) return 0.75 | ||||||
|  |       return 0.875 | ||||||
|  |     }, | ||||||
|     currentLibraryId() { |     currentLibraryId() { | ||||||
|       return this.$store.state.libraries.currentLibraryId |       return this.$store.state.libraries.currentLibraryId | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -17,7 +17,8 @@ export default { | |||||||
|     width: Number, |     width: Number, | ||||||
|     height: Number, |     height: Number, | ||||||
|     groupTo: String, |     groupTo: String, | ||||||
|     type: String |     type: String, | ||||||
|  |     isSearch: Boolean | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
| @ -53,8 +54,7 @@ export default { | |||||||
|       return this.$store.state.showExperimentalFeatures |       return this.$store.state.showExperimentalFeatures | ||||||
|     }, |     }, | ||||||
|     showCoverFan() { |     showCoverFan() { | ||||||
|       if (this.type === 'collection') return false |       return this.showExperimentalFeatures && this.windowWidth > 1024 && !this.isSearch | ||||||
|       return this.showExperimentalFeatures && this.windowWidth > 1024 |  | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
|   <modals-modal v-model="show" name="chapters" :width="500" :height="'unset'"> |   <modals-modal v-model="show" name="chapters" :width="500" :height="'unset'"> | ||||||
|     <div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> |     <div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> | ||||||
|       <template v-for="chap in chapters"> |       <template v-for="chap in chapters"> | ||||||
|         <div :key="chap.id" :id="`chapter-row-${chap.id}`" class="flex items-center px-6 py-3 justify-start cursor-pointer hover:bg-bg relative" :class="chap.id === currentChapterId ? 'bg-bg bg-opacity-80' : 'bg-opacity-20'" @click="clickChapter(chap)"> |         <div :key="chap.id" :id="`chapter-row-${chap.id}`" class="flex items-center px-6 py-3 justify-start cursor-pointer hover:bg-bg relative" :class="chap.id === currentChapterId ? 'bg-yellow-400 bg-opacity-10' : chap.end <= currentChapterStart ? 'bg-success bg-opacity-5' : 'bg-opacity-20'" @click="clickChapter(chap)"> | ||||||
|           {{ chap.title }} |           {{ chap.title }} | ||||||
|           <span class="flex-grow" /> |           <span class="flex-grow" /> | ||||||
|           <span class="font-mono text-sm text-gray-300">{{ $secondsToTimestamp(chap.start) }}</span> |           <span class="font-mono text-sm text-gray-300">{{ $secondsToTimestamp(chap.start) }}</span> | ||||||
| @ -46,6 +46,9 @@ export default { | |||||||
|     }, |     }, | ||||||
|     currentChapterId() { |     currentChapterId() { | ||||||
|       return this.currentChapter ? this.currentChapter.id : null |       return this.currentChapter ? this.currentChapter.id : null | ||||||
|  |     }, | ||||||
|  |     currentChapterStart() { | ||||||
|  |       return this.currentChapter ? this.currentChapter.start : 0 | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|  | |||||||
| @ -110,8 +110,8 @@ export default { | |||||||
|       this.$store.commit('setOpenModal', this.name) |       this.$store.commit('setOpenModal', this.name) | ||||||
|     }, |     }, | ||||||
|     setHide() { |     setHide() { | ||||||
|       this.content.style.transform = 'scale(0)' |       if (this.content) this.content.style.transform = 'scale(0)' | ||||||
|       this.el.remove() |       if (this.el) this.el.remove() | ||||||
|       document.documentElement.classList.remove('modal-open') |       document.documentElement.classList.remove('modal-open') | ||||||
| 
 | 
 | ||||||
|       this.$eventBus.$off('modal-hotkey', this.hotkey) |       this.$eventBus.$off('modal-hotkey', this.hotkey) | ||||||
|  | |||||||
| @ -98,10 +98,10 @@ export default { | |||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     scannerPreferAudioMetaTooltip() { |     scannerPreferAudioMetaTooltip() { | ||||||
|       return 'Audio file ID3 meta tags will be used for book details over folder & filenames' |       return 'Audio file ID3 meta tags will be used for book details over folder names' | ||||||
|     }, |     }, | ||||||
|     scannerPreferOpfMetaTooltip() { |     scannerPreferOpfMetaTooltip() { | ||||||
|       return 'OPF file metadata will be used for book details over folder & filenames' |       return 'OPF file metadata will be used for book details over folder names' | ||||||
|     }, |     }, | ||||||
|     saveMetadataTooltip() { |     saveMetadataTooltip() { | ||||||
|       return 'This will write a "metadata.nfo" file in all of your audiobook directories.' |       return 'This will write a "metadata.nfo" file in all of your audiobook directories.' | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ const Audnexus = require('./providers/Audnexus') | |||||||
| 
 | 
 | ||||||
| const { downloadFile } = require('./utils/fileUtils') | const { downloadFile } = require('./utils/fileUtils') | ||||||
| 
 | 
 | ||||||
| class AuthorController { | class AuthorFinder { | ||||||
|   constructor(MetadataPath) { |   constructor(MetadataPath) { | ||||||
|     this.MetadataPath = MetadataPath |     this.MetadataPath = MetadataPath | ||||||
|     this.AuthorPath = Path.join(MetadataPath, 'authors') |     this.AuthorPath = Path.join(MetadataPath, 'authors') | ||||||
| @ -16,7 +16,7 @@ class AuthorController { | |||||||
| 
 | 
 | ||||||
|   async downloadImage(url, outputPath) { |   async downloadImage(url, outputPath) { | ||||||
|     return downloadFile(url, outputPath).then(() => true).catch((error) => { |     return downloadFile(url, outputPath).then(() => true).catch((error) => { | ||||||
|       Logger.error('[AuthorController] Failed to download author image', error) |       Logger.error('[AuthorFinder] Failed to download author image', error) | ||||||
|       return null |       return null | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| @ -50,7 +50,7 @@ class AuthorController { | |||||||
|       var success = await this.downloadImage(payload.image, outputPath) |       var success = await this.downloadImage(payload.image, outputPath) | ||||||
|       if (!success) { |       if (!success) { | ||||||
|         await fs.rmdir(authorDir).catch((error) => { |         await fs.rmdir(authorDir).catch((error) => { | ||||||
|           Logger.error(`[AuthorController] Failed to remove author dir`, authorDir, error) |           Logger.error(`[AuthorFinder] Failed to remove author dir`, authorDir, error) | ||||||
|         }) |         }) | ||||||
|         payload.image = null |         payload.image = null | ||||||
|         payload.imageFullPath = null |         payload.imageFullPath = null | ||||||
| @ -88,7 +88,7 @@ class AuthorController { | |||||||
|       var success = await this.downloadImage(authorData.image, outputPath) |       var success = await this.downloadImage(authorData.image, outputPath) | ||||||
|       if (!success) { |       if (!success) { | ||||||
|         await fs.rmdir(authorDir).catch((error) => { |         await fs.rmdir(authorDir).catch((error) => { | ||||||
|           Logger.error(`[AuthorController] Failed to remove author dir`, authorDir, error) |           Logger.error(`[AuthorFinder] Failed to remove author dir`, authorDir, error) | ||||||
|         }) |         }) | ||||||
|         authorData.image = null |         authorData.image = null | ||||||
|         authorData.imageFullPath = null |         authorData.imageFullPath = null | ||||||
| @ -107,4 +107,4 @@ class AuthorController { | |||||||
|     return author |     return author | ||||||
|   } |   } | ||||||
| } | } | ||||||
| module.exports = AuthorController | module.exports = AuthorFinder | ||||||
| @ -124,6 +124,8 @@ class FolderWatcher extends EventEmitter { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   addFileUpdate(libraryId, path, type) { |   addFileUpdate(libraryId, path, type) { | ||||||
|  |     console.log('add file update', libraryId, path, type) | ||||||
|  |     return | ||||||
|     path = path.replace(/\\/g, '/') |     path = path.replace(/\\/g, '/') | ||||||
|     if (this.pendingFilePaths.includes(path)) return |     if (this.pendingFilePaths.includes(path)) return | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										33
									
								
								server/scanner/AuthorScanner.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								server/scanner/AuthorScanner.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | const AuthorFinder = require('../AuthorFinder') | ||||||
|  | 
 | ||||||
|  | class AuthorScanner { | ||||||
|  |   constructor(db, MetadataPath) { | ||||||
|  |     this.db = db | ||||||
|  |     this.MetadataPath = MetadataPath | ||||||
|  |     this.authorFinder = new AuthorFinder(MetadataPath) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getUniqueAuthors() { | ||||||
|  |     var authorFls = this.db.audiobooks.map(b => b.book.authorFL) | ||||||
|  |     var authors = [] | ||||||
|  |     authorFls.forEach((auth) => { | ||||||
|  |       authors = authors.concat(auth.split(', ').map(a => a.trim())) | ||||||
|  |     }) | ||||||
|  |     return [...new Set(authors)] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async scanAuthors() { | ||||||
|  |     var authors = this.getUniqueAuthors() | ||||||
|  |     for (let i = 0; i < authors.length; i++) { | ||||||
|  |       var authorName = authors[i] | ||||||
|  |       var author = await this.authorFinder.getAuthorByName(authorName) | ||||||
|  |       if (!author) { | ||||||
|  |         return res.status(500).send('Failed to create author') | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       await this.db.insertEntity('author', author) | ||||||
|  |       this.emitter('author_added', author.toJSON()) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | module.exports = AuthorScanner | ||||||
| @ -206,7 +206,8 @@ function getAudiobookDataFromDir(folderPath, dir, parseSubtitle = false) { | |||||||
|   */ |   */ | ||||||
|   var volumeNumber = null |   var volumeNumber = null | ||||||
|   if (series) { |   if (series) { | ||||||
|     var volumeMatch = title.match(/(-? ?)\b((?:Book|Vol.?|Volume) (\d{1,3}))\b( ?-?)/i) |     // New volume regex to match volumes with decimal (OLD: /(-? ?)\b((?:Book|Vol.?|Volume) (\d{1,3}))\b( ?-?)/i)
 | ||||||
|  |     var volumeMatch = title.match(/(-? ?)\b((?:Book|Vol.?|Volume) (\d{0,3}(?:\.\d{1,2})?))\b( ?-?)/i) | ||||||
|     if (volumeMatch && volumeMatch.length > 3 && volumeMatch[2] && volumeMatch[3]) { |     if (volumeMatch && volumeMatch.length > 3 && volumeMatch[2] && volumeMatch[3]) { | ||||||
|       volumeNumber = volumeMatch[3] |       volumeNumber = volumeMatch[3] | ||||||
|       var replaceChunk = volumeMatch[2] |       var replaceChunk = volumeMatch[2] | ||||||
| @ -226,9 +227,6 @@ function getAudiobookDataFromDir(folderPath, dir, parseSubtitle = false) { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   var publishYear = null |   var publishYear = null | ||||||
|   // OLD regex (not matching parentheses)
 |  | ||||||
|   // var publishYearMatch = title.match(/^([0-9]{4}) - (.+)/)
 |  | ||||||
| 
 |  | ||||||
|   // If Title is of format 1999 OR (1999) - Title, then use 1999 as publish year
 |   // If Title is of format 1999 OR (1999) - Title, then use 1999 as publish year
 | ||||||
|   var publishYearMatch = title.match(/^(\(?[0-9]{4}\)?) - (.+)/) |   var publishYearMatch = title.match(/^(\(?[0-9]{4}\)?) - (.+)/) | ||||||
|   if (publishYearMatch && publishYearMatch.length > 2 && publishYearMatch[1]) { |   if (publishYearMatch && publishYearMatch.length > 2 && publishYearMatch[1]) { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user