fix(rework): AI 자막 - 음성 없음은 오류 아닌 안내(회색)로 표시

말소리 없는 영상(음악·화면자막만)의 음성 패널을 빨강 오류가 아니라 회색 안내
('음성 없음 — 말소리가 없거나 전사 미실행')로 표시. renderAiPanel에 note 인자 추가.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-06-25 17:31:02 +09:00
parent 6380a28a6f
commit 7b94515180

View File

@ -470,14 +470,18 @@
} }
// ===== AI 자막 (음성 Whisper / 화면 Gemini) ===== // ===== AI 자막 (음성 Whisper / 화면 Gemini) =====
function renderAiPanel(elId, segs, error){ // segs 있으면 목록, 없으면 error(빨강) 또는 note(회색 안내) 표시.
function renderAiPanel(elId, segs, error, note){
const box = document.getElementById(elId); const box = document.getElementById(elId);
if(error){ box.innerHTML = '<div class="ai-empty" style="color:#f87171;">' + esc(error) + '</div>'; return; } if(segs && segs.length){
if(!segs || !segs.length){ box.innerHTML = '<div class="ai-empty">자막 없음</div>'; return; }
box.innerHTML = segs.map(sg => box.innerHTML = segs.map(sg =>
'<div class="ai-row" data-start="' + sg.start + '" onclick="seekTo(' + sg.start + ')">' '<div class="ai-row" data-start="' + sg.start + '" onclick="seekTo(' + sg.start + ')">'
+ '<span class="seg-time">' + mmss(sg.start) + '</span>' + '<span class="seg-time">' + mmss(sg.start) + '</span>'
+ '<span class="seg-text">' + esc(sg.text) + '</span></div>').join(''); + '<span class="seg-text">' + esc(sg.text) + '</span></div>').join('');
return;
}
if(error){ box.innerHTML = '<div class="ai-empty" style="color:#f87171;">' + esc(error) + '</div>'; return; }
box.innerHTML = '<div class="ai-empty">' + esc(note || '자막 없음') + '</div>';
} }
// 음성(Whisper 정밀+한국어) + 화면(Gemini 한국어)을 동시에 생성해 좌우 패널에 표시. // 음성(Whisper 정밀+한국어) + 화면(Gemini 한국어)을 동시에 생성해 좌우 패널에 표시.
@ -490,7 +494,8 @@
// 왼쪽: 저장된 Whisper 세그먼트 → 한국어 번역(LibreTranslate) // 왼쪽: 저장된 Whisper 세그먼트 → 한국어 번역(LibreTranslate)
const audioP = (async () => { const audioP = (async () => {
if(!SEGMENTS || !SEGMENTS.length) return { error: '음성 자막 없음 — 이 영상에 말소리가 없거나(음악·화면자막만) 아직 ‘전사’ 미실행' }; // 말소리가 없는(음악·화면자막만) 영상은 오류가 아니라 '음성 없음' 안내.
if(!SEGMENTS || !SEGMENTS.length) return { note: '음성 없음 — 말소리가 없거나(음악·화면자막만) ‘전사’ 미실행' };
const r = await api(API + '/' + VIDEO_ID + '/translate?target=ko&format=segments', { method:'POST' }); const r = await api(API + '/' + VIDEO_ID + '/translate?target=ko&format=segments', { method:'POST' });
const ko = (r && r.texts) || []; const ko = (r && r.texts) || [];
return { segments: SEGMENTS.map((sg, i) => ({ start: sg.start, end: sg.end, text: (ko[i] != null ? ko[i] : (sg.text || '')) })) }; return { segments: SEGMENTS.map((sg, i) => ({ start: sg.start, end: sg.end, text: (ko[i] != null ? ko[i] : (sg.text || '')) })) };
@ -504,8 +509,8 @@
const [audio, screen] = await Promise.all([audioP, screenP]); const [audio, screen] = await Promise.all([audioP, screenP]);
AI_AUDIO = audio.segments || []; AI_AUDIO = audio.segments || [];
AI_SCREEN = screen.segments || []; AI_SCREEN = screen.segments || [];
renderAiPanel('aiAudioList', AI_AUDIO, audio.error); renderAiPanel('aiAudioList', AI_AUDIO, audio.error, audio.note);
renderAiPanel('aiScreenList', AI_SCREEN, screen.error); renderAiPanel('aiScreenList', AI_SCREEN, screen.error, screen.note);
status.style.color = '#4ade80'; status.style.color = '#4ade80';
status.textContent = '완료 · 음성 ' + AI_AUDIO.length + '개 · 화면 ' + AI_SCREEN.length + '개' status.textContent = '완료 · 음성 ' + AI_AUDIO.length + '개 · 화면 ' + AI_SCREEN.length + '개'