diff --git a/Changelog.md b/Changelog.md index 2cc9b6cb..6187fd6a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,10 @@ https://github.com/Etherna/YoutubeExplode/releases # Changelog +## v6.2.13 (27-Apr-2023) + +- Improved support for older target frameworks via polyfills. + ## v6.2.12 (31-Mar-2023) - Fixed an issue where calling `VideoClient.GetAsync(...)` failed with `Could not extract video upload date` on certain videos. diff --git a/Readme.md b/Readme.md index 886099fd..75f29c61 100644 --- a/Readme.md +++ b/Readme.md @@ -4,7 +4,7 @@ Behind a layer of abstraction, this library works by scraping raw page data and exploiting reverse-engineered internal endpoints. > 📝 Want to learn more about how YouTube works under the hood? -> See [Reverse-Engineering YouTube: Revisited](https://tyrrrz.me/blog/reverse-engineering-youtube-revisited). +> [Read this article](https://tyrrrz.me/blog/reverse-engineering-youtube-revisited). ## Install diff --git a/YoutubeDownloader.Converter.Tests/YoutubeDownloader.Converter.Tests.csproj b/YoutubeDownloader.Converter.Tests/YoutubeDownloader.Converter.Tests.csproj index da21375a..cfd8c832 100644 --- a/YoutubeDownloader.Converter.Tests/YoutubeDownloader.Converter.Tests.csproj +++ b/YoutubeDownloader.Converter.Tests/YoutubeDownloader.Converter.Tests.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/YoutubeDownloader.Converter/Readme.md b/YoutubeDownloader.Converter/Readme.md index fe650b5b..33ac3a91 100644 --- a/YoutubeDownloader.Converter/Readme.md +++ b/YoutubeDownloader.Converter/Readme.md @@ -10,18 +10,18 @@ This package relies on [FFmpeg](https://ffmpeg.org) under the hood. - 📦 [NuGet](https://nuget.org/packages/YoutubeExplode.Converter): `dotnet add package YoutubeExplode.Converter` +> **Warning**: +> This package requires the [FFmpeg CLI](https://ffmpeg.org) to work, which can be downloaded [here](https://ffbinaries.com/downloads). +> Ensure that it's located in your application's probe directory or on the system's `PATH`, or provide a custom location yourself using one of the available method overloads. + ## Usage **YoutubeExplode.Converter** exposes its functionality by enhancing **YoutubeExplode**'s clients with additional extension methods. To use them, simply add the corresponding namespace and follow the examples below. -> **Warning**: -> This package requires the [FFmpeg](https://ffmpeg.org) executable to work, which can be downloaded [here](https://ffbinaries.com/downloads). -> Ensure that it's located in your application's probe directory or on the system's `PATH`, or provide a custom location directly using various overloads. - ### Downloading videos -You can download a video directly through one of the extension methods provided on `VideoClient`. +You can download a video directly to a file through one of the extension methods provided on `VideoClient`. For example, to download a video in the specified format using the highest quality streams, simply call `DownloadAsync(...)` with the video ID and the destination path: ```csharp @@ -40,9 +40,8 @@ Under the hood, this resolves the video's media streams, downloads the best cand > If the specified output format is a known audio-only container (e.g. `mp3` or `ogg`) then only the audio stream is downloaded. > **Warning**: -> Stream muxing is a resource-intensive process. -> You can improve the execution speed by making sure that both the input streams and the output file use the same format, which eliminates the need for transcoding. -> Currently, YouTube provides adaptive streams only in `mp4` and `webm` containers, with the highest quality video streams (e.g. 4K) only available in `webm`. +> Stream muxing is a resource-intensive process, especially when transcoding is involved. +> To avoid transcoding, consider specifying either `mp4` or `webm` for the output format, as these are the containers that YouTube uses for most of its streams. ### Customizing the conversion process @@ -77,11 +76,23 @@ var youtube = new YoutubeClient(); var videoUrl = "https://youtube.com/watch?v=u_yIGGhubZs"; var streamManifest = await youtube.Videos.Streams.GetManifestAsync(videoUrl); -// Select streams (1080p60 / highest bitrate audio) -var audioStreamInfo = streamManifest.GetAudioStreams().GetWithHighestBitrate(); -var videoStreamInfo = streamManifest.GetVideoStreams().First(s => s.VideoQuality.Label == "1080p60"); -var streamInfos = new IStreamInfo[] { audioStreamInfo, videoStreamInfo }; +// Select best audio stream (highest bitrate) +var audioStreamInfo = streamManifest + .GetAudioStreams() + .Where(s => s.Container == Container.Mp4) + .GetWithHighestBitrate(); -// Download and process them into one file +// Select best video stream (1080p60 in this example) +var videoStreamInfo = streamManifest + .GetVideoStreams() + .Where(s => s.Container == Container.Mp4) + .First(s => s.VideoQuality.Label == "1080p60"); + +// Download and mux streams into a single file +var streamInfos = new IStreamInfo[] { audioStreamInfo, videoStreamInfo }; await youtube.Videos.DownloadAsync(streamInfos, new ConversionRequestBuilder("video.mp4").Build()); -``` \ No newline at end of file +``` + +> **Warning**: +> Stream muxing is a resource-intensive process, especially when transcoding is involved. +> To avoid transcoding, consider prioritizing streams that are already encoded in the desired format (e.g. `mp4` or `webm`). \ No newline at end of file diff --git a/YoutubeDownloader.Converter/Utils/Polyfills.Collections.cs b/YoutubeDownloader.Converter/Utils/Polyfills.Collections.cs deleted file mode 100644 index 3b770c5b..00000000 --- a/YoutubeDownloader.Converter/Utils/Polyfills.Collections.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ReSharper disable CheckNamespace - -#if NETSTANDARD2_0 || NETSTANDARD2_1 || NET461 -using System.Collections.Generic; -using System.Linq; - -internal static class CollectionPolyfills -{ - public static IEnumerable<(TFirst left, TSecond right)> Zip( - this IEnumerable first, - IEnumerable second) => - first.Zip(second, (x, y) => (x, y)); -} -#endif \ No newline at end of file diff --git a/YoutubeDownloader.Converter/Utils/Polyfills.Regex.cs b/YoutubeDownloader.Converter/Utils/Polyfills.Regex.cs deleted file mode 100644 index 73199223..00000000 --- a/YoutubeDownloader.Converter/Utils/Polyfills.Regex.cs +++ /dev/null @@ -1,12 +0,0 @@ -// ReSharper disable CheckNamespace - -#if NETSTANDARD2_0 || NET461 -using System.Linq; -using System.Text.RegularExpressions; - -internal static class RegexPolyfills -{ - public static Match[] ToArray(this MatchCollection matches) => - matches.Cast().ToArray(); -} -#endif \ No newline at end of file diff --git a/YoutubeDownloader.Converter/YoutubeDownloader.Converter.csproj b/YoutubeDownloader.Converter/YoutubeDownloader.Converter.csproj index 91ba2b40..e9cf1ac4 100644 --- a/YoutubeDownloader.Converter/YoutubeDownloader.Converter.csproj +++ b/YoutubeDownloader.Converter/YoutubeDownloader.Converter.csproj @@ -1,7 +1,7 @@  - net5.0;netstandard2.1;netstandard2.0;net461;netcoreapp3.1 + netstandard2.0;netstandard2.1;netcoreapp3.1;net461;net5.0 true Etherna.$(AssemblyName) @@ -14,10 +14,6 @@ https://github.com/Etherna/youtube-downloader - - - - @@ -26,7 +22,7 @@ - + diff --git a/YoutubeDownloader.DemoWpf/YoutubeDownloader.DemoWpf.csproj b/YoutubeDownloader.DemoWpf/YoutubeDownloader.DemoWpf.csproj index 0285e474..400e0851 100644 --- a/YoutubeDownloader.DemoWpf/YoutubeDownloader.DemoWpf.csproj +++ b/YoutubeDownloader.DemoWpf/YoutubeDownloader.DemoWpf.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/YoutubeDownloader.Tests/YoutubeDownloader.Tests.csproj b/YoutubeDownloader.Tests/YoutubeDownloader.Tests.csproj index e6568e56..7c8f0724 100644 --- a/YoutubeDownloader.Tests/YoutubeDownloader.Tests.csproj +++ b/YoutubeDownloader.Tests/YoutubeDownloader.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/YoutubeDownloader/Bridge/PlayerSource.cs b/YoutubeDownloader/Bridge/PlayerSource.cs index bc447e1c..c3076f18 100644 --- a/YoutubeDownloader/Bridge/PlayerSource.cs +++ b/YoutubeDownloader/Bridge/PlayerSource.cs @@ -77,7 +77,7 @@ internal partial class PlayerSource var operations = new List(); - foreach (var statement in CipherCallsite.Split(";")) + foreach (var statement in CipherCallsite.Split(';')) { var calledFuncName = Regex.Match(statement, @"\w+\.(\w+)\(\w+,\d+\)").Groups[1].Value; if (string.IsNullOrWhiteSpace(calledFuncName)) diff --git a/YoutubeDownloader/Utils/Polyfills.Collections.cs b/YoutubeDownloader/Utils/Polyfills.Collections.cs deleted file mode 100644 index fd9cb8a4..00000000 --- a/YoutubeDownloader/Utils/Polyfills.Collections.cs +++ /dev/null @@ -1,29 +0,0 @@ -// ReSharper disable CheckNamespace - -#if NETSTANDARD2_0 || NET461 -using System; -using System.Collections.Generic; - -internal static class CollectionPolyfills -{ - public static void Deconstruct(this KeyValuePair pair, out TKey key, out TValue value) - { - key = pair.Key; - value = pair.Value; - } - - public static string[] Split(this string input, params string[] separators) => - input.Split(separators, StringSplitOptions.RemoveEmptyEntries); -} - -namespace System.Collections.Generic -{ - internal static class CollectionPolyfills - { - public static TValue GetValueOrDefault( - this IReadOnlyDictionary dic, - TKey key) => - dic.TryGetValue(key!, out var result) ? result! : default!; - } -} -#endif \ No newline at end of file diff --git a/YoutubeDownloader/Utils/Polyfills.HashCode.cs b/YoutubeDownloader/Utils/Polyfills.HashCode.cs deleted file mode 100644 index 1070223a..00000000 --- a/YoutubeDownloader/Utils/Polyfills.HashCode.cs +++ /dev/null @@ -1,29 +0,0 @@ -// ReSharper disable CheckNamespace - -#if NETSTANDARD2_0 || NET461 -namespace System -{ - internal static class HashCode - { - public static int Combine(T value) => value?.GetHashCode() ?? 0; - - public static int Combine(T1 value1, T2 value2) - { - var h1 = value1?.GetHashCode() ?? 0; - var h2 = value2?.GetHashCode() ?? 0; - var rol5 = ((uint) h1 << 5) | ((uint) h1 >> 27); - return ((int) rol5 + h1) ^ h2; - } - - public static int Combine(T1 value1, T2 value2, T3 value3) - { - var h1 = value1?.GetHashCode() ?? 0; - var h2 = value2?.GetHashCode() ?? 0; - var h3 = value3?.GetHashCode() ?? 0; - var rol3 = ((uint) h1 << 3) | ((uint) h1 >> 29); - var rol15 = ((uint) h2 << 15) | ((uint) h2 >> 17); - return ((int) rol3 + h1) ^ ((int) rol15 + h2) ^ h3; - } - } -} -#endif \ No newline at end of file diff --git a/YoutubeDownloader/Utils/Polyfills.Http.cs b/YoutubeDownloader/Utils/Polyfills.Http.cs deleted file mode 100644 index 10a033aa..00000000 --- a/YoutubeDownloader/Utils/Polyfills.Http.cs +++ /dev/null @@ -1,60 +0,0 @@ -// ReSharper disable CheckNamespace - -#if !NET5_0_OR_GREATER -using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -internal static class HttpPolyfills -{ - public static async Task ReadAsStreamAsync( - this HttpContent httpContent, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - return await httpContent.ReadAsStreamAsync(); - } - - public static async Task ReadAsStringAsync( - this HttpContent httpContent, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - return await httpContent.ReadAsStringAsync(); - } - - public static async Task GetStringAsync( - this HttpClient httpClient, - string requestUri, - CancellationToken cancellationToken) - { - using var response = await httpClient.GetAsync( - requestUri, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken - ); - - response.EnsureSuccessStatusCode(); - - return await response.Content.ReadAsStringAsync(cancellationToken); - } - - public static async Task GetStreamAsync( - this HttpClient httpClient, - string requestUri, - CancellationToken cancellationToken) - { - // Must not be disposed for the stream to be usable - var response = await httpClient.GetAsync( - requestUri, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken - ); - - response.EnsureSuccessStatusCode(); - - return await response.Content.ReadAsStreamAsync(cancellationToken); - } -} -#endif \ No newline at end of file diff --git a/YoutubeDownloader/Utils/Polyfills.Regex.cs b/YoutubeDownloader/Utils/Polyfills.Regex.cs deleted file mode 100644 index 73199223..00000000 --- a/YoutubeDownloader/Utils/Polyfills.Regex.cs +++ /dev/null @@ -1,12 +0,0 @@ -// ReSharper disable CheckNamespace - -#if NETSTANDARD2_0 || NET461 -using System.Linq; -using System.Text.RegularExpressions; - -internal static class RegexPolyfills -{ - public static Match[] ToArray(this MatchCollection matches) => - matches.Cast().ToArray(); -} -#endif \ No newline at end of file diff --git a/YoutubeDownloader/Utils/Polyfills.Streams.cs b/YoutubeDownloader/Utils/Polyfills.Streams.cs deleted file mode 100644 index 1a29efc9..00000000 --- a/YoutubeDownloader/Utils/Polyfills.Streams.cs +++ /dev/null @@ -1,13 +0,0 @@ -// ReSharper disable CheckNamespace - -#if NETSTANDARD2_0 || NET461 -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -internal static class StreamPolyfills -{ - public static async Task ReadAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken) => - await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken); -} -#endif \ No newline at end of file diff --git a/YoutubeDownloader/Utils/Polyfills.Strings.cs b/YoutubeDownloader/Utils/Polyfills.Strings.cs deleted file mode 100644 index 7f1ace7e..00000000 --- a/YoutubeDownloader/Utils/Polyfills.Strings.cs +++ /dev/null @@ -1,9 +0,0 @@ -// ReSharper disable CheckNamespace - -#if NETSTANDARD2_0 || NET461 -internal static class StringPolyfills -{ - public static bool Contains(this string str, char c) => - str.IndexOf(c) >= 0; -} -#endif \ No newline at end of file diff --git a/YoutubeDownloader/Utils/UriEx.cs b/YoutubeDownloader/Utils/UriEx.cs index 4af91b99..7673eb94 100644 --- a/YoutubeDownloader/Utils/UriEx.cs +++ b/YoutubeDownloader/Utils/UriEx.cs @@ -15,7 +15,7 @@ private static IEnumerable> EnumerateQueryParameter ? url.SubstringAfter("?") : url; - foreach (var parameter in query.Split("&")) + foreach (var parameter in query.Split('&')) { var key = WebUtility.UrlDecode(parameter.SubstringUntil("=")); var value = WebUtility.UrlDecode(parameter.SubstringAfter("=")); diff --git a/YoutubeDownloader/YoutubeDownloader.csproj b/YoutubeDownloader/YoutubeDownloader.csproj index 2d21d66d..0f287ca6 100644 --- a/YoutubeDownloader/YoutubeDownloader.csproj +++ b/YoutubeDownloader/YoutubeDownloader.csproj @@ -1,41 +1,33 @@  - net5.0;netstandard2.1;netstandard2.0;net461;netcoreapp3.1 + netstandard2.0;netstandard2.1;netcoreapp3.1;net461;net5.0 true Etherna.$(AssemblyName) - Abstraction layer over YouTube's internal API + + Abstraction layer over YouTube's internal API. + true - - - - all runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - - - - - - - + + + - +