From 6380a28a6f951e81b8e75f98f02ac8d44a0610fd Mon Sep 17 00:00:00 2001 From: "hehihoho3@gmail.com" Date: Thu, 25 Jun 2026 17:16:29 +0900 Subject: [PATCH] =?UTF-8?q?fix(rework):=20Gemini=20=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EC=82=AC=EC=9C=A0=EB=A5=BC=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=97=90=EA=B2=8C=20=EB=AA=85=ED=99=95=ED=9E=88=20+=20?= =?UTF-8?q?=EC=9D=8C=EC=84=B1=20=EC=97=86=EC=9D=8C=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GeminiSubtitleService: 403(프로젝트 차단)/429(한도)/400(키 무효)을 한국어 메시지로 IllegalArgumentException throw → 'Internal Server Error' 대신 실제 사유 노출 - rework.html: 음성 패널 빈 경우 '말소리 없음(음악/화면자막만) 또는 전사 미실행'으로 정정 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../service/GeminiSubtitleService.java | 22 ++++++++++++++----- src/main/resources/templates/rework.html | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/hlab/yanalyst/service/GeminiSubtitleService.java b/src/main/java/com/hlab/yanalyst/service/GeminiSubtitleService.java index b14dfcb..f20ee6a 100644 --- a/src/main/java/com/hlab/yanalyst/service/GeminiSubtitleService.java +++ b/src/main/java/com/hlab/yanalyst/service/GeminiSubtitleService.java @@ -46,7 +46,7 @@ public class GeminiSubtitleService { public List fetchKoreanScreenSubtitles(String videoId) { String key = sanitizeApiKey(apiKey); if (key.isEmpty()) { - throw new IllegalStateException("GEMINI_API_KEY 가 설정되지 않았습니다."); + throw new IllegalArgumentException("GEMINI_API_KEY 가 설정되지 않았습니다."); } String videoUrl = "https://www.youtube.com/watch?v=" + videoId; String apiUrl = "https://generativelanguage.googleapis.com/v1beta/models/" @@ -66,13 +66,25 @@ public class GeminiSubtitleService { return segs; } catch (HttpStatusCodeException e) { // 키는 URL 쿼리에 있으므로 메시지에 apiUrl 을 넣지 않는다(노출 방지). + int code = e.getStatusCode().value(); String body = e.getResponseBodyAsString(); - String tail = body.length() > 300 ? body.substring(0, 300) : body; - throw new RuntimeException("Gemini 호출 실패(" + e.getStatusCode() + "): " + tail, e); - } catch (RuntimeException e) { + String msg; + if (code == 403) { + msg = "Gemini 접근 거부(403): 이 API 키의 프로젝트가 차단되었습니다. 다른 프로젝트의 새 API 키가 필요합니다."; + } else if (code == 429) { + msg = "Gemini 무료 한도 초과(429): 잠시 후 다시 시도하세요."; + } else if (code == 400 && body.contains("API key not valid")) { + msg = "Gemini API 키가 유효하지 않습니다(GEMINI_API_KEY 값을 확인하세요)."; + } else { + String tail = body.length() > 200 ? body.substring(0, 200) : body; + msg = "Gemini 호출 실패(" + code + "): " + tail; + } + log.error("Gemini 호출 실패({}) for video {}", code, videoId); + throw new IllegalArgumentException(msg, e); // GlobalExceptionHandler 가 메시지를 그대로 노출 + } catch (IllegalArgumentException e) { throw e; } catch (Exception e) { - throw new RuntimeException("Gemini 자막 추출 실패: " + e.getMessage(), e); + throw new IllegalArgumentException("Gemini 자막 추출 실패: " + e.getMessage(), e); } } diff --git a/src/main/resources/templates/rework.html b/src/main/resources/templates/rework.html index 442a4a1..496bdb3 100644 --- a/src/main/resources/templates/rework.html +++ b/src/main/resources/templates/rework.html @@ -490,7 +490,7 @@ // 왼쪽: 저장된 Whisper 세그먼트 → 한국어 번역(LibreTranslate) const audioP = (async () => { - if(!SEGMENTS || !SEGMENTS.length) return { error: '음성 자막: 먼저 ‘전사’를 실행하세요' }; + if(!SEGMENTS || !SEGMENTS.length) return { error: '음성 자막 없음 — 이 영상에 말소리가 없거나(음악·화면자막만) 아직 ‘전사’ 미실행' }; const r = await api(API + '/' + VIDEO_ID + '/translate?target=ko&format=segments', { method:'POST' }); 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 || '')) })) };