-
-
Notifications
You must be signed in to change notification settings - Fork 516
/
Copy pathConversionExtensions.cs
173 lines (161 loc) · 6.01 KB
/
ConversionExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using YoutubeExplode.Converter.Utils.Extensions;
using YoutubeExplode.Videos;
using YoutubeExplode.Videos.ClosedCaptions;
using YoutubeExplode.Videos.Streams;
namespace YoutubeExplode.Converter;
/// <summary>
/// Extensions for <see cref="VideoClient" /> that provide interface for downloading videos through FFmpeg.
/// </summary>
public static class ConversionExtensions
{
/// <summary>
/// Checks whether the container is a known audio-only container.
/// </summary>
[Obsolete("Use the Container.IsAudioOnly property instead."), ExcludeFromCodeCoverage]
public static bool IsAudioOnly(this Container container) => container.IsAudioOnly;
private static async IAsyncEnumerable<IStreamInfo> GetOptimalStreamInfosAsync(
this VideoClient videoClient,
VideoId videoId,
Container container,
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{
var streamManifest = await videoClient.Streams.GetManifestAsync(videoId, cancellationToken);
if (
streamManifest.GetAudioOnlyStreams().Any() && streamManifest.GetVideoOnlyStreams().Any()
)
{
// Include audio stream
// Priority: transcoding -> bitrate
yield return streamManifest
.GetAudioOnlyStreams()
.OrderByDescending(s => s.Container == container)
.ThenByDescending(s => s.Bitrate)
.First();
// Include video stream
if (!container.IsAudioOnly)
{
// Priority: video quality -> transcoding
yield return streamManifest
.GetVideoOnlyStreams()
.OrderByDescending(s => s.VideoQuality)
.ThenByDescending(s => s.Container == container)
.First();
}
}
// Use single muxed stream if adaptive streams are not available
else
{
// Priority: video quality -> transcoding
yield return streamManifest
.GetMuxedStreams()
.OrderByDescending(s => s.VideoQuality)
.ThenByDescending(s => s.Container == container)
.First();
}
}
/// <summary>
/// Downloads the specified media streams and closed captions and processes them into a single file.
/// </summary>
public static async ValueTask DownloadAsync(
this VideoClient videoClient,
IReadOnlyList<IStreamInfo> streamInfos,
IReadOnlyList<ClosedCaptionTrackInfo> closedCaptionTrackInfos,
ConversionRequest request,
IProgress<double>? progress = null,
CancellationToken cancellationToken = default
)
{
var ffmpeg = new FFmpeg(request.FFmpegCliFilePath);
var converter = new Converter(videoClient, ffmpeg, request.Preset);
await converter.ProcessAsync(
request.OutputFilePath,
request.Container,
streamInfos,
closedCaptionTrackInfos,
progress,
cancellationToken
);
}
/// <summary>
/// Downloads the specified media streams and processes them into a single file.
/// </summary>
public static async ValueTask DownloadAsync(
this VideoClient videoClient,
IReadOnlyList<IStreamInfo> streamInfos,
ConversionRequest request,
IProgress<double>? progress = null,
CancellationToken cancellationToken = default
) => await videoClient.DownloadAsync(streamInfos, [], request, progress, cancellationToken);
/// <summary>
/// Resolves the most optimal media streams for the specified video, downloads them,
/// and processes into a single file.
/// </summary>
public static async ValueTask DownloadAsync(
this VideoClient videoClient,
VideoId videoId,
ConversionRequest request,
IProgress<double>? progress = null,
CancellationToken cancellationToken = default
) =>
await videoClient.DownloadAsync(
await videoClient.GetOptimalStreamInfosAsync(
videoId,
request.Container,
cancellationToken
),
request,
progress,
cancellationToken
);
/// <summary>
/// Resolves the most optimal media streams for the specified video, downloads them,
/// and processes into a single file.
/// </summary>
/// <remarks>
/// Output container is inferred from the file extension, unless explicitly specified.
/// </remarks>
public static async ValueTask DownloadAsync(
this VideoClient videoClient,
VideoId videoId,
string outputFilePath,
Action<ConversionRequestBuilder> configure,
IProgress<double>? progress = null,
CancellationToken cancellationToken = default
)
{
var requestBuilder = new ConversionRequestBuilder(outputFilePath);
configure(requestBuilder);
var request = requestBuilder.Build();
await videoClient.DownloadAsync(videoId, request, progress, cancellationToken);
}
/// <summary>
/// Resolves the most optimal media streams for the specified video,
/// downloads them, and processes into a single file.
/// </summary>
/// <remarks>
/// Output container is inferred from the file extension.
/// If none is specified, mp4 is chosen by default.
/// </remarks>
public static async ValueTask DownloadAsync(
this VideoClient videoClient,
VideoId videoId,
string outputFilePath,
IProgress<double>? progress = null,
CancellationToken cancellationToken = default
) =>
await videoClient.DownloadAsync(
videoId,
outputFilePath,
_ => { },
progress,
cancellationToken
);
}