Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/components/sidebar/tabs/queue/ResultAudio.vue
Original file line number Diff line number Diff line change
@@ -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>
2 changes: 2 additions & 0 deletions src/components/sidebar/tabs/queue/ResultGallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
class="galleria-image"
/>
<ResultVideo v-else-if="item.isVideo" :result="item" />
<ResultAudio v-else-if="item.isAudio" :result="item" />
</template>
</Galleria>
</template>
Expand All @@ -46,6 +47,7 @@ import { onMounted, onUnmounted, ref, watch } from 'vue'
import ComfyImage from '@/components/common/ComfyImage.vue'
import { ResultItemImpl } from '@/stores/queueStore'

import ResultAudio from './ResultAudio.vue'
import ResultVideo from './ResultVideo.vue'

const galleryVisible = ref(false)
Expand Down
2 changes: 2 additions & 0 deletions src/components/sidebar/tabs/queue/ResultItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
:alt="result.filename"
/>
<ResultVideo v-else-if="result.isVideo" :result="result" />
<ResultAudio v-else-if="result.isAudio" :result="result" />
<div v-else class="task-result-preview">
<i class="pi pi-file" />
<span>{{ result.mediaType }}</span>
Expand All @@ -26,6 +27,7 @@ import ComfyImage from '@/components/common/ComfyImage.vue'
import { ResultItemImpl } from '@/stores/queueStore'
import { useSettingStore } from '@/stores/settingStore'

import ResultAudio from './ResultAudio.vue'
import ResultVideo from './ResultVideo.vue'

const props = defineProps<{
Expand Down
1 change: 1 addition & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"terminal": "Terminal",
"logs": "Logs",
"videoFailedToLoad": "Video failed to load",
"audioFailedToLoad": "Audio failed to load",
"extensionName": "Extension Name",
"reloadToApplyChanges": "Reload to apply changes",
"insert": "Insert",
Expand Down
1 change: 1 addition & 0 deletions src/locales/es/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"all": "Todo",
"amount": "Cantidad",
"apply": "Aplicar",
"audioFailedToLoad": "No se pudo cargar el audio",
"back": "Atrás",
"cancel": "Cancelar",
"capture": "captura",
Expand Down
1 change: 1 addition & 0 deletions src/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"all": "Tout",
"amount": "Quantité",
"apply": "Appliquer",
"audioFailedToLoad": "Échec du chargement de l'audio",
"back": "Retour",
"cancel": "Annuler",
"capture": "capture",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ja/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"all": "すべて",
"amount": "量",
"apply": "適用する",
"audioFailedToLoad": "オーディオの読み込みに失敗しました",
"back": "戻る",
"cancel": "キャンセル",
"capture": "キャプチャ",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ko/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"all": "모두",
"amount": "수량",
"apply": "적용",
"audioFailedToLoad": "오디오를 불러오지 못했습니다",
"back": "뒤로",
"cancel": "취소",
"capture": "캡처",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ru/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"all": "Все",
"amount": "Количество",
"apply": "Применить",
"audioFailedToLoad": "Не удалось загрузить аудио",
"back": "Назад",
"cancel": "Отмена",
"capture": "захват",
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
"all": "全部",
"amount": "数量",
"apply": "应用",
"audioFailedToLoad": "音频加载失败",
"back": "返回",
"cancel": "取消",
"capture": "捕获",
Expand Down
56 changes: 53 additions & 3 deletions src/stores/queueStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ export class ResultItemImpl {
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 {
return this.filename.endsWith('.gif')
}
Expand All @@ -130,21 +146,55 @@ export class ResultItemImpl {
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 {
const isVideoByType =
this.mediaType === 'video' || !!this.format?.startsWith('video/')
return this.isVideoBySuffix || (isVideoByType && !this.isImageBySuffix)
return (
this.isVideoBySuffix ||
(isVideoByType && !this.isImageBySuffix && !this.isAudioBySuffix)
)
}

get isImage(): boolean {
return (
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 {
return this.isImage || this.isVideo
return this.isImage || this.isVideo || this.isAudio
}
}

Expand Down
38 changes: 38 additions & 0 deletions tests-ui/tests/store/queueStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,42 @@ describe('TaskItemImpl', () => {
expect(output.isVideo).toBe(true)
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)
})
})
})
})