mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			270 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			270 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <div id="page-wrapper" class="bg-bg page p-8 overflow-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
 | |
|     <div class="flex justify-center mb-2">
 | |
|       <div class="w-full max-w-2xl">
 | |
|         <p class="text-xl">Metadata to embed</p>
 | |
|       </div>
 | |
|       <div class="w-full max-w-2xl"></div>
 | |
|     </div>
 | |
|     <div class="flex justify-center flex-wrap">
 | |
|       <div class="w-full max-w-2xl border border-opacity-10 bg-bg mx-2">
 | |
|         <div class="flex py-2 px-4">
 | |
|           <div class="w-1/3 text-xs font-semibold uppercase text-gray-200">Meta Tag</div>
 | |
|           <div class="w-2/3 text-xs font-semibold uppercase text-gray-200">Value</div>
 | |
|         </div>
 | |
|         <div class="w-full max-h-72 overflow-auto">
 | |
|           <template v-for="(keyValue, index) in metadataKeyValues">
 | |
|             <div :key="keyValue.key" class="flex py-1 px-4 text-sm" :class="index % 2 === 0 ? 'bg-primary bg-opacity-25' : ''">
 | |
|               <div class="w-1/3 font-semibold">{{ keyValue.key }}</div>
 | |
|               <div class="w-2/3">
 | |
|                 {{ keyValue.value }}
 | |
|               </div>
 | |
|             </div>
 | |
|           </template>
 | |
|         </div>
 | |
|       </div>
 | |
|       <div class="w-full max-w-2xl border border-opacity-10 bg-bg mx-2">
 | |
|         <div class="flex py-2 px-4">
 | |
|           <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">
 | |
|           <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>
 | |
|               <div class="w-24">
 | |
|                 {{ chapter.start.toFixed(2) }}
 | |
|               </div>
 | |
|               <div class="w-24">
 | |
|                 {{ chapter.end.toFixed(2) }}
 | |
|               </div>
 | |
|             </div>
 | |
|           </template>
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>
 | |
| 
 | |
|     <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-4">
 | |
|         <p class="text-warning text-lg font-semibold">Warning: Modifies your audio files</p>
 | |
|         <ui-btn v-if="!embedFinished" color="primary" :loading="updatingMetadata" @click="updateAudioFileMetadata">Embed Metadata</ui-btn>
 | |
|         <p v-else class="text-success text-lg font-semibold">Embed Finished!</p>
 | |
|       </div>
 | |
|       <div class="w-full mx-auto border border-opacity-10 bg-bg">
 | |
|         <div class="flex py-2 px-4">
 | |
|           <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>
 | |
|           <div class="w-24"></div>
 | |
|         </div>
 | |
|         <template v-for="file in audioFiles">
 | |
|           <div :key="file.index" class="flex py-2 px-4 text-sm" :class="file.index % 2 === 0 ? 'bg-primary bg-opacity-25' : ''">
 | |
|             <div class="w-10">{{ file.index }}</div>
 | |
|             <div class="flex-grow">
 | |
|               {{ file.metadata.filename }}
 | |
|             </div>
 | |
|             <div class="w-16 font-mono text-gray-200">
 | |
|               {{ $bytesPretty(file.metadata.size) }}
 | |
|             </div>
 | |
|             <div class="w-24">
 | |
|               <div class="flex justify-center">
 | |
|                 <span v-if="audiofilesFinished[file.ino]" class="material-icons text-xl text-success leading-none">check_circle</span>
 | |
|                 <div v-else-if="audiofilesEncoding[file.ino]">
 | |
|                   <widgets-loading-spinner />
 | |
|                 </div>
 | |
|               </div>
 | |
|             </div>
 | |
|           </div>
 | |
|         </template>
 | |
|       </div>
 | |
|     </div>
 | |
|   </div>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| export default {
 | |
|   async asyncData({ store, params, app, redirect, route }) {
 | |
|     if (!store.state.user.user) {
 | |
|       return redirect(`/login?redirect=${route.path}`)
 | |
|     }
 | |
|     if (!store.getters['user/getIsAdminOrUp']) {
 | |
|       return redirect('/?error=unauthorized')
 | |
|     }
 | |
|     var libraryItem = await app.$axios.$get(`/api/items/${params.id}?expanded=1`).catch((error) => {
 | |
|       console.error('Failed', error)
 | |
|       return false
 | |
|     })
 | |
|     if (!libraryItem) {
 | |
|       console.error('Not found...', params.id)
 | |
|       return redirect('/?error=not found')
 | |
|     }
 | |
|     if (libraryItem.mediaType !== 'book') {
 | |
|       console.error('Invalid media type')
 | |
|       return redirect('/?error=invalid media type')
 | |
|     }
 | |
|     if (!libraryItem.media.audioFiles.length) {
 | |
|       cnosole.error('No audio files')
 | |
|       return redirect('/?error=no audio files')
 | |
|     }
 | |
|     return {
 | |
|       libraryItem
 | |
|     }
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       audiofilesEncoding: {},
 | |
|       audiofilesFinished: {},
 | |
|       updatingMetadata: false,
 | |
|       embedFinished: false
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     libraryItemId() {
 | |
|       return this.libraryItem.id
 | |
|     },
 | |
|     media() {
 | |
|       return this.libraryItem.media || {}
 | |
|     },
 | |
|     mediaMetadata() {
 | |
|       return this.media.metadata || {}
 | |
|     },
 | |
|     audioFiles() {
 | |
|       return this.media.audioFiles || []
 | |
|     },
 | |
|     streamLibraryItem() {
 | |
|       return this.$store.state.streamLibraryItem
 | |
|     },
 | |
|     metadataKeyValues() {
 | |
|       const keyValues = [
 | |
|         {
 | |
|           key: 'title',
 | |
|           value: this.mediaMetadata.title
 | |
|         },
 | |
|         {
 | |
|           key: 'artist',
 | |
|           value: this.mediaMetadata.authorName
 | |
|         },
 | |
|         {
 | |
|           key: 'album_artist',
 | |
|           value: this.mediaMetadata.authorName
 | |
|         },
 | |
|         {
 | |
|           key: 'date',
 | |
|           value: this.mediaMetadata.publishedYear
 | |
|         },
 | |
|         {
 | |
|           key: 'description',
 | |
|           value: this.mediaMetadata.description
 | |
|         },
 | |
|         {
 | |
|           key: 'genre',
 | |
|           value: this.mediaMetadata.genres.join(';')
 | |
|         },
 | |
|         {
 | |
|           key: 'performer',
 | |
|           value: this.mediaMetadata.narratorName
 | |
|         }
 | |
|       ]
 | |
| 
 | |
|       if (this.mediaMetadata.subtitle) {
 | |
|         keyValues.push({
 | |
|           key: 'subtitle',
 | |
|           value: this.mediaMetadata.subtitle
 | |
|         })
 | |
|       }
 | |
| 
 | |
|       if (this.mediaMetadata.asin) {
 | |
|         keyValues.push({
 | |
|           key: 'asin',
 | |
|           value: this.mediaMetadata.asin
 | |
|         })
 | |
|       }
 | |
|       if (this.mediaMetadata.isbn) {
 | |
|         keyValues.push({
 | |
|           key: 'isbn',
 | |
|           value: this.mediaMetadata.isbn
 | |
|         })
 | |
|       }
 | |
|       if (this.mediaMetadata.language) {
 | |
|         keyValues.push({
 | |
|           key: 'language',
 | |
|           value: this.mediaMetadata.language
 | |
|         })
 | |
|       }
 | |
|       if (this.mediaMetadata.series.length) {
 | |
|         var firstSeries = this.mediaMetadata.series[0]
 | |
|         keyValues.push({
 | |
|           key: 'series',
 | |
|           value: firstSeries.name
 | |
|         })
 | |
|         if (firstSeries.sequence) {
 | |
|           keyValues.push({
 | |
|             key: 'series-part',
 | |
|             value: firstSeries.sequence
 | |
|           })
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return keyValues
 | |
|     },
 | |
|     metadataChapters() {
 | |
|       var chapters = this.media.chapters || []
 | |
|       return chapters.concat(chapters)
 | |
|     }
 | |
|   },
 | |
|   methods: {
 | |
|     updateAudioFileMetadata() {
 | |
|       if (confirm(`Warning!\n\nThis will modify the audio files for this audiobook.\nMake sure your audio files are backed up before using this feature.`)) {
 | |
|         this.updatingMetadata = true
 | |
|         this.$axios
 | |
|           .$get(`/api/items/${this.libraryItemId}/audio-metadata`)
 | |
|           .then(() => {
 | |
|             console.log('Audio metadata encode started')
 | |
|           })
 | |
|           .catch((error) => {
 | |
|             console.error('Audio metadata encode failed', error)
 | |
|             this.updatingMetadata = false
 | |
|           })
 | |
|       }
 | |
|     },
 | |
|     audioMetadataStarted(data) {
 | |
|       console.log('audio metadata started', data)
 | |
|       if (data.libraryItemId !== this.libraryItemId) return
 | |
|       this.audiofilesFinished = {}
 | |
|       this.updatingMetadata = true
 | |
|     },
 | |
|     audioMetadataFinished(data) {
 | |
|       console.log('audio metadata finished', data)
 | |
|       if (data.libraryItemId !== this.libraryItemId) return
 | |
|       this.updatingMetadata = false
 | |
|       this.embedFinished = true
 | |
|       this.audiofilesEncoding = {}
 | |
|       this.$toast.success('Audio file metadata updated')
 | |
|     },
 | |
|     audiofileMetadataStarted(data) {
 | |
|       if (data.libraryItemId !== this.libraryItemId) return
 | |
|       this.$set(this.audiofilesEncoding, data.ino, true)
 | |
|     },
 | |
|     audiofileMetadataFinished(data) {
 | |
|       if (data.libraryItemId !== this.libraryItemId) return
 | |
|       this.$set(this.audiofilesEncoding, data.ino, false)
 | |
|       this.$set(this.audiofilesFinished, data.ino, true)
 | |
|     }
 | |
|   },
 | |
|   mounted() {
 | |
|     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)
 | |
|     this.$root.socket.on('audiofile_metadata_finished', this.audiofileMetadataFinished)
 | |
|   },
 | |
|   beforeDestroy() {
 | |
|     this.$root.socket.off('audio_metadata_started', this.audioMetadataStarted)
 | |
|     this.$root.socket.off('audio_metadata_finished', this.audioMetadataFinished)
 | |
|     this.$root.socket.off('audiofile_metadata_started', this.audiofileMetadataStarted)
 | |
|     this.$root.socket.off('audiofile_metadata_finished', this.audiofileMetadataFinished)
 | |
|   }
 | |
| }
 | |
| </script> |