コード例 #1
0
        public static string DecryptM3u8(byte[] byteArray)
        {
            string tmp = DecodeNfmovies.DecryptM3u8(byteArray);

            if (tmp.StartsWith("duoduo.key"))
            {
                tmp = Regex.Replace(tmp, @"#EXT-X-BYTERANGE:.*\s", "");
                tmp = tmp.Replace("https:", "jump/https:")
                      .Replace("inews.gtimg.com", "puui.qpic.cn");
            }
            return(tmp);
        }
コード例 #2
0
        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();
        }