Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`
Expand All @@ -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")`
Expand Down
53 changes: 17 additions & 36 deletions src/Ytdlp.NET.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();

Expand Down Expand Up @@ -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) =>
Expand Down
6 changes: 5 additions & 1 deletion src/Ytdlp.NET/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`
Expand All @@ -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")`
Expand Down
84 changes: 66 additions & 18 deletions src/Ytdlp.NET/Ytdlp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ namespace ManuHub.Ytdlp.NET;
/// </remarks>
public sealed class Ytdlp : IAsyncDisposable
{
// ==================================================================================================================
// Immutable configuration fields events and flags and contructors
// ==================================================================================================================
#region Frozen configuration
private readonly string _ytdlpPath;
private readonly ILogger _logger;
Expand Down Expand Up @@ -879,8 +882,12 @@ public Ytdlp WithMergeOutputFormat(string format)
/// <param name="auto">Write automatically generated subtitle file</param>
public Ytdlp WithSubtitles(string languages = "all", bool auto = false)
{
var flags = new List<string> { "--write-subs" };
if (auto) flags.Add("--write-auto-subs");
var flags = new List<string>();

if (auto)
flags.Add("--write-auto-subs");
else
flags.Add("--write-subs");

return new Ytdlp(this, extraFlags: flags, extraOptions: new[] { ("--sub-langs", languages) });
}
Expand Down Expand Up @@ -936,7 +943,7 @@ public Ytdlp WithExtractAudio(AudioFormat format = AudioFormat.Best, int quality
/// <param name="format">(currently supported: avi, flv, gif, mkv, mov, mp4, webm, aac, aiff, alac, flac, m4a, mka, mp3, ogg, opus, vorbis, wav).</param>
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());
}
Expand All @@ -949,7 +956,7 @@ public Ytdlp WithRemuxVideo(string format)
/// <param name="audioCodec"></param>
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))
Expand Down Expand Up @@ -988,18 +995,7 @@ public Ytdlp WithPostprocessorArgs(PostProcessors postprocessor, string args)
/// <summary>
/// Embed subtitles in the video (only for mp4, webm and mkv videos)
/// </summary>
/// <param name="languages"></param>
/// <param name="convertTo"></param>
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");

/// <summary>
/// Embed thumbnail in the video as cover art
Expand Down Expand Up @@ -1056,11 +1052,26 @@ public Ytdlp WithFFmpegLocation(string? ffmpegPath)
return new Ytdlp(this, ffmpegLocation: ffmpegPath);
}

/// <summary>
/// Convert the subtitles to another format
/// </summary>
/// <param name="format">(currently supported: ass, lrc, srt, vtt)</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
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());
}

/// <summary>
/// Convert the thumbnails to another format. You can specify multiple rules using similar WithRemuxVideo().
/// </summary>
/// <param name="format">(currently supported: jpg, png, webp)</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public Ytdlp WithConvertThumbnails(string format = "jpg")
{
// Supported: jpg, png, webp
Expand All @@ -1070,13 +1081,46 @@ public Ytdlp WithConvertThumbnails(string format = "jpg")
return AddOption("--convert-thumbnails", format.Trim().ToLowerInvariant());
}

/// <summary>
/// Split video into multiple files based on internal chapters. The "chapter:" prefix can be used with the output filename for the split files.
/// </summary>
public Ytdlp WithSplitChapters() => AddFlag("--split-chapters");

/// <summary>
/// Remove chapters whose title matches the given regular expression. The syntax is the same as <see cref="WithDownloadSections(string)"/>.
/// This option can be used multiple times to remove multiple sections"/>
/// </summary>
/// <param name="regex"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public Ytdlp WithRemoveChapters(string regex)
{
if (string.IsNullOrWhiteSpace(regex))
throw new ArgumentException("Regex cannot be empty", nameof(regex));
return AddOption("--remove-chapters", regex);
}

/// <summary>
/// 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
/// </summary>
/// <returns></returns>
public Ytdlp WithForceKeyframesAtCuts() => AddFlag("--force-keyframes-at-cuts");

/// <summary>
/// The (case-sensitive) name of plugin postprocessors to be enabled
/// This option can be used multiple times to add different postprocessors
/// </summary>
/// <param name="postProcessor"></param>
/// <param name="postProcessorArgs"></param>
/// <returns></returns>
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
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -1847,6 +1891,10 @@ public async Task DownloadBatchAsync(IEnumerable<string> urls, int maxConcurrenc

#endregion

// ==================================================================================================================
// Internal Helpers and Utilities
// ==================================================================================================================

#region Helpers

// Get probe runner
Expand Down
Loading