├── .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 | 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 | 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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 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 | 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(); 59 | 60 | foreach (var presetSubmenu in presetMenuItems.Items) 61 | if (presetSubmenu is MenuItem submenu) 62 | if (submenu.IsCheckable) 63 | deletable.Add(submenu); 64 | 65 | foreach (var deleteSubmenu in deletable) presetMenuItems.Items.Remove(deleteSubmenu); 66 | 67 | AppContext.PresetContext.IsPresetEdit = true; 68 | 69 | if (AppContext.PresetContext.PresetList.Count == 0) 70 | { 71 | var emptySubMenu = new MenuItem 72 | { 73 | Header = "(空)", 74 | IsCheckable = true, 75 | IsEnabled = false, 76 | IsChecked = false 77 | }; 78 | presetMenuItems.Items.Add(emptySubMenu); 79 | 80 | return; 81 | } 82 | 83 | foreach (var preset in AppContext.PresetContext.PresetList) 84 | { 85 | var presetSubMenu = new MenuItem 86 | { 87 | Header = preset.PresetName, 88 | IsCheckable = true 89 | }; 90 | presetSubMenu.Click += PresetSubMenu_Click; 91 | if (preset.PresetName == AppContext.PresetContext.CurrentPreset.PresetName) 92 | { 93 | presetSubMenu.IsChecked = true; 94 | 95 | if (DeepCompare.EqualsDeep1(preset, AppContext.PresetContext.CurrentPreset)) 96 | AppContext.PresetContext.IsPresetEdit = false; 97 | } 98 | 99 | presetMenuItems.Items.Add(presetSubMenu); 100 | } 101 | } 102 | 103 | private static async void PresetSubMenu_Click(object sender, RoutedEventArgs e) 104 | { 105 | if (sender is MenuItem m) 106 | { 107 | if (AppContext.PresetContext.IsPresetEdit) 108 | if (MessageBox.Show("当前预设未保存,是否放弃?", "预设", MessageBoxButton.YesNo, MessageBoxImage.Question) != 109 | MessageBoxResult.Yes) 110 | { 111 | ReBuildPresetMenu(m.Parent as MenuItem); 112 | return; 113 | } 114 | 115 | var checkName = m.Header as string; 116 | 117 | foreach (var p in AppContext.PresetContext.PresetList) 118 | if (p.PresetName == checkName) 119 | { 120 | AppContext.PresetContext.CurrentPreset = DeepCompare.CloneDeep1(p); 121 | break; 122 | } 123 | 124 | ReBuildPresetMenu(m.Parent as MenuItem); 125 | //存储预设到文件 126 | await SystemOption.SaveOption(AppContext.PresetContext.CurrentPreset); 127 | } 128 | } 129 | 130 | public static async Task LoadPresetAutoSave() 131 | { 132 | AppContext.PresetContext.CurrentPreset = await SystemOption.ReadOption(); 133 | AppContext.PresetContext.PresetList = 134 | (await SystemOption.ReadListOption()).OrderBy(it => it.PresetName).ToList(); 135 | } 136 | 137 | public static void NewPreset(Window parentWindow = null) 138 | { 139 | if (AppContext.PresetContext.IsPresetEdit) 140 | { 141 | var res = MessageBox.Show(parentWindow, "当前预设未保存,是否确认覆盖", "预设", MessageBoxButton.YesNo, 142 | MessageBoxImage.Question); 143 | if (res != MessageBoxResult.Yes) return; 144 | } 145 | 146 | AppContext.PresetContext.CurrentPreset = new Preset(); 147 | AppContext.PresetContext.IsPresetEdit = true; 148 | } 149 | 150 | public static async Task RenamePreset(MenuItem presetMenuItems, string newName) 151 | { 152 | var oldName = AppContext.PresetContext.CurrentPreset.PresetName; 153 | AppContext.PresetContext.CurrentPreset.PresetName = newName; 154 | 155 | if (AppContext.PresetContext.PresetList.Count(p => p.PresetName == oldName) > 0) 156 | for (var p = 0; p < AppContext.PresetContext.PresetList.Count; p++) 157 | if (AppContext.PresetContext.PresetList[p].PresetName == oldName) 158 | { 159 | AppContext.PresetContext.PresetList[p].PresetName = newName; 160 | await SystemOption.SaveListOption(AppContext.PresetContext.PresetList); 161 | break; 162 | } 163 | 164 | ReBuildPresetMenu(presetMenuItems); 165 | } 166 | 167 | public static async Task ExportPreset(string fileName) 168 | { 169 | await SystemOption.SaveListOption(AppContext.PresetContext.PresetList, fileName); 170 | } 171 | 172 | public static async Task ImportPreset(MenuItem presetMenuItems, string fileName) 173 | { 174 | var configList = await SystemOption.ReadListOption(fileName); 175 | 176 | foreach (var config in configList) 177 | { 178 | var cName = config.PresetName; 179 | if (AppContext.PresetContext.PresetList.Count(p => p.PresetName == cName) > 0) 180 | { 181 | if (MessageBox.Show($"正在导入的预设 {cName} 存在同名预设,要覆盖吗?", "导入预设冲突", MessageBoxButton.YesNo, 182 | MessageBoxImage.Question) == MessageBoxResult.Yes) 183 | for (var p = 0; p < AppContext.PresetContext.PresetList.Count; p++) 184 | if (AppContext.PresetContext.PresetList[p].PresetName == cName) 185 | { 186 | AppContext.PresetContext.PresetList[p] = config; 187 | break; 188 | } 189 | } 190 | else 191 | { 192 | AppContext.PresetContext.PresetList.Add(config); 193 | } 194 | } 195 | 196 | await SystemOption.SaveListOption(AppContext.PresetContext.PresetList); 197 | ReBuildPresetMenu(presetMenuItems); 198 | } 199 | 200 | public static async Task DeletePreset(MenuItem presetMenuItems) 201 | { 202 | var presetName = AppContext.PresetContext.CurrentPreset.PresetName; 203 | Preset deletePreset = null; 204 | if (AppContext.PresetContext.PresetList.Count(p => p.PresetName == presetName) > 0) 205 | for (var p = 0; p < AppContext.PresetContext.PresetList.Count; p++) 206 | if (AppContext.PresetContext.PresetList[p].PresetName == presetName) 207 | { 208 | deletePreset = AppContext.PresetContext.PresetList[p]; 209 | break; 210 | } 211 | 212 | if (deletePreset != null) 213 | { 214 | AppContext.PresetContext.PresetList.Remove(deletePreset); 215 | await SystemOption.SaveListOption(AppContext.PresetContext.PresetList); 216 | } 217 | 218 | if (AppContext.PresetContext.PresetList.Count > 0) 219 | { 220 | AppContext.PresetContext.CurrentPreset = DeepCompare.CloneDeep1(AppContext.PresetContext.PresetList[0]); 221 | AppContext.PresetContext.IsPresetEdit = false; 222 | } 223 | else 224 | { 225 | AppContext.PresetContext.CurrentPreset = new Preset(); 226 | AppContext.PresetContext.IsPresetEdit = true; 227 | } 228 | 229 | ReBuildPresetMenu(presetMenuItems); 230 | } 231 | 232 | public static async Task SavePreset(MenuItem presetMenuItems) 233 | { 234 | var presetName = AppContext.PresetContext.CurrentPreset.PresetName; 235 | if (AppContext.PresetContext.PresetList.Count(p => p.PresetName == presetName) > 0) 236 | { 237 | for (var p = 0; p < AppContext.PresetContext.PresetList.Count; p++) 238 | if (AppContext.PresetContext.PresetList[p].PresetName == presetName) 239 | { 240 | AppContext.PresetContext.PresetList[p] = AppContext.PresetContext.CurrentPreset; 241 | await SystemOption.SaveListOption(AppContext.PresetContext.PresetList); 242 | break; 243 | } 244 | } 245 | else 246 | { 247 | await AddPresetList(presetMenuItems, AppContext.PresetContext.CurrentPreset); 248 | } 249 | 250 | ReBuildPresetMenu(presetMenuItems); 251 | AppContext.PresetContext.IsPresetEdit = false; 252 | } 253 | 254 | public static async Task SaveAsPreset(MenuItem presetMenuItems, string newName) 255 | { 256 | AppContext.PresetContext.CurrentPreset = DeepCompare.CloneDeep1(AppContext.PresetContext.CurrentPreset); 257 | AppContext.PresetContext.CurrentPreset.PresetName = newName; 258 | 259 | await SavePreset(presetMenuItems); 260 | } 261 | 262 | private static async Task AddPresetList(MenuItem presetMenuItems, Preset currentPreset) 263 | { 264 | AppContext.PresetContext.PresetList.Add(currentPreset); 265 | presetMenuItems.Items.Add(new MenuItem 266 | { 267 | Header = currentPreset.PresetName, 268 | IsCheckable = true, 269 | IsChecked = true 270 | }); 271 | 272 | await SystemOption.SaveListOption(AppContext.PresetContext.PresetList); 273 | } 274 | } -------------------------------------------------------------------------------- /NegativeEncoder/Presets/PresetReName.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 12 |