Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added authors/assets/goodguypeci-design.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions authors/petar-ivanov.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Author: Petar Ivanov
Title: Contributor
Description: Petar Ivanov contributed this guide to running Sapat with Azure AI Speech in a Daytona sandbox. The guide covers setup, a first transcription, and checks for common configuration and output problems.
Author Image: ![GitHub account avatar for goodguypeci-design](./assets/goodguypeci-design.png)
Author LinkedIn: Not provided
Author Twitter: Not provided
Company Name: Not provided
Company Description: Not provided
Company Logo Dark: Not applicable
Company Logo White: Not applicable
23 changes: 23 additions & 0 deletions definitions/20260908_definition_speech_to_text_api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: 'Speech-to-text API'
description: 'A service interface that accepts recorded speech and returns recognized text.'
date: 2026-09-08
author: 'GoodGuyPeci Design'
---

# Speech-to-text API

## Definition

A speech-to-text API is a programmatic service that receives audio containing
spoken language and returns a text transcription. Applications usually send an
audio file or stream together with settings such as the spoken language and the
desired response format.

## Context and Usage

Speech-to-text APIs are used for meeting notes, subtitles, searchable media,
voice interfaces, and call analysis. A client must prepare audio in a format the
provider accepts, authenticate the request, handle size or duration limits, and
store the returned transcript. Tools such as Sapat hide much of that provider
specific work behind one command-line interface.
349 changes: 349 additions & 0 deletions guides/20260908_azure_ai_speech_transcription_with_sapat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
---
title: 'Azure AI Speech Transcription with Sapat'
description:
'Run Sapat in a Daytona sandbox and transcribe audio through the Azure AI
Speech short-audio REST API.'
date: 2026-09-08
author: 'Petar Ivanov'
tags: ['Daytona', 'Sapat', 'Azure AI Speech', 'speech-to-text']
---

# Azure AI Speech Transcription with Sapat

Sapat gives one command-line interface to several transcription services. That
is useful when an application needs to change providers without rewriting its
whole audio pipeline. The project already supports Azure OpenAI, but Azure has
another product with a similar name: Azure AI Speech. Azure OpenAI expects a
deployed Whisper model; Azure AI Speech uses a Speech resource and its own
speech-to-text endpoint.

This guide uses the `azure_speech` provider proposed in
[Sapat pull request #80](https://github.com/nibzard/sapat/pull/80). You will put
Sapat in a fresh Daytona sandbox, configure an Azure Speech resource, run a
transcription, and check the output. The sandbox keeps the setup away from your
main machine, which makes it easier to repeat the same work or discard it when
you are finished.

![Daytona sandbox runs Sapat, which sends converted audio to Azure AI Speech and saves the returned text as sample.txt in the sandbox](assets/20260908_azure_ai_speech_sapat_daytona_img1.png)

## TL;DR

- Create a Daytona sandbox and clone the Sapat feature branch into it.
- Install Python dependencies and FFmpeg inside the sandbox.
- Put the Azure Speech key and resource endpoint in an ignored `.env` file.
- Run `sapat sample.mp4 --provider azure_speech --language en-US`.
- Confirm that `sample.txt` contains the recognized speech.

## What you need

Have these ready before opening a terminal:

| Item | Why it is needed |
| --- | --- |
| Daytona account and API key | Creates and controls the isolated sandbox. |
| Daytona CLI | Runs sandbox commands from your computer. |
| Azure subscription and Speech resource | Supplies the transcription service, key, and endpoint. |
| A copy of a short MP4 with speech | Gives you a known input for the first test. |

Use Bash for the commands with `\` line continuations, including when running
them from Windows through WSL or Git Bash. The installer command below is the
one exception: run it in PowerShell. Commands after `daytona ssh` run inside the
Linux sandbox.

Microsoft limits a direct short-audio request to 60 seconds. The provider asks
Sapat for 16 kHz mono WAV audio and uses a conservative 1.8 MB chunk size, which
keeps normal PCM chunks close to that limit. Longer videos can still be used:
Sapat converts them with FFmpeg and splits the resulting audio before sending
the pieces one at a time.

## Step 1: Install and authenticate the Daytona CLI

Daytona now calls its isolated environments _sandboxes_. If you have an older
Daytona installation, check its version before following commands from an older
workspace tutorial:

```bash
daytona version
```

On macOS, install the current CLI with Homebrew:

```bash
brew install daytonaio/cli/daytona
```

On Windows, run the official installer from PowerShell:

```powershell
powershell -Command "irm https://get.daytona.io/windows | iex"
```

Get an API key from the Daytona dashboard and log in:

```bash
daytona login --api-key=YOUR_DAYTONA_API_KEY
```

Do not copy the real key into documentation, a commit, or a shared terminal
recording. Verify the connection with `daytona list`. An empty list is fine; it
means authentication works and you have no sandboxes yet.

## Step 2: Create a clean Sapat sandbox

Create a named sandbox in the European region:

```bash
daytona create --name sapat-azure-speech --target eu
```

The name makes later commands easier to read. Confirm that Daytona started it:

```bash
daytona info sapat-azure-speech
```

Use `daytona exec` to install the system packages Sapat needs. `git` downloads
the source; `ffmpeg` and `ffprobe` convert and inspect the media; the Python venv
package creates an isolated package environment inside the sandbox.

```bash
daytona exec sapat-azure-speech -- bash -lc \
"sudo apt-get update && sudo apt-get install -y git ffmpeg python3-venv"
```

Next, clone the branch from the open implementation PR and install Sapat:

```bash
daytona exec sapat-azure-speech -- bash -lc \
"git clone --branch feat/azure-speech-provider \
https://github.com/goodguypeci-design/sapat.git workspace/sapat && \
cd workspace/sapat && python3 -m venv .venv && \
. .venv/bin/activate && python -m pip install --upgrade pip && \
python -m pip install -e ."
```

The fork and branch are needed while PR #80 is under review. After it is merged,
clone `https://github.com/nibzard/sapat.git` without the `--branch` argument.

Check the installation without contacting Azure:

```bash
daytona exec sapat-azure-speech -- bash -lc \
"cd workspace/sapat && . .venv/bin/activate && \
ffmpeg -version | head -n 1 && sapat --version"
```

## Step 3: Find the correct Azure values

In the Azure portal, create or open an Azure AI Speech resource. On its **Keys
and Endpoint** page, copy one resource key and the endpoint. A current endpoint
normally looks like this:

```text
https://your-resource-name.cognitiveservices.azure.com
```

The endpoint matters because Microsoft builds the short-audio URL from the
resource name. A region such as `westeurope` can be used as a fallback for an
older or non-custom-domain setup, but it is not the preferred value for a new
resource.

Connect to the sandbox and move into the cloned repository:

```bash
daytona ssh sapat-azure-speech
cd workspace/sapat
```

Create the local settings file:

```bash
cp .env.example .env
chmod 600 .env
```

Open `.env` in your terminal editor and set these values:

```dotenv
AZURE_SPEECH_KEY=your_real_speech_resource_key
AZURE_SPEECH_ENDPOINT=https://your-resource-name.cognitiveservices.azure.com
AZURE_SPEECH_RESPONSE_FORMAT=detailed
AZURE_SPEECH_PROFANITY=masked
```

Sapat's `.gitignore` excludes `.env`, MP4, MP3, WAV, and TXT files.
Still run `git status --short` before every push. The command should not show
`.env` or your sample media.

For a quick private experiment, the ignored file is the shortest route. For a
long-lived or shared environment, use
[Daytona Secrets](https://www.daytona.io/docs/en/secrets/) instead. A Daytona
Secret keeps the real key outside the sandbox and can restrict substitution to
your `*.cognitiveservices.azure.com` host. Azure accepts this key in the
`Ocp-Apim-Subscription-Key` HTTPS header, so it fits Daytona's header-based
secret substitution model.

## Step 4: Put a sample in the sandbox

If the media is already in a repository the sandbox can clone, place it under
`workspace/sapat` and skip this step. Keep private recordings out of public Git
history.

For a local file, the Daytona Python SDK can upload it directly. Open a second
terminal on your own computer, leaving the SSH session open. Install the SDK
with `python -m pip install daytona`. Set the
[`DAYTONA_API_KEY` environment variable](../definitions/20241126_definition_environment_variables.md)
in that terminal to the same key used for the CLI. The SDK needs its own environment
configuration; a successful CLI login does not configure this Python process.

Save the following as `upload_sample.py` alongside `sample.mp4`, then run
`python upload_sample.py` from that directory:

```python
from pathlib import Path

from daytona import Daytona

daytona = Daytona()
sandbox = daytona.get("sapat-azure-speech")
sandbox.fs.upload_file(
Path("sample.mp4").read_bytes(),
"workspace/sapat/sample.mp4",
)
```

The upload goes to the sandbox file system and does not add the recording to
Git. For the first test, choose a clear 10-to-20-second clip whose words you
already know. Use a copy named `sample.mp4`, with no existing `sample.wav` or
`sample.txt` beside it. This branch reuses and then removes a same-named WAV
file, and overwrites a same-named transcript. Do not use your only copy of a
recording as the test input.

## Step 5: Run the transcription

Return to the SSH session, which is still in `workspace/sapat` from Step 3.
Activate the virtual environment and run Sapat:

```bash
. .venv/bin/activate
sapat sample.mp4 --provider azure_speech --language en-US
```

For Bulgarian audio, use `--language bg-BG`. Short aliases such as `en` and
`bg` are also normalized by the provider, but an explicit locale is clearer in
reproducible commands.

Sapat first invokes FFmpeg to produce the 16 kHz, mono, 16-bit WAV format Azure
expects. It then sends the bytes to the resource endpoint with the language and
response format as query parameters. A successful response becomes
`sample.txt`, and the temporary converted WAV file is removed.

Read the output and compare it with the words in your clip. A created text file
alone does not tell you whether the transcription is accurate. Also check that
no temporary media was staged for Git:

```bash
cat sample.txt
git status --short
```

You can also confirm that Sapat discovered the new provider before making a
request:

```bash
python -c "from sapat.providers import get_provider_choices; print(get_provider_choices())"
```

The printed list should contain `azure_speech`. If it does not, check that `.env`
is in the Sapat repository root and contains both the key and either the endpoint
or region.

## Step 6: Verify the provider without spending an API call

The implementation includes mocked request tests. They inspect the URL, headers,
language normalization, detailed-result parsing, `NoMatch`, and HTTP error
messages without sending real audio to Azure:

```bash
python -m pip install -e ".[dev]"
python -m pytest tests/providers/test_group_e.py tests/test_registry.py \
tests/test_cli.py tests/test_base.py -q
```

These tests check client behavior against simulated responses. They do not
verify Azure credentials, service availability, or recognition accuracy. A
successful request with your sample is a separate check that the key, endpoint,
account, and media work together; listening and comparing the words checks the
transcript itself.

## Common issues and troubleshooting

### `Provider 'azure_speech' is not available`

Provider discovery only exposes services whose required configuration is
present. Confirm the `.env` filename, its directory, and the variable names.
Restart the command after editing because the environment is read when Sapat is
imported.

### Azure returns `401 Unauthorized`

The key and endpoint usually belong to different resources, or a regional URL
does not match the key's region. Copy both values again from the same **Keys and
Endpoint** page. Do not add the recognition path yourself when
`AZURE_SPEECH_ENDPOINT` contains only the resource root; the provider appends it.

### Azure returns `400 Bad Request`

Use a supported locale such as `en-US` or `bg-BG`, and let Sapat perform the
conversion. Microsoft accepts PCM WAV at 16 kHz mono or Ogg Opus for this API.
A damaged file or unsupported language also produces a 4xx response.

### The transcript is empty

Azure uses `NoMatch` when it detects audio but cannot match words in the chosen
language. Try the exact locale spoken in the clip, reduce background noise, and
test with a short recording whose contents are known.

### A long recording fails on one chunk

Check that FFmpeg and `ffprobe` are installed, then try a shorter source. Inspect
the output for `[Chunk N transcription failed]` markers: this branch can write
a transcript even when individual chunks fail. Keep the original recording so
you can retry those sections. If your application regularly processes long
recordings, review Microsoft's fast and batch transcription options in the
[Speech API documentation](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-speech-to-text-short).

## Clean up when finished

Leave the SSH session with `exit`. Daytona can stop, archive, or delete the
sandbox according to how long you need the files. List it first so you target
the correct environment:

```bash
daytona list
daytona stop sapat-azure-speech
```

Delete it only when you no longer need the transcript or setup:

```bash
daytona delete sapat-azure-speech
```

## Conclusion

The workflow has three parts to check when something goes wrong: Daytona runs
the environment, Sapat converts the media and saves the result, and Azure AI
Speech recognizes the words. Start with a short clip, compare the transcript
with what you hear, then try a longer recording and inspect every failed-chunk
marker before using the text elsewhere.

## References

- [Sapat repository](https://github.com/nibzard/sapat)
- [Azure AI Speech provider PR](https://github.com/nibzard/sapat/pull/80)
- [Microsoft short-audio REST API](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-speech-to-text-short)
- [Daytona CLI reference](https://www.daytona.io/docs/en/tools/cli/)
- [Daytona authentication](https://www.daytona.io/docs/en/api-keys/)
- [Daytona Secrets](https://www.daytona.io/docs/en/secrets/)
- [Daytona file-system operations](https://www.daytona.io/docs/file-system-operations/)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.