Skip to content
10 changes: 10 additions & 0 deletions Backend/App/MessageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ public static async Task HandleMessage(string message)
root.TryGetProperty("Parameters", out JsonElement aiClipParameterElement);
_ = Task.Run(() => HandleCreateAiClip(aiClipParameterElement));
break;
case "CreateLowlight":
root.TryGetProperty("Parameters", out JsonElement lowlightParameterElement);
_ = Task.Run(() => HandleCreateLowlight(lowlightParameterElement));
break;
case "CompressVideo":
root.TryGetProperty("Parameters", out JsonElement compressParameterElement);
_ = Task.Run(() => HandleCompressVideo(compressParameterElement));
Expand Down Expand Up @@ -316,6 +320,12 @@ public static async Task HandleMessage(string message)
Log.Error($"Stack trace: {ex.StackTrace}");
}
}
private static async Task HandleCreateLowlight(JsonElement message)
{
Log.Information($"{message}");
message.TryGetProperty("FileName", out JsonElement fileNameElement);
await AiService.CreateLowlight(fileNameElement.GetString()!);
}

public static async Task HandleDeleteContent(JsonElement message)
{
Expand Down
15 changes: 14 additions & 1 deletion Backend/Core/Models/Bookmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public enum BookmarkType
[IncludeInHighlight] Kill,
[IncludeInHighlight] Goal,
Assist,
Death
[IncludeInLowlight] Death
}

/// <summary>
Expand All @@ -31,6 +31,12 @@ public enum BookmarkType
[AttributeUsage(AttributeTargets.Field)]
public class IncludeInHighlightAttribute : Attribute { }

/// <summary>
/// Marks a BookmarkType as one that should be included in auto-generated lowlights.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class IncludeInLowlightAttribute : Attribute { }

public static class BookmarkTypeExtensions
{
/// <summary>
Expand All @@ -39,6 +45,13 @@ public static class BookmarkTypeExtensions
public static bool IncludeInHighlight(this BookmarkType type) =>
typeof(BookmarkType).GetField(type.ToString())!
.GetCustomAttributes(typeof(IncludeInHighlightAttribute), false).Length > 0;

/// <summary>
/// Returns true if this bookmark type should be included in auto-generated lowlights.
/// </summary>
public static bool IncludeInLowlight(this BookmarkType type) =>
typeof(BookmarkType).GetField(type.ToString())!
.GetCustomAttributes(typeof(IncludeInLowlightAttribute), false).Length > 0;
}

[JsonConverter(typeof(JsonStringEnumConverter))]
Expand Down
60 changes: 59 additions & 1 deletion Backend/Core/Models/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal class Settings
"Replay Buffer",
"Clips",
"Highlights",
"Lowlights",
"Settings"
};

Expand Down Expand Up @@ -48,6 +49,10 @@ internal class Settings
private bool _autoGenerateHighlights = true;
private double _highlightPaddingBefore = 4;
private double _highlightPaddingAfter = 4;
private bool _enableLowlights = false;
private bool _autoGenerateLowlights = true;
private double _lowlightPaddingBefore = 4;
private double _lowlightPaddingAfter = 4;
private bool _runOnStartup = false;
private StartupWindowMode _startupWindowMode = StartupWindowMode.Minimized;
private bool _receiveBetaUpdates = false;
Expand Down Expand Up @@ -374,6 +379,19 @@ public bool EnableAi
}
}

[JsonPropertyName("enableLowlights")]
public bool EnableLowlights
{
get => _enableLowlights;
set
{
if (_enableLowlights != value)
{
_enableLowlights = value;
}
}
}

[JsonPropertyName("autoGenerateHighlights")]
public bool AutoGenerateHighlights
{
Expand Down Expand Up @@ -413,6 +431,45 @@ public double HighlightPaddingAfter
}
}

[JsonPropertyName("autoGenerateLowlights")]
public bool AutoGenerateLowlights
{
get => _autoGenerateLowlights;
set
{
if (_autoGenerateLowlights != value)
{
_autoGenerateLowlights = value;
}
}
}

[JsonPropertyName("lowlightPaddingBefore")]
public double LowlightPaddingBefore
{
get => _lowlightPaddingBefore;
set
{
if (_lowlightPaddingBefore != value)
{
_lowlightPaddingBefore = value;
}
}
}

[JsonPropertyName("lowlightPaddingAfter")]
public double LowlightPaddingAfter
{
get => _lowlightPaddingAfter;
set
{
if (_lowlightPaddingAfter != value)
{
_lowlightPaddingAfter = value;
}
}
}

[JsonPropertyName("gameIntegrations")]
public GameIntegrations GameIntegrations
{
Expand Down Expand Up @@ -1088,7 +1145,8 @@ public enum ContentType
Session,
Buffer,
Clip,
Highlight
Highlight,
Lowlight
}

public ContentType Type { get; set; } = ContentType.Session;
Expand Down
31 changes: 31 additions & 0 deletions Backend/Core/SettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,37 @@ private static async Task UpdateSettingsInstance(Settings updatedSettings)
hasChanges = true;
}

// Update EnableLowlights
if (settings.EnableLowlights != updatedSettings.EnableLowlights)
{
Log.Information($"EnableLowlights changed from '{settings.EnableLowlights}' to '{updatedSettings.EnableLowlights}'");
settings.EnableLowlights = updatedSettings.EnableLowlights;
hasChanges = true;
}

// Update AutoGenerateLowlights
if (settings.AutoGenerateLowlights != updatedSettings.AutoGenerateLowlights)
{
Log.Information($"AutoGenerateLowlights changed from '{settings.AutoGenerateLowlights}' to '{updatedSettings.AutoGenerateLowlights}'");
settings.AutoGenerateLowlights = updatedSettings.AutoGenerateLowlights;
hasChanges = true;
}

// Update LowlightPaddingBefore
if (settings.LowlightPaddingBefore != updatedSettings.LowlightPaddingBefore)
{
Log.Information($"LowlightPaddingBefore changed from '{settings.LowlightPaddingBefore}' to '{updatedSettings.LowlightPaddingBefore}'");
settings.LowlightPaddingBefore = updatedSettings.LowlightPaddingBefore;
hasChanges = true;
}

// Update LowlightPaddingAfter
if (settings.LowlightPaddingAfter != updatedSettings.LowlightPaddingAfter)
{
Log.Information($"LowlightPaddingAfter changed from '{settings.LowlightPaddingAfter}' to '{updatedSettings.LowlightPaddingAfter}'");
settings.LowlightPaddingAfter = updatedSettings.LowlightPaddingAfter;
hasChanges = true;
}
if (settings.ReceiveBetaUpdates != updatedSettings.ReceiveBetaUpdates)
{
Log.Information($"ReceiveBetaUpdates changed from '{settings.ReceiveBetaUpdates}' to '{updatedSettings.ReceiveBetaUpdates}'");
Expand Down
58 changes: 51 additions & 7 deletions Backend/Media/AiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,40 +26,83 @@ public static async Task CreateHighlight(string fileName)
if (momentCount == 0)
{
Log.Information($"No highlight bookmarks found for: {fileName}");
await SendProgress(highlightId, -1, "error", "No highlight moments found in this session", content);
await SendProgress(highlightId, -1, "error", "No highlight moments found in this session", content, "AiProgress");
return;
}

await SendProgress(highlightId, 0, "processing", $"Found {momentCount} moments", content);
await SendProgress(highlightId, 0, "processing", $"Found {momentCount} moments", content, "AiProgress");

await HighlightService.CreateHighlightFromBookmarks(fileName, async (progress, message) =>
{
string status = progress < 0 ? "error" : progress >= 100 ? "done" : "processing";
await SendProgress(highlightId, progress, status, message, content);
await SendProgress(highlightId, progress, status, message, content, "AiProgress");
});
}
catch (Exception ex)
{
Log.Error(ex, $"Error creating highlight for {fileName}");
if (content != null)
{
await SendProgress(highlightId, -1, "error", $"Error: {ex.Message}", content);
await SendProgress(highlightId, -1, "error", $"Error: {ex.Message}", content, "AiProgress");
}
}
}

private static async Task SendProgress(string id, int progress, string status, string message, Content content)
public static async Task CreateLowlight(string fileName)
{
string lowlightId = Guid.NewGuid().ToString();
Content? content = null;

try
{
Log.Information($"Starting lowlight creation for: {fileName}");

content = AppState.Instance.Content.FirstOrDefault(x => x.FileName == fileName);
if (content == null)
{
Log.Warning($"No content found matching fileName: {fileName}");
return;
}

int momentCount = content.Bookmarks.Count(b => b.Type.IncludeInLowlight());
if (momentCount == 0)
{
Log.Information($"No lowlight bookmarks found for: {fileName}");
await SendProgress(lowlightId, -1, "error", "No lowlight moments found in this session", content, "LowlightAiProgress");
return;
}

await SendProgress(lowlightId, 0, "processing", $"Found {momentCount} moments", content, "LowlightAiProgress");

await LowlightService.CreateLowlightFromBookmarks(fileName, async (progress, message) =>
{
string status = progress < 0 ? "error" : progress >= 100 ? "done" : "processing";
await SendProgress(lowlightId, progress, status, message, content, "LowlightAiProgress");
});
}
catch (Exception ex)
{
Log.Error(ex, $"Error creating lowlight for {fileName}");
if (content != null)
{
await SendProgress(lowlightId, -1, "error", $"Error: {ex.Message}", content, "LowlightAiProgress");
}
}
}

private static async Task SendProgress(string id, int progress, string status, string message, Content content, string messageType)
{
var progressMessage = new HighlightProgressMessage
{
Id = id,
Progress = progress,
Status = status,
Message = message,
Content = content
Content = content,
MessageType = messageType
};

await MessageService.SendFrontendMessage("AiProgress", progressMessage);
await MessageService.SendFrontendMessage(messageType, progressMessage);
}
}

Expand All @@ -70,5 +113,6 @@ public class HighlightProgressMessage
public required string Status { get; set; }
public required string Message { get; set; }
public required Content Content { get; set; }
public required string MessageType { get; set; }
}
}
2 changes: 1 addition & 1 deletion Backend/Media/ContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ public static async Task DeleteContent(string filePath, Content.ContentType type
{
// Only delete if the folder is empty and is a game subfolder (not the root video type folder)
string contentRoot = Settings.Instance.ContentFolder;
string[] rootFolders = { FolderNames.Sessions, FolderNames.Buffers, FolderNames.Clips, FolderNames.Highlights };
string[] rootFolders = { FolderNames.Sessions, FolderNames.Buffers, FolderNames.Clips, FolderNames.Highlights, FolderNames.Lowlights };
bool isGameSubfolder = rootFolders.Any(rf =>
videoDirectory.StartsWith(Path.Combine(contentRoot, rf), StringComparison.OrdinalIgnoreCase) &&
!videoDirectory.Equals(Path.Combine(contentRoot, rf), StringComparison.OrdinalIgnoreCase));
Expand Down
2 changes: 0 additions & 2 deletions Backend/Media/HighlightService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ public static async Task CreateHighlightFromBookmarks(string fileName, Action<in
{
try
{
Log.Information($"Starting highlight creation for: {fileName}");

Content? content = AppState.Instance.Content.FirstOrDefault(x => x.FileName == fileName);
if (content == null)
{
Expand Down
Loading