Implement BumperEndpoints and remove deprecated bumper-related functionality
Added new BumperEndpoints to the API for managing bumper templates and variants, enhancing the channel management capabilities. Removed outdated bumper-related commands and handlers from the application, streamlining the codebase and improving maintainability. Updated ChannelEndpoints to reflect these changes and ensure proper routing for the new endpoints.
This commit is contained in:
@@ -410,32 +410,60 @@ SlotState
|
||||
```
|
||||
JunctionTemplate
|
||||
id
|
||||
channelId
|
||||
name — «Прайм», «День», «Ночь», «Внутри блока»
|
||||
maxTotalSeconds int? — потолок длины стыка целиком
|
||||
elements [JunctionElement]
|
||||
|
||||
JunctionElement
|
||||
position int
|
||||
position int — порядок показа в эфире
|
||||
kind enum — ad | promo | bumper | filler
|
||||
groupId uuid? — для ad/promo/filler: откуда брать
|
||||
bumperTemplateId uuid? — для bumper: какой блок заставки
|
||||
bumperVariantId uuid? — для bumper: конкретный подблок; null — по триггеру и весам
|
||||
amountMode enum — count | duration
|
||||
amountValue int — единиц либо минут
|
||||
isRequired bool — нельзя выбросить при нехватке времени
|
||||
choiceKey string? — метка развилки: из врезок с одной меткой играет одна
|
||||
choiceWeight int — вес внутри развилки
|
||||
conditions jsonb
|
||||
```
|
||||
|
||||
**Стык общий для всех каналов**, как группа. Своя цепочка врезок у каждого канала — это ровно та
|
||||
ошибка, из-за которой копирование сетки сопоставляло заставки по имени и рапортовало о потерянных
|
||||
ссылках. «Рекламный блок на две минуты с заставкой в конце» — такой же переиспользуемый ресурс,
|
||||
как «Боевики 90-х»; канальным остаётся только выбор, какой стык поставить в слот.
|
||||
|
||||
**Порядок показа — это `position`, и только он.** Обязательность (`isRequired`) участвует
|
||||
исключительно в отборе «кого выбросить, если до якоря не влезает»: сначала считается, что помещается,
|
||||
потом уцелевшее играет в исходном порядке. Иначе галочка «обязательно» молча поднимала бы рекламу
|
||||
перед заставкой, и цепочка в редакторе перестала бы соответствовать эфиру.
|
||||
|
||||
```json
|
||||
{
|
||||
"onlyOnElementChange": true,
|
||||
"minMinutesSinceSameKind": 30,
|
||||
"dayparts": ["prime", "day"]
|
||||
"minMinutesBetween": 30,
|
||||
"dayparts": ["prime", "day"],
|
||||
"timeWindow": { "from": "20:00", "to": "23:00" },
|
||||
"chance": 40
|
||||
}
|
||||
```
|
||||
|
||||
Условия — **структурированные поля, а не выражения-строки**: выражения потребовали бы парсера,
|
||||
валидации и отдельного UI, а покрывают те же три-четыре реальных случая.
|
||||
|
||||
`chance` — вероятность показа врезки в процентах, самый дешёвый источник разнообразия: «в 40%
|
||||
стыков ставим анонс». `minMinutesBetween` считается **по конкретной врезке**, а не по её виду:
|
||||
две рекламные врезки в разных стыках — это разные ограничения, общий счётчик на вид склеил бы их.
|
||||
|
||||
**Развилка** (`choiceKey`) — несколько врезок с одной меткой, из которых играет одна, выбранная по
|
||||
весам: «иногда заставка, иногда короткий рекламный блок». Врезки одной развилки обязаны занимать
|
||||
непрерывный отрезок позиций (иначе неясно, куда встаёт выбранная), обязательность и условия
|
||||
относятся к развилке целиком.
|
||||
|
||||
Оба жребия — `chance` и выбор внутри развилки — берутся из seed генерации (4.4), а не из живого
|
||||
`Random`. Иначе пересборка хвоста тасовала бы врезки на каждое применение, и диф из 6.6 показывал бы
|
||||
изменения там, где ничего не менялось.
|
||||
|
||||
**Рекламные ролики — это тоже группы.** Ролик регистрируется как `Show(Kind = Interstitial)` — новое
|
||||
значение `ShowKind` — и складывается в группу. Это убирает `ChannelAd` и бесплатно даёт рекламе всё,
|
||||
что есть у контента: остывание (не крутить один ролик дважды подряд), разные группы на утро и прайм,
|
||||
@@ -461,17 +489,100 @@ JunctionElement
|
||||
и коллекция при этом всегда входит целиком — блок не разрезается. Для смешанных групп правильный
|
||||
режим — `duration`.
|
||||
|
||||
**Заставки-переходы переиспользуются как есть.** `BumperTemplate` / `BumperTextVariant`, рендер через
|
||||
ffmpeg с кэшем по паре шоу, `ScheduleBumperResolver` — вся эта подсистема не меняется, меняется только
|
||||
точка вызова: раньше вероятности и минимальный интервал жили на канале, теперь это `conditions`
|
||||
элемента стыка. Настройки `Channel.BumperSelection` / `NextBumperIndex` / `BumperMinIntervalMinutes` /
|
||||
`BumperShowChangeChance` / `BumperEpisodeChangeChance` уходят, `BumperFont` остаётся общим для канала.
|
||||
**Заставка-переход — это врезка стыка**, а не настройка канала: `Channel.BumperSelection` /
|
||||
`NextBumperIndex` / `BumperMinIntervalMinutes` / `BumperShowChangeChance` /
|
||||
`BumperEpisodeChangeChance` / `BumpersEnabled` уходят с канала, вероятность и интервал выражаются
|
||||
`conditions`, а выбор подблока — полем `bumperVariantId` врезки. Сама подсистема заставок описана
|
||||
в 3.7.1.
|
||||
|
||||
Важно для пайплайна: ассет заставки зависит от **пары соседей**, а пара известна только после наполнения
|
||||
слотов. Значит рендер — обязательный шаг между сборкой ленты и записью в БД, он долгий и может упасть.
|
||||
В предпросмотре заставка показывается плейсхолдером известной длины, реальный рендер — только при
|
||||
применении.
|
||||
|
||||
### 3.7.1. Заставки: блок, подблок, текст
|
||||
|
||||
Блок заставки (`BumperTemplate`) — это оформление и звук: палитра, фон-картинка, шрифт, джингл.
|
||||
Подблок (`BumperTextVariant`) — то, что на нём написано, плюс правило показа и вес. Как и стык,
|
||||
**оба общие для всех каналов**: файлы звука и фона и так лежат по `templateId`, канал в них
|
||||
не участвовал никогда.
|
||||
|
||||
```
|
||||
BumperTemplate — оформление и звук, общий
|
||||
id
|
||||
name
|
||||
font enum — sans | serif (было на канале)
|
||||
backgroundColor, backgroundColor2, accentColor, textColor
|
||||
backgroundImageId uuid?
|
||||
audioExtension, audioDurationSeconds
|
||||
revision int — версия файлов; входит в сигнатуру рендера
|
||||
variants [BumperTextVariant]
|
||||
|
||||
BumperTextVariant
|
||||
position, name
|
||||
trigger enum — onShowChange | betweenEpisodes | both
|
||||
weight int
|
||||
background enum — template | nextPoster | nowPoster
|
||||
lines [BumperLine]
|
||||
|
||||
BumperLine
|
||||
position int
|
||||
style enum — label | title | caption
|
||||
color enum — accent | text
|
||||
text string — с плейсхолдерами
|
||||
```
|
||||
|
||||
Шрифт переезжает с канала на блок не ради симметрии: он не входил в сигнатуру кэша, и его смена
|
||||
не пересобирала уже отрендеренные заставки. На общем блоке эта дыра стала бы видимой сразу — два
|
||||
канала с разным шрифтом делили бы один ассет.
|
||||
|
||||
**Текст — список строк, а не два фиксированных режима.** Прежние `NowNext` (две подписи + названия
|
||||
шоу) и `Free` (две произвольные строки) схлопываются: «СЕЙЧАС / {now.title} / ДАЛЕЕ / {next.title}» —
|
||||
это просто четыре строки, и оно же превращается в «ДАЛЕЕ В 21:30 / {next.title}» правкой текста,
|
||||
а не переключением режима. Готовые наборы строк вставляются пресетами — это данные редактора,
|
||||
не сущность.
|
||||
|
||||
Источник фона становится явным полем подблока. Раньше постер шоу подставлялся молча и только
|
||||
в режиме `NowNext`; при свободных строках такой связи взяться неоткуда.
|
||||
|
||||
**Плейсхолдеры** подставляются в момент планирования — там уже известны канал, пара соседей
|
||||
и точное время врезки:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `{channel}` `{channel.number}` | канал |
|
||||
| `{now.title}` `{next.title}` | шоу до и после стыка |
|
||||
| `{now.episode}` `{next.episode}` | «с5э12» либо название серии |
|
||||
| `{next.year}` `{next.genre}` | из метаданных |
|
||||
| `{next.time}` | во сколько начнётся следующая программа |
|
||||
| `{time}` `{date}` `{weekday}` | момент показа заставки во времени канала |
|
||||
| `{slot}` | название слота — «Вечернее кино» |
|
||||
|
||||
Неизвестное значение подставляется пустым, строка со схлопнувшимися пробелами не рисуется.
|
||||
Неизвестный плейсхолдер — **ошибка валидации при сохранении**, а не сюрприз в эфире.
|
||||
|
||||
Цена гибкости — кэш: `{time}` и `{date}` делают каждый показ уникальным, а рендер это ffmpeg
|
||||
на несколько секунд, помноженный на недельный горизонт. Запрещать нечего, но редактор обязан
|
||||
предупреждать у таких полей, а предпросмотр — показывать, сколько новых рендеров потребуется.
|
||||
`{next.time}` при работающих якорях и `snapToMinutes` даёт круглые времена и кэшируется нормально.
|
||||
|
||||
**Кэш ассетов ключуется содержимым, а не ссылками.**
|
||||
|
||||
```
|
||||
BumperAsset
|
||||
id
|
||||
signature — sha256(templateId | revision | variantId | подставленные строки | фон | posterShowId)
|
||||
templateId, variantId — оформление рендера и чистка осиротевших
|
||||
renderedLinesJson — уже подставленный текст
|
||||
posterShowId uuid? — если фоном стоит постер шоу
|
||||
mediaAssetId
|
||||
```
|
||||
|
||||
`channelId` и пара шоу из ключа уходят. Так одинаковая заставка на трёх каналах рендерится один раз,
|
||||
а `{channel}` в тексте разводит их по разным сигнатурам сам собой — без единого спецправила.
|
||||
Подставленный текст приходится хранить: время показа из ссылок задним числом не восстанавливается,
|
||||
а фоновый рендерер запускается уже после того, как лента записана.
|
||||
|
||||
### 3.8. Правила
|
||||
|
||||
Делятся на два вида по способу применения — это принципиально, потому что определяет, ломается ли
|
||||
@@ -733,15 +844,39 @@ seed = hash(channelId, date, slotId, occurrenceInDay)
|
||||
|
||||
### 6.3. Редактор стыка
|
||||
|
||||
Стыки и блоки заставок общие, поэтому живут не на экране канала, а своим разделом админки — рядом
|
||||
с группами и роликами. На канале остаётся выбор: какой стык поставить в слот и какой считать
|
||||
стыком по умолчанию, с теми же секциями списка, что у групп («используется в этом канале» →
|
||||
«все» + поиск) и счётчиком «используется в 3 каналах» у каждого.
|
||||
|
||||
Горизонтальная цепочка с перетаскиванием:
|
||||
|
||||
```
|
||||
[КОНЕЦ] → [Реклама ×2] → [Заставка] → [Промо ×1] → [НАЧАЛО]
|
||||
обязательно если смена если прайм
|
||||
[КОНЕЦ] → [Реклама ×2] → [Заставка] → ⌥[Промо ×1 | Реклама 30с] → [НАЧАЛО]
|
||||
обязательно если смена развилка 70/30
|
||||
```
|
||||
|
||||
Под цепочкой — линейка суммарной длительности. Клик по элементу — параметры (тип, группа, количество,
|
||||
обязательность, условия).
|
||||
Под цепочкой — линейка суммарной длительности с потолком стыка. Клик по элементу — параметры (тип,
|
||||
источник, количество, обязательность, условия). Развилка рисуется одной стопкой: врезки внутри
|
||||
переставляются вместе, а веса показаны процентами прямо на цепочке — иначе «иногда так, иногда
|
||||
эдак» невозможно прочитать, не открывая каждый элемент.
|
||||
|
||||
Перетаскивание элемента внутрь развилки и наружу — тем же drag & drop, что и переупорядочивание:
|
||||
отдельной кнопки «сгруппировать» нет, метка развилки проставляется самим перетаскиванием.
|
||||
|
||||
### 6.3.1. Редактор заставки
|
||||
|
||||
Двухпанельный: слева строки, справа — постоянный предпросмотр кадра, который перерисовывается
|
||||
на каждый ввод (то же оформление, что даёт ffmpeg, но нарисованное в браузере — ждать рендера
|
||||
ради проверки опечатки нельзя). Полный ffmpeg-рендер остаётся кнопкой и играется плеером.
|
||||
|
||||
- строка — это `[стиль ▾] [цвет ▾] [текст]`, порядок перетаскиванием, добавление кнопкой;
|
||||
- палитра плейсхолдеров под полем: клик вставляет в позицию курсора, наведение показывает пример
|
||||
подстановки; в самом поле плейсхолдеры подсвечены;
|
||||
- пресеты («Сейчас / Далее», «Далее в …», «Логотип канала») — кнопка, заполняющая строки;
|
||||
- у полей с `{time}`/`{date}` — предупреждение о том, что кэш перестаёт работать;
|
||||
- образцы подстановки берутся из реального канала, выбранного тут же: заставка общая, но
|
||||
посмотреть её надо глазами конкретного канала.
|
||||
|
||||
### 6.4. Предпросмотр
|
||||
|
||||
@@ -980,9 +1115,10 @@ drag & drop в календаре, переключатель даты.
|
||||
|
||||
**Меняется:**
|
||||
|
||||
- `Channel` худеет до `id / name / slug / isEnabled / epochUtc / fillerAssetId / bumperFont`
|
||||
- `Channel` худеет до `id / name / slug / isEnabled / epochUtc / fillerAssetId`
|
||||
плюс новые `utcOffsetMinutes`, `dayStartTime`, `templateId`, `number` и настройки зрительской
|
||||
части (`logoImageId`, `showClock`, `analogFilterStrength`) — см. 6.8.
|
||||
части (`logoImageId`, `showClock`, `analogFilterStrength`) — см. 6.8. Заставок на канале
|
||||
не остаётся вовсе: блоки общие, шрифт переехал на блок, условия показа — в стык (3.7.1).
|
||||
- `ScheduleEntry` — новые `collectionId`, `slotId`, `trace`, расширенный `kind`.
|
||||
- `SchedulerOptions.RetentionHours` → `RetentionDays` (дефолт 90) — история нужна для остывания
|
||||
и для слотов `repeat`.
|
||||
|
||||
Reference in New Issue
Block a user