Однажды мне захотелось изучить Kubernetes и мобильную разработку, но не знал, с чего начать. В то же время меня сильно тревожило отсутствие в интернете новостей без политической повестки и негативного окраса. Отовсюду лился поток манипулятивной информации, и я мечтал о фильтре, который бы отсеивал весь этот информационный шлак.
Это моя небольшая история, как я за месяц написал собственное медиа с полной автоматизацией, где роботы-ведущие заменили кожаных мешков и ведут подкасты, в которых шутят про AI
Спойлер: Короткая запись эфира
Я изучил самые естественные русскоязычные TTS-решения на рынке и остановился на ElevenLabs и SaluteSpeech от Сбера. Быстро собрал POC: озвучил несколько новостей, затем склеил их в один файл, добавив перебивки и фоновый звук с помощью ffmpeg.
Первая версия новостного блока
Результат меня очень порадовал и я вдохновился преступить к реализации собственного медиа с аудио контентом!
Стек радио
Для вещания был выбран icecast2 + liqudsoap. Техническая статья уже выходила на хабре с описанием настройки.
Iceast2 - это сервер потокового вещания. Его задача принимать потоки от источников и отдавать слушателям.
Liqudsoap - мощная среда для создания аудиопотоков, позволяющая гибко управлять контентом перед его трансляцией через Icecast2.
Liqudsoap заинтересовал меня тем, что он умеет:
Динамически управлять очередью вещания через telnet. (Добавление, удаление, пропуск треков)
Эквализацию и нормализацию звука. (Что бы эфир звучал с одинаковой громкостью на разных треках)
Фолбэк на случай если эфирная очередь пустая. (Проигрывать заглушку)
Аудио
Радиоэфир состоит из множества аудиофайлов, которые необходимо где-то хранить. Изначально я планировал размещать статику на S3, но из-за негативного опыта в прошлом и неподходящей ценовой политики для моего небольшого pet-проекта отказался от этого варианта. После поиска альтернатив я наткнулся на Yandex Cloud Object Storage. К слову, AWS-модуль для Go отлично подошёл для работы с Яндексом, что меня весьма удивило:
cfg := aws.Config{
Region: os.Getenv("S3_REGION"),
Credentials: credentials.NewStaticCredentialsProvider(
os.Getenv("S3_IDENT"),
os.Getenv("S3_SECRET"),
"",
),
EndpointResolver: aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error)
{
return aws.Endpoint{
URL: "https://storage.yandexcloud.net" // Magic
}, nil
}),
}
Да и по стоимости выходит немного - сейчас в хранилище около 40 ГБ данных, за которые приходит инвойс на 50 рублей.
В итоге что в хранилище:
Музыка
Сгенерированные новостные блоки и подкасты
Джинглы
Сгенерированный прогноз погоды на день
Картинки для приложения
Новости
Как пополняется база новостей:
Кроулер просматривает подписанные каналы в поисках нового контента
Каждый пост проходит проверку через GPT/DeepSeek на отсутствие политики, рекламы, конкурсов и негативной окраски
Оценивается новостная ценность поста
Пост суммаризируется для удобного чтения: создаётся краткая версия и набор тегов. По тегам проверяется уникальность новости, поскольку многие издания публикуют одни и те же события. Тегирование помогает отсеивать дублирующиеся материалы.
В финале GPT/DeepSeek определяет, к какой из моих категорий можно отнести новость
Когда новости попадают в базу, отдельный процесс генерирует для каждой из них TTS и сохраняет в хранилище.
Другой процесс раз в час формирует новостные блоки по тематикам:
Добавляет интро и аутро
Собирает вместе последние сгенерированные TTS за последние 12 часов
Накладывает фоновую музыку
Раннее демо с результатом в шапке поста.
Подкасты
Принцип сборки подкаста немного проще новостей. В базе данных хранятся темы подкастов, каждый из которых имеет:
Промпт для генерации сценария
Провайдера TTS (я подключился к нескольким)
Голосовую модель
Скорость чтения
Фоновую музыку
Раз в сутки GPT генерирует сценарий подкаста по заданному промпту. Затем текст разбивается на чанки по 1000 символов, с учетом завершения последнего предложения, и обрабатывается через TTS.
Полученные аудиофайлы склеиваются с помощью ffmpeg и загружаются в хранилище.
Эфир
Как я уже упоминал ранее, эфир управляется через telnet. Выделенный процесс периодически выкачивает контент из хранилища и добавляет его в эфир по мере того, как очередь вещания подходит к концу.
При запуске сценария приложение на Go пытается вставить в эфир:
Рандомный джингл
Новостной блок
Прогноз погоды
Музыкальный трек (Так как музыка в базе размечаны по bpm, пытается подобрать похожий по bpm)
Каждому новостному блоку и музыкальному треку соответствует своё расписание для включения в эфир. У каждого тайм-слота свой жанр музыки: днем — энергичная, вечером — спокойная.
Общий стек
В итоге, как я и говорил, мне всё-таки удалось получить свои знания о k8s. Сделав из приложения распределённый монолит, каждый компонент - будь то генерация TTS для новостей, сборщик новостных блоков или переключатель эфира - собрал в отдельный Docker-контейнер и запушил на GitHub ghcr.io.
Также под Docker-контейнеризацию попали все сервисы, включая PostgreSQL базу, Icecast2, Liquidsoap, Prometheus и Grafana.
Результат:
Icecast2
Liquidsoap
Go
Python (использую модель для анализа BPM для треков в базе)
NodeJS (грабер треков с suno.ai с использованием капчи и Playwright)
ffmpeg (компиляция аудио)
PostgreSQL
Kubernetes
Sber Salute Speech TTS
ElevenLabs TTS
Cartesia TTS
Preact + Vite - Amplitude (аналитика)
Prometheus
Grafana
Frontend/TG Mini app
Сначала меня заинтересовала идея создать Telegram Mini App, которая всегда будет под рукой в Телеграме. Телеграм предоставляет удобный SDK, с помощью которого можно получить доступ к текущему пользователю, а также использовать его для авторизации пользователя на стороне бекенда. Так же, при сворачивании моего приложения, трансляция продолжала играть и мне это очень подходило. Немного времени спустя добавил поддержку PWA, натолкнувшись на статью на хабре "PWA - это просто"
Для веб-апп выбрал, кажется, стандартный стек: Preact + TypeScript + Vite. Бекенд разработан в общем распределённом монолите на Go с использованием Gorm и Gin.
Так как я совсем не дизайнер, эта часть оказалась для меня довольно сложной. Примерно неделю я пытался в Figma создать экраны, которые бы меня не бесили
Скриншоты экранов

Авторские права
Так как весь аудиоконтент сгенерирован нейросетями, эфир не подпадает под авторское право. В данный момент из живых голосов звучат только джинглы - их записали мои друзья и профессиональные дикторы.
С удивлением обнаружил, что четвёртая модель Suno.ai генерирует вполне слушабельную музыку, если убрать вокал. Особенно хорошо у неё получаются jazz, blues, lofi и rock.
Итог
Для всей инфраструктуры использовал две вируалки. На одной (в MSK) размещены вещание и сайт, на второй (в Амстердаме) - тяжелые фоновые задачи (TTS-запросы к провайдерам, склеивание новостей и подкастов, скачивание и обработка музыки с suno.ai). Амстердам выбран, потому что иностранные TTS-провайдеры ограничили доступ для СНГ ip-адресов.
Результат того, что у меня получилось, можно посмотреть здесь:
Также можно подписаться на бота (он же Telegram mini app), который будет отправлять аудио-сводки новостей по разным тематикам, такие как в шапке статьи в удобно время.
Если кто-то интересуется онлайн-вещанием, надеюсь, моя информация была полезной. Готов ответить на любые вопросы.
Спасибо!