process_bg_template을 bg_analyze/bg_draft로 분할
댓글 매칭을 받아쓰기 다음 단계에 끼워 넣으려면 파이프라인이 "받아쓰기까지"에서 한 번 멈출 수 있어야 한다. 다운로드~asr(bg_analyze)과 장면분할~draft(bg_draft) 두 조각으로 나누고, 기존 동작을 완전히 보존하는 얇은 래퍼(process_bg_template)로 다시 감쌌다. 이벤트 스트림을 분할 전/후로 떠서 문자 단위로 비교해 11개 이벤트가 완전히 동일함을 확인했다(state 이벤트는 밖으로 새지 않음). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
22a9758e74
commit
475b7b0f71
@ -202,29 +202,30 @@ BG_STEPS: List[Dict[str, str]] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def process_bg_template(
|
def bg_steps(youtube: Optional[dict]) -> List[Dict[str, str]]:
|
||||||
|
"""배경템플릿 파이프라인의 manifest 스텝. 쪼갠 두 조각(bg_analyze/bg_draft)이 함께 내는 전체 목록."""
|
||||||
|
steps = ([{"id": "download", "label": "유튜브 여러 구간 다운로드·병합"}] if youtube else [])
|
||||||
|
return steps + [{"id": "silence", "label": "무음·발화 분석"},
|
||||||
|
{"id": "asr", "label": "받아쓰기 (Gemini/Whisper)"},
|
||||||
|
{"id": "draft", "label": "템플릿 드래프트 생성"}]
|
||||||
|
|
||||||
|
|
||||||
|
async def bg_analyze(
|
||||||
video_path: Optional[str],
|
video_path: Optional[str],
|
||||||
draft_name: str,
|
draft_name: str,
|
||||||
*,
|
*,
|
||||||
title_top: str = "",
|
title_top: str = "",
|
||||||
title_main: str = "",
|
title_main: str = "",
|
||||||
channel: str = "",
|
channel: str = "",
|
||||||
video_scale: float = 1.0,
|
|
||||||
flip_horizontal: bool = False,
|
|
||||||
scene_split: bool = False,
|
|
||||||
comments_dir: str = "",
|
|
||||||
cards_fixed: bool = False,
|
|
||||||
bg_white: bool = False,
|
|
||||||
youtube: Optional[dict] = None,
|
youtube: Optional[dict] = None,
|
||||||
) -> AsyncIterator[dict]:
|
) -> AsyncIterator[dict]:
|
||||||
"""배경템플릿 파이프라인: [유튜브 구간 다운로드] → 무음컷 → 자막 → 드래프트. SSE 이벤트."""
|
"""배경템플릿 파이프라인 앞부분: [유튜브 구간 다운로드] → probe → 무음컷 → 받아쓰기.
|
||||||
|
|
||||||
|
마지막에 `bg_draft` 로 이어줄 `{"type": "state", ...}` 를 낸다.
|
||||||
|
(오디오가 전부 무음이면 `error` 를 내고 조용히 끝난다 — 이때는 `state` 가 안 나온다.)
|
||||||
|
"""
|
||||||
t_all = time.perf_counter()
|
t_all = time.perf_counter()
|
||||||
use_gemini = has_gemini_key()
|
use_gemini = has_gemini_key()
|
||||||
steps = ([{"id": "download", "label": "유튜브 여러 구간 다운로드·병합"}] if youtube else [])
|
|
||||||
steps += [{"id": "silence", "label": "무음·발화 분석"},
|
|
||||||
{"id": "asr", "label": "받아쓰기 (Gemini/Whisper)"},
|
|
||||||
{"id": "draft", "label": "템플릿 드래프트 생성"}]
|
|
||||||
yield {"type": "manifest", "steps": steps}
|
|
||||||
|
|
||||||
# ── 유튜브 여러 구간 다운로드 + 병합 (URL 입력 시) ──
|
# ── 유튜브 여러 구간 다운로드 + 병합 (URL 입력 시) ──
|
||||||
if youtube:
|
if youtube:
|
||||||
@ -301,6 +302,41 @@ async def process_bg_template(
|
|||||||
"elapsed": round(time.perf_counter() - t, 1),
|
"elapsed": round(time.perf_counter() - t, 1),
|
||||||
"detail": f"{method} · 자막 {len(captions)}개"}
|
"detail": f"{method} · 자막 {len(captions)}개"}
|
||||||
|
|
||||||
|
yield {"type": "state", "state": {
|
||||||
|
"video_path": video_path, "meta": meta, "keep": keep,
|
||||||
|
"video_clips": video_clips, "captions": captions, "total": total,
|
||||||
|
"draft_name": draft_name, "title_top": title_top, "title_main": title_main,
|
||||||
|
"channel": channel, "t_all": t_all,
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
async def bg_draft(
|
||||||
|
state: dict,
|
||||||
|
*,
|
||||||
|
video_scale: float = 1.0,
|
||||||
|
flip_horizontal: bool = False,
|
||||||
|
scene_split: bool = False,
|
||||||
|
comments_dir: str = "",
|
||||||
|
cards_fixed: bool = False,
|
||||||
|
bg_white: bool = False,
|
||||||
|
comment_cards: Optional[list] = None,
|
||||||
|
) -> AsyncIterator[dict]:
|
||||||
|
"""배경템플릿 파이프라인 뒷부분: 장면전환 분할 → 댓글 카드 → 드래프트 생성 → result.
|
||||||
|
|
||||||
|
`bg_analyze` 가 낸 `state` 를 이어받는다. `comment_cards` 가 주어지면
|
||||||
|
`_load_comment_cards` 대신 그걸 쓴다(댓글 매칭 단계 준비).
|
||||||
|
"""
|
||||||
|
video_path = state["video_path"]
|
||||||
|
meta = state["meta"]
|
||||||
|
video_clips = state["video_clips"]
|
||||||
|
captions = state["captions"]
|
||||||
|
total = state["total"]
|
||||||
|
draft_name = state["draft_name"]
|
||||||
|
title_top = state["title_top"]
|
||||||
|
title_main = state["title_main"]
|
||||||
|
channel = state["channel"]
|
||||||
|
t_all = state["t_all"]
|
||||||
|
|
||||||
# ── 장면전환 분할 (선택): 컷이 바뀌는 지점에서 세그먼트 추가 분할 ──
|
# ── 장면전환 분할 (선택): 컷이 바뀌는 지점에서 세그먼트 추가 분할 ──
|
||||||
if scene_split:
|
if scene_split:
|
||||||
yield {"type": "log", "msg": "장면전환 감지 중… (화면 바뀌는 컷 찾기)"}
|
yield {"type": "log", "msg": "장면전환 감지 중… (화면 바뀌는 컷 찾기)"}
|
||||||
@ -314,7 +350,8 @@ async def process_bg_template(
|
|||||||
yield {"type": "step", "id": "draft", "status": "start"}
|
yield {"type": "step", "id": "draft", "status": "start"}
|
||||||
t = time.perf_counter()
|
t = time.perf_counter()
|
||||||
frame, bg, pos = await asyncio.to_thread(_template_pos, bg_white)
|
frame, bg, pos = await asyncio.to_thread(_template_pos, bg_white)
|
||||||
cards = _load_comment_cards(comments_dir, total, fixed=cards_fixed)
|
cards = comment_cards if comment_cards is not None else _load_comment_cards(
|
||||||
|
comments_dir, total, fixed=cards_fixed)
|
||||||
if cards:
|
if cards:
|
||||||
yield {"type": "log", "msg": f"댓글 카드 {len(cards)}개 하단 삽입(3초 간격)"}
|
yield {"type": "log", "msg": f"댓글 카드 {len(cards)}개 하단 삽입(3초 간격)"}
|
||||||
path = await asyncio.to_thread(
|
path = await asyncio.to_thread(
|
||||||
@ -344,6 +381,43 @@ async def process_bg_template(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def process_bg_template(
|
||||||
|
video_path: Optional[str],
|
||||||
|
draft_name: str,
|
||||||
|
*,
|
||||||
|
title_top: str = "",
|
||||||
|
title_main: str = "",
|
||||||
|
channel: str = "",
|
||||||
|
video_scale: float = 1.0,
|
||||||
|
flip_horizontal: bool = False,
|
||||||
|
scene_split: bool = False,
|
||||||
|
comments_dir: str = "",
|
||||||
|
cards_fixed: bool = False,
|
||||||
|
bg_white: bool = False,
|
||||||
|
youtube: Optional[dict] = None,
|
||||||
|
) -> AsyncIterator[dict]:
|
||||||
|
"""배경템플릿 파이프라인 — 📁 파일 탭 / ▶ 유튜브 구간 탭용 얇은 래퍼.
|
||||||
|
|
||||||
|
analyze/draft 두 조각을 연달아 부른다. `state` 이벤트는 밖으로 안 흘린다
|
||||||
|
(기존 UI가 모르는 타입이라 흘리면 로그에 정체불명 이벤트가 찍힌다).
|
||||||
|
"""
|
||||||
|
yield {"type": "manifest", "steps": bg_steps(youtube)}
|
||||||
|
state = None
|
||||||
|
async for ev in bg_analyze(video_path, draft_name, title_top=title_top,
|
||||||
|
title_main=title_main, channel=channel, youtube=youtube):
|
||||||
|
if ev.get("type") == "state":
|
||||||
|
state = ev["state"]
|
||||||
|
continue
|
||||||
|
yield ev
|
||||||
|
if state is None:
|
||||||
|
return # analyze 가 error 로 끝난 경우
|
||||||
|
async for ev in bg_draft(state, video_scale=video_scale,
|
||||||
|
flip_horizontal=flip_horizontal, scene_split=scene_split,
|
||||||
|
comments_dir=comments_dir, cards_fixed=cards_fixed,
|
||||||
|
bg_white=bg_white):
|
||||||
|
yield ev
|
||||||
|
|
||||||
|
|
||||||
# ── 템플릿 레이아웃 (캔버스 1080×1920, 단위 = 픽셀, 위가 0) ───────────────────
|
# ── 템플릿 레이아웃 (캔버스 1080×1920, 단위 = 픽셀, 위가 0) ───────────────────
|
||||||
# 레퍼런스 템플릿을 실측해 잡은 값. **여기만 고치면 전체 배치가 같이 움직인다.**
|
# 레퍼런스 템플릿을 실측해 잡은 값. **여기만 고치면 전체 배치가 같이 움직인다.**
|
||||||
# (예전엔 배경.png 흰밴드 자동감지였는데, 좌표를 정확히 통제하려고 상수로 바꿨다)
|
# (예전엔 배경.png 흰밴드 자동감지였는데, 좌표를 정확히 통제하려고 상수로 바꿨다)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user