├── .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 |
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 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
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 |
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 | 
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 | 
--------------------------------------------------------------------------------
/img/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/butaixianran/N_m3u8DL-CLI-SimpleG_List/6992dd56280fad1c2605ea10ffc72f30bac29902/img/screenshot.jpg
--------------------------------------------------------------------------------