├── .gitattributes ├── .gitignore ├── LICENSE ├── N_m3u8DL-CLI-SimpleG ├── App.config ├── App.xaml ├── App.xaml.cs ├── Helper.cs ├── M3u8TaskItem.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── N_m3u8DL-CLI-SimpleG_List.csproj ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.en-US.Designer.cs │ ├── Resources.en-US.resx │ ├── Resources.resx │ ├── Resources.zh-TW.Designer.cs │ ├── Resources.zh-TW.resx │ ├── Settings.Designer.cs │ └── Settings.settings └── logo_3Iv_icon.ico ├── N_m3u8DL-CLI-SimpleG_List.sln ├── README.md └── img └── screenshot.jpg /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 nilaoda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Windows; 12 | 13 | namespace N_m3u8DL_CLI_SimpleG 14 | { 15 | /// 16 | /// App.xaml 的交互逻辑 17 | /// 18 | public partial class App : Application 19 | { 20 | 21 | protected override void OnStartup(StartupEventArgs e) 22 | { 23 | 24 | 25 | //单进程:https://blog.csdn.net/smartmz/article/details/7264467 26 | Process process = Helper.RuningInstance(); 27 | if (process != null) 28 | { 29 | //唤醒已经运行的实例到前台 30 | //MessageBox.Show("应用程序已经在运行中。。。"); 31 | Helper.HandleRunningInstance(process); 32 | 33 | //process.Kill(); 34 | 35 | //把获得的启动参数,通过命名管道传递过去 36 | if (!(e.Args is null) && e.Args.Length > 0) 37 | { 38 | Helper.SendData(e.Args[0]); 39 | //await Helper.SendMessage(Helper.Args[0]); 40 | } 41 | 42 | 43 | //退出 44 | Application.Current.Shutdown(); 45 | Environment.Exit(1); 46 | } 47 | 48 | string loc = "en-US"; 49 | string currLoc = Thread.CurrentThread.CurrentUICulture.Name; 50 | if (currLoc == "zh-TW" || currLoc == "zh-HK" || currLoc == "zh-MO") loc = "zh-TW"; 51 | else if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-CN"; 52 | //设置语言 53 | Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(loc); 54 | 55 | //保存参数 56 | Helper.Args = e.Args; 57 | 58 | 59 | } 60 | 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Helper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Text.RegularExpressions; 10 | using System.Threading.Tasks; 11 | using System.IO; 12 | using System.IO.Pipes; 13 | using System.Security.Principal; 14 | using System.Windows; 15 | using System.Threading; 16 | 17 | namespace N_m3u8DL_CLI_SimpleG 18 | { 19 | public static class Helper 20 | { 21 | 22 | public static string pipeName = "N_m3u8DL_CLI_SimpleG_List_Pipe"; 23 | public static NamedPipeServerStream serverStream; 24 | public static NamedPipeClientStream clientStream; 25 | public static CancellationTokenSource cts = new CancellationTokenSource(); 26 | 27 | //m3u8dl协议传入的参数。由app启动的时候,传到这里暂存 28 | //再在main window的处理方法中去处理。 29 | public static string[] Args; 30 | 31 | 32 | 33 | public static bool ValidateUrlWithRegex(string url) 34 | { 35 | string pattern = @"^(https?|ftp)://[^\s/$.?#].[^\s]*$"; 36 | Regex regex = new Regex(pattern, RegexOptions.IgnoreCase); 37 | return regex.IsMatch(url); 38 | } 39 | 40 | public static string RegisterUriScheme(string scheme, string applicationPath) 41 | { 42 | string msg = "Registered m3u8DL Protocol"; 43 | 44 | try 45 | { 46 | using (var schemeKey = Registry.ClassesRoot.CreateSubKey(scheme, writable: true)) 47 | { 48 | schemeKey.SetValue("", "URL:m3u8DL Protocol"); 49 | schemeKey.SetValue("URL Protocol", ""); 50 | using (var defaultIconKey = schemeKey.CreateSubKey("DefaultIcon")) 51 | { 52 | defaultIconKey.SetValue("", $"\"{applicationPath}\",1"); 53 | } 54 | using (var shellKey = schemeKey.CreateSubKey("shell")) 55 | using (var openKey = shellKey.CreateSubKey("open")) 56 | using (var commandKey = openKey.CreateSubKey("command")) 57 | { 58 | commandKey.SetValue("", $"\"{applicationPath}\" \"%1\""); 59 | return msg; 60 | } 61 | } 62 | } 63 | catch (Exception e) 64 | { 65 | Console.WriteLine(e); 66 | return e.Message; 67 | } 68 | 69 | } 70 | 71 | public static string UnregisterUriScheme(string scheme) 72 | { 73 | string msg = "Unregistered m3u8DL Protocol"; 74 | 75 | try 76 | { 77 | Registry.ClassesRoot.DeleteSubKeyTree(scheme); 78 | return msg; 79 | } 80 | catch (Exception e) 81 | { 82 | return e.Message; 83 | } 84 | 85 | 86 | } 87 | 88 | 89 | [DllImport("shell32.dll", SetLastError = true)] 90 | static extern IntPtr CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs); 91 | //使用Win32 API解析字符串为命令行参数 92 | public static IEnumerable ParseArguments(string commandLine) 93 | { 94 | int argc; 95 | var argv = CommandLineToArgvW(commandLine, out argc); 96 | if (argv == IntPtr.Zero) 97 | throw new System.ComponentModel.Win32Exception(); 98 | try 99 | { 100 | var args = new string[argc]; 101 | for (var i = 0; i < args.Length; i++) 102 | { 103 | var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size); 104 | args[i] = Marshal.PtrToStringUni(p); 105 | } 106 | 107 | return args; 108 | } 109 | finally 110 | { 111 | Marshal.FreeHGlobal(argv); 112 | } 113 | } 114 | 115 | /// 116 | /// 该函数设置由不同线程产生的窗口的显示状态 117 | /// 118 | /// 窗口句柄 119 | /// 指定窗口如何显示。查看允许值列表,请查阅ShowWlndow函数的说明部分 120 | /// 如果函数原来可见,返回值为非零;如果函数原来被隐藏,返回值为零 121 | [DllImport("User32.dll")] 122 | public static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow); 123 | /// 124 | /// 该函数将创建指定窗口的线程设置到前台,并且激活该窗口。键盘输入转向该窗口,并为用户改各种可视的记号。 125 | /// 系统给创建前台窗口的线程分配的权限稍高于其他线程。 126 | /// 127 | /// 将被激活并被调入前台的窗口句柄 128 | /// 如果窗口设入了前台,返回值为非零;如果窗口未被设入前台,返回值为零 129 | [DllImport("User32.dll")] 130 | public static extern bool SetForegroundWindow(IntPtr hWnd); 131 | 132 | private const int SW_SHOWNOMAL = 1; 133 | public static void HandleRunningInstance(Process instance) 134 | { 135 | ShowWindowAsync(instance.MainWindowHandle, SW_SHOWNOMAL);//显示 136 | SetForegroundWindow(instance.MainWindowHandle);//唤醒到最前端 137 | } 138 | 139 | /// 140 | /// 单进程用 141 | /// 142 | /// 143 | public static Process RuningInstance() 144 | { 145 | Process currentProcess = Process.GetCurrentProcess(); 146 | Process[] Processes = Process.GetProcessesByName(currentProcess.ProcessName); 147 | 148 | foreach (Process process in Processes) 149 | { 150 | if (process.Id != currentProcess.Id) 151 | { 152 | if (Assembly.GetExecutingAssembly().Location.Replace("/", "\\") == currentProcess.MainModule.FileName) 153 | { 154 | return process; 155 | } 156 | } 157 | } 158 | return null; 159 | } 160 | 161 | /// 162 | /// 向命名管道发送数据。同步 163 | /// 164 | public static void SendData(string msg) 165 | { 166 | try 167 | { 168 | using (NamedPipeClientStream pipeClient =new NamedPipeClientStream(".", Helper.pipeName, PipeDirection.Out)) 169 | { 170 | pipeClient.Connect(); 171 | using (StreamWriter sw = new StreamWriter(pipeClient)) 172 | { 173 | sw.WriteLine(msg); 174 | sw.Flush(); 175 | } 176 | } 177 | } 178 | catch (Exception e) 179 | { 180 | MessageBox.Show($"发送管道消息出错:{e.Message}"); 181 | } 182 | 183 | } 184 | 185 | 186 | /// 187 | /// 通过命名管道实现进程间通讯。异步 188 | /// windows的自定义协议,只能执行指定程序,并传递参数。 189 | /// 但是对于已经运行中的程序,无法得到自定义协议的参数。 190 | /// 唯一解决办法是,在自定义协议启动的新实例中,找到已经运行中的实例。并通过命名管道,把信息传递过来。 191 | /// 管道默认会卡UI,所以要用异步管道: 192 | /// https://developer.aliyun.com/article/1312874 193 | /// 194 | public static async Task SendMessage(string messageToSend) 195 | { 196 | //创建命名管道-client 197 | clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous); 198 | // 连接到命名管道服务器 199 | await clientStream.ConnectAsync(); 200 | //转为byte[] 201 | byte[] data = Encoding.UTF8.GetBytes(messageToSend); 202 | //发送消息 203 | await clientStream.WriteAsync(data, 0, data.Length); 204 | //刷新 205 | await clientStream.FlushAsync(); 206 | } 207 | 208 | 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/M3u8TaskItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace N_m3u8DL_CLI_SimpleG 8 | { 9 | public class M3u8TaskItem 10 | { 11 | public string Name { get; set; } 12 | public string M3u8Url { get; set; } 13 | public string PageUrl { get; set; } 14 | public string Parameter { get; set; } 15 | public string Status { get; set; } 16 | 17 | public M3u8TaskItem(string name, string m3u8Url, string pageUrl, string parameter) 18 | { 19 | this.Name = name; 20 | this.M3u8Url = m3u8Url; 21 | this.PageUrl = pageUrl; 22 | this.Parameter = parameter; 23 | this.Status = ""; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.IO.Pipes; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Net.Security; 10 | using System.Reflection; 11 | using System.Runtime.Remoting.Messaging; 12 | using System.Security.Cryptography.X509Certificates; 13 | using System.Text; 14 | using System.Text.RegularExpressions; 15 | using System.Threading; 16 | using System.Threading.Tasks; 17 | using System.Web.UI.WebControls; 18 | using System.Windows; 19 | using System.Windows.Controls; 20 | using System.Windows.Data; 21 | using System.Windows.Documents; 22 | using System.Windows.Forms; 23 | using System.Windows.Forms.VisualStyles; 24 | using System.Windows.Input; 25 | using System.Windows.Markup; 26 | using System.Windows.Media; 27 | using System.Windows.Media.Animation; 28 | using System.Windows.Media.Imaging; 29 | using System.Windows.Navigation; 30 | using System.Windows.Shapes; 31 | using System.Windows.Threading; 32 | using System.Xml.Linq; 33 | using static System.Windows.Forms.VisualStyles.VisualStyleElement; 34 | using Application = System.Windows.Application; 35 | using Button = System.Windows.Controls.Button; 36 | using Clipboard = System.Windows.Clipboard; 37 | using DataFormats = System.Windows.DataFormats; 38 | using DragDropEffects = System.Windows.DragDropEffects; 39 | using MessageBox = System.Windows.MessageBox; 40 | using Path = System.IO.Path; 41 | using TextBox = System.Windows.Controls.TextBox; 42 | 43 | namespace N_m3u8DL_CLI_SimpleG 44 | { 45 | /// 46 | /// MainWindow.xaml 的交互逻辑 47 | /// 48 | /// 2019年6月17日 49 | /// - 重构界面并修复爱奇艺标题获取BUG 50 | /// 2019年6月18日 51 | /// - 添加图标 52 | /// 2019年6月23日 53 | /// - 调整寻找主程序的逻辑 54 | /// - 修改匹配URL的正则表达式 55 | /// - 启动时自动匹配URL并识别标题 56 | /// - 启动后M3U8地址文本框会自动获得焦点 57 | /// - M3U8地址和标题两个文本框能够响应回车事件 58 | /// - GO按钮点击可以使用ALT+S快捷键来触发 59 | /// 2019年7月24日 60 | /// - 优化获取视频标题的逻辑 61 | /// - 增加生成--downloadRange参数 62 | /// 2019年8月11日 63 | /// - 批量txt支持自定义文件名 64 | /// 2019年8月17日 65 | /// - 支持爱奇艺dash链接直接下载 66 | /// - 修复腾讯视频标题获取bug 67 | /// 2019年9月18日 68 | /// - 支持限速 69 | /// - 全新界面 70 | /// - 增加控件悬浮提示 71 | /// 2019年9月28日 72 | /// - 双击时判断URL是否一致再赋值 73 | /// - 细节优化 74 | /// 2019年10月9日 75 | /// - 自动获取文件编码 76 | /// 2019年10月24日 77 | /// - 请求dash链接时尝试读取iqiyicookie.txt 78 | /// 2019年12月16日 79 | /// - 批量读取txt跳过空白行 80 | /// - 腾讯Unicode转换 81 | /// 2020年2月1日 82 | /// - 修复部分wetv无法识别标题的问题 83 | /// 2020年2月17日 84 | /// - 拖入meta.json自动命名 85 | /// - 拖入KEY文件校验是否正确 86 | /// - 可调节大小 87 | /// 2020年4月17日 88 | /// - 修改BAT为UTF-8编码 89 | /// - 细微优化 90 | /// 2020年11月21日 91 | /// - 修正UI 92 | /// 2021年1月24日 93 | /// - 支持简繁英多语言 94 | /// 2021年3月4日 95 | /// - 支持设置代理 96 | /// - 支持存储代理、请求头 97 | /// 2021年3月21日 98 | /// - 支持MPD批量 99 | /// 2023年12月 100 | /// - 添加任务列表支持 101 | /// - Go按钮变成Add按钮,不是直接执行,而是添加到列表 102 | /// 103 | public partial class MainWindow : System.Windows.Window 104 | { 105 | public List m3u8_tasks; 106 | public M3u8TaskItem selected_task; 107 | public string tasks_status; 108 | 109 | //下载进程 110 | private Process m3u8dlProcess; 111 | private TaskCompletionSource m3u8dlEventHandled; 112 | //下载时,读取后台下载进程的log到UI用 113 | StreamReader logBlockReader; 114 | 115 | public MainWindow() 116 | { 117 | InitializeComponent(); 118 | TextBox_URL.Focus(); 119 | 120 | this.m3u8_tasks = new List(); 121 | this.tasks_status = ""; 122 | lbTaskList.ItemsSource = this.m3u8_tasks; 123 | 124 | HandleArgs(); 125 | 126 | 127 | //启动命名管道,接收数据 128 | //这是异步管道,不要等待结果,等待会卡UI。 129 | ReceiveDataAsync(); 130 | 131 | } 132 | 133 | 134 | //用于证书验证 135 | public static bool CertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) 136 | { 137 | X509Chain verify = new X509Chain(); 138 | verify.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; 139 | verify.ChainPolicy.RevocationMode = X509RevocationMode.Online; //revocation checking 140 | verify.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; 141 | if (verify.Build(new X509Certificate2(certificate))) 142 | { 143 | return true; 144 | } 145 | return false; 146 | } 147 | 148 | 149 | private void Button_SelectDir_Click(object sender, RoutedEventArgs e) 150 | { 151 | FolderBrowserDialog openFileDialog = new FolderBrowserDialog(); //选择文件夹 152 | openFileDialog.Description = Properties.Resources.String1; 153 | if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) 154 | { 155 | TextBox_WorkDir.Text = openFileDialog.SelectedPath; 156 | } 157 | } 158 | 159 | private void GetParameter() 160 | { 161 | if (TextBox_Parameter == null) 162 | return; 163 | 164 | StringBuilder sb = new StringBuilder(); 165 | sb.Append("\"" + TextBox_URL.Text + "\" "); 166 | if (!string.IsNullOrEmpty(TextBox_WorkDir.Text)) 167 | { 168 | if (TextBox_WorkDir.Text.Trim('\\').EndsWith(":")) //根目录 169 | { 170 | sb.Append("--workDir \"" + TextBox_WorkDir.Text.Trim('\\') + "\\\\" + "\" "); 171 | } 172 | else 173 | { 174 | sb.Append("--workDir \"" + TextBox_WorkDir.Text.Trim('\\') + "\" "); 175 | } 176 | } 177 | if (!string.IsNullOrEmpty(TextBox_Title.Text)) 178 | sb.Append("--saveName \"" + TextBox_Title.Text + "\" "); 179 | if (!string.IsNullOrEmpty(TextBox_Headers.Text)) 180 | sb.Append("--headers \"" + TextBox_Headers.Text + "\" "); 181 | if (!string.IsNullOrEmpty(TextBox_Baseurl.Text)) 182 | sb.Append("--baseUrl \"" + TextBox_Baseurl.Text + "\" "); 183 | if (!string.IsNullOrEmpty(TextBox_MuxJson.Text)) 184 | sb.Append("--muxSetJson \"" + TextBox_MuxJson.Text + "\" "); 185 | if (TextBox_Max.Text != "32") 186 | sb.Append("--maxThreads \"" + TextBox_Max.Text + "\" "); 187 | if (TextBox_Min.Text != "16") 188 | sb.Append("--minThreads \"" + TextBox_Min.Text + "\" "); 189 | if (TextBox_Retry.Text != "15") 190 | sb.Append("--retryCount \"" + TextBox_Retry.Text + "\" "); 191 | if (TextBox_Timeout.Text != "10") 192 | sb.Append("--timeOut \"" + TextBox_Timeout.Text + "\" "); 193 | if (TextBox_StopSpeed.Text != "0") 194 | sb.Append("--stopSpeed \"" + TextBox_StopSpeed.Text + "\" "); 195 | if (TextBox_MaxSpeed.Text != "0") 196 | sb.Append("--maxSpeed \"" + TextBox_MaxSpeed.Text + "\" "); 197 | if (TextBox_Key.Text != "") 198 | { 199 | if (File.Exists(TextBox_Key.Text)) 200 | sb.Append("--useKeyFile \"" + TextBox_Key.Text + "\" "); 201 | else 202 | sb.Append("--useKeyBase64 \"" + TextBox_Key.Text + "\" "); 203 | } 204 | if (TextBox_IV.Text != "") 205 | { 206 | sb.Append("--useKeyIV \"" + TextBox_IV.Text + "\" "); 207 | } 208 | if (TextBox_Proxy.Text != "") 209 | { 210 | sb.Append("--proxyAddress \"" + TextBox_Proxy.Text.Trim() + "\" "); 211 | } 212 | if (CheckBox_Del.IsChecked == true) 213 | sb.Append("--enableDelAfterDone "); 214 | if (CheckBox_FastStart.IsChecked == true) 215 | sb.Append("--enableMuxFastStart "); 216 | if (CheckBox_BinaryMerge.IsChecked == true) 217 | sb.Append("--enableBinaryMerge "); 218 | if (CheckBox_ParserOnly.IsChecked == true) 219 | sb.Append("--enableParseOnly "); 220 | if (CheckBox_DisableDate.IsChecked == true) 221 | sb.Append("--disableDateInfo "); 222 | if (CheckBox_DisableMerge.IsChecked == true) 223 | sb.Append("--noMerge "); 224 | if (CheckBox_DisableProxy.IsChecked == true) 225 | sb.Append("--noProxy "); 226 | if (CheckBox_DisableCheck.IsChecked == true) 227 | sb.Append("--disableIntegrityCheck "); 228 | if (CheckBox_AudioOnly.IsChecked == true) 229 | sb.Append("--enableAudioOnly "); 230 | if (TextBox_RangeStart.Text!="00:00:00"|| TextBox_RangeEnd.Text != "00:00:00") 231 | { 232 | sb.Append($"--downloadRange \"{TextBox_RangeStart.Text}-{TextBox_RangeEnd.Text}\""); 233 | } 234 | 235 | TextBox_Parameter.Text = sb.ToString(); 236 | } 237 | 238 | private void TextChanged(object sender, TextChangedEventArgs e) 239 | { 240 | GetParameter(); 241 | } 242 | 243 | private void CheckBoxChanged(object sender, RoutedEventArgs e) 244 | { 245 | if (((System.Windows.Controls.CheckBox)sender).IsChecked == true) 246 | { 247 | ((System.Windows.Controls.CheckBox)sender).Foreground = new SolidColorBrush(Color.FromRgb(46, 204, 113)); 248 | } 249 | else 250 | { 251 | ((System.Windows.Controls.CheckBox)sender).Foreground = new SolidColorBrush(Color.FromRgb(241, 241, 241)); 252 | } 253 | GetParameter(); 254 | } 255 | 256 | private void FlashTextBox(TextBox textBox) 257 | { 258 | var orgColor = textBox.Background; 259 | SolidColorBrush myBrush = new SolidColorBrush(); 260 | ColorAnimation myColorAnimation = new ColorAnimation(); 261 | myColorAnimation.To = (Color)ColorConverter.ConvertFromString("#2ecc71"); 262 | myColorAnimation.Duration = TimeSpan.FromMilliseconds(300); 263 | myBrush.BeginAnimation(SolidColorBrush.ColorProperty, myColorAnimation, HandoffBehavior.Compose); 264 | textBox.Background = myBrush; 265 | 266 | myColorAnimation.To = (Color)ColorConverter.ConvertFromString(orgColor.ToString()); 267 | myColorAnimation.Duration = TimeSpan.FromMilliseconds(1000); 268 | myBrush.BeginAnimation(SolidColorBrush.ColorProperty, myColorAnimation, HandoffBehavior.Compose); 269 | textBox.Background = myBrush; 270 | } 271 | 272 | private void TextBox_URL_MouseDoubleClick(object sender, MouseButtonEventArgs e) 273 | { 274 | //从剪切板读取url 275 | Regex url = new Regex(@"(https?)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]", RegexOptions.Compiled | RegexOptions.Singleline);//取已下载大小 276 | string str = url.Match(Clipboard.GetText()).Value; 277 | if (str != "" && str != TextBox_URL.Text) 278 | { 279 | TextBox_URL.Text = str; 280 | FlashTextBox(TextBox_URL); 281 | } 282 | } 283 | 284 | public static byte[] HexStringToBytes(string hexStr) 285 | { 286 | if (string.IsNullOrEmpty(hexStr)) 287 | { 288 | return new byte[0]; 289 | } 290 | 291 | if (hexStr.StartsWith("0x") || hexStr.StartsWith("0X")) 292 | { 293 | hexStr = hexStr.Remove(0, 2); 294 | } 295 | 296 | int count = hexStr.Length; 297 | 298 | if (count % 2 == 1) 299 | { 300 | throw new ArgumentException("Invalid length of bytes:" + count); 301 | } 302 | 303 | int byteCount = count / 2; 304 | byte[] result = new byte[byteCount]; 305 | for (int ii = 0; ii < byteCount; ++ii) 306 | { 307 | var tempBytes = Byte.Parse(hexStr.Substring(2 * ii, 2), System.Globalization.NumberStyles.HexNumber); 308 | result[ii] = tempBytes; 309 | } 310 | 311 | return result; 312 | } 313 | 314 | private string GetTitleFromURL(string url) 315 | { 316 | try 317 | { 318 | if (File.Exists(url)) 319 | return Path.GetFileNameWithoutExtension(url); 320 | if (url.StartsWith("http")) 321 | url = url.Replace("http://", "").Replace("https://", ""); 322 | //从爱奇艺dash接口获取内容 323 | if (url.Contains("dash") && (url.StartsWith("cache.video.iqiyi.com") || url.StartsWith("intel-cache.video.iqiyi.com"))) 324 | { 325 | string tvid = GetQueryString("tvid", url); 326 | string webSource = GetWebSource($"https://pcw-api.iqiyi.com/video/video/baseinfo/{tvid}"); 327 | Regex rexTitle = new Regex("name\":\"(.*?)\""); 328 | string title = GetValidFileName(rexTitle.Match(webSource).Groups[1].Value); 329 | 330 | webSource = GetWebSource("https://" + url, "Cookie:" + (File.Exists("iqiyicookie.txt") ? File.ReadAllText("iqiyicookie.txt").Trim() : "")); 331 | string[] videoes = new Regex("\"video\"[\\s\\S]*").Match(webSource).Value.Replace("},{", "|").Split('|'); 332 | string size = ""; 333 | string m3u8Content = ""; 334 | string code = ""; 335 | string duration = ""; 336 | string scrsz = ""; 337 | string fileName = ""; 338 | string filePath = ""; 339 | foreach (var video in videoes) 340 | { 341 | if (video.Contains("\"_selected\":true")) 342 | { 343 | size = FormatFileSize(Convert.ToDouble(new Regex("\"vsize\":(\\d+)").Match(video).Groups[1].Value)); 344 | m3u8Content = new Regex("\"m3u8\":\"(.*?)\"").Match(video).Groups[1].Value.Replace("\\n", "\n").Replace("\\/", "/"); 345 | code = new Regex("\"code\":(\\d+)").Match(video).Groups[1].Value; 346 | duration = FormatTime(Convert.ToInt32(new Regex("\"duration\":(\\d+)").Match(video).Groups[1].Value)); 347 | scrsz = new Regex("\"scrsz\":\"(.*?)\"").Match(video).Groups[1].Value; 348 | fileName = title + "_" + scrsz + "_" + (code == "2" ? "H264" : "H265") + "_" + duration + "_" + size; 349 | filePath = Path.Combine(Path.GetTempPath(), fileName + ".m3u8"); 350 | break; 351 | } 352 | } 353 | File.WriteAllText(filePath, m3u8Content); 354 | TextBox_URL.Text = filePath; 355 | return GetValidFileName(fileName); 356 | } 357 | else if (url.StartsWith("cache.m.iqiyi.com")) 358 | { 359 | string tvid = GetQueryString("tvid", url); 360 | string webSource = GetWebSource($"https://pcw-api.iqiyi.com/video/video/baseinfo/{tvid}"); 361 | Regex rexTitle = new Regex("name\":\"(.*?)\""); 362 | Regex rexDur = new Regex("duration\":\"(.*?)\""); 363 | string title = rexTitle.Match(webSource).Groups[1].Value 364 | + "_" 365 | + rexDur.Match(webSource).Groups[1].Value; 366 | //获得有效文件名 367 | return GetValidFileName(title); 368 | } 369 | else if (url.Contains("ccode=") && url.Contains("vid=")) 370 | { 371 | string vid = GetQueryString("vid", url); 372 | string webSource = GetWebSource($"https://openapi.youku.com/v2/videos/show.json?video_id={vid}&client_id=3d01f04416cbe807"); 373 | Regex rexTitle = new Regex("title\":\"(.*?)\""); 374 | Regex rexDur = new Regex("duration\":\"(.*?)\""); 375 | string type = GetQueryString("type", url); 376 | string title = Unicode2String(rexTitle.Match(webSource).Groups[1].Value) 377 | + "_" 378 | + FormatTime((int)Convert.ToDouble(rexDur.Match(webSource).Groups[1].Value)); 379 | if (type != "") 380 | title += "_" + type; 381 | return GetValidFileName(title); 382 | } 383 | else if ((url.Contains(".ts.m3u8") || url.Contains(".mp4.m3u8")) && url.Contains("qq.com")) 384 | { 385 | Regex rexVid = new Regex("\\/(\\w+).(\\d){6,}.*m3u8"); 386 | string match = rexVid.Match(url).Groups[1].Value; 387 | string vid = ""; 388 | if (match.Contains("_")) 389 | vid = match.Split('_')[1]; 390 | else 391 | vid = match; 392 | 393 | return GetValidFileName(GetQQTitle(vid)); 394 | } 395 | else 396 | { 397 | return GetUrlFileName(url); 398 | } 399 | } 400 | catch (Exception) 401 | { 402 | return DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss"); 403 | } 404 | } 405 | 406 | public static string GetQQTitle(string vid) 407 | { 408 | Regex rexTitle1 = new Regex("\"title\":(.*?),"); 409 | Regex rexDur = new Regex("duration\":\"(.*?)\""); 410 | string webSource = GetWebSource($"https://union.video.qq.com/fcgi-bin/data?tid=682&otype=json&appid=20001373&appkey=f6301da6035cd6cc&client=tim&idlist={vid}"); 411 | string title = ""; 412 | 413 | if (rexTitle1.Match(webSource).Groups[1].Value.Trim('\"') != "null") 414 | { 415 | string t = rexTitle1.Match(webSource).Groups[1].Value.Trim('\"'); 416 | if (t.Contains("\\u")) 417 | t = Unicode2String(t); 418 | title = t 419 | + "_" 420 | + FormatTime(Convert.ToInt32(rexDur.Match(webSource).Groups[1].Value)); 421 | return title; 422 | } 423 | else 424 | { 425 | Regex rexTitle = new Regex("\"ti\":\"(.*?)\""); 426 | webSource = GetWebSource($"https://vv.video.qq.com/getinfo?otype=json&appver=3.4.40&platform=4830701&vid={vid}"); 427 | title = rexTitle.Match(webSource).Groups[1].Value; 428 | if (title.Contains("\\u")) 429 | title = Unicode2String(title); 430 | if (string.IsNullOrEmpty(title)) 431 | { 432 | rexTitle = new Regex("VIDEO_INFO.*\"title\":\"(.*?)\""); 433 | webSource = new WebClient() { Encoding = Encoding.UTF8 }.DownloadString($"https://v.qq.com/x/page/{vid}.html"); 434 | title = rexTitle.Match(webSource).Groups[1].Value; 435 | return title; 436 | } 437 | else 438 | return title; 439 | } 440 | } 441 | 442 | //获取网页源码 443 | private static string GetWebSource(String url, string headers = "", int TimeOut = 60000) 444 | { 445 | ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallback; 446 | //Init时执行,用于注册方法。 447 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 448 | | SecurityProtocolType.Tls 449 | | (SecurityProtocolType)0x300 //Tls11 450 | | (SecurityProtocolType)0xC00; //Tls12 451 | string htmlCode = string.Empty; 452 | try 453 | { 454 | HttpWebRequest webRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url); 455 | webRequest.Method = "GET"; 456 | webRequest.UserAgent = "Mozilla/4.0"; 457 | webRequest.Headers.Add("Accept-Encoding", "gzip, deflate"); 458 | webRequest.Timeout = TimeOut; //设置超时 459 | webRequest.KeepAlive = false; 460 | //添加headers 461 | if (headers != "") 462 | { 463 | foreach (string att in headers.Split('|')) 464 | { 465 | try 466 | { 467 | if (att.Split(':')[0].ToLower() == "referer") 468 | webRequest.Referer = att.Substring(att.IndexOf(":") + 1); 469 | else if (att.Split(':')[0].ToLower() == "user-agent") 470 | webRequest.UserAgent = att.Substring(att.IndexOf(":") + 1); 471 | else if (att.Split(':')[0].ToLower() == "range") 472 | webRequest.AddRange(Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[0], Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[1]))); 473 | else if (att.Split(':')[0].ToLower() == "accept") 474 | webRequest.Accept = att.Substring(att.IndexOf(":") + 1); 475 | else 476 | webRequest.Headers.Add(att); 477 | } 478 | catch (Exception e) 479 | { 480 | 481 | } 482 | } 483 | } 484 | HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse(); 485 | if (webResponse.ContentEncoding != null 486 | && webResponse.ContentEncoding.ToLower() == "gzip") //如果使用了GZip则先解压 487 | { 488 | using (Stream streamReceive = webResponse.GetResponseStream()) 489 | { 490 | using (var zipStream = 491 | new System.IO.Compression.GZipStream(streamReceive, System.IO.Compression.CompressionMode.Decompress)) 492 | { 493 | using (StreamReader sr = new StreamReader(zipStream, Encoding.UTF8)) 494 | { 495 | htmlCode = sr.ReadToEnd(); 496 | } 497 | } 498 | } 499 | } 500 | else 501 | { 502 | using (Stream streamReceive = webResponse.GetResponseStream()) 503 | { 504 | using (StreamReader sr = new StreamReader(streamReceive, Encoding.UTF8)) 505 | { 506 | htmlCode = sr.ReadToEnd(); 507 | } 508 | } 509 | } 510 | 511 | if (webResponse != null) 512 | { 513 | webResponse.Close(); 514 | } 515 | if (webRequest != null) 516 | { 517 | webRequest.Abort(); 518 | } 519 | } 520 | catch (Exception e) //捕获所有异常 521 | { 522 | 523 | } 524 | 525 | return htmlCode; 526 | } 527 | 528 | /// 529 | /// Unicode转字符串 530 | /// 531 | /// 经过Unicode编码的字符串 532 | /// 正常字符串 533 | public static string Unicode2String(string source) 534 | { 535 | return new Regex(@"\\u([0-9A-F]{4})", RegexOptions.IgnoreCase | RegexOptions.Compiled).Replace( 536 | source, x => string.Empty + Convert.ToChar(Convert.ToUInt16(x.Result("$1"), 16))); 537 | } 538 | 539 | /// 540 | /// 获取url字符串参数,返回参数值字符串 541 | /// 542 | /// 参数名称 543 | /// url字符串 544 | /// 545 | public string GetQueryString(string name, string url) 546 | { 547 | Regex re = new Regex(@"(^|&)?(\w+)=([^&]+)(&|$)?", System.Text.RegularExpressions.RegexOptions.Compiled); 548 | MatchCollection mc = re.Matches(url); 549 | foreach (Match m in mc) 550 | { 551 | if (m.Result("$2").Equals(name)) 552 | { 553 | return m.Result("$3"); 554 | } 555 | } 556 | return ""; 557 | } 558 | 559 | private void TextBox_Title_MouseDoubleClick(object sender, MouseButtonEventArgs e) 560 | { 561 | if (TextBox_URL.Text != "") 562 | TextBox_Title.Text = GetTitleFromURL(TextBox_URL.Text); 563 | } 564 | 565 | 566 | //寻找cookie字符串中的value 567 | public static string FindCookie(string key, string cookie) 568 | { 569 | string[] values = cookie.Split(';'); 570 | string value = ""; 571 | foreach (var v in values) 572 | { 573 | if (v.Trim().StartsWith(key + "=")) 574 | value = v.Remove(0, v.IndexOf('=') + 1).Trim(); 575 | } 576 | return value; 577 | } 578 | 579 | //此函数用于格式化输出时长 580 | public static String FormatTime(Int32 time) 581 | { 582 | TimeSpan ts = new TimeSpan(0, 0, time); 583 | string str = ""; 584 | str = (ts.Hours.ToString("00") == "00" ? "" : ts.Hours.ToString("00") + ".") + ts.Minutes.ToString("00") + "." + ts.Seconds.ToString("00"); 585 | return str; 586 | } 587 | 588 | //此函数用于格式化输出文件大小 589 | public static String FormatFileSize(Double fileSize) 590 | { 591 | if (fileSize < 0) 592 | { 593 | throw new ArgumentOutOfRangeException("fileSize"); 594 | } 595 | else if (fileSize >= 1024 * 1024 * 1024) 596 | { 597 | return string.Format("{0:########0.00}GB", ((Double)fileSize) / (1024 * 1024 * 1024)); 598 | } 599 | else if (fileSize >= 1024 * 1024) 600 | { 601 | return string.Format("{0:####0.00}MB", ((Double)fileSize) / (1024 * 1024)); 602 | } 603 | else if (fileSize >= 1024) 604 | { 605 | return string.Format("{0:####0.00}KB", ((Double)fileSize) / 1024); 606 | } 607 | else 608 | { 609 | return string.Format("{0}bytes", fileSize); 610 | } 611 | } 612 | 613 | public static string GetUrlFileName(string url) 614 | { 615 | if (string.IsNullOrEmpty(url)) 616 | { 617 | return "None"; 618 | } 619 | try 620 | { 621 | string[] strs1 = url.Split(new char[] { '/' }); 622 | return GetValidFileName(System.Web.HttpUtility.UrlDecode(strs1[strs1.Length - 1].Split(new char[] { '?' })[0].Replace(".m3u8", ""))); 623 | } 624 | catch (Exception) 625 | { 626 | return DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss"); 627 | } 628 | } 629 | 630 | public static string GetValidFileName(string input, string re = ".") 631 | { 632 | string title = input; 633 | foreach (char invalidChar in Path.GetInvalidFileNameChars()) 634 | { 635 | title = title.Replace(invalidChar.ToString(), re); 636 | } 637 | return title; 638 | } 639 | 640 | private void TextBox_URL_PreviewDragOver(object sender, System.Windows.DragEventArgs e) 641 | { 642 | e.Effects = DragDropEffects.Copy; 643 | e.Handled = true; 644 | } 645 | 646 | private void TextBox_URL_PreviewDragEnter(object sender, System.Windows.DragEventArgs e) 647 | { 648 | e.Effects = DragDropEffects.Copy; 649 | e.Handled = true; 650 | } 651 | 652 | private void TextBox_URL_PreviewDrop(object sender, System.Windows.DragEventArgs e) 653 | { 654 | if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true) 655 | { 656 | //获取拖拽的文件地址 657 | var filenames = (string[])e.Data.GetData(DataFormats.FileDrop); 658 | var hz = filenames[0].LastIndexOf('.') + 1; 659 | var houzhui = filenames[0].Substring(hz).ToLower();//文件后缀名 660 | string path = ((System.Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString(); 661 | if (houzhui == "m3u8" || houzhui == "txt" || houzhui == "json" || houzhui == "mpd") //只允许拖入部分文件 662 | { 663 | e.Effects = DragDropEffects.Copy; 664 | e.Handled = true; 665 | if (TextBox_URL.Text != path) FlashTextBox(TextBox_URL); 666 | TextBox_URL.Text = path; //将获取到的完整路径赋值到textBox1 667 | if (houzhui == "m3u8" || houzhui == "json" || houzhui == "mpd") 668 | TextBox_Title.Text = Path.GetFileNameWithoutExtension(path); //自动获取文件名 669 | } 670 | if (Directory.Exists(path)) 671 | { 672 | if (TextBox_URL.Text != path) FlashTextBox(TextBox_URL); 673 | TextBox_URL.Text = path; 674 | } 675 | } 676 | } 677 | 678 | private void TextBox_MuxJson_PreviewDragEnter(object sender, System.Windows.DragEventArgs e) 679 | { 680 | e.Effects = DragDropEffects.Copy; 681 | e.Handled = true; 682 | } 683 | 684 | private void TextBox_MuxJson_PreviewDragOver(object sender, System.Windows.DragEventArgs e) 685 | { 686 | e.Effects = DragDropEffects.Copy; 687 | e.Handled = true; 688 | } 689 | 690 | private void TextBox_MuxJson_PreviewDrop(object sender, System.Windows.DragEventArgs e) 691 | { 692 | if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true) 693 | { 694 | //获取拖拽的文件地址 695 | var filenames = (string[])e.Data.GetData(DataFormats.FileDrop); 696 | var hz = filenames[0].LastIndexOf('.') + 1; 697 | var houzhui = filenames[0].Substring(hz).ToLower();//文件后缀名 698 | string path = ((System.Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString(); 699 | if (houzhui == "json") //只允许拖入部分文件 700 | { 701 | e.Effects = DragDropEffects.Copy; 702 | e.Handled = true; 703 | TextBox_MuxJson.Text = path; //将获取到的完整路径赋值到textBox1 704 | } 705 | } 706 | } 707 | 708 | private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) 709 | { 710 | //string to base64 711 | Encoding encode = Encoding.UTF8; 712 | byte[] bytedata = encode.GetBytes(TextBox_EXE.Text); 713 | string exePath = Convert.ToBase64String(bytedata, 0, bytedata.Length); 714 | bytedata = encode.GetBytes(TextBox_WorkDir.Text); 715 | string saveDir = Convert.ToBase64String(bytedata, 0, bytedata.Length); 716 | bytedata = encode.GetBytes(TextBox_Proxy.Text); 717 | string proxy = Convert.ToBase64String(bytedata, 0, bytedata.Length); 718 | bytedata = encode.GetBytes(TextBox_Headers.Text); 719 | string headers = Convert.ToBase64String(bytedata, 0, bytedata.Length); 720 | 721 | string config = "程序路径=" + exePath 722 | + ";保存路径=" + saveDir 723 | + ";代理=" + proxy 724 | + ";请求头=" + headers 725 | + ";删除临时文件=" + (CheckBox_Del.IsChecked == true ? "1" : "0") 726 | + ";MP4混流边下边看=" + (CheckBox_FastStart.IsChecked == true ? "1" : "0") 727 | + ";二进制合并=" + (CheckBox_BinaryMerge.IsChecked == true ? "1" : "0") 728 | + ";仅解析模式=" + (CheckBox_ParserOnly.IsChecked == true ? "1" : "0") 729 | + ";不写入日期=" + (CheckBox_DisableDate.IsChecked == true ? "1" : "0") 730 | + ";最大线程=" + TextBox_Max.Text 731 | + ";最小线程=" + TextBox_Min.Text 732 | + ";重试次数=" + TextBox_Retry.Text 733 | + ";超时秒数=" + TextBox_Timeout.Text 734 | + ";停止速度=" + TextBox_StopSpeed.Text 735 | + ";最大速度=" + TextBox_MaxSpeed.Text 736 | + ";不合并=" + (CheckBox_DisableMerge.IsChecked == true ? "1" : "0") 737 | + ";不使用系统代理=" + (CheckBox_DisableProxy.IsChecked == true ? "1" : "0") 738 | + ";仅合并音频=" + (CheckBox_AudioOnly.IsChecked == true ? "1" : "0"); 739 | File.WriteAllText("config.txt", config); 740 | 741 | //关闭任务 742 | Helper.cts.Cancel(); 743 | //Process.GetCurrentProcess().Kill(); 744 | 745 | } 746 | 747 | //private void Window_Closed(object sender, System.ComponentModel.CancelEventArgs e) 748 | //{ 749 | // //退出环境。 750 | // Application.Current.Shutdown(); 751 | 752 | //} 753 | 754 | 755 | private void Window_Loaded(object sender, RoutedEventArgs e) 756 | { 757 | Environment.CurrentDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); 758 | //读取配置 759 | if (File.Exists("config.txt")) 760 | { 761 | string config = File.ReadAllText("config.txt"); 762 | 763 | TextBox_EXE.Text = Encoding.UTF8.GetString(Convert.FromBase64String(FindCookie("程序路径", config))); 764 | TextBox_WorkDir.Text = Encoding.UTF8.GetString(Convert.FromBase64String(FindCookie("保存路径", config))); 765 | try 766 | { 767 | TextBox_Proxy.Text = Encoding.UTF8.GetString(Convert.FromBase64String(FindCookie("代理", config))); 768 | } 769 | catch (Exception) {; } 770 | try 771 | { 772 | TextBox_Headers.Text = Encoding.UTF8.GetString(Convert.FromBase64String(FindCookie("请求头", config))); 773 | } 774 | catch (Exception) {; } 775 | if (FindCookie("删除临时文件", config) == "1") 776 | CheckBox_Del.IsChecked = true; 777 | if (FindCookie("MP4混流边下边看", config) == "1") 778 | CheckBox_FastStart.IsChecked = true; 779 | if (FindCookie("二进制合并", config) == "1") 780 | CheckBox_BinaryMerge.IsChecked = true; 781 | if (FindCookie("仅解析模式", config) == "1") 782 | CheckBox_ParserOnly.IsChecked = true; 783 | if (FindCookie("不写入日期", config) == "1") 784 | CheckBox_DisableDate.IsChecked = true; 785 | TextBox_Max.Text = FindCookie("最大线程", config); 786 | TextBox_Min.Text = FindCookie("最小线程", config); 787 | TextBox_Retry.Text = FindCookie("重试次数", config); 788 | try 789 | { 790 | if (!string.IsNullOrEmpty(FindCookie("超时秒数", config))) 791 | TextBox_Timeout.Text = FindCookie("超时秒数", config); 792 | } 793 | catch (Exception) {; } 794 | try 795 | { 796 | if (!string.IsNullOrEmpty(FindCookie("停止速度", config))) 797 | TextBox_StopSpeed.Text = FindCookie("停止速度", config); 798 | } 799 | catch (Exception) {; } 800 | try 801 | { 802 | if (!string.IsNullOrEmpty(FindCookie("最大速度", config))) 803 | TextBox_MaxSpeed.Text = FindCookie("最大速度", config); 804 | } 805 | catch (Exception) {; } 806 | try 807 | { 808 | if (FindCookie("不合并", config) == "1") 809 | CheckBox_DisableMerge.IsChecked = true; 810 | if (FindCookie("不使用系统代理", config) == "1") 811 | CheckBox_DisableProxy.IsChecked = true; 812 | } 813 | catch (Exception) {; } 814 | try 815 | { 816 | if (FindCookie("仅合并音频", config) == "1") 817 | CheckBox_AudioOnly.IsChecked = true; 818 | } 819 | catch (Exception) {; } 820 | } 821 | 822 | if (!File.Exists(TextBox_EXE.Text))//尝试寻找主程序 823 | { 824 | DirectoryInfo d = new DirectoryInfo(Environment.CurrentDirectory); 825 | foreach (FileInfo fi in d.GetFiles().Reverse()) 826 | { 827 | if (fi.Extension.ToUpper() == ".exe".ToUpper() && fi.Name.StartsWith("N_m3u8DL-CLI_")) 828 | { 829 | TextBox_EXE.Text = fi.Name; 830 | } 831 | } 832 | } 833 | 834 | if (Environment.GetCommandLineArgs().Length > 1) 835 | { 836 | var ext = Path.GetExtension(Environment.GetCommandLineArgs()[1]); 837 | if (ext == ".m3u8" || ext == ".json" || ext == ".txt" || Directory.Exists(Environment.GetCommandLineArgs()[1])) 838 | TextBox_URL.Text = Environment.GetCommandLineArgs()[1]; 839 | if (TextBox_URL.Text != "") 840 | { 841 | FlashTextBox(TextBox_URL); 842 | if (!Directory.Exists(TextBox_URL.Text)) 843 | TextBox_Title.Text = GetTitleFromURL(TextBox_URL.Text); 844 | } 845 | } 846 | else 847 | { 848 | //从剪切板读取url 849 | Regex url = new Regex(@"(https?)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]", RegexOptions.Compiled | RegexOptions.Singleline); 850 | string str = url.Match(Clipboard.GetText()).Value; 851 | TextBox_URL.Text = str; 852 | if (TextBox_URL.Text != "") 853 | { 854 | FlashTextBox(TextBox_URL); 855 | TextBox_Title.Text = GetTitleFromURL(TextBox_URL.Text); 856 | } 857 | } 858 | } 859 | 860 | private void Button_GO_Click(object sender, RoutedEventArgs e) 861 | { 862 | //hex to base64 863 | try 864 | { 865 | TextBox_Key.Text = Convert.ToBase64String(HexStringToBytes(TextBox_Key.Text)); 866 | } 867 | catch(Exception) { 868 | 869 | } 870 | if (!File.Exists(TextBox_EXE.Text)) 871 | { 872 | MessageBox.Show(Properties.Resources.String2); 873 | return; 874 | } 875 | if (TextBox_URL.Text == "") 876 | { 877 | MessageBox.Show(Properties.Resources.String3); 878 | return; 879 | } 880 | if (TextBox_Proxy.Text != "" && (!TextBox_Proxy.Text.StartsWith("http://") && !TextBox_Proxy.Text.StartsWith("socks5://"))) 881 | { 882 | MessageBox.Show(Properties.Resources.String7); 883 | return; 884 | } 885 | 886 | //批量 887 | if ((!TextBox_URL.Text.StartsWith("http") && TextBox_URL.Text.EndsWith(".txt") && File.Exists(TextBox_URL.Text)) 888 | || Directory.Exists(TextBox_URL.Text)) 889 | { 890 | this.IsEnabled = false; 891 | Button_GO.Content = Properties.Resources.String4; 892 | string inputUrl = TextBox_URL.Text; 893 | string exePath = TextBox_EXE.Text; 894 | List m3u8list = new List(); 895 | if (Directory.Exists(inputUrl)) 896 | { 897 | foreach (var file in Directory.GetFiles(inputUrl)) 898 | { 899 | if (new FileInfo(file).Name.ToLower().EndsWith(".m3u8") || new FileInfo(file).Name.ToLower().EndsWith(".mpd")) 900 | { 901 | m3u8list.Add(new FileInfo(file).FullName); 902 | } 903 | } 904 | StringBuilder sb = new StringBuilder(); 905 | sb.AppendLine("@echo off"); 906 | sb.AppendLine("::Created by N_m3u8DL-CLI-SimpleG\r\n"); 907 | //sb.AppendLine("chcp 65001 >nul"); 908 | int i = 0; 909 | foreach (var item in m3u8list) 910 | { 911 | TextBox_Title.Text = GetTitleFromURL(item); 912 | sb.AppendLine($"TITLE \"[{++i}/{m3u8list.Count}] - {TextBox_Title.Text}\""); 913 | sb.AppendLine("\"" + exePath + "\" \"" + item.Replace("%", "%%") + "\" " + TextBox_Parameter.Text.Remove(0, TextBox_Parameter.Text.IndexOf("\" ") + 2)); 914 | } 915 | //sb.AppendLine("del %0"); 916 | string bat = "Batch-" + DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss") + ".bat"; 917 | File.WriteAllText(bat, 918 | sb.ToString(), 919 | Encoding.Default); 920 | Process.Start(bat); 921 | } 922 | else 923 | { 924 | m3u8list = File.ReadAllLines(inputUrl, GetType(inputUrl)).ToList(); 925 | StringBuilder sb = new StringBuilder(); 926 | sb.AppendLine("@echo off"); 927 | sb.AppendLine("::Created by N_m3u8DL-CLI-SimpleG"); 928 | //sb.AppendLine("chcp 65001 >nul"); 929 | int i = 0; 930 | foreach (var item in m3u8list) 931 | { 932 | if (item.Trim() != "") 933 | { 934 | if (item.StartsWith("http")) 935 | { 936 | TextBox_Title.Text = GetTitleFromURL(item); 937 | sb.AppendLine($"TITLE \"[{++i}/{m3u8list.Count}] - {TextBox_Title.Text}\""); 938 | sb.AppendLine("\"" + exePath + "\" \"" + item.Replace("%", "%%") + "\" " + TextBox_Parameter.Text.Remove(0, TextBox_Parameter.Text.IndexOf("\" ") + 2)); 939 | } 940 | //自定义文件名 941 | else 942 | { 943 | TextBox_Title.Text = item.Substring(0, item.IndexOf(",http")); 944 | sb.AppendLine($"TITLE \"[{++i}/{m3u8list.Count}] - {TextBox_Title.Text}\""); 945 | sb.AppendLine("\"" + exePath + "\" \"" + item.Replace(TextBox_Title.Text + ",", "").Replace("%", "%%") + "\" " + TextBox_Parameter.Text.Remove(0, TextBox_Parameter.Text.IndexOf("\" ") + 2)); 946 | } 947 | } 948 | } 949 | //sb.AppendLine("del %0"); 950 | string bat = "Batch-" + DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss") + ".bat"; 951 | File.WriteAllText(bat, 952 | sb.ToString(), 953 | Encoding.Default); 954 | Process.Start(bat); 955 | } 956 | 957 | Button_GO.Content = "GO"; 958 | this.IsEnabled = true; 959 | } 960 | else 961 | { 962 | Button_GO.IsEnabled = false; 963 | Process.Start(TextBox_EXE.Text, TextBox_Parameter.Text); 964 | Button_GO.IsEnabled = true; 965 | } 966 | } 967 | 968 | 969 | private void TextBox_URL_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) 970 | { 971 | if (e.Key == Key.Enter) 972 | Button_GO.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ButtonBase.ClickEvent)); 973 | } 974 | 975 | private void TextBox_Title_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) 976 | { 977 | if (e.Key == Key.Enter) 978 | Button_GO.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ButtonBase.ClickEvent)); 979 | } 980 | 981 | private void SetTopMost(object sender, RoutedEventArgs e) 982 | { 983 | if (CheckBox_TopMost.IsChecked == true) 984 | { 985 | Topmost = true; 986 | } 987 | else 988 | { 989 | Topmost = false; 990 | } 991 | } 992 | 993 | private void Menu_GetDownloader(object sender, RoutedEventArgs e) 994 | { 995 | Process.Start("https://github.com/nilaoda/N_m3u8DL-CLI/releases"); 996 | } 997 | 998 | /// 999 | /// 给定文件的路径,读取文件的二进制数据,判断文件的编码类型 1000 | /// 1001 | /// 文件路径 1002 | /// 文件的编码类型 1003 | public static Encoding GetType(string FILE_NAME) 1004 | { 1005 | FileStream fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read); 1006 | Encoding r = GetType(fs); 1007 | fs.Close(); 1008 | return r; 1009 | } 1010 | 1011 | /// 1012 | /// 通过给定的文件流,判断文件的编码类型 1013 | /// 1014 | /// 文件流 1015 | /// 文件的编码类型 1016 | public static Encoding GetType(FileStream fs) 1017 | { 1018 | byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 }; 1019 | byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 }; 1020 | byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF }; //带BOM 1021 | Encoding reVal = Encoding.Default; 1022 | 1023 | BinaryReader r = new BinaryReader(fs, System.Text.Encoding.Default); 1024 | int i; 1025 | int.TryParse(fs.Length.ToString(), out i); 1026 | byte[] ss = r.ReadBytes(i); 1027 | if (IsUTF8Bytes(ss) || (ss[0] == 0xEF && ss[1] == 0xBB && ss[2] == 0xBF)) 1028 | { 1029 | reVal = Encoding.UTF8; 1030 | } 1031 | else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00) 1032 | { 1033 | reVal = Encoding.BigEndianUnicode; 1034 | } 1035 | else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41) 1036 | { 1037 | reVal = Encoding.Unicode; 1038 | } 1039 | r.Close(); 1040 | return reVal; 1041 | } 1042 | 1043 | /// 1044 | /// 判断是否是不带 BOM 的 UTF8 格式 1045 | /// 1046 | /// 1047 | /// 1048 | private static bool IsUTF8Bytes(byte[] data) 1049 | { 1050 | int charByteCounter = 1; //计算当前正分析的字符应还有的字节数 1051 | byte curByte; //当前分析的字节. 1052 | for (int i = 0; i < data.Length; i++) 1053 | { 1054 | curByte = data[i]; 1055 | if (charByteCounter == 1) 1056 | { 1057 | if (curByte >= 0x80) 1058 | { 1059 | //判断当前 1060 | while (((curByte <<= 1) & 0x80) != 0) 1061 | { 1062 | charByteCounter++; 1063 | } 1064 | //标记位首位若为非0 则至少以2个1开始 如:110XXXXX...........1111110X 1065 | if (charByteCounter == 1 || charByteCounter > 6) 1066 | { 1067 | return false; 1068 | } 1069 | } 1070 | } 1071 | else 1072 | { 1073 | //若是UTF-8 此时第一位必须为1 1074 | if ((curByte & 0xC0) != 0x80) 1075 | { 1076 | return false; 1077 | } 1078 | charByteCounter--; 1079 | } 1080 | } 1081 | if (charByteCounter > 1) 1082 | { 1083 | throw new Exception(Properties.Resources.String5); 1084 | } 1085 | return true; 1086 | } 1087 | 1088 | private void TextBox_Key_PreviewDragEnter(object sender, System.Windows.DragEventArgs e) 1089 | { 1090 | e.Effects = DragDropEffects.Copy; 1091 | e.Handled = true; 1092 | } 1093 | 1094 | private void TextBox_Key_PreviewDragOver(object sender, System.Windows.DragEventArgs e) 1095 | { 1096 | e.Effects = DragDropEffects.Copy; 1097 | e.Handled = true; 1098 | } 1099 | 1100 | private void TextBox_Key_PreviewDrop(object sender, System.Windows.DragEventArgs e) 1101 | { 1102 | if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true) 1103 | { 1104 | //获取拖拽的文件地址 1105 | string path = ((System.Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString(); 1106 | e.Effects = DragDropEffects.Copy; 1107 | e.Handled = true; 1108 | if (new FileInfo(path).Length == 16) 1109 | TextBox_Key.Text = path; //将获取到的完整路径赋值到textBox1 1110 | else 1111 | MessageBox.Show(Properties.Resources.String6); 1112 | } 1113 | } 1114 | 1115 | private void AddTaskToList(M3u8TaskItem a_task) 1116 | { 1117 | bool find_exist = false; 1118 | 1119 | //查找列表中是否已经存在同名的任务 1120 | foreach (M3u8TaskItem item in this.m3u8_tasks) 1121 | { 1122 | //查找同名任务 1123 | if (item.Name == a_task.Name) 1124 | { 1125 | //检查任务状态 1126 | if (string.IsNullOrEmpty(item.Status)) 1127 | { 1128 | //可以重设任务,而不需要添加新任务 1129 | item.M3u8Url = a_task.M3u8Url; 1130 | item.PageUrl = a_task.PageUrl; 1131 | item.Parameter = a_task.Parameter; 1132 | find_exist = true; 1133 | MessageBox.Show("找到了同名任务,已更新其参数"); 1134 | break; 1135 | } 1136 | } 1137 | } 1138 | 1139 | 1140 | 1141 | if (!find_exist) 1142 | { 1143 | //没有找到存在的任务,才添加新任务 1144 | this.m3u8_tasks.Add(a_task); 1145 | } 1146 | 1147 | try 1148 | { 1149 | lbTaskList.Items.Refresh(); 1150 | } 1151 | catch (Exception e) 1152 | { 1153 | Debug.WriteLine($"遇到错误:{e.Message}"); 1154 | Debug.WriteLine("改为用this.Dispatcher.BeginInvoke调用控件"); 1155 | //线程中,不能调用UI控件,只能这样间接调用 1156 | //不然会报错:调用线程无法访问此对象 因为另一个线程拥有该对象 1157 | //参考:https://www.yuantk.com/weblog/dee39674-c663-4e31-bf81-523518165a78.html 1158 | //在创建控件的基础句柄所在线程上异步执行指定委托。 1159 | this.Dispatcher.BeginInvoke(new Action(() => 1160 | { 1161 | lbTaskList.Items.Refresh(); 1162 | })); 1163 | } 1164 | 1165 | 1166 | } 1167 | 1168 | /// 1169 | /// 添加任务到列表 1170 | /// 1171 | /// 1172 | /// 1173 | private void AddToList_Click(object sender, RoutedEventArgs e) 1174 | { 1175 | M3u8TaskItem a_task = new M3u8TaskItem(TextBox_Title.Text, TextBox_URL.Text, TextBox_PageUrl.Text, TextBox_Parameter.Text); 1176 | 1177 | AddTaskToList(a_task); 1178 | 1179 | } 1180 | 1181 | /// 1182 | /// 向LogBlock控件写入一行下载日志 1183 | /// 1184 | /// 1185 | private void WriteLogBlockLine(string line) 1186 | { 1187 | line = line.Trim() + Environment.NewLine; 1188 | 1189 | try 1190 | { 1191 | LogBlock.Text = line + LogBlock.Text; 1192 | } 1193 | catch (Exception e) 1194 | { 1195 | //Debug.WriteLine($"更新LogBlock控件遇到错误:{e.Message}"); 1196 | //Debug.WriteLine("改为用this.Dispatcher.BeginInvoke调用控件"); 1197 | //线程中,不能调用UI控件,只能这样间接调用 1198 | //不然会报错:调用线程无法访问此对象 因为另一个线程拥有该对象 1199 | //参考:https://www.yuantk.com/weblog/dee39674-c663-4e31-bf81-523518165a78.html 1200 | //在创建控件的基础句柄所在线程上异步执行指定委托。 1201 | this.Dispatcher.BeginInvoke(new Action(() => 1202 | { 1203 | LogBlock.Text = line + LogBlock.Text; 1204 | })); 1205 | } 1206 | 1207 | } 1208 | 1209 | 1210 | /// 1211 | /// 清理LogBlock控件内容 1212 | /// 1213 | /// 1214 | private void ClearLogBlock() 1215 | { 1216 | try 1217 | { 1218 | LogBlock.Text = ""; 1219 | } 1220 | catch (Exception e) 1221 | { 1222 | //Debug.WriteLine($"更新LogBlock控件遇到错误:{e.Message}"); 1223 | //Debug.WriteLine("改为用this.Dispatcher.BeginInvoke调用控件"); 1224 | //线程中,不能调用UI控件,只能这样间接调用 1225 | //不然会报错:调用线程无法访问此对象 因为另一个线程拥有该对象 1226 | //参考:https://www.yuantk.com/weblog/dee39674-c663-4e31-bf81-523518165a78.html 1227 | //在创建控件的基础句柄所在线程上异步执行指定委托。 1228 | this.Dispatcher.BeginInvoke(new Action(() => 1229 | { 1230 | LogBlock.Text = ""; 1231 | })); 1232 | } 1233 | 1234 | } 1235 | 1236 | 1237 | /// 1238 | /// 异步启动下载进程,并触发进程完成的事件。这样可以在进程完成的事件中,做后续的事情 1239 | /// 1240 | /// 1241 | /// 1242 | /// 1243 | private async Task StartDownloadProcessAsync(string fileName, string arguments) 1244 | { 1245 | m3u8dlEventHandled = new TaskCompletionSource(); 1246 | 1247 | using (m3u8dlProcess = new Process()) 1248 | { 1249 | try 1250 | { 1251 | // Start a process to print a file and raise an event when done. 1252 | m3u8dlProcess.StartInfo.FileName = fileName; 1253 | m3u8dlProcess.StartInfo.Arguments = arguments; 1254 | m3u8dlProcess.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动 1255 | m3u8dlProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口 1256 | m3u8dlProcess.StartInfo.RedirectStandardOutput = true; //由调用程序获取输出信息 1257 | //m3u8dlProcess.StartInfo.StandardOutputEncoding = Encoding.UTF8; 1258 | //m3u8dlProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出 1259 | //m3u8dlProcess.StartInfo.StandardErrorEncoding = Encoding.UTF8; 1260 | //m3u8dlProcess.StartInfo.RedirectStandardInput = true; //接受来自调用程序的输入信息 1261 | //m3u8dlProcess.StandardInput.AutoFlush = true; 1262 | m3u8dlProcess.EnableRaisingEvents = true; //要求引发Exited事件 1263 | m3u8dlProcess.Exited += new EventHandler(DownloadProcess_Exited); 1264 | m3u8dlProcess.Start(); //启动 1265 | 1266 | } 1267 | catch (Exception e) 1268 | { 1269 | MessageBox.Show($"调用下载进程出错:{e.Message}"); 1270 | return; 1271 | } 1272 | 1273 | // 等待后台进程Log 1274 | try 1275 | { 1276 | //获取输出 1277 | logBlockReader = m3u8dlProcess.StandardOutput; 1278 | // 每次读取一行 1279 | string line = ""; 1280 | while (logBlockReader != null && !logBlockReader.EndOfStream) 1281 | { 1282 | line = await logBlockReader.ReadLineAsync(); 1283 | WriteLogBlockLine(line); 1284 | 1285 | //检查内容是不是地址无效 1286 | if (line.Contains("地址无效")) 1287 | { 1288 | //要终止写入,并停止这个任务的下载 1289 | StopTask("Failed"); 1290 | break; 1291 | } 1292 | } 1293 | } 1294 | catch (Exception e) 1295 | { 1296 | MessageBox.Show($"读取后台下载进程Log出错:{e.Message}"); 1297 | return; 1298 | } 1299 | 1300 | // 等待完成事件 1301 | try 1302 | { 1303 | await Task.WhenAny(m3u8dlEventHandled.Task); 1304 | } 1305 | catch (Exception e) 1306 | { 1307 | MessageBox.Show($"等待进程完成出错:{e.Message}"); 1308 | return; 1309 | } 1310 | 1311 | 1312 | 1313 | } 1314 | } 1315 | 1316 | /// 1317 | /// 处理进程完成的事件。这里就是打开下一个下载任务 1318 | /// 1319 | /// 1320 | /// 1321 | private void DownloadProcess_Exited(object sender, System.EventArgs e) 1322 | { 1323 | try 1324 | { 1325 | m3u8dlEventHandled.TrySetResult(true); 1326 | } 1327 | catch (Exception ex) 1328 | { 1329 | MessageBox.Show($"设置下载进程为完成时出错:{ex.Message}"); 1330 | return; 1331 | } 1332 | 1333 | } 1334 | 1335 | /// 1336 | /// 获取下一个需要下载的任务,不存在,就返回null 1337 | /// 1338 | /// 1339 | private M3u8TaskItem GetNextTaskToDownload() 1340 | { 1341 | //遍历任务列表,查找下一个需要下载的任务 1342 | foreach (M3u8TaskItem item in this.m3u8_tasks) 1343 | { 1344 | //检查当前遍历到的任务状态 1345 | if (string.IsNullOrEmpty(item.Status)) 1346 | { 1347 | //检查参数 1348 | if (string.IsNullOrEmpty(item.Parameter)) 1349 | { 1350 | item.Status = "No Parameter"; 1351 | continue; 1352 | } 1353 | else 1354 | { 1355 | //找到需要下载的任务,可以返回 1356 | return item; 1357 | } 1358 | } 1359 | } 1360 | 1361 | return null; 1362 | } 1363 | 1364 | 1365 | /// 1366 | /// 启动任务列表,异步 1367 | /// 1368 | /// 1369 | /// 1370 | private async void TaskStartAsync_Click(object sender, RoutedEventArgs e) 1371 | { 1372 | //重置任务列表状态 1373 | this.tasks_status = ""; 1374 | 1375 | //定义要下载的任务 1376 | M3u8TaskItem item = GetNextTaskToDownload(); 1377 | 1378 | //在下载期间,用户可能继续添加任务到列表,所以列表长度是动态变化的 1379 | //因此,不能简单用foreach遍历列表,会报错:集合已修改 可能无法执行枚举操作 1380 | //解决办法是,单独遍历任务列表,获取要下载的任务。 1381 | //再把下载放在while循环中,然后每完成一个任务,都重新检查列表,判断是否需要继续 1382 | while (item != null) 1383 | { 1384 | //检查任务列表状态 1385 | if (this.tasks_status == "Stopped") 1386 | { 1387 | //重置状态并退出运行 1388 | this.tasks_status = ""; 1389 | break; 1390 | } 1391 | 1392 | //清理LogBlock 1393 | ClearLogBlock(); 1394 | 1395 | //修改任务状态 1396 | item.Status = "Downloading"; 1397 | lbTaskList.Items.Refresh(); 1398 | 1399 | //启动异步下载 1400 | try 1401 | { 1402 | await StartDownloadProcessAsync(TextBox_EXE.Text, item.Parameter); 1403 | } 1404 | catch (Exception ex) 1405 | { 1406 | MessageBox.Show($"运行异步下载出错:{ex.Message}"); 1407 | return; 1408 | } 1409 | 1410 | 1411 | //再次修改任务状态 1412 | if (item.Status == "Downloading") 1413 | { 1414 | item.Status = "Done"; 1415 | lbTaskList.Items.Refresh(); 1416 | } 1417 | 1418 | //获取下一个任务 1419 | item = GetNextTaskToDownload(); 1420 | } 1421 | 1422 | 1423 | } 1424 | 1425 | 1426 | private void StopTask(string new_status) 1427 | { 1428 | //改变任务列表状态 1429 | this.tasks_status = "Stopped"; 1430 | 1431 | //遍历任务列表,查找当前任务 1432 | foreach (M3u8TaskItem item in this.m3u8_tasks) 1433 | { 1434 | //检查任务状态 1435 | if (item.Status == "Downloading") 1436 | { 1437 | //修改任务状态 1438 | item.Status = new_status; 1439 | } 1440 | } 1441 | 1442 | //终止logBlockReader写入 1443 | if (logBlockReader != null) 1444 | { 1445 | try 1446 | { 1447 | logBlockReader.Close(); 1448 | logBlockReader = null; 1449 | } 1450 | catch (Exception ex) 1451 | { 1452 | MessageBox.Show($"中止下载Log读取器时出错:{ex.Message}"); 1453 | } 1454 | } 1455 | 1456 | //终止正在进行的下载进程 1457 | if (m3u8dlProcess != null) 1458 | { 1459 | try 1460 | { 1461 | m3u8dlProcess.Kill(); 1462 | m3u8dlProcess.WaitForExit(); 1463 | m3u8dlProcess.Close(); 1464 | m3u8dlProcess = null; 1465 | } 1466 | catch (Exception ex) 1467 | { 1468 | MessageBox.Show($"中止下载进程出错:{ex.Message}"); 1469 | } 1470 | } 1471 | 1472 | //刷新任务列表 1473 | try 1474 | { 1475 | lbTaskList.Items.Refresh(); 1476 | } 1477 | catch (Exception e) 1478 | { 1479 | Debug.WriteLine($"遇到错误:{e.Message}"); 1480 | Debug.WriteLine("改为用this.Dispatcher.BeginInvoke调用控件"); 1481 | //线程中,不能调用UI控件,只能这样间接调用 1482 | //不然会报错:调用线程无法访问此对象 因为另一个线程拥有该对象 1483 | //参考:https://www.yuantk.com/weblog/dee39674-c663-4e31-bf81-523518165a78.html 1484 | //在创建控件的基础句柄所在线程上异步执行指定委托。 1485 | this.Dispatcher.BeginInvoke(new Action(() => 1486 | { 1487 | lbTaskList.Items.Refresh(); 1488 | })); 1489 | } 1490 | 1491 | } 1492 | 1493 | 1494 | /// 1495 | /// 停止任务列表。因为命令是发送给cmd调用其他下载工具的,所以并不能真的停止。这里只是简单的修改任务状态。 1496 | /// 1497 | /// 1498 | /// 1499 | private void TaskStop_Click(object sender, RoutedEventArgs e) 1500 | { 1501 | StopTask("Stopped"); 1502 | } 1503 | 1504 | 1505 | /// 1506 | /// 重置当前选择的任务的状态。 1507 | /// 1508 | /// 1509 | /// 1510 | private void TaskReset_Click(object sender, RoutedEventArgs e) 1511 | { 1512 | 1513 | if(this.selected_task is null) { return; } 1514 | 1515 | this.selected_task.Status = ""; 1516 | 1517 | lbTaskList.Items.Refresh(); 1518 | } 1519 | 1520 | 1521 | /// 1522 | /// 移除当前选择的任务。 1523 | /// 1524 | /// 1525 | /// 1526 | private void TaskRemove_Click(object sender, RoutedEventArgs e) 1527 | { 1528 | 1529 | if (this.selected_task is null) { return; } 1530 | 1531 | this.m3u8_tasks.Remove(this.selected_task); 1532 | 1533 | lbTaskList.Items.Refresh(); 1534 | } 1535 | 1536 | /// 1537 | /// 移除全部任务。 1538 | /// 1539 | /// 1540 | /// 1541 | private void TaskClear_Click(object sender, RoutedEventArgs e) 1542 | { 1543 | 1544 | this.m3u8_tasks.Clear(); 1545 | lbTaskList.Items.Refresh(); 1546 | } 1547 | 1548 | /// 1549 | /// 打开当前选择的任务对应的网页,便于重新获取新的m3u8 1550 | /// 1551 | /// 1552 | /// 1553 | private void TaskOpenUrl_Click(object sender, RoutedEventArgs e) 1554 | { 1555 | 1556 | if (this.selected_task is null) { return; } 1557 | 1558 | if (string.IsNullOrEmpty(this.selected_task.PageUrl)) 1559 | { 1560 | MessageBox.Show("这个任务没有设置页面地址"); 1561 | return; 1562 | } 1563 | 1564 | if (!Helper.ValidateUrlWithRegex(this.selected_task.PageUrl)) 1565 | { 1566 | MessageBox.Show($"这个任务的页面地址不是网页: {this.selected_task.PageUrl}"); 1567 | return; 1568 | } 1569 | 1570 | Process.Start(this.selected_task.PageUrl); 1571 | } 1572 | 1573 | 1574 | 1575 | /// 1576 | /// 在任务列表上点击了一个任务时触发。 1577 | /// 1578 | /// 1579 | /// 1580 | private void SelectionChanged_Click(object sender, RoutedEventArgs e) 1581 | { 1582 | //设置当前选择的任务为当前任务 1583 | this.selected_task = ((sender as System.Windows.Controls.ListBox).SelectedItem as M3u8TaskItem); 1584 | 1585 | if (!(this.selected_task is null)) 1586 | { 1587 | //把当前任务的内容,填充到左边的表格界面中 1588 | TextBox_Title.Text = this.selected_task.Name; 1589 | TextBox_URL.Text = this.selected_task.M3u8Url; 1590 | TextBox_PageUrl.Text = this.selected_task.PageUrl; 1591 | TextBox_Parameter.Text = this.selected_task.Parameter; 1592 | } 1593 | 1594 | } 1595 | 1596 | /// 1597 | /// 注册m3u8dl协议。 1598 | /// 1599 | /// 1600 | /// 1601 | private void RegisterUrlProtocol(object sender, RoutedEventArgs e) 1602 | { 1603 | string[] args = new string[1]; 1604 | args[0] = "--registerUrlProtocol"; 1605 | 1606 | string result = Helper.RegisterUriScheme("m3u8dl", Assembly.GetExecutingAssembly().Location); 1607 | 1608 | MessageBox.Show(result); 1609 | } 1610 | 1611 | /// 1612 | /// 取消注册m3u8dl协议。 1613 | /// 1614 | /// 1615 | /// 1616 | private void UnregisterUrlProtocol(object sender, RoutedEventArgs e) 1617 | { 1618 | string[] args = new string[1]; 1619 | args[0] = "--unregisterUrlProtocol"; 1620 | 1621 | string result = Helper.UnregisterUriScheme("m3u8dl"); 1622 | 1623 | MessageBox.Show(result); 1624 | } 1625 | 1626 | /// 1627 | /// 处理参数 1628 | /// 1629 | private void HandleArgs() 1630 | { 1631 | string[] args = Helper.Args; 1632 | 1633 | if (args is null) 1634 | { 1635 | return; 1636 | } 1637 | 1638 | if (args.Length < 1) 1639 | { 1640 | return; 1641 | } 1642 | 1643 | if (args.Length > 1) 1644 | { 1645 | MessageBox.Show("运行参数长度大于1,无法识别"); 1646 | return; 1647 | } 1648 | 1649 | if (!args[0].ToLower().StartsWith("m3u8dl:")) 1650 | { 1651 | MessageBox.Show("运行参数开头不是m3u8dl,无法识别"); 1652 | return; 1653 | } 1654 | 1655 | //MessageBox.Show($"运行参数为:{args[0]}"); 1656 | 1657 | string base64 = args[0].Replace("m3u8dl://", "").Replace("m3u8dl:", "").Trim(); 1658 | base64 = base64.TrimEnd('/'); 1659 | string cmd = ""; 1660 | 1661 | //MessageBox.Show($"base64格式运行参数为:{base64}"); 1662 | 1663 | try 1664 | { 1665 | cmd = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); 1666 | } 1667 | catch (Exception e) 1668 | { 1669 | MessageBox.Show($"格式异常:{e.Message}"); 1670 | try 1671 | { 1672 | cmd = Encoding.UTF8.GetString(Convert.FromBase64String(base64.TrimEnd('/'))); 1673 | } 1674 | catch (Exception e2) 1675 | { 1676 | MessageBox.Show($"格式异常2:{e2.Message}"); 1677 | return; 1678 | } 1679 | } 1680 | 1681 | //MessageBox.Show($"cmd为:{cmd}"); 1682 | 1683 | //修正参数转义符 1684 | cmd = cmd.Replace("\\\"", "\""); 1685 | //修正工作目录 1686 | Environment.CurrentDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); 1687 | 1688 | args = Helper.ParseArguments(cmd).ToArray(); //解析命令行 1689 | 1690 | //MessageBox.Show($"解析得到的参数为:{string.Join(", ", args)}"); 1691 | //MessageBox.Show($"参数列表长度为:{args.Length}"); 1692 | 1693 | 1694 | //根据参数,创建对应的M3u8TaskItem 1695 | string m3u8Url = args[0]; 1696 | string name = ""; 1697 | string pageUrl = ""; 1698 | string workDir = ""; 1699 | string headers = ""; 1700 | string proxyAddress = ""; 1701 | 1702 | int name_index = Array.IndexOf(args, "--saveName") + 1; 1703 | int pageUrl_index = Array.IndexOf(args, "--pageUrl") + 1; 1704 | int workDir_index = Array.IndexOf(args, "--workDir") + 1; 1705 | int headers_index = Array.IndexOf(args, "--headers") + 1; 1706 | int proxyAddress_index = Array.IndexOf(args, "--proxyAddress") + 1; 1707 | 1708 | 1709 | 1710 | //参数内容同时更新到界面 1711 | if (name_index > 0) 1712 | { 1713 | name = args[name_index]; 1714 | } 1715 | else 1716 | { 1717 | MessageBox.Show($"无法从参数列表获取文件名:{cmd}"); 1718 | return; 1719 | } 1720 | 1721 | if (pageUrl_index > 0) 1722 | { 1723 | //MessageBox.Show($"更新前cmd: {cmd}"); 1724 | pageUrl = args[pageUrl_index]; 1725 | //N_m3u8DL-CLI无法识别pageUrl所以要重新构建参数 1726 | cmd = ""; 1727 | for (int i = 0; i < args.Length; i++) 1728 | { 1729 | if (i == pageUrl_index || i == pageUrl_index - 1) { continue; } 1730 | 1731 | if (args[i].StartsWith("--")) 1732 | { 1733 | cmd += " " + args[i]; 1734 | } 1735 | else 1736 | { 1737 | cmd += $" \"{args[i]}\""; 1738 | } 1739 | 1740 | } 1741 | //MessageBox.Show($"更新后cmd: {cmd}"); 1742 | } 1743 | 1744 | if (workDir_index > 0) 1745 | { 1746 | workDir = args[workDir_index]; 1747 | } 1748 | 1749 | if (headers_index > 0) 1750 | { 1751 | headers = args[headers_index]; 1752 | } 1753 | 1754 | if (proxyAddress_index > 0) 1755 | { 1756 | proxyAddress = args[proxyAddress_index]; 1757 | } 1758 | 1759 | try 1760 | { 1761 | TextBox_Title.Text = name; 1762 | TextBox_WorkDir.Text = workDir; 1763 | TextBox_URL.Text = m3u8Url; 1764 | TextBox_PageUrl.Text = pageUrl; 1765 | TextBox_Headers.Text = headers; 1766 | TextBox_Proxy.Text = proxyAddress; 1767 | TextBox_Parameter.Text = cmd; 1768 | } 1769 | catch(Exception e) 1770 | { 1771 | Debug.WriteLine($"遇到错误:{e.Message}"); 1772 | Debug.WriteLine("改为用this.Dispatcher.BeginInvoke调用控件"); 1773 | //线程中,不能调用UI控件,只能这样间接调用 1774 | //不然会报错:调用线程无法访问此对象 因为另一个线程拥有该对象 1775 | //参考:https://www.yuantk.com/weblog/dee39674-c663-4e31-bf81-523518165a78.html 1776 | //在创建控件的基础句柄所在线程上异步执行指定委托。 1777 | this.Dispatcher.BeginInvoke(new Action(() => 1778 | { 1779 | TextBox_Title.Text = name; 1780 | TextBox_WorkDir.Text = workDir; 1781 | TextBox_URL.Text = m3u8Url; 1782 | TextBox_PageUrl.Text = pageUrl; 1783 | TextBox_Headers.Text = headers; 1784 | TextBox_Proxy.Text = proxyAddress; 1785 | TextBox_Parameter.Text = cmd; 1786 | })); 1787 | } 1788 | 1789 | 1790 | 1791 | 1792 | M3u8TaskItem a_task = new M3u8TaskItem(name, m3u8Url, pageUrl, cmd); 1793 | 1794 | //添加到列表 1795 | AddTaskToList(a_task); 1796 | 1797 | 1798 | } 1799 | 1800 | 1801 | 1802 | 1803 | /// 1804 | /// 通过命名管道实现进程间通讯。 1805 | /// windows的自定义协议,只能执行指定程序,并传递参数。 1806 | /// 但是对于已经运行中的程序,无法得到自定义协议的参数。 1807 | /// 唯一解决办法是,在自定义协议启动的新实例中,找到已经运行中的实例。并通过命名管道,把信息传递过来。 1808 | /// 管道默认会卡UI,所以要用异步管道: 1809 | /// https://developer.aliyun.com/article/1312874 1810 | /// 1811 | private async Task ReceiveDataAsync() 1812 | { 1813 | 1814 | { 1815 | await Task.Run(async () => 1816 | { 1817 | string data = ""; 1818 | while (!Helper.cts.IsCancellationRequested) 1819 | { 1820 | using (var server = new NamedPipeServerStream(Helper.pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) 1821 | { 1822 | 1823 | try 1824 | { 1825 | //等待连接 1826 | await server.WaitForConnectionAsync(Helper.cts.Token); 1827 | } 1828 | catch (Exception e) 1829 | { 1830 | //MessageBox.Show($"等待管道连接出错:{e.Message}"); 1831 | Debug.WriteLine($"等待管道连接出错:{e.Message}"); 1832 | return; 1833 | } 1834 | 1835 | try 1836 | { 1837 | //申请内存 1838 | byte[] buffer = new byte[2048]; 1839 | //等待1秒 1840 | await Task.Delay(500, Helper.cts.Token); 1841 | //将管道消息给buffer 1842 | int bytesRead = await server.ReadAsync(buffer, 0, buffer.Length); 1843 | 1844 | //将byte[]转换至string 1845 | data = Encoding.UTF8.GetString(buffer, 0, bytesRead); 1846 | } 1847 | catch (Exception e) 1848 | { 1849 | MessageBox.Show($"读取管道出错:{e.Message}"); 1850 | return; 1851 | } 1852 | } 1853 | 1854 | 1855 | //显示接收的消息 1856 | //MessageBox.Show($"收到管道传递的信息:{data}"); 1857 | 1858 | if (string.IsNullOrEmpty(data)) { return; } 1859 | 1860 | //把消息拼成参数列表 1861 | string[] args = new string[1]; 1862 | args[0] = data; 1863 | //塞给全局变量 1864 | try 1865 | { 1866 | Helper.Args = args; 1867 | 1868 | } 1869 | catch (Exception e) { 1870 | MessageBox.Show($"全局赋值出错:{e.Message}"); 1871 | return; 1872 | } 1873 | 1874 | HandleArgs(); 1875 | 1876 | } 1877 | 1878 | 1879 | }); 1880 | } 1881 | 1882 | 1883 | } 1884 | 1885 | 1886 | 1887 | } 1888 | 1889 | 1890 | 1891 | } 1892 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/N_m3u8DL-CLI-SimpleG_List.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {527FA325-6ECD-40FB-A108-8C2747AE6BCD} 8 | WinExe 9 | N_m3u8DL_CLI_SimpleG 10 | N_m3u8DL-CLI-SimpleG_List 11 | v4.8 12 | 512 13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 4 15 | true 16 | true 17 | 18 | 19 | false 20 | 21 | 22 | publish\ 23 | true 24 | Disk 25 | false 26 | Foreground 27 | 7 28 | Days 29 | false 30 | false 31 | true 32 | 1 33 | 1.0.0.%2a 34 | false 35 | true 36 | true 37 | 38 | 39 | true 40 | 41 | 42 | AnyCPU 43 | true 44 | full 45 | false 46 | bin\Debug\ 47 | DEBUG;TRACE 48 | prompt 49 | 4 50 | false 51 | 52 | 53 | AnyCPU 54 | pdbonly 55 | true 56 | bin\Release\ 57 | TRACE 58 | prompt 59 | 4 60 | false 61 | 62 | 63 | logo_3Iv_icon.ico 64 | 65 | 66 | 67 | true 68 | 69 | 70 | E5E1164B3AE3A1D2A8D7B48ACB87479F6DB77431 71 | 72 | 73 | N_m3u8DL-CLI-SimpleG_TemporaryKey.pfx 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 4.0 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | MSBuild:Compile 101 | Designer 102 | 103 | 104 | MSBuild:Compile 105 | Designer 106 | 107 | 108 | App.xaml 109 | Code 110 | 111 | 112 | 113 | MainWindow.xaml 114 | Code 115 | 116 | 117 | 118 | 119 | 120 | Code 121 | 122 | 123 | True 124 | True 125 | Resources.resx 126 | 127 | 128 | True 129 | True 130 | Resources.en-US.resx 131 | 132 | 133 | True 134 | True 135 | Resources.zh-TW.resx 136 | 137 | 138 | True 139 | Settings.settings 140 | True 141 | 142 | 143 | PublicResXFileCodeGenerator 144 | Resources.en-US.Designer.cs 145 | 146 | 147 | PublicResXFileCodeGenerator 148 | Resources.Designer.cs 149 | 150 | 151 | PublicResXFileCodeGenerator 152 | Resources.zh-TW.Designer.cs 153 | 154 | 155 | 156 | SettingsSingleFileGenerator 157 | Settings.Designer.cs 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | False 169 | .NET Framework 3.5 SP1 170 | false 171 | 172 | 173 | 174 | 175 | 2.2.0 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // 有关程序集的一般信息由以下 8 | // 控制。更改这些特性值可修改 9 | // 与程序集关联的信息。 10 | [assembly: AssemblyTitle("N_m3u8DL-CLI-SimpleG")] 11 | [assembly: AssemblyDescription("N_m3u8DL-CLI的简易GUI程序")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("nilaoda")] 14 | [assembly: AssemblyProduct("N_m3u8DL-CLI-SimpleG")] 15 | [assembly: AssemblyCopyright("Copyright © 2022")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // 将 ComVisible 设置为 false 会使此程序集中的类型 20 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 21 | //请将此类型的 ComVisible 特性设置为 true。 22 | [assembly: ComVisible(false)] 23 | 24 | //若要开始生成可本地化的应用程序,请设置 25 | //.csproj 文件中的 CultureYouAreCodingWith 26 | //例如,如果您在源文件中使用的是美国英语, 27 | //使用的是美国英语,请将 设置为 en-US。 然后取消 28 | //对以下 NeutralResourceLanguage 特性的注释。 更新 29 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置 36 | //(未在页面中找到资源时使用, 37 | //或应用程序资源字典中找到时使用) 38 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 39 | //(未在页面中找到资源时使用, 40 | //、应用程序或任何主题专用资源字典中找到时使用) 41 | )] 42 | 43 | 44 | // 程序集的版本信息由下列四个值组成: 45 | // 46 | // 主版本 47 | // 次版本 48 | // 生成号 49 | // 修订号 50 | // 51 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 52 | //通过使用 "*",如下所示: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace N_m3u8DL_CLI_SimpleG.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// 一个强类型的资源类,用于查找本地化的字符串等。 17 | /// 18 | // 此类是由 StronglyTypedResourceBuilder 19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 21 | // (以 /str 作为命令选项),或重新生成 VS 项目。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | public class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// 返回此类使用的缓存的 ResourceManager 实例。 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | public static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("N_m3u8DL_CLI_SimpleG.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// 重写当前线程的 CurrentUICulture 属性,对 51 | /// 使用此强类型资源类的所有资源查找执行重写。 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | public static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// 查找类似 仅合并音频轨道 的本地化字符串。 65 | /// 66 | public static string audioOnly { 67 | get { 68 | return ResourceManager.GetString("audioOnly", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// 查找类似 下载全部分片,合并为音轨 的本地化字符串。 74 | /// 75 | public static string audioOnly_Tip { 76 | get { 77 | return ResourceManager.GetString("audioOnly_Tip", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// 查找类似 设置m3u8文件的Baseurl 的本地化字符串。 83 | /// 84 | public static string baseUrl_Tip { 85 | get { 86 | return ResourceManager.GetString("baseUrl_Tip", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// 查找类似 使用二进制合并 的本地化字符串。 92 | /// 93 | public static string binaryMerge { 94 | get { 95 | return ResourceManager.GetString("binaryMerge", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// 查找类似 采用二进制直接追加合并文件 的本地化字符串。 101 | /// 102 | public static string binaryMerge_Tip { 103 | get { 104 | return ResourceManager.GetString("binaryMerge_Tip", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// 查找类似 选择 的本地化字符串。 110 | /// 111 | public static string changeDir { 112 | get { 113 | return ResourceManager.GetString("changeDir", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// 查找类似 选择存储目录 的本地化字符串。 119 | /// 120 | public static string changeDir_Tip { 121 | get { 122 | return ResourceManager.GetString("changeDir_Tip", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// 查找类似 复制(_C) 的本地化字符串。 128 | /// 129 | public static string copy { 130 | get { 131 | return ResourceManager.GetString("copy", resourceCulture); 132 | } 133 | } 134 | 135 | /// 136 | /// 查找类似 剪切(_T) 的本地化字符串。 137 | /// 138 | public static string cut { 139 | get { 140 | return ResourceManager.GetString("cut", resourceCulture); 141 | } 142 | } 143 | 144 | /// 145 | /// 查找类似 合并后删除分片 的本地化字符串。 146 | /// 147 | public static string deleteAfterDone { 148 | get { 149 | return ResourceManager.GetString("deleteAfterDone", resourceCulture); 150 | } 151 | } 152 | 153 | /// 154 | /// 查找类似 勾选此项后合并完毕将删除所有分片 的本地化字符串。 155 | /// 156 | public static string deleteAfterDone_Tip { 157 | get { 158 | return ResourceManager.GetString("deleteAfterDone_Tip", resourceCulture); 159 | } 160 | } 161 | 162 | /// 163 | /// 查找类似 关闭完整性检查 的本地化字符串。 164 | /// 165 | public static string disableCheck { 166 | get { 167 | return ResourceManager.GetString("disableCheck", resourceCulture); 168 | } 169 | } 170 | 171 | /// 172 | /// 查找类似 下载完毕后跳过检查分片数量是否与m3u8内描述的文件数量一致 的本地化字符串。 173 | /// 174 | public static string disableCheck_Tip { 175 | get { 176 | return ResourceManager.GetString("disableCheck_Tip", resourceCulture); 177 | } 178 | } 179 | 180 | /// 181 | /// 查找类似 合并时不写入日期 的本地化字符串。 182 | /// 183 | public static string disableDate { 184 | get { 185 | return ResourceManager.GetString("disableDate", resourceCulture); 186 | } 187 | } 188 | 189 | /// 190 | /// 查找类似 使用ffmpeg混流时不写入Date属性 的本地化字符串。 191 | /// 192 | public static string disableDate_Tip { 193 | get { 194 | return ResourceManager.GetString("disableDate_Tip", resourceCulture); 195 | } 196 | } 197 | 198 | /// 199 | /// 查找类似 下载完成后不合并 的本地化字符串。 200 | /// 201 | public static string disableMerge { 202 | get { 203 | return ResourceManager.GetString("disableMerge", resourceCulture); 204 | } 205 | } 206 | 207 | /// 208 | /// 查找类似 不合并视频分片 的本地化字符串。 209 | /// 210 | public static string disableMerge_Tip { 211 | get { 212 | return ResourceManager.GetString("disableMerge_Tip", resourceCulture); 213 | } 214 | } 215 | 216 | /// 217 | /// 查找类似 不使用系统代理 的本地化字符串。 218 | /// 219 | public static string disableProxy { 220 | get { 221 | return ResourceManager.GetString("disableProxy", resourceCulture); 222 | } 223 | } 224 | 225 | /// 226 | /// 查找类似 不主动使用系统代理 的本地化字符串。 227 | /// 228 | public static string disableProxy_Tip { 229 | get { 230 | return ResourceManager.GetString("disableProxy_Tip", resourceCulture); 231 | } 232 | } 233 | 234 | /// 235 | /// 查找类似 执行程序 的本地化字符串。 236 | /// 237 | public static string exeToRun { 238 | get { 239 | return ResourceManager.GetString("exeToRun", resourceCulture); 240 | } 241 | } 242 | 243 | /// 244 | /// 查找类似 下载器主程序的文件名 的本地化字符串。 245 | /// 246 | public static string exeToRun_Tip { 247 | get { 248 | return ResourceManager.GetString("exeToRun_Tip", resourceCulture); 249 | } 250 | } 251 | 252 | /// 253 | /// 查找类似 混流MP4边下边看 的本地化字符串。 254 | /// 255 | public static string fastStart { 256 | get { 257 | return ResourceManager.GetString("fastStart", resourceCulture); 258 | } 259 | } 260 | 261 | /// 262 | /// 查找类似 使用ffmpeg混流时将元数据移动到头部 的本地化字符串。 263 | /// 264 | public static string fastStart_Tip { 265 | get { 266 | return ResourceManager.GetString("fastStart_Tip", resourceCulture); 267 | } 268 | } 269 | 270 | /// 271 | /// 查找类似 获取下载器(_N) 的本地化字符串。 272 | /// 273 | public static string getDL { 274 | get { 275 | return ResourceManager.GetString("getDL", resourceCulture); 276 | } 277 | } 278 | 279 | /// 280 | /// 查找类似 点击我即可调用下载器 的本地化字符串。 281 | /// 282 | public static string go_Tip { 283 | get { 284 | return ResourceManager.GetString("go_Tip", resourceCulture); 285 | } 286 | } 287 | 288 | /// 289 | /// 查找类似 请求头 的本地化字符串。 290 | /// 291 | public static string headers { 292 | get { 293 | return ResourceManager.GetString("headers", resourceCulture); 294 | } 295 | } 296 | 297 | /// 298 | /// 查找类似 为请求添加 HTTP Header 的本地化字符串。 299 | /// 300 | public static string headers_Tip { 301 | get { 302 | return ResourceManager.GetString("headers_Tip", resourceCulture); 303 | } 304 | } 305 | 306 | /// 307 | /// 查找类似 自定义IV 的本地化字符串。 308 | /// 309 | public static string iv { 310 | get { 311 | return ResourceManager.GetString("iv", resourceCulture); 312 | } 313 | } 314 | 315 | /// 316 | /// 查找类似 输入HEX字符串 的本地化字符串。 317 | /// 318 | public static string iv_Tip { 319 | get { 320 | return ResourceManager.GetString("iv_Tip", resourceCulture); 321 | } 322 | } 323 | 324 | /// 325 | /// 查找类似 自定义KEY 的本地化字符串。 326 | /// 327 | public static string key { 328 | get { 329 | return ResourceManager.GetString("key", resourceCulture); 330 | } 331 | } 332 | 333 | /// 334 | /// 查找类似 可拖入16字节的本地KEY文件或输入Base64字符串 的本地化字符串。 335 | /// 336 | public static string key_Tip { 337 | get { 338 | return ResourceManager.GetString("key_Tip", resourceCulture); 339 | } 340 | } 341 | 342 | /// 343 | /// 查找类似 M3U8地址 的本地化字符串。 344 | /// 345 | public static string m3u8 { 346 | get { 347 | return ResourceManager.GetString("m3u8", resourceCulture); 348 | } 349 | } 350 | 351 | /// 352 | /// 查找类似 可以输入或拖入文件; 拖入文件夹或txt以批量下载; 双击从剪贴板获取 的本地化字符串。 353 | /// 354 | public static string m3u8_Tip { 355 | get { 356 | return ResourceManager.GetString("m3u8_Tip", resourceCulture); 357 | } 358 | } 359 | 360 | /// 361 | /// 查找类似 限速(kb/s) 的本地化字符串。 362 | /// 363 | public static string maxSpeed { 364 | get { 365 | return ResourceManager.GetString("maxSpeed", resourceCulture); 366 | } 367 | } 368 | 369 | /// 370 | /// 查找类似 设置下载限速;0为不限制 的本地化字符串。 371 | /// 372 | public static string maxSpeed_Tip { 373 | get { 374 | return ResourceManager.GetString("maxSpeed_Tip", resourceCulture); 375 | } 376 | } 377 | 378 | /// 379 | /// 查找类似 最大线程 的本地化字符串。 380 | /// 381 | public static string maxThread { 382 | get { 383 | return ResourceManager.GetString("maxThread", resourceCulture); 384 | } 385 | } 386 | 387 | /// 388 | /// 查找类似 程序将尝试使用不高于此数值的线程下载 的本地化字符串。 389 | /// 390 | public static string maxThread_Tip { 391 | get { 392 | return ResourceManager.GetString("maxThread_Tip", resourceCulture); 393 | } 394 | } 395 | 396 | /// 397 | /// 查找类似 最小线程 的本地化字符串。 398 | /// 399 | public static string minThread { 400 | get { 401 | return ResourceManager.GetString("minThread", resourceCulture); 402 | } 403 | } 404 | 405 | /// 406 | /// 查找类似 程序将尝试使用不低于此数值的线程下载 的本地化字符串。 407 | /// 408 | public static string minThread_Tip { 409 | get { 410 | return ResourceManager.GetString("minThread_Tip", resourceCulture); 411 | } 412 | } 413 | 414 | /// 415 | /// 查找类似 混流文件 的本地化字符串。 416 | /// 417 | public static string muxJson { 418 | get { 419 | return ResourceManager.GetString("muxJson", resourceCulture); 420 | } 421 | } 422 | 423 | /// 424 | /// 查找类似 可拖入特定的json文件 的本地化字符串。 425 | /// 426 | public static string muxJson_Tip { 427 | get { 428 | return ResourceManager.GetString("muxJson_Tip", resourceCulture); 429 | } 430 | } 431 | 432 | /// 433 | /// 查找类似 启动参数 的本地化字符串。 434 | /// 435 | public static string parm { 436 | get { 437 | return ResourceManager.GetString("parm", resourceCulture); 438 | } 439 | } 440 | 441 | /// 442 | /// 查找类似 本程序生成的启动参数 的本地化字符串。 443 | /// 444 | public static string parm_Tip { 445 | get { 446 | return ResourceManager.GetString("parm_Tip", resourceCulture); 447 | } 448 | } 449 | 450 | /// 451 | /// 查找类似 仅解析m3u8 的本地化字符串。 452 | /// 453 | public static string parseOnly { 454 | get { 455 | return ResourceManager.GetString("parseOnly", resourceCulture); 456 | } 457 | } 458 | 459 | /// 460 | /// 查找类似 在解析m3u8之后退出程序,仅生成meta.json 的本地化字符串。 461 | /// 462 | public static string parseOnly_Tip { 463 | get { 464 | return ResourceManager.GetString("parseOnly_Tip", resourceCulture); 465 | } 466 | } 467 | 468 | /// 469 | /// 查找类似 粘贴(_P) 的本地化字符串。 470 | /// 471 | public static string paste { 472 | get { 473 | return ResourceManager.GetString("paste", resourceCulture); 474 | } 475 | } 476 | 477 | /// 478 | /// 查找类似 范围选择 的本地化字符串。 479 | /// 480 | public static string range { 481 | get { 482 | return ResourceManager.GetString("range", resourceCulture); 483 | } 484 | } 485 | 486 | /// 487 | /// 查找类似 设置开始时间或开始分片序号 的本地化字符串。 488 | /// 489 | public static string range_Tip1 { 490 | get { 491 | return ResourceManager.GetString("range_Tip1", resourceCulture); 492 | } 493 | } 494 | 495 | /// 496 | /// 查找类似 设置终止时间或终止分片序号 的本地化字符串。 497 | /// 498 | public static string range_Tip2 { 499 | get { 500 | return ResourceManager.GetString("range_Tip2", resourceCulture); 501 | } 502 | } 503 | 504 | /// 505 | /// 查找类似 重试次数 的本地化字符串。 506 | /// 507 | public static string retryCount { 508 | get { 509 | return ResourceManager.GetString("retryCount", resourceCulture); 510 | } 511 | } 512 | 513 | /// 514 | /// 查找类似 遇到停止速度时,程序尝试重新下载的次数 的本地化字符串。 515 | /// 516 | public static string retryCount_Tip { 517 | get { 518 | return ResourceManager.GetString("retryCount_Tip", resourceCulture); 519 | } 520 | } 521 | 522 | /// 523 | /// 查找类似 设置代理 的本地化字符串。 524 | /// 525 | public static string setProxy { 526 | get { 527 | return ResourceManager.GetString("setProxy", resourceCulture); 528 | } 529 | } 530 | 531 | /// 532 | /// 查找类似 输入http/socks5代理地址,如http://127.0.0.1:8080. 的本地化字符串。 533 | /// 534 | public static string setProxy_Tip { 535 | get { 536 | return ResourceManager.GetString("setProxy_Tip", resourceCulture); 537 | } 538 | } 539 | 540 | /// 541 | /// 查找类似 停速(kb/s) 的本地化字符串。 542 | /// 543 | public static string stopSpeed { 544 | get { 545 | return ResourceManager.GetString("stopSpeed", resourceCulture); 546 | } 547 | } 548 | 549 | /// 550 | /// 查找类似 当下载速度连续几次低于此数值时,重新下载 的本地化字符串。 551 | /// 552 | public static string stopSpeed_Tip { 553 | get { 554 | return ResourceManager.GetString("stopSpeed_Tip", resourceCulture); 555 | } 556 | } 557 | 558 | /// 559 | /// 查找类似 选择一个目录,视频将会下载到此处 的本地化字符串。 560 | /// 561 | public static string String1 { 562 | get { 563 | return ResourceManager.GetString("String1", resourceCulture); 564 | } 565 | } 566 | 567 | /// 568 | /// 查找类似 找不到程序 的本地化字符串。 569 | /// 570 | public static string String2 { 571 | get { 572 | return ResourceManager.GetString("String2", resourceCulture); 573 | } 574 | } 575 | 576 | /// 577 | /// 查找类似 URL为必填项 的本地化字符串。 578 | /// 579 | public static string String3 { 580 | get { 581 | return ResourceManager.GetString("String3", resourceCulture); 582 | } 583 | } 584 | 585 | /// 586 | /// 查找类似 请稍候 的本地化字符串。 587 | /// 588 | public static string String4 { 589 | get { 590 | return ResourceManager.GetString("String4", resourceCulture); 591 | } 592 | } 593 | 594 | /// 595 | /// 查找类似 非预期的byte格式 的本地化字符串。 596 | /// 597 | public static string String5 { 598 | get { 599 | return ResourceManager.GetString("String5", resourceCulture); 600 | } 601 | } 602 | 603 | /// 604 | /// 查找类似 非16 Bytes文件 的本地化字符串。 605 | /// 606 | public static string String6 { 607 | get { 608 | return ResourceManager.GetString("String6", resourceCulture); 609 | } 610 | } 611 | 612 | /// 613 | /// 查找类似 不支持该代理链接! 的本地化字符串。 614 | /// 615 | public static string String7 { 616 | get { 617 | return ResourceManager.GetString("String7", resourceCulture); 618 | } 619 | } 620 | 621 | /// 622 | /// 查找类似 超时时长(s) 的本地化字符串。 623 | /// 624 | public static string timeout { 625 | get { 626 | return ResourceManager.GetString("timeout", resourceCulture); 627 | } 628 | } 629 | 630 | /// 631 | /// 查找类似 设置请求超时时长 的本地化字符串。 632 | /// 633 | public static string timeout_Tip { 634 | get { 635 | return ResourceManager.GetString("timeout_Tip", resourceCulture); 636 | } 637 | } 638 | 639 | /// 640 | /// 查找类似 视频标题 的本地化字符串。 641 | /// 642 | public static string title { 643 | get { 644 | return ResourceManager.GetString("title", resourceCulture); 645 | } 646 | } 647 | 648 | /// 649 | /// 查找类似 双击此处可以自动获取视频标题 的本地化字符串。 650 | /// 651 | public static string title_Tip { 652 | get { 653 | return ResourceManager.GetString("title_Tip", resourceCulture); 654 | } 655 | } 656 | 657 | /// 658 | /// 查找类似 置顶窗口 的本地化字符串。 659 | /// 660 | public static string topMost { 661 | get { 662 | return ResourceManager.GetString("topMost", resourceCulture); 663 | } 664 | } 665 | 666 | /// 667 | /// 查找类似 置顶当前窗口 的本地化字符串。 668 | /// 669 | public static string topMost_Tip { 670 | get { 671 | return ResourceManager.GetString("topMost_Tip", resourceCulture); 672 | } 673 | } 674 | 675 | /// 676 | /// 查找类似 工作目录 的本地化字符串。 677 | /// 678 | public static string workDir { 679 | get { 680 | return ResourceManager.GetString("workDir", resourceCulture); 681 | } 682 | } 683 | 684 | /// 685 | /// 查找类似 存储目录,文件将会被下载到这里 的本地化字符串。 686 | /// 687 | public static string workDir_Tip { 688 | get { 689 | return ResourceManager.GetString("workDir_Tip", resourceCulture); 690 | } 691 | } 692 | } 693 | } 694 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/Resources.en-US.Designer.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/butaixianran/N_m3u8DL-CLI-SimpleG_List/6992dd56280fad1c2605ea10ffc72f30bac29902/N_m3u8DL-CLI-SimpleG/Properties/Resources.en-US.Designer.cs -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/Resources.en-US.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Audio Only 122 | 123 | 124 | Download all segements but only mux for audio track 125 | 126 | 127 | Set the Baseurl of the m3u8 file 128 | 129 | 130 | BinaryMerge 131 | 132 | 133 | Use binary to directly append and merge files 134 | 135 | 136 | Select 137 | 138 | 139 | Select storage directory 140 | 141 | 142 | Copy(_C) 143 | 144 | 145 | Cut(_T) 146 | 147 | 148 | DeleteAfterDone 149 | 150 | 151 | After checking this option, all segments will be deleted after the merge is completed 152 | 153 | 154 | NoIntegrityCheck 155 | 156 | 157 | After the download is complete, skip checking whether the number of segments is consistent with the number of files described in m3u8 158 | 159 | 160 | NoDateInfo 161 | 162 | 163 | Do not write the Date info when using ffmpeg muxing 164 | 165 | 166 | NoMerge 167 | 168 | 169 | Do not merge segments 170 | 171 | 172 | NoProxy 173 | 174 | 175 | Do not actively use system's proxy 176 | 177 | 178 | ToExecute 179 | 180 | 181 | Path or file name of the main program 182 | 183 | 184 | MuxFastStart 185 | 186 | 187 | Move metadata to the head when using ffmpeg muxing 188 | 189 | 190 | Get the downloader 191 | 192 | 193 | Click me to call the downloader 194 | 195 | 196 | Headers 197 | 198 | 199 | Add HTTP headers to the request 200 | 201 | 202 | SetIV 203 | 204 | 205 | Enter the HEX string 206 | 207 | 208 | SetKEY 209 | 210 | 211 | You can drag in a 16-byte local KEY file or input a Base64 string 212 | 213 | 214 | M3U8 215 | 216 | 217 | You can input or drag in file; drag into folders or txt to batch download; double click to get from the clipboard 218 | 219 | 220 | SpeedLimit 221 | 222 | 223 | Set download speed limit(kb/s); 0 means no limit 224 | 225 | 226 | MaxThread 227 | 228 | 229 | The program will try to download using threads not higher than this value 230 | 231 | 232 | MinThread 233 | 234 | 235 | The program will try to download using threads not lower than this value 236 | 237 | 238 | MuxJson 239 | 240 | 241 | You can drag in a specific json file 242 | 243 | 244 | Params 245 | 246 | 247 | Startup parameters generated by this program 248 | 249 | 250 | ParseOnly 251 | 252 | 253 | Exit the program after parsing m3u8 and only generate meta.json 254 | 255 | 256 | Paste(_P) 257 | 258 | 259 | SetRange 260 | 261 | 262 | Set the start time or start segment sequence number 263 | 264 | 265 | Set the end time or end fragment segment number 266 | 267 | 268 | RetryCount 269 | 270 | 271 | The number of times the program tries to download again when it encounters a stop speed 272 | 273 | 274 | StopSpeed 275 | 276 | 277 | When the download speed is lower than this value(kb/s) for several consecutive times, download again 278 | 279 | 280 | Timeout(s) 281 | 282 | 283 | Set the request timeout 284 | 285 | 286 | FileName 287 | 288 | 289 | Double click here to get the video title automatically 290 | 291 | 292 | Set Top 293 | 294 | 295 | Set Top most for current window 296 | 297 | 298 | WorkDir 299 | 300 | 301 | Storage directory, files will be downloaded here 302 | 303 | 304 | Choose a directory and the video will be downloaded here 305 | 306 | 307 | File not found 308 | 309 | 310 | URL is required 311 | 312 | 313 | Wait 314 | 315 | 316 | Unexpected byte format 317 | 318 | 319 | Not 16 Bytes file 320 | 321 | 322 | Set HTTP/SOCKS5 Proxy, like http://127.0.0.1:8080 323 | 324 | 325 | SetProxy 326 | 327 | 328 | Proxy is invaild! 329 | 330 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 仅合并音频轨道 122 | 123 | 124 | 下载全部分片,合并为音轨 125 | 126 | 127 | 设置m3u8文件的Baseurl 128 | 129 | 130 | 使用二进制合并 131 | 132 | 133 | 采用二进制直接追加合并文件 134 | 135 | 136 | 选择 137 | 138 | 139 | 选择存储目录 140 | 141 | 142 | 复制(_C) 143 | 144 | 145 | 剪切(_T) 146 | 147 | 148 | 合并后删除分片 149 | 150 | 151 | 勾选此项后合并完毕将删除所有分片 152 | 153 | 154 | 关闭完整性检查 155 | 156 | 157 | 下载完毕后跳过检查分片数量是否与m3u8内描述的文件数量一致 158 | 159 | 160 | 合并时不写入日期 161 | 162 | 163 | 使用ffmpeg混流时不写入Date属性 164 | 165 | 166 | 下载完成后不合并 167 | 168 | 169 | 不合并视频分片 170 | 171 | 172 | 不使用系统代理 173 | 174 | 175 | 不主动使用系统代理 176 | 177 | 178 | 执行程序 179 | 180 | 181 | 下载器主程序的文件名 182 | 183 | 184 | 混流MP4边下边看 185 | 186 | 187 | 使用ffmpeg混流时将元数据移动到头部 188 | 189 | 190 | 获取下载器(_N) 191 | 192 | 193 | 点击我即可调用下载器 194 | 195 | 196 | 请求头 197 | 198 | 199 | 为请求添加 HTTP Header 200 | 201 | 202 | 自定义IV 203 | 204 | 205 | 输入HEX字符串 206 | 207 | 208 | 自定义KEY 209 | 210 | 211 | 可拖入16字节的本地KEY文件或输入Base64字符串 212 | 213 | 214 | M3U8地址 215 | 216 | 217 | 可以输入或拖入文件; 拖入文件夹或txt以批量下载; 双击从剪贴板获取 218 | 219 | 220 | 限速(kb/s) 221 | 222 | 223 | 设置下载限速;0为不限制 224 | 225 | 226 | 最大线程 227 | 228 | 229 | 程序将尝试使用不高于此数值的线程下载 230 | 231 | 232 | 最小线程 233 | 234 | 235 | 程序将尝试使用不低于此数值的线程下载 236 | 237 | 238 | 混流文件 239 | 240 | 241 | 可拖入特定的json文件 242 | 243 | 244 | 启动参数 245 | 246 | 247 | 本程序生成的启动参数 248 | 249 | 250 | 仅解析m3u8 251 | 252 | 253 | 在解析m3u8之后退出程序,仅生成meta.json 254 | 255 | 256 | 粘贴(_P) 257 | 258 | 259 | 范围选择 260 | 261 | 262 | 设置开始时间或开始分片序号 263 | 264 | 265 | 设置终止时间或终止分片序号 266 | 267 | 268 | 重试次数 269 | 270 | 271 | 遇到停止速度时,程序尝试重新下载的次数 272 | 273 | 274 | 停速(kb/s) 275 | 276 | 277 | 当下载速度连续几次低于此数值时,重新下载 278 | 279 | 280 | 选择一个目录,视频将会下载到此处 281 | 282 | 283 | 找不到程序 284 | 285 | 286 | URL为必填项 287 | 288 | 289 | 请稍候 290 | 291 | 292 | 非预期的byte格式 293 | 294 | 295 | 非16 Bytes文件 296 | 297 | 298 | 超时时长(s) 299 | 300 | 301 | 设置请求超时时长 302 | 303 | 304 | 视频标题 305 | 306 | 307 | 双击此处可以自动获取视频标题 308 | 309 | 310 | 置顶窗口 311 | 312 | 313 | 置顶当前窗口 314 | 315 | 316 | 工作目录 317 | 318 | 319 | 存储目录,文件将会被下载到这里 320 | 321 | 322 | 设置代理 323 | 324 | 325 | 输入http/socks5代理地址,如http://127.0.0.1:8080. 326 | 327 | 328 | 不支持该代理链接! 329 | 330 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/Resources.zh-TW.Designer.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/butaixianran/N_m3u8DL-CLI-SimpleG_List/6992dd56280fad1c2605ea10ffc72f30bac29902/N_m3u8DL-CLI-SimpleG/Properties/Resources.zh-TW.Designer.cs -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/Resources.zh-TW.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 最小線程 122 | 123 | 124 | 最大線程 125 | 126 | 127 | 自設KEY 128 | 129 | 130 | 自設IV 131 | 132 | 133 | 重試次數 134 | 135 | 136 | 置頂當前窗口 137 | 138 | 139 | 置頂窗口 140 | 141 | 142 | 執行程式 143 | 144 | 145 | 粘貼(_P) 146 | 147 | 148 | 在解析m3u8之後退出程式,僅生成meta.json 149 | 150 | 151 | 遇到停止速度時,程式嘗試重新下載的次數 152 | 153 | 154 | 選擇存儲目錄 155 | 156 | 157 | 選擇 158 | 159 | 160 | 限速(kb/s) 161 | 162 | 163 | 下載完成後不合並 164 | 165 | 166 | 下載完畢後跳過檢查分片數量是否與m3u8內描述的文件數量壹致 167 | 168 | 169 | 下載全部分片,合並為音軌 170 | 171 | 172 | 下載器主程式的文件名 173 | 174 | 175 | 為請求添加 HTTP Header 176 | 177 | 178 | 停速(kb/s) 179 | 180 | 181 | 雙擊此處可以自動獲取視頻標題 182 | 183 | 184 | 輸入HEX字符串 185 | 186 | 187 | 檔案標題 188 | 189 | 190 | 使用二進制合並 191 | 192 | 193 | 使用ffmpeg混流時將元數據移動到頭部 194 | 195 | 196 | 使用ffmpeg混流時不寫入Date屬性 197 | 198 | 199 | 設置終止時間或終止分片序號 200 | 201 | 202 | 設置下載限速;0為不限制 203 | 204 | 205 | 設置請求超時時長 206 | 207 | 208 | 設置開始時間或開始分片序號 209 | 210 | 211 | 設置m3u8文件的Baseurl 212 | 213 | 214 | 請求頭 215 | 216 | 217 | 啟動參數 218 | 219 | 220 | 可以輸入或拖入文件; 拖入文件夾或txt以批量下載; 雙擊從剪貼板獲取 221 | 222 | 223 | 可拖入特定的json文件 224 | 225 | 226 | 可拖入16字節的本地KEY文件或輸入Base64字符串 227 | 228 | 229 | 僅解析m3u8 230 | 231 | 232 | 僅合並音頻軌道 233 | 234 | 235 | 剪切(_T) 236 | 237 | 238 | 獲取下載器(_N) 239 | 240 | 241 | 混流文件 242 | 243 | 244 | 混流MP4邊下邊看 245 | 246 | 247 | 合並時不寫入日期 248 | 249 | 250 | 合並後刪除分片 251 | 252 | 253 | 關閉完整性檢查 254 | 255 | 256 | 勾選此項後合並完畢將刪除所有分片 257 | 258 | 259 | 工作目錄 260 | 261 | 262 | 復制(_C) 263 | 264 | 265 | 範圍選擇 266 | 267 | 268 | 點擊我即可調用下載器 269 | 270 | 271 | 當下載速度連續幾次低於此數值時,重新下載 272 | 273 | 274 | 存儲目錄,檔案將會被下載到這裏 275 | 276 | 277 | 程序將嘗試使用不高於此數值的線程下載 278 | 279 | 280 | 程序將嘗試使用不低於此數值的線程下載 281 | 282 | 283 | 超時時長(s) 284 | 285 | 286 | 采用二進制直接追加合並文件 287 | 288 | 289 | 不主動使用系統代理 290 | 291 | 292 | 不使用系統代理 293 | 294 | 295 | 不合並視頻分片 296 | 297 | 298 | 本程序生成的啟動參數 299 | 300 | 301 | M3U8地址 302 | 303 | 304 | 選擇壹個目錄,視頻將會下載到此處 305 | 306 | 307 | 找不到程式 308 | 309 | 310 | URL為必填項 311 | 312 | 313 | 請稍候 314 | 315 | 316 | 非預期的byte格式 317 | 318 | 319 | 非16 Bytes文件 320 | 321 | 322 | 輸入http/socks5代理地址,如http://127.0.0.1:8080 323 | 324 | 325 | 設置代理 326 | 327 | 328 | 不支持該代理鏈接! 329 | 330 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace N_m3u8DL_CLI_SimpleG.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG/logo_3Iv_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/butaixianran/N_m3u8DL-CLI-SimpleG_List/6992dd56280fad1c2605ea10ffc72f30bac29902/N_m3u8DL-CLI-SimpleG/logo_3Iv_icon.ico -------------------------------------------------------------------------------- /N_m3u8DL-CLI-SimpleG_List.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34330.188 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "N_m3u8DL-CLI-SimpleG_List", "N_m3u8DL-CLI-SimpleG\N_m3u8DL-CLI-SimpleG_List.csproj", "{527FA325-6ECD-40FB-A108-8C2747AE6BCD}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {527FA325-6ECD-40FB-A108-8C2747AE6BCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {527FA325-6ECD-40FB-A108-8C2747AE6BCD}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {527FA325-6ECD-40FB-A108-8C2747AE6BCD}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {527FA325-6ECD-40FB-A108-8C2747AE6BCD}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {2AE62102-3D14-448F-8C53-811AF11AB5D3} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # N_m3u8DL-CLI-SimpleG-List (Windows Only) 2 | 根据m3u8下载工具 [N_m3u8DL-CLI](https://github.com/nilaoda/N_m3u8DL-CLI) 命令行工具的官方 Simple GUI 图形界面启动器改动。 3 | [https://github.com/nilaoda/N_m3u8DL-CLI-SimpleG](https://github.com/nilaoda/N_m3u8DL-CLI-SimpleG) 4 | 5 | # 新功能 6 | * 新增任务列表功能 7 | * 列表区域下方会显示下载进度的Log。 8 | * 原版的"Go"按钮,变为"Add",改为把任务添加到列表,而不是直接下载。 9 | * GUI端支持 m3u8dl 协议。可以配合chrome浏览器扩展 [猫抓](https://chromewebstore.google.com/detail/%E7%8C%AB%E6%8A%93/jfedfbgedapdagkghmgibemcoggfppbb?hl=zh-CN) 使用。在[猫抓](https://chromewebstore.google.com/detail/%E7%8C%AB%E6%8A%93/jfedfbgedapdagkghmgibemcoggfppbb?hl=zh-CN)中,点击下载,可以自动打开本GUI工具,把下载任务添加到列表中。 10 | * 参数中,增加"--pageUrl"参数。这个参数是指下载的网页。实际下载时,不会用到,但是会保存在任务中。这样,选择一个任务,点击"打开网页"按钮,就能方便的打开该视频的网页。当下载失败时,便于重新获取m3u8地址。 11 | * 点击任务列表中的一个任务,任务的内容,会填充到左边表单,可以修改之后,重新添加。 12 | * 任务列表中的任务,右侧会显示简单的下载状态。 13 | * 同时只下载1个任务。 14 | 15 | ![image](img/screenshot.jpg) 16 | 17 | # 使用方法 18 | ## 下载N_m3u8DL-CLI 19 | (这一步,会用N_m3u8DL-CLI直接跳过) 20 | 在[N_m3u8DL-CLI项目页面](https://github.com/nilaoda/N_m3u8DL-CLI)项目页面,点击右侧Release页面,去下载最新版本。 21 | 22 | 要下载的版本是:`N_m3u8DL-CLI_v版本号_with_ffmpeg_and_SimpleG.zip`。 23 | 24 | **下载后解压到自定义的位置,以后位置不要再换。** 25 | 26 | ## 对于已经有N_m3u8DL-CLI的用户 27 | 如果注册过它的m3u8DL协议,要先注销这个协议。因为要改为在本增强版GUI工具那边注册这个协议。 28 | 29 | 注销方法:命令行模式,前往N_m3u8DL-CLI目录。执行: 30 | > N_m3u8DL-CLI可执行文件名 --unregisterUrlProtocol 31 | 32 | ## 下载本增强版GUI 33 | 前往项目池右侧Release页面,下载最新版。 34 | 解压.exe文件到N_m3u8DL-CLI同目录。 35 | 36 | ## 配置本增强版GUI 37 | **先用管理员模式打开本增强版GUI。** 点击左下角的"注册m3u8DL协议"。注册成功后,关闭。 **以后使用无须管理员模式。** 只有要注销协议,才用管理员模式打开。 38 | 39 | 用普通模式打开本增强版GUI工具,在左侧表单: 40 | * 填写N_m3u8DL-CLI可执行文件名 41 | * 选择要下载到的目录 42 | * 勾选"合并后删除分片" 43 | 44 | 配置完成。 45 | 46 | 47 | ## 手动添加任务到列表(不推荐) 48 | 有了m3u8地址,可以手动填写左侧的表单,然后点击最下面"Add"按钮,把任务添加到右侧列表。 49 | 50 | 添加了足够的任务,下载即可。 51 | 52 | 支持一边下载,一边添加新任务。 53 | 54 | ## 配合浏览器扩展"猫抓"一键新建任务到列表(推荐) 55 | 猫抓 是个视频嗅探 浏览器扩展。可以嗅探m3u8,而且支持刚才注册的那个N_m3u8DL-CLI自定义协议。 56 | 57 | * 安装猫抓浏览器扩展 58 | [https://chromewebstore.google.com/detail/%E7%8C%AB%E6%8A%93/jfedfbgedapdagkghmgibemcoggfppbb?hl=zh-CN](https://chromewebstore.google.com/detail/%E7%8C%AB%E6%8A%93/jfedfbgedapdagkghmgibemcoggfppbb?hl=zh-CN) 59 | * 在猫抓的设置中,开启`调用N_m3u8DL-CLI的m3u8dl://协议下载m3u8 和 mpd`。填写好协议要用的下载参数。 60 | 61 | 下面的参数供参考: 62 | ``` 63 | "${url}" --saveName "${title}" --workDir "你的下载目录" --enableDelAfterDone --headers "Referer:${initiator}" --pageUrl "${webUrl}" --proxyAddress "socks5://127.0.0.1:你的代理端口" 64 | ``` 65 | 66 | 好了,配置完毕。 67 | 68 | ## 在视频网页上使用 69 | 以后在有m3u8的网页上,点击猫抓扩展图标,点击m3u8旁边的下载按钮。就会触发之前注册的协议,自动打开本增强版GUI工具,把这个地址,自动添加到任务列表中。 70 | 71 | 添加够了之后,点击下载即可。 72 | 73 | 74 | # 下载规则 75 | ## 同时进行任务数 76 | 同时只下载一个任务,完成一个再下一个。 77 | 78 | ## 列表保存 79 | 任务列表不保存,关闭GUI既清空。 80 | m3u8地址经常一两个小时就会失效,所以就懒得做保存列表功能了,凑合吧。 81 | 82 | ## 任务状态 83 | 下载列表右侧有个任务状态显示。默认为空,其他几个状态是:"Downloading, Stopped, Failed, Done"等。 84 | 85 | ## 跳过的任务 86 | 凡是状态不为空的任务,都会直接跳过。所以,停止的任务,再次下载前要重置状态: 87 | 88 | 选择状态不为空的任务,点击按钮"重置"。 **只会重置状态,不会删除已经下载的分片。** 89 | 90 | ## 同名任务 91 | 添加新任务时,如果存在同名的任务 : 92 | * 如果存在的任务状态是空,就会去更新这个任务的地址。 93 | * 如果存在的任务状态不是空,就会新增一个同名的任务。 94 | 95 | ## 选择任务 96 | 任务列表中,点击选择一个任务,任务内容,会被填充到左侧的表单。可以修改后再次添加。 97 | 98 | ## 新参数:页面地址 99 | 猫抓里,对应的命令参数是`pageUrl`。这个参数,下载时并不使用,但是会保存在任务中。 100 | 101 | 这样,点击一个任务,再点击按钮`打开网页`就会在浏览器打开这个视频地址。方便获取新的m3u8。 102 | 103 | ## 某些情况界面卡住 104 | 下载期间,右下方会显示下载过程的Log信息。当下载速度太慢 或 文件合并的时候,会没有新的Log,而UI要一直等待Log,界面就会短暂卡住。这是人家的简易项目架构,这里只是改动,所以就凑合吧。 105 | 106 | 107 | # 开发人员信息 108 | 如果要自己继续开发,以下信息需要了解。 109 | 110 | 原版是C#的WPF项目。框架最高到.net framework 4.8,难以移植到更高。 111 | 112 | 虽然看起来只是添加了个任务列表,但背后的辛苦非常之多。 113 | 114 | 首先,windows的注册自定义协议,有很大的局限。当浏览器中,触发了协议时,windows只能打开指定程序,并传递参数。 115 | 116 | 但是,如果这个程序已经打开了,那么是收不到任何参数的。windows会再打开一个新实例。 117 | 118 | 所以,这里只能是在新实例里面,查找是否有同名的进程已经打开,然后,把参数,传递给已经打开的进程,并把这个进程,显示到前台。之后,这个新实例,再把自己关闭。 119 | 120 | 两个进程之间通讯,使用的是命名管道。程序一旦运行,就有个反复监听命名管道的服务。 121 | 122 | 监听命名管道是手写的异步管道,才能不卡界面。异步管道,以任务的方式,跑在不同线程中。这样,才能不卡住界面。 123 | 124 | 然而,在WPF中,不同的线程,不能修改UI。所以,获得了数据之后,不能简单添加到UI列表中。必须使用Invoke和Action的方式修改UI。 125 | 126 | 最后,下载的时候,是在后台运行N_m3u8DL-CLI进程进行下载。要不卡住界面,这个进程就要异步。异步等待进程结束的方法,是.net 6.0才有的。在.net framework 4.8上,还没有提供。所以,这里是手写了一个异步等待进程结束的方法。 127 | 128 | 同样的,N_m3u8DL-CLI进程的命令行输出的Log,要用异步的方式,重定向到本工具界面上。然而,即使异步,也要等待N_m3u8DL-CLI提供Log信息。如前所述,N_m3u8DL-CLI进程不提供的时候,就会卡住界面。 129 | 130 | # 后续 131 | 没有。 132 | 133 | 本来以为几个小时就能搞定的简单功能,没想到一大堆雷区,折磨了好几天,已经严重超时。不打算更新,用的人将就。开发人员可以自行继续开发。 134 | 135 | 136 | # 原版文档 137 | 138 | 对应命令行工具:https://github.com/nilaoda/N_m3u8DL-CLI 139 | 140 | 相关说明:https://nilaoda.github.io/N_m3u8DL-CLI/SimpleGUI.html 141 | 142 | ![image](https://user-images.githubusercontent.com/20772925/153235235-712b338e-4e2a-4a77-8b3b-119bceb45f24.png) -------------------------------------------------------------------------------- /img/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/butaixianran/N_m3u8DL-CLI-SimpleG_List/6992dd56280fad1c2605ea10ffc72f30bac29902/img/screenshot.jpg --------------------------------------------------------------------------------