diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index d6cc83f..475cc74 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -288,9 +288,9 @@ namespace N_m3u8DL_RE.Common.Resource ), ["cmd_customHLSMethod"] = new TextContainer ( - zhCN: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)", - zhTW: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)", - enUS: "Set HLS encryption method (AES_128|AES_128_ECB|CENC|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)" + zhCN: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|PLAYREADY|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)", + zhTW: "指定HLS加密方式 (AES_128|AES_128_ECB|CENC|PLAYREADY|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)", + enUS: "Set HLS encryption method (AES_128|AES_128_ECB|CENC|PLAYREADY|CHACHA20|NONE|SAMPLE_AES|SAMPLE_AES_CTR|UNKNOWN)" ), ["cmd_customHLSKey"] = new TextContainer ( diff --git a/src/N_m3u8DL-RE.Parser/Extractor/MSSExtractor.cs b/src/N_m3u8DL-RE.Parser/Extractor/MSSExtractor.cs index 29b81a6..8a01bce 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/MSSExtractor.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/MSSExtractor.cs @@ -164,6 +164,11 @@ namespace N_m3u8DL_RE.Parser.Extractor var _duration = Convert.ToInt64(_durationStr); var timescale = Convert.ToInt32(timeScaleStr); var _repeatCount = Convert.ToInt64(_repeatCountStr); + if (_repeatCount > 0) + { + // This value is one-based. (A value of 2 means two fragments in the contiguous series). + _repeatCount -= 1; + } varDic[MSSTags.StartTime] = currentTime; var oriUrl = ParserUtil.CombineURL(this.BaseUrl, urlPattern!); @@ -215,10 +220,6 @@ namespace N_m3u8DL_RE.Parser.Extractor ProtectionData = protectionData, ProtectionSystemID = protectionSystemId, }; - if (streamSpec.MSSData.Duration == 0) - { - streamSpec.MSSData.Duration = streamSpec.MSSData.Timesacle; - } var processor = new MSSMoovProcessor(streamSpec); var header = processor.GenHeader(); //trackId可能不正确 streamSpec.Playlist!.MediaInit!.Url = $"base64://{Convert.ToBase64String(header)}"; diff --git a/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs b/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs index 3d610f8..b57045c 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs @@ -37,6 +37,7 @@ namespace N_m3u8DL_RE.Parser.Mp4 private string ProtectionSystemId; private string ProtectionData; private string ProtecitonKID; + private string ProtecitonKID_PR; private byte[] UnityMatrix { get @@ -165,6 +166,8 @@ namespace N_m3u8DL_RE.Parser.Mp4 var bytes = HexUtil.HexToBytes(ProtectionData.Replace("00", "")); var text = Encoding.ASCII.GetString(bytes); var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value); + //save kid for playready + this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes); //fix byte order var reverse1 = new byte[4] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] }; var reverse2 = new byte[4] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] }; @@ -244,7 +247,7 @@ namespace N_m3u8DL_RE.Parser.Mp4 writer.Write("iso6"); //major brand writer.WriteUInt(1); //minor version writer.Write("isom"); //compatible brand - writer.Write("iso6"); //compatible brand + writer.Write("iso5"); //compatible brand writer.Write("msdh"); //compatible brand return Box("ftyp", stream.ToArray()); @@ -525,9 +528,9 @@ namespace N_m3u8DL_RE.Parser.Mp4 else if (FourCC == "HVC1" || FourCC == "HEV1") { var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries); - var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x3F) == 0x20).First()); - var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x3F) == 0x21).First()); - var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x3F) == 0x22).First()); + var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First()); + var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First()); + var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First()); //make hvcC var hvcC = GetHvcC(sps, pps, vps); writer.Write(hvcC); @@ -730,6 +733,35 @@ namespace N_m3u8DL_RE.Parser.Mp4 return FullBox("trex", 0, 0, stream.ToArray()); //Track Extends Box } + private byte[] GenPsshBoxForPlayReady() + { + using var _stream = new MemoryStream(); + using var _writer = new BinaryWriter2(_stream); + var sysIdData = HexUtil.HexToBytes(ProtectionSystemId.Replace("-", "")); + var psshData = HexUtil.HexToBytes(ProtectionData); + + _writer.Write(sysIdData); // SystemID 16 bytes + _writer.WriteUInt(psshData.Length); //Size of Data 4 bytes + _writer.Write(psshData); //Data + var psshBox = FullBox("pssh", 0, 0, _stream.ToArray()); + return psshBox; + } + + private byte[] GenPsshBoxForWideVine() + { + using var _stream = new MemoryStream(); + using var _writer = new BinaryWriter2(_stream); + var sysIdData = HexUtil.HexToBytes("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".Replace("-", "")); + //var kid = HexUtil.HexToBytes(ProtecitonKID); + + _writer.Write(sysIdData); // SystemID 16 bytes + var psshData = HexUtil.HexToBytes($"08011210{ProtecitonKID}1A046E647265220400000000"); + _writer.WriteUInt(psshData.Length); //Size of Data 4 bytes + _writer.Write(psshData); //Data + var psshBox = FullBox("pssh", 0, 0, _stream.ToArray()); + return psshBox; + } + public byte[] GenHeader(byte[] firstSegment) { new MP4Parser() @@ -801,12 +833,12 @@ namespace N_m3u8DL_RE.Parser.Mp4 var mvexBox = Box("mvex", mvexPayload); //Movie Extends Box moovPayload = moovPayload.Concat(mvexBox).ToArray(); - /*if (IsProtection) //gen pssh + if (IsProtection) { - var pssh = HexUtil.HexToBytes(ProtectionData); - var psshBox = FullBox("pssh", 1, 0, pssh); - moovPayload = moovPayload.Concat(psshBox).ToArray(); - }*/ + var psshBox1 = GenPsshBoxForPlayReady(); + var psshBox2 = GenPsshBoxForWideVine(); + moovPayload = moovPayload.Concat(psshBox1).Concat(psshBox2).ToArray(); + } var moovBox = Box("moov", moovPayload); //Movie Box diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index 61d5ecd..c402a12 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -178,7 +178,7 @@ namespace N_m3u8DL_RE.DownloadManager //从文件读取KEY await SearchKeyAsync(currentKID); //实时解密 - if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID)) + if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS) { var enc = result.ActualFilePath; var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); @@ -226,6 +226,17 @@ namespace N_m3u8DL_RE.DownloadManager var processor = new MSSMoovProcessor(streamSpec); var header = processor.GenHeader(File.ReadAllBytes(result.ActualFilePath)); await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header); + if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID)) + { + //需要重新解密init + var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath; + var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); + var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID); + if (dResult) + { + FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec; + } + } } //读取init信息 if (string.IsNullOrEmpty(currentKID)) diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs index 683dbdc..ea91816 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs @@ -241,7 +241,7 @@ namespace N_m3u8DL_RE.DownloadManager //从文件读取KEY await SearchKeyAsync(currentKID); //实时解密 - if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID)) + if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS) { var enc = result.ActualFilePath; var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); @@ -296,6 +296,17 @@ namespace N_m3u8DL_RE.DownloadManager var processor = new MSSMoovProcessor(streamSpec); var header = processor.GenHeader(File.ReadAllBytes(result.ActualFilePath)); await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header); + if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID)) + { + //需要重新解密init + var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath; + var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); + var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID); + if (dResult) + { + FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec; + } + } } //读取init信息 if (string.IsNullOrEmpty(currentKID))