You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

11 KiB

OpenAI Transcription and Resumable Jobs Design

Goal and Scope

Add production-quality transcription of preprocessed chunks through the current OpenAI Audio Transcriptions API. Jobs must survive crashes and network loss, resume without repeating successful work, remain cancellable, and expose useful progress and sanitized API failures in the desktop GUI.

Each chunk is submitted independently. Completed chunk transcripts are concatenated in source order without overlap deduplication. Translation, diarization, overlap reconciliation, and word-level timestamp inference are out of scope.

Verified OpenAI Interface

The implementation targets the installed OpenAI Python SDK 3.6.0 interface verified on 2026-08-30:

client.audio.transcriptions.create(
    file=audio_file,
    model=model,
    language="pt",
    prompt=prompt,
    response_format="json",
)

The method accepts arbitrary model strings and returns a transcription object with a text value for JSON responses. The SDK exposes distinct RateLimitError, APIConnectionError, APITimeoutError, and APIStatusError classes.

The application default is the explicitly requested gpt-transcribe. The editable selector is seeded with gpt-transcribe, gpt-4o-transcribe, and gpt-4o-mini-transcribe, while allowing future compatible identifiers. Current official OpenAI documentation lists the two GPT-4o transcription models but does not list gpt-transcribe; therefore rejection of that identifier is treated as a permanent, clearly displayed API error rather than silently substituting a model.

The app uses the transcription endpoint, never the translation endpoint. It sends language="pt", the ISO-639-1 code documented by the API, while the prompt explicitly requests Brazilian Portuguese conventions.

Architecture

  • models.py extends application settings with an optional context/vocabulary string.
  • services/settings.py persists the non-secret model, language, output directory, preprocessing values, and context.
  • services/credentials.py reads and updates the existing Windows Credential Manager generic credential whose target name is exactly OPENAI_API_KEY.
  • services/job_manifest.py owns schema validation, atomic manifest writes, resumable state transitions, and transcript assembly.
  • services/transcription.py adapts the OpenAI SDK, classifies failures, implements bounded exponential backoff, and orchestrates one request per unfinished chunk.
  • services/preprocessing.py creates durable job directories inside the configured output directory and produces chunk records suitable for transcription.
  • ui/settings_dialog.py provides an editable model selector and multiline Context / Vocabulary field.
  • ui/main_window.py runs the job service in a Qt worker and displays progress, cancellation, elapsed time, current chunk, and API errors.
  • app.py wires the credential, OpenAI client, preprocessing, manifest, and transcription services.

Services do not import PySide6. Network calls, retries, waits, manifest I/O, and preprocessing run outside the GUI thread.

API Credential Integration

The OpenAI API key already exists in Windows Credential Manager under the generic credential target name OPENAI_API_KEY. The credential service uses the Windows keyring backend's credential lookup for that exact target and accepts the username stored with the matching credential; it does not require or assume the previous voice-transcriptor / openai-api-key service-account pair.

Saving a replacement key updates the same OPENAI_API_KEY target while preserving the credential's existing username when one is available. If no matching credential exists, the settings dialog reports that the API key is not configured and may create the target using OPENAI_API_KEY as the stable username. The key is passed directly to the OpenAI client and is never copied into application settings, job manifests, GUI logs, exception messages, or transcript files.

Durable Job Layout and Manifest

Every run has a stable directory below <output_directory>/voice-transcriptor-jobs/<job-id>/:

manifest.json
chunks/chunk-00000.m4a
chunks/chunk-00001.m4a
transcript.txt

The manifest remains the source of truth and is replaced atomically after every state transition. It contains no API key, authorization header, or sensitive request metadata. Schema version 2 records job identity and overall state; canonical source metadata; effective preprocessing and transcription settings; timestamps; and each chunk's index, relative audio path, exact source timing, status, attempt count, transcript, sanitized last error, and completion timestamp.

Chunk statuses are pending, processing, completed, and failed. The overall job may additionally be preprocessing, transcribing, completed, cancelled, or failed.

A successfully encoded chunk is recorded immediately. A successfully transcribed chunk is changed to completed, its transcript is stored, counters are updated, and the manifest is atomically replaced before the next API call. transcript.txt is then atomically regenerated from all completed chunk texts in index order. This ordering ensures a crash cannot make an unrecorded success appear complete.

Resume Semantics

Starting a job for a source first looks for an existing non-complete manifest whose source identity and effective settings match. The GUI may also resume a retained job directly from its manifest.

On resume:

  • completed chunks are never submitted again;
  • interrupted processing chunks return to pending because no durable successful response exists;
  • failed chunks return to pending for an explicit user resume action;
  • existing valid encoded chunk files are reused;
  • missing or invalid encoded files are regenerated without affecting completed transcript records;
  • an explicit retranscribe action is the only operation allowed to clear completed transcript state.

Cancellation stops before the next request or retry, marks the overall job cancelled, and retains the durable directory. Cancellation during an active synchronous SDK request takes effect immediately after that request returns; the UI remains responsive because the call is on a worker thread.

Prompting and Language Preservation

The effective prompt combines a fixed Brazilian-Portuguese instruction with optional user context:

Conversa em português brasileiro. Preserve a língua falada; não traduza. Preserve ortografia e pontuação brasileiras, números, nomes próprios, termos técnicos e siglas com máxima fidelidade.

When supplied, the Context / Vocabulary value follows this instruction. It is trimmed, user-editable, and persisted as non-secret configuration. The default example is:

Brazilian Portuguese conversation. Preserve Brazilian spelling and punctuation. Vocabulary may include AWS, Kubernetes, OpenAI, PostgreSQL, Brasília, Banco do Brasil.

No previous chunk transcript is injected into a later request in this milestone, keeping chunks independent and avoiding accidental propagation of errors.

Retry and Error Classification

The OpenAI client is configured with SDK retries disabled so application behavior is deterministic and visible. The job service uses cancellation-aware exponential backoff with jitter and a finite maximum attempt count.

Retryable failures include rate limits, connection failures, timeouts, HTTP 408, HTTP 409, HTTP 429, and HTTP 5xx responses. A server-provided Retry-After delay is honored when available, subject to a reasonable cap. The GUI reports rate limiting and the next retry without exposing headers.

Permanent failures include authentication and permission errors, invalid requests, unsupported models, missing files, and other non-retryable 4xx responses. They immediately mark the current chunk failed and the job failed. Retries never continue indefinitely.

All logged errors use exception type, status code when safe, request ID when available, chunk index, and a sanitized message. API keys, authorization values, request headers, and raw SDK request objects are never logged or included in manifests.

Progress and GUI Behavior

The main action prepares and transcribes the recording as one worker-owned job. Visible state includes chunks completed / total, elapsed wall-clock time, current chunk, phase, retry/rate-limit notices, and sanitized API errors.

The progress bar reflects completed chunks during transcription and preprocessing percentage before API work begins. Conflicting file/settings actions are disabled while active. Cancel remains available. All worker terminal paths restore controls, and closing the window requests cancellation.

The settings dialog uses an editable combo box for model selection and a multiline Context / Vocabulary editor. The language remains editable for future languages, defaults to pt-BR in presentation and persistence, and is normalized to pt for the API when Brazilian Portuguese is selected.

Transcript Timing Metadata

Every chunk transcript retains the preprocessing manifest's exact decimal source_start_seconds, source_end_seconds, and duration_seconds. These values identify the source interval covered by the raw text. The plain transcript.txt contains text only, separated by newlines in chunk order; timing-rich data remains in manifest.json for future structured export and overlap deduplication.

Testing and Validation

Development follows test-driven cycles. Unit tests use a fake transcription endpoint and deterministic sleeper/random sources; they never require a real API key or network access.

Coverage includes exact SDK arguments and independent chunk calls; lookup and update of the exact OPENAI_API_KEY Windows credential target; Brazilian language/prompt composition; arbitrary model identifiers; retry, rate-limit, exhaustion, and permanent failures; atomic persistence; crash recovery and resume skipping; cancellation; timing retention and ordered concatenation; error sanitization; GUI worker progress; and settings migration/UI behavior.

Final validation runs the complete pytest suite, Python compilation, whitespace checks, an offscreen GUI startup probe, and an optional local FFmpeg preprocessing smoke test when tools are installed. No live OpenAI request is required for automated validation.

Success Criteria

The application preprocesses and independently transcribes every unfinished chunk through the verified OpenAI Python SDK interface; defaults to the requested model and Brazilian-Portuguese guidance; persists each success and exact source timing immediately; resumes without retranscribing completed chunks; uses finite, graceful retry behavior; remains responsive and cancellable; exposes complete job progress and safe errors; concatenates raw chunk text without deduplication; and passes all automated and startup validation.