diff --git a/README.md b/README.md
index 1fdb9e5..572f907 100644
--- a/README.md
+++ b/README.md
@@ -324,7 +324,7 @@ await ytdlp.DownloadBatchAsync(urls, maxConcurrency: 3);
* `.WithPostprocessorArgs(PostProcessors postprocessor, string args)`
* `.WithKeepVideo()`
* `.WithNoPostOverwrites()`
-* `.WithEmbedSubtitles(string languages = "all", string? convertTo = null)`
+* `.WithEmbedSubtitles()`
* `.WithEmbedThumbnail()`
* `.WithEmbedMetadata()`
* `.WithEmbedChapters()`
@@ -333,8 +333,12 @@ await ytdlp.DownloadBatchAsync(urls, maxConcurrency: 3);
* `.WithReplaceInMetadata(string field, string regex, string replacement)`
* `.WithConcatPlaylist(string policy = "always")`
* `.WithFFmpegLocation(string? ffmpegPath)`
+* `.WithConvertSubtitles(string format = "none")`
* `.WithConvertThumbnails(string format = "jpg")`
+* `.WithSplitChapters() => AddFlag("--split-chapters")`
+* `.WithRemoveChapters(string regex)`
* `.WithForceKeyframesAtCuts()`
+* `.WithUsePostProcessor(PostProcessors postProcessor, string? postProcessorArgs = null)`
### SponsorBlock Options
* `.WithSponsorblockMark(string categories = "all")`
diff --git a/src/Ytdlp.NET.Console/Program.cs b/src/Ytdlp.NET.Console/Program.cs
index ebcdc30..e1573a0 100644
--- a/src/Ytdlp.NET.Console/Program.cs
+++ b/src/Ytdlp.NET.Console/Program.cs
@@ -7,24 +7,6 @@ internal class Program
{
private static async Task Main(string[] args)
{
- //Console.WriteLine("=== yt-dlp Output to Regex Converter ===\n");
- //Console.WriteLine("Paste a yt-dlp output line below (or type 'exit' to quit):\n");
-
- //while (true)
- //{
- // Console.Write("> ");
- // string? input = Console.ReadLine();
-
- // if (string.IsNullOrWhiteSpace(input) || input.Trim().ToLower() == "exit")
- // break;
-
- // string regex = ConvertToRegex(input);
- // Console.WriteLine("\nGenerated Regex Pattern:");
- // Console.WriteLine(regex);
- // Console.WriteLine("\n" + new string('-', 60) + "\n");
- //}
-
-
// Must be the FIRST line — before any Console.WriteLine
Console.OutputEncoding = Encoding.UTF8;
Console.InputEncoding = Encoding.UTF8;
@@ -38,20 +20,20 @@ private static async Task Main(string[] args)
.WithFFmpegLocation("tools");
// Run all demos/tests sequentially
- //await TestGetVersionAsync(baseYtdlp);
- //await TestUpdateAsync(baseYtdlp);
+ await TestGetVersionAsync(baseYtdlp);
+ await TestUpdateAsync(baseYtdlp);
- //await TestGetFormatsAsync(baseYtdlp);
- //await TestGetMetadataAsync(baseYtdlp);
- //await TestGetLiteMetadataAsync(baseYtdlp);
- //await TestGetTitleAsync(baseYtdlp);
+ await TestGetFormatsAsync(baseYtdlp);
+ await TestGetMetadataAsync(baseYtdlp);
+ await TestGetLiteMetadataAsync(baseYtdlp);
+ await TestGetTitleAsync(baseYtdlp);
await TestDownloadVideoAsync(baseYtdlp);
- //await TestDownloadAudioAsync(baseYtdlp);
- //await TestBatchDownloadAsync(baseYtdlp);
- //await TestSponsorBlockAsync(baseYtdlp);
- //await TestConcurrentFragmentsAsync(baseYtdlp);
- //await TestCancellationAsync(baseYtdlp);
+ await TestDownloadAudioAsync(baseYtdlp);
+ await TestBatchDownloadAsync(baseYtdlp);
+ await TestSponsorBlockAsync(baseYtdlp);
+ await TestConcurrentFragmentsAsync(baseYtdlp);
+ await TestCancellationAsync(baseYtdlp);
var lists = await baseYtdlp.ExtractorsAsync();
@@ -184,13 +166,12 @@ private static async Task TestDownloadVideoAsync(Ytdlp ytdlpBase)
.WithFormat("ba/b")
.WithExtractAudio(AudioFormat.Mp3)
.WithConcurrentFragments(8)
- .WithOutputFolder("./downloads")
- //.WithHomeFolder("./downloads")
- //.WithTempFolder("./downloads/temp")
- .WithOutputTemplate("%(title)s.%(ext)s");
- //.WithEmbedMetadata()
- //.WithEmbedThumbnail()
- //.WithRemuxVideo(MediaFormat.Mp4);
+ .WithHomeFolder("./downloads")
+ .WithTempFolder("./downloads/temp")
+ .WithOutputTemplate("%(title)s.%(ext)s")
+ .WithEmbedMetadata()
+ .WithEmbedThumbnail()
+ .WithRemuxVideo("mp4");
// Subscribe to events
ytdlp.OnCommandCompleted += (sender, args) =>
diff --git a/src/Ytdlp.NET/README.md b/src/Ytdlp.NET/README.md
index 620c690..72a8479 100644
--- a/src/Ytdlp.NET/README.md
+++ b/src/Ytdlp.NET/README.md
@@ -347,7 +347,7 @@ await ytdlp.DownloadBatchAsync(urls, maxConcurrency: 3);
* `.WithPostprocessorArgs(PostProcessors postprocessor, string args)`
* `.WithKeepVideo()`
* `.WithNoPostOverwrites()`
-* `.WithEmbedSubtitles(string languages = "all", string? convertTo = null)`
+* `.WithEmbedSubtitles()`
* `.WithEmbedThumbnail()`
* `.WithEmbedMetadata()`
* `.WithEmbedChapters()`
@@ -356,8 +356,12 @@ await ytdlp.DownloadBatchAsync(urls, maxConcurrency: 3);
* `.WithReplaceInMetadata(string field, string regex, string replacement)`
* `.WithConcatPlaylist(string policy = "always")`
* `.WithFFmpegLocation(string? ffmpegPath)`
+* `.WithConvertSubtitles(string format = "none")`
* `.WithConvertThumbnails(string format = "jpg")`
+* `.WithSplitChapters() => AddFlag("--split-chapters")`
+* `.WithRemoveChapters(string regex)`
* `.WithForceKeyframesAtCuts()`
+* `.WithUsePostProcessor(PostProcessors postProcessor, string? postProcessorArgs = null)`
### SponsorBlock Options
* `.WithSponsorblockMark(string categories = "all")`
diff --git a/src/Ytdlp.NET/Ytdlp.cs b/src/Ytdlp.NET/Ytdlp.cs
index 2194748..861ffc6 100644
--- a/src/Ytdlp.NET/Ytdlp.cs
+++ b/src/Ytdlp.NET/Ytdlp.cs
@@ -41,6 +41,9 @@ namespace ManuHub.Ytdlp.NET;
///
public sealed class Ytdlp : IAsyncDisposable
{
+ // ==================================================================================================================
+ // Immutable configuration fields events and flags and contructors
+ // ==================================================================================================================
#region Frozen configuration
private readonly string _ytdlpPath;
private readonly ILogger _logger;
@@ -879,8 +882,12 @@ public Ytdlp WithMergeOutputFormat(string format)
/// Write automatically generated subtitle file
public Ytdlp WithSubtitles(string languages = "all", bool auto = false)
{
- var flags = new List { "--write-subs" };
- if (auto) flags.Add("--write-auto-subs");
+ var flags = new List();
+
+ if (auto)
+ flags.Add("--write-auto-subs");
+ else
+ flags.Add("--write-subs");
return new Ytdlp(this, extraFlags: flags, extraOptions: new[] { ("--sub-langs", languages) });
}
@@ -936,7 +943,7 @@ public Ytdlp WithExtractAudio(AudioFormat format = AudioFormat.Best, int quality
/// (currently supported: avi, flv, gif, mkv, mov, mp4, webm, aac, aiff, alac, flac, m4a, mka, mp3, ogg, opus, vorbis, wav).
public Ytdlp WithRemuxVideo(string format)
{
- if(string.IsNullOrWhiteSpace(format))
+ if (string.IsNullOrWhiteSpace(format))
throw new ArgumentException("Remux format cannot be empty", nameof(format));
return this.AddOption("--remux-video", format.ToLowerInvariant());
}
@@ -949,7 +956,7 @@ public Ytdlp WithRemuxVideo(string format)
///
public Ytdlp WithRecodeVideo(string format, string? videoCodec = null, string? audioCodec = null)
{
- if(string.IsNullOrWhiteSpace(format))
+ if (string.IsNullOrWhiteSpace(format))
throw new ArgumentException("Recode format cannot be empty", nameof(format));
var builder = AddOption("--recode-video", format.ToLowerInvariant());
if (!string.IsNullOrWhiteSpace(videoCodec))
@@ -988,18 +995,7 @@ public Ytdlp WithPostprocessorArgs(PostProcessors postprocessor, string args)
///
/// Embed subtitles in the video (only for mp4, webm and mkv videos)
///
- ///
- ///
- public Ytdlp WithEmbedSubtitles(string languages = "all", string? convertTo = null)
- {
- var builder = AddFlag("--sub-langs")
- .AddOption("--write-sub", languages);
- if (!string.IsNullOrWhiteSpace(convertTo))
- builder = builder.AddOption("--convert-subs", convertTo);
- if (convertTo?.Equals("embed", StringComparison.OrdinalIgnoreCase) == true)
- builder = builder.AddFlag("--embed-subs");
- return builder;
- }
+ public Ytdlp WithEmbedSubtitles() => AddFlag("--embed-subs");
///
/// Embed thumbnail in the video as cover art
@@ -1056,11 +1052,26 @@ public Ytdlp WithFFmpegLocation(string? ffmpegPath)
return new Ytdlp(this, ffmpegLocation: ffmpegPath);
}
+ ///
+ /// Convert the subtitles to another format
+ ///
+ /// (currently supported: ass, lrc, srt, vtt)
+ ///
+ ///
+ public Ytdlp WithConvertSubtitles(string format = "none")
+ {
+ if (string.IsNullOrWhiteSpace(format))
+ throw new ArgumentException("Subtitle format cannot be empty", nameof(format));
+
+ return AddOption("--convert-subs", format.Trim().ToLowerInvariant());
+ }
+
///
/// Convert the thumbnails to another format. You can specify multiple rules using similar WithRemuxVideo().
///
/// (currently supported: jpg, png, webp)
///
+ ///
public Ytdlp WithConvertThumbnails(string format = "jpg")
{
// Supported: jpg, png, webp
@@ -1070,6 +1081,25 @@ public Ytdlp WithConvertThumbnails(string format = "jpg")
return AddOption("--convert-thumbnails", format.Trim().ToLowerInvariant());
}
+ ///
+ /// Split video into multiple files based on internal chapters. The "chapter:" prefix can be used with the output filename for the split files.
+ ///
+ public Ytdlp WithSplitChapters() => AddFlag("--split-chapters");
+
+ ///
+ /// Remove chapters whose title matches the given regular expression. The syntax is the same as .
+ /// This option can be used multiple times to remove multiple sections"/>
+ ///
+ ///
+ ///
+ ///
+ public Ytdlp WithRemoveChapters(string regex)
+ {
+ if (string.IsNullOrWhiteSpace(regex))
+ throw new ArgumentException("Regex cannot be empty", nameof(regex));
+ return AddOption("--remove-chapters", regex);
+ }
+
///
/// Force keyframes at cuts when downloading/splitting/removing sections.
/// This is slow due to needing a re-encode, but the resulting video may have fewer artifacts around the cuts
@@ -1077,6 +1107,20 @@ public Ytdlp WithConvertThumbnails(string format = "jpg")
///
public Ytdlp WithForceKeyframesAtCuts() => AddFlag("--force-keyframes-at-cuts");
+ ///
+ /// The (case-sensitive) name of plugin postprocessors to be enabled
+ /// This option can be used multiple times to add different postprocessors
+ ///
+ ///
+ ///
+ ///
+ public Ytdlp WithUsePostProcessor(PostProcessors postProcessor, string? postProcessorArgs = null)
+ {
+ if (!string.IsNullOrWhiteSpace(postProcessorArgs))
+ return AddOption("--use-postprocessor", $"{postProcessor.ToString().Trim()}:{postProcessorArgs.Trim()}");
+ return AddOption("--use-postprocessor", postProcessor.ToString().Trim());
+ }
+
#endregion
#region SponsorBlock Options
@@ -1781,10 +1825,10 @@ public async Task DownloadAsync(string url, CancellationToken ct = default, bool
progressParser.OnErrorMessage += OnErrorMessageHandler;
progressParser.OnPostProcessingStart += OnPostProcessingStartHandler;
progressParser.OnPostProcessingComplete += OnPostProcessingCompleteHandler;
-
+
// Command completion
void OnCommandCompletedHandler(object? s, CommandCompletedEventArgs e) => OnCommandCompleted?.Invoke(this, e);
- download.OnCommandCompleted += OnCommandCompletedHandler;
+ download.OnCommandCompleted += OnCommandCompletedHandler;
try
{
@@ -1847,6 +1891,10 @@ public async Task DownloadBatchAsync(IEnumerable urls, int maxConcurrenc
#endregion
+ // ==================================================================================================================
+ // Internal Helpers and Utilities
+ // ==================================================================================================================
+
#region Helpers
// Get probe runner