diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 8038e25..3543070 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -8,6 +8,7 @@ using N_m3u8DL_RE.Util; using NiL.JS.Expressions; using System.CommandLine; using System.CommandLine.Binding; +using System.CommandLine.Builder; using System.CommandLine.Parsing; using System.Globalization; using System.Net; @@ -17,7 +18,7 @@ namespace N_m3u8DL_RE.CommandLine { internal partial class CommandInvoker { - public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20221029"; + public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20221030"; [GeneratedRegex("((best|worst)\\d*|all)")] private static partial Regex ForStrRegex(); @@ -450,7 +451,22 @@ namespace N_m3u8DL_RE.CommandLine rootCommand.TreatUnmatchedTokensAsErrors = true; rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder()); - return await rootCommand.InvokeAsync(args); + var parser = new CommandLineBuilder(rootCommand) + .UseDefaults() + .EnablePosixBundling(false) + .UseExceptionHandler((ex, context) => + { + try { Console.CursorVisible = true; } catch { } + string msg = Logger.LogLevel == Common.Log.LogLevel.DEBUG ? ex.ToString() : ex.Message; +#if DEBUG + msg = ex.ToString(); +#endif + Logger.Error(msg); + Thread.Sleep(3000); + }, 1) + .Build(); + + return await parser.InvokeAsync(args); } } } diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index b5432d3..4ada5aa 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -78,272 +78,260 @@ namespace N_m3u8DL_RE HTTPUtil.HttpClientHandler.UseProxy = true; } - try + //检查互斥的选项 + + if (!option.MuxAfterDone && option.MuxImports != null && option.MuxImports.Count > 0) { - //检查互斥的选项 + throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!"); + } - if (!option.MuxAfterDone && option.MuxImports != null && option.MuxImports.Count > 0) + //预先检查ffmpeg + if (option.FFmpegBinaryPath == null) + option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg"); + + if (string.IsNullOrEmpty(option.FFmpegBinaryPath) || !File.Exists(option.FFmpegBinaryPath)) + { + throw new FileNotFoundException(ResString.ffmpegNotFound); + } + + //预先检查mkvmerge + if (option.UseMkvmerge && option.MuxAfterDone) + { + if (option.MkvmergeBinaryPath == null) + option.MkvmergeBinaryPath = GlobalUtil.FindExecutable("mkvmerge"); + if (string.IsNullOrEmpty(option.MkvmergeBinaryPath) || !File.Exists(option.MkvmergeBinaryPath)) { - throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!"); - } - - //预先检查ffmpeg - if (option.FFmpegBinaryPath == null) - option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg"); - - if (string.IsNullOrEmpty(option.FFmpegBinaryPath) || !File.Exists(option.FFmpegBinaryPath)) - { - throw new FileNotFoundException(ResString.ffmpegNotFound); - } - - //预先检查mkvmerge - if (option.UseMkvmerge && option.MuxAfterDone) - { - if (option.MkvmergeBinaryPath == null) - option.MkvmergeBinaryPath = GlobalUtil.FindExecutable("mkvmerge"); - if (string.IsNullOrEmpty(option.MkvmergeBinaryPath) || !File.Exists(option.MkvmergeBinaryPath)) - { - throw new FileNotFoundException("mkvmerge not found"); - } - } - - //预先检查 - if ((option.Keys != null && option.Keys.Length > 0) || option.KeyTextFile != null) - { - if (string.IsNullOrEmpty(option.DecryptionBinaryPath)) - { - if (option.UseShakaPackager) - { - var file = GlobalUtil.FindExecutable("shaka-packager"); - var file2 = GlobalUtil.FindExecutable("packager-linux-x64"); - var file3 = GlobalUtil.FindExecutable("packager-osx-x64"); - var file4 = GlobalUtil.FindExecutable("packager-win-x64"); - if (file == null && file2 == null && file3 == null && file4 == null) throw new FileNotFoundException("shaka-packager not found!"); - option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4; - } - else - { - var file = GlobalUtil.FindExecutable("mp4decrypt"); - if (file == null) throw new FileNotFoundException("mp4decrypt not found!"); - option.DecryptionBinaryPath = file; - } - } - else if (!File.Exists(option.DecryptionBinaryPath)) - { - throw new FileNotFoundException(option.DecryptionBinaryPath); - } - } - - //默认的headers - var headers = new Dictionary() - { - ["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" - }; - //添加或替换用户输入的headers - foreach (var item in option.Headers) - { - headers[item.Key] = item.Value; - } - - var parserConfig = new ParserConfig() - { - AppendUrlParams = option.AppendUrlParams, - UrlProcessorArgs = option.UrlProcessorArgs, - BaseUrl = option.BaseUrl!, - Headers = headers, - CustomMethod = option.CustomHLSMethod, - CustomeKey = option.CustomHLSKey, - CustomeIV = option.CustomHLSIv, - }; - - //demo1 - parserConfig.ContentProcessors.Insert(0, new DemoProcessor()); - //demo2 - parserConfig.KeyProcessors.Insert(0, new DemoProcessor2()); - //for www.nowehoryzonty.pl - parserConfig.UrlProcessors.Insert(0, new NowehoryzontyUrlProcessor()); - - var url = string.Empty; - //url = "https://vod-ftc-eu-west-1.media.dssott.com/ps01/disney/29a73209-b706-4f21-8384-acddccb154d2/ctr-all-6cf6fec6-94dc-4f8b-ae67-5ada60ee1e83-42bd5bca-f9a8-4299-83e3-0fb2b4ec0a62.m3u8"; //迪士尼 - //url = "https://play.itunes.apple.com/WebObjects/MZPlay.woa/hls/subscription/playlist.m3u8?cc=US&svcId=tvs.vds.4105&a=1580273278&isExternal=true&brandId=tvs.sbd.4000&id=337246031&l=en-US&aec=UHD&xtrick=true&webbrowser=true"; //啥都有 - //url = "https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd"; //多音轨多字幕 - //url = "https://cmafref.akamaized.net/cmaf/live-ull/2006350/akambr/out.mpd"; //直播 - //url = "http://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8"; - //url = "https://vod.sdn.wavve.com/hls/S01/S01_E461382925.1/1/5000/chunklist.m3u8"; - url = "https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd"; - //url = "http://tv-live.ynkmit.com/tv/anning.m3u8?txSecret=7528f35fb4b62bd24d55b891899db68f&txTime=632C8680"; //直播 - //url = "https://rest-as.ott.kaltura.com/api_v3/service/assetFile/action/playManifest/partnerId/147/assetId/1304099/assetType/media/assetFileId/16136929/contextType/PLAYBACK/isAltUrl/False/ks/djJ8MTQ3fMusTFH6PCZpcrfKLQwI-pPm9ex6b6r49wioe32WH2udXeM4reyWIkSDpi7HhvhxBHAHAKiHrcnkmIJQpyAt4MuDBG0ywGQ-jOeqQFcTRQ8BGJGw6g-smSBLwSbo4CCx9M9vWNJX3GkOfhoMAY4yRU-ur3okHiVq1mUJ82XBd_iVqLuzodnc9sJEtcHH0zc5CoPiTq2xor-dq3yDURnZm3isfSN3t9uLIJEW09oE-SJ84DM5GUuFUdbnIV8bdcWUsPicUg-Top1G2D3WcWXq4EvPnwvD8jrC_vsiOpLHf5akAwtdGsJ6__cXUmT7a-QlfjdvaZ5T8UhDLnttHmsxYs2E5c0lh4uOvvJou8dD8iYxUexlPI2j4QUkBRxqOEVLSNV3Y82-5TTRqgnK_uGYXHwk7EAmDws7hbLj2-DJ1heXDcye3OJYdunJgAS-9ma5zmQQNiY_HYh6wj2N1HpCTNAtWWga6R9fC0VgBTZbidW-YwMSGzIvMQfIfWKe15X7Oc_hCs-zGfW9XeRJZrutcWKK_D_HlzpQVBF2vIF3XgaI/a.mpd"; - //url = "https://dash.akamaized.net/dash264/TestCases/2c/qualcomm/1/MultiResMPEG2.mpd"; - url = "https://cmaf.lln.latam.hbomaxcdn.com/videos/GYPGKMQjoDkVLBQEAAAAo/1/1b5ad5/1_single_J8sExA_1080hi.mpd"; - //url = "https://livesim.dashif.org/dash/vod/testpic_2s/multi_subs.mpd"; //ttml + mp4 - //url = "http://media.axprod.net/TestVectors/v6-Clear/Manifest_1080p.mpd"; //vtt + mp4 - //url = "https://livesim.dashif.org/dash/vod/testpic_2s/xml_subs.mpd"; //ttml - //url = "https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8"; //HLS vtt - //url = "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8"; //高级HLS fMP4+VTT - //url = "https://events-delivery.apple.com/0205eyyhwbbqexozkwmgccegwnjyrktg/m3u8/vod_index-dpyfrsVksFWjneFiptbXnAMYBtGYbXeZ.m3u8"; //高级HLS fMP4+VTT - //url = "https://apionvod5.seezntv.com/ktmain1/cold/CP/55521/202207/media/MIAM61RPSGL150000100_DRM/MIAM61RPSGL150000100_H.m3u8?sid=0000000F50000040000A700000020000"; - //url = "https://ewcdn12.nowe.com/session/16-5-72579e3-2103014898783810281/Content/DASH_VOS3/VOD/6908/19585/d2afa5fe-e9c8-40f0-8d18-648aaaf292b6/f677841a-9d8f-2ff5-3517-674ba49ef192/manifest.mpd?token=894db5d69931835f82dd8e393974ef9f_1658146180"; - //url = "https://ols-ww100-cp.akamaized.net/manifest/master/06ee6f68-ee80-11ea-9bc5-02b68fb543c4/65794a72596d6c30496a6f7a4e6a67324e4441774d444173496e42735958526d62334a74496a6f695a47567a6133527663434973496d526c646d6c6a5a565235634755694f694a335a5749694c434a746232526c62434936496e6470626d527664334d694c434a7663315235634755694f694a6a61484a76625755694c434a7663794936496a45774d6934774c6a41694c434a68634841694f69497a4c6a416966513d3d/dash.mpd?cpatoken=exp=1658223027~acl=/manifest/master/06ee6f68-ee80-11ea-9bc5-02b68fb543c4/*~hmac=644c608aac361f688e9b24b0f345c801d0f2d335819431d1873ff7aeac46d6b2&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXZpY2VfaWQiOm51bGwsIndhdGNoX3R5cGUiOiJQUkVNSVVNIiwicHJvZ3JhbV9pZCI6ImUwMWRmYjAyLTM1YmItMTFlOS1hNDI3LTA2YTA0MTdjMWQxZSIsImFkX3RhZyI6ZmFsc2UsInBhcmVudF9wcm9ncmFtX2lkIjoiZmJmMDc2MDYtMzNmYi0xMWU5LWE0MjctMDZhMDQxN2MxZDFlIiwiY2xpZW50X2lkIjoiNGQ3MDViZTQtYTQ5ZS0xMWVhLWJiMzctMDI0MmFjMTMwMDAyIiwidmlkZW9fdHlwZSI6InZvZCIsImdyYW50X3R5cGUiOiJwbGF5X3ZpZGVvIiwidXNlcl9pZCI6ImFhNTMxZWQ2LWM2NTMtNDliYS04NGI1LWFkZDRmNGIzNGMyNyIsImN1cnJlbnRfc2Vjb25kIjowLCJyZXBvcnRfaWQiOiJOU1RHIiwic2NvcGUiOlsicHVibGljOi4qIiwibWU6LioiXSwiZXhwIjoxNjU4Mzk1ODI2LCJkZXRlY3Rpb25faWQiOm51bGwsInZpZGVvX2lkIjoiODc0Yjk0ZDItNzZiYi00YzliLTgzODQtNzJlMTA0NWVjOGMxIiwiaXNzIjoiQXNpYXBsYXktT0F1dGgtU2VydmVyIiwiaWF0IjoxNjU4MTM2NjI2LCJ0ZXJyaXRvcnkiOiJUVyJ9.1juciYIyMNzykXKu-nGLR_cYWvPMEAE9ub-ny7RzFnM"; - //url = "https://a38avoddashs3ww-a.akamaihd.net/ondemand/iad_2/8e91/f2f2/ec5a/430f-bd7a-0779f4a0189d/685cda75-609c-41c1-86bb-688f4cdb5521_corrected.mpd"; - //url = "https://dcs-vod.mp.lura.live/vod/p/session/manifest.mpd?i=i177610817-nb45239a2-e962-4137-bc70-1790359619e6"; - //url = "https://theater.kktv.com.tw/98/04000198010001_584b26392f7f7f11fc62299214a55fb7/16113081449d8d5e9960_sub_dash.mpd"; //MPD+VTT - //url = "https://vsl.play.kakao.com/vod/rvty90n7btua6u9oebr97i8zl/dash/vhs/cenc/adaptive.mpd?e=1658297362&p=71&h=53766bdde112d59da2b2514e8ab41e81"; //需要补params - //url = "https://a38avoddashs3ww-a.akamaihd.net/ondemand/iad_2/8e91/f2f2/ec5a/430f-bd7a-0779f4a0189d/685cda75-609c-41c1-86bb-688f4cdb5521_corrected.mpd"; - //url = ""; - - if (!string.IsNullOrEmpty(option.Input)) - { - url = option.Input; - } - - if (string.IsNullOrEmpty(url)) - { - url = AnsiConsole.Ask("Input [green]URL[/]: "); - } - - //流提取器配置 - var extractor = new StreamExtractor(parserConfig); - extractor.LoadSourceFromUrl(url); - - //解析流信息 - var streams = await extractor.ExtractStreamsAsync(); - - //全部媒体 - var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder); - //基本流 - var basicStreams = lists.Where(x => x.MediaType == null || x.MediaType == MediaType.VIDEO); - //可选音频轨道 - var audios = lists.Where(x => x.MediaType == MediaType.AUDIO); - //可选字幕轨道 - var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES); - - if (option.WriteMetaJson) - { - Logger.Warn(ResString.writeJson); - await File.WriteAllTextAsync("meta.json", GlobalUtil.ConvertToJson(lists), Encoding.UTF8); - } - - Logger.Info(ResString.streamsInfo, lists.Count(), basicStreams.Count(), audios.Count(), subs.Count()); - - foreach (var item in lists) - { - Logger.InfoMarkUp(item.ToString()); - } - - var selectedStreams = new List(); - if (option.AutoSelect) - { - if (basicStreams.Any()) - selectedStreams.Add(basicStreams.First()); - var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language); - foreach (var lang in langs) - { - selectedStreams.Add(audios.Where(a => a.Language == lang).OrderByDescending(a => a.Bandwidth).ThenByDescending(GetOrder).First()); - } - selectedStreams.AddRange(subs); - } - else if (option.SubOnly) - { - selectedStreams.AddRange(subs); - } - else if (option.VideoFilter != null || option.AudioFilter != null || option.SubtitleFilter != null) - { - basicStreams = FilterUtil.DoFilter(basicStreams, option.VideoFilter); - audios = FilterUtil.DoFilter(audios, option.AudioFilter); - subs = FilterUtil.DoFilter(subs, option.SubtitleFilter); - selectedStreams = basicStreams.Concat(audios).Concat(subs).ToList(); - } - else - { - //展示交互式选择框 - selectedStreams = FilterUtil.SelectStreams(lists); - } - - if (!selectedStreams.Any()) - throw new Exception(ResString.noStreamsToDownload); - - //HLS: 选中流中若有没加载出playlist的,加载playlist - //DASH: 加载playlist (调用url预处理器) - if (selectedStreams.Any(s => s.Playlist == null) || extractor.ExtractorType == ExtractorType.MPEG_DASH) - await extractor.FetchPlayListAsync(selectedStreams); - - //直播检测 - var livingFlag = selectedStreams.Any(s => s.Playlist?.IsLive == true) && !option.LivePerformAsVod; - if (livingFlag) - { - Logger.WarnMarkUp($"[white on darkorange3_1]{ResString.liveFound}[/]"); - } - - //无法识别的加密方式,自动开启二进制合并 - if (selectedStreams.Any(s => s.Playlist.MediaParts.Any(p => p.MediaSegments.Any(m => m.EncryptInfo.Method == EncryptMethod.UNKNOWN)))) - { - Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge3}[/]"); - } - - if (option.WriteMetaJson) - { - Logger.Warn(ResString.writeJson); - await File.WriteAllTextAsync("meta_selected.json", GlobalUtil.ConvertToJson(selectedStreams), Encoding.UTF8); - } - - Logger.Info(ResString.selectedStream); - foreach (var item in selectedStreams) - { - Logger.InfoMarkUp(item.ToString()); - } - - if (option.SkipDownload) - { - return; - } - -#if DEBUG - Console.ReadKey(); -#endif - - //尝试从URL或文件读取文件名 - if (string.IsNullOrEmpty(option.SaveName)) - { - option.SaveName = OtherUtil.GetFileNameFromInput(option.Input); - } - - Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]"); - - //下载配置 - var downloadConfig = new DownloaderConfig() - { - MyOptions = option, - Headers = parserConfig.Headers, //使用命令行解析得到的Headers - }; - - if (!livingFlag) - { - //开始下载 - var sdm = new SimpleDownloadManager(downloadConfig); - var result = await sdm.StartDownloadAsync(selectedStreams); - if (result) - Logger.InfoMarkUp("[white on green]Done[/]"); - else - Logger.ErrorMarkUp("[white on red]Faild[/]"); - } - else - { - var sldm = new SimpleLiveRecordManager2(downloadConfig, selectedStreams, extractor); - var result = await sldm.StartRecordAsync(); - if (result) - Logger.InfoMarkUp("[white on green]Done[/]"); - else - Logger.ErrorMarkUp("[white on red]Faild[/]"); + throw new FileNotFoundException("mkvmerge not found"); } } - catch (Exception ex) + + //预先检查 + if ((option.Keys != null && option.Keys.Length > 0) || option.KeyTextFile != null) { - string msg = Logger.LogLevel == LogLevel.DEBUG ? ex.ToString() : ex.Message; + if (string.IsNullOrEmpty(option.DecryptionBinaryPath)) + { + if (option.UseShakaPackager) + { + var file = GlobalUtil.FindExecutable("shaka-packager"); + var file2 = GlobalUtil.FindExecutable("packager-linux-x64"); + var file3 = GlobalUtil.FindExecutable("packager-osx-x64"); + var file4 = GlobalUtil.FindExecutable("packager-win-x64"); + if (file == null && file2 == null && file3 == null && file4 == null) throw new FileNotFoundException("shaka-packager not found!"); + option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4; + } + else + { + var file = GlobalUtil.FindExecutable("mp4decrypt"); + if (file == null) throw new FileNotFoundException("mp4decrypt not found!"); + option.DecryptionBinaryPath = file; + } + } + else if (!File.Exists(option.DecryptionBinaryPath)) + { + throw new FileNotFoundException(option.DecryptionBinaryPath); + } + } + + //默认的headers + var headers = new Dictionary() + { + ["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" + }; + //添加或替换用户输入的headers + foreach (var item in option.Headers) + { + headers[item.Key] = item.Value; + } + + var parserConfig = new ParserConfig() + { + AppendUrlParams = option.AppendUrlParams, + UrlProcessorArgs = option.UrlProcessorArgs, + BaseUrl = option.BaseUrl!, + Headers = headers, + CustomMethod = option.CustomHLSMethod, + CustomeKey = option.CustomHLSKey, + CustomeIV = option.CustomHLSIv, + }; + + //demo1 + parserConfig.ContentProcessors.Insert(0, new DemoProcessor()); + //demo2 + parserConfig.KeyProcessors.Insert(0, new DemoProcessor2()); + //for www.nowehoryzonty.pl + parserConfig.UrlProcessors.Insert(0, new NowehoryzontyUrlProcessor()); + + var url = string.Empty; + //url = "https://vod-ftc-eu-west-1.media.dssott.com/ps01/disney/29a73209-b706-4f21-8384-acddccb154d2/ctr-all-6cf6fec6-94dc-4f8b-ae67-5ada60ee1e83-42bd5bca-f9a8-4299-83e3-0fb2b4ec0a62.m3u8"; //迪士尼 + //url = "https://play.itunes.apple.com/WebObjects/MZPlay.woa/hls/subscription/playlist.m3u8?cc=US&svcId=tvs.vds.4105&a=1580273278&isExternal=true&brandId=tvs.sbd.4000&id=337246031&l=en-US&aec=UHD&xtrick=true&webbrowser=true"; //啥都有 + //url = "https://media.axprod.net/TestVectors/v7-Clear/Manifest_1080p.mpd"; //多音轨多字幕 + //url = "https://cmafref.akamaized.net/cmaf/live-ull/2006350/akambr/out.mpd"; //直播 + //url = "http://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8"; + //url = "https://vod.sdn.wavve.com/hls/S01/S01_E461382925.1/1/5000/chunklist.m3u8"; + url = "https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd"; + //url = "http://tv-live.ynkmit.com/tv/anning.m3u8?txSecret=7528f35fb4b62bd24d55b891899db68f&txTime=632C8680"; //直播 + //url = "https://rest-as.ott.kaltura.com/api_v3/service/assetFile/action/playManifest/partnerId/147/assetId/1304099/assetType/media/assetFileId/16136929/contextType/PLAYBACK/isAltUrl/False/ks/djJ8MTQ3fMusTFH6PCZpcrfKLQwI-pPm9ex6b6r49wioe32WH2udXeM4reyWIkSDpi7HhvhxBHAHAKiHrcnkmIJQpyAt4MuDBG0ywGQ-jOeqQFcTRQ8BGJGw6g-smSBLwSbo4CCx9M9vWNJX3GkOfhoMAY4yRU-ur3okHiVq1mUJ82XBd_iVqLuzodnc9sJEtcHH0zc5CoPiTq2xor-dq3yDURnZm3isfSN3t9uLIJEW09oE-SJ84DM5GUuFUdbnIV8bdcWUsPicUg-Top1G2D3WcWXq4EvPnwvD8jrC_vsiOpLHf5akAwtdGsJ6__cXUmT7a-QlfjdvaZ5T8UhDLnttHmsxYs2E5c0lh4uOvvJou8dD8iYxUexlPI2j4QUkBRxqOEVLSNV3Y82-5TTRqgnK_uGYXHwk7EAmDws7hbLj2-DJ1heXDcye3OJYdunJgAS-9ma5zmQQNiY_HYh6wj2N1HpCTNAtWWga6R9fC0VgBTZbidW-YwMSGzIvMQfIfWKe15X7Oc_hCs-zGfW9XeRJZrutcWKK_D_HlzpQVBF2vIF3XgaI/a.mpd"; + //url = "https://dash.akamaized.net/dash264/TestCases/2c/qualcomm/1/MultiResMPEG2.mpd"; + url = "https://cmaf.lln.latam.hbomaxcdn.com/videos/GYPGKMQjoDkVLBQEAAAAo/1/1b5ad5/1_single_J8sExA_1080hi.mpd"; + //url = "https://livesim.dashif.org/dash/vod/testpic_2s/multi_subs.mpd"; //ttml + mp4 + //url = "http://media.axprod.net/TestVectors/v6-Clear/Manifest_1080p.mpd"; //vtt + mp4 + //url = "https://livesim.dashif.org/dash/vod/testpic_2s/xml_subs.mpd"; //ttml + //url = "https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8"; //HLS vtt + //url = "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8"; //高级HLS fMP4+VTT + //url = "https://events-delivery.apple.com/0205eyyhwbbqexozkwmgccegwnjyrktg/m3u8/vod_index-dpyfrsVksFWjneFiptbXnAMYBtGYbXeZ.m3u8"; //高级HLS fMP4+VTT + //url = "https://apionvod5.seezntv.com/ktmain1/cold/CP/55521/202207/media/MIAM61RPSGL150000100_DRM/MIAM61RPSGL150000100_H.m3u8?sid=0000000F50000040000A700000020000"; + //url = "https://ewcdn12.nowe.com/session/16-5-72579e3-2103014898783810281/Content/DASH_VOS3/VOD/6908/19585/d2afa5fe-e9c8-40f0-8d18-648aaaf292b6/f677841a-9d8f-2ff5-3517-674ba49ef192/manifest.mpd?token=894db5d69931835f82dd8e393974ef9f_1658146180"; + //url = "https://ols-ww100-cp.akamaized.net/manifest/master/06ee6f68-ee80-11ea-9bc5-02b68fb543c4/65794a72596d6c30496a6f7a4e6a67324e4441774d444173496e42735958526d62334a74496a6f695a47567a6133527663434973496d526c646d6c6a5a565235634755694f694a335a5749694c434a746232526c62434936496e6470626d527664334d694c434a7663315235634755694f694a6a61484a76625755694c434a7663794936496a45774d6934774c6a41694c434a68634841694f69497a4c6a416966513d3d/dash.mpd?cpatoken=exp=1658223027~acl=/manifest/master/06ee6f68-ee80-11ea-9bc5-02b68fb543c4/*~hmac=644c608aac361f688e9b24b0f345c801d0f2d335819431d1873ff7aeac46d6b2&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXZpY2VfaWQiOm51bGwsIndhdGNoX3R5cGUiOiJQUkVNSVVNIiwicHJvZ3JhbV9pZCI6ImUwMWRmYjAyLTM1YmItMTFlOS1hNDI3LTA2YTA0MTdjMWQxZSIsImFkX3RhZyI6ZmFsc2UsInBhcmVudF9wcm9ncmFtX2lkIjoiZmJmMDc2MDYtMzNmYi0xMWU5LWE0MjctMDZhMDQxN2MxZDFlIiwiY2xpZW50X2lkIjoiNGQ3MDViZTQtYTQ5ZS0xMWVhLWJiMzctMDI0MmFjMTMwMDAyIiwidmlkZW9fdHlwZSI6InZvZCIsImdyYW50X3R5cGUiOiJwbGF5X3ZpZGVvIiwidXNlcl9pZCI6ImFhNTMxZWQ2LWM2NTMtNDliYS04NGI1LWFkZDRmNGIzNGMyNyIsImN1cnJlbnRfc2Vjb25kIjowLCJyZXBvcnRfaWQiOiJOU1RHIiwic2NvcGUiOlsicHVibGljOi4qIiwibWU6LioiXSwiZXhwIjoxNjU4Mzk1ODI2LCJkZXRlY3Rpb25faWQiOm51bGwsInZpZGVvX2lkIjoiODc0Yjk0ZDItNzZiYi00YzliLTgzODQtNzJlMTA0NWVjOGMxIiwiaXNzIjoiQXNpYXBsYXktT0F1dGgtU2VydmVyIiwiaWF0IjoxNjU4MTM2NjI2LCJ0ZXJyaXRvcnkiOiJUVyJ9.1juciYIyMNzykXKu-nGLR_cYWvPMEAE9ub-ny7RzFnM"; + //url = "https://a38avoddashs3ww-a.akamaihd.net/ondemand/iad_2/8e91/f2f2/ec5a/430f-bd7a-0779f4a0189d/685cda75-609c-41c1-86bb-688f4cdb5521_corrected.mpd"; + //url = "https://dcs-vod.mp.lura.live/vod/p/session/manifest.mpd?i=i177610817-nb45239a2-e962-4137-bc70-1790359619e6"; + //url = "https://theater.kktv.com.tw/98/04000198010001_584b26392f7f7f11fc62299214a55fb7/16113081449d8d5e9960_sub_dash.mpd"; //MPD+VTT + //url = "https://vsl.play.kakao.com/vod/rvty90n7btua6u9oebr97i8zl/dash/vhs/cenc/adaptive.mpd?e=1658297362&p=71&h=53766bdde112d59da2b2514e8ab41e81"; //需要补params + //url = "https://a38avoddashs3ww-a.akamaihd.net/ondemand/iad_2/8e91/f2f2/ec5a/430f-bd7a-0779f4a0189d/685cda75-609c-41c1-86bb-688f4cdb5521_corrected.mpd"; + //url = ""; + + if (!string.IsNullOrEmpty(option.Input)) + { + url = option.Input; + } + + if (string.IsNullOrEmpty(url)) + { + url = AnsiConsole.Ask("Input [green]URL[/]: "); + } + + //流提取器配置 + var extractor = new StreamExtractor(parserConfig); + extractor.LoadSourceFromUrl(url); + + //解析流信息 + var streams = await extractor.ExtractStreamsAsync(); + + //全部媒体 + var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder); + //基本流 + var basicStreams = lists.Where(x => x.MediaType == null || x.MediaType == MediaType.VIDEO); + //可选音频轨道 + var audios = lists.Where(x => x.MediaType == MediaType.AUDIO); + //可选字幕轨道 + var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES); + + if (option.WriteMetaJson) + { + Logger.Warn(ResString.writeJson); + await File.WriteAllTextAsync("meta.json", GlobalUtil.ConvertToJson(lists), Encoding.UTF8); + } + + Logger.Info(ResString.streamsInfo, lists.Count(), basicStreams.Count(), audios.Count(), subs.Count()); + + foreach (var item in lists) + { + Logger.InfoMarkUp(item.ToString()); + } + + var selectedStreams = new List(); + if (option.AutoSelect) + { + if (basicStreams.Any()) + selectedStreams.Add(basicStreams.First()); + var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language); + foreach (var lang in langs) + { + selectedStreams.Add(audios.Where(a => a.Language == lang).OrderByDescending(a => a.Bandwidth).ThenByDescending(GetOrder).First()); + } + selectedStreams.AddRange(subs); + } + else if (option.SubOnly) + { + selectedStreams.AddRange(subs); + } + else if (option.VideoFilter != null || option.AudioFilter != null || option.SubtitleFilter != null) + { + basicStreams = FilterUtil.DoFilter(basicStreams, option.VideoFilter); + audios = FilterUtil.DoFilter(audios, option.AudioFilter); + subs = FilterUtil.DoFilter(subs, option.SubtitleFilter); + selectedStreams = basicStreams.Concat(audios).Concat(subs).ToList(); + } + else + { + //展示交互式选择框 + selectedStreams = FilterUtil.SelectStreams(lists); + } + + if (!selectedStreams.Any()) + throw new Exception(ResString.noStreamsToDownload); + + //HLS: 选中流中若有没加载出playlist的,加载playlist + //DASH: 加载playlist (调用url预处理器) + if (selectedStreams.Any(s => s.Playlist == null) || extractor.ExtractorType == ExtractorType.MPEG_DASH) + await extractor.FetchPlayListAsync(selectedStreams); + + //直播检测 + var livingFlag = selectedStreams.Any(s => s.Playlist?.IsLive == true) && !option.LivePerformAsVod; + if (livingFlag) + { + Logger.WarnMarkUp($"[white on darkorange3_1]{ResString.liveFound}[/]"); + } + + //无法识别的加密方式,自动开启二进制合并 + if (selectedStreams.Any(s => s.Playlist.MediaParts.Any(p => p.MediaSegments.Any(m => m.EncryptInfo.Method == EncryptMethod.UNKNOWN)))) + { + Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge3}[/]"); + } + + if (option.WriteMetaJson) + { + Logger.Warn(ResString.writeJson); + await File.WriteAllTextAsync("meta_selected.json", GlobalUtil.ConvertToJson(selectedStreams), Encoding.UTF8); + } + + Logger.Info(ResString.selectedStream); + foreach (var item in selectedStreams) + { + Logger.InfoMarkUp(item.ToString()); + } + + if (option.SkipDownload) + { + return; + } + #if DEBUG - msg = ex.ToString(); + Console.ReadKey(); #endif - Logger.Error(msg); - await Task.Delay(3000); + + //尝试从URL或文件读取文件名 + if (string.IsNullOrEmpty(option.SaveName)) + { + option.SaveName = OtherUtil.GetFileNameFromInput(option.Input); + } + + Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]"); + + //下载配置 + var downloadConfig = new DownloaderConfig() + { + MyOptions = option, + Headers = parserConfig.Headers, //使用命令行解析得到的Headers + }; + + if (!livingFlag) + { + //开始下载 + var sdm = new SimpleDownloadManager(downloadConfig); + var result = await sdm.StartDownloadAsync(selectedStreams); + if (result) + Logger.InfoMarkUp("[white on green]Done[/]"); + else + Logger.ErrorMarkUp("[white on red]Faild[/]"); + } + else + { + var sldm = new SimpleLiveRecordManager2(downloadConfig, selectedStreams, extractor); + var result = await sldm.StartRecordAsync(); + if (result) + Logger.InfoMarkUp("[white on green]Done[/]"); + else + Logger.ErrorMarkUp("[white on red]Faild[/]"); } } }