74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
"""CLI entry point.
|
|
|
|
Usage:
|
|
python main.py "https://www.youtube.com/watch?v=..." \
|
|
--num-clips 3 --aspect-ratio 9:16
|
|
"""
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
# Windows uses 'charmap' by default, which can't encode Unicode characters
|
|
# like →. Reconfigure stdout/stderr to UTF-8 so output works on all platforms.
|
|
if hasattr(sys.stdout, "reconfigure"):
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
if hasattr(sys.stderr, "reconfigure"):
|
|
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
|
|
from shorts_generator import generate_shorts
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="AI YouTube Shorts Generator")
|
|
parser.add_argument("url", help="YouTube URL, file:// URL, or local file path")
|
|
parser.add_argument(
|
|
"--mode",
|
|
choices=["api", "local"],
|
|
default="api",
|
|
help="api (default, MuAPI) or local (remote URL, file://, or local path + faster-whisper + LLM provider + ffmpeg).",
|
|
)
|
|
parser.add_argument("--num-clips", type=int, default=3, help="How many shorts to render (default: 3)")
|
|
parser.add_argument("--aspect-ratio", default="9:16", help="Output aspect ratio (default: 9:16)")
|
|
parser.add_argument("--format", default="720", help="Source download resolution: 360 / 480 / 720 / 1080 (default: 720)")
|
|
parser.add_argument("--language", default=None, help="Force Whisper language code, e.g. 'en' (default: auto-detect)")
|
|
parser.add_argument("--output-json", default=None, help="Write the full result JSON to this path")
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
result = generate_shorts(
|
|
youtube_url=args.url,
|
|
num_clips=args.num_clips,
|
|
aspect_ratio=args.aspect_ratio,
|
|
download_format=args.format,
|
|
language=args.language,
|
|
mode=args.mode,
|
|
)
|
|
except Exception as e:
|
|
print(f"\nFAILED: {e}", file=sys.stderr)
|
|
return 1
|
|
|
|
print("\n" + "=" * 72)
|
|
print(f"Mode: {result.get('mode', args.mode)}")
|
|
print(f"Source video: {result['source_video_url']}")
|
|
print(f"Highlights: {len(result['highlights'])} candidates → kept top {len(result['shorts'])}")
|
|
print("=" * 72)
|
|
for i, s in enumerate(result["shorts"], 1):
|
|
print(f"\n#{i} score={s.get('score')} {s.get('start_time'):.1f}s → {s.get('end_time'):.1f}s")
|
|
print(f" title: {s.get('title')}")
|
|
print(f" hook: {s.get('hook_sentence')}")
|
|
if s.get("clip_url"):
|
|
print(f" clip: {s['clip_url']}")
|
|
else:
|
|
print(f" clip: FAILED ({s.get('error')})")
|
|
|
|
if args.output_json:
|
|
with open(args.output_json, "w") as f:
|
|
json.dump(result, f, indent=2)
|
|
print(f"\nFull JSON written to {args.output_json}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|