using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace axXez.TS3.Audio
{
public static class YtDlp
{
/// Returns true if yt-dlp is installed and accessible on PATH.
public static bool IsAvailable()
{
try
{
using var p = Start("--version");
p.WaitForExit();
return p.ExitCode == 0;
}
catch { return false; }
}
/// Resolves a yt-dlp-supported URL to a direct audio stream URL.
public static async Task ResolveAsync(string url, IReadOnlyList extraArgs = null, CancellationToken ct = default)
{
using var p = Start([.. (extraArgs ?? []), "-f", "bestaudio", "--no-playlist", "-g", url]);
// Read concurrently — sequential reads risk deadlock if either buffer fills up
var stdoutTask = p.StandardOutput.ReadToEndAsync(ct);
var stderrTask = p.StandardError.ReadToEndAsync(ct);
await p.WaitForExitAsync(ct);
string stdout = await stdoutTask;
string stderr = await stderrTask;
if (p.ExitCode != 0)
{
string err = stderr.Trim();
throw new Exception($"yt-dlp failed: {err[..Math.Min(200, err.Length)]}");
}
string resolved = stdout.Trim().Split('\n')[0];
if (string.IsNullOrEmpty(resolved)) throw new Exception("yt-dlp returned no URL.");
return resolved;
}
/// Resolves all entries of a playlist URL to individual watch URLs without downloading audio.
public static async Task GetPlaylistUrlsAsync(string url, IReadOnlyList extraArgs = null, CancellationToken ct = default)
{
using var p = Start([.. (extraArgs ?? []), "--flat-playlist", "-g", url]);
var stdoutTask = p.StandardOutput.ReadToEndAsync(ct);
var stderrTask = p.StandardError.ReadToEndAsync(ct);
await p.WaitForExitAsync(ct);
string stdout = await stdoutTask;
string stderr = await stderrTask;
if (p.ExitCode != 0)
{
string err = stderr.Trim();
throw new Exception($"yt-dlp failed: {err[..Math.Min(200, err.Length)]}");
}
string[] urls = stdout.Trim().Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (urls.Length == 0) throw new Exception("Playlist is empty or could not be resolved.");
return urls;
}
/// Runs yt-dlp --update to self-update the binary in place.
public static async Task UpdateAsync(CancellationToken ct = default)
{
using var p = Start("--update");
await p.WaitForExitAsync(ct);
}
private static Process Start(params string[] args)
{
var psi = new ProcessStartInfo("yt-dlp")
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
foreach (var a in args) psi.ArgumentList.Add(a);
return Process.Start(psi) ?? throw new InvalidOperationException("yt-dlp not found.");
}
}
}