diff --git a/src/components/sidebar/tabs/queue/ResultAudio.vue b/src/components/sidebar/tabs/queue/ResultAudio.vue new file mode 100644 index 0000000000..60c70673a3 --- /dev/null +++ b/src/components/sidebar/tabs/queue/ResultAudio.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/components/sidebar/tabs/queue/ResultGallery.vue b/src/components/sidebar/tabs/queue/ResultGallery.vue index e60a91c74b..7309c901e5 100644 --- a/src/components/sidebar/tabs/queue/ResultGallery.vue +++ b/src/components/sidebar/tabs/queue/ResultGallery.vue @@ -35,6 +35,7 @@ class="galleria-image" /> + @@ -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) diff --git a/src/components/sidebar/tabs/queue/ResultItem.vue b/src/components/sidebar/tabs/queue/ResultItem.vue index 77c19ec1e4..9f05ab687b 100644 --- a/src/components/sidebar/tabs/queue/ResultItem.vue +++ b/src/components/sidebar/tabs/queue/ResultItem.vue @@ -12,6 +12,7 @@ :alt="result.filename" /> +
{{ result.mediaType }} @@ -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<{ diff --git a/src/locales/en/main.json b/src/locales/en/main.json index e2fca8680b..76cdaab9fb 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -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", diff --git a/src/locales/es/main.json b/src/locales/es/main.json index 71a150aa1a..bfefaa3291 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -248,6 +248,7 @@ "all": "Todo", "amount": "Cantidad", "apply": "Aplicar", + "audioFailedToLoad": "No se pudo cargar el audio", "back": "Atrás", "cancel": "Cancelar", "capture": "captura", diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index 4cdc02b95b..52662f278c 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -248,6 +248,7 @@ "all": "Tout", "amount": "Quantité", "apply": "Appliquer", + "audioFailedToLoad": "Échec du chargement de l'audio", "back": "Retour", "cancel": "Annuler", "capture": "capture", diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index 4824f5eb18..889a068d8d 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -248,6 +248,7 @@ "all": "すべて", "amount": "量", "apply": "適用する", + "audioFailedToLoad": "オーディオの読み込みに失敗しました", "back": "戻る", "cancel": "キャンセル", "capture": "キャプチャ", diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index 72db448f0d..9f277c662d 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -248,6 +248,7 @@ "all": "모두", "amount": "수량", "apply": "적용", + "audioFailedToLoad": "오디오를 불러오지 못했습니다", "back": "뒤로", "cancel": "취소", "capture": "캡처", diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index fda4a11a0d..10652401fa 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -248,6 +248,7 @@ "all": "Все", "amount": "Количество", "apply": "Применить", + "audioFailedToLoad": "Не удалось загрузить аудио", "back": "Назад", "cancel": "Отмена", "capture": "захват", diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index 53504b9bf2..4d5e461f1a 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -248,6 +248,7 @@ "all": "全部", "amount": "数量", "apply": "应用", + "audioFailedToLoad": "音频加载失败", "back": "返回", "cancel": "取消", "capture": "捕获", diff --git a/src/stores/queueStore.ts b/src/stores/queueStore.ts index e702a0f8eb..376063958d 100644 --- a/src/stores/queueStore.ts +++ b/src/stores/queueStore.ts @@ -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') } @@ -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 } } diff --git a/tests-ui/tests/store/queueStore.test.ts b/tests-ui/tests/store/queueStore.test.ts index 7fd2f5bb6f..313673e693 100644 --- a/tests-ui/tests/store/queueStore.test.ts +++ b/tests-ui/tests/store/queueStore.test.ts @@ -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) + }) + }) + }) })