mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
[Feature] Add audio preview support to queue sidebar (#3954)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
19
src/components/sidebar/tabs/queue/ResultAudio.vue
Normal file
19
src/components/sidebar/tabs/queue/ResultAudio.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<audio controls width="100%" height="100%">
|
||||||
|
<source :src="url" :type="htmlAudioType" />
|
||||||
|
{{ $t('g.audioFailedToLoad') }}
|
||||||
|
</audio>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { ResultItemImpl } from '@/stores/queueStore'
|
||||||
|
|
||||||
|
const { result } = defineProps<{
|
||||||
|
result: ResultItemImpl
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const url = computed(() => result.url)
|
||||||
|
const htmlAudioType = computed(() => result.htmlAudioType)
|
||||||
|
</script>
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
class="galleria-image"
|
class="galleria-image"
|
||||||
/>
|
/>
|
||||||
<ResultVideo v-else-if="item.isVideo" :result="item" />
|
<ResultVideo v-else-if="item.isVideo" :result="item" />
|
||||||
|
<ResultAudio v-else-if="item.isAudio" :result="item" />
|
||||||
</template>
|
</template>
|
||||||
</Galleria>
|
</Galleria>
|
||||||
</template>
|
</template>
|
||||||
@@ -46,6 +47,7 @@ import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|||||||
import ComfyImage from '@/components/common/ComfyImage.vue'
|
import ComfyImage from '@/components/common/ComfyImage.vue'
|
||||||
import { ResultItemImpl } from '@/stores/queueStore'
|
import { ResultItemImpl } from '@/stores/queueStore'
|
||||||
|
|
||||||
|
import ResultAudio from './ResultAudio.vue'
|
||||||
import ResultVideo from './ResultVideo.vue'
|
import ResultVideo from './ResultVideo.vue'
|
||||||
|
|
||||||
const galleryVisible = ref(false)
|
const galleryVisible = ref(false)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
:alt="result.filename"
|
:alt="result.filename"
|
||||||
/>
|
/>
|
||||||
<ResultVideo v-else-if="result.isVideo" :result="result" />
|
<ResultVideo v-else-if="result.isVideo" :result="result" />
|
||||||
|
<ResultAudio v-else-if="result.isAudio" :result="result" />
|
||||||
<div v-else class="task-result-preview">
|
<div v-else class="task-result-preview">
|
||||||
<i class="pi pi-file" />
|
<i class="pi pi-file" />
|
||||||
<span>{{ result.mediaType }}</span>
|
<span>{{ result.mediaType }}</span>
|
||||||
@@ -26,6 +27,7 @@ import ComfyImage from '@/components/common/ComfyImage.vue'
|
|||||||
import { ResultItemImpl } from '@/stores/queueStore'
|
import { ResultItemImpl } from '@/stores/queueStore'
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
import { useSettingStore } from '@/stores/settingStore'
|
||||||
|
|
||||||
|
import ResultAudio from './ResultAudio.vue'
|
||||||
import ResultVideo from './ResultVideo.vue'
|
import ResultVideo from './ResultVideo.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"terminal": "Terminal",
|
"terminal": "Terminal",
|
||||||
"logs": "Logs",
|
"logs": "Logs",
|
||||||
"videoFailedToLoad": "Video failed to load",
|
"videoFailedToLoad": "Video failed to load",
|
||||||
|
"audioFailedToLoad": "Audio failed to load",
|
||||||
"extensionName": "Extension Name",
|
"extensionName": "Extension Name",
|
||||||
"reloadToApplyChanges": "Reload to apply changes",
|
"reloadToApplyChanges": "Reload to apply changes",
|
||||||
"insert": "Insert",
|
"insert": "Insert",
|
||||||
|
|||||||
@@ -248,6 +248,7 @@
|
|||||||
"all": "Todo",
|
"all": "Todo",
|
||||||
"amount": "Cantidad",
|
"amount": "Cantidad",
|
||||||
"apply": "Aplicar",
|
"apply": "Aplicar",
|
||||||
|
"audioFailedToLoad": "No se pudo cargar el audio",
|
||||||
"back": "Atrás",
|
"back": "Atrás",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"capture": "captura",
|
"capture": "captura",
|
||||||
|
|||||||
@@ -248,6 +248,7 @@
|
|||||||
"all": "Tout",
|
"all": "Tout",
|
||||||
"amount": "Quantité",
|
"amount": "Quantité",
|
||||||
"apply": "Appliquer",
|
"apply": "Appliquer",
|
||||||
|
"audioFailedToLoad": "Échec du chargement de l'audio",
|
||||||
"back": "Retour",
|
"back": "Retour",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"capture": "capture",
|
"capture": "capture",
|
||||||
|
|||||||
@@ -248,6 +248,7 @@
|
|||||||
"all": "すべて",
|
"all": "すべて",
|
||||||
"amount": "量",
|
"amount": "量",
|
||||||
"apply": "適用する",
|
"apply": "適用する",
|
||||||
|
"audioFailedToLoad": "オーディオの読み込みに失敗しました",
|
||||||
"back": "戻る",
|
"back": "戻る",
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
"capture": "キャプチャ",
|
"capture": "キャプチャ",
|
||||||
|
|||||||
@@ -248,6 +248,7 @@
|
|||||||
"all": "모두",
|
"all": "모두",
|
||||||
"amount": "수량",
|
"amount": "수량",
|
||||||
"apply": "적용",
|
"apply": "적용",
|
||||||
|
"audioFailedToLoad": "오디오를 불러오지 못했습니다",
|
||||||
"back": "뒤로",
|
"back": "뒤로",
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"capture": "캡처",
|
"capture": "캡처",
|
||||||
|
|||||||
@@ -248,6 +248,7 @@
|
|||||||
"all": "Все",
|
"all": "Все",
|
||||||
"amount": "Количество",
|
"amount": "Количество",
|
||||||
"apply": "Применить",
|
"apply": "Применить",
|
||||||
|
"audioFailedToLoad": "Не удалось загрузить аудио",
|
||||||
"back": "Назад",
|
"back": "Назад",
|
||||||
"cancel": "Отмена",
|
"cancel": "Отмена",
|
||||||
"capture": "захват",
|
"capture": "захват",
|
||||||
|
|||||||
@@ -248,6 +248,7 @@
|
|||||||
"all": "全部",
|
"all": "全部",
|
||||||
"amount": "数量",
|
"amount": "数量",
|
||||||
"apply": "应用",
|
"apply": "应用",
|
||||||
|
"audioFailedToLoad": "音频加载失败",
|
||||||
"back": "返回",
|
"back": "返回",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"capture": "捕获",
|
"capture": "捕获",
|
||||||
|
|||||||
@@ -106,6 +106,22 @@ export class ResultItemImpl {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get htmlAudioType(): string | undefined {
|
||||||
|
if (this.isMp3) {
|
||||||
|
return 'audio/mpeg'
|
||||||
|
}
|
||||||
|
if (this.isWav) {
|
||||||
|
return 'audio/wav'
|
||||||
|
}
|
||||||
|
if (this.isOgg) {
|
||||||
|
return 'audio/ogg'
|
||||||
|
}
|
||||||
|
if (this.isFlac) {
|
||||||
|
return 'audio/flac'
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
get isGif(): boolean {
|
get isGif(): boolean {
|
||||||
return this.filename.endsWith('.gif')
|
return this.filename.endsWith('.gif')
|
||||||
}
|
}
|
||||||
@@ -130,21 +146,55 @@ export class ResultItemImpl {
|
|||||||
return this.isGif || this.isWebp
|
return this.isGif || this.isWebp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isMp3(): boolean {
|
||||||
|
return this.filename.endsWith('.mp3')
|
||||||
|
}
|
||||||
|
|
||||||
|
get isWav(): boolean {
|
||||||
|
return this.filename.endsWith('.wav')
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOgg(): boolean {
|
||||||
|
return this.filename.endsWith('.ogg')
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFlac(): boolean {
|
||||||
|
return this.filename.endsWith('.flac')
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAudioBySuffix(): boolean {
|
||||||
|
return this.isMp3 || this.isWav || this.isOgg || this.isFlac
|
||||||
|
}
|
||||||
|
|
||||||
get isVideo(): boolean {
|
get isVideo(): boolean {
|
||||||
const isVideoByType =
|
const isVideoByType =
|
||||||
this.mediaType === 'video' || !!this.format?.startsWith('video/')
|
this.mediaType === 'video' || !!this.format?.startsWith('video/')
|
||||||
return this.isVideoBySuffix || (isVideoByType && !this.isImageBySuffix)
|
return (
|
||||||
|
this.isVideoBySuffix ||
|
||||||
|
(isVideoByType && !this.isImageBySuffix && !this.isAudioBySuffix)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get isImage(): boolean {
|
get isImage(): boolean {
|
||||||
return (
|
return (
|
||||||
this.isImageBySuffix ||
|
this.isImageBySuffix ||
|
||||||
(this.mediaType === 'images' && !this.isVideoBySuffix)
|
(this.mediaType === 'images' &&
|
||||||
|
!this.isVideoBySuffix &&
|
||||||
|
!this.isAudioBySuffix)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAudio(): boolean {
|
||||||
|
const isAudioByType =
|
||||||
|
this.mediaType === 'audio' || !!this.format?.startsWith('audio/')
|
||||||
|
return (
|
||||||
|
this.isAudioBySuffix ||
|
||||||
|
(isAudioByType && !this.isImageBySuffix && !this.isVideoBySuffix)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get supportsPreview(): boolean {
|
get supportsPreview(): boolean {
|
||||||
return this.isImage || this.isVideo
|
return this.isImage || this.isVideo || this.isAudio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,4 +115,42 @@ describe('TaskItemImpl', () => {
|
|||||||
expect(output.isVideo).toBe(true)
|
expect(output.isVideo).toBe(true)
|
||||||
expect(output.isImage).toBe(false)
|
expect(output.isImage).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('audio format detection', () => {
|
||||||
|
const audioFormats = [
|
||||||
|
{ extension: 'mp3', mimeType: 'audio/mpeg' },
|
||||||
|
{ extension: 'wav', mimeType: 'audio/wav' },
|
||||||
|
{ extension: 'ogg', mimeType: 'audio/ogg' },
|
||||||
|
{ extension: 'flac', mimeType: 'audio/flac' }
|
||||||
|
]
|
||||||
|
|
||||||
|
audioFormats.forEach(({ extension, mimeType }) => {
|
||||||
|
it(`should recognize ${extension} audio`, () => {
|
||||||
|
const taskItem = new TaskItemImpl(
|
||||||
|
'History',
|
||||||
|
[0, 'prompt-id', {}, { client_id: 'client-id' }, []],
|
||||||
|
{ status_str: 'success', messages: [], completed: true },
|
||||||
|
{
|
||||||
|
'node-1': {
|
||||||
|
audio: [
|
||||||
|
{
|
||||||
|
filename: `test.${extension}`,
|
||||||
|
type: 'output',
|
||||||
|
subfolder: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const output = taskItem.flatOutputs[0]
|
||||||
|
|
||||||
|
expect(output.htmlAudioType).toBe(mimeType)
|
||||||
|
expect(output.isAudio).toBe(true)
|
||||||
|
expect(output.isVideo).toBe(false)
|
||||||
|
expect(output.isImage).toBe(false)
|
||||||
|
expect(output.supportsPreview).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user