컷별 댓글 추천 작업을 태스크 단위로 되돌릴 수 있게 버전관리를 시작한다. .gitignore 로 영상·캐시(.downloads 2.7G, .comments 72M, .media 28M)와 비밀키(.gemini_key)를 제외했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
73 lines
2.6 KiB
Python
73 lines
2.6 KiB
Python
"""레터박스 CLI: 미리 잘라둔 클립 → 무음 컷 + 자막 + 9:16 검은 띠 드래프트.
|
||
|
||
사용:
|
||
python build_letterbox.py <video> [draft_name]
|
||
|
||
처리:
|
||
1. 영상을 9:16 레터박스(가로영상 중앙 + 위아래 검은 띠) h264 mp4 로 굽기(.media/)
|
||
2. 전사(캐시) → 발화 세그먼트만 남겨 점프컷 + 세그먼트 자막 동기
|
||
3. 캔버스 1080×1920, 레터박스 영상은 scale 1.0(비율 일치) → 크롭/transform 추측 없음
|
||
"""
|
||
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.highlight import clips_in_window
|
||
from capcut_agent.media import to_letterbox
|
||
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 main(argv: list[str]) -> int:
|
||
if len(argv) < 2:
|
||
print("usage: python build_letterbox.py <video> [name]")
|
||
return 2
|
||
video = os.path.abspath(argv[1])
|
||
if not os.path.isfile(video):
|
||
print(f"파일 없음: {video}")
|
||
return 2
|
||
name = argv[2] if len(argv) > 2 else (
|
||
os.path.splitext(os.path.basename(video))[0] + "_레터박스"
|
||
)
|
||
|
||
t0 = time.time()
|
||
src_meta = probe(video)
|
||
print(f"[probe] {src_meta.width}x{src_meta.height} @ {src_meta.fps}fps, {src_meta.duration:.1f}s")
|
||
|
||
# 1) 9:16 레터박스 굽기 (전체 클립, 타임라인 보존)
|
||
lb_path = os.path.join(MEDIA_DIR, f"{name}.mp4")
|
||
to_letterbox(video, lb_path)
|
||
lb_meta = probe(lb_path)
|
||
print(f"[letterbox] {lb_meta.width}x{lb_meta.height} → {lb_path}")
|
||
|
||
# 2) 전사 → 발화 세그먼트 = 점프컷 + 자막
|
||
tr = transcribe(video, model_size="medium", language="ko", use_cache=True)
|
||
clips = clips_in_window(tr, 0.0, src_meta.duration)
|
||
if not clips:
|
||
print("[asr] 발화 세그먼트 없음 — 오디오 확인 필요")
|
||
return 1
|
||
kept = sum(e - s for s, e, _ in clips)
|
||
print(f"[cut] 발화 {len(clips)}구간, {kept:.1f}s (원본 {src_meta.duration:.1f}s, "
|
||
f"{src_meta.duration - kept:.1f}s 무음 제거)")
|
||
|
||
# 3) 드래프트 (레터박스 영상은 9:16 → scale 1.0 자동)
|
||
path = build_shortform_draft(lb_path, clips, lb_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))
|