diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ca45ca1..c871cf9 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -27,7 +27,7 @@ capcut2/ ├─ .gemini_key (선택) Gemini API 키 — 파일/유튜브 탭 자막 교정용 ├─ assets/ 파생물(frame_template.png 등) 자동 생성 ├─ server/ -│ ├─ app.py FastAPI. 엔드포인트 4개 + SSE 스트림 +│ ├─ app.py FastAPI. 탭별 analyze→stream→build 엔드포인트들 + SSE 스트림(§3) │ └─ static/index.html UI 전체(단일 파일, 탭 3개 + 옵션 + SSE 렌더) └─ capcut_agent/ ├─ pipeline.py ★ 두 파이프라인, 각각 analyze/draft 두 조각 + 얇은 래퍼(§4) @@ -37,7 +37,7 @@ capcut2/ ├─ silence.py ffmpeg silencedetect → 발화 구간 ├─ transcribe.py faster-whisper(medium/int8/cpu) 단어 타임스탬프 ├─ correct.py Gemini 자막 글자 교정(시간 불변) — gemini-2.5-flash - ├─ recommend.py 컷별 댓글 추천(자동 탭) — 타임스탬프 우선 + Gemini 텍스트 추천 + ├─ recommend.py 컷별 댓글 추천(세 탭 공통, cuts_from_state) — 타임스탬프 우선 + Gemini 텍스트 추천 ├─ highlight.py 자막 청킹(cut_plan) 유틸 ├─ scene.py ffmpeg scene 필터 장면전환 감지·분할 ├─ media.py 프레임 PNG 생성, 흰밴드 감지, 오디오 추출 @@ -58,16 +58,37 @@ CapCut 인스펙터 값 ↔ pycapcut 변환: | 엔드포인트 | 역할 | |---|---| | `GET /` | index.html 서빙. `__CDIR__` 토큰을 그 PC의 `capcut2/댓글카드` 절대경로로 치환 | -| `POST /upload` | 파일 탭. multipart 파일 + 옵션 → job 등록(content-hash id) | -| `POST /youtube` | 유튜브 탭. `url` + `ranges`(JSON `[["mm:ss","mm:ss"],…]`) + 옵션 | -| `POST /paste` | 붙여넣기 탭. `data`(편집안 JSON 문자열) + 옵션 | -| `GET /stream/{job_id}` | SSE. job 종류에 따라 파이프라인 실행, 이벤트 스트림 | +| `POST /upload` | 📁 파일 탭. multipart 파일 + 옵션 → job 등록(content-hash id) — **유일하게 옛 단일 흐름**(분석/검토 단계·댓글 매칭 없음) | +| `GET /stream/{job_id}` | SSE. `JOBS[job_id]`에 `bg_state`/`paste_state`가 있으면 **드래프트만**(§4의 build 단계), 없으면 `process_bg_template`/`process_paste` 통짜 실행 | | `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자. -- SSE 이벤트 형식: `{"type": "manifest"|"step"|"log"|"error"|"result", ...}` +**▶ 유튜브 구간 / 📋 붙여넣기 / 🤖 자동 — 공통 3단계 흐름**(analyze → stream(SSE) → build). +셋 다 "받아쓰기까지 끝낸 뒤 검토 화면에서 댓글 카드를 고르고, 그 상태 그대로 드래프트만 +만든다"는 같은 모양이라 엔드포인트 이름도 대응된다: + +| 탭 | 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}` - - `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`, `comments_dir`(폴더 경로 문자열), `title_top/title_main/channel`(파일·유튜브만), `remove_silence`(붙여넣기만). 불리언은 "1"/"0" 문자열 → `_truthy()`. @@ -77,8 +98,14 @@ CapCut 인스펙터 값 ↔ pycapcut 변환: 두 파이프라인 모두 내부적으로 **analyze/draft 두 조각 + 얇은 래퍼**로 나뉜다 (`process_bg_template` → `bg_analyze`+`bg_draft`, `process_paste` → `paste_analyze`+`paste_draft`). **왜**: 댓글 매칭을 받아쓰기(ASR) 뒤로 옮기려면 "받아쓰기까지 끝낸 상태"에서 한 번 멈출 -수 있어야 한다 — analyze 조각이 거기서 멈추고 그 결과를 draft 조각이 이어받는 구조로 -미리 갈라놨다(1단계 기준으로는 사용자에게 보이는 동작은 그대로). +수 있어야 한다 — analyze 조각이 거기서 멈추고 그 결과를 draft 조각이 이어받는다. +이 갈라짐은 이제(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":{…}}`를 낸다. **이 이벤트는 내부 전용**이라 래퍼가 걸러내고 밖으로 흘리지 않는다 — 기존 UI가 @@ -89,12 +116,23 @@ CapCut 인스펙터 값 ↔ pycapcut 변환: 별개 요청이 됨) 각자 다른 manifest가 필요하기 때문. 목록 생성은 `bg_steps(youtube)` / `paste_steps(asr_bottom)`가 맡는다. - `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, -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)` — 구간별로 `yt-dlp --download-sections`(h264 우선) 다운로드 → 구간 2개 이상이면 @@ -123,9 +161,15 @@ draft 조각 = scene~draft) 보존 구간을 장면전환 지점에서 **인접 분할**(누적 길이 불변 → 자막 싱크 무영향). 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` (analyze 조각 = download~[asr_bottom], draft 조각 = [scene]~draft) @@ -149,8 +193,9 @@ LLM이 만든 편집안 JSON을 **그대로** 사용. 무음컷·ASR **기본 JSON bottom 폴백. effect/제목/채널은 JSON 유지. ⚠ "remove_silence 후에 돌리면 remap 불필요"는 틀림 — 파일은 압축 안 되므로 cut_plan 매핑 필수. 6. **scene / 댓글카드 / draft**: 4-A와 동일(좌표도 공통 — §9). - 자동 탭에서 넘어온 경우 `process_paste(card_cuts=[...])`로 카드별 소속 컷을 - 받는다(§6 참고) — 붙여넣기 탭 직접 사용 시엔 생략(기존 전체 균등 배치). + 🤖 자동 탭·▶ 유튜브 구간 탭에서 넘어온 경우 build 단계(`paste_draft`/`bg_draft`)가 + `card_cuts=[...]`로 카드별 소속 컷을 받는다(§6 참고) — 붙여넣기 탭 직접 사용 시엔 + 생략(기존 전체 균등 배치). #### 붙여넣기 JSON 스키마 (LLM에게 시킬 형식) @@ -245,15 +290,16 @@ title_top 서브제목 / title_main 메인제목 / channel 출처 / effect 효 - `_load_comment_cards(folder, dur, interval=3.0)`: - 모든 파일명이 숫자로 시작 → 숫자순(1,2,10). 아니면 → **파일 생성시각(저장 순서)**. - png/jpg/jpeg/webp. 카드당 3초, 영상 길이 초과분은 생략. -- **배치 방식이 두 갈래**(정렬 자체는 `_card_paths()`로 공통): 자동 탭은 검토 화면에서 - 카드별 소속 컷 인덱스(`card_cuts`)를 보내고, `_cards_by_cut(paths, card_cuts, placements, dur)`가 +- **배치 방식이 두 갈래**(정렬 자체는 `_card_paths()`로 공통): 🤖 자동 · ▶ 유튜브 구간 · + 📋 붙여넣기 세 탭 모두 검토 화면(§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`와 같은 규칙. 초과분은 버린다(카드가 1초씩 번쩍이느니 몇 장 빼는 게 낫다). - - ⚠ **카드 시간은 서버가 미리 확정하지 않는다.** `/auto/build`는 "몇 번 컷 소속"만 넘기고, - 파이프라인이 컷 누적 위치(`placements`)로 시간을 계산한다. + - ⚠ **카드 시간은 서버가 미리 확정하지 않는다.** `/auto/build`·`/yt/build`·`/paste/build`는 + "몇 번 컷 소속"만 넘기고, 파이프라인이 컷 누적 위치(`placements`)로 시간을 계산한다. - ⚠ **카드 시간 계산은 무음 제거 *뒤*다.** 자막은 `_remap_caps()`로 시간을 옮기지만 (발화 시각을 따라가야 하니까), 카드는 **구간 자체**를 `_remap_placements()`로 옮기고 그 안에서 나눈다. 카드 시간을 압축 전에 만들어 자막처럼 재매핑하면 @@ -264,8 +310,20 @@ title_top 서브제목 / title_main 메인제목 / channel 출처 / effect 효 - 출처: 사용자가 h-lab(https://h-lab.tolag.shop/comment-cards)에서 실제 유튜브 댓글을 카드 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)` — 컷 구간마다 그 구간에 걸친 자막을 이어붙여 댓글 추천의 근거 텍스트를 만든다. **`captions`·`places` 둘 다 같은(압축) 타임라인 좌표여야 한다** — 좌표계가 다르면 엉뚱한 컷에 엉뚱한 자막이 @@ -320,12 +378,13 @@ title_top 서브제목 / title_main 메인제목 / channel 출처 / effect 효 장면분할(**기본 체크**) / 배경 흰색(**기본 체크**) / 무음 제거(붙여넣기용, 기본 꺼짐). - 헤더 우측 고정 링크: ✨ AI Studio(aistudio.google.com), 💬 댓글 카드(h-lab). - 완료 시 결과 카드(총 소요시간 포함) + "완료되면 CapCut 자동 실행" 체크. -- `server/static/auto.js`(🤖 자동 탭): 컷별 카드 패널 렌더는 `renderCutPanel(box, - panelId, data, opts)` 하나로 통합돼 있다 — 자동 탭(`onResult`)과 ▶ 유튜브 구간 탭 - `💬 구간 댓글 매칭`(`ytMatch`)이 이 함수를 같이 쓴다. **왜**: 예전엔 두 갈래로 따로 - 구현돼 있었는데, 여기에 📋 붙여넣기 탭까지 더하면 세 갈래가 되어 한 곳만 고치는 - 실수가 나기 쉽다. `data.cuts`가 있으면 컷별 섹션(`컷 N · X초 · 카드 Q장 — 자막`), - 없으면 기존 ⭐/➕ 폴백을 그린다. +- `server/static/auto.js`: 컷별 카드 패널 렌더는 `renderCutPanel(box, panelId, data, + opts)` 하나로 통합돼 있다 — 🤖 자동 탭(2차 검토, `panelId=hl.id`) · ▶ 유튜브 구간 탭 + (`panelId="yt"`) · 📋 붙여넣기 탭(`panelId="paste"`) 검토 화면이 전부 이 함수를 같이 + 쓴다. **왜**: 예전엔 자동 탭만 있었는데, 유튜브 구간·붙여넣기 탭에 같은 검토 화면을 + 추가하면서 세 갈래가 되어 한 곳만 고치는 실수가 나기 쉬웠다(하나로 통합해 예방). + `data.cuts`가 있으면 컷별 섹션(`컷 N · X초 · 카드 Q장 — 자막`), 없으면 기존 ⭐/➕ + 폴백을 그린다 — 세 탭 모두 `*_stream`이 낸 `result`(§3·§6)를 그대로 이 함수에 넘긴다. ## 9. 현재 고정값 치트시트 @@ -377,7 +436,10 @@ CHANNEL_RATIO = 0.85 # 아래 띠에서 85% 지점 UI 하단 **"🩹 레이어 수리"** / `GET /drafts`(꼬임 감지) · `POST /repair`. - ⚠ `POST /repair` 는 **CapCut 실행 중이면 409 로 거부** — 열어둔 채 수리하면 CapCut 이 메모리 상태로 덮어써 되돌아간다(실측: 11:04:32 수리 → 11:05:39 CapCut 저장으로 원복). -- `JOBS`는 메모리 저장 — 서버 재시작하면 job 소실(스트림 전에 재시작하면 재제출 필요). +- `JOBS`/`ANALYSES`/`YSTATES`/`PSTATES`/`PREPARES` 전부 메모리 저장 — 서버 재시작하면 + 전부 소실(§3). ▶ 유튜브 구간·📋 붙여넣기·🤖 자동 탭은 분석(1단계)과 빌드(3단계) + 사이에 서버가 재시작되면 검토 화면에서 빌드를 눌러도 "분석 결과가 만료됐습니다" + 404가 뜬다 — 해결은 재분석뿐(재시작 원인 자체를 없앨 수는 없음, SETUP.md §10). - 검증은 최종적으로 **사용자가 CapCut에서 열어 확인**하는 방식. - v1(`../capcut`, 포트 8000)은 별개 코드베이스 — 여기 수정해도 v1에 반영 안 됨(역도 동일). diff --git a/README.md b/README.md index 6adb131..c469770 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,20 @@ ## 2. 세 가지 입력 방법 (탭) ### 📋 붙여넣기 (기본 탭) — 추천 -LLM이 만든 편집안 JSON을 붙여넣으면 컷·자막을 **그대로** 사용합니다. (무음컷·받아쓰기 없음) +LLM이 만든 편집안 JSON을 붙여넣으면 컷·자막을 **그대로** 사용합니다. +**댓글 매칭 시작**을 누르면 컷 정밀 다운로드·병합 → 무음 제거 → 받아쓰기(Whisper) → +h-lab 댓글 수집 → 컷별 댓글 카드 추천까지 자동으로 돌고, 검토 화면에서 컷마다 카드를 +확인·선택한 뒤 **선택한 카드로 드래프트 만들기**를 누르면 완성됩니다. +(⚠ 무음 제거·받아쓰기는 댓글 매칭 정확도를 위해 항상 켜져 있고 끌 수 없습니다 — +화면 자막은 그대로 JSON의 `bottom`을 쓸지, Whisper 자동 자막으로 바꿀지 따로 고를 수 있습니다.) ### ▶ 유튜브 구간 한 URL + 여러 구간(+ 구간 추가) → 이어붙여 **무음컷 + 자동 자막(Whisper)**. +📋 붙여넣기 탭과 같은 흐름 — 분석(다운로드·무음·받아쓰기)이 끝나면 h-lab 댓글을 +구간별로 자동 추천해 검토 화면을 보여주고, 카드를 고른 뒤 드래프트를 만듭니다. ### 📁 파일 -로컬 영상 파일 → **무음컷 + 자동 자막**. +로컬 영상 파일 → **무음컷 + 자동 자막**. (댓글 카드는 폴더 지정 방식만 — 검토 화면 없음) 세 방법 모두 아래 **영상 옵션**을 함께 적용합니다. @@ -122,7 +129,7 @@ capcut2/ ├─ requirements.txt 파이썬 패키지 목록 ├─ .gemini_key (선택) Gemini 키 ├─ server/ -│ ├─ app.py FastAPI 서버 (/upload /youtube /paste /stream) +│ ├─ app.py FastAPI 서버 (/upload, /stream, 탭별 analyze→stream→build) │ └─ static/index.html 웹 UI └─ capcut_agent/ ├─ pipeline.py 처리 파이프라인(다운로드→컷→자막→드래프트) diff --git a/SETUP.md b/SETUP.md index c1c3a1b..9663bc9 100644 --- a/SETUP.md +++ b/SETUP.md @@ -285,6 +285,9 @@ yt-dlp --version | 댓글이 엉뚱한 장면에 뜬다 | 컷 소속 없이 전체 균등 배치로 깔림(위 폴백 화면) 또는 추천 자체가 안 맞음 | 위 두 줄 확인. 컷 섹션이 보인다면 그 컷 섹션 안에서 카드를 갈아끼우면 그 컷 위로 옮겨진다 | | "컷별 댓글 추천"에서 오래 멈춰 보인다 | 하이라이트마다 Gemini 를 순차로 부른다(429 회피). 최악 5×90초 | **새로고침하지 말 것** — 분석이 통째로 날아간다. 로그에 `ID n 컷별 댓글 추천 중… (i/N)` 이 올라오면 정상 진행 중 | | 콘솔에 한글 깨짐 | Windows cp949 | 표시만 깨짐. 로직·결과와 무관 | +| 카드 고르기까지 오래 걸린다 | 받아쓰기를 먼저 돌린다(추천 정확도를 위해) | 정상. 1분 영상당 ≈30초 | +| 🤖 배지가 하나도 없다 | Gemini 실패 또는 자막 없음 | 로그의 경고 확인. 🔤·➕는 계속 동작 | +| 분석 결과가 만료됐다고 나온다 | 서버 재시작으로 메모리 상태 소실 | 분석을 다시 돌린다 | ---