forked from Mercury/2dxAutoClip
0.0.4 prep
- added custom settings loading from prop.txt - added `-tune zerolatency` to FFMPEG capture - reduced latency for WASAPI audio capture - cleared up a bit of the ffmpeg spawning/killing logic
This commit is contained in:
parent
c67c1ac504
commit
8f96bb6029
|
@ -11,29 +11,34 @@ class Program
|
||||||
private static readonly string WebsocketAddress = "localhost";
|
private static readonly string WebsocketAddress = "localhost";
|
||||||
private static readonly int WebsocketPort = 10573;
|
private static readonly int WebsocketPort = 10573;
|
||||||
private static Process? _ffmpegProcess;
|
private static Process? _ffmpegProcess;
|
||||||
private static string _ffmpegFolderPath = GetFolderPath();
|
private static string _ffmpegFolderPath = GetDefaultVideosFolderPath();
|
||||||
private static WasapiCapture? _waveSource;
|
private static WasapiCapture? _waveSource;
|
||||||
private static WaveFileWriter _writer = null!;
|
private static WaveFileWriter _writer = null!;
|
||||||
private static string _audioFilePath = null!;
|
private static string _audioFilePath = null!;
|
||||||
private static string _videoFilePath = 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 async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
var spiceProcesses = Process.GetProcessesByName("spice64");
|
// Load settings from prop.txt
|
||||||
|
LoadSettingsFromPropFile();
|
||||||
|
|
||||||
if (spiceProcesses.Length > 0)
|
var gameProcesses = Process.GetProcessesByName(_gameProcessName);
|
||||||
|
if (gameProcesses.Length > 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Found spice64, Attempting connection to TickerHookWS...");
|
Console.WriteLine($"Found {_gameProcessName}, Attempting connection to TickerHookWS...");
|
||||||
await TryConnectWebSocket();
|
await TryConnectWebSocket();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine("Unable to find Spice64. Are you sure Beatmania IIDX is running and TickerHook is enabled?");
|
Console.WriteLine($"Unable to find {_gameProcessName}. Is the game running and TickerHook enabled?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void LoadSettingsFromPropFile()
|
||||||
private static string GetFolderPath()
|
|
||||||
{
|
{
|
||||||
const string filePath = "prop.txt";
|
const string filePath = "prop.txt";
|
||||||
if (File.Exists(filePath))
|
if (File.Exists(filePath))
|
||||||
|
@ -47,68 +52,80 @@ class Program
|
||||||
var path = line["path:".Length..].Trim();
|
var path = line["path:".Length..].Trim();
|
||||||
if (Directory.Exists(path))
|
if (Directory.Exists(path))
|
||||||
{
|
{
|
||||||
return path;
|
_ffmpegFolderPath = path; // Recording path
|
||||||
|
|
||||||
}
|
}
|
||||||
Console.WriteLine($"The path specified in {filePath} does not exist.");
|
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
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine($"The file {filePath} does not exist.");
|
Console.WriteLine($"The file {filePath} does not exist. Using default values.");
|
||||||
|
_ffmpegFolderPath = GetDefaultVideosFolderPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetDefaultVideosFolderPath();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetDefaultVideosFolderPath()
|
private static string GetDefaultVideosFolderPath()
|
||||||
{
|
{
|
||||||
var userName = Environment.UserName;
|
var userName = Environment.UserName;
|
||||||
var defaultPath = Path.Combine($@"C:\Users\{userName}", "Videos");
|
return Path.Combine($@"C:\Users\{userName}", "Videos");
|
||||||
return defaultPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task TryConnectWebSocket()
|
private static async Task TryConnectWebSocket()
|
||||||
{
|
{
|
||||||
const int maxRetries = 5;
|
const int maxRetries = 5;
|
||||||
int attempt = 0;
|
int attempt = 0;
|
||||||
|
|
||||||
while (attempt < maxRetries)
|
while (attempt < maxRetries)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Attempt {attempt + 1} of {maxRetries} to connect...");
|
Console.WriteLine($"Attempt {attempt + 1} of {maxRetries} to connect...");
|
||||||
|
|
||||||
bool success = await ConnectWebSocket();
|
bool success = await ConnectWebSocket();
|
||||||
|
if (success) break;
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
break; // Exit the loop if connection was successful
|
|
||||||
}
|
|
||||||
|
|
||||||
attempt++;
|
attempt++;
|
||||||
|
|
||||||
if (attempt < maxRetries)
|
if (attempt < maxRetries)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Retrying in 10 seconds... {maxRetries - attempt} attempts remaining.");
|
Console.WriteLine($"Retrying in 10 seconds... {maxRetries - attempt} attempts remaining.");
|
||||||
await Task.Delay(10000); // Wait for 10 seconds before retrying
|
await Task.Delay(10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attempt == maxRetries)
|
if (attempt == maxRetries)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Failed to connect after 5 attempts.");
|
Console.WriteLine("Failed to connect after 5 attempts.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<bool> ConnectWebSocket()
|
private static async Task<bool> ConnectWebSocket()
|
||||||
{
|
{
|
||||||
var tickerUri = new Uri($"ws://{WebsocketAddress}:{WebsocketPort}");
|
var tickerUri = new Uri($"ws://{WebsocketAddress}:{WebsocketPort}");
|
||||||
var reconnecting = false;
|
var reconnecting = false;
|
||||||
|
|
||||||
var lastMessage = string.Empty;
|
var lastMessage = string.Empty;
|
||||||
var consecutiveMessageCount = 0;
|
var consecutiveMessageCount = 0;
|
||||||
var isRecording = false;
|
var isRecording = false;
|
||||||
var currentSongName = string.Empty;
|
var currentSongName = string.Empty;
|
||||||
|
|
||||||
var shouldCheckForMusicSelect = false;
|
var shouldCheckForMusicSelect = false;
|
||||||
|
|
||||||
using var clientWebSocket = new ClientWebSocket();
|
using var clientWebSocket = new ClientWebSocket();
|
||||||
|
@ -120,11 +137,10 @@ private static async Task<bool> ConnectWebSocket()
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error connecting to TickerHook WebSocket: {ex.Message}");
|
Console.WriteLine($"Error connecting to TickerHook WebSocket: {ex.Message}");
|
||||||
return false; // Indicate connection failure
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = new byte[1024];
|
var buffer = new byte[1024];
|
||||||
|
|
||||||
while (clientWebSocket.State == WebSocketState.Open)
|
while (clientWebSocket.State == WebSocketState.Open)
|
||||||
{
|
{
|
||||||
WebSocketReceiveResult result;
|
WebSocketReceiveResult result;
|
||||||
|
@ -177,7 +193,7 @@ private static async Task<bool> ConnectWebSocket()
|
||||||
}
|
}
|
||||||
|
|
||||||
return !reconnecting;
|
return !reconnecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StartRecording(string songName)
|
private static void StartRecording(string songName)
|
||||||
{
|
{
|
||||||
|
@ -191,8 +207,7 @@ private static async Task<bool> ConnectWebSocket()
|
||||||
{
|
{
|
||||||
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
_audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
|
_audioFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.wav";
|
||||||
var outputFolder = Path.GetDirectoryName(_audioFilePath)!;
|
Directory.CreateDirectory(Path.GetDirectoryName(_audioFilePath)!);
|
||||||
Directory.CreateDirectory(outputFolder);
|
|
||||||
_waveSource = new WasapiLoopbackCapture();
|
_waveSource = new WasapiLoopbackCapture();
|
||||||
_writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat);
|
_writer = new WaveFileWriter(_audioFilePath, _waveSource.WaveFormat);
|
||||||
_waveSource.DataAvailable += (sender, args) =>
|
_waveSource.DataAvailable += (sender, args) =>
|
||||||
|
@ -202,11 +217,8 @@ private static async Task<bool> ConnectWebSocket()
|
||||||
_waveSource.RecordingStopped += (sender, args) =>
|
_waveSource.RecordingStopped += (sender, args) =>
|
||||||
{
|
{
|
||||||
_writer.Dispose();
|
_writer.Dispose();
|
||||||
_writer = null!;
|
|
||||||
_waveSource.Dispose();
|
_waveSource.Dispose();
|
||||||
_waveSource = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_waveSource.StartRecording();
|
_waveSource.StartRecording();
|
||||||
Console.WriteLine("WASAPI Audio recording started.");
|
Console.WriteLine("WASAPI Audio recording started.");
|
||||||
}
|
}
|
||||||
|
@ -216,65 +228,54 @@ private static async Task<bool> ConnectWebSocket()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static void StartFfmpegRecording(string songName)
|
private static void StartFfmpegRecording(string songName)
|
||||||
{
|
{
|
||||||
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
var date = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
_videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv";
|
_videoFilePath = $"{_ffmpegFolderPath}\\{songName}_{date}.mkv";
|
||||||
|
var ffmpegArguments = $"-framerate {_framerate} " +
|
||||||
var ffmpegArguments = $"-framerate 60 " +
|
|
||||||
$"-filter_complex \"ddagrab=0,hwdownload,format=bgra\" " +
|
$"-filter_complex \"ddagrab=0,hwdownload,format=bgra\" " +
|
||||||
$"-c:v libx264 -movflags +faststart -crf 20 -y \"{_videoFilePath}\"";
|
$"-c:v libx264 -tune zerolatency -crf {_crf} -video_size {_resolution} -movflags +faststart -y \"{_videoFilePath}\"";
|
||||||
|
_ffmpegProcess = new Process
|
||||||
_ffmpegProcess = new Process();
|
{
|
||||||
_ffmpegProcess.StartInfo.FileName = "ffmpeg";
|
StartInfo = new ProcessStartInfo
|
||||||
_ffmpegProcess.StartInfo.Arguments = ffmpegArguments;
|
{
|
||||||
_ffmpegProcess.StartInfo.UseShellExecute = false;
|
FileName = "ffmpeg",
|
||||||
_ffmpegProcess.StartInfo.RedirectStandardOutput = false;
|
Arguments = ffmpegArguments,
|
||||||
_ffmpegProcess.StartInfo.RedirectStandardError = true;
|
UseShellExecute = false,
|
||||||
_ffmpegProcess.StartInfo.CreateNoWindow = true;
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
}
|
||||||
|
};
|
||||||
_ffmpegProcess.ErrorDataReceived += (_, args) => Console.WriteLine(args.Data);
|
_ffmpegProcess.ErrorDataReceived += (_, args) => Console.WriteLine(args.Data);
|
||||||
|
|
||||||
_ffmpegProcess.Start();
|
_ffmpegProcess.Start();
|
||||||
_ffmpegProcess.BeginErrorReadLine();
|
_ffmpegProcess.BeginErrorReadLine();
|
||||||
|
|
||||||
|
Console.WriteLine("FFmpeg recording started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StopRecording(string songName)
|
private static void StopRecording(string songName)
|
||||||
{
|
{
|
||||||
StopFfmpegRecording();
|
|
||||||
StopAudioRecording();
|
StopAudioRecording();
|
||||||
|
StopFfmpegRecording();
|
||||||
CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName);
|
CombineAudioAndVideo(_videoFilePath, _audioFilePath, songName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StopAudioRecording()
|
private static void StopAudioRecording()
|
||||||
{
|
{
|
||||||
if (_waveSource == null) return;
|
_waveSource?.StopRecording();
|
||||||
|
Console.WriteLine("WASAPI Audio recording stopped.");
|
||||||
_waveSource.StopRecording();
|
|
||||||
_waveSource.Dispose();
|
|
||||||
_waveSource = null;
|
|
||||||
if (_writer != null)
|
|
||||||
{
|
|
||||||
_writer.Dispose();
|
|
||||||
_writer = null!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine("Audio recording stopped.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void StopFfmpegRecording()
|
private static void StopFfmpegRecording()
|
||||||
{
|
{
|
||||||
if (_ffmpegProcess != null && _ffmpegProcess.HasExited) return;
|
if (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
|
||||||
_ffmpegProcess?.Kill();
|
{
|
||||||
_ffmpegProcess?.WaitForExit();
|
_ffmpegProcess.Kill();
|
||||||
_ffmpegProcess?.Dispose();
|
_ffmpegProcess.WaitForExit();
|
||||||
_ffmpegProcess = null;
|
_ffmpegProcess = null!;
|
||||||
Console.WriteLine("FFMPEG process stopped.");
|
Console.WriteLine("FFmpeg recording stopped.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CombineAudioAndVideo(string videoFilePath, string audioFilePath, string songName)
|
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 combinedOutputFilePath = $"{_ffmpegFolderPath}\\{songName}_combined_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.mp4";
|
||||||
|
|
Loading…
Reference in a new issue