
Дисклеймер сразу: я не собирался писать «ещё один стартап». Мне просто было лень руками пересматривать видео, чтобы выписать из него тезисы. Дальше — история о том, как решение личной проблемы обросло LLM-цепочками, защитой от SSRF и вопросами вроде «а что если сертификат не выпустится».
Сервис называется postvid.ru: ссылка на видео или файл на входе, расшифровка, тезисы, таблица, презентация и карточки для карусели на выходе.
Стек, если коротко
— FastAPI — веб-приложение и API, ничего экзотического.
— faster-whisper, модель small — расшифровка речи. Брал именно small, потому что сервер без GPU, а medium на CPU превращает ожидание в пытку.
— YandexGPT / любой OpenAI-совместимый API — выделение тезисов, с фолбэком на локальную модель через Ollama и на офлайн-алгоритм без нейросети вообще.
— Pillow — генерация карточек карусели.
— openpyxl, python-pptx — таблица и презентация из тех же тезисов.
— SQLite — аккаунты, сессии, подписки, платежи. Да, SQLite, не Postgres — для текущей нагрузки хватает с запасом, а лишнюю инфраструктуру я себе усложнять не стал.
— Docker Compose + Caddy — деплой и автовыпуск TLS-сертификатов сразу для нескольких доменов.
— ЮKassa — оплата и автопродление.
Пайплайн
«`
ссылка/файл → проверка SSRF → faster-whisper (текст + таймкоды)
→ LLM (тезисы + цитаты + время) → {xlsx, pptx, PNG-карусель, srt}
«`
Задача обрабатывается в пуле потоков, статус лежит в памяти процесса и отдаётся polling’ом через /api/jobs/{id}. На бумаге — просто. На практике каждый пункт этой схемы принёс свой сюрприз, и вот четыре самых запоминающихся.
Грабли №1: облако решило отдохнуть, а сервис — вместе с ним
Первая версия дёргала облачную LLM напрямую, без всякой защиты. Всё было отлично, пока однажды ночью модель не легла на пару минут. Для меня это была строчка в логе. Для пользователей в этот момент — «сервис сломан, ничего не работает». Неприятное ощущение, когда узнаёшь об этом постфактум.
Сделал цепочку:
1. Облачная модель.
2. Если недоступна — локальная модель через Ollama на том же сервере.
3. Если и её нет — офлайн-алгоритм: рубит текст на предложения и выбирает самые «весомые» по простым эвристикам.
Плюс кулдаун: если облако упало, следующая минута задач сразу идёт в запасной вариант, не тратя время на повторный обречённый запрос. На странице результата честно пишется, какой движок сработал — cloud, local или offline. Мне кажется, это важнее любой красивой анимации загрузки: пользователь должен понимать, почему результат вдруг стал немного хуже.
Грабли №2: «просто вставь ссылку» — ха
Казалось бы, что сложного в том, чтобы скачать видео по ссылке. Сложного оказалось много: нужно отсечь запросы во внутреннюю сеть сервера (привет, SSRF), проверить, что схема — именно http(s), и не пускать всё подряд.
«`python
def validate_public_url(url: str, allowed_hosts: set[str] | None = None) -> None:
parsed = urlparse(url.strip())
if parsed.scheme not in («http», «https»):
raise SourceRejected(«Разрешены только http(s) ссылки»)
# резолвинг хоста, отказ на приватные/