Automated pipeline for generating short-form video content (TikTok, YouTube Shorts, Instagram Reels) from JSON scripts. Combines AI-generated video clips, TTS narration, stock footage, and burned-in subtitles into ready-to-upload 9:16 vertical videos.
content/*.json ──┬──> AI Video Clips (Wan 2.2 14B) ──┐
│ ├──> Video Assembly ──> final_video.mp4
├──> TTS Narration (Qwen3-TTS) ──────┤
│ │
└──> Stock Footage (Pexels API) ──────┘
(gap-filler only)
What it produces:
The pipeline uses two separate environments due to incompatible dependencies:
| Environment | Purpose | GPU VRAM |
|---|---|---|
wan21 | AI video generation (Wan 2.2 14B GGUF) | ~14 GB |
qwen3-tts | TTS + video assembly | ~4 GB |
# TTS environments
bash setup_envs.sh # Linux/macOS
setup_envs.bat # Windows
# AI video generation environment
setup_wan21.bat # Windows (creates wan21 env with PyTorch + diffusers)
# ffmpeg (required for video assembly)
winget install ffmpeg # Windows
brew install ffmpeg # macOS
sudo apt install ffmpeg # Ubuntu
# Base Python deps (in any env)
pip install requests python-dotenv tqdm
# Create .env in project root
echo "PEXELS_API_KEY=your_key_here" > .env
Get a free key at pexels.com/api. Stock footage is only used when AI clips don't cover the full audio duration.
Two-step workflow:
# Step 1: Generate AI video clips (GPU-heavy, can run overnight)
conda activate wan21
python batch_ai_clips.py
# Step 2: Generate TTS + assemble videos + copy to output folder
conda activate qwen3-tts
python batch_videos.py
Or process a single video:
# AI clips for one content
conda activate wan21
python batch_ai_clips.py --only immortal_jellyfish
# TTS + assemble for one content
conda activate qwen3-tts
python batch_videos.py --only immortal_jellyfish
Finished videos are copied to D:\Shorts by default (configurable with --dest).
Text-To-Speech-VID/
├── content/ # Content scripts (one per video)
│ ├── axolotl.json
│ ├── cuttlefish.json
│ ├── honey_badger.json
│ ├── immortal_jellyfish.json
│ ├── lyrebird.json
│ ├── mantis_shrimp.json
│ ├── octopus_intelligence.json
│ ├── pistol_shrimp.json
│ ├── platypus.json
│ └── tardigrade.json
├── voices/ # Voice profile definitions
│ └── narrator_storyteller.json
├── tts_backends/ # TTS model scripts (run in their conda envs)
│ ├── qwen_generate.py # Qwen3-TTS VoiceDesign backend
│ └── moss_generate.py # MOSS-TTS VoiceGenerator backend
├── video_gen/ # AI video generation
│ ├── wan_generate.py # Wan 2.2 T2V-A14B GGUF clip generator
│ └── wan_test.py # Single-clip test script
├── output/ # All generated artifacts (git-ignored)
│ └── <content-id>/
│ ├── ai_clips/ # AI-generated video clips
│ │ ├── ai_clip_00.mp4
│ │ ├── ai_clip_01.mp4
│ │ └── ...
│ ├── qwen3/ # TTS output
│ │ ├── full.wav # Complete narration audio
│ │ └── section_timings.json# Per-section duration manifest
│ ├── footage/ # Downloaded Pexels stock clips
│ └── final_video.mp4 # Finished video ready to upload
│
├── batch_ai_clips.py # Batch: AI clip generation (wan21 env)
├── batch_videos.py # Batch: TTS + assembly + copy (qwen3-tts env)
├── batch_generate.py # Legacy: all-in-one batch (single env)
├── generate.py # TTS orchestrator (dispatches to backends)
├── video_assemble.py # Video assembly (clips + audio + subtitles)
├── audio_utils.py # WAV concatenation utility
├── requirements.txt # Base deps: requests, python-dotenv, tqdm
├── setup_envs.bat # Windows env setup (qwen3-tts, moss-tts)
├── setup_envs.sh # Linux/macOS env setup
├── setup_wan21.bat # Windows wan21 env setup
├── .env # Pexels API key (git-ignored)
└── .gitignore
JSON files in content/ define each video:
{
"id": "immortal_jellyfish",
"title": "The Immortal Jellyfish",
"voice": "voices/narrator_storyteller.json",
"video_keywords": ["jellyfish", "jellyfish underwater", "jellyfish glowing"],
"video_prompts": [
"A tiny translucent Turritopsis dohrnii immortal jellyfish smaller than a fingernail glowing softly in dark deep ocean water, real nature documentary footage, macro lens, photorealistic, 4K",
"..."
],
"sections": {
"hook": "Attention-grabbing opener...",
"body": "Main fact with rich detail...",
"closer": "Punchy ending line..."
}
}
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier, used for output directory name |
title | Yes | Video title |
voice | Yes | Path to voice profile JSON |
sections | Yes | hook, body, closer — the narration script |
video_prompts | No | List of text-to-video prompts for AI clip generation (one clip per prompt) |
video_keywords | No | Pexels search terms for stock footage fallback |
JSON files in voices/ describe the narrator voice style. Each TTS backend has its own config key:
{
"name": "narrator_storyteller",
"description": "A warm, confident male narrator with a deep, steady voice...",
"qwen3": {
"language": "English",
"instruct": "A warm, confident male narrator..."
},
"moss": {
"instruction": "A warm, confident male narrator..."
}
}
Uses Wan 2.2 T2V-A14B with GGUF Q3_K_M quantization (~14.3 GB total for both experts). The model uses a Mixture of Experts architecture with separate high-noise and low-noise transformer experts.
Specs:
Prompt tips for 14B model:
Qwen3-TTS VoiceDesign (primary): Describe a voice in natural language — no reference audio needed. Generates the full script as one audio for voice consistency, then measures per-section timing ratios for subtitle alignment.
MOSS-TTS (secondary): Alternative model for A/B comparison.
The TTS orchestrator (generate.py) handles environment detection:
conda run -n <env>video_assemble.py combines everything into the final video:
section_timings.json from TTS if available)faststart for streamingbatch_ai_clips.py (run in wan21 env)Generates AI video clips for all content scripts. Skips content that already has all clips generated.
python batch_ai_clips.py # all content
python batch_ai_clips.py --only axolotl # single content
batch_videos.py (run in qwen3-tts env)Generates TTS, assembles videos, and copies finished videos to the destination folder. Skips cached TTS and up-to-date videos.
python batch_videos.py # all content
python batch_videos.py --only honey_badger # single content
python batch_videos.py --skip-tts # only assemble + copy
python batch_videos.py --dest "D:\MyVideos" # custom destination
Both batch scripts skip work that's already done:
ai_clip_XX.mp4 exists for all promptsfull.wav exists and content hash matches (re-generates if script text changes)final_video.mp4 is newer than both TTS audio and all AI clips# Generate TTS only
python generate.py content/axolotl.json --models qwen3
python generate.py content/axolotl.json --models qwen3 --concat --video
# Concatenate section WAVs into full.wav
python audio_utils.py output/axolotl/qwen3
# Assemble video from existing clips + audio
python video_assemble.py content/axolotl.json --audio output/axolotl/qwen3/full.wav
# Generate AI clips for one content
python video_gen/wan_generate.py content/axolotl.json
# Test a single AI clip generation
python video_gen/wan_test.py
20 commits
Python
96.1%
Batchfile
2.5%
Shell
1.3%
Automated pipeline for generating short-form video content (TikTok, YouTube Shorts, Instagram Reels) from JSON scripts. Combines AI-generated video clips, TTS narration, stock footage, and burned-in subtitles into ready-to-upload 9:16 vertical videos.
content/*.json ──┬──> AI Video Clips (Wan 2.2 14B) ──┐
│ ├──> Video Assembly ──> final_video.mp4
├──> TTS Narration (Qwen3-TTS) ──────┤
│ │
└──> Stock Footage (Pexels API) ──────┘
(gap-filler only)
What it produces:
The pipeline uses two separate environments due to incompatible dependencies:
| Environment | Purpose | GPU VRAM |
|---|---|---|
wan21 | AI video generation (Wan 2.2 14B GGUF) | ~14 GB |
qwen3-tts | TTS + video assembly | ~4 GB |
# TTS environments
bash setup_envs.sh # Linux/macOS
setup_envs.bat # Windows
# AI video generation environment
setup_wan21.bat # Windows (creates wan21 env with PyTorch + diffusers)
# ffmpeg (required for video assembly)
winget install ffmpeg # Windows
brew install ffmpeg # macOS
sudo apt install ffmpeg # Ubuntu
# Base Python deps (in any env)
pip install requests python-dotenv tqdm
# Create .env in project root
echo "PEXELS_API_KEY=your_key_here" > .env
Get a free key at pexels.com/api. Stock footage is only used when AI clips don't cover the full audio duration.
Two-step workflow:
# Step 1: Generate AI video clips (GPU-heavy, can run overnight)
conda activate wan21
python batch_ai_clips.py
# Step 2: Generate TTS + assemble videos + copy to output folder
conda activate qwen3-tts
python batch_videos.py
Or process a single video:
# AI clips for one content
conda activate wan21
python batch_ai_clips.py --only immortal_jellyfish
# TTS + assemble for one content
conda activate qwen3-tts
python batch_videos.py --only immortal_jellyfish
Finished videos are copied to D:\Shorts by default (configurable with --dest).
Text-To-Speech-VID/
├── content/ # Content scripts (one per video)
│ ├── axolotl.json
│ ├── cuttlefish.json
│ ├── honey_badger.json
│ ├── immortal_jellyfish.json
│ ├── lyrebird.json
│ ├── mantis_shrimp.json
│ ├── octopus_intelligence.json
│ ├── pistol_shrimp.json
│ ├── platypus.json
│ └── tardigrade.json
├── voices/ # Voice profile definitions
│ └── narrator_storyteller.json
├── tts_backends/ # TTS model scripts (run in their conda envs)
│ ├── qwen_generate.py # Qwen3-TTS VoiceDesign backend
│ └── moss_generate.py # MOSS-TTS VoiceGenerator backend
├── video_gen/ # AI video generation
│ ├── wan_generate.py # Wan 2.2 T2V-A14B GGUF clip generator
│ └── wan_test.py # Single-clip test script
├── output/ # All generated artifacts (git-ignored)
│ └── <content-id>/
│ ├── ai_clips/ # AI-generated video clips
│ │ ├── ai_clip_00.mp4
│ │ ├── ai_clip_01.mp4
│ │ └── ...
│ ├── qwen3/ # TTS output
│ │ ├── full.wav # Complete narration audio
│ │ └── section_timings.json# Per-section duration manifest
│ ├── footage/ # Downloaded Pexels stock clips
│ └── final_video.mp4 # Finished video ready to upload
│
├── batch_ai_clips.py # Batch: AI clip generation (wan21 env)
├── batch_videos.py # Batch: TTS + assembly + copy (qwen3-tts env)
├── batch_generate.py # Legacy: all-in-one batch (single env)
├── generate.py # TTS orchestrator (dispatches to backends)
├── video_assemble.py # Video assembly (clips + audio + subtitles)
├── audio_utils.py # WAV concatenation utility
├── requirements.txt # Base deps: requests, python-dotenv, tqdm
├── setup_envs.bat # Windows env setup (qwen3-tts, moss-tts)
├── setup_envs.sh # Linux/macOS env setup
├── setup_wan21.bat # Windows wan21 env setup
├── .env # Pexels API key (git-ignored)
└── .gitignore
JSON files in content/ define each video:
{
"id": "immortal_jellyfish",
"title": "The Immortal Jellyfish",
"voice": "voices/narrator_storyteller.json",
"video_keywords": ["jellyfish", "jellyfish underwater", "jellyfish glowing"],
"video_prompts": [
"A tiny translucent Turritopsis dohrnii immortal jellyfish smaller than a fingernail glowing softly in dark deep ocean water, real nature documentary footage, macro lens, photorealistic, 4K",
"..."
],
"sections": {
"hook": "Attention-grabbing opener...",
"body": "Main fact with rich detail...",
"closer": "Punchy ending line..."
}
}
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier, used for output directory name |
title | Yes | Video title |
voice | Yes | Path to voice profile JSON |
sections | Yes | hook, body, closer — the narration script |
video_prompts | No | List of text-to-video prompts for AI clip generation (one clip per prompt) |
video_keywords | No | Pexels search terms for stock footage fallback |
JSON files in voices/ describe the narrator voice style. Each TTS backend has its own config key:
{
"name": "narrator_storyteller",
"description": "A warm, confident male narrator with a deep, steady voice...",
"qwen3": {
"language": "English",
"instruct": "A warm, confident male narrator..."
},
"moss": {
"instruction": "A warm, confident male narrator..."
}
}
Uses Wan 2.2 T2V-A14B with GGUF Q3_K_M quantization (~14.3 GB total for both experts). The model uses a Mixture of Experts architecture with separate high-noise and low-noise transformer experts.
Specs:
Prompt tips for 14B model:
Qwen3-TTS VoiceDesign (primary): Describe a voice in natural language — no reference audio needed. Generates the full script as one audio for voice consistency, then measures per-section timing ratios for subtitle alignment.
MOSS-TTS (secondary): Alternative model for A/B comparison.
The TTS orchestrator (generate.py) handles environment detection:
conda run -n <env>video_assemble.py combines everything into the final video:
section_timings.json from TTS if available)faststart for streamingbatch_ai_clips.py (run in wan21 env)Generates AI video clips for all content scripts. Skips content that already has all clips generated.
python batch_ai_clips.py # all content
python batch_ai_clips.py --only axolotl # single content
batch_videos.py (run in qwen3-tts env)Generates TTS, assembles videos, and copies finished videos to the destination folder. Skips cached TTS and up-to-date videos.
python batch_videos.py # all content
python batch_videos.py --only honey_badger # single content
python batch_videos.py --skip-tts # only assemble + copy
python batch_videos.py --dest "D:\MyVideos" # custom destination
Both batch scripts skip work that's already done:
ai_clip_XX.mp4 exists for all promptsfull.wav exists and content hash matches (re-generates if script text changes)final_video.mp4 is newer than both TTS audio and all AI clips# Generate TTS only
python generate.py content/axolotl.json --models qwen3
python generate.py content/axolotl.json --models qwen3 --concat --video
# Concatenate section WAVs into full.wav
python audio_utils.py output/axolotl/qwen3
# Assemble video from existing clips + audio
python video_assemble.py content/axolotl.json --audio output/axolotl/qwen3/full.wav
# Generate AI clips for one content
python video_gen/wan_generate.py content/axolotl.json
# Test a single AI clip generation
python video_gen/wan_test.py
20 commits
Python
96.1%
Batchfile
2.5%
Shell
1.3%