diff --git a/Directory.Packages.props b/Directory.Packages.props
index 346bc415..a6247567 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -5,7 +5,7 @@
-
+
@@ -24,7 +24,9 @@
-
+
+
+
diff --git a/YoutubeExplode.Converter.Tests/EnvironmentSpecs.cs b/YoutubeExplode.Converter.Tests/EnvironmentSpecs.cs
index d6880e15..5645380d 100644
--- a/YoutubeExplode.Converter.Tests/EnvironmentSpecs.cs
+++ b/YoutubeExplode.Converter.Tests/EnvironmentSpecs.cs
@@ -1,6 +1,7 @@
using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
+using PowerKit;
using Xunit;
using Xunit.Abstractions;
using YoutubeExplode.Converter.Tests.Utils;
@@ -19,7 +20,7 @@ public async Task I_can_download_a_video_with_custom_environment_variables_passe
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.mp4");
var logFilePath = Path.Combine(dir.Path, "ffreport.log");
diff --git a/YoutubeExplode.Converter.Tests/GeneralSpecs.cs b/YoutubeExplode.Converter.Tests/GeneralSpecs.cs
index 11c770b6..9cb68e95 100644
--- a/YoutubeExplode.Converter.Tests/GeneralSpecs.cs
+++ b/YoutubeExplode.Converter.Tests/GeneralSpecs.cs
@@ -5,10 +5,11 @@
using System.Threading.Tasks;
using FluentAssertions;
using Gress;
+using PowerKit;
+using PowerKit.Extensions;
using Xunit;
using Xunit.Abstractions;
using YoutubeExplode.Converter.Tests.Utils;
-using YoutubeExplode.Converter.Tests.Utils.Extensions;
using YoutubeExplode.Videos.Streams;
namespace YoutubeExplode.Converter.Tests;
@@ -25,7 +26,7 @@ public async Task I_can_download_a_video_as_a_single_mp4_file()
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.mp4");
// Act
@@ -41,7 +42,7 @@ public async Task I_can_download_a_video_as_a_single_webm_file()
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.webm");
// Act
@@ -57,7 +58,7 @@ public async Task I_can_download_a_video_as_a_single_mp3_file()
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.mp3");
// Act
@@ -73,7 +74,7 @@ public async Task I_can_download_a_video_as_a_single_ogg_file()
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.ogg");
// Act
@@ -89,7 +90,7 @@ public async Task I_can_download_a_video_as_a_single_mp4_file_with_multiple_stre
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.mp4");
// Act
@@ -122,7 +123,7 @@ await youtube.Videos.DownloadAsync(
{
if (streamInfo.AudioLanguage is not null)
{
- File.ContainsBytes(
+ File.Contains(
filePath,
Encoding.ASCII.GetBytes(streamInfo.AudioLanguage.Value.Name)
)
@@ -133,7 +134,7 @@ await youtube.Videos.DownloadAsync(
foreach (var streamInfo in videoStreamInfos)
{
- File.ContainsBytes(filePath, Encoding.ASCII.GetBytes(streamInfo.VideoQuality.Label))
+ File.Contains(filePath, Encoding.ASCII.GetBytes(streamInfo.VideoQuality.Label))
.Should()
.BeTrue();
}
@@ -145,7 +146,7 @@ public async Task I_can_download_a_video_as_a_single_webm_file_with_multiple_str
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.webm");
// Act
@@ -178,7 +179,7 @@ await youtube.Videos.DownloadAsync(
{
if (streamInfo.AudioLanguage is not null)
{
- File.ContainsBytes(
+ File.Contains(
filePath,
Encoding.ASCII.GetBytes(streamInfo.AudioLanguage.Value.Name)
)
@@ -189,7 +190,7 @@ await youtube.Videos.DownloadAsync(
foreach (var streamInfo in videoStreamInfos)
{
- File.ContainsBytes(filePath, Encoding.ASCII.GetBytes(streamInfo.VideoQuality.Label))
+ File.Contains(filePath, Encoding.ASCII.GetBytes(streamInfo.VideoQuality.Label))
.Should()
.BeTrue();
}
@@ -201,7 +202,7 @@ public async Task I_can_download_a_video_with_custom_conversion_settings()
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.mp3");
// Act
@@ -224,7 +225,7 @@ public async Task I_can_try_to_download_a_video_and_get_an_error_if_the_conversi
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.mp4");
// Act
@@ -252,7 +253,7 @@ public async Task I_can_download_a_video_while_tracking_progress()
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.mp3");
var progress = new ProgressCollector();
diff --git a/YoutubeExplode.Converter.Tests/SubtitleSpecs.cs b/YoutubeExplode.Converter.Tests/SubtitleSpecs.cs
index 60b8a909..b124d6ae 100644
--- a/YoutubeExplode.Converter.Tests/SubtitleSpecs.cs
+++ b/YoutubeExplode.Converter.Tests/SubtitleSpecs.cs
@@ -3,9 +3,10 @@
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
+using PowerKit;
+using PowerKit.Extensions;
using Xunit;
using YoutubeExplode.Converter.Tests.Utils;
-using YoutubeExplode.Converter.Tests.Utils.Extensions;
using YoutubeExplode.Videos.Streams;
namespace YoutubeExplode.Converter.Tests;
@@ -22,7 +23,7 @@ public async Task I_can_download_a_video_as_a_single_mp4_file_with_subtitles()
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.mp4");
var streamManifest = await youtube.Videos.Streams.GetManifestAsync("NtQkz0aRDe8");
@@ -48,7 +49,7 @@ await youtube.Videos.DownloadAsync(
foreach (var trackInfo in trackInfos)
{
- File.ContainsBytes(filePath, Encoding.ASCII.GetBytes(trackInfo.Language.Name))
+ File.Contains(filePath, Encoding.ASCII.GetBytes(trackInfo.Language.Name))
.Should()
.BeTrue();
}
@@ -60,7 +61,7 @@ public async Task I_can_download_a_video_as_a_single_webm_file_with_subtitles()
// Arrange
using var youtube = new YoutubeClient();
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "video.webm");
var streamManifest = await youtube.Videos.Streams.GetManifestAsync("NtQkz0aRDe8");
@@ -86,7 +87,7 @@ await youtube.Videos.DownloadAsync(
foreach (var trackInfo in trackInfos)
{
- File.ContainsBytes(filePath, Encoding.ASCII.GetBytes(trackInfo.Language.Name))
+ File.Contains(filePath, Encoding.ASCII.GetBytes(trackInfo.Language.Name))
.Should()
.BeTrue();
}
diff --git a/YoutubeExplode.Converter.Tests/Utils/Extensions/FileExtensions.cs b/YoutubeExplode.Converter.Tests/Utils/Extensions/FileExtensions.cs
deleted file mode 100644
index 7d91c6f9..00000000
--- a/YoutubeExplode.Converter.Tests/Utils/Extensions/FileExtensions.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.IO;
-
-namespace YoutubeExplode.Converter.Tests.Utils.Extensions;
-
-internal static class FileExtensions
-{
- extension(File)
- {
- public static bool ContainsBytes(string filePath, ReadOnlySpan data)
- {
- using var stream = File.OpenRead(filePath);
- using var reader = new BinaryReader(stream);
-
- var referenceIndex = 0;
-
- while (stream.Position < stream.Length)
- {
- if (reader.ReadByte() == data[referenceIndex])
- {
- referenceIndex++;
- }
- else
- {
- referenceIndex = 0;
- }
-
- if (referenceIndex >= data.Length)
- return true;
- }
-
- return false;
- }
- }
-}
diff --git a/YoutubeExplode.Converter.Tests/Utils/Extensions/HttpExtensions.cs b/YoutubeExplode.Converter.Tests/Utils/Extensions/HttpExtensions.cs
deleted file mode 100644
index 5da6d20a..00000000
--- a/YoutubeExplode.Converter.Tests/Utils/Extensions/HttpExtensions.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.IO;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace YoutubeExplode.Converter.Tests.Utils.Extensions;
-
-internal static class HttpExtensions
-{
- extension(HttpClient http)
- {
- public async Task DownloadAsync(
- string url,
- string filePath,
- CancellationToken cancellationToken = default
- )
- {
- using var response = await http.GetAsync(
- url,
- HttpCompletionOption.ResponseHeadersRead,
- cancellationToken
- );
-
- response.EnsureSuccessStatusCode();
-
- await using var source = await response.Content.ReadAsStreamAsync(cancellationToken);
- await using var destination = File.Create(filePath);
-
- await source.CopyToAsync(destination, cancellationToken);
- }
- }
-}
diff --git a/YoutubeExplode.Converter.Tests/Utils/FFmpeg.cs b/YoutubeExplode.Converter.Tests/Utils/FFmpeg.cs
index 4335c2ae..f1029956 100644
--- a/YoutubeExplode.Converter.Tests/Utils/FFmpeg.cs
+++ b/YoutubeExplode.Converter.Tests/Utils/FFmpeg.cs
@@ -6,7 +6,8 @@
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
-using YoutubeExplode.Converter.Tests.Utils.Extensions;
+using PowerKit;
+using PowerKit.Extensions;
namespace YoutubeExplode.Converter.Tests.Utils;
diff --git a/YoutubeExplode.Converter.Tests/Utils/TempDir.cs b/YoutubeExplode.Converter.Tests/Utils/TempDir.cs
deleted file mode 100644
index e294ce9b..00000000
--- a/YoutubeExplode.Converter.Tests/Utils/TempDir.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-
-namespace YoutubeExplode.Converter.Tests.Utils;
-
-internal partial class TempDir(string path) : IDisposable
-{
- public string Path { get; } = path;
-
- public void Dispose()
- {
- try
- {
- Directory.Delete(Path, true);
- }
- catch (DirectoryNotFoundException) { }
- }
-}
-
-internal partial class TempDir
-{
- public static TempDir Create()
- {
- var dirPath = System.IO.Path.Combine(
- System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
- ?? Directory.GetCurrentDirectory(),
- "Temp",
- Guid.NewGuid().ToString()
- );
-
- Directory.CreateDirectory(dirPath);
-
- return new TempDir(dirPath);
- }
-}
diff --git a/YoutubeExplode.Converter.Tests/Utils/TempFile.cs b/YoutubeExplode.Converter.Tests/Utils/TempFile.cs
deleted file mode 100644
index 5e04e1c8..00000000
--- a/YoutubeExplode.Converter.Tests/Utils/TempFile.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-
-namespace YoutubeExplode.Converter.Tests.Utils;
-
-internal partial class TempFile(string path) : IDisposable
-{
- public string Path { get; } = path;
-
- public void Dispose()
- {
- try
- {
- File.Delete(Path);
- }
- catch (FileNotFoundException) { }
- }
-}
-
-internal partial class TempFile
-{
- public static TempFile Create()
- {
- var dirPath = System.IO.Path.Combine(
- System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
- ?? Directory.GetCurrentDirectory(),
- "Temp"
- );
-
- Directory.CreateDirectory(dirPath);
-
- var filePath = System.IO.Path.Combine(dirPath, Guid.NewGuid() + ".tmp");
-
- return new TempFile(filePath);
- }
-}
diff --git a/YoutubeExplode.Converter.Tests/YoutubeExplode.Converter.Tests.csproj b/YoutubeExplode.Converter.Tests/YoutubeExplode.Converter.Tests.csproj
index ece322cf..7b3e105c 100644
--- a/YoutubeExplode.Converter.Tests/YoutubeExplode.Converter.Tests.csproj
+++ b/YoutubeExplode.Converter.Tests/YoutubeExplode.Converter.Tests.csproj
@@ -14,6 +14,7 @@
+
diff --git a/YoutubeExplode.Converter/ConversionExtensions.cs b/YoutubeExplode.Converter/ConversionExtensions.cs
index 68feaf3c..2cc384f1 100644
--- a/YoutubeExplode.Converter/ConversionExtensions.cs
+++ b/YoutubeExplode.Converter/ConversionExtensions.cs
@@ -5,7 +5,7 @@
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
-using YoutubeExplode.Converter.Utils.Extensions;
+using PowerKit.Extensions;
using YoutubeExplode.Videos;
using YoutubeExplode.Videos.ClosedCaptions;
using YoutubeExplode.Videos.Streams;
diff --git a/YoutubeExplode.Converter/ConversionRequestBuilder.cs b/YoutubeExplode.Converter/ConversionRequestBuilder.cs
index f534dfc6..8d02b224 100644
--- a/YoutubeExplode.Converter/ConversionRequestBuilder.cs
+++ b/YoutubeExplode.Converter/ConversionRequestBuilder.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
-using YoutubeExplode.Converter.Utils.Extensions;
+using PowerKit.Extensions;
using YoutubeExplode.Videos.Streams;
namespace YoutubeExplode.Converter;
diff --git a/YoutubeExplode.Converter/Converter.cs b/YoutubeExplode.Converter/Converter.cs
index beffcd9f..efc4fc61 100644
--- a/YoutubeExplode.Converter/Converter.cs
+++ b/YoutubeExplode.Converter/Converter.cs
@@ -5,7 +5,8 @@
using System.Threading;
using System.Threading.Tasks;
using CliWrap.Builders;
-using YoutubeExplode.Converter.Utils;
+using PowerKit;
+using PowerKit.Extensions;
using YoutubeExplode.Converter.Utils.Extensions;
using YoutubeExplode.Videos;
using YoutubeExplode.Videos.ClosedCaptions;
@@ -155,10 +156,8 @@ private async ValueTask ProcessAsync(
// Language codes can be stored in any format, but most players expect
// three-letter codes, so we'll try to convert to that first.
var languageCode =
- subtitleInput.Info.Language.TryGetThreeLetterCode() ?? subtitleInput
- .Info
- .Language
- .Code;
+ subtitleInput.Info.Language.TryGetThreeLetterCode()
+ ?? subtitleInput.Info.Language.Code;
arguments
.Add($"-metadata:s:s:{i}")
diff --git a/YoutubeExplode.Converter/FFmpeg.cs b/YoutubeExplode.Converter/FFmpeg.cs
index 49ab6f47..c1ddd5ce 100644
--- a/YoutubeExplode.Converter/FFmpeg.cs
+++ b/YoutubeExplode.Converter/FFmpeg.cs
@@ -9,7 +9,7 @@
using System.Threading.Tasks;
using CliWrap;
using CliWrap.Exceptions;
-using YoutubeExplode.Converter.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Converter;
@@ -135,11 +135,9 @@ private static PipeTarget CreateProgressRouter(IProgress progress)
+ TimeSpan.FromSeconds(seconds);
progress.Report(
- Math.Clamp(
- processedDuration.TotalMilliseconds / totalDuration.Value.TotalMilliseconds,
- 0,
- 1
- )
+ (
+ processedDuration.TotalMilliseconds / totalDuration.Value.TotalMilliseconds
+ ).Clamp(0, 1)
);
}
});
diff --git a/YoutubeExplode.Converter/Utils/Extensions/AsyncCollectionExtensions.cs b/YoutubeExplode.Converter/Utils/Extensions/AsyncCollectionExtensions.cs
deleted file mode 100644
index c268115a..00000000
--- a/YoutubeExplode.Converter/Utils/Extensions/AsyncCollectionExtensions.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-
-namespace YoutubeExplode.Converter.Utils.Extensions;
-
-internal static class AsyncCollectionExtensions
-{
- extension(IAsyncEnumerable source)
- {
- public async ValueTask> ToListAsync()
- {
- var list = new List();
-
- await foreach (var i in source)
- list.Add(i);
-
- return list;
- }
-
- public ValueTaskAwaiter> GetAwaiter() => source.ToListAsync().GetAwaiter();
- }
-}
diff --git a/YoutubeExplode.Converter/Utils/Extensions/GenericExtensions.cs b/YoutubeExplode.Converter/Utils/Extensions/GenericExtensions.cs
deleted file mode 100644
index 1eb20167..00000000
--- a/YoutubeExplode.Converter/Utils/Extensions/GenericExtensions.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace YoutubeExplode.Converter.Utils.Extensions;
-
-internal static class GenericExtensions
-{
- extension(TIn input)
- {
- public TOut Pipe(Func transform) => transform(input);
- }
-}
diff --git a/YoutubeExplode.Converter/Utils/Extensions/LanguageExtensions.cs b/YoutubeExplode.Converter/Utils/Extensions/LanguageExtensions.cs
index 1ff28e49..d9fb23d9 100644
--- a/YoutubeExplode.Converter/Utils/Extensions/LanguageExtensions.cs
+++ b/YoutubeExplode.Converter/Utils/Extensions/LanguageExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using PowerKit.Extensions;
using YoutubeExplode.Videos.ClosedCaptions;
namespace YoutubeExplode.Converter.Utils.Extensions;
diff --git a/YoutubeExplode.Converter/Utils/Extensions/StringExtensions.cs b/YoutubeExplode.Converter/Utils/Extensions/StringExtensions.cs
deleted file mode 100644
index afb1e753..00000000
--- a/YoutubeExplode.Converter/Utils/Extensions/StringExtensions.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-
-namespace YoutubeExplode.Converter.Utils.Extensions;
-
-internal static class StringExtensions
-{
- extension(string str)
- {
- public string? NullIfWhiteSpace() => !string.IsNullOrWhiteSpace(str) ? str : null;
-
- public string SubstringUntil(
- string sub,
- StringComparison comparison = StringComparison.Ordinal
- ) =>
- str.IndexOf(sub, comparison) switch
- {
- >= 0 and var index => str[..index],
- _ => str,
- };
- }
-}
diff --git a/YoutubeExplode.Converter/Utils/ProgressMuxer.cs b/YoutubeExplode.Converter/Utils/ProgressMuxer.cs
deleted file mode 100644
index 46c04a42..00000000
--- a/YoutubeExplode.Converter/Utils/ProgressMuxer.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-
-namespace YoutubeExplode.Converter.Utils;
-
-internal class ProgressMuxer(IProgress target)
-{
- private readonly Lock _lock = new();
- private readonly Dictionary _splitWeights = new();
- private readonly Dictionary _splitValues = new();
-
- public IProgress CreateInput(double weight = 1)
- {
- using (_lock.EnterScope())
- {
- var index = _splitWeights.Count;
-
- _splitWeights[index] = weight;
- _splitValues[index] = 0;
-
- return new Progress(p =>
- {
- using (_lock.EnterScope())
- {
- _splitValues[index] = p;
-
- var weightedSum = 0.0;
- var weightedMax = 0.0;
-
- for (var i = 0; i < _splitWeights.Count; i++)
- {
- weightedSum += _splitWeights[i] * _splitValues[i];
- weightedMax += _splitWeights[i];
- }
-
- target.Report(weightedSum / weightedMax);
- }
- });
- }
- }
-}
diff --git a/YoutubeExplode.Converter/YoutubeExplode.Converter.csproj b/YoutubeExplode.Converter/YoutubeExplode.Converter.csproj
index 0b214e2c..592c8d7f 100644
--- a/YoutubeExplode.Converter/YoutubeExplode.Converter.csproj
+++ b/YoutubeExplode.Converter/YoutubeExplode.Converter.csproj
@@ -27,6 +27,7 @@
+
diff --git a/YoutubeExplode.Demo.Gui/Utils/DelegateProgress.cs b/YoutubeExplode.Demo.Gui/Utils/DelegateProgress.cs
deleted file mode 100644
index 90bfaf7f..00000000
--- a/YoutubeExplode.Demo.Gui/Utils/DelegateProgress.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace YoutubeExplode.Demo.Gui.Utils;
-
-// Straightforward implementation of IProgress that simply invokes a delegate
-// without using any synchronization (unlike the built-in Progress class).
-// This is required in Avalonia because the built-in Progress class causes race conditions.
-internal class DelegateProgress(Action report) : IProgress
-{
- public void Report(T value) => report(value);
-}
diff --git a/YoutubeExplode.Demo.Gui/Utils/Extensions/PathExtensions.cs b/YoutubeExplode.Demo.Gui/Utils/Extensions/PathExtensions.cs
deleted file mode 100644
index 7baf46c6..00000000
--- a/YoutubeExplode.Demo.Gui/Utils/Extensions/PathExtensions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.IO;
-
-namespace YoutubeExplode.Demo.Gui.Utils.Extensions;
-
-internal static class PathExtensions
-{
- extension(Path)
- {
- public static string SanitizeFileName(string fileName, char replacement = '_')
- {
- foreach (var invalidChar in Path.GetInvalidFileNameChars())
- fileName = fileName.Replace(invalidChar, replacement);
-
- return fileName;
- }
- }
-}
diff --git a/YoutubeExplode.Demo.Gui/ViewModels/MainViewModel.cs b/YoutubeExplode.Demo.Gui/ViewModels/MainViewModel.cs
index 7b10ca32..d615efdf 100644
--- a/YoutubeExplode.Demo.Gui/ViewModels/MainViewModel.cs
+++ b/YoutubeExplode.Demo.Gui/ViewModels/MainViewModel.cs
@@ -7,9 +7,10 @@
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using PowerKit;
+using PowerKit.Extensions;
using YoutubeExplode.Channels;
using YoutubeExplode.Common;
-using YoutubeExplode.Demo.Gui.Utils;
using YoutubeExplode.Demo.Gui.Utils.Extensions;
using YoutubeExplode.Videos;
using YoutubeExplode.Videos.ClosedCaptions;
@@ -182,9 +183,7 @@ private async Task DownloadStreamAsync(IStreamInfo? streamInfo)
Progress = 0;
// Generate a default file name
- var defaultFileName = Path.SanitizeFileName(
- $"{Video.Title}.{streamInfo.Container.Name}"
- );
+ var defaultFileName = Path.EscapeFileName($"{Video.Title}.{streamInfo.Container.Name}");
// Prompt for file path
var filePath = await PromptSaveFilePathAsync(
@@ -225,7 +224,7 @@ private async Task DownloadClosedCaptionTrackAsync(ClosedCaptionTrackInfo? track
Progress = 0;
// Generate a default file name
- var defaultFileName = Path.SanitizeFileName(
+ var defaultFileName = Path.EscapeFileName(
$"{Video.Title}.{trackInfo.Language.Name}.srt"
);
diff --git a/YoutubeExplode.Demo.Gui/YoutubeExplode.Demo.Gui.csproj b/YoutubeExplode.Demo.Gui/YoutubeExplode.Demo.Gui.csproj
index 2ba3eeda..63d1389d 100644
--- a/YoutubeExplode.Demo.Gui/YoutubeExplode.Demo.Gui.csproj
+++ b/YoutubeExplode.Demo.Gui/YoutubeExplode.Demo.Gui.csproj
@@ -17,6 +17,7 @@
+
diff --git a/YoutubeExplode.Tests/ClosedCaptionSpecs.cs b/YoutubeExplode.Tests/ClosedCaptionSpecs.cs
index 1a6680fc..fb976c5a 100644
--- a/YoutubeExplode.Tests/ClosedCaptionSpecs.cs
+++ b/YoutubeExplode.Tests/ClosedCaptionSpecs.cs
@@ -2,9 +2,9 @@
using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
+using PowerKit;
using Xunit;
using YoutubeExplode.Tests.TestData;
-using YoutubeExplode.Tests.Utils;
namespace YoutubeExplode.Tests;
diff --git a/YoutubeExplode.Tests/StreamSpecs.cs b/YoutubeExplode.Tests/StreamSpecs.cs
index c657fa91..72e24383 100644
--- a/YoutubeExplode.Tests/StreamSpecs.cs
+++ b/YoutubeExplode.Tests/StreamSpecs.cs
@@ -3,11 +3,11 @@
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
+using PowerKit;
using Xunit;
using Xunit.Abstractions;
using YoutubeExplode.Exceptions;
using YoutubeExplode.Tests.TestData;
-using YoutubeExplode.Tests.Utils;
using YoutubeExplode.Videos.Streams;
namespace YoutubeExplode.Tests;
diff --git a/YoutubeExplode.Tests/Utils/TempFile.cs b/YoutubeExplode.Tests/Utils/TempFile.cs
deleted file mode 100644
index 18793874..00000000
--- a/YoutubeExplode.Tests/Utils/TempFile.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-
-namespace YoutubeExplode.Tests.Utils;
-
-internal partial class TempFile(string path) : IDisposable
-{
- public string Path { get; } = path;
-
- public void Dispose()
- {
- try
- {
- File.Delete(Path);
- }
- catch (FileNotFoundException) { }
- }
-}
-
-internal partial class TempFile
-{
- public static TempFile Create()
- {
- var dirPath = System.IO.Path.Combine(
- System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
- ?? Directory.GetCurrentDirectory(),
- "Temp"
- );
-
- Directory.CreateDirectory(dirPath);
-
- var filePath = System.IO.Path.Combine(dirPath, Guid.NewGuid() + ".tmp");
-
- return new TempFile(filePath);
- }
-}
diff --git a/YoutubeExplode.Tests/YoutubeExplode.Tests.csproj b/YoutubeExplode.Tests/YoutubeExplode.Tests.csproj
index 0845d734..7fd231aa 100644
--- a/YoutubeExplode.Tests/YoutubeExplode.Tests.csproj
+++ b/YoutubeExplode.Tests/YoutubeExplode.Tests.csproj
@@ -17,6 +17,7 @@
+
diff --git a/YoutubeExplode/Bridge/ChannelPage.cs b/YoutubeExplode/Bridge/ChannelPage.cs
index af0adf4d..22d290c6 100644
--- a/YoutubeExplode/Bridge/ChannelPage.cs
+++ b/YoutubeExplode/Bridge/ChannelPage.cs
@@ -1,8 +1,8 @@
using System;
using AngleSharp.Html.Dom;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Utils;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Bridge;
diff --git a/YoutubeExplode/Bridge/Cipher/ReverseCipherOperation.cs b/YoutubeExplode/Bridge/Cipher/ReverseCipherOperation.cs
index 18c2bbf3..8d0bf891 100644
--- a/YoutubeExplode/Bridge/Cipher/ReverseCipherOperation.cs
+++ b/YoutubeExplode/Bridge/Cipher/ReverseCipherOperation.cs
@@ -1,5 +1,5 @@
using System.Diagnostics.CodeAnalysis;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Bridge.Cipher;
diff --git a/YoutubeExplode/Bridge/Cipher/SwapCipherOperation.cs b/YoutubeExplode/Bridge/Cipher/SwapCipherOperation.cs
index ede39089..c29ed139 100644
--- a/YoutubeExplode/Bridge/Cipher/SwapCipherOperation.cs
+++ b/YoutubeExplode/Bridge/Cipher/SwapCipherOperation.cs
@@ -1,11 +1,12 @@
using System.Diagnostics.CodeAnalysis;
-using YoutubeExplode.Utils.Extensions;
+using System.Text;
namespace YoutubeExplode.Bridge.Cipher;
internal class SwapCipherOperation(int index) : ICipherOperation
{
- public string Decipher(string input) => input.SwapChars(0, index);
+ public string Decipher(string input) =>
+ new StringBuilder(input) { [0] = input[index], [index] = input[0] }.ToString();
[ExcludeFromCodeCoverage]
public override string ToString() => $"Swap ({index})";
diff --git a/YoutubeExplode/Bridge/ClosedCaptionTrackResponse.cs b/YoutubeExplode/Bridge/ClosedCaptionTrackResponse.cs
index f890c795..7b902a46 100644
--- a/YoutubeExplode/Bridge/ClosedCaptionTrackResponse.cs
+++ b/YoutubeExplode/Bridge/ClosedCaptionTrackResponse.cs
@@ -3,8 +3,8 @@
using System.Linq;
using System.Xml.Linq;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Utils;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Bridge;
diff --git a/YoutubeExplode/Bridge/DashManifest.cs b/YoutubeExplode/Bridge/DashManifest.cs
index 8e31fb55..2228f92b 100644
--- a/YoutubeExplode/Bridge/DashManifest.cs
+++ b/YoutubeExplode/Bridge/DashManifest.cs
@@ -5,8 +5,8 @@
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Utils;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Bridge;
@@ -54,11 +54,7 @@ public class StreamData(XElement content) : IStreamData
(long?)content.Attribute("contentLength")
?? Url?.Pipe(s => Regex.Match(s, @"[/\?]clen[/=](\d+)").Groups[1].Value)
.NullIfWhiteSpace()
- ?.Pipe(s =>
- long.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (long?)null
- );
+ ?.Pipe(s => long.ParseOrNull(s, CultureInfo.InvariantCulture));
[Lazy]
public long? Bitrate => (long?)content.Attribute("bandwidth");
diff --git a/YoutubeExplode/Bridge/PlayerResponse.cs b/YoutubeExplode/Bridge/PlayerResponse.cs
index 4ca4feec..35131cb1 100644
--- a/YoutubeExplode/Bridge/PlayerResponse.cs
+++ b/YoutubeExplode/Bridge/PlayerResponse.cs
@@ -5,9 +5,10 @@
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
+using JsonExtensions.Reading;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Utils;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Bridge;
@@ -57,11 +58,7 @@ internal partial class PlayerResponse(JsonElement content)
Details
?.GetPropertyOrNull("lengthSeconds")
?.GetStringOrNull()
- ?.Pipe(s =>
- double.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (double?)null
- )
+ ?.Pipe(s => double.ParseOrNull(s, CultureInfo.InvariantCulture))
?.Pipe(TimeSpan.FromSeconds);
[Lazy]
@@ -71,7 +68,8 @@ internal partial class PlayerResponse(JsonElement content)
?.GetPropertyOrNull("thumbnails")
?.EnumerateArrayOrNull()
?.Select(j => new ThumbnailData(j))
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
public IReadOnlyList Keywords =>
Details
@@ -79,7 +77,8 @@ internal partial class PlayerResponse(JsonElement content)
?.EnumerateArrayOrNull()
?.Select(j => j.GetStringOrNull())
.WhereNotNull()
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
[Lazy]
public string? Description => Details?.GetPropertyOrNull("shortDescription")?.GetStringOrNull();
@@ -89,11 +88,7 @@ internal partial class PlayerResponse(JsonElement content)
Details
?.GetPropertyOrNull("viewCount")
?.GetStringOrNull()
- ?.Pipe(s =>
- long.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (long?)null
- );
+ ?.Pipe(s => long.ParseOrNull(s, CultureInfo.InvariantCulture));
[Lazy]
public string? PreviewVideoId =>
@@ -172,7 +167,8 @@ public IReadOnlyList Streams
?.GetPropertyOrNull("captionTracks")
?.EnumerateArrayOrNull()
?.Select(j => new ClosedCaptionTrackData(j))
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
}
internal partial class PlayerResponse
@@ -201,7 +197,8 @@ public class ClosedCaptionTrackData(JsonElement content)
content
.GetPropertyOrNull("vssId")
?.GetStringOrNull()
- ?.StartsWith("a.", StringComparison.OrdinalIgnoreCase) ?? false;
+ ?.StartsWith("a.", StringComparison.OrdinalIgnoreCase)
+ ?? false;
}
}
@@ -236,18 +233,10 @@ public class StreamData(JsonElement content) : IStreamData
content
.GetPropertyOrNull("contentLength")
?.GetStringOrNull()
- ?.Pipe(s =>
- long.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (long?)null
- )
+ ?.Pipe(s => long.ParseOrNull(s, CultureInfo.InvariantCulture))
?? Url?.Pipe(s => UrlEx.TryGetQueryParameterValue(s, "clen"))
?.NullIfWhiteSpace()
- ?.Pipe(s =>
- long.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (long?)null
- );
+ ?.Pipe(s => long.ParseOrNull(s, CultureInfo.InvariantCulture));
[Lazy]
public long? Bitrate => content.GetPropertyOrNull("bitrate")?.GetInt64OrNull();
diff --git a/YoutubeExplode/Bridge/PlayerSource.cs b/YoutubeExplode/Bridge/PlayerSource.cs
index 4c486740..7d3d76e8 100644
--- a/YoutubeExplode/Bridge/PlayerSource.cs
+++ b/YoutubeExplode/Bridge/PlayerSource.cs
@@ -3,8 +3,8 @@
using System.Globalization;
using System.Text.RegularExpressions;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Bridge.Cipher;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Bridge;
diff --git a/YoutubeExplode/Bridge/PlaylistBrowseResponse.cs b/YoutubeExplode/Bridge/PlaylistBrowseResponse.cs
index 781660ab..ec2a8dbd 100644
--- a/YoutubeExplode/Bridge/PlaylistBrowseResponse.cs
+++ b/YoutubeExplode/Bridge/PlaylistBrowseResponse.cs
@@ -2,9 +2,10 @@
using System.Globalization;
using System.Linq;
using System.Text.Json;
+using JsonExtensions.Reading;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Utils;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Bridge;
@@ -113,9 +114,7 @@ internal partial class PlaylistBrowseResponse(JsonElement content) : IPlaylistDa
?.FirstOrNull()
?.GetPropertyOrNull("text")
?.GetStringOrNull()
- ?.Pipe(s =>
- int.TryParse(s, CultureInfo.InvariantCulture, out var result) ? result : (int?)null
- )
+ ?.Pipe(s => int.ParseOrNull(s, CultureInfo.InvariantCulture))
?? SidebarPrimary
?.GetPropertyOrNull("stats")
?.EnumerateArrayOrNull()
@@ -124,9 +123,7 @@ internal partial class PlaylistBrowseResponse(JsonElement content) : IPlaylistDa
?.GetStringOrNull()
?.Split(' ')
?.FirstOrDefault()
- ?.Pipe(s =>
- int.TryParse(s, CultureInfo.InvariantCulture, out var result) ? result : (int?)null
- );
+ ?.Pipe(s => int.ParseOrNull(s, CultureInfo.InvariantCulture));
[Lazy]
public IReadOnlyList Thumbnails =>
diff --git a/YoutubeExplode/Bridge/PlaylistNextResponse.cs b/YoutubeExplode/Bridge/PlaylistNextResponse.cs
index 7497d540..447c245e 100644
--- a/YoutubeExplode/Bridge/PlaylistNextResponse.cs
+++ b/YoutubeExplode/Bridge/PlaylistNextResponse.cs
@@ -2,9 +2,10 @@
using System.Globalization;
using System.Linq;
using System.Text.Json;
+using JsonExtensions.Reading;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Utils;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Bridge;
@@ -44,9 +45,7 @@ internal partial class PlaylistNextResponse(JsonElement content) : IPlaylistData
?.FirstOrNull()
?.GetPropertyOrNull("text")
?.GetStringOrNull()
- ?.Pipe(s =>
- int.TryParse(s, CultureInfo.InvariantCulture, out var result) ? result : (int?)null
- )
+ ?.Pipe(s => int.ParseOrNull(s, CultureInfo.InvariantCulture))
?? ContentRoot
?.GetPropertyOrNull("videoCountText")
?.GetPropertyOrNull("runs")
@@ -54,9 +53,7 @@ internal partial class PlaylistNextResponse(JsonElement content) : IPlaylistData
?.ElementAtOrNull(2)
?.GetPropertyOrNull("text")
?.GetStringOrNull()
- ?.Pipe(s =>
- int.TryParse(s, CultureInfo.InvariantCulture, out var result) ? result : (int?)null
- );
+ ?.Pipe(s => int.ParseOrNull(s, CultureInfo.InvariantCulture));
[Lazy]
public IReadOnlyList Thumbnails => Videos.FirstOrDefault()?.Thumbnails ?? [];
@@ -69,7 +66,8 @@ internal partial class PlaylistNextResponse(JsonElement content) : IPlaylistData
?.Select(j => j.GetPropertyOrNull("playlistPanelVideoRenderer"))
.WhereNotNull()
.Select(j => new PlaylistVideoData(j))
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
[Lazy]
public string? VisitorData =>
diff --git a/YoutubeExplode/Bridge/PlaylistVideoData.cs b/YoutubeExplode/Bridge/PlaylistVideoData.cs
index f386dd1e..0f95019f 100644
--- a/YoutubeExplode/Bridge/PlaylistVideoData.cs
+++ b/YoutubeExplode/Bridge/PlaylistVideoData.cs
@@ -3,8 +3,9 @@
using System.Globalization;
using System.Linq;
using System.Text.Json;
+using JsonExtensions.Reading;
using Lazy;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Bridge;
@@ -82,11 +83,7 @@ internal class PlaylistVideoData(JsonElement content)
content
.GetPropertyOrNull("lengthSeconds")
?.GetStringOrNull()
- ?.Pipe(s =>
- double.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (double?)null
- )
+ ?.Pipe(s => double.ParseOrNull(s, CultureInfo.InvariantCulture))
?.Pipe(TimeSpan.FromSeconds)
?? content
.GetPropertyOrNull("lengthText")
@@ -127,5 +124,6 @@ out var result
?.GetPropertyOrNull("thumbnails")
?.EnumerateArrayOrNull()
?.Select(j => new ThumbnailData(j))
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
}
diff --git a/YoutubeExplode/Bridge/SearchResponse.cs b/YoutubeExplode/Bridge/SearchResponse.cs
index af645bab..79011f68 100644
--- a/YoutubeExplode/Bridge/SearchResponse.cs
+++ b/YoutubeExplode/Bridge/SearchResponse.cs
@@ -3,7 +3,9 @@
using System.Globalization;
using System.Linq;
using System.Text.Json;
+using JsonExtensions.Reading;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Utils;
using YoutubeExplode.Utils.Extensions;
@@ -24,7 +26,8 @@ internal partial class SearchResponse(JsonElement content)
ContentRoot
?.EnumerateDescendantProperties("videoRenderer")
.Select(j => new VideoData(j))
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
[Lazy]
public IReadOnlyList Playlists =>
@@ -43,7 +46,8 @@ internal partial class SearchResponse(JsonElement content)
ContentRoot
?.EnumerateDescendantProperties("channelRenderer")
.Select(j => new ChannelData(j))
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
[Lazy]
public string? ContinuationToken =>
@@ -144,7 +148,8 @@ out var result
?.GetPropertyOrNull("thumbnails")
?.EnumerateArrayOrNull()
?.Select(j => new ThumbnailData(j))
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
}
}
@@ -259,7 +264,8 @@ public class ChannelData(JsonElement content)
?.GetPropertyOrNull("thumbnails")
?.EnumerateArrayOrNull()
?.Select(j => new ThumbnailData(j))
- .ToArray() ?? [];
+ .ToArray()
+ ?? [];
}
}
diff --git a/YoutubeExplode/Bridge/ThumbnailData.cs b/YoutubeExplode/Bridge/ThumbnailData.cs
index 71193e91..7f6ec1a6 100644
--- a/YoutubeExplode/Bridge/ThumbnailData.cs
+++ b/YoutubeExplode/Bridge/ThumbnailData.cs
@@ -1,6 +1,6 @@
using System.Text.Json;
+using JsonExtensions.Reading;
using Lazy;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Bridge;
diff --git a/YoutubeExplode/Bridge/VideoWatchPage.cs b/YoutubeExplode/Bridge/VideoWatchPage.cs
index 6f2af388..086faecd 100644
--- a/YoutubeExplode/Bridge/VideoWatchPage.cs
+++ b/YoutubeExplode/Bridge/VideoWatchPage.cs
@@ -5,7 +5,9 @@
using System.Text.RegularExpressions;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
+using JsonExtensions.Reading;
using Lazy;
+using PowerKit.Extensions;
using YoutubeExplode.Utils;
using YoutubeExplode.Utils.Extensions;
@@ -23,18 +25,14 @@ internal partial class VideoWatchPage(IHtmlDocument content)
?.GetAttribute("content")
?.NullIfWhiteSpace()
?.Pipe(s =>
- DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (DateTimeOffset?)null
+ DateTimeOffset.ParseOrNull(s, CultureInfo.InvariantCulture, DateTimeStyles.None)
)
?? content
.QuerySelector("meta[itemprop=\"datePublished\"]")
?.GetAttribute("content")
?.NullIfWhiteSpace()
?.Pipe(s =>
- DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (DateTimeOffset?)null
+ DateTimeOffset.ParseOrNull(s, CultureInfo.InvariantCulture, DateTimeStyles.None)
);
[Lazy]
@@ -53,11 +51,7 @@ internal partial class VideoWatchPage(IHtmlDocument content)
)
.NullIfWhiteSpace()
?.StripNonDigit()
- .Pipe(s =>
- long.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (long?)null
- )
+ .Pipe(s => long.ParseOrNull(s, CultureInfo.InvariantCulture))
?? content
.Source.Text.Pipe(s =>
Regex
@@ -72,11 +66,7 @@ along with ([\d,\.]+) other people"
)
.NullIfWhiteSpace()
?.StripNonDigit()
- .Pipe(s =>
- long.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (long?)null
- );
+ .Pipe(s => long.ParseOrNull(s, CultureInfo.InvariantCulture));
[Lazy]
public long? DislikeCount =>
@@ -94,11 +84,7 @@ along with ([\d,\.]+) other people"
)
.NullIfWhiteSpace()
?.StripNonDigit()
- .Pipe(s =>
- long.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (long?)null
- );
+ .Pipe(s => long.ParseOrNull(s, CultureInfo.InvariantCulture));
[Lazy]
private JsonElement? PlayerConfig =>
@@ -106,8 +92,8 @@ along with ([\d,\.]+) other people"
.GetElementsByTagName("script")
.Select(e => e.Text())
.Select(s => Regex.Match(s, @"ytplayer\.config\s*=\s*(\{.*\})").Groups[1].Value)
- .FirstOrDefault(s => !string.IsNullOrWhiteSpace(s))
- ?.NullIfWhiteSpace()
+ .WhereNotNullOrWhiteSpace()
+ .FirstOrDefault()
?.Pipe(Json.Extract)
.Pipe(Json.TryParse);
@@ -119,8 +105,8 @@ along with ([\d,\.]+) other people"
.Select(s =>
Regex.Match(s, @"var\s+ytInitialPlayerResponse\s*=\s*(\{.*\})").Groups[1].Value
)
- .FirstOrDefault(s => !string.IsNullOrWhiteSpace(s))
- ?.NullIfWhiteSpace()
+ .WhereNotNullOrWhiteSpace()
+ .FirstOrDefault()
?.Pipe(Json.Extract)
.Pipe(Json.TryParse)
?.Pipe(j => new PlayerResponse(j))
diff --git a/YoutubeExplode/Channels/ChannelClient.cs b/YoutubeExplode/Channels/ChannelClient.cs
index 8cf531c2..2ba4424e 100644
--- a/YoutubeExplode/Channels/ChannelClient.cs
+++ b/YoutubeExplode/Channels/ChannelClient.cs
@@ -5,11 +5,11 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using PowerKit.Extensions;
using YoutubeExplode.Bridge;
using YoutubeExplode.Common;
using YoutubeExplode.Exceptions;
using YoutubeExplode.Playlists;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Channels;
@@ -41,11 +41,7 @@ private Channel Get(ChannelPage channelPage)
.LastOrDefault()
?.Groups[1]
.Value.NullIfWhiteSpace()
- ?.Pipe(s =>
- int.TryParse(s, CultureInfo.InvariantCulture, out var result)
- ? result
- : (int?)null
- )
+ ?.Pipe(s => int.ParseOrNull(s, CultureInfo.InvariantCulture))
?? 100;
var thumbnails = new[] { new Thumbnail(logoUrl, new Resolution(logoSize, logoSize)) };
diff --git a/YoutubeExplode/Channels/ChannelHandle.cs b/YoutubeExplode/Channels/ChannelHandle.cs
index a5b97350..9c69c73a 100644
--- a/YoutubeExplode/Channels/ChannelHandle.cs
+++ b/YoutubeExplode/Channels/ChannelHandle.cs
@@ -2,7 +2,7 @@
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Channels;
diff --git a/YoutubeExplode/Channels/ChannelId.cs b/YoutubeExplode/Channels/ChannelId.cs
index 769728fe..0698ac91 100644
--- a/YoutubeExplode/Channels/ChannelId.cs
+++ b/YoutubeExplode/Channels/ChannelId.cs
@@ -2,7 +2,7 @@
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Channels;
diff --git a/YoutubeExplode/Channels/ChannelSlug.cs b/YoutubeExplode/Channels/ChannelSlug.cs
index f0879dfa..ba6f8104 100644
--- a/YoutubeExplode/Channels/ChannelSlug.cs
+++ b/YoutubeExplode/Channels/ChannelSlug.cs
@@ -2,7 +2,7 @@
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Channels;
diff --git a/YoutubeExplode/Channels/UserName.cs b/YoutubeExplode/Channels/UserName.cs
index 10fbfcfd..56ff0ce9 100644
--- a/YoutubeExplode/Channels/UserName.cs
+++ b/YoutubeExplode/Channels/UserName.cs
@@ -2,7 +2,7 @@
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Channels;
diff --git a/YoutubeExplode/Common/Batch.cs b/YoutubeExplode/Common/Batch.cs
index 46bd87b3..01632417 100644
--- a/YoutubeExplode/Common/Batch.cs
+++ b/YoutubeExplode/Common/Batch.cs
@@ -1,5 +1,5 @@
using System.Collections.Generic;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Common;
diff --git a/YoutubeExplode/Common/IBatchItem.cs b/YoutubeExplode/Common/IBatchItem.cs
index d8ebfea8..584ea279 100644
--- a/YoutubeExplode/Common/IBatchItem.cs
+++ b/YoutubeExplode/Common/IBatchItem.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Common;
diff --git a/YoutubeExplode/Playlists/PlaylistId.cs b/YoutubeExplode/Playlists/PlaylistId.cs
index 5cb2d75e..b3f36fb5 100644
--- a/YoutubeExplode/Playlists/PlaylistId.cs
+++ b/YoutubeExplode/Playlists/PlaylistId.cs
@@ -2,7 +2,7 @@
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
-using YoutubeExplode.Utils.Extensions;
+using PowerKit.Extensions;
namespace YoutubeExplode.Playlists;
diff --git a/YoutubeExplode/Search/SearchClient.cs b/YoutubeExplode/Search/SearchClient.cs
index 04bbbb1b..eb0ad827 100644
--- a/YoutubeExplode/Search/SearchClient.cs
+++ b/YoutubeExplode/Search/SearchClient.cs
@@ -5,10 +5,10 @@
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
+using PowerKit.Extensions;
using YoutubeExplode.Channels;
using YoutubeExplode.Common;
using YoutubeExplode.Exceptions;
-using YoutubeExplode.Utils.Extensions;
namespace YoutubeExplode.Search;
diff --git a/YoutubeExplode/Utils/ClientDelegatingHandler.cs b/YoutubeExplode/Utils/ClientDelegatingHandler.cs
deleted file mode 100644
index 4b2d82ae..00000000
--- a/YoutubeExplode/Utils/ClientDelegatingHandler.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using YoutubeExplode.Utils.Extensions;
-
-namespace YoutubeExplode.Utils;
-
-// Like DelegatingHandler, but wraps an HttpClient instead of an HttpMessageHandler.
-// Used to extend an externally provided HttpClient with additional behavior.
-internal abstract class ClientDelegatingHandler(HttpClient http, bool disposeClient = false)
- : HttpMessageHandler
-{
- protected override async Task SendAsync(
- HttpRequestMessage request,
- CancellationToken cancellationToken
- )
- {
- // Clone the request to reset its completion status, which is required
- // in order to pass the request from one HttpClient to another.
- using var clonedRequest = request.Clone();
-
- return await http.SendAsync(
- clonedRequest,
- HttpCompletionOption.ResponseHeadersRead,
- cancellationToken
- );
- }
-
- protected override void Dispose(bool disposing)
- {
- if (disposing && disposeClient)
- http.Dispose();
-
- base.Dispose(disposing);
- }
-}
diff --git a/YoutubeExplode/Utils/Extensions/AsyncCollectionExtensions.cs b/YoutubeExplode/Utils/Extensions/AsyncCollectionExtensions.cs
deleted file mode 100644
index 6f9e23a9..00000000
--- a/YoutubeExplode/Utils/Extensions/AsyncCollectionExtensions.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-
-namespace YoutubeExplode.Utils.Extensions;
-
-internal static class AsyncCollectionExtensions
-{
- extension(IAsyncEnumerable source)
- {
- public async IAsyncEnumerable TakeAsync(int count)
- {
- var currentCount = 0;
-
- await foreach (var i in source)
- {
- if (currentCount >= count)
- yield break;
-
- yield return i;
- currentCount++;
- }
- }
-
- public async IAsyncEnumerable SelectManyAsync(Func> transform)
- {
- await foreach (var i in source)
- {
- foreach (var j in transform(i))
- yield return j;
- }
- }
-
- public async ValueTask> ToListAsync()
- {
- var list = new List();
-
- await foreach (var i in source)
- list.Add(i);
-
- return list;
- }
-
- public ValueTaskAwaiter> GetAwaiter() => source.ToListAsync().GetAwaiter();
- }
-
- extension(IAsyncEnumerable