119 Commits

Author SHA1 Message Date
calesthio
9482eddeff fix: recover bounded defects from PR backlog 2026-08-03 02:14:01 -07:00
Calesthio
0af32ce5e1 Merge pull request #341 from yiyabo/feat/jimeng-video
Add Volcengine Jimeng (即梦 AI) video provider with V4 signing
2026-07-23 07:59:05 -07:00
Yiyabo
d4426f6e94 fix: declare env dependencies and map selector duration to frames
- Add env:VOLC_ACCESSKEY and env:VOLC_SECRETKEY to dependencies
- Map selector 'duration' (seconds) to Jimeng 'frames' (121/241)
- Add 4 selector duration mapping regression tests
- 59 tests pass

Fixes calesthio's third review feedback on PR #341.
2026-07-23 01:33:36 +08:00
Calesthio
888fe5a73c Merge pull request #337 from 0xDevNinja/fix/green-screen-chromakey-1x1-collapse
fix(green_screen): scale chromakey background to frame size, not 1x1
2026-07-21 14:57:37 -07:00
Yiyabo
53773cd5fc fix: tighten input_schema per Volcengine Jimeng 3.0 Pro contract
- frames: enum [121, 241] (was minimum 1)
- prompt: maxLength 800 (was 2000)
- seed: minimum -1 (was unbounded)
- Add 9 schema validation rejection tests
- Add authoritative API reference link to PROVIDERS.md
- Document CVSync2Async* route choice and schema constraints

Fixes calesthio's second review feedback on PR #341.
2026-07-19 21:47:06 +08:00
calesthio
0ff7bb30e3 feat(music): add Google Lyria generation skill 2026-07-18 16:47:35 -07:00
calesthio
af87fc1337 fix(backlot): surface approval artifacts before gates 2026-07-18 01:33:12 -07:00
Calesthio
33c7000e68 Merge pull request #374 from ShiroKSH/fix/hyperframes-relative-output-path
fix: resolve relative HyperFrames output paths
2026-07-17 18:53:52 -07:00
Calesthio
2a6bf1e039 Merge pull request #391 from tianrking/agent/fix-delayed-audio-fades
fix(audio): schedule delayed track fades correctly
2026-07-17 18:51:56 -07:00
Calesthio
9f94c1fe0e Merge pull request #389 from 0xDevNinja/fix/imagen-multi-image-drop
fix(google_imagen): write every generated image, not just the first
2026-07-17 18:50:08 -07:00
0xDevNinja
7ad68f28ec fix(corpus): normalize diversify() position term so similarity can compete
The greedy score mixed incommensurate scales: cosine similarity bounded
to [-1, 1] against an absolute list index that grows with the pool. For
a candidate j positions later to be preferred at the default
diversity=0.5, its similarity advantage had to exceed j -- impossible for
the non-negative cosines real footage embeddings produce. diversify()
therefore returned the input order verbatim, placing exact-duplicate
clips in adjacent edit slots, the one thing its docstring promises to
prevent. The threshold where the knob started working also depended on
pool size (0.66 at 4 candidates, 0.95 at 11).

Normalize the position term to [0, 1] so both terms share a scale. The
documented endpoints hold exactly as before: diversity=0 returns input
order, diversity=1 picks the most mutually dissimilar. Enumerating the
position also drops the O(n^2) remaining.index() lookup per candidate.

Closes #392
2026-07-16 18:44:14 +05:30
tianrking
5e21f1b78a fix(audio): schedule delayed track fades correctly 2026-07-16 13:33:17 +08:00
0xDevNinja
011a27df6a fix(google_imagen): write every generated image, not just the first
execute() sends sampleCount=number_of_images and estimate_cost() bills
0.04 * n, but result handling decoded only predictions[0] and wrote it to
a single output_path. Images 2..n were dropped: never decoded, never
written, absent from artifacts. The user paid for n and received one.

The result also misreported the drop rather than failing loudly --
images_generated returned len(predictions) (what the API sent) while
artifacts held a single path, so an agent picking between variants read a
count that did not match the artifact list.

Add _output_paths() and loop over every prediction, mirroring the pattern
already used by openai_image and grok_image: suffix multi-image paths
_1/_2/... so none overwrite each other, keep the exact requested path when
n=1, return all paths in artifacts, and report images_generated as the
count actually written.

Closes #388
2026-07-15 16:30:57 +05:30
0xDevNinja
fa756fbec5 fix(green_screen): make chromakey compositing portable across FFmpeg builds
The CI Linux FFmpeg build carried the keyed frame forward without an alpha
plane, so overlay drew opaque green over the background (corner stayed green)
instead of compositing — the E2E test failed there even though it passed on
macOS/Windows.

Force `format=yuva420p` immediately after chromakey so the keyed transparency
always has an explicit alpha plane, and size the background to the frame up
front (color=...:size=WxH, passing the probed width/height into
_process_chromakey) instead of scaling a 1x1 source with scale2ref — dropping
scale2ref also removes the format negotiation that discarded the alpha on some
builds. Output is flattened to yuv420p after the overlay.
2026-07-14 14:27:40 +05:30
ShiroKSH
24617af460 fix: resolve relative HyperFrames output paths 2026-07-13 21:33:11 +03:00
Yiyabo
8414c485ad fix: restore requests module after test_lazy_imports_requests
Use monkeypatch.delitem instead of manual del sys.modules['requests']
so pytest automatically restores the module after the test. This
prevents 12 downstream Google auth/music/Veo tests from failing with
AttributeError: module 'requests' has no attribute 'exceptions'.

Fixes calesthio's review feedback on PR #341.
2026-07-13 12:55:27 +08:00
Calesthio
f8d94632ea Merge pull request #354 from amartya-dev/feat/azure-speech-to-text
feat(stt): add Azure AI Speech as an optional cloud speech-to-text provider
2026-07-12 10:48:36 -07:00
Calesthio
318e0843ee Merge pull request #325 from 0xDevNinja/fix/subtitle-ts-overflow-and-checkpoint-keyerror
fix: subtitle timestamp ms overflow; checkpoint KeyError on manifest-only stages
2026-07-12 10:45:47 -07:00
amartya-dev
a2a0d8c8af feat(stt): add Azure AI Speech as an optional cloud speech-to-text provider
Add an Azure AI Speech transcription tool. It is opt-in: when
AZURE_SPEECH_KEY is configured the agent may prefer it for cloud STT,
while the local faster-whisper `transcriber` stays the default offline
path. Shared pipeline manifests are intentionally left unchanged, so no
default provider selection is altered for existing users.

- tools/analysis/azure_stt.py: new `azure_stt` tool (capability=analysis,
  provider=azure) calling the Fast Transcription REST API. The local file
  is uploaded via multipart and transcribed synchronously with word-level
  timestamps and optional diarization — no Blob storage or async polling.
  Output schema mirrors `transcriber` exactly, so it is a drop-in for
  `subtitle_gen` and other transcript consumers. Follows the existing
  provider-tool conventions (env-var status check, `_transcribe` helper,
  cost_usd/model on the result, fallback="transcriber").
- Auto-discovered by the registry; no registry or selector changes.
- tests/tools/test_azure_stt.py: contract, discovery, status, response
  mapping, execute guardrails, and a mocked-network success path (no live
  API calls).
- .agents/skills + .claude/skills: azure-speech-to-text Layer-3 skill.
- docs/PROVIDERS.md: Azure AI Speech setup, API notes, and pricing.
- .env.example, skills/INDEX.md, AGENT_GUIDE.md: document the optional
  cloud STT path alongside the default whisper transcriber.
2026-07-10 23:30:19 +05:30
xucailiang
6b9ba782d8 fix: complete Kling idempotency inputs
Include every request field that can alter Kling video, image, avatar, or lip-sync media in the public idempotency contract. Add a shared regression matrix that detects future cache-key collisions while excluding transport-only controls.
2026-07-10 21:24:11 +08:00
xucailiang
b9b9b82b64 fix: harden Kling integration verification
Isolate Kling contract tests from the singleton registry so discovery state cannot leak into later selector tests. Align lip-sync face, audio, and timing payloads with the current official API and extend the live smoke coverage.
2026-07-10 20:53:45 +08:00
Yiyabo
2ad71dd591 fix: use minimax_video (not minimax_tokenplan_video) in fallback_tools
minimax_tokenplan_video does not exist in main branch (added in PR #297,
not yet merged). Use minimax_video which is the existing tool.

Fixes Copilot review comments on fallback_tools reference and
contract test assertion.
2026-07-10 18:54:25 +08:00
xucailiang
2b6d717f00 Merge remote-tracking branch 'origin/main' into codex/kling-official-phase-1
# Conflicts:
#	.agents/skills/ai-video-gen/SKILL.md
#	.env.example
#	docs/PROVIDERS.md
2026-07-10 14:02:30 +08:00
Yiyabo
e06f56d26a jimeng: add Volcengine Jimeng (即梦 AI) video provider with V4 signing
Implements the Jimeng/Volcengine part of issue #249, as agreed with
@xucailiang (who is handling the Kling provider separately).

Adds a first-class Jimeng video provider that calls the Volcengine
visual API directly (visual.volcengineapi.com) using HMAC-SHA256 V4
request signing with IAM AK/SK credentials. This is the first provider
in OpenMontage to use V4 signing (all others use Bearer token auth).

API flow: POST CVSync2AsyncSubmitTask -> poll CVSync2AsyncGetResult ->
download video_url.

Features:
- Text-to-video and image-to-video (Jimeng 3.0 Pro)
- Configurable frame count (121=5s, 241=10s at 24fps)
- Aspect ratio selection (16:9, 9:16, 1:1, etc.)
- Seed for reproducibility
- Full V4 HMAC-SHA256 request signing (not Bearer token)
- Error handling with Jimeng code 10000 success convention
- API key redaction in error messages (both env vars, no empty-string bug)

Env vars: VOLC_ACCESSKEY + VOLC_SECRETKEY (IAM AK/SK pair).
Idempotency keys include all output-affecting fields.

Files:
- tools/video/jimeng_video.py — new tool (V4 signing + submit/poll/download)
- tests/contracts/test_jimeng_video.py — 46 contract tests (no AK/SK needed)
- .env.example — VOLC_ACCESSKEY + VOLC_SECRETKEY
- docs/PROVIDERS.md — Volcengine Jimeng provider section

End-to-end tested with real Volcengine IAM credentials: generated a
1920x1088 H.264 5.04s video, ffprobe verified.

Test results:
  python -m pytest tests/contracts/test_jimeng_video.py -q  # 46 passed
2026-07-10 10:50:43 +08:00
calesthio
ad02ce3639 fix(ci): stabilize character HyperFrames contract test 2026-07-09 07:32:21 -07:00
Karl Weinmeister
30018a6a8f feat(tools): add Google Lyria and direct Google Veo generation tools 2026-07-09 07:11:06 -07:00
0xDevNinja
df8cf21310 fix(green_screen): scale chromakey background to frame size, not 1x1
_process_chromakey built the composite background from a 1x1 lavfi color source
and tried to size it with `[0:v]scale=iw:ih`. That scale is a no-op — iw/ih are
the 1x1 source's own dimensions, and there is no cross-reference to the frame.
FFmpeg's overlay then takes the size of its first input (the 1x1 background), so
every processed frame is clipped to a single pixel. The exception fallback never
runs because the primary command exits 0 (a valid 1x1 PNG), and
_reconstruct_video upscales those 1x1 frames — producing a solid-color video
with the keyed subject entirely gone. Total data loss for method="chromakey"
(and method="auto" when it selects chromakey).

Use scale2ref to resize the background to the actual frame dimensions before
overlaying, so the keyed subject is composited at full resolution.

Verified with ffmpeg: a 320x240 green frame with a red subject now produces a
320x240 output with the subject preserved and green replaced by the background,
instead of a 1x1 (then upscaled solid-color) frame.
2026-07-09 18:15:05 +05:30
calesthio
2ef18e77a9 fix(video): normalize Gemini Omni file URIs; document provider in PROVIDERS.md
Review findings from PR #333:

P1: _download_via_uri assumed output_video.uri is always files/<id>.
The API can return a full resource URI or a ready-made
.../files/<id>:download?alt=media download URL, which produced an
invalid poll path with a second :download appended. New
_file_id_from_uri() extracts the bare id from every documented shape;
regression tests cover the full-URL form plus a parametrized matrix of
URI shapes.

P2: docs/PROVIDERS.md still described the Google key as TTS + Imagen
only. The shared-key section now covers gemini_omni_video (model id,
~$0.10/sec pricing table, paid-tier-only, edit-turn billing note), and
the env snippet, provider-to-tool mapping, and capability coverage
tables include the new provider.
2026-07-08 23:45:47 -07:00
calesthio
34d1053526 feat(video): add Gemini Omni Flash provider with conversational editing
Add gemini_omni_video, a native Gemini API provider wrapping
gemini-omni-flash-preview via the Interactions API. Text-to-video,
image/reference-to-video with <FIRST_FRAME>/<IMAGE_REF_N> prompt tags,
and stateful edit_video turns via previous_interaction_id — the only
provider in the fleet that can refine a clip without regenerating it.
Reuses the existing GOOGLE_API_KEY / GEMINI_API_KEY, so one Google key
now unlocks images, TTS, and video.

- New Layer 3 skill .agents/skills/gemini-omni (prompting, edit-loop
  rules, tag/timecode syntax, preview limits) sourced from official
  Google docs; linked via agent_skills and the AGENT_GUIDE Layer 3 map
- ai-video-gen gains the Gemini API gateway row + editing pointer
- veo_video/sora_video fallback lists and video_selector agent_skills
  reference the new provider; quality_score 0.85 with rationale
- Contract tests: registry discovery, selector routing, status from
  env keys, uri + inline delivery, edit turns, typed image parts,
  store=false editability, cost clamp
2026-07-08 11:00:57 -07:00
0xDevNinja
5dcd026ef7 fix(checkpoint): don't KeyError on manifest stages without a canonical artifact
_validate_artifacts_for_stage looked up CANONICAL_STAGE_ARTIFACTS[stage]
unconditionally, but the valid stage list comes from the pipeline manifest via
get_pipeline_stages(), which declares stages beyond the 9 canonical ones — e.g.
character-animation adds `character_design`/`rig_plan`. Such a stage passes the
`stage in valid_stages` guard, then raised an unhandled KeyError on the
canonical lookup, so those stages could never be checkpointed (the crash hits
write/read_checkpoint and friends, even for in_progress checkpoints).

Look the canonical artifact up defensively with `.get()` and skip the
required-artifact check when there is none. Canonical stages still require their
artifact when completed.
2026-07-07 15:29:03 +05:30
0xDevNinja
bcd8eb6e53 fix(subtitle_gen): stop millisecond rounding from overflowing timestamps
_ts_srt/_ts_vtt computed the seconds and millisecond fields independently:
`ms = int(round((seconds % 1) * 1000))`. When the fractional part is >= 0.9995
that rounds to 1000, emitting a malformed 4-digit `…,1000` value with no carry
into the seconds field (and, at 59.9999/3599.9999, no carry into minutes/hours).
For example 0.9999s became `00:00:00,1000` instead of `00:00:01,000`. ASR word
and segment end-times routinely land on such fractional boundaries, and the
resulting cue is rejected or mistimed by strict SRT/VTT parsers (ffmpeg
subtitles filter, VLC, browser WebVTT).

Decompose from a single rounded total-milliseconds value so the carry
propagates across all fields. Both formatters now share one `_hmsms` helper.
2026-07-07 15:29:03 +05:30
xucailiang
8e1128400b Clean up Kling official PR artifacts 2026-07-07 15:34:23 +08:00
xucailiang
1471ef47c3 test: consolidate Kling official contract tests 2026-07-07 15:07:50 +08:00
xucailiang
5a23fe6cf5 Merge branch 'main' of https://github.com/calesthio/OpenMontage into codex/kling-official-phase-1 2026-07-07 15:03:07 +08:00
xucailiang
7c5dfdd31a Add official Kling API providers 2026-07-07 14:56:40 +08:00
calesthio
015809c103 fix(ci): isolate requests module tests 2026-07-06 22:36:03 -07:00
Calesthio
9566c3488f Merge pull request #317 from ziyu4huang/fix/music-gen-force-instrumental
fix(music_gen): send force_instrumental per music-gen-usage mandate (§8 #8)
2026-07-06 21:47:32 -07:00
Calesthio
0c0780b00d Merge pull request #319 from ziyu4huang/fix/audio-mixer-loudnorm-target
fix(audio_mixer): parameterize loudnorm LUFS target (§8 #1)
2026-07-06 21:35:29 -07:00
Calesthio
ec8f1ba99b Merge pull request #318 from ziyu4huang/fix/grok-video-quality-score
fix(grok_video): set quality_score=0.9 to stop under-ranking (§8 #6)
2026-07-06 21:35:13 -07:00
Calesthio
5dcc364fca Merge pull request #321 from ziyu4huang/fix/cogvideo-2b-i2v-variant
fix(cogvideo_video): consult variant i2v flag instead of advertising it unconditionally (§8 #4)
2026-07-06 21:22:52 -07:00
Calesthio
0e69906e36 Merge pull request #320 from ziyu4huang/fix/video-selector-routing
fix(video_selector): dedup race + preferred-provider gap + motion-aware fallback (§8 #3,#5,#7,#10)
2026-07-06 21:22:38 -07:00
Calesthio
cadff79217 Merge pull request #263 from mojahurtowniapl/claude/distracted-morse-47198e
Clamp Pixabay per_page to API-required 3-200 range
2026-07-06 18:39:26 -07:00
Calesthio
796f5d7c6e Merge pull request #281 from scorp323/oracle/batch-b-safe-hardening-20260703
fix: harden tool governance and subprocess safety
2026-07-06 18:17:08 -07:00
Calesthio
f9f53ecbcf Merge pull request #286 from drainsmichael-tech/claude/eloquent-feistel-40f311
fix: check ffmpeg availability via shutil.which instead of hardcoding True
2026-07-06 17:59:42 -07:00
Ziyu Huang
dbc639f608 fix(music_gen): send force_instrumental per music-gen-usage mandate
skills/creative/music-gen-usage.md mandates 'Always set
force_instrumental=true for video background', but music_gen.py never
sent the kwarg, so ElevenLabs could return vocal tracks that collide
with narration/dialogue.

- Add force_instrumental to input_schema (default True) so the mandate
  holds by default; callers may opt out only with an explicit False.
- Include force_instrumental in the /v1/music payload.
- Add tests pinning: kwarg sent True by default, explicit opt-out
  honored, and the schema default.

Refs: docs/REVIEW-image-to-video-voice.md §8 #8

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 07:42:35 +08:00
Ziyu Huang
5cc144f232 fix(cogvideo_video): consult variant i2v flag instead of advertising it unconditionally
COGVIDEO_VARIANTS declares cogvideo-2b i2v=False (it is t2v-only), but
cogvideo_video advertised image_to_video + reference_image unconditionally
and the variant flag was never consulted. An image_to_video brief against
the 2B variant reached the diffusion pipeline and failed opaquely.

- Add is_operation_available(operation) that derives capability from the
  variant table (the selector calls it without inputs, so it reports the
  DEFAULT variant cogvideo-5b: t2v + i2v both True). This replaces an
  implicit unconditional-True.
- Add an execute()-time guard that consults the CALLER's chosen variant
  and fails fast with a clear error when it lacks the requested mode
  (2B + image_to_video), instead of dropping into generate_local_video.
- Add _variant_for(inputs) helper shared by estimate_runtime / the guard.

Tests pin: the 2B premise (i2v=False), default-variant capability
reporting, fast-fail for 2B+i2v (generation never runs), and that 5B+i2v
still routes through to generate_local_video.

Refs: docs/REVIEW-image-to-video-voice.md §8 #4

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 07:38:52 +08:00
Ziyu Huang
712a54bea9 fix(video_selector): dedup race + preferred-provider gap + motion-aware fallback
Three routing defects in video_selector, none previously covered by
tests (REVIEW §8 #3, #5, #7); plus the routing-test coverage itself (#10).

#3 Seedance dedup race
  tool_by_provider keyed by provider STRING, so two tools legitimately
  sharing provider="seedance" (seedance_video=fal, seedance_replicate)
  collided — only the first-registered was ever selectable; the other
  was invisible to the selector regardless of rank. Key selectable tools
  by NAME instead; ranking picks the best of the shared-provider backends.

#5 preferred_provider had no score-gap gate
  The selector returned the preferred provider on the first ranking match
  no matter how far below the top it scored (the comment claimed "unless
  drastically worse" but nothing enforced it). Add a configurable
  preferred_provider_gap (default 0.15): honor the preference only when
  its best ranked tool is within the gap of the overall top, else yield
  to the top-ranked provider.

#7 fallback_tools appended image_selector unconditionally
  The motion-required prohibition lived only in director skills, so a
  direct caller could silently fall back to an image-only tool for an
  image_to_video / reference_to_video brief. Add input-aware
  fallback_tools_for(inputs) that drops image_selector for
  motion-required operations; keep the static fallback_tools property
  (with image_selector) for external consumers / contracts.

#10 routing coverage
  First routing tests for video_selector: dedup reachability, the gap
  gate (honored / ignored / configurable), motion-aware fallback, and
  estimate_cost / estimate_runtime delegation. 13 tests, scoring patched
  for determinism so they test routing logic, not the scorer.

Full tools + contracts suite green (638 passed, 6 skipped).

Refs: docs/REVIEW-image-to-video-voice.md §8 #3, #5, #7, #10

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 07:36:59 +08:00
Ziyu Huang
0ab9779a04 fix(audio_mixer): parameterize loudnorm LUFS target
audio_mixer hard-coded loudnorm I=-16 (Apple Podcasts) in both _mix
and _full_mix. sound-design.md targets -14 for YouTube/TikTok/IG, and
edit_decisions.metadata.loudnorm_target is the declarative form — but
the mixer never read it, so the executed loudness silently defaulted
to podcast levels regardless of the target platform.

- Add loudnorm_target to input_schema (default -16, clamped to [-40, 0]).
- Extract _loudnorm_filter() helper and use it in _mix and _full_mix so
  a director can forward edit_decisions.metadata.loudnorm_target (or a
  caller can pass it directly) to hit the right platform target.
- Add tests pinning: default -16, -14 honored, out-of-range clamped,
  non-numeric fallback, and the schema default.

Refs: docs/REVIEW-image-to-video-voice.md §8 #1

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 07:32:44 +08:00
Ziyu Huang
6e10038d0e fix(grok_video): set quality_score=0.9 to stop under-ranking
Every premium video provider sets quality_score (seedance 0.95, runway /
higgsfield 0.9) so the scorer ranks them above stock/local options.
grok_video had none, so it was scored only on supports/stability flags
despite shipping native synchronized audio (lip-sync + dialogue + SFX
in a single generation pass) — likely under-ranked.

Set quality_score=0.9, on par with the other native-audio premium
providers. Add a regression pinning the field and its get_info() surface.

Refs: docs/REVIEW-image-to-video-voice.md §8 #6

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-07 07:31:33 +08:00
0xDevNinja
4c10481011 fix(audio_mixer): stop segmented_music from halving narration volume
`_segmented_music` mixed the video's audio with the shaped music via
`amix=inputs=2`, whose default `normalize=1` scales every input by 1/inputs
(x0.5, -6 dB). Unlike `_mix` and `_full_mix`, this path has no `loudnorm` stage
afterward to re-normalize, so the narration was permanently attenuated across
the entire timeline — including the stretches where the music volume expression
evaluates to 0. A one-second music segment quietly dropped the narration by
~6 dB for the whole video.

Add `normalize=0` to the amix: the music is already scaled to `music_volume`
by the `volume` expression, so speech passes at unity. Verified with ffmpeg —
narration in a no-music region tracks the stereo/aac conversion baseline
instead of sitting 6 dB below it.
2026-07-06 13:12:33 +05:30