334 lines
11 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|