├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── NegativeEncoder.sln
├── NegativeEncoder
├── About
│ ├── AboutWindow.xaml
│ ├── AboutWindow.xaml.cs
│ ├── CheckUpdate.cs
│ └── Version.cs
├── App.xaml
├── App.xaml.cs
├── AppContext.cs
├── AssemblyInfo.cs
├── EncodingTask
│ ├── EncodingContext.cs
│ ├── EncodingTask.cs
│ ├── TaskArgs
│ │ ├── AudioEncoding.cs
│ │ ├── AudioExtract.cs
│ │ ├── FFMpegPipe.cs
│ │ ├── HDRTagUseFFMpeg.cs
│ │ ├── Muxer.cs
│ │ ├── SimpleEncoding.cs
│ │ ├── SimpleWithAss.cs
│ │ ├── TaskArgBuilder.cs
│ │ └── VSPipe.cs
│ ├── TaskBuilder.cs
│ ├── TaskDetailWindow.xaml
│ ├── TaskDetailWindow.xaml.cs
│ ├── TaskNameConverter.cs
│ └── TaskProvider.cs
├── FileSelector
│ ├── FileList.xaml
│ ├── FileList.xaml.cs
│ ├── FileName.cs
│ └── FileSelector.cs
├── FodyWeavers.xml
├── FodyWeavers.xsd
├── FunctionTabs
│ ├── FunctionTabs.xaml
│ └── FunctionTabs.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── NegativeEncoder.csproj
├── Presets
│ ├── Converters
│ │ ├── AudioEncodeEnableModeConverter.cs
│ │ ├── CustomVisibilityConverter.cs
│ │ ├── EncodeModeConverter.cs
│ │ ├── QSVNVENCEnableConverter.cs
│ │ ├── SDREnableConverter.cs
│ │ └── TitleConverter.cs
│ ├── EncoderEnums.cs
│ ├── Preset.cs
│ ├── PresetContext.cs
│ ├── PresetOption.cs
│ ├── PresetProvider.cs
│ ├── PresetReName.xaml
│ └── PresetReName.xaml.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Resources
│ ├── baseline_add_black_18dp.png
│ ├── baseline_remove_black_18dp.png
│ └── icon.png
├── StatusBar
│ ├── ProgressToVisibilityValueConverter.cs
│ └── Status.cs
├── SystemOptions
│ ├── Config.cs
│ └── SystemOption.cs
├── Utils
│ ├── CmdTools
│ │ ├── InstallReg.cs
│ │ └── SaveCmdTools.cs
│ ├── DeepCompare.cs
│ ├── HttpClientFactory.cs
│ ├── OpenBrowserViewLink.cs
│ ├── ProcessExtend.cs
│ ├── SystemInfo.cs
│ └── TempFile.cs
├── VsScriptBuilder
│ ├── VsScript.cs
│ └── VsScriptBuilder.cs
└── ne.ico
├── README.md
└── install
├── install.ico
├── install.png
├── installnecmdtools.reg
├── licence.txt
├── nvinstall_v5.0.4_v2.nsi
├── removenecmdtools.reg
└── up.bmp
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: windows-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v3
12 | - name: Setup .NET
13 | uses: actions/setup-dotnet@v2
14 | with:
15 | dotnet-version: 6.0.x
16 | - name: Restore dependencies
17 | run: dotnet restore
18 | - name: Build
19 | run: dotnet build --no-restore
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | [Aa][Rr][Mm]/
24 | [Aa][Rr][Mm]64/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | [Ll]og/
29 |
30 | # Visual Studio 2015/2017 cache/options directory
31 | .vs/
32 | # Uncomment if you have tasks that create the project's static files in wwwroot
33 | #wwwroot/
34 |
35 | # Visual Studio 2017 auto generated files
36 | Generated\ Files/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # Benchmark Results
52 | BenchmarkDotNet.Artifacts/
53 |
54 | # .NET Core
55 | project.lock.json
56 | project.fragment.lock.json
57 | artifacts/
58 |
59 | # StyleCop
60 | StyleCopReport.xml
61 |
62 | # Files built by Visual Studio
63 | *_i.c
64 | *_p.c
65 | *_h.h
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.iobj
70 | *.pch
71 | *.pdb
72 | *.ipdb
73 | *.pgc
74 | *.pgd
75 | *.rsp
76 | *.sbr
77 | *.tlb
78 | *.tli
79 | *.tlh
80 | *.tmp
81 | *.tmp_proj
82 | *_wpftmp.csproj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # Visual Studio Trace Files
112 | *.e2e
113 |
114 | # TFS 2012 Local Workspace
115 | $tf/
116 |
117 | # Guidance Automation Toolkit
118 | *.gpState
119 |
120 | # ReSharper is a .NET coding add-in
121 | _ReSharper*/
122 | *.[Rr]e[Ss]harper
123 | *.DotSettings.user
124 |
125 | # JustCode is a .NET coding add-in
126 | .JustCode
127 |
128 | # TeamCity is a build add-in
129 | _TeamCity*
130 |
131 | # DotCover is a Code Coverage Tool
132 | *.dotCover
133 |
134 | # AxoCover is a Code Coverage Tool
135 | .axoCover/*
136 | !.axoCover/settings.json
137 |
138 | # Visual Studio code coverage results
139 | *.coverage
140 | *.coveragexml
141 |
142 | # NCrunch
143 | _NCrunch_*
144 | .*crunch*.local.xml
145 | nCrunchTemp_*
146 |
147 | # MightyMoose
148 | *.mm.*
149 | AutoTest.Net/
150 |
151 | # Web workbench (sass)
152 | .sass-cache/
153 |
154 | # Installshield output folder
155 | [Ee]xpress/
156 |
157 | # DocProject is a documentation generator add-in
158 | DocProject/buildhelp/
159 | DocProject/Help/*.HxT
160 | DocProject/Help/*.HxC
161 | DocProject/Help/*.hhc
162 | DocProject/Help/*.hhk
163 | DocProject/Help/*.hhp
164 | DocProject/Help/Html2
165 | DocProject/Help/html
166 |
167 | # Click-Once directory
168 | publish/
169 |
170 | # Publish Web Output
171 | *.[Pp]ublish.xml
172 | *.azurePubxml
173 | # Note: Comment the next line if you want to checkin your web deploy settings,
174 | # but database connection strings (with potential passwords) will be unencrypted
175 | *.pubxml
176 | *.publishproj
177 |
178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
179 | # checkin your Azure Web App publish settings, but sensitive information contained
180 | # in these scripts will be unencrypted
181 | PublishScripts/
182 |
183 | # NuGet Packages
184 | *.nupkg
185 | # The packages folder can be ignored because of Package Restore
186 | **/[Pp]ackages/*
187 | # except build/, which is used as an MSBuild target.
188 | !**/[Pp]ackages/build/
189 | # Uncomment if necessary however generally it will be regenerated when needed
190 | #!**/[Pp]ackages/repositories.config
191 | # NuGet v3's project.json files produces more ignorable files
192 | *.nuget.props
193 | *.nuget.targets
194 |
195 | # Microsoft Azure Build Output
196 | csx/
197 | *.build.csdef
198 |
199 | # Microsoft Azure Emulator
200 | ecf/
201 | rcf/
202 |
203 | # Windows Store app package directories and files
204 | AppPackages/
205 | BundleArtifacts/
206 | Package.StoreAssociation.xml
207 | _pkginfo.txt
208 | *.appx
209 |
210 | # Visual Studio cache files
211 | # files ending in .cache can be ignored
212 | *.[Cc]ache
213 | # but keep track of directories ending in .cache
214 | !?*.[Cc]ache/
215 |
216 | # Others
217 | ClientBin/
218 | ~$*
219 | *~
220 | *.dbmdl
221 | *.dbproj.schemaview
222 | *.jfm
223 | *.pfx
224 | *.publishsettings
225 | orleans.codegen.cs
226 |
227 | # Including strong name files can present a security risk
228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
229 | #*.snk
230 |
231 | # Since there are multiple workflows, uncomment next line to ignore bower_components
232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
233 | #bower_components/
234 |
235 | # RIA/Silverlight projects
236 | Generated_Code/
237 |
238 | # Backup & report files from converting an old project file
239 | # to a newer Visual Studio version. Backup files are not needed,
240 | # because we have git ;-)
241 | _UpgradeReport_Files/
242 | Backup*/
243 | UpgradeLog*.XML
244 | UpgradeLog*.htm
245 | ServiceFabricBackup/
246 | *.rptproj.bak
247 |
248 | # SQL Server files
249 | *.mdf
250 | *.ldf
251 | *.ndf
252 |
253 | # Business Intelligence projects
254 | *.rdl.data
255 | *.bim.layout
256 | *.bim_*.settings
257 | *.rptproj.rsuser
258 | *- Backup*.rdl
259 |
260 | # Microsoft Fakes
261 | FakesAssemblies/
262 |
263 | # GhostDoc plugin setting file
264 | *.GhostDoc.xml
265 |
266 | # Node.js Tools for Visual Studio
267 | .ntvs_analysis.dat
268 | node_modules/
269 |
270 | # Visual Studio 6 build log
271 | *.plg
272 |
273 | # Visual Studio 6 workspace options file
274 | *.opt
275 |
276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
277 | *.vbw
278 |
279 | # Visual Studio LightSwitch build output
280 | **/*.HTMLClient/GeneratedArtifacts
281 | **/*.DesktopClient/GeneratedArtifacts
282 | **/*.DesktopClient/ModelManifest.xml
283 | **/*.Server/GeneratedArtifacts
284 | **/*.Server/ModelManifest.xml
285 | _Pvt_Extensions
286 |
287 | # Paket dependency manager
288 | .paket/paket.exe
289 | paket-files/
290 |
291 | # FAKE - F# Make
292 | .fake/
293 |
294 | # JetBrains Rider
295 | .idea/
296 | *.sln.iml
297 |
298 | # CodeRush personal settings
299 | .cr/personal
300 |
301 | # Python Tools for Visual Studio (PTVS)
302 | __pycache__/
303 | *.pyc
304 |
305 | # Cake - Uncomment if you are using it
306 | # tools/**
307 | # !tools/packages.config
308 |
309 | # Tabs Studio
310 | *.tss
311 |
312 | # Telerik's JustMock configuration file
313 | *.jmconfig
314 |
315 | # BizTalk build output
316 | *.btp.cs
317 | *.btm.cs
318 | *.odx.cs
319 | *.xsd.cs
320 |
321 | # OpenCover UI analysis results
322 | OpenCover/
323 |
324 | # Azure Stream Analytics local run output
325 | ASALocalRun/
326 |
327 | # MSBuild Binary and Structured Log
328 | *.binlog
329 |
330 | # NVidia Nsight GPU debugger configuration file
331 | *.nvuser
332 |
333 | # MFractors (Xamarin productivity tool) working folder
334 | .mfractor/
335 |
336 | # Local History for Visual Studio
337 | .localhistory/
338 |
339 | # BeatPulse healthcheck temp database
340 | healthchecksdb
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright © 2018-2021 MeowSound Idols
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/NegativeEncoder.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.2.32526.322
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NegativeEncoder", "NegativeEncoder\NegativeEncoder.csproj", "{F4B7B767-B11F-4F61-BCAC-B95378ADED8D}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Debug|x64 = Debug|x64
12 | Release|Any CPU = Release|Any CPU
13 | Release|x64 = Release|x64
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {F4B7B767-B11F-4F61-BCAC-B95378ADED8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {F4B7B767-B11F-4F61-BCAC-B95378ADED8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {F4B7B767-B11F-4F61-BCAC-B95378ADED8D}.Debug|x64.ActiveCfg = Debug|x64
19 | {F4B7B767-B11F-4F61-BCAC-B95378ADED8D}.Debug|x64.Build.0 = Debug|x64
20 | {F4B7B767-B11F-4F61-BCAC-B95378ADED8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {F4B7B767-B11F-4F61-BCAC-B95378ADED8D}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {F4B7B767-B11F-4F61-BCAC-B95378ADED8D}.Release|x64.ActiveCfg = Release|x64
23 | {F4B7B767-B11F-4F61-BCAC-B95378ADED8D}.Release|x64.Build.0 = Release|x64
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {714AA5FE-44A5-494B-A6A4-3018A926A5BB}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/NegativeEncoder/About/AboutWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
11 |
13 |
15 |
16 |
17 | Special Thanks to MeowSound Idols and other supporters.
18 |
19 | 项目地址:https://github.com/zyzsdy/NegativeEncoder
20 |
21 |
22 | 本软件按照 MIT 协议授权。随本软件分发的其他开源视频工具按照它们各自的授权协议授权。
23 |
24 | 详情请查看 LICENSE 文件。
25 |
26 |
28 |
30 |
31 |
--------------------------------------------------------------------------------
/NegativeEncoder/About/AboutWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using NegativeEncoder.Utils;
3 |
4 | namespace NegativeEncoder.About;
5 |
6 | ///
7 | /// AboutWindow.xaml 的交互逻辑
8 | ///
9 | public partial class AboutWindow : Window
10 | {
11 | public AboutWindow()
12 | {
13 | InitializeComponent();
14 |
15 | AboutVersionBlock.Text = $"Version v{AppContext.Version.CurrentVersion}";
16 | }
17 |
18 | private void OpenWebsiteButton_Click(object sender, RoutedEventArgs e)
19 | {
20 | OpenBrowserViewLink.OpenUrl("https://github.com/zyzsdy/NegativeEncoder");
21 | }
22 |
23 | private void CloseAboutWindowButton_Click(object sender, RoutedEventArgs e)
24 | {
25 | Close();
26 | }
27 | }
--------------------------------------------------------------------------------
/NegativeEncoder/About/CheckUpdate.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Threading.Tasks;
3 | using NegativeEncoder.Utils;
4 | using Newtonsoft.Json.Linq;
5 |
6 | namespace NegativeEncoder.About;
7 |
8 | public class CheckUpdate
9 | {
10 | public static async Task Check()
11 | {
12 | AppContext.Status.MainStatus = "检查更新...";
13 |
14 | string responseStr;
15 | try
16 | {
17 | var githubApiUrl = "https://api.github.com/repos/zyzsdy/NegativeEncoder/releases";
18 | var httpClient = HttpClientFactory.GetHttpClient();
19 |
20 | var response = await httpClient.GetAsync(githubApiUrl);
21 |
22 | if (response.StatusCode != HttpStatusCode.OK)
23 | {
24 | AppContext.Status.MainStatus = "检查更新失败";
25 | return;
26 | }
27 |
28 | responseStr = await response.Content.ReadAsStringAsync();
29 |
30 | if (string.IsNullOrEmpty(responseStr))
31 | {
32 | AppContext.Status.MainStatus = "检查更新失败";
33 | return;
34 | }
35 | }
36 | catch
37 | {
38 | AppContext.Status.MainStatus = "检查更新:网络连接失败";
39 | return;
40 | }
41 |
42 | string tag;
43 | string url;
44 | try
45 | {
46 | var releaseObj = JArray.Parse(responseStr);
47 | var releaseNote = releaseObj[0];
48 | tag = releaseNote["tag_name"].ToString();
49 | url = releaseNote["html_url"].ToString();
50 | }
51 | catch
52 | {
53 | AppContext.Status.MainStatus = "检查更新:版本信息解析失败";
54 | return;
55 | }
56 |
57 | var nowVersionTag = $"v{AppContext.Version.CurrentVersion}";
58 | if (nowVersionTag == tag)
59 | {
60 | AppContext.Version.IsLatest = true;
61 | AppContext.Status.MainStatus = "当前已是最新版本";
62 | return;
63 | }
64 |
65 | AppContext.Version.LatestVersion = tag;
66 | AppContext.Version.UpdateVersionLinkUrl = url;
67 | AppContext.Version.IsLatest = false;
68 |
69 | AppContext.Status.MainStatus = $"发现新版本 {tag},请使用「帮助」-「新版本 {tag}」更新。";
70 | }
71 | }
--------------------------------------------------------------------------------
/NegativeEncoder/About/Version.cs:
--------------------------------------------------------------------------------
1 | using PropertyChanged;
2 |
3 | namespace NegativeEncoder.About;
4 |
5 | [AddINotifyPropertyChangedInterface]
6 | public class Version
7 | {
8 | public string CurrentVersion { get; set; } = string.Empty;
9 | public bool IsLatest { get; set; } = true;
10 | public string LatestVersion { get; set; }
11 | public string UpdateVersionLinkUrl { get; set; }
12 |
13 | public string NewVersionMenuHeader => $"新版本 {LatestVersion}";
14 | public bool IsShowMenuItem => !IsLatest;
15 | }
--------------------------------------------------------------------------------
/NegativeEncoder/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/NegativeEncoder/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace NegativeEncoder;
4 |
5 | ///
6 | /// Interaction logic for App.xaml
7 | ///
8 | public partial class App : Application
9 | {
10 | }
--------------------------------------------------------------------------------
/NegativeEncoder/AppContext.cs:
--------------------------------------------------------------------------------
1 | using NegativeEncoder.About;
2 | using NegativeEncoder.EncodingTask;
3 | using NegativeEncoder.Presets;
4 | using NegativeEncoder.StatusBar;
5 | using NegativeEncoder.SystemOptions;
6 |
7 | namespace NegativeEncoder;
8 |
9 | public static class AppContext
10 | {
11 | ///
12 | /// 文件选择器
13 | ///
14 | public static FileSelector.FileSelector FileSelector { get; set; } = new();
15 |
16 | ///
17 | /// 当前程序版本
18 | ///
19 | public static Version Version { get; set; } = new();
20 |
21 | ///
22 | /// 状态栏显示对象
23 | ///
24 | public static Status Status { get; set; } = new();
25 |
26 | ///
27 | /// 全局系统设置
28 | ///
29 | public static Config Config { get; set; } = new();
30 |
31 | ///
32 | /// 预设全局对象
33 | ///
34 | public static PresetContext PresetContext { get; set; } = new();
35 |
36 | ///
37 | /// 编码任务队列
38 | ///
39 | public static EncodingContext EncodingContext { get; set; } = new();
40 | }
--------------------------------------------------------------------------------
/NegativeEncoder/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/EncodingContext.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace NegativeEncoder.EncodingTask;
4 |
5 | public class EncodingContext
6 | {
7 | ///
8 | /// 基目录
9 | ///
10 | public string BaseDir { get; set; }
11 |
12 | ///
13 | /// 软件自身执行文件
14 | ///
15 | public string AppSelf { get; set; }
16 |
17 | ///
18 | /// 任务队列
19 | ///
20 | public ObservableCollection TaskQueue { get; set; } = new();
21 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/EncodingTask.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 | using NegativeEncoder.Presets;
8 | using NegativeEncoder.Utils;
9 | using PropertyChanged;
10 |
11 | namespace NegativeEncoder.EncodingTask;
12 |
13 | public delegate void EncodingTaskHandle(object sender);
14 |
15 | [AddINotifyPropertyChangedInterface]
16 | public class EncodingTask
17 | {
18 | private string exeArgs;
19 | private string exeFile;
20 | private Process mainProcess;
21 |
22 | //=================================================
23 |
24 | //private int totalFrames = 0;
25 |
26 | public EncodingTask()
27 | {
28 | }
29 |
30 | public EncodingTask(string taskName, EncodingAction action)
31 | {
32 | TaskName = taskName;
33 | EncodingAction = action;
34 | Progress = 0;
35 | }
36 |
37 | public string TaskName { get; set; }
38 |
39 | ///
40 | /// 0~1000
41 | ///
42 | public int Progress { get; set; }
43 |
44 | public bool IsFinished { get; set; }
45 | public bool Running { get; set; }
46 | public string RunLog { get; set; }
47 | public EncodingAction EncodingAction { get; set; }
48 | public string EncodingParam { get; set; }
49 | public string Input { get; set; }
50 | public string Output { get; set; }
51 |
52 | public event EncodingTaskHandle Destroyed;
53 | public event EncodingTaskHandle ProcessStop;
54 |
55 | public void Destroy()
56 | {
57 | Destroyed?.Invoke(this);
58 | AppContext.EncodingContext.TaskQueue.Remove(this);
59 | }
60 |
61 | public void Stop()
62 | {
63 | mainProcess?.KillProcessTree();
64 | IsFinished = true;
65 |
66 | Progress = 0;
67 | ProcessStop?.Invoke(this);
68 | }
69 |
70 | public void MainProc_Exited(object sender, EventArgs e)
71 | {
72 | IsFinished = true;
73 | Progress = 1000;
74 | ProcessStop?.Invoke(this);
75 | }
76 |
77 | public void RegTask(string exefile, string args)
78 | {
79 | exeFile = exefile;
80 | exeArgs = args;
81 | }
82 |
83 | public void RegInputOutput(string input, string output)
84 | {
85 | Input = input;
86 | Output = output;
87 | }
88 |
89 | public void Start()
90 | {
91 | if (string.IsNullOrEmpty(exeFile)) return;
92 |
93 |
94 | mainProcess = new Process
95 | {
96 | StartInfo =
97 | {
98 | FileName = exeFile,
99 | Arguments = exeArgs,
100 | CreateNoWindow = true,
101 | UseShellExecute = false,
102 | RedirectStandardOutput = true,
103 | RedirectStandardInput = true,
104 | RedirectStandardError = true
105 | },
106 | EnableRaisingEvents = true
107 | };
108 |
109 | mainProcess.Exited += MainProc_Exited;
110 |
111 | Task.Run(() =>
112 | {
113 | mainProcess.Start();
114 | Running = true;
115 |
116 | using (var reader = new StreamReader(mainProcess.StandardError.BaseStream, Encoding.UTF8))
117 | {
118 | var thisline = reader.ReadLine();
119 |
120 | while (!IsFinished)
121 | {
122 | if (thisline != null)
123 | {
124 | RunLog += thisline + '\n';
125 |
126 | //进度条处理
127 | var tempP = new Regex(@"(?<=\[)(.*)(?=%\])").Match(thisline).Value;
128 | if (!string.IsNullOrEmpty(tempP))
129 | try
130 | {
131 | Progress = (int)Math.Floor(double.Parse(tempP) * 10);
132 | }
133 | catch
134 | {
135 | // 解析不了的时候,我们就当无事发生(
136 | }
137 | }
138 |
139 | thisline = reader.ReadLine();
140 | }
141 | }
142 |
143 | mainProcess.WaitForExit();
144 | });
145 | }
146 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/AudioEncoding.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using NegativeEncoder.FileSelector;
4 | using NegativeEncoder.Presets;
5 | using NegativeEncoder.Utils;
6 |
7 | namespace NegativeEncoder.EncodingTask.TaskArgs;
8 |
9 | public class AudioEncoding
10 | {
11 | public static (string exefile, string args) Build(string param, string input, string output, Preset preset,
12 | bool useHdr, string originInput, string extra)
13 | {
14 | var baseDir = AppContext.EncodingContext.BaseDir;
15 | var workDir = Path.GetDirectoryName(output);
16 |
17 | var audioOutput = FileName.RecalcOutputPath(input, extra, "_neAAC", "m4a");
18 | if (input == originInput) audioOutput = extra;
19 |
20 | //build bat
21 | var batName = Path.GetFileNameWithoutExtension(output) + "_audioBatTemp.bat";
22 | var batFullname = Path.Combine(workDir!, batName); //bat文件本身不记录在临时文件里
23 |
24 | var batSb = new StringBuilder();
25 | batSb.Append("@echo off\n");
26 |
27 | var ffmpegFile = Path.Combine(baseDir, "Libs\\ffmpeg.exe");
28 | var qaacFile = Path.Combine(baseDir, "Libs\\qaac64.exe");
29 |
30 | batSb.Append(
31 | $"\"{ffmpegFile}\" -y -i \"{input}\" -vn -sn -v 0 -c:a pcm_s16le -f wav pipe: | \"{qaacFile}\" -q 2 --ignorelength -c {preset.AudioBitrate} - -o \"{audioOutput}\"\n");
32 | batSb.Append($"@del \"{batFullname}\"\n"); //删除bat文件自身
33 |
34 | //save bat
35 | TempFile.SaveTempFile(batFullname, batSb.ToString());
36 |
37 | return (batFullname, "");
38 | }
39 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/AudioExtract.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using NegativeEncoder.FileSelector;
3 | using NegativeEncoder.Presets;
4 |
5 | namespace NegativeEncoder.EncodingTask.TaskArgs;
6 |
7 | public class AudioExtract
8 | {
9 | public static (string exefile, string args) Build(string param, string input, string output, Preset preset,
10 | bool useHdr, string originInput, string extra)
11 | {
12 | var baseDir = AppContext.EncodingContext.BaseDir;
13 | var ffmpegFile = Path.Combine(baseDir, "Libs\\ffmpeg.exe");
14 |
15 | var audioOutput = FileName.RecalcOutputPath(input, extra, "_neAAC", "m4a");
16 | if (input == originInput) audioOutput = extra;
17 |
18 | var args = $"-y -i \"{input}\" -vn -sn -c:a copy -y -map 0:a \"{audioOutput}\"";
19 |
20 | return (ffmpegFile, args);
21 | }
22 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/FFMpegPipe.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using NegativeEncoder.Presets;
6 | using NegativeEncoder.Utils;
7 |
8 | namespace NegativeEncoder.EncodingTask.TaskArgs;
9 |
10 | public class FFMpegPipe
11 | {
12 | public static (string exefile, string args) Build(string param, string input, string output, Preset preset,
13 | bool useHdr, string originInput, string extra)
14 | {
15 | var tempFileList = new List(); //临时文件
16 |
17 | var baseDir = AppContext.EncodingContext.BaseDir;
18 | var workDir = Path.GetDirectoryName(output);
19 |
20 | //build bat
21 | var batName = Path.GetFileNameWithoutExtension(output) + "_ffmpegPipeBatTemp.bat";
22 | var batFullname = Path.Combine(workDir!, batName); //bat文件本身不记录在临时文件里
23 |
24 | var batSb = new StringBuilder();
25 | batSb.Append("@echo off\n");
26 |
27 | var ffmpegFile = Path.Combine(baseDir, "Libs\\ffmpeg.exe");
28 | var encoderFile = TaskArgBuilder.GetBaseEncoderFile(preset);
29 | var gargs = TaskArgBuilder.GenericArgumentBuilder(preset, useHdr, true);
30 |
31 | var addArgList = new List();
32 |
33 | if (preset.IsSetOutputRes)
34 | {
35 | addArgList.Add("--output-res");
36 | addArgList.Add($"{preset.OutputResWidth}x{preset.OUtputResHeight}");
37 | }
38 |
39 | if (preset.IsSetAvSync && preset.AudioEncode != AudioEncode.None)
40 | {
41 | addArgList.Add("--avsync");
42 | addArgList.Add(preset.AVSync switch
43 | {
44 | AVSync.Cfr => "cfr",
45 | AVSync.ForceCfr => "forcecfr",
46 | AVSync.Vfr => "vfr",
47 | _ => throw new ArgumentOutOfRangeException()
48 | });
49 | }
50 |
51 | var addArgs = string.Join(" ", addArgList);
52 |
53 |
54 | switch (param)
55 | {
56 | case "ProcessAudio":
57 | {
58 | //处理音频流
59 |
60 | //--生成无音频流视频
61 | var tempVideoExt = Path.GetExtension(output);
62 | var tempVideoOutput = Path.Combine(workDir,
63 | Path.GetFileNameWithoutExtension(output) + "_tempVideo" + tempVideoExt);
64 | var ioargs = TaskArgBuilder.GetIOArgs("-", tempVideoOutput, preset);
65 |
66 | batSb.Append(
67 | $"\"{ffmpegFile}\" -y -i \"{input}\" -an -sn -pix_fmt yuv420p -f yuv4mpegpipe - | \"{encoderFile}\" --y4m {ioargs} {addArgs} {gargs}\n");
68 | tempFileList.Add(tempVideoOutput);
69 |
70 | //--处理音频
71 | var qaacFile = Path.Combine(baseDir, "Libs\\qaac64.exe");
72 | var audioOutput = Path.Combine(workDir, Path.GetFileNameWithoutExtension(output) + "_tempAudio.m4a");
73 | batSb.Append(
74 | $"\"{ffmpegFile}\" -y -i \"{input}\" -vn -sn -v 0 -c:a pcm_s16le -af aresample=async=1 -f wav pipe: | \"{qaacFile}\" -q 2 --ignorelength -c {preset.AudioBitrate} - -o \"{audioOutput}\"\n");
75 | tempFileList.Add(audioOutput);
76 |
77 | //--混流
78 | var format = TaskArgBuilder.GetFormat(preset.OutputFormat);
79 | var extraArgs = "";
80 | if (preset.OutputFormat == OutputFormat.MP4) extraArgs = "-movflags faststart";
81 | batSb.Append(
82 | $"\"{ffmpegFile}\" -y -i \"{tempVideoOutput}\" -i \"{audioOutput}\" -map 0:v -map 1:a -c copy {extraArgs} -f {format} \"{output}\"\n");
83 | break;
84 | }
85 | case "CopyAudio":
86 | {
87 | //复制音频流
88 |
89 | //--生成无音频流视频
90 | var tempVideoExt = Path.GetExtension(output);
91 | var tempVideoOutput = Path.Combine(workDir,
92 | Path.GetFileNameWithoutExtension(output) + "_tempVideo" + tempVideoExt);
93 | var ioargs = TaskArgBuilder.GetIOArgs("-", tempVideoOutput, preset);
94 |
95 | batSb.Append(
96 | $"\"{ffmpegFile}\" -y -i \"{input}\" -an -sn -pix_fmt yuv420p -f yuv4mpegpipe - | \"{encoderFile}\" --y4m {ioargs} {addArgs} {gargs}\n");
97 | tempFileList.Add(tempVideoOutput);
98 |
99 | //--混流
100 | var format = TaskArgBuilder.GetFormat(preset.OutputFormat);
101 | var extraArgs = "";
102 | if (preset.OutputFormat == OutputFormat.MP4) extraArgs = "-movflags faststart";
103 | batSb.Append(
104 | $"\"{ffmpegFile}\" -y -i \"{tempVideoOutput}\" -i \"{input}\" -map 0:v -map 1:a -c copy {extraArgs} -f {format} \"{output}\"\n");
105 | break;
106 | }
107 | default:
108 | {
109 | //无音频流
110 | var ioargs = TaskArgBuilder.GetIOArgs("-", output, preset);
111 |
112 | batSb.Append(
113 | $"\"{ffmpegFile}\" -y -i \"{input}\" -an -sn -pix_fmt yuv420p -f yuv4mpegpipe - | \"{encoderFile}\" --y4m {ioargs} {addArgs} {gargs}\n");
114 | break;
115 | }
116 | }
117 |
118 | foreach (var tempFile in tempFileList) batSb.Append($"@del \"{tempFile}\"\n");
119 | batSb.Append($"@del \"{batFullname}\"\n"); //删除bat文件自身
120 |
121 | //save bat
122 | TempFile.SaveTempFile(batFullname, batSb.ToString());
123 |
124 | return (batFullname, "");
125 | }
126 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/HDRTagUseFFMpeg.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using NegativeEncoder.Presets;
3 |
4 | namespace NegativeEncoder.EncodingTask.TaskArgs;
5 |
6 | public class HDRTagUseFFMpeg
7 | {
8 | public static (string exefile, string args) Build(string param, string input, string output, Preset preset,
9 | bool useHdr, string originInput, string extra)
10 | {
11 | var exeFileName = Path.Combine(AppContext.EncodingContext.BaseDir, "Libs\\ffmpeg.exe");
12 | int transfer;
13 | int prim;
14 | int matrix;
15 | switch (param)
16 | {
17 | case "HDR10":
18 | prim = 9;
19 | transfer = 16;
20 | matrix = 9;
21 | break;
22 | case "HLG":
23 | prim = 9;
24 | transfer = 18;
25 | matrix = 9;
26 | break;
27 | case "SDR":
28 | default:
29 | prim = 1;
30 | transfer = 1;
31 | matrix = 1;
32 | break;
33 | }
34 |
35 | var format = TaskArgBuilder.GetFormat(preset.OutputFormat);
36 |
37 | var args = $"-y -i \"{input}\" -c copy " +
38 | $"-bsf:v hevc_metadata=colour_primaries={prim}:transfer_characteristics={transfer}:matrix_coefficients={matrix} " +
39 | $"-f {format} \"{output}\"";
40 |
41 | return (exeFileName, args);
42 | }
43 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/Muxer.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using NegativeEncoder.FileSelector;
3 | using NegativeEncoder.Presets;
4 |
5 | namespace NegativeEncoder.EncodingTask.TaskArgs;
6 |
7 | public class Muxer
8 | {
9 | ///
10 | /// 生成混流任务
11 | ///
12 | /// 音频输入地址
13 | ///
14 | ///
15 | ///
16 | ///
17 | ///
18 | /// 混流目标输出地址
19 | ///
20 | public static (string exefile, string args) Build(string param, string input, string output, Preset preset,
21 | bool useHdr, string originInput, string extra)
22 | {
23 | var baseDir = AppContext.EncodingContext.BaseDir;
24 |
25 | var (ext, _) = FileName.GetOutputExt(preset.MuxFormat);
26 | var muxOutput = FileName.RecalcOutputPath(input, extra, "_mux", ext);
27 | if (input == originInput) muxOutput = extra;
28 |
29 | var audioInput = param;
30 |
31 | if (preset.MuxFormat == OutputFormat.MKV)
32 | {
33 | var mkvmerge = Path.Combine(baseDir, "Libs\\mkvmerge.exe");
34 | var mkvArgs = $"-o \"{muxOutput}\" -A \"{input}\" -D \"{audioInput}\"";
35 |
36 | return (mkvmerge, mkvArgs);
37 | }
38 |
39 | var ffmpegFile = Path.Combine(baseDir, "Libs\\ffmpeg.exe");
40 | var extraArgs = "";
41 | if (preset.MuxFormat == OutputFormat.MP4) extraArgs = "-movflags faststart";
42 | var format = TaskArgBuilder.GetFormat(preset.MuxFormat);
43 | var args =
44 | $"-y -i \"{input}\" -i \"{audioInput}\" -map 0:v -map 1:a -c copy {extraArgs} -f {format} \"{muxOutput}\"";
45 |
46 | return (ffmpegFile, args);
47 | }
48 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/SimpleEncoding.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using NegativeEncoder.Presets;
4 |
5 | namespace NegativeEncoder.EncodingTask.TaskArgs;
6 |
7 | public static class SimpleEncoding
8 | {
9 | public static (string exefile, string args) Build(string param, string input, string output, Preset preset,
10 | bool useHdr, string originInput, string extra)
11 | {
12 | var exeFileName = TaskArgBuilder.GetBaseEncoderFile(preset);
13 |
14 | var ioargs = TaskArgBuilder.GetIOArgs(input, output, preset);
15 | var gargs = TaskArgBuilder.GenericArgumentBuilder(preset, useHdr, false);
16 |
17 | var addArgs = new List
18 | {
19 | preset.Decoder switch
20 | {
21 | Decoder.AVSW => "--avsw",
22 | Decoder.AVHW => "--avhw",
23 | _ => throw new ArgumentOutOfRangeException()
24 | }
25 | };
26 |
27 | if (preset.IsSetOutputRes)
28 | {
29 | addArgs.Add("--output-res");
30 | addArgs.Add($"{preset.OutputResWidth}x{preset.OUtputResHeight}");
31 | }
32 |
33 | if (preset.IsSetAvSync && preset.AudioEncode != AudioEncode.None)
34 | {
35 | addArgs.Add("--avsync");
36 | addArgs.Add(preset.AVSync switch
37 | {
38 | AVSync.Cfr => "cfr",
39 | AVSync.ForceCfr => "forcecfr",
40 | AVSync.Vfr => "vfr",
41 | _ => throw new ArgumentOutOfRangeException()
42 | });
43 | }
44 |
45 | if (preset.AudioEncode == AudioEncode.Copy)
46 | {
47 | addArgs.Add("--audio-copy");
48 | }
49 | else if (preset.AudioEncode == AudioEncode.Encode)
50 | {
51 | addArgs.Add("--audio-codec");
52 | addArgs.Add("--audio-bitrate");
53 | addArgs.Add(preset.AudioBitrate);
54 | }
55 |
56 | var args = $"{ioargs} {string.Join(" ", addArgs)} {gargs}";
57 | return (exeFileName, args);
58 | }
59 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/SimpleWithAss.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using NegativeEncoder.Presets;
4 |
5 | namespace NegativeEncoder.EncodingTask.TaskArgs;
6 |
7 | public static class SimpleWithAss
8 | {
9 | public static (string exefile, string args) Build(string param, string input, string output, Preset preset,
10 | bool useHdr, string originInput, string assInput)
11 | {
12 | var exeFileName = TaskArgBuilder.GetBaseEncoderFile(preset);
13 |
14 | var ioargs = TaskArgBuilder.GetIOArgs(input, output, preset);
15 | var gargs = TaskArgBuilder.GenericArgumentBuilder(preset, useHdr, false);
16 |
17 | var addArgs = new List
18 | {
19 | preset.Decoder switch
20 | {
21 | Decoder.AVSW => "--avsw",
22 | Decoder.AVHW => "--avhw",
23 | _ => throw new ArgumentOutOfRangeException()
24 | }
25 | };
26 |
27 | if (preset.IsSetOutputRes)
28 | {
29 | addArgs.Add("--output-res");
30 | addArgs.Add($"{preset.OutputResWidth}x{preset.OUtputResHeight}");
31 | }
32 |
33 | if (preset.IsSetAvSync && preset.AudioEncode != AudioEncode.None)
34 | {
35 | addArgs.Add("--avsync");
36 | addArgs.Add(preset.AVSync switch
37 | {
38 | AVSync.Cfr => "cfr",
39 | AVSync.ForceCfr => "forcecfr",
40 | AVSync.Vfr => "vfr",
41 | _ => throw new ArgumentOutOfRangeException()
42 | });
43 | }
44 |
45 | if (preset.AudioEncode == AudioEncode.Copy)
46 | {
47 | addArgs.Add("--audio-copy");
48 | }
49 | else if (preset.AudioEncode == AudioEncode.Encode)
50 | {
51 | addArgs.Add("--audio-codec");
52 | addArgs.Add("--audio-bitrate");
53 | addArgs.Add(preset.AudioBitrate);
54 | }
55 |
56 | var subBurnArgs = $"--vpp-subburn filename=\"{assInput}\"";
57 |
58 | var args = $"{ioargs} {string.Join(" ", addArgs)} {gargs} {subBurnArgs}";
59 | return (exeFileName, args);
60 | }
61 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/TaskArgBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using NegativeEncoder.Presets;
5 |
6 | namespace NegativeEncoder.EncodingTask.TaskArgs;
7 |
8 | public static class TaskArgBuilder
9 | {
10 | public static string GenericArgumentBuilder(Preset preset, bool useHdr, bool usePipe)
11 | {
12 | if (preset.IsUseCustomParameters) return preset.CustomParameters;
13 |
14 | var argList = new List
15 | {
16 | "--codec",
17 | preset.Codec switch
18 | {
19 | Codec.AVC => "h264",
20 | Codec.HEVC => "hevc",
21 | Codec.AV1 => "av1",
22 | _ => throw new ArgumentOutOfRangeException()
23 | }
24 | };
25 |
26 | switch (preset.EncodeMode)
27 | {
28 | case EncodeMode.CQP:
29 | argList.Add("--cqp");
30 | argList.Add(preset.CqpParam);
31 | break;
32 | case EncodeMode.CBR:
33 | argList.Add("--cbr");
34 | argList.Add(preset.CbrParam);
35 | break;
36 | case EncodeMode.VBR:
37 | argList.Add("--vbr");
38 | argList.Add(preset.VbrParam);
39 | break;
40 | case EncodeMode.LA:
41 | argList.Add("--la");
42 | argList.Add(preset.LaParam);
43 | break;
44 | case EncodeMode.LAICQ:
45 | argList.Add("--la-icq");
46 | argList.Add(preset.LaicqParam);
47 | break;
48 | case EncodeMode.QVBR:
49 | argList.Add("--qvbr");
50 | argList.Add(preset.QvbrParam);
51 | break;
52 | }
53 |
54 | argList.Add("-u");
55 | switch (preset.QualityPreset)
56 | {
57 | case QualityPreset.Performance:
58 | argList.Add(preset.Encoder switch
59 | {
60 | Encoder.NVENC => "performance",
61 | Encoder.QSV => "faster",
62 | Encoder.VCE => "fast",
63 | _ => throw new ArgumentOutOfRangeException()
64 | });
65 | break;
66 | case QualityPreset.Balanced:
67 | argList.Add(preset.Encoder switch
68 | {
69 | Encoder.NVENC => "default",
70 | Encoder.QSV => "balanced",
71 | Encoder.VCE => "balanced",
72 | _ => throw new ArgumentOutOfRangeException()
73 | });
74 | break;
75 | case QualityPreset.Quality:
76 | argList.Add(preset.Encoder switch
77 | {
78 | Encoder.NVENC => "quality",
79 | Encoder.QSV => "best",
80 | Encoder.VCE => "slow",
81 | _ => throw new ArgumentOutOfRangeException()
82 | });
83 | break;
84 | }
85 |
86 | if (preset.Encoder != Encoder.VCE)
87 | {
88 | argList.Add("--output-depth");
89 | argList.Add(preset.ColorDepth switch
90 | {
91 | ColorDepth.C10Bit => "10",
92 | ColorDepth.C8Bit => "8",
93 | _ => throw new ArgumentOutOfRangeException()
94 | });
95 | }
96 |
97 | switch (preset.Encoder)
98 | {
99 | case Encoder.NVENC:
100 | argList.Add("--vbr-quality");
101 | argList.Add(preset.VbrQuailty);
102 | break;
103 | case Encoder.QSV when preset.EncodeMode == EncodeMode.QVBR && preset.Codec == Codec.AVC:
104 | argList.Add("--qvbr-quality");
105 | argList.Add(preset.VbrQuailty);
106 | break;
107 | }
108 |
109 | if (preset.IsSetMaxGop)
110 | {
111 | argList.Add("--gop-len");
112 | argList.Add(preset.MaxGop);
113 |
114 | if (preset.IsStrictGop && preset.Encoder != Encoder.VCE) argList.Add("--strict-gop");
115 | }
116 |
117 | if (preset.IsSetDar)
118 | {
119 | argList.Add("--dar");
120 | argList.Add(preset.Dar);
121 | }
122 |
123 | if (preset.IsSetMaxBitrate)
124 | {
125 | argList.Add("--max-bitrate");
126 | argList.Add(preset.MaxBitrate);
127 | }
128 |
129 | if (preset.Encoder == Encoder.QSV)
130 | argList.Add(preset.D3DMode switch
131 | {
132 | D3DMode.Disable => "--disable-d3d",
133 | D3DMode.Auto => "--d3d",
134 | D3DMode.D3D9 => "--d3d9",
135 | D3DMode.D3D11 => "--d3d11",
136 | _ => throw new ArgumentOutOfRangeException()
137 | });
138 |
139 | if (preset.IsUseDeInterlace)
140 | {
141 | argList.Add("--interlace");
142 | argList.Add(preset.FieldOrder switch
143 | {
144 | FieldOrder.TFF => "tff",
145 | FieldOrder.BFF => "bff",
146 | _ => throw new ArgumentOutOfRangeException()
147 | });
148 |
149 | switch (preset.DeInterlaceMethodPreset)
150 | {
151 | case DeInterlaceMethodPreset.HwNormal:
152 | argList.Add("--vpp-deinterlace");
153 | argList.Add("normal");
154 | break;
155 | case DeInterlaceMethodPreset.HwBob:
156 | argList.Add("--vpp-deinterlace");
157 | argList.Add("bob");
158 | break;
159 | case DeInterlaceMethodPreset.HwIt:
160 | argList.Add("--vpp-deinterlace");
161 | argList.Add("it");
162 | break;
163 | case DeInterlaceMethodPreset.AfsDefault:
164 | argList.Add("--vpp-afs");
165 | argList.Add("preset=default");
166 | break;
167 | case DeInterlaceMethodPreset.AfsTriple:
168 | argList.Add("--vpp-afs");
169 | argList.Add("preset=triple");
170 | break;
171 | case DeInterlaceMethodPreset.AfsDouble:
172 | argList.Add("--vpp-afs");
173 | argList.Add("preset=double");
174 | break;
175 | case DeInterlaceMethodPreset.AfsAnime:
176 | argList.Add("--vpp-afs");
177 | argList.Add("preset=anime");
178 | break;
179 | case DeInterlaceMethodPreset.AfsAnime24fps:
180 | argList.Add("--vpp-afs");
181 | argList.Add("preset=anime,24fps=true");
182 | break;
183 | case DeInterlaceMethodPreset.Afs24fps:
184 | argList.Add("--vpp-afs");
185 | argList.Add("preset=24fps");
186 | break;
187 | case DeInterlaceMethodPreset.Afs30fps:
188 | argList.Add("--vpp-afs");
189 | argList.Add("preset=30fps");
190 | break;
191 | case DeInterlaceMethodPreset.Nnedi64NoPre:
192 | argList.Add("--vpp-nnedi");
193 | argList.Add("field=auto,nns=64,nsize=32x6,quality=slow,prescreen=none,prec=fp32");
194 | break;
195 | case DeInterlaceMethodPreset.Nnedi64Fast:
196 | argList.Add("--vpp-nnedi");
197 | argList.Add("field=auto,nns=64,nsize=32x6,quality=fast,prescreen=new,prec=fp32");
198 | break;
199 | case DeInterlaceMethodPreset.Nnedi32Fast:
200 | argList.Add("--vpp-nnedi");
201 | argList.Add("field=auto,nns=32,nsize=32x4,quality=fast,prescreen=new,prec=fp32");
202 | break;
203 | case DeInterlaceMethodPreset.YadifTff:
204 | argList.Add("--vpp-yadif");
205 | argList.Add("mode=tff");
206 | break;
207 | case DeInterlaceMethodPreset.YadifBff:
208 | argList.Add("--vpp-yadif");
209 | argList.Add("mode=bff");
210 | break;
211 | case DeInterlaceMethodPreset.YadifBob:
212 | argList.Add("--vpp-yadif");
213 | argList.Add("mode=bob");
214 | break;
215 | }
216 | }
217 |
218 | if (useHdr)
219 | {
220 | if (preset.IsOutputHdr)
221 | switch (preset.OutputHdrType)
222 | {
223 | case HdrType.SDR:
224 | argList.Add("--colormatrix");
225 | argList.Add("bt709");
226 | argList.Add("--colorprim");
227 | argList.Add("bt709");
228 | argList.Add("--transfer");
229 | argList.Add("bt709");
230 | break;
231 | case HdrType.HDR10:
232 | argList.Add("--colormatrix");
233 | argList.Add("bt2020nc");
234 | argList.Add("--colorprim");
235 | argList.Add("bt2020");
236 | argList.Add("--transfer");
237 | argList.Add("smpte2084");
238 | break;
239 | case HdrType.HLG:
240 | argList.Add("--colormatrix");
241 | argList.Add("bt2020nc");
242 | argList.Add("--colorprim");
243 | argList.Add("bt2020");
244 | argList.Add("--transfer");
245 | argList.Add("arib-std-b67");
246 | break;
247 | }
248 |
249 | if (preset.IsRepeatHeaders && preset.Encoder == Encoder.NVENC) argList.Add("--repeat-headers");
250 |
251 | if (preset.IsConvertHdrType && preset.Encoder == Encoder.NVENC)
252 | {
253 | argList.Add("--vpp-colorspace");
254 | var (oldMatrix, oldPrim, oldTransfer) = preset.OldHdrType switch
255 | {
256 | HdrType.SDR => ("bt709", "bt709", "bt709"),
257 | HdrType.HDR10 => ("bt2020nc", "bt2020", "smpte2084"),
258 | HdrType.HLG => ("bt2020nc", "bt2020", "arib-std-b67"),
259 | _ => throw new ArgumentOutOfRangeException()
260 | };
261 | var (newMatrix, newPrim, newTransfer) = preset.NewHdrType switch
262 | {
263 | HdrType.SDR => ("bt709", "bt709", "bt709"),
264 | HdrType.HDR10 => ("bt2020nc", "bt2020", "smpte2084"),
265 | HdrType.HLG => ("bt2020nc", "bt2020", "arib-std-b67"),
266 | _ => throw new ArgumentOutOfRangeException()
267 | };
268 |
269 | var param =
270 | $"matrix={oldMatrix}:{newMatrix},colorprim={oldPrim}:{newPrim},transfer={oldTransfer}:{newTransfer}";
271 | if (preset.OldHdrType != HdrType.SDR && preset.NewHdrType == HdrType.SDR)
272 | {
273 | param += ",hdr2sdr=" + preset.Hdr2SdrMethod switch
274 | {
275 | Hdr2Sdr.None => "none",
276 | Hdr2Sdr.Hable => "hable",
277 | Hdr2Sdr.Mobius => "mobius",
278 | Hdr2Sdr.Reinhard => "reinhard",
279 | Hdr2Sdr.Bt2390 => "bt2390",
280 | _ => throw new ArgumentOutOfRangeException()
281 | };
282 |
283 | param += ",desat_strength=" + preset.Hdr2SdrDeSatStrength;
284 | }
285 |
286 | argList.Add(param);
287 | }
288 | }
289 | else
290 | {
291 | if (!usePipe)
292 | {
293 | argList.Add("--colorrange");
294 | argList.Add("auto");
295 | argList.Add("--colormatrix");
296 | argList.Add("auto");
297 | argList.Add("--colorprim");
298 | argList.Add("auto");
299 | argList.Add("--transfer");
300 | argList.Add("auto");
301 | argList.Add("--chromaloc");
302 | argList.Add("auto");
303 | }
304 | }
305 |
306 | return string.Join(" ", argList);
307 | }
308 |
309 | public static string GetBaseEncoderFile(Preset preset)
310 | {
311 | var encodingPath = "Libs\\" + preset.Encoder switch
312 | {
313 | Encoder.NVENC => "NVEncC64.exe",
314 | Encoder.QSV => "QSVEncC64.exe",
315 | Encoder.VCE => "VCEEncC64.exe",
316 | _ => throw new ArgumentOutOfRangeException()
317 | };
318 |
319 | return Path.Combine(AppContext.EncodingContext.BaseDir, encodingPath);
320 | }
321 |
322 | public static string GetFormat(OutputFormat format)
323 | {
324 | return format switch
325 | {
326 | OutputFormat.MP4 => "mp4",
327 | OutputFormat.MPEGTS => "mpegts",
328 | OutputFormat.FLV => "flv",
329 | OutputFormat.MKV => "matroska",
330 | _ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
331 | };
332 | }
333 |
334 | public static string GetIOArgs(string input, string output, Preset preset)
335 | {
336 | if (input != "-") input = $"\"{input}\"";
337 | if (output != "-") output = $"\"{output}\"";
338 | var format = GetFormat(preset.OutputFormat);
339 |
340 | return $"-i {input} -f {format} -o {output}";
341 | }
342 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskArgs/VSPipe.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Text;
4 | using NegativeEncoder.Presets;
5 | using NegativeEncoder.Utils;
6 |
7 | namespace NegativeEncoder.EncodingTask.TaskArgs;
8 |
9 | public class VSPipe
10 | {
11 | public static (string exefile, string args) Build(string param, string input, string output, Preset preset,
12 | bool useHdr, string originInput, string extra)
13 | {
14 | var tempFileList = new List(); //临时文件
15 |
16 | var baseDir = AppContext.EncodingContext.BaseDir;
17 | var workDir = Path.GetDirectoryName(output);
18 |
19 | var vpyName = Path.GetFileNameWithoutExtension(output) + "_vpyTemp.vpy";
20 | var vpyFullname = Path.Combine(workDir!, vpyName);
21 | tempFileList.Add(vpyFullname);
22 |
23 | var pyExtraLibName = Path.Combine(baseDir, "Libs\\site-packages");
24 | var vpyFullContent = $"import sys\nsys.path.append(r'{pyExtraLibName}')\n\n" + param;
25 |
26 | TempFile.SaveTempFile(vpyFullname, vpyFullContent, false);
27 |
28 | //build bat
29 | var batName = Path.GetFileNameWithoutExtension(output) + "_batTemp.bat";
30 | var batFullname = Path.Combine(workDir, batName); //bat文件本身不记录在临时文件里
31 |
32 | var batSb = new StringBuilder();
33 | batSb.Append("@echo off\n");
34 |
35 | var vspipeFile = Path.Combine(baseDir, "Libs\\VSPipe.exe");
36 | var encoderFile = TaskArgBuilder.GetBaseEncoderFile(preset);
37 | var gargs = TaskArgBuilder.GenericArgumentBuilder(preset, useHdr, true);
38 |
39 |
40 | switch (preset.AudioEncode)
41 | {
42 | case AudioEncode.Copy:
43 | {
44 | //复制音频流
45 |
46 | //--生成无音频流视频
47 | var tempVideoExt = Path.GetExtension(output);
48 | var tempVideoOutput = Path.Combine(workDir,
49 | Path.GetFileNameWithoutExtension(output) + "_tempVideo" + tempVideoExt);
50 | var ioargs = TaskArgBuilder.GetIOArgs("-", tempVideoOutput, preset);
51 |
52 | batSb.Append(
53 | $"\"{vspipeFile}\" --y4m \"{vpyFullname}\" - | \"{encoderFile}\" --y4m {ioargs} {gargs}\n");
54 | tempFileList.Add(tempVideoOutput);
55 |
56 | //--混流
57 | var ffmpegFile = Path.Combine(baseDir, "Libs\\ffmpeg.exe");
58 | var format = TaskArgBuilder.GetFormat(preset.OutputFormat);
59 | var extraArgs = "";
60 | if (preset.OutputFormat == OutputFormat.MP4) extraArgs = "-movflags faststart";
61 | batSb.Append(
62 | $"\"{ffmpegFile}\" -y -i \"{tempVideoOutput}\" -i \"{input}\" -map 0:v -map 1:a -c copy {extraArgs} -f {format} \"{output}\"\n");
63 | break;
64 | }
65 | case AudioEncode.Encode:
66 | {
67 | //压制音频流
68 |
69 | //--生成无音频流视频
70 | var tempVideoExt = Path.GetExtension(output);
71 | var tempVideoOutput = Path.Combine(workDir,
72 | Path.GetFileNameWithoutExtension(output) + "_tempVideo" + tempVideoExt);
73 | var ioargs = TaskArgBuilder.GetIOArgs("-", tempVideoOutput, preset);
74 |
75 | batSb.Append(
76 | $"\"{vspipeFile}\" --y4m \"{vpyFullname}\" - | \"{encoderFile}\" --y4m {ioargs} {gargs}\n");
77 | tempFileList.Add(tempVideoOutput);
78 |
79 | //--qaac处理音频
80 | var ffmpegFile = Path.Combine(baseDir, "Libs\\ffmpeg.exe");
81 | var qaacFile = Path.Combine(baseDir, "Libs\\qaac64.exe");
82 | var audioOutput = Path.Combine(workDir, Path.GetFileNameWithoutExtension(output) + "_tempAudio.m4a");
83 |
84 | batSb.Append(
85 | $"\"{ffmpegFile}\" -y -i \"{input}\" -vn -sn -v 0 -c:a pcm_s16le -f wav pipe: | \"{qaacFile}\" -q 2 --ignorelength -c {preset.AudioBitrate} - -o \"{audioOutput}\"\n");
86 | tempFileList.Add(audioOutput);
87 |
88 | //--混流
89 | var format = TaskArgBuilder.GetFormat(preset.OutputFormat);
90 | var extraArgs = "";
91 | if (preset.OutputFormat == OutputFormat.MP4) extraArgs = "-movflags faststart";
92 | batSb.Append(
93 | $"\"{ffmpegFile}\" -y -i \"{tempVideoOutput}\" -i \"{audioOutput}\" -map 0:v -map 1:a -c copy {extraArgs} -f {format} \"{output}\"\n");
94 | break;
95 | }
96 | default:
97 | {
98 | //无音频流
99 | var ioargs = TaskArgBuilder.GetIOArgs("-", output, preset);
100 |
101 | batSb.Append(
102 | $"\"{vspipeFile}\" --y4m \"{vpyFullname}\" - | \"{encoderFile}\" --y4m {ioargs} {gargs}\n");
103 | break;
104 | }
105 | }
106 |
107 | foreach (var tempFile in tempFileList) batSb.Append($"@del \"{tempFile}\"\n");
108 | batSb.Append($"@del \"{batFullname}\"\n"); //删除bat文件自身
109 |
110 | //save bat
111 | TempFile.SaveTempFile(batFullname, batSb.ToString());
112 |
113 | return (batFullname, "");
114 | }
115 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using NegativeEncoder.EncodingTask.TaskArgs;
4 | using NegativeEncoder.FileSelector;
5 | using NegativeEncoder.Presets;
6 |
7 | namespace NegativeEncoder.EncodingTask;
8 |
9 | public delegate (string exefile, string args) BuildAction(string param, string input, string output, Preset preset,
10 | bool useHdr, string originInput, string extra);
11 |
12 | public static class TaskBuilder
13 | {
14 | private static readonly Dictionary TaskArgBuilders =
15 | new()
16 | {
17 | { EncodingAction.Simple, SimpleEncoding.Build },
18 | { EncodingAction.HDRTagUseFFMpeg, HDRTagUseFFMpeg.Build },
19 | { EncodingAction.VSPipe, VSPipe.Build },
20 | { EncodingAction.AudioEncoding, AudioEncoding.Build },
21 | { EncodingAction.AudioExtract, AudioExtract.Build },
22 | { EncodingAction.Muxer, Muxer.Build },
23 | { EncodingAction.FFMpegPipe, FFMpegPipe.Build },
24 | { EncodingAction.SimpleWithAss, SimpleWithAss.Build }
25 | };
26 |
27 |
28 | public static void AddEncodingTask(EncodingAction action, string param,
29 | Preset currentPreset, List selectPaths,
30 | string input, string output, string extra)
31 | {
32 | if (!TaskArgBuilders.ContainsKey(action))
33 | {
34 | AppContext.Status.MainStatus = $"Error: 没有找到可用的处理流程模板:{action}";
35 | return;
36 | }
37 |
38 | var taskArgBuilder = TaskArgBuilders[action];
39 |
40 | var useHdr = param == "HDR" || AppContext.Config.ForceHDR;
41 |
42 | var (ext, _) = FileName.GetOutputExt(currentPreset.OutputFormat);
43 |
44 | //为每个选中的项目生成任务并推入任务队列
45 | foreach (var filePath in selectPaths)
46 | {
47 | var name = filePath.Filename;
48 | var newTask = new EncodingTask(name, action);
49 |
50 | AppContext.Status.MainStatus = $"生成任务 {name}";
51 |
52 | var thisInput = filePath.Path;
53 | var thisOutput = FileName.RecalcOutputPath(thisInput, output, "_neenc", ext);
54 | if (thisInput == input) thisOutput = output;
55 |
56 | var (exeFile, exeArgs) =
57 | taskArgBuilder.Invoke(param, thisInput, thisOutput, currentPreset, useHdr, input, extra);
58 |
59 | newTask.RegTask(exeFile, exeArgs);
60 | newTask.RegInputOutput(thisInput, thisOutput);
61 | newTask.RunLog += $"{exeFile} {exeArgs}\n";
62 | newTask.ProcessStop += async _ =>
63 | {
64 | AppContext.Status.MainStatus = $"{name} 任务完成";
65 | await Task.Delay(500);
66 | TaskProvider.Schedule();
67 | };
68 |
69 | AppContext.EncodingContext.TaskQueue.Add(newTask);
70 | }
71 |
72 | //调度任务
73 | AppContext.Status.MainStatus = "开始任务调度";
74 | TaskProvider.Schedule();
75 | }
76 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskDetailWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
24 |
40 |
41 |
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskDetailWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 |
4 | namespace NegativeEncoder.EncodingTask;
5 |
6 | ///
7 | /// TaskDetailWindow.xaml 的交互逻辑
8 | ///
9 | public partial class TaskDetailWindow : Window
10 | {
11 | public TaskDetailWindow()
12 | {
13 | InitializeComponent();
14 | }
15 |
16 | private void logBox_TextChanged(object sender, TextChangedEventArgs e)
17 | {
18 | logBox.ScrollToEnd();
19 | }
20 |
21 | private void mainButton_Click(object sender, RoutedEventArgs e)
22 | {
23 | var source = (EncodingTask)DataContext;
24 | if (source.IsFinished)
25 | {
26 | source.Destroy();
27 | Close();
28 | }
29 | else
30 | {
31 | var result1 = MessageBox.Show(this, "中止后当前的进度会丢失,确认吗?", "确认中止?", MessageBoxButton.YesNo,
32 | MessageBoxImage.Warning);
33 | if (result1 == MessageBoxResult.Yes)
34 | {
35 | var result2 = MessageBox.Show(this, "请再次确认:中止后已经编码的进度会丢失,确认中止吗?", "再次确认", MessageBoxButton.YesNo,
36 | MessageBoxImage.Warning);
37 | if (result2 == MessageBoxResult.Yes) source.Stop();
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskNameConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Data;
6 |
7 | namespace NegativeEncoder.EncodingTask;
8 |
9 | public class TaskNameConverter : IMultiValueConverter
10 | {
11 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
12 | {
13 | if (values.Length != 2) throw new Exception("TaskNameConverter 必须绑定2个对象");
14 |
15 | if (values[0] != null && values[1] != null)
16 | {
17 | if ((bool)values[0])
18 | return $"已完成 ({values[1]})";
19 | return $"正在编码 ({values[1]})";
20 | }
21 |
22 | return "编码任务";
23 | }
24 |
25 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
26 | {
27 | return targetTypes.Select(p => DependencyProperty.UnsetValue).ToArray();
28 | }
29 | }
--------------------------------------------------------------------------------
/NegativeEncoder/EncodingTask/TaskProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 |
4 | namespace NegativeEncoder.EncodingTask;
5 |
6 | public static class TaskProvider
7 | {
8 | public static void Schedule()
9 | {
10 | var queue = AppContext.EncodingContext.TaskQueue;
11 | var runningCount = queue.Count(x => x.Running && x.IsFinished == false);
12 | var restCount = queue.Count(x => x.IsFinished == false);
13 |
14 | AppContext.Status.EncoderStatus = $"{runningCount} 编码中,还剩 {restCount} 个";
15 | if (runningCount == 0) AppContext.Status.EncoderStatus = "空闲";
16 |
17 | if (runningCount >= AppContext.Config.MaxEncodingTaskNumber)
18 | {
19 | AppContext.Status.MainStatus =
20 | $"已有任务 {runningCount} 个,超出最大同时执行上限 {AppContext.Config.MaxEncodingTaskNumber} 个,等待现有任务完成。";
21 | return;
22 | }
23 |
24 | //寻找第一个待调度任务
25 | var firstTask = queue.FirstOrDefault(x => x.Running == false);
26 |
27 | if (firstTask != default)
28 | {
29 | AppContext.Status.MainStatus = $"任务 {firstTask.TaskName} 开始执行。";
30 | firstTask.Start();
31 | Task.Run(async () =>
32 | {
33 | await Task.Delay(500);
34 | Schedule();
35 | });
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/NegativeEncoder/FileSelector/FileList.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
14 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/NegativeEncoder/FileSelector/FileList.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 |
5 | namespace NegativeEncoder.FileSelector;
6 |
7 | ///
8 | /// FileList.xaml 的交互逻辑
9 | ///
10 | public partial class FileList : UserControl
11 | {
12 | public FileList()
13 | {
14 | InitializeComponent();
15 |
16 | if (AppContext.FileSelector != null)
17 | {
18 | FileListBox.ItemsSource = AppContext.FileSelector.Files;
19 | AppContext.FileSelector.OnFileListChange += FileSelector_OnFileListChange;
20 | }
21 | }
22 |
23 | private void FileSelector_OnFileListChange(int pos)
24 | {
25 | CheckSelectAllOrSelectPos(pos);
26 | }
27 |
28 | private void FileListControl_Loaded(object sender, RoutedEventArgs e)
29 | {
30 | }
31 |
32 | private void ImportVideoButton_Click(object sender, RoutedEventArgs e)
33 | {
34 | ImportVideoAction(sender, e);
35 | }
36 |
37 | public void ImportVideoAction(object sender, RoutedEventArgs e)
38 | {
39 | if (AppContext.FileSelector == null)
40 | {
41 | MessageBox.Show("错误:初始化未正确完成。\ncode: 0x2001", "初始化错误", MessageBoxButton.OK, MessageBoxImage.Error);
42 | return;
43 | }
44 |
45 | var firstNewFilePos = AppContext.FileSelector.BrowseImportFiles();
46 |
47 | if (firstNewFilePos >= 0) CheckSelectAllOrSelectPos(firstNewFilePos);
48 | }
49 |
50 | private void SelectAllCheckBox_Click(object sender, RoutedEventArgs e)
51 | {
52 | CheckSelectAllOrSelectPos(0);
53 | }
54 |
55 | public void CheckSelectAllOrSelectPos(int pos)
56 | {
57 | if (FileListBox.Items.Count > pos)
58 | {
59 | if (SelectAllCheckBox.IsChecked ?? false)
60 | {
61 | FileListBox.SelectAll();
62 | }
63 | else
64 | {
65 | FileListBox.UnselectAll();
66 | FileListBox.SelectedIndex = pos;
67 | }
68 | }
69 | }
70 |
71 | private void RemoveVideoItemButton_Click(object sender, RoutedEventArgs e)
72 | {
73 | GetAndRemoveAllSelectFilePath();
74 | }
75 |
76 | private void FileListBox_DragOver(object sender, DragEventArgs e)
77 | {
78 | e.Effects = DragDropEffects.Copy;
79 | e.Handled = true;
80 | }
81 |
82 | private void FileListBox_Drop(object sender, DragEventArgs e)
83 | {
84 | var dropedFiles = (string[])e.Data.GetData(DataFormats.FileDrop);
85 |
86 | var firstNewFilePos = AppContext.FileSelector.AddFiles(dropedFiles);
87 |
88 | if (firstNewFilePos >= 0) CheckSelectAllOrSelectPos(firstNewFilePos);
89 | }
90 |
91 | public void ClearFileList(object sender, RoutedEventArgs e)
92 | {
93 | AppContext.FileSelector.Clear();
94 | }
95 |
96 | private void FileListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
97 | {
98 | if (FileListBox.SelectedIndex >= 0)
99 | {
100 | var nowSelectFile = AppContext.FileSelector.Files[FileListBox.SelectedIndex];
101 |
102 | AppContext.PresetContext.InputFile = nowSelectFile.Path;
103 | AppContext.PresetContext.NotifyInputFileChange(sender, e);
104 | }
105 | }
106 |
107 | public List GetAndRemoveAllSelectFilePath()
108 | {
109 | var result = AppContext.FileSelector.RemoveFiles(FileListBox.SelectedItems);
110 |
111 | CheckSelectAllOrSelectPos(0);
112 |
113 | return result;
114 | }
115 | }
--------------------------------------------------------------------------------
/NegativeEncoder/FileSelector/FileName.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using NegativeEncoder.Presets;
3 |
4 | namespace NegativeEncoder.FileSelector;
5 |
6 | public static class FileName
7 | {
8 | public static string RecalcOutputPath(string input, string oldOutput, string suffix, string ext)
9 | {
10 | if (string.IsNullOrEmpty(input)) return "";
11 |
12 | var outputPath = Path.GetDirectoryName(oldOutput);
13 | var basicOutputPath = Path.GetDirectoryName(AppContext.PresetContext.OutputFile);
14 |
15 | var inputWithoutExt = Path.GetFileNameWithoutExtension(input);
16 | var outputName = Path.ChangeExtension($"{inputWithoutExt}{suffix}.out", ext);
17 |
18 | if (!string.IsNullOrEmpty(outputPath)) return Path.Combine(outputPath, outputName);
19 |
20 | if (!string.IsNullOrEmpty(basicOutputPath)) return Path.Combine(basicOutputPath, outputName);
21 |
22 | var basePath = Path.GetDirectoryName(input);
23 | return Path.Combine(basePath!, outputName);
24 | }
25 |
26 | public static string RecalcOutputPath(string input, string suffix, string ext)
27 | {
28 | if (string.IsNullOrEmpty(input)) return "";
29 |
30 | var inputWithoutExt = Path.GetFileNameWithoutExtension(input);
31 | var outputName = Path.ChangeExtension($"{inputWithoutExt}{suffix}.out", ext);
32 |
33 | var basePath = Path.GetDirectoryName(input);
34 | return Path.Combine(basePath!, outputName);
35 | }
36 |
37 | public static (string ext, string filter) GetOutputExt(OutputFormat outputFormat)
38 | {
39 | return outputFormat switch
40 | {
41 | OutputFormat.MP4 => ("mp4", "MP4 Video(*.mp4)|*.mp4|所有文件(*.*)|*.*"),
42 | OutputFormat.MPEGTS => ("ts", "MPEG TS文件(*.ts)|*.ts|所有文件(*.*)|*.*"),
43 | OutputFormat.FLV => ("flv", "Flash Video(*.flv)|*.flv|所有文件(*.*)|*.*"),
44 | OutputFormat.MKV => ("mkv", "Matroska Video(*.mkv)|*.mkv|所有文件(*.*)|*.*"),
45 | _ => ("mp4", "MP4 Video(*.mp4)|*.mp4|所有文件(*.*)|*.*")
46 | };
47 | }
48 | }
--------------------------------------------------------------------------------
/NegativeEncoder/FileSelector/FileSelector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.IO;
6 | using System.Windows;
7 | using Microsoft.Win32;
8 |
9 | namespace NegativeEncoder.FileSelector;
10 |
11 | public delegate void FileListChangeEvent(int pos);
12 |
13 | public class FileSelector
14 | {
15 | public ObservableCollection Files { get; set; } = new();
16 |
17 | public event FileListChangeEvent OnFileListChange;
18 |
19 | public int BrowseImportFiles()
20 | {
21 | var ofd = new OpenFileDialog
22 | {
23 | CheckFileExists = true,
24 | CheckPathExists = true,
25 | Multiselect = true
26 | };
27 | if (ofd.ShowDialog() == true) return AddFiles(ofd.FileNames);
28 | return -1;
29 | }
30 |
31 | public int AddFiles(string[] fileNames)
32 | {
33 | var succ = 0;
34 | var firstPos = Files.Count;
35 | var notFiles = new List();
36 | var errFiles = new List();
37 |
38 | foreach (var file in fileNames)
39 | {
40 | var newFile = new FileInfo(file);
41 |
42 | if (!File.Exists(file))
43 | {
44 | notFiles.Add(file);
45 | }
46 | else if (Files.Contains(newFile))
47 | {
48 | errFiles.Add(file);
49 | }
50 | else
51 | {
52 | Files.Add(newFile);
53 | succ++;
54 | }
55 | }
56 |
57 | if (errFiles.Count > 0 || notFiles.Count > 0)
58 | {
59 | var msg = "";
60 | if (errFiles.Count > 0) msg += $"以下 {errFiles.Count} 个文件已存在于列表中,未能导入:\n{string.Join("\n", errFiles)}\n\n";
61 | if (notFiles.Count > 0)
62 | msg += $"以下 {notFiles.Count} 个文件是不支持的格式或无法读取,未能导入:\n{string.Join("\n", notFiles)}\n\n";
63 | MessageBox.Show(msg,
64 | "文件导入中出现问题",
65 | MessageBoxButton.OK,
66 | MessageBoxImage.Asterisk);
67 | }
68 |
69 | if (succ > 0) return firstPos;
70 | return -1;
71 | }
72 |
73 | public List RemoveFiles(IList files)
74 | {
75 | var tempRemoveList = new List();
76 |
77 | if (files != null && files.Count > 0)
78 | {
79 | foreach (var f in files) tempRemoveList.Add(f as FileInfo);
80 |
81 | if (tempRemoveList.Count > 0)
82 | foreach (var tf in tempRemoveList)
83 | Files.Remove(tf);
84 | }
85 |
86 | return tempRemoveList;
87 | }
88 |
89 | public void NotifyChanged(int pos)
90 | {
91 | OnFileListChange?.Invoke(pos);
92 | }
93 |
94 | public void Clear()
95 | {
96 | Files.Clear();
97 | }
98 | }
99 |
100 | public class FileInfo : IEquatable
101 | {
102 | public FileInfo(string path)
103 | {
104 | Path = path;
105 | Filename = System.IO.Path.GetFileNameWithoutExtension(path);
106 | }
107 |
108 | public string Path { get; set; }
109 | public string Filename { get; set; }
110 |
111 | public bool Equals(FileInfo other)
112 | {
113 | return other != null &&
114 | Path == other.Path;
115 | }
116 |
117 | public override bool Equals(object obj)
118 | {
119 | return Equals(obj as FileInfo);
120 | }
121 |
122 | public override int GetHashCode()
123 | {
124 | return Path.GetHashCode();
125 | }
126 | }
--------------------------------------------------------------------------------
/NegativeEncoder/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/NegativeEncoder/FodyWeavers.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Used to control if the On_PropertyName_Changed feature is enabled.
12 |
13 |
14 |
15 |
16 | Used to control if the Dependent properties feature is enabled.
17 |
18 |
19 |
20 |
21 | Used to control if the IsChanged property feature is enabled.
22 |
23 |
24 |
25 |
26 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.
27 |
28 |
29 |
30 |
31 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.
32 |
33 |
34 |
35 |
36 | Used to control if equality checks should use the Equals method resolved from the base class.
37 |
38 |
39 |
40 |
41 | Used to control if equality checks should use the static Equals method resolved from the base class.
42 |
43 |
44 |
45 |
46 | Used to turn off build warnings from this weaver.
47 |
48 |
49 |
50 |
51 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods.
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
60 |
61 |
62 |
63 |
64 | A comma-separated list of error codes that can be safely ignored in assembly verification.
65 |
66 |
67 |
68 |
69 | 'false' to turn off automatic generation of the XML Schema file.
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/NegativeEncoder/FunctionTabs/FunctionTabs.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.IO;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using Microsoft.Win32;
6 | using NegativeEncoder.EncodingTask;
7 | using NegativeEncoder.FileSelector;
8 | using NegativeEncoder.Presets;
9 | using NegativeEncoder.Utils;
10 |
11 | namespace NegativeEncoder.FunctionTabs;
12 |
13 | ///
14 | /// FunctionTabs.xaml 的交互逻辑
15 | ///
16 | public partial class FunctionTabs : UserControl
17 | {
18 | private bool _isAutoChange, _pathChanged;
19 |
20 | public FunctionTabs()
21 | {
22 | InitializeComponent();
23 |
24 | AppContext.PresetContext.InputFileChanged += PresetContext_InputFileChanged;
25 | AppContext.PresetContext.VsScript.PropertyChanged += VsScript_PropertyChanged;
26 | }
27 |
28 | private void VsScript_PropertyChanged(object sender, PropertyChangedEventArgs e)
29 | {
30 | BuildUpdateVsScript();
31 | }
32 |
33 | private void PresetContext_InputFileChanged(object sender, SelectionChangedEventArgs e)
34 | {
35 | //触发output重算
36 | RecalcOutputPath();
37 | RecalcAudioOutputPath();
38 | RecalcMuxOutputPath();
39 |
40 | //触发重新生成VS脚本
41 | BuildUpdateVsScript();
42 | }
43 |
44 | private void RecalcOutputPath()
45 | {
46 | var input = AppContext.PresetContext.InputFile;
47 | var (ext, _) = FileName.GetOutputExt(AppContext.PresetContext.CurrentPreset.OutputFormat);
48 |
49 | var output = _pathChanged
50 | ? FileName.RecalcOutputPath(input, AppContext.PresetContext.OutputFile, "_neenc", ext)
51 | : FileName.RecalcOutputPath(input, "_neenc", ext);
52 | _isAutoChange = true;
53 | AppContext.PresetContext.OutputFile = output;
54 | }
55 |
56 | private void RecalcAudioOutputPath()
57 | {
58 | var input = AppContext.PresetContext.InputFile;
59 | var output = FileName.RecalcOutputPath(input, AppContext.PresetContext.AudioOutputFile, "_neAAC", "m4a");
60 | AppContext.PresetContext.AudioOutputFile = output;
61 | }
62 |
63 | private void RecalcMuxOutputPath()
64 | {
65 | var input = AppContext.PresetContext.InputFile;
66 | var (ext, _) = FileName.GetOutputExt(AppContext.PresetContext.CurrentPreset.MuxFormat);
67 |
68 | var output = FileName.RecalcOutputPath(input, AppContext.PresetContext.MuxOutputFile, "_mux", ext);
69 | AppContext.PresetContext.MuxOutputFile = output;
70 | }
71 |
72 |
73 | private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
74 | {
75 | var output = AppContext.PresetContext.OutputFile;
76 | if (string.IsNullOrEmpty(output)) return;
77 |
78 | var (ext, _) = FileName.GetOutputExt(AppContext.PresetContext.CurrentPreset.OutputFormat);
79 | var newOutput = Path.ChangeExtension(output, ext);
80 |
81 | AppContext.PresetContext.OutputFile = newOutput;
82 | }
83 |
84 | private void OutputBrowseButton_Click(object sender, RoutedEventArgs e)
85 | {
86 | var (defaultExt, filter) = FileName.GetOutputExt(AppContext.PresetContext.CurrentPreset.OutputFormat);
87 |
88 | var sfd = new SaveFileDialog
89 | {
90 | DefaultExt = defaultExt,
91 | Filter = filter
92 | };
93 |
94 | if (sfd.ShowDialog() == true) AppContext.PresetContext.OutputFile = sfd.FileName;
95 | }
96 |
97 | private void GenVsMenuItem_Click(object sender, RoutedEventArgs e)
98 | {
99 | BuildUpdateVsScript();
100 | }
101 |
102 | private void BuildUpdateVsScript()
103 | {
104 | var vsText =
105 | VsScriptBuilder.VsScriptBuilder.Build(AppContext.PresetContext.VsScript,
106 | AppContext.PresetContext.InputFile);
107 | VsEditor.Document.Text = vsText;
108 | }
109 |
110 | private void VsSubBrowseButton_Click(object sender, RoutedEventArgs e)
111 | {
112 | var ofd = new OpenFileDialog
113 | {
114 | Filter = "ASS 字幕文件(*.ass)|*.ass"
115 | };
116 |
117 | if (ofd.ShowDialog() == true) AppContext.PresetContext.VsScript.SubFile = ofd.FileName;
118 | }
119 |
120 | private void SubBrowseButton_Click(object sender, RoutedEventArgs e)
121 | {
122 | var ofd = new OpenFileDialog
123 | {
124 | Filter = "ASS 字幕文件(*.ass)|*.ass"
125 | };
126 |
127 | if (ofd.ShowDialog() == true) AppContext.PresetContext.SubBurnAssFile = ofd.FileName;
128 | }
129 |
130 | private void VsSubTextBox_PreviewDragOver(object sender, DragEventArgs e)
131 | {
132 | e.Effects = DragDropEffects.Copy;
133 | e.Handled = true;
134 | }
135 |
136 | private void VsSubTextBox_PreviewDragDrop(object sender, DragEventArgs e)
137 | {
138 | foreach (var f in (string[])e.Data.GetData(DataFormats.FileDrop)) AppContext.PresetContext.VsScript.SubFile = f;
139 | }
140 |
141 | private void SubTextBox_PreviewDragOver(object sender, DragEventArgs e)
142 | {
143 | e.Effects = DragDropEffects.Copy;
144 | e.Handled = true;
145 | }
146 |
147 | private void SubTextBox_PreviewDragDrop(object sender, DragEventArgs e)
148 | {
149 | foreach (var f in (string[])e.Data.GetData(DataFormats.FileDrop)) AppContext.PresetContext.SubBurnAssFile = f;
150 | }
151 |
152 | private void AudioOutputBrowseButton_Click(object sender, RoutedEventArgs e)
153 | {
154 | var sfd = new SaveFileDialog
155 | {
156 | DefaultExt = "aac",
157 | Filter = "AAC音频(*.aac)|*.aac|所有文件(*.*)|*.*"
158 | };
159 |
160 | if (sfd.ShowDialog() == true) AppContext.PresetContext.AudioOutputFile = sfd.FileName;
161 | }
162 |
163 | private void MuxOutputBrowseButton_Click(object sender, RoutedEventArgs e)
164 | {
165 | var (defaultExt, filter) = FileName.GetOutputExt(AppContext.PresetContext.CurrentPreset.MuxFormat);
166 |
167 | var sfd = new SaveFileDialog
168 | {
169 | DefaultExt = defaultExt,
170 | Filter = filter
171 | };
172 |
173 | if (sfd.ShowDialog() == true) AppContext.PresetContext.MuxOutputFile = sfd.FileName;
174 | }
175 |
176 | private void MuxFormatComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
177 | {
178 | var output = AppContext.PresetContext.MuxOutputFile;
179 | if (string.IsNullOrEmpty(output)) return;
180 |
181 | var (ext, _) = FileName.GetOutputExt(AppContext.PresetContext.CurrentPreset.MuxFormat);
182 | var newOutput = Path.ChangeExtension(output, ext);
183 |
184 | AppContext.PresetContext.MuxOutputFile = newOutput;
185 | }
186 |
187 | private void MuxAudioBrowseButton_Click(object sender, RoutedEventArgs e)
188 | {
189 | var ofd = new OpenFileDialog();
190 |
191 | if (ofd.ShowDialog() == true) AppContext.PresetContext.MuxAudioInputFile = ofd.FileName;
192 | }
193 |
194 | private void MuxAudioTextBox_PreviewDragOver(object sender, DragEventArgs e)
195 | {
196 | e.Effects = DragDropEffects.Copy;
197 | e.Handled = true;
198 | }
199 |
200 | private void MuxAudioTextBox_PreviewDragDrop(object sender, DragEventArgs e)
201 | {
202 | foreach (var f in (string[])e.Data.GetData(DataFormats.FileDrop))
203 | AppContext.PresetContext.MuxAudioInputFile = f;
204 | }
205 |
206 | private void InputTextBox_PreviewDragOver(object sender, DragEventArgs e)
207 | {
208 | e.Effects = DragDropEffects.Copy;
209 | e.Handled = true;
210 | }
211 |
212 | private void InputTextBox_PreviewDragDrop(object sender, DragEventArgs e)
213 | {
214 | var dropedFiles = (string[])e.Data.GetData(DataFormats.FileDrop);
215 |
216 | var firstNewFilePos = AppContext.FileSelector.AddFiles(dropedFiles);
217 |
218 | if (firstNewFilePos >= 0) AppContext.FileSelector.NotifyChanged(firstNewFilePos);
219 | }
220 |
221 | private void SimpleEncButton_Click(object sender, RoutedEventArgs e)
222 | {
223 | BuildTaskAndAddEncodingQueueAction(EncodingAction.Simple, "Normal", null);
224 | }
225 |
226 | private void SimpleHDREncButton_Click(object sender, RoutedEventArgs e)
227 | {
228 | BuildTaskAndAddEncodingQueueAction(EncodingAction.Simple, "HDR", null);
229 | }
230 |
231 | private void HDRTagEncButton_Click_SDR(object sender, RoutedEventArgs e)
232 | {
233 | BuildTaskAndAddEncodingQueueAction(EncodingAction.HDRTagUseFFMpeg, "SDR", null);
234 | }
235 |
236 | private void HDRTagEncButton_Click_HDR10(object sender, RoutedEventArgs e)
237 | {
238 | BuildTaskAndAddEncodingQueueAction(EncodingAction.HDRTagUseFFMpeg, "HDR10", null);
239 | }
240 |
241 | private void HDRTagEncButton_Click_HLG(object sender, RoutedEventArgs e)
242 | {
243 | BuildTaskAndAddEncodingQueueAction(EncodingAction.HDRTagUseFFMpeg, "HLG", null);
244 | }
245 |
246 | private void VSPipeEncButton_Click(object sender, RoutedEventArgs e)
247 | {
248 | var vsScript = VsEditor.Document.Text;
249 | BuildTaskAndAddEncodingQueueAction(EncodingAction.VSPipe, vsScript, null);
250 | }
251 |
252 | private void AudioEncButton_Click(object sender, RoutedEventArgs e)
253 | {
254 | var audioOutput = AppContext.PresetContext.AudioOutputFile;
255 | BuildTaskAndAddEncodingQueueAction(EncodingAction.AudioEncoding, "Normal", audioOutput);
256 | }
257 |
258 | private void AudioExtractButton_Click(object sender, RoutedEventArgs e)
259 | {
260 | var audioOutput = AppContext.PresetContext.AudioOutputFile;
261 | BuildTaskAndAddEncodingQueueAction(EncodingAction.AudioExtract, "Normal", audioOutput);
262 | }
263 |
264 | private void MuxButton_Click(object sender, RoutedEventArgs e)
265 | {
266 | var muxAudioInput = AppContext.PresetContext.MuxAudioInputFile;
267 | var muxOutput = AppContext.PresetContext.MuxOutputFile;
268 | BuildTaskAndAddEncodingQueueAction(EncodingAction.Muxer, muxAudioInput, muxOutput);
269 | }
270 |
271 | private void FfmpegPipe_Click_NoAudio(object sender, RoutedEventArgs e)
272 | {
273 | BuildTaskAndAddEncodingQueueAction(EncodingAction.FFMpegPipe, "NoAudio", null);
274 | }
275 |
276 | private void FfmpegPipe_Click_CopyAudio(object sender, RoutedEventArgs e)
277 | {
278 | BuildTaskAndAddEncodingQueueAction(EncodingAction.FFMpegPipe, "CopyAudio", null);
279 | }
280 |
281 | private void FfmpegPipe_Click_ProcessAudio(object sender, RoutedEventArgs e)
282 | {
283 | BuildTaskAndAddEncodingQueueAction(EncodingAction.FFMpegPipe, "ProcessAudio", null);
284 | }
285 |
286 | private void SimpleWithAss_Click(object sender, RoutedEventArgs e)
287 | {
288 | var assFileInput = AppContext.PresetContext.SubBurnAssFile;
289 | BuildTaskAndAddEncodingQueueAction(EncodingAction.SimpleWithAss, "Normal", assFileInput);
290 | }
291 |
292 | private void SimpleWithAssHdr_Click(object sender, RoutedEventArgs e)
293 | {
294 | var assFileInput = AppContext.PresetContext.SubBurnAssFile;
295 | BuildTaskAndAddEncodingQueueAction(EncodingAction.SimpleWithAss, "HDR", assFileInput);
296 | }
297 |
298 | private void BuildTaskAndAddEncodingQueueAction(EncodingAction action, string param, string extra)
299 | {
300 | var input = AppContext.PresetContext.InputFile;
301 | var output = AppContext.PresetContext.OutputFile;
302 | var preset = DeepCompare.CloneDeep1(AppContext.PresetContext.CurrentPreset);
303 |
304 | //调用GetAndRemoveAllSelectFilePath后会引起列表改变,进而修改input和output的值,因此必须在获取input和output值后再调用
305 | var mainWindow = (MainWindow)Window.GetWindow(this);
306 | var selectPaths = mainWindow!.MainFileList.GetAndRemoveAllSelectFilePath();
307 |
308 | TaskBuilder.AddEncodingTask(action, param, preset, selectPaths, input, output, extra);
309 | }
310 |
311 | private void Tab_DragOver(object sender, DragEventArgs e)
312 | {
313 | var tabItem1 = (TabItem)sender;
314 | var index = ((TabControl)(tabItem1.Parent)).Items.IndexOf(tabItem1);
315 | AppContext.PresetContext.SelectedTab = index;
316 | }
317 |
318 | private void OutputFile_TextChanged(object sender, TextChangedEventArgs e)
319 | {
320 | if (!_isAutoChange)
321 | _pathChanged = true;
322 | else
323 | _isAutoChange = false;
324 | }
325 | }
--------------------------------------------------------------------------------
/NegativeEncoder/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
101 |
102 |
103 |
104 |
105 |
106 |
109 |
110 |
111 |
114 |
115 |
116 |
117 |
118 |
119 |
121 |
122 |
123 |
124 |
125 |
126 |
128 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/NegativeEncoder/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading.Tasks;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Input;
7 | using Microsoft.Win32;
8 | using NegativeEncoder.About;
9 | using NegativeEncoder.EncodingTask;
10 | using NegativeEncoder.Presets;
11 | using NegativeEncoder.SystemOptions;
12 | using NegativeEncoder.Utils;
13 | using NegativeEncoder.Utils.CmdTools;
14 |
15 | namespace NegativeEncoder;
16 |
17 | ///
18 | /// Interaction logic for MainWindow.xaml
19 | ///
20 | public partial class MainWindow
21 | {
22 | public MainWindow()
23 | {
24 | InitializeComponent();
25 | }
26 |
27 | private async void Window_Loaded(object sender, RoutedEventArgs e)
28 | {
29 | //初始化(阶段2)
30 | DataContext = new
31 | {
32 | AppContext.Version,
33 | AppContext.PresetContext
34 | };
35 | StatusBar.DataContext = AppContext.Status;
36 | FunctionTabs.DataContext = AppContext.PresetContext;
37 | TaskQueueListBox.ItemsSource = AppContext.EncodingContext.TaskQueue;
38 |
39 | AppContext.Status.MainStatus = "载入系统配置...";
40 | AppContext.Config = await SystemOption.ReadOption(); //读取全局配置
41 | await PresetProvider.LoadPresetAutoSave(); //读取当前预设
42 |
43 | PresetProvider.InitPresetAutoSave(PresetMenuItems); //初始化预设自动保存
44 |
45 | AutoCheckUpdateAfterStartupMenuItem.IsChecked = AppContext.Config.AutoCheckUpdate;
46 |
47 | OpenNewVersionReleasePageMenuItem.DataContext = AppContext.Version;
48 |
49 | if (AppContext.Config.AutoCheckUpdate) CheckUpdateMenuItem_Click(sender, e);
50 |
51 | AppContext.Status.MainStatus = "就绪";
52 | }
53 |
54 | private void ImportVideoMenuItem_Click(object sender, RoutedEventArgs e)
55 | {
56 | MainFileList.ImportVideoAction(sender, e);
57 | }
58 |
59 | private void ClearFilesMenuItem_Click(object sender, RoutedEventArgs e)
60 | {
61 | MainFileList.ClearFileList(sender, e);
62 | }
63 |
64 | private void ExitAppMenuItem_Click(object sender, RoutedEventArgs e)
65 | {
66 | Application.Current.Shutdown();
67 | }
68 |
69 | private async void AutoCheckUpdateAfterStartupMenuItem_Click(object sender, RoutedEventArgs e)
70 | {
71 | AppContext.Config.AutoCheckUpdate = AutoCheckUpdateAfterStartupMenuItem.IsChecked;
72 | await SystemOption.SaveOption(AppContext.Config);
73 | }
74 |
75 | private void CheckUpdateMenuItem_Click(object sender, RoutedEventArgs e)
76 | {
77 | Task.Run(async () => { await CheckUpdate.Check(); });
78 | }
79 |
80 | private void OpenNewVersionReleasePageMenuItem_Click(object sender, RoutedEventArgs e)
81 | {
82 | var url = AppContext.Version.UpdateVersionLinkUrl;
83 | if (!string.IsNullOrEmpty(url)) OpenBrowserViewLink.OpenUrl(url);
84 | }
85 |
86 | private void OpenAboutWindowMenuItem_Click(object sender, RoutedEventArgs e)
87 | {
88 | var aboutWindow = new AboutWindow
89 | {
90 | WindowStartupLocation = WindowStartupLocation.CenterOwner,
91 | Owner = this
92 | };
93 | aboutWindow.Show();
94 | }
95 |
96 | private void NewPresetMenuItem_Click(object sender, RoutedEventArgs e)
97 | {
98 | PresetProvider.NewPreset(this);
99 | }
100 |
101 | private void SavePresetMenuItem_Click(object sender, RoutedEventArgs e)
102 | {
103 | _ = PresetProvider.SavePreset(PresetMenuItems);
104 | }
105 |
106 | private void SaveAsPresetMenuItem_Click(object sender, RoutedEventArgs e)
107 | {
108 | var oldName = AppContext.PresetContext.CurrentPreset.PresetName;
109 |
110 | var newNameWindow = new PresetReName(oldName)
111 | {
112 | WindowStartupLocation = WindowStartupLocation.CenterOwner,
113 | Owner = this
114 | };
115 | if (newNameWindow.ShowDialog() == true)
116 | {
117 | var newName = newNameWindow.NameBox.Text;
118 |
119 | _ = PresetProvider.SaveAsPreset(PresetMenuItems, newName);
120 | }
121 | }
122 |
123 | private void RenamePresetMenuItem_Click(object sender, RoutedEventArgs e)
124 | {
125 | var oldName = AppContext.PresetContext.CurrentPreset.PresetName;
126 |
127 | var newNameWindow = new PresetReName(oldName)
128 | {
129 | WindowStartupLocation = WindowStartupLocation.CenterOwner,
130 | Owner = this
131 | };
132 | if (newNameWindow.ShowDialog() == true)
133 | {
134 | var newName = newNameWindow.NameBox.Text;
135 |
136 | _ = PresetProvider.RenamePreset(PresetMenuItems, newName);
137 | }
138 | }
139 |
140 | private void DeletePresetMenuItem_Click(object sender, RoutedEventArgs e)
141 | {
142 | _ = PresetProvider.DeletePreset(PresetMenuItems);
143 | }
144 |
145 | private void ExportPresetMenuItem_Click(object sender, RoutedEventArgs e)
146 | {
147 | var sfd = new SaveFileDialog
148 | {
149 | DefaultExt = "json",
150 | Filter = "预设配置文件(JSON) (*.json)|*.json|所有文件(*.*)|*.*"
151 | };
152 |
153 | if (sfd.ShowDialog() == true) _ = PresetProvider.ExportPreset(sfd.FileName);
154 | }
155 |
156 | private void ImportPresetMenuItem_Click(object sender, RoutedEventArgs e)
157 | {
158 | var ofd = new OpenFileDialog
159 | {
160 | Filter = "预设配置文件(JSON) (*.json)|*.json|所有文件(*.*)|*.*"
161 | };
162 |
163 | if (ofd.ShowDialog() == true) _ = PresetProvider.ImportPreset(PresetMenuItems, ofd.FileName);
164 | }
165 |
166 | private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
167 | {
168 | var source = ((ListBoxItem)sender).Content as EncodingTask.EncodingTask;
169 |
170 | var taskDetailWindow = new TaskDetailWindow
171 | {
172 | Owner = this,
173 | DataContext = source
174 | };
175 |
176 | taskDetailWindow.Show();
177 | }
178 |
179 | private void TaskScheduleMenuItem_Click(object sender, RoutedEventArgs e)
180 | {
181 | TaskProvider.Schedule();
182 | }
183 |
184 | private void EncodeContextMenuOpenDetailMenuItem_Click(object sender, RoutedEventArgs e)
185 | {
186 | var source = TaskQueueListBox.SelectedItem as EncodingTask.EncodingTask;
187 | var taskDetailWindow = new TaskDetailWindow
188 | {
189 | Owner = this,
190 | DataContext = source
191 | };
192 |
193 | taskDetailWindow.Show();
194 | }
195 |
196 | private void EncodeContextMenuBrowseOutputDirMenuItem_Click(object sender, RoutedEventArgs e)
197 | {
198 | var source = TaskQueueListBox.SelectedItem as EncodingTask.EncodingTask;
199 |
200 | if (!string.IsNullOrEmpty(source!.Output))
201 | {
202 | var psi = new ProcessStartInfo("explorer.exe")
203 | {
204 | Arguments = "/e,/select," + source.Output
205 | };
206 | Process.Start(psi);
207 | }
208 | }
209 |
210 | private void OpenNEENCToolsCmdMenuItem_Click(object sender, RoutedEventArgs e)
211 | {
212 | try
213 | {
214 | SaveCmdTools.SaveOpenBat();
215 | }
216 | catch (UnauthorizedAccessException)
217 | {
218 | var psi = new ProcessStartInfo(AppContext.EncodingContext.AppSelf)
219 | {
220 | Arguments = $"--runFunc SaveCmdTools --baseDir \"{AppContext.EncodingContext.BaseDir}\"",
221 | UseShellExecute = true,
222 | Verb = "RunAs"
223 | };
224 | Process.Start(psi);
225 | }
226 | finally
227 | {
228 | SaveCmdTools.OpenCmdTools();
229 | }
230 | }
231 |
232 | private void NEToolsInstallMenuItem_Click(object sender, RoutedEventArgs e)
233 | {
234 | var psi = new ProcessStartInfo(AppContext.EncodingContext.AppSelf)
235 | {
236 | Arguments = $"--runFunc InstallCmdTools --baseDir \"{AppContext.EncodingContext.BaseDir}\"",
237 | UseShellExecute = true,
238 | Verb = "RunAs"
239 | };
240 | Process.Start(psi);
241 | }
242 |
243 | private void NEToolsRemoveMenuItem_Click(object sender, RoutedEventArgs e)
244 | {
245 | var psi = new ProcessStartInfo(AppContext.EncodingContext.AppSelf)
246 | {
247 | Arguments = $"--runFunc UninstallCmdTools --baseDir \"{AppContext.EncodingContext.BaseDir}\"",
248 | UseShellExecute = true,
249 | Verb = "RunAs"
250 | };
251 | Process.Start(psi);
252 | }
253 | }
--------------------------------------------------------------------------------
/NegativeEncoder/NegativeEncoder.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net8.0-windows10.0.20348.0
6 | true
7 | 5.0.8
8 | Ted Zyzsdy
9 | MeowSound Idols
10 | 一个可爱的NVENC/QSVENC/VCEENC的外壳
11 |
12 | https://github.com/zyzsdy/NegativeEncoder
13 | icon.png
14 | 消极压制
15 | Copyright © 2018-2022 MeowSound Idols
16 | LICENSE
17 | ne.ico
18 | NegativeEncoder.Program
19 | 10.0.17763.0
20 | AnyCPU;x64
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | True
32 |
33 |
34 |
35 | True
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | all
45 | runtime; build; native; contentfiles; analyzers; buildtransitive
46 |
47 |
48 |
49 |
50 | all
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Always
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/Converters/AudioEncodeEnableModeConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace NegativeEncoder.Presets.Converters;
7 |
8 | public class AudioEncodeEnableModeConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | if (value != null)
13 | {
14 | var v = (AudioEncode)value;
15 | return v == AudioEncode.Encode;
16 | }
17 |
18 | return DependencyProperty.UnsetValue;
19 | }
20 |
21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
22 | {
23 | return DependencyProperty.UnsetValue;
24 | }
25 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/Converters/CustomVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace NegativeEncoder.Presets.Converters;
7 |
8 | public class CustomVisibilityConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | if (value != null)
13 | {
14 | var v = (bool)value;
15 | return v ? "Collapsed" : "Visible";
16 | }
17 |
18 | return DependencyProperty.UnsetValue;
19 | }
20 |
21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
22 | {
23 | return DependencyProperty.UnsetValue;
24 | }
25 | }
26 |
27 | public class CustomVisibilityNotConverter : IValueConverter
28 | {
29 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
30 | {
31 | if (value != null)
32 | {
33 | var v = (bool)value;
34 | return v ? "Visible" : "Collapsed";
35 | }
36 |
37 | return DependencyProperty.UnsetValue;
38 | }
39 |
40 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
41 | {
42 | return DependencyProperty.UnsetValue;
43 | }
44 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/Converters/EncodeModeConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace NegativeEncoder.Presets.Converters;
7 |
8 | public class EncodeModeConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | if (value != null)
13 | {
14 | var v = (EncodeMode)value;
15 | return v == (EncodeMode)int.Parse(parameter.ToString());
16 | }
17 |
18 | return DependencyProperty.UnsetValue;
19 | }
20 |
21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
22 | {
23 | var isChecked = (bool)value;
24 | if (isChecked) return (EncodeMode)int.Parse(parameter.ToString());
25 |
26 | return DependencyProperty.UnsetValue;
27 | }
28 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/Converters/QSVNVENCEnableConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace NegativeEncoder.Presets.Converters;
7 |
8 | public class QSVEnableConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | if (value != null)
13 | {
14 | var v = (Encoder)value;
15 | return v == Encoder.QSV;
16 | }
17 |
18 | return DependencyProperty.UnsetValue;
19 | }
20 |
21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
22 | {
23 | return DependencyProperty.UnsetValue;
24 | }
25 | }
26 |
27 | public class NVENCEnableConverter : IValueConverter
28 | {
29 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
30 | {
31 | if (value != null)
32 | {
33 | var v = (Encoder)value;
34 | return v == Encoder.NVENC;
35 | }
36 |
37 | return DependencyProperty.UnsetValue;
38 | }
39 |
40 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
41 | {
42 | return DependencyProperty.UnsetValue;
43 | }
44 | }
45 |
46 | public class VCEDisableConverter : IValueConverter
47 | {
48 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
49 | {
50 | if (value != null)
51 | {
52 | var v = (Encoder)value;
53 | return v != Encoder.VCE;
54 | }
55 |
56 | return DependencyProperty.UnsetValue;
57 | }
58 |
59 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
60 | {
61 | return DependencyProperty.UnsetValue;
62 | }
63 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/Converters/SDREnableConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace NegativeEncoder.Presets.Converters;
7 |
8 | public class SDREnableConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | if (value != null)
13 | {
14 | var v = (HdrType)value;
15 | return v == HdrType.SDR;
16 | }
17 |
18 | return DependencyProperty.UnsetValue;
19 | }
20 |
21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
22 | {
23 | return DependencyProperty.UnsetValue;
24 | }
25 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/Converters/TitleConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Data;
6 |
7 | namespace NegativeEncoder.Presets.Converters;
8 |
9 | public class TitleConverter : IMultiValueConverter
10 | {
11 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
12 | {
13 | if (values.Length != 3) throw new Exception("TitleConverter 必须绑定3个对象");
14 |
15 | var title = "消极压制";
16 |
17 | if (values[0] != null) title += $" v{values[0]}";
18 |
19 | if (values[1] != null && values[2] != null)
20 | {
21 | if ((bool)values[2])
22 | title += $" (预设: *{values[1]})";
23 | else
24 | title += $" (预设: {values[1]})";
25 | }
26 |
27 | return title;
28 | }
29 |
30 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
31 | {
32 | return targetTypes.Select(p => DependencyProperty.UnsetValue).ToArray();
33 | }
34 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/EncoderEnums.cs:
--------------------------------------------------------------------------------
1 | namespace NegativeEncoder.Presets;
2 |
3 | public enum EncodingAction
4 | {
5 | Simple,
6 | HDRTagUseFFMpeg,
7 | VSPipe,
8 | AudioEncoding,
9 | AudioExtract,
10 | Muxer,
11 | FFMpegPipe,
12 | SimpleWithAss
13 | }
14 |
15 | public enum Encoder
16 | {
17 | NVENC,
18 | QSV,
19 | VCE
20 | }
21 |
22 | public enum Codec
23 | {
24 | AVC,
25 | HEVC,
26 | AV1
27 | }
28 |
29 | public enum EncodeMode
30 | {
31 | CQP,
32 | CBR,
33 | VBR,
34 | LA,
35 | LAICQ,
36 | QVBR
37 | }
38 |
39 | public enum QualityPreset
40 | {
41 | Performance = 1,
42 | Balanced = 4,
43 | Quality = 7
44 | }
45 |
46 | public enum ColorDepth
47 | {
48 | C10Bit = 10,
49 | C8Bit = 8
50 | }
51 |
52 | public enum Decoder
53 | {
54 | AVSW,
55 | AVHW
56 | }
57 |
58 | public enum D3DMode
59 | {
60 | Disable = -1,
61 | Auto,
62 | D3D9 = 9,
63 | D3D11 = 11
64 | }
65 |
66 | public enum AVSync
67 | {
68 | Cfr,
69 | ForceCfr,
70 | Vfr
71 | }
72 |
73 | public enum FieldOrder
74 | {
75 | TFF,
76 | BFF
77 | }
78 |
79 | public enum DeInterlaceMethodPreset
80 | {
81 | ///
82 | /// 硬件反交错 普通模式(NVENC/QSV)
83 | ///
84 | HwNormal,
85 |
86 | ///
87 | /// 硬件反交错 Double(NVENC/QSV)
88 | ///
89 | HwBob,
90 |
91 | ///
92 | /// 硬件反交错 IVTC (QSV)
93 | ///
94 | HwIt,
95 |
96 | ///
97 | /// AFS Default (NVENC/VCE)
98 | ///
99 | AfsDefault,
100 |
101 | ///
102 | /// AFS Triple (NVENC/VCE)
103 | ///
104 | AfsTriple,
105 |
106 | ///
107 | /// AFS Double (NVENC/VCE)
108 | ///
109 | AfsDouble,
110 |
111 | ///
112 | /// AFS Anime (NVENC/VCE)
113 | ///
114 | AfsAnime,
115 |
116 | ///
117 | /// AFS Anime 24fps (NVENC/VCE)
118 | ///
119 | AfsAnime24fps,
120 |
121 | ///
122 | /// AFS 24fps IVTC (NVENC/VCE)
123 | ///
124 | Afs24fps,
125 |
126 | ///
127 | /// AFS 30fps (NVENC/VCE)
128 | ///
129 | Afs30fps,
130 |
131 | ///
132 | /// nnedi 64(32x6) no prescreen slow (NVENC/VCE)
133 | ///
134 | Nnedi64NoPre,
135 |
136 | ///
137 | /// nnedi 64(32x6) fast (NVENC/VCE)
138 | ///
139 | Nnedi64Fast,
140 |
141 | ///
142 | /// nnedi 32(32x4) fast (NVENC/VCE)
143 | ///
144 | Nnedi32Fast,
145 |
146 | ///
147 | /// Yadif TFF (NVENC)
148 | ///
149 | YadifTff,
150 |
151 | ///
152 | /// Yadif BFF (NVENC)
153 | ///
154 | YadifBff,
155 |
156 | ///
157 | /// Yadif Double (NVENC)
158 | ///
159 | YadifBob
160 | }
161 |
162 | public enum AudioEncode
163 | {
164 | None,
165 | Copy,
166 | Encode
167 | }
168 |
169 | public enum OutputFormat
170 | {
171 | MP4,
172 | MPEGTS,
173 | FLV,
174 | MKV
175 | }
176 |
177 | public enum HdrType
178 | {
179 | SDR,
180 | HDR10,
181 | HLG
182 | }
183 |
184 | public enum Hdr2Sdr
185 | {
186 | None,
187 | Hable,
188 | Mobius,
189 | Reinhard,
190 | Bt2390
191 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/Preset.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using PropertyChanged;
3 |
4 | namespace NegativeEncoder.Presets;
5 |
6 | [AddINotifyPropertyChangedInterface]
7 | public class Preset : INotifyPropertyChanged
8 | {
9 | public Preset()
10 | {
11 | PropertyChanged += PresetProvider.CurrentPreset_PropertyChanged;
12 | }
13 |
14 | ///
15 | /// 预设标题
16 | ///
17 | public string PresetName { get; set; } = "Default";
18 |
19 | ///
20 | /// 编码器
21 | ///
22 | public Encoder Encoder { get; set; } = Encoder.NVENC;
23 |
24 | ///
25 | /// 目标编码
26 | ///
27 | public Codec Codec { get; set; } = Codec.AVC;
28 |
29 | ///
30 | /// 编码模式
31 | ///
32 | public EncodeMode EncodeMode { get; set; } = EncodeMode.VBR;
33 |
34 | public string CqpParam { get; set; } = "24:26:27";
35 | public string CbrParam { get; set; } = "6500";
36 | public string VbrParam { get; set; } = "6500";
37 | public string LaParam { get; set; } = "6500";
38 | public string LaicqParam { get; set; } = "23";
39 | public string QvbrParam { get; set; } = "6500";
40 |
41 | ///
42 | /// 编码质量预设
43 | /// (预设名-说明=NVENC/QSV/VCE)
44 | /// Performance-性能优先=performance/faster/fast
45 | /// Balanced-平衡=default/balanced/default
46 | /// Quality-质量优先=quality/best/slow
47 | ///
48 | public QualityPreset QualityPreset { get; set; } = QualityPreset.Balanced;
49 |
50 | ///
51 | /// 色深
52 | ///
53 | public ColorDepth ColorDepth { get; set; } = ColorDepth.C8Bit;
54 |
55 | ///
56 | /// VBR质量,当使用VBR模式(NVENC)/QVBR模式(QSV)时,可以指定目标质量(0~51,默认23)
57 | ///
58 | public string VbrQuailty { get; set; } = "23";
59 |
60 | ///
61 | /// 是否设置最大GOP
62 | ///
63 | public bool IsSetMaxGop { get; set; } = false;
64 |
65 | ///
66 | /// 最大GOP的设置值
67 | ///
68 | public string MaxGop { get; set; } = "600";
69 |
70 | ///
71 | /// 是否使用固定GOP
72 | ///
73 | public bool IsStrictGop { get; set; } = false;
74 |
75 | ///
76 | /// 是否设置显示比例
77 | ///
78 | public bool IsSetDar { get; set; } = false;
79 |
80 | ///
81 | /// 显示比例
82 | ///
83 | public string Dar { get; set; } = "16:9";
84 |
85 | ///
86 | /// 是否设置限制最大码率
87 | ///
88 | public bool IsSetMaxBitrate { get; set; } = false;
89 |
90 | ///
91 | /// 最大码率
92 | ///
93 | public string MaxBitrate { get; set; } = "22000";
94 |
95 | ///
96 | /// 解码器 (avhw-avformat+cuvid硬解 avsw-avformat+ffmpeg软解)
97 | ///
98 | public Decoder Decoder { get; set; } = Decoder.AVHW;
99 |
100 | ///
101 | /// D3D显存模式(仅QSV)
102 | ///
103 | public D3DMode D3DMode { get; set; } = D3DMode.Auto;
104 |
105 | ///
106 | /// 是否设置音频同步
107 | ///
108 | public bool IsSetAvSync { get; set; } = false;
109 |
110 | ///
111 | /// 音频同步
112 | ///
113 | public AVSync AVSync { get; set; } = AVSync.Cfr;
114 |
115 | ///
116 | /// 是否启用反交错
117 | ///
118 | public bool IsUseDeInterlace { get; set; } = false;
119 |
120 | ///
121 | /// 交错源场顺序
122 | ///
123 | public FieldOrder FieldOrder { get; set; } = FieldOrder.TFF;
124 |
125 | ///
126 | /// 硬件反交错模式
127 | ///
128 | public DeInterlaceMethodPreset DeInterlaceMethodPreset { get; set; } = DeInterlaceMethodPreset.HwNormal;
129 |
130 | ///
131 | /// 是否设置输出分辨率(调整大小)
132 | ///
133 | public bool IsSetOutputRes { get; set; } = false;
134 |
135 | public string OutputResWidth { get; set; } = "1920";
136 | public string OUtputResHeight { get; set; } = "1080";
137 |
138 | ///
139 | /// 音频编码选项
140 | ///
141 | public AudioEncode AudioEncode { get; set; } = AudioEncode.Copy;
142 |
143 | public string AudioBitrate { get; set; } = "192";
144 |
145 | ///
146 | /// 输出格式
147 | ///
148 | public OutputFormat OutputFormat { get; set; } = OutputFormat.MP4;
149 |
150 | ///
151 | /// 使用自定义参数
152 | ///
153 | public bool IsUseCustomParameters { get; set; } = false;
154 |
155 | public string CustomParameters { get; set; } = "";
156 |
157 | ///
158 | /// 是否输出带有HDR标记的视频
159 | ///
160 | public bool IsOutputHdr { get; set; } = false;
161 |
162 | ///
163 | /// 输出HDR格式
164 | ///
165 | public HdrType OutputHdrType { get; set; } = HdrType.HLG;
166 |
167 | ///
168 | /// 是否在每个IDR帧重复输出Header
169 | ///
170 | public bool IsRepeatHeaders { get; set; } = false;
171 |
172 | ///
173 | /// 是否进行HDR格式转换
174 | ///
175 | public bool IsConvertHdrType { get; set; } = false;
176 |
177 | public HdrType OldHdrType { get; set; } = HdrType.HLG;
178 | public HdrType NewHdrType { get; set; } = HdrType.HDR10;
179 |
180 |
181 | ///
182 | /// HDR转SDR算法
183 | ///
184 | public Hdr2Sdr Hdr2SdrMethod { get; set; } = Hdr2Sdr.None;
185 |
186 | public string Hdr2SdrDeSatStrength { get; set; } = "0.75";
187 |
188 | ///
189 | /// 封装格式
190 | ///
191 | public OutputFormat MuxFormat { get; set; } = OutputFormat.MP4;
192 |
193 | public event PropertyChangedEventHandler PropertyChanged;
194 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/PresetContext.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Windows.Controls;
3 | using NegativeEncoder.VsScriptBuilder;
4 | using PropertyChanged;
5 |
6 | namespace NegativeEncoder.Presets;
7 |
8 | [AddINotifyPropertyChangedInterface]
9 | public class PresetContext
10 | {
11 | ///
12 | /// 当前使用的预设(保存编辑中状态)
13 | ///
14 | public Preset CurrentPreset { get; set; } = new();
15 |
16 | ///
17 | /// 输入文件路径
18 | ///
19 | public string InputFile { get; set; } = string.Empty;
20 |
21 | ///
22 | /// 字幕文件
23 | ///
24 | public string SubBurnAssFile { get; set; } = string.Empty;
25 |
26 | ///
27 | /// 输出文件路径
28 | ///
29 | public string OutputFile { get; set; } = string.Empty;
30 |
31 | public string AudioOutputFile { get; set; } = string.Empty;
32 | public string MuxAudioInputFile { get; set; } = string.Empty;
33 | public string MuxOutputFile { get; set; } = string.Empty;
34 |
35 | ///
36 | /// 已存储的预设
37 | ///
38 | public List PresetList { get; set; } = new();
39 |
40 | ///
41 | /// 当前预设相对于已存储的预设是否有编辑
42 | ///
43 | public bool IsPresetEdit { get; set; } = true;
44 |
45 | ///
46 | /// 下拉框可选项列表
47 | ///
48 | public PresetOption PresetOption { get; set; } = new();
49 |
50 | ///
51 | /// VS脚本生成器界面元素
52 | ///
53 | public VsScript VsScript { get; set; } = new();
54 |
55 | public int SelectedTab { get; set; } = 0;
56 |
57 | public event SelectionChangedEventHandler InputFileChanged;
58 |
59 | public void NotifyInputFileChange(object sender, SelectionChangedEventArgs e)
60 | {
61 | InputFileChanged?.Invoke(sender, e);
62 | }
63 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/PresetOption.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using PropertyChanged;
3 |
4 | namespace NegativeEncoder.Presets;
5 |
6 | [AddINotifyPropertyChangedInterface]
7 | public class PresetOption
8 | {
9 | public ObservableCollection> EncoderOptions { get; set; } = new()
10 | {
11 | new EnumOption { Value = Encoder.NVENC, Name = "NVENC" },
12 | new EnumOption { Value = Encoder.QSV, Name = "QuickSync" },
13 | new EnumOption { Value = Encoder.VCE, Name = "AMD VCE" }
14 | };
15 |
16 | public ObservableCollection> CodecOptions { get; set; } = new()
17 | {
18 | new EnumOption { Value = Codec.AVC, Name = "AVC (H.264)" },
19 | new EnumOption { Value = Codec.HEVC, Name = "HEVC (H.265)" },
20 | new EnumOption { Value = Codec.AV1, Name = "AV1" }
21 | };
22 |
23 | public ObservableCollection> QualityPresetOptions { get; set; } = new()
24 | {
25 | new EnumOption { Value = QualityPreset.Performance, Name = "性能优先(快)" },
26 | new EnumOption { Value = QualityPreset.Balanced, Name = "平衡(默认)" },
27 | new EnumOption { Value = QualityPreset.Quality, Name = "质量优先(慢)" }
28 | };
29 |
30 | public ObservableCollection> ColorDepthOptions { get; set; } = new()
31 | {
32 | new EnumOption { Value = ColorDepth.C8Bit, Name = "8 Bit" },
33 | new EnumOption { Value = ColorDepth.C10Bit, Name = "10 Bit" }
34 | };
35 |
36 | public ObservableCollection> DecoderOptions { get; set; } = new()
37 | {
38 | new EnumOption { Value = Decoder.AVHW, Name = "硬件解码" },
39 | new EnumOption { Value = Decoder.AVSW, Name = "软件解码" }
40 | };
41 |
42 | public ObservableCollection> D3DModeOptions { get; set; } = new()
43 | {
44 | new EnumOption { Value = D3DMode.Auto, Name = "自动" },
45 | new EnumOption { Value = D3DMode.Disable, Name = "禁用" },
46 | new EnumOption { Value = D3DMode.D3D9, Name = "d3d9" },
47 | new EnumOption { Value = D3DMode.D3D11, Name = "d3d11" }
48 | };
49 |
50 | public ObservableCollection> AVSyncOptions { get; set; } = new()
51 | {
52 | new EnumOption { Value = AVSync.Cfr, Name = "CFR(默认)" },
53 | new EnumOption { Value = AVSync.ForceCfr, Name = "Force CFR(强制转换)" },
54 | new EnumOption { Value = AVSync.Vfr, Name = "VFR(使用时间码)" }
55 | };
56 |
57 | public ObservableCollection> FieldOrderOptions { get; set; } = new()
58 | {
59 | new EnumOption { Value = FieldOrder.TFF, Name = "TFF" },
60 | new EnumOption { Value = FieldOrder.BFF, Name = "BFF" }
61 | };
62 |
63 | public ObservableCollection> DeInterlaceMethodPresetOptions { get; set; } =
64 | new()
65 | {
66 | new EnumOption
67 | { Value = DeInterlaceMethodPreset.HwNormal, Name = "硬件反交错 普通模式(NVENC/QuickSync)" },
68 | new EnumOption
69 | { Value = DeInterlaceMethodPreset.HwBob, Name = "硬件反交错 Double(NVENC/QuickSync)" },
70 | new EnumOption
71 | { Value = DeInterlaceMethodPreset.HwIt, Name = "硬件反交错 IVTC (QuickSync)" },
72 | new EnumOption
73 | { Value = DeInterlaceMethodPreset.AfsDefault, Name = "AFS Default (NVENC/VCE)" },
74 | new EnumOption
75 | { Value = DeInterlaceMethodPreset.AfsTriple, Name = "AFS Triple (NVENC/VCE)" },
76 | new EnumOption
77 | { Value = DeInterlaceMethodPreset.AfsDouble, Name = "AFS Double (NVENC/VCE)" },
78 | new EnumOption
79 | { Value = DeInterlaceMethodPreset.AfsAnime, Name = "AFS Anime (NVENC/VCE)" },
80 | new EnumOption
81 | { Value = DeInterlaceMethodPreset.AfsAnime24fps, Name = "AFS Anime 24fps IVTC (NVENC/VCE)" },
82 | new EnumOption
83 | { Value = DeInterlaceMethodPreset.Afs24fps, Name = "AFS 24fps IVTC (NVENC/VCE)" },
84 | new EnumOption
85 | { Value = DeInterlaceMethodPreset.Afs30fps, Name = "AFS 30fps (NVENC/VCE)" },
86 | new EnumOption
87 | {
88 | Value = DeInterlaceMethodPreset.Nnedi64NoPre, Name = "nnedi 64(32x6) no prescreen slow (NVENC/VCE)"
89 | },
90 | new EnumOption
91 | { Value = DeInterlaceMethodPreset.Nnedi64Fast, Name = "nnedi 64(32x6) fast (NVENC/VCE)" },
92 | new EnumOption
93 | { Value = DeInterlaceMethodPreset.Nnedi32Fast, Name = "nnedi 32(32x4) fast (NVENC/VCE)" },
94 | new EnumOption
95 | { Value = DeInterlaceMethodPreset.YadifTff, Name = "Yadif TFF (NVENC)" },
96 | new EnumOption
97 | { Value = DeInterlaceMethodPreset.YadifBff, Name = "Yadif BFF (NVENC)" },
98 | new EnumOption
99 | { Value = DeInterlaceMethodPreset.YadifBob, Name = "Yadif Double (NVENC)" }
100 | };
101 |
102 | public ObservableCollection> AudioEncodeOptions { get; set; } = new()
103 | {
104 | new EnumOption { Value = AudioEncode.None, Name = "无音频流" },
105 | new EnumOption { Value = AudioEncode.Copy, Name = "复制音频流" },
106 | new EnumOption { Value = AudioEncode.Encode, Name = "编码音频" }
107 | };
108 |
109 | public ObservableCollection> OutputFormatOptions { get; set; } = new()
110 | {
111 | new EnumOption { Value = OutputFormat.MP4, Name = "MP4" },
112 | new EnumOption { Value = OutputFormat.MPEGTS, Name = "MPEG TS" },
113 | new EnumOption { Value = OutputFormat.FLV, Name = "FLV" },
114 | new EnumOption { Value = OutputFormat.MKV, Name = "MKV" }
115 | };
116 |
117 | public ObservableCollection> HdrTypeOptions { get; set; } = new()
118 | {
119 | new EnumOption { Value = HdrType.SDR, Name = "SDR" },
120 | new EnumOption { Value = HdrType.HDR10, Name = "HDR10" },
121 | new EnumOption { Value = HdrType.HLG, Name = "HLG" }
122 | };
123 |
124 | public ObservableCollection> Hdr2SdrOptions { get; set; } = new()
125 | {
126 | new EnumOption { Value = Hdr2Sdr.None, Name = "不转换" },
127 | new EnumOption { Value = Hdr2Sdr.Hable, Name = "hable" },
128 | new EnumOption { Value = Hdr2Sdr.Mobius, Name = "mobius" },
129 | new EnumOption { Value = Hdr2Sdr.Reinhard, Name = "reinhard" },
130 | new EnumOption { Value = Hdr2Sdr.Bt2390, Name = "bt2390" }
131 | };
132 | }
133 |
134 | [AddINotifyPropertyChangedInterface]
135 | public class EnumOption
136 | {
137 | public T Value { get; set; }
138 | public string Name { get; set; }
139 | }
--------------------------------------------------------------------------------
/NegativeEncoder/Presets/PresetProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using NegativeEncoder.SystemOptions;
8 | using NegativeEncoder.Utils;
9 |
10 | namespace NegativeEncoder.Presets;
11 |
12 | public static class PresetProvider
13 | {
14 | public static void InitPresetAutoSave(MenuItem presetMenuItems)
15 | {
16 | //AppContext.PresetContext.CurrentPreset.PropertyChanged += CurrentPreset_PropertyChanged;
17 |
18 | ReBuildPresetMenu(presetMenuItems);
19 | }
20 |
21 | public static void CurrentPreset_PropertyChanged(object sender, PropertyChangedEventArgs e)
22 | {
23 | //检查预设是否合法
24 | var preset = AppContext.PresetContext.CurrentPreset;
25 |
26 | //非QSV
27 | if (preset.Encoder != Encoder.QSV)
28 | {
29 | //编码器非QSV的时候禁止选择LA/LA-ICQ和QVBR模式
30 | if (preset.EncodeMode == EncodeMode.LA || preset.EncodeMode == EncodeMode.LAICQ ||
31 | preset.EncodeMode == EncodeMode.QVBR)
32 | AppContext.PresetContext.CurrentPreset.EncodeMode = EncodeMode.VBR;
33 |
34 | //编码器非QSV的时候禁止选择D3D模式
35 | if (preset.D3DMode != D3DMode.Auto) AppContext.PresetContext.CurrentPreset.D3DMode = D3DMode.Auto;
36 | }
37 |
38 | //非NVENC
39 | if (preset.Encoder == Encoder.VCE)
40 | //编码器非NVENC时,只能使用8 bit模式
41 | if (preset.ColorDepth != ColorDepth.C8Bit)
42 | AppContext.PresetContext.CurrentPreset.ColorDepth = ColorDepth.C8Bit;
43 |
44 | //目标HDR格式不为SDR时,SDR转换只能是None
45 | if (preset.NewHdrType != HdrType.SDR)
46 | if (preset.Hdr2SdrMethod != Hdr2Sdr.None)
47 | AppContext.PresetContext.CurrentPreset.Hdr2SdrMethod = Hdr2Sdr.None;
48 |
49 | //标记当前预设已修改
50 | AppContext.PresetContext.IsPresetEdit = true;
51 |
52 | //存储预设到文件
53 | _ = SystemOption.SaveOption(AppContext.PresetContext.CurrentPreset);
54 | }
55 |
56 | public static void ReBuildPresetMenu(MenuItem presetMenuItems)
57 | {
58 | var deletable = new List