diff --git a/YoutubeDLSharp.Tests/DownloadTests.cs b/YoutubeDLSharp.Tests/DownloadTests.cs index 3525cce..f8257f0 100644 --- a/YoutubeDLSharp.Tests/DownloadTests.cs +++ b/YoutubeDLSharp.Tests/DownloadTests.cs @@ -18,7 +18,7 @@ public class DownloadTests [ClassInitialize] public static async Task Initialize(TestContext context) { - await PrepTests.DownloadBinaries(); + await Utils.DownloadBinaries(true); ydl = new YoutubeDL(); ydl.OutputFileTemplate = "%(title)s.%(ext)s"; downloadedFiles = new List(); diff --git a/YoutubeDLSharp.Tests/MetadataTests.cs b/YoutubeDLSharp.Tests/MetadataTests.cs index fcd64ce..4b2af86 100644 --- a/YoutubeDLSharp.Tests/MetadataTests.cs +++ b/YoutubeDLSharp.Tests/MetadataTests.cs @@ -14,7 +14,7 @@ public class MetadataTests [ClassInitialize] public static async Task Initialize(TestContext context) { - await PrepTests.DownloadBinaries(); + await Utils.DownloadBinaries(true); ydl = new YoutubeDL(); ydl.OutputFileTemplate = "%(title)s.%(ext)s"; Trace.WriteLine("yt-dlp Version: " + ydl.Version); diff --git a/YoutubeDLSharp.Tests/PrepTests.cs b/YoutubeDLSharp.Tests/PrepTests.cs deleted file mode 100644 index 123b797..0000000 --- a/YoutubeDLSharp.Tests/PrepTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.IO; - -namespace YoutubeDLSharp.Tests -{ - internal static class PrepTests - { - private static bool _didDownloadBinaries = false; - - internal static async Task DownloadBinaries() - { - if (_didDownloadBinaries == false) - { - await Utils.DownloadBinaries(); - _didDownloadBinaries = true; - } - } - } -} \ No newline at end of file diff --git a/YoutubeDLSharp/Utils.cs b/YoutubeDLSharp/Utils.cs index d391ded..319eb0c 100644 --- a/YoutubeDLSharp/Utils.cs +++ b/YoutubeDLSharp/Utils.cs @@ -1,14 +1,15 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Http; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using YoutubeDLSharp.Helpers; namespace YoutubeDLSharp @@ -18,7 +19,6 @@ namespace YoutubeDLSharp /// public static class Utils { - private static readonly HttpClient _client = new HttpClient(); private static readonly Regex rgxTimestamp = new Regex("[0-9]+(?::[0-9]+)+", RegexOptions.Compiled); private static readonly Dictionary accentChars @@ -90,7 +90,9 @@ public static string GetFullPath(string fileName) return null; } -#region Download Helpers + #region Download Helpers + + public static int TimeoutSeconds { get; set; } = 60 * 5; public static string YtDlpBinaryName => GetYtDlpBinaryName(); public static string FfmpegBinaryName => GetFfmpegBinaryName(); @@ -121,62 +123,64 @@ public static async Task DownloadBinaries(bool skipExisting = true, string direc } } - private static string GetYtDlpDownloadUrl() + private static string GetYtDlpBinaryName() { - const string BASE_GITHUB_URL = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp"; + string ytdlpDownloadPath = GetYtDlpDownloadUrl(); + return Path.GetFileName(ytdlpDownloadPath); + } - string downloadUrl; + private static string GetFfmpegBinaryName() + { switch (OSHelper.GetOSVersion()) { case OSVersion.Windows: - downloadUrl = $"{BASE_GITHUB_URL}.exe"; - break; + return "ffmpeg.exe"; case OSVersion.OSX: - downloadUrl = $"{BASE_GITHUB_URL}_macos"; - break; case OSVersion.Linux: - downloadUrl = BASE_GITHUB_URL; - break; + return "ffmpeg"; default: throw new Exception("Your OS isn't supported"); } - return downloadUrl; } - private static string GetYtDlpBinaryName() - { - string ytdlpDownloadPath = GetYtDlpDownloadUrl(); - return Path.GetFileName(ytdlpDownloadPath); - } - - private static string GetFfmpegBinaryName() + private static string GetFfprobeBinaryName() { switch (OSHelper.GetOSVersion()) { case OSVersion.Windows: - return "ffmpeg.exe"; + return "ffprobe.exe"; case OSVersion.OSX: case OSVersion.Linux: - return "ffmpeg"; + return "ffprobe"; default: throw new Exception("Your OS isn't supported"); - } + } } - private static string GetFfprobeBinaryName() + private static string GetYtDlpDownloadUrl() { + const string BASE_GITHUB_URL = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp"; + + string downloadUrl; switch (OSHelper.GetOSVersion()) { case OSVersion.Windows: - return "ffprobe.exe"; + downloadUrl = $"{BASE_GITHUB_URL}.exe"; + break; case OSVersion.OSX: + downloadUrl = $"{BASE_GITHUB_URL}_macos"; + break; case OSVersion.Linux: - return "ffprobe"; + downloadUrl = BASE_GITHUB_URL; + break; default: throw new Exception("Your OS isn't supported"); } + return downloadUrl; } + + public static async Task DownloadYtDlp(string directoryPath = "") { string downloadUrl = GetYtDlpDownloadUrl(); @@ -186,26 +190,48 @@ public static async Task DownloadYtDlp(string directoryPath = "") var downloadLocation = Path.Combine(directoryPath, Path.GetFileName(downloadUrl)); var data = await DownloadFileBytesAsync(downloadUrl); File.WriteAllBytes(downloadLocation, data); + SetUnixExecPerms(downloadLocation); } public static async Task DownloadFFmpeg(string directoryPath = "") { - await FFDownloader(directoryPath, FFmpegApi.BinaryType.FFmpeg); + await FFDownloader(directoryPath, binary: FFmpegApi.BinaryType.FFmpeg); } public static async Task DownloadFFprobe(string directoryPath = "") { - await FFDownloader(directoryPath, FFmpegApi.BinaryType.FFprobe); + await FFDownloader(directoryPath, binary:FFmpegApi.BinaryType.FFprobe); } - + private static void SetUnixExecPerms(string filename) + { + if (OSHelper.IsWindows) return; +#if NET7_0_OR_GREATER + if (!OSHelper.IsWindows) + File.SetUnixFileMode(filename, UnixFileMode.UserExecute | UnixFileMode.UserRead | UnixFileMode.UserWrite); +#else + throw new PlatformNotSupportedException("Setting Unix permissions is not supported on this platform."); +#endif + } + + private static HttpClient GetHttpClient() + { + HttpClient _client = null; + if (_client == null) + { + _client = new HttpClient(); + _client.Timeout = TimeSpan.FromSeconds(TimeoutSeconds); + } + return _client; + } private static async Task FFDownloader(string directoryPath = "", FFmpegApi.BinaryType binary = FFmpegApi.BinaryType.FFmpeg) { + var client = GetHttpClient(); if (string.IsNullOrEmpty(directoryPath)) { directoryPath = Directory.GetCurrentDirectory(); } const string FFMPEG_API_URL = "https://ffbinaries.com/api/v1/version/latest"; - var ffmpegVersion = JsonConvert.DeserializeObject(await (await _client.GetAsync(FFMPEG_API_URL)).Content.ReadAsStringAsync()); + var ffmpegVersion = JsonConvert.DeserializeObject(await (await client.GetAsync(FFMPEG_API_URL)).Content.ReadAsStringAsync()); FFmpegApi.OsBinVersion ffContent; switch (OSHelper.GetOSVersion()) @@ -225,24 +251,32 @@ private static async Task FFDownloader(string directoryPath = "", FFmpegApi.Bina string downloadUrl = binary == FFmpegApi.BinaryType.FFmpeg ? ffContent.Ffmpeg : ffContent.Ffprobe; var dataBytes = await DownloadFileBytesAsync(downloadUrl); + string fileName = string.Empty; using (var stream = new MemoryStream(dataBytes)) { using (var archive = new ZipArchive(stream, ZipArchiveMode.Read)) { if (archive.Entries.Count > 0) { + fileName = Path.Combine(directoryPath, archive.Entries[0].FullName); archive.Entries[0].ExtractToFile(Path.Combine(directoryPath, archive.Entries[0].FullName), true); } } - }; + }; + SetUnixExecPerms(fileName); + client?.Dispose(); } + + private static async Task DownloadFileBytesAsync(string uri) { if (!Uri.TryCreate(uri, UriKind.Absolute, out Uri _)) throw new InvalidOperationException("URI is invalid."); + var client = GetHttpClient();// Set a reasonable timeout for downloads - byte[] fileBytes = await _client.GetByteArrayAsync(uri); + byte[] fileBytes = await client.GetByteArrayAsync(uri); + client?.Dispose(); return fileBytes; }