namespace TeleWave.Domain.Broadcast; /// /// Блок ТВ-заставки канала: свой звук + своё оформление (цвета, опциональная фон-картинка) + набор /// подблоков () с разным текстом и правилом показа. Длительность заставки — по /// длине звука (выравнивается на сегмент при рендере). Общий для канала — только шрифт. /// /// Первый блок ( == 0) — дефолтный, не удаляется; если звук в нём не загружен, /// рендер синтезирует джингл по умолчанию. /// public class BumperTemplate { private readonly List _variants = []; public Guid Id { get; private set; } public Guid ChannelId { get; private set; } /// Порядковый номер (0 — дефолтный блок). Используется ротацией и как признак дефолта. public int Position { get; private set; } public string Name { get; private set; } = string.Empty; // ── Оформление блока (цвета — в нотации ffmpeg: 0xRRGGBB или имя) ── public string BackgroundColor { get; private set; } = DefaultBackgroundColor; public string BackgroundColor2 { get; private set; } = DefaultBackgroundColor2; public string AccentColor { get; private set; } = DefaultAccentColor; public string TextColor { get; private set; } = DefaultTextColor; /// Фон-картинка блока — ссылка на запись реестра изображений или null (тогда фон градиент/постер). public Guid? BackgroundImageId { get; private set; } /// Расширение загруженного звука (с точкой) или null — тогда синтезируется джингл. public string? AudioExtension { get; private set; } /// Длина загруженного звука в секундах (замер ffprobe) или null, если звука нет. public double? AudioDurationSeconds { get; private set; } /// Версия файлов блока (звук/фон). Входит в кэш-ключ рендера — замена файла пересобирает заставки. public int Revision { get; private set; } public DateTimeOffset CreatedAt { get; private set; } public const string DefaultBackgroundColor = "0x0b1020"; public const string DefaultBackgroundColor2 = "0x1e293b"; public const string DefaultAccentColor = "0x38bdf8"; public const string DefaultTextColor = "white"; public bool IsDefault => Position == 0; /// Подблоки (текст-варианты); порядок — по . public IReadOnlyList Variants => _variants; private const string DefaultVariantName = "Текст 1"; private BumperTemplate() { } internal static BumperTemplate Create(Guid channelId, int position, string name) { var template = new BumperTemplate { Id = Guid.NewGuid(), ChannelId = channelId, Position = position, Name = name, BackgroundColor = DefaultBackgroundColor, BackgroundColor2 = DefaultBackgroundColor2, AccentColor = DefaultAccentColor, TextColor = DefaultTextColor, BackgroundImageId = null, AudioExtension = null, AudioDurationSeconds = null, Revision = 0, CreatedAt = DateTimeOffset.UtcNow, }; // Дефолтный подблок «Сейчас/Далее», показывается на смене шоу. template._variants.Add( BumperTextVariant.Create(template.Id, 0, DefaultVariantName, BumperTrigger.OnShowChange) ); return template; } public BumperTextVariant AddVariant(string name) { var nextPosition = _variants.Count == 0 ? 0 : _variants.Max(v => v.Position) + 1; var variant = BumperTextVariant.Create(Id, nextPosition, name, BumperTrigger.OnShowChange); _variants.Add(variant); return variant; } public BumperTextVariant? FindVariant(Guid variantId) => _variants.FirstOrDefault(v => v.Id == variantId); /// Удалить подблок. Последний подблок удалить нельзя (нужен хотя бы один) — вернёт false. public bool RemoveVariant(Guid variantId) { if (_variants.Count <= 1) return false; var variant = _variants.FirstOrDefault(v => v.Id == variantId); if (variant is null) return false; _variants.Remove(variant); return true; } /// Обновить имя и цвета блока. Цвета — в нотации ffmpeg (0xRRGGBB или имя). public void UpdateStyle( string name, string backgroundColor, string backgroundColor2, string accentColor, string textColor ) { Name = name; BackgroundColor = backgroundColor; BackgroundColor2 = backgroundColor2; AccentColor = accentColor; TextColor = textColor; } /// Отметить загруженный звук (extension — с точкой, нижний регистр) и его длину. Меняет ревизию. public void SetAudio(string extension, double durationSeconds) { AudioExtension = extension; AudioDurationSeconds = durationSeconds > 0 ? durationSeconds : null; Revision++; } public void ClearAudio() { if (AudioExtension is null && AudioDurationSeconds is null) return; AudioExtension = null; AudioDurationSeconds = null; Revision++; } /// Привязать фон-картинку блока (ссылка на реестр изображений). Меняет ревизию. public void SetBackgroundImage(Guid imageId) { BackgroundImageId = imageId; Revision++; } public void ClearBackgroundImage() { if (BackgroundImageId is null) return; BackgroundImageId = null; Revision++; } }