문서: 파이프라인 analyze/draft 분할과 컷별 댓글 추천 근거를 ARCHITECTURE.md에 반영

댓글 매칭을 받아쓰기 뒤로 옮기려고 미리 파이프라인을 analyze/draft 두 조각으로
쪼갠 구조라, 왜 그렇게 했는지·state 이벤트는 내부 전용이라는 것·manifest는
래퍼가 낸다는 함정을 남기지 않으면 다음 사람이 되돌리기 쉽다. captions_for_places와
recommend.py의 is_time_based/quotas 변경, auto.js 렌더러 통합도 같은 이유로 기록.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-08-04 17:46:06 +09:00
parent e79f2dfaa3
commit 0967e88e7b

View File

@ -30,7 +30,7 @@ capcut2/
│ ├─ app.py FastAPI. 엔드포인트 4개 + SSE 스트림 │ ├─ app.py FastAPI. 엔드포인트 4개 + SSE 스트림
│ └─ static/index.html UI 전체(단일 파일, 탭 3개 + 옵션 + SSE 렌더) │ └─ static/index.html UI 전체(단일 파일, 탭 3개 + 옵션 + SSE 렌더)
└─ capcut_agent/ └─ capcut_agent/
├─ pipeline.py ★ 두 파이프라인(process_bg_template / process_paste) ├─ pipeline.py ★ 두 파이프라인, 각각 analyze/draft 두 조각 + 얇은 래퍼(§4)
├─ draft.py ★ CapCut 드래프트 생성(pycapcut) + JSON 후처리 ├─ draft.py ★ CapCut 드래프트 생성(pycapcut) + JSON 후처리
├─ youtube.py yt-dlp 다운로드(단일/다중/정밀) + ffmpeg 병합 ├─ youtube.py yt-dlp 다운로드(단일/다중/정밀) + ffmpeg 병합
├─ paste.py 붙여넣기 JSON 파서(관대한 파싱) ├─ paste.py 붙여넣기 JSON 파서(관대한 파싱)
@ -74,9 +74,27 @@ CapCut 인스펙터 값 ↔ pycapcut 변환:
## 4. 탭별 파이프라인 ## 4. 탭별 파이프라인
두 파이프라인 모두 내부적으로 **analyze/draft 두 조각 + 얇은 래퍼**로 나뉜다
(`process_bg_template` → `bg_analyze`+`bg_draft`, `process_paste``paste_analyze`+`paste_draft`).
**왜**: 댓글 매칭을 받아쓰기(ASR) 뒤로 옮기려면 "받아쓰기까지 끝낸 상태"에서 한 번 멈출
수 있어야 한다 — analyze 조각이 거기서 멈추고 그 결과를 draft 조각이 이어받는 구조로
미리 갈라놨다(1단계 기준으로는 사용자에게 보이는 동작은 그대로).
- `*_analyze`는 끝나면 다음 조각에 넘길 상태를 실어 `{"type":"state","state":{…}}`
낸다. **이 이벤트는 내부 전용**이라 래퍼가 걸러내고 밖으로 흘리지 않는다 — 기존 UI가
모르는 타입이라 흘리면 로그에 정체불명 이벤트가 찍힌다. `t_all`(총 소요 측정
시작점)도 이 state에 실려 넘어가 `result.stats.elapsed`(전체 소요시간) 의미를 유지한다.
- `manifest`(진행 단계 목록) 이벤트는 조각 안이 아니라 **래퍼가 낸다** — 두 조각이
서로 다른 SSE 스트림에 걸쳐 쓰일 수 있어(예: 댓글 매칭이 끼어들면 analyze와 draft가
별개 요청이 됨) 각자 다른 manifest가 필요하기 때문. 목록 생성은
`bg_steps(youtube)` / `paste_steps(asr_bottom)`가 맡는다.
- `bg_draft(..., comment_cards=None)` — 주어지면 폴더에서 읽는 대신 그 목록을 그대로
쓴다(댓글 매칭 결과를 다음 단계에서 주입할 자리, 아직 미사용).
### 4-A. 📁 파일 / ▶ 유튜브 구간 → `process_bg_template()` (pipeline.py) ### 4-A. 📁 파일 / ▶ 유튜브 구간 → `process_bg_template()` (pipeline.py)
단계: `[download] → silence → asr → [scene] → draft` 단계: `[download] → silence → asr → [scene] → draft` (analyze 조각 = download~asr,
draft 조각 = scene~draft)
1. **download** (유튜브만): `cut_youtube_multi(url, ranges, out)` 1. **download** (유튜브만): `cut_youtube_multi(url, ranges, out)`
구간별로 `yt-dlp --download-sections`(h264 우선) 다운로드 → 구간 2개 이상이면 구간별로 `yt-dlp --download-sections`(h264 우선) 다운로드 → 구간 2개 이상이면
@ -109,7 +127,8 @@ CapCut 인스펙터 값 ↔ pycapcut 변환:
LLM이 만든 편집안 JSON을 **그대로** 사용. 무음컷·ASR **기본 없음**(옵션으로 무음 제거 가능). LLM이 만든 편집안 JSON을 **그대로** 사용. 무음컷·ASR **기본 없음**(옵션으로 무음 제거 가능).
단계: `download(컷 정밀) → [remove_silence] → [scene] → draft` 단계: `download(컷 정밀) → [remove_silence] → [asr_bottom] → [scene] → draft`
(analyze 조각 = download~[asr_bottom], draft 조각 = [scene]~draft)
1. **파싱** (`paste.parse_paste`): 관대한 JSON 파싱 — 1. **파싱** (`paste.parse_paste`): 관대한 JSON 파싱 —
`json.loads(strict=False)`(자막 안 실제 줄바꿈 허용), 코드펜스(```) 자동 제거, `json.loads(strict=False)`(자막 안 실제 줄바꿈 허용), 코드펜스(```) 자동 제거,
@ -245,6 +264,24 @@ title_top 서브제목 / title_main 메인제목 / channel 출처 / effect 효
- 출처: 사용자가 h-lab(https://h-lab.tolag.shop/comment-cards)에서 실제 유튜브 댓글을 - 출처: 사용자가 h-lab(https://h-lab.tolag.shop/comment-cards)에서 실제 유튜브 댓글을
카드 PNG로 저장해 폴더에 넣음. (향후: h-lab API 연동해 완전 자동화 아이디어 있음) 카드 PNG로 저장해 폴더에 넣음. (향후: h-lab API 연동해 완전 자동화 아이디어 있음)
#### 컷별 댓글 추천 근거 (recommend.py, 자동 탭)
- `pipeline.captions_for_places(captions, places, *, cap=500)` — 컷 구간마다 그 구간에
걸친 자막을 이어붙여 댓글 추천의 근거 텍스트를 만든다. **`captions`·`places` 둘 다
같은(압축) 타임라인 좌표여야 한다** — 좌표계가 다르면 엉뚱한 컷에 엉뚱한 자막이
붙는다. 겹치면 포함, 경계에 닿기만 하면 제외. 500자에서 자른다(그 이상은 Gemini
토큰만 먹고 매칭 정확도가 안 오름).
- `recommend.is_time_based(cuts)` (구 `is_whole`) — **모든 컷의 자막이 비어 있으면**
시각 기반 배정으로 판단한다. 예전엔 "컷 1개 + 자막 없음"만 걸렸는데, 자막 없는
구간이 여러 개인 경우(▶ 유튜브 구간 탭)도 걸리도록 일반화했다. 통짜(컷 1개) 모드는
이 규칙의 특수 케이스가 됐다.
- `recommend._time_based_picks(cuts, comments, quotas)` (구 `_whole_picks`) — 컷마다
슬롯을 배정하되 `used` 집합을 컷 사이에 공유해 **한 댓글이 두 컷에 중복 배정되지
않게** 한다(먼저 도는 컷이 우선).
- `recommend.build_highlight_cuts(hl, comments, *, key=None, quotas=None)``quotas`
주면 그대로 쓰고, 안 주면 `quotas_for(cuts)`(원본 컷 길이 기준)로 계산한다. 무음
제거 뒤 실제 길이 기준 장수를 밖에서 계산해 넘기는 경로를 위한 훅.
## 7. yt-dlp 관련 (youtube.py) — 함정 모음 ## 7. yt-dlp 관련 (youtube.py) — 함정 모음
- **JS 런타임 필수**: 최신 유튜브는 JS 챌린지 필요. `_js_runtime_args()` - **JS 런타임 필수**: 최신 유튜브는 JS 챌린지 필요. `_js_runtime_args()`
@ -283,6 +320,12 @@ title_top 서브제목 / title_main 메인제목 / channel 출처 / effect 효
장면분할(**기본 체크**) / 배경 흰색(**기본 체크**) / 무음 제거(붙여넣기용, 기본 꺼짐). 장면분할(**기본 체크**) / 배경 흰색(**기본 체크**) / 무음 제거(붙여넣기용, 기본 꺼짐).
- 헤더 우측 고정 링크: ✨ AI Studio(aistudio.google.com), 💬 댓글 카드(h-lab). - 헤더 우측 고정 링크: ✨ AI Studio(aistudio.google.com), 💬 댓글 카드(h-lab).
- 완료 시 결과 카드(총 소요시간 포함) + "완료되면 CapCut 자동 실행" 체크. - 완료 시 결과 카드(총 소요시간 포함) + "완료되면 CapCut 자동 실행" 체크.
- `server/static/auto.js`(🤖 자동 탭): 컷별 카드 패널 렌더는 `renderCutPanel(box,
panelId, data, opts)` 하나로 통합돼 있다 — 자동 탭(`onResult`)과 ▶ 유튜브 구간 탭
`💬 구간 댓글 매칭`(`ytMatch`)이 이 함수를 같이 쓴다. **왜**: 예전엔 두 갈래로 따로
구현돼 있었는데, 여기에 📋 붙여넣기 탭까지 더하면 세 갈래가 되어 한 곳만 고치는
실수가 나기 쉽다. `data.cuts`가 있으면 컷별 섹션(`컷 N · X초 · 카드 Q장 — 자막`),
없으면 기존 ⭐/ 폴백을 그린다.
## 9. 현재 고정값 치트시트 ## 9. 현재 고정값 치트시트