Commit Graph

19 Commits

Author SHA1 Message Date
447645c19a feat(rework): AI 자막 2종(음성 Whisper / 화면 Gemini)→한국어, 좌우 분할 + SRT
재가공 화면에 '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>
2026-06-25 17:04:24 +09:00
7ebee1caa3 revert(rework): 화면 자막 OCR 기능 제거
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>
2026-06-25 16:19:32 +09:00
fbce1ec8ea feat(rework): 화면 자막 OCR 속도(sample_fps) 컨트롤 추가, 기본 1
저사양 서버(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>
2026-06-25 15:42:57 +09:00
32b2eae62c feat(rework): 받아진 상태면 '원본 다운로드' 숨기고 '전사' 버튼 노출
캐시(받은 원본) 보유 시 '원본 다운로드'를 숨기고, 그 자리에 '전사' 버튼을 띄운다
(전사만 실패했을 때 재다운로드 없이 재시도). 캐시 없으면 다시 '원본 다운로드' 표시.

- updateCacheUI: downloadBtn 숨김/표시 대칭 토글 + transcribeCachedBtn 노출
- runTranscribeCached 헬퍼 추출(다운로드 2단계 전사와 '전사' 버튼이 공유)
- '전사' 버튼 onclick=transcribeCached()

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:25:35 +09:00
bba81760a2 fix(rework): 원본 다운로드와 전사 분리 — 전사 지연/실패에 안 갇히게
기존엔 '원본 다운로드'가 다운로드+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>
2026-06-25 15:19:18 +09:00
6a0a7294bb feat(rework): 화면 자막 OCR 에 자막영역(crop) 전달 추가
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>
2026-06-25 14:53:42 +09:00
f7ac96f1dd feat(rework): 화면 자막 OCR(영상 박힌 자막) 연동
받은 원본 영상을 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>
2026-06-25 14:31:56 +09:00
e9ad800134 feat(rework): 한국어 SRT 내보내기 추가
세그먼트를 한국어로 일괄번역해 기존 타임라인(타임코드·배속·말없는구간 제거)에
그대로 끼워 SRT로 내보낸다. 원본 SRT와 별개로 '한국어 SRT' 버튼 추가.

- translateScript format=segments: 세그먼트와 1:1 정렬된 번역 배열 반환
- rework.html exportKoSrt(): texts 배열을 workingSegments에 매핑해 buildSrt로 생성
  (텍스트만 한국어로 교체, 타임코드/배속은 기존 로직 재사용)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:45:20 +09:00
198334c6f8 feat(rework): 타임라인 [00:00] 형식 번역 옵션 추가
'[00:00] 번역' 버튼 추가 — 세그먼트별로 번역해 '[mm:ss] 한국어' 줄로 재작성 칸에 내린다.
LibreTranslate의 q 배열 일괄번역(1회 호출, 입력순서 1:1 정렬)을 사용.
기존 '번역→재작성'(평문 흐름)은 그대로 유지(format=plain|timeline).

- TranslateService.translateBatch + buildBatchRequestBody/extractTranslatedTexts(+단위테스트)
- translateScript(id,target,format): timeline 분기, 세그먼트 시각 [mm:ss] 머리표시
- 컨트롤러 format 파라미터, rework.html '[00:00] 번역' 버튼

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:10:05 +09:00
77e77894c9 feat(rework): 원본 스크립트 번역 → 재작성(내 버전) 버튼 추가
자가호스팅 LibreTranslate(h-etc2.tolag.shop) 연동. 재가공 화면 '번역→재작성'
버튼으로 원본 전사 스크립트를 한국어로 번역해 재작성 칸 초안으로 채운다.
소스 언어는 전사 언어로 자동(없으면 auto), 타깃 기본 ko.

- TranslateService 신규: POST /translate {q,source,target} → translatedText
  (요청 조립/응답 파싱 순수 메서드 + 단위테스트)
- POST /{id}/translate 엔드포인트, CurationService.translateScript
- rework.html: '번역→재작성' 버튼 + translateToEditor()
- 설정 translate.base-url(기본값 有)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:41:23 +09:00
ff9609c9d1 feat(rework): 받은 원본을 왼쪽 플레이어에 싣고 타임라인 클릭으로 seek
원본 다운로드/캐시 보유 시 왼쪽 플레이어를 YouTube 임베드 대신 받은 원본 mp4로
전환한다. 세그먼트(타임라인) 클릭 시 기존 seekTo/하이라이트가 그대로 동작해
해당 지점으로 이동·재생된다. (업로드본이 있으면 업로드본 우선)

- GET /{id}/download/file: 받은 원본 스트리밍(Resource 반환 → HTTP Range 206 자동 지원)
- CurationService.cachedDownloadFile(id)
- rework.html: showServerVideoInPlayer/showYoutubeInPlayer, updateCacheUI가 플레이어 전환,
  다운로드 직후/진입 시 캐시 있으면 자동 적용, 삭제 시 YouTube로 복귀

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:58:23 +09:00
986dcffd55 feat(rework): 받은 원본 삭제 + 영상 삭제 시 캐시 자동정리
1) 재가공 화면에 '받은 원본 삭제' 버튼 추가(캐시 보유 시에만 노출).
   GET/DELETE /{id}/download 로 캐시 상태 조회·삭제(자막/세그먼트는 유지).
   진입 시 캐시 상태를 조회해 삭제버튼 노출 + 렌더 캐시 사용 여부를 갱신.
2) 수집함 영상 삭제 시 downloads/{id}.mp4 도 함께 삭제(고아 파일 방지).

- VideoDownloadService.deleteCache(id) + 단위테스트(@TempDir)
- CurationService: downloadStatus/deleteDownloadCache, delete() 캐시 정리

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:10:02 +09:00
f4ac1be171 docs(rework): 사용법 모달에 '원본 다운로드' 이미지 가이드 추가
재가공 사용법 모달 상단에 실제 화면 캡쳐 3장으로 단계별 안내 추가:
1) 원본 다운로드 버튼  2) 자동 전사된 시간싱크 자막  3) 내보내기(업로드 불필요).
?help=1 딥링크로 사용법을 바로 펼칠 수 있게 함.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 16:09:30 +09:00
26b8be1ff1 feat(rework): yt-dlp 원본 다운로드 → 전사·렌더 파이프라인 자동연결
재가공 화면 '원본 다운로드' 버튼으로 저장된 videoId를 yt-dlp(로컬 ProcessBuilder)로
받아 서버에 캐시하고, 그 파일을 기존 Python /transcribe·/render 에 그대로 투입한다.
수동 다운로드+업로드 단계를 전사·렌더 양쪽에서 제거.

- VideoDownloadService 신규: 인자 리스트 ProcessBuilder(셸 미사용), videoId 검증, 캐시 조회
- ChannelService: 전사·렌더를 Resource 기반 공통 메서드로 추출(업로드/캐시 공유)
- 컨트롤러: POST /{id}/download(다운로드+전사), /render 의 file 을 선택값으로(없으면 캐시)
- rework.html: '원본 다운로드' 버튼 + downloadOriginal(), 렌더가 서버 캐시 재사용
- 설정 ytdlp.*/download.dir(기본값 有), downloads/ gitignore
- buildCommand/videoId 검증 단위테스트(src/test 신규)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:58:50 +09:00
1ebe2dda44 feat(ui): discover filter chips + help modals, localize, tokenize long pages
- discover: filter converted to toggle chips + inline selects (matches 수집함);
  page-header + 사용법 modal (배율 지표/필터/고른 뒤 안내)
- rework: 사용법 modal (전사·세그먼트·무음제거·내보내기·발행 guide) + page-header
- dashboard: h1 "Dashboard" -> "대시보드"
- channel_detail: sort-active header uses accent color (was invisible text-white)
- multi_channel_videos/videos/production_detail: tokenize dark-assuming colors
  and title-link text-white for light-theme readability

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 06:22:55 +09:00
52c6b51e61 feat(ui): roll out light/dark theme to board, publish, rework, discover, etc.
- board: tokenized kanban columns/cards; page-header + 사용법 modal (drag guide)
- publish: tokenized table/tabs; accent tab-active; page-header + 사용법 modal
- rework: tokenized the many dark-assuming inline styles so the editor renders
  correctly in the light theme
- discover/channels/production/channel_detail: tokenized dark-assuming colors
  (white text, translucent-white surfaces, #1e1e2d) for light-theme readability

Verified board/publish/rework/discover render cleanly in light.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 06:07:51 +09:00
8178d45209 feat(rework): speech-gap trimming + render, language override
Phase 3: remove "no-talk" gaps (Whisper-segment based, not audio silencedetect
which finds nothing under background music) and render a trimmed (+speed) video
via ffmpeg, with subtitles remapped to match.

- KeepIntervalPlanner + TimelineRemapper (pure, unit-tested): keep/remove plan
  from segments (pad/minGap) and timestamp remap f(t)=t-removedBefore(t)
- GET /{id}/trim-plan (preview: keep/remove/remapped segments/kept duration)
- POST /{id}/render (multipart: file,pad,minGap,speed) -> proxy Python /render
  (ffmpeg trim/atrim+concat+atempo) -> mp4 download; ffmpeg graph validated locally
- rework.html: export panel (speed + speech-gap trim preview + SRT/video export),
  client-side SRT from working segments, language selector (auto/ko/en/zh/ja)
- transcribeFromFile forwards optional language (Whisper auto-detect misfired -> zh)

Spec updated with the audio-silence -> speech-gap design correction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 16:53:32 +09:00
b9aa04d4a3 feat(rework): synced subtitle extraction via file upload + Whisper, SRT export
Phase 1 of subtitle timeline studio. Upload a video in the rework editor →
proxy to Python /transcribe (faster-whisper) → store segments → render a
playback-synced segment list and export CapCut-importable SRT.

- ScriptSegment + SrtFormatter (pure, unit-tested) with speed rescaling
- ChannelVideoScript.segmentsJson column (ddl-auto adds it)
- ChannelService.transcribeFromFile + getSegments/parseSegments
- POST /{id}/transcribe (multipart), GET /{id}/script.srt?speed=, /script now returns segments
- rework.html: upload button, local <video>, segment list, SRT export + speed
- multipart 200MB limit; python.base-url config (PYTHON_BASE_URL)
- repo: findFirstByVideoIdOrderByIdDesc/findAllByVideoId (dedup-safe lookups)

Spec: docs/superpowers/specs/2026-06-12-subtitle-timeline-studio-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 16:23:55 +09:00
hehih
da04dbe15c Baseline before video model consolidation 2026-05-30 18:56:21 +09:00