mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +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