mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-06 07:59:43 +00:00
Merge branch 'master' of github.com:advplyr/audiobookshelf
This commit is contained in:
commit
6ea5348460
50 changed files with 1434 additions and 563 deletions
|
|
@ -217,6 +217,16 @@ export default {
|
|||
})
|
||||
}
|
||||
|
||||
if (this.results.episodes?.length) {
|
||||
shelves.push({
|
||||
id: 'episodes',
|
||||
label: 'Episodes',
|
||||
labelStringKey: 'LabelEpisodes',
|
||||
type: 'episode',
|
||||
entities: this.results.episodes.map((res) => res.libraryItem)
|
||||
})
|
||||
}
|
||||
|
||||
if (this.results.series?.length) {
|
||||
shelves.push({
|
||||
id: 'series',
|
||||
|
|
|
|||
|
|
@ -274,15 +274,10 @@ export default {
|
|||
isAuthorsPage() {
|
||||
return this.page === 'authors'
|
||||
},
|
||||
isAlbumsPage() {
|
||||
return this.page === 'albums'
|
||||
},
|
||||
numShowing() {
|
||||
return this.totalEntities
|
||||
},
|
||||
entityName() {
|
||||
if (this.isAlbumsPage) return 'Albums'
|
||||
|
||||
if (this.isPodcastLibrary) return this.$strings.LabelPodcasts
|
||||
if (!this.page) return this.$strings.LabelBooks
|
||||
if (this.isSeriesPage) return this.$strings.LabelSeries
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="flex items-center h-full px-1 overflow-hidden">
|
||||
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<div class="grow px-2 episodeSearchCardContent">
|
||||
<p class="truncate text-sm">{{ episodeTitle }}</p>
|
||||
<p class="text-xs text-gray-200 truncate">{{ podcastTitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
episode: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
bookCoverAspectRatio() {
|
||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||
},
|
||||
coverWidth() {
|
||||
if (this.bookCoverAspectRatio === 1) return 50 * 1.2
|
||||
return 50
|
||||
},
|
||||
media() {
|
||||
return this.libraryItem?.media || {}
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
episodeTitle() {
|
||||
return this.episode.title || 'No Title'
|
||||
},
|
||||
podcastTitle() {
|
||||
return this.mediaMetadata.title || 'No Title'
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.episodeSearchCardContent {
|
||||
width: calc(100% - 80px);
|
||||
height: 75px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
<template>
|
||||
<div ref="card" :id="`album-card-${index}`" :style="{ width: cardWidth + 'px' }" class="absolute top-0 left-0 rounded-xs z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||
<div class="relative" :style="{ height: coverHeight + 'px' }">
|
||||
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
||||
<div class="w-full h-full bg-primary relative rounded-sm overflow-hidden">
|
||||
<covers-preview-cover ref="cover" :src="coverSrc" :width="cardWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative w-full">
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-xs border" :style="{ padding: `0em ${0.5}em` }">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8e h-8e py-1e rounded-md text-center">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ artist || ' ' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
index: Number,
|
||||
width: Number,
|
||||
height: {
|
||||
type: Number,
|
||||
default: 192
|
||||
},
|
||||
bookshelfView: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
albumMount: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
album: null,
|
||||
isSelectionMode: false,
|
||||
selected: false,
|
||||
isHovering: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
bookCoverAspectRatio() {
|
||||
return this.store.getters['libraries/getBookCoverAspectRatio']
|
||||
},
|
||||
cardWidth() {
|
||||
return this.width || this.coverHeight
|
||||
},
|
||||
coverHeight() {
|
||||
return this.height * this.sizeMultiplier
|
||||
},
|
||||
/*
|
||||
cardHeight() {
|
||||
return this.coverHeight + this.bottomTextHeight
|
||||
},
|
||||
bottomTextHeight() {
|
||||
if (!this.isAlternativeBookshelfView) return 0
|
||||
const lineHeight = 1.5
|
||||
const remSize = 16
|
||||
const baseHeight = this.sizeMultiplier * lineHeight * remSize
|
||||
const titleHeight = this.labelFontSize * baseHeight
|
||||
const paddingHeight = 4 * 2 * this.sizeMultiplier // py-1
|
||||
return titleHeight + paddingHeight
|
||||
},
|
||||
*/
|
||||
coverSrc() {
|
||||
const config = this.$config || this.$nuxt.$config
|
||||
if (!this.album || !this.album.libraryItemId) return `${config.routerBasePath}/book_placeholder.jpg`
|
||||
return this.store.getters['globals/getLibraryItemCoverSrcById'](this.album.libraryItemId)
|
||||
},
|
||||
labelFontSize() {
|
||||
if (this.width < 160) return 0.75
|
||||
return 0.9
|
||||
},
|
||||
sizeMultiplier() {
|
||||
return this.store.getters['user/getSizeMultiplier']
|
||||
},
|
||||
title() {
|
||||
return this.album ? this.album.title : ''
|
||||
},
|
||||
artist() {
|
||||
return this.album ? this.album.artist : ''
|
||||
},
|
||||
store() {
|
||||
return this.$store || this.$nuxt.$store
|
||||
},
|
||||
currentLibraryId() {
|
||||
return this.store.state.libraries.currentLibraryId
|
||||
},
|
||||
isAlternativeBookshelfView() {
|
||||
const constants = this.$constants || this.$nuxt.$constants
|
||||
return this.bookshelfView == constants.BookshelfView.DETAIL
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setEntity(album) {
|
||||
this.album = album
|
||||
},
|
||||
setSelectionMode(val) {
|
||||
this.isSelectionMode = val
|
||||
},
|
||||
mouseover() {
|
||||
this.isHovering = true
|
||||
},
|
||||
mouseleave() {
|
||||
this.isHovering = false
|
||||
},
|
||||
clickCard() {
|
||||
if (!this.album) return
|
||||
// const router = this.$router || this.$nuxt.$router
|
||||
// router.push(`/album/${this.$encode(this.title)}`)
|
||||
},
|
||||
clickEdit() {
|
||||
this.$emit('edit', this.album)
|
||||
},
|
||||
destroy() {
|
||||
// destroy the vue listeners, etc
|
||||
this.$destroy()
|
||||
|
||||
// remove the element from the DOM
|
||||
if (this.$el && this.$el.parentNode) {
|
||||
this.$el.parentNode.removeChild(this.$el)
|
||||
} else if (this.$el && this.$el.remove) {
|
||||
this.$el.remove()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.albumMount) {
|
||||
this.setEntity(this.albumMount)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -39,6 +39,15 @@
|
|||
</li>
|
||||
</template>
|
||||
|
||||
<p v-if="episodeResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">{{ $strings.LabelEpisodes }}</p>
|
||||
<template v-for="item in episodeResults">
|
||||
<li :key="item.libraryItem.recentEpisode.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
||||
<nuxt-link :to="`/item/${item.libraryItem.id}`">
|
||||
<cards-episode-search-card :episode="item.libraryItem.recentEpisode" :library-item="item.libraryItem" />
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<p v-if="authorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelAuthors }}</p>
|
||||
<template v-for="item in authorResults">
|
||||
<li :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
|
||||
|
|
@ -100,6 +109,7 @@ export default {
|
|||
isFetching: false,
|
||||
search: null,
|
||||
podcastResults: [],
|
||||
episodeResults: [],
|
||||
bookResults: [],
|
||||
authorResults: [],
|
||||
seriesResults: [],
|
||||
|
|
@ -115,7 +125,7 @@ export default {
|
|||
return this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
totalResults() {
|
||||
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length
|
||||
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length + this.episodeResults.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -132,6 +142,7 @@ export default {
|
|||
this.search = null
|
||||
this.lastSearch = null
|
||||
this.podcastResults = []
|
||||
this.episodeResults = []
|
||||
this.bookResults = []
|
||||
this.authorResults = []
|
||||
this.seriesResults = []
|
||||
|
|
@ -175,6 +186,7 @@ export default {
|
|||
if (!this.isFetching) return
|
||||
|
||||
this.podcastResults = searchResults.podcast || []
|
||||
this.episodeResults = searchResults.episodes || []
|
||||
this.bookResults = searchResults.book || []
|
||||
this.authorResults = searchResults.authors || []
|
||||
this.seriesResults = searchResults.series || []
|
||||
|
|
|
|||
|
|
@ -74,19 +74,12 @@ export default {
|
|||
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
|
||||
return true
|
||||
},
|
||||
queuedEmbedLIds() {
|
||||
return this.$store.state.tasks.queuedEmbedLIds || []
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
<template>
|
||||
<div id="lazy-episodes-table" class="w-full py-6">
|
||||
<div class="flex flex-wrap flex-col md:flex-row md:items-center mb-4">
|
||||
|
|
@ -176,6 +175,13 @@ export default {
|
|||
return episodeProgress && !episodeProgress.isFinished
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Swap values if sort descending
|
||||
if (this.sortDesc) {
|
||||
const temp = a
|
||||
a = b
|
||||
b = temp
|
||||
}
|
||||
|
||||
let aValue
|
||||
let bValue
|
||||
|
||||
|
|
@ -194,10 +200,23 @@ export default {
|
|||
if (!bValue) bValue = Number.MAX_VALUE
|
||||
}
|
||||
|
||||
if (this.sortDesc) {
|
||||
return String(bValue).localeCompare(String(aValue), undefined, { numeric: true, sensitivity: 'base' })
|
||||
const primaryCompare = String(aValue).localeCompare(String(bValue), undefined, { numeric: true, sensitivity: 'base' })
|
||||
if (primaryCompare !== 0 || this.sortKey === 'publishedAt') return primaryCompare
|
||||
|
||||
// When sorting by season, secondary sort is by episode number
|
||||
if (this.sortKey === 'season') {
|
||||
const aEpisode = a.episode || ''
|
||||
const bEpisode = b.episode || ''
|
||||
|
||||
const secondaryCompare = String(aEpisode).localeCompare(String(bEpisode), undefined, { numeric: true, sensitivity: 'base' })
|
||||
if (secondaryCompare !== 0) return secondaryCompare
|
||||
}
|
||||
return String(aValue).localeCompare(String(bValue), undefined, { numeric: true, sensitivity: 'base' })
|
||||
|
||||
// Final sort by publishedAt
|
||||
let aPubDate = a.publishedAt || Number.MAX_VALUE
|
||||
let bPubDate = b.publishedAt || Number.MAX_VALUE
|
||||
|
||||
return String(aPubDate).localeCompare(String(bPubDate), undefined, { numeric: true, sensitivity: 'base' })
|
||||
})
|
||||
},
|
||||
episodesList() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="inline-flex toggle-btn-wrapper shadow-md">
|
||||
<button v-for="item in items" :key="item.value" type="button" class="toggle-btn outline-hidden relative border border-gray-600 px-4 py-1" :class="{ selected: item.value === value }" @click.stop="clickBtn(item.value)">
|
||||
<button v-for="item in items" :key="item.value" type="button" :disabled="disabled" class="toggle-btn outline-hidden relative border border-gray-600 px-4 py-1" :class="{ selected: item.value === value }" @click.stop="clickBtn(item.value)">
|
||||
{{ item.text }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -9,13 +9,17 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: String,
|
||||
value: [String, Number],
|
||||
/**
|
||||
* [{ "text", "", "value": "" }]
|
||||
*/
|
||||
items: {
|
||||
type: Array,
|
||||
default: Object
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -76,10 +80,19 @@ export default {
|
|||
.toggle-btn.selected {
|
||||
color: white;
|
||||
}
|
||||
.toggle-btn.selected:disabled {
|
||||
color: white;
|
||||
}
|
||||
.toggle-btn.selected::before {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
button.toggle-btn.selected:disabled::before {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
button.toggle-btn:disabled::before {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
button.toggle-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
211
client/components/widgets/EncoderOptionsCard.vue
Normal file
211
client/components/widgets/EncoderOptionsCard.vue
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<div class="w-full py-2">
|
||||
<div class="flex -mb-px">
|
||||
<button type="button" :disabled="disabled" class="w-1/2 h-8 rounded-tl-md relative border border-black-200 flex items-center justify-center disabled:cursor-not-allowed" :class="!showAdvancedView ? 'text-white bg-bg hover:bg-bg/60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary/70 hover:bg-primary/60'" @click="showAdvancedView = false">
|
||||
<p class="text-sm">{{ $strings.HeaderPresets }}</p>
|
||||
</button>
|
||||
<button type="button" :disabled="disabled" class="w-1/2 h-8 rounded-tr-md relative border border-black-200 flex items-center justify-center -ml-px disabled:cursor-not-allowed" :class="showAdvancedView ? 'text-white bg-bg hover:bg-bg/60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary/70 hover:bg-primary/60'" @click="showAdvancedView = true">
|
||||
<p class="text-sm">{{ $strings.HeaderAdvanced }}</p>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-4 md:p-8 border border-black-200 rounded-b-md mr-px bg-bg">
|
||||
<template v-if="!showAdvancedView">
|
||||
<div class="flex flex-wrap gap-4 sm:gap-8 justify-start sm:justify-center">
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<p class="text-sm w-40">{{ $strings.LabelCodec }}</p>
|
||||
<ui-toggle-btns v-model="selectedCodec" :items="codecItems" :disabled="disabled" />
|
||||
<p class="text-xs text-gray-300">
|
||||
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentCodec }}</span> <span v-if="isCodecsDifferent" class="text-warning">(mixed)</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<p class="text-sm w-40">{{ $strings.LabelBitrate }}</p>
|
||||
<ui-toggle-btns v-model="selectedBitrate" :items="bitrateItems" :disabled="disabled" />
|
||||
<p class="text-xs text-gray-300">
|
||||
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentBitrate }} KB/s</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-2">
|
||||
<p class="text-sm w-40">{{ $strings.LabelChannels }}</p>
|
||||
<ui-toggle-btns v-model="selectedChannels" :items="channelsItems" :disabled="disabled" />
|
||||
<p class="text-xs text-gray-300">
|
||||
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentChannels }} ({{ currentChanelLayout }})</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<div class="flex flex-wrap gap-4 sm:gap-8 justify-start sm:justify-center mb-4">
|
||||
<div class="w-40">
|
||||
<ui-text-input-with-label v-model="customCodec" :label="$strings.LabelAudioCodec" :disabled="disabled" @input="customCodecChanged" />
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<ui-text-input-with-label v-model="customBitrate" :label="$strings.LabelAudioBitrate" :disabled="disabled" @input="customBitrateChanged" />
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<ui-text-input-with-label v-model="customChannels" :label="$strings.LabelAudioChannels" type="number" :disabled="disabled" @input="customChannelsChanged" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs sm:text-sm text-warning sm:text-center">{{ $strings.LabelEncodingWarningAdvancedSettings }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
audioTracks: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAdvancedView: false,
|
||||
selectedCodec: 'aac',
|
||||
selectedBitrate: '128k',
|
||||
selectedChannels: 2,
|
||||
customCodec: 'aac',
|
||||
customBitrate: '128k',
|
||||
customChannels: 2,
|
||||
currentCodec: '',
|
||||
currentBitrate: '',
|
||||
currentChannels: '',
|
||||
currentChanelLayout: '',
|
||||
isCodecsDifferent: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
codecItems() {
|
||||
return [
|
||||
{
|
||||
text: 'Copy',
|
||||
value: 'copy'
|
||||
},
|
||||
{
|
||||
text: 'AAC',
|
||||
value: 'aac'
|
||||
},
|
||||
{
|
||||
text: 'OPUS',
|
||||
value: 'opus'
|
||||
}
|
||||
]
|
||||
},
|
||||
bitrateItems() {
|
||||
return [
|
||||
{
|
||||
text: '32k',
|
||||
value: '32k'
|
||||
},
|
||||
{
|
||||
text: '64k',
|
||||
value: '64k'
|
||||
},
|
||||
{
|
||||
text: '128k',
|
||||
value: '128k'
|
||||
},
|
||||
{
|
||||
text: '192k',
|
||||
value: '192k'
|
||||
}
|
||||
]
|
||||
},
|
||||
channelsItems() {
|
||||
return [
|
||||
{
|
||||
text: '1 (mono)',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
text: '2 (stereo)',
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
customBitrateChanged(val) {
|
||||
localStorage.setItem('embedMetadataBitrate', val)
|
||||
},
|
||||
customChannelsChanged(val) {
|
||||
localStorage.setItem('embedMetadataChannels', val)
|
||||
},
|
||||
customCodecChanged(val) {
|
||||
localStorage.setItem('embedMetadataCodec', val)
|
||||
},
|
||||
getEncodingOptions() {
|
||||
return {
|
||||
codec: this.selectedCodec || 'aac',
|
||||
bitrate: this.selectedBitrate || '128k',
|
||||
channels: this.selectedChannels || 2
|
||||
}
|
||||
},
|
||||
setPreset() {
|
||||
// If already AAC and not mixed, set copy
|
||||
if (this.currentCodec === 'aac' && !this.isCodecsDifferent) {
|
||||
this.selectedCodec = 'copy'
|
||||
} else {
|
||||
this.selectedCodec = 'aac'
|
||||
}
|
||||
|
||||
if (!this.currentBitrate) {
|
||||
this.selectedBitrate = '128k'
|
||||
} else {
|
||||
// Find closest bitrate rounding up
|
||||
const bitratesToMatch = [32, 64, 128, 192]
|
||||
const closestBitrate = bitratesToMatch.find((bitrate) => bitrate >= this.currentBitrate)
|
||||
this.selectedBitrate = closestBitrate + 'k'
|
||||
}
|
||||
|
||||
if (!this.currentChannels || isNaN(this.currentChannels)) {
|
||||
this.selectedChannels = 2
|
||||
} else {
|
||||
// Either 1 or 2
|
||||
this.selectedChannels = Math.max(Math.min(Number(this.currentChannels), 2), 1)
|
||||
}
|
||||
},
|
||||
setCurrentValues() {
|
||||
if (this.audioTracks.length === 0) return
|
||||
|
||||
this.currentChannels = this.audioTracks[0].channels
|
||||
this.currentChanelLayout = this.audioTracks[0].channelLayout
|
||||
this.currentCodec = this.audioTracks[0].codec
|
||||
|
||||
let totalBitrate = 0
|
||||
for (const track of this.audioTracks) {
|
||||
const trackBitrate = !isNaN(track.bitRate) ? track.bitRate : 0
|
||||
totalBitrate += trackBitrate
|
||||
|
||||
if (track.channels > this.currentChannels) this.currentChannels = track.channels
|
||||
if (track.codec !== this.currentCodec) {
|
||||
console.warn('Audio track codec is different from the first track', track.codec)
|
||||
this.isCodecsDifferent = true
|
||||
}
|
||||
}
|
||||
|
||||
this.currentBitrate = Math.round(totalBitrate / this.audioTracks.length / 1000)
|
||||
},
|
||||
init() {
|
||||
this.customBitrate = localStorage.getItem('embedMetadataBitrate') || '128k'
|
||||
this.customChannels = localStorage.getItem('embedMetadataChannels') || 2
|
||||
this.customCodec = localStorage.getItem('embedMetadataCodec') || 'aac'
|
||||
|
||||
this.setCurrentValues()
|
||||
|
||||
this.setPreset()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue