ow
This commit is contained in:
1 parent
90bb20e75f
commit
bcf82d0f45
8 files changed
+342
-37
No files matched your search
@@ -10,7 +10,8 @@ namespace WinFormsApp3
|
||||
/// <param name="Url">可直接 HTTP 访问的视频地址;PlaylistText 不为空时作为相对地址基准。</param>
|
||||
/// <param name="PlaylistText">只在内存里拿到 m3u8 文本时使用(抖音这类 MSE 播放)。</param>
|
||||
/// <param name="Referer">发出这个请求的文档地址;跨域 iframe 播放器必须用它,不然会被防盗链拒掉。</param>
|
||||
public record VideoCandidate(string Url, string? PlaylistText = null, string? Referer = null);
|
||||
/// <param name="AudioUrl">DASH 分离音轨地址;不为空时需要和 Url 一起下载后用 ffmpeg 合并。</param>
|
||||
public record VideoCandidate(string Url, string? PlaylistText = null, string? Referer = null, string? AudioUrl = null);
|
||||
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
@@ -503,16 +504,19 @@ namespace WinFormsApp3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只取当前正在播放的那一个视频地址,按三层递降:
|
||||
/// 只取当前正在播放的那一个视频地址,按四层递降:
|
||||
/// 1. video 标签自己的播放源(最准确,就是正在播的这个);
|
||||
/// 2. 抖音这类 blob+MSE 播放、或播放器在 iframe 里取不到源时,
|
||||
/// 退回用本页最近嗅探到的那一个整片地址(m3u8 或直链,分片不算);
|
||||
/// 3. 只在内存里拿到播放列表文本时,用页面地址做分片相对地址的基准。
|
||||
/// 2. DASH 分离轨(抖音这类 blob+MSE):视频轨 + 音频轨配对,下载后合并;
|
||||
/// 3. 播放器在 iframe 里取不到源时,退回用本页最近嗅探到的那一个整片地址(m3u8 或直链,分片不算);
|
||||
/// 4. 只在内存里拿到播放列表文本时,用页面地址做分片相对地址的基准。
|
||||
/// </summary>
|
||||
private VideoCandidate? CollectCurrentCandidate(string grabResultJson)
|
||||
{
|
||||
string current = string.Empty;
|
||||
string pageUrl = string.Empty;
|
||||
string dashVideo = string.Empty;
|
||||
string dashAudio = string.Empty;
|
||||
string videoId = string.Empty;
|
||||
List<string> sniffed = new List<string>();
|
||||
|
||||
try
|
||||
@@ -524,6 +528,9 @@ namespace WinFormsApp3
|
||||
JsonNode? node = JsonNode.Parse(inner);
|
||||
current = node?["current"]?.GetValue<string>() ?? string.Empty;
|
||||
pageUrl = node?["pageUrl"]?.GetValue<string>() ?? string.Empty;
|
||||
dashVideo = node?["dashVideo"]?.GetValue<string>() ?? string.Empty;
|
||||
dashAudio = node?["dashAudio"]?.GetValue<string>() ?? string.Empty;
|
||||
videoId = node?["videoId"]?.GetValue<string>() ?? string.Empty;
|
||||
|
||||
if (node?["sniffed"] is JsonArray arr)
|
||||
{
|
||||
@@ -540,23 +547,44 @@ namespace WinFormsApp3
|
||||
// 页面没有返回可解析结果时,只用宿主侧嗅探到的地址
|
||||
}
|
||||
|
||||
// 第一层:video 标签自己的播放源
|
||||
if (VideoDownloader.LooksLikeMedia(current) && !VideoDownloader.IsSegmentUrl(current))
|
||||
{
|
||||
return new VideoCandidate(current, null, pageUrl);
|
||||
}
|
||||
|
||||
// 嗅探结果里最后一个整片地址最接近“正在播的这个”,前面翻页留下的旧地址和单个分片都不取
|
||||
// 第二层:DASH 分离轨配对(抖音)—— JS 端已按 videoId 过滤
|
||||
if (!string.IsNullOrEmpty(dashVideo) && VideoDownloader.LooksLikeMedia(dashVideo))
|
||||
{
|
||||
return new VideoCandidate(dashVideo, null, RefererFor(dashVideo, pageUrl),
|
||||
string.IsNullOrEmpty(dashAudio) ? null : dashAudio);
|
||||
}
|
||||
|
||||
// 第三层:从嗅探结果里找当前视频的地址
|
||||
List<string> pool = sniffed.Concat(SnapshotSniffedUrls())
|
||||
.Where(VideoDownloader.LooksLikeMedia)
|
||||
.Where(u => !VideoDownloader.IsDashAudioOnly(u)) // 纯音频轨不能单独当视频下
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
int index = pool.FindLastIndex(u => !VideoDownloader.IsSegmentUrl(u));
|
||||
int index;
|
||||
if (!string.IsNullOrEmpty(videoId))
|
||||
{
|
||||
// 有视频 ID 时,优先找 URL 里包含该 ID 的整片地址
|
||||
index = pool.FindLastIndex(u => !VideoDownloader.IsSegmentUrl(u) && u.Contains(videoId));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有 ID 就退回最后一个整片地址
|
||||
index = pool.FindLastIndex(u => !VideoDownloader.IsSegmentUrl(u));
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
string url = pool[index];
|
||||
return new VideoCandidate(url, null, RefererFor(url, pageUrl));
|
||||
// 看看能不能从嗅探记录里配到同组音频轨
|
||||
string? pairedAudio = FindPairedAudio(url, videoId);
|
||||
return new VideoCandidate(url, null, RefererFor(url, pageUrl), pairedAudio);
|
||||
}
|
||||
|
||||
string? text = sniffedPlaylists.Concat(ReadBlobPlaylists(grabResultJson))
|
||||
@@ -566,6 +594,34 @@ namespace WinFormsApp3
|
||||
return text == null ? null : new VideoCandidate(pageUrl, text, pageUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从已嗅探地址里找和视频轨同组的 DASH 音频轨。
|
||||
/// 有 videoId 时优先选 URL 里包含该 ID 的音频轨,否则取最后一条 media-audio。
|
||||
/// </summary>
|
||||
private string? FindPairedAudio(string videoUrl, string videoId = "")
|
||||
{
|
||||
lock (sniffedUrls)
|
||||
{
|
||||
// 有视频 ID 时,优先找同 ID 的音频轨
|
||||
if (!string.IsNullOrEmpty(videoId))
|
||||
{
|
||||
for (int i = sniffedUrls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (VideoDownloader.IsDashAudioOnly(sniffedUrls[i]) &&
|
||||
sniffedUrls[i].Contains(videoId))
|
||||
return sniffedUrls[i];
|
||||
}
|
||||
}
|
||||
// 退回:从后往前找最后一条音频轨
|
||||
for (int i = sniffedUrls.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (VideoDownloader.IsDashAudioOnly(sniffedUrls[i]))
|
||||
return sniffedUrls[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<string> ReadBlobPlaylists(string grabResultJson)
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
@@ -634,9 +690,19 @@ namespace WinFormsApp3
|
||||
|
||||
try
|
||||
{
|
||||
DownloadResult result = candidate.PlaylistText != null
|
||||
? await downloader.DownloadFromPlaylistTextAsync(candidate.PlaylistText, candidate.Url, filePath, progress, downloadCts.Token)
|
||||
: await downloader.DownloadAsync(candidate.Url, filePath, progress, downloadCts.Token);
|
||||
DownloadResult result;
|
||||
if (candidate.PlaylistText != null)
|
||||
{
|
||||
result = await downloader.DownloadFromPlaylistTextAsync(candidate.PlaylistText, candidate.Url, filePath, progress, downloadCts.Token);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(candidate.AudioUrl))
|
||||
{
|
||||
result = await downloader.DownloadDashPairAsync(candidate.Url, candidate.AudioUrl, filePath, progress, downloadCts.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = await downloader.DownloadAsync(candidate.Url, filePath, progress, downloadCts.Token);
|
||||
}
|
||||
|
||||
SetStatus($"已下载 {Path.GetFileName(result.FilePath)}({VideoDownloader.FormatSize(result.Bytes)})");
|
||||
|
||||
|
||||
Reference in new issue
Block a user