SDKs

Python

Build Batch, fast, realtime, diarization, and TTS workflows with humain-voice 0.18.0.

This guide targets exactly humain-voice==0.18.0 on Python 3.10 or newer. Its displayed programs are parsed and type-checked against the python/v0.18.0 release tag.

Install and configure

Install the pinned package in your server-side environment or virtual environment:

python -m pip install humain-voice==0.18.0

Set the values issued for your environment:

export API_URL="https://api.voice.humain.com"
export API_KEY="YOUR_API_KEY"
export API_VERSION="v1"

Import new code through humain_voice. The historical sautech namespace is a deprecated compatibility import in 0.18.0 and emits a warning.

Batch uses API_URL, API_KEY, and API_VERSION. Socket.IO clients require only api_url and api_key; api_path defaults to /socket.io. Pass a path only for a deployment override; the legacy sautech.humain.com endpoint requires /realtime/socket.io.

Every Python client supports async cleanup plus async and synchronous context managers. Method coverage is not symmetrical: use only the sync methods named for each client below. There are no public connect_sync() or disconnect_sync() methods.

First task: transcribe a complete recording

Start with BatchTranscribeClient when the complete recording already exists, especially for a long meeting, podcast, interview, or archive file. Save this tested source as batch_transcription.py beside an input recording:

batch_transcription.py
from __future__ import annotations

import asyncio
import os
import sys
from pathlib import Path

from humain_voice import stt
from humain_voice.stt.batchtranscription import BatchDiarization


def required_env(name: str) -> str:
    value = os.environ.get(name)
    if not value:
        raise RuntimeError(f"{name} is required")
    return value


async def main() -> None:
    input_path = Path(sys.argv[1] if len(sys.argv) > 1 else "meeting.wav")
    output_path = Path(sys.argv[2] if len(sys.argv) > 2 else "meeting.vtt")

    async with stt.BatchTranscribeClient(
        api_url=required_env("API_URL"),
        api_key=required_env("API_KEY"),
        api_version=os.environ.get("API_VERSION", "v1"),
    ) as client:
        result = await client.transcribe(
            input_path,
            lang=stt.Language.ArEn,
            asr=stt.BatchTranscriptionModel.BayanArEn,
            diarization=BatchDiarization.On,
            save_result=True,
            poll_interval=2.0,
            timeout_seconds=300.0,
            on_progress=lambda response: print("status:", response.status.value),
        )

    print(result.results.transcript if result.results else "")
    output_path.write_text(
        stt.Subtitles.from_response(result).to_vtt(),
        encoding="utf-8",
    )


if __name__ == "__main__":
    asyncio.run(main())

Run it with an input file and output path:

python batch_transcription.py meeting.wav meeting.vtt

A successful run reports job progress, prints the final transcript, and writes finalized WebVTT cues to meeting.vtt.

Released Batch surface

Surface0.18.0 contract
ConstructorBatchTranscribeClient(api_url, api_key, max_retries=0, api_version="v1")
Async methodssubmit(), get_result(), transcribe(), close()
Sync methodssubmit_sync(), get_result_sync(), transcribe_sync(), close_sync()
Options and defaultssubmit: diarization, asr, itn, redact; get_result: save_result; transcribe: those options plus poll_interval=2, timeout_seconds=300, on_progress, and save_result. max_retries is ignored.
InputBatch AudioInput accepts bytes-like values, Path or string paths, buffered readers, and BytesIO.

transcribe() succeeds on done, raises on failed, and keeps polling queued, processing, or cleared until its deadline. If your worker owns the polling loop, stop explicitly on all three terminal states: done, failed, and cleared.

The default save_result=False can clear a done or failed result after the response is built. Pass save_result=True before polling when terminal delivery must survive a lost response. This does not establish a result retention duration.

Use the client as an async or regular context manager. Context exit closes the internal aiohttp session. Batch result and subtitle types are indexed in the reference sections below.

Choose another task

Input and goalClientCompletion signal
Complete recording, long meeting, podcast, interview, or archive mediaBatchTranscribeClientJob reaches done, failed, or cleared
Complete latency-sensitive audio unit, such as one conversational turn for an AI agentFastTranscriptionClientFinal response has is_final=True
Audio still arriving from a microphone, call, or live sourceRealtimeClientProtocol response has is_final=True
Live speaker segmentationRealtimeDiarizationClientFinal update arrives or close returns the best-known timeline
Text to synthesized speechTTSClientAudio response has is_last=True

Fast transcription is not the path for long meetings, podcasts, or archive media. Use Batch for those complete, longer recordings.

Fast task: transcribe one complete conversational unit

Use FastTranscriptionClient after a bounded, latency-sensitive audio unit is complete, such as one user turn in an AI-agent conversation. It sends the whole unit over Socket.IO; it does not accept an open-ended microphone stream.

Released Fast surface

Surface0.18.0 contract
ConstructorFastTranscriptionClient(api_url, api_key, api_path=None, on_connect?, on_file_upload?, on_error?, verbose=False); the pre-0.17 (api_url, api_path, api_key, ...) order still works with a deprecation warning
Async methodsconnect(), transcribe(), close()
Sync methodstranscribe_sync(), close_sync(); there is no connect_sync()
Options and defaultstranscribe(audio, language, model, …) accepts on_response, on_file_upload, on_error, timeout_seconds=60, diarization_model, itn_model, and redact_model.
Inputbytes or a buffered reader; a path must be opened or read first.

This tested program reports upload progress, distinguishes partial and final text, requires a final result, and writes SRT:

fast_transcription.py
from __future__ import annotations

import asyncio
import os
import sys
from pathlib import Path

from humain_voice import stt


def required_env(name: str) -> str:
    value = os.environ.get(name)
    if not value:
        raise RuntimeError(f"{name} is required")
    return value


def handle_response(response: stt.FtTranscribeResponse) -> None:
    kind = "final" if response.is_final else "partial"
    print(f"{kind}:", response.transcription)


def handle_upload(response: stt.FileUploadedResponse) -> None:
    print("uploaded:", response.id)


def handle_error(error: stt.ErrorResponse | None) -> None:
    if error is not None:
        print("server error:", error.code, error.message)


async def main() -> None:
    input_path = Path(sys.argv[1] if len(sys.argv) > 1 else "short-call.wav")
    output_path = Path(sys.argv[2] if len(sys.argv) > 2 else "short-call.srt")

    async with stt.FastTranscriptionClient(
        api_url=required_env("API_URL"),
        api_path=required_env("API_PATH"),
        api_key=required_env("API_KEY"),
    ) as client:
        result = await client.transcribe(
            input_path.read_bytes(),
            stt.Language.Ar,
            stt.FastTranscriptionModel.BayanAr,
            on_response=handle_response,
            on_file_upload=handle_upload,
            on_error=handle_error,
            timeout_seconds=60.0,
        )

    if result is None:
        raise RuntimeError("Fast transcription ended without a final result")
    output_path.write_text(
        stt.Subtitles.from_response(result).to_srt(),
        encoding="utf-8",
    )


if __name__ == "__main__":
    asyncio.run(main())

FileUploadedResponse identifies the upload. Partial and final callbacks receive FtTranscribeResponse; the returned final response has is_final=True and can produce subtitles. Python defaults to a 60-second request timeout.

SDK 0.18.0 accepts diarization_model, itn_model, and redact_model for wire compatibility, but the validated public Fast service does not apply them. Omit them; use Batch when those processing options are required.

After an ambiguous timeout, do not blindly resubmit the audio unit: no idempotency-key contract is published. Use the client context manager so the Socket.IO and underlying HTTP resources close on every path.

Realtime task: transcribe audio as it arrives

Use RealtimeClient for microphone, call, or other audio that is still arriving. Input must be PCM16 little-endian, 16 kHz, mono.

Released Realtime surface

Surface0.18.0 contract
ConstructorRealtimeClient(api_url, api_key, api_path=None, verbose=False)
Async methodsconnect(), start_stream(), disconnect()
Sync methodsstart_stream_sync(); stream send_sync(), close_sync(), and stop_sync() are public, but connect_sync() and disconnect_sync() are not
Start optionslanguage, on_connect, on_disconnect, on_response, on_error, and subtitles
Streamsend() / send_sync(); close(timeout_seconds=1) / close_sync() sends the terminator and waits; stop() / stop_sync() removes the stream without that final wait.

This program sends 3,200-byte chunks, representing 100 ms of the required audio, and closes the client through its async context manager:

realtime_transcription.py
from __future__ import annotations

import asyncio
import os
import sys
from pathlib import Path

from humain_voice import stt


CHUNK_BYTES = 3_200  # 100 ms of PCM16LE, 16 kHz, mono audio.


def required_env(name: str) -> str:
    value = os.environ.get(name)
    if not value:
        raise RuntimeError(f"{name} is required")
    return value


async def main() -> None:
    input_path = Path(sys.argv[1] if len(sys.argv) > 1 else "speech.pcm")
    output_path = Path(sys.argv[2] if len(sys.argv) > 2 else "speech.vtt")
    finalized_words: list[stt.WordSegment] = []
    server_error: stt.ErrorResponse | None = None
    protocol_final_observed = False

    def handle_response(response: stt.RtTranscribeResponse) -> None:
        nonlocal protocol_final_observed
        if response.is_final:
            kind = "final"
        elif response.is_speech_final:
            kind = "speech-final"
        else:
            kind = "partial"
        print(f"{kind}:", response.transcription)
        if response.is_final:
            protocol_final_observed = True
        if response.is_final or response.is_speech_final:
            # The current public Realtime contract does not guarantee increasing
            # seq values, so collect final words in arrival order instead of
            # asking RealtimeSubtitles to deduplicate by id:seq.
            finalized_words.extend(response.words)

    def handle_error(error: stt.ErrorResponse | None) -> None:
        # The released SDK can invoke a stream handler more than once for one
        # routed error, so keep this callback idempotent.
        nonlocal server_error
        server_error = error

    client = stt.RealtimeClient(
        api_url=required_env("API_URL"),
        api_path=required_env("API_PATH"),
        api_key=required_env("API_KEY"),
    )
    async with client:
        stream = await client.start_stream(
            language=stt.Language.ArEn,
            on_response=handle_response,
            on_error=handle_error,
        )
        pcm = input_path.read_bytes()
        for offset in range(0, len(pcm), CHUNK_BYTES):
            await stream.send(pcm[offset : offset + CHUNK_BYTES])
            await asyncio.sleep(0.1)


        # close() sends the last frame and waits for protocol is_final, a routed
        # error, or this timeout. It returns rather than raising on timeout.
        await stream.close(timeout_seconds=5.0)

    if server_error is not None:
        raise RuntimeError(server_error.message or server_error.code or "Realtime stream failed")
    if not protocol_final_observed:
        raise RuntimeError("Realtime stream ended before protocol is_final")
    output_path.write_text(
        stt.Subtitles.from_words(finalized_words).to_vtt(),
        encoding="utf-8",
    )


if __name__ == "__main__":
    asyncio.run(main())

RtTranscribeResponse adds is_speech_final to the Fast response fields. Treat is_speech_final as an utterance boundary, replace provisional UI text, and keep the stream open until protocol-level is_final.

stream.close(timeout_seconds=...) sends the final frame and returns when its protocol-final wait expires. Return does not guarantee that is_final arrived; is_speech_final does not release the wait. The fixture collects final-event words in arrival order and renders them with Subtitles; it does not rely on seq, whose ordering and uniqueness are not part of the current public wire contract. RealtimeSubtitles deduplicates by id:seq and can collapse distinct final events. The client context remains required because a routed error can remove the stream context before close runs.

Diarization task: track speakers live

Use RealtimeDiarizationClient when the app needs a speaker timeline while audio is arriving. Feed and consume concurrently; waiting to consume until all audio has been sent can stall the workflow.

Released diarization surface

Surface0.18.0 contract
ConstructorRealtimeDiarizationClient(api_url, api_key, api_path=None, verbose=False)
Async methodsconnect(), start_stream(), disconnect()
Sync methodsstart_stream_sync(); stream send_sync() and close_sync() are public, but connect_sync() and disconnect_sync() are not
Start optionslanguage=Language.Ar, plus connection, update, and error callbacks
Streamstream_id, speakers, send() / send_sync(), close(timeout_seconds=5) / close_sync(), async context management, and one async iterator. Iterator failure raises DiarizationStreamError.

DIARIZATION_RECOMMENDED_CHUNK_BYTES is imported from humain_voice.stt.constants, not the top-level stt namespace. This program receives updates through on_update while it feeds audio, so a close timeout cannot leave an iterator waiting:

realtime_diarization.py
from __future__ import annotations

import asyncio
import os
import sys
from pathlib import Path

from humain_voice import stt
from humain_voice.stt.constants import DIARIZATION_RECOMMENDED_CHUNK_BYTES


def required_env(name: str) -> str:
    value = os.environ.get(name)
    if not value:
        raise RuntimeError(f"{name} is required")
    return value


async def main() -> None:
    input_path = Path(sys.argv[1] if len(sys.argv) > 1 else "meeting.pcm")
    output_path = Path(sys.argv[2] if len(sys.argv) > 2 else "meeting.rttm")
    client = stt.RealtimeDiarizationClient(
        api_url=required_env("API_URL"),
        api_path=required_env("API_PATH"),
        api_key=required_env("API_KEY"),
    )

    async with client:
        final_observed = False

        def on_update(update: stt.DiarizationUpdate) -> None:
            nonlocal final_observed
            final_observed = final_observed or update.is_final
            for segment in update.newly_finalized:
                print(segment.speaker, segment.start_time, segment.end_time)

        stream = await client.start_stream(
            on_update=on_update,
            on_error=lambda error: print("server error:", error),
        )
        pcm = input_path.read_bytes()
        if not pcm or len(pcm) % 2:
            raise ValueError("Input must be nonempty PCM16 with an even byte length")
        for offset in range(0, len(pcm), DIARIZATION_RECOMMENDED_CHUNK_BYTES):
            await stream.send(
                pcm[offset : offset + DIARIZATION_RECOMMENDED_CHUNK_BYTES]
            )
            await asyncio.sleep(0.48)

        # close() returns the best-known reconciled timeline after five seconds,
        # even when no is_final update arrived. A callback avoids leaving an async
        # iterator waiting forever on that timeout path.
        timeline = await stream.close(timeout_seconds=5.0)

    destination = output_path if final_observed else Path(f"{output_path}.partial")
    destination.write_text(stt.to_rttm(timeline, uri="meeting"), encoding="utf-8")
    if not final_observed:
        print(f"Final result not observed; wrote incomplete output to {destination}")


if __name__ == "__main__":
    asyncio.run(main())

Each DiarizationUpdate exposes the full reconciled segments timeline, newly_finalized, active_segments, and the raw response. Close waits up to 5 seconds for a final update and returns the best-known timeline when that wait expires. The fixture writes a .partial RTTM file unless is_final was observed. Context exit still owns client cleanup.

TTS task: write a playable WAV file

Use TTSClient to discover a voice and synthesize raw audio. Python voice listing and synthesis have no timeout unless you supply timeout_seconds. list_voices() returns multilingual { id, label, profile } dictionaries. The profile contains shared speaker metadata and an open-ended languages list; pass its id as voice_id.

For the current Arabic/English profiles, any Arabic-script letter in text selects Arabic; otherwise English is selected. Physical variant IDs are internal and rejected.

Released TTS surface

Surface0.18.0 contract
ConstructorTTSClient(api_url, api_key, api_path=None, verbose=False, on_connect?, on_error?); the pre-0.17 (api_url, api_path, api_key, ...) order still works with a deprecation warning
Async methodsconnect(), list_voices(), synthesize(), synthesize_stream(), close()
Sync methodslist_voices_sync(), synthesize_sync(), close_sync(); there is no synthesize_stream_sync(), connect_sync(), or disconnect_sync()
Options and defaultsSynthesis requires text containing at least one Unicode letter or number after trimming and exactly one of voice_id or non-empty voice_references; model=TtsModel.Nebula; optional timeout_seconds, on_audio for buffered synthesis, on_error, and request_id.
Resultlist_voices() returns dictionaries with id, label, and optional profile; synthesis responses contain id, is_last, and audio: bytes.

This program rejects an empty voice list, applies explicit timeouts, and wraps the returned PCM in a WAV header:

tts_to_wav.py
from __future__ import annotations

import asyncio
import os
import sys
import wave
from pathlib import Path

from humain_voice import stt, tts


def required_env(name: str) -> str:
    value = os.environ.get(name)
    if not value:
        raise RuntimeError(f"{name} is required")
    return value


def write_pcm16_wav(path: Path, pcm: bytes, sample_rate: int) -> None:
    with wave.open(str(path), "wb") as wav_file:
        wav_file.setnchannels(1)
        wav_file.setsampwidth(2)
        wav_file.setframerate(sample_rate)
        wav_file.writeframes(pcm)


def handle_error(error: stt.ErrorResponse | None) -> None:
    if error is not None:
        print("server error:", error.code, error.message)


async def main() -> None:
    output_path = Path(sys.argv[1] if len(sys.argv) > 1 else "speech.wav")
    async with tts.TTSClient(
        api_url=required_env("API_URL"),
        api_path=required_env("API_PATH"),
        api_key=required_env("API_KEY"),
    ) as client:
        voices = await client.list_voices(timeout_seconds=5.0)
        if not voices:
            raise RuntimeError("No TTS voices are available")
        voice = next((item for item in voices if item.get("profile")), voices[0])
        if profile := voice.get("profile"):
            print(
                "profile:",
                voice["label"],
                profile["speaker"]["dialect"],
                profile["languages"],
            )

        model = tts.TtsModel.Nebula
        pcm = await client.synthesize(
            "Hello from HUMAIN Voice",
            voice_id=voice["id"],
            model=model,
            # This is an inactivity timeout applied while awaiting each chunk.
            timeout_seconds=30.0,
            on_error=handle_error,
        )

    write_pcm16_wav(output_path, pcm, tts.get_sample_rate(model))


if __name__ == "__main__":
    asyncio.run(main())

Socket.IO TTS returns raw PCM16 little-endian, 24 kHz, mono bytes. The fixture uses the standard-library wave module to write the matching container. Use synthesize_stream() when the app should process each audio chunk.

For voice_references, send one reference whose audio is standard-base64 RIFF/WAVE containing non-empty mono PCM16 data.

The service independently enforces a non-resetting 25-second overall synthesis deadline and a 60-second inactivity watchdog. If the overall deadline wins, TTS_DEADLINE_EXCEEDED is retryable and any audio already received is partial.

The on_error callback receives a normalized ErrorResponse: structured payloads retain code and retryable, while a legacy non-object payload becomes a message. A rejected synthesis coroutine raises a generic message-only RuntimeError, so preserve callback details before cleanup. Always close the client with a context manager.

Retry task: read a preserved Batch result

SDK 0.18.0 makes one HTTP call per Batch operation. max_retries is deprecated and ignored. This fixture sets save_result=True, then retries the preserved result with bounded backoff, the released status_code and retry_after attributes, and capacity logging. Without preservation, a lost terminal response can be followed by cleared; no retention duration is defined even when preservation is enabled.

batch_error_retry.py
from __future__ import annotations

import asyncio
import os
import random
import sys

from humain_voice import stt
from humain_voice.stt.batchtranscription import TranscriptionResponse


def required_env(name: str) -> str:
    value = os.environ.get(name)
    if not value:
        raise RuntimeError(f"{name} is required")
    return value


async def get_result_with_retry(
    client: stt.BatchTranscribeClient,
    job_id: str,
    attempts: int = 5,
) -> TranscriptionResponse:
    for attempt in range(1, attempts + 1):
        try:
            # save_result prevents a terminal read from clearing the stored
            # result before a retry. It does not define a retention duration.
            return await client.get_result(
                job_id, stt.Language.ArEn, save_result=True
            )
        except stt.BatchTranscribeError as error:
            rate_limited = isinstance(error, stt.BatchTranscribeRateLimitError)
            retryable = rate_limited or error.retryable is True
            print(
                {
                    "status_code": error.status_code,
                    "code": error.code,
                    "capacity": error.capacity,
                }
            )
            if not retryable or attempt == attempts:
                raise

            server_delay = 0
            if isinstance(error, stt.BatchTranscribeRateLimitError):
                server_delay = error.retry_after or 0
            exponential_delay = 0.5 * 2 ** (attempt - 1)
            await asyncio.sleep(max(server_delay, exponential_delay) + random.random() * 0.25)

    raise RuntimeError("Retry loop exhausted")


async def main() -> None:
    if len(sys.argv) < 2:
        raise RuntimeError("Pass a batch job ID as the first argument")

    async with stt.BatchTranscribeClient(
        api_url=required_env("API_URL"),
        api_key=required_env("API_KEY"),
    ) as client:
        result = await get_result_with_retry(client, sys.argv[1])
        print(result.status.value, result.results.transcript if result.results else "")


if __name__ == "__main__":
    asyncio.run(main())

Do not reuse that loop blindly for job creation. If an upload times out, the app might not know whether a job was created.

Reference: result and error types

TypeReleased fields and behavior
JobResponsejob_id, status; the wire alias remains jobId
TranscriptionResponsestatus; optional results, api_version, version, metadata, diarization_segments, error, error_code; properties job_id, file_duration, is_complete, is_failed, is_pending; subtitles()
FileUploadedResponse / FtTranscribeResponseUpload: id, optional message. Fast result: id, seq, transcription, words, is_final, plus subtitles().
RtTranscribeResponseFast result fields plus is_speech_final; the latter marks an utterance boundary, while only is_final ends the stream
DiarizationUpdateid, reconciled segments, newly_finalized, active_segments, is_final, raw
SpeakerContext / VoiceProfile / VoiceInfo{ gender, dialect }; { speaker, languages }; { id, label, profile? }. The current API always supplies profile.
VoiceReference / TtsAudioResponse{ text, audio } with base64 audio; id, is_last, audio: bytes. voice_id and voice_references are mutually exclusive.
ErrorResponseOptional id, message, code, retryable, timestamp, retry_after_seconds, data, reason, and retry_scope; legacy non-object Socket.IO errors normalize to a message
Batch exceptionsBatchTranscribeError exposes status_code, payload, code, retryable, job_id, detail, timestamp, capacity, raw_body; subclasses are BatchTranscribeAuthError, BatchTranscribeTimeoutError (elapsed_seconds), BatchTranscribeJobFailedError (error, error_code), and BatchTranscribeRateLimitError (retry_after).
Socket.IO failure pathsRouted Fast errors call on_error then raise a message-only RuntimeError; Realtime signals its callback and final wait; diarization iteration raises DiarizationStreamError; routed TTS errors keep the callback structured but synthesis raises a message-only RuntimeError. An unrouteable error can reach only the global callback, so keep an app deadline and always clean up.

Reference: imports and event constants

BatchDiarization, BatchRedact, Batch AudioInput, and Batch response types are exported from humain_voice.stt.batchtranscription, not the top-level humain_voice.stt namespace. BatchTranscriptionModel is available through stt.

TTS exports TtsModel, DEFAULT_SAMPLE_RATE, MODEL_SAMPLE_RATES, get_sample_rate(), and decode_tts_audio_frame().

Import pathPublic event constants and wire values
humain_voice.stt.constantsEVENT_FT_ERROR="error", EVENT_FT_TRANSCRIBE_FILE="audio_file", EVENT_FT_TRANSCRIBE_FILE_UPLOAD_SUCCESS="audio_file_upload_success", EVENT_FT_TRANSCRIBE_RESULT="transcription_result"
humain_voice.stt.constantsEVENT_RT_AUDIO_STREAM="audio_stream", EVENT_RT_END_AUDIO_STREAM="end_audio_stream"
humain_voice.stt.constantsEVENT_DIARIZATION_STREAM="diarization_stream", EVENT_DIARIZATION_RESULT="diarization_result"
humain_voice.ttsEVENT_TTS_REQUEST="tts", EVENT_TTS_AUDIO="tts_audio", EVENT_TTS_ERROR="error", EVENT_TTS_VOICE_LIST_REQUEST="tts_voice_list", EVENT_TTS_VOICE_LIST_RESULT="tts_voice_list_result"

humain_voice.errors exports the same uppercase error-code constants listed in the JavaScript guide. It also exports is_asr_code(), is_tts_code(), is_request_scoped_code(), is_realtime_owned(), is_tts_owned(), is_diarization_code(), and is_diarization_owned() for structured Socket.IO error routing. The top-level humain_voice.stt namespace does not re-export the STT event constants.

Reference: subtitle helpers

API0.18.0 contract
SubtitlesSubtitleCue, SubtitleOptions, and SubtitleError; constructor and cues; from_words, from_cues, from_response; to_srt, to_vtt
RealtimeSubtitleswords, cues, add_response, subtitles, to_srt, to_vtt; ignores partials and deduplicates finalized id:seq responses
Top-level helperswords_to_cues, cues_to_srt, cues_to_vtt, subtitles, to_srt, to_vtt
Shaping defaultsmax_duration_seconds=6, max_gap_seconds=0.7, min_duration_seconds=0.5, max_chars_per_line=42, max_lines=2, split_on_speaker_change=True, strict=False; SRT start_index=1

Subtitle input accepts Batch word offsets and Realtime word segments. Enable strict mode when malformed or out-of-order timing must fail instead of being normalized or skipped.

Next steps

On this page