Истории, которые можно смотреть
Спросите про случай из жизни Шрилы Прабхупады — и ответ теперь не только текст: приходят короткие видео, где ученики сами рассказывают, как это было.
Чат-ассистент в Слушай Садху — тот, что отвечает на вопросы о лекциях и писаниях, — теперь отвечает не только текстом. Спросите про эпизод из жизни Шрилы Прабхупады — и в ответ могут прийти короткие видео: ученики сами рассказывают, как это было.
Раньше, чтобы найти нужную историю, надо было точно знать, где искать. Теперь — не надо. Опишите её своими словами — «кажется, кто-то рассказывал, как Прабхупада заботился об учениках» — и чат найдёт фрагмент.
Что под капотом
Источник — фильм Following Śrīla Prabhupāda, разрезанный на отдельные истории:
- ~2600 видеофрагментов
- ~56 часов воспоминаний учеников
- на двух языках: ~1390 русских клипов (27 ч) и ~1240 английских (29 ч)
- 350+ учеников Шрилы Прабхупады рассказывают свои истории: Шьямасундара, Брахмананда, Шрутакирти, Ямуна, Гирираджа Свами и многие другие
- 11 DVD, события 1966–1977 годов: Маяпур, Лос-Анджелес, Вриндаван, Бомбей, Лондон…
От фильма к ~2600 историям
Ни одного из этих фрагментов не было как файла. Источник — одиннадцать DVD со сплошной съёмкой: одно воспоминание переходит в другое, сцена обрывается, начинает говорить новый ученик. Чтобы превратить это в библиотеку с поиском, нужен конвейер, и каждый его этап здесь не зря.
flowchart TD F["11 DVD · 56 ч съёмки"] --> T["1 · Распознать речь, Parakeet на Neural Engine, тайминг по словам"] T --> SEG["2 · Найти истории, диаризация, затем LLM-судья на стыках"] SEG --> REV["3 · Вычитать распознанный текст, имена, санскрит"] REV --> META["4 · Определить говорящего и написать строку контекста"] META --> CUT["5 · Нарезать видео, ffmpeg, перекодировка в 720p H.264"] CUT --> EMB["6 · Встроить в общий корпус и сохранить на CDN"] EMB --> CHAT["~2600 клипов, найденных по смыслу"]
Распознать речь
С каждого DVD мы вытягиваем звук через yt-dlp, сводим в моно 16 кГц и прогоняем
через распознавание речи. Модель — NVIDIA Parakeet-TDT-0.6b-v3. Одна
многоязычная модель на русский и английский материал сразу, причём язык передаётся
подсказкой, а не переключается целиком. Работает она локально, на Apple Neural
Engine у Mac с M4: веса CoreML держатся в памяти за небольшой HTTP-очередью задач,
поэтому 56 часов распознаются в сотни раз быстрее реального времени и без
поминутной платы облаку. Управляем прямо из агента через MCP-инструмент: даём
файл, получаем job_id, ждём результата.
Приходит не просто текст, а тайминг и уверенность по каждому слову. Именно это потом позволяет резать по границе предложения, а не на полувздохе:
{ "word": "Prabhupada", "startTime": 4.24, "endTime": 4.83, "confidence": 0.997 }
По умолчанию сервис работает на русском. Участки с низкой уверенностью — киртан,
речь на другом языке — падают ниже 0.5 и помечаются для проверки человеком.
Найти, где кончается история
Транскрипт — это сплошная стена слов. А у истории есть начало и конец. Почти всегда это целый рассказ одного ученика: с того момента, как он заговорил, и до того, как начнёт говорить другой. Этап самый трудный, и две редакции фильма требуют двух противоположных подходов.
Английская — границы проводят сами голоса. Каждого преданного снимают и
подписывают на экране, а монтаж чисто склеивает их между собой. Поэтому модель
диаризации (nova-2 от Deepgram) отмечает каждую точку, где голос меняется, и
непрерывный отрезок одного голоса становится отдельной историей. Лекция или
интервью Прабхупады — одна история. Чистая музыка и титры без слов отбрасываются.
Дальше несколько правил подчищают края. Всё короче ~18 секунд не живёт отдельным клипом — его присоединяют к соседу. Всё длиннее ~4 минут не остаётся целым — его снова делят по самой длинной внутренней паузе. А финальная проверка отмечает клип, который начинается со строчной буквы или кончается без знака препинания: верный признак среза посреди фразы.
Русская задача: один голос, много рассказчиков
Русская редакция ломает этот подход целиком. Дубляж тут не по ролям: всех преданных озвучивает один переводчик. Голос не меняется, и диаризация слепа — для неё весь пятичасовой DVD один говорящий.
Поэтому метод переворачиваем: сначала дробим с запасом, потом судим. Сперва режем транскрипт слишком мелко — по каждой паузе, что длинна для стыка, и каждый рез притягиваем к ближайшему настоящему промежутку между словами. Фрагментов выходит куда больше, чем историй. Дальше для каждой соседней пары рассуждающая модель (Claude) отвечает на один вопрос. Судит по тому, кто говорит и о чём, а не по голосу — голос-то везде один:
Два идущих подряд фрагмента русской озвучки документального фильма, где многие преданные вспоминают Шрилу Прабхупаду (всех озвучивает один переводчик — голос не подсказывает, кто говорит). Отвечай MERGE, только если B — тот же человек и то же воспоминание, что и в A… Отвечай SPLIT, если B — рассказ другого человека или другая история. Ответь одним словом: MERGE или SPLIT.
Тысячи стыков взвешиваются параллельно, а на выходе — целые истории, по одной на рассказчика, заново собранные из кусочков. Две редакции одного фильма, две противоположные стратегии — и обе продиктованы одним решением: как каждую озвучили.
Вычитать распознанное
Распознавание путает санскрит и имена. Поэтому языковая модель проходит по каждой истории и правит только ошибки распознавания: Трипурари Махараджа, Маяпур, Ратха-ятра, единое написание Прабхупады. Ей строго велено ничего не пересказывать, не сокращать и не добавлять ни строчки. (Если модель разойдётся и вернёт текст вдвое длиннее исходного, мы отбрасываем её ответ и оставляем сырой транскрипт.)
Определить и вписать каждую историю в контекст
Кто говорит. Подписанный клип стоит куда больше. В английской редакции имя
есть на экране — подпись в нижней трети кадра, «Śyāmasundara dāsa remembers».
Поэтому ffmpeg берёт один кадр из каждой истории, обрезает нижнюю треть, а
визуальная модель читает подпись и возвращает только имя. У русских клипов
подписей нет, только голос переводчика. Поэтому имя берут у английского двойника:
каждую русскую историю сопоставляют с ним по содержанию (редакции отличаются
порядком и длиной, так что совпадение ищут по сути рассказа, а не по таймкоду) и
берут имя оттуда.
Строка контекста. Отдельный клип легко понять неверно: «и тогда он просто улыбнулся мне» ничего не значит, пока не знаешь, кто этот он, кто говорит и когда. Поэтому в том же проходе модель пишет для каждой истории поясняющую строку в одно-два предложения: кто говорит, о чём, когда и где. В помощь ей идёт написанная вручную заметка об эпохе для каждого DVD (DVD 7, например, — это «американский тур 1975 года и фестиваль в Маяпуре»). Заодно модель вытаскивает структурные метаданные: год, места, темы, людей. Эту строку ставят перед транскриптом ещё до встраивания, чтобы семантическому поиску было за что зацепиться, даже если клип нигде не называет своего героя. Это идея контекстного поиска от Anthropic: немного контекста на каждый фрагмент, чтобы поиск не сбивал с толку клип, который сам по себе почти ничего не говорит. Зритель по-прежнему видит только чистый транскрипт — контекст работает за кадром.
Нарезать видео
Только теперь доходит до самого видео. ffmpeg вырезает каждую историю из
скачанного файла полного разрешения — с перекодировкой, а не копированием потока,
чтобы клип открывался на настоящем кадре — в 720p H.264 со звуком AAC,
веб-fast-start и четвертью секунды запаса с каждой стороны, чтобы никого не
обрезало на полуслове:
ffmpeg -ss {start-0.25} -i source.mp4 -t {duration} \
-map 0:v:0 -map 0:a:0 \
-c:v libx264 -preset veryfast -crf 23 -vf scale=-2:720 \
-c:a aac -b:a 128k -movflags +faststart clip.mp4
На том же шаге для каждого клипа берётся кадр-постер.
Встроить, проиндексировать, отдать
Каждую историю — строку контекста перед вычитанным транскриптом — модель
text-embedding-3-small от OpenAI (1536 измерений) превращает в эмбеддинг и пишет
в тот же корпус chunks, что и лекции, стихи и письма: клип — это просто
kind = 'media', один из шести видов. Повторный запуск индексатора встраивает
заново только изменившееся, ведь у каждой строки есть хеш содержимого. Тот же
поиск, что уже отдаёт аудиофрагменты —
частичный HNSW-поиск в pgvector — дотягивается и до клипов, без отдельного
видеоиндекса.
Сами нарезки уходят в объектное хранилище (бакет S3 с зеркалами на Yandex и Bunny CDN). База хранит только относительный путь, а приложение при старте достраивает его до ближайшего узла CDN. Поэтому, когда клип выигрывает в поиске, чат стримит вам именно этот момент, а рядом — его транскрипт.
В итоге видео — не сбоку прикрученная функция. Клип — просто ещё один вид записи в корпусе, который остальная система уже понимает. Потому-то вопрос, написанный вашими словами, и может его найти.
Спросите о местах, годах или событиях — или просто «расскажи историю о…» — и смотрите, как это было.
Часть проекта
Слушай Садху