Commit Graph

10 Commits

Author SHA1 Message Date
d16f23a4c0 feat(rework): AI 자막 패널에 원문+번역 비교 표시
화면(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>
2026-06-26 09:12:31 +09:00
b151204267 fix(gemini): 화면 자막 타임스탬프 0/빈결과 문제 해결
원인: 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>
2026-06-25 18:43:47 +09:00
6380a28a6f fix(rework): Gemini 실패 사유를 사용자에게 명확히 + 음성 없음 메시지 정정
- GeminiSubtitleService: 403(프로젝트 차단)/429(한도)/400(키 무효)을 한국어 메시지로
  IllegalArgumentException throw → 'Internal Server Error' 대신 실제 사유 노출
- rework.html: 음성 패널 빈 경우 '말소리 없음(음악/화면자막만) 또는 전사 미실행'으로 정정

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 17:16:29 +09:00
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
a2934af1a3 chore(translate): 번역 서비스 URL을 h-translate.tolag.shop 으로 변경
h-etc2.tolag.shop → https://h-translate.tolag.shop (application.yml 기본값 + TranslateService @Value).
새 URL 동작 확인(/translate, /languages 정상).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:05:26 +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
2ec3915789 feat(dashboard): add full-set pipeline summary with single endpoint
Add GET /api/dashboard/summary aggregating pipeline status, category
distribution, publish summary, and outperformers in one call. Rewrite
dashboard.html with 5 KPI cards, pipeline funnel, publish status, and
category/source-format breakdowns (CSS bars, no chart lib).

Backend: ChannelVideoRepository counts (shorts/uncategorized),
PublishPackageRepository.countByStatus, pipelineStats shorts/longForm,
CategoryService.distribution, PublishService.dashboardSummary, new
DashboardService + DashboardApiController.

Fix: PublishService.list(null) hit UnsupportedOperationException because
findAll(Sort) uses Criteria, which rejects nullsLast precedence. Route the
no-status path through a @Query method so Sort is appended as HQL ORDER BY
(supports NULLS LAST). Also fixes the latent bug in /api/v1/publish all-list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 22:31:21 +09:00
hehih
9bd7e80542 Retire Opal/YtVideo pipeline; ChannelVideo is the single video master
The legacy Opal content pipeline (YtVideo + ScriptGen + OpalDraft/Final/
FinalAsset, driven by AnalysisWorkflowService via hardcoded Google Docs)
is no longer used. The active flow is ChannelVideo: collect -> curate
(board) -> rework -> publish.

Removed:
- service: AnalysisWorkflowService, YtVideoService, external/ExternalApiService(+Impl/Stub)
- web: YtVideoController, VideoActionController (/api/videos), video_detail.html
- web/dto: Video{Response,SearchCondition,AddRequest,DetailResponse},
  FinalAssetResponse, OpalDraftResponse, DraftGenerateRequest
- domain/video: YtVideo, YtVideoRepository, dto/Video{List,Detail}Response
- domain/script: ScriptGen(+Repository)
- domain/opal: OpalDraft/OpalFinal/OpalFinalAsset(+Repositories, dto)

Preserved the active YouTube search by extracting searchYoutubeVideos()
into a new dedicated YoutubeSearchService (no Opal deps); rewired
YoutubeSearchApiController. WebController drops the /videos/{id} Opal
detail route + YtVideoService dependency.

DB note: ddl-auto=update never drops tables, so yt_video / scriptgen /
opal_* remain as orphaned tables (harmless, no data loss). Verified by
clean compileJava + reference sweep across java/html/yml.
2026-05-30 19:51:53 +09:00
hehih
da04dbe15c Baseline before video model consolidation 2026-05-30 18:56:21 +09:00