ГлавнаяБлогСоздание Lo-Fi радиостанции с ИИ на Python и AWS
AI / Нейросети

Создание Lo-Fi радиостанции с ИИ на Python и AWS

Узнайте, как создать Lo-Fi радиостанцию с генерацией сцен, музыки и видео с помощью Python, AWS и ИИ. Пошаговое руководство для разработчиков.

Al
Редакция Algolitalgolit.ru
12 мин чтения26 августа 2026 г.

Создание Lo-Fi радиостанции с ИИ: от идеи до реализации

Вы когда-нибудь замечали, как в потоке кода время летит незаметно? Музыка часто помогает войти в это состояние. Я создал собственный Lo-Fi сервер под названием Compile & Chill, который генерирует сцены, музыку и анимации с помощью ИИ. В этой статье вы узнаете, как построить такую радиостанцию с нуля, используя Python, AWS и современные ИИ-сервисы.

Архитектура проекта и необходимые инструменты

Проект состоит из трёх радиостанций, каждая из которых генерирует сцену 16:9 через Amazon Bedrock, создаёт инструментальный цикл через ElevenLabs и превращает иллюстрацию в шестисекундное видео через OpenRouter. Все файлы хранятся в приватном Amazon S3 и возвращаются в браузер через Nuxt-сервер. Для управления добавлен интерфейс Stream Deck API.

Для полной сборки вам понадобятся:

  • Node.js 22.19 или новее (Nuxt 4.5.2 требует Node 22.19+).
  • npm 10 или новее.
  • Аккаунт AWS и настроенный профиль AWS CLI.
  • AWS SAM CLI для приватного хранилища.
  • Доступ к Stability AI Stable Image Ultra через Amazon Bedrock в us-west-2.
  • Ключ API ElevenLabs для генерации музыки.
  • Ключ API OpenRouter для анимированных сцен.

Учётные данные провайдеров необязательны — без них интерфейс, встроенная сцена, переключение станций, плеер и таймер Focus Block всё равно работают.

Для безопасности используйте роль или профиль с ограниченными правами: bedrock:InvokeModel, а также разрешения на S3-бакет: s3:GetObject, s3:PutObject, s3:DeleteObject, s3:DeleteObjectVersion и s3:ListBucketVersions.

Шаг 1: Запуск станции без учётных данных

Склонируйте репозиторий и установите зависимости:

git clone https://github.com/ErikCH/compile-and-chill.git
cd compile-and-chill
npm ci
cp .env.example .env
npm run dev

Откройте http://127.0.0.1:8231 — вы увидите интерфейс станции с встроенной сценой.

Файл окружения разделяет функции:

ELEVENLABS_API_KEY=
BEDROCK_IMAGE_REGION=us-west-2
BEDROCK_IMAGE_MODEL_ID=stability.stable-image-ultra-v1:1
AWS_PROFILE=default
STATION_S3_BUCKET=
STATION_S3_REGION=us-west-2
OPENROUTER_API_KEY=
STATION_BIND_HOST=127.0.0.1
NUXT_CONTROL_TOKEN=

Не добавляйте NUXT_PUBLIC_ к этим именам — Nuxt раскрывает публичную конфигурацию в браузерном коде. Держите STATION_BIND_HOST на loopback, если не нужен удалённый доступ. Для Stream Deck с другой машины привяжите к адресу приватной VPN, никогда к 0.0.0.0 или публичному IP, и установите длинный случайный NUXT_CONTROL_TOKEN.

Шаг 2: Проксирование вызовов через серверные маршруты Nuxt

Браузер не должен напрямую обращаться к провайдерам. В Compile & Chill учётные данные хранятся в runtimeConfig, а код провайдеров — в server/api/ и server/utils/.

Фрагмент nuxt.config.ts:

export default defineNuxtConfig({
  runtimeConfig: {
    elevenLabsApiKey: process.env.ELEVENLABS_API_KEY || '',
    imageGenerationRegion: process.env.BEDROCK_IMAGE_REGION || 'us-west-2',
    imageGenerationModel: process.env.BEDROCK_IMAGE_MODEL_ID || 'stability.stable-image-ultra-v1:1',
    stationStorageBucket: process.env.STATION_S3_BUCKET || '',
    stationStorageRegion: process.env.STATION_S3_REGION || 'us-west-2',
    openRouterApiKey: process.env.OPENROUTER_API_KEY || '',
  },
})

Этот барьер позволяет централизованно проверять запросы, ограничивать платные параметры, редактировать подписанные URL и преобразовывать ошибки провайдеров в полезные HTTP-ответы.

Шаг 3: Генерация сцены с Amazon Bedrock

Сначала аутентифицируйтесь с помощью AWS CLI:

aws sso login --profile your-profile

Добавьте профиль и настройки модели в .env:

AWS_PROFILE=your-profile
BEDROCK_IMAGE_REGION=us-west-2
BEDROCK_IMAGE_MODEL_ID=stability.stable-image-ultra-v1:1

Маршрут изображения проверяет режим станции, визуальный стиль, представление разработчика и опциональное зерно. Затем вызывает generateStationScene() в server/utils/station-generation.ts.

Полезная нагрузка провайдера выглядит так:

const payload = {
  prompt: buildScenePrompt(mode, direction, { visualStyle, developer, diversitySeed: seed }),
  negative_prompt: [
    'logos, readable text, watermark, rear view, motion blur, extra fingers',
    visualStyle === 'illustrated' ? 'photograph, live action, photorealistic skin, 3D render' : 'cartoon, anime, cel shading, flat illustration'
  ].join(', '),
  mode: 'text-to-image',
  aspect_ratio: '16:9',
  output_format: 'png',
  seed,
}

const response = await client.send(new InvokeModelCommand({
  modelId: config.imageGenerationModel,
  contentType: 'application/json',
  accept: 'application/json',
  body: JSON.stringify(payload),
}))

Сервер нормализует зерно перед созданием запроса, так как Number(undefined) становится NaN, а JSON сериализует NaN как null. Stable Image Ultra ожидает целое число.

Промпт связывает визуальный стиль с типом вывода: статические сцены используют реалистичный стиль, анимированные — явно иллюстрированный 2D, чтобы источник не был фотореалистичным.

Проверка: перезапустите приложение, откройте Visual settings, выберите Realistic static и сгенерируйте одну сцену. Если модель недоступна в настроенном регионе, маршрут вернёт ошибку провайдера и идентификатор модели.

Шаг 4: Создание зацикленной музыки с ElevenLabs

Интерфейс отправляет длительность 60 секунд, а сервер должен отклонять или нормализовать нечисловой ввод перед применением диапазона 10–60 секунд. Это защищает от отправки null провайдеру:

const requestedDuration = Number(input.durationSeconds ?? 60)
const durationSeconds = Number.isFinite(requestedDuration) ? Math.min(60, Math.max(10, requestedDuration)) : 60

const response = await fetch('https://api.elevenlabs.io/v1/music?output_format=mp3_44100_128', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'xi-api-key': config.elevenLabsApiKey,
  },
  body: JSON.stringify({
    prompt: buildMusicPrompt(input.mode, input.direction),
    music_length_ms: durationSeconds * 1000,
    model_id: 'music_v2',
    generation_mode: 'loop',
    force_instrumental: true,
    sign_with_c2pa: true,
  }),
})

generation_mode: 'loop' — одна из частей, делающих повтор естественным. Промпт также просит без интро, завершающей каденции или затухания. Трек, написанный как обычная песня, будет звучать сломанно при переходе от конца к первому биту.

Compile & Chill хранит один сгенерированный трек на каждый режим станции. Переключение между Deep Work и Rainy Debug повторно использует существующий трек вместо нового платного запроса.

Проверка: добавьте ELEVENLABS_API_KEY в .env, перезапустите сервер и сгенерируйте один 60-секундный трек. Переключите режимы и убедитесь, что возврат к исходному режиму использует его аудио.

Шаг 5: Развёртывание приватной медиабиблиотеки

Репозиторий включает AWS SAM шаблон, управляемый CloudFormation. Проверьте и разверните:

sam validate --template-file infra/compile-and-chill-private-station.yaml --region us-west-2

sam deploy --region us-west-2 --stack-name compile-and-chill-private-station --template-file infra/compile-and-chill-private-station.yaml --no-confirm-changeset --no-fail-on-empty-changeset

Получите имя медиа-бакета:

aws cloudformation describe-stacks --region us-west-2 --stack-name compile-and-chill-private-station --query "Stacks[0].Outputs[?OutputKey=='StationMediaBucketName'].OutputValue" --output text

Скопируйте значение в .env:

STATION_S3_BUCKET=YOUR_STACK_OUTPUT
STATION_S3_REGION=us-west-2

Стек создаёт два версионированных бакета: один для сгенерированных медиа, другой для журналов данных CloudTrail, зашифрованных KMS. CloudWatch-аларм отслеживает запросы на удаление в медиа-бакете.

Приложение назначает каждому браузерному профилю случайный station ID в HttpOnly, SameSite cookie. Объекты хранятся в структуре:

stations/<station-id>/manifest.json
stations/<station-id>/<mode>/scene.png
stations/<station-id>/<mode>/scene.mp4
stations/<station-id>/<mode>/music.mp3

Браузер получает same-origin URL, например /api/library/assets/deepWork/scene. Маршрут читает объект S3 в память сервера и возвращает его. Текущая реализация — прокси, а не потоковая передача, поэтому учитывайте память при увеличении лимита видео 50 МиБ.

Проверка: перегенерируйте сцену или трек, перезагрузите страницу в том же браузерном профиле и убедитесь, что медиа возвращается. Откройте окно в приватном режиме — там будет отдельная станция.

Шаг 6: Превращение иллюстрации в бесшовный видеоцикл

Анимированный режим генерирует свежую иллюстрированную сцену через Amazon Bedrock, сохраняет её в S3 и создаёт 300-секундный подписанный URL. OpenRouter нужен этот временный URL, так как его видео-провайдер должен загрузить входное изображение.

Хитрость в том, чтобы отправить один и тот же якорь как ограничения первого и последнего кадра:

body: JSON.stringify({
  model: modelId,
  duration: 6,
  resolution: '720p',
  aspect_ratio: '16:9',
  generate_audio: false,
  prompt: [
    'Animate this 2D cartoon illustration of a fictional character.',
    'Use a locked-off camera and keep the illustrated composition unchanged.',
    'The provided illustration is both the first and last frame.',
    'Allow only tiny two-hand typing motion and one gentle blink.',
    'No zoom, pan, cut, identity change, new objects, or geometry changes.',
    'Create a silent seamless six-second ambient loop.'
  ].join(' '),
  frame_images: [
    { type: 'image_url', image_url: { url: sourceUrl }, frame_type: 'first_frame' },
    { type: 'image_url', image_url: { url: sourceUrl }, frame_type: 'last_frame' }
  ],
})

Модель может отойти от якоря, но ограничения кадров требуют возврата к той же композиции в конце. Промпт ограничивает движение небольшим движением рук и одним морганием, без движения камеры, перекадрирования, склеек, новых объектов или изменения личности.

Приложение определяет совместимые видео-модели в рантайме. Кандидат должен поддерживать 6 секунд, 720p, first_frame и last_frame. Предпочтительна Seedance 2.0 Fast, затем проверяется короткий список запасных.

Политика отказов намеренно узкая:

  • HTTP 402 — стоп, так как кредиты недоступны.
  • HTTP 429 — стоп, так как аккаунт ограничен по скорости.
  • Отказ модерации входного изображения может попробовать следующую совместимую модель, так как видео-задание не создано.
  • Другие ошибки останавливают, чтобы не рисковать дублирующим платным заданием.

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

Шаг 7: Отслеживание реального прогресса во время длинного запроса

Длинные запросы к ИИ-провайдерам могут занимать несколько минут. Чтобы пользователь не думал, что приложение зависло, реализуйте прогресс через Server-Sent Events или WebSocket. Например, при генерации видео можно отправлять события о статусе: started, image_ready, video_started, video_completed. Это улучшает UX и позволяет отображать индикатор прогресса.

Практические выводы

Теперь вы знаете, как создать собственную Lo-Fi радиостанцию с ИИ. Начните с клонирования репозитория, запустите базовую версию без ключей, затем постепенно добавляйте генерацию сцен, музыки и видео. Не забывайте про безопасность: храните ключи на сервере, ограничивайте права AWS и используйте подписанные URL.

Попробуйте прямо сейчас: склонируйте проект, запустите npm run dev и поэкспериментируйте с настройками визуального стиля. Затем добавьте свой ключ ElevenLabs и сгенерируйте первый трек. Удачи в творчестве!

#Lo-Fi радиостанция#генерация музыки#Amazon Bedrock#ElevenLabs#OpenRouter
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →