Esempio n. 1
0
        /// <inheritdoc />
        public async Task <IReadOnlyList <ClosedCaptionTrackInfo> > GetVideoClosedCaptionTrackInfosAsync(string videoId)
        {
            videoId.GuardNotNull(nameof(videoId));

            if (!ValidateVideoId(videoId))
            {
                throw new ArgumentException($"Invalid YouTube video ID [{videoId}].", nameof(videoId));
            }

            // Get parser
            var parser = await GetPlayerResponseParserAsync(videoId);

            // Parse closed caption track infos
            var closedCaptionTrackInfos = new List <ClosedCaptionTrackInfo>();

            foreach (var closedCaptionTrackInfoParser in parser.GetClosedCaptionTrackInfos())
            {
                // Parse info
                var url             = closedCaptionTrackInfoParser.ParseUrl();
                var isAutoGenerated = closedCaptionTrackInfoParser.ParseIsAutoGenerated();

                // Parse language
                var code     = closedCaptionTrackInfoParser.ParseLanguageCode();
                var name     = closedCaptionTrackInfoParser.ParseLanguageName();
                var language = new Language(code, name);

                // Enforce format to the one we know how to parse
                url = UrlEx.SetQueryParameter(url, "format", "3");

                var closedCaptionTrackInfo = new ClosedCaptionTrackInfo(url, language, isAutoGenerated);
                closedCaptionTrackInfos.Add(closedCaptionTrackInfo);
            }

            return(closedCaptionTrackInfos);
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
        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);
        }
Esempio n. 4
0
        public void PluginRawPageUrl_PluginId_ReturnsUrlEncodedPathToPlugin(string pluginId, string expectedPluginUrl)
        {
            // Arrange & Act
            var actualPluginUrl = UrlEx.PluginRawPageUrl(pluginId);

            // Assert
            Assert.AreEqual(expectedPluginUrl, actualPluginUrl);
        }
        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());
        }
Esempio n. 6
0
        /// <inheritdoc />
        public List <ClosedCaptionTrackInfo> GetVideoClosedCaptionTrackInfosAsync(string videoId)
        {
            videoId.GuardNotNull(nameof(videoId));

            if (!ValidateVideoId(videoId))
            {
                throw new ArgumentException($"Invalid YouTube video ID [{videoId}].", nameof(videoId));
            }

            // Get video info
            var videoInfo = GetVideoInfoAsync(videoId); //.ConfigureAwait(false);

            if (videoInfo == null)
            {
                return new List <ClosedCaptionTrackInfo>()
                       {
                       }
            }
            ;

            // Extract captions metadata
            var playerResponseRaw  = videoInfo["player_response"];
            var playerResponseJson = JToken.Parse(playerResponseRaw);
            var captionTracksJson  = playerResponseJson.SelectToken("$..captionTracks").EmptyIfNull();

            // Parse closed caption tracks
            var closedCaptionTrackInfos = new List <ClosedCaptionTrackInfo>();

            foreach (var captionTrackJson in captionTracksJson)
            {
                // Extract values
                var code = captionTrackJson["languageCode"].Value <string>();

                var name     = captionTrackJson["name"]["simpleText"].Value <string>();
                var language = new Language(code, name);
                var isAuto   = captionTrackJson["vssId"].Value <string>()
                               .StartsWith("a.", StringComparison.OrdinalIgnoreCase);
                var url = captionTrackJson["baseUrl"].Value <string>();

                // Enforce format
                url = UrlEx.SetQueryParameter(url, "format", "3");

                var closedCaptionTrackInfo = new ClosedCaptionTrackInfo(url, language, isAuto);
                closedCaptionTrackInfos.Add(closedCaptionTrackInfo);
            }

            return(closedCaptionTrackInfos);
        }
    }
Esempio n. 7
0
        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);
        }
Esempio n. 8
0
        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());
        }
Esempio n. 9
0
        /// <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));
        }
        /// <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 parser
            var parser = await GetVideoInfoParserAsync(videoId, playerContext.Sts).ConfigureAwait(false);

            // Check if video requires purchase
            var previewVideoId = parser.ParsePreviewVideoId();

            if (previewVideoId.IsNotBlank())
            {
                throw new VideoRequiresPurchaseException(videoId, previewVideoId);
            }

            // Prepare stream info maps
            var muxedStreamInfoMap = new Dictionary <int, MuxedStreamInfo>();
            var audioStreamInfoMap = new Dictionary <int, AudioStreamInfo>();
            var videoStreamInfoMap = new Dictionary <int, VideoStreamInfo>();

            // Parse muxed stream infos
            foreach (var muxedStreamInfoParser in parser.GetMuxedStreamInfos())
            {
                // Extract itag
                var itag = muxedStreamInfoParser.ParseItag();

#if RELEASE
                // Skip unknown itags
                if (!ItagHelper.IsKnown(itag))
                {
                    continue;
                }
#endif

                // Extract URL
                var url = muxedStreamInfoParser.ParseUrl();

                // Decipher signature if needed
                var signature = muxedStreamInfoParser.ParseSignature();
                if (signature.IsNotBlank())
                {
                    var playerSource =
                        await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                    signature = playerSource.Decipher(signature);
                    url       = UrlEx.SetQueryParameter(url, "signature", signature);
                }

                // Probe stream and get content length
                var contentLength = await _httpClient.GetContentLengthAsync(url, false).ConfigureAwait(false) ?? -1;

                // If probe failed or content length is 0, it means the stream is gone or faulty
                if (contentLength <= 0)
                {
                    continue;
                }

                var streamInfo = new MuxedStreamInfo(itag, url, contentLength);
                muxedStreamInfoMap[itag] = streamInfo;
            }

            // Parse adaptive stream infos
            foreach (var adaptiveStreamInfoParser in parser.GetAdaptiveStreamInfos())
            {
                // Extract info
                var itag = adaptiveStreamInfoParser.ParseItag();

#if RELEASE
                // Skip unknown itags
                if (!ItagHelper.IsKnown(itag))
                {
                    continue;
                }
#endif

                // Extract content length
                var contentLength = adaptiveStreamInfoParser.ParseContentLength();

                // If content length is 0, it means that the stream is gone or faulty
                if (contentLength <= 0)
                {
                    continue;
                }

                // Extract URL
                var url = adaptiveStreamInfoParser.ParseUrl();

                // Decipher signature if needed
                var signature = adaptiveStreamInfoParser.ParseSignature();
                if (signature.IsNotBlank())
                {
                    var playerSource =
                        await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                    signature = playerSource.Decipher(signature);
                    url       = UrlEx.SetQueryParameter(url, "signature", signature);
                }

                // Extract bitrate
                var bitrate = adaptiveStreamInfoParser.ParseBitrate();

                // If audio-only
                if (adaptiveStreamInfoParser.ParseIsAudioOnly())
                {
                    var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate);
                    audioStreamInfoMap[itag] = streamInfo;
                }
                // If video-only
                else
                {
                    // Extract info
                    var width        = adaptiveStreamInfoParser.ParseWidth();
                    var height       = adaptiveStreamInfoParser.ParseHeight();
                    var framerate    = adaptiveStreamInfoParser.ParseFramerate();
                    var qualityLabel = adaptiveStreamInfoParser.ParseQualityLabel();

                    var resolution = new VideoResolution(width, height);
                    var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate,
                                                         qualityLabel);
                    videoStreamInfoMap[itag] = streamInfo;
                }
            }

            // Parse dash manifest
            var dashManifestUrl = parser.ParseDashManifestUrl();
            if (dashManifestUrl.IsNotBlank())
            {
                // Parse signature
                var signature = Regex.Match(dashManifestUrl, @"/s/(.*?)(?:/|$)").Groups[1].Value;

                // Decipher signature if needed
                if (signature.IsNotBlank())
                {
                    var playerSource = await GetVideoPlayerSourceAsync(playerContext.SourceUrl).ConfigureAwait(false);

                    signature       = playerSource.Decipher(signature);
                    dashManifestUrl = UrlEx.SetRouteParameter(dashManifestUrl, "signature", signature);
                }

                // Get the dash manifest parser
                var dashManifestParser = await GetDashManifestParserAsync(dashManifestUrl).ConfigureAwait(false);

                // Parse dash stream infos
                foreach (var dashStreamInfoParser in dashManifestParser.GetStreamInfos())
                {
                    // Extract itag
                    var itag = dashStreamInfoParser.ParseItag();

#if RELEASE
                    // Skip unknown itags
                    if (!ItagHelper.IsKnown(itag))
                    {
                        continue;
                    }
#endif

                    // Extract info
                    var url           = dashStreamInfoParser.ParseUrl();
                    var contentLength = dashStreamInfoParser.ParseContentLength();
                    var bitrate       = dashStreamInfoParser.ParseBitrate();

                    // If audio-only
                    if (dashStreamInfoParser.ParseIsAudioOnly())
                    {
                        var streamInfo = new AudioStreamInfo(itag, url, contentLength, bitrate);
                        audioStreamInfoMap[itag] = streamInfo;
                    }
                    // If video-only
                    else
                    {
                        // Parse additional data
                        var width     = dashStreamInfoParser.ParseWidth();
                        var height    = dashStreamInfoParser.ParseHeight();
                        var framerate = dashStreamInfoParser.ParseFramerate();

                        var resolution = new VideoResolution(width, height);
                        var streamInfo = new VideoStreamInfo(itag, url, contentLength, bitrate, resolution, framerate);
                        videoStreamInfoMap[itag] = streamInfo;
                    }
                }
            }

            // Get the raw HLS stream playlist (*.m3u8)
            var hlsPlaylistUrl = parser.ParseHlsPlaylistUrl();

            // 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, hlsPlaylistUrl));
        }
Esempio n. 11
0
        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));
        }