diff --git a/.github/scripts/verify_package.py b/.github/scripts/verify_package.py index f3b1cbb..d9fc5b3 100644 --- a/.github/scripts/verify_package.py +++ b/.github/scripts/verify_package.py @@ -12,6 +12,7 @@ from pathlib import Path import imageio_ffmpeg +from agent_voice.media import CONTENT_TYPES, generating_audio SERVICE_URL = "http://127.0.0.1:18765" @@ -129,6 +130,9 @@ def main() -> None: output_dir = Path(os.environ["RUNNER_TEMP"]) / "agent-voice-package-e2e" output_dir.mkdir(parents=True, exist_ok=True) + for audio_format in CONTENT_TYPES: + assert generating_audio(audio_format) + subprocess.run([str(cli), "setup", "--model", "int8"], check=True) offline_doctor = run_cli(cli, "doctor", "--service-url", SERVICE_URL, "--json") check_doctor(offline_doctor, "warn") @@ -200,18 +204,27 @@ def main() -> None: ) check_doctor(online_doctor, "pass") - service_wav = output_dir / "service.wav" remote = run_cli( cli, "speak", f"{system} localhost service verification.", "--service-url", SERVICE_URL, - "--output", - str(service_wav), + "--label", + "Package Service E2E", + "--format", + "wav", ) + service_wav = Path(str(remote["path"])) assert remote["backend"] == "service" + assert remote["generation"] == {"state": "started"} + assert "stream_url" in remote["delivery"] assert "playback" not in remote + pending = service_wav.with_name(f".{service_wav.name}.pending") + deadline = time.monotonic() + 120 + while pending.is_file() and time.monotonic() < deadline: + time.sleep(0.1) + assert not pending.exists(), "background generation did not finish" validate_wav(service_wav) finally: service.terminate() diff --git a/README.md b/README.md index faf3e67..534c9e3 100644 --- a/README.md +++ b/README.md @@ -163,13 +163,8 @@ agent-voice speak "The build is finished." # Agent output through stdin printf '%s' "$TEXT" | agent-voice speak --label build-summary -# Spoken response text and written response Markdown in one command -agent-voice speak "$RESPONSE_AS_TEXT" \ - --markdown "$RESPONSE_AS_MARKDOWN" --label response - -# Use separate files for a long spoken response and its written Markdown -agent-voice speak --response-file "$RESPONSE_AS_MARKDOWN_FILE" \ - < "$RESPONSE_AS_TEXT_FILE" +# Use a file for a long spoken response +agent-voice speak --label response < "$RESPONSE_AS_TEXT_FILE" # Choose the output and delivery agent-voice speak "Here is your summary." \ @@ -180,8 +175,6 @@ agent-voice speak "Here is your summary." \ | --- | --- | | `-o, --output PATH` | Write to an exact path. | | `--label TEXT` | Set the managed filename prefix. | -| `--markdown TEXT` | Show an inline Markdown response in the viewer. | -| `--response-file PATH` | Show a Markdown response in the browser viewer. | | `--output-dir DIR` | Choose the managed output directory. | | `-f, --format FORMAT` | Use `wav`, `mp3`, `opus`, or `m4a`. | | `-v, --voice NAME` | Select a voice. | @@ -190,12 +183,14 @@ agent-voice speak "Here is your summary." \ | `-p, --play` | Start local playback after creation, without waiting for it to finish. | | `--play-after SECONDS` | Schedule local playback after creation, without waiting. | | `--controls` | Include experimental `agent-voice://` playback control links. | +| `--wait` | Wait for the completed recording instead of returning live links. | | `--no-service` | Run the same Agent Voice model inside this command, then unload it. | | `--model-id ID`, `--variant NAME` | Select a model and build. | -`speak` prints one JSON receipt with the absolute recording path, file URI, -audio metadata, playback state (`started` or `scheduled`), and available viewer -links. This makes the command reliable for both people and agents. +`speak` prints one JSON receipt with the absolute recording path, generation +state, audio metadata, playback state (`started` or `scheduled`), and available +delivery links. By default it returns live links while generation continues; +pass `--wait` when the completed file is required before the command returns. ### Configure defaults @@ -258,11 +253,11 @@ agent-voice viewer stop The lightweight viewer starts automatically when needed and serves only local recordings. It prefers `http://127.0.0.1:8779` and selects a free port if that port is unavailable. Each managed recording keeps an editable `.txt` source -beside it. At startup and every six hours, the viewer removes owned audio older -than four days and 18 hours; files without Agent Voice source, transcript, and -language metadata are left alone. Opening a player or audio URL regenerates -missing audio from its source with the original language and current voice and -speed. +beside it. At startup and every hour, the viewer removes temporary live-stream +audio older than one hour and owned recordings older than four days and 18 +hours; files without Agent Voice source, transcript, and language metadata are +left alone. Opening a player or audio URL regenerates missing audio from its +source with the original language and current voice and speed. Playback commands return as soon as local playback starts, or immediately with `scheduled` when a delay is requested; they never wait for the recording to end. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 127ea3a..43d9f42 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -8,6 +8,3 @@ This project depends on third-party software and downloads model assets. - **imageio-ffmpeg** — Copyright imageio-ffmpeg contributors, licensed under the BSD 2-Clause License. Its platform wheels include an FFmpeg executable: - **FFmpeg** — The executable bundled by imageio-ffmpeg is licensed under GNU GPL version 2 or later. License and source information: - **miniaudio Python bindings** — Copyright Irmen de Jong and contributors, licensed under the MIT License: -- **linkify-it-py and uc-micro-py** — Copyright their contributors, licensed under the MIT License: -- **markdown-it-py and mdurl** — Copyright their contributors, licensed under the MIT License: -- **Pygments** — Copyright Georg Brandl and contributors, licensed under the BSD 2-Clause License: diff --git a/pyproject.toml b/pyproject.toml index 1e6a326..722bd10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agent-voice" -version = "0.8.0" +version = "0.9.0" description = "Local, free voice artifacts for AI agents" readme = "README.md" requires-python = ">=3.11,<3.14" @@ -24,11 +24,8 @@ dependencies = [ "filelock>=3.20,<4", "imageio-ffmpeg==0.6.0", "kokoro-onnx==0.5.0", - "linkify-it-py>=2,<3", - "markdown-it-py>=4.2,<5", "miniaudio==1.71", "numpy>=2,<3", - "pygments>=2.19,<3", ] [project.urls] diff --git a/skills/create-speech-recording-controls/SKILL.md b/skills/create-speech-recording-controls/SKILL.md index d0ef6f3..0142220 100644 --- a/skills/create-speech-recording-controls/SKILL.md +++ b/skills/create-speech-recording-controls/SKILL.md @@ -10,10 +10,7 @@ matching voice with `agent-voice voices` and pass `--voice` and `--lang`. ## Prepare -Set `RESPONSE_AS_MARKDOWN` to the original text or Markdown. Set -`RESPONSE_AS_TEXT` to its spoken form. Never use `RESPONSE_AS_MARKDOWN` as the -speech input. -Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text. +Set `RESPONSE_AS_TEXT` to the text's spoken form. - For supplied text, preserve every word and punctuation mark in order while translating presentation syntax into speech. @@ -24,13 +21,13 @@ Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text. ## Record ```sh -agent-voice speak "$RESPONSE_AS_TEXT" --markdown "$RESPONSE_AS_MARKDOWN" --controls +agent-voice speak "$RESPONSE_AS_TEXT" --controls ``` For long text, use temporary files outside the workspace and remove them afterward: ```sh -agent-voice speak --label "$LABEL" --response-file "$RESPONSE_AS_MARKDOWN_FILE" --controls < "$RESPONSE_AS_TEXT_FILE" +agent-voice speak --label "$LABEL" --controls < "$RESPONSE_AS_TEXT_FILE" ``` Set `LABEL` to a short subject. For an exact requested filename, replace @@ -42,7 +39,8 @@ blocking. ## Deliver -Use [default.md](references/delivery/default.md). +Use [default.md](references/delivery/default.md) immediately after `speak` +returns a receipt with generation.state: "started" ## Setup diff --git a/skills/create-speech-recording-desktop/SKILL.md b/skills/create-speech-recording-desktop/SKILL.md index 522fcf6..811870e 100644 --- a/skills/create-speech-recording-desktop/SKILL.md +++ b/skills/create-speech-recording-desktop/SKILL.md @@ -10,10 +10,7 @@ matching voice with `agent-voice voices` and pass `--voice` and `--lang`. ## Prepare -Set `RESPONSE_AS_MARKDOWN` to the original text or Markdown. Set -`RESPONSE_AS_TEXT` to its spoken form. Never use `RESPONSE_AS_MARKDOWN` as the -speech input. -Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text. +Set `RESPONSE_AS_TEXT` to the text's spoken form. - For supplied text, preserve every word and punctuation mark in order while translating presentation syntax into speech. @@ -24,23 +21,23 @@ Use real line breaks in `RESPONSE_AS_MARKDOWN`, not escaped `\n` text. ## Record ```sh -agent-voice speak "$RESPONSE_AS_TEXT" --markdown "$RESPONSE_AS_MARKDOWN" +agent-voice speak "$RESPONSE_AS_TEXT" --wait ``` For long text, use temporary files outside the workspace and remove them afterward: ```sh -agent-voice speak --label "$LABEL" --response-file "$RESPONSE_AS_MARKDOWN_FILE" < "$RESPONSE_AS_TEXT_FILE" +agent-voice speak --label "$LABEL" --wait < "$RESPONSE_AS_TEXT_FILE" ``` -On Antigravity App, follow the special +On OpenCode Desktop, omit `--wait`. On Antigravity App, follow the special [recording and delivery instructions](references/delivery/antigravity.md). Set `LABEL` to a short subject. For an exact requested filename, replace `--label "$LABEL"` with `--output "$PATH"`. -Use the returned `path` to deliver the recording. For speaker playback, add -`-p`; continue after the result reports `playback.state: "started"`. Add +Use the matching delivery reference. For speaker playback, add `-p`; continue +after the result reports `playback.state: "started"`. Add `--play-after SECONDS` to schedule it without blocking. ## Deliver diff --git a/skills/create-speech-recording-desktop/references/delivery/opencode-desktop.md b/skills/create-speech-recording-desktop/references/delivery/opencode-desktop.md index 8f7c259..f0424f5 100644 --- a/skills/create-speech-recording-desktop/references/delivery/opencode-desktop.md +++ b/skills/create-speech-recording-desktop/references/delivery/opencode-desktop.md @@ -1,15 +1,16 @@ # OpenCode Desktop Set `PLAYER_ID` to a unique lowercase ID. Set `RECORDING_NAME` to the basename -of the returned `path`. Set `audio_url` to `delivery.audio_url`. Replace the -placeholders in the template. Render the HTML directly without the code fence. +of `path`. Set `AUDIO_SOURCE` to `delivery.stream_url` when present, otherwise +to `delivery.audio_url`. Replace the placeholders and render the HTML directly +without the code fence. ```html
$RECORDING_NAME
-