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
24 changes: 12 additions & 12 deletions src/Ytdlp.NET.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,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 @@ -164,7 +164,7 @@ private static async Task TestDownloadVideoAsync(Ytdlp ytdlpBase)

var ytdlp = ytdlpBase
.WithFormat("ba/b")
.WithExtractAudio(AudioFormat.Mp3)
//.WithExtractAudio(AudioFormat.Mp3)
.WithConcurrentFragments(8)
.WithHomeFolder("./downloads")
.WithTempFolder("./downloads/temp")
Expand Down
145 changes: 11 additions & 134 deletions src/Ytdlp.NET/Core/DownloadRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,15 @@ public DownloadRunner(ProcessFactory factory, ProgressParser parser, ILogger log
_logger = logger;
}

public async Task RunAsync(string arguments, CancellationToken ct, bool tuneProcess = true)
public async Task RunAsync(string args, CancellationToken ct, bool tuneProcess = true)
{
using var process = _factory.Create(arguments);

using var process = _factory.Create(args);
int completed = 0;

void Complete(bool success, string message)
{
if (Interlocked.Exchange(ref completed, 1) == 0)
{
OnCommandCompleted?.Invoke(this, new CommandCompletedEventArgs(success, message));
}
}

try
Expand All @@ -57,15 +54,11 @@ void Complete(bool success, string message)
while (!ct.IsCancellationRequested)
{
var readTask = reader.ReadLineAsync();

var completedTask = await Task.WhenAny(readTask, Task.Delay(Timeout.Infinite, ct));

if (completedTask != readTask)
break;
if (completedTask != readTask) break;

var line = await readTask;
if (line == null)
break;
if (line == null) break;

try
{
Expand Down Expand Up @@ -95,15 +88,11 @@ void Complete(bool success, string message)
while (!ct.IsCancellationRequested)
{
var readTask = reader.ReadLineAsync();

var completedTask = await Task.WhenAny(readTask, Task.Delay(Timeout.Infinite, ct));

if (completedTask != readTask)
break;
if (completedTask != readTask) break;

var line = await readTask;
if (line == null)
break;
if (line == null) break;

OnError?.Invoke(this, line);
_logger.Log(LogType.Error, line);
Expand All @@ -117,9 +106,7 @@ void Complete(bool success, string message)
{
if (!process.HasExited)
{
_logger.Log(LogType.Info,
"Cancellation requested → killing process tree");

_logger.Log(LogType.Info, "Cancellation requested → killing process tree");
ProcessFactory.SafeKill(process, _logger);
}
});
Expand All @@ -136,12 +123,9 @@ void Complete(bool success, string message)
ProcessFactory.SafeKill(process);

var success = process.ExitCode == 0 && !ct.IsCancellationRequested;

var message = success
? "Completed successfully"
: ct.IsCancellationRequested
? "Cancelled by user"
: $"Failed with exit code {process.ExitCode}";
var message = success ? "Completed successfully"
: ct.IsCancellationRequested ? "Cancelled by user"
: $"Failed with exit code {process.ExitCode}";

Complete(success, message);
}
Expand All @@ -158,112 +142,5 @@ void Complete(bool success, string message)

throw new YtdlpException(msg, ex);
}
}

// Working code - old method
//public async Task RunAsync(string arguments, CancellationToken ct, bool tuneProcess = true)
//{
// using var process = _factory.Create(arguments);

// int completed = 0;

// void Complete(bool success, string message)
// {
// if (Interlocked.Exchange(ref completed, 1) == 0)
// {
// OnCommandCompleted?.Invoke(this, new CommandCompletedEventArgs(success, message));
// }
// }

// try
// {
// var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

// // ✅ Attach BEFORE Start (fix race condition)
// process.Exited += (_, _) => tcs.TrySetResult(true);

// process.OutputDataReceived += (s, e) =>
// {
// if (e.Data == null) return;

// try
// {
// _progressParser.ParseProgress(e.Data);
// OnProgress?.Invoke(this, e.Data);
// }
// catch (Exception ex)
// {
// _logger.Log(LogType.Error, $"Parse error: {ex.Message}");
// }
// };

// process.ErrorDataReceived += (s, e) =>
// {
// if (e.Data == null) return;

// OnErrorMessage?.Invoke(this, e.Data);
// _logger.Log(LogType.Error, e.Data);
// };

// if (!process.Start())
// throw new YtdlpException("Failed to start yt-dlp process.");

// if (tuneProcess)
// ProcessFactory.Tune(process);

// // ✅ Start reading AFTER handlers
// process.BeginOutputReadLine();
// process.BeginErrorReadLine();

// // 🔥 Cancellation
// using var registration = ct.Register(() =>
// {
// if (!process.HasExited)
// {
// _logger.Log(LogType.Info, "Cancellation requested → killing process tree");
// ProcessFactory.SafeKill(process, _logger);
// }
// });

// // Wait for exit OR cancellation
// await Task.WhenAny(tcs.Task, Task.Delay(Timeout.Infinite, ct));

// // Ensure process is dead
// if (!process.HasExited)
// ProcessFactory.SafeKill(process);

// try
// {
// await process.WaitForExitAsync(ct);
// }
// catch (OperationCanceledException)
// {
// if (!process.HasExited)
// ProcessFactory.SafeKill(process);
// }

// var success = process.ExitCode == 0 && !ct.IsCancellationRequested;

// var message = success
// ? "Completed successfully"
// : ct.IsCancellationRequested
// ? "Cancelled by user"
// : $"Failed with exit code {process.ExitCode}";

// Complete(success, message);
// }
// catch (OperationCanceledException)
// {
// Complete(false, "Cancelled by user");
// throw;
// }
// catch (Exception ex)
// {
// var msg = $"Error executing yt-dlp: {ex.Message}";
// _logger.Log(LogType.Error, msg);
// OnErrorMessage?.Invoke(this, msg);

// throw new YtdlpException(msg, ex);
// }
//}
}
}
4 changes: 0 additions & 4 deletions src/Ytdlp.NET/Core/ProbeRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ public sealed class ProbeRunner
private readonly ProcessFactory _factory;
private readonly ILogger _logger;

public event EventHandler<string>? OnOutput; // optional: for live output if needed
public event EventHandler<string>? OnErrorMessage;
public event EventHandler<CommandCompletedEventArgs>? OnCommandCompleted;

Expand All @@ -19,9 +18,6 @@ public ProbeRunner(ProcessFactory factory, ILogger logger)

public async Task<string?> RunAsync(string args, CancellationToken ct = default, bool tuneProcess = true, int bufferKb = 256)
{
if (string.IsNullOrWhiteSpace(args))
throw new ArgumentException("Arguments cannot be empty", nameof(args));

// Reasonable buffer: 256 KB default (good for large JSON), min 64 KB
if (bufferKb < 64) bufferKb = 64;
int bufferSize = bufferKb * 1024;
Expand Down
14 changes: 7 additions & 7 deletions src/Ytdlp.NET/Models/MetadataLight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,37 @@ public class MetadataLight
/// <summary>
/// Video ID (e.g. "Xt50Sodg7sA")
/// </summary>
public string? Id { get; init; }
public string? Id { get; set; }

/// <summary>
/// Video title (supports Unicode / emoji / special characters)
/// </summary>
public string? Title { get; init; }
public string? Title { get; set; }

/// <summary>
/// Video duration in seconds (null if not available)
/// </summary>
public double? Duration { get; init; }
public double? Duration { get; set; }

/// <summary>
/// Primary thumbnail URL
/// </summary>
public string? Thumbnail { get; init; }
public string? Thumbnail { get; set; }

/// <summary>
/// View count (null if not available)
/// </summary>
public long? ViewCount { get; init; }
public long? ViewCount { get; set; }

/// <summary>
/// Approximate file size of best format (bytes, null if not available)
/// </summary>
public long? FileSize { get; init; }
public long? FileSize { get; set; }

/// <summary>
/// Video description (first ~500 characters, supports Unicode)
/// </summary>
public string? Description { get; init; }
public string? Description { get; set; }

/// <summary>
/// Convenience: Duration as TimeSpan
Expand Down
25 changes: 11 additions & 14 deletions src/Ytdlp.NET/Parsing/ProgressParser.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;

namespace ManuHub.Ytdlp.NET;

Expand All @@ -14,8 +13,17 @@
private bool _isDownloadCompleted;
private bool _postProcessingStarted;
private int _postProcessStepCount;
private int _deleteCount;

Check warning on line 16 in src/Ytdlp.NET/Parsing/ProgressParser.cs

View workflow job for this annotation

GitHub Actions / build

The field 'ProgressParser._deleteCount' is assigned but its value is never used

Check warning on line 16 in src/Ytdlp.NET/Parsing/ProgressParser.cs

View workflow job for this annotation

GitHub Actions / build

The field 'ProgressParser._deleteCount' is assigned but its value is never used

Check warning on line 16 in src/Ytdlp.NET/Parsing/ProgressParser.cs

View workflow job for this annotation

GitHub Actions / build

The field 'ProgressParser._deleteCount' is assigned but its value is never used

// ───────────── Events (unchanged) ─────────────
#region Events
public event EventHandler<string>? OnProgressMessage;
public event EventHandler<DownloadProgressEventArgs>? OnProgressDownload;
public event EventHandler<string>? OnCompleteDownload;
public event EventHandler<string>? OnPostProcessingStart;
public event EventHandler<string>? OnPostProcessingComplete;
#endregion

public ProgressParser(ILogger? logger = null)
{
_logger = logger ?? new DefaultLogger();
Expand Down Expand Up @@ -57,8 +65,6 @@
if (string.IsNullOrWhiteSpace(output))
return;

//OnOutputMessage?.Invoke(this, output.TrimEnd());

foreach (var (regex, handler) in _regexHandlers)
{
var match = regex.Match(output);
Expand Down Expand Up @@ -284,14 +290,5 @@
_logger.Log(LogType.Info, message);
OnCompleteDownload?.Invoke(this, message);
}
#endregion

// ───────────── Events (unchanged) ─────────────
#region Events
public event EventHandler<string>? OnProgressMessage;
public event EventHandler<DownloadProgressEventArgs>? OnProgressDownload;
public event EventHandler<string>? OnCompleteDownload;
public event EventHandler<string>? OnPostProcessingStart;
public event EventHandler<string>? OnPostProcessingComplete;
#endregion
#endregion
}
2 changes: 1 addition & 1 deletion src/Ytdlp.NET/Ytdlp.NET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageId>Ytdlp.NET</PackageId>
<AssemblyName>Ytdlp.NET</AssemblyName>
<RootNamespace>ManuHub.Ytdlp.NET</RootNamespace>
<Version>3.0.3</Version>
<Version>3.0.4</Version>
<Authors>ManuHub Manojbabu</Authors>
<PackageDescription>A .NET wrapper for yt-dlp with advanced features like concurrent downloads, SponsorBlock, and improved format parsing</PackageDescription>
<Copyright>© 2025-2026 ManuHub. Allrights researved</Copyright>
Expand Down
Loading