Send a topic. Get a published YouTube video. No cloud APIs, no recurring costs, no data leaving your machine. Content Factory runs a 6-stage agent pipeline entirely on your own hardware — from research and script writing through voice synthesis, image generation, video rendering, and optional scheduled upload.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Topic │───▶│ Writer │───▶│ Voice │───▶│ Assembler │───▶│ Visual │───▶│ Render │───▶│ Upload │
│ Input │ │ Agent │ │ Agent │ │ Agent │ │ Agent │ │ Agent │ │ Agent │
│ │ │ (Ollama) │ │ (Piper TTS) │ │ │ │ (ComfyUI) │ │ (ffmpeg) │ │ (YouTube) │
└─────────────┘ └─────────────┘ └─────────────┘ └──────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │ │ │
script.txt voice.wav edit_plan.json image_01..N.png final_video.mp4 youtube_upload.json
metadata.json
shots.json
A Foreman Agent orchestrates scheduling — it issues time-limited leases to workers, detects stalls, retries failed jobs (up to 3×), and enforces resource limits so a heavy GPU task can't starve lighter phases. A Monitor Agent exposes an HTTP dashboard + API on port 7000.
| Layer | Tool | Notes |
|---|---|---|
| Script writing | Ollama + local LLM | Default: qwen2.5:7b. Configurable via OLLAMA_MODEL env var |
| Voice synthesis | Piper TTS | CPU-only, offline. Default voice: en_US-lessac-medium |
| Image generation | ComfyUI + DreamShaper 8 | Auto-started/stopped to free RAM. KSampler + Euler scheduler |
| Video rendering | ffmpeg | Ken Burns zoom, 1080p 30fps, caption overlay, optional background music |
| Upload | YouTube Data API v3 | Opt-in. Scheduled publishing (default 9am). Auto-sets thumbnail |
| Orchestration | Python 3.10+ | File-based job queue, lease manager, resource guard, event log |
Python packages: psutil, pathvalidate, piper-tts, requests, Pillow, google-auth-oauthlib, google-api-python-client
The writer calls Ollama with a prompt derived from your topic and template. It produces three artifacts in sequence:
script.txt— the narration script (default ~400 words, configurable via template)metadata.json— title, description, tags, target audience, duration estimateshots.json— an array of visual shot objects, each withimage_prompt,negative_prompt,mood, and dimensions
Each artifact is checked before generation, so the agent can safely resume after a crash.
Reads script.txt, strips markdown formatting, and pipes the clean text to Piper TTS. The result is a WAV file of narrated audio.
Validates that script.txt, shots.json, and voice.wav all exist and are non-empty. Then builds a production manifest:
timeline— an ordered list of scenes mapping shots to images, with computed start/end times based on word count (~150 WPM)assets— absolute paths to every artifact the render agent will needvalidation— checksum of what's present vs. what's required
The heaviest phase. The visual agent:
- Stops all other agents to free RAM (ComfyUI needs ~6GB VRAM)
- Starts ComfyUI as a subprocess and waits for
/system_statsto respond - Submits each shot as a KSampler workflow (DreamShaper 8 checkpoint, 20 steps, CFG 7.0)
- Polls
/history/{prompt_id}until the image is ready, then downloads it - Generates a thumbnail from the first shot's prompt
- Shuts down ComfyUI and restarts the other agents
Images are generated individually with resume support — only missing files are produced.
Reads edit_plan.json and renders each scene as a short MP4 clip with:
- Ken Burns effect — alternating zoom-in / zoom-out via ffmpeg's
zoompanfilter - Caption overlay — shot description as centered white text with black outline
- Voiceover mix — the narration track is mixed as the primary audio
- Optional background music — loops an MP3 from
assets/music/at 8% volume, fading out 3 seconds before the end
Final output: 1920×1080, 30fps, H.264/AAC.
If YouTube OAuth credentials are configured, the upload agent:
- Waits until the scheduled publish time (default 09:00 local)
- Uploads
final_video.mp4with metadata frommetadata.json - Sets the custom thumbnail from
thumbnail.png - Records
youtube_upload.jsonwithvideo_id,video_url, and timestamp
Without credentials, it writes a skip marker and moves on.
Pre-built pipeline configurations that control script tone, length, visual style, and shot count:
| Template | Duration | Shots | Style |
|---|---|---|---|
documentary_video |
8-12 min | 12 | Cinematic, authoritative narrator |
educational_video |
5-10 min | 8 | Clear, engaging explainer |
tutorial_video |
5-8 min | 6 | Step-by-step instructional |
short_form_video |
60-90 sec | 4 | Punchy, social-media vertical |
python3 factoryctl.py new-job "The rise and fall of Kodak" --template documentary_video
python3 factoryctl.py new-job "How HTTPS encryption works" --template educational_videoEach template ships its own pipeline_config — system prompts, word counts, negative prompts, image dimensions, and Piper voice selection.
~/factory/
├── jobs/
│ ├── inbox/ ← New jobs land here
│ ├── active/ ← Jobs currently being processed
│ ├── completed/ ← Finished jobs
│ └── failed/ ← Jobs that exceeded max retries
├── artifacts/ ← Output files per job (script, images, video, etc.)
├── state/
│ ├── agents/ ← Heartbeat JSON files (one per agent)
│ ├── leases/ ← Lease tokens (foreman → agent assignments)
│ ├── events/ ← Structured JSONL event log
│ └── metrics/ ← System resource snapshots
├── logs/ ← Per-agent log files
├── dashboard/ ← Static HTML inspector
└── assets/music/ ← Drop .mp3 files for background music
- File-based job queue — No database. Jobs are
job.jsonfiles moved between directories. This makes the system inspectable, debuggable, and trivially resumable. - Lease-based scheduling — The Foreman is the only process that writes leases. Agents poll for their lease token; if it expires (heavy: 2h, medium: 15min, light: 5min), the Foreman reclaims the job and retries (up to 3×) or marks it failed.
- Resource isolation — Only 1 heavy (visual) job runs at a time. The
ResourceGuardenforces limits per class:heavy: 1,medium: 1,light: ∞. Anti-starvation scheduling guarantees each class gets at least 1 slot per tick. - Crash-safe resume — Every agent checks
artifact_exists()before generating anything. If an agent dies mid-pipeline, it picks up where it left off on restart. - Atomic writes — All JSON state files use write-then-rename (
path.tmp → path) to prevent partial reads. - Agent pause/resume — Operators can send pause signals via
factoryctl pause-agent <id>or the HTTP API. The foreman skips paused agents.
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Lease │────▶│ Retry │────▶│ Mark │
│ expires │ │ (up to │ │ Failed │
│ │ │ 3×) │ │ │
└──────────┘ └──────────┘ └──────────┘
│ │
│ Job resets to
│ phase=created,
│ re-enters inbox
│
Foreman detects
on next tick (3s)
The Foreman scans for expired leases every 3 seconds. If retries < max (3), the job resets to phase=created and re-enters the inbox. After 3 failures, it's moved to failed/.
| Minimum | Recommended | |
|---|---|---|
| RAM | 8 GB | 16 GB+ |
| GPU VRAM | — | 6 GB (GTX 1060 or better) |
| Disk | 10 GB | 20 GB+ |
| OS | Ubuntu 22.04+ | Ubuntu 24.04 |
Software installed automatically by install.sh: ComfyUI, DreamShaper 8, Piper TTS, ffmpeg, Python dependencies (~2.5 GB total model downloads).
git clone https://github.com/xbrxr03/content-factory
cd content-factory
bash install.shFirst run downloads ~2.5 GB of models. Re-runs are fast — skips anything already installed.
# Start all agents
bash ~/factory/start.sh
# Submit a job
cd ~/factory
python3 factoryctl.py new-job "the rise and fall of Kodak" --template documentary_video
# Watch progress
python3 factoryctl.py status
# Dashboard
open http://localhost:7000make a video about the history of NASA
make a video about the rise and fall of Nokia
factoryctl new-job "Your topic" [--template TEMPLATE] [--priority 1-10]
factoryctl status [JOB_ID]
factoryctl agents # Agent health & heartbeat status
factoryctl logs [AGENT_ID] [--tail] # View agent logs
factoryctl events [--tail N] # Structured event stream
factoryctl metrics # System resource snapshot
factoryctl inspect JOB_ID # Deep-inspect a job
factoryctl retry JOB_ID # Re-queue a failed job
factoryctl cancel-job JOB_ID # Cancel a pending/active job
factoryctl pause-job JOB_ID # Pause a pending job
factoryctl resume-job JOB_ID # Resume a paused job
factoryctl pause-agent AGENT_ID # Pause an agent
factoryctl resume-agent AGENT_ID # Resume an agent
factoryctl version # Show versionThe Monitor Agent serves a REST API on port 7000:
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
System snapshot (CPU, RAM, disk, queue depth) |
| GET | /agents |
Agent heartbeat status |
| GET | /jobs |
All jobs across all queues |
| GET | /jobs/<id> |
Single job detail + artifact list |
| GET | /jobs/<id>/artifact/<name> |
Raw artifact file |
| GET | /jobs/<id>/logs |
Agent log tail for a job |
| GET | /events?tail=N |
Last N structured events |
| GET | /metrics |
System resource metrics |
| POST | /api/jobs |
Create job {topic, priority, template} |
| POST | /api/jobs/<id>/retry |
Retry a failed job |
| POST | /api/jobs/<id>/cancel |
Cancel a job |
| POST | /api/jobs/<id>/pause |
Pause a job |
| POST | /api/jobs/<id>/resume |
Resume a job |
| POST | /api/agents/<id>/pause |
Pause an agent |
| POST | /api/agents/<id>/resume |
Resume an agent |
YouTube upload is disabled by default. To enable:
- Go to console.cloud.google.com
- Create project → Enable YouTube Data API v3
- Credentials → OAuth 2.0 → Desktop app → Download JSON
- Save as
~/.claw/skills/content-factory/youtube_credentials.json - Test:
python3 youtube_upload.py --test
Videos upload at 9am daily (configurable in schedule.json).
openclaw skills install content-factoryThis skill ships with ClawOS — the agent-native Linux OS.
MIT