# Court Hearing Transcription (Degravação) with Speakers (`plum_spear/aztec-court-hearing-transcription`) Actor

Transcribe court hearings and legal audio (degravação de audiências) into a DOCX and TXT with speaker separation and timestamps per turn. High-precision Whisper for Portuguese. Pay per minute. Draft for review, not a certified transcript.

- **URL**: https://apify.com/plum_spear/aztec-court-hearing-transcription.md
- **Developed by:** [Roberto Kerber](https://apify.com/plum_spear) (community)
- **Categories:** AI, Automation, Other
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $25.00 / 1,000 audio minute, high precisions

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Court Hearing Transcription (Degravação) with Speakers

Turn hearing recordings and other legal audio into a **draft transcript with the speakers told apart**: a formatted **DOCX** (header, speaker, timestamp per turn), a **TXT**, and the same turns as JSON in the dataset. Built for degravação de audiências in Portuguese (Brazil and Portugal), also works in English. Default engine is Whisper large-v3-turbo, the one that handles Portuguese and noisy rooms well.

Built and maintained by **Az Digital Consulting LU**.

> **It produces a draft for review, not a certified transcript.** Whisper tidies speech instead of copying it word for word (hesitations and false starts are often dropped), so the result is not ipsis litteris. Check it against the audio and have a qualified person review it before any procedural use.

### What it does

- Takes **direct URLs to audio or video files** (mp3, wav, m4a, aac, flac, ogg, opus, mp4, webm, mkv, mov), one dataset item per file.
- Transcribes with **Whisper large-v3-turbo** (`quality: "high"`, default). If the high-precision engine fails for a block, that block is transcribed inside the Actor with a smaller Whisper model and the item says so.
- **Separates the speakers** (diarization) with the pyannote segmentation 3.0 model plus a speaker-embedding model, running on CPU inside the run. Speakers are numbered in order of first appearance (`Falante 1`, `Falante 2`...), or named by you (`Juiz`, `Advogado`, `Testemunha`).
- Gives every turn a **start time** and splits a Whisper phrase where the speaker changes (it works from word timestamps).
- Writes a **DOCX** (title, case number, court, hearing date, source file, duration, language, a speakers table with speaking time, then `[hh:mm:ss] Speaker: text` for every turn, with a review notice in the footer) and a **TXT**, both saved in the run's key-value store with a download link in each dataset item.
- Transcription runs in 10-minute blocks, but the speaker separation holds the whole decoded audio in memory (about 0.5 GB per hour), which is why the limit per file is 240 minutes.

### What it does not do

- No certification, no legal advice, no word-for-word (ipsis litteris) text.
- **Overlapping speech is not separated**: when two people talk at once, the words go to one speaker.
- Speaker boundaries are not perfect. A word at the very edge of a turn can land on the neighbouring speaker. When you know how many people speak, set `speakerCount`: it makes the separation more reliable.
- Very short appearances (a clerk saying one sentence in a two-hour hearing) can be merged into the nearest speaker when the number of speakers is detected automatically. Set `speakerCount` to keep them.
- No translation. No downloads from video and social platforms: YouTube, Instagram, TikTok, Facebook, X/Twitter, Twitch, Vimeo, Dailymotion, Spotify, SoundCloud, Reddit, LinkedIn, Snapchat, Pinterest, Netflix and their media CDNs are refused with `platform_not_supported`, and the check is repeated on every redirect. Live radio streams and files over 500 MB are refused, and each download must finish within 10 minutes.

You are responsible for having the right to transcribe the recordings you submit.

### Input

| Field | Type | Description |
|---|---|---|
| `mediaUrls` | array of strings | Direct links to media files. For a file you uploaded to an Apify key-value store, use that record's public URL (including its signature). |
| `language` | string | ISO 639-1 code: `pt` (default), `en`, or `auto`. |
| `quality` | string | `high` (default): Whisper large-v3-turbo on Groq, **the audio is sent to Groq (United States)**. `standard`: a small Whisper model inside the Actor, the audio never leaves the run, **accuracy in Portuguese is much lower**. |
| `speakerCount` | integer | Number of speakers, 1 to 10. Empty = detect automatically. |
| `speakerLabels` | array | Names or roles in order of first appearance, for example `["Juiz", "Advogado", "Testemunha"]`. |
| `caseNumber`, `court`, `hearingDate`, `documentTitle` | strings | Shown in the document header. The default title is "Degravação de audiência". Empty fields are left out. |
| `outputs` | array | `docx`, `txt`. Default both. |
| `maxDurationMinutes` | integer | Audio beyond this length is not transcribed or charged. Default 120, maximum 240. A file whose header already shows a longer duration is refused without charge. |

```json
{
  "mediaUrls": ["https://example.com/audiencia.mp4"],
  "language": "pt",
  "speakerCount": 3,
  "speakerLabels": ["Juiz", "Advogado", "Testemunha"],
  "caseNumber": "5001234-56.2024.8.13.0024",
  "court": "2ª Vara do Trabalho de Belo Horizonte",
  "hearingDate": "07/10/2026"
}
```

### Output

One dataset item per input URL:

```json
{
  "url": "https://example.com/audiencia.mp4",
  "status": "ok",
  "language": "pt",
  "durationSeconds": 3725.4,
  "billedMinutes": 63,
  "billedMinutesHigh": 63,
  "model": "whisper-large-v3-turbo",
  "quality": "high",
  "speakers": [{ "label": "Juiz", "turns": 41, "speakingSeconds": 612.3 }],
  "turns": [{ "n": 1, "speaker": "Juiz", "start": 2.3, "end": 14.1, "startTime": "00:00:02", "text": "Declaro aberta a audiência..." }],
  "text": "Juiz: Declaro aberta a audiência...\nAdvogado: ...",
  "files": { "docx": "https://api.apify.com/v2/key-value-stores/.../records/hearing-1.docx?signature=...", "txt": "..." },
  "diarization": { "method": "pyannote-segmentation-3.0 + NeMo TitaNet-small (CPU, onnx)", "speakersFound": 3, "speakersRequested": 3 }
}
```

`status` is `ok`, `partial` (stopped at `maxDurationMinutes` or at your maximum cost per run; `errorCode` and `error` say which) or `failed`. Items may carry `warnings`: `damaged_audio`, `diarization_failed` (the transcript is delivered with one speaker), `fewer_speakers_found` (you asked for more speakers than could be told apart), `postprocess_failed` (the transcript is delivered without documents).

Error codes of failed items: `platform_not_supported`, `invalid_url`, `not_media`, `http_error`, `unreachable`, `too_large`, `too_long`, `download_timeout`, `download_too_slow`, `live_stream`, `undecodable`, `no_audio`, `invalid_language`, `charge_limit`, `high_quality_unavailable`, `invalid_input` (also for an invalid `speakerCount`, `speakerLabels` or `outputs`) and `internal_error`. Details of each are in the README of the underlying transcriber, [Audio & Video Transcriber](https://apify.com/plum_spear/aztec-audio-video-transcriber). If no file in a run can be transcribed, the run status message names the first reason and the `OUTPUT` record of the key-value store lists the failures.

### Pricing

Pay per event:

- **US$ 0.025 per minute of audio** (event `audio-minute-high`, charged for blocks transcribed by the high-precision engine) and the same **US$ 0.025 per minute** for blocks transcribed inside the Actor (event `audio-minute`: `quality: "standard"`, or a block where the high-precision engine failed). Rounded up per file with 1 second of tolerance: a 63-minute hearing is billed as 63 minutes, US$ 1.58.
- A small Actor start fee that Apify applies to every pay-per-event Actor.
- Files that are refused, skipped or fail before any audio is transcribed are **not** charged. Minutes already transcribed are charged even if a later internal error stops the file (delivered as `partial`).

Billing follows the audio actually decoded and transcribed, not the duration in the file header. Each block is charged after it is transcribed, so `maxDurationMinutes` and the maximum cost per run you set in Apify both stop a file at the limit and only the audio up to the limit is charged. For reference, human degravação services in Brazil advertise from R$ 2.00 to R$ 3.20 per minute (checked 07/10/2026): this price is well under a tenth of that.

If the platform restarts the run, files already finished are not repeated and minutes already charged for a file are not charged again.

### Accuracy

**Transcription** (word error rate, lower is better; same engine as [Audio & Video Transcriber](https://apify.com/plum_spear/aztec-audio-video-transcriber)). Portuguese read sentences from the public FLEURS set, clean: 3.9%; the same with noise at 5 dB: 11.1%; a 20-minute Portuguese audiobook: 9.6%. The in-container model (`standard`) scored 13.4%, 49.6% and 29.5% on the same files. These are read speech, a favourable case. **We did not measure real courtroom audio** (microphones far from the speakers, reverberation, interruptions): expect higher error rates and review every line.

**Speaker separation**, share of words attributed to the right speaker, automatic speaker count. With an exact answer key (text-to-speech voices, the turns were generated):

| Audio | Speakers | Words attributed correctly |
|---|---|---|
| Synthetic hearing, 3 voices, 91 s | 3 | 93.8% |
| The same, degraded (telephone band, echo, noise) | 3 | 96.6% |
| Synthetic interview, 2 voices | 2 | 100% |
| The same, degraded | 2 | 99.4% |

Real interviews from Wikimedia Commons, 7 minutes each. They have **no official answer key**: the reference was built from the detected turns after reading them and checking that questions and answers alternate, so these two numbers are softer evidence:

| Audio | Speakers | Words attributed correctly |
|---|---|---|
| 2 women (CC BY-SA) | 2 | 97.6% |
| 1 woman and 1 man (CC0) | 2 | 96.4% |

The synthetic audio is easy to separate and is not a courtroom: it exists to give an exact answer. **Real hearings with 4 or more people, overlaps and distant microphones are harder than any of these.** Treat the separation as a strong first draft and fix the labels when needed.

Speed: a 91-second file took 23 seconds in the cloud and a 70-minute file about 18 minutes (measured on the sibling interview Actor, anonymization included: container start, transcription, speaker separation and documents). High precision depends on Groq's capacity: when Groq refuses a block (for example a rate limit), that block is transcribed on this Actor's CPU instead, which is slower, much less accurate in Portuguese and shown in the item (`fallbackBlocks`, `fallbackReason`).

### Call it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/plum_spear~aztec-court-hearing-transcription/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mediaUrls": ["https://example.com/audiencia.mp3"], "speakerCount": 3, "speakerLabels": ["Juiz", "Advogado", "Testemunha"]}'
```

Limit the spend of a single run with `maxTotalChargeUsd`:

```bash
curl -X POST "https://api.apify.com/v2/acts/plum_spear~aztec-court-hearing-transcription/runs?token=$APIFY_TOKEN&maxTotalChargeUsd=2" \
  -H "Content-Type: application/json" \
  -d '{"mediaUrls": ["https://example.com/audiencia.mp3"]}'
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("plum_spear/aztec-court-hearing-transcription").call(
    run_input={"mediaUrls": ["https://example.com/audiencia.mp3"], "speakerCount": 2}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["status"], item.get("files"), item.get("error"))
```

Apify exposes the Actor to AI agents through the Apify MCP server: add `?tools=plum_spear/aztec-court-hearing-transcription` to `https://mcp.apify.com` and authenticate with your Apify token. Set `maxDurationMinutes` and a maximum cost per run so an agent cannot start an unexpectedly large job.

### Privacy

**Hearings can be confidential or under judicial secrecy. Read this before you use `quality: "high"`.**

- With `quality: "high"` (default) the audio, in blocks of up to 10 minutes, is sent over HTTPS to the Groq API (United States) and transcribed there. Groq's terms apply and we do not control them. **Do not use `high` for audio you are not allowed to share with a third party.** Blocks that fall back to the in-container model stay inside the run.
- With `quality: "standard"` nothing is sent to an external transcription service, and the speaker separation always runs inside the container. The cost is a much less accurate transcript in Portuguese.
- Only public network addresses are fetched; files are downloaded to the run's temporary storage and deleted when the file is done. The URL you submit is echoed in the output.
- The DOCX and TXT are stored in the run's key-value store. The links in the dataset carry a signature: **anyone who has the link can read the file.** Keep the links private, or delete the key-value store when you are done. While a file is being processed, a record `PARTIAL-<file number>` holds the transcript so far; it is removed when the file finishes.

### Em português

**O que é:** envie o link de um arquivo de áudio ou vídeo da audiência e receba uma **minuta de degravação** com os falantes separados: um DOCX formatado (cabeçalho com processo, órgão e data; quadro de falantes com tempo de fala; cada intervenção com horário e nome), um TXT e o mesmo conteúdo em JSON.

**Como usar:** informe `mediaUrls` (link direto para o arquivo, não para uma página), `language: "pt"`, e se souber quantas pessoas falam, `speakerCount`. Em `speakerLabels` coloque os nomes na ordem em que cada um fala pela primeira vez, por exemplo Juiz, Advogado, Testemunha. Os links do DOCX e do TXT vêm em `files` de cada item.

**Atenção:**

- É uma **minuta para revisão**, não uma degravação certificada nem literal (ipsis litteris): o Whisper costuma omitir hesitações e repetições. Confira com o áudio antes de qualquer uso no processo.
- Falas sobrepostas não são separadas, e a palavra na fronteira entre dois falantes pode ir para o vizinho. Informar `speakerCount` melhora a separação.
- Com `quality: "high"` (padrão) **o áudio é enviado à Groq, nos Estados Unidos**. Não use em audiência sigilosa ou em segredo de justiça sem autorização. Com `quality: "standard"` o áudio não sai da execução, mas a precisão em português é bem menor.
- Preço: US$ 0,025 por minuto de áudio (uma audiência de 1 hora custa US$ 1,50). Serviços humanos de degravação anunciam de R$ 2,00 a R$ 3,20 por minuto (consultado em 07/10/2026).
- Não medimos áudio real de sala de audiência, só entrevistas reais e áudio sintético. Espere mais erros em salas com eco, microfone distante e interrupções.

### Third-party components

Speaker separation uses the pyannote segmentation 3.0 model (MIT license) and the NVIDIA NeMo TitaNet-small speaker model (CC BY 4.0), run through sherpa-onnx (Apache 2.0). Transcription uses Whisper through faster-whisper and, in high-precision mode, the Groq API.

# Actor input Schema

## `mediaUrls` (type: `array`):

Direct links to audio or video files (mp3, wav, m4a, aac, flac, ogg, opus, mp4, webm, mkv, mov). Each link must return the file itself, not a web page. For a file you uploaded to an Apify key-value store, use the record's public URL (with its signature). YouTube, Instagram, TikTok and similar platforms are refused.

## `language` (type: `string`):

ISO 639-1 code of the spoken language. 'pt' for Portuguese (Brazil and Portugal), 'en' for English. Use 'auto' to detect it.

## `quality` (type: `string`):

'high' (default) transcribes with Whisper large-v3-turbo on Groq: far more accurate than the in-container model, especially in Portuguese and in noisy audio. WITH 'high' THE AUDIO IS SENT TO GROQ (United States). 'standard' runs a smaller Whisper model inside this Actor, the audio never leaves the run, and the accuracy is clearly lower. If Groq is unavailable for a block, that block is transcribed in the container and billed at the standard price.

## `speakerCount` (type: `integer`):

If you know how many people speak, set it (1 to 10): it makes the speaker separation more reliable. Leave empty to detect it automatically.

## `speakerLabels` (type: `array`):

Names for the speakers in the order they first speak, for example Juiz, Advogado, Testemunha. Speakers without a name are called Falante 3, Falante 4 and so on.

## `caseNumber` (type: `string`):

Shown in the header of the DOCX and TXT.

## `court` (type: `string`):

Court or panel, shown in the header.

## `hearingDate` (type: `string`):

Free text, shown in the header (for example 07/10/2026).

## `documentTitle` (type: `string`):

Replaces the default title 'Degravação de audiência'.

## `outputs` (type: `array`):

Files saved in the key-value store of the run, with a download link in each dataset item. The dataset item always carries the full transcript as JSON.

## `maxDurationMinutes` (type: `integer`):

Audio beyond this length is not transcribed or charged. Files whose header already shows a longer duration are refused without charge.

## Actor input object example

```json
{
  "mediaUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/5/55/Entrevista_Isabel.ogg"
  ],
  "language": "pt",
  "quality": "high",
  "speakerLabels": [
    "Juiz",
    "Advogado",
    "Testemunha"
  ],
  "outputs": [
    "docx",
    "txt"
  ],
  "maxDurationMinutes": 120
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `documents` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mediaUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/5/55/Entrevista_Isabel.ogg"
    ],
    "speakerLabels": [
        "Juiz",
        "Advogado",
        "Testemunha"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("plum_spear/aztec-court-hearing-transcription").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mediaUrls": ["https://upload.wikimedia.org/wikipedia/commons/5/55/Entrevista_Isabel.ogg"],
    "speakerLabels": [
        "Juiz",
        "Advogado",
        "Testemunha",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("plum_spear/aztec-court-hearing-transcription").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mediaUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/5/55/Entrevista_Isabel.ogg"
  ],
  "speakerLabels": [
    "Juiz",
    "Advogado",
    "Testemunha"
  ]
}' |
apify call plum_spear/aztec-court-hearing-transcription --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,plum_spear/aztec-court-hearing-transcription"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/iHEcfvRj8nlhdwHgz/builds/SJuE24li8e0q4hhTI/openapi.json
