Tools for generating vertical short videos from scripts, stock clips, narration, and storyboard images.
main.py: end-to-end pipeline using ChatGPT script generation, Pexels clips, ElevenLabs narration, Whisper subtitles, and final render.local_render.py: local stitcher for your owninput/*.mp4clips +input/*.mp3voiceover into a 1080x1920 subtitled short.storyline_image_generator.py: generates/approves narration + storyboard images instoryline_images/<run>/.runway_clip_generator.py: creates video clips from storyboard images via Runway API.script_generator.py: helper class used for generating scripts with OpenAI.
- Python 3.11
- FFmpeg installed and on
PATH(required by MoviePy/Whisper) - ImageMagick installed
- API keys (depending on workflow):
- OpenAI
- ElevenLabs
- Pexels
- Runway (only for
runway_clip_generator.py)
cd E:\Code\YTshorts
.\venv311\Scripts\Activate.ps1
pip install --upgrade pip
pip install openai python-dotenv moviepy openai-whisper requests elevenlabsIf the venv does not exist:
python -m venv venv311
.\venv311\Scripts\Activate.ps1- Copy
.env.exampleto.env - Fill in real values:
PEXELS_API_KEY=...
ELEVENLABS_API_KEY=...
OPENAI_API_KEY=...
RUNWAY_API_KEY=...RUNWAY_API_KEY is only needed for Runway clip generation.
- Put source videos in
input/as.mp4 - Put one narration file in
input/as.mp3 - Run:
python local_render.pyOutput: output/<audio_name>_local.mp4
python main.pyThis is interactive and will prompt for topics/script choices.
python storyline_image_generator.py --topic "Your story idea"Optional flags:
--seconds-per-image 5--style "cinematic, emotionally engaging, easy to follow"--voice-id <ELEVENLABS_VOICE_ID>--character bob--resume-dir <existing_run_folder>
python runway_clip_generator.py --project-dir "<run_folder_name>"You can also pass an absolute project path. Optional flags include --model, --ratio, --duration, --poll-seconds, and --max-wait-seconds.
input/,output/,published/,storyline_images/, andelevenlabs_input/are runtime folders.- They are kept in git with
.gitkeepbut their generated contents are ignored by.gitignore.