Впровадження AI автоматичного генерування субтитрів до відео в мобільному додатку
AI-субтитри — Whisper або його аналоги. Завдання звучить просто на поверхні: відправ відео, отримай текст з таймкодами. Але в мобільному контексті виникає кілька нетривіальних питань: транскрибувати на пристрої або через API, як рендерити субтитри поверх відео, як дати користувачу редагувати результат.
Whisper на пристрої проти 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 на пристрої — реально для 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='FontSize=20,PrimaryColour=&HFFFFFF'" output.mp4
Запечені краще для спільного доступу, overlay краще для редагування.
Редагування корекцій
Whisper робить помилки. Дай користувачу редагувати:
// iOS: редаговані cues субтитрів
@State private var subtitles: [SubtitleCue] = []
@State private var editingIndex: Int? = nil
if let index = editingIndex {
TextField("Редагувати субтитр", text: $subtitles[index].text)
}
Зберігай редаговування локально або синхронізуй на бекенд.
Терміни
Інтеграція Whisper API + вилучення аудіо + рендеринг субтитрів — 4–6 днів. З варіантом на пристрої, редаганням користувача, експортом запечених — 2–3 тижні.







