fix: sync _version.py to 0.15.4, and guard the pair - #39
Merged
Merged
Conversation
Lightsage docs evalsWaiting for the staging docs URL before running evals. Lightsage will start the selected PR evals automatically when GitHub reports a successful docs deployment for this PR. This usually happens within 15 minutes. Commit: |
`#38` bumped `pyproject.toml` to 0.15.4 but left `src/sonilo/_version.py` on 0.15.3. `__version__` ships as the `x-sonilo-client-version` header, so a 0.15.4 release would have had every client on it identify itself as 0.15.3. Nothing caught it: the existing version tests compare the header to `__version__`, which stays self-consistent however stale it is, and no test compared either to pyproject. The release commits have always bumped both by hand (a41a37c, 2c78bd8), which works right up until someone bumps only one. Adds the missing comparison so the next one-sided bump fails in CI instead of on PyPI. sonilo-cli is unaffected — its own version and its `sonilo>=0.15.0,<0.16` pin both still hold. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFP9xPKwk2S1Qjmk5krx6h
spencer-zqian
force-pushed
the
fix/version-sync-0.15.4
branch
from
September 4, 2026 23:28
dc4cdb7 to
6d733ae
Compare
steven-panxd
added a commit
that referenced
this pull request
Sep 5, 2026
Both packages gained the dubbing `lipsync` parameter in #37, so both get a minor bump. _version.py moves with pyproject.toml, which the guard added in #39 now enforces: `__version__` ships as the x-sonilo-client-version header, so a release that bumped only pyproject would have every client on 0.16.0 identifying itself as 0.15.4. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Y2tCTSPesvLbw3hcnQ5ct
spencer-zqian
added a commit
that referenced
this pull request
Sep 14, 2026
* feat: dubbing subtitles, export_srt and lipsync on the client `POST /v1/dubbing` takes a target-language script per language as the repeatable multipart field `subtitles[<language>]` (an https URL or an uploaded .srt/.vtt), and `export_srt` returns a re-timed .srt per language once the dub is delivered. `lipsync` has existed on the endpoint all along and never reached this SDK. `build_dubbing_parts` now returns a ready-made `close_after` as its third element instead of an `opened` bool, the shape `build_ducking_parts` already uses. One request can open the video plus one script per target language, and a bool cannot say how many handles that is — nor which of them this builder opened. Every handle is closed on the way out if a later open fails, and the caller closes the rest through the `close_after` seam `_post_json` already exposes, so the routine 422 from a rejected script set does not leak them. Client-side checks are limited to the two guaranteed 422s: a script path whose suffix is not .srt/.vtt, and `export_srt` with no scripts. The language set is not checked against `languages` — the server owns that rule, its message names the offending code, and a local copy would break a caller who relies on the server default without passing `languages` at all. `DubbingResult` gains `subtitles` (with save_subtitle / save_all_subtitles mirroring save / save_all), `subtitle_preflight` and `subtitle_export`. The two report maps are coerced but their values are left as they arrived: the pipeline stores numbers as strings, so `cue_count` may be "5" and `alignment_loss` "0.63", and guessing at a type here would only mislead. A blocked export does not fail the task, so `subtitles` can lack a language whose video was still delivered — save_all_subtitles iterates `subtitles`, not `outputs`. `lipsync` defaults ON server-side and every dubbing task ran that way before the field existed, so it is sent only when the caller passes it: absent has to keep meaning true. Claude-Session: https://claude.ai/code/session_01WWHfnbRaXRdAH2sjm8mApk * feat(cli): --subtitle and --export-srt on `sonilo dubbing` `--subtitle <language>=<path-or-url>`, repeated once per target language, carries the script to speak in it; `--export-srt` asks for a re-timed .srt back. The `=` is split once only so a path or URL containing one survives. Neither the codes nor the language set are checked here — the server owns both rules and its message names what it refused; the SDK still refuses a suffix that is not .srt/.vtt, which is a guaranteed 422. Each .srt is written beside its video (clip.es.mp4 -> clip.es.srt) and every requested language gets a status line, including a language whose export was blocked: that still delivers the video, just without a script. The alignment loss is formatted through float() because the pipeline stores numbers as strings — "0.63" and 0.63 both have to print — and is omitted when it is neither. Claude-Session: https://claude.ai/code/session_01WWHfnbRaXRdAH2sjm8mApk * chore: sonilo 0.16.0, sonilo-cli 0.15.0, and widen the CLI's pin New parameters on an existing endpoint, so a minor bump on both packages — and `__version__` is bumped alongside `pyproject.toml` in each, since it ships as the `x-sonilo-client-version` header and CI now fails on a one-sided bump (#39). The CLI's `sonilo>=0.15.0,<0.16` pin has to widen to `>=0.16.0,<0.17` in this same commit: it calls `dubbing.generate(subtitles=..., export_srt=...)`, which only exists from 0.16.0, and left alone the pin would make the editable install of both packages unresolvable. Claude-Session: https://claude.ai/code/session_01WWHfnbRaXRdAH2sjm8mApk * docs: subtitles, export_srt and lipsync in both READMEs The dubbing section of each README gains the new parameters: the target- language scripts and what the server checks about them, the re-timed .srt `export_srt` returns, the three new maps on `DubbingResult` (including that a blocked export still delivers the video), and `lipsync`, which has been on the endpoint all along and was documented nowhere. Also f-strings the two `subtitles[<language>]` field names, matching the rest of the module. Claude-Session: https://claude.ai/code/session_01WWHfnbRaXRdAH2sjm8mApk * feat(cli): --ducking and --no-lipsync on `sonilo dubbing` The JavaScript CLI's dubbing command has had --ducking since the ducking round and has just gained --no-lipsync; the two CLIs mirror each other, so this side was the one out of step. --ducking goes through the same `_ducking()` resolution the sound commands use, so --no-ducking keeps parsing as the explicit-default no-op it is elsewhere and passing both still exits 1. Only --no-lipsync exists, with no positive counterpart: lip sync is default-ON server-side, so the sole useful direction is turning it off, and an absent flag sends nothing rather than restating a default that could move. Claude-Session: https://claude.ai/code/session_01WWHfnbRaXRdAH2sjm8mApk * fix: return the 202's subtitle_preflight from dubbing submit() `submit()` parsed the dubbing ack with the shared `parse_sfx_task`, which builds an `SfxTask` of `task_id` and `status` — so the whole per-language `subtitle_preflight` map was read off the wire and dropped. That map is the free, pre-charge check: a language whose status is `review_required` had lines changed in the script the caller submitted, and with `submit()` there was no way to see it before the billed dub finished. Adds `DubbingTask`, an additive subclass of `SfxTask` carrying the map, and `parse_dubbing_task` to fill it — coerced through the same `_report_map_from` the finished task's copy uses. `SfxTask` itself is untouched: it acks every other async endpoint, and the preflight is dubbing's alone. Mirrors the JavaScript SDK's `DubbingTask extends SfxTask`. Also drops the unreachable `or "subtitles.srt"` fallback in `_subtitle_filename` — an empty name has an empty suffix, so the extension check raises before it — and fixes the README example, which read `report["status"]` two lines under its own advice to read these defensively. Claude-Session: https://claude.ai/code/session_01WWHfnbRaXRdAH2sjm8mApk * fix(cli): refuse --output *.srt with --export-srt, and tighten three tests `sonilo dubbing --export-srt --output clip.srt` wrote the dubbed video to clip.es.srt and then wrote the subtitle over it, printing a "Wrote" line for each: both files come from the one template and the subtitle goes second, so the mp4 was silently truncated away. It is refused at parse time now, before anything runs, with a message naming the cause and the fix — matched case-insensitively, and the same refusal the JavaScript CLI makes. The three tests that asserted only an exit code plus a flag name were passing against `main` for the wrong reason: argparse's own "unrecognized arguments" error quotes the flag, so they would not have caught a regression that deleted the flags outright. Each now matches the message this CLI actually prints. Claude-Session: https://claude.ai/code/session_01WWHfnbRaXRdAH2sjm8mApk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#38 bumped
pyproject.tomlto 0.15.4 but leftsrc/sonilo/_version.pyon 0.15.3 — my miss, caught while verifying main after the merge.__version__ships as thex-sonilo-client-versionheader, so releasing 0.15.4 as-is would have had every client on that version identify itself as 0.15.3 to the API.Why nothing caught it: the existing version tests compare the header to
__version__, which stays self-consistent no matter how stale it is, and nothing compared either topyproject.toml. The release commits have always bumped both by hand (a41a37c,2c78bd8) — which works right up until someone bumps only one.This syncs the file and adds the missing comparison, so the next one-sided bump fails in CI rather than on PyPI.
sonilo-cliis unaffected: its own version and itssonilo>=0.15.0,<0.16pin both still hold.290 passed.🤖 Generated with Claude Code
https://claude.ai/code/session_01SFP9xPKwk2S1Qjmk5krx6h