/// <summary> /// Selects best media streams for the specified video, downloads them, and processes into a single file. /// </summary> public static async Task DownloadAsync( this VideoClient videoClient, VideoId videoId, ConversionRequest request, IProgress <double>?progress = null, CancellationToken cancellationToken = default) { var streamManifest = await videoClient.Streams.GetManifestAsync(videoId); var streamInfos = GetBestMediaStreamInfos(streamManifest, request.Format).ToArray(); await videoClient.DownloadAsync( streamInfos, request, progress, cancellationToken ); }
/// <summary> /// Downloads 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) { Platform.EnsureDesktop(); // Ensure that the provided stream collection is not empty if (!streamInfos.Any()) { throw new InvalidOperationException("No streams provided."); } // If all streams have the same container as the output format, then transcoding is not required var isTranscodingRequired = streamInfos.Any(s => IsTranscodingRequired(s.Container, request.Format)); // Progress setup var progressMixer = progress?.Pipe(p => new ProgressMixer(p)); var downloadProgressPortion = isTranscodingRequired ? 0.15 : 0.99; var totalStreamSize = streamInfos.Sum(s => s.Size.Bytes); // Temp files for streams var streamFilePaths = new List <string>(streamInfos.Count); try { // Download streams foreach (var streamInfo in streamInfos) { var streamIndex = streamFilePaths.Count + 1; var streamFilePath = $"{request.OutputFilePath}.stream-{streamIndex}.tmp"; streamFilePaths.Add(streamFilePath); var streamDownloadProgress = progressMixer?.Split( downloadProgressPortion * streamInfo.Size.Bytes / totalStreamSize ); await videoClient.Streams.DownloadAsync( streamInfo, streamFilePath, streamDownloadProgress, cancellationToken ).ConfigureAwait(false); } // Mux/convert streams var conversionProgress = progressMixer?.Split(1 - downloadProgressPortion); await new FFmpeg(request.FFmpegCliFilePath).ExecuteAsync( streamFilePaths, request.OutputFilePath, request.Format.Name, request.Preset.ToString().ToLowerInvariant(), isTranscodingRequired, conversionProgress, cancellationToken ).ConfigureAwait(false); progress?.Report(1); } finally { // Delete temp files foreach (var streamFilePath in streamFilePaths) { try { File.Delete(streamFilePath); } catch { // Try our best but don't crash } } } }