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