diff --git a/2dxAutoClip/iidxAutoClip.cs b/2dxAutoClip/iidxAutoClip.cs index 48ce9e4..2698386 100644 --- a/2dxAutoClip/iidxAutoClip.cs +++ b/2dxAutoClip/iidxAutoClip.cs @@ -3,394 +3,312 @@ using System.Net.WebSockets; using System.Text; using NAudio.CoreAudioApi; using NAudio.Wave; -#pragma warning disable CA1416 -namespace _2dxAutoClip +namespace _2dxAutoClip; + +class Program { - class Program + private static readonly string WebsocketAddress = "localhost"; + private static readonly int WebsocketPort = 10573; + private static Process? _ffmpegProcess; + private static string _ffmpegFolderPath = GetDefaultVideosFolderPath(); + private static WasapiCapture? _waveSource; + private static WaveFileWriter _writer = null!; + private static string _audioFilePath = null!; + private static string _videoFilePath = null!; + private static string _resolution = "1920x1080"; // Default resolution + private static int _framerate = 60; // Default framerate + private static float _crf = 23; // Default CRF value + private static string _gameProcessName = "spice64"; // Default game process name + + private static async Task Main(string[] args) { - private static readonly string WebsocketAddress = "localhost"; - private static readonly int WebsocketPort = 10573; - private static Process? _ffmpegProcess; - private static string _ffmpegFolderPath = GetDefaultVideosFolderPath(); - private static WasapiCapture? _waveSource; - private static AsioOut? _asioSource; - private static WaveFileWriter _writer = null!; - private static string _audioFilePath = null!; - private static string _videoFilePath = null!; - private static string _resolution = "1920x1080"; // Default resolution - private static int _framerate = 60; // Default framerate - private static float _crf = 23; // Default CRF value - private static bool _useAsio = false; - private static string _gameProcessName = "spice64"; // Default game process name + // Load settings from prop.txt + LoadSettingsFromPropFile(); - private static async Task Main(string[] args) + var gameProcesses = Process.GetProcessesByName(_gameProcessName); + if (gameProcesses.Length > 0) { - // Load settings from prop.txt - LoadSettingsFromPropFile(); - - var gameProcesses = Process.GetProcessesByName(_gameProcessName); - if (gameProcesses.Length > 0) - { - Console.WriteLine($"Found {_gameProcessName}, Attempting connection to TickerHookWS..."); - await TryConnectWebSocket(); - } - else - { - Console.WriteLine($"Unable to find {_gameProcessName}. Is the game running and TickerHook enabled?"); - } + Console.WriteLine($"Found {_gameProcessName}, Attempting connection to TickerHookWS..."); + await TryConnectWebSocket(); } - - private static void LoadSettingsFromPropFile() + else { - const string filePath = "prop.txt"; - if (File.Exists(filePath)) - { - var fileContent = File.ReadAllText(filePath); - var lines = fileContent.Split('\n'); - foreach (var line in lines) - { - if (line.StartsWith("path:")) - { - var path = line["path:".Length..].Trim(); - if (Directory.Exists(path)) - { - _ffmpegFolderPath = path; // Recording path - } - else - { - Console.WriteLine( - $"The path specified in {filePath} does not exist. Using default recording path."); - _ffmpegFolderPath = GetDefaultVideosFolderPath(); - } - } - - if (line.StartsWith("resolution:")) - { - _resolution = line["resolution:".Length..].Trim(); - Console.WriteLine($"Custom Resolution: {_resolution}"); - } - - if (line.StartsWith("framerate:")) - { - _framerate = int.Parse(line["framerate:".Length..].Trim()); - Console.WriteLine($"Custom framerate: {_framerate}"); - } - - if (line.StartsWith("crf:")) - { - _crf = float.Parse(line["crf:".Length..].Trim()); - Console.WriteLine($"Custom crf: {_crf}"); - } - - if (line.StartsWith("game_process_name:")) - { - _gameProcessName = line["game_process_name:".Length..].Trim(); - Console.WriteLine($"Custom process name: {_gameProcessName}"); - } - - if (line.StartsWith("useAsio")) - { - _useAsio = line.Split('=')[1].Trim().ToLower() == "true"; - Console.WriteLine("ASIO Recording is enabled"); - } - } - } - else - { - Console.WriteLine($"The file {filePath} does not exist. Using default values."); - _ffmpegFolderPath = GetDefaultVideosFolderPath(); - } - } - - private static string GetDefaultVideosFolderPath() - { - var userName = Environment.UserName; - return Path.Combine($@"C:\Users\{userName}", "Videos"); - } - - private static async Task TryConnectWebSocket() - { - const int maxRetries = 5; - int attempt = 0; - while (attempt < maxRetries) - { - Console.WriteLine($"Attempt {attempt + 1} of {maxRetries} to connect..."); - bool success = await ConnectWebSocket(); - if (success) break; - attempt++; - if (attempt < maxRetries) - { - Console.WriteLine($"Retrying in 10 seconds... {maxRetries - attempt} attempts remaining."); - await Task.Delay(10000); - } - } - - if (attempt == maxRetries) - { - Console.WriteLine("Failed to connect after 5 attempts."); - } - } - - private static async Task ConnectWebSocket() - { - var tickerUri = new Uri($"ws://{WebsocketAddress}:{WebsocketPort}"); - var reconnecting = false; - var lastMessage = string.Empty; - var consecutiveMessageCount = 0; - var isRecording = false; - var currentSongName = string.Empty; - var shouldCheckForMusicSelect = false; - - using var clientWebSocket = new ClientWebSocket(); - try - { - await clientWebSocket.ConnectAsync(tickerUri, CancellationToken.None); - Console.WriteLine("Connected to TickerHook WebSocket."); - } - catch (Exception ex) - { - Console.WriteLine($"Error connecting to TickerHook WebSocket: {ex.Message}"); - return false; - } - - var buffer = new byte[1024]; - while (clientWebSocket.State == WebSocketState.Open) - { - WebSocketReceiveResult result; - try - { - result = await clientWebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - } - catch (Exception ex) - { - Console.WriteLine($"Error receiving message: {ex.Message}"); - reconnecting = true; - break; - } - - var message = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim().ToUpper(); - Console.WriteLine($"Received message: {message}"); - - if (message == lastMessage && !message.Contains("SELECT FROM")) - { - consecutiveMessageCount++; - } - else - { - consecutiveMessageCount = 1; - lastMessage = message; - } - - if (consecutiveMessageCount >= 2 && !message.Contains("SELECT FROM") && !isRecording) - { - currentSongName = message; - Console.WriteLine("Starting recording..."); - StartRecording(currentSongName); - isRecording = true; - } - - if (isRecording) - { - if (shouldCheckForMusicSelect && message.Contains("MUSIC SELECT!!")) - { - Console.WriteLine("Stopping recording..."); - StopRecording(currentSongName); - isRecording = false; - shouldCheckForMusicSelect = false; - } - else if (message.EndsWith("CLEAR!") || message.EndsWith("FAILED..")) - { - shouldCheckForMusicSelect = true; - } - } - } - - return !reconnecting; - } - - private static void StartRecording(string songName) - { - Task.Run(() => StartAudioRecording(songName)); - StartFfmpegRecording(songName); - } - - private static void StartAudioRecording(string songName) - { - if (_useAsio) - { - var staThread = new Thread(() => - { - StartAsioAudioRecording(songName); - }); - staThread.SetApartmentState(ApartmentState.STA); - staThread.Start(); - } - else - { - StartWasapiAudioRecording(songName); - } - } - - - private static void StartWasapiAudioRecording(string songName) - { - try - { - var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); - _audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav"; - Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!); - _waveSource = new WasapiLoopbackCapture(); - _writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat); - _waveSource.DataAvailable += (sender, args) => { _writer.Write(args.Buffer, 0, args.BytesRecorded); }; - _waveSource.RecordingStopped += (sender, args) => - { - _writer.Dispose(); - _waveSource.Dispose(); - }; - _waveSource.StartRecording(); - Console.WriteLine("WASAPI Audio recording started."); - } - catch (Exception ex) - { - Console.WriteLine($"Error starting WASAPI audio recording: {ex.Message}"); - } - } - - private static void StartAsioAudioRecording(string songName) - { - try - { - var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); - _audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav"; - Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!); - - _asioSource = new AsioOut(0); - _asioSource.InitRecordAndPlayback(null, 0, 48000); - - _writer = new WaveFileWriter(_audioFilePath, new WaveFormat(44100, 16, 2)); - - _asioSource.AudioAvailable += (s, e) => - { - var buffer = new byte[e.SamplesPerBuffer * 4]; - int bufferIndex = 0; - - for (int i = 0; i < e.SamplesPerBuffer; i++) - { - short sampleL = (short)(e.GetAsInterleavedSamples()[i * 2] * short.MaxValue); - short sampleR = (short)(e.GetAsInterleavedSamples()[i * 2 + 1] * short.MaxValue); - - buffer[bufferIndex++] = (byte)(sampleL & 0xFF); - buffer[bufferIndex++] = (byte)((sampleL >> 8) & 0xFF); - buffer[bufferIndex++] = (byte)(sampleR & 0xFF); - buffer[bufferIndex++] = (byte)((sampleR >> 8) & 0xFF); - } - - _writer.Write(buffer, 0, buffer.Length); - }; - - _asioSource.Play(); - Console.WriteLine("ASIO Audio recording started."); - } - catch (Exception ex) - { - Console.WriteLine($"Error starting ASIO audio recording: {ex.Message}"); - } - } - - - private static void StopRecording(string songName) - { - StopAudioRecording(); - StopFfmpegRecording(); - CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName); - } - - private static void StopAudioRecording() - { - if (_useAsio) - { - StopAsioAudioRecording(); - } - else - { - StopWasapiAudioRecording(); - } - } - - private static void StopWasapiAudioRecording() - { - _waveSource?.StopRecording(); - _waveSource = null; - } - - private static void StopAsioAudioRecording() - { - _asioSource?.Stop(); - _asioSource?.Dispose(); - _asioSource = null; - } - - private static void StartFfmpegRecording(string songName) - { - var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); - _videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv"; - Directory.CreateDirectory(Path.GetDirectoryName(_videoFilePath)!); - var ffmpegArgs = - $"-f dshow -framerate {_framerate} -i video=\"screen-capture-recorder\" -c:v libx264 -preset ultrafast -crf {_crf} -r {_framerate} -s {_resolution} \"{_videoFilePath}\""; - var startInfo = new ProcessStartInfo - { - FileName = "ffmpeg", - Arguments = ffmpegArgs, - UseShellExecute = false, - CreateNoWindow = true - }; - _ffmpegProcess = Process.Start(startInfo); - } - - private static void StopFfmpegRecording() - { - if (_ffmpegProcess != null && !_ffmpegProcess.HasExited) - { - _ffmpegProcess.Kill(); - _ffmpegProcess.Dispose(); - _ffmpegProcess = null; - } - } - private static void CombineAudioAndVideo(string videoFilePath, string audioFilePath, string songName) - { - var combinedOutputFilePath = $"{_ffmpegFolderPath}\\{songName}_combined_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.mp4"; - var ffmpegArgs = - $"-y -i \"{videoFilePath}\" -i \"{audioFilePath}\" -c:v copy -c:a aac -strict experimental -shortest \"{combinedOutputFilePath}\""; - - var processInfo = new ProcessStartInfo - { - FileName = "ffmpeg", - Arguments = ffmpegArgs, - RedirectStandardOutput = false, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = Process.Start(processInfo); - if (process == null) - { - Console.WriteLine("FFmpeg failed to start."); - return; - } - - var error = process.StandardError.ReadToEnd(); - - process.WaitForExit(); - - Console.WriteLine("FFmpeg Error: " + error); - - Console.WriteLine(File.Exists(combinedOutputFilePath) - ? "Audio and video have been successfully combined." - : "Failed to combine audio and video. Check the logs for errors."); - File.Delete(videoFilePath); - File.Delete(audioFilePath); + Console.WriteLine($"Unable to find {_gameProcessName}. Is the game running and TickerHook enabled?"); } } -} + + private static void LoadSettingsFromPropFile() + { + const string filePath = "prop.txt"; + if (File.Exists(filePath)) + { + var fileContent = File.ReadAllText(filePath); + var lines = fileContent.Split('\n'); + foreach (var line in lines) + { + if (line.StartsWith("path:")) + { + var path = line["path:".Length..].Trim(); + if (Directory.Exists(path)) + { + _ffmpegFolderPath = path; // Recording path + + } + else + { + Console.WriteLine($"The path specified in {filePath} does not exist. Using default recording path."); + _ffmpegFolderPath = GetDefaultVideosFolderPath(); + } + } + if (line.StartsWith("resolution:")) + { + _resolution = line["resolution:".Length..].Trim(); + Console.WriteLine($"Custom Resolution: {_resolution}"); + } + if (line.StartsWith("framerate:")) + { + _framerate = int.Parse(line["framerate:".Length..].Trim()); + Console.WriteLine($"Custom framerate: {_framerate}"); + } + if (line.StartsWith("crf:")) + { + _crf = float.Parse(line["crf:".Length..].Trim()); + Console.WriteLine($"custom crf: {_crf}"); + } + if (line.StartsWith("game_process_name:")) + { + _gameProcessName = line["game_process_name:".Length..].Trim(); + Console.WriteLine($"custom process name: {_gameProcessName}"); + } + } + } + else + { + Console.WriteLine($"The file {filePath} does not exist. Using default values."); + _ffmpegFolderPath = GetDefaultVideosFolderPath(); + } + } + + private static string GetDefaultVideosFolderPath() + { + var userName = Environment.UserName; + return Path.Combine($@"C:\Users\{userName}", "Videos"); + } + + private static async Task TryConnectWebSocket() + { + const int maxRetries = 5; + int attempt = 0; + while (attempt < maxRetries) + { + Console.WriteLine($"Attempt {attempt + 1} of {maxRetries} to connect..."); + bool success = await ConnectWebSocket(); + if (success) break; + attempt++; + if (attempt < maxRetries) + { + Console.WriteLine($"Retrying in 10 seconds... {maxRetries - attempt} attempts remaining."); + await Task.Delay(10000); + } + } + if (attempt == maxRetries) + { + Console.WriteLine("Failed to connect after 5 attempts."); + } + } + + private static async Task ConnectWebSocket() + { + var tickerUri = new Uri($"ws://{WebsocketAddress}:{WebsocketPort}"); + var reconnecting = false; + var lastMessage = string.Empty; + var consecutiveMessageCount = 0; + var isRecording = false; + var currentSongName = string.Empty; + var shouldCheckForMusicSelect = false; + + using var clientWebSocket = new ClientWebSocket(); + try + { + await clientWebSocket.ConnectAsync(tickerUri, CancellationToken.None); + Console.WriteLine("Connected to TickerHook WebSocket."); + } + catch (Exception ex) + { + Console.WriteLine($"Error connecting to TickerHook WebSocket: {ex.Message}"); + return false; + } + + var buffer = new byte[1024]; + while (clientWebSocket.State == WebSocketState.Open) + { + WebSocketReceiveResult result; + try + { + result = await clientWebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + } + catch (Exception ex) + { + Console.WriteLine($"Error receiving message: {ex.Message}"); + reconnecting = true; + break; + } + + var message = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim().ToUpper(); + Console.WriteLine($"Received message: {message}"); + + if (message == lastMessage && !message.Contains("SELECT FROM")) + { + consecutiveMessageCount++; + } + else + { + consecutiveMessageCount = 1; + lastMessage = message; + } + + if (consecutiveMessageCount >= 2 && !message.Contains("SELECT FROM") && !isRecording) + { + currentSongName = message; + Console.WriteLine("Starting recording..."); + StartRecording(currentSongName); + isRecording = true; + } + + if (isRecording) + { + if (shouldCheckForMusicSelect && message.Contains("MUSIC SELECT!!")) + { + Console.WriteLine("Stopping recording..."); + StopRecording(currentSongName); + isRecording = false; + shouldCheckForMusicSelect = false; + } + else if (message.EndsWith("CLEAR!") || message.EndsWith("FAILED..")) + { + shouldCheckForMusicSelect = true; + } + } + } + + return !reconnecting; + } + + private static void StartRecording(string songName) + { + Task.Run(() => StartAudioRecording(songName)); + StartFfmpegRecording(songName); + } + + private static void StartAudioRecording(string songName) + { + try + { + var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); + _audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav"; + Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!); + _waveSource = new WasapiLoopbackCapture(); + _writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat); + _waveSource.DataAvailable += (sender, args) => + { + _writer.Write(args.Buffer, 0, args.BytesRecorded); + }; + _waveSource.RecordingStopped += (sender, args) => + { + _writer.Dispose(); + _waveSource.Dispose(); + }; + _waveSource.StartRecording(); + Console.WriteLine("WASAPI Audio recording started."); + } + catch (Exception ex) + { + Console.WriteLine($"Error starting audio recording: {ex.Message}"); + } + } + + private static void StartFfmpegRecording(string songName) + { + var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); + _videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv"; + var ffmpegArguments = $"-framerate {_framerate} " + + $"-filter_complex \"ddagrab=framerate={_framerate},hwdownload,format=bgra\" " + + $"-c:v libx264 -tune zerolatency -crf {_crf} -video_size {_resolution} -movflags +faststart -y \"{_videoFilePath}\""; + _ffmpegProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "ffmpeg", + Arguments = ffmpegArguments, + UseShellExecute = false, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + _ffmpegProcess.ErrorDataReceived += (_, args) => Console.WriteLine(args.Data); + _ffmpegProcess.Start(); + _ffmpegProcess.BeginErrorReadLine(); + + Console.WriteLine("FFmpeg recording started."); + } + + private static void StopRecording(string songName) + { + StopAudioRecording(); + StopFfmpegRecording(); + CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName); + } + + private static void StopAudioRecording() + { + _waveSource?.StopRecording(); + Console.WriteLine("WASAPI Audio recording stopped."); + } + + private static void StopFfmpegRecording() + { + if (_ffmpegProcess != null && !_ffmpegProcess.HasExited) + { + _ffmpegProcess.Kill(); + _ffmpegProcess.WaitForExit(); + _ffmpegProcess = null!; + Console.WriteLine("FFmpeg recording stopped."); + } + } + private static void CombineAudioAndVideo(string videoFilePath, string audioFilePath, string songName) + { + var combinedOutputFilePath = $"{_ffmpegFolderPath}\\{songName}_combined_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.mp4"; + var ffmpegArgs = + $"-y -i \"{videoFilePath}\" -i \"{audioFilePath}\" -c:v copy -c:a aac -strict experimental -shortest \"{combinedOutputFilePath}\""; + + var processInfo = new ProcessStartInfo + { + FileName = "ffmpeg", + Arguments = ffmpegArgs, + RedirectStandardOutput = false, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(processInfo); + if (process == null) + { + Console.WriteLine("FFmpeg failed to start."); + return; + } + + var error = process.StandardError.ReadToEnd(); + + process.WaitForExit(); + + Console.WriteLine("FFmpeg Error: " + error); + + Console.WriteLine(File.Exists(combinedOutputFilePath) + ? "Audio and video have been successfully combined." + : "Failed to combine audio and video. Check the logs for errors."); + File.Delete(videoFilePath); + File.Delete(audioFilePath); + } +} \ No newline at end of file