private async Task ResolveAdaptiveStreamInfosAsync(PlayerContext context, string encodedData, ICollection <AudioStreamInfo> audioStreamInfos, ICollection <VideoStreamInfo> videoStreamInfos) { foreach (var streamEncoded in encodedData.Split(",")) { var streamInfoDic = UrlHelper.GetDictionaryFromUrlQuery(streamEncoded); var itag = streamInfoDic.Get("itag").ParseInt(); var url = streamInfoDic.Get("url"); var sig = streamInfoDic.GetOrDefault("s"); var contentLength = streamInfoDic.Get("clen").ParseLong(); var bitrate = streamInfoDic.Get("bitrate").ParseLong(); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetPlayerSourceAsync(context.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlHelper.SetUrlQueryParameter(url, "signature", sig); } // Set rate bypass url = UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes"); // Check if audio var isAudio = streamInfoDic.Get("type").Contains("audio/"); // If audio stream if (isAudio) { var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreamInfos.Add(streamInfo); } // If video stream else { // Parse additional data var size = streamInfoDic.Get("size"); var width = size.SubstringUntil("x").ParseInt(); var height = size.SubstringAfter("x").ParseInt(); var resolution = new VideoResolution(width, height); var framerate = streamInfoDic.Get("fps").ParseInt(); var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreamInfos.Add(streamInfo); } } }
private async Task ResolveMuxedStreamInfosAsync(PlayerContext context, string encodedData, ICollection <MuxedStreamInfo> streamInfos) { foreach (var streamEncoded in encodedData.Split(",")) { var streamInfoDic = UrlHelper.GetDictionaryFromUrlQuery(streamEncoded); var itag = streamInfoDic.Get("itag").ParseInt(); var url = streamInfoDic.Get("url"); var sig = streamInfoDic.GetOrDefault("s"); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetPlayerSourceAsync(context.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlHelper.SetUrlQueryParameter(url, "signature", sig); } // Probe stream and get content length long contentLength; using (var request = new HttpRequestMessage(HttpMethod.Head, url)) using (var response = await _httpService.PerformRequestAsync(request).ConfigureAwait(false)) { // Some muxed streams can be gone if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Gone) { continue; } // Ensure success response.EnsureSuccessStatusCode(); // Extract content length contentLength = response.Content.Headers.ContentLength ?? throw new ParseException("Could not extract content length"); } // Set rate bypass url = UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes"); var streamInfo = new MuxedStreamInfo(itag, url, contentLength); streamInfos.Add(streamInfo); } }
/// <inheritdoc /> public async Task <MediaStreamInfoSet> GetVideoMediaStreamInfosAsync(string videoId) { videoId.GuardNotNull(nameof(videoId)); if (!ValidateVideoId(videoId)) { throw new ArgumentException($"Invalid YouTube video ID [{videoId}].", nameof(videoId)); } // Get player context var playerContext = await GetVideoPlayerContextAsync(videoId).ConfigureAwait(false); // Get video info var videoInfo = await GetVideoInfoAsync(videoId, playerContext.Sts).ConfigureAwait(false); // Check if requires purchase if (videoInfo.ContainsKey("ypc_vid")) { var previewVideoId = videoInfo["ypc_vid"]; throw new VideoRequiresPurchaseException(videoId, previewVideoId); } // Prepare stream info collections var muxedStreamInfoMap = new Dictionary <int, MuxedStreamInfo>(); var audioStreamInfoMap = new Dictionary <int, AudioStreamInfo>(); var videoStreamInfoMap = new Dictionary <int, VideoStreamInfo>(); // Resolve muxed streams var muxedStreamInfosEncoded = videoInfo.GetOrDefault("url_encoded_fmt_stream_map"); if (muxedStreamInfosEncoded.IsNotBlank()) { foreach (var streamEncoded in muxedStreamInfosEncoded.Split(",")) { var streamInfoDic = UrlEx.SplitQuery(streamEncoded); // Extract values var itag = streamInfoDic["itag"].ParseInt(); var url = streamInfoDic["url"]; var sig = streamInfoDic.GetOrDefault("s"); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlEx.SetQueryParameter(url, "signature", sig); } // Probe stream and get content length var contentLength = await _httpClient.GetContentLengthAsync(url, false).ConfigureAwait(false) ?? -1; // If probe failed, the stream is gone or faulty if (contentLength < 0) { continue; } var streamInfo = new MuxedStreamInfo(itag, url, contentLength); muxedStreamInfoMap[itag] = streamInfo; } } // Resolve adaptive streams var adaptiveStreamInfosEncoded = videoInfo.GetOrDefault("adaptive_fmts"); if (adaptiveStreamInfosEncoded.IsNotBlank()) { foreach (var streamEncoded in adaptiveStreamInfosEncoded.Split(",")) { var streamInfoDic = UrlEx.SplitQuery(streamEncoded); // Extract values var itag = streamInfoDic["itag"].ParseInt(); var url = streamInfoDic["url"]; var sig = streamInfoDic.GetOrDefault("s"); var contentLength = streamInfoDic["clen"].ParseLong(); var bitrate = streamInfoDic["bitrate"].ParseLong(); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlEx.SetQueryParameter(url, "signature", sig); } // Check if audio var isAudio = streamInfoDic["type"].Contains("audio/"); // If audio stream if (isAudio) { var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreamInfoMap[itag] = streamInfo; } // If video stream else { // Parse additional data var size = streamInfoDic["size"]; var width = size.SubstringUntil("x").ParseInt(); var height = size.SubstringAfter("x").ParseInt(); var resolution = new VideoResolution(width, height); var framerate = streamInfoDic["fps"].ParseInt(); var qualityLabel = streamInfoDic["quality_label"]; var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate, qualityLabel); videoStreamInfoMap[itag] = streamInfo; } } } // Resolve dash streams var dashManifestUrl = videoInfo.GetOrDefault("dashmpd"); if (dashManifestUrl.IsNotBlank()) { // Parse signature var sig = Regex.Match(dashManifestUrl, @"/s/(.*?)(?:/|$)").Groups[1].Value; // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); dashManifestUrl = UrlEx.SetRouteParameter(dashManifestUrl, "signature", sig); } // Get the manifest var response = await _httpClient.GetStringAsync(dashManifestUrl).ConfigureAwait(false); var dashManifestXml = XElement.Parse(response).StripNamespaces(); var streamsXml = dashManifestXml.Descendants("Representation"); // Parse streams foreach (var streamXml in streamsXml) { // Skip partial streams if (streamXml.Descendants("Initialization").FirstOrDefault()?.Attribute("sourceURL")?.Value .Contains("sq/") == true) { continue; } // Extract values var itag = (int)streamXml.Attribute("id"); var url = (string)streamXml.Element("BaseURL"); var bitrate = (long)streamXml.Attribute("bandwidth"); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Parse content length var contentLength = Regex.Match(url, @"clen[/=](\d+)").Groups[1].Value.ParseLong(); // Check if audio stream var isAudio = streamXml.Element("AudioChannelConfiguration") != null; // If audio stream if (isAudio) { var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreamInfoMap[itag] = streamInfo; } // If video stream else { // Parse additional data var width = (int)streamXml.Attribute("width"); var height = (int)streamXml.Attribute("height"); var resolution = new VideoResolution(width, height); var framerate = (int)streamXml.Attribute("frameRate"); var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreamInfoMap[itag] = streamInfo; } } } // Get the raw HLS stream playlist (*.m3u8) var hlsLiveStreamUrl = videoInfo.GetOrDefault("hlsvp"); // Finalize stream info collections var muxedStreamInfos = muxedStreamInfoMap.Values.OrderByDescending(s => s.VideoQuality).ToArray(); var audioStreamInfos = audioStreamInfoMap.Values.OrderByDescending(s => s.Bitrate).ToArray(); var videoStreamInfos = videoStreamInfoMap.Values.OrderByDescending(s => s.VideoQuality).ToArray(); return(new MediaStreamInfoSet(muxedStreamInfos, audioStreamInfos, videoStreamInfos, hlsLiveStreamUrl)); }
/// <summary> /// Gets video info by ID /// </summary> public async Task <VideoInfo> GetVideoInfoAsync(string videoId) { if (videoId == null) { throw new ArgumentNullException(nameof(videoId)); } if (!ValidateVideoId(videoId)) { throw new ArgumentException("Invalid Youtube video ID", nameof(videoId)); } // Get player context var playerContext = await GetPlayerContextAsync(videoId).ConfigureAwait(false); // Get video info string request = $"https://www.youtube.com/get_video_info?video_id={videoId}&sts={playerContext.Sts}&el=info&ps=default&hl=en"; string response = await _httpService.GetStringAsync(request).ConfigureAwait(false); var videoInfoDic = UrlHelper.GetDictionaryFromUrlQuery(response); // Check for error if (videoInfoDic.ContainsKey("errorcode")) { int errorCode = videoInfoDic.Get("errorcode").ParseInt(); string errorReason = videoInfoDic.GetOrDefault("reason"); throw new VideoNotAvailableException(errorCode, errorReason); } // Check for paid content if (videoInfoDic.GetOrDefault("requires_purchase") == "1") { throw new VideoRequiresPurchaseException(); } // Parse metadata string title = videoInfoDic.Get("title"); var duration = TimeSpan.FromSeconds(videoInfoDic.Get("length_seconds").ParseDouble()); long viewCount = videoInfoDic.Get("view_count").ParseLong(); var keywords = videoInfoDic.Get("keywords").Split(","); var watermarks = videoInfoDic.Get("watermark").Split(","); bool isListed = videoInfoDic.Get("is_listed") == "1"; bool isRatingAllowed = videoInfoDic.Get("allow_ratings") == "1"; bool isMuted = videoInfoDic.Get("muted") == "1"; bool isEmbeddingAllowed = videoInfoDic.Get("allow_embed") == "1"; // Parse mixed streams var mixedStreams = new List <MixedStreamInfo>(); string mixedStreamsEncoded = videoInfoDic.GetOrDefault("url_encoded_fmt_stream_map"); if (mixedStreamsEncoded.IsNotBlank()) { foreach (string streamEncoded in mixedStreamsEncoded.Split(",")) { var streamDic = UrlHelper.GetDictionaryFromUrlQuery(streamEncoded); int itag = streamDic.Get("itag").ParseInt(); #if RELEASE // Skip unknown itags on RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif string url = streamDic.Get("url"); string sig = streamDic.GetOrDefault("s"); // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetPlayerSourceAsync(playerContext.Version).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlHelper.SetUrlQueryParameter(url, "signature", sig); } // Get content length long contentLength; using (var reqMsg = new HttpRequestMessage(HttpMethod.Head, url)) using (var resMsg = await _httpService.PerformRequestAsync(reqMsg).ConfigureAwait(false)) { // Check status code (https://github.com/Tyrrrz/YoutubeExplode/issues/36) if (resMsg.StatusCode == HttpStatusCode.NotFound || resMsg.StatusCode == HttpStatusCode.Gone) { continue; } // Ensure success resMsg.EnsureSuccessStatusCode(); // Extract content length contentLength = resMsg.Content.Headers.ContentLength ?? -1; if (contentLength < 0) { throw new ParseException("Could not extract content length"); } } // Set rate bypass url = UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes"); var stream = new MixedStreamInfo(itag, url, contentLength); mixedStreams.Add(stream); } } // Parse adaptive streams var audioStreams = new List <AudioStreamInfo>(); var videoStreams = new List <VideoStreamInfo>(); string adaptiveStreamsEncoded = videoInfoDic.GetOrDefault("adaptive_fmts"); if (adaptiveStreamsEncoded.IsNotBlank()) { foreach (string streamEncoded in adaptiveStreamsEncoded.Split(",")) { var streamDic = UrlHelper.GetDictionaryFromUrlQuery(streamEncoded); int itag = streamDic.Get("itag").ParseInt(); #if RELEASE // Skip unknown itags on RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif string url = streamDic.Get("url"); string sig = streamDic.GetOrDefault("s"); long contentLength = streamDic.Get("clen").ParseLong(); long bitrate = streamDic.Get("bitrate").ParseLong(); // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetPlayerSourceAsync(playerContext.Version).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlHelper.SetUrlQueryParameter(url, "signature", sig); } // Set rate bypass url = UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes"); // Check if audio bool isAudio = streamDic.Get("type").Contains("audio/"); // If audio stream if (isAudio) { var stream = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreams.Add(stream); } // If video stream else { // Parse additional data string size = streamDic.Get("size"); int width = size.SubstringUntil("x").ParseInt(); int height = size.SubstringAfter("x").ParseInt(); var resolution = new VideoResolution(width, height); double framerate = streamDic.Get("fps").ParseDouble(); var stream = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreams.Add(stream); } } } // Parse adaptive streams from dash string dashManifestUrl = videoInfoDic.GetOrDefault("dashmpd"); if (dashManifestUrl.IsNotBlank()) { // Parse signature string sig = Regex.Match(dashManifestUrl, @"/s/(.*?)(?:/|$)").Groups[1].Value; // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetPlayerSourceAsync(playerContext.Version).ConfigureAwait(false); sig = playerSource.Decipher(sig); dashManifestUrl = UrlHelper.SetUrlPathParameter(dashManifestUrl, "signature", sig); } // Get the manifest response = await _httpService.GetStringAsync(dashManifestUrl).ConfigureAwait(false); var dashManifestXml = XElement.Parse(response).StripNamespaces(); var streamsXml = dashManifestXml.Descendants("Representation"); // Filter out partial streams streamsXml = streamsXml .Where(x => !(x.Descendant("Initialization") ?.Attribute("sourceURL") ?.Value.Contains("sq/") ?? false)); // Parse streams foreach (var streamXml in streamsXml) { int itag = (int)streamXml.AttributeStrict("id"); #if RELEASE // Skip unknown itags on RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif string url = streamXml.ElementStrict("BaseURL").Value; long bitrate = (long)streamXml.AttributeStrict("bandwidth"); // Parse content length long contentLength = Regex.Match(url, @"clen[/=](\d+)").Groups[1].Value.ParseLong(); // Set rate bypass url = url.Contains("&") ? UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes") : UrlHelper.SetUrlPathParameter(url, "ratebypass", "yes"); // Check if audio stream bool isAudio = streamXml.Element("AudioChannelConfiguration") != null; // If audio stream if (isAudio) { var stream = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreams.Add(stream); } // If video stream else { // Parse additional data int width = (int)streamXml.AttributeStrict("width"); int height = (int)streamXml.AttributeStrict("height"); var resolution = new VideoResolution(width, height); double framerate = (double)streamXml.AttributeStrict("frameRate"); var stream = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreams.Add(stream); } } } // Finalize stream lists mixedStreams = mixedStreams.Distinct(s => s.Itag).OrderByDescending(s => s.VideoQuality).ToList(); audioStreams = audioStreams.Distinct(s => s.Itag).OrderByDescending(s => s.Bitrate).ToList(); videoStreams = videoStreams.Distinct(s => s.Itag).OrderByDescending(s => s.VideoQuality).ToList(); // Parse closed caption tracks var captions = new List <ClosedCaptionTrackInfo>(); string captionsEncoded = videoInfoDic.GetOrDefault("caption_tracks"); if (captionsEncoded.IsNotBlank()) { foreach (string captionEncoded in captionsEncoded.Split(",")) { var captionDic = UrlHelper.GetDictionaryFromUrlQuery(captionEncoded); string url = captionDic.Get("u"); bool isAuto = captionDic.Get("v").Contains("a."); string code = captionDic.Get("lc"); string name = captionDic.Get("n"); var language = new Language(code, name); var caption = new ClosedCaptionTrackInfo(url, language, isAuto); captions.Add(caption); } } // Get metadata extension request = $"https://www.youtube.com/get_video_metadata?video_id={videoId}"; response = await _httpService.GetStringAsync(request).ConfigureAwait(false); var videoInfoExtXml = XElement.Parse(response).StripNamespaces().ElementStrict("html_content"); // Parse string description = videoInfoExtXml.ElementStrict("video_info").ElementStrict("description").Value; long likeCount = (long)videoInfoExtXml.ElementStrict("video_info").ElementStrict("likes_count_unformatted"); long dislikeCount = (long)videoInfoExtXml.ElementStrict("video_info").ElementStrict("dislikes_count_unformatted"); // Parse author info string authorId = videoInfoExtXml.ElementStrict("user_info").ElementStrict("channel_external_id").Value; string authorName = videoInfoExtXml.ElementStrict("user_info").ElementStrict("username").Value; string authorTitle = videoInfoExtXml.ElementStrict("user_info").ElementStrict("channel_title").Value; bool authorIsPaid = videoInfoExtXml.ElementStrict("user_info").ElementStrict("channel_paid").Value == "1"; string authorLogoUrl = videoInfoExtXml.ElementStrict("user_info").ElementStrict("channel_logo_url").Value; string authorBannerUrl = videoInfoExtXml.ElementStrict("user_info").ElementStrict("channel_banner_url").Value; var author = new ChannelInfo( authorId, authorName, authorTitle, authorIsPaid, authorLogoUrl, authorBannerUrl); return(new VideoInfo( videoId, title, author, duration, description, keywords, watermarks, viewCount, likeCount, dislikeCount, isListed, isRatingAllowed, isMuted, isEmbeddingAllowed, mixedStreams, audioStreams, videoStreams, captions)); }
/// <summary> /// Gets video info by ID /// </summary> public async Task <VideoInfo> GetVideoInfoAsync(string videoId) { if (videoId == null) { throw new ArgumentNullException(nameof(videoId)); } if (!ValidateVideoId(videoId)) { throw new ArgumentException("Invalid Youtube video ID", nameof(videoId)); } // Get player context var playerContext = await GetPlayerContextAsync(videoId).ConfigureAwait(false); // Get video info var request = $"https://www.youtube.com/get_video_info?video_id={videoId}&sts={playerContext.Sts}&el=info&ps=default&hl=en"; var response = await _httpService.GetStringAsync(request).ConfigureAwait(false); var videoInfoDic = UrlHelper.GetDictionaryFromUrlQuery(response); // Check for error if (videoInfoDic.ContainsKey("errorcode")) { var errorCode = videoInfoDic.Get("errorcode").ParseInt(); var errorReason = videoInfoDic.Get("reason"); throw new VideoNotAvailableException(errorCode, errorReason); } // Check for paid content if (videoInfoDic.GetOrDefault("requires_purchase") == "1") { var previewVideoId = videoInfoDic.Get("ypc_vid"); throw new VideoRequiresPurchaseException(previewVideoId); } // Parse metadata var title = videoInfoDic.Get("title"); var duration = TimeSpan.FromSeconds(videoInfoDic.Get("length_seconds").ParseDouble()); var viewCount = videoInfoDic.Get("view_count").ParseLong(); var keywords = videoInfoDic.Get("keywords").Split(","); var watermarks = videoInfoDic.Get("watermark").Split(","); var isListed = videoInfoDic.GetOrDefault("is_listed") == "1"; // https://github.com/Tyrrrz/YoutubeExplode/issues/45 var isRatingAllowed = videoInfoDic.Get("allow_ratings") == "1"; var isMuted = videoInfoDic.Get("muted") == "1"; var isEmbeddingAllowed = videoInfoDic.Get("allow_embed") == "1"; // Parse adaptive streams var audioStreams = new List <AudioStreamInfo>(); var videoStreams = new List <VideoStreamInfo>(); var adaptiveStreamsEncoded = videoInfoDic.GetOrDefault("adaptive_fmts"); if (adaptiveStreamsEncoded.IsNotBlank()) { foreach (var streamEncoded in adaptiveStreamsEncoded.Split(",")) { var streamDic = UrlHelper.GetDictionaryFromUrlQuery(streamEncoded); var itag = streamDic.Get("itag").ParseInt(); #if RELEASE // Skip unknown itags on RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif var url = streamDic.Get("url"); var sig = streamDic.GetOrDefault("s"); var contentLength = streamDic.Get("clen").ParseLong(); var bitrate = streamDic.Get("bitrate").ParseLong(); // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlHelper.SetUrlQueryParameter(url, "signature", sig); } // Set rate bypass url = UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes"); // Check if audio var isAudio = streamDic.Get("type").Contains("audio/"); // If audio stream if (isAudio) { var stream = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreams.Add(stream); } // If video stream else { // Parse additional data var size = streamDic.Get("size"); var width = size.SubstringUntil("x").ParseInt(); var height = size.SubstringAfter("x").ParseInt(); var resolution = new VideoResolution(width, height); var framerate = streamDic.Get("fps").ParseDouble(); var stream = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreams.Add(stream); } } } // Parse adaptive streams from dash var dashManifestUrl = videoInfoDic.GetOrDefault("dashmpd"); if (dashManifestUrl.IsNotBlank()) { // Parse signature var sig = Regex.Match(dashManifestUrl, @"/s/(.*?)(?:/|$)").Groups[1].Value; // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); dashManifestUrl = UrlHelper.SetUrlPathParameter(dashManifestUrl, "signature", sig); } // Get the manifest response = await _httpService.GetStringAsync(dashManifestUrl).ConfigureAwait(false); var dashManifestXml = XElement.Parse(response).StripNamespaces(); var streamsXml = dashManifestXml.Descendants("Representation"); // Filter out partial streams streamsXml = streamsXml .Where(x => !(x.Descendant("Initialization") ?.Attribute("sourceURL") ?.Value.Contains("sq/") ?? false)); // Parse streams foreach (var streamXml in streamsXml) { var itag = (int)streamXml.AttributeStrict("id"); #if RELEASE // Skip unknown itags on RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif var url = streamXml.ElementStrict("BaseURL").Value; var bitrate = (long)streamXml.AttributeStrict("bandwidth"); // Parse content length var contentLength = Regex.Match(url, @"clen[/=](\d+)").Groups[1].Value.ParseLong(); // Set rate bypass url = url.Contains("&") ? UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes") : UrlHelper.SetUrlPathParameter(url, "ratebypass", "yes"); // Check if audio stream var isAudio = streamXml.Element("AudioChannelConfiguration") != null; // If audio stream if (isAudio) { var stream = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreams.Add(stream); } // If video stream else { // Parse additional data var width = (int)streamXml.AttributeStrict("width"); var height = (int)streamXml.AttributeStrict("height"); var resolution = new VideoResolution(width, height); var framerate = (double)streamXml.AttributeStrict("frameRate"); var stream = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreams.Add(stream); } } } // Parse live stream var hlsvpManifestUrl = videoInfoDic.GetOrDefault("hlsvp"); if (hlsvpManifestUrl.IsNotBlank()) { } audioStreams = audioStreams.Distinct(s => s.Itag).OrderByDescending(s => s.Bitrate).ToList(); videoStreams = videoStreams.Distinct(s => s.Itag).OrderByDescending(s => s.VideoQuality).ToList(); return(new VideoInfo( videoId, title, duration, keywords, watermarks, viewCount, isListed, isRatingAllowed, isMuted, isEmbeddingAllowed, audioStreams, videoStreams, hlsvpManifestUrl)); }
/// <summary> /// Gets a set of all available media stream infos for given video. /// </summary> public async Task <MediaStreamInfoSet> GetVideoMediaStreamInfosAsync(string videoId) { videoId.GuardNotNull(nameof(videoId)); if (!ValidateVideoId(videoId)) { throw new ArgumentException($"Invalid YouTube video ID [{videoId}].", nameof(videoId)); } // Get player context var playerContext = await GetVideoPlayerContextAsync(videoId).ConfigureAwait(false); // Get video info var videoInfo = await GetVideoInfoAsync(videoId, playerContext.Sts).ConfigureAwait(false); // Check if requires purchase if (videoInfo.ContainsKey("ypc_vid")) { var previewVideoId = videoInfo["ypc_vid"]; throw new VideoRequiresPurchaseException(videoId, previewVideoId); } // Prepare stream info collections var muxedStreamInfos = new List <MuxedStreamInfo>(); var audioStreamInfos = new List <AudioStreamInfo>(); var videoStreamInfos = new List <VideoStreamInfo>(); // Resolve muxed streams var muxedStreamInfosEncoded = videoInfo.GetOrDefault("url_encoded_fmt_stream_map"); if (muxedStreamInfosEncoded.IsNotBlank()) { foreach (var streamEncoded in muxedStreamInfosEncoded.Split(",")) { var streamInfoDic = UrlHelper.SplitUrlQuery(streamEncoded); // Extract values var itag = streamInfoDic["itag"].ParseInt(); var url = streamInfoDic["url"]; var sig = streamInfoDic.GetOrDefault("s"); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlHelper.SetUrlQueryParameter(url, "signature", sig); } // Set rate bypass url = UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes"); // Probe stream and get content length long contentLength; using (var request = new HttpRequestMessage(HttpMethod.Head, url)) using (var response = await _httpService.PerformRequestAsync(request).ConfigureAwait(false)) { // Some muxed streams can be gone if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Gone) { continue; } // Ensure success response.EnsureSuccessStatusCode(); // Extract content length contentLength = response.Content.Headers.ContentLength ?? throw new ParseException("Could not extract content length of muxed stream."); } var streamInfo = new MuxedStreamInfo(itag, url, contentLength); muxedStreamInfos.Add(streamInfo); } } // Resolve adaptive streams var adaptiveStreamInfosEncoded = videoInfo.GetOrDefault("adaptive_fmts"); if (adaptiveStreamInfosEncoded.IsNotBlank()) { foreach (var streamEncoded in adaptiveStreamInfosEncoded.Split(",")) { var streamInfoDic = UrlHelper.SplitUrlQuery(streamEncoded); // Extract values var itag = streamInfoDic["itag"].ParseInt(); var url = streamInfoDic["url"]; var sig = streamInfoDic.GetOrDefault("s"); var contentLength = streamInfoDic["clen"].ParseLong(); var bitrate = streamInfoDic["bitrate"].ParseLong(); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); url = UrlHelper.SetUrlQueryParameter(url, "signature", sig); } // Set rate bypass url = UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes"); // Check if audio var isAudio = streamInfoDic["type"].Contains("audio/"); // If audio stream if (isAudio) { var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreamInfos.Add(streamInfo); } // If video stream else { // Parse additional data var size = streamInfoDic["size"]; var width = size.SubstringUntil("x").ParseInt(); var height = size.SubstringAfter("x").ParseInt(); var resolution = new VideoResolution(width, height); var framerate = streamInfoDic["fps"].ParseInt(); var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreamInfos.Add(streamInfo); } } } // Resolve dash streams var dashManifestUrl = videoInfo.GetOrDefault("dashmpd"); if (dashManifestUrl.IsNotBlank()) { // Parse signature var sig = Regex.Match(dashManifestUrl, @"/s/(.*?)(?:/|$)").Groups[1].Value; // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); dashManifestUrl = UrlHelper.SetUrlRouteParameter(dashManifestUrl, "signature", sig); } // Get the manifest var response = await _httpService.GetStringAsync(dashManifestUrl).ConfigureAwait(false); var dashManifestXml = XElement.Parse(response).StripNamespaces(); var streamsXml = dashManifestXml.Descendants("Representation"); // Parse streams foreach (var streamXml in streamsXml) { // Skip partial streams if (streamXml.Descendants("Initialization").FirstOrDefault()?.Attribute("sourceURL")?.Value .Contains("sq/") == true) { continue; } // Extract values var itag = (int)streamXml.Attribute("id"); var url = (string)streamXml.Element("BaseURL"); var bitrate = (long)streamXml.Attribute("bandwidth"); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Parse content length var contentLength = Regex.Match(url, @"clen[/=](\d+)").Groups[1].Value.ParseLong(); // Set rate bypass url = url.Contains("?") ? UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes") : UrlHelper.SetUrlRouteParameter(url, "ratebypass", "yes"); // Check if audio stream var isAudio = streamXml.Element("AudioChannelConfiguration") != null; // If audio stream if (isAudio) { var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreamInfos.Add(streamInfo); } // If video stream else { // Parse additional data var width = (int)streamXml.Attribute("width"); var height = (int)streamXml.Attribute("height"); var resolution = new VideoResolution(width, height); var framerate = (int)streamXml.Attribute("frameRate"); var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreamInfos.Add(streamInfo); } } } // Get the raw HLS stream playlist (*.m3u8) var hlsLiveStreamUrl = videoInfo.GetOrDefault("hlsvp"); // Finalize stream info collections muxedStreamInfos = muxedStreamInfos.Distinct(s => s.Itag).OrderByDescending(s => s.VideoQuality).ToList(); audioStreamInfos = audioStreamInfos.Distinct(s => s.Itag).OrderByDescending(s => s.Bitrate).ToList(); videoStreamInfos = videoStreamInfos.Distinct(s => s.Itag).OrderByDescending(s => s.VideoQuality).ToList(); return(new MediaStreamInfoSet(muxedStreamInfos, audioStreamInfos, videoStreamInfos, hlsLiveStreamUrl)); }
private async Task ResolveDashStreamInfosAsync(PlayerContext context, string dashManifestUrl, ICollection <AudioStreamInfo> audioStreamInfos, ICollection <VideoStreamInfo> videoStreamInfos) { // Parse signature var sig = Regex.Match(dashManifestUrl, @"/s/(.*?)(?:/|$)").Groups[1].Value; // Decipher signature if needed if (sig.IsNotBlank()) { var playerSource = await GetPlayerSourceAsync(context.SourceUrl).ConfigureAwait(false); sig = playerSource.Decipher(sig); dashManifestUrl = UrlHelper.SetUrlPathParameter(dashManifestUrl, "signature", sig); } // Get the manifest var response = await _httpService.GetStringAsync(dashManifestUrl).ConfigureAwait(false); var dashManifestXml = XElement.Parse(response).StripNamespaces(); var streamsXml = dashManifestXml.Descendants("Representation"); // Filter out partial streams streamsXml = streamsXml.Where(x => !(x.Descendant("Initialization") ?.Attribute("sourceURL") ?.Value.Contains("sq/") ?? false)); // Parse streams foreach (var streamXml in streamsXml) { var itag = (int)streamXml.AttributeStrict("id"); var url = streamXml.ElementStrict("BaseURL").Value; var bitrate = (long)streamXml.AttributeStrict("bandwidth"); #if RELEASE if (!MediaStreamInfo.IsKnown(itag)) { continue; } #endif // Parse content length var contentLength = Regex.Match(url, @"clen[/=](\d+)").Groups[1].Value.ParseLong(); // Set rate bypass url = url.Contains("?") ? UrlHelper.SetUrlQueryParameter(url, "ratebypass", "yes") : UrlHelper.SetUrlPathParameter(url, "ratebypass", "yes"); // Check if audio stream var isAudio = streamXml.Element("AudioChannelConfiguration") != null; // If audio stream if (isAudio) { var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate); audioStreamInfos.Add(streamInfo); } // If video stream else { // Parse additional data var width = (int)streamXml.AttributeStrict("width"); var height = (int)streamXml.AttributeStrict("height"); var resolution = new VideoResolution(width, height); var framerate = (int)streamXml.AttributeStrict("frameRate"); var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate); videoStreamInfos.Add(streamInfo); } } }