Exemplo n.º 1
0
        /// <summary>
        /// Gets video by ID
        /// </summary>
        public async Task <Video> GetVideoAsync(string videoId)
        {
            videoId.GuardNotNull(nameof(videoId));
            if (!ValidateVideoId(videoId))
            {
                throw new ArgumentException("Invalid Youtube video ID", nameof(videoId));
            }

            // Get player context
            var context = await GetPlayerContextAsync(videoId).ConfigureAwait(false);

            // Get video info
            var request  = $"{YoutubeHost}/get_video_info?video_id={videoId}&sts={context.Sts}&el=info&ps=default&hl=en";
            var response = await _httpService.GetStringAsync(request).ConfigureAwait(false);

            var videoDic = UrlHelper.GetDictionaryFromUrlQuery(response);

            // Check error code
            if (videoDic.ContainsKey("errorcode"))
            {
                var errorCode   = videoDic.Get("errorcode").ParseInt();
                var errorReason = videoDic.Get("reason");
                throw new VideoNotAvailableException(videoId, errorCode, errorReason);
            }

            // Check if video requires purchase
            if (videoDic.GetOrDefault("requires_purchase") == "1")
            {
                var previewVideoId = videoDic.Get("ypc_vid");
                throw new VideoRequiresPurchaseException(videoId, previewVideoId);
            }

            // Parse metadata
            var title              = videoDic.Get("title");
            var duration           = TimeSpan.FromSeconds(videoDic.Get("length_seconds").ParseDouble());
            var viewCount          = videoDic.Get("view_count").ParseLong();
            var keywords           = videoDic.Get("keywords").Split(",");
            var isListed           = videoDic.GetOrDefault("is_listed") == "1"; // unlisted videos don't have this
            var isRatingAllowed    = videoDic.Get("allow_ratings") == "1";
            var isMuted            = videoDic.Get("muted") == "1";
            var isEmbeddingAllowed = videoDic.Get("allow_embed") == "1";

            // Prepare stream info collections
            var muxedStreamInfos = new List <MuxedStreamInfo>();
            var audioStreamInfos = new List <AudioStreamInfo>();
            var videoStreamInfos = new List <VideoStreamInfo>();

            // Resolve muxed streams
            var muxedStreamInfosEncoded = videoDic.GetOrDefault("url_encoded_fmt_stream_map");

            if (muxedStreamInfosEncoded.IsNotBlank())
            {
                await ResolveMuxedStreamInfosAsync(context, muxedStreamInfosEncoded, muxedStreamInfos)
                .ConfigureAwait(false);
            }

            // Resolve adaptive streams
            var adaptiveStreamInfosEncoded = videoDic.GetOrDefault("adaptive_fmts");

            if (adaptiveStreamInfosEncoded.IsNotBlank())
            {
                await ResolveAdaptiveStreamInfosAsync(context, adaptiveStreamInfosEncoded,
                                                      audioStreamInfos, videoStreamInfos)
                .ConfigureAwait(false);
            }

            // Resolve dash streams
            var dashManifestUrl = videoDic.GetOrDefault("dashmpd");

            if (dashManifestUrl.IsNotBlank())
            {
                await ResolveDashStreamInfosAsync(context, dashManifestUrl,
                                                  audioStreamInfos, videoStreamInfos)
                .ConfigureAwait(false);
            }

            // 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();

            // Parse closed caption tracks
            var closedCaptionTrackInfos        = new List <ClosedCaptionTrackInfo>();
            var closedCaptionTrackInfosEncoded = videoDic.GetOrDefault("caption_tracks");

            if (closedCaptionTrackInfosEncoded.IsNotBlank())
            {
                ParseClosedCaptionTrackInfos(closedCaptionTrackInfosEncoded, closedCaptionTrackInfos);
            }

            // Get metadata extension
            request  = $"{YoutubeHost}/get_video_metadata?video_id={videoId}";
            response = await _httpService.GetStringAsync(request).ConfigureAwait(false);

            var videoXml = XElement.Parse(response).StripNamespaces().ElementStrict("html_content");

            // Parse metadata extension
            var description  = videoXml.ElementStrict("video_info").ElementStrict("description").Value;
            var likeCount    = (long)videoXml.ElementStrict("video_info").ElementStrict("likes_count_unformatted");
            var dislikeCount = (long)videoXml.ElementStrict("video_info").ElementStrict("dislikes_count_unformatted");

            // Parse author info
            var authorId        = videoXml.ElementStrict("user_info").ElementStrict("channel_external_id").Value;
            var authorName      = videoXml.ElementStrict("user_info").ElementStrict("username").Value;
            var authorTitle     = videoXml.ElementStrict("user_info").ElementStrict("channel_title").Value;
            var authorIsPaid    = videoXml.ElementStrict("user_info").ElementStrict("channel_paid").Value == "1";
            var authorLogoUrl   = videoXml.ElementStrict("user_info").ElementStrict("channel_logo_url").Value;
            var authorBannerUrl = videoXml.ElementStrict("user_info").ElementStrict("channel_banner_url").Value;

            // Concat metadata
            var author     = new Channel(authorId, authorName, authorTitle, authorIsPaid, authorLogoUrl, authorBannerUrl);
            var thumbnails = new VideoThumbnails(videoId);
            var status     = new VideoStatus(isListed, isRatingAllowed, isMuted, isEmbeddingAllowed);
            var statistics = new Statistics(viewCount, likeCount, dislikeCount);

            return(new Video(videoId, author, title, description, thumbnails, duration, keywords, status, statistics,
                             muxedStreamInfos, audioStreamInfos, videoStreamInfos, closedCaptionTrackInfos));
        }
        /// <summary>
        /// Gets playlist by ID, truncating resulting video list at given number of pages (1 page ≤ 200 videos)
        /// </summary>
        public async Task <Playlist> GetPlaylistAsync(string playlistId, int maxPages)
        {
            playlistId.GuardNotNull(nameof(playlistId));
            maxPages.GuardPositive(nameof(maxPages));
            if (!ValidatePlaylistId(playlistId))
            {
                throw new ArgumentException("Invalid Youtube playlist ID", nameof(playlistId));
            }

            // Get all videos across pages
            var      pagesDone = 0;
            var      offset    = 0;
            XElement playlistXml;
            var      videoIds = new HashSet <string>();
            var      videos   = new List <PlaylistVideo>();

            do
            {
                // Get manifest
                var request  = $"{YoutubeHost}/list_ajax?style=xml&action_get_list=1&list={playlistId}&index={offset}";
                var response = await _httpService.GetStringAsync(request).ConfigureAwait(false);

                playlistXml = XElement.Parse(response).StripNamespaces();

                // Parse videos
                var total = 0;
                var delta = 0;
                foreach (var videoXml in playlistXml.Elements("video"))
                {
                    // Basic info
                    var videoId          = videoXml.ElementStrict("encrypted_id").Value;
                    var videoTitle       = videoXml.ElementStrict("title").Value;
                    var videoThumbnails  = new VideoThumbnails(videoId);
                    var videoDuration    = TimeSpan.FromSeconds((double)videoXml.ElementStrict("length_seconds"));
                    var videoDescription = videoXml.ElementStrict("description").Value;

                    // Keywords
                    var videoKeywordsJoined = videoXml.ElementStrict("keywords").Value;
                    var videoKeywords       = Regex
                                              .Matches(videoKeywordsJoined, @"(?<=(^|\s)(?<q>""?))([^""]|(""""))*?(?=\<q>(?=\s|$))")
                                              .Cast <Match>()
                                              .Select(m => m.Value)
                                              .Where(s => s.IsNotBlank())
                                              .ToArray();

                    // Statistics
                    // The inner text is already formatted so we have to parse it manually
                    var videoViewCount =
                        Regex.Replace(videoXml.ElementStrict("views").Value, @"\D", "").ParseLong();
                    var videoLikeCount =
                        Regex.Replace(videoXml.ElementStrict("likes").Value, @"\D", "").ParseLong();
                    var videoDislikeCount =
                        Regex.Replace(videoXml.ElementStrict("dislikes").Value, @"\D", "").ParseLong();
                    var videoStatistics = new Statistics(videoViewCount, videoLikeCount, videoDislikeCount);

                    // Video
                    var video = new PlaylistVideo(videoId, videoTitle, videoDescription, videoThumbnails, videoDuration,
                                                  videoKeywords, videoStatistics);

                    // Add to list if not already there
                    if (videoIds.Add(video.Id))
                    {
                        videos.Add(video);
                        delta++;
                    }
                    total++;
                }

                // Break if the videos started repeating
                if (delta <= 0)
                {
                    break;
                }

                // Prepare for next page
                pagesDone++;
                offset += total;
            } while (pagesDone < maxPages);

            // Basic info
            var title       = playlistXml.ElementStrict("title").Value;
            var author      = playlistXml.Element("author")?.Value ?? ""; // system playlists don't have an author
            var description = playlistXml.ElementStrict("description").Value;

            // Statistics
            var viewCount    = (long?)playlistXml.Element("views") ?? 0;    // watchlater does not have views
            var likeCount    = (long?)playlistXml.Element("likes") ?? 0;    // system playlists don't have likes
            var dislikeCount = (long?)playlistXml.Element("dislikes") ?? 0; // system playlists don't have dislikes
            var statistics   = new Statistics(viewCount, likeCount, dislikeCount);

            return(new Playlist(playlistId, title, author, description, statistics, videos));
        }