diff --git a/authors/assets/goodguypeci-design.png b/authors/assets/goodguypeci-design.png new file mode 100644 index 00000000..ec7ae083 Binary files /dev/null and b/authors/assets/goodguypeci-design.png differ diff --git a/authors/petar-ivanov.md b/authors/petar-ivanov.md new file mode 100644 index 00000000..fb3e2faf --- /dev/null +++ b/authors/petar-ivanov.md @@ -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 diff --git a/definitions/20260908_definition_speech_to_text_api.md b/definitions/20260908_definition_speech_to_text_api.md new file mode 100644 index 00000000..32825cb7 --- /dev/null +++ b/definitions/20260908_definition_speech_to_text_api.md @@ -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. diff --git a/guides/20260908_azure_ai_speech_transcription_with_sapat.md b/guides/20260908_azure_ai_speech_transcription_with_sapat.md new file mode 100644 index 00000000..8edd520d --- /dev/null +++ b/guides/20260908_azure_ai_speech_transcription_with_sapat.md @@ -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/) diff --git a/guides/assets/20260908_azure_ai_speech_sapat_daytona_img1.png b/guides/assets/20260908_azure_ai_speech_sapat_daytona_img1.png new file mode 100644 index 00000000..77ebd667 Binary files /dev/null and b/guides/assets/20260908_azure_ai_speech_sapat_daytona_img1.png differ