Files
ytdlp-gui/YTdlp.cs
2025-05-23 01:34:48 +02:00

334 lines
11 KiB
C#

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<string>();
}
public async Task<string[]> 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<string> files;
public event EventHandler<YTdlpStartEventArgs> Start;
public event EventHandler<YTdlpProgressEventArgs> Progress;
public event EventHandler<YTdlpErrorEventArgs> Error;
public event EventHandler<YTdlpFinishedEventArgs> 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);
}
}
}