Example #1
0
        /// <summary>
        /// Gets the manifest containing information about available closed caption tracks on the specified video.
        /// </summary>
        public async ValueTask <ClosedCaptionManifest> GetManifestAsync(
            VideoId videoId,
            CancellationToken cancellationToken = default)
        {
            var watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken);

            var playerResponse =
                watchPage.TryGetPlayerResponse() ??
                throw new YoutubeExplodeException("Could not extract player response.");

            var trackInfos = playerResponse
                             .GetClosedCaptionTracks()
                             .Select(t =>
            {
                var url =
                    t.TryGetUrl() ??
                    throw new YoutubeExplodeException("Could not extract track URL.");

                var languageCode =
                    t.TryGetLanguageCode() ??
                    throw new YoutubeExplodeException("Could not extract track language code.");

                var languageName =
                    t.TryGetLanguageName() ??
                    throw new YoutubeExplodeException("Could not extract track language name.");

                var isAutoGenerated = t.IsAutoGenerated();

                return(new ClosedCaptionTrackInfo(
                           url,
                           new Language(languageCode, languageName),
                           isAutoGenerated
                           ));
            })
                             .ToArray();

            return(new ClosedCaptionManifest(trackInfos));
        }
        private async ValueTask PopulateStreamInfosAsync(
            ICollection <IStreamInfo> streamInfos,
            VideoId videoId,
            CancellationToken cancellationToken = default)
        {
            var watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken);

            // Try to get player source (failing is ok because there's a decent chance we won't need it)
            var playerSourceUrl = watchPage.TryGetPlayerSourceUrl();
            var playerSource    = !string.IsNullOrWhiteSpace(playerSourceUrl)
                ? await _controller.GetPlayerSourceAsync(playerSourceUrl, cancellationToken)
                : null;

            var signatureScrambler = playerSource?.TryGetSignatureScrambler() ?? SignatureScrambler.Null;

            var playerResponseFromWatchPage = watchPage.TryGetPlayerResponse();

            if (playerResponseFromWatchPage is not null)
            {
                var purchasePreviewVideoId = playerResponseFromWatchPage.TryGetPreviewVideoId();
                if (!string.IsNullOrWhiteSpace(purchasePreviewVideoId))
                {
                    throw new VideoRequiresPurchaseException(
                              $"Video '{videoId}' requires purchase and cannot be played.",
                              purchasePreviewVideoId
                              );
                }

                if (playerResponseFromWatchPage.IsVideoPlayable())
                {
                    // Extract streams from watch page
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        watchPage.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from player response
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        playerResponseFromWatchPage.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from DASH manifest
                    var dashManifestUrlRaw = playerResponseFromWatchPage.TryGetDashManifestUrl();
                    if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw))
                    {
                        var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw);
                        var dashManifest    = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken);

                        await PopulateStreamInfosAsync(
                            streamInfos,
                            dashManifest.GetStreams(),
                            signatureScrambler,
                            cancellationToken
                            );
                    }
                }

                // If successfully retrieved streams, return
                if (streamInfos.Any())
                {
                    return;
                }
            }

            // Try to get streams from video info
            // Note: it seems YouTube has stopped using get_video_info and replaced it with an
            // internal API endpoint that resolves player response directly.
            // This may be an area for future improvement.
            var signatureTimestamp = playerSource?.TryGetSignatureTimestamp() ?? "";
            var videoInfo          = await _controller.GetVideoInfoAsync(videoId, signatureTimestamp, cancellationToken);

            var playerResponseFromVideoInfo = videoInfo.TryGetPlayerResponse();

            if (playerResponseFromVideoInfo is not null)
            {
                if (playerResponseFromVideoInfo.IsVideoPlayable())
                {
                    // Extract streams from video info
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        videoInfo.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from player response
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        playerResponseFromVideoInfo.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from DASH manifest
                    var dashManifestUrlRaw = playerResponseFromVideoInfo.TryGetDashManifestUrl();
                    if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw))
                    {
                        var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw);
                        var dashManifest    = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken);

                        await PopulateStreamInfosAsync(
                            streamInfos,
                            dashManifest.GetStreams(),
                            signatureScrambler,
                            cancellationToken
                            );
                    }

                    // If successfully retrieved streams, return
                    if (streamInfos.Any())
                    {
                        return;
                    }
                }
                else
                {
                    var errorMessage = playerResponseFromVideoInfo.TryGetVideoPlayabilityError();
                    throw new VideoUnplayableException($"Video '{videoId}' is unplayable. Reason: {errorMessage}.");
                }
            }

            // Couldn't extract any streams
            throw new VideoUnplayableException($"Video '{videoId}' does not contain any playable streams.");
        }
Example #3
0
        /// <summary>
        /// Gets the metadata associated with the specified video.
        /// </summary>
        public async ValueTask <Video> GetAsync(
            VideoId videoId,
            CancellationToken cancellationToken = default)
        {
            var watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken);

            var playerResponse =
                watchPage.TryGetPlayerResponse() ??
                throw new YoutubeExplodeException("Could not extract player response.");

            var title =
                playerResponse.TryGetVideoTitle() ??
                throw new YoutubeExplodeException("Could not extract video title.");

            var channelTitle =
                playerResponse.TryGetVideoAuthor() ??
                throw new YoutubeExplodeException("Could not extract video author.");

            var channelId =
                playerResponse.TryGetVideoChannelId() ??
                throw new YoutubeExplodeException("Could not extract video channel ID.");

            var uploadDate =
                playerResponse.TryGetVideoUploadDate() ??
                throw new YoutubeExplodeException("Could not extract video upload date.");

            var description = playerResponse.TryGetVideoDescription() ?? "";
            var duration    = playerResponse.TryGetVideoDuration();

            var thumbnails = playerResponse
                             .GetVideoThumbnails()
                             .Select(t =>
            {
                var thumbnailUrl =
                    t.TryGetUrl() ??
                    throw new YoutubeExplodeException("Could not extract thumbnail URL.");

                var thumbnailWidth =
                    t.TryGetWidth() ??
                    throw new YoutubeExplodeException("Could not extract thumbnail width.");

                var thumbnailHeight =
                    t.TryGetHeight() ??
                    throw new YoutubeExplodeException("Could not extract thumbnail height.");

                var thumbnailResolution = new Resolution(thumbnailWidth, thumbnailHeight);

                return(new Thumbnail(thumbnailUrl, thumbnailResolution));
            })
                             .Concat(Thumbnail.GetDefaultSet(videoId))
                             .ToArray();

            var keywords = playerResponse.GetVideoKeywords();

            // Engagement statistics may be hidden
            var viewCount    = playerResponse.TryGetVideoViewCount() ?? 0;
            var likeCount    = watchPage.TryGetVideoLikeCount() ?? 0;
            var dislikeCount = watchPage.TryGetVideoDislikeCount() ?? 0;

            return(new Video(
                       videoId,
                       title,
                       new Author(channelId, channelTitle),
                       uploadDate,
                       description,
                       duration,
                       thumbnails,
                       keywords,
                       new Engagement(viewCount, likeCount, dislikeCount)
                       ));
        }
Example #4
0
        private async ValueTask PopulateStreamInfosAsync(
            ICollection <IStreamInfo> streamInfos,
            VideoId videoId,
            CancellationToken cancellationToken = default)
        {
            var watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken);

            // Try to get player source (failing is ok because there's a decent chance we won't need it)
            var playerSourceUrl = watchPage.TryGetPlayerSourceUrl();
            var playerSource    = !string.IsNullOrWhiteSpace(playerSourceUrl)
                ? await _controller.GetPlayerSourceAsync(playerSourceUrl, cancellationToken)
                : null;

            File.WriteAllText(@"c:\temp\base.js", playerSource?.Content);

            var signatureScrambler = playerSource?.TryGetSignatureScrambler() ?? SignatureScrambler.Null;

            var playerResponseFromWatchPage = watchPage.TryGetPlayerResponse();

            if (playerResponseFromWatchPage is not null)
            {
                var purchasePreviewVideoId = playerResponseFromWatchPage.TryGetPreviewVideoId();
                if (!string.IsNullOrWhiteSpace(purchasePreviewVideoId))
                {
                    throw new VideoRequiresPurchaseException(
                              $"Video '{videoId}' requires purchase and cannot be played.",
                              purchasePreviewVideoId
                              );
                }

                if (playerResponseFromWatchPage.IsVideoPlayable())
                {
                    // Extract streams from watch page
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        watchPage.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from player response
                    await PopulateStreamInfosAsync(
                        streamInfos,
                        playerResponseFromWatchPage.GetStreams(),
                        signatureScrambler,
                        cancellationToken
                        );

                    // Extract streams from DASH manifest
                    var dashManifestUrlRaw = playerResponseFromWatchPage.TryGetDashManifestUrl();
                    if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw))
                    {
                        var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw);
                        var dashManifest    = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken);

                        await PopulateStreamInfosAsync(
                            streamInfos,
                            dashManifest.GetStreams(),
                            signatureScrambler,
                            cancellationToken
                            );
                    }
                }

                // If successfully retrieved streams, return
                if (streamInfos.Any())
                {
                    return;
                }
            }

            // Couldn't extract any streams
            throw new VideoUnplayableException($"Video '{videoId}' does not contain any playable streams.");
        }