Brand logo: one locked image layer per scene above the background, image from App Settings

- Source.IsBrandLogo persisted (additive migration); layer healed on load, removal blocked
- Image stored as an asset; settings keep the asset id and file name
- App Settings: Brand Logo file field with Browse
- Default placement top-left; per-scene eye uses the existing element visibility
Test: BrandLogoTests; layout/background tests pass (34). Not pushed.
This commit is contained in:
2026-10-27 09:25:49 -07:00
parent b86b6bb601
commit 841c905c06
12 changed files with 194 additions and 7 deletions
+2 -1
View File
@@ -102,7 +102,7 @@ public partial class LayoutStore : IDisposable
ChatShowTimestamps, ChatShowBadges, ChatMemberColor, ChatSuperChatColor,
WebUri, MediaPath, MediaIsLooping, MediaVolume, MediaPlaybackState,
AlertVideoAssetId, AlertVideoPath, AlertUseDefaultVideo, AlertShowTicker, AlertVideoVolume, SortOrder,
AlertDisplayMethod
AlertDisplayMethod, IsBrandLogo
FROM Source ORDER BY SortOrder
""";
using var reader = cmd.ExecuteReader();
@@ -154,6 +154,7 @@ public partial class LayoutStore : IDisposable
AlertShowTicker = reader.IsDBNull(33) ? true : reader.GetInt32(33) != 0,
AlertVideoVolume = reader.IsDBNull(34) ? 1.0 : reader.GetDouble(34),
AlertDisplayMethod = reader.IsDBNull(36) ? AlertDisplayMethod.TickerScroll : (AlertDisplayMethod)reader.GetInt32(36),
IsBrandLogo = reader.GetInt32(reader.GetOrdinal("IsBrandLogo")) != 0,
};
if (!sourcesByScene.TryGetValue(sceneId, out var list))
sourcesByScene[sceneId] = list = new List<(Source, int)>();
+7
View File
@@ -199,6 +199,13 @@ public partial class LayoutStore : IDisposable
cmd.ExecuteNonQuery();
}
if (!columns.Contains("IsBrandLogo"))
{
using var cmd = _connection.CreateCommand();
cmd.CommandText = "ALTER TABLE Source ADD COLUMN IsBrandLogo INTEGER NOT NULL DEFAULT 0;";
cmd.ExecuteNonQuery();
}
if (!columns.Contains("CaptureKey"))
{
using var cmd = _connection.CreateCommand();
+4 -2
View File
@@ -92,7 +92,7 @@ public partial class LayoutStore : IDisposable
ChatShowTimestamps, ChatShowBadges, ChatMemberColor, ChatSuperChatColor,
WebUri, MediaPath, MediaIsLooping, MediaVolume, MediaPlaybackState,
AlertVideoAssetId, AlertVideoPath, AlertUseDefaultVideo, AlertShowTicker, AlertVideoVolume,
AlertDisplayMethod)
AlertDisplayMethod, IsBrandLogo)
VALUES ($id, $sceneId, $assetId, $type, $name, $isEnabled,
$x, $y, $w, $h, $opacity, $monitor,
$clip, $mirrored, $isBackground, $captureKey, $showDesktop, $sort,
@@ -100,7 +100,7 @@ public partial class LayoutStore : IDisposable
$chatTimestamps, $chatBadges, $chatMemberColor, $chatSuperChatColor,
$webUri, $mediaPath, $mediaIsLooping, $mediaVolume, $mediaPlaybackState,
$alertVideoAssetId, $alertVideoPath, $alertUseDefaultVideo, $alertShowTicker, $alertVideoVolume,
$alertDisplayMethod)
$alertDisplayMethod, $isBrandLogo)
""";
cmd.Transaction = tx;
var idP = cmd.Parameters.Add("$id", SqliteType.Text);
@@ -140,6 +140,7 @@ public partial class LayoutStore : IDisposable
var alertShowTickerP = cmd.Parameters.Add("$alertShowTicker", SqliteType.Integer);
var alertVideoVolumeP = cmd.Parameters.Add("$alertVideoVolume", SqliteType.Real);
var alertDisplayMethodP = cmd.Parameters.Add("$alertDisplayMethod", SqliteType.Integer);
var isBrandLogoP = cmd.Parameters.Add("$isBrandLogo", SqliteType.Integer);
// Source and WebcamSceneConfig share ONE z-order space per scene:
// each element gets its index within scene.Elements regardless of
@@ -188,6 +189,7 @@ public partial class LayoutStore : IDisposable
alertShowTickerP.Value = source.AlertShowTicker ? 1 : 0;
alertVideoVolumeP.Value = source.AlertVideoVolume;
alertDisplayMethodP.Value = (int)source.AlertDisplayMethod;
isBrandLogoP.Value = source.IsBrandLogo ? 1 : 0;
cmd.ExecuteNonQuery();
}
}
+16
View File
@@ -348,4 +348,20 @@ public partial class LayoutStore : IDisposable
/// <summary>The stored on-air image's Asset id, or null when never stamped.</summary>
public string? GetOnAirImageId() => GetSetting(OnAirImageKey);
private const string BrandLogoAssetKey = "BrandLogoAssetId";
private const string BrandLogoNameKey = "BrandLogoFileName";
public string? GetBrandLogoAssetId() => GetSetting(BrandLogoAssetKey);
public string? GetBrandLogoFileName() => GetSetting(BrandLogoNameKey);
/// <summary>Stores the brand logo image as an asset and records its file name for display.</summary>
public string SetBrandLogo(byte[] data, string fileName)
{
var id = UpsertAsset(data, 0, 0);
UpsertSetting(BrandLogoAssetKey, id);
UpsertSetting(BrandLogoNameKey, fileName);
return id;
}
}