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);
            }
        }
Example #3
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));
        }
Example #4
0
        /// <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));
        }
Example #5
0
        /// <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));
        }
Example #6
0
        /// <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);
                }
            }
        }