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
1 change: 1 addition & 0 deletions src/Ytdlp.NET.Console/Helpers/MenuRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public static string ShowMainMenu()
WriteItem("8", "Benchmarks");
WriteItem("9", "Extractors List");
WriteItem("10", "Subtitle List");
WriteItem("11", "Live Stream Download");
WriteItem("0", "Exit");

Console.WriteLine();
Expand Down
1 change: 0 additions & 1 deletion src/Ytdlp.NET.Console/Pages/DownloadVideoPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,3 @@ public static async Task ShowAsync(Ytdlp baseYtdlp)
Console.ReadKey();
}
}

50 changes: 50 additions & 0 deletions src/Ytdlp.NET.Console/Pages/LivetreamDownloadPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using ManuHub.Ytdlp.NET;
using YtdlpNetConsoleApp.Helpers;

namespace YtdlpNetConsoleApp.Pages;

internal static class LivetreamDownloadPage
{
public static async Task ShowAsync(Ytdlp baseYtdlp)
{
Console.Clear();
ConsoleTheme.WriteSection("Livestream Downloader");

var url = ConsoleExtensions.ReadInput("Enter livestream URL");

if (string.IsNullOrWhiteSpace(url))
return;

var ytdlp = baseYtdlp
.WithFormat("bv+ba/b")
.WithLiveFromStart()
.WithFFmpegAsLiveDownloader()
.WithOutputTemplate("%(title)s.%(ext)s")
.WithOutputFolder("./livestreams")
.WithConcurrentFragments(8)
.WithEmbedMetadata()
.WithEmbedThumbnail();

var dashboard = new ProgressDashboard();
ytdlp.ProgressDownload += (_, p) =>
{
dashboard.Update(p.Percent, p.Speed, p.ETA, p.Size);
};
ytdlp.OutputMessage += (_, msg) =>
{
Console.WriteLine(msg);
};
ytdlp.CommandCompleted += (_, result) =>
{
Console.WriteLine();
ConsoleExtensions.WriteLineColor(result.Success ? "SUCCESS" : "FAILED", result.Success ? ConsoleColor.Green : ConsoleColor.Red
);
};
Console.WriteLine("\nStarting download...\n");
Console.WriteLine(ytdlp.Preview(url));
await ytdlp.DownloadAsync(url);
dashboard.Complete();
Console.WriteLine("\nPress any key...");
Console.ReadKey();
}
}
4 changes: 4 additions & 0 deletions src/Ytdlp.NET.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ static async Task Main(string[] args)
await SubtitlePage.ShowAsync(_ytdlp!);
break;

case "11":
await LivetreamDownloadPage.ShowAsync(_ytdlp!);
break;

case "0":
return;
}
Expand Down
6 changes: 6 additions & 0 deletions src/Ytdlp.NET/Events/DownloadProgressEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public class DownloadProgressEventArgs : EventArgs
/// </summary>
public string ETA { get; set; } = default!;

/// <summary>
/// Gets or sets the elapsed time since the download started.
/// Especially useful for livestreams where ETA may not be applicable.
/// </summary>
public string Elapsed { get; set; } = default!;

/// <summary>
/// Gets or sets the number of fragments downloaded (if applicable).
/// </summary>
Expand Down
35 changes: 35 additions & 0 deletions src/Ytdlp.NET/Parsing/ProgressParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public ProgressParser(ILogger? logger = null)
{ new Regex(RegexPatterns.ResumeDownload, compiledIgnoreCase), HandleResumeDownload },
{ new Regex(RegexPatterns.DownloadAlreadyDownloaded, compiledIgnoreCase), HandleDownloadAlreadyCompleted },


// Live / no-total-size progress (livestreams)
// Matches both:
// [download] 8.36MiB at 169.71KiB/s (00:01:07) (frag 244)
// 2: [download] 8.36MiB at 169.71KiB/s (00:01:07) (frag 244)
{ new Regex(RegexPatterns.DownloadProgressLive, compiledIgnoreCase), HandleDownloadProgressLive },

// Progress
{ new Regex(RegexPatterns.DownloadProgressComplete, compiledIgnoreCase), HandleDownloadProgressComplete },
{ new Regex(RegexPatterns.DownloadProgressWithFrag, compiledIgnoreCase), HandleDownloadProgressWithFrag },
Expand All @@ -68,6 +75,7 @@ public ProgressParser(ILogger? logger = null)
// Optional: add playlist awareness
{ new Regex(RegexPatterns.PlaylistItem, compiledIgnoreCase), m =>
LogAndNotify(LogType.Information, $"Playlist progress: item {m.Groups["item"].Value}/{m.Groups["total"].Value} - {m.Groups["playlist"].Value}") },

};
}

Expand Down Expand Up @@ -190,6 +198,33 @@ private void HandleDownloadProgressWithFrag(Match match)
}
}

private void HandleDownloadProgressLive(Match match)
{
string sizeString = match.Groups["size"].Value;
string speedString = match.Groups["speed"].Value;
string elapsedString = match.Groups["elapsed"].Value;
string fragString = match.Groups["frag"].Success ? match.Groups["frag"].Value : string.Empty;

var args = new DownloadProgressEventArgs
{
// No reliable percent for live streams
Percent = 0,
Size = sizeString, // bytes downloaded so far
Speed = speedString,
ETA = null, // or leave empty – we only have elapsed
Elapsed = elapsedString,
Fragments = fragString,
Message = string.IsNullOrEmpty(fragString)
? $"Live: {sizeString} | {speedString} | elapsed {elapsedString}"
: $"Live: {sizeString} | {speedString} | elapsed {elapsedString} | frag {fragString}"
};

LogAndNotify(LogType.Information, args.Message);
ProgressDownload?.Invoke(this, args);

// Never mark download as completed from a live progress line
}

private static bool IsFinalFragment(string frag)
{
if (string.IsNullOrEmpty(frag) || !frag.Contains('/')) return true;
Expand Down
5 changes: 5 additions & 0 deletions src/Ytdlp.NET/Parsing/RegexPatterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ internal static class RegexPatterns
public const string DownloadProgress = @"\[download\]\s+(?:(?<percent>[\d\.]+)%(?:\s+of\s+\~?\s*(?<total>[\d\.\w]+))?\s+at\s+(?:(?<speed>[\d\.\w]+\/s)|[\w\s]+)\s+ETA\s(?<eta>[\d\:]+))?";
//@"\[download\]\s*(?<percent>\d+\.\d+)%\s*of\s*(?<size>[^\s]+)\s*at\s*(?<speed>[^\s]+)\s*ETA\s*(?<eta>[^\s]+)";
public const string DownloadProgressWithFrag = @"\[download\]\s*(?<percent>\d+\.\d+)%\s*of\s*(~?\s*(?<size>[^\s]+))\s*at\s*(?<speed>[^\s]+)\s*ETA\s*(?<eta>[^\s]+)\s*\(frag\s*(?<frag>\d+/\d+)\)";

// Live progress – no percent / total size
// Groups: size, speed, elapsed, frag (optional)
public const string DownloadProgressLive =
@"^(?:\d+:\s*)?\[download\]\s+(?<size>[\d.]+\s*[KMGT]?i?B)\s+at\s+(?<speed>[\d.]+\s*[KMGT]?i?B/s)\s+\((?<elapsed>[\d:]+)\)(?:\s+\(frag\s+(?<frag>\d+(?:/\d+)?)\))?";
public const string DownloadProgressComplete = @"\[download\]\s*(?<percent>100(?:\.0)?)%\s*of\s*(?<size>[^\s]+)\s*at\s*(?<speed>[^\s]+|Unknown)\s*ETA\s*(?<eta>[^\s]+|Unknown)";
public const string UnknownError = @"\[download\]\s*Unknown error";
public const string MergingFormats = @"\[Merger\]\s*Merging formats into\s*""(?<path>.+)""";
Expand Down
2 changes: 1 addition & 1 deletion src/Ytdlp.NET/Ytdlp.NET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<BuildYear>$([System.DateTime]::UtcNow.Year)</BuildYear>
<BuildDay>$([System.DateTime]::UtcNow.DayOfYear)</BuildDay>

<Version>4.0.0</Version>
<Version>4.3.0</Version>
<FileVersion>$(Version)</FileVersion>

<InformationalVersion>$(Version)+build.$(BuildYear).$(BuildDay)</InformationalVersion>
Expand Down
Loading