컷별 댓글 추천 작업을 태스크 단위로 되돌릴 수 있게 버전관리를 시작한다. .gitignore 로 영상·캐시(.downloads 2.7G, .comments 72M, .media 28M)와 비밀키(.gemini_key)를 제외했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
93 lines
3.9 KiB
Python
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))
|