using System; using System.Collections.Generic; using System.ComponentModel; using System.Deployment.Application; using System.Diagnostics; using System.Drawing.Printing; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace ytdlp_gui { public class YTdlpStartEventArgs : EventArgs { public YTdlpStartEventArgs(string provider) { this.provider = provider; } public readonly string provider; } public class YTdlpProgressEventArgs : EventArgs { public YTdlpProgressEventArgs( string provider, string action, string file, float progress, string playlist, int playlist_current, int playlist_total ){ this.provider = provider; this.action = action; this.file = file; this.progress = progress; this.playlist = playlist; this.playlist_current = playlist_current; this.playlist_total = playlist_total; } public readonly string provider; public readonly string action; public readonly string file; public readonly float progress; public readonly string playlist; public readonly int playlist_current; public readonly int playlist_total; } public class YTdlpErrorEventArgs : EventArgs { public YTdlpErrorEventArgs(string provider, string error) { this.provider = provider; this.error = error; } public readonly string provider; public readonly string error; } public class YTdlpFinishedEventArgs : EventArgs { public YTdlpFinishedEventArgs(string provider, string file, string[] files) { this.provider = provider; this.file = file; this.files = files; } public readonly string provider; public readonly string file; public readonly string[] files; } public class YTdlpProcess { public YTdlpProcess(Process process) { this.process = process; ci = (CultureInfo)CultureInfo.CurrentCulture.Clone(); ci.NumberFormat.CurrencyDecimalSeparator = "."; files = new List(); } public async Task Run() { process.Start(); string stdout = ""; int playlist_last = playlist_current; string line; bool first = true; while ((line = await process.StandardOutput.ReadLineAsync()) != null) { Console.WriteLine(line); stdout += line; if (first) { provider = (new Regex(@"\w+")).Match(line).Value; first = false; } line = line.Trim(); /* Match match = this.already_downloaded_regex.Match(line); if (match.Success) { file = match.Groups[1].Value; break; } */ if (line.StartsWith("[info] ")) ProgInfo(line.Substring("[info] ".Length).TrimStart()); if (line.StartsWith("[download] ")) ProgDownload(line.Substring("[download] ".Length).TrimStart()); if (line.StartsWith("[Merger] ")) ProgMerger(line.Substring("[Merger] ".Length).TrimStart()); if (line.StartsWith("[youtube:tab] ")) ProgYoutube(line.Substring("[youtube:tab] ".Length).TrimStart()); if (playlist_last != playlist_current) { playlist_last = playlist_current; if (File != null) Finished?.Invoke(this, new YTdlpFinishedEventArgs(provider, File, files.ToArray())); } } if (stop) return new string[0]; process.WaitForExit(); if (process.ExitCode == 0) { if (File != null) Finished?.Invoke(this, new YTdlpFinishedEventArgs(provider, File, files.ToArray())); } else Error?.Invoke(this, new YTdlpErrorEventArgs(provider, stdout)); return files.ToArray(); } public void Cancel() { stop = true; if (!process.HasExited) try { ProcessUtil.KillProcessAndChildren(process.Id); } catch (Exception) { /* We tried ¯\_(ツ)_/¯ */ } process.WaitForExit(); } [DllImport("kernel32.dll")] private static extern int GetParentProcessId(int processId); protected void ProgInfo(string line) { } protected void ProgDownload(string line) { const string dest_str = "Destination: "; if (line.StartsWith(dest_str)) File = line.Substring(dest_str.Length); Match match; match = download_regex.Match(line); if (match.Success) { GroupCollection groups = match.Groups; float progress = ParseFloat( groups[1].Value ) / 100 ; float total = ParseFloat( groups[2].Value ) ; string total_unit = groups[3].Value ; float speed = ParseFloat( groups[4].Value ) ; string speed_unit = groups[5].Value ; string estamated_time = groups[6].Value ; YTdlpProgressEventArgs args = new YTdlpProgressEventArgs( provider, "download", File, progress, playlist, playlist_current, playlist_total ); Progress?.Invoke(this, args); } match = download_finish_regex.Match(line); if (match.Success) { GroupCollection groups = match.Groups; float progress = ParseFloat(groups[1].Value) / 100; float total = ParseFloat(groups[2].Value); string total_unit = groups[3].Value; string total_time = groups[4].Value; float speed = ParseFloat(groups[5].Value); string speed_unit = groups[6].Value; YTdlpProgressEventArgs args = new YTdlpProgressEventArgs( provider, "download", File, progress, playlist, playlist_current, playlist_total ); Progress?.Invoke(this, args); } match = download_playlist_item_regex.Match(line); if (match.Success) { GroupCollection groups = match.Groups; playlist_current = int.Parse(groups[1].Value); playlist_total = int.Parse(groups[2].Value); } } protected void ProgMerger(string line) { Match match; match = merge_regex.Match(line); if (match.Success) { File = match.Groups[1].Value; YTdlpProgressEventArgs args = new YTdlpProgressEventArgs( provider, "merge", file, 1, playlist, playlist_current, playlist_total ); Progress?.Invoke(this, args); } } protected void ProgYoutube(string line) { Match match; match = download_playlist_regex.Match(line); if (match.Success) { playlist = match.Groups[1].Value; // How much we downloading playlist_total = int.Parse(match.Groups[2].Value); // How much there is // playlist_total = int.Parse(match.Groups[3].Value); } } private float ParseFloat(string str) { return float.Parse(str, NumberStyles.Any, ci); } private readonly Process process; private readonly CultureInfo ci; private readonly Regex download_playlist_regex = new Regex(@"Playlist (.+):\s+Downloading\s+(\d+)\s+items\s+of\s+(\d+)"); private readonly Regex download_playlist_item_regex = new Regex(@"Downloading\s+item\s(\d+)\s+of\s+(\d+)"); private readonly Regex already_downloaded_regex = new Regex(@"\[download\]\s+(.+) has already been downloaded"); private readonly Regex download_regex = new Regex(@"([0-9.]+)%\s+of\s*~?\s+([0-9.]+)(\w+)\s+at\s+([0-9.]+)(\w+\/\w+)\sETA\s(\d+:\d+)"); private readonly Regex download_finish_regex = new Regex(@"([0-9.]+)%\s+of\s+([0-9.]+)(\w+)\s+in\s+(\d+:\d+:\d+)\s+at\s+([0-9.]+)(\w+\/\w+)"); private readonly Regex merge_regex = new Regex(@"Merging formats into ""(.+)"""); private string provider; private string playlist; private int playlist_current = 1; private int playlist_total = 1; private bool stop = false; private string File { get { return file; } set { file = value; if (!files.Contains(value)) files.Add(value); } } private string file; private List files; public event EventHandler Start; public event EventHandler Progress; public event EventHandler Error; public event EventHandler Finished; } public class YTdlp : Program { public YTdlp() : base("yt-dlp") { } public YTdlpProcess Download(string url, bool audio_only, bool download_playlist, string workdir) { Process process = new Process(); process.StartInfo.FileName = program; process.StartInfo.Arguments = String.Format( "{0} {1} \"{2}\"", audio_only ? "-f ba" : "", download_playlist ? "--yes-playlist" : "--no-playlist", url.Replace("\"", "\"\"\"") ); process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; // process.StartInfo.RedirectStandardError = true; process.StartInfo.WorkingDirectory = workdir; return new YTdlpProcess(process); } } }