TASK 64 slices D and E: the feature is usable end to end. - Add a video file → one frame is probed (new `IChromaProbe`/`FfmpegChromaProbe`, one ffmpeg pass at 160x90, off the UI thread) and the layer gets an offer: "Looks like a green screen — Remove it", with the measured colour as a swatch. - Accepting it turns the offer into settings (Amount / Softness / Fringe) that reach ffmpeg as `colorkey` + `despill` + `format=bgra`, so the key is computed in the pipe and both consumers (the shared preview bitmap and the compositor's frame) carry the same alpha. - Media sessions are now keyed by **path + look** (`MainViewModel.MediaSessionKey`), so two layers with the same settings still share one decoder, and changing the look releases the old session and starts the new one. Removal, load reconciliation, loop and audio settings all move to the same key. - Settings persist on the `Source` row (six ALTER TABLE guards + SELECT/INSERT), so a keyed layer comes back keyed. - Panel section is plain creator words: Green/Blue Screen, Amount, Softness, Fringe. Test: `ChromaKeyApplyTests` — the pipe asks ffmpeg to key (and does not when off); a green-screen clip is offered, accepting it starts a keyed session, the layer's resolver then reads that session rather than the old one; the setting survives a save. Docs: TASK 64 D/E ticked with slice F (auto-apply setting + alert clips) left open; frame-pipeline records the decode-option design; HANDOFF rewritten. verify.sh: 544 passed, 0 failed, 0 warnings.
475 lines
27 KiB
C#
475 lines
27 KiB
C#
using Microsoft.Data.Sqlite;
|
|
using ytLive.Models;
|
|
|
|
namespace ytLive.Services;
|
|
|
|
public partial class LayoutStore : IDisposable
|
|
{
|
|
public void Save(IEnumerable<Scene> scenes, Webcam? webcam, SocialsConfig? socials, Music? music = null)
|
|
{
|
|
using var tx = _connection.BeginTransaction();
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM WebcamSceneConfig;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Source;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Webcam;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM SocialEntry;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Socials;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Music;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Scene;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
INSERT INTO Scene (Id, Name, IsHidden, IsChatScene, HasBackground, HasSocialBar, ThumbnailAssetId, SortOrder)
|
|
VALUES ($id, $name, $isHidden, $isChat, $hasBackground, $hasSocialBar, $thumbnail, $sort)
|
|
""";
|
|
cmd.Transaction = tx;
|
|
var idP = cmd.Parameters.Add("$id", SqliteType.Text);
|
|
var nameP = cmd.Parameters.Add("$name", SqliteType.Text);
|
|
var hiddenP = cmd.Parameters.Add("$isHidden", SqliteType.Integer);
|
|
var chatP = cmd.Parameters.Add("$isChat", SqliteType.Integer);
|
|
var backgroundP = cmd.Parameters.Add("$hasBackground", SqliteType.Integer);
|
|
var socialBarP = cmd.Parameters.Add("$hasSocialBar", SqliteType.Integer);
|
|
var thumbnailP = cmd.Parameters.Add("$thumbnail", SqliteType.Text);
|
|
var sortP = cmd.Parameters.Add("$sort", SqliteType.Integer);
|
|
|
|
var sort = 0;
|
|
foreach (var scene in scenes)
|
|
{
|
|
idP.Value = scene.Id;
|
|
nameP.Value = scene.Name;
|
|
hiddenP.Value = scene.IsHidden ? 1 : 0;
|
|
chatP.Value = scene.IsChatScene ? 1 : 0;
|
|
backgroundP.Value = scene.HasBackground ? 1 : 0;
|
|
socialBarP.Value = scene.HasSocialBar ? 1 : 0;
|
|
thumbnailP.Value = (object?)scene.ThumbnailAssetId ?? DBNull.Value;
|
|
sortP.Value = sort++;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
INSERT INTO Source (Id, SceneId, AssetId, Type, Name, IsEnabled,
|
|
X, Y, Width, Height, Opacity, MonitorIndex,
|
|
ClipShape, IsMirrored, IsBackground, CaptureKey, ShowDesktop, SortOrder,
|
|
ChatFontSize, ChatFontColor, ChatBackgroundColor, ChatMaxMessages,
|
|
ChatShowTimestamps, ChatShowBadges, ChatMemberColor, ChatSuperChatColor,
|
|
WebUri, MediaPath, MediaIsLooping, MediaVolume, MediaPlaybackState,
|
|
AlertVideoAssetId, AlertVideoPath, AlertUseDefaultVideo, AlertShowTicker, AlertVideoVolume,
|
|
AlertDisplayMethod, IsBrandLogo, BlurAmount, AlertAudioPath, AlertAudioLoop,
|
|
TextContent, TextFontFamily, TextFontSize, TextColor, TextBold, TextItalic,
|
|
TextUnderline, TextAlignment, TextVerticalAlignment, MediaMuted,
|
|
ChromaKeyEnabled, ChromaKeyColor, ChromaKeySimilarity,
|
|
ChromaKeySoftness, ChromaKeySpill, ChromaScreen)
|
|
VALUES ($id, $sceneId, $assetId, $type, $name, $isEnabled,
|
|
$x, $y, $w, $h, $opacity, $monitor,
|
|
$clip, $mirrored, $isBackground, $captureKey, $showDesktop, $sort,
|
|
$chatFontSize, $chatFontColor, $chatBgColor, $chatMaxMsgs,
|
|
$chatTimestamps, $chatBadges, $chatMemberColor, $chatSuperChatColor,
|
|
$webUri, $mediaPath, $mediaIsLooping, $mediaVolume, $mediaPlaybackState,
|
|
$alertVideoAssetId, $alertVideoPath, $alertUseDefaultVideo, $alertShowTicker, $alertVideoVolume,
|
|
$alertDisplayMethod, $isBrandLogo, $blurAmount, $alertAudioPath, $alertAudioLoop,
|
|
$textContent, $textFontFamily, $textFontSize, $textColor, $textBold, $textItalic,
|
|
$textUnderline, $textAlignment, $textVerticalAlignment, $mediaMuted,
|
|
$chromaEnabled, $chromaColor, $chromaSimilarity, $chromaSoftness,
|
|
$chromaSpill, $chromaScreen)
|
|
""";
|
|
cmd.Transaction = tx;
|
|
var idP = cmd.Parameters.Add("$id", SqliteType.Text);
|
|
var sceneIdP = cmd.Parameters.Add("$sceneId", SqliteType.Text);
|
|
var assetIdP = cmd.Parameters.Add("$assetId", SqliteType.Text);
|
|
var typeP = cmd.Parameters.Add("$type", SqliteType.Text);
|
|
var nameP = cmd.Parameters.Add("$name", SqliteType.Text);
|
|
var enabledP = cmd.Parameters.Add("$isEnabled", SqliteType.Integer);
|
|
var xP = cmd.Parameters.Add("$x", SqliteType.Real);
|
|
var yP = cmd.Parameters.Add("$y", SqliteType.Real);
|
|
var wP = cmd.Parameters.Add("$w", SqliteType.Real);
|
|
var hP = cmd.Parameters.Add("$h", SqliteType.Real);
|
|
var opacityP = cmd.Parameters.Add("$opacity", SqliteType.Real);
|
|
var monitorP = cmd.Parameters.Add("$monitor", SqliteType.Integer);
|
|
var clipP = cmd.Parameters.Add("$clip", SqliteType.Text);
|
|
var mirroredP = cmd.Parameters.Add("$mirrored", SqliteType.Integer);
|
|
var isBackgroundP = cmd.Parameters.Add("$isBackground", SqliteType.Integer);
|
|
var captureKeyP = cmd.Parameters.Add("$captureKey", SqliteType.Text);
|
|
var showDesktopP = cmd.Parameters.Add("$showDesktop", SqliteType.Integer);
|
|
var sortP = cmd.Parameters.Add("$sort", SqliteType.Integer);
|
|
var chatFontSizeP = cmd.Parameters.Add("$chatFontSize", SqliteType.Real);
|
|
var chatFontColorP = cmd.Parameters.Add("$chatFontColor", SqliteType.Text);
|
|
var chatBgColorP = cmd.Parameters.Add("$chatBgColor", SqliteType.Text);
|
|
var chatMaxMsgsP = cmd.Parameters.Add("$chatMaxMsgs", SqliteType.Integer);
|
|
var chatTimestampsP = cmd.Parameters.Add("$chatTimestamps", SqliteType.Integer);
|
|
var chatBadgesP = cmd.Parameters.Add("$chatBadges", SqliteType.Integer);
|
|
var chatMemberColorP = cmd.Parameters.Add("$chatMemberColor", SqliteType.Text);
|
|
var chatSuperChatColorP = cmd.Parameters.Add("$chatSuperChatColor", SqliteType.Text);
|
|
var webUriP = cmd.Parameters.Add("$webUri", SqliteType.Text);
|
|
var mediaPathP = cmd.Parameters.Add("$mediaPath", SqliteType.Text);
|
|
var mediaIsLoopingP = cmd.Parameters.Add("$mediaIsLooping", SqliteType.Integer);
|
|
var mediaVolumeP = cmd.Parameters.Add("$mediaVolume", SqliteType.Real);
|
|
var mediaMutedP = cmd.Parameters.Add("$mediaMuted", SqliteType.Integer);
|
|
var chromaEnabledP = cmd.Parameters.Add("$chromaEnabled", SqliteType.Integer);
|
|
var chromaColorP = cmd.Parameters.Add("$chromaColor", SqliteType.Text);
|
|
var chromaSimilarityP = cmd.Parameters.Add("$chromaSimilarity", SqliteType.Real);
|
|
var chromaSoftnessP = cmd.Parameters.Add("$chromaSoftness", SqliteType.Real);
|
|
var chromaSpillP = cmd.Parameters.Add("$chromaSpill", SqliteType.Integer);
|
|
var chromaScreenP = cmd.Parameters.Add("$chromaScreen", SqliteType.Integer);
|
|
var mediaPlaybackStateP = cmd.Parameters.Add("$mediaPlaybackState", SqliteType.Text);
|
|
var alertVideoAssetIdP = cmd.Parameters.Add("$alertVideoAssetId", SqliteType.Text);
|
|
var alertVideoPathP = cmd.Parameters.Add("$alertVideoPath", SqliteType.Text);
|
|
var alertUseDefaultVideoP = cmd.Parameters.Add("$alertUseDefaultVideo", SqliteType.Integer);
|
|
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);
|
|
cmd.Parameters.Add("$blurAmount", SqliteType.Real);
|
|
var alertAudioPathP = cmd.Parameters.Add("$alertAudioPath", SqliteType.Text);
|
|
var alertAudioLoopP = cmd.Parameters.Add("$alertAudioLoop", SqliteType.Integer);
|
|
var textContentP = cmd.Parameters.Add("$textContent", SqliteType.Text);
|
|
var textFontFamilyP = cmd.Parameters.Add("$textFontFamily", SqliteType.Text);
|
|
var textFontSizeP = cmd.Parameters.Add("$textFontSize", SqliteType.Real);
|
|
var textColorP = cmd.Parameters.Add("$textColor", SqliteType.Text);
|
|
var textBoldP = cmd.Parameters.Add("$textBold", SqliteType.Integer);
|
|
var textItalicP = cmd.Parameters.Add("$textItalic", SqliteType.Integer);
|
|
var textUnderlineP = cmd.Parameters.Add("$textUnderline", SqliteType.Integer);
|
|
var textAlignmentP = cmd.Parameters.Add("$textAlignment", SqliteType.Integer);
|
|
var textVerticalAlignmentP = cmd.Parameters.Add("$textVerticalAlignment", SqliteType.Integer);
|
|
|
|
// Source and WebcamSceneConfig share ONE z-order space per scene:
|
|
// each element gets its index within scene.Elements regardless of
|
|
// table, so a webcam slotted between sources survives save→load.
|
|
// (Pre-unified rows tie at 0 — load breaks ties sources-first.)
|
|
foreach (var scene in scenes)
|
|
{
|
|
var z = 0;
|
|
foreach (var element in scene.Elements)
|
|
{
|
|
if (element is not Source source) { z++; continue; }
|
|
idP.Value = source.Id;
|
|
sceneIdP.Value = scene.Id;
|
|
assetIdP.Value = (object?)source.AssetId ?? DBNull.Value;
|
|
typeP.Value = source.Type.ToString();
|
|
nameP.Value = source.Name;
|
|
enabledP.Value = source.IsEnabled ? 1 : 0;
|
|
xP.Value = source.X;
|
|
yP.Value = source.Y;
|
|
wP.Value = source.Width;
|
|
hP.Value = source.Height;
|
|
opacityP.Value = source.Opacity;
|
|
monitorP.Value = (object?)source.MonitorIndex ?? DBNull.Value;
|
|
clipP.Value = source.ClipShape.ToString();
|
|
mirroredP.Value = source.IsMirrored ? 1 : 0;
|
|
isBackgroundP.Value = source.IsBackground ? 1 : 0;
|
|
captureKeyP.Value = (object?)source.CaptureKey ?? DBNull.Value;
|
|
showDesktopP.Value = source.ShowDesktop ? 1 : 0;
|
|
sortP.Value = z++;
|
|
chatFontSizeP.Value = source.ChatFontSize;
|
|
chatFontColorP.Value = source.ChatFontColor;
|
|
chatBgColorP.Value = source.ChatBackgroundColor;
|
|
chatMaxMsgsP.Value = source.ChatMaxMessages;
|
|
chatTimestampsP.Value = source.ChatShowTimestamps ? 1 : 0;
|
|
chatBadgesP.Value = source.ChatShowBadges ? 1 : 0;
|
|
chatMemberColorP.Value = source.ChatMemberColor;
|
|
chatSuperChatColorP.Value = source.ChatSuperChatColor;
|
|
webUriP.Value = (object?)source.WebUri ?? DBNull.Value;
|
|
mediaPathP.Value = (object?)source.MediaPath ?? DBNull.Value;
|
|
mediaIsLoopingP.Value = source.MediaIsLooping ? 1 : 0;
|
|
mediaVolumeP.Value = source.MediaVolume;
|
|
mediaMutedP.Value = source.MediaMuted ? 1 : 0;
|
|
chromaEnabledP.Value = source.ChromaKeyEnabled ? 1 : 0;
|
|
chromaColorP.Value = source.ChromaKeyColor;
|
|
chromaSimilarityP.Value = source.ChromaKeySimilarity;
|
|
chromaSoftnessP.Value = source.ChromaKeySoftness;
|
|
chromaSpillP.Value = source.ChromaKeySpill ? 1 : 0;
|
|
chromaScreenP.Value = (int)source.ChromaScreen;
|
|
mediaPlaybackStateP.Value = source.MediaPlaybackState.ToString();
|
|
alertVideoAssetIdP.Value = (object?)source.AlertVideoAssetId ?? DBNull.Value;
|
|
alertVideoPathP.Value = (object?)source.AlertVideoPath ?? DBNull.Value;
|
|
alertUseDefaultVideoP.Value = source.AlertUseDefaultVideo ? 1 : 0;
|
|
alertShowTickerP.Value = source.AlertShowTicker ? 1 : 0;
|
|
alertVideoVolumeP.Value = source.AlertVideoVolume;
|
|
alertDisplayMethodP.Value = (int)source.AlertDisplayMethod;
|
|
isBrandLogoP.Value = source.IsBrandLogo ? 1 : 0;
|
|
cmd.Parameters["$blurAmount"].Value = source.BlurAmount;
|
|
alertAudioPathP.Value = (object?)source.AlertAudioPath ?? DBNull.Value;
|
|
alertAudioLoopP.Value = source.AlertAudioLoop ? 1 : 0;
|
|
textContentP.Value = source.TextContent;
|
|
textFontFamilyP.Value = source.TextFontFamily;
|
|
textFontSizeP.Value = source.TextFontSize;
|
|
textColorP.Value = source.TextColor;
|
|
textBoldP.Value = source.TextBold ? 1 : 0;
|
|
textItalicP.Value = source.TextItalic ? 1 : 0;
|
|
textUnderlineP.Value = source.TextUnderline ? 1 : 0;
|
|
textAlignmentP.Value = (int)source.TextAlignment;
|
|
textVerticalAlignmentP.Value = (int)source.TextVerticalAlignment;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (webcam != null)
|
|
{
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "INSERT INTO Webcam (Id, DeviceId, Name) VALUES ($id, $device, $name);";
|
|
cmd.Transaction = tx;
|
|
cmd.Parameters.AddWithValue("$id", webcam.Id);
|
|
cmd.Parameters.AddWithValue("$device", webcam.DeviceId);
|
|
cmd.Parameters.AddWithValue("$name", webcam.Name);
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
INSERT INTO WebcamSceneConfig
|
|
(SceneId, WebcamId, IsVisible, X, Y, Width, Height, Opacity,
|
|
ClipShape, IsMirrored, BorderColor, BorderOpacity, BorderWidth, BorderAnimation,
|
|
RectWidth, RectHeight, SortOrder, BackgroundEffect, BackgroundStrength,
|
|
VerticalX, VerticalY, VerticalWidth, VerticalHeight, VerticalClipShape,
|
|
VerticalRectWidth, VerticalRectHeight)
|
|
VALUES ($sceneId, $webcamId, $isVisible, $x, $y, $w, $h, $opacity,
|
|
$clip, $mirrored, $borderColor, $borderOpacity, $borderWidth, $borderAnimation,
|
|
$rectWidth, $rectHeight, $sort, $bgEffect, $bgStrength,
|
|
$vX, $vY, $vW, $vH, $vClip, $vRectWidth, $vRectHeight)
|
|
""";
|
|
cmd.Transaction = tx;
|
|
var sceneIdP = cmd.Parameters.Add("$sceneId", SqliteType.Text);
|
|
var webcamIdP = cmd.Parameters.Add("$webcamId", SqliteType.Text);
|
|
var visibleP = cmd.Parameters.Add("$isVisible", SqliteType.Integer);
|
|
var xP = cmd.Parameters.Add("$x", SqliteType.Real);
|
|
var yP = cmd.Parameters.Add("$y", SqliteType.Real);
|
|
var wP = cmd.Parameters.Add("$w", SqliteType.Real);
|
|
var hP = cmd.Parameters.Add("$h", SqliteType.Real);
|
|
var opacityP = cmd.Parameters.Add("$opacity", SqliteType.Real);
|
|
var clipP = cmd.Parameters.Add("$clip", SqliteType.Text);
|
|
var mirroredP = cmd.Parameters.Add("$mirrored", SqliteType.Integer);
|
|
var colorP = cmd.Parameters.Add("$borderColor", SqliteType.Text);
|
|
var borderOpacityP = cmd.Parameters.Add("$borderOpacity", SqliteType.Real);
|
|
var borderWidthP = cmd.Parameters.Add("$borderWidth", SqliteType.Integer);
|
|
var animationP = cmd.Parameters.Add("$borderAnimation", SqliteType.Text);
|
|
var rectWidthP = cmd.Parameters.Add("$rectWidth", SqliteType.Real);
|
|
var rectHeightP = cmd.Parameters.Add("$rectHeight", SqliteType.Real);
|
|
var sortP = cmd.Parameters.Add("$sort", SqliteType.Integer);
|
|
var bgEffectP = cmd.Parameters.Add("$bgEffect", SqliteType.Text);
|
|
var bgStrengthP = cmd.Parameters.Add("$bgStrength", SqliteType.Real);
|
|
var vXP = cmd.Parameters.Add("$vX", SqliteType.Real);
|
|
var vYP = cmd.Parameters.Add("$vY", SqliteType.Real);
|
|
var vWP = cmd.Parameters.Add("$vW", SqliteType.Real);
|
|
var vHP = cmd.Parameters.Add("$vH", SqliteType.Real);
|
|
var vClipP = cmd.Parameters.Add("$vClip", SqliteType.Text);
|
|
var vRectWidthP = cmd.Parameters.Add("$vRectWidth", SqliteType.Real);
|
|
var vRectHeightP = cmd.Parameters.Add("$vRectHeight", SqliteType.Real);
|
|
|
|
foreach (var scene in scenes)
|
|
{
|
|
var z = 0;
|
|
foreach (var element in scene.Elements)
|
|
{
|
|
if (element is not WebcamSceneConfig config) { z++; continue; }
|
|
bgEffectP.Value = config.BackgroundEffect.ToString();
|
|
bgStrengthP.Value = config.BackgroundStrength;
|
|
sceneIdP.Value = scene.Id;
|
|
webcamIdP.Value = config.WebcamId;
|
|
visibleP.Value = config.IsVisible ? 1 : 0;
|
|
opacityP.Value = config.Opacity;
|
|
mirroredP.Value = config.IsMirrored ? 1 : 0;
|
|
colorP.Value = (object?)(string.IsNullOrEmpty(config.BorderColor) ? null : config.BorderColor) ?? DBNull.Value;
|
|
borderOpacityP.Value = config.BorderOpacity;
|
|
borderWidthP.Value = config.BorderWidth;
|
|
animationP.Value = config.BorderAnimation.ToString();
|
|
sortP.Value = z++;
|
|
|
|
// TASK 57: the X/Y/W/H/ClipShape columns stay the 16:9
|
|
// layout — while the vertical tier is live the LIVE
|
|
// geometry is the vertical placement and the 16:9 one is
|
|
// parked in the model — and the Vertical* columns carry
|
|
// the vertical placement itself.
|
|
if (config.VerticalLayoutLive)
|
|
{
|
|
config.CommitLiveLayoutToVertical();
|
|
xP.Value = config.LandscapeX;
|
|
yP.Value = config.LandscapeY;
|
|
wP.Value = config.LandscapeWidth;
|
|
hP.Value = config.LandscapeHeight;
|
|
clipP.Value = config.LandscapeClipShape.ToString();
|
|
rectWidthP.Value = (object?)config.LandscapeRectWidth ?? DBNull.Value;
|
|
rectHeightP.Value = (object?)config.LandscapeRectHeight ?? DBNull.Value;
|
|
}
|
|
else
|
|
{
|
|
xP.Value = config.X;
|
|
yP.Value = config.Y;
|
|
wP.Value = config.Width;
|
|
hP.Value = config.Height;
|
|
clipP.Value = config.ClipShape.ToString();
|
|
rectWidthP.Value = (object?)config.RectWidth ?? DBNull.Value;
|
|
rectHeightP.Value = (object?)config.RectHeight ?? DBNull.Value;
|
|
}
|
|
|
|
vXP.Value = (object?)config.VerticalX ?? DBNull.Value;
|
|
vYP.Value = (object?)config.VerticalY ?? DBNull.Value;
|
|
vWP.Value = (object?)config.VerticalWidth ?? DBNull.Value;
|
|
vHP.Value = (object?)config.VerticalHeight ?? DBNull.Value;
|
|
vClipP.Value = (object?)config.VerticalClipShape?.ToString() ?? DBNull.Value;
|
|
vRectWidthP.Value = (object?)config.VerticalRectWidth ?? DBNull.Value;
|
|
vRectHeightP.Value = (object?)config.VerticalRectHeight ?? DBNull.Value;
|
|
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (socials != null && socials.Entries.Count > 0)
|
|
{
|
|
var socialsId = Guid.NewGuid().ToString();
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
INSERT INTO Socials (Id, BarPosition, BarEnabled, TextColor, BackgroundEnabled,
|
|
BackgroundColor, BackgroundOpacity, TextShadow)
|
|
VALUES ($id, $pos, $enabled, $text, $bgen, $bg, $bgopacity, $shadow);
|
|
""";
|
|
cmd.Transaction = tx;
|
|
cmd.Parameters.AddWithValue("$id", socialsId);
|
|
cmd.Parameters.AddWithValue("$pos", socials.BarPosition.ToString());
|
|
cmd.Parameters.AddWithValue("$enabled", socials.BarEnabled ? 1 : 0);
|
|
cmd.Parameters.AddWithValue("$text", socials.BarTextColor);
|
|
cmd.Parameters.AddWithValue("$bgen", socials.BarBackgroundEnabled ? 1 : 0);
|
|
cmd.Parameters.AddWithValue("$bg", socials.BarBackgroundColor);
|
|
cmd.Parameters.AddWithValue("$bgopacity", socials.BarBackgroundOpacity);
|
|
cmd.Parameters.AddWithValue("$shadow", socials.BarTextShadow ? 1 : 0);
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
using var entryCmd = _connection.CreateCommand();
|
|
entryCmd.CommandText = """
|
|
INSERT INTO SocialEntry (Id, SocialsId, Service, Handle, ProfileUrl, Software, SortOrder)
|
|
VALUES ($id, $socialsId, $service, $handle, $url, $software, $sort)
|
|
""";
|
|
entryCmd.Transaction = tx;
|
|
var eId = entryCmd.Parameters.Add("$id", SqliteType.Text);
|
|
var eSocialsId = entryCmd.Parameters.Add("$socialsId", SqliteType.Text);
|
|
var eService = entryCmd.Parameters.Add("$service", SqliteType.Text);
|
|
var eHandle = entryCmd.Parameters.Add("$handle", SqliteType.Text);
|
|
var eUrl = entryCmd.Parameters.Add("$url", SqliteType.Text);
|
|
var eSoftware = entryCmd.Parameters.Add("$software", SqliteType.Text);
|
|
var eSort = entryCmd.Parameters.Add("$sort", SqliteType.Integer);
|
|
|
|
var entrySort = 0;
|
|
foreach (var entry in socials.Entries)
|
|
{
|
|
eId.Value = Guid.NewGuid().ToString();
|
|
eSocialsId.Value = socialsId;
|
|
eService.Value = entry.Service.ToString();
|
|
eHandle.Value = entry.Handle;
|
|
eUrl.Value = entry.ProfileUrl;
|
|
eSoftware.Value = (object?)entry.FediverseSoftware ?? DBNull.Value;
|
|
eSort.Value = entrySort++;
|
|
entryCmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
if (music != null && !string.IsNullOrWhiteSpace(music.TrackPath))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "INSERT INTO Music (Id, TrackPath, IsEnabled) VALUES ($id, $path, $enabled);";
|
|
cmd.Transaction = tx;
|
|
cmd.Parameters.AddWithValue("$id", Guid.NewGuid().ToString());
|
|
cmd.Parameters.AddWithValue("$path", music.TrackPath);
|
|
cmd.Parameters.AddWithValue("$enabled", music.IsEnabled ? 1 : 0);
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
DELETE FROM Asset WHERE Id NOT IN (
|
|
SELECT AssetId FROM Source WHERE AssetId IS NOT NULL
|
|
UNION
|
|
SELECT ThumbnailAssetId FROM Scene WHERE ThumbnailAssetId IS NOT NULL
|
|
UNION
|
|
SELECT AlertVideoAssetId FROM Source WHERE AlertVideoAssetId IS NOT NULL
|
|
UNION
|
|
SELECT Value FROM Settings WHERE Key = 'AlertDefaultVideoAssetId'
|
|
UNION
|
|
SELECT Value FROM Settings WHERE Key = 'OnAirImageAssetId'
|
|
);
|
|
""";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
// Save custom background paths and use-default flags per background source
|
|
foreach (var scene in scenes)
|
|
{
|
|
var bg = scene.Elements.OfType<Source>().FirstOrDefault(s => s.IsBackground);
|
|
if (bg == null) continue;
|
|
|
|
var pathKey = $"BackgroundPath_{bg.Id}";
|
|
if (!string.IsNullOrWhiteSpace(bg.CustomBackgroundPath))
|
|
UpsertSetting(pathKey, bg.CustomBackgroundPath);
|
|
else
|
|
{
|
|
using var del = _connection.CreateCommand();
|
|
del.CommandText = "DELETE FROM Settings WHERE Key = $key;";
|
|
del.Parameters.AddWithValue("$key", pathKey);
|
|
del.ExecuteNonQuery();
|
|
}
|
|
|
|
var defaultKey = $"BackgroundUseDefault_{bg.Id}";
|
|
UpsertSetting(defaultKey, bg.UseDefaultBackground ? "1" : "0");
|
|
}
|
|
|
|
// Purge background settings left behind by deleted/duplicated rows
|
|
// (TASK 25 heal drops extras; their keys must not accumulate).
|
|
using (var purge = _connection.CreateCommand())
|
|
{
|
|
purge.Transaction = tx;
|
|
purge.CommandText =
|
|
@"DELETE FROM Settings
|
|
WHERE (Key LIKE 'BackgroundUseDefault\_%' ESCAPE '\'
|
|
OR Key LIKE 'BackgroundPath\_%' ESCAPE '\')
|
|
AND SUBSTR(Key, INSTR(Key, '_') + 1) NOT IN (SELECT Id FROM Source);";
|
|
purge.ExecuteNonQuery();
|
|
}
|
|
|
|
tx.Commit();
|
|
}
|
|
}
|