mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-04-30 03:22:04 -04:00
Added support for selectively overriding capture settings on a per-stream basis
This commit is contained in:
@@ -33,12 +33,35 @@ public enum StreamService
|
||||
KiwiPeerTube
|
||||
}
|
||||
|
||||
public class KickStreamMetaModel
|
||||
public class BaseMetaModel
|
||||
{
|
||||
public CaptureOverridesModel? CaptureOverrides { get; set; } = null;
|
||||
}
|
||||
|
||||
public class CaptureOverridesModel
|
||||
{
|
||||
// Options applicable to YtDlp will not work with Streamlink and vice versa
|
||||
// That being said, some options are shared while still being explicitly marked as YtDlp
|
||||
// This applies to CaptureYtDlpWorkingDirectory, CaptureYtDlpParentTerminal, and CaptureYtDlpScriptPath
|
||||
public string? CaptureYtDlpBinaryPath { get; set; } = null;
|
||||
public string? CaptureYtDlpWorkingDirectory { get; set; } = null;
|
||||
public string? CaptureYtDlpCookiesFromBrowser { get; set; } = null;
|
||||
public string? CaptureYtDlpOutputFormat { get; set; } = null;
|
||||
public string? CaptureYtDlpParentTerminal { get; set; } = null;
|
||||
public string? CaptureYtDlpScriptPath { get; set; } = null;
|
||||
public string? CaptureYtDlpUserAgent { get; set; } = null;
|
||||
public string? CaptureStreamlinkBinaryPath { get; set; } = null;
|
||||
public string? CaptureStreamlinkOutputFormat { get; set; } = null;
|
||||
public string? CaptureStreamlinkRemuxScript { get; set; } = null;
|
||||
public string? CaptureStreamlinkTwitchOptions { get; set; } = null;
|
||||
}
|
||||
|
||||
public class KickStreamMetaModel : BaseMetaModel
|
||||
{
|
||||
public required int ChannelId { get; set; }
|
||||
}
|
||||
|
||||
public class PeerTubeMetaModel
|
||||
public class PeerTubeMetaModel : BaseMetaModel
|
||||
{
|
||||
public required string AccountName { get; set; }
|
||||
}
|
||||
@@ -920,7 +920,7 @@ public class BotServices
|
||||
{
|
||||
_logger.Info($"BossmanJack stream event came in. isLive => {isLive}");
|
||||
var settings = SettingsProvider.GetMultipleValuesAsync([
|
||||
BuiltIn.Keys.TwitchBossmanJackUsername, BuiltIn.Keys.CaptureEnabled, BuiltIn.Keys.TwitchIcon
|
||||
BuiltIn.Keys.TwitchBossmanJackUsername, BuiltIn.Keys.CaptureEnabled, BuiltIn.Keys.TwitchIcon, BuiltIn.Keys.CaptureStreamlinkBmjWorkingDirectory
|
||||
]).Result;
|
||||
var bmjUsername = settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value;
|
||||
|
||||
@@ -930,7 +930,12 @@ public class BotServices
|
||||
if (settings[BuiltIn.Keys.CaptureEnabled].ToBoolean())
|
||||
{
|
||||
_logger.Info("Capturing Bossman's stream");
|
||||
_ = new StreamCapture($"https://www.twitch.tv/{settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value}", StreamCaptureMethods.Streamlink, _cancellationToken).CaptureAsync();
|
||||
_ = new StreamCapture($"https://www.twitch.tv/{settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value}",
|
||||
StreamCaptureMethods.Streamlink,
|
||||
new CaptureOverridesModel
|
||||
{
|
||||
CaptureYtDlpWorkingDirectory = settings[BuiltIn.Keys.CaptureStreamlinkBmjWorkingDirectory].Value
|
||||
}, _cancellationToken).CaptureAsync();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1025,6 +1030,7 @@ public class BotServices
|
||||
using var db = new ApplicationDbContext();
|
||||
var channels = db.Streams.Where(s => s.Service == StreamService.Kick).Include(s => s.User);
|
||||
StreamDbModel? channel = null;
|
||||
KickStreamMetaModel? meta = null;
|
||||
foreach (var ch in channels)
|
||||
{
|
||||
if (ch.Metadata == null)
|
||||
@@ -1033,7 +1039,6 @@ public class BotServices
|
||||
continue;
|
||||
}
|
||||
|
||||
KickStreamMetaModel meta;
|
||||
try
|
||||
{
|
||||
meta = JsonSerializer.Deserialize<KickStreamMetaModel>(ch.Metadata) ??
|
||||
@@ -1071,7 +1076,7 @@ public class BotServices
|
||||
if (channel.AutoCapture && settings[BuiltIn.Keys.CaptureEnabled].ToBoolean())
|
||||
{
|
||||
_logger.Info($"{channel.StreamUrl} is configured to auto capture");
|
||||
_ = new StreamCapture(channel.StreamUrl, StreamCaptureMethods.YtDlp, _cancellationToken).CaptureAsync();
|
||||
_ = new StreamCapture(channel.StreamUrl, StreamCaptureMethods.YtDlp, meta?.CaptureOverrides, _cancellationToken).CaptureAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1098,12 +1103,26 @@ public class BotServices
|
||||
{
|
||||
identity = "@" + channel.User.KfUsername;
|
||||
}
|
||||
|
||||
BaseMetaModel? meta = null;
|
||||
if (channel.Metadata != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
meta = JsonSerializer.Deserialize<BaseMetaModel>(channel.Metadata);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error($"Failed to deserialize metadata for Parti stream: {channel.StreamUrl}");
|
||||
_logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
_chatBot.SendChatMessage($"{identity} is live! {data.EventTitle} {url}", true);
|
||||
if (channel.AutoCapture && settings[BuiltIn.Keys.CaptureEnabled].ToBoolean())
|
||||
{
|
||||
_logger.Info($"{channel.StreamUrl} is configured to auto capture");
|
||||
_ = new StreamCapture(url, StreamCaptureMethods.YtDlp, _cancellationToken).CaptureAsync();
|
||||
_ = new StreamCapture(url, StreamCaptureMethods.YtDlp, meta?.CaptureOverrides, _cancellationToken).CaptureAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1158,7 +1177,6 @@ public class BotServices
|
||||
$"{identity} is no longer live! :lossmanjack:", true);
|
||||
}
|
||||
|
||||
// TODO: Fix this so it aligns with the new Persisted Live setting instead of tracking separately
|
||||
public async Task<bool> CheckBmjIsLive()
|
||||
{
|
||||
var isLive =
|
||||
|
||||
@@ -72,10 +72,24 @@ public class DLive(ChatBot kfChatBot) : IDisposable
|
||||
|
||||
await kfChatBot.SendChatMessageAsync($"{identity} is live! {status.Title} {stream.StreamUrl}", true);
|
||||
|
||||
BaseMetaModel? meta = null;
|
||||
if (stream.Metadata != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
meta = JsonSerializer.Deserialize<BaseMetaModel>(stream.Metadata);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error($"Caught an exception when attempting to deserialize metadata for DLive stream {stream.StreamUrl}");
|
||||
_logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.AutoCapture && settings[BuiltIn.Keys.CaptureEnabled].ToBoolean())
|
||||
{
|
||||
_logger.Info($"{stream.StreamUrl} is live and set to auto capture");
|
||||
_ = new StreamCapture(stream.StreamUrl, StreamCaptureMethods.Streamlink, ct).CaptureAsync();
|
||||
_ = new StreamCapture(stream.StreamUrl, StreamCaptureMethods.Streamlink, meta?.CaptureOverrides, ct).CaptureAsync();
|
||||
}
|
||||
currentlyLive.Add(username);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class Owncast(ChatBot kfChatBot) : IDisposable
|
||||
if (!status.Online) continue;
|
||||
await kfChatBot.SendChatMessageAsync("https://bossmanjack.tv restream is live!", true);
|
||||
if (!(await SettingsProvider.GetValueAsync(BuiltIn.Keys.CaptureEnabled)).ToBoolean()) continue;
|
||||
_ = new StreamCapture("https://bossmanjack.tv", StreamCaptureMethods.YtDlp, ct).CaptureAsync();
|
||||
_ = new StreamCapture("https://bossmanjack.tv", StreamCaptureMethods.YtDlp, null, ct).CaptureAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ public class PeerTube(ChatBot kfChatBot) : IDisposable
|
||||
{
|
||||
if (persistedLive.Contains(stream.Uuid)) continue;
|
||||
StreamDbModel? dbEntry = null;
|
||||
PeerTubeMetaModel? meta = null;
|
||||
foreach (var row in streams)
|
||||
{
|
||||
if (row.Metadata == null)
|
||||
@@ -64,13 +65,16 @@ public class PeerTube(ChatBot kfChatBot) : IDisposable
|
||||
_logger.Error($"Stream ID {row.Id} has null metadata");
|
||||
continue;
|
||||
}
|
||||
var meta = JsonSerializer.Deserialize<PeerTubeMetaModel>(row.Metadata);
|
||||
meta = JsonSerializer.Deserialize<PeerTubeMetaModel>(row.Metadata);
|
||||
if (meta == null)
|
||||
{
|
||||
_logger.Error($"Caught a null when deserializing the metadata for {row.Id}");
|
||||
continue;
|
||||
}
|
||||
if (meta.AccountName == stream.Account.Name) dbEntry = row;
|
||||
|
||||
if (meta.AccountName != stream.Account.Name) continue;
|
||||
dbEntry = row;
|
||||
break;
|
||||
}
|
||||
if (settings[BuiltIn.Keys.KiwiPeerTubeEnforceWhitelist].ToBoolean() && dbEntry == null)
|
||||
{
|
||||
@@ -89,7 +93,7 @@ public class PeerTube(ChatBot kfChatBot) : IDisposable
|
||||
continue;
|
||||
}
|
||||
_logger.Info($"{stream.Url} is live and set to auto capture (if configured)");
|
||||
_ = new StreamCapture(stream.Url, StreamCaptureMethods.YtDlp, ct).CaptureAsync();
|
||||
_ = new StreamCapture(stream.Url, StreamCaptureMethods.YtDlp, meta?.CaptureOverrides, ct).CaptureAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using KfChatDotNetBot.Models.DbModels;
|
||||
using KfChatDotNetBot.Settings;
|
||||
using NLog;
|
||||
|
||||
@@ -9,7 +10,7 @@ namespace KfChatDotNetBot.Services;
|
||||
/// </summary>
|
||||
/// <param name="streamUrl">Streamer URL</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod, CancellationToken ct = default)
|
||||
public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod, CaptureOverridesModel? captureOverrides = null, CancellationToken ct = default)
|
||||
{
|
||||
private readonly Dictionary<string, Setting> _settings = SettingsProvider
|
||||
.GetMultipleValuesAsync([BuiltIn.Keys.CaptureYtDlpBinaryPath, BuiltIn.Keys.CaptureYtDlpWorkingDirectory,
|
||||
@@ -38,7 +39,7 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
|
||||
{
|
||||
pStartInfoFileName = _settings[BuiltIn.Keys.CaptureYtDlpParentTerminal].Value!;
|
||||
pStartInfoFileName = captureOverrides?.CaptureYtDlpParentTerminal ?? _settings[BuiltIn.Keys.CaptureYtDlpParentTerminal].Value!;
|
||||
pStartInfoExecuteArgument = "-x";
|
||||
pStartInfoExecuteScript = scriptPath;
|
||||
}
|
||||
@@ -51,7 +52,7 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
|
||||
{
|
||||
FileName = pStartInfoFileName,
|
||||
ArgumentList = { pStartInfoExecuteArgument, pStartInfoExecuteScript },
|
||||
WorkingDirectory = _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value
|
||||
WorkingDirectory = captureOverrides?.CaptureYtDlpWorkingDirectory ?? _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value
|
||||
});
|
||||
|
||||
if (process == null)
|
||||
@@ -94,7 +95,7 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
|
||||
private async Task<string> CreateScriptAsync()
|
||||
{
|
||||
var random = Convert.ToHexString(Guid.NewGuid().ToByteArray()[..4]);
|
||||
var scriptPath = Path.Join(_settings[BuiltIn.Keys.CaptureYtDlpScriptPath].Value,
|
||||
var scriptPath = Path.Join(_settings[captureOverrides?.CaptureYtDlpScriptPath ?? BuiltIn.Keys.CaptureYtDlpScriptPath].Value,
|
||||
$"bot_ytdlp_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{random}.sh");
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
@@ -105,9 +106,10 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
|
||||
string captureLine;
|
||||
if (captureMethod == StreamCaptureMethods.YtDlp)
|
||||
{
|
||||
captureLine = $"{_settings[BuiltIn.Keys.CaptureYtDlpBinaryPath].Value} -o \"{_settings[BuiltIn.Keys.CaptureYtDlpOutputFormat].Value}\" " +
|
||||
$"--user-agent \"{_settings[BuiltIn.Keys.CaptureYtDlpUserAgent].Value}\" " +
|
||||
$"--cookies-from-browser {_settings[BuiltIn.Keys.CaptureYtDlpCookiesFromBrowser].Value} " +
|
||||
captureLine = $"{captureOverrides?.CaptureYtDlpBinaryPath ?? _settings[BuiltIn.Keys.CaptureYtDlpBinaryPath].Value} " +
|
||||
$"-o \"{captureOverrides?.CaptureYtDlpOutputFormat ?? _settings[BuiltIn.Keys.CaptureYtDlpOutputFormat].Value}\" " +
|
||||
$"--user-agent \"{captureOverrides?.CaptureYtDlpUserAgent ?? _settings[BuiltIn.Keys.CaptureYtDlpUserAgent].Value}\" " +
|
||||
$"--cookies-from-browser {captureOverrides?.CaptureYtDlpCookiesFromBrowser ?? _settings[BuiltIn.Keys.CaptureYtDlpCookiesFromBrowser].Value} " +
|
||||
$"--write-info-json --wait-for-video 15 --merge-output-format mp4 --verbose {streamUrl}";
|
||||
}
|
||||
else if (captureMethod == StreamCaptureMethods.Streamlink)
|
||||
@@ -115,9 +117,10 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
|
||||
var twitchOpts = string.Empty;
|
||||
if (streamUrl.Contains("twitch.tv"))
|
||||
{
|
||||
twitchOpts = _settings[BuiltIn.Keys.CaptureStreamlinkTwitchOptions].Value;
|
||||
twitchOpts = captureOverrides?.CaptureStreamlinkTwitchOptions ?? _settings[BuiltIn.Keys.CaptureStreamlinkTwitchOptions].Value;
|
||||
}
|
||||
captureLine = $"{_settings[BuiltIn.Keys.CaptureStreamlinkBinaryPath].Value} {twitchOpts} --output \"{_settings[BuiltIn.Keys.CaptureStreamlinkOutputFormat].Value}\" " +
|
||||
captureLine = $"{captureOverrides?.CaptureStreamlinkBinaryPath ?? _settings[BuiltIn.Keys.CaptureStreamlinkBinaryPath].Value} {twitchOpts} " +
|
||||
$"--output \"{captureOverrides?.CaptureStreamlinkOutputFormat ?? _settings[BuiltIn.Keys.CaptureStreamlinkOutputFormat].Value}\" " +
|
||||
$"--retry-streams 15 --retry-max 10 {streamUrl} best";
|
||||
}
|
||||
else
|
||||
@@ -129,7 +132,7 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
|
||||
var remuxLine = string.Empty;
|
||||
if (captureMethod == StreamCaptureMethods.Streamlink)
|
||||
{
|
||||
remuxLine = _settings[BuiltIn.Keys.CaptureStreamlinkRemuxScript].Value;
|
||||
remuxLine = captureOverrides?.CaptureStreamlinkRemuxScript ?? _settings[BuiltIn.Keys.CaptureStreamlinkRemuxScript].Value;
|
||||
}
|
||||
|
||||
string scriptContent;
|
||||
@@ -138,8 +141,8 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
|
||||
{
|
||||
// GetPathRoot on Windows returns the top level directory, e.g. "C:\". Assuming the working directory is on another drive such as D:
|
||||
// we'll need to swap to that drive letter, so this just trims off the \ to transform it to D: or whatever. UNC paths not supported
|
||||
scriptContent = $"{Path.GetPathRoot(_settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value)?.TrimEnd('\\')}{Environment.NewLine}" +
|
||||
$"CD {_settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value}{Environment.NewLine}" +
|
||||
scriptContent = $"{Path.GetPathRoot(captureOverrides?.CaptureYtDlpWorkingDirectory ?? _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value)?.TrimEnd('\\')}{Environment.NewLine}" +
|
||||
$"CD {captureOverrides?.CaptureYtDlpWorkingDirectory ?? _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value}{Environment.NewLine}" +
|
||||
$"{captureLine}{Environment.NewLine}" +
|
||||
$"{remuxLine}{Environment.NewLine}" +
|
||||
$"PAUSE";
|
||||
@@ -147,7 +150,7 @@ public class StreamCapture(string streamUrl, StreamCaptureMethods captureMethod,
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
|
||||
{
|
||||
scriptContent = $"#!/bin/bash{Environment.NewLine}" +
|
||||
$"cd {_settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value}{Environment.NewLine}" +
|
||||
$"cd {captureOverrides?.CaptureYtDlpWorkingDirectory ?? _settings[BuiltIn.Keys.CaptureYtDlpWorkingDirectory].Value}{Environment.NewLine}" +
|
||||
$"{captureLine}{Environment.NewLine}" +
|
||||
$"{remuxLine}{Environment.NewLine}" +
|
||||
$"read -p \"Press enter to exit\"";
|
||||
|
||||
@@ -1061,6 +1061,13 @@ public static class BuiltIn
|
||||
Default = "300",
|
||||
ValueType = SettingValueType.Text,
|
||||
Regex = WholeNumberRegex
|
||||
},
|
||||
new BuiltInSettingsModel
|
||||
{
|
||||
Key = Keys.CaptureStreamlinkBmjWorkingDirectory,
|
||||
Description = "Working directory for BMJ's Twitch streams captured with streamlink",
|
||||
Default = "/root/twitch/",
|
||||
ValueType = SettingValueType.Text
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1183,5 +1190,6 @@ public static class BuiltIn
|
||||
public static string BotImageChinkSelfDestructDelay = "Bot.Image.ChinkSelfDestructDelay";
|
||||
public static string BotRateLimitCooldownAutoDeleteDelay = "Bot.RateLimit.CooldownAutoDeleteDelay";
|
||||
public static string BotRateLimitExpiredEntryCleanupInterval = "Bot.RateLimit.ExpiredEntryCleanupInterval";
|
||||
public static string CaptureStreamlinkBmjWorkingDirectory = "Bot.Streamlink.BmjWorkingDirectory";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user