Skip to content

docs(examples): add microphone live transcription example - #780

Merged
GregHolmes merged 3 commits into
mainfrom
gh/microphone-live-transcription-example
Sep 3, 2026
Merged

docs(examples): add microphone live transcription example#780
GregHolmes merged 3 commits into
mainfrom
gh/microphone-live-transcription-example

Conversation

@GregHolmes

@GregHolmes GregHolmes commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Closes #779

Summary

  • add a maintained Listen V1 microphone transcription example using the optional sounddevice dependency
  • support device listing, device selection by name or numeric ID, sample rate, channel count, block duration, and language configuration
  • decouple the PortAudio callback from WebSocket I/O with a bounded queue, reporting dropped chunks when the sender cannot keep up
  • handle final transcript events, SDK errors, Ctrl-C, Finalize, and CloseStream cleanly
  • list the new example in examples/README.md

Scope

This is an example only. It adds no core microphone capture abstraction, optional package extra, or platform-audio dependency to deepgram-sdk; microphone drivers, permissions, and backend installation remain owned by sounddevice and the host platform.

Validation

  • live macOS microphone run with device ID 2: received final Deepgram transcripts and returned cleanly after Ctrl-C
  • verified the missing sounddevice path prints the install command without a traceback
  • verified --list-devices with an isolated fake backend
  • Python compilation, Ruff, and mypy pass
  • existing SDK suite: 1019 passed, 1 skipped
  • git diff --check passes

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
src.deepgram 97% 94% 0
src.deepgram.agent 100% 100% 0
src.deepgram.agent.v1 98% 100% 0
src.deepgram.agent.v1.settings 100% 100% 0
src.deepgram.agent.v1.settings.think 100% 100% 0
src.deepgram.agent.v1.settings.think.models 97% 100% 0
src.deepgram.auth 100% 100% 0
src.deepgram.auth.v1 100% 100% 0
src.deepgram.auth.v1.tokens 97% 100% 0
src.deepgram.core 88% 81% 0
src.deepgram.errors 100% 100% 0
src.deepgram.helpers 100% 95% 0
src.deepgram.listen 100% 100% 0
src.deepgram.listen.v1 98% 93% 0
src.deepgram.listen.v1.media 97% 100% 0
src.deepgram.listen.v2 98% 93% 0
src.deepgram.manage 100% 100% 0
src.deepgram.manage.v1 100% 100% 0
src.deepgram.manage.v1.models 96% 100% 0
src.deepgram.manage.v1.projects 97% 100% 0
src.deepgram.manage.v1.projects.billing 100% 100% 0
src.deepgram.manage.v1.projects.billing.balances 96% 100% 0
src.deepgram.manage.v1.projects.billing.breakdown 97% 100% 0
src.deepgram.manage.v1.projects.billing.fields 97% 100% 0
src.deepgram.manage.v1.projects.billing.purchases 97% 100% 0
src.deepgram.manage.v1.projects.keys 96% 100% 0
src.deepgram.manage.v1.projects.members 97% 100% 0
src.deepgram.manage.v1.projects.members.invites 96% 100% 0
src.deepgram.manage.v1.projects.members.scopes 96% 100% 0
src.deepgram.manage.v1.projects.models 96% 100% 0
src.deepgram.manage.v1.projects.usage 98% 100% 0
src.deepgram.manage.v1.projects.usage.breakdown 97% 100% 0
src.deepgram.manage.v1.projects.usage.fields 97% 100% 0
src.deepgram.read 100% 100% 0
src.deepgram.read.v1 100% 100% 0
src.deepgram.read.v1.text 98% 100% 0
src.deepgram.self_hosted 100% 100% 0
src.deepgram.self_hosted.v1 100% 100% 0
src.deepgram.self_hosted.v1.distribution_credentials 96% 100% 0
src.deepgram.speak 100% 100% 0
src.deepgram.speak.v1 98% 97% 0
src.deepgram.speak.v1.audio 91% 80% 0
src.deepgram.speak.v2 98% 93% 0
src.deepgram.speak.v2.audio 100% 100% 0
src.deepgram.voice_agent 100% 100% 0
src.deepgram.voice_agent.configurations 95% 100% 0
src.deepgram.voice_agent.variables 95% 100% 0
Summary 95% (6498 / 6813) 91% (1419 / 1552) 0

Scope: hand-maintained SDK logic. Fern-generated data models (types/, requests/), package __init__.py files, version.py, and the unused core/http_sse/ scaffolding are excluded — see .coveragerc. Unscoped whole-package coverage is ~70%.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What this PR does

Adds example 18: live microphone transcription through Listen V1 using the optional sounddevice package, plus its line in examples/README.md. The microphone callback hands audio to a bounded 20-slot queue that a separate thread drains, so a slow network can never block the audio driver; dropped chunks are counted and reported, and Ctrl-C sends Finalize + CloseStream before exiting.

What I checked

  • Does every SDK call it makes exist? Yes — all seven connect() parameters and every method (send_media, send_finalize, send_close_stream, start_listening), event, and response field it uses match the Listen V1 surface by name.
  • Is the threading sound? Yes — I traced callback → queue → sender thread → error propagation → shutdown; sender failures and SDK error events both stop the run and surface as a non-zero exit, and both worker threads are daemons so exit can't hang.
  • Can the API key leak? No — the key comes from the environment only and is never printed.
  • Not verified here: the live macOS microphone run is author-attested; a real mic can't be exercised headlessly.

One fix before merge

When the connection fails (a bad key is the most common case), the developer sees only Microphone transcription failed: ApiError — the status code and message are discarded. This SDK's ApiError already masks the Authorization header at construction, so printing the exception is safe and makes the failure actionable. In examples/18-transcription-live-microphone.py, line ~183:

print(f"Microphone transcription failed: {type(exc).__name__}: {exc}", file=sys.stderr)

@GregHolmes

Copy link
Copy Markdown
Contributor Author

Thank you @dg-coreylweathers could you please re-review this?

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What this PR does

Adds example 18: live microphone transcription through Listen V1 using the optional sounddevice package, plus its line in examples/README.md. Closes #779. No SDK code changes.

Re-review of the earlier fix

The requested change is in (f600496): connection failures now print the exception type and message. I ran the script with a deliberately bad key and got Microphone transcription failed: InvalidStatus: server rejected WebSocket connection: HTTP 401, exit code 1, with the key absent from all output. One note on my earlier wording: the SDK surfaces a rejected handshake as the websockets library's InvalidStatus, not ApiError, because the generated compat shim binds the wrong exception class. Your fix handles either, so nothing to change there.

The widened type: ignore[import-not-found,import-untyped] is correct: mypy reports the first code when sounddevice is absent and the second when it is installed without stubs. Both environments pass under mypy 1.13.

One fix before merge

On Debian or Ubuntu with sounddevice installed but the PortAudio system library missing, import sounddevice raises OSError, not ImportError. The script's friendly install hint only catches ImportError, so the developer gets a full traceback and exit 1. I reproduced this in a python:3.12 container. Fix is to catch both and name the apt package. In examples/18-transcription-live-microphone.py, line ~41:

    except (ImportError, OSError) as exc:
        raise RuntimeError(
            "Microphone capture requires sounddevice and the PortAudio library. "
            "Install them with: pip install sounddevice "
            "(on Debian/Ubuntu also: sudo apt install libportaudio2)"
        ) from exc

What I checked

  • Every connect() argument, socket method, event type, and response field the script uses exists on the Listen V1 surface.
  • Every RawInputStream argument exists in sounddevice 0.5.6.
  • Sample rate and channel count flow from the CLI args into both the mic stream and the websocket, never hardcoded.
  • ruff, mypy, py_compile, git diff --check, and CI on 3.10-3.13 all pass.
  • Not verified here: the live macOS microphone run (author-attested; no mic headlessly).

Nits, optional

  • on_close (line 117) sets the stop flag silently, so a clean server close exits 0 with no output after "Recording..."; example 13 prints "Connection closed". Suggest printing the same.
  • README entry: "Live microphone transcription (Listen V1) with the optional sounddevice dependency", matching how entries 13 and 14 name the API version.

@dg-coreylweathers dg-coreylweathers left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What this PR does

Adds example 18: live microphone transcription through Listen V1 using the optional sounddevice package, plus its line in examples/README.md. Closes #779. No SDK code changes.

Re-review of the requested fix

Commit 3157d39 applies the PortAudio change as suggested: load_sounddevice() now catches OSError alongside ImportError and names libportaudio2 in the hint. I reproduced the Debian case in a python:3.12 container with sounddevice installed and no PortAudio library on the system: the script prints the one-line install hint, exits 2, no traceback. Same result for --list-devices in that environment and for the no-sounddevice environment. Only those four lines changed since the last review.

What I checked

  • ruff check and ruff format pass; mypy 1.13 passes with and without sounddevice installed; py_compile passes.
  • CI at 3157d39: compile and test on 3.10-3.13, Title Check, all green.
  • Not verified here: the live macOS microphone run (author-attested; no mic headlessly).

The two earlier nits (printing "Connection closed" in on_close, and naming Listen V1 in the README entry) were not taken and remain optional. Approving.

@GregHolmes
GregHolmes merged commit 08f0471 into main Sep 3, 2026
10 checks passed
@GregHolmes
GregHolmes deleted the gh/microphone-live-transcription-example branch September 3, 2026 12:31
GregHolmes added a commit that referenced this pull request Sep 3, 2026
🤖 I have created a release *beep* *boop*
---


##
[7.8.1](v7.8.0...v7.8.1)
(2026-09-03)


### Bug Fixes

* **TextBuilder:** `ssml_to_deepgram()` now preserves a `<phoneme>`
pronunciation when its valid `ph` and `alphabet` attributes appear in
either order.
([#741](#741))
([7fd4b63](7fd4b63))
* **Credentials:** Explicitly passing `api_key=None` continues to
disable ambient `DEEPGRAM_API_KEY` lookup, which is important for
multi-tenant and test environments.
([#778](#778))
([e675990](e675990))
* **Credentials:** `DeepgramClient()` and `AsyncDeepgramClient()` now
resolve `DEEPGRAM_API_KEY` when constructed, so `load_dotenv()` can run
after importing the SDK. Closes
[#734](#734).
([#767](#767))
([ec362ec](ec362ec))
* **Custom transports:** Speak V2 WebSocket connections now honor
`transport_factory`, matching the routing behavior of other WebSocket
APIs for proxies, test doubles, and custom-hosted transports.
([#766](#766))
([0980663](0980663))


### Documentation

* **Transcription:** Clarified that Nova-3 assumes English when
`language` is omitted; non-English and multilingual audio require an
explicit language such as `fr` or `multi`.
([#771](#771))
([4574337](4574337))
* **Examples:** Added Listen V1 live microphone transcription with
optional `sounddevice`, device selection, bounded audio buffering,
transcript output, and clean Ctrl-C shutdown.
([#780](#780))
([08f0471](08f0471))
* **Examples:** Added a resilient Listen V1 live transcription pattern
with exponential backoff, reconnect-aware audio buffering, timestamp
continuity, and clean shutdown.
([#776](#776))
([96b2d11](96b2d11))
* **Examples:** Added an application-owned Voice Agent session recorder
that serializes received transcripts, function calls, and latency
reports as JSON while leaving consent, redaction, retention, and storage
policy to the application. Closes
[#775](#775).
([#781](#781))
([30ad152](30ad152))
* **Text-to-Speech:** Corrected streaming synthesis snippets to iterate
the response byte chunks instead of accessing a nonexistent `.stream`
attribute.
([#749](#749))
([178724e](178724e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Greg Holmes <greg.holmes@deepgram.com>
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.

docs(examples): add microphone live transcription example

2 participants