Skip to content

Fix Mixkit stock adapter; add keyless mixkit_music tool; configurable atelier render timeout - #801

Open
SophieXueZhang wants to merge 5 commits into
OpenMontageInc:mainfrom
SophieXueZhang:tools/mixkit-gemini-tts-compose-timeout
Open

SophieXueZhang wants to merge 5 commits into
OpenMontageInc:mainfrom
SophieXueZhang:tools/mixkit-gemini-tts-compose-timeout

Conversation

@SophieXueZhang

@SophieXueZhang SophieXueZhang commented Oct 15, 2026 •

Copy link
Copy Markdown

Summary

Fixes and small additions found while producing a 20-minute narrated explainer end-to-end with the hybrid pipeline (Remotion atelier, keyless stock only):

  • The Mixkit stock adapter returned zero results because mixkit.co changed its markup.
  • pixabay_music gets HTTP 403 from Pixabay, which left no working keyless music source.
  • Atelier renders over 30 minutes were killed by the hard-coded timeout=1800 in video_compose.

(An earlier revision of this PR also added a gemini_tts tool. main has since gained its own, more complete gemini_tts, so the merge takes main's version and this PR no longer touches it.)

Related issue

None filed — happy to open one if preferred.

Changes

  • tools/video/stock_sources/mixkit.py:
    • Match the current .item-grid-card card markup.
    • Download the direct assets.mixkit.co/videos/<id>/<id>-1080.mp4 file by id, with a -720.mp4 fallback.
    • Keep the old detail-page resolution path for URLs that aren't direct.
  • tools/audio/mixkit_music.py (new; capability="music_search"; no API key):
    • Searches Mixkit tag/discover pages by parsing their embedded schema.org JSON-LD (title, artist, genre, duration, license, mp3 URL).
    • Lists tracks, or downloads one with pick + output_path.
    • Tracks are under the Mixkit Stock Music Free License: commercial use, no attribution.
  • tools/video/video_compose.py: the atelier render reads an optional bespoke.timeout_seconds. The default stays 1800 s, so existing callers are unchanged.

Testing

  • Contract tests: pytest tests/contracts/ on the merged branch gives 1241 passed, 7 skipped.
    • One test was deselected: test_phase2_contracts.py::TestPhase2ErrorHandling::test_image_selector_no_provider.
    • It segfaults inside tools/graphics/local_diffusion.py (torch) on my machine, and also does so on main without this change. It's an environment issue unrelated to this PR.
  • Mixkit adapter: search("tomato") now returns 20 candidates (it returned 0 before). About 40 clips downloaded through the adapter.
  • mixkit_music: searched comedy, cartoons and quirky, downloaded 5 tracks, and used them in a rendered video.
  • Atelier timeout: a ~20-minute 1080p bespoke render that previously hit the 1800 s timeout completed with bespoke.timeout_seconds=5400, and final_review passed.

Checklist

  • The change is focused on a single logical concern (keyless sourcing + long-render support found in one production run; happy to split).
  • I ran the relevant tests locally (pytest tests/contracts/) where applicable.
  • I updated docs/README if behavior or usage changed — not done; the tool describes itself via install_instructions / best_for.
  • No unrelated files (build artifacts, local config) are included in the diff.

Notes for reviewers

  • mixkit_music is EXPERIMENTAL, like pixabay_music, because it depends on the JSON-LD that Mixkit embeds in its pages.

🤖 Generated with Claude Code

AI Assistant and others added 2 commits October 14, 2026 20:47
… atelier timeout

- stock_sources/mixkit: match current `.item-grid-card` markup and download
  the direct 1080p asset by video id (720p fallback); the old selectors
  returned zero results.
- audio/mixkit_music: keyless music search/download from Mixkit's schema.org
  JSON-LD (Mixkit Stock Music Free License); fallback when Pixabay 403s.
- audio/gemini_tts: Gemini API speech generation with natural-language
  style prompts (GEMINI_API_KEY / GOOGLE_API_KEY, google-genai SDK).
- video_compose: atelier render honours optional
  bespoke.timeout_seconds (default unchanged at 1800s) for long renders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tools/mixkit-gemini-tts-compose-timeout

# Conflicts:
#	tests/contracts/test_phase3_contracts.py
#	tools/audio/gemini_tts.py
@SophieXueZhang SophieXueZhang changed the title Fix Mixkit adapter; add mixkit_music + gemini_tts tools; configurable atelier render timeout Fix Mixkit stock adapter; add keyless mixkit_music tool; configurable atelier render timeout Oct 15, 2026

@calesthio calesthio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the sourcing repair and configurable atelier timeout. The 35 stock-source adapter tests pass (three existing xfails), but the new direct download path needs two corrections.

[P1] Resolve the clip's actual license before downloading a guessed rendition. tools/video/stock_sources/mixkit.py constructs an ID-based 1080 URL and, on any failure, downloads the 720 variant without checking the detail page. Every candidate is still labelled free for commercial use. Mixkit has both Free and Restricted video licenses: for example clip31544 explicitly offers its free 720p file for personal use only. A mocked listing/download reproduction takes exactly that fallback while retaining the commercial-use label. Resolve the published file/rendition and per-item license, preserve the license evidence, and exclude restricted assets when commercial use is required. Add both license variants and failed-1080 cases to the adapter tests.

[P2] Keep music download failures inside ToolResult. MixkitMusic.execute catches search errors, but the subsequent urlopen/read/write at tools/audio/mixkit_music.py:104 is outside the try block. A normal CDN/network error escapes the tool instead of returning a failed result; I reproduced this with a mocked URLError. Catch download/filesystem errors and add list/download/failure/registry coverage plus provider setup/license documentation. The music license also has distribution limits that should accompany its broad commercial-use description.

No real media was downloaded or generation called.

AI Assistant and others added 2 commits November 10, 2026 19:13
Addresses review on OpenMontageInc#801.

P1 - stock_sources/mixkit:
- Mixkit publishes video under two licences per clip: Stock Video Free
  (commercial OK) and Stock Video Restricted (free 720p for personal use
  only; YouTube, ads and commercial projects not allowed).
- search() now labels each candidate with the licence the listing page's
  schema.org data declares and, by default, drops Restricted clips
  (new SearchFilters.require_commercial_use, default True; other sources
  may ignore it). Candidates no longer carry a guessed file URL.
- download() reads the clip's detail page (authoritative), refuses
  Restricted clips when commercial use is required (MixkitLicenseError),
  and downloads only renditions the page publishes via its download
  modal, best first; a failed 1080p falls back to the page's own 720p
  link, never a guessed URL. The verified licence and evidence (key,
  rendition, file URL, detail URL, timestamp) are written to
  Candidate.license / extra["license_evidence"].

P2 - audio/mixkit_music:
- Download/filesystem errors return a failed ToolResult (partial file
  removed) instead of escaping the tool.
- Documents the Stock Music Free License limits (no CDs/DVDs, broadcast,
  video games, remixes or Content ID registration) in the module,
  install_instructions, not_good_for and on every returned track.
- Both tools: accept ld+json script tags with extra attributes.

Docs: Mixkit section in docs/PROVIDERS.md (setup + licence table).
Tests: tests/tools/test_mixkit_source.py (both licence variants,
default exclusion, failed-1080 fallback, all-renditions-fail, no-licence
page) and tests/tools/test_mixkit_music.py (list, download, URLError,
search failure, pick range, registry).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves the conflict in tools/video/stock_sources/mixkit.py by keeping
both intents: upstream's JSON-LD resolution (contentUrl at download, the
JSON-LD search fallback, declared width/height) and this branch's per-clip
licence verification. JSON-LD contentUrl and the <video> source are now
fallbacks after the page's published download renditions, and every path
requires a stated licence when commercial use is required. Upstream tests
keep their coverage; their fixtures now state a licence, and the JSON-LD
search fallback points candidates at the detail page.

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

Copy link
Copy Markdown
Author

Thanks for the careful review — both points were right, and the P1 one was worse than it looked: when I re-checked against live pages, most Mixkit clips I'd pulled for a real project are published under the Restricted licence (free 720p for personal use only; YouTube/commercial explicitly not allowed).

Pushed in bf93d71 (fixes) + 9da0da0 (merge with current main):

[P1] Per-clip licence, published renditions only — tools/video/stock_sources/mixkit.py

  • search() labels each candidate with the licence the listing's schema.org data declares, and by default drops Restricted clips (new SearchFilters.require_commercial_use, default True; other sources may ignore it). Candidates point at the detail page, not a file URL.
  • download() treats the detail page as authoritative: the licence comes from the download block's data-license (fallback: the page's VideoObject copyrightNotice/license). When commercial use is required it raises MixkitLicenseError for Restricted clips and for pages that state no licence.
  • Files are fetched only from what the page publishes, best first: its download renditions (resolved via the download modal's data-download--modal-url-value), then the JSON-LD contentUrl, then <video src>. A failed 1080p falls back to the page's own 720p link — never a guessed URL.
  • Verified licence + evidence (key, label, rendition, file URL, detail URL, terms URL, timestamp) are written to Candidate.license / extra["license_evidence"].
  • Merge with main: kept upstream's JSON-LD contentUrl resolution, JSON-LD search fallback and declared-dimension handling, all routed through the licence check. Upstream tests keep their coverage; their fixtures now state a licence, the JSON-LD search fallback asserts the detail-page URL, and there's a new case for an unstated licence.

[P2] Music — tools/audio/mixkit_music.py

  • Download/filesystem errors return a failed ToolResult (partial file removed).
  • Stock Music Free License limits (no CDs/DVDs, TV/radio broadcast, video games, remixes/music-only releases, Content ID registration) are documented in the module, install_instructions, not_good_for, and on every returned track (license_limits).
  • Both tools now accept ld+json script tags carrying extra attributes (live pages add data-test-id).

Docs: Mixkit section in docs/PROVIDERS.md with a licence table (video Free / video Restricted / music).

Tests

  • New tests/tools/test_mixkit_source.py: both licence variants, default exclusion (card and JSON-LD paths), failed-1080 → published 720p, all renditions failing, no-licence page.
  • New tests/tools/test_mixkit_music.py: list, download, URLError on download, search failure, pick range, registry.
  • Stock-source + Mixkit + music tests: 90+ passed, the three existing xfails unchanged. Per-file run of tests/contracts + tests/tools: 2337 passed. The only failures/crashes are local-environment issues present without this change (old google-genai for google_imagen, missing Thai segmenter for caption burn, Xcode licence blocking make, and a torch segfault in modules that import face_restore/local_diffusion).
  • Live smoke test (no generation): search("tomato") → Restricted clips excluded; six Free clips downloaded at their published 1080p/2160p with evidence; a Restricted clip is refused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants