private IDictionary <string, string> GetVideoInfoAsync(string videoId, string sts = "") { // Get video info var raw = GetVideoInfoRawAsync(videoId, "embedded", sts); //.ConfigureAwait(false); var videoInfo = UrlEx.SplitQuery(raw); // If can't be embedded - try another value of el if (videoInfo.ContainsKey("errorcode")) { var errorReason = videoInfo["reason"]; if (errorReason.Contains("&feature=player_embedded")) { raw = GetVideoInfoRawAsync(videoId, "detailpage", sts); //.ConfigureAwait(false); videoInfo = UrlEx.SplitQuery(raw); } } // Check error if (videoInfo.ContainsKey("errorcode")) { var errorCode = videoInfo["errorcode"].ParseInt(); var errorReason = videoInfo["reason"]; //throw new VideoUnavailableException(videoId, errorCode, errorReason); return(null); } return(videoInfo); }
private async Task <IReadOnlyDictionary <string, string> > GetVideoInfoAsync(string videoId, string sts = "") { // Get video info with 'el=embedded' var raw = await GetVideoInfoRawAsync(videoId, "embedded", sts).ConfigureAwait(false); var videoInfo = UrlEx.SplitQuery(raw); // If there is no error - return if (!videoInfo.ContainsKey("errorcode")) { return(videoInfo); } // Get video info with 'el=detailpage' raw = await GetVideoInfoRawAsync(videoId, "detailpage", sts).ConfigureAwait(false); videoInfo = UrlEx.SplitQuery(raw); // If there is no error - return if (!videoInfo.ContainsKey("errorcode")) { return(videoInfo); } // If there is error - throw var errorCode = videoInfo["errorcode"].ParseInt(); var errorReason = videoInfo["reason"]; throw new VideoUnavailableException(videoId, errorCode, errorReason); }
public string ParseDescription() { var buffer = new StringBuilder(); var descriptionNode = _root.QuerySelector("p#eow-description"); var childNodes = descriptionNode.ChildNodes; foreach (var childNode in childNodes) { // If it's a text node - display text content if (childNode.NodeType == NodeType.Text) { buffer.Append(childNode.TextContent); } // If it's an anchor node - perform some special transformation else if (childNode is IHtmlAnchorElement anchorNode) { // If the link appears shortened - get full link if (anchorNode.TextContent.EndsWith("...", StringComparison.OrdinalIgnoreCase)) { // Get href var href = anchorNode.GetAttribute("href"); // If it's a relative link that goes through YouTube redirect - extract the actual link if (href.StartsWith("/redirect", StringComparison.OrdinalIgnoreCase)) { // Get query parameters var queryParams = UrlEx.SplitQuery(anchorNode.Search); // Get the actual href href = queryParams["q"]; } // If it's a relative link - prepend YouTube's host else if (href.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { // Prepend host to the link to make it absolute href = "https://youtube.com" + anchorNode.GetAttribute("href"); } buffer.Append(href); } // Otherwise - just use its inner text else { buffer.Append(anchorNode.TextContent); } } // If it's a break row node - append new line else if (childNode is IHtmlBreakRowElement) { buffer.AppendLine(); } } return(buffer.ToString()); }
private async Task <IReadOnlyDictionary <string, string> > GetVideoInfoAsync(string videoId, string sts = "") { var raw = await GetVideoInfoRawAsync(videoId, "embedded", sts).ConfigureAwait(false); var videoInfo = UrlEx.SplitQuery(raw); // Check if there is an error if (videoInfo.ContainsKey("errorcode")) { var errorCode = videoInfo["errorcode"].ParseInt(); var errorReason = videoInfo["reason"]; throw new VideoUnavailableException(videoId, errorCode, errorReason); } return(videoInfo); }
public string ParseDescription() { var buffer = new StringBuilder(); var descriptionNode = _root.QuerySelector("p#eow-description"); var childNodes = descriptionNode.ChildNodes; foreach (var childNode in childNodes) { if (childNode.NodeType == NodeType.Text) { buffer.Append(childNode.TextContent); } else if (childNode is IHtmlAnchorElement anchorNode) { // If it uses YouTube redirect - get the actual link if (anchorNode.PathName.Equals("/redirect", StringComparison.OrdinalIgnoreCase)) { // Get query parameters var queryParams = UrlEx.SplitQuery(anchorNode.Search); // Get the actual href var actualHref = queryParams["q"].UrlDecode(); buffer.Append(actualHref); } else { buffer.Append(anchorNode.TextContent); } } else if (childNode is IHtmlBreakRowElement) { buffer.AppendLine(); } } return(buffer.ToString()); }
/// <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)); }
private async Task <IReadOnlyDictionary <string, string> > GetVideoInfoAsync(string videoId, string el, string sts) { var raw = await GetVideoInfoRawAsync(videoId, el, sts).ConfigureAwait(false); return(UrlEx.SplitQuery(raw)); }