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."); } } }