parked-sketch-sandbox-autovideo-moneyprinter-2026-06-11
DARE.CO.UK · PARKED SKETCH · 2026-06-14
Mirrored from ~/.claude/.../memory/parked_sketch_sandbox_autovideo_moneyprinter_2026-06-11.md. This is a design sketch parked for future build — read for context, not as a current deliverable.
sandbox.gf.cx /autovideo slot idea — generate net-new short-form video from a topic via MoneyPrinterTurbo; needs a VM data-plane (can’t run on a Worker), parked pending POC
Idea (Dan, 2026-06-11): “sandbox /autovideo idea using https://github.com/harry0703/MoneyPrinterTurbo” — a new sandbox.gf.cx slot that generates short-form videos from a topic.
What MoneyPrinterTurbo (MPT) does: topic/keyword → LLM writes script → derives search terms → pulls stock clips (Pexels/Pixabay) → TTS voiceover (edge-tts / Azure) → auto-subtitles → MoviePy composites → 9:16 or 16:9 MP4 + BGM. Ships a Streamlit WebUI AND a FastAPI. Heavy Python + ffmpeg + MoviePy stack; Docker support.
THE ARCHITECTURAL FORK (decides everything): MPT cannot run on a Cloudflare
Worker (edge has no Python/ffmpeg/MoviePy and renders take minutes). Unlike the
/fal slot (pure API passthrough, [[project_sandbox_gf_cx_fal_slot_shipped_2026-06-07]]),
autovideo needs a real compute host. Clean split:
- sandbox Worker = control plane — POST /api/autovideo/generate
(X-Sandbox-Key gated like every slot), validate {topic, aspect, voice, duration},
enqueue job → R2 gf-cx-media/sandbox/autovideo/jobs/<id>.json status=queued.
- gf-cx-singapore VM = data plane — runs MPT’s FastAPI (Python + ffmpeg already
live there for the gvr-* pipelines), renders, uploads MP4 →
gf-cx-media/sandbox/autovideo/out/<id>.mp4.
- GET /api/autovideo/{status,result} + health; tier-2 in surfaces.json.
sandbox.gf.cx is wrangler-direct (no git) — deploy npx wrangler deploy.
Distinct from [[pattern_happiness_video_clip_ingest_pipeline_2026-06-10]]: that ANNOTATES existing source video; autovideo GENERATES net-new short-form from a topic. No overlap.
Prereqs before a POC:
- ✅ Pexels API key DONE (2026-06-11): op://Code Shared/Pexels/api_key (item
“Pexels”, field api_key, 56 chars) — verified live: /videos/search → HTTP 200,
total_results 8000 for “ocean”. The exact endpoint MPT uses is authorized.
- LLM provider MPT supports — it does NOT take Anthropic natively (last checked), so
either stand up an OpenAI-compatible shim pointing at Claude, or use a
DeepSeek/Moonshot key. Decide at build time.
- VM CPU/disk headroom + the home/VM upload ceiling (~8.5 MB/s anchor,
[[feedback_home_upload_ceiling_is_the_real_mac_bottleneck_2026-06-07]]) for clip
pull + result push.
- Content/licence: Pexels licence permissive → fine for sandbox.
Gotchas to encode at build: MoviePy renders are minutes-long + CPU-heavy (job queue, not synchronous response) · edge-tts is free but rate-limited · MPT’s API is unauthenticated by default → keep it bound to the VM’s Tailscale iface, never public · vertical 9:16 default suits Shorts/Reels.
BUILD STATUS — control plane SHIPPED + E2E-verified (2026-06-11 eve)
Dan green-lit “build the autovideo POC … see how far we can go”. Built:
Control plane (sandbox Worker, version fd18d556) — LIVE + TESTED E2E.
Endpoints in ~/Code/sandbox.gf.cx/src/index.ts: /api/autovideo/
health (queue depth) · generate (X-Sandbox-Key gated, 202, writes job→R2
sandbox/autovideo/jobs/<id>.json status=queued) · status?id= · result?id=
(streams MP4 from R2, range-aware 206 + content-range) · claim (VM leases
oldest queued, →rendering) · update (VM patches status/progress/error) ·
upload?id= (VM streams finished MP4 bytes → R2 through the Worker so the VM
needs NO R2 S3 creds). Verified the full lifecycle generate→status→claim→update
→upload→result with an ephemeral key (mint+bind+test in one shell, key never
printed); gate returns 401 without key. R2 gotcha relearned: wrangler r2
object put defaults to the LOCAL sim bucket — MUST pass --remote (silent
404 on read otherwise).
Gate key: dedicated AUTOVIDEO_SANDBOX_KEY (Worker secret), VOIP_SANDBOX_KEY
accepted as fallback. 1P service account is READ-ONLY on “Code Shared” (op
item create → (101) no permission) — same wall the voip POC hit — so the
durable key must be created interactively by Dan.
LLM provider DECIDED + VERIFIED: Anthropic via OpenAI-compat. MPT openai
provider → openai_base_url=https://api.anthropic.com/v1/, key
op://Code Shared/anthropic api-key/credential, model claude-haiku-4-5.
Live-tested 2026-06-11: POST /v1/chat/completions → HTTP 200, correct reply.
No new vendor, reuses portfolio Anthropic spend, pennies/render.
Data plane (gf-cx-singapore = GCP gvr-ingest-singapore) — PROVISIONED + LIVE
(2026-06-11 eve). ~/Code/sandbox.gf.cx/autovideo/: poller.py ·
provision.sh · vm_deploy.sh · README.md. vm_deploy.sh ran clean: apt
ffmpeg/imagemagick, cloned MPT, venv+deps, wrote config.toml (provider=openai,
model=claude-haiku-4-5, Pexels), started two tmux sessions mpt-api (FastAPI
:8080) + autovideo-poller. MPT API contract: POST /api/v1/videos
{video_subject, video_aspect, voice_name, video_source=pexels,
video_clip_duration, video_count}, poll GET /api/v1/tasks/{id} until
combined_videos/videos populated. FULL END-TO-END PROVEN 2026-06-11 ~21:38Z
— job …-567ac96d “compounding habits” → Claude script ✓ Pexels ✓ edge-TTS ✓
MoviePy composite ✓ → 1080×1920 / 85s / 42 MB MP4 landed in R2
(sandbox/autovideo/out/<id>.mp4) and downloaded+played. NOTE: the two
earlier renders MPT-composited fine but errored at the fetch-back step —
do NOT trust an “MPT render finished” as end-to-end; only status=done + a
populated result_key counts. Render wall-time ~13 min (50%→75% MoviePy is the
slow stretch; flips to done ~14 min). Web UI LIVE at sandbox.gf.cx/autovideo/ (topic box, aspect,
voice; key in localStorage). VM = gvr-ingest-singapore zone asia-southeast1-b
proj ambient-odyssey-498416-h3 acct studio@audreylam.com; SSH via gcloud
compute ssh. MPT API on 127.0.0.1:8080 (GCP firewall blocks 8080) — never public.
Both original blockers RESOLVED: gcloud re-authed; gate key SELF-GENERATED by
vm_deploy.sh (1P SA can’t create items, AND op item create via the ! shell
ALSO hits the read-only SA — so the durable key lives on the Worker secret + VM
~/autovideo.env only; mirror to 1P via the desktop app later if wanted). Grab
the key on the VM: grep AUTOVIDEO_SANDBOX_KEY ~/autovideo.env | cut -d= -f2.
THE MUTE-OUTPUT BUG (found 2026-06-11 late, after Dan flagged the voice
picker vs a silent MP4): every render up to job …-567ac96d shipped a
SILENT video. Root cause: MPT’s task result exposes TWO file sets —
videos = final-N.mp4 (the final mux: TTS voiceover + subtitles) and
combined_videos = combined-N.mp4 (just the stitched Pexels clips, no
audio). Confirmed in MPT app/services/task.py:374-375. The poller’s
await_render did t.get("combined_videos") or t.get("videos") — preferring
the SILENT intermediate. VM log was the tell: render complete →
…/combined-1.mp4. FIX: flip to t.get("videos") or t.get("combined_videos"),
redeploy poller to VM + restart tmux. PROVEN: fresh job …-c5c7f90f (UK Ryan)
came back H.264 + AAC stereo (ffprobe codec_type=audio channels=2).
LESSON: ffprobe must check for an AUDIO stream, not just that an MP4 exists —
a playable file ≠ a correct file. The 42 MB 567ac96d clip delivered earlier
is mute; re-render if it matters.
Gallery shipped (Worker 8f504e04): public GET /api/autovideo/list
(newest-first job summaries, cap 60, reads R2 sandbox/autovideo/jobs/) +
page dist/autovideo/gallery/index.html (responsive grid, inline aspect-
correct <video> per done job, error msg shown, status placeholder for
in-flight) linked from the main /autovideo/ page. Orphan gotcha: restarting
the poller drops its IN-MEMORY render tracking, so a job mid-render (e.g.
…-666932e8) is stranded at status=rendering forever — reset to queued or
just fire fresh.
Two earlier bugs found + fixed live: (1) /claim on EMPTY queue returned
500 — a jsonResp(204, {body}) (HTTP 204 forbids a body → Workers runtime
throws); fixed to 200 {job:null}. (2) Poller’s requests.get(video_uri) failed
No scheme supplied because MPT returns the result as a RELATIVE path
(/tasks/<id>/combined-1.mp4) when app.endpoint is unset; fixed — poller now
reads from local disk (<MPT_DIR>/storage/<relpath>) when the uri is relative.
Worker now version ab0a3f1c. GOTCHA that bit twice: editing poller.py on
the Mac is NOT enough — the VM runs ~/MoneyPrinterTurbo/autovideo_poller.py in
tmux autovideo-poller. Must scp the file to the VM AND restart that tmux
session for the fix to take. The relative-path “fix” sat un-deployed through two
failed renders because only the Mac copy had it. Redeploy poller w/o full
provision: gcloud compute scp poller.py dansellars@gvr-ingest-singapore:~/
(SSH user is dansellars, NOT the @-laden account — --account=studio@audreylam.com
handles auth; studio@…@instance confuses scp arg-parsing) → ssh: cp ~/poller.py
~/MoneyPrinterTurbo/autovideo_poller.py && tmux kill-session -t autovideo-poller
then relaunch with venv + . ~/autovideo.env. Errored jobs do NOT auto-retry —
fire a fresh /generate (do it from the VM so the gate key stays in
~/autovideo.env, never the transcript).
MPT upstream fix SHIPPED as PR #1026 (https://github.com/harry0703/MoneyPrinterTurbo/pull/1026,
from xlab-nyc fork): generate_terms() returned empty search terms on
fenced/multi-line JSON (the failed to generate video terms: Expecting value
warning we hit) — added _strip_code_fence() + re.DOTALL to the array regex,
matching the maintainer’s existing _parse_social_metadata handling. Branch
fix/robust-json-parse-for-non-openai-llms, proven against real failure modes.
Sister sandbox slots for pattern reference: fal (passthrough), voip (webhook receivers, [[parked_sketch_voipms_advanced_tools_sandbox_poc_2026-06-09]]), one-pager (lead capture, [[project_sandbox_one_pager_lead_capture_slot_2026-06-11]]).