캡컷은 클립보드 이미지 붙여넣기를 못 받아 파일 불러오기가 필요 → 카드별 '⬇ 저장' 버튼으로
PNG 다운로드 제공. 저장 버튼은 cc-noexport+modern-screenshot filter로 캡처 이미지에서 제외.
카드 클릭=클립보드 복사는 유지.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
복사 버튼이 캡처 이미지에 함께 찍히던 문제 해결. 버튼을 없애고 카드 자체 클릭으로
복사하도록 변경, 완료 피드백은 카드 밖 토스트로 표시(캡처에 미포함). JS 캐시버스터 갱신.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid가 행 단위로 최대 높이에 맞춰 늘어나던 것을 CSS 다단(column)으로 변경.
각 카드가 내용 높이에 맞고 빈틈없이 쌓이도록 break-inside:avoid + margin-bottom 적용.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commentThreads가 order=relevance에서 간헐적으로 403 forbidden을 반환하는 문제 대응.
generic 403/rateLimit/5xx는 백오프 후 최대 2회 재시도, commentsDisabled/quotaExceeded는
재시도 없이 즉시 안내. 재시도 성공/비재시도 단위 테스트 추가.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
403을 무조건 '댓글 비활성화'로 표시하던 문제 수정. error.errors[0].reason을 파싱해
commentsDisabled/quotaExceeded/기타(keyInvalid 등)를 각각 안내하고, 비-403 오류가
opaque 500으로 새던 것도 의미있는 메시지로 노출. 단위 테스트 추가.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HTML5 드래그가 터치에서 동작 안 하는 문제 — 카드마다 상태 드롭다운을 두어
터치로도 컬럼 이동 가능. 데스크톱은 드래그 유지(드롭다운은 모바일/터치에서만 노출).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Channel 에 country 추가(YouTube snippet.country 저장), 추천 채널 region 으로 기존 채널
백필(POST /api/channels/backfill-country), /channels 에 국가 탭+카운트 필터.
선택 영상 보기 버튼 한글화.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
querySelector('i')가 lucide 치환 후 null→TypeError로 시각 토글이 첫 카드에서 멈춰
전체 선택이 안 되는 것처럼 보였음. querySelector('svg, i') + null 가드로 수정.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 목록 API status 파라미터(NEW|EXCLUDED|REGISTERED), POST /{id}/restore(→NEW)
- recommend.html: 헤더 '제외 목록' 토글로 EXCLUDED 채널 보기, 카드에 '복원' 버튼
(제외 보기에선 등록/제외 대신 복원+채널이동만), 지역 탭은 그대로 적용
검증: 제외→EXCLUDED 목록 노출→복원→NEW 복귀 end-to-end 확인.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- region 을 채널 국가가 아닌 '검색 발견 지역(regionCode)'으로 태깅(UNKNOWN 해소)
- upsert: 같은 배율이면 갱신(지역 등 메타 최신화)
- recommend.html: 전체/KR/JP/US 탭으로 필터, 등록/제외 시 목록 갱신
검증: 재발굴 후 KR14·JP7·US1 로 분포, 탭 필터 동작.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
지역(KR,JP,US) 인기 Shorts 주기 검색 → 작은구독자·고배율 떡상 채널 발굴 →
RecommendedChannel 저장 → /recommend 페이지에서 등록/제외. 쿼터 가드·기존 검색 재사용.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
URL자막 버튼 제거에 따라 안내문/placeholder의 'URL자막' 언급 삭제,
미사용 함수 extractScript/translateToEditor 제거(copyToEditor는 유지).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AI 자막(음성/화면) 기능이 번역을 대체해 중복되는 버튼 3개 삭제.
원본 다운로드·자막 추출(음성)·받은 원본 삭제·영상 업로드·에디터로 복사는 유지.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
화면(Gemini)·음성 패널에서 원문과 한국어 번역을 함께 보여줘 비교 가능하게 함.
- ScriptResponseDto.Segment: original 필드 추가(NON_NULL)
- Gemini 프롬프트/스키마: original(화면 원문) + text(한국어 번역) 둘 다 요청,
text 는 반드시 한국어(영어 방지) 명시
- rework.html: 각 줄에 한국어(위)+원문(아래 회색) 표시, 음성 패널은 Whisper 원문 사용
검증: 470 화면자막 → 원문 일본어 + 한국어 번역 + 진행형 타임스탬프 정상.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
원인: Gemini 2.5 Flash의 thinking(타임스탬프 추론)+JSON 출력이 자막 많은 영상에서
출력 토큰을 초과해 JSON이 잘림 → 0개 또는 타임스탬프 전부 0.
- maxOutputTokens 65536(최대)로 상향 — thinking+JSON 잘림 방지(핵심)
- temperature 0 — 일관성
- 프롬프트: 시작/끝 시각 정확히, 전부 0 금지, 누락 없이 추출하도록 보강
- 빈 결과면 최대 3회 재시도(영상이해 비결정성 보완)
검증: 470·560(자막 많은 영상) 모두 진행형 타임스탬프로 정상 추출.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
재생 중 활성 세그먼트로 이동할 때 scrollIntoView가 페이지를 스크롤해 sticky 영상이
위아래로 튀던 현상 수정. 세그먼트 박스(#segmentList) 내부에서만 스크롤하도록 변경.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 이해하기 쉽게 모든 '전사' 문구를 '자막 추출'로 변경(메인 버튼은 '자막 추출(음성)')
- 좌측 영상 카드 position:sticky(top:1rem) → 스크롤해도 상단에 고정되어 항상 보임
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
말소리 없는 영상(음악·화면자막만)의 음성 패널을 빨강 오류가 아니라 회색 안내
('음성 없음 — 말소리가 없거나 전사 미실행')로 표시. renderAiPanel에 note 인자 추가.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GeminiSubtitleService: 403(프로젝트 차단)/429(한도)/400(키 무효)을 한국어 메시지로
IllegalArgumentException throw → 'Internal Server Error' 대신 실제 사유 노출
- rework.html: 음성 패널 빈 경우 '말소리 없음(음악/화면자막만) 또는 전사 미실행'으로 정정
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
재가공 화면에 'AI 자막(음성/화면)' 카드 추가. '생성' 한 번에:
- 왼쪽(음성): 저장된 Whisper 세그먼트(정밀 타임스탬프) → LibreTranslate 한국어
- 오른쪽(화면): 유튜브 URL → Gemini가 화면 박힌 자막 추출+한국어 번역
각 패널을 [00:00] 한국어 리스트로 표시, 각각 SRT 다운로드(audio_ko/screen_ko).
- GeminiSubtitleService 신규: youtube URL을 generativelanguage API에 전송,
responseMimeType=json 구조화 응답 파싱. buildRequest/extractSegments/sanitizeApiKey 순수+단위테스트
- sanitizeApiKey: 잘못 붙은 선행 '='·공백 정리(env var 오타 방어)
- geminiRestTemplate 빈(5분), POST /{id}/gemini-subtitles, CurationService.geminiScreenSubtitles
- 설정 gemini.api-key/model(gemini-2.5-flash), rework.html 분할 카드+JS
- 음성 한국어는 기존 translate(format=segments) 재사용
검증: 470 화면자막 → 한국어 27세그먼트 30초 추출(스타일 일본어 자막도 정확).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
좌우 분할: 왼쪽=음성(Whisper 정밀+한국어), 오른쪽=화면(Gemini 한국어). 각각 SRT 다운로드.
음성 한국어는 기존 전사+LibreTranslate 재사용, 화면은 Gemini 신규(transient).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
'Gemini 자막' 버튼으로 유튜브 URL을 Gemini에 보내 화면/음성 자막을 추출+한국어 번역,
[00:00] 한국어 세그먼트로 저장 → SRT 내보내기가 곧 한국어 SRT. 제거된 OCR의 대체.
무료 티어(하루 8시간) 사용, 타임스탬프는 ~1초 근사.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tesseract 기반 OCR이 실제 쇼츠의 스타일 자막을 거의 못 읽어 실효성이 없고,
URL→Gemini 방향으로 전환하기로 해 h-lab의 OCR 연동을 제거한다.
- 컨트롤러 POST /{id}/ocr 제거
- CurationService.ocrScreenSubtitles 제거
- ChannelService.ocrFromCached/joinSegmentText 제거(persistScript는 전사가 계속 사용)
- rework.html '화면 자막 OCR' 버튼·crop/fps 입력·ocrScreen() 제거
- docs/python-service/ocr_video_endpoint.py(videocr 예시) 삭제
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
저사양 서버(N150)에서 sample_fps=3은 프레임 과다로 10분 타임아웃 발생.
OCR 영역(crop %) 옆에 'fps' 입력을 추가하고 기본을 1로 낮춰 빠르게 끝나게 함
(자막이 빨리 바뀌면 2~3으로 올림). ocrScreen()이 sampleFps 를 함께 전달.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
캐시(받은 원본) 보유 시 '원본 다운로드'를 숨기고, 그 자리에 '전사' 버튼을 띄운다
(전사만 실패했을 때 재다운로드 없이 재시도). 캐시 없으면 다시 '원본 다운로드' 표시.
- updateCacheUI: downloadBtn 숨김/표시 대칭 토글 + transcribeCachedBtn 노출
- runTranscribeCached 헬퍼 추출(다운로드 2단계 전사와 '전사' 버튼이 공유)
- '전사' 버튼 onclick=transcribeCached()
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
기존엔 '원본 다운로드'가 다운로드+Whisper전사를 한 동기호출로 처리해, 전사 서버
(h-python)가 느리거나 타임아웃나면 화면이 '다운로드 중'에서 안 풀렸다.
다운로드(빠름)와 전사(분리)를 나눠, 다운로드는 즉시 끝나고 영상이 바로 뜨며
전사가 실패해도 다운로드 결과는 유지(화면자막 OCR/재시도 가능).
- POST /{id}/download: 다운로드만(캐시), 응답 {downloaded, sizeBytes}
- POST /{id}/transcribe-cached: 받은 원본 전사(분리)
- downloadOriginal(): 1)다운로드 즉시완료·영상표시 2)전사 별도(실패해도 다운로드 유지)
검증: /download 2.7초 완료. 전사는 분리 단계로 진행/실패 격리.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OCR 정확도·속도 향상을 위해 '자막영역 하단 N%' 입력을 추가, 플레이어 영상의 실제
해상도로 crop 픽셀(crop_x/y/width/height)을 환산해 /ocr_video 로 전달한다.
100%면 use_fullframe=true, 해상도 못 읽으면 생략(서버 기본 하단30%).
- ocrFromCached(file, formParams Map)로 일반화 — 서버 필드명 그대로 전달
- CurationService/Controller에 useFullframe·cropX/Y/Width/Height 파라미터 추가
- rework.html: 자막영역 % 입력 + ocrScreen()이 영상 해상도로 crop 계산
검증: sample_fps=3 + 하단30% crop 으로 2m28s 완료(프록시 600s 적용 후 504 해소),
crop 전달 정상 동작 확인.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
받은 원본 영상을 Python /ocr_video(ffmpeg+Tesseract)로 보내 화면에 박힌 자막을
시간 싱크 세그먼트로 추출·저장한다. 음성 전사와 같은 자리(ChannelVideoScript)에
저장되어 스크립트 리스트·SRT·번역·한국어 SRT에 그대로 흐른다.
- ChannelService.ocrFromCached + persistScript 공통 추출(전사/OCR 공유), joinSegmentText
- CurationService.ocrScreenSubtitles(받은 원본 캐시 사용), POST /{id}/ocr
- rework.html '화면 자막 OCR' 버튼 + ocrScreen()
- sampleFps/confThreshold null이면 미전송 → 서버 기본값 사용
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>