Как стать автором
Обновить

Как я создал полностью автоматизированное онлайн радио с AI ведущими и музыкой

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров3.1K

Однажды мне захотелось изучить Kubernetes и мобильную разработку, но не знал, с чего начать. В то же время меня сильно тревожило отсутствие в интернете новостей без политической повестки и негативного окраса. Отовсюду лился поток манипулятивной информации, и я мечтал о фильтре, который бы отсеивал весь этот информационный шлак.

Это моя небольшая история, как я за месяц написал собственное медиа с полной автоматизацией, где роботы-ведущие заменили кожаных мешков и ведут подкасты, в которых шутят про AI

Спойлер: Короткая запись эфира


Я изучил самые естественные русскоязычные TTS-решения на рынке и остановился на ElevenLabs и SaluteSpeech от Сбера. Быстро собрал POC: озвучил несколько новостей, затем склеил их в один файл, добавив перебивки и фоновый звук с помощью ffmpeg.

Первая версия новостного блока

Результат меня очень порадовал и я вдохновился преступить к реализации собственного медиа с аудио контентом!

Стек радио

Для вещания был выбран icecast2 + liqudsoap. Техническая статья уже выходила на хабре с описанием настройки.

Iceast2 - это сервер потокового вещания. Его задача принимать потоки от источников и отдавать слушателям.

Liqudsoap - мощная среда для создания аудиопотоков, позволяющая гибко управлять контентом перед его трансляцией через Icecast2.

Liqudsoap заинтересовал меня тем, что он умеет:

  1. Динамически управлять очередью вещания через telnet. (Добавление, удаление, пропуск треков)

  2. Эквализацию и нормализацию звука. (Что бы эфир звучал с одинаковой громкостью на разных треках)

  3. Фолбэк на случай если эфирная очередь пустая. (Проигрывать заглушку)

Аудио

Радиоэфир состоит из множества аудиофайлов, которые необходимо где-то хранить. Изначально я планировал размещать статику на 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 рублей.

В итоге что в хранилище:

  • Музыка

  • Сгенерированные новостные блоки и подкасты

  • Джинглы

  • Сгенерированный прогноз погоды на день

  • Картинки для приложения

Новости

Как пополняется база новостей:

  1. Кроулер просматривает подписанные каналы в поисках нового контента

  2. Каждый пост проходит проверку через GPT/DeepSeek на отсутствие политики, рекламы, конкурсов и негативной окраски

  3. Оценивается новостная ценность поста

  4. Пост суммаризируется для удобного чтения: создаётся краткая версия и набор тегов. По тегам проверяется уникальность новости, поскольку многие издания публикуют одни и те же события. Тегирование помогает отсеивать дублирующиеся материалы.

  5. В финале 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 создать экраны, которые бы меня не бесили

Скриншоты экранов
Telegram Mini App
Telegram Mini App

Авторские права

Так как весь аудиоконтент сгенерирован нейросетями, эфир не подпадает под авторское право. В данный момент из живых голосов звучат только джинглы - их записали мои друзья и профессиональные дикторы.

С удивлением обнаружил, что четвёртая модель Suno.ai генерирует вполне слушабельную музыку, если убрать вокал. Особенно хорошо у неё получаются jazz, blues, lofi и rock.

Итог

Для всей инфраструктуры использовал две вируалки. На одной (в MSK) размещены вещание и сайт, на второй (в Амстердаме) - тяжелые фоновые задачи (TTS-запросы к провайдерам, склеивание новостей и подкастов, скачивание и обработка музыки с suno.ai). Амстердам выбран, потому что иностранные TTS-провайдеры ограничили доступ для СНГ ip-адресов.

Результат того, что у меня получилось, можно посмотреть здесь:

Также можно подписаться на бота (он же Telegram mini app), который будет отправлять аудио-сводки новостей по разным тематикам, такие как в шапке статьи в удобно время.

Если кто-то интересуется онлайн-вещанием, надеюсь, моя информация была полезной. Готов ответить на любые вопросы.

Спасибо!

Ссылки

Теги:
Хабы:
+37
Комментарии26

Публикации

Истории

Работа

DevOps инженер
31 вакансия

Ближайшие события

27 марта
Deckhouse Conf 2025
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань