On-device caption transcription, multilingual SRT export, and selectable MOV caption embedding for Swift apps on macOS.
OnDeviceCaptionKit provides:
- On-device speech transcription through Apple's Speech framework.
- Modern
SpeechAnalyzertranscription with legacySFSpeechRecognizerfallback. - SRT sidecar generation with deterministic timestamp and text wrapping behavior.
- CEA-608 closed-caption embedding for
.movfiles using AVFoundation. - Unicode
tx3gMOV embedding with multiple selectable BCP-47 language tracks. - Typed errors and stable warning codes for host-app localization.
v1 scope is caption generation and export only. UI, localization copy, save panels, settings, logging policy, microphone capture, screen recording, and user-facing fallback messaging stay in the consuming app.
| Item | Requirement |
|---|---|
| Swift tools | 6.2+ |
| macOS | 26+ |
| Dependencies | None |
Add OnDeviceCaptionKit to your Package.swift dependencies:
dependencies: [
.package(url: "https://github.com/dadederk/OnDeviceCaptionKit.git", from: "0.3.0")
]Then add the product to your target:
target(
name: "YourApp",
dependencies: [
.product(name: "OnDeviceCaptionKit", package: "OnDeviceCaptionKit")
]
)OnDeviceCaptionKit is built around three common tasks: transcribe audio, write SRT, and embed captions into a MOV.
Transcribe audio on device:
import Foundation
import OnDeviceCaptionKit
let pipeline = CaptionPipeline(
configuration: .init(
transcription: CaptionTranscriptionConfiguration(
locale: Locale(identifier: "en-US")
)
)
)
let result = try await pipeline.transcribe(from: audioURL)
let segments = result.segmentsWrite an SRT file:
try await pipeline.writeSRT(segments: segments, besideVideoAt: savedVideoURL)Embed closed captions in a MOV:
let export = try await pipeline.exportCaptions(
segments: segments,
videoURL: videoURL,
format: .embeddedMovCaptions
)
let captionedVideoURL = export.videoURLEmbed multiple Unicode caption tracks without re-encoding video or audio:
let tracks = [
try CaptionLanguageTrack(languageIdentifier: "en-US", segments: originalSegments),
try CaptionLanguageTrack(languageIdentifier: "es-ES", segments: spanishSegments),
]
let export = try await pipeline.exportCaptions(
tracks: tracks,
videoURL: videoURL,
format: .embeddedMovCaptions
)Asset consent example:
let locale = Locale(identifier: "en-US")
if let requirement = await CaptionPipelineCapabilities.requiresAssetDownload(for: locale) {
// Show host-app UI explaining Apple's speech model download.
// Continue only after explicit user consent.
try await CaptionPipeline.prepareAssets(for: requirement.locale, consentGranted: true)
}CaptionPipeline: transcribe, export, and write-SRT orchestration.CaptionPipeline.Configuration: host-app configuration for transcription, authorization, and prepared speech assets.CaptionSegment: timed caption text with start/end times.CaptionLanguageTrack: one validated Unicode caption track with a canonical BCP-47 tag.CaptionTranscriptionConfiguration: locale, asset policy, transcript debug logging, and provider preference.CaptionTranscriptionResult: transcript segments plus the provider that produced them.CaptionOutputFormat:.embeddedMovCaptionsor.srtSidecar.CaptionExportResult: exported video URL, source segments, deferred single- or multilingual SRT values, and warning code.CaptionError: stable error cases andcodestrings for host-app localization.CaptionPipelineCapabilities: provider, locale, and asset-download capability helpers.SpeechAuthorizationProviding: injectable speech authorization boundary for apps and tests.
Async transcription, asset preparation, caption embedding, and SRT writing use explicit background execution semantics. The synchronous SRT methods remain available for 0.2.x source compatibility but are deprecated in favor of the async overloads.
Cancelling transcription or caption export propagates CancellationError. Caption-embedding timeouts still return the documented SRT fallback result, while AVFoundation cleanup continues independently when an SDK operation is slow to cancel.
- User audio is transcribed on device.
- The package requests Speech authorization only when transcription starts.
- Network access is limited to Apple speech model downloads, and only after the host app passes explicit consent to
prepareAssets. - Production logs contain counts and error codes only. Transcript text is logged only when a caller opts into debug transcript logging in debug builds.
- The single-language compatibility API embeds CEA-608 closed captions.
- The multilingual API embeds playable Unicode
tx3gsubtitle tracks with exact extended language tags and validates them before returning. - Multilingual embedding authors caption-only
tx3gsubtitle tracks using the source video's presentation dimensions, then uses an AVFoundation passthrough composition to preserve compressed video and audio samples without re-encoding them. - Validation requires exact decoded text and timing, selectable language metadata, and player-compatible subtitle tracks.
- Empty or whitespace-only caption text is skipped.
- Long caption text is split into row-sized CEA-608 events so AVFoundation does not silently truncate it.
- If MOV embedding fails after transcription succeeds,
CaptionExportResultpreserves the original video URL and returns deferred SRT segments so the host app can offer a fallback file. - If multilingual MOV embedding fails, every nonempty language is returned in
deferredSRTTracks; the package never adopts a partial track set. - SRT output is UTF-8, uses
HH:MM:SS,mmmtimestamps, and avoids an extra trailing blank separator. - Multilingual SRT bundles use the original video basename plus canonical suffixes such as
Recording.es-ES.srtand stage the complete bundle before replacing sidecars.
For a diagram-first view of provider selection, speech asset preparation, SRT writing, MOV embedding, and fallback behavior, see ARCHITECTURE.md.
- Mestre! - macOS screen recorder with optional embedded captions and SRT export.
- Let us know if you'd like your app to be listed here.
- In Xcode, open your project and select
File > Add Package Dependencies... - Enter
https://github.com/dadederk/OnDeviceCaptionKit.git. - Choose a version rule and add the
OnDeviceCaptionKitlibrary product to your target.
- Speech authorization fails: request authorization from a user-visible flow and localize
CaptionError.codevalues in the host app. - A locale is unavailable: use
CaptionPipelineCapabilities.supportedTranscriptionLocales()and let the user choose a supported locale. - Asset download requires consent: call
requiresAssetDownload(for:), explain the Apple speech asset download, then callprepareAssets(for:consentGranted:)after consent. - MOV embedding fails: preserve the original video and use
deferredSRTSegmentsto write an SRT fallback. - Import problems in Xcode: confirm your target links the
OnDeviceCaptionKitproduct and uses a compatible macOS deployment target.
Run tests from the repository root:
swift testDeterministic tests use local AVFoundation fixtures and stubbed muxers. CI tests must not depend on a real microphone, screen capture, network, or live Speech recognition.
See CHANGELOG.md for release history and breaking-change notes.
See SUPPORT.md for issue-reporting guidance and package support boundaries.
See CONTRIBUTING.md for local setup and PR guidelines.
MIT. See LICENSE.
