Baseline: WPF/Avalonia/Cli/Core + plans (BROWSER_FEATURES_PLAN v2, MOBILE_PLAN v1)
This commit is contained in:
commit
21b7c4d8bd
60 changed files with 6581 additions and 0 deletions
93
VideoDownloader.Cli/Program.cs
Normal file
93
VideoDownloader.Cli/Program.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using VideoDownloader.Core.Config;
|
||||
using VideoDownloader.Core.Dash;
|
||||
using VideoDownloader.Core.Hls;
|
||||
using VideoDownloader.Core.Models;
|
||||
|
||||
// [임시] 실제 공개 DASH 매니페스트 파싱 검증: 트랙/세그먼트 구조 출력.
|
||||
if (args.Length > 0 && args[0] == "--dashparse")
|
||||
{
|
||||
var mpdUrl = args.Length > 1 ? args[1] : "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd";
|
||||
using var h = new HttpClient();
|
||||
h.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0");
|
||||
var xml = await h.GetStringAsync(mpdUrl);
|
||||
Console.WriteLine($"MPD {xml.Length} bytes");
|
||||
var m = DashParser.Parse(xml, new Uri(mpdUrl));
|
||||
if (m == null) { Console.WriteLine("PARSE NULL"); return 1; }
|
||||
if (m.Video != null) Console.WriteLine($"VIDEO: init={m.Video.InitUrl} segs={m.Video.Segments.Count} first={m.Video.Segments.FirstOrDefault()?.Url}");
|
||||
if (m.Audio != null) Console.WriteLine($"AUDIO: init={m.Audio.InitUrl} segs={m.Audio.Segments.Count} first={m.Audio.Segments.FirstOrDefault()?.Url}");
|
||||
else Console.WriteLine("AUDIO: (없음)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// [임시] 실제 공개 DASH 종단간 다운로드 검증.
|
||||
if (args.Length > 0 && (args[0] == "--dash" || args[0] == "--dashlow"))
|
||||
{
|
||||
var mpdUrl = args.Length > 1 ? args[1] : "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd";
|
||||
var outBase = args.Length > 2 ? args[2] : Path.Combine(Directory.GetCurrentDirectory(), "out", "dash");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outBase)!);
|
||||
var dashConfig = new AppConfig { Concurrency = 8 };
|
||||
using var h = new HttpClient();
|
||||
h.DefaultRequestHeaders.UserAgent.ParseAdd(dashConfig.UserAgent);
|
||||
h.Timeout = TimeSpan.FromMinutes(5);
|
||||
var dl = new DashDownloader(h, dashConfig);
|
||||
var prog = new Progress<DownloadProgress>(p =>
|
||||
{
|
||||
if (p.Stage == "downloading") Console.Write($"\rDASH: {p.Completed}/{p.Total} ({p.Percent,5:F1}%) ");
|
||||
else Console.WriteLine($"\n단계: {p.Stage}");
|
||||
});
|
||||
try
|
||||
{
|
||||
var final = await dl.DownloadAsync(new Uri(mpdUrl), outBase, prog);
|
||||
var fi = new FileInfo(final);
|
||||
Console.WriteLine($"\n완료: {final} ({fi.Length / 1048576.0:F2} MB)");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception e) { Console.WriteLine($"\n오류: {e.GetType().Name}: {e.Message}"); return 1; }
|
||||
}
|
||||
|
||||
// 엔진 하네스: 공개 샘플 HLS 를 받아 파싱→병렬다운로드→병합→ffmpeg 리먹스까지 검증.
|
||||
// 사용: dotnet run --project VideoDownloader.Cli -- <m3u8 URL> [출력경로(확장자빼고)]
|
||||
// 기본 URL = Mux 공개 테스트스트림(Tears of Steel 일부)
|
||||
if (args.Length > 0 && args[0] is "--help" or "-h")
|
||||
{
|
||||
Console.WriteLine("Usage: dotnet run --project VideoDownloader.Cli -- <m3u8-url> [outputBase]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var url = args.Length > 0 ? args[0] : "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8";
|
||||
var outputBase = args.Length > 1
|
||||
? args[1]
|
||||
: Path.Combine(Directory.GetCurrentDirectory(), "out", "sample");
|
||||
|
||||
Console.WriteLine($"대상: {url}");
|
||||
Console.WriteLine($"출력: {outputBase}.mp4");
|
||||
|
||||
var config = new AppConfig { Concurrency = 8 };
|
||||
using var http = new HttpClient();
|
||||
http.DefaultRequestHeaders.UserAgent.ParseAdd(config.UserAgent);
|
||||
http.Timeout = TimeSpan.FromMinutes(3);
|
||||
|
||||
var downloader = new HlsDownloader(http, config);
|
||||
var progress = new Progress<DownloadProgress>(p =>
|
||||
{
|
||||
if (p.Stage == "downloading")
|
||||
Console.Write($"\r다운로드: {p.Completed}/{p.Total} ({p.Percent,5:F1}%) ");
|
||||
else
|
||||
Console.WriteLine($"\n단계: {p.Stage} ...");
|
||||
});
|
||||
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
var final = await downloader.DownloadAsync(new Uri(url), outputBase, progress);
|
||||
sw.Stop();
|
||||
var fi = new FileInfo(final);
|
||||
Console.WriteLine($"\n완료: {final}");
|
||||
Console.WriteLine($"크기: {fi.Length / 1024.0 / 1024.0:F2} MB / 소요: {sw.Elapsed.TotalSeconds:F1}s");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"\n오류: {e.GetType().Name}: {e.Message}");
|
||||
return 1;
|
||||
}
|
||||
14
VideoDownloader.Cli/VideoDownloader.Cli.csproj
Normal file
14
VideoDownloader.Cli/VideoDownloader.Cli.csproj
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VideoDownloader.Core\VideoDownloader.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Add table
Add a link
Reference in a new issue