mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-26 15:58:40 +01:00
Update:M4b Merge tool moved to manage page
This commit is contained in:
parent
d456ec2786
commit
441b8c5bb7
@ -66,9 +66,9 @@ export default {
|
||||
component: 'modals-item-tabs-match'
|
||||
},
|
||||
{
|
||||
id: 'manage',
|
||||
title: 'Manage',
|
||||
component: 'modals-item-tabs-manage',
|
||||
id: 'tools',
|
||||
title: 'Tools',
|
||||
component: 'modals-item-tabs-tools',
|
||||
mediaType: 'book',
|
||||
admin: true
|
||||
},
|
||||
@ -141,10 +141,10 @@ export default {
|
||||
if (tab.mediaType && this.mediaType !== tab.mediaType) return false
|
||||
if (tab.admin && !this.userIsAdminOrUp) return false
|
||||
|
||||
if (tab.id === 'manage' && this.isMissing) return false
|
||||
if (tab.id === 'tools' && this.isMissing) return false
|
||||
|
||||
if ((tab.id === 'manage' || tab.id === 'files') && this.userCanDownload) return true
|
||||
if (tab.id !== 'manage' && tab.id !== 'files' && this.userCanUpdate) return true
|
||||
if ((tab.id === 'tools' || tab.id === 'files') && this.userCanDownload) return true
|
||||
if (tab.id !== 'tools' && tab.id !== 'files' && this.userCanUpdate) return true
|
||||
if (tab.id === 'match' && this.userCanUpdate) return true
|
||||
return false
|
||||
})
|
||||
|
@ -1,256 +0,0 @@
|
||||
<template>
|
||||
<div class="w-full h-full overflow-hidden overflow-y-auto px-4 py-6">
|
||||
<!-- Merge to m4b -->
|
||||
<div v-if="showM4bDownload" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div>
|
||||
<p class="text-lg">Make M4B Audiobook File <span class="text-error">*</span></p>
|
||||
<p class="max-w-sm text-sm pt-2 text-gray-300">Generate a .M4B audiobook file with embedded metadata, cover image, and chapters. <br /><span class="text-warning">*</span> Does not delete existing audio files.</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div class="mt-2 md:mt-0">
|
||||
<p v-if="abmergeStatus === $constants.DownloadStatus.FAILED" class="text-error mb-2">Download Failed</p>
|
||||
<p v-if="abmergeStatus === $constants.DownloadStatus.READY" class="text-success mb-2">Download Ready!</p>
|
||||
<p v-if="abmergeStatus === $constants.DownloadStatus.EXPIRED" class="text-error mb-2">Download Expired</p>
|
||||
|
||||
<ui-btn v-if="abmergeStatus !== $constants.DownloadStatus.READY" :loading="abmergeStatus === $constants.DownloadStatus.PENDING" :disabled="tempDisable" @click="startAudiobookMerge">Start Merge</ui-btn>
|
||||
<div v-else>
|
||||
<div class="flex">
|
||||
<!-- <ui-btn @click="downloadWithProgress(abmergeDownload)">Download</ui-btn>
|
||||
<ui-icon-btn small icon="delete" bg-color="error" class="ml-2" @click="removeDownload" /> -->
|
||||
</div>
|
||||
<p class="px-0.5 py-1 text-sm font-mono text-center">Size: {{ $bytesPretty(abmergeDownload.size) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Split to mp3 -->
|
||||
<div v-if="showMp3Split && showExperimentalFeatures" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="text-lg">Split M4B to MP3's</p>
|
||||
<p class="max-w-sm text-sm pt-2 text-gray-300">Generate multiple MP3's split by chapters with embedded metadata, cover image, and chapters. <br /><span class="text-warning">*</span> Does not delete existing audio files.</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div>
|
||||
<ui-btn :loading="abmergeStatus === $constants.DownloadStatus.PENDING" :disabled="true" @click="startAudiobookMerge">Not yet implemented</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Embed Metadata -->
|
||||
<div v-if="mediaTracks.length && showExperimentalFeatures" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="text-lg">Embed Metadata</p>
|
||||
<p class="max-w-sm text-sm pt-2 text-gray-300">Embed metadata into audio files including cover image and chapters. <br /><span class="text-warning">*</span> Modifies audio files.</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div>
|
||||
<ui-btn :to="`/item/${libraryItemId}/manage`" class="flex items-center"
|
||||
>Open Manager
|
||||
<span class="material-icons text-lg ml-2">launch</span>
|
||||
</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="showM4bDownload" class="text-left text-base mb-4 py-4">
|
||||
<span class="text-error">* <strong>Experimental</strong></span
|
||||
> - M4b merge can take several minutes and will be stored in <span class="bg-primary bg-opacity-75 font-mono p-1 text-base">/metadata/downloads</span>. After the download is ready, it will remain available for 60 minutes, then be deleted. Download will timeout after 30 minutes.
|
||||
</p>
|
||||
|
||||
<!-- <p v-if="isSingleM4b" class="text-lg text-center my-8">Audiobook is already a single m4b!</p> -->
|
||||
<p v-if="!mediaTracks.length" class="text-lg text-center my-8">No audio tracks to merge</p>
|
||||
|
||||
<div v-if="isDownloading" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 z-50 flex items-center justify-center">
|
||||
<div class="w-80 border border-black-400 bg-bg rounded-xl h-20">
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<p class="text-lg">Download.... {{ downloadPercent }}%</p>
|
||||
<p class="w-24 font-mono pl-8 text-right">
|
||||
{{ downloadAmount }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
processing: Boolean,
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tempDisable: false,
|
||||
isDownloading: false,
|
||||
downloadPercent: '0',
|
||||
downloadAmount: '0 KB'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
abmergeStatus(newVal) {
|
||||
if (newVal) {
|
||||
this.tempDisable = false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showExperimentalFeatures() {
|
||||
return this.$store.state.showExperimentalFeatures
|
||||
},
|
||||
libraryItemId() {
|
||||
return this.libraryItem ? this.libraryItem.id : null
|
||||
},
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
downloads() {
|
||||
return this.$store.getters['downloads/getDownloads'](this.libraryItemId)
|
||||
},
|
||||
abmergeDownload() {
|
||||
return this.downloads.find((d) => d.type === 'abmerge')
|
||||
},
|
||||
abmergeStatus() {
|
||||
return this.abmergeDownload ? this.abmergeDownload.status : false
|
||||
},
|
||||
libraryFiles() {
|
||||
return this.libraryItem.libraryFiles
|
||||
},
|
||||
totalFiles() {
|
||||
return this.libraryFiles.length
|
||||
},
|
||||
mediaTracks() {
|
||||
return this.media.tracks || []
|
||||
},
|
||||
isSingleM4b() {
|
||||
return this.mediaTracks.length === 1 && this.mediaTracks[0].metadata.ext.toLowerCase() === '.m4b'
|
||||
},
|
||||
chapters() {
|
||||
return this.media.chapters || []
|
||||
},
|
||||
showM4bDownload() {
|
||||
if (!this.mediaTracks.length) return false
|
||||
return !this.isSingleM4b
|
||||
},
|
||||
showMp3Split() {
|
||||
if (!this.mediaTracks.length) return false
|
||||
return this.isSingleM4b && this.chapters.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeDownload() {
|
||||
if (!this.abmergeDownload) return
|
||||
if (!confirm(`Are you sure you want to remove this merge download?`)) return
|
||||
|
||||
var downloadId = this.abmergeDownload.id
|
||||
|
||||
this.tempDisable = true
|
||||
this.$axios
|
||||
.$delete(`/api/ab-manager-tasks/${downloadId}`)
|
||||
.then(() => {
|
||||
this.tempDisable = false
|
||||
this.$toast.success('Merge download deleted')
|
||||
this.$store.commit('downloads/removeDownload', { id: downloadId })
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||
this.$toast.error(errorMsg)
|
||||
this.tempDisable = false
|
||||
})
|
||||
},
|
||||
startAudiobookMerge() {
|
||||
this.tempDisable = true
|
||||
|
||||
this.$axios
|
||||
.$get(`/api/audiobook-merge/${this.libraryItemId}`)
|
||||
.then(() => {
|
||||
this.tempDisable = false
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||
this.$toast.error(errorMsg)
|
||||
this.tempDisable = false
|
||||
})
|
||||
},
|
||||
downloadWithProgress(download) {
|
||||
var downloadId = download.id
|
||||
var downloadUrl = `${process.env.serverUrl}/api/ab-manager-tasks/${downloadId}`
|
||||
var filename = download.filename
|
||||
|
||||
this.isDownloading = true
|
||||
|
||||
var request = new XMLHttpRequest()
|
||||
request.responseType = 'blob'
|
||||
request.open('get', downloadUrl, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${this.$store.getters['user/getToken']}`)
|
||||
request.send()
|
||||
|
||||
request.onreadystatechange = () => {
|
||||
if (request.readyState === 4) {
|
||||
this.isDownloading = false
|
||||
}
|
||||
if (request.readyState == 4 && request.status == 200) {
|
||||
const url = window.URL.createObjectURL(request.response)
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = url
|
||||
anchor.download = filename
|
||||
document.body.appendChild(anchor)
|
||||
anchor.click()
|
||||
setTimeout(() => {
|
||||
if (anchor) anchor.remove()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = (err) => {
|
||||
console.error('Download error', err)
|
||||
this.isDownloading = false
|
||||
}
|
||||
|
||||
request.onprogress = (e) => {
|
||||
const percent_complete = Math.floor((e.loaded / e.total) * 100)
|
||||
this.downloadAmount = this.$bytesPretty(e.loaded)
|
||||
this.downloadPercent = percent_complete
|
||||
|
||||
// const duration = (new Date().getTime() - startTime) / 1000
|
||||
// const bps = e.loaded / duration
|
||||
// const kbps = Math.floor(bps / 1024)
|
||||
// const time = (e.total - e.loaded) / bps
|
||||
// const seconds = Math.floor(time % 60)
|
||||
// const minutes = Math.floor(time / 60)
|
||||
// console.log(`${percent_complete}% - ${kbps} Kbps - ${minutes} min ${seconds} sec remaining`)
|
||||
}
|
||||
},
|
||||
loadDownloads() {
|
||||
this.$axios
|
||||
.$get(`/api/ab-manager-tasks`)
|
||||
.then((data) => {
|
||||
var pendingTasks = data.pendingTasks.map((pd) => {
|
||||
pd.download.status = this.$constants.DownloadStatus.PENDING
|
||||
return pd.download
|
||||
})
|
||||
var tasks = data.tasks.map((d) => {
|
||||
d.status = this.$constants.DownloadStatus.READY
|
||||
return d
|
||||
})
|
||||
var allTasks = tasks.concat(pendingTasks)
|
||||
this.$store.commit('downloads/setDownloads', allTasks)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to load downloads', error)
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadDownloads()
|
||||
}
|
||||
}
|
||||
</script>
|
100
client/components/modals/item/tabs/Tools.vue
Normal file
100
client/components/modals/item/tabs/Tools.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="w-full h-full overflow-hidden overflow-y-auto px-4 py-6">
|
||||
<p class="text-xl font-semibold mb-2">Audiobook File Management Tools</p>
|
||||
|
||||
<!-- Merge to m4b -->
|
||||
<div v-if="showM4bDownload" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div>
|
||||
<p class="text-lg">Make M4B Audiobook File</p>
|
||||
<p class="max-w-sm text-sm pt-2 text-gray-300">Generate a .M4B audiobook file with embedded metadata, cover image, and chapters.</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div>
|
||||
<ui-btn :to="`/audiobook/${libraryItemId}/manage?tool=m4b`" class="flex items-center"
|
||||
>Open Manager
|
||||
<span class="material-icons text-lg ml-2">launch</span>
|
||||
</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Split to mp3 -->
|
||||
<div v-if="showMp3Split && showExperimentalFeatures" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="text-lg">Split M4B to MP3's</p>
|
||||
<p class="max-w-sm text-sm pt-2 text-gray-300">Generate multiple MP3's split by chapters with embedded metadata, cover image, and chapters.</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div>
|
||||
<ui-btn :disabled="true">Not yet implemented</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Embed Metadata -->
|
||||
<div v-if="mediaTracks.length" class="w-full border border-black-200 p-4 my-8">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="text-lg">Embed Metadata</p>
|
||||
<p class="max-w-sm text-sm pt-2 text-gray-300">Embed metadata into audio files including cover image and chapters.</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div>
|
||||
<ui-btn :to="`/audiobook/${libraryItemId}/manage`" class="flex items-center"
|
||||
>Open Manager
|
||||
<span class="material-icons text-lg ml-2">launch</span>
|
||||
</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="!mediaTracks.length" class="text-lg text-center my-8">No audio tracks</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
processing: Boolean,
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
showExperimentalFeatures() {
|
||||
return this.$store.state.showExperimentalFeatures
|
||||
},
|
||||
libraryItemId() {
|
||||
return this.libraryItem ? this.libraryItem.id : null
|
||||
},
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
mediaTracks() {
|
||||
return this.media.tracks || []
|
||||
},
|
||||
isSingleM4b() {
|
||||
return this.mediaTracks.length === 1 && this.mediaTracks[0].metadata.ext.toLowerCase() === '.m4b'
|
||||
},
|
||||
chapters() {
|
||||
return this.media.chapters || []
|
||||
},
|
||||
showM4bDownload() {
|
||||
if (!this.mediaTracks.length) return false
|
||||
return !this.isSingleM4b
|
||||
},
|
||||
showMp3Split() {
|
||||
if (!this.mediaTracks.length) return false
|
||||
return this.isSingleM4b && this.chapters.length
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
@ -1,9 +1,20 @@
|
||||
<template>
|
||||
<div id="page-wrapper" class="bg-bg page p-8 overflow-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="flex items-center justify-center mb-6">
|
||||
<div class="w-full max-w-2xl">
|
||||
<p class="text-xl mb-2">Metadata to embed</p>
|
||||
<p class="mb-4 text-base text-gray-300">audiobookshelf uses <a href="https://github.com/sandreas/tone" target="_blank" class="hover:underline text-blue-400 hover:text-blue-300">tone</a> to write metadata.</p>
|
||||
<p class="text-2xl mb-2">Audiobook File Management Tools</p>
|
||||
</div>
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="flex justify-end">
|
||||
<ui-dropdown v-model="selectedTool" :items="availableTools" :disabled="processing" class="max-w-sm" @input="selectedToolUpdated" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<div class="w-full max-w-2xl">
|
||||
<p class="text-xl mb-1">Metadata to embed</p>
|
||||
<p class="mb-2 text-base text-gray-300">audiobookshelf uses <a href="https://github.com/sandreas/tone" target="_blank" class="hover:underline text-blue-400 hover:text-blue-300">tone</a> to write metadata.</p>
|
||||
</div>
|
||||
<div class="w-full max-w-2xl"></div>
|
||||
</div>
|
||||
@ -26,12 +37,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full max-w-2xl border border-white border-opacity-10 bg-bg mx-2">
|
||||
<div class="flex py-2 px-4">
|
||||
<div class="flex py-2 px-4 bg-primary bg-opacity-25">
|
||||
<div class="flex-grow text-xs font-semibold uppercase text-gray-200">Chapter Title</div>
|
||||
<div class="w-24 text-xs font-semibold uppercase text-gray-200">Start</div>
|
||||
<div class="w-24 text-xs font-semibold uppercase text-gray-200">End</div>
|
||||
</div>
|
||||
<div class="w-full max-h-72 overflow-auto">
|
||||
<p v-if="!metadataChapters.length" class="py-5 text-center text-gray-200">No chapters</p>
|
||||
<template v-for="(chapter, index) in metadataChapters">
|
||||
<div :key="index" class="flex py-1 px-4 text-sm" :class="index % 2 === 0 ? 'bg-primary bg-opacity-25' : ''">
|
||||
<div class="flex-grow font-semibold">{{ chapter.title }}</div>
|
||||
@ -50,18 +62,44 @@
|
||||
<div class="w-full h-px bg-white bg-opacity-10 my-8" />
|
||||
|
||||
<div class="w-full max-w-4xl mx-auto">
|
||||
<div class="w-full flex justify-between items-center mb-2">
|
||||
<p class="text-warning text-lg font-semibold">Warning: Modifies your audio files</p>
|
||||
<ui-btn v-if="!embedFinished" color="primary" :loading="updatingMetadata" @click.stop="embedClick">Embed Metadata</ui-btn>
|
||||
<div v-if="selectedTool === 'embed'" class="w-full flex justify-end items-center mb-4">
|
||||
<ui-btn v-if="!isFinished" color="primary" :loading="processing" @click.stop="embedClick">Start Metadata Embed</ui-btn>
|
||||
<p v-else class="text-success text-lg font-semibold">Embed Finished!</p>
|
||||
</div>
|
||||
<div class="flex mb-4">
|
||||
<p class="text-gray-200">
|
||||
A backup of your audio files will be stored in <span class="rounded-md bg-neutral-600 text-sm text-white py-0.5 px-1 font-mono">/metadata/cache/items/{{ libraryItemId }}/</span> so you can restore the originals if necessary.
|
||||
</p>
|
||||
<div v-else class="w-full flex justify-end items-center mb-4">
|
||||
<ui-btn v-if="!isFinished" color="primary" :loading="processing" @click.stop="encodeM4bClick">Start M4B Encode</ui-btn>
|
||||
<p v-else class="text-success text-lg font-semibold">M4B Finished!</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div v-if="selectedTool === 'embed'" class="flex items-start mb-2">
|
||||
<span class="material-icons text-base text-warning pt-1">star</span>
|
||||
<p class="text-gray-200 ml-2">Metadata will be embedded on the audio tracks inside your audiobook folder.</p>
|
||||
</div>
|
||||
<div v-else class="flex items-start mb-2">
|
||||
<span class="material-icons text-base text-warning pt-1">star</span>
|
||||
<p class="text-gray-200 ml-2">
|
||||
Finished M4B will be put into your audiobook folder at <span class="rounded-md bg-neutral-600 text-sm text-white py-0.5 px-1 font-mono">.../{{ libraryItemRelPath }}/</span>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start mb-2">
|
||||
<span class="material-icons text-base text-warning pt-1">star</span>
|
||||
<p class="text-gray-200 ml-2">
|
||||
A backup of your original audio files will be stored in <span class="rounded-md bg-neutral-600 text-sm text-white py-0.5 px-1 font-mono">/metadata/cache/items/{{ libraryItemId }}/</span> so you can restore the originals if necessary.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-start mb-2">
|
||||
<span class="material-icons text-base text-warning pt-1">star</span>
|
||||
<p class="text-gray-200 ml-2">Once the task is started you can navigate away from this page.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full max-w-4xl mx-auto">
|
||||
<p class="mb-2 font-semibold">Audio Tracks</p>
|
||||
<div class="w-full mx-auto border border-white border-opacity-10 bg-bg">
|
||||
<div class="flex py-2 px-4">
|
||||
<div class="flex py-2 px-4 bg-primary bg-opacity-25">
|
||||
<div class="w-10 text-xs font-semibold text-gray-200">#</div>
|
||||
<div class="flex-grow text-xs font-semibold uppercase text-gray-200">Filename</div>
|
||||
<div class="w-16 text-xs font-semibold uppercase text-gray-200">Size</div>
|
||||
@ -124,15 +162,19 @@ export default {
|
||||
return {
|
||||
audiofilesEncoding: {},
|
||||
audiofilesFinished: {},
|
||||
updatingMetadata: false,
|
||||
embedFinished: false,
|
||||
toneObject: null
|
||||
processing: false,
|
||||
isFinished: false,
|
||||
toneObject: null,
|
||||
selectedTool: 'embed'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
libraryItemId() {
|
||||
return this.libraryItem.id
|
||||
},
|
||||
libraryItemRelPath() {
|
||||
return this.libraryItem.relPath
|
||||
},
|
||||
media() {
|
||||
return this.libraryItem.media || {}
|
||||
},
|
||||
@ -140,16 +182,42 @@ export default {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
audioFiles() {
|
||||
return this.media.audioFiles || []
|
||||
return (this.media.audioFiles || []).filter((af) => !af.exclude && !af.invalid)
|
||||
},
|
||||
isSingleM4b() {
|
||||
return this.audioFiles.length === 1 && this.audioFiles[0].metadata.ext.toLowerCase() === '.m4b'
|
||||
},
|
||||
streamLibraryItem() {
|
||||
return this.$store.state.streamLibraryItem
|
||||
},
|
||||
metadataChapters() {
|
||||
return this.media.chapters || []
|
||||
},
|
||||
availableTools() {
|
||||
if (this.isSingleM4b) {
|
||||
return [{ value: 'embed', text: 'Embed Metadata' }]
|
||||
} else {
|
||||
return [
|
||||
{ value: 'embed', text: 'Embed Metadata' },
|
||||
{ value: 'm4b', text: 'M4B Encoder' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
encodeM4bClick() {
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$get(`/api/audiobook-merge/${this.libraryItemId}`)
|
||||
.then(() => {
|
||||
this.processing = false
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||
this.$toast.error(errorMsg)
|
||||
this.processing = false
|
||||
})
|
||||
},
|
||||
embedClick() {
|
||||
const payload = {
|
||||
message: `Are you sure you want to embed metadata in ${this.audioFiles.length} audio files?`,
|
||||
@ -163,7 +231,7 @@ export default {
|
||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||
},
|
||||
updateAudioFileMetadata() {
|
||||
this.updatingMetadata = true
|
||||
this.processing = true
|
||||
this.$axios
|
||||
.$get(`/api/items/${this.libraryItemId}/audio-metadata?tone=1`)
|
||||
.then(() => {
|
||||
@ -171,20 +239,20 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Audio metadata encode failed', error)
|
||||
this.updatingMetadata = false
|
||||
this.processing = false
|
||||
})
|
||||
},
|
||||
audioMetadataStarted(data) {
|
||||
console.log('audio metadata started', data)
|
||||
if (data.libraryItemId !== this.libraryItemId) return
|
||||
this.audiofilesFinished = {}
|
||||
this.updatingMetadata = true
|
||||
this.processing = true
|
||||
},
|
||||
audioMetadataFinished(data) {
|
||||
console.log('audio metadata finished', data)
|
||||
if (data.libraryItemId !== this.libraryItemId) return
|
||||
this.updatingMetadata = false
|
||||
this.embedFinished = true
|
||||
this.processing = false
|
||||
this.isFinished = true
|
||||
this.audiofilesEncoding = {}
|
||||
this.$toast.success('Audio file metadata updated')
|
||||
},
|
||||
@ -197,6 +265,20 @@ export default {
|
||||
this.$set(this.audiofilesEncoding, data.ino, false)
|
||||
this.$set(this.audiofilesFinished, data.ino, true)
|
||||
},
|
||||
selectedToolUpdated() {
|
||||
let newurl = window.location.protocol + '//' + window.location.host + window.location.pathname + `?tool=${this.selectedTool}`
|
||||
window.history.replaceState({ path: newurl }, '', newurl)
|
||||
},
|
||||
init() {
|
||||
this.fetchToneObject()
|
||||
if (this.$route.query.tool === 'm4b') {
|
||||
if (this.availableTools.some((t) => t.value === 'm4b')) {
|
||||
this.selectedTool = 'm4b'
|
||||
} else {
|
||||
this.selectedToolUpdated()
|
||||
}
|
||||
}
|
||||
},
|
||||
fetchToneObject() {
|
||||
this.$axios
|
||||
.$get(`/api/items/${this.libraryItemId}/tone-object`)
|
||||
@ -210,7 +292,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchToneObject()
|
||||
this.init()
|
||||
this.$root.socket.on('audio_metadata_started', this.audioMetadataStarted)
|
||||
this.$root.socket.on('audio_metadata_finished', this.audioMetadataFinished)
|
||||
this.$root.socket.on('audiofile_metadata_started', this.audiofileMetadataStarted)
|
@ -223,10 +223,10 @@ class Server {
|
||||
// Client dynamic routes
|
||||
const dyanimicRoutes = [
|
||||
'/item/:id',
|
||||
'/item/:id/manage',
|
||||
'/author/:id',
|
||||
'/audiobook/:id/chapters',
|
||||
'/audiobook/:id/edit',
|
||||
'/audiobook/:id/manage',
|
||||
'/library/:library',
|
||||
'/library/:library/search',
|
||||
'/library/:library/bookshelf/:id?',
|
||||
|
@ -84,8 +84,8 @@ class MiscController {
|
||||
|
||||
// GET: api/audiobook-merge/:id
|
||||
async mergeAudiobook(req, res) {
|
||||
if (!req.user.canDownload) {
|
||||
Logger.error('User attempting to download without permission', req.user)
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error('[MiscController] mergeAudiobook: Non-admin user attempting to make m4b', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user