public void MasterListCheck() { //若存在多个清晰度条目,输出另一个json文件存放 if (extLists.Count != 0) { File.Copy(m3u8SavePath, Path.GetDirectoryName(m3u8SavePath) + "\\master.m3u8", true); LOGGER.WriteLine("Master List Found"); LOGGER.PrintLine(strings.masterListFound, LOGGER.Warning); string t = "{" + "\"masterUri\":\"" + M3u8Url + "\"," + "\"updateTime\":\"" + DateTime.Now.ToString("o") + "\"," + "\"playLists:\":[" + string.Join(",", extLists.ToArray()) + "]" + "}"; //输出json文件 LOGGER.WriteLine(strings.wrtingMasterMeta); LOGGER.PrintLine(strings.wrtingMasterMeta); File.WriteAllText(Path.GetDirectoryName(jsonSavePath) + "\\playLists.json", Global.ConvertJsonString(t)); LOGGER.WriteLine(strings.selectPlaylist + ": " + bestUrl); LOGGER.PrintLine(strings.selectPlaylist); LOGGER.WriteLine(strings.startReParsing); LOGGER.PrintLine(strings.startReParsing, LOGGER.Warning); //重置Baseurl并重新解析 M3u8Url = bestUrl; BaseUrl = ""; Parse(); } }
private static void Output(object sendProcess, DataReceivedEventArgs output) { if (!String.IsNullOrEmpty(output.Data)) { LOGGER.PrintLine(output.Data, LOGGER.Warning); } }
/// <summary> /// 返回生成的master文件地址 /// </summary> /// <param name="downDir">文件存储目录</param> /// <param name="mpdUrl">MPD链接</param> /// <param name="mpdContent">MPD内容</param> /// <param name="defaultBase">BaseUrl</param> /// <returns></returns> public static string Parse(string downDir, string mpdUrl, string mpdContent, string defaultBase = "") { //XiGua if (mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas")) { mpdContent = mpdContent.Replace("<MPD ", "<MPD xmlns:mas=\"urn:marlin:mas:1-0:services:schemas:mpd\" "); } XmlDocument mpdDoc = new XmlDocument(); mpdDoc.LoadXml(mpdContent); XmlNode xn = mpdDoc.LastChild; var mediaPresentationDuration = ((XmlElement)xn).GetAttribute("mediaPresentationDuration"); var ns = ((XmlElement)xn).GetAttribute("xmlns"); XmlNamespaceManager nsMgr = new XmlNamespaceManager(mpdDoc.NameTable); nsMgr.AddNamespace("ns", ns); TimeSpan ts = XmlConvert.ToTimeSpan(mediaPresentationDuration); //时长 var formatList = new List <Dictionary <string, dynamic> >(); //存放所有音视频清晰度 foreach (XmlElement period in xn.SelectNodes("ns:Period", nsMgr)) { var periodDuration = string.IsNullOrEmpty(period.GetAttribute("duration")) ? XmlConvert.ToTimeSpan(mediaPresentationDuration) : XmlConvert.ToTimeSpan(period.GetAttribute("duration")); var periodMsInfo = ExtractMultisegmentInfo(period, nsMgr, new Dictionary <string, dynamic>() { ["StartNumber"] = 1, ["Timescale"] = 1 }); foreach (XmlElement adaptationSet in period.SelectNodes("ns:AdaptationSet", nsMgr)) { var adaptionSetMsInfo = ExtractMultisegmentInfo(adaptationSet, nsMgr, periodMsInfo); foreach (XmlElement representation in adaptationSet.SelectNodes("ns:Representation", nsMgr)) { string GetAttribute(string key) { var v1 = representation.GetAttribute(key); if (string.IsNullOrEmpty(v1)) { return(adaptationSet.GetAttribute(key)); } return(v1); } var mimeType = GetAttribute("mimeType"); var contentType = mimeType.Split('/')[0]; if (contentType == "text") { continue; //暂不支持字幕下载 } else if (contentType == "video" || contentType == "audio") { var baseUrl = ""; bool CheckBaseUrl() { return(Regex.IsMatch(baseUrl, @"^https?://")); } var list = new List <XmlNodeList>() { representation.ChildNodes, adaptationSet.ChildNodes, period.ChildNodes, mpdDoc.ChildNodes }; foreach (XmlNodeList xmlNodeList in list) { foreach (XmlNode node in xmlNodeList) { if (node.Name == "BaseURL") { baseUrl = node.InnerText + baseUrl; if (CheckBaseUrl()) { break; } } } if (CheckBaseUrl()) { break; } } string GetBaseUrl(string url) { if (url.Contains("?")) { url = url.Remove(url.LastIndexOf('?')); } url = url.Substring(0, url.LastIndexOf('/') + 1); return(url); } var mpdBaseUrl = string.IsNullOrEmpty(defaultBase) ? GetBaseUrl(mpdUrl) : defaultBase; if (!string.IsNullOrEmpty(mpdBaseUrl) && !CheckBaseUrl()) { if (!mpdBaseUrl.EndsWith("/") && !baseUrl.StartsWith("/")) { mpdBaseUrl += "/"; } baseUrl = mpdBaseUrl + baseUrl; } var representationId = GetAttribute("id"); var lang = GetAttribute("lang"); var bandwidth = IntOrNull(GetAttribute("bandwidth")); var f = new Dictionary <string, dynamic> { ["FormatId"] = representationId, ["ManifestUrl"] = mpdUrl, ["Width"] = IntOrNull(GetAttribute("width")), ["Height"] = IntOrNull(GetAttribute("height")), ["Tbr"] = DoubleOrNull(bandwidth, 1000), ["Asr"] = IntOrNull(GetAttribute("audioSamplingRate")), ["Fps"] = IntOrNull(GetAttribute("frameRate")), ["Language"] = lang, ["Codecs"] = GetAttribute("codecs") }; var representationMsInfo = ExtractMultisegmentInfo(representation, nsMgr, adaptionSetMsInfo); string PrepareTemplate(string templateName, string[] identifiers) { var tmpl = representationMsInfo[templateName]; var t = new StringBuilder(); var inTemplate = false; foreach (var ch in tmpl) { t.Append(ch); if (ch == '$') { inTemplate = !inTemplate; } else if (ch == '%' && !inTemplate) { t.Append(ch); } } var str = t.ToString(); str = str.Replace("$RepresentationID$", representationId); str = Regex.Replace(str, "\\$(" + string.Join("|", identifiers) + ")\\$", "{{$1}}"); str = Regex.Replace(str, "\\$(" + string.Join("|", identifiers) + ")%([^$]+)d\\$", "{{$1}}{0:D$2}"); str = str.Replace("$$", "$"); return(str); } string PadNumber(string template, string key, long value) { string ReplaceFirst(string text, string search, string replace) { int pos = text.IndexOf(search); if (pos < 0) { return(text); } return(text.Substring(0, pos) + replace + text.Substring(pos + search.Length)); } template = template.Replace("{{" + key + "}}", ""); var m = Regex.Match(template, "{0:D(\\d+)}"); return(ReplaceFirst(template, m.Value, value.ToString("0".PadRight(Convert.ToInt32(m.Groups[1].Value), '0')))); } if (representationMsInfo.ContainsKey("Initialization")) { var initializationTemplate = PrepareTemplate("Initialization", new string[] { "Bandwidth" }); var initializationUrl = ""; if (initializationTemplate.Contains("{0:D")) { if (initializationTemplate.Contains("{{Bandwidth}}")) { initializationUrl = PadNumber(initializationTemplate, "Bandwidth", bandwidth); } } else { initializationUrl = initializationTemplate.Replace("{{Bandwidth}}", bandwidth.ToString()); } representationMsInfo["InitializationUrl"] = CombineURL(baseUrl, initializationUrl); } string LocationKey(string location) { return(Regex.IsMatch(location, "^https?://") ? "url" : "path"); } if (!representationMsInfo.ContainsKey("SegmentUrls") && representationMsInfo.ContainsKey("Media")) { var mediaTemplate = PrepareTemplate("Media", new string[] { "Number", "Bandwidth", "Time" }); var mediaLocationKey = LocationKey(mediaTemplate); if (mediaTemplate.Contains("{{Number") && !representationMsInfo.ContainsKey("S")) { var segmentDuration = 0.0; if (!representationMsInfo.ContainsKey("TotalNumber") && representationMsInfo.ContainsKey("SegmentDuration")) { segmentDuration = DoubleOrNull(representationMsInfo["SegmentDuration"], representationMsInfo["Timescale"]); representationMsInfo["TotalNumber"] = (int)Math.Ceiling(periodDuration.TotalSeconds / segmentDuration); } var fragments = new List <Dictionary <string, dynamic> >(); for (int i = representationMsInfo["StartNumber"]; i < representationMsInfo["StartNumber"] + representationMsInfo["TotalNumber"]; i++) { var segUrl = ""; if (mediaTemplate.Contains("{0:D")) { if (mediaTemplate.Contains("{{Bandwidth}}")) { segUrl = PadNumber(mediaTemplate, "Bandwidth", bandwidth); } if (mediaTemplate.Contains("{{Number}}")) { segUrl = PadNumber(mediaTemplate, "Number", i); } } else { segUrl = mediaTemplate.Replace("{{Bandwidth}}", bandwidth.ToString()); segUrl = segUrl.Replace("{{Number}}", i.ToString()); } fragments.Add(new Dictionary <string, dynamic>() { [mediaLocationKey] = CombineURL(baseUrl, segUrl), ["duration"] = segmentDuration }); } representationMsInfo["Fragments"] = fragments; } else { var fragments = new List <Dictionary <string, dynamic> >(); var segmentTime = 0L; var segmentD = 0L; var segmentNumber = representationMsInfo["StartNumber"]; void addSegmentUrl() { var segUrl = ""; if (mediaTemplate.Contains("{0:D")) { if (mediaTemplate.Contains("{{Bandwidth}}")) { segUrl = PadNumber(mediaTemplate, "Bandwidth", bandwidth); } if (mediaTemplate.Contains("{{Number}}")) { segUrl = PadNumber(mediaTemplate, "Number", segmentNumber); } if (mediaTemplate.Contains("{{Time}}")) { segUrl = PadNumber(mediaTemplate, "Time", segmentTime); } } else { segUrl = mediaTemplate.Replace("{{Bandwidth}}", bandwidth.ToString()); segUrl = segUrl.Replace("{{Number}}", segmentNumber.ToString()); segUrl = segUrl.Replace("{{Time}}", segmentTime.ToString()); } fragments.Add(new Dictionary <string, dynamic>() { [mediaLocationKey] = CombineURL(baseUrl, segUrl), ["duration"] = DoubleOrNull(segmentD, representationMsInfo["Timescale"]) }); } if (representationMsInfo.ContainsKey("S")) { for (int i = 0; i < representationMsInfo["S"].Count; i++) { var s = representationMsInfo["S"][i]; segmentTime = s["t"] == 0 ? segmentTime : s["t"]; segmentD = s["d"]; addSegmentUrl(); segmentNumber++; for (int j = 0; j < s["r"]; j++) { segmentTime += segmentD; addSegmentUrl(); segmentNumber++; } segmentTime += segmentD; } } representationMsInfo["Fragments"] = fragments; } } else if (representationMsInfo.ContainsKey("SegmentUrls") && representationMsInfo.ContainsKey("S")) { var fragments = new List <Dictionary <string, dynamic> >(); var segmentIndex = 0; var timescale = representationMsInfo["Timescale"]; foreach (var s in representationMsInfo["S"]) { var duration = DoubleOrNull(s["d"], timescale); for (int j = 0; j < s["r"] + 1; j++) { var segmentUri = representationMsInfo["SegmentUrls"][segmentIndex]; fragments.Add(new Dictionary <string, dynamic>() { [LocationKey(segmentUri)] = CombineURL(baseUrl, segmentUri), ["duration"] = duration }); segmentIndex++; } } representationMsInfo["Fragments"] = fragments; } else if (representationMsInfo.ContainsKey("SegmentUrls")) { var fragments = new List <Dictionary <string, dynamic> >(); var segmentDuration = DoubleOrNull(representationMsInfo["SegmentDuration"], representationMsInfo.ContainsKey("SegmentDuration") ? representationMsInfo["Timescale"] : 1); foreach (var segmentUrl in representationMsInfo["SegmentUrls"]) { if (segmentDuration != null) { fragments.Add(new Dictionary <string, dynamic>() { [LocationKey(segmentUrl)] = CombineURL(baseUrl, segmentUrl), ["duration"] = segmentDuration }); } else { fragments.Add(new Dictionary <string, dynamic>() { [LocationKey(segmentUrl)] = CombineURL(baseUrl, segmentUrl) }); } } representationMsInfo["Fragments"] = fragments; } if (representationMsInfo.ContainsKey("Fragments")) { f["Url"] = string.IsNullOrEmpty(mpdUrl) ? baseUrl : mpdUrl; f["FragmentBaseUrl"] = baseUrl; if (representationMsInfo.ContainsKey("InitializationUrl")) { f["InitializationUrl"] = representationMsInfo["InitializationUrl"]; f["Fragments"] = representationMsInfo["Fragments"]; } } else { //整段mp4 f["Fragments"] = new List <Dictionary <string, dynamic> > { new Dictionary <string, dynamic>() { ["url"] = baseUrl, ["duration"] = ts.TotalSeconds } }; } //处理同一ID分散在不同Period的情况 if (formatList.Any(_f => _f["FormatId"] == f["FormatId"] && _f["Width"] == f["Width"])) { for (int i = 0; i < formatList.Count; i++) { if (formatList[i]["FormatId"] == f["FormatId"] && formatList[i]["Width"] == f["Width"]) { formatList[i]["Fragments"].AddRange(f["Fragments"]); break; } } } else { formatList.Add(f); } } } } } //排序 formatList.Sort((a, b) => { return((a["Width"] + a["Height"]) * 1000 + a["Tbr"] > (b["Width"] + b["Height"]) * 1000 + b["Tbr"] ? -1 : 1); }); //默认为最高码率的视频和音频 var bestVideo = SelectBestVideo(formatList); var bestAudio = SelectBestAudio(formatList); var audioLangList = new List <string>(); formatList.ForEach(f => { if (f["Width"] == -1 && !audioLangList.Contains(f["Language"])) { audioLangList.Add(f["Language"]); } }); if (audioLangList.Count > 1) { string Stringify(Dictionary <string, dynamic> f) { var type = f["Width"] == -1 && f["Height"] == -1 ? "Audio" : "Video"; var res = type == "Video" ? $"[{f["Width"]}x{f["Height"]}]" : ""; var id = $"[{f["FormatId"]}] "; var tbr = $"[{((int)f["Tbr"]).ToString().PadLeft(4)} Kbps] "; var asr = f["Asr"] != -1 ? $"[{f["Asr"]} Hz] " : ""; var fps = f["Fps"] != -1 ? $"[{f["Fps"]} fps] " : ""; var lang = string.IsNullOrEmpty(f["Language"]) ? "" : $"[{f["Language"]}] "; var codecs = $"[{f["Codecs"]}] "; return($"{type} => {id}{tbr}{asr}{fps}{lang}{codecs}{res}"); } for (int i = 0; i < formatList.Count; i++) { Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {Stringify(formatList[i])}"); LOGGER.CursorIndex++; } Console.CursorVisible = true; LOGGER.PrintLine("Found Multiple Language Audio Tracks.\r\n" + "".PadRight(13) + "Please Select What You Want(Up to 1 Video and 1 Audio)."); Console.Write("".PadRight(13) + "Enter Numbers Separated By A Space: "); var input = Console.ReadLine(); LOGGER.CursorIndex += 2; Console.CursorVisible = false; if (!string.IsNullOrEmpty(input)) { bestVideo = new Dictionary <string, dynamic>() { ["Tbr"] = 0 }; bestAudio = new Dictionary <string, dynamic>() { ["Tbr"] = 0 }; foreach (var index in input.Split()) { var n = 0; int.TryParse(index, out n); if (formatList[n]["Width"] == -1) { bestAudio = formatList[n]; } else { bestVideo = formatList[n]; } } } } if (bestVideo.Keys.Count > 1 && bestAudio.Keys.Count > 1) //音视频 { return(GenerateMasterList(downDir, bestVideo, bestAudio)); } else if (bestAudio.Keys.Count > 1) //仅有音频 { return(GenerateMasterList(downDir, bestAudio)); } else if (bestVideo.Keys.Count > 1) //仅有视频 { return(GenerateMasterList(downDir, bestVideo)); } else { return("ERROR"); } }
public void IsComplete(int segCount) { int tsCount = 0; if (DisableIntegrityCheck) { tsCount = segCount; goto ll; } for (int i = 0; i < PartsCount; i++) { tsCount += Global.GetFileCount(DownDir + "\\Part_" + i.ToString(partsPadZero), ".ts"); } ll: if (tsCount != segCount) { LOGGER.PrintLine(strings.downloadedCount + tsCount + " / " + segCount); LOGGER.WriteLine(strings.downloadedCount + tsCount + " of " + segCount); if (Count <= RetryCount) { Count++; LOGGER.WriteLine(strings.retryCount + Count + " / " + RetryCount); LOGGER.PrintLine(strings.retryCount + Count + " / " + RetryCount, LOGGER.Warning); Thread.Sleep(3000); DoDownload(); } } else //开始合并 { LOGGER.PrintLine(strings.downloadComplete + (DisableIntegrityCheck ? "(" + strings.disableIntegrityCheck + ")" : "")); if (NoMerge == false) { string exePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); string driverName = exePath.Remove(exePath.IndexOf(':')); Console.Title = "Done."; LOGGER.WriteLine(strings.startMerging); LOGGER.PrintLine(strings.startMerging, LOGGER.Warning); //VTT字幕 if (isVTT == true) { MuxFormat = "vtt"; Global.ReAdjustVtt(Global.GetFiles(DownDir + "\\Part_0", ".ts")); } //只有一个Part直接用ffmpeg合并 if (PartsCount == 1) { /* * FFREPORT=file=C\:/Users/nilao/Desktop/新建文件夹/3.log:level=32 * Test with Powershell, its C:/Users/nilao/Desktop/新建文件夹/3.log */ FFmpeg.OutPutPath = Path.Combine(Directory.GetParent(DownDir).FullName, DownName); FFmpeg.ReportFile = driverName + "\\:" + exePath.Remove(0, exePath.IndexOf(':') + 1).Replace("\\", "/") + "/Logs/" + Path.GetFileNameWithoutExtension(LOGGER.LOGFILE) + fflogName; if (File.Exists(DownDir + "\\!MAP.ts")) { File.Move(DownDir + "\\!MAP.ts", DownDir + "\\Part_0\\!MAP.ts"); } if (BinaryMerge) { LOGGER.PrintLine(strings.binaryMergingPleaseWait); MuxFormat = "ts"; //有MAP文件,一般为mp4,采取默认动作 if (File.Exists(DownDir + "\\Part_0\\!MAP.ts")) { MuxFormat = "mp4"; } if (isVTT) { MuxFormat = "vtt"; } if (Global.AUDIO_TYPE != "") { MuxFormat = Global.AUDIO_TYPE; } Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir + "\\Part_0", ".ts"), FFmpeg.OutPutPath + $".{MuxFormat}"); } else { if (Global.VIDEO_TYPE != "DV") //不是杜比视界 { //检测是否为MPEG-TS封装,不是的话就转换为TS封装 foreach (string s in Global.GetFiles(DownDir + "\\Part_0", ".ts")) { //跳过有MAP的情况 if (!isVTT && !File.Exists(DownDir + "\\Part_0\\!MAP.ts") && !FFmpeg.CheckMPEGTS(s)) { //转换 LOGGER.PrintLine(strings.remuxToMPEGTS + Path.GetFileName(s)); LOGGER.WriteLine(strings.remuxToMPEGTS + Path.GetFileName(s)); FFmpeg.ConvertToMPEGTS(s); } } //分片过多的情况 if (tsCount >= 1800) { LOGGER.WriteLine(strings.partialMergingPleaseWait); LOGGER.PrintLine(strings.partialMergingPleaseWait, LOGGER.Warning); Global.PartialCombineMultipleFiles(Global.GetFiles(DownDir + "\\Part_0", ".ts")); } if (Global.AUDIO_TYPE != "") { MuxFormat = Global.AUDIO_TYPE; } LOGGER.PrintLine(strings.ffmpegMergingPleaseWait); if (!File.Exists(MuxSetJson)) { FFmpeg.Merge(Global.GetFiles(DownDir + "\\Part_0", ".ts"), MuxFormat, MuxFastStart); } else { JObject json = JObject.Parse(File.ReadAllText(MuxSetJson, Encoding.UTF8)); string muxFormat = json["muxFormat"].Value <string>(); bool fastStart = Convert.ToBoolean(json["fastStart"].Value <string>()); string poster = json["poster"].Value <string>(); string audioName = json["audioName"].Value <string>(); string title = json["title"].Value <string>(); string copyright = json["copyright"].Value <string>(); string comment = json["comment"].Value <string>(); string encodingTool = ""; try { encodingTool = json["encodingTool"].Value <string>(); } catch (Exception) {; } FFmpeg.Merge(Global.GetFiles(DownDir + "\\Part_0", ".ts"), muxFormat, fastStart, poster, audioName, title, copyright, comment, encodingTool); } //Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir + "\\Part_0", ".ts"), FFmpeg.OutPutPath + ".ts"); //Global.ExplorerFile(FFmpeg.OutPutPath + ".mp4"); } else { LOGGER.PrintLine(strings.dolbyVisionContentMerging); Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir + "\\Part_0", ".ts"), FFmpeg.OutPutPath + ".mp4"); } } LOGGER.WriteLine(strings.taskDone + "\r\n\r\nTask End: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "\r\nFile: " + FFmpeg.OutPutPath + "." + (MuxFormat == "aac" ? "m4a" : MuxFormat) + "\r\n\r\n"); //删除文件夹 if (DelAfterDone) { try { DirectoryInfo directoryInfo = new DirectoryInfo(DownDir); directoryInfo.Delete(true); } catch (Exception) { } } if (externalAudio) //下载独立音轨 { externalAudio = false; DownloadedSize = 0; Global.WriteInit(); LOGGER.PrintLine(strings.downloadingExternalAudioTrack, LOGGER.Warning); Parser parser = new Parser(); parser.Headers = Headers; //继承Header parser.BaseUrl = ""; parser.M3u8Url = externalAudioUrl; parser.DownName = DownName + "(Audio)"; parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName); LOGGER.WriteLine(strings.startParsing + externalAudioUrl); LOGGER.WriteLine(strings.downloadingExternalAudioTrack); DownName = DownName + "(Audio)"; fflogName = "_ffreport(Audio).log"; DownDir = parser.DownDir; parser.Parse(); //开始解析 Thread.Sleep(1000); Global.HadReadInfo = false; Global.VIDEO_TYPE = ""; Global.AUDIO_TYPE = ""; DoDownload(); } if (externalSub) //下载独立字幕 { externalSub = false; DownloadedSize = 0; Global.WriteInit(); LOGGER.PrintLine(strings.downloadingExternalSubtitleTrack, LOGGER.Warning); Parser parser = new Parser(); parser.Headers = Headers; //继承Header parser.BaseUrl = ""; parser.M3u8Url = externalSubUrl; parser.DownName = DownName.Replace("(Audio)", "") + "(Subtitle)"; parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName); LOGGER.WriteLine(strings.startParsing + externalSubUrl); LOGGER.WriteLine(strings.downloadingExternalSubtitleTrack); DownName = parser.DownName; fflogName = "_ffreport(Subtitle).log"; DownDir = parser.DownDir; parser.Parse(); //开始解析 Thread.Sleep(1000); Global.HadReadInfo = false; Global.VIDEO_TYPE = ""; Global.AUDIO_TYPE = ""; DoDownload(); } LOGGER.PrintLine(strings.taskDone, LOGGER.Warning); Environment.Exit(0); //正常退出程序 return; } FFmpeg.OutPutPath = Path.Combine(Directory.GetParent(DownDir).FullName, DownName); FFmpeg.ReportFile = driverName + "\\:" + exePath.Remove(0, exePath.IndexOf(':') + 1).Replace("\\", "/") + "/Logs/" + Path.GetFileNameWithoutExtension(LOGGER.LOGFILE) + fflogName; //合并分段 LOGGER.PrintLine(strings.startMerging); for (int i = 0; i < PartsCount; i++) { string outputFilePath = DownDir + "\\Part_" + i.ToString(partsPadZero) + ".ts"; Global.CombineMultipleFilesIntoSingleFile( Global.GetFiles(DownDir + "\\Part_" + i.ToString(partsPadZero), ".ts"), outputFilePath); try { DirectoryInfo directoryInfo = new DirectoryInfo(DownDir + "\\Part_" + i.ToString(partsPadZero)); directoryInfo.Delete(true); } catch (Exception) { } } if (BinaryMerge) { LOGGER.PrintLine(strings.binaryMergingPleaseWait); MuxFormat = "ts"; //有MAP文件,一般为mp4,采取默认动作 if (File.Exists(DownDir + "\\!MAP.ts")) { MuxFormat = "mp4"; } if (isVTT) { MuxFormat = "vtt"; } Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir, ".ts"), FFmpeg.OutPutPath + $".{MuxFormat}"); } else { if (Global.VIDEO_TYPE != "DV") //不是爱奇艺杜比视界 { //检测是否为MPEG-TS封装,不是的话就转换为TS封装 foreach (string s in Global.GetFiles(DownDir, ".ts")) { //跳过有MAP的情况 if (!isVTT && !File.Exists(DownDir + "\\!MAP.ts") && !FFmpeg.CheckMPEGTS(s)) { //转换 LOGGER.PrintLine(strings.remuxToMPEGTS + Path.GetFileName(s)); LOGGER.WriteLine(strings.remuxToMPEGTS + Path.GetFileName(s)); FFmpeg.ConvertToMPEGTS(s); } } if (Global.AUDIO_TYPE != "") { MuxFormat = Global.AUDIO_TYPE; } LOGGER.PrintLine(strings.ffmpegMergingPleaseWait); if (!File.Exists(MuxSetJson)) { FFmpeg.Merge(Global.GetFiles(DownDir, ".ts"), MuxFormat, MuxFastStart); } else { JObject json = JObject.Parse(File.ReadAllText(MuxSetJson, Encoding.UTF8)); string muxFormat = json["muxFormat"].Value <string>(); bool fastStart = Convert.ToBoolean(json["fastStart"].Value <string>()); string poster = json["poster"].Value <string>(); string audioName = json["audioName"].Value <string>(); string title = json["title"].Value <string>(); string copyright = json["copyright"].Value <string>(); string comment = json["comment"].Value <string>(); string encodingTool = ""; try { encodingTool = json["encodingTool"].Value <string>(); } catch (Exception) {; } FFmpeg.Merge(Global.GetFiles(DownDir, ".ts"), muxFormat, fastStart, poster, audioName, title, copyright, comment, encodingTool); } } else { LOGGER.PrintLine(strings.dolbyVisionContentMerging); Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir, ".ts"), FFmpeg.OutPutPath + ".mp4"); } } LOGGER.WriteLine(strings.taskDone + "\r\n\r\nTask End: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "\r\nFile: " + FFmpeg.OutPutPath + "." + (MuxFormat == "aac" ? "m4a" : MuxFormat) + "\r\n\r\n"); //Global.ExplorerFile(FFmpeg.OutPutPath + ".mp4"); //删除文件夹 if (DelAfterDone) { try { DirectoryInfo directoryInfo = new DirectoryInfo(DownDir); directoryInfo.Delete(true); } catch (Exception) { } } if (externalAudio) //下载独立音轨 { externalAudio = false; DownloadedSize = 0; Global.WriteInit(); LOGGER.PrintLine(strings.downloadingExternalAudioTrack, LOGGER.Warning); Parser parser = new Parser(); parser.Headers = Headers; //继承Header parser.BaseUrl = ""; parser.M3u8Url = externalAudioUrl; parser.DownName = DownName + "(Audio)"; parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName); LOGGER.WriteLine(strings.startParsing + externalAudioUrl); LOGGER.WriteLine(strings.downloadingExternalAudioTrack); DownName = parser.DownName; fflogName = "_ffreport(Audio).log"; DownDir = parser.DownDir; parser.Parse(); //开始解析 Thread.Sleep(1000); Global.HadReadInfo = false; Global.VIDEO_TYPE = ""; Global.AUDIO_TYPE = ""; DoDownload(); } if (externalSub) //下载独立字幕 { externalSub = false; DownloadedSize = 0; Global.WriteInit(); LOGGER.PrintLine(strings.downloadingExternalSubtitleTrack, LOGGER.Warning); Parser parser = new Parser(); parser.Headers = Headers; //继承Header parser.BaseUrl = ""; parser.M3u8Url = externalSubUrl; parser.DownName = DownName.Replace("(Audio)", "") + "(Subtitle)"; parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName); LOGGER.WriteLine(strings.startParsing + externalSubUrl); LOGGER.WriteLine(strings.downloadingExternalSubtitleTrack); DownName = parser.DownName; fflogName = "_ffreport(Subtitle).log"; DownDir = parser.DownDir; parser.Parse(); //开始解析 Thread.Sleep(1000); Global.HadReadInfo = false; Global.VIDEO_TYPE = ""; Global.AUDIO_TYPE = ""; DoDownload(); } LOGGER.PrintLine(strings.taskDone, LOGGER.Warning); Environment.Exit(0); //正常退出程序 } else { Console.Title = "Done."; LOGGER.PrintLine(strings.taskDone, LOGGER.Warning); LOGGER.WriteLine(strings.taskDone + "\r\n\r\nTask End: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")); Environment.Exit(0); //正常退出程序 } } }
public void DoDownload() { jsonFile = Path.Combine(DownDir, "meta.json"); if (!File.Exists(jsonFile)) { return; } string jsonContent = File.ReadAllText(jsonFile); JObject initJson = JObject.Parse(jsonContent); JArray parts = JArray.Parse(initJson["m3u8Info"]["segments"].ToString()); //大分组 string segCount = initJson["m3u8Info"]["count"].ToString(); string oriCount = initJson["m3u8Info"]["originalCount"].ToString(); //原始分片数量 string isVOD = initJson["m3u8Info"]["vod"].ToString(); try { if (initJson["m3u8Info"]["audio"].ToString() != "") { externalAudio = true; } externalAudioUrl = initJson["m3u8Info"]["audio"].ToString(); LOGGER.WriteLine(strings.hasExternalAudioTrack); LOGGER.PrintLine(strings.hasExternalAudioTrack, LOGGER.Warning); } catch (Exception) {} try { if (initJson["m3u8Info"]["sub"].ToString() != "") { externalSub = true; } externalSubUrl = initJson["m3u8Info"]["sub"].ToString(); LOGGER.WriteLine(strings.hasExternalSubtitleTrack); LOGGER.PrintLine(strings.hasExternalSubtitleTrack, LOGGER.Warning); } catch (Exception) { } total = Convert.ToInt32(segCount); PartsCount = parts.Count; segsPadZero = string.Empty.PadRight(oriCount.Length, '0'); partsPadZero = string.Empty.PadRight(Convert.ToString(parts.Count).Length, '0'); //是直播视频 if (isVOD == "False") { return; } Global.ShouldStop = false; //是否该停止下载 if (!Directory.Exists(DownDir)) { Directory.CreateDirectory(DownDir); //新建文件夹 } Watcher watcher = new Watcher(DownDir); watcher.Total = total; watcher.PartsCount = PartsCount; watcher.WatcherStrat(); cts = new CancellationTokenSource(); //开始调用下载 LOGGER.WriteLine(strings.startDownloading); LOGGER.PrintLine(strings.startDownloading, LOGGER.Warning); //下载MAP文件(若有) downloadMap: if (HasExtMap) { LOGGER.PrintLine(strings.downloadingMapFile); Downloader sd = new Downloader(); sd.TimeOut = TimeOut; sd.FileUrl = initJson["m3u8Info"]["extMAP"].Value <string>(); sd.Headers = Headers; sd.Method = "NONE"; if (sd.FileUrl.Contains("|")) //有range { string[] tmp = sd.FileUrl.Split('|'); sd.FileUrl = tmp[0]; sd.StartByte = Convert.ToUInt32(tmp[1].Split('@')[1]); sd.ExpectByte = Convert.ToUInt32(tmp[1].Split('@')[0]); } sd.SavePath = DownDir + "\\!MAP.tsdownloading"; if (File.Exists(sd.SavePath)) { File.Delete(sd.SavePath); } if (File.Exists(DownDir + "\\Part_0\\!MAP.ts")) { File.Delete(DownDir + "\\Part_0\\!MAP.ts"); } sd.Down(); //开始下载 if (!File.Exists(DownDir + "\\!MAP.ts")) //检测是否成功下载 { Thread.Sleep(1000); goto downloadMap; } } //首先下载第一个分片 JToken firstSeg = JArray.Parse(parts[0].ToString())[0]; if (!File.Exists(DownDir + "\\Part_" + 0.ToString(partsPadZero) + "\\" + firstSeg["index"].Value <int>().ToString(segsPadZero) + ".ts")) { try { Downloader sd = new Downloader(); sd.TimeOut = TimeOut; sd.SegDur = firstSeg["duration"].Value <double>(); if (sd.SegDur < 0) { sd.SegDur = 0; //防止负数 } sd.FileUrl = firstSeg["segUri"].Value <string>(); //VTT字幕 if (isVTT == false && (sd.FileUrl.Trim('\"').EndsWith(".vtt") || sd.FileUrl.Trim('\"').EndsWith(".webvtt"))) { isVTT = true; } sd.Method = firstSeg["method"].Value <string>(); if (sd.Method != "NONE") { sd.Key = firstSeg["key"].Value <string>(); sd.Iv = firstSeg["iv"].Value <string>(); } if (firstSeg["expectByte"] != null) { sd.ExpectByte = firstSeg["expectByte"].Value <long>(); } if (firstSeg["startByte"] != null) { sd.StartByte = firstSeg["startByte"].Value <long>(); } sd.Headers = Headers; sd.SavePath = DownDir + "\\Part_" + 0.ToString(partsPadZero) + "\\" + firstSeg["index"].Value <int>().ToString(segsPadZero) + ".tsdownloading"; if (File.Exists(sd.SavePath)) { File.Delete(sd.SavePath); } LOGGER.PrintLine(strings.downloadingFirstSegement); //开始计算速度 timer.Enabled = true; if (!Global.ShouldStop) { sd.Down(); //开始下载 } } catch (Exception e) { //LOG.WriteLineError(e.ToString()); } } if (Global.HadReadInfo == false) { string href = DownDir + "\\Part_" + 0.ToString(partsPadZero) + "\\" + firstSeg["index"].Value <int>().ToString(segsPadZero) + ".ts"; if (File.Exists(DownDir + "\\!MAP.ts")) { href = DownDir + "\\!MAP.ts"; } Global.GzipHandler(href); bool flag = false; foreach (string ss in (string[])Global.GetVideoInfo(href).ToArray(typeof(string))) { LOGGER.WriteLine(ss.Trim()); LOGGER.PrintLine(ss.Trim(), 0); if (ss.Trim().Contains("Error in reading file")) { flag = true; } } LOGGER.PrintLine(strings.waitForCompletion, LOGGER.Warning); if (!flag) { Global.HadReadInfo = true; } } //多线程设置 ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Threads, CancellationToken = cts.Token }; //构造包含所有分片的新的segments JArray segments = new JArray(); for (int i = 0; i < parts.Count; i++) { var tmp = JArray.Parse(parts[i].ToString()); for (int j = 0; j < tmp.Count; j++) { JObject t = (JObject)tmp[j]; t.Add("part", i); segments.Add(t); } } //剔除第一个分片(已下载过) segments.RemoveAt(0); try { ParallelLoopResult result = Parallel.ForEach(segments, parallelOptions, () => new Downloader(), (info, loopstate, index, sd) => { if (Global.ShouldStop) { loopstate.Stop(); } else { sd.TimeOut = TimeOut; sd.SegDur = info["duration"].Value <double>(); if (sd.SegDur < 0) { sd.SegDur = 0; //防止负数 } sd.FileUrl = info["segUri"].Value <string>(); //VTT字幕 if (isVTT == false && (sd.FileUrl.Trim('\"').EndsWith(".vtt") || sd.FileUrl.Trim('\"').EndsWith(".webvtt"))) { isVTT = true; } sd.Method = info["method"].Value <string>(); if (sd.Method != "NONE") { sd.Key = info["key"].Value <string>(); sd.Iv = info["iv"].Value <string>(); } if (firstSeg["expectByte"] != null) { sd.ExpectByte = info["expectByte"].Value <long>(); } if (firstSeg["startByte"] != null) { sd.StartByte = info["startByte"].Value <long>(); } sd.Headers = Headers; sd.SavePath = DownDir + "\\Part_" + info["part"].Value <int>().ToString(partsPadZero) + "\\" + info["index"].Value <int>().ToString(segsPadZero) + ".tsdownloading"; if (File.Exists(sd.SavePath)) { File.Delete(sd.SavePath); } if (!Global.ShouldStop) { sd.Down(); //开始下载 } } return(sd); }, (sd) => { }); if (result.IsCompleted) { //LOGGER.WriteLine("Part " + (info["part"].Value<int>() + 1).ToString(partsPadZero) + " of " + parts.Count + " Completed"); } } catch (Exception) { ;//捕获取消循环产生的异常 } finally { cts.Dispose(); } watcher.WatcherStop(); //停止速度监测 timer.Enabled = false; //检测是否下完 IsComplete(Convert.ToInt32(segCount)); }
static string GenerateM3u8(Dictionary <string, dynamic> f) { StringBuilder sb = new StringBuilder(); sb.AppendLine("#EXTM3U"); sb.AppendLine("#EXT-X-VERSION:3"); sb.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI"); //Video if (f["ContentType"] != "audio") { sb.AppendLine($"#EXT-VIDEO-WIDTH:{f["Width"]}"); sb.AppendLine($"#EXT-VIDEO-HEIGHT:{f["Height"]}"); } sb.AppendLine($"#EXT-CODEC:{f["Codecs"]}"); sb.AppendLine($"#EXT-TBR:{f["Tbr"]}"); if (f.ContainsKey("InitializationUrl")) { string initUrl = f["InitializationUrl"]; if (Regex.IsMatch(initUrl, "\\$\\$Range=(\\d+)-(\\d+)")) { var match = Regex.Match(initUrl, "\\$\\$Range=(\\d+)-(\\d+)"); string rangeStr = match.Value; long start = Convert.ToInt64(match.Groups[1].Value); long end = Convert.ToInt64(match.Groups[2].Value); sb.AppendLine($"#EXT-X-MAP:URI=\"{initUrl.Replace(rangeStr, "")}\",BYTERANGE=\"{end + 1 - start}@{start}\""); } else { sb.AppendLine($"#EXT-X-MAP:URI=\"{initUrl}\""); } } sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\""); //使下载器使用二进制合并 List <Dictionary <string, dynamic> > fragments = f["Fragments"]; //检测最后一片的有效性 if (fragments.Count > 1) { bool checkValid(string url) { try { HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(new Uri(url)); request.Timeout = 120000; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); if (((int)response.StatusCode).ToString().StartsWith("2")) { return(true); } else { return(false); } } catch (Exception) { return(false); } } var last = fragments.Last(); var secondToLast = fragments[fragments.Count - 2]; var urlLast = last.ContainsKey("url") ? last["url"] : last["path"]; var urlSecondToLast = secondToLast.ContainsKey("url") ? secondToLast["url"] : secondToLast["path"]; //普通分段才判断 if (urlLast.StartsWith("http") && !Regex.IsMatch(urlLast, "\\$\\$Range=(\\d+)-(\\d+)")) { LOGGER.PrintLine(strings.checkingLast + (f["ContentType"] != "audio" ? "(Video)" : "(Audio)")); LOGGER.WriteLine(strings.checkingLast + (f["ContentType"] != "audio" ? "(Video)" : "(Audio)")); //倒数第二段正常,倒数第一段不正常 if (checkValid(urlSecondToLast) && !checkValid(urlLast)) { fragments.RemoveAt(fragments.Count - 1); } } } //添加分段 foreach (var seg in fragments) { var dur = seg.ContainsKey("duration") ? seg["duration"] : 0.0; var url = seg.ContainsKey("url") ? seg["url"] : seg["path"]; sb.AppendLine($"#EXTINF:{dur.ToString("0.00")}"); if (Regex.IsMatch(url, "\\$\\$Range=(\\d+)-(\\d+)")) { var match = Regex.Match(url, "\\$\\$Range=(\\d+)-(\\d+)"); string rangeStr = match.Value; long start = Convert.ToInt64(match.Groups[1].Value); long end = Convert.ToInt64(match.Groups[2].Value); sb.AppendLine($"#EXT-X-BYTERANGE:{end + 1 - start}@{start}"); sb.AppendLine(url.Replace(rangeStr, "")); } else { sb.AppendLine(url); } } sb.AppendLine("#EXT-X-ENDLIST"); return(sb.ToString()); }
public void Parse() { FFmpeg.REC_TIME = ""; m3u8SavePath = Path.Combine(DownDir, "raw.m3u8"); jsonSavePath = Path.Combine(DownDir, "meta.json"); if (!Directory.Exists(DownDir)) //若文件夹不存在则新建文件夹 { Directory.CreateDirectory(DownDir); //新建文件夹 } //存放分部的所有信息(#EXT-X-DISCONTINUITY) JArray parts = new JArray(); //存放分片的所有信息 JArray segments = new JArray(); JObject segInfo = new JObject(); extLists.Clear(); string m3u8Content = string.Empty; string m3u8Method = string.Empty; string[] extMAP = { "", "" }; string[] extList = new string[10]; long segIndex = 0; long startIndex = 0; int targetDuration = 0; double totalDuration = 0; bool expectSegment = false, expectPlaylist = false, isIFramesOnly = false, isIndependentSegments = false, isEndlist = false, isAd = false, isM3u = false; //获取m3u8内容 if (!LiveStream) { LOGGER.PrintLine(strings.downloadingM3u8, LOGGER.Warning); } if (M3u8Url.StartsWith("http")) { if (M3u8Url.Contains("nfmovies.com/hls")) { m3u8Content = DecodeNfmovies.DecryptM3u8(Global.HttpDownloadFileToBytes(M3u8Url, Headers)); } else if (M3u8Url.Contains("hls.ddyunp.com/ddyun")) { m3u8Content = DecodeDdyun.DecryptM3u8(Global.HttpDownloadFileToBytes(DecodeDdyun.GetVaildM3u8Url(M3u8Url), Headers)); } else { m3u8Content = Global.GetWebSource(M3u8Url, Headers); } } else if (M3u8Url.StartsWith("file:")) { Uri t = new Uri(M3u8Url); m3u8Content = File.ReadAllText(t.LocalPath); } else if (File.Exists(M3u8Url)) { m3u8Content = File.ReadAllText(M3u8Url); if (!M3u8Url.Contains("\\")) { M3u8Url = Path.Combine(Environment.CurrentDirectory, M3u8Url); } Uri t = new Uri(M3u8Url); M3u8Url = t.ToString(); } if (m3u8Content == "") { return; } if (m3u8Content.Contains("qiqiuyun.net/") || m3u8Content.Contains("aliyunedu.net/") || m3u8Content.Contains("qncdn.edusoho.net/")) //气球云 { isQiQiuYun = true; } if (M3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && M3u8Url.Contains("endtime=")) { isEndlist = true; } if (M3u8Url.Contains("imooc.com/")) { m3u8Content = DecodeImooc.DecodeM3u8(m3u8Content); } if (m3u8Content.Contains("</MPD>") && m3u8Content.Contains("<MPD")) { var mpdSavePath = Path.Combine(DownDir, "dash.mpd"); //输出mpd文件 File.WriteAllText(mpdSavePath, m3u8Content); //分析mpd文件 M3u8Url = Global.Get302(M3u8Url, Headers); var newUri = MPDParser.Parse(DownDir, M3u8Url, m3u8Content, BaseUrl); M3u8Url = newUri; m3u8Content = File.ReadAllText(new Uri(M3u8Url).LocalPath); } //输出m3u8文件 File.WriteAllText(m3u8SavePath, m3u8Content); //针对优酷#EXT-X-VERSION:7杜比视界片源修正 if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode=")) { Regex ykmap = new Regex("#EXT-X-DISCONTINUITY\\s+#EXT-X-MAP:URI=\\\"(.*?)\\\",BYTERANGE=\\\"(.*?)\\\""); foreach (Match m in ykmap.Matches(m3u8Content)) { m3u8Content = m3u8Content.Replace(m.Value, $"#EXTINF:0.000000,\n#EXT-X-BYTERANGE:{m.Groups[2].Value}\n{m.Groups[1].Value}"); } } //如果BaseUrl为空则截取字符串充当 if (BaseUrl == "") { if (new Regex("#YUMING\\|(.*)").IsMatch(m3u8Content)) { BaseUrl = new Regex("#YUMING\\|(.*)").Match(m3u8Content).Groups[1].Value; } else { BaseUrl = GetBaseUrl(M3u8Url, Headers); } } if (!LiveStream) { LOGGER.WriteLine(strings.parsingM3u8); LOGGER.PrintLine(strings.parsingM3u8); } if (!string.IsNullOrEmpty(KeyBase64)) { string line = ""; if (string.IsNullOrEmpty(KeyIV)) { line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"base64:{KeyBase64}\""; } else { line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"base64:{KeyBase64}\",IV=0x{KeyIV.Replace("0x", "")}"; } m3u8CurrentKey = ParseKey(line); } if (!string.IsNullOrEmpty(KeyFile)) { string line = ""; Uri u = new Uri(KeyFile); if (string.IsNullOrEmpty(KeyIV)) { line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"{u.ToString()}\""; } else { line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"{u.ToString()}\",IV=0x{KeyIV.Replace("0x", "")}"; } m3u8CurrentKey = ParseKey(line); } //逐行分析 using (StringReader sr = new StringReader(m3u8Content)) { string line; double segDuration = 0; string segUrl = string.Empty; //#EXT-X-BYTERANGE:<n>[@<o>] long expectByte = -1; //parm n long startByte = 0; //parm o while ((line = sr.ReadLine()) != null) { if (string.IsNullOrEmpty(line)) { continue; } if (line.StartsWith(HLSTags.ext_m3u)) { isM3u = true; } //只下载部分字节 else if (line.StartsWith(HLSTags.ext_x_byterange)) { string[] t = line.Replace(HLSTags.ext_x_byterange + ":", "").Split('@'); if (t.Length > 0) { if (t.Length == 1) { expectByte = Convert.ToInt64(t[0]); segInfo.Add("expectByte", expectByte); } if (t.Length == 2) { expectByte = Convert.ToInt64(t[0]); startByte = Convert.ToInt64(t[1]); segInfo.Add("expectByte", expectByte); segInfo.Add("startByte", startByte); } } expectSegment = true; } //国家地理去广告 else if (line.StartsWith("#UPLYNK-SEGMENT")) { if (line.Contains(",ad")) { isAd = true; } else if (line.Contains(",segment")) { isAd = false; } } //国家地理去广告 else if (isAd) { continue; } //解析定义的分段长度 else if (line.StartsWith(HLSTags.ext_x_targetduration)) { targetDuration = Convert.ToInt32(Convert.ToDouble(line.Replace(HLSTags.ext_x_targetduration + ":", "").Trim())); } //解析起始编号 else if (line.StartsWith(HLSTags.ext_x_media_sequence)) { segIndex = Convert.ToInt64(line.Replace(HLSTags.ext_x_media_sequence + ":", "").Trim()); startIndex = segIndex; } else if (line.StartsWith(HLSTags.ext_x_discontinuity_sequence)) { } else if (line.StartsWith(HLSTags.ext_x_program_date_time)) { if (string.IsNullOrEmpty(FFmpeg.REC_TIME)) { FFmpeg.REC_TIME = line.Replace(HLSTags.ext_x_program_date_time + ":", "").Trim(); } } //解析不连续标记,需要单独合并(timestamp不同) else if (line.StartsWith(HLSTags.ext_x_discontinuity)) { //修复优酷去除广告后的遗留问题 if (hasAd && parts.Count > 0) { segments = (JArray)parts[parts.Count - 1]; parts.RemoveAt(parts.Count - 1); hasAd = false; continue; } //常规情况的#EXT-X-DISCONTINUITY标记,新建part if (!hasAd && segments.Count > 1) { parts.Add(segments); segments = new JArray(); } } else if (line.StartsWith(HLSTags.ext_x_cue_out)) { ; } else if (line.StartsWith(HLSTags.ext_x_cue_out_start)) { ; } else if (line.StartsWith(HLSTags.ext_x_cue_span)) { ; } else if (line.StartsWith(HLSTags.ext_x_version)) { ; } else if (line.StartsWith(HLSTags.ext_x_allow_cache)) { ; } //解析KEY else if (line.StartsWith(HLSTags.ext_x_key)) { //自定义KEY情况 判断是否需要读取IV if (!string.IsNullOrEmpty(KeyFile) || !string.IsNullOrEmpty(KeyBase64)) { if (m3u8CurrentKey[2] == "" && line.Contains("IV=0x")) { var temp = ParseKey(line); //{METHOD,Key,IV} m3u8CurrentKey[2] = temp[2]; //使用m3u8中的IV } } else { m3u8CurrentKey = ParseKey(line);//获取method,key,iv //存储为上一行的key信息 lastKeyLine = line; } } //解析分片时长(暂时不考虑标题属性) else if (line.StartsWith(HLSTags.extinf)) { string[] tmp = line.Replace(HLSTags.extinf + ":", "").Split(','); segDuration = Convert.ToDouble(tmp[0]); segInfo.Add("index", segIndex); segInfo.Add("method", m3u8CurrentKey[0]); //是否有加密,有的话写入KEY和IV if (m3u8CurrentKey[0] != "NONE") { segInfo.Add("key", m3u8CurrentKey[1]); //没有读取到IV,自己生成 if (m3u8CurrentKey[2] == "") { segInfo.Add("iv", "0x" + Convert.ToString(segIndex, 16).PadLeft(32, '0')); } else { segInfo.Add("iv", m3u8CurrentKey[2]); } } totalDuration += segDuration; segInfo.Add("duration", segDuration); expectSegment = true; segIndex++; } //解析STREAM属性 else if (line.StartsWith(HLSTags.ext_x_stream_inf)) { expectPlaylist = true; string bandwidth = Global.GetTagAttribute(line, "BANDWIDTH"); string average_bandwidth = Global.GetTagAttribute(line, "AVERAGE-BANDWIDTH"); string codecs = Global.GetTagAttribute(line, "CODECS"); string resolution = Global.GetTagAttribute(line, "RESOLUTION"); string frame_rate = Global.GetTagAttribute(line, "FRAME-RATE"); string hdcp_level = Global.GetTagAttribute(line, "HDCP-LEVEL"); string audio = Global.GetTagAttribute(line, "AUDIO"); string video = Global.GetTagAttribute(line, "VIDEO"); string subtitles = Global.GetTagAttribute(line, "SUBTITLES"); string closed_captions = Global.GetTagAttribute(line, "CLOSED-CAPTIONS"); extList = new string[] { bandwidth, average_bandwidth, codecs, resolution, frame_rate, hdcp_level, audio, video, subtitles, closed_captions }; } else if (line.StartsWith(HLSTags.ext_x_i_frame_stream_inf)) { ; } else if (line.StartsWith(HLSTags.ext_x_media)) { if (Global.GetTagAttribute(line, "TYPE") == "AUDIO" && !MEDIA_AUDIO.ContainsKey(Global.GetTagAttribute(line, "GROUP-ID"))) { MEDIA_AUDIO.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI"))); } if (Global.GetTagAttribute(line, "TYPE") == "SUBTITLES") { if (!MEDIA_SUB.ContainsKey(Global.GetTagAttribute(line, "GROUP-ID"))) { MEDIA_SUB.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI"))); } } } else if (line.StartsWith(HLSTags.ext_x_playlist_type)) { ; } else if (line.StartsWith(HLSTags.ext_i_frames_only)) { isIFramesOnly = true; } else if (line.StartsWith(HLSTags.ext_is_independent_segments)) { isIndependentSegments = true; } //m3u8主体结束 else if (line.StartsWith(HLSTags.ext_x_endlist)) { if (segments.Count > 0) { parts.Add(segments); } segments = new JArray(); isEndlist = true; } //#EXT-X-MAP else if (line.StartsWith(HLSTags.ext_x_map)) { if (extMAP[0] == "") { extMAP[0] = Global.GetTagAttribute(line, "URI"); if (line.Contains("BYTERANGE")) { extMAP[1] = Global.GetTagAttribute(line, "BYTERANGE"); } if (!extMAP[0].StartsWith("http")) { extMAP[0] = CombineURL(BaseUrl, extMAP[0]); } } //遇到了其他的map,说明已经不是一个视频了,全部丢弃即可 else { if (segments.Count > 0) { parts.Add(segments); } segments = new JArray(); isEndlist = true; break; } } else if (line.StartsWith(HLSTags.ext_x_start)) { ; } //评论行不解析 else if (line.StartsWith("#")) { continue; } //空白行不解析 else if (line.StartsWith("\r\n")) { continue; } //解析分片的地址 else if (expectSegment) { segUrl = CombineURL(BaseUrl, line); if (M3u8Url.Contains("?__gda__")) { segUrl += new Regex("\\?__gda__.*").Match(M3u8Url).Value; } if (M3u8Url.Contains("//dlsc.hcs.cmvideo.cn") && (segUrl.EndsWith(".ts") || segUrl.EndsWith(".mp4"))) { segUrl += new Regex("\\?.*").Match(M3u8Url).Value; } segInfo.Add("segUri", segUrl); segments.Add(segInfo); segInfo = new JObject(); //优酷的广告分段则清除此分片 //需要注意,遇到广告说明程序对上文的#EXT-X-DISCONTINUITY做出的动作是不必要的, //其实上下文是同一种编码,需要恢复到原先的part上 if (DelAd && segUrl.Contains("ccode=") && segUrl.Contains("/ad/") && segUrl.Contains("duration=")) { segments.RemoveAt(segments.Count - 1); segIndex--; hasAd = true; } //优酷广告(4K分辨率测试) if (DelAd && segUrl.Contains("ccode=0902") && segUrl.Contains("duration=")) { segments.RemoveAt(segments.Count - 1); segIndex--; hasAd = true; } expectSegment = false; } //解析STREAM属性的URI else if (expectPlaylist) { string listUrl; listUrl = CombineURL(BaseUrl, line); if (M3u8Url.Contains("?__gda__")) { listUrl += new Regex("\\?__gda__.*").Match(M3u8Url).Value; } StringBuilder sb = new StringBuilder(); sb.Append("{"); sb.Append("\"URL\":\"" + listUrl + "\","); for (int i = 0; i < 10; i++) { if (extList[i] != "") { switch (i) { case 0: sb.Append("\"BANDWIDTH\":\"" + extList[i] + "\","); break; case 1: sb.Append("\"AVERAGE-BANDWIDTH\":\"" + extList[i] + "\","); break; case 2: sb.Append("\"CODECS\":\"" + extList[i] + "\","); break; case 3: sb.Append("\"RESOLUTION\":\"" + extList[i] + "\","); break; case 4: sb.Append("\"FRAME-RATE\":\"" + extList[i] + "\","); break; case 5: sb.Append("\"HDCP-LEVEL\":\"" + extList[i] + "\","); break; case 6: sb.Append("\"AUDIO\":\"" + extList[i] + "\","); break; case 7: sb.Append("\"VIDEO\":\"" + extList[i] + "\","); break; case 8: sb.Append("\"SUBTITLES\":\"" + extList[i] + "\","); break; case 9: sb.Append("\"CLOSED-CAPTIONS\":\"" + extList[i] + "\","); break; } } } sb.Append("}"); extLists.Add(sb.ToString().Replace(",}", "}")); if (Convert.ToInt64(extList[0]) > bestBandwidth) { bestBandwidth = Convert.ToInt64(extList[0]); bestUrl = listUrl; bestUrlAudio = extList[6]; bestUrlSub = extList[8]; } extList = new string[8]; expectPlaylist = false; } } } if (isM3u == false) { LOGGER.WriteLineError(strings.invalidM3u8); LOGGER.PrintLine(strings.invalidM3u8, LOGGER.Error); return; } //直播的情况,无法遇到m3u8结束标记,需要手动将segments加入parts if (parts.HasValues == false) { parts.Add(segments); } //构造JSON文件 JObject jsonResult = new JObject(); jsonResult.Add("m3u8", M3u8Url); jsonResult.Add("m3u8BaseUri", BaseUrl); jsonResult.Add("updateTime", DateTime.Now.ToString("o")); JObject jsonM3u8Info = new JObject(); jsonM3u8Info.Add("originalCount", segIndex - startIndex); jsonM3u8Info.Add("count", segIndex - startIndex); jsonM3u8Info.Add("vod", isEndlist); jsonM3u8Info.Add("targetDuration", targetDuration); jsonM3u8Info.Add("totalDuration", totalDuration); if (bestUrlAudio != "" && MEDIA_AUDIO.ContainsKey(bestUrlAudio)) { jsonM3u8Info.Add("audio", MEDIA_AUDIO[bestUrlAudio]); } if (bestUrlSub != "" && MEDIA_SUB.ContainsKey(bestUrlSub)) { jsonM3u8Info.Add("sub", MEDIA_SUB[bestUrlSub]); } if (extMAP[0] != "") { if (extMAP[1] == "") { jsonM3u8Info.Add("extMAP", extMAP[0]); } else { jsonM3u8Info.Add("extMAP", extMAP[0] + "|" + extMAP[1]); } } //根据DurRange来生成分片Range if (DurStart != "" || DurEnd != "") { double secStart = 0; double secEnd = -1; if (DurEnd == "") { secEnd = totalDuration; } //时间码 Regex reg2 = new Regex(@"(\d+):(\d+):(\d+)"); if (reg2.IsMatch(DurStart)) { int HH = Convert.ToInt32(reg2.Match(DurStart).Groups[1].Value); int MM = Convert.ToInt32(reg2.Match(DurStart).Groups[2].Value); int SS = Convert.ToInt32(reg2.Match(DurStart).Groups[3].Value); secStart = SS + MM * 60 + HH * 60 * 60; } if (reg2.IsMatch(DurEnd)) { int HH = Convert.ToInt32(reg2.Match(DurEnd).Groups[1].Value); int MM = Convert.ToInt32(reg2.Match(DurEnd).Groups[2].Value); int SS = Convert.ToInt32(reg2.Match(DurEnd).Groups[3].Value); secEnd = SS + MM * 60 + HH * 60 * 60; } bool flag1 = false; bool flag2 = false; if (secEnd - secStart > 0) { double dur = 0; //当前时间 foreach (JArray part in parts) { foreach (var seg in part) { dur += Convert.ToDouble(seg["duration"].ToString()); if (flag1 == false && dur > secStart) { RangeStart = seg["index"].Value <int>(); flag1 = true; } if (flag2 == false && dur >= secEnd) { RangeEnd = seg["index"].Value <int>(); flag2 = true; } } } } } //根据Range来清除部分分片 if (RangeStart != 0 || RangeEnd != -1) { if (RangeEnd == -1) { RangeEnd = (int)(segIndex - startIndex - 1); } int newCount = 0; double newTotalDuration = 0; JArray newParts = new JArray(); foreach (JArray part in parts) { JArray newPart = new JArray(); foreach (var seg in part) { if (RangeStart <= seg["index"].Value <int>() && seg["index"].Value <int>() <= RangeEnd) { newPart.Add(seg); newCount++; newTotalDuration += Convert.ToDouble(seg["duration"].ToString()); } } if (newPart.Count != 0) { newParts.Add(newPart); } } parts = newParts; jsonM3u8Info["count"] = newCount; jsonM3u8Info["totalDuration"] = newTotalDuration; } //添加 jsonM3u8Info.Add("segments", parts); jsonResult.Add("m3u8Info", jsonM3u8Info); //输出JSON文件 if (!LiveStream) { LOGGER.WriteLine(strings.wrtingMeta); LOGGER.PrintLine(strings.wrtingMeta); } File.WriteAllText(jsonSavePath, jsonResult.ToString()); //检测是否为master list MasterListCheck(); }
public string[] ParseKey(string line) { if (!downloadingM3u8KeyTip) { LOGGER.PrintLine(strings.downloadingM3u8Key, LOGGER.Warning); downloadingM3u8KeyTip = true; } string[] tmp = line.Replace(HLSTags.ext_x_key + ":", "").Split(','); string[] key = new string[] { "NONE", "", "" }; string u_l = Global.GetTagAttribute(lastKeyLine.Replace(HLSTags.ext_x_key + ":", ""), "URI"); string method = Global.GetTagAttribute(line.Replace(HLSTags.ext_x_key + ":", ""), "METHOD"); string uri = Global.GetTagAttribute(line.Replace(HLSTags.ext_x_key + ":", ""), "URI"); string iv = Global.GetTagAttribute(line.Replace(HLSTags.ext_x_key + ":", ""), "IV"); //存在加密 if (method != "") { if (method != "AES-128") { LOGGER.PrintLine(string.Format(strings.notSupportMethod, method), LOGGER.Error); DownloadManager.BinaryMerge = true; return(new string[] { $"{method}(NOTSUPPORTED)", "", "" }); } //METHOD key[0] = method; //URI key[1] = uri; if (u_l == uri) { key[1] = m3u8CurrentKey[1]; } else { LOGGER.WriteLine(strings.downloadingM3u8Key + " " + key[1]); if (key[1].StartsWith("http")) { string keyUrl = key[1]; if (isQiQiuYun) { /*string encKey = Encoding.Default.GetString(Global.HttpDownloadFileToBytes(keyUrl, Headers)); * var indexs = "0-1-2-3-4-5-6-7-8-10-11-12-14-15-16-18".Split('-'); * if (encKey.Length == 20) * { * var algorithmCharCode = (int)Encoding.ASCII.GetBytes(encKey)[0]; * var algorithmChar = Encoding.ASCII.GetString(new byte[] { (byte)algorithmCharCode }); * var algorithmCharStart = Global.GetNum(algorithmChar, 36) % 7; * var firstAlgorithmCharCode = (int)Encoding.ASCII.GetBytes(encKey)[algorithmCharStart]; * var firstAlgorithmChar = Encoding.ASCII.GetString(new byte[] { (byte)firstAlgorithmCharCode }); * var secondAlgorithmCharCode = (int)Encoding.ASCII.GetBytes(encKey)[algorithmCharStart + 1]; * var secondAlgorithmChar = Encoding.ASCII.GetString(new byte[] { (byte)secondAlgorithmCharCode }); * var algorithmNum = Global.GetNum(firstAlgorithmChar + secondAlgorithmChar, 36) % 3; * * if (algorithmNum == 1) * { * indexs = "0-1-2-3-4-5-6-7-18-16-15-13-12-11-10-8".Split('-'); * } * else if (algorithmNum == 0) * { * indexs = "0-1-2-3-4-5-6-7-8-10-11-12-14-15-16-18".Split('-'); * } * else if (algorithmNum == 2) * { * var a_CODE = (int)Encoding.ASCII.GetBytes("a")[0]; * * var c9 = (int)Encoding.ASCII.GetBytes(encKey)[8]; * var c9t = (int)Encoding.ASCII.GetBytes(encKey)[9]; * var c10 = (int)Encoding.ASCII.GetBytes(encKey)[10]; * var c10t = (int)Encoding.ASCII.GetBytes(encKey)[11]; * var c14 = (int)Encoding.ASCII.GetBytes(encKey)[15]; * var c14t = (int)Encoding.ASCII.GetBytes(encKey)[16]; * var c15 = (int)Encoding.ASCII.GetBytes(encKey)[17]; * var c15t = (int)Encoding.ASCII.GetBytes(encKey)[18]; * * var c9r = c9 - a_CODE + (Global.GetNum(Encoding.ASCII.GetString(new byte[] { (byte)c9t }), 10) + 1) * 26 - a_CODE; * var c10r = c10 - a_CODE + (Global.GetNum(Encoding.ASCII.GetString(new byte[] { (byte)c10t }), 10) + 1) * 26 - a_CODE; * var c14r = c14 - a_CODE + (Global.GetNum(Encoding.ASCII.GetString(new byte[] { (byte)c14t }), 10) + 1) * 26 - a_CODE; * var c15r = c15 - a_CODE + (Global.GetNum(Encoding.ASCII.GetString(new byte[] { (byte)c15t }), 10) + 2) * 26 - a_CODE; * * //构造key * key[1] = Convert.ToBase64String( * new byte[] * { * Encoding.ASCII.GetBytes(encKey)[0], * Encoding.ASCII.GetBytes(encKey)[1], * Encoding.ASCII.GetBytes(encKey)[2], * Encoding.ASCII.GetBytes(encKey)[3], * Encoding.ASCII.GetBytes(encKey)[4], * Encoding.ASCII.GetBytes(encKey)[5], * Encoding.ASCII.GetBytes(encKey)[6], * Encoding.ASCII.GetBytes(encKey)[7], * (byte)c9r, * (byte)c10r, * Encoding.ASCII.GetBytes(encKey)[12], * Encoding.ASCII.GetBytes(encKey)[13], * Encoding.ASCII.GetBytes(encKey)[14], * (byte)c14r, * (byte)c15r, * Encoding.ASCII.GetBytes(encKey)[19] * } * ); * //IV * key[2] = i; * return key; * } * } * else if (encKey.Length == 17) * { * indexs = "1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16".Split('-'); * } * else * { * indexs = "1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16".Split('-'); * } * * string decKey = ""; * foreach (var _i in indexs) * { * decKey += encKey[Convert.ToInt32(_i)]; * } * key[1] = Convert.ToBase64String(Encoding.Default.GetBytes(decKey));*/ key[1] = Convert.ToBase64String(Global.HttpDownloadFileToBytes(keyUrl, "User-Agent:Mozilla/5.0 (Linux; U; Android 7.0; zh-cn; 15 Plus Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.4 Mobile Safari/537.36")); } //气球云 else if (key[1].Contains("imooc.com/")) { key[1] = DecodeImooc.DecodeKey(Global.GetWebSource(key[1], Headers)); } else { if (keyUrl.Contains("https://keydeliver.linetv.tw/jurassicPark")) //linetv { keyUrl = keyUrl + "?time=" + Global.GetTimeStamp(false); } key[1] = Convert.ToBase64String(Global.HttpDownloadFileToBytes(keyUrl, Headers)); } } //DMM网站 else if (key[1].StartsWith("base64:")) { key[1] = key[1].Replace("base64:", ""); } else { string keyUrl = CombineURL(BaseUrl, key[1]); if (keyUrl.Contains("edu.51cto.com")) //51cto { string lessonId = Global.GetQueryString("lesson_id", keyUrl); keyUrl = keyUrl + "&sign=" + Decode51CtoKey.GetSign(lessonId); var encodeKey = Encoding.UTF8.GetString(Global.HttpDownloadFileToBytes(keyUrl, Headers)); key[1] = Decode51CtoKey.GetDecodeKey(encodeKey, lessonId); } else { key[1] = Convert.ToBase64String(Global.HttpDownloadFileToBytes(keyUrl, Headers));//keyUrl读取key } } } //IV key[2] = iv; } return(key); }
public string[] ParseKey(string line) { if (!downloadingM3u8KeyTip) { LOGGER.PrintLine(strings.downloadingM3u8Key, LOGGER.Warning); downloadingM3u8KeyTip = true; } string[] tmp = line.Replace(HLSTags.ext_x_key + ":", "").Split(','); string[] key = new string[] { "NONE", "", "" }; string u_l = Global.GetTagAttribute(lastKeyLine.Replace(HLSTags.ext_x_key + ":", ""), "URI"); string m = Global.GetTagAttribute(line.Replace(HLSTags.ext_x_key + ":", ""), "METHOD"); string u = Global.GetTagAttribute(line.Replace(HLSTags.ext_x_key + ":", ""), "URI"); string i = Global.GetTagAttribute(line.Replace(HLSTags.ext_x_key + ":", ""), "IV"); //存在加密 if (m != "") { if (m != "AES-128") { LOGGER.PrintLine(string.Format(strings.notSupportMethod, m), LOGGER.Error); DownloadManager.BinaryMerge = true; return(new string[] { $"{m}(NOTSUPPORTED)", "", "" }); } //METHOD key[0] = m; //URI key[1] = u; if (u_l == u) { key[1] = m3u8CurrentKey[1]; } else { LOGGER.WriteLine(strings.downloadingM3u8Key + " " + key[1]); if (key[1].StartsWith("http")) { string keyUrl = key[1]; if (isQiQiuYun) { string encKey = Encoding.Default.GetString(Global.HttpDownloadFileToBytes(keyUrl, Headers)); var indexs = "0-1-2-3-4-5-6-7-8-10-11-12-14-15-16-18".Split('-'); if (encKey.Length == 20) { var algorithmCharCode = (int)Encoding.ASCII.GetBytes(encKey)[0]; var algorithmChar = Encoding.ASCII.GetString(new byte[] { (byte)algorithmCharCode }); var algorithmCharStart = Global.GetNum(algorithmChar, 36) % 7; var firstAlgorithmCharCode = (int)Encoding.ASCII.GetBytes(encKey)[algorithmCharStart]; var firstAlgorithmChar = Encoding.ASCII.GetString(new byte[] { (byte)firstAlgorithmCharCode }); var secondAlgorithmCharCode = (int)Encoding.ASCII.GetBytes(encKey)[algorithmCharStart + 1]; var secondAlgorithmChar = Encoding.ASCII.GetString(new byte[] { (byte)secondAlgorithmCharCode }); var algorithmNum = Global.GetNum(firstAlgorithmChar + secondAlgorithmChar, 36) % 3; if (algorithmNum == 1) { indexs = "0-1-2-3-4-5-6-7-18-16-15-13-12-11-10-8".Split('-'); } else if (algorithmNum == 0) { indexs = "0-1-2-3-4-5-6-7-8-10-11-12-14-15-16-18".Split('-'); } else if (algorithmNum == 2) { var a_CODE = (int)Encoding.ASCII.GetBytes("a")[0]; var c9 = (int)Encoding.ASCII.GetBytes(encKey)[8]; var c9t = (int)Encoding.ASCII.GetBytes(encKey)[9]; var c10 = (int)Encoding.ASCII.GetBytes(encKey)[10]; var c10t = (int)Encoding.ASCII.GetBytes(encKey)[11]; var c14 = (int)Encoding.ASCII.GetBytes(encKey)[15]; var c14t = (int)Encoding.ASCII.GetBytes(encKey)[16]; var c15 = (int)Encoding.ASCII.GetBytes(encKey)[17]; var c15t = (int)Encoding.ASCII.GetBytes(encKey)[18]; var c9r = c9 - a_CODE + (Global.GetNum(Encoding.ASCII.GetString(new byte[] { (byte)c9t }), 10) + 1) * 26 - a_CODE; var c10r = c10 - a_CODE + (Global.GetNum(Encoding.ASCII.GetString(new byte[] { (byte)c10t }), 10) + 1) * 26 - a_CODE; var c14r = c14 - a_CODE + (Global.GetNum(Encoding.ASCII.GetString(new byte[] { (byte)c14t }), 10) + 1) * 26 - a_CODE; var c15r = c15 - a_CODE + (Global.GetNum(Encoding.ASCII.GetString(new byte[] { (byte)c15t }), 10) + 2) * 26 - a_CODE; //构造key key[1] = Convert.ToBase64String( new byte[] { Encoding.ASCII.GetBytes(encKey)[0], Encoding.ASCII.GetBytes(encKey)[1], Encoding.ASCII.GetBytes(encKey)[2], Encoding.ASCII.GetBytes(encKey)[3], Encoding.ASCII.GetBytes(encKey)[4], Encoding.ASCII.GetBytes(encKey)[5], Encoding.ASCII.GetBytes(encKey)[6], Encoding.ASCII.GetBytes(encKey)[7], (byte)c9r, (byte)c10r, Encoding.ASCII.GetBytes(encKey)[12], Encoding.ASCII.GetBytes(encKey)[13], Encoding.ASCII.GetBytes(encKey)[14], (byte)c14r, (byte)c15r, Encoding.ASCII.GetBytes(encKey)[19] } ); //IV key[2] = i; return(key); } } else if (encKey.Length == 17) { indexs = "1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16".Split('-'); } else { indexs = "1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16".Split('-'); } string decKey = ""; foreach (var _i in indexs) { decKey += encKey[Convert.ToInt32(_i)]; } key[1] = Convert.ToBase64String(Encoding.Default.GetBytes(decKey)); } //气球云 else if (key[1].Contains("imooc.com/")) { key[1] = DecodeImooc.DecodeKey(Global.GetWebSource(key[1], Headers)); } else { if (keyUrl.Contains("https://keydeliver.linetv.tw/jurassicPark")) //linetv { keyUrl = keyUrl + "?time=" + Global.GetTimeStamp(false); } key[1] = Convert.ToBase64String(Global.HttpDownloadFileToBytes(keyUrl, Headers)); } } //DMM网站 else if (key[1].StartsWith("base64:")) { key[1] = key[1].Replace("base64:", ""); } else { string keyUrl = CombineURL(BaseUrl, key[1]); if (keyUrl.Contains("edu.51cto.com")) //51cto { string lessonId = Global.GetQueryString("lesson_id", keyUrl); keyUrl = keyUrl + "&sign=" + Decode51CtoKey.GetSign(lessonId); var encodeKey = Encoding.UTF8.GetString(Global.HttpDownloadFileToBytes(keyUrl, Headers)); key[1] = Decode51CtoKey.GetDecodeKey(encodeKey, lessonId); } else { key[1] = Convert.ToBase64String(Global.HttpDownloadFileToBytes(keyUrl, Headers)); } } } //IV key[2] = i; } return(key); }