Commit Graph

50 Commits

Author SHA1 Message Date
Conal Mullan 290d7d345b Adopt uv for Python dependency management (#33 rebased, thanks @AsharibAli) (#59)
* Adopt uv for Python dependency management

Replace manual pip/venv setup with `uv sync` (pyproject.toml + uv.lock),
and switch all tool invocations across docs, skills, commands, and
examples to `uv run`. Optional extras: --extra whisper, --extra modal.
tools/requirements.txt is kept as a pip fallback.

* FIX: bring uv migration current with main — post-June deps and docs

Adds websocket-client (60db websocket transport) to core deps and a
'youtube' extra for the google-* packages (tools/youtube_upload.py),
regenerates uv.lock, bumps pyproject version to 0.18.0, and converts the
51 'python3 tools/…' invocations added since the PR was opened (60db,
YouTube, Kiro docs, publish command) to 'uv run'. Verified with
uv sync --frozen --extra youtube; voiceover, sixtydb_tts, youtube_upload
and migrate_to_kiro all run through the project environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Asharib Ali <asharibarain4@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 22:55:43 +01:00
Conal Mullan c05a0cb7e6 Add 60db TTS provider (Hindi/Indian voices) — third voiceover provider via --provider 60db (#27)
* Added 60dB integration

* FIX: make 60db TTS synthesize transport work against the live API

The original integration coded to 60db's documented /tts-synthesize contract
(single JSON object with `audio_base64` in the requested container format).
In production the endpoint instead streams newline-delimited JSON of raw
16-bit mono PCM (Content-Type: application/x-ndjson) with a trailing
`{metadata}` line, so the default voiceover path failed with "Invalid JSON
response".

Changes (tools/sixtydb_tts.py):
- Rewrite _synthesize_rest to consume the NDJSON PCM stream, while still
  accepting the documented single-JSON shape if 60db ships it.
- Add _finalize_audio: sniff bytes for an audio container (mp3/wav/ogg/flac)
  and write/transcode as-is, else wrap raw PCM as WAV and transcode to the
  requested --output-format via ffmpeg.
- Add _derive_pcm_sample_rate: infer the rate from byte-count and the
  metadata audio_sec (snap to nearest standard rate) instead of hardcoding.
- Surface 60db metadata warnings; route _synthesize_stream through the same
  PCM-aware finalizer and flag that /tts-stream currently 500s upstream.

Verified live: sixtydb_tts.py and `voiceover.py --provider 60db --scene-dir`
both produce valid 48kHz MP3s for English and Hindi.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* FIX: apply pacing QC (wpm/--max-wpm) to the 60db provider path

Rebase integration: pacing QC landed on main after this branch was cut;
elevenlabs and qwen3 paths report wpm/pacing and honor --max-wpm, but the
60db path bypassed it. Wire _apply_pacing_qc into generate_single_audio_60db
and pass max_wpm at both call sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKbS3m7TXrfYjE7gDVEpnV

---------

Co-authored-by: manishEMS47 <manish.prasad@engineermaster.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-13 22:12:21 +01:00
Conal Mullan 1f79d98c42 FIX: accept both modal CLI JSON key casings in verify_setup app detection (follow-up to #51) (#53)
modal app list --json emits capitalised keys ('Description', 'State') on
CLI <=1.3.x and lowercase keys on newer CLIs. #51 fixed detection for new
CLIs but broke it for older ones; accept both so the check works on any
Modal version. Verified live against modal 1.3.5 (6 apps detected) and
via mocked lowercase output.


Claude-Session: https://claude.ai/code/session_01MKbS3m7TXrfYjE7gDVEpnV

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:06:55 +01:00
Edgar 7cca516f96 FIX: wire up LTX-2 Modal config, repair verify_setup app detection, document Windows gotchas (#51)
Three unrelated gaps found while running /setup end to end on Windows.

1. MODAL_LTX2_ENDPOINT_URL was read by tools/cloud_gpu.py but appeared in
   neither .env.example nor verify_setup.py, so LTX-2 could only be configured
   by someone who had read the source. Added to both, plus the deploy command
   and env var in docs/modal-setup.md.

2. verify_setup.py always reported "no toolkit apps deployed". `modal app list
   --json` emits lowercase keys ("description", "state") while the check read
   the capitalised column headers of the human-readable table, so the filter
   never matched. Verified against a workspace with 6 deployed apps: now
   correctly reports "6 deployed". Also made the "N/7 tools" header derive its
   total from the tool list instead of hardcoding it, since adding LTX-2 would
   otherwise have desynced the count.

3. Documented three Windows blockers that each present as a toolkit bug:
   - modal deploy aborts mid-build with a cp1252 'charmap' encode error unless
     PYTHONIOENCODING=utf-8 is set
   - python3 resolves to the zero-byte WindowsApps App Execution Alias, which
     fails with "Permission denied" — this breaks every documented
     `python3 tools/...` invocation
   - FFmpeg needs a manual install, with a fallback for when winget's source
     index is corrupted

   Also documented two Modal behaviours that cost real debugging time: AppCreate
   is rate-limited so deploys must be serial, and A100-class apps (image-edit,
   ltx2) fail at deploy time until a payment method is added. Noted that ltx2
   separately needs a `huggingface-token` secret, and that Modal truncates long
   endpoint labels with a hash so URLs must be copied rather than reconstructed.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 22:05:14 +01:00
Conal Mullan d696350bc8 FIX: classify YouTube 403s by API reason instead of labeling all as quota (#39)
youtube_upload.py mapped every HTTP 403 to errorType "quota", but accessNotConfigured
(API not enabled) and forbidden (permission denied) are also 403s — the first one bit
us on the very first real upload. Add extract_api_reason() + classify_http_error() to
read YouTube's machine reason from the error body and map to accurate types:
quota | config | forbidden | auth | http, plus a new errorReason field in --json-out.
The config case now prints an actionable "enable the API, wait 1-2 min" hint.

Unit-tested across 9 cases (each 403 reason, 401, 500, malformed body, status fallback).
Docs + /publish error table updated with the new types.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 23:41:27 +01:00
Conal Mullan d2579b51c1 DEPS: make YouTube google-* deps opt-in in requirements.txt (match openai-whisper) (#37)
The three google-api-python-client / google-auth-oauthlib / google-auth-httplib2
packages were uncommented, so every `pip install -r tools/requirements.txt` pulled
them in even for users who never publish to YouTube. Comment them out under an
"uncomment to enable" header, mirroring how openai-whisper is handled, keeping base
installs lean. youtube_upload.py already prints an actionable install hint when the
deps are missing, so the opt-in path is self-documenting.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 21:33:49 +01:00
dascope d93298b691 Add YouTube upload pipeline (tools/youtube_upload.py + /publish) (#29)
* feat: add YouTube upload pipeline (tools/youtube_upload.py + /publish)

Adds the toolkit's first publishing capability: upload a rendered video to
YouTube via the Data API v3.

- tools/youtube_upload.py: OAuth 2.0 installed-app flow with silent token
  refresh (per-account cache), resumable upload with exponential-backoff
  retry, optional thumbnail/captions/playlist, and private/unlisted/public
  or scheduled (publishAt) visibility. Flags for --auth, --dry-run, --json-out.
- /publish command: guided workflow that auto-fills title/description/tags
  from a project's project.json, dry-runs, uploads, and writes back the URL.
- config.py: get_youtube_client_secrets_file(), get_youtube_token_dir().
- docs/youtube-upload.md: Google Cloud Console walkthrough, quota math, and
  the unverified-app private-lock / 7-day token gotchas.
- Wiring: requirements (google-api-python-client/auth-oauthlib/auth-httplib2),
  registry entries, CLAUDE.md publishing section, .env.example, and a
  .gitignore rule for the _internal/.youtube/ token cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: soften the unaudited-project private-lock warning to match reality

The pre-upload warning and docs asserted that unverified apps force every
upload to private. Verified empirically that first-party uploads (own Cloud
project + own channel + own account) publish public/scheduled fine without an
audit — the lock mainly targets third-party apps uploading to other channels.

Rewords the runtime warning, module comment, docs/youtube-upload.md, CLAUDE.md,
and .env.example to say public uploads MAY be locked (chiefly cross-account),
and to treat the tool's actual returned privacyStatus as the source of truth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Make /publish channel-agnostic for public use

Drop the SUPERINTELLIGENCE-specific category default and the
schedule-by-default cadence from the /publish command:
- category: neutral default "22" (People & Blogs) with override examples
- privacy: default to `private` (safe least-surprise default) instead of
  auto-scheduling a public go-live

Channel-specific defaults (playlist, public-pairs cadence, Studio
checklist) stay in the local working copy, out of the public toolkit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Robert Strobl <rs@digitalsamba.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:57:57 +01:00
Conal Mullan 6db40e9299 Add concept-explainer-short template: 9:16 TikTok/Reels explainer shorts (Python/moviepy) (#31)
* Add concept-explainer-short template: 9:16 TikTok/Reels explainer shorts (Python/moviepy)

First non-Remotion template. The whole video derives from scenes.json
(per-scene narration text + visual asset); a three-script pipeline turns
it into a finished vertical short:

- gen_vo.py — per-scene TTS via tools/voiceover.py (clone or built-in
  speaker, batch mode, --max-wpm pacing clamp), writes the
  vo_durations.json timeline anchor; warns past the 3-minute
  Shorts/Reels ceiling
- gen_captions.py — burned karaoke caption timing: whisper word
  timestamps force-aligned to the ground-truth script text via difflib
  (never burns whisper's own transcription, which mishears TTS)
- build.py — audio-anchored moviepy composite: Ken Burns on stills,
  boomerang-looped clips (asset extension decides), caption pills with
  width cap and platform-safe positioning, ducked looped music; renders
  at every stage (gradient placeholder cards before assets exist,
  word-count timing estimates before VO, silent before audio)

Pattern proven on a 3:41 hallucinations explainer; tested end-to-end
(out-of-box placeholder render + full Modal clone pipeline with three
takes auto-clamped 181-188→165 wpm). Registered in the toolkit registry,
CLAUDE.md, and /video (with Python-flow notes); openai-whisper documented
as an optional dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add sky-blue-short showcase example + fix Modal qwen3-tts batch custom_voice

examples/sky-blue-short: complete worked example of the
concept-explainer-short template — a 52s vertical "Why is the sky blue?"
short with Qwen3 VO (built-in Ryan, warm tone), two Ideogram diagram/CTA
cards, two LTX sky clips, ACE-Step music bed, and burned aligned captions
(~$0.80 total generation cost). All source assets committed; re-renders
in place with python3 build.py.

Building it surfaced two real bugs, fixed here:
- Modal qwen3-tts handler: batch custom_voice hit the same qwen-tts
  tensor-aliasing crash the clone path already works around — now
  iterates per text with the shared speaker (redeployed and verified)
- gen_vo.py: failed batch scenes crashed with KeyError instead of
  reporting which scenes failed and why

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add hallucinations-short and sky-blue-short demo videos to README showcase

Both produced with the concept-explainer-short pipeline: sky-blue-short
uses the stock Qwen3-TTS Ryan voice; hallucinations-short uses a Qwen3
voice clone from a Pixabay voice-over sample.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 07:58:39 +01:00
Conal Mullan 35e17c8e2d Add TTS pacing QC: per-take wpm reporting + --max-wpm auto-clamp (voiceover, qwen3_tts) (#30)
Cloned voices inherit speaking pace from their reference audio, and
regenerating at lower temperature does not correct it — a short, snappy
reference reliably produces 170-210 wpm narration on every take (observed
across 11 consecutive regens). Measuring pace and correcting it
deterministically beats sampling.

- New tools/pacing.py: wpm measurement (pause-marker/SSML aware),
  fast/slow/ok labels, and a pitch-preserving ffmpeg atempo clamp
  (floor 0.85x so stretching never becomes audible)
- voiceover.py and qwen3_tts.py results now always include wpm + pacing;
  human mode prints per-scene pace notes and warnings
- New --max-wpm flag on both tools (all providers, single/batch/scene-dir)
  slows rushed takes in place and reports pace_adjusted in JSON
- /voice-clone now validates reference audio before saving (duration +
  word-count gates) and teaches that the clone copies the reference's pace
- CLAUDE.md: pacing row in the TTS drift table; registry: pacingQC option

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 22:04:24 +01:00
Conal Mullan 6cb64ef067 Add Ideogram 4 text-to-image (best-in-class in-image text) (#28)
* FEAT: add Ideogram 4 text-to-image (best-in-class in-image text rendering)

New `tools/ideogram4.py` generates images via Ideogram's hosted v4 API —
its strength is legible baked-in text (title cards, thumbnails, CTAs, quote
cards, in-scene signage), exact hex color palettes, and bounding-box layout,
where FLUX.2 and LTX-2 both garble text.

- Thin multipart wrapper over POST /v1/ideogram-v4/generate (Api-Key header).
  Mutually-exclusive --json (structured json_prompt, the quality path) and
  --prompt (text_prompt, server-side magic prompt). --brand injects brand.json
  hex colors into the caption palette; --speed FLASH/TURBO/DEFAULT/QUALITY.
- New `ideogram4` skill (.claude/skills/ideogram4/) teaching the structured
  JSON caption format — Claude acts as the magic-prompt expander (beats the
  weaker free hosted one). SKILL.md + prompting.md + examples.md.
- Hosted paid API chosen over self-hosting: the v4 weights are non-commercial,
  the API includes a commercial license, and the API's json_prompt preserves
  the full prompting-skill value (~$0.03-0.09/image).
- get_ideogram_api_key() in tools/config.py; registry entries for skill+tool.
- CLAUDE.md: FLUX.2-vs-Ideogram-4 decision guidance (baked-in text vs.
  background-for-Remotion-overlay).

Tested live: json_prompt title card and text_prompt thumbnail both render with
crisp legible text; resolution 2048x2048 accepted. Closes #25.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* DOCS: refresh ideogram4 registry notes (tool built + live-tested)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:28:54 +01:00
lnlscreatives71 81961c7cc4 FIX: restore Python 3.9 compatibility in tools/ (#22)
16 tools use `X | None` union syntax in type annotations, which is
evaluated at runtime on function definition and raises TypeError on
Python 3.9 (PEP 604 unions require 3.10+). This broke every cloud GPU
tool on import, despite CLAUDE.md and verify_setup.py advertising
"Python 3.9+" support.

Add `from __future__ import annotations` to each affected file so all
annotations are treated as lazy strings. Verified no non-annotation
3.10+ usage (no match statements, no runtime isinstance unions), so
this fully restores 3.9 support with zero behavior change.

Co-authored-by: Lainie M (LNL Group) <lainiem@lnlcreatives.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:33:26 +01:00
Conal Mullan dfe2091695 QWEN3-TTS: VoiceDesign mode + shared-prompt cloning + caption aligner
Replaces the old "fresh ref upload + per-scene clone" pattern with the
proper Qwen3-TTS workflow: VoiceDesign generates a character voice from
a natural-language brief once, the resulting ref is cached, and all
subsequent scenes share a single voice_clone_prompt extracted from it —
Qwen's recommended pattern for consistent multi-scene narration.

Handler (Modal + RunPod, kept in lockstep):
- New "voice_design" mode (Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign baked
  into the image alongside CustomVoice + Base; ~19GB total)
- New "outputs": [...] response shape; legacy top-level keys still
  populated for single-text calls (back-compat)
- Three modes: custom_voice / clone / voice_design

Clone path: extracts voice_clone_prompt ONCE per request and reuses it
across all utterances. Note: qwen-tts's batch path
(generate_voice_clone(text=[list])) hits a PyTorch tensor-aliasing bug
("written-to tensor refers to a single memory location") — we serialize
the calls per-text instead, while still sharing the pre-computed
prompt, which is what gives us the consistency win.

Tools:
- tools/qwen3_tts.py: new --design-instruct flag; generate_audio()
  accepts list[str] for batch; design_instruct kwarg routes to
  voice_design mode.
- tools/voiceover.py: --scene-dir auto-batches qwen3 clone mode (one
  call, shared prompt); brand loader handles new clone.design block
  with self-caching of the designed ref.
- tools/align_captions.py (new): transcribes each scene MP3 via
  ElevenLabs Scribe, fuzzy-matches each cap() phrase to the word
  stream, and patches sprint-config.ts in --apply mode.
- scratch/update_qwen3_template.py (new): one-shot helper to point the
  RunPod template at a new image tag via saveTemplate GraphQL.

Deployment status:
- Modal: deployed and live
- RunPod: image built locally as ghcr.io/conalmullan/video-toolkit-
  qwen3-tts:0.2.0-voicedesign (20.4GB); GHCR push was auth-denied
  (PAT refresh needed). Push + scratch/update_qwen3_template.py to
  promote when ready.

NOT YET REVIEWED OR TESTED ON RUNPOD. Modal smoke-test passed
(designed Lugh in 60s, batch-cloned 9 scenes successfully). Verify
RunPod parity once the image is pushed and the template updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:58:20 +01:00
Conal Mullan 543cec46d6 LTX-2: add style LoRA support with crt-terminal preset
Ships the lovis93/crt-animation-terminal LoRA baked into the Modal image.
Pipeline rebuilds only when the requested LoRA differs from the currently
loaded one, so repeat calls skip the ~60s reload. Client CLI gains a
--lora flag with LORA_PRESETS that auto-prepend the trigger token and
switch geometry/negative-prompt defaults to what the LoRA expects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 22:24:45 +01:00
Conal Mullan 6f94523345 DEPS: declare moviepy/Pillow/matplotlib in tools/requirements.txt
The two new moviepy examples (quick-spot, data-viz-chart) imported
moviepy, Pillow, and matplotlib but had no declaration anywhere in
the toolkit — anyone cloning the repo and running `python3 build.py`
would hit a bare ModuleNotFoundError with no guidance.

This also fixes a pre-existing latent gap: tools/flux2.py and
tools/image_edit.py both `from PIL import Image` inside a try/except
guard but Pillow was never declared in tools/requirements.txt either.
The "friendly error" message was the only signal users got that they
needed to install Pillow themselves. Adding Pillow to the central
file as a free rider.

Changes
-------
- tools/requirements.txt: adds Pillow>=10.0, moviepy>=2.0, matplotlib>=3.7
  with comments explaining which tools/examples use each
- examples/quick-spot/build.py, examples/data-viz-chart/build.py: wrap
  the third-party imports in a try/except ImportError matching the
  existing flux2.py/image_edit.py pattern, with a precise error
  message pointing at `python3 -m pip install -r tools/requirements.txt`
- examples/*/README.md: Quick Start now lists the toolkit-level pip
  install as step one, then `python3 build.py`
- README.md: updates the optional Python install callout to use
  `python3 -m pip install` (avoids the pip/python3 env mismatch
  footgun) and mentions moviepy examples alongside AI voiceover/
  image gen/music in the "what this installs" note
- .claude/skills/moviepy/SKILL.md: adds a short Dependencies note so
  Claude Code knows where the deps live and what to run on ImportError

Net effect: one install command covers every Python feature in the
toolkit. Both AI agents (via stderr-reactive friendly errors) and
humans (via the README Quick Start) can recover from a missing-dep
state without guesswork.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 08:51:57 +01:00
Conal Mullan a076f87a4a Harden chain_video: timeout, error handling, cleanup, and registry entry
- Add 20-min subprocess timeout (buffer over ltx2's 15-min cloud timeout)
- Switch to capture_output=True with stderr in error messages
- Validate --prompts-file JSON with helpful error on malformed input
- Clean up .frame-*.png temp files after run completes
- Fix glob pattern to require hyphen separator (prevents 05 matching 050)
- Add chain_video to toolkit-registry.json

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:19:48 +01:00
Conal Mullan 65f9f4adbb Fix chain_video: glob for suffix variants when finding previous clips
chain-04-brigid.mp4 wasn't matched by exact path chain-04.mp4.
Now globs for chain-{num}*.mp4 to handle manually-named clips.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:29:09 +01:00
Conal Mullan c3218f9952 Add chain_video.py: sequential LTX-2 with visual continuity
Runs all scenes as a single command — solves OpenClaw agent stalling
between per-scene tool calls. Supports per-scene prompts via JSON,
auto-resume (skips existing files), and --progress json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 12:21:54 +01:00
Conal Mullan e18aab5ff4 Add structured progress reporting for all cloud GPU tools
ProgressReporter class in cloud_gpu.py with two modes:
- human (default): colored stderr logs, unchanged from today
- json (--progress json): JSON Lines to stderr for bots/agents

Supports heartbeat thread for synchronous providers (acemusic, Modal)
and poll-based events for RunPod. All 8 cloud tools wired up:
music_gen, qwen3_tts, flux2, upscale, sadtalker, image_edit,
dewatermark, ltx2.

OpenClaw skill updated with --progress json in all example commands
and a CRITICAL directive for the bot to always use it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 00:28:46 +01:00
Conal Mullan b9f39563a2 Add acemusic cloud API as default music generation provider
Switch music_gen.py from self-hosted Modal/RunPod to the official ACE-Step
cloud API (acemusic.ai) as the default provider. This gives access to the
XL Turbo 4B model with 5Hz LM thinking mode — significantly better quality
and variety than the self-hosted 2B model, with free API keys and no GPU costs.

New features: --thinking/--no-thinking, --variations, --repaint,
--continuation, --guidance-scale, --infer-method. Modal/RunPod remain as
--cloud modal/runpod fallbacks.

Skill doc updated with vocal gender control and duet patterns discovered
through testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:11:05 +01:00
Conal Mullan 42a54de830 Increase SadTalker timeouts: 20x multiplier + 300s buffer for size=512+gfpgan on A10G 2026-04-03 22:11:05 +01:00
Conal Mullan e3ea1bbc9b Release v0.13.3: SadTalker dynamic timeouts and pre-flight estimates
- Fix hardcoded 1800s Modal container timeout that could kill jobs during
  cold starts or moderate-length audio
- Dynamic per-chunk subprocess timeouts scale with audio duration
  instead of flat 1800s
- Add pre-flight estimate (chunks, time, cost) before job submission
- Add chunk-level progress logging to Modal app

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:11:05 +01:00
Conal Mullan 8862852f64 Remove unused imports in ltx2.py (json, requests)
Flagged by code quality bot on PR #9.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:11:05 +01:00
Conal Mullan 977a39708a Default all cloud GPU tools to Modal instead of RunPod
All 6 tools (flux2, image_edit, music_gen, qwen3_tts, sadtalker,
voiceover) now default to --cloud modal. RunPod remains available
as --cloud runpod for users who prefer it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:11:05 +01:00
Conal Mullan d26b043f91 Add LTX-2.3 video generation via Modal (text-to-video, image-to-video)
Integrates Lightricks LTX-2.3 22B DiT model for AI video generation.
Generates ~5s video clips at up to 1024x1536 with synchronized audio.

- docker/modal-ltx2/app.py: Modal deployment on A100-80GB with baked weights
  (~55GB: dev checkpoint + distilled LoRA + spatial upsampler + Gemma 3 12B)
- tools/ltx2.py: CLI tool with --prompt, --input (I2V), --width/--height,
  --num-frames, --quality, --seed
- tools/cloud_gpu.py: Wire ltx2 into Modal endpoint routing and cost estimation
- docs/ltx2.md: Setup guide, parameters, prompting tips, troubleshooting

Key implementation notes:
- torch.inference_mode() required around pipeline call (GitHub #152) —
  without it, Gemma text encoder retains ~37GB of autograd activations
  causing OOM even on 80GB GPU
- Weights are on Lightricks/LTX-2.3 HF repo (not Lightricks/LTX-2)
- transformers pinned to 4.57.6 (5.x breaks Gemma3TextConfig)
- Pipeline returns lazy iterator — inference_mode scope must include
  encode_video() call

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:11:04 +01:00
Conal Mullan ce63b788eb Fix R2 region, SadTalker timeouts, and tool path guidance
- file_transfer.py: Add region_name="auto" for R2 client — fixes
  InvalidRegionName error when boto3 inherits AWS config region
- modal-sadtalker/app.py: Bump subprocess timeout 600s→1800s and
  app timeout 900s→1800s — 60s audio needs ~10min processing
- sadtalker.py: Bump client timeout multiplier 4x→12x to match
  actual processing time observed on Modal A10G
- CLAUDE.md: Add guidance to always invoke tools from toolkit root
  directory (not from project subdirectories)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 21:21:08 +00:00
Conal Mullan eb90d47d68 Add /setup wizard, verify script, and migrate dewatermark to cloud_gpu
- New /setup command: interactive 6-phase onboarding wizard for new users
  (prerequisites, cloud GPU provider, R2 file transfer, tool deployment,
  voice config, verification)
- New verify_setup.py: checks all prerequisites, env vars, deployed apps,
  and optionally runs smoke tests (--test flag)
- Migrate dewatermark.py to cloud_gpu.py: replace ~400 lines of inline
  RunPod code with shared abstractions, add --cloud runpod|modal flag
- Add dewatermark to Modal env vars and GPU cost tables in cloud_gpu.py
- Rewrite .env.example: all 22 env vars organized by service, Modal
  section added, references /setup
- Update README, CLAUDE.md, getting-started.md, video.md to reference
  /setup as the primary onboarding path
- Register setup command and verify_setup tool in toolkit-registry.json

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:42:11 +00:00
Conal Mullan 5bebb53160 Migrate all cloud GPU tools to shared abstraction (Phase 2)
Extract duplicated RunPod submit/poll and R2 file transfer code into
shared modules. All 6 cloud GPU tools now route through cloud_gpu.py
and file_transfer.py, reducing ~1,150 lines of duplication.

Changes:
- Create tools/file_transfer.py with shared R2 upload/download + fallback
- Migrate image_edit, music_gen, upscale, sadtalker to call_cloud_endpoint()
- Add --cloud runpod|modal flag to all cloud GPU tools
- Deprecate --runpod flag in upscale.py (alias for --cloud runpod)
- Add --cloud passthrough to voiceover.py for Qwen3-TTS
- Backport file_transfer.py to qwen3_tts.py and flux2.py
- Remove dead legacy submit/poll code from Phase 1 tools

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:32:46 +00:00
Conal Mullan 303c69214b Add Modal as alternative cloud GPU provider (Phase 1)
RunPod has been unreliable — Modal provides faster cold starts and
more robust infrastructure. This adds Modal support for qwen3_tts
and flux2 behind a new --cloud runpod|modal flag (default: runpod).

New files:
- tools/cloud_gpu.py: shared provider abstraction with cost estimation
- docker/modal-qwen3-tts/app.py: Modal deployment for Qwen3-TTS
- docker/modal-flux2/app.py: Modal deployment for FLUX.2 Klein 4B

Modified:
- tools/qwen3_tts.py: --cloud flag, routes through cloud_gpu
- tools/flux2.py: --cloud flag, routes through cloud_gpu
- tools/config.py: Modal config getters
- _internal/toolkit-registry.json: Modal provider section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:53:43 +00:00
Conal Mullan 929dc74760 Add ACE-Step 1.5 music generation tool with RunPod deployment
Integrates ACE-Step 1.5, an open-source music generation model, as a free
alternative to ElevenLabs Music with precise BPM/key/duration control,
vocal support, and stem extraction capabilities.

- tools/music_gen.py: CLI tool with text2music, cover, stem extraction,
  8 scene presets, brand integration, and RunPod --setup
- docker/runpod-acestep/: Dockerfile (CUDA 12.8, torch 2.10, baked weights)
  and RunPod serverless handler using ACE-Step inference API
- Registry updated with music_gen tool and acestep cloud endpoint

RunPod end-to-end test pending GPU availability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 16:59:06 +00:00
Conal Mullan 6060c43da8 Add RunPod queue timeout safeguards and R2 upload fixes (v0.11.1)
All 6 RunPod tools now auto-cancel jobs stuck in queue >5 min and
cancel orphaned jobs on polling timeout, preventing runaway billing
from GPU unavailability. Fixed R2 upload support in flux2 and
image_edit clients. Switched flux2 default GPU to AMPERE_24 with
ADA_24 fallback. Added RunPod API reference to skill docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 08:34:46 +00:00
Conal Mullan 079a3a7572 Add FLUX.2 Klein 4B image generation tool with scene presets (v0.11.0)
- New tools/flux2.py: text-to-image, image editing, multi-image compositing
- 8 scene presets for video production (title-bg, problem, solution, etc.)
- Brand-aware generation reads brand.json colors into prompts
- Docker image with baked model weights (ghcr.io/conalmullan/video-toolkit-flux2)
- Rebuilt qwen3-tts Docker image with baked models
- "The Space Between" showcase video (all AI-generated)
- Updated README, registry, and changelog

Closes #6

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 00:21:08 +00:00
Conal Mullan 695da4d28d Add sync_timing tool, slim CLAUDE.md by deferring to registry (v0.10.1)
New tool: tools/sync_timing.py — syncs scene durationSeconds in Remotion
config with actual audio durations after TTS generation. Auto-detects
template type, matches audio to scenes via 3-pass algorithm (audioFile →
index → name), shows comparison table, and applies updates in-place.

CLAUDE.md reduced from 861 → 480 lines (44%) by removing catalog data
duplicated in toolkit-registry.json (skills, commands, components,
transitions, presets, Docker images) and adding cross-references.
All workflow guidance, timing knowledge, and code patterns retained.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 22:47:09 +00:00
Conal Mullan 8ef3fcc517 Lower barrier to entry: all API keys optional, friendlier error messages
Make it clear that videos render with just Node.js installed. Reorganize
.env.example with commented-out defaults, add "Try It Now" quick start
pointing to hello-world example, and replace terse API key errors in all
Python tools with actionable guidance (add key, use alternative, or skip).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:54:17 +00:00
Conal Mullan 5f467b8f1c Add temperature/top_p generation params to Qwen3-TTS and fix CI workflow
Pass temperature and top_p kwargs through the full stack (handler.py →
qwen3_tts.py → voiceover.py) to control voice expressiveness and sampling.
Fix docker-build.yml multi-line JSON breaking $GITHUB_OUTPUT by using jq -sc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 14:05:27 +00:00
Conal Mullan 30d9d4730d Add tone presets, voice-clone command, and brand integration for Qwen3-TTS
- Add named tone presets (warm, professional, excited, calm, etc.) with
  --tone flag and --list-tones for both qwen3_tts.py and voiceover.py
- Add per-scene instruct overrides via [tone: X] frontmatter in .txt files
- Add /voice-clone command for guided clone setup workflow
- Add brand voice config loading (speaker, tone, clone from voice.json)
- Guard against tone/instruct with cloned voice (clone mode ignores instruct)
- Fix PR #3 code quality issues (unused import, unused variable, empty except)
- Update commands, docs, and CLAUDE.md with tone examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:38:53 +00:00
Conal Mullan 3edca56ad4 Add Qwen3-TTS integration for self-hosted speech generation
Adds Qwen3-TTS as a free, self-hosted alternative to ElevenLabs for
voiceover generation via RunPod serverless. Supports 9 built-in speakers,
natural-language emotion control, and voice cloning from reference audio.

- Docker image (docker/runpod-qwen3-tts/) with RunPod handler
- CLI tool (tools/qwen3_tts.py) with --setup, --list-voices, generation
- voiceover.py: --provider qwen3 support with lazy ElevenLabs imports
- GitHub Actions workflow for automated Docker image builds
- Config, registry, docs, and generate-voiceover command updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 17:54:41 +00:00
Conal Mullan 80f55b2bfa Add per-scene voiceover generation (v0.9.2)
- voiceover.py: --scene-dir processes .txt files, --concat for SadTalker
- sprint-review: audioFile field on DemoConfig, info, overview, summary
- SprintReview.tsx: per-scene <Audio> elements with backward compatibility
- /generate-voiceover: detects scene scripts, offers per-scene as default

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:40:25 +00:00
Conal Mullan ffa515a23d Add SadTalker NarratorPiP integration patterns and improvements
- Add comprehensive docs/sadtalker.md with NarratorPiP workflow
- Document critical patterns: 16:9 image prep, --preprocess full
- Add auto-timeout calculation based on audio duration
- Add --retrieve flag for job recovery after client timeout
- Update NarratorPiP component with objectFit: contain
- Track learnings in BACKLOG.md for future improvements

Key insight: Always use --preprocess full to preserve image
dimensions for video overlays. Let users crop their own images
for best face framing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 02:31:07 +00:00
Conal Mullan 82181fb177 Fix SadTalker numpy 2.0 compatibility and output detection
- Add constraints file to enforce numpy<2 across all pip installs
- Patch numpy 2.0 deprecated APIs in SadTalker and dependencies
- Fix trans_params ValueError with dtype=object
- Fix output video detection (check direct files, not just subdirs)
- Add verbose error logging to handler
- Update PROGRESS.md with testing results and resume instructions

Tested with short (15s) and long (2.3min) audio - chunking works.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 20:24:27 +00:00
Conal Mullan 38b4bdf7fa Add SadTalker integration (experimental)
- Docker image: runpod-sadtalker with model weights baked in
- Handler with automatic chunking for long audio (>45s)
- CLI tool following existing patterns (--setup, R2 integration)
- GFPGAN face enhancement included

Experiment branch - needs Docker build/push and testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 21:42:44 +00:00
Conal Mullan e0a094a9c7 Add AI image editing tools (qwen-edit, upscale) (#1)
* Add AI image editing tools (qwen-edit, upscale)

New features:
- image_edit.py: AI image editing using Qwen-Image-Edit-2511
  - Style transfer, background replacement, custom prompts
  - Multi-image compositing support
  - Presets for common operations
- upscale.py: AI image upscaling using RealESRGAN
  - 2x/4x upscaling with general, anime, and photo models
  - Optional face enhancement (GFPGAN)
- qwen-edit skill: Prompting patterns and best practices
- RunPod workers for cloud GPU processing

Both tools use RunPod serverless for cloud processing.
See docs/qwen-edit-patterns.md for prompting guidance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add pre-built Docker image references for RunPod tools

- Add GHCR image URLs for qwen-edit, realesrgan, propainter
- Consolidate Cloud GPU tools category (image_edit, upscale, dewatermark)
- Document that --setup uses pre-built images (no building required)

Images hosted at ghcr.io/conalmullan/video-toolkit-*

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update RunPod docs for all Cloud GPU tools, bump to v0.8.0

- Restructure runpod-setup.md to cover image_edit, upscale, dewatermark
- Add GPU requirements table for each tool
- Document all three endpoint environment variables
- Update pre-built image references
- Bump version to 0.8.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 15:23:46 +00:00
Conal Mullan d9d56c1779 Add sora preset for OpenAI Sora watermark removal
Adds 'sora' preset to both dewatermark.py and locate_watermark.py
for removing the bottom-right "SORA" text watermark from Sora-generated videos.

Region: 1710,965,140,50 at 1920x1080

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 20:25:15 +00:00
Conal Mullan 54ec952130 Add dewatermark and locate_watermark tools (v0.7.0)
AI-powered watermark removal using ProPainter video inpainting:

dewatermark.py (2400+ lines):
- Cloud processing via RunPod serverless GPUs
- Local processing for NVIDIA GPU users
- Auto resize-ratio based on video duration (1.0 for <30s, 0.75 for <1min)
- Cloudflare R2 for reliable file transfer
- Automated --setup for one-command RunPod configuration
- Chunked processing for long videos
- Preset watermark regions (notebooklm, tiktok, stock variants)

locate_watermark.py:
- Coordinate grid overlay for identifying watermark positions
- Region verification across multiple frames
- Preset support for common watermarks

Infrastructure:
- docker/runpod-propainter/ - Serverless Docker image + handler
- docs/runpod-setup.md - Cloud GPU setup guide
- docs/optional-components.md - ML component documentation
- R2 + RunPod configuration in .env.example

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 20:22:16 +00:00
Conal Mullan 2edb69dee9 Add eleven_v3 (alpha) model option to TTS tools
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 10:17:01 +00:00
Conal Mullan ed73779fc6 Add notebooklm_brand tool for NotebookLM video post-processing
New tool handles the specific challenge of replacing NotebookLM branding
on redubbed/synced videos where TTS audio extends beyond the visual trim point.

Features:
- Separates video and audio tracks for independent handling
- Trims video to remove NotebookLM visual outro
- Creates freeze frame to bridge gap while audio finishes
- Appends custom branded outro card
- Auto-detects or accepts manual trim point

Usage:
  python tools/notebooklm_brand.py \
    --input synced.mp4 \
    --audio-file tts.mp3 \
    --outro-card outro.png \
    --output final.mp4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 10:10:26 +00:00
Conal Mullan 0fdd459698 Add word-level sync to redub tool (v0.6.0)
New --sync flag enables precise audio-video synchronization:
- Uses ElevenLabs convert_with_timestamps API
- Scribe provides word-level timestamps from original
- Builds segment mapping (default: 15 words/segment)
- FFmpeg filtergraph applies variable speed per segment
- Fixes TTS pacing drift (often 3-4+ seconds by end)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 23:58:29 +00:00
Conal Mullan db1c4596e7 Add addmusic tool for background music on existing videos (v0.5.0)
New utility tool that adds background music to existing videos:
- Generate music via ElevenLabs prompt (auto-matches video duration)
- Or use existing music file
- Volume controls for both original audio and music
- Fade in/out options for smooth transitions
- Standard flags: --json, --dry-run, --keep-temp

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:26:43 +00:00
Conal Mullan 84aa4b05c0 Add redub tool and command for voice replacement (v0.4.0)
New features:
- tools/redub.py: Voice replacement utility (extract → transcribe → TTS → replace)
- /redub command: Guided workflow for redubbing videos
- Utility tools concept: Documented distinction from project tools

Pipeline: FFmpeg audio extraction → ElevenLabs Scribe STT → ElevenLabs TTS → FFmpeg audio replacement

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 19:38:12 +00:00
Conal Mullan 111550fdfd Documentation review and toolkit registry cleanup
Documentation fixes:
- Fix outdated /new-sprint-video → /video references in docs
- Fix /skill → /skills typo in README
- Add templates/product-demo/README.md
- Update docs/getting-started.md with multi-session workflow
- Update docs/creating-brands.md for /brand command
- Add shared components section to CLAUDE.md
- Expand lib/ structure in README project tree

Registry changes:
- Rename skills-registry.json → toolkit-registry.json
- Standardize format (path, description, status, created, updated)
- Add components section (9 shared components)
- Update all file references

Date corrections:
- Fix all dates from 2024 → 2025 across _internal/ files

Roadmap:
- Add documentation improvement tasks for future sessions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 22:08:43 +00:00
Conal Mullan fde30d3f45 Initial release of claude-code-video-toolkit
AI-native video production workspace for Claude Code featuring:
- 4 skills: remotion, elevenlabs, ffmpeg, playwright-recording
- 3 commands: /new-sprint-video, /record-demo, /generate-voiceover
- Sprint review template with theme system
- Brand profiles for visual identity
- Python tools for audio generation (voiceover, music, sfx)
- 2 example projects: sprint-review-cho-oyu, digital-samba-skill-demo

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 20:38:32 +00:00