/// <summary> /// Downloads metadata for the YouTube video with the specified ID. /// </summary> protected virtual async Task <YouTubeMetadata> DownloadMetadata(string id, CancellationToken cancellationToken) { IDictionary <string, string> rawMetadata; Status.Update(null, "Downloading metadata...", WorkStatusType.DownloadingFile); // make http client var client = HttpHelper.CreateClient(); // send http request using (var response = await client.GetAsync(GetYouTubeVideoInfoUri(id), cancellationToken)) { // decode metadata into rawMetadata var content = await response.Content.ReadAsStringAsync(); rawMetadata = YouTubeUtils.ExtractUrlEncodedParamMap(content); } // extract metadata var metadata = new YouTubeMetadata { FormatList = rawMetadata.ContainsKey("fmt_list") ? rawMetadata["fmt_list"] : null, Status = rawMetadata["status"], }; // extract player response var rawPlayerResponse = rawMetadata["player_response"] ?? throw new GrabParseException("Failed to fetch player_response from metadata."); var playerResponse = JToken.Parse(rawPlayerResponse); metadata.PlayerResponse = ExtractPlayerResponseMetadata(playerResponse); // extract muxed streams if (metadata.PlayerResponse.MuxedStreams != null) { metadata.MuxedStreams = metadata.PlayerResponse.MuxedStreams; } if (metadata.MuxedStreams == null) { var urlEncodedFmtStreamMap = rawMetadata.GetOrDefault("url_encoded_fmt_stream_map") ?? throw new GrabParseException("Failed to fetch url_encoded_fmt_stream_map from metadata."); var fmtStreamMap = YouTubeUtils.ExtractUrlEncodedParamList(urlEncodedFmtStreamMap); metadata.MuxedStreams = ExtractMuxedStreamsMetadata(fmtStreamMap); } // extract adaptive streams if (metadata.PlayerResponse.AdaptiveStreams != null) { metadata.AdaptiveStreams = metadata.PlayerResponse.AdaptiveStreams; } if (metadata.AdaptiveStreams == null) { var urlEncodedAdaptiveFormats = rawMetadata["adaptive_fmts"] ?? throw new GrabParseException("Failed to fetch adaptive_fmts from metadata."); var adaptiveFmts = YouTubeUtils.ExtractUrlEncodedParamList(urlEncodedAdaptiveFormats); metadata.AdaptiveStreams = ExtractAdaptiveFormatsMetadata(adaptiveFmts); } return(metadata); }
/// <summary> /// Updates <see cref="GrabResult"/> according to the information obtained from metadata. /// </summary> private void UpdateResult(GrabResult result, YouTubeMetadata md) { var response = md.PlayerResponse; result.Title = response.Title; result.CreationDate = response.UploadedAt; result.Description = response.ShortDescription; result.Statistics = new GrabStatisticInfo { Author = response.Author, Length = response.Length, ViewCount = response.ViewCount }; }
/// <summary> /// Invoked by <see cref="GrabAsync"/> method when the target video has a signature. /// This method downloads the necessary script (base.js) and decipher all grabbed links. /// </summary> protected virtual async Task Decipher(YouTubeEmbedPageData embedPageData, YouTubeMetadata metaData, CancellationToken cancellationToken) { // download base.js var client = HttpHelper.CreateClient(); using var response = await client.GetAsync(embedPageData.BaseJsUri, cancellationToken); var scriptContent = await response.Content.ReadAsStringAsync(); var script = new YouTubeScript(scriptContent); // find decipher function name var match = DecipherFunctionRegex.Match(scriptContent); if (!match.Success) { throw new GrabParseException("Failed to locate decipher function."); } var fn = match.Groups[1].Value; // prepare script host to execute the decipher function along with its used functions script.PrepareDecipherFunctionCall(fn); // call decipher function foreach (var streamInfo in metaData.AllStreams) { if (string.IsNullOrEmpty(streamInfo.Signature)) { continue; } // calculate decipher streamInfo.Decipher = script.CallDecipherFunction(fn, streamInfo.Signature); // update uri streamInfo.Url += $"&sig={Uri.EscapeDataString(streamInfo.Decipher)}"; } }