mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-09 21:39:37 +00:00
Merge branch 'master' into liaocl
This commit is contained in:
commit
2ec52a7a45
119 changed files with 11524 additions and 25894 deletions
105
client/components/modals/AddCustomMetadataProviderModal.vue
Normal file
105
client/components/modals/AddCustomMetadataProviderModal.vue
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<modals-modal ref="modal" v-model="show" name="custom-metadata-provider" :width="600" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="text-3xl text-white truncate">Add custom metadata provider</p>
|
||||
</div>
|
||||
</template>
|
||||
<form @submit.prevent="submitForm">
|
||||
<div class="px-4 w-full flex items-center text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="min-height: 400px; max-height: 80vh">
|
||||
<div class="w-full p-8">
|
||||
<div class="flex mb-2">
|
||||
<div class="w-3/4 p-1">
|
||||
<ui-text-input-with-label v-model="newName" :label="$strings.LabelName" />
|
||||
</div>
|
||||
<div class="w-1/4 p-1">
|
||||
<ui-text-input-with-label value="Book" readonly :label="$strings.LabelMediaType" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full mb-2 p-1">
|
||||
<ui-text-input-with-label v-model="newUrl" label="URL" />
|
||||
</div>
|
||||
<div class="w-full mb-2 p-1">
|
||||
<ui-text-input-with-label v-model="newAuthHeaderValue" :label="'Authorization Header Value'" type="password" />
|
||||
</div>
|
||||
<div class="flex px-1 pt-4">
|
||||
<div class="flex-grow" />
|
||||
<ui-btn color="success" type="submit">{{ $strings.ButtonAdd }}</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</modals-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
processing: false,
|
||||
newName: '',
|
||||
newUrl: '',
|
||||
newAuthHeaderValue: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submitForm() {
|
||||
if (!this.newName || !this.newUrl) {
|
||||
this.$toast.error('Must add name and url')
|
||||
return
|
||||
}
|
||||
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$post('/api/custom-metadata-providers', {
|
||||
name: this.newName,
|
||||
url: this.newUrl,
|
||||
mediaType: 'book', // Currently only supporting book mediaType
|
||||
authHeaderValue: this.newAuthHeaderValue
|
||||
})
|
||||
.then((data) => {
|
||||
this.$emit('added', data.provider)
|
||||
this.$toast.success('New provider added')
|
||||
this.show = false
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorMsg = error.response?.data || 'Unknown error'
|
||||
console.error('Failed to add provider', error)
|
||||
this.$toast.error('Failed to add provider: ' + errorMsg)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
})
|
||||
},
|
||||
init() {
|
||||
this.processing = false
|
||||
this.newName = ''
|
||||
this.newUrl = ''
|
||||
this.newAuthHeaderValue = ''
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -328,6 +328,17 @@ export default {
|
|||
console.error('PersistProvider', error)
|
||||
}
|
||||
},
|
||||
getDefaultBookProvider() {
|
||||
let provider = localStorage.getItem('book-provider')
|
||||
if (!provider) return 'google'
|
||||
// Validate book provider
|
||||
if (!this.$store.getters['scanners/checkBookProviderExists'](provider)) {
|
||||
console.error('Stored book provider does not exist', provider)
|
||||
localStorage.removeItem('book-provider')
|
||||
return 'google'
|
||||
}
|
||||
return provider
|
||||
},
|
||||
getSearchQuery() {
|
||||
if (this.isPodcast) return `term=${encodeURIComponent(this.searchTitle)}`
|
||||
var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${encodeURIComponent(this.searchTitle)}`
|
||||
|
|
@ -434,7 +445,9 @@ export default {
|
|||
this.searchTitle = this.libraryItem.media.metadata.title
|
||||
this.searchAuthor = this.libraryItem.media.metadata.authorName || ''
|
||||
if (this.isPodcast) this.provider = 'itunes'
|
||||
else this.provider = localStorage.getItem('book-provider') || 'google'
|
||||
else {
|
||||
this.provider = this.getDefaultBookProvider()
|
||||
}
|
||||
|
||||
// Prefer using ASIN if set and using audible provider
|
||||
if (this.provider.startsWith('audible') && this.libraryItem.media.metadata.asin) {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
<div class="w-full h-full overflow-hidden overflow-y-auto px-4 py-6">
|
||||
<p class="text-xl font-semibold mb-2">{{ $strings.HeaderAudiobookTools }}</p>
|
||||
|
||||
<!-- alert for windows install -->
|
||||
<widgets-alert v-if="isWindowsInstall" type="warning" class="my-8 text-base">Not supported for the Windows install yet</widgets-alert>
|
||||
|
||||
<!-- Merge to m4b -->
|
||||
<div v-if="showM4bDownload" class="w-full border border-black-200 p-4 my-8">
|
||||
<div v-if="showM4bDownload && !isWindowsInstall" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div>
|
||||
<p class="text-lg">{{ $strings.LabelToolsMakeM4b }}</p>
|
||||
|
|
@ -19,22 +22,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Split to mp3 -->
|
||||
<!-- <div v-if="showMp3Split" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="text-lg">{{ $strings.LabelToolsSplitM4b }}</p>
|
||||
<p class="max-w-sm text-sm pt-2 text-gray-300">{{ $strings.LabelToolsSplitM4bDescription }}</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div>
|
||||
<ui-btn :disabled="true">{{ $strings.MessageNotYetImplemented }}</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- Embed Metadata -->
|
||||
<div v-if="mediaTracks.length" class="w-full border border-black-200 p-4 my-8">
|
||||
<div v-if="mediaTracks.length && !isWindowsInstall" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="text-lg">{{ $strings.LabelToolsEmbedMetadata }}</p>
|
||||
|
|
@ -122,6 +111,12 @@ export default {
|
|||
},
|
||||
isEncodeTaskRunning() {
|
||||
return this.encodeTask && !this.encodeTask?.isFinished
|
||||
},
|
||||
isWindowsInstall() {
|
||||
return this.Source == 'windows'
|
||||
},
|
||||
Source() {
|
||||
return this.$store.state.Source
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@
|
|||
<div v-else class="py-12 text-center max-w-sm mx-auto">
|
||||
<p class="text-lg mb-2">{{ $strings.MessageNoFoldersAvailable }}</p>
|
||||
<p class="text-gray-300 mb-2">{{ $strings.NoteFolderPicker }}</p>
|
||||
<p v-if="isDebian" class="text-red-400">{{ $strings.NoteFolderPickerDebian }}</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full py-2">
|
||||
|
|
@ -93,12 +92,6 @@ export default {
|
|||
...d
|
||||
}
|
||||
})
|
||||
},
|
||||
isDebian() {
|
||||
return this.Source == 'debian'
|
||||
},
|
||||
Source() {
|
||||
return this.$store.state.Source
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export default {
|
|||
},
|
||||
audioMetatags: {
|
||||
id: 'audioMetatags',
|
||||
name: 'Audio file meta tags',
|
||||
name: 'Audio file meta tags OR ebook metadata',
|
||||
include: true
|
||||
},
|
||||
nfoFile: {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ export default {
|
|||
selectAll: false,
|
||||
search: null,
|
||||
searchTimeout: null,
|
||||
searchText: null
|
||||
searchText: null,
|
||||
downloadedEpisodeGuidMap: {},
|
||||
downloadedEpisodeUrlMap: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -122,11 +124,13 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getIsEpisodeDownloaded(episode) {
|
||||
return this.itemEpisodes.some((downloadedEpisode) => {
|
||||
if (episode.guid && downloadedEpisode.guid === episode.guid) return true
|
||||
if (!downloadedEpisode.enclosure?.url) return false
|
||||
return this.getCleanEpisodeUrl(downloadedEpisode.enclosure.url) === episode.cleanUrl
|
||||
})
|
||||
if (episode.guid && !!this.downloadedEpisodeGuidMap[episode.guid]) {
|
||||
return true
|
||||
}
|
||||
if (this.downloadedEpisodeUrlMap[episode.cleanUrl]) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
/**
|
||||
* UPDATE: As of v2.4.5 guid is used for matching existing downloaded episodes if it is found on the RSS feed.
|
||||
|
|
@ -219,6 +223,14 @@ export default {
|
|||
})
|
||||
},
|
||||
init() {
|
||||
this.downloadedEpisodeGuidMap = {}
|
||||
this.downloadedEpisodeUrlMap = {}
|
||||
|
||||
this.itemEpisodes.forEach((episode) => {
|
||||
if (episode.guid) this.downloadedEpisodeGuidMap[episode.guid] = episode.id
|
||||
if (episode.enclosure?.url) this.downloadedEpisodeUrlMap[this.getCleanEpisodeUrl(episode.enclosure.url)] = episode.id
|
||||
})
|
||||
|
||||
this.episodesCleaned = this.episodes
|
||||
.filter((ep) => ep.enclosure?.url)
|
||||
.map((_ep) => {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<div class="w-full p-1">
|
||||
<ui-textarea-with-label v-model="newEpisode.subtitle" :label="$strings.LabelSubtitle" :rows="3" />
|
||||
</div>
|
||||
<div class="w-full p-1 default-style">
|
||||
<div class="w-full p-1">
|
||||
<ui-rich-text-editor :label="$strings.LabelDescription" v-model="newEpisode.description" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue