문서: 세 탭(구간/붙여넣기/자동) analyze→stream→build 3단계 흐름을 문서에 반영
받아쓰기 후 댓글 매칭으로 바뀐 서버 흐름이 ARCHITECTURE.md에 전혀 안 남아 있어 다음 세션이 옛 단일 엔드포인트(POST /youtube 등)를 전제로 코드를 읽을 위험이 있었다. 탭별 흐름 표·cuts_from_state() 공통 조립점·두 좌표계 금기를 명시하고, SETUP.md 문제 해결표에 새 증상 3개, README.md 탭 설명에 새 흐름을 반영했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
71d0f2fc4e
commit
77265f822e
126
ARCHITECTURE.md
126
ARCHITECTURE.md
@ -27,7 +27,7 @@ capcut2/
|
|||||||
├─ .gemini_key (선택) Gemini API 키 — 파일/유튜브 탭 자막 교정용
|
├─ .gemini_key (선택) Gemini API 키 — 파일/유튜브 탭 자막 교정용
|
||||||
├─ assets/ 파생물(frame_template.png 등) 자동 생성
|
├─ assets/ 파생물(frame_template.png 등) 자동 생성
|
||||||
├─ server/
|
├─ server/
|
||||||
│ ├─ app.py FastAPI. 엔드포인트 4개 + SSE 스트림
|
│ ├─ app.py FastAPI. 탭별 analyze→stream→build 엔드포인트들 + SSE 스트림(§3)
|
||||||
│ └─ static/index.html UI 전체(단일 파일, 탭 3개 + 옵션 + SSE 렌더)
|
│ └─ static/index.html UI 전체(단일 파일, 탭 3개 + 옵션 + SSE 렌더)
|
||||||
└─ capcut_agent/
|
└─ capcut_agent/
|
||||||
├─ pipeline.py ★ 두 파이프라인, 각각 analyze/draft 두 조각 + 얇은 래퍼(§4)
|
├─ pipeline.py ★ 두 파이프라인, 각각 analyze/draft 두 조각 + 얇은 래퍼(§4)
|
||||||
@ -37,7 +37,7 @@ capcut2/
|
|||||||
├─ silence.py ffmpeg silencedetect → 발화 구간
|
├─ silence.py ffmpeg silencedetect → 발화 구간
|
||||||
├─ transcribe.py faster-whisper(medium/int8/cpu) 단어 타임스탬프
|
├─ transcribe.py faster-whisper(medium/int8/cpu) 단어 타임스탬프
|
||||||
├─ correct.py Gemini 자막 글자 교정(시간 불변) — gemini-2.5-flash
|
├─ correct.py Gemini 자막 글자 교정(시간 불변) — gemini-2.5-flash
|
||||||
├─ recommend.py 컷별 댓글 추천(자동 탭) — 타임스탬프 우선 + Gemini 텍스트 추천
|
├─ recommend.py 컷별 댓글 추천(세 탭 공통, cuts_from_state) — 타임스탬프 우선 + Gemini 텍스트 추천
|
||||||
├─ highlight.py 자막 청킹(cut_plan) 유틸
|
├─ highlight.py 자막 청킹(cut_plan) 유틸
|
||||||
├─ scene.py ffmpeg scene 필터 장면전환 감지·분할
|
├─ scene.py ffmpeg scene 필터 장면전환 감지·분할
|
||||||
├─ media.py 프레임 PNG 생성, 흰밴드 감지, 오디오 추출
|
├─ media.py 프레임 PNG 생성, 흰밴드 감지, 오디오 추출
|
||||||
@ -58,16 +58,37 @@ CapCut 인스펙터 값 ↔ pycapcut 변환:
|
|||||||
| 엔드포인트 | 역할 |
|
| 엔드포인트 | 역할 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `GET /` | index.html 서빙. `__CDIR__` 토큰을 그 PC의 `capcut2/댓글카드` 절대경로로 치환 |
|
| `GET /` | index.html 서빙. `__CDIR__` 토큰을 그 PC의 `capcut2/댓글카드` 절대경로로 치환 |
|
||||||
| `POST /upload` | 파일 탭. multipart 파일 + 옵션 → job 등록(content-hash id) |
|
| `POST /upload` | 📁 파일 탭. multipart 파일 + 옵션 → job 등록(content-hash id) — **유일하게 옛 단일 흐름**(분석/검토 단계·댓글 매칭 없음) |
|
||||||
| `POST /youtube` | 유튜브 탭. `url` + `ranges`(JSON `[["mm:ss","mm:ss"],…]`) + 옵션 |
|
| `GET /stream/{job_id}` | SSE. `JOBS[job_id]`에 `bg_state`/`paste_state`가 있으면 **드래프트만**(§4의 build 단계), 없으면 `process_bg_template`/`process_paste` 통짜 실행 |
|
||||||
| `POST /paste` | 붙여넣기 탭. `data`(편집안 JSON 문자열) + 옵션 |
|
|
||||||
| `GET /stream/{job_id}` | SSE. job 종류에 따라 파이프라인 실행, 이벤트 스트림 |
|
|
||||||
| `POST /open-capcut` | CapCut 실행(시작메뉴 lnk → LOCALAPPDATA exe 폴백) |
|
| `POST /open-capcut` | CapCut 실행(시작메뉴 lnk → LOCALAPPDATA exe 폴백) |
|
||||||
|
| `GET /drafts` / `POST /repair` | 레이어 꼬임 목록 조회 · 수리(§10) |
|
||||||
|
| `GET /auto/avatar` | 댓글 프로필 이미지 동일 출처 프록시(구글 도메인만, SSRF 방지) |
|
||||||
|
| `GET /prompts` / `POST /prompts` | 🤖 자동 탭 Step1·Step3 프롬프트 조회/저장 |
|
||||||
|
|
||||||
- job 은 메모리 dict `JOBS[hash]`. hash = 입력 시그니처 sha1 12자.
|
**▶ 유튜브 구간 / 📋 붙여넣기 / 🤖 자동 — 공통 3단계 흐름**(analyze → stream(SSE) → build).
|
||||||
- SSE 이벤트 형식: `{"type": "manifest"|"step"|"log"|"error"|"result", ...}`
|
셋 다 "받아쓰기까지 끝낸 뒤 검토 화면에서 댓글 카드를 고르고, 그 상태 그대로 드래프트만
|
||||||
|
만든다"는 같은 모양이라 엔드포인트 이름도 대응된다:
|
||||||
|
|
||||||
|
| 탭 | 1단계: 분석 예약 | 2단계: SSE(다운로드·받아쓰기·댓글매칭·추천) | 3단계: 빌드 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ▶ 유튜브 구간 | `POST /yt/analyze` → `ANALYSES[aid]` | `GET /yt/stream/{aid}` → `YSTATES[aid]` | `POST /yt/build` → `JOBS[h]["bg_state"]` |
|
||||||
|
| 📋 붙여넣기 | `POST /paste/analyze` → `ANALYSES[aid]` | `GET /paste/stream/{aid}` → `PSTATES[aid]` | `POST /paste/build` → `JOBS[h]["paste_state"]` |
|
||||||
|
| 🤖 자동 | `POST /auto/analyze` → `ANALYSES[aid]`(Step1+3만, 댓글 매칭 없음) | 1차 검토(제목 선택·✕ 제외) 뒤 `POST /auto/prepare` → `PREPARES[pid]`, `GET /auto/prepare/{pid}` → `PSTATES["{aid}:{id}"]`(제외 안 된 ID만 순차) | 2차 검토(컷별 댓글) 뒤 `POST /auto/build` → `JOBS[h]["paste_state"]` |
|
||||||
|
|
||||||
|
- 3단계(빌드)는 셋 다 결과로 받은 `job_id`를 그대로 기존 `GET /stream/{job_id}`에 물려
|
||||||
|
드래프트만 만든다(다운로드·받아쓰기 재실행 없음) — `bg_state`/`paste_state`가 그 분기 키다.
|
||||||
|
- 옛 `POST /youtube`(유튜브 탭 단일 엔드포인트)·`POST /yt/comments`는 이 흐름으로 대체되며 삭제됐다.
|
||||||
|
- `POST /paste`(붙여넣기 편집안 통짜 처리)는 코드에 남아 있지만 **현재 UI는 전부
|
||||||
|
`/paste/analyze` 흐름을 쓴다** — 하위 호환/직접 호출용으로만 존재.
|
||||||
|
- job 은 메모리 dict `JOBS[hash]`. hash = 입력 시그니처 sha1 12자. 분석·검토 단계 상태는
|
||||||
|
`ANALYSES[aid]`(입력)·`YSTATES[aid]`/`PSTATES[aid 또는 "aid:id"]`(받아쓰기 결과)·
|
||||||
|
`PREPARES[pid]`(자동 탭 2단계 예약) — **전부 메모리라 서버 재시작 시 소실**된다(§10).
|
||||||
|
- SSE 이벤트 형식: `{"type": "manifest"|"step"|"log"|"error"|"result"|"state", ...}`
|
||||||
- `manifest`: `{steps:[{id,label}]}` / `step`: `{id,status:"start"|"done",elapsed,detail}`
|
- `manifest`: `{steps:[{id,label}]}` / `step`: `{id,status:"start"|"done",elapsed,detail}`
|
||||||
- `result`: `{draft_name, draft_path, stats:{duration,kept,cut,segments,captions,elapsed}}`
|
- `state`: **내부 전용**(analyze↔draft 조각 간 상태 전달, §4) — 래퍼가 걸러내 밖으로 안 흘림
|
||||||
|
- `result`: 파이프라인 통짜 흐름은 `{draft_name, draft_path, stats:{…}}`,
|
||||||
|
analyze 단계는 `{cuts, need, cutRanges, matched, candidates, comments, warnings, …}`
|
||||||
|
(검토 화면 렌더용 — §6)
|
||||||
- 공통 폼 필드: `video_scale`(% 문자열, 기본 144), `flip`, `scene`, `bg_white`,
|
- 공통 폼 필드: `video_scale`(% 문자열, 기본 144), `flip`, `scene`, `bg_white`,
|
||||||
`comments_dir`(폴더 경로 문자열), `title_top/title_main/channel`(파일·유튜브만),
|
`comments_dir`(폴더 경로 문자열), `title_top/title_main/channel`(파일·유튜브만),
|
||||||
`remove_silence`(붙여넣기만). 불리언은 "1"/"0" 문자열 → `_truthy()`.
|
`remove_silence`(붙여넣기만). 불리언은 "1"/"0" 문자열 → `_truthy()`.
|
||||||
@ -77,8 +98,14 @@ CapCut 인스펙터 값 ↔ pycapcut 변환:
|
|||||||
두 파이프라인 모두 내부적으로 **analyze/draft 두 조각 + 얇은 래퍼**로 나뉜다
|
두 파이프라인 모두 내부적으로 **analyze/draft 두 조각 + 얇은 래퍼**로 나뉜다
|
||||||
(`process_bg_template` → `bg_analyze`+`bg_draft`, `process_paste` → `paste_analyze`+`paste_draft`).
|
(`process_bg_template` → `bg_analyze`+`bg_draft`, `process_paste` → `paste_analyze`+`paste_draft`).
|
||||||
**왜**: 댓글 매칭을 받아쓰기(ASR) 뒤로 옮기려면 "받아쓰기까지 끝낸 상태"에서 한 번 멈출
|
**왜**: 댓글 매칭을 받아쓰기(ASR) 뒤로 옮기려면 "받아쓰기까지 끝낸 상태"에서 한 번 멈출
|
||||||
수 있어야 한다 — analyze 조각이 거기서 멈추고 그 결과를 draft 조각이 이어받는 구조로
|
수 있어야 한다 — analyze 조각이 거기서 멈추고 그 결과를 draft 조각이 이어받는다.
|
||||||
미리 갈라놨다(1단계 기준으로는 사용자에게 보이는 동작은 그대로).
|
이 갈라짐은 이제(2~4단계) **서버 HTTP 계층에도 그대로 노출돼 있다** — ▶ 유튜브 구간
|
||||||
|
· 📋 붙여넣기 · 🤖 자동 세 탭 모두 `*_analyze`(§3 표의 1단계)가 요청 하나로 끝나고,
|
||||||
|
그 결과를 받아쓰기까지 끝낸 SSE 스트림(2단계)이 이어받아 **h-lab 댓글 수집 →
|
||||||
|
`recommend.cuts_from_state()`로 컷별 카드 추천**까지 마친 뒤 검토 화면용 `result`를
|
||||||
|
낸다(엔드포인트 대응표는 §3). 사용자가 검토 화면에서 카드를 고르고 나서야
|
||||||
|
3단계(`*_build`)가 그 상태로 `*_draft`를 돌려 실제 드래프트를 만든다. **파일 탭만 이 3단계 분리 없이
|
||||||
|
`/upload → GET /stream/{job_id}`로 통짜 실행되는 옛 흐름 그대로**다(댓글 매칭 없음).
|
||||||
|
|
||||||
- `*_analyze`는 끝나면 다음 조각에 넘길 상태를 실어 `{"type":"state","state":{…}}`를
|
- `*_analyze`는 끝나면 다음 조각에 넘길 상태를 실어 `{"type":"state","state":{…}}`를
|
||||||
낸다. **이 이벤트는 내부 전용**이라 래퍼가 걸러내고 밖으로 흘리지 않는다 — 기존 UI가
|
낸다. **이 이벤트는 내부 전용**이라 래퍼가 걸러내고 밖으로 흘리지 않는다 — 기존 UI가
|
||||||
@ -89,12 +116,23 @@ CapCut 인스펙터 값 ↔ pycapcut 변환:
|
|||||||
별개 요청이 됨) 각자 다른 manifest가 필요하기 때문. 목록 생성은
|
별개 요청이 됨) 각자 다른 manifest가 필요하기 때문. 목록 생성은
|
||||||
`bg_steps(youtube)` / `paste_steps(asr_bottom)`가 맡는다.
|
`bg_steps(youtube)` / `paste_steps(asr_bottom)`가 맡는다.
|
||||||
- `bg_draft(..., comment_cards=None)` — 주어지면 폴더에서 읽는 대신 그 목록을 그대로
|
- `bg_draft(..., comment_cards=None)` — 주어지면 폴더에서 읽는 대신 그 목록을 그대로
|
||||||
쓴다(댓글 매칭 결과를 다음 단계에서 주입할 자리, 아직 미사용).
|
쓴다. 이제 실제로 쓰인다: `/yt/build`가 검토 화면에서 고른 `card_cuts`를
|
||||||
|
`_cards_by_cut()`으로 컷별 카드 목록으로 바꿔 여기 넘긴다(§6).
|
||||||
|
- ⚠ **`recommend.cuts_from_state()`가 세 탭의 공통 조립점**이다(`capcut_agent/recommend.py`).
|
||||||
|
각 `*_stream` SSE가 받아쓰기 상태(`places`/`captions`, 압축 좌표)와 h-lab 댓글을
|
||||||
|
이 함수 하나에 넘기면 `(cuts, need, ai_failed)`를 돌려주고, 그걸 그대로 검토 화면
|
||||||
|
`result`에 실어 보낸다 — 세 탭이 각자 추천 로직을 따로 구현하지 않는다(§6).
|
||||||
|
**압축 좌표(`places`/`captions`)와 원본 좌표(`orig_ranges`, ⭐ 분:초 매칭용)를 섞으면
|
||||||
|
안 된다** — 둘 다 같은 길이의 리스트로 인덱스만으로 짝지어 다닌다(§6 재강조).
|
||||||
|
|
||||||
### 4-A. 📁 파일 / ▶ 유튜브 구간 → `process_bg_template()` (pipeline.py)
|
### 4-A. 📁 파일 / ▶ 유튜브 구간 (pipeline.py)
|
||||||
|
|
||||||
단계: `[download] → silence → asr → [scene] → draft` (analyze 조각 = download~asr,
|
단계: `[download] → silence → asr → [scene] → draft` (analyze 조각 = download~asr,
|
||||||
draft 조각 = scene~draft)
|
draft 조각 = scene~draft). 두 탭이 이 단계들을 공유하지만 **호출 경로는 다르다**:
|
||||||
|
📁 파일 탭은 `process_bg_template()`(analyze+draft 통짜, `/upload → /stream/{job_id}`)를
|
||||||
|
그대로 쓰고, ▶ 유튜브 구간 탭은 §3/§4 서두의 3단계 흐름대로 `bg_analyze()`(`/yt/stream`)와
|
||||||
|
`bg_draft()`(`/yt/build`)를 **별개 요청으로 나눠 호출**한다(그 사이에 댓글 매칭이
|
||||||
|
끼기 때문). 아래 단계 설명은 두 경로 모두에 동일하게 적용된다.
|
||||||
|
|
||||||
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개 이상이면
|
||||||
@ -123,9 +161,15 @@ draft 조각 = scene~draft)
|
|||||||
보존 구간을 장면전환 지점에서 **인접 분할**(누적 길이 불변 → 자막 싱크 무영향).
|
보존 구간을 장면전환 지점에서 **인접 분할**(누적 길이 불변 → 자막 싱크 무영향).
|
||||||
5. **draft**: §5 빌더 호출. 좌표는 전부 `_template_pos()`가 레이아웃 상수에서 파생(§9).
|
5. **draft**: §5 빌더 호출. 좌표는 전부 `_template_pos()`가 레이아웃 상수에서 파생(§9).
|
||||||
|
|
||||||
### 4-B. 📋 붙여넣기 (기본 탭) → `process_paste()` (pipeline.py)
|
### 4-B. 📋 붙여넣기 (기본 탭) (pipeline.py)
|
||||||
|
|
||||||
LLM이 만든 편집안 JSON을 **그대로** 사용. 무음컷·ASR **기본 없음**(옵션으로 무음 제거 가능).
|
LLM이 만든 편집안 JSON을 **그대로** 사용. `paste_analyze()`/`process_paste()` 함수
|
||||||
|
자체의 `remove_silence`/`asr_bottom` 기본값은 각각 꺼짐/켜짐이지만, ⚠ **현재 UI의
|
||||||
|
기본 흐름(`/paste/analyze` → `/paste/stream`)은 댓글 매칭 추천을 위해 둘 다 항상
|
||||||
|
`True`로 고정해서 호출한다**(§3) — 사용자가 화면에서 끌 수 없다. 검토 화면에서
|
||||||
|
"하단 자막 자동 생성" 체크를 끄면 **받아쓰기 자체는 그대로 하되** 최종 화면 자막만
|
||||||
|
`/paste/build` 단계에서 JSON `bottom`으로 되돌린다(아래 5번). 옛 `POST /paste`
|
||||||
|
단일 엔드포인트는 폼 체크박스 값을 그대로 써서(§3) 지금도 두 옵션 다 끌 수 있다.
|
||||||
|
|
||||||
단계: `download(컷 정밀) → [remove_silence] → [asr_bottom] → [scene] → draft`
|
단계: `download(컷 정밀) → [remove_silence] → [asr_bottom] → [scene] → draft`
|
||||||
(analyze 조각 = download~[asr_bottom], draft 조각 = [scene]~draft)
|
(analyze 조각 = download~[asr_bottom], draft 조각 = [scene]~draft)
|
||||||
@ -149,8 +193,9 @@ LLM이 만든 편집안 JSON을 **그대로** 사용. 무음컷·ASR **기본
|
|||||||
JSON bottom 폴백. effect/제목/채널은 JSON 유지.
|
JSON bottom 폴백. effect/제목/채널은 JSON 유지.
|
||||||
⚠ "remove_silence 후에 돌리면 remap 불필요"는 틀림 — 파일은 압축 안 되므로 cut_plan 매핑 필수.
|
⚠ "remove_silence 후에 돌리면 remap 불필요"는 틀림 — 파일은 압축 안 되므로 cut_plan 매핑 필수.
|
||||||
6. **scene / 댓글카드 / draft**: 4-A와 동일(좌표도 공통 — §9).
|
6. **scene / 댓글카드 / draft**: 4-A와 동일(좌표도 공통 — §9).
|
||||||
자동 탭에서 넘어온 경우 `process_paste(card_cuts=[...])`로 카드별 소속 컷을
|
🤖 자동 탭·▶ 유튜브 구간 탭에서 넘어온 경우 build 단계(`paste_draft`/`bg_draft`)가
|
||||||
받는다(§6 참고) — 붙여넣기 탭 직접 사용 시엔 생략(기존 전체 균등 배치).
|
`card_cuts=[...]`로 카드별 소속 컷을 받는다(§6 참고) — 붙여넣기 탭 직접 사용 시엔
|
||||||
|
생략(기존 전체 균등 배치).
|
||||||
|
|
||||||
#### 붙여넣기 JSON 스키마 (LLM에게 시킬 형식)
|
#### 붙여넣기 JSON 스키마 (LLM에게 시킬 형식)
|
||||||
|
|
||||||
@ -245,15 +290,16 @@ title_top 서브제목 / title_main 메인제목 / channel 출처 / effect 효
|
|||||||
- `_load_comment_cards(folder, dur, interval=3.0)`:
|
- `_load_comment_cards(folder, dur, interval=3.0)`:
|
||||||
- 모든 파일명이 숫자로 시작 → 숫자순(1,2,10). 아니면 → **파일 생성시각(저장 순서)**.
|
- 모든 파일명이 숫자로 시작 → 숫자순(1,2,10). 아니면 → **파일 생성시각(저장 순서)**.
|
||||||
- png/jpg/jpeg/webp. 카드당 3초, 영상 길이 초과분은 생략.
|
- png/jpg/jpeg/webp. 카드당 3초, 영상 길이 초과분은 생략.
|
||||||
- **배치 방식이 두 갈래**(정렬 자체는 `_card_paths()`로 공통): 자동 탭은 검토 화면에서
|
- **배치 방식이 두 갈래**(정렬 자체는 `_card_paths()`로 공통): 🤖 자동 · ▶ 유튜브 구간 ·
|
||||||
카드별 소속 컷 인덱스(`card_cuts`)를 보내고, `_cards_by_cut(paths, card_cuts, placements, dur)`가
|
📋 붙여넣기 세 탭 모두 검토 화면(§4·§6 `cuts_from_state`)에서 컷별 소속 컷 인덱스
|
||||||
|
(`card_cuts`)를 보내고, `_cards_by_cut(paths, card_cuts, placements, dur)`가
|
||||||
**그 컷 구간 안에서** 균등 배치한다(한 컷이 덜 차도 다음 컷 카드가 앞으로 밀리지 않음).
|
**그 컷 구간 안에서** 균등 배치한다(한 컷이 덜 차도 다음 컷 카드가 앞으로 밀리지 않음).
|
||||||
파일/유튜브 탭·붙여넣기 탭 직접 사용은 컷 소속을 몰라 기존 `_load_comment_cards` 전체 균등
|
📁 파일 탭(검토 화면 없음)과 붙여넣기 옛 단일 엔드포인트(`POST /paste`) 직접 사용만
|
||||||
배치 그대로 쓴다.
|
컷 소속을 몰라 기존 `_load_comment_cards` 전체 균등 배치 그대로 쓴다.
|
||||||
- 컷당 장수 상한은 `max(1, floor(컷길이/3초))` — `_load_comment_cards`와 같은 규칙.
|
- 컷당 장수 상한은 `max(1, floor(컷길이/3초))` — `_load_comment_cards`와 같은 규칙.
|
||||||
초과분은 버린다(카드가 1초씩 번쩍이느니 몇 장 빼는 게 낫다).
|
초과분은 버린다(카드가 1초씩 번쩍이느니 몇 장 빼는 게 낫다).
|
||||||
- ⚠ **카드 시간은 서버가 미리 확정하지 않는다.** `/auto/build`는 "몇 번 컷 소속"만 넘기고,
|
- ⚠ **카드 시간은 서버가 미리 확정하지 않는다.** `/auto/build`·`/yt/build`·`/paste/build`는
|
||||||
파이프라인이 컷 누적 위치(`placements`)로 시간을 계산한다.
|
"몇 번 컷 소속"만 넘기고, 파이프라인이 컷 누적 위치(`placements`)로 시간을 계산한다.
|
||||||
- ⚠ **카드 시간 계산은 무음 제거 *뒤*다.** 자막은 `_remap_caps()`로 시간을 옮기지만
|
- ⚠ **카드 시간 계산은 무음 제거 *뒤*다.** 자막은 `_remap_caps()`로 시간을 옮기지만
|
||||||
(발화 시각을 따라가야 하니까), 카드는 **구간 자체**를 `_remap_placements()`로 옮기고
|
(발화 시각을 따라가야 하니까), 카드는 **구간 자체**를 `_remap_placements()`로 옮기고
|
||||||
그 안에서 나눈다. 카드 시간을 압축 전에 만들어 자막처럼 재매핑하면
|
그 안에서 나눈다. 카드 시간을 압축 전에 만들어 자막처럼 재매핑하면
|
||||||
@ -264,8 +310,20 @@ 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, 자동 탭)
|
#### 컷별 댓글 추천 근거 (recommend.py, 세 탭 공통)
|
||||||
|
|
||||||
|
- `recommend.cuts_from_state(places, orig_ranges, captions, comments, *, key=None)
|
||||||
|
-> (cuts, need, ai_failed)` — ★ **세 탭(▶ 유튜브 구간 · 📋 붙여넣기 · 🤖 자동)이
|
||||||
|
전부 이 함수 하나로 검토 화면용 컷 목록을 조립한다**(`*_stream` SSE의 recommend
|
||||||
|
스텝에서 호출, §4). `places`·`captions`는 받아쓰기 상태의 **압축 타임라인**(무음
|
||||||
|
제거 후 — 자막 추출·카드 장수·배치 기준), `orig_ranges`는 **원본 영상 시각**
|
||||||
|
(⭐ 분:초 매칭 기준). ⚠ **둘은 길이가 같아야 하고 인덱스로만 짝짓는다 — 좌표계를
|
||||||
|
섞으면 카드가 통째로 어긋난다.** 내부에서 `captions_for_places()`로 컷별 자막을
|
||||||
|
뽑고, `quotas_for()` 대신 압축 길이 기준 quota를 직접 계산한 뒤
|
||||||
|
`build_highlight_cuts()`에 넘긴다. 반환하는 `cuts[]` 원소는
|
||||||
|
`{"i","sec","bottom","quota","picks"}`(`picks` 원소 `{"idx","why"}`,
|
||||||
|
`why` ∈ `ts|ai|word|like` — 배정 순위 §1 참고), `sec`는 압축 길이 기준이라
|
||||||
|
"20초인데 왜 3장?" 같은 화면 표시 불일치가 안 생긴다.
|
||||||
- `pipeline.captions_for_places(captions, places, *, cap=500)` — 컷 구간마다 그 구간에
|
- `pipeline.captions_for_places(captions, places, *, cap=500)` — 컷 구간마다 그 구간에
|
||||||
걸친 자막을 이어붙여 댓글 추천의 근거 텍스트를 만든다. **`captions`·`places` 둘 다
|
걸친 자막을 이어붙여 댓글 추천의 근거 텍스트를 만든다. **`captions`·`places` 둘 다
|
||||||
같은(압축) 타임라인 좌표여야 한다** — 좌표계가 다르면 엉뚱한 컷에 엉뚱한 자막이
|
같은(압축) 타임라인 좌표여야 한다** — 좌표계가 다르면 엉뚱한 컷에 엉뚱한 자막이
|
||||||
@ -320,12 +378,13 @@ 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,
|
- `server/static/auto.js`: 컷별 카드 패널 렌더는 `renderCutPanel(box, panelId, data,
|
||||||
panelId, data, opts)` 하나로 통합돼 있다 — 자동 탭(`onResult`)과 ▶ 유튜브 구간 탭
|
opts)` 하나로 통합돼 있다 — 🤖 자동 탭(2차 검토, `panelId=hl.id`) · ▶ 유튜브 구간 탭
|
||||||
`💬 구간 댓글 매칭`(`ytMatch`)이 이 함수를 같이 쓴다. **왜**: 예전엔 두 갈래로 따로
|
(`panelId="yt"`) · 📋 붙여넣기 탭(`panelId="paste"`) 검토 화면이 전부 이 함수를 같이
|
||||||
구현돼 있었는데, 여기에 📋 붙여넣기 탭까지 더하면 세 갈래가 되어 한 곳만 고치는
|
쓴다. **왜**: 예전엔 자동 탭만 있었는데, 유튜브 구간·붙여넣기 탭에 같은 검토 화면을
|
||||||
실수가 나기 쉽다. `data.cuts`가 있으면 컷별 섹션(`컷 N · X초 · 카드 Q장 — 자막`),
|
추가하면서 세 갈래가 되어 한 곳만 고치는 실수가 나기 쉬웠다(하나로 통합해 예방).
|
||||||
없으면 기존 ⭐/➕ 폴백을 그린다.
|
`data.cuts`가 있으면 컷별 섹션(`컷 N · X초 · 카드 Q장 — 자막`), 없으면 기존 ⭐/➕
|
||||||
|
폴백을 그린다 — 세 탭 모두 `*_stream`이 낸 `result`(§3·§6)를 그대로 이 함수에 넘긴다.
|
||||||
|
|
||||||
## 9. 현재 고정값 치트시트
|
## 9. 현재 고정값 치트시트
|
||||||
|
|
||||||
@ -377,7 +436,10 @@ CHANNEL_RATIO = 0.85 # 아래 띠에서 85% 지점
|
|||||||
UI 하단 **"🩹 레이어 수리"** / `GET /drafts`(꼬임 감지) · `POST /repair`.
|
UI 하단 **"🩹 레이어 수리"** / `GET /drafts`(꼬임 감지) · `POST /repair`.
|
||||||
- ⚠ `POST /repair` 는 **CapCut 실행 중이면 409 로 거부** — 열어둔 채 수리하면 CapCut 이
|
- ⚠ `POST /repair` 는 **CapCut 실행 중이면 409 로 거부** — 열어둔 채 수리하면 CapCut 이
|
||||||
메모리 상태로 덮어써 되돌아간다(실측: 11:04:32 수리 → 11:05:39 CapCut 저장으로 원복).
|
메모리 상태로 덮어써 되돌아간다(실측: 11:04:32 수리 → 11:05:39 CapCut 저장으로 원복).
|
||||||
- `JOBS`는 메모리 저장 — 서버 재시작하면 job 소실(스트림 전에 재시작하면 재제출 필요).
|
- `JOBS`/`ANALYSES`/`YSTATES`/`PSTATES`/`PREPARES` 전부 메모리 저장 — 서버 재시작하면
|
||||||
|
전부 소실(§3). ▶ 유튜브 구간·📋 붙여넣기·🤖 자동 탭은 분석(1단계)과 빌드(3단계)
|
||||||
|
사이에 서버가 재시작되면 검토 화면에서 빌드를 눌러도 "분석 결과가 만료됐습니다"
|
||||||
|
404가 뜬다 — 해결은 재분석뿐(재시작 원인 자체를 없앨 수는 없음, SETUP.md §10).
|
||||||
- 검증은 최종적으로 **사용자가 CapCut에서 열어 확인**하는 방식.
|
- 검증은 최종적으로 **사용자가 CapCut에서 열어 확인**하는 방식.
|
||||||
- v1(`../capcut`, 포트 8000)은 별개 코드베이스 — 여기 수정해도 v1에 반영 안 됨(역도 동일).
|
- v1(`../capcut`, 포트 8000)은 별개 코드베이스 — 여기 수정해도 v1에 반영 안 됨(역도 동일).
|
||||||
|
|
||||||
|
|||||||
13
README.md
13
README.md
@ -18,13 +18,20 @@
|
|||||||
## 2. 세 가지 입력 방법 (탭)
|
## 2. 세 가지 입력 방법 (탭)
|
||||||
|
|
||||||
### 📋 붙여넣기 (기본 탭) — 추천
|
### 📋 붙여넣기 (기본 탭) — 추천
|
||||||
LLM이 만든 편집안 JSON을 붙여넣으면 컷·자막을 **그대로** 사용합니다. (무음컷·받아쓰기 없음)
|
LLM이 만든 편집안 JSON을 붙여넣으면 컷·자막을 **그대로** 사용합니다.
|
||||||
|
**댓글 매칭 시작**을 누르면 컷 정밀 다운로드·병합 → 무음 제거 → 받아쓰기(Whisper) →
|
||||||
|
h-lab 댓글 수집 → 컷별 댓글 카드 추천까지 자동으로 돌고, 검토 화면에서 컷마다 카드를
|
||||||
|
확인·선택한 뒤 **선택한 카드로 드래프트 만들기**를 누르면 완성됩니다.
|
||||||
|
(⚠ 무음 제거·받아쓰기는 댓글 매칭 정확도를 위해 항상 켜져 있고 끌 수 없습니다 —
|
||||||
|
화면 자막은 그대로 JSON의 `bottom`을 쓸지, Whisper 자동 자막으로 바꿀지 따로 고를 수 있습니다.)
|
||||||
|
|
||||||
### ▶ 유튜브 구간
|
### ▶ 유튜브 구간
|
||||||
한 URL + 여러 구간(+ 구간 추가) → 이어붙여 **무음컷 + 자동 자막(Whisper)**.
|
한 URL + 여러 구간(+ 구간 추가) → 이어붙여 **무음컷 + 자동 자막(Whisper)**.
|
||||||
|
📋 붙여넣기 탭과 같은 흐름 — 분석(다운로드·무음·받아쓰기)이 끝나면 h-lab 댓글을
|
||||||
|
구간별로 자동 추천해 검토 화면을 보여주고, 카드를 고른 뒤 드래프트를 만듭니다.
|
||||||
|
|
||||||
### 📁 파일
|
### 📁 파일
|
||||||
로컬 영상 파일 → **무음컷 + 자동 자막**.
|
로컬 영상 파일 → **무음컷 + 자동 자막**. (댓글 카드는 폴더 지정 방식만 — 검토 화면 없음)
|
||||||
|
|
||||||
세 방법 모두 아래 **영상 옵션**을 함께 적용합니다.
|
세 방법 모두 아래 **영상 옵션**을 함께 적용합니다.
|
||||||
|
|
||||||
@ -122,7 +129,7 @@ capcut2/
|
|||||||
├─ requirements.txt 파이썬 패키지 목록
|
├─ requirements.txt 파이썬 패키지 목록
|
||||||
├─ .gemini_key (선택) Gemini 키
|
├─ .gemini_key (선택) Gemini 키
|
||||||
├─ server/
|
├─ server/
|
||||||
│ ├─ app.py FastAPI 서버 (/upload /youtube /paste /stream)
|
│ ├─ app.py FastAPI 서버 (/upload, /stream, 탭별 analyze→stream→build)
|
||||||
│ └─ static/index.html 웹 UI
|
│ └─ static/index.html 웹 UI
|
||||||
└─ capcut_agent/
|
└─ capcut_agent/
|
||||||
├─ pipeline.py 처리 파이프라인(다운로드→컷→자막→드래프트)
|
├─ pipeline.py 처리 파이프라인(다운로드→컷→자막→드래프트)
|
||||||
|
|||||||
3
SETUP.md
3
SETUP.md
@ -285,6 +285,9 @@ yt-dlp --version
|
|||||||
| 댓글이 엉뚱한 장면에 뜬다 | 컷 소속 없이 전체 균등 배치로 깔림(위 폴백 화면) 또는 추천 자체가 안 맞음 | 위 두 줄 확인. 컷 섹션이 보인다면 그 컷 섹션 안에서 카드를 갈아끼우면 그 컷 위로 옮겨진다 |
|
| 댓글이 엉뚱한 장면에 뜬다 | 컷 소속 없이 전체 균등 배치로 깔림(위 폴백 화면) 또는 추천 자체가 안 맞음 | 위 두 줄 확인. 컷 섹션이 보인다면 그 컷 섹션 안에서 카드를 갈아끼우면 그 컷 위로 옮겨진다 |
|
||||||
| "컷별 댓글 추천"에서 오래 멈춰 보인다 | 하이라이트마다 Gemini 를 순차로 부른다(429 회피). 최악 5×90초 | **새로고침하지 말 것** — 분석이 통째로 날아간다. 로그에 `ID n 컷별 댓글 추천 중… (i/N)` 이 올라오면 정상 진행 중 |
|
| "컷별 댓글 추천"에서 오래 멈춰 보인다 | 하이라이트마다 Gemini 를 순차로 부른다(429 회피). 최악 5×90초 | **새로고침하지 말 것** — 분석이 통째로 날아간다. 로그에 `ID n 컷별 댓글 추천 중… (i/N)` 이 올라오면 정상 진행 중 |
|
||||||
| 콘솔에 한글 깨짐 | Windows cp949 | 표시만 깨짐. 로직·결과와 무관 |
|
| 콘솔에 한글 깨짐 | Windows cp949 | 표시만 깨짐. 로직·결과와 무관 |
|
||||||
|
| 카드 고르기까지 오래 걸린다 | 받아쓰기를 먼저 돌린다(추천 정확도를 위해) | 정상. 1분 영상당 ≈30초 |
|
||||||
|
| 🤖 배지가 하나도 없다 | Gemini 실패 또는 자막 없음 | 로그의 경고 확인. 🔤·➕는 계속 동작 |
|
||||||
|
| 분석 결과가 만료됐다고 나온다 | 서버 재시작으로 메모리 상태 소실 | 분석을 다시 돌린다 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user