using System.Diagnostics;
using System.Globalization;
using VideoDownloader.Core.Config;
using VideoDownloader.Core.Models;
namespace VideoDownloader.Core.Dash;
///
/// DASH(MPD) 다운로더. MPD 를 사전 파싱해 (a) DRM 여부 검증, (b) 진행률용 총 길이 산출한 뒤
/// ffmpeg 에게 네이티브 DASH 입력(-i mpd)으로 맡김 — 비디오/오디오 표현 선택·조합·fMP4 처리를
/// ffmpeg가 알아서 하므로 수동 세그먼트 병합보다 훨씬 견고. 클리어(비DRM) 전용.
///
public class DashDownloader
{
private readonly HttpClient _http;
private readonly AppConfig _config;
private readonly Platform.IMediaMuxer _muxer;
public DashDownloader(HttpClient http, AppConfig config) : this(http, config, Platform.DesktopMediaMuxer.Default) { }
public DashDownloader(HttpClient http, AppConfig config, Platform.IMediaMuxer muxer) { _http = http; _config = config; _muxer = muxer; }
public async Task DownloadAsync(Uri mpdUrl, string outputBase,
IProgress? progress = null, CancellationToken ct = default)
{
string? dir = Path.GetDirectoryName(outputBase);
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
// 사전 검증: 파싱(또는 DRM) + 총 길이.
double durationSec = 0;
try
{
var xml = await _http.GetStringAsync(mpdUrl, ct);
var manifest = DashParser.Parse(xml, mpdUrl);
if (manifest == null)
throw new InvalidOperationException("DASH 매니페스트 파싱 실패 (DRM 암호화이거나 미지원 형식)");
durationSec = manifest.DurationSec;
}
catch (OperationCanceledException) { throw; }
catch (InvalidOperationException) { throw; }
catch (Exception e)
{
throw new InvalidOperationException($"MPD 조회/파싱 실패: {e.Message}", e);
}
var ffmpeg = _muxer.ResolveFfmpeg(_config)
?? throw new InvalidOperationException("DASH 다운로드에는 ffmpeg가 필요합니다 (시스템 PATH 또는 설정의 ffmpeg 경로).");
var final = outputBase + ".mp4";
progress?.Report(new DownloadProgress(0, 0, "downloading", 0, "DASH 준비"));
bool success = false;
try
{
await _muxer.DownloadDashAsync(ffmpeg, mpdUrl, final, durationSec, _http, progress, ct);
if (!File.Exists(final) || new FileInfo(final).Length == 0)
throw new InvalidOperationException("ffmpeg 출력 파일이 비었거나 없습니다.");
success = true;
progress?.Report(new DownloadProgress(0, 0, "downloading", 100, null));
return final;
}
finally
{
if (!success) { try { if (File.Exists(final)) File.Delete(final); } catch { } }
}
}
}