diff --git a/YoutubeExtractor/YoutubeExtractor/DownloadUrlResolver.cs b/YoutubeExtractor/YoutubeExtractor/DownloadUrlResolver.cs deleted file mode 100644 index e07c280..0000000 --- a/YoutubeExtractor/YoutubeExtractor/DownloadUrlResolver.cs +++ /dev/null @@ -1,352 +0,0 @@ -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text.RegularExpressions; - -namespace YoutubeExtractor -{ - /// - /// Provides a method to get the download link of a YouTube video. - /// - public static class DownloadUrlResolver - { - private const string RateBypassFlag = "ratebypass"; - private const string SignatureQuery = "sig"; - - /// - /// Decrypts the signature in the property and sets it - /// to the decrypted URL. Use this method, if you have decryptSignature in the method set to false. - /// - /// The video info which's downlaod URL should be decrypted. - /// - /// There was an error while deciphering the signature. - /// - public static void DecryptDownloadUrl(VideoInfo videoInfo) - { - string decipheredSignature = string.Empty; - IDictionary strs = HttpHelper.ParseQueryString(videoInfo.DownloadUrl); - if (strs.ContainsKey(SignatureQuery)) - { - string item = strs[SignatureQuery]; - try - { - if (!string.IsNullOrEmpty(videoInfo.HtmlPlayerVersion)) - decipheredSignature = GetDecipheredSignature(videoInfo.HtmlPlayerVersion, item); - else - decipheredSignature = item; - } - catch (Exception exception) - { - throw new YoutubeParseException("Could not decipher signature", exception); - } - videoInfo.DownloadUrl = HttpHelper.ReplaceQueryStringParameter(videoInfo.DownloadUrl, SignatureQuery, decipheredSignature); - videoInfo.RequiresDecryption = false; - } - } - - /// - /// Gets a list of s for the specified URL. - /// - /// The URL of the YouTube video. - /// - /// A value indicating whether the video signatures should be decrypted or not. Decrypting - /// consists of a HTTP request for each , so you may want to set - /// this to false and call on your selected later. - /// - /// A list of s that can be used to download the video. - /// - /// The parameter is null. - /// - /// - /// The parameter is not a valid YouTube URL. - /// - /// The video is not available. - /// - /// An error occurred while downloading the YouTube page html. - /// - /// The Youtube page could not be parsed. - public static IEnumerable GetDownloadUrls(string videoUrl, bool decryptSignature = true) - { - if (videoUrl == null) - { - throw new ArgumentNullException("videoUrl"); - } - - if (!TryNormalizeYoutubeUrl(videoUrl, out videoUrl)) - { - throw new ArgumentException("URL is not a valid youtube URL!"); - } - - try - { - var model = LoadModel(videoUrl); - - string videoTitle = GetVideoTitle(model); - - IEnumerable infos = GetVideoInfos(model).ToList(); - - string html5PlayerVersion = GetHtml5PlayerVersion(model.Microformat?.PlayerMicroformatRenderer?.Embed?.IframeUrl); - - foreach (VideoInfo videoInfo in infos) - { - videoInfo.Title = videoTitle; - videoInfo.HtmlPlayerVersion = html5PlayerVersion; - - //It takes a long time to decrypt all of item. - /*if (decryptSignature && info.RequiresDecryption) - { - DecryptDownloadUrl(info); - }*/ - } - - return infos; - } - catch (Exception ex) - { - if (ex is WebException || ex is VideoNotAvailableException) - { - throw; - } - - ThrowYoutubeParseException(ex, videoUrl); - } - - return null; // Will never happen, but the compiler requires it - } - -#if PORTABLE - - public static System.Threading.Tasks.Task> GetDownloadUrlsAsync(string videoUrl, bool decryptSignature = true) - { - return System.Threading.Tasks.Task.Run(() => GetDownloadUrls(videoUrl, decryptSignature)); - } - -#endif - - /// - /// Normalizes the given YouTube URL to the format http://youtube.com/watch?v={youtube-id} - /// and returns whether the normalization was successful or not. - /// - /// The YouTube URL to normalize. - /// The normalized YouTube URL. - /// - /// true, if the normalization was successful; false, if the URL is invalid. - /// - public static bool TryNormalizeYoutubeUrl(string url, out string normalizedUrl) - { - url = url.Trim(); - url = url.Replace("youtu.be/", "youtube.com/watch?v="); - url = url.Replace("www.youtube", "youtube"); - url = url.Replace("youtube.com/embed/", "youtube.com/watch?v="); - if (url.Contains("/v/")) - { - url = string.Concat("http://youtube.com", (new Uri(url)).AbsolutePath.Replace("/v/", "/watch?v=")); - } - url = url.Replace("/watch#", "/watch?"); - - - if (!HttpHelper.ParseQueryString(url).TryGetValue("v", out string v)) - { - normalizedUrl = null; - return false; - } - - normalizedUrl = "https://youtube.com/watch?v=" + v; - - return true; - } - - private static List GetAdaptiveStreamMap(YoutubeModel model) - { - return model.StreamingData?.AdaptiveFormats.ToList(); - } - - private static string GetDecipheredSignature(string htmlPlayerVersion, string signature) - { - return Decipherer.DecipherWithVersion(signature, htmlPlayerVersion); - } - - private static string GetHtml5PlayerVersion(string url) - { - if (string.IsNullOrEmpty(url)) - return null; - - string str = HttpHelper.DownloadString(url); - Regex regex = new Regex(@"""jsUrl""\s*:\s*""([^""]+)"""); - return regex.Match(str).Result("$1").Replace("\\/", "/"); - } - - private static List GetStreamMap(YoutubeModel model) - { - return model.StreamingData?.Formats.ToList(); - } - - private static IEnumerable GetVideoInfos(YoutubeModel model) - { - var streamingFormats = GetStreamMap(model); - if (streamingFormats == null) - streamingFormats = new List(); - - var adaptiveStream = GetAdaptiveStreamMap(model); - if (adaptiveStream != null) - streamingFormats.AddRange(adaptiveStream); - - foreach (var fmt in streamingFormats) - { - if (!fmt.Itag.HasValue) continue; - - var itag = (int)fmt.Itag.Value; - - VideoInfo videoInfo = VideoInfo.Defaults.SingleOrDefault(info => info.FormatCode == itag); - - videoInfo = videoInfo ?? new VideoInfo(itag); - - if (!string.IsNullOrEmpty(fmt.Url)) - { - videoInfo.DownloadUrl = HttpHelper.UrlDecode(HttpHelper.UrlDecode(fmt.Url)); - } - else if (!string.IsNullOrEmpty(fmt.Cipher) || !string.IsNullOrEmpty(fmt.SignatureCipher)) - { - IDictionary cipher = null; - - if (!string.IsNullOrEmpty(fmt.Cipher)) - cipher = HttpHelper.ParseQueryString(fmt.Cipher); - if (!string.IsNullOrEmpty(fmt.SignatureCipher)) - cipher = HttpHelper.ParseQueryString(fmt.SignatureCipher); - - if (!cipher.ContainsKey("url")) - continue; - if (!cipher.ContainsKey("s")) - continue; - - var url = cipher["url"]; - var sig = cipher["s"]; - - url = HttpHelper.UrlDecode(url); - url = HttpHelper.UrlDecode(url); - - sig = HttpHelper.UrlDecode(sig); - sig = HttpHelper.UrlDecode(sig); - - url = url.Replace("&s=", "&sig="); - videoInfo.DownloadUrl = HttpHelper.ReplaceQueryStringParameter(url, SignatureQuery, sig); - videoInfo.RequiresDecryption = true; - } - else - continue; - - if (!HttpHelper.ParseQueryString(videoInfo.DownloadUrl).ContainsKey(RateBypassFlag)) - { - videoInfo.DownloadUrl = string.Concat(videoInfo.DownloadUrl, string.Format("&{0}={1}", "ratebypass", "yes")); - } - - if (fmt.AudioSampleRate.HasValue) - videoInfo.AudioBitrate = (int)fmt.AudioSampleRate.Value; - - if (fmt.ContentLength.HasValue) - videoInfo.FileSize = (int)fmt.ContentLength.Value; - - if (!string.IsNullOrEmpty(fmt.QualityLabel)) - videoInfo.FormatNote = fmt.QualityLabel; - else - videoInfo.FormatNote = fmt.Quality; - - if (fmt.Fps.HasValue) - videoInfo.FPS = (int)fmt.Fps.Value; - - if (fmt.Height.HasValue) - videoInfo.Height = (int)fmt.Height.Value; - - if (fmt.Width.HasValue) - videoInfo.Width = (int)fmt.Width.Value; - - // bitrate for itag 43 is always 2147483647 - if (itag != 43) - { - if (fmt.AverageBitrate.HasValue) - videoInfo.AverageBitrate = fmt.AverageBitrate.Value / 1000f; - else if (fmt.Bitrate.HasValue) - videoInfo.AverageBitrate = fmt.Bitrate.Value / 1000f; - } - - if (fmt.Height.HasValue) - videoInfo.Height = (int)fmt.Height.Value; - - if (fmt.Width.HasValue) - videoInfo.Width = (int)fmt.Width.Value; - - yield return videoInfo; - } - } - - private static string GetVideoTitle(YoutubeModel model) - { - return model.VideoDetails.Title.Replace("+", " "); - } - - private static YoutubeModel LoadModel(string videoUrl) - { - var videoId = videoUrl.Replace("https://youtube.com/watch?v=", ""); - - var url = $"https://www.youtube.com/watch?v={videoId}&gl=US&hl=en&has_verified=1&bpctr=9999999999"; - - var pageSource = HttpHelper.DownloadString(url); - var player_response = string.Empty; - - if (Regex.IsMatch(pageSource, @"[""\']status[""\']\s*:\s*[""\']LOGIN_REQUIRED")) - { - url = $"https://www.youtube.com/get_video_info?video_id={videoId}&eurl=https://youtube.googleapis.com/v/{videoId}"; - pageSource = HttpHelper.DownloadString(url); - player_response = HttpHelper.ParseQueryString(pageSource)["player_response"]; - player_response = HttpHelper.UrlDecode(player_response); - - return YoutubeModel.FromJson(player_response); - } - - var dataRegex = new Regex(@"ytplayer\.config\s*=\s*(\{.+?\});", RegexOptions.Multiline); - var dataMatch = dataRegex.Match(pageSource); - if (dataMatch.Success) - { - string extractedJson = dataMatch.Result("$1"); - if (!extractedJson.Contains("raw_player_response:ytInitialPlayerResponse")) //https://www.youtube.com/watch?v=9Y7TRMISkGE - { - player_response = JObject.Parse(extractedJson)["args"]["player_response"].ToString(); - return YoutubeModel.FromJson(player_response); - } - } - - dataRegex = new Regex(@"ytInitialPlayerResponse\s*=\s*({.+?})\s*;\s*(?:var\s+meta|