Update 2dxAutoClip/iidxAutoClip.cs

Disable asio recording.
This commit is contained in:
Mercury. 2024-09-09 21:27:06 +02:00
parent 82fb949125
commit cc70624b54

View file

@ -3,394 +3,312 @@ using System.Net.WebSockets;
using System.Text; using System.Text;
using NAudio.CoreAudioApi; using NAudio.CoreAudioApi;
using NAudio.Wave; 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"; // Load settings from prop.txt
private static readonly int WebsocketPort = 10573; LoadSettingsFromPropFile();
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
private static async Task Main(string[] args) var gameProcesses = Process.GetProcessesByName(_gameProcessName);
if (gameProcesses.Length > 0)
{ {
// Load settings from prop.txt Console.WriteLine($"Found {_gameProcessName}, Attempting connection to TickerHookWS...");
LoadSettingsFromPropFile(); await TryConnectWebSocket();
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?");
}
} }
else
private static void LoadSettingsFromPropFile()
{ {
const string filePath = "prop.txt"; Console.WriteLine($"Unable to find {_gameProcessName}. Is the game running and TickerHook enabled?");
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<bool> 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<byte>(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);
} }
} }
}
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<bool> 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<byte>(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);
}
}