capcut-agent/build_bg_template.py
hehihoho3@gmail.com bf1b387d6d chore: git 저장소 초기화 (기존 코드 스냅샷)
컷별 댓글 추천 작업을 태스크 단위로 되돌릴 수 있게 버전관리를 시작한다.
.gitignore 로 영상·캐시(.downloads 2.7G, .comments 72M, .media 28M)와
비밀키(.gemini_key)를 제외했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 11:36:05 +09:00

93 lines
3.9 KiB
Python

"""배경템플릿 CLI: 잘라둔 클립 → 무음컷 + 자막 + (검정-흰색-검정) 배경 템플릿.
영상은 흰 영역 위 '움직일 수 있는' 레이어 → 캡컷에서 확대/좌우 이동은 사용자가.
제목(2줄)·자막·채널은 캡컷 편집 가능 텍스트.
사용:
python build_bg_template.py <video> [draft_name]
"""
from __future__ import annotations
import os
import sys
import time
from capcut_agent.probe import probe
from capcut_agent.transcribe import transcribe
from capcut_agent.silence import detect_speech_segments
from capcut_agent.highlight import cut_plan
from capcut_agent.pipeline import _template_pos, VIDEO_TOP, VIDEO_BOTTOM
from capcut_agent.draft import build_bg_template_draft, _ty
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
ROOT = os.path.dirname(os.path.abspath(__file__))
# 템플릿(검정-흰색-검정, 흰=영상영역): 루트 배경.png 우선, 없으면 배경템플릿.png/assets
TEMPLATE = os.path.join(ROOT, "배경.png")
for alt in (os.path.join(ROOT, "배경템플릿.png"), os.path.join(ROOT, "assets", "bg_template.png")):
if not os.path.isfile(TEMPLATE):
TEMPLATE = alt
# 매 실행마다 투명 가운데(흰밴드) 프레임을 템플릿에서 파생(템플릿 수정 시 자동 반영)
FRAME_IMAGE = os.path.join(ROOT, "assets", "frame_template.png")
REC_TITLE_TOP = "<리센느 비하인드>"
REC_TITLE_MAIN = "'거제야호'는 대본에 없었다"
REC_CHANNEL = "@밥묵자 · 리센느"
def main(argv: list[str]) -> int:
if len(argv) < 2:
print("usage: python build_bg_template.py <video> [name]")
return 2
video = os.path.abspath(argv[1])
if not os.path.isfile(video):
print(f"파일 없음: {video}")
return 2
if not os.path.isfile(TEMPLATE):
print(f"배경 템플릿 없음: {TEMPLATE}")
return 2
name = argv[2] if len(argv) > 2 else (
os.path.splitext(os.path.basename(video))[0] + "_배경템플릿"
)
t0 = time.time()
# 레이아웃은 pipeline._template_pos 하나로 통일(값이 두 군데로 갈라지지 않게).
FRAME_IMAGE, _bg, pos = _template_pos(False) # 검은 띠 버전
print(f"[frame] 영상창 {VIDEO_TOP}~{VIDEO_BOTTOM}px "
f"(상단 띠 {VIDEO_TOP}px, 하단 띠 {1920 - VIDEO_BOTTOM}px=댓글영역)")
meta = probe(video)
print(f"[probe] {meta.width}x{meta.height} @ {meta.fps}fps, {meta.duration:.1f}s")
# 컷 기준 = 실제 오디오 무음(silencedetect). VAD가 놓치는 짧은 외침도 보존.
# 깔끔하게: 짧은 공백(0.3s↑)·룸톤(-28dB↓)까지 제거, pad 작게(컷 타이트).
keep = detect_speech_segments(video, meta.duration,
noise_db=-28.0, min_silence=0.3, pad=0.04)
if not keep:
print("[silence] 오디오 없음/전부 무음 — 확인 필요")
return 1
# 자막은 전사에서 가져와 시간 맞춰 얹음. VAD 끔 → 짧은 외침에도 자막 생성
tr = transcribe(video, model_size="medium", language="ko", use_cache=True,
vad_filter=False)
video_clips, captions, total = cut_plan(keep, tr)
print(f"[cut] 보존 {len(video_clips)}구간, {total:.1f}s "
f"({meta.duration - total:.1f}s 무음 제거) · 자막 {len(captions)}")
path = build_bg_template_draft(
video, None, FRAME_IMAGE, video_clips, captions, meta, name, # bg 레이어 없음(빈곳 검정)
title_top=REC_TITLE_TOP, title_main=REC_TITLE_MAIN, channel=REC_CHANNEL,
**pos,
)
print(f"[draft] {path}")
print(f" 제목: {REC_TITLE_TOP} / {REC_TITLE_MAIN} | 채널: {REC_CHANNEL}")
print(f"[done] {time.time()-t0:.1f}s. CapCut에서 '{name}' 열어 검증.")
print(" ※ 가운데는 투명(영상이 비침), 검은 띠는 영상 위. 영상 확대/이동은 캡컷에서 직접.")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))