← Гордеев Виталий · все кейсы
Кейс · в работе в клубе
Экраны и медиафасад · ночной клуб

Твой текст на этом экране

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

15–25 сот сообщения боту до экрана, замер 19.09.2026
3видеолинии: фасад 1920×576, танцпол 16:9, красный зал 4:3
22 + 4 содно сообщение на экране и пауза перед следующим
0людей в цепочке, модерация автоматическая
Кадр ролика для медиафасада 1920 на 576: программа будних дней, карточка «Признания на экране»
Фасад · 1920×576Ролик программы будних дней. Собирается кодом в Remotion из афиш, петля 15 секунд.
Красный зал · 4:3Заставка, пока очередь пуста: QR-код ведёт в бота.
01 · Путь сообщения

От телефона гостя
до экрана в зале

Сообщение проходит семь звеньев на трёх машинах. Каждое можно проверить отдельно, это меня и спасало при поломках.

PythonTelegram Bot APIGeminiffmpegRemotionReactResolume ArenaOSCRESTWindows-агенты

Ручной модерации нет принципиально: вся фишка в том, что сообщение появляется здесь и сейчас. Работу модератора делает правило «сомневаешься — откажи». Модель отклоняет всё, в чём не уверена, и гость может отправить другое.

На компьютеры в зале ставится агент: встроенный Python, установщик одним файлом, автозапуск при включении. По-другому нельзя, в клубе нет администратора, который будет что-то настраивать руками.

02 · Разбор

Пять поломок
на живом танцполе

Всё это вылезало в пятницу вечером при полном зале. Поэтому каждое исправление я сначала гонял на стенде и только потом выкатывал в клуб.

01

Два показа одного сообщения

Симптом
На одно сообщение гостя заводилось два видео, и оно показывалось дважды.
Причина
Служба и параллельный ручной прогон читали из очереди одну и ту же запись «ждёт показа». Проверка «свободна ли запись» и её захват были двумя разными шагами.
Захват одной операцией. Запись забирает тот, у кого обновление реально сработало, плюс файловая блокировка на тик. Прогнал четыре параллельных тика, дублей ноль.
идея решениясхема запроса, не дословный код
-- статус меняется только если запись всё ещё свободна;
-- число изменённых строк говорит, кто её получил
UPDATE facade_messages
   SET status = 'airing'
 WHERE id = ? AND status = 'queued';
-- rowcount = 1 → показываю я; 0 → уже забрал другой
02

Файл заменён, а на экране старый кадр

Симптом
Агент подменил видео на диске, отправил команду «подключить клип», а экран не изменился.
Причина
Resolume молча игнорирует повторное подключение ячейки, которая уже подключена. Ни ошибки, ни ответа.
Сброс и повторный выбор, как будто оператор кликнул по клипу мышкой. Сначала через REST, а если он недоступен, через OSC: «отпустить» и «нажать».
resolume_agent.py · дословно
def resolume_reconnect_clip(cfg, l, c) -> bool:
    """Тот же «клик по клипу» (сброс выбора + выбор заново), но для ЗАДАННОЙ ячейки."""
    base = "/composition/layers/%s/clips/%s/connect" % (l, c)
    if resolume_ping(cfg):
        try:
            _req(_resolume_url(cfg, base), "POST", {})
            log("клип %s/%s переподключён (REST)" % (l, c)); return True
        except Exception as e:
            log("reconnect %s/%s REST error: %s — пробую OSC" % (l, c, e))
    if not _truthy(cfg.get("RESOLUME_OSC_ENABLE", "true")):
        return False
    osc_send(cfg, base, 0)
    time.sleep(0.15)
    ok = osc_send(cfg, base, 1)
03

Часть сообщений не доходила до экрана

Симптом
Гость получал «принято», но на экране его сообщение так и не появлялось.
Причина
Окно показа было 20 секунд, а агент спрашивал сервер примерно раз в 30. Сообщение успевало устареть между двумя опросами.
Опрос каждые 8 секунд, окно показа 22 секунды. Проверил тремя сообщениями подряд, показались все.
правило, которое отсюда следует
# Период опроса обязан быть в несколько раз
# меньше, чем время жизни того, что опрашиваешь.
POLL_SEC   = 8     # было ~30
WINDOW_SEC = 22    # было 20
GAP_SEC    = 4
04

Проверка подписки пускала всех

Симптом
На экран попадали сообщения от тех, кто не подписан на канал клуба.
Причина
Бот не был администратором канала, и Telegram отвечал ему ошибкой. А код трактовал любую ошибку как «пропустить».
Бот назначен администратором, добавлена проверка вторым ботом. При сетевом сбое гость по-прежнему проходит, это я оставил специально.
что различаем теперь · схема
# «нельзя проверить из-за прав» ≠ «сеть моргнула»
if resp.ok:           allow = member_status in SUBSCRIBED
elif permission_error: allow = check_with_second_bot()
else:                 allow = True   # сбой сети — не наказываем гостя
05

Видимый шов на ролике фасада

Симптом
На стыке афиши и текста в ролике для фасада проступала вертикальная полоса.
Причина
Маска-растушёвка висела на картинке, которую ещё и увеличивали. После масштаба край маски уезжал за границу кадра, и яркость на стыке скакала с 8 до 24.
Маску повесил на обёртку без трансформаций, а увеличение и размытие оставил на картинке внутри. Шов исчез, растушёвка теперь задаётся в пикселях.
FacadeWeek.tsx · дословно
<div style={{ position: 'absolute', left: BED_X, top: 0, width: W - BED_X, height: H,
              overflow: 'hidden', opacity: vis }}>
  <div style={{ position: 'absolute', inset: 0, overflow: 'hidden',
                maskImage: BED_FEATHER, WebkitMaskImage: BED_FEATHER }}>
    <Img
      key={'bed-' + card.image}
      src={staticFile(card.image)}
      style={{ position: 'absolute', left: 0, top: 0, width: '100%', height: '100%',
               objectFit: 'cover', filter: 'blur(54px) saturate(1.3) brightness(0.8)',
               transform: 'scale(1.35)' }}
    />
  </div>
</div>
03 · Правила

Что я забрал
из этого проекта

Система, которая работает в зале с людьми, прощает меньше, чем сайт: ошибку сразу видят сотни человек.

01

Сомневаешься — откажи

Без модератора безопасно только одно: строгий фильтр. Гость переживёт отказ и отправит что-то другое, а неприемлемое сообщение на весь зал уже не вернёшь.

02

У команды есть адрес

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

03

Из очереди брать только захватом

Если одну задачу могут взять две службы, захватывать её надо одной атомарной операцией. Схема «проверил и взял» тут не работает.

04

Снимать баннер по дате события

Реклама вечеринки уходит с фасада утром после последнего дня события. Если снимать через сутки после загрузки, недельный баннер пропадёт раньше времени.

Нужно подружить бота с залом?

Свяжу бота, сервер и оборудование в зале так, чтобы всё работало без оператора. Работаю удалённо, из Самары.

Telegram · @vitaldemort