public VideoDownloadInfo(VideoAvailabilityEnum availability, string contentId, string contentTitle, string errorMessage, List <VideoMetadata> videos) { Availability = availability; ContentId = contentId; ContentTitle = contentTitle; ErrorMessage = errorMessage; Videos = (videos?.Count ?? 0) > 0 ? new List <VideoMetadata>(videos) : null; }
// Retrieves the available download urls by decoding the video info DOM for the 'contentId' to parse for itags. public VideoDownloadInfo GetVideoDownloadInfo(string contentId, string contentTitle, string dom) { const string _responseError = "&errorcode"; const string _responseErrorCode = "&errorcode=150"; const string _responsePurchase = "&requires_purchase"; const string _url = "url="; const string _streamMapPattern = "(?<=url_encoded_fmt_stream_map=).*"; if (string.IsNullOrEmpty(contentId)) { return(new VideoDownloadInfo(VideoAvailabilityEnum.NotAvailable, null, contentTitle, "Missing content id.", null)); } if (string.IsNullOrEmpty(contentTitle)) { return(new VideoDownloadInfo(VideoAvailabilityEnum.NotAvailable, contentId, null, "Missing content title.", null)); } if (string.IsNullOrEmpty(dom)) { return(new VideoDownloadInfo(VideoAvailabilityEnum.NotAvailable, contentId, contentTitle, "Missing dom.", null)); } // Decode pass 1. dom = WebUtility.UrlDecode(dom); // Check for errors and specific messages. if (dom.Contains(_responseError) || dom.Contains(_responsePurchase)) { string errorMessage = string.Empty; VideoAvailabilityEnum errorStatus = VideoAvailabilityEnum.NotAvailable; if (dom.Contains(_responseErrorCode)) { errorStatus = VideoAvailabilityEnum.ErrorCode; errorMessage = "YouTube video info response contains errorcode 150."; } else if (_responseError.Contains(_responsePurchase)) { errorStatus = VideoAvailabilityEnum.RequiresPurchase; errorMessage = "YouTube content requires purchase."; } else { errorStatus = VideoAvailabilityEnum.ErrorCode; errorMessage = "The dom contains an error in the response."; } return(new VideoDownloadInfo(errorStatus, contentId, contentTitle, errorMessage, null)); } // Get everything after "url_encoded_fmt_stream_map=". dom = Regex.Match(dom, _streamMapPattern).Value; // Decode pass 2. dom = WebUtility.UrlDecode(dom); // Split. string[] responseUrls = Regex.Split(dom, _url); // Clean up the URLs and place them in a new list. var data = new List <VideoMetadata>(responseUrls.Length); foreach (var response in responseUrls) { string url = RemoveQueryParameters(response); // Check the results after removing unnecessary query parameters and gather the metadata for the url. if (!string.IsNullOrEmpty(url)) { if (MediaQualityEnumHelpers.TryParseUrlForItag(url, out string itag)) { if (MediaQualityEnumHelpers.TryMapItagToEnum(itag, out MediaQualityEnum quality)) { data.Add(new VideoMetadata(contentId, contentTitle, quality, url)); // Hope } } } } // Check if there are urls. if (data.Count < 1) { return(new VideoDownloadInfo(VideoAvailabilityEnum.NotAvailable, contentId, contentTitle, "Download urls are not available.", null)); } // Lower the capacity of the metadata list. data.TrimExcess(); return(new VideoDownloadInfo( VideoAvailabilityEnum.Available, contentId, contentTitle, null, data)); }