using HandyControl.Controls;
using Microsoft.Win32;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using TwitchDownloaderCore;
using TwitchDownloaderCore.Chat;
using TwitchDownloaderCore.Options;
using TwitchDownloaderCore.TwitchObjects;
using TwitchDownloaderWPF.Extensions;
using TwitchDownloaderWPF.Models;
using TwitchDownloaderWPF.Properties;
using TwitchDownloaderWPF.Utils;
using WpfAnimatedGif;
using MessageBox = System.Windows.MessageBox;
using TextBox = System.Windows.Controls.TextBox;
namespace TwitchDownloaderWPF
{
///
/// Interaction logic for PageChatRender.xaml
///
public partial class PageChatRender : Page
{
public List ffmpegLog = new List();
public SKFontManager fontManager = SKFontManager.CreateDefault();
public string[] FileNames = Array.Empty();
private CancellationTokenSource _cancellationTokenSource;
public PageChatRender()
{
InitializeComponent();
App.CultureServiceSingleton.CultureChanged += OnCultureChanged;
}
private void OnCultureChanged(object sender, CultureInfo e)
{
if (IsInitialized)
{
LoadSettings();
}
}
private void btnBrowse_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "JSON Files | *.json;*.json.gz";
openFileDialog.Multiselect = true;
if (openFileDialog.ShowDialog() == false)
{
return;
}
FileNames = openFileDialog.FileNames;
textJson.Text = string.Join("&&", FileNames);
UpdateActionButtons(false);
}
private void UpdateActionButtons(bool isRendering)
{
if (isRendering)
{
SplitBtnRender.Visibility = Visibility.Collapsed;
BtnEnqueue.Visibility = Visibility.Collapsed;
BtnCancel.Visibility = Visibility.Visible;
return;
}
if (FileNames.Length > 1)
{
SplitBtnRender.Visibility = Visibility.Collapsed;
BtnEnqueue.Visibility = Visibility.Visible;
BtnCancel.Visibility = Visibility.Collapsed;
return;
}
SplitBtnRender.Visibility = Visibility.Visible;
BtnEnqueue.Visibility = Visibility.Collapsed;
BtnCancel.Visibility = Visibility.Collapsed;
}
public ChatRenderOptions GetOptions(string filename)
{
SKColor backgroundColor = new(colorBackground.SelectedColor.Value.R, colorBackground.SelectedColor.Value.G, colorBackground.SelectedColor.Value.B, colorBackground.SelectedColor.Value.A);
SKColor altBackgroundColor = new(colorAlternateBackground.SelectedColor.Value.R, colorAlternateBackground.SelectedColor.Value.G, colorAlternateBackground.SelectedColor.Value.B, colorAlternateBackground.SelectedColor.Value.A);
SKColor messageColor = new(colorFont.SelectedColor.Value.R, colorFont.SelectedColor.Value.G, colorFont.SelectedColor.Value.B);
ChatRenderOptions options = new()
{
OutputFile = filename,
InputFile = textJson.Text,
BackgroundColor = backgroundColor,
AlternateBackgroundColor = altBackgroundColor,
AlternateMessageBackgrounds = checkAlternateMessageBackgrounds.IsChecked.GetValueOrDefault(),
ChatHeight = int.Parse(textHeight.Text),
ChatWidth = int.Parse(textWidth.Text),
BttvEmotes = checkBTTV.IsChecked.GetValueOrDefault(),
FfzEmotes = checkFFZ.IsChecked.GetValueOrDefault(),
StvEmotes = checkSTV.IsChecked.GetValueOrDefault(),
Outline = checkOutline.IsChecked.GetValueOrDefault(),
Font = (string)comboFont.SelectedItem,
FontSize = numFontSize.Value,
UpdateRate = double.Parse(textUpdateTime.Text, CultureInfo.CurrentCulture),
EmoteScale = double.Parse(textEmoteScale.Text, CultureInfo.CurrentCulture),
BadgeScale = double.Parse(textBadgeScale.Text, CultureInfo.CurrentCulture),
EmojiScale = double.Parse(textEmojiScale.Text, CultureInfo.CurrentCulture),
SidePaddingScale = double.Parse(textSidePaddingScale.Text, CultureInfo.CurrentCulture),
SectionHeightScale = double.Parse(textSectionHeightScale.Text, CultureInfo.CurrentCulture),
WordSpacingScale = double.Parse(textWordSpaceScale.Text, CultureInfo.CurrentCulture),
EmoteSpacingScale = double.Parse(textEmoteSpaceScale.Text, CultureInfo.CurrentCulture),
AccentIndentScale = double.Parse(textAccentIndentScale.Text, CultureInfo.CurrentCulture),
AccentStrokeScale = double.Parse(textAccentStrokeScale.Text, CultureInfo.CurrentCulture),
VerticalSpacingScale = double.Parse(textVerticalScale.Text, CultureInfo.CurrentCulture),
IgnoreUsersArray = textIgnoreUsersList.Text.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries),
BannedWordsArray = textBannedWordsList.Text.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries),
Timestamp = checkTimestamp.IsChecked.GetValueOrDefault(),
MessageColor = messageColor,
Framerate = int.Parse(textFramerate.Text),
InputArgs = CheckRenderSharpening.IsChecked == true ? textFfmpegInput.Text + " -filter_complex \"smartblur=lr=1:ls=-1.0\"" : textFfmpegInput.Text,
OutputArgs = textFfmpegOutput.Text,
MessageFontStyle = SKFontStyle.Normal,
UsernameFontStyle = SKFontStyle.Bold,
GenerateMask = checkMask.IsChecked.GetValueOrDefault(),
OutlineSize = 4 * double.Parse(textOutlineScale.Text, CultureInfo.CurrentCulture),
FfmpegPath = "ffmpeg",
TempFolder = Settings.Default.TempPath,
SubMessages = checkSub.IsChecked.GetValueOrDefault(),
ChatBadges = checkBadge.IsChecked.GetValueOrDefault(),
Offline = checkOffline.IsChecked.GetValueOrDefault(),
AllowUnlistedEmotes = true,
DisperseCommentOffsets = checkDispersion.IsChecked.GetValueOrDefault(),
AdjustUsernameVisibility = checkAdjustUsernameVisibility.IsChecked.GetValueOrDefault(),
};
if (RadioEmojiNotoColor.IsChecked == true)
options.EmojiVendor = EmojiVendor.GoogleNotoColor;
else if (RadioEmojiTwemoji.IsChecked == true)
options.EmojiVendor = EmojiVendor.TwitterTwemoji;
else if (RadioEmojiNone.IsChecked == true)
options.EmojiVendor = EmojiVendor.None;
foreach (var item in comboBadges.SelectedItems)
{
options.ChatBadgeMask += (int)((CheckComboBoxItem)item).Tag;
}
return options;
}
private void LoadSettings()
{
try
{
comboFont.SelectedItem = Settings.Default.Font;
checkOutline.IsChecked = Settings.Default.Outline;
checkTimestamp.IsChecked = Settings.Default.Timestamp;
colorBackground.SelectedColor = System.Windows.Media.Color.FromArgb(Settings.Default.BackgroundColorA, Settings.Default.BackgroundColorR, Settings.Default.BackgroundColorG, Settings.Default.BackgroundColorB);
colorAlternateBackground.SelectedColor = System.Windows.Media.Color.FromArgb(Settings.Default.AlternateBackgroundColorA, Settings.Default.AlternateBackgroundColorR, Settings.Default.AlternateBackgroundColorG, Settings.Default.AlternateBackgroundColorB);
checkFFZ.IsChecked = Settings.Default.FFZEmotes;
checkBTTV.IsChecked = Settings.Default.BTTVEmotes;
checkSTV.IsChecked = Settings.Default.STVEmotes;
textHeight.Text = Settings.Default.Height.ToString();
textWidth.Text = Settings.Default.Width.ToString();
numFontSize.Value = Settings.Default.FontSize;
textUpdateTime.Text = Settings.Default.UpdateTime.ToString("0.0#");
colorFont.SelectedColor = System.Windows.Media.Color.FromRgb(Settings.Default.FontColorR, Settings.Default.FontColorG, Settings.Default.FontColorB);
textFramerate.Text = Settings.Default.Framerate.ToString();
checkMask.IsChecked = Settings.Default.GenerateMask;
CheckRenderSharpening.IsChecked = Settings.Default.ChatRenderSharpening;
checkSub.IsChecked = Settings.Default.SubMessages;
checkBadge.IsChecked = Settings.Default.ChatBadges;
textEmoteScale.Text = Settings.Default.EmoteScale.ToString("0.0#");
textEmojiScale.Text = Settings.Default.EmojiScale.ToString("0.0#");
textBadgeScale.Text = Settings.Default.BadgeScale.ToString("0.0#");
textVerticalScale.Text = Settings.Default.VerticalSpacingScale.ToString("0.0#");
textSidePaddingScale.Text = Settings.Default.LeftSpacingScale.ToString("0.0#");
textSectionHeightScale.Text = Settings.Default.SectionHeightScale.ToString("0.0#");
textWordSpaceScale.Text = Settings.Default.WordSpacingScale.ToString("0.0#");
textEmoteSpaceScale.Text = Settings.Default.EmoteSpacingScale.ToString("0.0#");
textAccentStrokeScale.Text = Settings.Default.AccentStrokeScale.ToString("0.0#");
textAccentIndentScale.Text = Settings.Default.AccentIndentScale.ToString("0.0#");
textOutlineScale.Text = Settings.Default.OutlineScale.ToString("0.0#");
textIgnoreUsersList.Text = Settings.Default.IgnoreUsersList;
textBannedWordsList.Text = Settings.Default.BannedWordsList;
checkOffline.IsChecked = Settings.Default.Offline;
checkDispersion.IsChecked = Settings.Default.DisperseCommentOffsets;
checkAlternateMessageBackgrounds.IsChecked = Settings.Default.AlternateMessageBackgrounds;
checkAdjustUsernameVisibility.IsChecked = Settings.Default.AdjustUsernameVisibility;
RadioEmojiNotoColor.IsChecked = (EmojiVendor)Settings.Default.RenderEmojiVendor == EmojiVendor.GoogleNotoColor;
RadioEmojiTwemoji.IsChecked = (EmojiVendor)Settings.Default.RenderEmojiVendor == EmojiVendor.TwitterTwemoji;
RadioEmojiNone.IsChecked = (EmojiVendor)Settings.Default.RenderEmojiVendor == EmojiVendor.None;
var badgeMask = (ChatBadgeType)Settings.Default.ChatBadgeMask;
foreach (CheckComboBoxItem item in comboBadges.Items)
{
if (badgeMask.HasFlag((Enum)item.Tag))
{
comboBadges.SelectedItems.Add(item);
}
}
foreach (VideoContainer container in comboFormat.Items)
{
if (container.Name == Settings.Default.VideoContainer)
{
comboFormat.SelectedItem = container;
foreach (Codec codec in container.SupportedCodecs)
{
comboCodec.Items.Add(codec);
if (codec.Name == Settings.Default.VideoCodec)
{
comboCodec.SelectedItem = codec;
}
}
break;
}
}
LoadFfmpegArgs();
}
catch { }
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });
e.Handled = true;
}
private void ComboCodec_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (comboCodec.SelectedItem != null)
{
LoadFfmpegArgs();
}
}
private void ComboFormat_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
VideoContainer currentContainer = (VideoContainer)comboFormat.SelectedItem;
comboCodec.Items.Clear();
foreach (Codec codec in currentContainer.SupportedCodecs)
{
comboCodec.Items.Add(codec);
if (Settings.Default.VideoCodec == codec.Name)
{
comboCodec.SelectedItem = codec;
}
}
if (comboCodec.SelectedItem == null)
{
comboCodec.SelectedIndex = 0;
}
}
public void SaveSettings()
{
Settings.Default.Font = comboFont.SelectedItem.ToString();
Settings.Default.Outline = checkOutline.IsChecked.GetValueOrDefault();
Settings.Default.Timestamp = checkTimestamp.IsChecked.GetValueOrDefault();
Settings.Default.BackgroundColorR = colorBackground.SelectedColor.GetValueOrDefault().R;
Settings.Default.BackgroundColorG = colorBackground.SelectedColor.GetValueOrDefault().G;
Settings.Default.BackgroundColorB = colorBackground.SelectedColor.GetValueOrDefault().B;
Settings.Default.BackgroundColorA = colorBackground.SelectedColor.GetValueOrDefault().A;
Settings.Default.AlternateBackgroundColorR = colorAlternateBackground.SelectedColor.GetValueOrDefault().R;
Settings.Default.AlternateBackgroundColorG = colorAlternateBackground.SelectedColor.GetValueOrDefault().G;
Settings.Default.AlternateBackgroundColorB = colorAlternateBackground.SelectedColor.GetValueOrDefault().B;
Settings.Default.AlternateBackgroundColorA = colorAlternateBackground.SelectedColor.GetValueOrDefault().A;
Settings.Default.FFZEmotes = checkFFZ.IsChecked.GetValueOrDefault();
Settings.Default.BTTVEmotes = checkBTTV.IsChecked.GetValueOrDefault();
Settings.Default.STVEmotes = checkSTV.IsChecked.GetValueOrDefault();
Settings.Default.FontColorR = colorFont.SelectedColor.GetValueOrDefault().R;
Settings.Default.FontColorG = colorFont.SelectedColor.GetValueOrDefault().G;
Settings.Default.FontColorB = colorFont.SelectedColor.GetValueOrDefault().B;
Settings.Default.GenerateMask = checkMask.IsChecked.GetValueOrDefault();
Settings.Default.ChatRenderSharpening = CheckRenderSharpening.IsChecked.GetValueOrDefault();
Settings.Default.SubMessages = checkSub.IsChecked.GetValueOrDefault();
Settings.Default.ChatBadges = checkBadge.IsChecked.GetValueOrDefault();
Settings.Default.Offline = checkOffline.IsChecked.GetValueOrDefault();
Settings.Default.DisperseCommentOffsets = checkDispersion.IsChecked.GetValueOrDefault();
Settings.Default.AlternateMessageBackgrounds = checkAlternateMessageBackgrounds.IsChecked.GetValueOrDefault();
Settings.Default.AdjustUsernameVisibility = checkAdjustUsernameVisibility.IsChecked.GetValueOrDefault();
if (comboFormat.SelectedItem != null)
{
Settings.Default.VideoContainer = ((VideoContainer)comboFormat.SelectedItem).Name;
}
if (comboCodec.SelectedItem != null)
{
Settings.Default.VideoCodec = ((Codec)comboCodec.SelectedItem).Name;
}
Settings.Default.IgnoreUsersList = string.Join(",", textIgnoreUsersList.Text
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries));
Settings.Default.BannedWordsList = string.Join(",", textBannedWordsList.Text
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries));
if (RadioEmojiNotoColor.IsChecked == true)
Settings.Default.RenderEmojiVendor = (int)EmojiVendor.GoogleNotoColor;
else if (RadioEmojiTwemoji.IsChecked == true)
Settings.Default.RenderEmojiVendor = (int)EmojiVendor.TwitterTwemoji;
else if (RadioEmojiNone.IsChecked == true)
Settings.Default.RenderEmojiVendor = (int)EmojiVendor.None;
int newMask = 0;
foreach (var item in comboBadges.SelectedItems)
{
newMask += (int)((CheckComboBoxItem)item).Tag;
}
Settings.Default.ChatBadgeMask = newMask;
try
{
Settings.Default.Height = int.Parse(textHeight.Text);
Settings.Default.Width = int.Parse(textWidth.Text);
Settings.Default.FontSize = (float)numFontSize.Value;
Settings.Default.UpdateTime = double.Parse(textUpdateTime.Text, CultureInfo.CurrentCulture);
Settings.Default.Framerate = int.Parse(textFramerate.Text);
Settings.Default.EmoteScale = double.Parse(textEmoteScale.Text, CultureInfo.CurrentCulture);
Settings.Default.EmojiScale = double.Parse(textEmojiScale.Text, CultureInfo.CurrentCulture);
Settings.Default.BadgeScale = double.Parse(textBadgeScale.Text, CultureInfo.CurrentCulture);
Settings.Default.VerticalSpacingScale = double.Parse(textVerticalScale.Text, CultureInfo.CurrentCulture);
Settings.Default.LeftSpacingScale = double.Parse(textSidePaddingScale.Text, CultureInfo.CurrentCulture);
Settings.Default.SectionHeightScale = double.Parse(textSectionHeightScale.Text, CultureInfo.CurrentCulture);
Settings.Default.WordSpacingScale = double.Parse(textWordSpaceScale.Text, CultureInfo.CurrentCulture);
Settings.Default.EmoteSpacingScale = double.Parse(textEmoteSpaceScale.Text, CultureInfo.CurrentCulture);
Settings.Default.AccentStrokeScale = double.Parse(textAccentStrokeScale.Text, CultureInfo.CurrentCulture);
Settings.Default.AccentIndentScale = double.Parse(textAccentIndentScale.Text, CultureInfo.CurrentCulture);
Settings.Default.OutlineScale = double.Parse(textOutlineScale.Text, CultureInfo.CurrentCulture);
}
catch { }
Settings.Default.Save();
}
private bool ValidateInputs()
{
if (FileNames.Length == 0)
{
AppendLog(Translations.Strings.ErrorLog + Translations.Strings.NoJsonFilesSelected);
return false;
}
foreach (string fileName in FileNames)
{
if (!File.Exists(fileName))
{
AppendLog(Translations.Strings.ErrorLog + Translations.Strings.FileNotFound + Path.GetFileName(fileName));
return false;
}
}
try
{
_ = int.Parse(textHeight.Text);
_ = int.Parse(textWidth.Text);
_ = double.Parse(textUpdateTime.Text, CultureInfo.CurrentCulture);
_ = int.Parse(textFramerate.Text);
_ = double.Parse(textEmoteScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textBadgeScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textEmojiScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textVerticalScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textSidePaddingScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textSectionHeightScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textWordSpaceScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textEmoteSpaceScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textAccentStrokeScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textAccentIndentScale.Text, CultureInfo.CurrentCulture);
_ = double.Parse(textOutlineScale.Text, CultureInfo.CurrentCulture);
}
catch (Exception ex)
{
AppendLog(Translations.Strings.ErrorLog + ex.Message);
return false;
}
if (checkMask.IsChecked == false && (colorBackground.SelectedColor!.Value.A < 255 || ((bool)checkAlternateMessageBackgrounds.IsChecked! && colorAlternateBackground.SelectedColor!.Value.A < 255)))
{
if (((VideoContainer)comboFormat.SelectedItem).Name is not "MOV" and not "WEBM" ||
((Codec)comboCodec.SelectedItem).Name is not "RLE" and not "ProRes" and not "VP8" and not "VP9")
{
AppendLog(Translations.Strings.ErrorLog + Translations.Strings.AlphaNotSupportedByCodec);
return false;
}
}
if (checkMask.IsChecked == true && colorBackground.SelectedColor!.Value.A == 255 && !((bool)checkAlternateMessageBackgrounds.IsChecked! && colorAlternateBackground.SelectedColor!.Value.A != 255))
{
AppendLog(Translations.Strings.ErrorLog + Translations.Strings.MaskWithNoAlpha);
return false;
}
if (int.Parse(textHeight.Text) % 2 != 0 || int.Parse(textWidth.Text) % 2 != 0)
{
AppendLog(Translations.Strings.ErrorLog + Translations.Strings.RenderWidthHeightMustBeEven);
return false;
}
return true;
}
private void SetPercent(int percent)
{
Dispatcher.BeginInvoke(() =>
statusProgressBar.Value = percent
);
}
private void SetStatus(string message)
{
Dispatcher.BeginInvoke(() =>
statusMessage.Text = message
);
}
private void AppendLog(string message)
{
textLog.Dispatcher.BeginInvoke(() =>
textLog.AppendText(message + Environment.NewLine)
);
}
public void SetImage(string imageUri, bool isGif)
{
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(imageUri, UriKind.Relative);
image.EndInit();
if (isGif)
{
ImageBehavior.SetAnimatedSource(statusImage, image);
}
else
{
ImageBehavior.SetAnimatedSource(statusImage, null);
statusImage.Source = image;
}
}
private void Page_Initialized(object sender, EventArgs e)
{
var fonts = fontManager.FontFamilies.ToList();
fonts.Add("Inter Embedded");
fonts.Sort();
foreach (var font in fonts)
{
comboFont.Items.Add(font);
}
comboFont.SelectedItem = "Inter Embedded";
Codec h264Codec = new Codec() { Name = "H264", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v libx264 -preset:v veryfast -crf 18 -pix_fmt yuv420p \"{save_path}\"" };
Codec h264NvencCodec = new Codec() { Name = "H264 NVIDIA", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v h264_nvenc -preset:v p4 -cq 20 -pix_fmt yuv420p \"{save_path}\"" };
Codec h264AmfCodec = new Codec() { Name = "H264 AMD", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v h264_amf -preset:v p4 -cq 20 -pix_fmt yuv420p \"{save_path}\"" };
Codec h265Codec = new Codec() { Name = "H265", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v libx265 -preset:v veryfast -crf 18 -pix_fmt yuv420p \"{save_path}\"" };
Codec h265NvencCodec = new Codec() { Name = "H265 NVIDIA", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v hevc_nvenc -preset:v p4 -cq 21 -pix_fmt yuv420p \"{save_path}\"" };
Codec h265AmfCodec = new Codec() { Name = "H265 AMD", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v hevc_amf -preset:v p4 -cq 21 -pix_fmt yuv420p \"{save_path}\"" };
Codec vp8Codec = new Codec() { Name = "VP8", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v libvpx -crf 18 -b:v 2M -pix_fmt yuva420p -auto-alt-ref 0 \"{save_path}\"" };
Codec vp9Codec = new Codec() { Name = "VP9", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v libvpx-vp9 -crf 18 -b:v 2M -deadline realtime -quality realtime -speed 3 -pix_fmt yuva420p \"{save_path}\"" };
Codec rleCodec = new Codec() { Name = "RLE", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v qtrle -pix_fmt argb \"{save_path}\"" };
Codec proresCodec = new Codec() { Name = "ProRes", InputArgs = "-framerate {fps} -f rawvideo -analyzeduration {max_int} -probesize {max_int} -pix_fmt bgra -video_size {width}x{height} -i -", OutputArgs = "-c:v prores_ks -qscale:v 62 -pix_fmt argb \"{save_path}\"" };
VideoContainer mp4Container = new VideoContainer() { Name = "MP4", SupportedCodecs = new List() { h264Codec, h265Codec, h264NvencCodec, h265NvencCodec, h264AmfCodec, h265AmfCodec } };
VideoContainer movContainer = new VideoContainer() { Name = "MOV", SupportedCodecs = new List() { h264Codec, h265Codec, rleCodec, proresCodec, h264NvencCodec, h265NvencCodec, h264AmfCodec, h265AmfCodec } };
VideoContainer webmContainer = new VideoContainer() { Name = "WEBM", SupportedCodecs = new List() { vp8Codec, vp9Codec } };
VideoContainer mkvContainer = new VideoContainer() { Name = "MKV", SupportedCodecs = new List() { h264Codec, h265Codec, vp8Codec, vp9Codec, h264NvencCodec, h265NvencCodec, h264AmfCodec, h265AmfCodec } };
comboFormat.Items.Add(mp4Container);
comboFormat.Items.Add(movContainer);
comboFormat.Items.Add(webmContainer);
comboFormat.Items.Add(mkvContainer);
comboBadges.Items.Add(new CheckComboBoxItem { Content = Translations.Strings.BadgeMaskBroadcaster, Tag = ChatBadgeType.Broadcaster });
comboBadges.Items.Add(new CheckComboBoxItem { Content = Translations.Strings.BadgeMaskModerator, Tag = ChatBadgeType.Moderator });
comboBadges.Items.Add(new CheckComboBoxItem { Content = Translations.Strings.BadgeMaskVIP, Tag = ChatBadgeType.VIP });
comboBadges.Items.Add(new CheckComboBoxItem { Content = Translations.Strings.BadgeMaskSubscriber, Tag = ChatBadgeType.Subscriber });
comboBadges.Items.Add(new CheckComboBoxItem { Content = Translations.Strings.BadgeMaskPredictions, Tag = ChatBadgeType.Predictions });
comboBadges.Items.Add(new CheckComboBoxItem { Content = Translations.Strings.BadgeMaskNoAudioNoVideo, Tag = ChatBadgeType.NoAudioVisual });
comboBadges.Items.Add(new CheckComboBoxItem { Content = Translations.Strings.BadgeMaskTwitchPrime, Tag = ChatBadgeType.PrimeGaming });
comboBadges.Items.Add(new CheckComboBoxItem { Content = Translations.Strings.BadgeMaskOthers, Tag = ChatBadgeType.Other });
}
private void Page_Unloaded(object sender, RoutedEventArgs e)
{
SaveSettings();
}
private void btnDonate_Click(object sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo("https://www.buymeacoffee.com/lay295") { UseShellExecute = true });
}
private void btnSettings_Click(object sender, RoutedEventArgs e)
{
SaveSettings();
var settings = new WindowSettings
{
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
settings.ShowDialog();
btnDonate.Visibility = Settings.Default.HideDonation ? Visibility.Collapsed : Visibility.Visible;
statusImage.Visibility = Settings.Default.ReduceMotion ? Visibility.Collapsed : Visibility.Visible;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
LoadSettings();
btnDonate.Visibility = Settings.Default.HideDonation ? Visibility.Collapsed : Visibility.Visible;
statusImage.Visibility = Settings.Default.ReduceMotion ? Visibility.Collapsed : Visibility.Visible;
}
private void btnResetFfmpeg_Click(object sender, RoutedEventArgs e)
{
textFfmpegInput.Text = ((Codec)comboCodec.SelectedItem).InputArgs;
textFfmpegOutput.Text = ((Codec)comboCodec.SelectedItem).OutputArgs;
SaveArguments();
}
private void SaveArguments()
{
List args = JsonSerializer.Deserialize>(Settings.Default.FfmpegArguments);
bool foundArg = false;
foreach (CustomFfmpegArgs arg in args)
{
if (arg.CodecName == ((Codec)comboCodec.SelectedItem).Name && arg.ContainerName == ((VideoContainer)comboFormat.SelectedItem).Name)
{
arg.InputArgs = textFfmpegInput.Text;
arg.OutputArgs = textFfmpegOutput.Text;
foundArg = true;
break;
}
}
//Didn't find pre-existing save, make a new one
if (!foundArg)
{
CustomFfmpegArgs newArgs = new CustomFfmpegArgs() { CodecName = ((Codec)comboCodec.SelectedItem).Name, ContainerName = ((VideoContainer)comboFormat.SelectedItem).Name, InputArgs = textFfmpegInput.Text, OutputArgs = textFfmpegOutput.Text };
args.Add(newArgs);
}
Settings.Default.FfmpegArguments = JsonSerializer.Serialize(args);
Settings.Default.Save();
}
private void LoadFfmpegArgs()
{
List args = JsonSerializer.Deserialize>(Settings.Default.FfmpegArguments);
bool foundArg = false;
foreach (CustomFfmpegArgs arg in args)
{
if (arg.CodecName == ((Codec)comboCodec.SelectedItem).Name && arg.ContainerName == ((VideoContainer)comboFormat.SelectedItem).Name)
{
textFfmpegInput.Text = arg.InputArgs;
textFfmpegOutput.Text = arg.OutputArgs;
foundArg = true;
break;
}
}
if (!foundArg)
{
textFfmpegInput.Text = ((Codec)comboCodec.SelectedItem).InputArgs;
textFfmpegOutput.Text = ((Codec)comboCodec.SelectedItem).OutputArgs;
}
}
private void textFfmpegInput_LostFocus(object sender, RoutedEventArgs e)
{
SaveArguments();
}
private void textFfmpegOutput_LostFocus(object sender, RoutedEventArgs e)
{
SaveArguments();
}
private async void SplitBtnRender_Click(object sender, RoutedEventArgs e)
{
if (ReferenceEquals(sender, MenuItemPartialRender) || !((SplitButton)sender).IsDropDownOpen)
{
if (!ValidateInputs())
{
MessageBox.Show(Application.Current.MainWindow!, Translations.Strings.UnableToParseInputsMessage, Translations.Strings.UnableToParseInputs, MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
string fileFormat = comboFormat.SelectedItem.ToString()!;
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = $"{fileFormat} Files | *.{fileFormat.ToLower()}",
FileName = Path.GetFileNameWithoutExtension(textJson.Text.Replace(".gz", "")) + "." + fileFormat.ToLower()
};
if (saveFileDialog.ShowDialog() != true)
{
return;
}
SaveSettings();
ChatRenderOptions options = GetOptions(saveFileDialog.FileName);
var renderProgress = new WpfTaskProgress((LogLevel)Settings.Default.LogLevels, SetPercent, SetStatus, AppendLog, s => ffmpegLog.Add(s));
ChatRenderer currentRender = new ChatRenderer(options, renderProgress);
try
{
await currentRender.ParseJsonAsync(CancellationToken.None);
}
catch (Exception ex)
{
AppendLog(Translations.Strings.ErrorLog + ex.Message);
if (Settings.Default.VerboseErrors)
{
MessageBox.Show(Application.Current.MainWindow!, ex.ToString(), Translations.Strings.VerboseErrorOutput, MessageBoxButton.OK, MessageBoxImage.Error);
}
return;
}
if (ReferenceEquals(sender, MenuItemPartialRender))
{
var window = new WindowRangeSelect(currentRender)
{
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
window.ShowDialog();
if (window.OK)
{
options.StartOverride = window.startSeconds;
options.EndOverride = window.endSeconds;
}
else
{
if (window.Invalid)
{
AppendLog(Translations.Strings.ErrorLog + Translations.Strings.InvalidStartEndTime);
}
return;
}
}
SetImage("Images/ppOverheat.gif", true);
statusMessage.Text = Translations.Strings.StatusRendering;
ffmpegLog.Clear();
_cancellationTokenSource = new CancellationTokenSource();
UpdateActionButtons(true);
try
{
await currentRender.RenderVideoAsync(_cancellationTokenSource.Token);
renderProgress.SetStatus(Translations.Strings.StatusDone);
SetImage("Images/ppHop.gif", true);
}
catch (Exception ex) when (ex is OperationCanceledException or TaskCanceledException && _cancellationTokenSource.IsCancellationRequested)
{
renderProgress.SetStatus(Translations.Strings.StatusCanceled);
SetImage("Images/ppHop.gif", true);
}
catch (Exception ex)
{
renderProgress.SetStatus(Translations.Strings.StatusError);
SetImage("Images/peepoSad.png", false);
AppendLog(Translations.Strings.ErrorLog + ex.Message);
if (Settings.Default.VerboseErrors)
{
if (ex.Message.Contains("The pipe has been ended"))
{
string errorLog = String.Join('\n', ffmpegLog.TakeLast(20).ToArray());
MessageBox.Show(Application.Current.MainWindow!, errorLog, Translations.Strings.VerboseErrorOutput, MessageBoxButton.OK, MessageBoxImage.Error);
}
else
{
MessageBox.Show(Application.Current.MainWindow!, ex.ToString(), Translations.Strings.VerboseErrorOutput, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
renderProgress.ReportProgress(0);
_cancellationTokenSource.Dispose();
UpdateActionButtons(false);
currentRender.Dispose();
GC.Collect();
}
}
private void BtnEnqueue_Click(object sender, RoutedEventArgs e)
{
EnqueueRender();
}
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
statusMessage.Text = Translations.Strings.StatusCanceling;
SetImage("Images/ppStretch.gif", true);
try
{
_cancellationTokenSource.Cancel();
}
catch (ObjectDisposedException) { }
}
private void MenuItemEnqueue_Click(object sender, RoutedEventArgs e)
{
if (!SplitBtnRender.IsDropDownOpen)
{
return;
}
if (ValidateInputs())
{
EnqueueRender();
}
else
{
MessageBox.Show(Application.Current.MainWindow!, Translations.Strings.UnableToParseInputsMessage, Translations.Strings.UnableToParseInputs, MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void EnqueueRender()
{
var queueOptions = new WindowQueueOptions(this)
{
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
queueOptions.ShowDialog();
}
private void MenuItemPartialRender_Click(object sender, RoutedEventArgs e)
{
SplitBtnRender_Click(sender, e);
}
private void TextJson_TextChanged(object sender, TextChangedEventArgs e)
{
if (!IsInitialized)
return;
FileNames = textJson.Text.Split("&&", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
UpdateActionButtons(false);
}
private void FfmpegParameter_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!IsInitialized || sender is not Run { Text: var parameter })
return;
if (e.ChangedButton is not MouseButton.Left and not MouseButton.Middle)
return;
var focusedElement = Keyboard.FocusedElement;
var textBox = GetFfmpegTemplateTextBox(focusedElement);
if (textBox is null)
return;
if (textBox.TryInsertAtCaret(parameter))
{
e.Handled = true;
}
}
[return: MaybeNull]
private TextBox GetFfmpegTemplateTextBox(IInputElement inputElement)
{
if (ReferenceEquals(inputElement, textFfmpegInput))
return textFfmpegInput;
if (ReferenceEquals(inputElement, textFfmpegOutput))
return textFfmpegOutput;
return null;
}
}
public class VideoContainer
{
public string Name;
public List SupportedCodecs;
public override string ToString()
{
return Name;
}
}
public class Codec
{
public string Name;
public string InputArgs;
public string OutputArgs;
public override string ToString()
{
return Name;
}
}
}