capcut-agent/build_shortform.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

90 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""숏폼 CLI: 롱폼 + 하이라이트 윈도우 → 9:16 중앙크롭 자막 드래프트.
사용:
python build_shortform.py <video> <start> <end> [draft_name]
start/end 는 초(예: 312) 또는 mm:ss(예: 5:12)
처리:
1. 전사(캐시) → 윈도우 내 발화 세그먼트(자막 동기) 추출
2. 윈도우 구간만 h264 mp4 로 추출(.media/) → AV1/webm 함정 + CapCut 미리보기 회피
3. clip 타임스탬프를 추출본 0 기준으로 리베이스 후 9:16 자막 드래프트 빌드
"""
from __future__ import annotations
import os
import subprocess
import sys
import time
from capcut_agent.probe import probe
from capcut_agent.transcribe import transcribe
from capcut_agent.highlight import clips_in_window, window_bounds
from capcut_agent.draft import build_shortform_draft
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
MEDIA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".media")
os.makedirs(MEDIA_DIR, exist_ok=True)
def parse_t(v: str) -> float:
if ":" in v:
m, s = v.split(":")
return int(m) * 60 + float(s)
return float(v)
def extract_window(video: str, start: float, end: float, out_path: str) -> None:
"""[start,end] 구간을 h264 mp4 로 추출. 출력 t=0 == start (정확 seek)."""
dur = end - start + 0.3 # 끝 pad 여유
cmd = [
"ffmpeg", "-y", "-hide_banner", "-loglevel", "error",
"-ss", f"{start:.3f}", "-i", video, "-t", f"{dur:.3f}",
"-c:v", "libx264", "-preset", "veryfast", "-crf", "20",
"-pix_fmt", "yuv420p", "-c:a", "aac", "-ar", "44100",
out_path,
]
subprocess.run(cmd, check=True, encoding="utf-8", errors="replace")
def main(argv: list[str]) -> int:
if len(argv) < 4:
print("usage: python build_shortform.py <video> <start> <end> [name]")
return 2
video = os.path.abspath(argv[1])
ws, we = parse_t(argv[2]), parse_t(argv[3])
name = argv[4] if len(argv) > 4 else f"short_{int(ws)}_{int(we)}"
t0 = time.time()
tr = transcribe(video, model_size="medium", language="ko", use_cache=True)
print(f"[asr] segments={len(tr.segments)} (cache)")
ws, we = window_bounds(tr, ws, we) # 앞뒤 무음 트림
clips = clips_in_window(tr, ws, we)
if not clips:
print("[window] 발화 없음 — 구간 확인 필요")
return 1
kept = sum(e - s for s, e, _ in clips)
print(f"[window] {ws:.1f}{we:.1f}s → clips={len(clips)}, kept={kept:.1f}s")
# 윈도우 구간만 h264 추출 (AV1/webm + CapCut 미리보기 함정 회피)
media_path = os.path.join(MEDIA_DIR, f"{name}.mp4")
extract_window(video, ws, we, media_path)
print(f"[extract] {media_path}")
# clip 을 추출본 0 기준으로 리베이스
rebased = [(s - ws, e - ws, txt) for s, e, txt in clips]
meta = probe(media_path) # 추출본 메타(해상도/fps)
path = build_shortform_draft(media_path, rebased, meta, name, captions=True)
print(f"[draft] {path}")
print(f"[done] {time.time()-t0:.1f}s. CapCut에서 '{name}' 열어 검증.")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))