Transcribe

The transcribe subcommand provides automated Speech-to-Text (STT) transcription of call recordings. It watches a directory for audio files, coordinates work allocation with QueueMetrics via its leaderboard, sends audio to a configured transcription service, and uploads the resulting transcripts back to QueueMetrics.

For ease of use, the default ./install.sh script in Uniloader will set up a systemd unit called uniloader-transcribe that is ready to be enabled. It can be fully configured in its

Overview

Uniloader acts as a worker agent in a distributed transcription pipeline:

  1. QueueMetrics tracks which recordings have already been transcribed and manages channel reservations.

  2. One or more Uniloader instances, each using their own separate login, watch a shared audio directory and advertise themselves to QueueMetrics.

  3. QueueMetrics assigns a transcription channel and service credentials to each worker.

  4. The worker sends the audio file to the transcription API, polls for results, and uploads the transcript to QueueMetrics.

Multiple Uniloader instances can run in parallel against the same QueueMetrics system — QueueMetrics ensures each file is assigned to only one worker.

Setting up transcription users in QM

On the QueueMetrics side, you can easily create new login credentials for Uniloader processes running transcribers:

  • user class ROBOT

  • custom keys: CHAT_WRITE

  • enabled: Yes

In recently installed systems, a ready-made transcriber user is ready to be used - just set a password and enable it for logins.

Each Uniloader instance must use their own unique QueueMetrics login. If two instances share the same login, QueueMetrics cannot distinguish them and will mismanage their channel reservations, leading to duplicate or missed transcriptions. As you need one per server, if you have multiple servers A and B, we suggest naming them transcriber-a and transcriber-b.

Transcription Servers and Capacity

Transcription servers are configured entirely within QueueMetrics. Each one points to a transcription service (local or remote), carries its credentials and configuration parameters, and has a maximum parallel capacity — the number of files that service can process simultaneously.

QueueMetrics can hold multiple transcription servers, each with a configurable priority. When a worker requests a channel reservation, QueueMetrics assigns from the highest-priority server that still has capacity. Lower-priority servers act as overflow: they are only used when higher-priority ones are full. This lets you combine a fast local server for normal load with a cloud API that absorbs peaks.

Servers, their capacities, and their priorities can be added, removed, or changed in QueueMetrics at any time without restarting Uniloader. Changes take effect immediately.

Drivers

A driver is the transcription backend Uniloader routes audio to. The driver to use is assigned by QueueMetrics at reservation time; you do not configure it locally. It supports a specific backend’s API and carries credentials and any configuration parameters set on the QueueMetrics side.

openai-tts — OpenAI Transcription

Sends audio to the official OpenAI transcription API at api.openai.com. The API key and model are configured on the QueueMetrics side; Uniloader receives them as part of the channel reservation.

For supported audio formats, language codes, and model options, refer to the OpenAI Speech-to-Text documentation.

While the model will auto-detect the language used, it is a good idea to set it explicitly if it is known in advance.

openai-api-tts — OpenAI-Compatible API

Sends audio to any API that implements the OpenAI transcription endpoint, such as a local model running with whisper.cpp or a self-hosted proxy. The OpenAI transcription endpoint has become a de-facto standard for STT and diarization, so most local services and many STT providers implement it.

The base URL, API key, and model are configured on the QueueMetrics side. Audio format support depends on the specific backend in use.

In most cases, you will need a model that supports both transcription (speech-to-text) and diarization (speaker identification).

deepgram-tts — Deepgram

Sends audio to the Deepgram cloud API at api.deepgram.com. The API key is configured on the QueueMetrics side and sent as an Authorization: Token header.

Transcription options travel in the request URL. Uniloader defaults to English with paragraph segmentation, and adds Deepgram’s diarization model when the task tag is TD. Any parameter set on the QueueMetrics side overrides these defaults, so you can select a different model or language without changing anything locally.

For the available options, refer to the Deepgram documentation.

krokoai-tts — Kroko-AI

Sends audio to a self-hosted Kroko-AI server over a WebSocket connection. Only the server URL is configured on the QueueMetrics side, in the form ws://host:port; this driver uses no API key.

Kroko-AI transcribes but does not label speakers, so it accepts the TR task tag only. To tell agent and customer apart with this driver, use a stereo recording together with --stereo-agent, which transcribes each channel separately.

The Kroko-AI server only accepts 16 kHz mono audio. Uniloader converts every file with ffmpeg before sending it, so ffmpeg must be available on the PATH when this driver is in use.

Kroko-AI returns word-level timings rather than usable sentences. Uniloader rebuilds the segments from the individual words, starting a new segment after a sentence ends and the speaker pauses for at least two seconds.

Task Tags

A task tag tells QueueMetrics what type of work this worker can handle. You configure this with --task-tags.

Tag Meaning

TD

Transcribe + Diarize — STT with automatic speaker labelling

TR

Transcribe only — STT without speaker detection

Only one tag may be active at a time. The tag must match what QueueMetrics expects for the configured transcription channels.

You can get a list of supported drivers and tags through uniloader test transcribe.

Running the Command

uniloader transcribe [flags]

Required Flags

Flag Type Description

--qm-url

string

Base URL of the QueueMetrics instance (e.g. https://qm.example.com)

--qm-login

string

QueueMetrics username (see Creating users).

--watch-dir

string

Directory to watch recursively for audio files

The password can be provided via --qm-password or the QM_PASSWORD environment variable (preferred, to avoid exposing credentials in shell history or process listings).

Optional Flags

Flag Type Default Description

--qm-password

string

—

The password for the user defined in qm-login. You could also set it in the QM_PASSWORD environment variable.

--extensions

string

.wav,.mp3,.raw

Comma-separated audio file extensions to process

--workers

int

2

Number of parallel transcription workers

--batch-size

int

500

Files per QueueMetrics existence-check batch (max 1000)

--retry-delay

int

10

Seconds between reservation retries when all channels are busy

--poll-interval

int

5

Seconds between driver status polls

--quiet-period

int

20

Seconds a file’s modification time must be stable before processing; 0 disables

--task-tags

string

td

Task tag to advertise to QueueMetrics (td or tr)

--skip-prc

int

0

Percentage of reserved files to skip without transcribing (0–99; 0 disables)

--http-log

string

—

File path for a full HTTP request/response trace log

--stereo-agent

string

—

Stereo channel assignment: L (agent on left) or R (agent on right)

Headerless recordings (.raw)

Asterisk can write its recordings as .raw files: raw samples, with no header saying how they should be read. Uniloader processes them by default, and assumes what Asterisk writes for a stereo recording: signed 16-bit little-endian samples, 8 kHz, two channels.

This is achieved in the dial-plan using the following command:

...
exten => 551,n,MixMonitor(stereo_recording_${UNIQUEID}.raw,Db)
...

The option D in MixMonitor writes a stereo file without headers, where left and right frames are interleaved; b starts recording only when there is a bridged channel (that is, when both caller and agent are available).

Uniloader can process those files natively. When the two legs are split with --stereo-agent, a single ffmpeg run reads the file and separates the channels; without it, the recording is downmixed to mono. Either way the audio is then handed to the driver in the format it expects.

A .raw file recorded at a different rate, such as 16 kHz slin16, is read at the wrong speed, and the transcription will be poor. Record at 8 kHz, or convert those files to WAV before Uniloader sees them.

Sizing --workers

The number of workers should be in the same order of magnitude as the total parallel channel capacity configured in QueueMetrics. Too few workers leave channels idle; too many workers spend most of their time waiting for a free channel.

As a starting point, set --workers equal to the total channel capacity across all transcription servers. If Uniloader is distributed across multiple hosts, divide that number among them.

Minimal Example

uniloader transcribe \
  --qm-url "https://qm.example.com" \
  --qm-login "admin" \
  --watch-dir "/var/spool/asterisk/monitor"

Full Example

uniloader transcribe \
  --qm-url "https://qm.example.com" \
  --qm-login "admin" \
  --watch-dir "/var/spool/asterisk/monitor" \
  --extensions ".wav,.mp3" \
  --workers 4 \
  --quiet-period 30 \
  --task-tags "tr" \
  --http-log "/var/log/uniloader-http.log"

How It Works

Diagram

The two Uniloader instances watch their local audio directories independently and compete for transcription capacity via QueueMetrics. QueueMetrics manages the transcription server pool and assigns channels; Uniloader sends audio directly to the assigned server and uploads the resulting transcript back to QueueMetrics.

File Discovery

At startup, Uniloader walks --watch-dir recursively and queries QueueMetrics for which files have not yet been transcribed. The resulting backlog is sorted newest-first by modification time, so recent recordings are processed before older ones.

After the initial walk, a filesystem watcher monitors the directory tree for new files. Newly detected files are placed at the front of the queue and processed before backlog items.

Quiet Period

To avoid processing files that are still being written (e.g. by a live recorder), files are held in quarantine until their modification time has been stable for --quiet-period seconds. Set this to 0 if mtime updates are unreliable on your storage (e.g. some SMB/NFS configurations).

Worker Loop

Each worker:

  1. Takes a file from the queue.

  2. Requests a channel reservation from QueueMetrics. If all channels are busy, it waits --retry-delay seconds and retries.

  3. Submits the audio file to the assigned transcription driver.

  4. Polls the driver every --poll-interval seconds. Sends a heartbeat to QueueMetrics every 30 seconds to keep the reservation alive.

  5. On completion, uploads the transcript to QueueMetrics and releases the reservation.

Transient errors (network failures, server errors) are retried up to 3 times. Permanent errors (unsupported file format, bad audio) are reported to QueueMetrics without retry.

Transcription Result

Each uploaded transcript contains:

  • Speaker list — speakers in order of first appearance (SPEAKER_00, SPEAKER_01, …)

  • Segments — each with speaker label, start time (ms), transcribed text, and confidence score

  • Language — detected language code (e.g. en)

  • Driver ID — identifies which service and channel processed the file

Stereo Agent Mode

For stereo recordings where one channel is the agent and the other is the customer, use --stereo-agent together with --task-tags tr.

uniloader transcribe \
  --qm-url "https://qm.example.com" \
  --qm-login "admin" \
  --watch-dir "/var/audio" \
  --task-tags "tr" \
  --stereo-agent L

--stereo-agent L means the agent’s voice is on the left channel; R means the right channel.

ffmpeg must be available on the system PATH.

How it works:

  1. The stereo file is split into two mono WAV files via ffmpeg.

  2. Each mono file is transcribed separately (no diarization within a channel).

  3. The results are merged and re-sorted by timestamp.

  4. The output transcript has exactly two speakers: Agent and Customer.

This approach produces cleaner speaker separation than diarization on a mixed mono track.

--stereo-agent requires --task-tags tr. The two flags are validated together at startup.

HTTP Trace Log

--http-log <file> writes a detailed trace of every HTTP request and response to the specified file. This is useful for debugging connectivity problems, inspecting transcription API responses, or measuring service latency.

Each exchange is written as a block:

----- w1-7: transcribe -- 2026-04-30 14:26:24 -> 2026-04-30 14:26:25 -------
POST https://api.openai.com/v1/audio/transcriptions multipart: file=call.wav (245632 bytes), ...

Status: 200 - took 1234 ms
{"language":"en","segments":[...]}

The caller identity (w1-7) encodes the worker number and attempt count. Other identities include engine (startup checks) and operation names such as reserve, heartbeat, and upload for QueueMetrics interactions.

Sampling (--skip-prc)

To reduce transcription costs when processing high call volumes, --skip-prc N causes N% of reserved files to be skipped without transcription. Skipped files are reported to QueueMetrics with status skip.

For example, --skip-prc 20 transcribes roughly 80% of files, skipping the rest at random.