Skip to content

feat(weixin): add native voice bubble support with OGG→SILK transcoding - #1

Open
ryan-flow wants to merge 1 commit into
mainfrom
feat/weixin-voice-bubble
Open

feat(weixin): add native voice bubble support with OGG→SILK transcoding#1
ryan-flow wants to merge 1 commit into
mainfrom
feat/weixin-voice-bubble

Conversation

@ryan-flow

Copy link
Copy Markdown
Owner

What does this PR do?

Adds native WeChat voice bubble delivery for outbound TTS audio. When TTS generates .ogg/.opus files, the adapter now converts them to SILK format via pilk + ffmpeg and sends them as native WeChat voice bubbles (the green audio bar), instead of falling back to a file attachment.

This addresses Issue #9971: TTS audio messages are not delivered as voice bubbles — need OGG → SILK transcoding.

How it works

Edge TTS output (.ogg)
  → ffmpeg decode to raw PCM
  → pilk.encode() to SILK format
  → weixin.py send_voice() uploads as ITEM_VOICE
  → WeChat client displays native voice bubble 🎵

Changes

File What changed
gateway/platforms/voice_utils.py New. OGG→SILK conversion pipeline (ogg_to_silk), duration detection (get_audio_duration_s), temp cleanup (cleanup_silk_dir).
gateway/platforms/weixin.py send_voice(): convert OGG/Opus to SILK before sending; native voice bubble for .silk files; fallback to file attachment on failure. _AUDIO_EXTS: added .ogg, .opus. _send_file(): added playtime for SILK items.
tests/gateway/test_voice_utils.py New. Unit tests for conversion, duration, cleanup, and pilk detection.
tests/gateway/test_weixin_send_voice.py New. Integration tests for send_voice() with SILK, OGG, conversion failure, and cleanup.

Fallback behavior

The implementation degrades gracefully at every layer:

Condition Behavior
pilk not installed OGG → file attachment (no crash)
ffmpeg not on PATH OGG → file attachment
Conversion fails OGG → file attachment with warning log
File is already .silk Sent as native voice bubble directly
Unsupported format File attachment

Dependencies

  • pilk>=0.2.4,<1 — added to pyproject.toml under [voice] extra (optional)
  • ffmpeg — system dependency, already widely available

How to test

  1. Install with voice extras: pip install hermes-agent[voice]
  2. Configure TTS (Edge TTS or any provider)
  3. Send a voice message via WeChat — should appear as a green voice bubble
  4. Verify file attachment fallback: temporarily rename ffmpeg binary

Related

Checklist

  • I've read the Contributing Guide
  • Commit messages follow Conventional Commits
  • Searched for existing PRs to ensure this isn't a duplicate
  • PR contains only changes related to this feature
  • Added tests for changes
  • Considered cross-platform impact (uses Python + optional pilk, no platform-specific shell)

Add outbound voice bubble delivery for WeChat. When TTS generates OGG/Opus
audio, the adapter now converts it to SILK format via pilk + ffmpeg and
sends it as a native WeChat voice bubble (the green audio bar), instead of
a file attachment.

Changes:
- New module `gateway/platforms/voice_utils.py`:
  - `ogg_to_silk()`: OGG→PCM→SILK conversion pipeline
  - `get_audio_duration_s()`: duration detection (SILK via pilk, others via ffprobe)
  - `cleanup_silk_dir()`: temp file cleanup
- Modified `gateway/platforms/weixin.py`:
  - `send_voice()`: convert OGG/Opus to SILK before sending; native voice
    bubble for SILK files; fallback to file attachment on failure
  - `_AUDIO_EXTS`: added `.ogg` and `.opus` so TTS output routes through send_voice
  - `_send_file()`: added playtime field for SILK voice items
- New tests: `tests/gateway/test_voice_utils.py`, `tests/gateway/test_weixin_send_voice.py`

Requires: `pilk` (optional, in `[voice]` extra) + `ffmpeg` on PATH.
Falls back gracefully to file attachment when pilk/ffmpeg unavailable.

Closes NousResearch#9971
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.

[Weixin] TTS audio messages are not delivered as voice bubbles — need OGG → SILK transcoding

1 participant