Реализация AI-автоматической генерации субтитров к видео в мобильном приложении
Субтитры через AI — это Whisper или его аналоги. Задача на первый взгляд простая: отдал видео, получил текст с таймкодами. Но в мобильном контексте появляется несколько нетривиальных вопросов: транскрибировать on-device или через API, как рендерить субтитры поверх видео, как дать пользователю отредактировать результат.
Whisper on-device vs API
Whisper через OpenAI API — самый простой путь. Отправляем аудио (до 25 MB), получаем JSON с segments (таймкоды + текст):
POST https://api.openai.com/v1/audio/transcriptions
model=whisper-1&response_format=verbose_json×tamp_granularities[]=word
verbose_json с word-гранулярностью даёт таймкод каждого слова — нужно для синхронизации субтитров. Время обработки: ~10-секундный клип — 2–4 сек, минута видео — 10–20 сек.
Whisper on-device — реально для iOS 16+ через WhisperKit (swift-transformers). Модель whisper-small — 244 MB, скорость ~0.3× реального времени на iPhone 14 (т.е. минута аудио = 3 минуты обработки). whisper-tiny — 77 MB, 0.7× реального времени, но точность заметно хуже. Для русского языка — хуже, чем для английского.
На Android: whisper.cpp через JNI, или openai-whisper-tflite — но это сложнее в сборке. Проще для большинства приложений — API.
Извлечение аудио из видео на клиенте
Перед отправкой в Whisper нужно извлечь аудиодорожку — отправлять всё видео избыточно:
// iOS: AVAssetExportSession для извлечения аудио
func extractAudio(from videoURL: URL) async throws -> URL {
let asset = AVURLAsset(url: videoURL)
guard let exportSession = AVAssetExportSession(
asset: asset, presetName: AVAssetExportPresetAppleM4A
) else { throw SubtitleError.exportFailed }
let outputURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString + ".m4a")
exportSession.outputURL = outputURL
exportSession.outputFileType = .m4a
await exportSession.export()
return outputURL
}
m4a/mp3 в 3–5 раз меньше оригинального видео — быстрее загружается и дешевле по API.
Сегменты субтитров: обработка на клиенте
Whisper verbose_json возвращает segments с start, end, text. Нарезаем по 5–7 слов на субтитр для читабельности:
// Android: разбивка на субтитры
data class SubtitleCue(val start: Double, val end: Double, val text: String)
fun segmentsToSubtitles(words: List<WhisperWord>, maxWords: Int = 7): List<SubtitleCue> {
val cues = mutableListOf<SubtitleCue>()
var chunk = mutableListOf<WhisperWord>()
for (word in words) {
chunk.add(word)
if (chunk.size >= maxWords || word.word.endsWith(".") || word.word.endsWith("!")) {
cues.add(SubtitleCue(
start = chunk.first().start,
end = chunk.last().end,
text = chunk.joinToString(" ") { it.word.trim() }
))
chunk.clear()
}
}
if (chunk.isNotEmpty()) {
cues.add(SubtitleCue(chunk.first().start, chunk.last().end,
chunk.joinToString(" ") { it.word.trim() }))
}
return cues
}
Рендеринг субтитров поверх видео
Два подхода:
Overlay во время воспроизведения — UILabel/TextView поверх AVPlayerLayer/ExoPlayer. Обновляем текст по таймеру через player.currentTime(). Просто, не модифицирует исходный файл.
Запечённые субтитры — FFmpeg subtitles фильтр, сохраняет субтитры в пикселях видео. Постоянно видны при любом воспроизведении:
ffmpeg -i input.mp4 -vf "subtitles=subs.srt:force_style='FontName=Arial,FontSize=20,PrimaryColour=&HFFFFFF,OutlineColour=&H000000,Bold=1'" output.mp4
Для Stories/Reels нужны запечённые субтитры — при шаринге overlay потеряется. Для внутреннего плеера приложения — overlay достаточен.
Редактор субтитров
Часто пользователь хочет исправить ошибки транскрипции. Минимальный редактор:
- Список
SubtitleCueвRecyclerView/List - Tap на элемент →
TextFieldдля редактирования текста - Drag/resize для сдвига таймкодов
- Preview в видеоплеере с обновлёнными субтитрами в реальном времени
Удобный UX: при редактировании текста — автоматически корректировать конец предыдущего / начало следующего субтитра.
Экспорт SRT/VTT
SRT — стандартный формат для экспорта:
1
00:00:01,200 --> 00:00:03,450
Привет, это тестовый текст
2
00:00:03,800 --> 00:00:06,100
Второй субтитр здесь
На мобиле пишем в FileManager.default.temporaryDirectory, шарим через UIActivityViewController/FileProvider.
Сроки
Транскрипция через Whisper API + базовый overlay player — 3–4 дня. Полная реализация с on-device транскрипцией, редактором, запечёнными субтитрами и экспортом SRT/VTT — 2–3 недели. Стоимость рассчитывается индивидуально.







