From e79f2dfaa39234e76a5ed4a0f52236b69ea89e5c Mon Sep 17 00:00:00 2001 From: "hehihoho3@gmail.com" Date: Tue, 4 Aug 2026 17:36:50 +0900 Subject: [PATCH] =?UTF-8?q?process=5Fpaste=EB=A5=BC=20paste=5Fanalyze/past?= =?UTF-8?q?e=5Fdraft=20=EB=91=90=20=EC=A1=B0=EA=B0=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=ED=95=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 댓글 매칭을 받아쓰기(asr_bottom) 다음에 넣으려면 파이프라인이 "받아쓰기까지" 상태에서 한 번 멈출 수 있어야 한다. 앞서 process_bg_template에 적용한 것과 같은 패턴(analyze/draft 분리 + state 이벤트 + 얇은 래퍼)을 process_paste에도 적용했다. 로직은 옮기기만 했고 바꾸지 않았다. Co-Authored-By: Claude Opus 5 (1M context) --- capcut_agent/pipeline.py | 94 +++++++++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/capcut_agent/pipeline.py b/capcut_agent/pipeline.py index b36d78a..395193e 100644 --- a/capcut_agent/pipeline.py +++ b/capcut_agent/pipeline.py @@ -460,26 +460,30 @@ def _template_pos(white: bool = False): return frame, bg, pos -async def process_paste( +def paste_steps(asr_bottom: bool) -> List[Dict[str, str]]: + """붙여넣기 파이프라인의 manifest 스텝.""" + steps = [{"id": "download", "label": "컷 정밀 다운로드·병합"}] + if asr_bottom: + steps.append({"id": "asr", "label": "받아쓰기 (Whisper)"}) + steps.append({"id": "draft", "label": "템플릿 드래프트 생성"}) + return steps + + +async def paste_analyze( payload: dict, draft_name: str, *, - video_scale: float = 1.0, - flip_horizontal: bool = False, - scene_split: bool = False, - comments_dir: str = "", - cards_fixed: bool = False, - card_cuts: Optional[List[int]] = None, - bg_white: bool = False, remove_silence: bool = False, asr_bottom: bool = False, name_suffix: str = "", ) -> AsyncIterator[dict]: - """붙여넣기(JSON) 파이프라인: 컷 정밀 다운로드·병합 → 공급된 자막 2트랙으로 드래프트. + """붙여넣기 파이프라인 앞부분: 컷 정밀 다운로드·병합 → probe → 컷 배치·자막 계산 → + [무음 제거] → [asr_bottom 받아쓰기]. 자막 배치 시간 = 컷 순서 누적. asr_bottom=True 면 JSON bottom 대신 병합본을 Whisper로 받아써 실제 발화 타이밍에 맞춘 하단 자막을 생성(effect/제목은 JSON 유지). payload: paste.parse_paste 결과 dict. + 마지막에 `paste_draft` 로 이어줄 `{"type": "state", ...}` 를 낸다. """ t_all = time.perf_counter() cuts = payload["cuts"] # [(s, e, bottom, effect)] @@ -488,12 +492,6 @@ async def process_paste( title_main = payload.get("title_main", "") channel = payload.get("channel", "") - steps = [{"id": "download", "label": "컷 정밀 다운로드·병합"}] - if asr_bottom: - steps.append({"id": "asr", "label": "받아쓰기 (Whisper)"}) - steps.append({"id": "draft", "label": "템플릿 드래프트 생성"}) - yield {"type": "manifest", "steps": steps} - # ── 컷 정밀 다운로드 + 병합 ── yield {"type": "step", "id": "download", "status": "start"} yield {"type": "log", "msg": f"{len(cuts)}개 컷 정밀 다운로드·병합 중… (프레임 정확 컷)"} @@ -587,6 +585,47 @@ async def process_paste( "elapsed": round(time.perf_counter() - t, 1), "detail": f"{method} · 자막 {len(bottom_caps)}개"} + yield {"type": "state", "state": { + "video_path": video_path, "meta": meta, "dur": dur, "cuts": cuts, + "placements": placements, "card_places": card_places, + "video_clips": video_clips, "timeline_dur": timeline_dur, + "bottom_caps": bottom_caps, "eff_caps": eff_caps, + "draft_name": draft_name, "title_top": title_top, "title_main": title_main, + "channel": channel, "t_all": t_all, + }} + + +async def paste_draft( + state: dict, + *, + video_scale: float = 1.0, + flip_horizontal: bool = False, + scene_split: bool = False, + comments_dir: str = "", + cards_fixed: bool = False, + card_cuts: Optional[List[int]] = None, + bg_white: bool = False, +) -> AsyncIterator[dict]: + """붙여넣기 파이프라인 뒷부분: [장면분할] → 댓글 카드 → 드래프트 생성 → result. + + `paste_analyze` 가 낸 `state` 를 이어받는다. + """ + video_path = state["video_path"] + meta = state["meta"] + dur = state["dur"] + cuts = state["cuts"] + placements = state["placements"] + card_places = state["card_places"] + video_clips = state["video_clips"] + timeline_dur = state["timeline_dur"] + bottom_caps = state["bottom_caps"] + eff_caps = state["eff_caps"] + draft_name = state["draft_name"] + title_top = state["title_top"] + title_main = state["title_main"] + channel = state["channel"] + t_all = state["t_all"] + # ── draft ── yield {"type": "step", "id": "draft", "status": "start"} t = time.perf_counter() @@ -646,6 +685,31 @@ async def process_paste( } +async def process_paste(payload, draft_name, *, video_scale=1.0, flip_horizontal=False, + scene_split=False, comments_dir="", cards_fixed=False, + card_cuts=None, bg_white=False, remove_silence=False, + asr_bottom=False, name_suffix="") -> AsyncIterator[dict]: + """붙여넣기(JSON) 파이프라인 — 기존 호출부용 얇은 래퍼. + + analyze/draft 두 조각을 연달아 부른다. `state` 이벤트는 밖으로 안 흘린다. + """ + yield {"type": "manifest", "steps": paste_steps(asr_bottom)} + state = None + async for ev in paste_analyze(payload, draft_name, remove_silence=remove_silence, + asr_bottom=asr_bottom, name_suffix=name_suffix): + if ev.get("type") == "state": + state = ev["state"] + continue + yield ev + if state is None: + return + async for ev in paste_draft(state, video_scale=video_scale, + flip_horizontal=flip_horizontal, scene_split=scene_split, + comments_dir=comments_dir, cards_fixed=cards_fixed, + card_cuts=card_cuts, bg_white=bg_white): + yield ev + + STEPS: List[Dict[str, str]] = [ {"id": "silence", "label": "무음·발화 분석"}, {"id": "draft", "label": "점프컷 드래프트 생성"},