155 lines
6.2 KiB
C#
Raw Permalink Normal View History

2022-04-10 03:38:39 +03:00
using System;
using System.Collections.Generic;
using System.Linq;
using YoutubeDownloader.Core.Utils.Extensions;
using YoutubeExplode.Videos.Streams;
namespace YoutubeDownloader.Core.Downloading;
public partial record VideoDownloadOption(
Container Container,
bool IsAudioOnly,
2023-10-21 17:58:59 +03:00
IReadOnlyList<IStreamInfo> StreamInfos
)
2022-04-10 03:38:39 +03:00
{
2024-04-24 23:37:55 +03:00
public VideoQuality? VideoQuality { get; } =
StreamInfos.OfType<IVideoStreamInfo>().MaxBy(s => s.VideoQuality)?.VideoQuality;
2022-04-10 03:38:39 +03:00
}
public partial record VideoDownloadOption
{
internal static IReadOnlyList<VideoDownloadOption> ResolveAll(
StreamManifest manifest,
bool includeLanguageSpecificAudioStreams = true
)
2022-04-10 03:38:39 +03:00
{
IEnumerable<VideoDownloadOption> GetVideoAndAudioOptions()
{
2023-05-19 03:14:23 +03:00
var videoStreamInfos = manifest
2022-04-10 03:38:39 +03:00
.GetVideoStreams()
.OrderByDescending(v => v.VideoQuality);
2023-05-19 03:14:23 +03:00
foreach (var videoStreamInfo in videoStreamInfos)
2022-04-10 03:38:39 +03:00
{
// Muxed stream
if (videoStreamInfo is MuxedStreamInfo)
{
yield return new VideoDownloadOption(
videoStreamInfo.Container,
false,
2024-01-11 22:45:37 +02:00
[videoStreamInfo]
2022-04-10 03:38:39 +03:00
);
}
// Separate audio + video stream
else
{
var audioStreamInfos = manifest
2022-04-10 03:38:39 +03:00
.GetAudioStreams()
// Prefer audio streams with the same container
2022-04-10 03:38:39 +03:00
.OrderByDescending(s => s.Container == videoStreamInfo.Container)
.ThenByDescending(s => s is AudioOnlyStreamInfo)
.ThenByDescending(s => s.Bitrate)
.ToArray();
2022-04-10 03:38:39 +03:00
// Prefer language-specific audio streams, if available and if allowed
var languageSpecificAudioStreamInfos = includeLanguageSpecificAudioStreams
? audioStreamInfos
.Where(s => s.AudioLanguage is not null)
.DistinctBy(s => s.AudioLanguage)
// Default language first so it's encoded as the first audio track in the output file
.OrderByDescending(s => s.IsAudioLanguageDefault)
.ToArray()
: [];
// If there are language-specific streams, include them all
if (languageSpecificAudioStreamInfos.Any())
2022-04-10 03:38:39 +03:00
{
yield return new VideoDownloadOption(
videoStreamInfo.Container,
false,
[videoStreamInfo, .. languageSpecificAudioStreamInfos]
2022-04-10 03:38:39 +03:00
);
}
// If there are no language-specific streams, download the single best quality audio stream
else
{
var audioStreamInfo = audioStreamInfos
// Prefer audio streams in the default language (or non-language-specific streams)
.OrderByDescending(s => s.IsAudioLanguageDefault ?? true)
.FirstOrDefault();
if (audioStreamInfo is not null)
{
yield return new VideoDownloadOption(
videoStreamInfo.Container,
false,
[videoStreamInfo, audioStreamInfo]
);
}
}
2022-04-10 03:38:39 +03:00
}
}
}
IEnumerable<VideoDownloadOption> GetAudioOnlyOptions()
{
// WebM-based audio-only containers
{
var audioStreamInfo = manifest
.GetAudioStreams()
// Prefer audio streams in the default language (or non-language-specific streams)
.OrderByDescending(s => s.IsAudioLanguageDefault ?? true)
// Prefer audio streams with the same container
.ThenByDescending(s => s.Container == Container.WebM)
2022-04-10 03:38:39 +03:00
.ThenByDescending(s => s is AudioOnlyStreamInfo)
.ThenByDescending(s => s.Bitrate)
.FirstOrDefault();
if (audioStreamInfo is not null)
{
2024-01-11 22:45:37 +02:00
yield return new VideoDownloadOption(Container.WebM, true, [audioStreamInfo]);
2024-01-11 22:45:37 +02:00
yield return new VideoDownloadOption(Container.Mp3, true, [audioStreamInfo]);
yield return new VideoDownloadOption(
new Container("ogg"),
true,
2024-01-11 22:45:37 +02:00
[audioStreamInfo]
);
2022-04-10 03:38:39 +03:00
}
}
// Mp4-based audio-only containers
{
var audioStreamInfo = manifest
.GetAudioStreams()
// Prefer audio streams in the default language (or non-language-specific streams)
.OrderByDescending(s => s.IsAudioLanguageDefault ?? true)
// Prefer audio streams with the same container
.ThenByDescending(s => s.Container == Container.Mp4)
2022-04-10 03:38:39 +03:00
.ThenByDescending(s => s is AudioOnlyStreamInfo)
.ThenByDescending(s => s.Bitrate)
.FirstOrDefault();
if (audioStreamInfo is not null)
{
2024-01-11 22:45:37 +02:00
yield return new VideoDownloadOption(Container.Mp4, true, [audioStreamInfo]);
2022-04-10 03:38:39 +03:00
}
}
}
// Deduplicate download options by video quality and container
2023-11-14 20:15:05 +02:00
var comparer = EqualityComparer<VideoDownloadOption>.Create(
(x, y) => x?.VideoQuality == y?.VideoQuality && x?.Container == y?.Container,
2022-04-10 03:38:39 +03:00
x => HashCode.Combine(x.VideoQuality, x.Container)
);
var options = new HashSet<VideoDownloadOption>(comparer);
options.AddRange(GetVideoAndAudioOptions());
options.AddRange(GetAudioOnlyOptions());
return options.ToArray();
}
2023-10-21 17:58:59 +03:00
}