@@ -17,157 +17,163 @@ namespace YoutubeExplode.Converter;
1717/// </summary>
1818public static class ConversionExtensions
1919{
20- /// <summary>
21- /// Checks whether the container is a known audio-only container.
22- /// </summary>
23- [ Obsolete ( "Use the Container.IsAudioOnly property instead." ) , ExcludeFromCodeCoverage ]
24- public static bool IsAudioOnly ( this Container container ) => container . IsAudioOnly ;
25-
26- private static async IAsyncEnumerable < IStreamInfo > GetOptimalStreamInfosAsync (
27- this VideoClient videoClient ,
28- VideoId videoId ,
29- Container container ,
30- [ EnumeratorCancellation ] CancellationToken cancellationToken = default
31- )
20+ /// <inheritdoc cref="ConversionExtensions" />
21+ extension ( Container container )
3222 {
33- var streamManifest = await videoClient . Streams . GetManifestAsync ( videoId , cancellationToken ) ;
23+ /// <summary>
24+ /// Checks whether the container is a known audio-only container.
25+ /// </summary>
26+ [ Obsolete ( "Use the Container.IsAudioOnly property instead." ) , ExcludeFromCodeCoverage ]
27+ public bool IsAudioOnly ( ) => container . IsAudioOnly ;
28+ }
3429
35- if (
36- streamManifest . GetAudioOnlyStreams ( ) . Any ( ) && streamManifest . GetVideoOnlyStreams ( ) . Any ( )
30+ /// <inheritdoc cref="ConversionExtensions" />
31+ extension ( VideoClient videoClient )
32+ {
33+ private async IAsyncEnumerable < IStreamInfo > GetOptimalStreamInfosAsync (
34+ VideoId videoId ,
35+ Container container ,
36+ [ EnumeratorCancellation ] CancellationToken cancellationToken = default
3737 )
3838 {
39- // Include audio stream
40- // Priority: transcoding -> bitrate
41- yield return streamManifest
42- . GetAudioOnlyStreams ( )
43- . OrderByDescending ( s => s . Container == container )
44- . ThenByDescending ( s => s . Bitrate )
45- . First ( ) ;
39+ var streamManifest = await videoClient . Streams . GetManifestAsync (
40+ videoId ,
41+ cancellationToken
42+ ) ;
43+
44+ if (
45+ streamManifest . GetAudioOnlyStreams ( ) . Any ( )
46+ && streamManifest . GetVideoOnlyStreams ( ) . Any ( )
47+ )
48+ {
49+ // Include audio stream
50+ // Priority: transcoding -> bitrate
51+ yield return streamManifest
52+ . GetAudioOnlyStreams ( )
53+ . OrderByDescending ( s => s . Container == container )
54+ . ThenByDescending ( s => s . Bitrate )
55+ . First ( ) ;
4656
47- // Include video stream
48- if ( ! container . IsAudioOnly )
57+ // Include video stream
58+ if ( ! container . IsAudioOnly )
59+ {
60+ // Priority: video quality -> transcoding
61+ yield return streamManifest
62+ . GetVideoOnlyStreams ( )
63+ . OrderByDescending ( s => s . VideoQuality )
64+ . ThenByDescending ( s => s . Container == container )
65+ . First ( ) ;
66+ }
67+ }
68+ // Use single muxed stream if adaptive streams are not available
69+ else
4970 {
5071 // Priority: video quality -> transcoding
5172 yield return streamManifest
52- . GetVideoOnlyStreams ( )
73+ . GetMuxedStreams ( )
5374 . OrderByDescending ( s => s . VideoQuality )
5475 . ThenByDescending ( s => s . Container == container )
5576 . First ( ) ;
5677 }
5778 }
58- // Use single muxed stream if adaptive streams are not available
59- else
79+
80+ /// <summary>
81+ /// Downloads the specified media streams and closed captions and processes them into a single file.
82+ /// </summary>
83+ public async ValueTask DownloadAsync (
84+ IReadOnlyList < IStreamInfo > streamInfos ,
85+ IReadOnlyList < ClosedCaptionTrackInfo > closedCaptionTrackInfos ,
86+ ConversionRequest request ,
87+ IProgress < double > ? progress = null ,
88+ CancellationToken cancellationToken = default
89+ )
6090 {
61- // Priority: video quality -> transcoding
62- yield return streamManifest
63- . GetMuxedStreams ( )
64- . OrderByDescending ( s => s . VideoQuality )
65- . ThenByDescending ( s => s . Container == container )
66- . First ( ) ;
91+ var ffmpeg = new FFmpeg ( request . FFmpegCliFilePath , request . EnvironmentVariables ) ;
92+ var converter = new Converter ( videoClient , ffmpeg , request . Preset ) ;
93+
94+ await converter . ProcessAsync (
95+ request . OutputFilePath ,
96+ request . Container ,
97+ streamInfos ,
98+ closedCaptionTrackInfos ,
99+ progress ,
100+ cancellationToken
101+ ) ;
67102 }
68- }
69103
70- /// <summary>
71- /// Downloads the specified media streams and closed captions and processes them into a single file.
72- /// </summary>
73- public static async ValueTask DownloadAsync (
74- this VideoClient videoClient ,
75- IReadOnlyList < IStreamInfo > streamInfos ,
76- IReadOnlyList < ClosedCaptionTrackInfo > closedCaptionTrackInfos ,
77- ConversionRequest request ,
78- IProgress < double > ? progress = null ,
79- CancellationToken cancellationToken = default
80- )
81- {
82- var ffmpeg = new FFmpeg ( request . FFmpegCliFilePath , request . EnvironmentVariables ) ;
83- var converter = new Converter ( videoClient , ffmpeg , request . Preset ) ;
104+ /// <summary>
105+ /// Downloads the specified media streams and processes them into a single file.
106+ /// </summary>
107+ public async ValueTask DownloadAsync (
108+ IReadOnlyList < IStreamInfo > streamInfos ,
109+ ConversionRequest request ,
110+ IProgress < double > ? progress = null ,
111+ CancellationToken cancellationToken = default
112+ ) => await videoClient . DownloadAsync ( streamInfos , [ ] , request , progress , cancellationToken ) ;
84113
85- await converter . ProcessAsync (
86- request . OutputFilePath ,
87- request . Container ,
88- streamInfos ,
89- closedCaptionTrackInfos ,
90- progress ,
91- cancellationToken
92- ) ;
93- }
114+ /// <summary>
115+ /// Resolves the most optimal media streams for the specified video, downloads them,
116+ /// and processes into a single file.
117+ /// </summary>
118+ public async ValueTask DownloadAsync (
119+ VideoId videoId ,
120+ ConversionRequest request ,
121+ IProgress < double > ? progress = null ,
122+ CancellationToken cancellationToken = default
123+ ) =>
124+ await videoClient . DownloadAsync (
125+ await videoClient . GetOptimalStreamInfosAsync (
126+ videoId ,
127+ request . Container ,
128+ cancellationToken
129+ ) ,
130+ request ,
131+ progress ,
132+ cancellationToken
133+ ) ;
134+
135+ /// <summary>
136+ /// Resolves the most optimal media streams for the specified video, downloads them,
137+ /// and processes into a single file.
138+ /// </summary>
139+ /// <remarks>
140+ /// Output container is inferred from the file extension, unless explicitly specified.
141+ /// </remarks>
142+ public async ValueTask DownloadAsync (
143+ VideoId videoId ,
144+ string outputFilePath ,
145+ Action < ConversionRequestBuilder > configure ,
146+ IProgress < double > ? progress = null ,
147+ CancellationToken cancellationToken = default
148+ )
149+ {
150+ var requestBuilder = new ConversionRequestBuilder ( outputFilePath ) ;
151+ configure ( requestBuilder ) ;
152+ var request = requestBuilder . Build ( ) ;
94153
95- /// <summary>
96- /// Downloads the specified media streams and processes them into a single file.
97- /// </summary>
98- public static async ValueTask DownloadAsync (
99- this VideoClient videoClient ,
100- IReadOnlyList < IStreamInfo > streamInfos ,
101- ConversionRequest request ,
102- IProgress < double > ? progress = null ,
103- CancellationToken cancellationToken = default
104- ) => await videoClient . DownloadAsync ( streamInfos , [ ] , request , progress , cancellationToken ) ;
154+ await videoClient . DownloadAsync ( videoId , request , progress , cancellationToken ) ;
155+ }
105156
106- /// <summary>
107- /// Resolves the most optimal media streams for the specified video, downloads them,
108- /// and processes into a single file.
109- /// </summary>
110- public static async ValueTask DownloadAsync (
111- this VideoClient videoClient ,
112- VideoId videoId ,
113- ConversionRequest request ,
114- IProgress < double > ? progress = null ,
115- CancellationToken cancellationToken = default
116- ) =>
117- await videoClient . DownloadAsync (
118- await videoClient . GetOptimalStreamInfosAsync (
157+ /// <summary>
158+ /// Resolves the most optimal media streams for the specified video,
159+ /// downloads them, and processes into a single file.
160+ /// </summary>
161+ /// <remarks>
162+ /// Output container is inferred from the file extension.
163+ /// If none is specified, mp4 is chosen by default.
164+ /// </remarks>
165+ public async ValueTask DownloadAsync (
166+ VideoId videoId ,
167+ string outputFilePath ,
168+ IProgress < double > ? progress = null ,
169+ CancellationToken cancellationToken = default
170+ ) =>
171+ await videoClient . DownloadAsync (
119172 videoId ,
120- request . Container ,
173+ outputFilePath ,
174+ _ => { } ,
175+ progress ,
121176 cancellationToken
122- ) ,
123- request ,
124- progress ,
125- cancellationToken
126- ) ;
127-
128- /// <summary>
129- /// Resolves the most optimal media streams for the specified video, downloads them,
130- /// and processes into a single file.
131- /// </summary>
132- /// <remarks>
133- /// Output container is inferred from the file extension, unless explicitly specified.
134- /// </remarks>
135- public static async ValueTask DownloadAsync (
136- this VideoClient videoClient ,
137- VideoId videoId ,
138- string outputFilePath ,
139- Action < ConversionRequestBuilder > configure ,
140- IProgress < double > ? progress = null ,
141- CancellationToken cancellationToken = default
142- )
143- {
144- var requestBuilder = new ConversionRequestBuilder ( outputFilePath ) ;
145- configure ( requestBuilder ) ;
146- var request = requestBuilder . Build ( ) ;
147-
148- await videoClient . DownloadAsync ( videoId , request , progress , cancellationToken ) ;
177+ ) ;
149178 }
150-
151- /// <summary>
152- /// Resolves the most optimal media streams for the specified video,
153- /// downloads them, and processes into a single file.
154- /// </summary>
155- /// <remarks>
156- /// Output container is inferred from the file extension.
157- /// If none is specified, mp4 is chosen by default.
158- /// </remarks>
159- public static async ValueTask DownloadAsync (
160- this VideoClient videoClient ,
161- VideoId videoId ,
162- string outputFilePath ,
163- IProgress < double > ? progress = null ,
164- CancellationToken cancellationToken = default
165- ) =>
166- await videoClient . DownloadAsync (
167- videoId ,
168- outputFilePath ,
169- _ => { } ,
170- progress ,
171- cancellationToken
172- ) ;
173179}
0 commit comments