├── dist └── windows │ ├── upx.exe │ ├── OKEGui.ico │ ├── nsis plugins │ └── UAC.zip │ ├── examples │ ├── 00001.m2ts.json │ ├── launch.cmd │ ├── demo_720p.json │ ├── vfr.vpy │ ├── demo.vpy │ ├── vfr.json │ ├── demo_720p.vpy │ └── demo.json │ ├── okegui.nsi │ ├── translations.nsi │ ├── README.md │ ├── uninstaller.nsi │ ├── installer-translations │ ├── simpchinese.nsi │ └── english.nsi │ ├── options.nsi │ └── installer.nsi ├── OKEGui ├── OKEGui │ ├── Gui │ │ ├── App.ico │ │ ├── ConfigPanel.xaml.cs │ │ ├── ConfigPanel.xaml │ │ └── WizardWindow.xaml │ ├── App.config │ ├── Properties │ │ ├── Settings.settings │ │ ├── Settings.Designer.cs │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── Utils │ │ ├── PathUtils.cs │ │ ├── TChapterExtension.cs │ │ ├── WmiUtils.cs │ │ ├── RegistryStorage.cs │ │ ├── SafeProxy.cs │ │ ├── Cleaner.cs │ │ ├── Constants.cs │ │ ├── SystemMenu.cs │ │ ├── CRC32.cs │ │ ├── Initializer.cs │ │ └── EnvironmentChecker.cs │ ├── App.xaml │ ├── Model │ │ ├── Track │ │ │ ├── AudioTrack.cs │ │ │ ├── ChapterTrack.cs │ │ │ ├── VideoTrack.cs │ │ │ ├── SubtitleTrack.cs │ │ │ └── Track.cs │ │ ├── Info │ │ │ ├── AudioInfo.cs │ │ │ ├── VideoInfo.cs │ │ │ └── Info.cs │ │ ├── MediaFile.cs │ │ └── OKEFile.cs │ ├── Job │ │ ├── AudioJob │ │ │ └── AudioJob.cs │ │ ├── VideoJob │ │ │ ├── VideoInfoJob.cs │ │ │ └── VideoJob.cs │ │ ├── RpcJob │ │ │ └── RpcJob.cs │ │ └── Job.cs │ ├── packages.config │ ├── Task │ │ ├── EpisodeProfile.cs │ │ ├── TaskDetail.cs │ │ ├── TaskProfile.cs │ │ ├── SubProcessService.cs │ │ ├── TaskStatus.cs │ │ └── ChapterService.cs │ ├── JobProcessor │ │ ├── IJobProcessor.cs │ │ ├── Audio │ │ │ ├── FFmpegVolumeChecker.cs │ │ │ └── QAACEncoder.cs │ │ ├── Demuxer │ │ │ └── TrackInfo.cs │ │ ├── Video │ │ │ ├── X264Encoder.cs │ │ │ ├── svtav1Encoder.cs │ │ │ ├── x265Encoder.cs │ │ │ ├── VSPipeProcessor.cs │ │ │ └── CommandlineVideoEncoder.cs │ │ ├── ExceptionParser.cs │ │ └── RpChecker │ │ │ └── RpChecker.cs │ ├── App.xaml.cs │ └── Worker │ │ ├── NumaNode.cs │ │ └── WorkerManager.cs ├── .editorconfig └── OKEGui.sln ├── .gitmodules ├── README.md ├── .github └── workflows │ ├── dotnet-ci.yml │ └── release.yaml ├── .gitattributes ├── RELNOTES.md └── .gitignore /dist/windows/upx.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKEGui/mod/dist/windows/upx.exe -------------------------------------------------------------------------------- /dist/windows/OKEGui.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKEGui/mod/dist/windows/OKEGui.ico -------------------------------------------------------------------------------- /OKEGui/OKEGui/Gui/App.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKEGui/mod/OKEGui/OKEGui/Gui/App.ico -------------------------------------------------------------------------------- /dist/windows/nsis plugins/UAC.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKEGui/mod/dist/windows/nsis plugins/UAC.zip -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "TChapter"] 2 | path = TChapter 3 | url = https://github.com/vcb-s/TChapter.git 4 | branch = net-45 5 | -------------------------------------------------------------------------------- /dist/windows/examples/00001.m2ts.json: -------------------------------------------------------------------------------- 1 | { 2 | "VspipeArgs" : [ 3 | "op_start=8000", 4 | "op_end=16000" 5 | ] 6 | } -------------------------------------------------------------------------------- /dist/windows/okegui.nsi: -------------------------------------------------------------------------------- 1 | !include options.nsi 2 | !include translations.nsi 3 | !include installer.nsi 4 | !include uninstaller.nsi 5 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /dist/windows/translations.nsi: -------------------------------------------------------------------------------- 1 | ;Nsis translations 2 | 3 | !insertmacro MUI_LANGUAGE "English" 4 | !insertmacro MUI_LANGUAGE "SimpChinese" 5 | 6 | ;Installer/Uninstaller translations 7 | !addincludedir installer-translations 8 | 9 | ;The languages should be in alphabetical order 10 | !include english.nsi 11 | !include simpchinese.nsi 12 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/PathUtils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace OKEGui 4 | { 5 | class PathUtils 6 | { 7 | public static string GetFullPath(string rel, string baseDir) 8 | { 9 | if (Path.IsPathRooted(rel)) 10 | return rel; 11 | return Path.Combine(baseDir, rel); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /OKEGui/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA1712: Do not prefix enum values with type name 4 | dotnet_diagnostic.CA1712.severity = none 5 | 6 | # CS0649: Field 'field' is never assigned to, and will always have its default value 'value' 7 | dotnet_diagnostic.CS0649.severity = suggestion 8 | 9 | # CA2235: Mark all non-serializable fields 10 | dotnet_diagnostic.CA2235.severity = suggestion 11 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/Track/AudioTrack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OKEGui.Model 8 | { 9 | public class AudioTrack : Track 10 | { 11 | public AudioTrack(OKEFile file, AudioInfo info) : base(file, info) 12 | { 13 | TrackType = TrackType.Audio; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/Track/ChapterTrack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OKEGui.Model 8 | { 9 | public class ChapterTrack : Track 10 | { 11 | public ChapterTrack(OKEFile file) : base(file, new Info()) 12 | { 13 | TrackType = TrackType.Chapter; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/Track/VideoTrack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OKEGui.Model 8 | { 9 | public class VideoTrack : Track 10 | { 11 | public VideoTrack(OKEFile file, VideoInfo info) : base(file, info) 12 | { 13 | TrackType = TrackType.Video; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Job/AudioJob/AudioJob.cs: -------------------------------------------------------------------------------- 1 | using OKEGui.Model; 2 | 3 | namespace OKEGui 4 | { 5 | class AudioJob : Job 6 | { 7 | public readonly AudioInfo Info; 8 | 9 | public AudioJob(AudioInfo info) : base(info.OutputCodec) 10 | { 11 | Info = info; 12 | } 13 | 14 | public override JobType GetJobType() 15 | { 16 | return JobType.Audio; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/Info/AudioInfo.cs: -------------------------------------------------------------------------------- 1 | using OKEGui.Utils; 2 | 3 | namespace OKEGui.Model 4 | { 5 | public class AudioInfo : Info 6 | { 7 | public string OutputCodec; 8 | public int Bitrate = Constants.QAACBitrate; 9 | public bool Lossy = false; 10 | public int Quality = 0; 11 | 12 | public AudioInfo() : base() 13 | { 14 | InfoType = InfoType.Audio; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/TChapterExtension.cs: -------------------------------------------------------------------------------- 1 | using TChapter.Chapters; 2 | 3 | namespace OKEGui.Utils 4 | { 5 | static class TChapterExtension 6 | { 7 | public static void Save(this ChapterInfo info, ChapterTypeEnum chapterType, string savePath, int index = 0, 8 | bool removeName = false, string language = "", string sourceFileName = "") 9 | { 10 | new MultiChapterData(ChapterTypeEnum.UNKNOWN) {info} 11 | .Save(chapterType, savePath, index, removeName, language, sourceFileName); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Job/VideoJob/VideoInfoJob.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace OKEGui 4 | { 5 | public class VideoInfoJob : Job 6 | { 7 | public List Args = new List(); 8 | public VideoJob vJob; 9 | public VideoInfoJob(VideoJob job) : base() 10 | { 11 | vJob = job; 12 | Input = job.Input; 13 | Args.AddRange(job.VspipeArgs); 14 | } 15 | 16 | public override JobType GetJobType() 17 | { 18 | return JobType.VideoInfo; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/Track/SubtitleTrack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OKEGui.Model 8 | { 9 | public class SubtitleTrack : Track 10 | { 11 | public SubtitleTrack(OKEFile file, Info info) : base(file, info) 12 | { 13 | if (info.InfoType != InfoType.Default) 14 | { 15 | throw new ArgumentException("Invalid media info for subtitle track"); 16 | } 17 | TrackType = TrackType.Subtitle; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Job/VideoJob/VideoJob.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace OKEGui 4 | { 5 | public class VideoJob : Job 6 | { 7 | public string EncoderPath; 8 | public string EncodeParam; 9 | public List VspipeArgs = new List(); 10 | public bool Vfr; 11 | public double Fps; 12 | public uint FpsNum; 13 | public uint FpsDen; 14 | public int NumaNode; 15 | public ulong NumberOfFrames; 16 | 17 | public VideoJob(string codec) : base(codec) 18 | { 19 | } 20 | 21 | public override JobType GetJobType() 22 | { 23 | return JobType.Video; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/Info/VideoInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OKEGui.Model 8 | { 9 | public class VideoInfo : Info 10 | { 11 | public uint FpsNum; 12 | public uint FpsDen = 1; 13 | public string TimeCodeFile; 14 | 15 | public VideoInfo() : base() 16 | { 17 | InfoType = InfoType.Video; 18 | } 19 | public VideoInfo(uint fpsNum, uint fpsDen, string timeCodeFile) : this() 20 | { 21 | TimeCodeFile = timeCodeFile; 22 | FpsNum = fpsNum; 23 | FpsDen = fpsDen; 24 | } 25 | 26 | public double GetFps() 27 | { 28 | return (double)FpsNum / FpsDen; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dist/windows/examples/launch.cmd: -------------------------------------------------------------------------------- 1 | :: This script launches OKEGui.exe and then elevates privilege to 2 | :: disable power throattling of bundled vspipe.exe and x265.exe. 3 | :: 4 | :: Use this to launch OKEGui when you're using Intel hybrid cores 5 | :: and don't want Windows to put those processes into the efficient 6 | :: cores when the OKEGui window is in the background. 7 | :: 8 | :: Also note this scripts requires that you're not running it as 9 | :: Administrator. 10 | 11 | cd %~dp0 12 | net file 1>NUL 2>NUL 13 | if not '%errorlevel%' == '0' ( 14 | start "" OKEGui.exe 15 | powershell Start-Process -FilePath "%0" -verb runas >NUL 2>&1 16 | exit /b 17 | ) 18 | powercfg /powerthrottling disable /PATH %~dp0\tools\x26x\x265.exe 19 | powercfg /powerthrottling disable /PATH %~dp0\tools\x26x\x264.exe 20 | powercfg /powerthrottling disable /PATH %~dp0\tools\vapoursynth\vspipe.exe 21 | -------------------------------------------------------------------------------- /dist/windows/examples/demo_720p.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version" : 2, 3 | "ProjectName" : "Demo - 720p", 4 | "EncoderType" : "x264", 5 | "Encoder" : "x264_64_tMod-8bit-420.exe", 6 | "EncoderParam" : "--preset veryslow --tune animation --crf 19.0 --deblock 0:0 --keyint 360 --min-keyint 1 --bframes 8 --ref 9 --pbratio 1.25 --qcomp 0.7 --rc-lookahead 70 --aq-strength 0.9 --merange 24 --psy-rd 0.00:0.20 --no-dct-decimate --no-fast-pskip --colormatrix bt709 --fgo 1", 7 | "ContainerFormat" : "mp4", 8 | "AudioTracks" : [{ 9 | "TrackId" : 0, 10 | "OutputCodec" : "aac", 11 | "Bitrate" : 128 12 | },{ 13 | "TrackId" : 1, 14 | "OutputCodec" : "flac", 15 | "MuxOption" : "Skip" 16 | }], 17 | "InputScript" : "demo_720p.vpy", 18 | "Fps" : 23.976, 19 | "SubtitleTracks" : [{ 20 | "MuxOption" : "Skip" 21 | }], 22 | "InputFiles" : [ 23 | "00000.m2ts", 24 | "00001.m2ts", 25 | "00002.m2ts", 26 | ], 27 | "Rpc" : true 28 | } 29 | -------------------------------------------------------------------------------- /dist/windows/examples/vfr.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | from vapoursynth import core 3 | import pathlib 4 | import mvsfunc as mvf 5 | 6 | #OKE:INPUTFILE 7 | A="00000.m2ts" 8 | 9 | if 'a' not in globals(): a = A 10 | 11 | src8 = core.lsmas.LWLibavSource(a) 12 | src16 = core.fmtc.bitdepth(src8,bits=16) 13 | 14 | # preprocess 15 | res = core.grain.Add(src16, 1) 16 | 17 | # generate cfr clips 18 | res_a = core.std.AssumeFPS(res, fpsnum=24000,fpsden=1001)[:res.num_frames//2] 19 | res_b = core.std.AssumeFPS(res, fpsnum=30000,fpsden=1001)[res.num_frames//2:] 20 | 21 | # VFR splice 22 | path = pathlib.Path(a) 23 | res = mvf.VFRSplice([res_a, res_b], tcfile=str(path.with_suffix('.tcfile')), v2=False) # v2=True also ok 24 | # x265 only support CFR input, so use an approximate FPS here. 25 | res = core.std.AssumeFPS(res, fpsnum=27000, fpsden=1001) 26 | 27 | #OKE:DEBUG 28 | Debug = 0 29 | if Debug: 30 | res=core.std.Interleave([src16, res]) 31 | res=mvf.ToRGB(res,full=False,depth=8) 32 | else: res = mvf.Depth(res, 10) 33 | 34 | res.set_output() 35 | src16.set_output(1) 36 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace OKEGui.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dist/windows/examples/demo.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | import sys 3 | import os.path 4 | import math 5 | from vapoursynth import core 6 | import havsfunc as haf 7 | import mvsfunc as mvf 8 | 9 | core.num_threads = 8 10 | 11 | #OKE:PROJECTDIR 12 | projDir = '.' 13 | sys.path.insert(1, projDir) # some packages rely on having '' as sys.path[0] 14 | #import custom # import python modules under the project directory 15 | #core.std.LoadPlugin(os.path.join(projDir, 'libcustom.dll')) # or load custom plugins 16 | 17 | #OKE:MEMORY 18 | core.max_cache_size = 8000 19 | 20 | #OKE:INPUTFILE 21 | a="00000.m2ts" 22 | src8 = core.lsmas.LWLibavSource(a) 23 | src16 = core.fmtc.bitdepth(src8,bits=16) 24 | 25 | op = core.rgvs.RemoveGrain(src16, 20) 26 | 27 | res = core.std.Trim(src16, 0, int(op_start) - 1) + core.std.Trim(op, int(op_start), int(op_end)) + core.std.Trim(src16, int(op_end) + 1, src16.num_frames - 1) 28 | 29 | #OKE:DEBUG 30 | Debug = 1 31 | if Debug: 32 | res=core.std.Interleave([src16, res]) 33 | res=mvf.ToRGB(res,full=False,depth=8) 34 | else: res = mvf.Depth(res, 10) 35 | 36 | res.set_output() 37 | src16.set_output(1) 38 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/Track/Track.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace OKEGui.Model 8 | { 9 | public enum TrackType 10 | { 11 | Default, 12 | Audio, 13 | Subtitle, 14 | Video, 15 | Chapter, 16 | } 17 | 18 | public class Track : ICloneable 19 | { 20 | public OKEFile File; 21 | public Info Info; 22 | public TrackType TrackType { get; protected set; } = TrackType.Default; 23 | 24 | public Track(OKEFile file, Info info) 25 | { 26 | File = file; 27 | Info = info; 28 | } 29 | 30 | public virtual Object Clone() 31 | { 32 | Track clone = this.MemberwiseClone() as Track; 33 | HandleCloned(clone); 34 | return clone; 35 | } 36 | 37 | 38 | protected virtual void HandleCloned(Track clone) 39 | { 40 | if (Info != null) 41 | { 42 | Info = Info.Clone() as Info; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Task/EpisodeProfile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OKEGui.Task 5 | { 6 | public class EpisodeConfig : ICloneable 7 | { 8 | public List VspipeArgs = new List(); 9 | 10 | public object Clone() 11 | { 12 | EpisodeConfig clone = MemberwiseClone() as EpisodeConfig; 13 | if (VspipeArgs != null) 14 | { 15 | clone.VspipeArgs = new List(); 16 | foreach (string arg in VspipeArgs) 17 | { 18 | clone.VspipeArgs.Add(arg); 19 | } 20 | } 21 | return clone; 22 | } 23 | 24 | public override string ToString() 25 | { 26 | string str = "EpisodeConfig{"; 27 | str += "VspipeArgs: "; 28 | if (VspipeArgs == null) 29 | { 30 | str += "null"; 31 | } 32 | else 33 | { 34 | str += "[" + string.Join(",", VspipeArgs) + "]"; 35 | } 36 | str += "]"; 37 | return str; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/IJobProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace OKEGui 2 | { 3 | public delegate void JobProcessingStatusUpdateCallback(StatusUpdate su); 4 | 5 | /// 6 | /// 任务处理。可执行单元 7 | /// 8 | public interface IJobProcessor 9 | { 10 | /// 11 | /// starts the encoding process 12 | /// 13 | void start(); 14 | 15 | /// 16 | /// stops the encoding process 17 | /// 18 | void stop(); 19 | 20 | /// 21 | /// pauses the encoding process 22 | /// 23 | void pause(); 24 | 25 | /// 26 | /// resumes the encoding process 27 | /// 28 | void resume(); 29 | 30 | /// 31 | /// wait until job is finished 32 | /// 33 | void waitForFinish(); 34 | 35 | /// 36 | /// changes the priority of the encoding process/thread 37 | /// 38 | void changePriority(ProcessPriority priority); 39 | 40 | event JobProcessingStatusUpdateCallback StatusUpdate; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dist/windows/examples/vfr.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version" : 3, 3 | "VSVersion" : "2023H1b1", 4 | "ProjectName" : "VFR Demo - 1080p", 5 | "EncoderType" : "x265", 6 | "EncoderParam" : "-D 10 --deblock -1:-1 --preset slower --limit-tu 4 --no-strong-intra-smoothing --ctu 32 --crf 16 --qg-size 8 --pbratio 1.2 --cbqpoffs -2 --crqpoffs -2 --no-sao --me 3 --subme 5 --merange 38 --b-intra --no-amp --ref 4 --weightb --keyint 360 --min-keyint 1 --bframes 6 --aq-mode 1 --aq-strength 0.7 --rd 5 --psy-rd 1.5 --psy-rdoq 0.8 --rdoq-level 2 --no-open-gop --rc-lookahead 80 --scenecut 40 --qcomp 0.65 --vbv-bufsize 40000 --vbv-maxrate 30000 --colormatrix bt709 --range limited", 7 | "ContainerFormat" : "mkv", 8 | "AudioTracks" : [{ 9 | "OutputCodec" : "flac" 10 | },{ 11 | "OutputCodec" : "aac", 12 | "Bitrate" : 192, 13 | "Name": "Commentary", 14 | "Language" : "eng", 15 | "Optional": true 16 | }], 17 | "InputScript" : "vfr.vpy", 18 | "InputFiles" : [ 19 | "Main_Disc\\BDMV\\STREAM\\00000.m2ts", 20 | "Main_Disc\\BDMV\\STREAM\\00001.m2ts", 21 | "Main_Disc\\BDMV\\STREAM\\00002.m2ts" 22 | ], 23 | "TimeCode": true, 24 | "Rpc" : true 25 | } 26 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Task/TaskDetail.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using OKEGui.Model; 4 | using OKEGui.Worker; 5 | 6 | namespace OKEGui 7 | { 8 | /// 9 | /// 在TaskStatus基础上,继续定义无需显示的域,来整体构成一个Task所需的所有数据和函数。 10 | /// 11 | public class TaskDetail : TaskStatus 12 | { 13 | // Task信息。从Json中读入。(见WizardWindow) 14 | public TaskProfile Taskfile; 15 | // Task所分解成的Job队列。 16 | public Queue JobQueue = new Queue(); 17 | 18 | public string ChapterFileName; 19 | public string ChapterLanguage; 20 | 21 | // 输出文件轨道。MediaOutFile是主文件(mp4/mkv), MkaOutFile是外挂mka 22 | public MediaFile MediaOutFile; 23 | public MediaFile MkaOutFile; 24 | 25 | public string Tid; 26 | public long LengthInMiliSec; 27 | 28 | // 自动生成输出文件名 29 | public void UpdateOutputFileName() 30 | { 31 | var finfo = new System.IO.FileInfo(InputFile); 32 | if (Taskfile.ContainerFormat != "") 33 | { 34 | OutputFile = finfo.Name + "." + Taskfile.ContainerFormat.ToLower(); 35 | } 36 | else 37 | { 38 | OutputFile = finfo.Name + "." + Taskfile.VideoFormat.ToLower(); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dist/windows/examples/demo_720p.vpy: -------------------------------------------------------------------------------- 1 | import vapoursynth as vs 2 | import sys 3 | from vapoursynth import core 4 | import havsfunc as haf 5 | import mvsfunc as mvf 6 | 7 | core.num_threads = 12 8 | 9 | #OKE:MEMORY 10 | core.max_cache_size = 9000 11 | 12 | #OKE:INPUTFILE 13 | a="00001.m2ts" 14 | src8 = core.lsmas.LWLibavSource(a) 15 | src16 = core.fmtc.bitdepth(src8,bits=16) 16 | 17 | gray = core.std.ShufflePlanes(src16, 0, colorfamily=vs.GRAY) 18 | gray = core.fmtc.transfer(gray,transs="709",transd="linear") 19 | gray = core.fmtc.resample(gray,1280,720) 20 | gray = core.fmtc.transfer(gray,transs="linear",transd="709") 21 | UV = core.fmtc.resample(src16,1280,720) 22 | down = core.std.ShufflePlanes([gray,UV],[0,1,2], vs.YUV) 23 | 24 | nr16 = core.knlm.KNLMeansCL(down,device_type="GPU",h=0.6,s=3,d=1,a=2,channels="Y") 25 | noise16 = core.std.MakeDiff(down,nr16,0) 26 | dbed = core.f3kdb.Deband(nr16, 8,48,48,48,0,0,output_depth=16) 27 | dbed = core.f3kdb.Deband(dbed,16,32,32,32,0,0,output_depth=16) 28 | dbed = mvf.LimitFilter(dbed,nr16,thr=0.5,thrc=0.4,elast=1.5) 29 | dbed = core.std.MergeDiff(dbed,noise16,0) 30 | 31 | 32 | bright = mvf.Depth(dbed,8,dither=1) 33 | dark = mvf.Depth(dbed,8,dither=0,ampo=1.5) 34 | res = core.std.MaskedMerge(dark, bright, core.std.Binarize(bright, 128, planes=0), first_plane=True) 35 | 36 | res.set_output(0) 37 | src8.set_output(1) 38 | -------------------------------------------------------------------------------- /dist/windows/README.md: -------------------------------------------------------------------------------- 1 | PACKAGERS: 2 | 3 | You will need NSIS and upx to make the installer. You need a unicode version of NSIS. 4 | Make sure you install the NSIS with full installation mode, otherwise you would miss the built-in plugins required. 5 | 6 | 1. Open the options.nsi file in an editor and change line that contains 7 | "!define PROG_VERSION "6.5"" to the version of OKEGui you just built. 8 | 2. Extract the plugins found in the folder "nsis plugins" into your 9 | NSIS's unicode Plugin directory(usually C:\Program Files\NSIS\Plugins\x86-unicode). 10 | Only the *.dll files are needed. Use the unicode version of the dlls if there are multiple versions. 11 | 3. The script you need to compile is "okegui.nsi". It includes all other necessary scripts. 12 | 4. The script expects the following file tree: 13 | 14 | The installer script expects the following file tree: 15 | 16 | ``` 17 | Root: 18 | installer-translations\ 19 | english.nsi 20 | simpchinese.nsi 21 | ... 22 | (all the .nsi files found here in every source release) 23 | samples\ 24 | (all sample sciprt or config files found here) 25 | tools\ 26 | (all required tools found here) 27 | installer.nsi 28 | options.nsi 29 | okegui.nsi 30 | translations.nsi 31 | UAC.nsh 32 | uninstaller.nsi 33 | ``` 34 | 35 | 5. Make sure a relese build has been performed. 36 | 6. "`OKEGui_{VERSION}_setup.exe`" is the compiled binary file. 37 | -------------------------------------------------------------------------------- /dist/windows/examples/demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version" : 3, 3 | "VSVersion" : "20210901", 4 | "ProjectName" : "Demo - 1080p", 5 | "EncoderType" : "x265", 6 | "Encoder" : "x265-10b.exe", 7 | "EncoderParam" : "-D 10 --deblock -1:-1 --preset slower --limit-tu 4 --no-strong-intra-smoothing --ctu 32 --crf 16 --qg-size 8 --pbratio 1.2 --cbqpoffs -2 --crqpoffs -2 --no-sao --me 3 --subme 5 --merange 38 --b-intra --no-amp --ref 4 --weightb --keyint 360 --min-keyint 1 --bframes 6 --aq-mode 1 --aq-strength 0.7 --rd 5 --psy-rd 1.5 --psy-rdoq 0.8 --rdoq-level 2 --no-open-gop --rc-lookahead 80 --scenecut 40 --qcomp 0.65 --vbv-bufsize 40000 --vbv-maxrate 30000 --colormatrix bt709 --range limited", 8 | "ContainerFormat" : "mkv", 9 | "AudioTracks" : [{ 10 | "OutputCodec" : "flac" 11 | },{ 12 | "OutputCodec" : "aac", 13 | "Bitrate" : 192, 14 | "Name": "Commentary", 15 | "Language" : "eng", 16 | "Optional": true 17 | }], 18 | "InputScript" : "demo.vpy", 19 | "Fps" : 23.976, 20 | "SubtitleTracks" : [{ 21 | "Language" : "jpn" 22 | }], 23 | "InputFiles" : [ 24 | "Main_Disc\\BDMV\\STREAM\\00000.m2ts", 25 | "Main_Disc\\BDMV\\STREAM\\00001.m2ts", 26 | "Main_Disc\\BDMV\\STREAM\\00002.m2ts", 27 | ], 28 | "Config" : { 29 | "VspipeArgs" : [ 30 | "op_start=10000", 31 | "op_end=15000" 32 | ] 33 | }, 34 | "Rpc" : true 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [OKEGui](https://github.com/vcb-s/OKEGui/) · [![GitHub license](https://img.shields.io/badge/license-GPLv2-blue.svg)](https://github.com/vcb-s/OKEGui/blob/master/LICENSE) [![Build status](https://ci.appveyor.com/api/projects/status/p4p7upa6hmsgu599?svg=true&passingText=%E7%BC%96%E8%AF%91%20-%20%E7%A8%B3%20&pendingText=%E5%B0%8F%E5%9C%9F%E8%B1%86%E7%82%B8%E4%BA%86%20&failingText=%E6%88%91%E6%84%9F%E8%A7%89%E5%8D%9C%E8%A1%8C%20)](https://ci.appveyor.com/project/vcb-s/okegui) 2 | 3 | 4 | ![alt text](http://www.ajivin.com/images/spsimpleportfolio/site-clearing/portfolio6_600x400.jpg) 5 | 6 | ## 安装 7 | 8 | 1. OKEGui 需要.NET 4.5。Windows 8/Windows Server 2012及以上自带;Windows 7和Windows Server 2008需要自行安装: https://www.microsoft.com/zh-cn/download/details.aspx?id=30653 9 | 10 | 2. OKEGui 自带的 qaac 工具依赖 Apple Quicktime. 这点请确保你的机器按照压制组需要正确安装了64bit iTunes 组件或者 AppleApplicationSupport: https://github.com/vcb-s/OKEGui/releases/download/4.0/AppleApplicationSupport64.msi 11 | 12 | 3. 下载最新 Release 的 zip 压缩包,解压到一个纯英文目录下。双击其中 OKEGui.exe,如果能正确运行显示出窗口,即安装成功。 13 | 14 | ## 代码中相关概念解释: 15 | 16 | Task: 从单个源(例如m2ts)到成品(例如mkv)的整个过程。task会在主程序界面的列表里显示。 17 | 18 | Job: 每个Task会被分解成不同的Job,并依次执行。例如抽流,压制,封装等。Job是可以独立运行的最低单位。 19 | 20 | JobProcessror: 负责执行每个Job的命令行Warpper。比如X265Encoder调用x265压制HEVC,FFMpegVolumeChecker调用ffmpeg检查音轨音量 21 | 22 | Model: 储存媒体文件相关的信息。Info只带例如语言、封装选项等信息,Track则是File+Info的组合,MediaFile则是多条Track的合集 23 | 24 | Worker: 每一个Task只会在一个Worker里进行,因此有几个Worker就允许几个Task同时进行。多开相关的选项。每个Task具体的实现流程由Worker负责执行。 25 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/WmiUtils.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using System; 3 | using System.Management; 4 | 5 | namespace OKEGui.Utils 6 | { 7 | static class WmiUtils 8 | { 9 | private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); 10 | public static int GetTotalPhysicalMemory() 11 | { 12 | long capacity = 0; 13 | try 14 | { 15 | foreach (ManagementObject mo1 in new ManagementClass("Win32_PhysicalMemory").GetInstances()) 16 | capacity += long.Parse(mo1.Properties["Capacity"].Value.ToString()); 17 | } 18 | catch (Exception ex) 19 | { 20 | capacity = -1; 21 | Logger.Error(ex, "Failed to get total physical memory"); 22 | } 23 | return (int)(capacity / 1024.0 / 1024); 24 | } 25 | 26 | public static int GetAvailablePhysicalMemory() 27 | { 28 | int capacity = 0; 29 | try 30 | { 31 | foreach (ManagementObject mo1 in new ManagementClass("Win32_OperatingSystem").GetInstances()) 32 | capacity += int.Parse(mo1.Properties["FreePhysicalMemory"].Value.ToString()) / 1024; 33 | } 34 | catch (Exception ex) 35 | { 36 | capacity = -1; 37 | Logger.Error(ex, "Failed to get available physical memory"); 38 | } 39 | return capacity; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /OKEGui/OKEGui/Job/RpcJob/RpcJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using static OKEGui.RpChecker; 8 | 9 | namespace OKEGui 10 | { 11 | public class RpcJob : Job 12 | { 13 | public readonly string RippedFile; 14 | public readonly ulong TotalFrame; 15 | public readonly Dictionary Args = new Dictionary(); 16 | public readonly string FailedRPCOutputFile; 17 | public RpcStatus RpcStatus 18 | { 19 | set 20 | { 21 | if (ts != null) 22 | { 23 | ts.RpcStatus = value.ToString(); 24 | } 25 | } 26 | } 27 | public RpcJob(string sourceFile, VideoJob videoJob, string outputPath) 28 | { 29 | Input = sourceFile; 30 | Output = Path.ChangeExtension(sourceFile, "rpc"); 31 | RippedFile = videoJob.Output; 32 | FailedRPCOutputFile = outputPath + ".rpc"; 33 | TotalFrame = videoJob.NumberOfFrames; 34 | foreach (string arg in videoJob.VspipeArgs) 35 | { 36 | int pos = arg.IndexOf('='); 37 | string variable = arg.Substring(0, pos); 38 | string value = arg.Substring(pos + 1); 39 | Args[variable] = value; 40 | } 41 | } 42 | public override JobType GetJobType() 43 | { 44 | return JobType.RpCheck; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/RegistryStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Win32; 7 | 8 | namespace OKEGui.Utils 9 | { 10 | class RegistryStorage 11 | { 12 | public static string DefaultSubKey => $@"Software\OKEGui"; 13 | 14 | public static string Load(string subKey = null, string name = "") 15 | { 16 | if (subKey == null) subKey = DefaultSubKey; 17 | var path = string.Empty; 18 | // HKCU_CURRENT_USER\Software\ 19 | var registryKey = Registry.CurrentUser.OpenSubKey(subKey); 20 | if (registryKey == null) return path; 21 | path = (string)registryKey.GetValue(name); 22 | registryKey.Close(); 23 | return path; 24 | } 25 | 26 | public static void Save(string value, string subKey = null, string name = "") 27 | { 28 | if (subKey == null) subKey = DefaultSubKey; 29 | // HKCU_CURRENT_USER\Software\ 30 | var registryKey = Registry.CurrentUser.CreateSubKey(subKey); 31 | registryKey?.SetValue(name, value); 32 | registryKey?.Close(); 33 | } 34 | 35 | public static int RegistryAddCount(string subKey, string name, int delta = 1) 36 | { 37 | var countS = Load(subKey, name); 38 | var count = string.IsNullOrEmpty(countS) ? 0 : int.Parse(countS); 39 | count += delta; 40 | Save(count.ToString(), subKey, name); 41 | return count - delta; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("OKEGui")] 9 | [assembly: AssemblyDescription("The Protagonist Returns")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("OKEGui")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | //若要开始生成可本地化的应用程序,请 23 | // 中的 .csproj 文件中 24 | //例如,如果您在源文件中使用的是美国英语, 25 | //使用的是美国英语,请将 设置为 en-US。 然后取消 26 | //对以下 NeutralResourceLanguage 特性的注释。 更新 27 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 28 | 29 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 30 | 31 | [assembly: ThemeInfo( 32 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置 33 | //(当资源未在页面 34 | //或应用程序资源字典中找到时使用) 35 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 36 | //(当资源未在页面 37 | //、应用程序或任何主题专用资源字典中找到时使用) 38 | )] 39 | 40 | // 程序集的版本信息由下列四个值组成: 41 | // 42 | // 主版本 43 | // 次版本 44 | // 生成号 45 | // 修订号 46 | // 47 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 48 | // 方法是按如下所示使用“*”: : 49 | // [assembly: AssemblyVersion("1.0.*")] 50 | [assembly: AssemblyVersion("8.6.1.*")] 51 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using OKEGui.Utils; 2 | using System; 3 | using System.Reflection; 4 | using System.Windows; 5 | 6 | namespace OKEGui 7 | { 8 | /// 9 | /// 程序接入点。使用静态构造器来执行程序开始前的检查和其他任务。 10 | /// 主界面的设计和逻辑请见 Gui/MainWindow 11 | /// 12 | public partial class App : Application 13 | { 14 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("App"); 15 | 16 | private void AppStartup(object sender, StartupEventArgs e) 17 | { 18 | AppDomain.CurrentDomain.AssemblyResolve += (sender_, args) => 19 | { 20 | AssemblyName assemblyName = new AssemblyName(args.Name); 21 | var path = assemblyName.Name + ".dll"; 22 | 23 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)) 24 | { 25 | if (stream == null) return null; 26 | 27 | var assemblyRawBytes = new byte[stream.Length]; 28 | stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length); 29 | return Assembly.Load(assemblyRawBytes); 30 | } 31 | }; 32 | if (EnvironmentChecker.CheckEnviornment()) 33 | { 34 | System.Windows.Media.RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly; 35 | Initializer.ConfigLogger(); 36 | Initializer.WriteConfig(); 37 | Initializer.ClearOldLogs(); 38 | Logger.Info("程序正常启动"); 39 | } 40 | else 41 | { 42 | Environment.Exit(0); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Worker/NumaNode.cs: -------------------------------------------------------------------------------- 1 | using OKEGui.Utils; 2 | using System; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace OKEGui.Worker 7 | { 8 | static class NumaNode 9 | { 10 | [DllImport("Kernel32.dll")] 11 | [return: MarshalAsAttribute(UnmanagedType.Bool)] 12 | public static extern bool GetNumaHighestNodeNumber([Out] out uint HighestNodeNumber); 13 | 14 | public static readonly int NumaCount; 15 | public static readonly int UsableCoreCount; 16 | static int CurrentNuma; 17 | 18 | static NumaNode() 19 | { 20 | if (Initializer.Config.singleNuma) 21 | { 22 | CurrentNuma = 0; 23 | NumaCount = 1; 24 | } 25 | else 26 | { 27 | GetNumaHighestNodeNumber(out uint temp); 28 | CurrentNuma = (int)temp; 29 | NumaCount = CurrentNuma + 1; 30 | } 31 | UsableCoreCount = Environment.ProcessorCount; 32 | } 33 | 34 | public static int NextNuma() 35 | { 36 | int res = CurrentNuma; 37 | CurrentNuma = (CurrentNuma - 1 + NumaCount) % NumaCount; 38 | return res; 39 | } 40 | 41 | public static int PrevNuma() 42 | { 43 | int res = CurrentNuma; 44 | CurrentNuma = (CurrentNuma + 1) % NumaCount; 45 | return res; 46 | } 47 | 48 | public static string X265PoolsParam(int currentNuma) 49 | { 50 | string[] res = Enumerable.Repeat("-", NumaCount).ToArray(); 51 | res[currentNuma] = "+"; 52 | return string.Join(",", res); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/Audio/FFmpegVolumeChecker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | using OKEGui.Utils; 8 | 9 | namespace OKEGui 10 | { 11 | class FFmpegVolumeChecker : CommandlineJobProcessor 12 | { 13 | public double MeanVolume { get; private set; } 14 | public double MaxVolume { get; private set; } 15 | 16 | private Regex rmsLevelRegex = new Regex(@"RMS level dB: (-?(?:\d+\.\d+|inf))"); 17 | private Regex peakLevelRegex = new Regex(@"Peak level dB: (-?(?:\d+\.\d+|inf))"); 18 | 19 | public FFmpegVolumeChecker(string inputFile) 20 | { 21 | executable = Constants.ffmpegPath; 22 | commandLine = $"-i \"{inputFile}\" -af astats=measure_perchannel=none -f null /dev/null"; 23 | } 24 | 25 | public override void ProcessLine(string line, StreamType stream) 26 | { 27 | base.ProcessLine(line, stream); 28 | 29 | var rmsLevel = rmsLevelRegex.Match(line); 30 | if (rmsLevel.Success) 31 | { 32 | var success = double.TryParse(rmsLevel.Groups[1].Value, out double meanVolume); 33 | MeanVolume = success ? meanVolume : double.NegativeInfinity; 34 | return; 35 | } 36 | 37 | var peakLevel = peakLevelRegex.Match(line); 38 | if (peakLevel.Success) 39 | { 40 | var success = double.TryParse(peakLevel.Groups[1].Value, out double maxVolume); 41 | MaxVolume = success ? maxVolume : double.NegativeInfinity; 42 | SetFinish(); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dist/windows/uninstaller.nsi: -------------------------------------------------------------------------------- 1 | Section "un.$(remove_files_str)" ;"un.Remove files" 2 | SectionIn RO 3 | 4 | ; Remove files and uninstaller 5 | Delete "$INSTDIR\*.dll" 6 | Delete "$INSTDIR\*.xml" 7 | Delete "$INSTDIR\OKEGui.exe" 8 | Delete "$INSTDIR\LICENSE" 9 | Delete "$INSTDIR\uninst.exe" 10 | RMDIr /r "$INSTDIR\examples" 11 | RMDIr /r "$INSTDIR\x86" 12 | RMDIr /r "$INSTDIR\x64" 13 | 14 | ; Remove directories used 15 | RMDir "$INSTDIR" 16 | SectionEnd 17 | 18 | 19 | Section /o "un.$(remove_config_str)" ;"un.Remove config file" 20 | Delete "$INSTDIR\OKEGuiConfig.json" 21 | RMDir "$INSTDIR" 22 | SectionEnd 23 | 24 | 25 | Section /o "un.$(remove_logs_str)" ;"un.Remove log files" 26 | RMDIr /r "$INSTDIR\log" 27 | RMDir "$INSTDIR" 28 | SectionEnd 29 | 30 | 31 | Section /o "un.$(remove_tools_str)" ;"un.Remove external tools" 32 | RMDIr /r "$INSTDIR\tools" 33 | RMDir "$INSTDIR" 34 | SectionEnd 35 | 36 | 37 | Section "un.$(remove_shortcuts_str)" ;"un.Remove shortcuts" 38 | SectionIn RO 39 | ; Remove shortcuts, if any 40 | RMDir /r "$SMPROGRAMS\OKEGui" 41 | Delete "$DESKTOP\OKEGui.lnk" 42 | SectionEnd 43 | 44 | 45 | Section "un.$(remove_registry_str)" ;"un.Remove registry keys" 46 | SectionIn RO 47 | ; Remove registry keys 48 | DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" 49 | DeleteRegKey HKLM "Software\OKEGui" 50 | DeleteRegKey HKLM "Software\Classes\OKEGui" 51 | 52 | System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)' 53 | SectionEnd 54 | 55 | 56 | ;-------------------------------- 57 | ;Uninstaller Functions 58 | 59 | Function un.onInit 60 | 61 | !insertmacro Init "uninstaller" 62 | !insertmacro MUI_UNGETLANGUAGE 63 | 64 | FunctionEnd 65 | 66 | Function un.onUninstSuccess 67 | SetErrorLevel 0 68 | FunctionEnd 69 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/Info/Info.cs: -------------------------------------------------------------------------------- 1 | using OKEGui.Utils; 2 | using System; 3 | 4 | namespace OKEGui.Model 5 | { 6 | public enum MuxOption 7 | { 8 | Default, 9 | Mka, 10 | External, 11 | ExtractOnly, 12 | Skip 13 | } 14 | 15 | public enum InfoType 16 | { 17 | Default, 18 | Video, 19 | Audio 20 | } 21 | 22 | public class Info : ICloneable 23 | { 24 | // base class of all info object 25 | public InfoType InfoType { get; protected set; } = InfoType.Default; 26 | public MuxOption MuxOption = MuxOption.Default; 27 | public string Language = Constants.language; 28 | public string Name = ""; 29 | public bool Optional = false; 30 | public int Order = Int32.MaxValue; 31 | private bool _dupOrEmpty; 32 | public bool DupOrEmpty 33 | { 34 | get { return _dupOrEmpty; } 35 | set 36 | { 37 | _dupOrEmpty = value; 38 | if (value) 39 | { 40 | switch (MuxOption) 41 | { 42 | case MuxOption.Default: 43 | case MuxOption.Mka: 44 | case MuxOption.External: 45 | MuxOption = MuxOption.ExtractOnly; 46 | break; 47 | default: 48 | break; 49 | } 50 | } 51 | } 52 | } 53 | 54 | public virtual Object Clone() 55 | { 56 | Info clone = this.MemberwiseClone() as Info; 57 | HandleCloned(clone); 58 | return clone; 59 | } 60 | 61 | 62 | protected virtual void HandleCloned(Info clone) 63 | { 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /dist/windows/installer-translations/simpchinese.nsi: -------------------------------------------------------------------------------- 1 | ;Installer strings 2 | 3 | ;LangString inst_req_str ${LANG_ENGLISH} "OKEGui (required)" 4 | LangString inst_req_str ${LANG_SIMPCHINESE} "OKEGui (必要)" 5 | ;LangString inst_tools_str ${LANG_ENGLISH} "External tools (recommend)" 6 | LangString inst_tools_str ${LANG_SIMPCHINESE} "外部工具 (推荐)" 7 | ;LangString inst_samples_str ${LANG_ENGLISH} "Example files (optional)" 8 | LangString inst_samples_str ${LANG_SIMPCHINESE} "示例文件 (可选)" 9 | ;LangString inst_dekstop_str ${LANG_ENGLISH} "Create Desktop Shortcut" 10 | LangString inst_dekstop_str ${LANG_SIMPCHINESE} "创建桌面快捷方式" 11 | ;LangString inst_startmenu_str ${LANG_ENGLISH} "Create Start Menu Shortcut" 12 | LangString inst_startmenu_str ${LANG_SIMPCHINESE} "创建开始菜单快捷方式" 13 | ;LangString inst_uninstall_question_str ${LANG_ENGLISH} "A previous installation was detected. It will be uninstalled without deleting user settings." 14 | LangString inst_uninstall_question_str ${LANG_SIMPCHINESE} "检测到以前的安装。 它将被卸载但不删除用户设置。" 15 | ;LangString inst_unist_str ${LANG_ENGLISH} "Uninstalling previous version." 16 | LangString inst_unist_str ${LANG_SIMPCHINESE} "卸载以前的版本。" 17 | ;LangString launch_str ${LANG_ENGLISH} "Launch OKEGui." 18 | LangString launch_str ${LANG_SIMPCHINESE} "启动 OKEGui." 19 | 20 | 21 | ;------------------------------------ 22 | ;Uninstaller strings 23 | 24 | ;LangString remove_files_str ${LANG_ENGLISH} "Remove files" 25 | LangString remove_files_str ${LANG_SIMPCHINESE} "删除文件" 26 | ;LangString remove_shortcuts_str ${LANG_ENGLISH} "Remove shortcuts" 27 | LangString remove_shortcuts_str ${LANG_SIMPCHINESE} "删除快捷方式" 28 | ;LangString remove_registry_str ${LANG_ENGLISH} "Remove registry keys" 29 | LangString remove_registry_str ${LANG_SIMPCHINESE} "删除注册表键" 30 | ;LangString remove_config_str ${LANG_ENGLISH} "Remove config file" 31 | LangString remove_config_str ${LANG_SIMPCHINESE} "移除配置文件" 32 | ;LangString remove_tools_str ${LANG_ENGLISH} "Remove external tools" 33 | LangString remove_tools_str ${LANG_SIMPCHINESE} "移除外部工具" 34 | ;LangString remove_logs_str ${LANG_ENGLISH} "Remove log files" 35 | LangString remove_logs_str ${LANG_SIMPCHINESE} "删除日志" 36 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Gui/ConfigPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using OKEGui.Utils; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Controls; 10 | using System.Windows.Data; 11 | using System.Windows.Documents; 12 | using System.Windows.Input; 13 | using System.Windows.Media; 14 | using System.Windows.Media.Imaging; 15 | using System.Windows.Shapes; 16 | 17 | namespace OKEGui 18 | { 19 | /// 20 | /// Config.xaml 的交互逻辑 21 | /// 22 | public partial class ConfigPanel : Window 23 | { 24 | public OKEGuiConfig Config { get; } 25 | 26 | private void Vspipe_Click(object sender, RoutedEventArgs e) 27 | { 28 | OpenFileDialog ofd = new OpenFileDialog 29 | { 30 | Multiselect = false, 31 | Filter = "vspipe.exe (vspipe.exe)|vspipe.exe", 32 | InitialDirectory = Config.vspipePath 33 | }; 34 | bool result = ofd.ShowDialog().GetValueOrDefault(false); 35 | if (result) 36 | { 37 | Config.vspipePath = ofd.FileName; 38 | } 39 | } 40 | 41 | private void RPChecker_Click(object sender, RoutedEventArgs e) 42 | { 43 | OpenFileDialog ofd = new OpenFileDialog 44 | { 45 | Multiselect = false, 46 | Filter = "RPChecker.exe (RPChecker*.exe)|RPChecker*.exe", 47 | InitialDirectory = Config.rpCheckerPath 48 | }; 49 | bool result = ofd.ShowDialog().GetValueOrDefault(false); 50 | if (result) 51 | { 52 | Config.rpCheckerPath = ofd.FileName; 53 | } 54 | } 55 | 56 | private void Save_Click(object sender, RoutedEventArgs e) 57 | { 58 | Initializer.Config = Config; 59 | Initializer.WriteConfig(); 60 | Close(); 61 | } 62 | 63 | public ConfigPanel() 64 | { 65 | Config = Initializer.Config.Clone() as OKEGuiConfig; 66 | InitializeComponent(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /dist/windows/installer-translations/english.nsi: -------------------------------------------------------------------------------- 1 | ;Installer strings 2 | 3 | ;LangString inst_req_str ${LANG_ENGLISH} "OKEGui (required)" 4 | LangString inst_req_str ${LANG_ENGLISH} "OKEGui (required)" 5 | ;LangString inst_tools_str ${LANG_ENGLISH} "External tools (recommend)" 6 | LangString inst_tools_str ${LANG_ENGLISH} "External tools (recommend)" 7 | ;LangString inst_samples_str ${LANG_ENGLISH} "Example files (optional)" 8 | LangString inst_samples_str ${LANG_ENGLISH} "Example files (optional)" 9 | ;LangString inst_dekstop_str ${LANG_ENGLISH} "Create Desktop Shortcut" 10 | LangString inst_dekstop_str ${LANG_ENGLISH} "Create Desktop Shortcut" 11 | ;LangString inst_startmenu_str ${LANG_ENGLISH} "Create Start Menu Shortcut" 12 | LangString inst_startmenu_str ${LANG_ENGLISH} "Create Start Menu Shortcut" 13 | ;LangString inst_uninstall_question_str ${LANG_ENGLISH} "A previous installation was detected. It will be uninstalled without deleting user settings." 14 | LangString inst_uninstall_question_str ${LANG_ENGLISH} "A previous installation was detected. It will be uninstalled without deleting user settings." 15 | ;LangString inst_unist_str ${LANG_ENGLISH} "Uninstalling previous version." 16 | LangString inst_unist_str ${LANG_ENGLISH} "Uninstalling previous version." 17 | ;LangString launch_str ${LANG_ENGLISH} "Launch OKEGui." 18 | LangString launch_str ${LANG_ENGLISH} "Launch OKEGui." 19 | 20 | 21 | ;------------------------------------ 22 | ;Uninstaller strings 23 | 24 | ;LangString remove_files_str ${LANG_ENGLISH} "Remove files" 25 | LangString remove_files_str ${LANG_ENGLISH} "Remove files" 26 | ;LangString remove_shortcuts_str ${LANG_ENGLISH} "Remove shortcuts" 27 | LangString remove_shortcuts_str ${LANG_ENGLISH} "Remove shortcuts" 28 | ;LangString remove_registry_str ${LANG_ENGLISH} "Remove registry keys" 29 | LangString remove_registry_str ${LANG_ENGLISH} "Remove registry keys" 30 | ;LangString remove_config_str ${LANG_ENGLISH} "Remove config file" 31 | LangString remove_config_str ${LANG_ENGLISH} "Remove config file" 32 | ;LangString remove_tools_str ${LANG_ENGLISH} "Remove external tools" 33 | LangString remove_tools_str ${LANG_ENGLISH} "Remove external tools" 34 | ;LangString remove_logs_str ${LANG_ENGLISH} "Remove log files" 35 | LangString remove_logs_str ${LANG_ENGLISH} "Remove log files" 36 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/Audio/QAACEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Text.RegularExpressions; 5 | using System.Threading; 6 | using OKEGui.Utils; 7 | using OKEGui.JobProcessor; 8 | 9 | namespace OKEGui 10 | { 11 | internal class QAACEncoder : CommandlineJobProcessor 12 | { 13 | private ManualResetEvent retrieved = new ManualResetEvent(false); 14 | private Action _progressCallback; 15 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("QAACEncoder"); 16 | 17 | // TODO: 变更编码参数 18 | public QAACEncoder(AudioJob j, Action progressCallback, int bitrate = Constants.QAACBitrate, int quality = 0) : base() 19 | { 20 | _progressCallback = progressCallback; 21 | if (j.Input != "-") 22 | { //not from stdin, but an actual file 23 | j.Input = $"\"{j.Input}\""; 24 | } 25 | 26 | executable = Constants.QAACPath; 27 | if (quality > 0) 28 | { 29 | commandLine = $"-V {quality}"; 30 | } 31 | else 32 | { 33 | commandLine = $"-v {bitrate}"; 34 | } 35 | commandLine += $" -i -q 2 --no-delay -o \"{j.Output}\" {j.Input}"; 36 | } 37 | 38 | public QAACEncoder(string commandLine) 39 | { 40 | this.executable = Constants.QAACPath; 41 | this.commandLine = commandLine; 42 | } 43 | 44 | public override void ProcessLine(string line, StreamType stream) 45 | { 46 | Regex rAnalyze = new Regex("\\[([0-9.]+)%\\]"); 47 | double p = 0; 48 | if (rAnalyze.IsMatch(line)) 49 | { 50 | double.TryParse(rAnalyze.Split(line)[1], out p); 51 | if (p > 1) 52 | { 53 | _progressCallback(p); 54 | } 55 | } 56 | else 57 | { 58 | Logger.Debug(line); 59 | if (line.Contains(".done")) 60 | { 61 | SetFinish(); 62 | } 63 | if (line.Contains("ERROR")) 64 | { 65 | throw new OKETaskException(Constants.qaacErrorSmr); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-ci.yml: -------------------------------------------------------------------------------- 1 | # For more information on GitHub Actions, refer to https://github.com/features/actions 2 | # For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications, 3 | # refer to https://github.com/microsoft/github-actions-for-desktop-apps 4 | 5 | name: .NET Desktop 6 | 7 | on: 8 | push: 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | 14 | build: 15 | 16 | strategy: 17 | matrix: 18 | configuration: [Release] 19 | targetplatform: [x64] 20 | 21 | runs-on: windows-2019 # For a list of available runner types, refer to 22 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on 23 | 24 | env: 25 | Solution_Name: OKEGui\OKEGui.sln # Replace with your solution name, i.e. MyWpfApp.sln. 26 | 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | with: 31 | submodules: true 32 | 33 | # Install the .NET workload 34 | - name: Install .NET 35 | uses: actions/setup-dotnet@v3 36 | with: 37 | dotnet-version: 5.0.x 38 | 39 | # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild 40 | - name: Setup MSBuild.exe 41 | uses: microsoft/setup-msbuild@v1.1 42 | 43 | - uses: nuget/setup-nuget@v1 44 | with: 45 | nuget-version: '5.x' 46 | 47 | - name: Restore Nuget packages 48 | run: nuget restore $env:Solution_Name 49 | 50 | # Restore the application to populate the obj folder with RuntimeIdentifiers 51 | - name: Restore the application 52 | run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration 53 | env: 54 | Configuration: ${{ matrix.configuration }} 55 | 56 | # Build the Application project 57 | - name: Build the Application Project 58 | run: msbuild $env:Solution_Name /p:Platform=$env:TargetPlatform /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:BuildMode /p:AppxBundle=$env:AppxBundle 59 | env: 60 | AppxBundle: Never 61 | BuildMode: SideloadOnly 62 | Configuration: ${{ matrix.configuration }} 63 | TargetPlatform: ${{ matrix.targetplatform }} 64 | 65 | # Upload the package: https://github.com/actions/upload-artifact 66 | - name: Upload build artifacts 67 | uses: actions/upload-artifact@v3 68 | with: 69 | name: Package-${{ matrix.configuration }} 70 | path: | 71 | OKEGui\OKEGui\bin\${{ matrix.configuration }}\ 72 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace OKEGui.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// 一个强类型的资源类,用于查找本地化的字符串等。 17 | /// 18 | // 此类是由 StronglyTypedResourceBuilder 19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen 21 | // (以 /str 作为命令选项),或重新生成 VS 项目。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// 返回此类使用的缓存的 ResourceManager 实例。 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OKEGui.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// 重写当前线程的 CurrentUICulture 属性 51 | /// 重写当前线程的 CurrentUICulture 属性。 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/SafeProxy.cs: -------------------------------------------------------------------------------- 1 | /* This is .NET safe implementation of Crc32 algorithm. 2 | * This implementation has been found fastest from some variants, based on Robert Vazan native implementations of Crc32C 3 | * Also, it is good for x64 and for x86, so, it seems, there is no sense to do 2 different realizations. 4 | * 5 | * This algorithm is fast fork of Crc32C algorithm 6 | * 7 | * Max Vysokikh, 2016 8 | */ 9 | 10 | namespace OKEGui.Utils 11 | { 12 | internal class SafeProxy 13 | { 14 | private const uint Poly = 0xedb88320u; 15 | 16 | private static readonly uint[] Table = new uint[16 * 256]; 17 | 18 | static SafeProxy() 19 | { 20 | for (uint i = 0; i < 256; i++) 21 | { 22 | var res = i; 23 | for (uint t = 0; t < 16; t++) 24 | { 25 | for (uint k = 0; k < 8; k++) res = (res & 1) == 1 ? Poly ^ (res >> 1) : (res >> 1); 26 | Table[(t * 256) + i] = res; 27 | } 28 | } 29 | } 30 | 31 | public static uint Append(uint crc, byte[] input, int offset, int length) 32 | { 33 | var crcLocal = uint.MaxValue ^ crc; 34 | 35 | var table = Table; 36 | while (length >= 16) 37 | { 38 | crcLocal = 39 | table[(15 * 256) + ((crcLocal ^ input[offset + 0]) & 0xff)] 40 | ^ table[(14 * 256) + (((crcLocal >> 8) ^ input[offset + 1]) & 0xff)] 41 | ^ table[(13 * 256) + (((crcLocal >> 16) ^ input[offset + 2]) & 0xff)] 42 | ^ table[(12 * 256) + (((crcLocal >> 24) ^ input[offset + 3]) & 0xff)] 43 | ^ table[(11 * 256) + input[offset + 4]] 44 | ^ table[(10 * 256) + input[offset + 5]] 45 | ^ table[(9 * 256) + input[offset + 6]] 46 | ^ table[(8 * 256) + input[offset + 7]] 47 | ^ table[(7 * 256) + input[offset + 8]] 48 | ^ table[(6 * 256) + input[offset + 9]] 49 | ^ table[(5 * 256) + input[offset + 10]] 50 | ^ table[(4 * 256) + input[offset + 11]] 51 | ^ table[(3 * 256) + input[offset + 12]] 52 | ^ table[(2 * 256) + input[offset + 13]] 53 | ^ table[(1 * 256) + input[offset + 14]] 54 | ^ table[(0 * 256) + input[offset + 15]]; 55 | offset += 16; 56 | length -= 16; 57 | } 58 | 59 | while (--length >= 0) 60 | crcLocal = table[(crcLocal ^ input[offset++]) & 0xff] ^ crcLocal >> 8; 61 | return crcLocal ^ uint.MaxValue; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /OKEGui/OKEGui/Task/TaskProfile.cs: -------------------------------------------------------------------------------- 1 | using OKEGui.Task; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | /// 6 | /// 记录一个Task所需要的所有信息。跟Json文件挂钩。 7 | /// 8 | namespace OKEGui.Model 9 | { 10 | public class TaskProfile : ICloneable 11 | { 12 | // 在json里会使用的参数 13 | public int Version; 14 | public string VSVersion; // v3+ 15 | public string ProjectName; 16 | public string EncoderType; 17 | public string Encoder; 18 | public string EncoderParam; 19 | public string ContainerFormat; 20 | public double Fps; 21 | public uint FpsNum; 22 | public uint FpsDen; 23 | public List AudioTracks; 24 | public string InputScript; 25 | public List SubtitleTracks; 26 | public List InputFiles; 27 | public EpisodeConfig Config; 28 | public bool Rpc; 29 | public bool TimeCode; 30 | public bool RenumberChapters; 31 | 32 | //后续任务中填写的参数 33 | public string VideoFormat; 34 | public string AudioFormat; 35 | 36 | public string WorkingPathPrefix; 37 | public string OutputPathPrefix; 38 | 39 | public Object Clone() 40 | { 41 | TaskProfile clone = MemberwiseClone() as TaskProfile; 42 | if (AudioTracks != null) 43 | { 44 | clone.AudioTracks = new List(); 45 | foreach (AudioInfo info in AudioTracks) 46 | { 47 | clone.AudioTracks.Add(info.Clone() as AudioInfo); 48 | } 49 | } 50 | if (SubtitleTracks != null) 51 | { 52 | clone.SubtitleTracks = new List(); 53 | foreach (Info info in SubtitleTracks) 54 | { 55 | clone.SubtitleTracks.Add(info.Clone() as Info); 56 | } 57 | } 58 | return clone; 59 | } 60 | 61 | public override string ToString() 62 | { 63 | string str = "项目名字: " + ProjectName; 64 | str += "\n\n编码器类型: " + EncoderType; 65 | str += "\n编码器路径: " + Encoder; 66 | str += "\n编码参数: " + EncoderParam.Substring(0, Math.Min(30, EncoderParam.Length - 1)) + "......"; 67 | str += "\n\n封装格式: " + ContainerFormat; 68 | str += "\n视频编码: " + VideoFormat; 69 | str += "\n视频帧率: " + (TimeCode ? "VFR" : string.Format("{0:0.000} fps", Fps)); 70 | str += "\n音频编码(主音轨): " + AudioFormat; 71 | if (RenumberChapters) 72 | str += "\n章节名重编号: YES"; 73 | str += "\n输入文件数量: " + InputFiles?.Count; 74 | 75 | return str; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | # Specify vpy files should be treated as Python source. 7 | *.vpy linguist-language=Python 8 | 9 | ############################################################################### 10 | # Set default behavior for command prompt diff. 11 | # 12 | # This is need for earlier builds of msysgit that does not have it on by 13 | # default for csharp files. 14 | # Note: This is only used by command line 15 | ############################################################################### 16 | #*.cs diff=csharp 17 | 18 | ############################################################################### 19 | # Set the merge driver for project and solution files 20 | # 21 | # Merging from the command prompt will add diff markers to the files if there 22 | # are conflicts (Merging from VS is not affected by the settings below, in VS 23 | # the diff markers are never inserted). Diff markers may cause the following 24 | # file extensions to fail to load in VS. An alternative would be to treat 25 | # these files as binary and thus will always conflict and require user 26 | # intervention with every merge. To do so, just uncomment the entries below 27 | ############################################################################### 28 | #*.sln merge=binary 29 | #*.csproj merge=binary 30 | #*.vbproj merge=binary 31 | #*.vcxproj merge=binary 32 | #*.vcproj merge=binary 33 | #*.dbproj merge=binary 34 | #*.fsproj merge=binary 35 | #*.lsproj merge=binary 36 | #*.wixproj merge=binary 37 | #*.modelproj merge=binary 38 | #*.sqlproj merge=binary 39 | #*.wwaproj merge=binary 40 | 41 | ############################################################################### 42 | # behavior for image files 43 | # 44 | # image files are treated as binary by default. 45 | ############################################################################### 46 | #*.jpg binary 47 | #*.png binary 48 | #*.gif binary 49 | 50 | ############################################################################### 51 | # diff behavior for common document formats 52 | # 53 | # Convert binary document formats to text before diffing them. This feature 54 | # is only available from the command line. Turn it on by uncommenting the 55 | # entries below. 56 | ############################################################################### 57 | #*.doc diff=astextplain 58 | #*.DOC diff=astextplain 59 | #*.docx diff=astextplain 60 | #*.DOCX diff=astextplain 61 | #*.dot diff=astextplain 62 | #*.DOT diff=astextplain 63 | #*.pdf diff=astextplain 64 | #*.PDF diff=astextplain 65 | #*.rtf diff=astextplain 66 | #*.RTF diff=astextplain 67 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Job/Job.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace OKEGui 5 | { 6 | public enum ProcessPriority : int { IDLE = 0, BELOW_NORMAL, NORMAL, ABOVE_NORMAL, HIGH, PARALLEL }; 7 | 8 | public enum JobType 9 | { 10 | Video, 11 | Audio, 12 | VideoInfo, 13 | Mux, 14 | RpCheck 15 | } 16 | 17 | /// 18 | /// 任务信息 19 | /// 20 | public abstract class Job 21 | { 22 | #region important details 23 | 24 | public string Input; 25 | public string Output; 26 | public List FilesToDelete; 27 | 28 | #endregion important details 29 | 30 | #region JobStatus 31 | 32 | public string Status 33 | { 34 | set 35 | { 36 | if (ts != null) 37 | { 38 | ts.CurrentStatus = value; 39 | } 40 | } 41 | } 42 | 43 | public double Progress 44 | { 45 | set 46 | { 47 | if (ts != null) 48 | { 49 | ts.ProgressValue = value; 50 | } 51 | } 52 | } 53 | 54 | public string Speed 55 | { 56 | set 57 | { 58 | if (ts != null) 59 | { 60 | ts.Speed = value; 61 | } 62 | } 63 | } 64 | 65 | public TimeSpan TimeRemain 66 | { 67 | set 68 | { 69 | if (ts != null) 70 | { 71 | ts.TimeRemain = value; 72 | } 73 | } 74 | } 75 | 76 | public string BitRate 77 | { 78 | set 79 | { 80 | if (ts != null) 81 | { 82 | ts.BitRate = value; 83 | } 84 | } 85 | } 86 | 87 | protected TaskStatus ts; 88 | 89 | public void SetUpdate(TaskStatus taskStatus) 90 | { 91 | ts = taskStatus; 92 | } 93 | 94 | #endregion JobStatus 95 | 96 | #region init 97 | public Job() 98 | { 99 | 100 | } 101 | 102 | public Job(string codec) : base() 103 | { 104 | CodecString = codec.ToUpper(); 105 | } 106 | 107 | #endregion init 108 | 109 | #region queue display details 110 | 111 | /// 112 | /// 使用的编码格式 113 | /// 114 | public string CodecString; 115 | 116 | public abstract JobType GetJobType(); 117 | 118 | #endregion queue display details 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/MediaFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using OKEGui.Model; 7 | 8 | namespace OKEGui.Model 9 | { 10 | /// 11 | /// 松散媒体文件结构类 12 | /// 程序内部抽象媒体结构,包含的轨道一般为未封装文件; 13 | /// 需要调用IMediaContainer封装保存到硬盘里。 14 | /// 15 | /// 最终轨道ID顺序:视频轨->音频轨-> 16 | public class MediaFile 17 | { 18 | /// 19 | /// 返回所有轨道 20 | /// 21 | /// 通过此变量删除轨道不会产生影响。 22 | public List Tracks 23 | { 24 | get 25 | { 26 | List tracks = new List(); 27 | if (VideoTrack != null) 28 | { 29 | tracks.Add(VideoTrack); 30 | } 31 | 32 | tracks.AddRange(AudioTracks); 33 | tracks.AddRange(SubtitleTracks); 34 | 35 | if (ChapterTrack != null) 36 | { 37 | tracks.Add(ChapterTrack); 38 | } 39 | 40 | return tracks; 41 | } 42 | } 43 | 44 | public VideoTrack VideoTrack = null; 45 | public List AudioTracks = new List(); 46 | public List SubtitleTracks = new List(); 47 | 48 | public ChapterTrack ChapterTrack = null; 49 | public string ChapterLanguage = null; 50 | 51 | /// 52 | /// 插入多媒体轨道 53 | /// 54 | /// 待添加轨道 55 | /// 56 | /// 一个多媒体文件只能有一条视频轨道和章节。如果已经存在,返回false。 57 | /// TrackId如果为0则添加到末尾;如果不为0,插入到所属轨道类型的第TrackId个之前(从1开始)。 58 | /// 视频轨道永远为第一条。 59 | /// 60 | /// 是否成功 61 | public void AddTrack(Track track) 62 | { 63 | if (track is VideoTrack) 64 | { 65 | if (VideoTrack == null) 66 | { 67 | VideoTrack = track as VideoTrack; 68 | } 69 | else 70 | { 71 | throw new ArgumentException("Multiple Video tracks are being added!"); 72 | } 73 | } 74 | 75 | if (track is AudioTrack) 76 | { 77 | AudioTracks.Add(track as AudioTrack); 78 | } 79 | 80 | if (track is SubtitleTrack) 81 | { 82 | SubtitleTracks.Add(track as SubtitleTrack); 83 | } 84 | 85 | if (track is ChapterTrack) 86 | { 87 | if (ChapterTrack == null) 88 | { 89 | ChapterTrack = track as ChapterTrack; 90 | } 91 | else 92 | { 93 | throw new ArgumentException("Multiple Chapter tracks are being added!"); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/Cleaner.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.IO; 4 | using System; 5 | 6 | 7 | namespace OKEGui.Utils 8 | { 9 | class Cleaner 10 | { 11 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("Cleaner"); 12 | 13 | private List sfxRemove, sfxRename; 14 | private const string TIME_FMT = "ddHHmm"; 15 | 16 | public Cleaner() : this( 17 | new List { "flac", "alac", "aac", "ac3", "dts", "sup", "Log.txt", "vpy", "qpf", "lwi", "rpc", "pyc" }, 18 | new List { "hevc", "mkv", "mp4", "mka", "h264" }) 19 | { 20 | } 21 | public Cleaner(List sfxRemove, List sfxRename) 22 | { 23 | this.sfxRemove = sfxRemove; 24 | this.sfxRename = sfxRename; 25 | } 26 | 27 | public List Clean(string inputFile, List whiteList) 28 | { 29 | if (!whiteList.Contains(inputFile)) 30 | { 31 | whiteList.Add(inputFile); 32 | } 33 | List removed = Remove(inputFile, whiteList); 34 | //List renamed = Rename(inputFile, whiteList); 35 | //removed.AddRange(renamed); 36 | return removed; 37 | } 38 | 39 | public List Remove(string inputFile, List whiteList) 40 | { 41 | string directory = Path.GetDirectoryName(inputFile); 42 | string rawName = Path.GetFileNameWithoutExtension(inputFile); 43 | List files = new List(Directory.GetFiles(directory, rawName + "*.*", SearchOption.AllDirectories) 44 | .Where(s => !whiteList.Contains(s) && sfxRemove.Any(x => s.EndsWith(x)))); 45 | foreach (string file in files) { 46 | File.Delete(file); 47 | } 48 | return files; 49 | } 50 | 51 | public List Rename(string inputFile, List whiteList) 52 | { 53 | string directory = Path.GetDirectoryName(inputFile); 54 | string rawName = Path.GetFileNameWithoutExtension(inputFile); 55 | List files = new List(Directory.GetFiles(directory, rawName + "*.*", SearchOption.TopDirectoryOnly) 56 | .Where(s => !whiteList.Contains(s) && sfxRename.Any(x => s.EndsWith(x)))); 57 | DateTime time = DateTime.Now; 58 | for (int i = 0; i < files.Count; i++) 59 | { 60 | string oldFile = files[i]; 61 | rawName = Path.GetFileNameWithoutExtension(oldFile); 62 | string extension = Path.GetExtension(oldFile); 63 | string newFile = directory + @"\" + rawName + "_b_" + time.ToString(TIME_FMT) + extension; 64 | try 65 | { 66 | File.Move(oldFile, newFile); 67 | files[i] = newFile; 68 | } 69 | catch (Exception) 70 | { 71 | Logger.Error($"无法备份{oldFile},直接删除。"); 72 | File.Delete(oldFile); 73 | } 74 | 75 | } 76 | return files; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /OKEGui/OKEGui.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29411.108 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OKEGui", "OKEGui\OKEGui.csproj", "{BD980C51-9FCF-483E-BB88-B903952FDDDD}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TChapter", "..\TChapter\TChapter\TChapter.csproj", "{D3EFF606-0562-45E4-8E0B-1DF967220A9B}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4920AE9-71EE-45B9-AC73-EC85A881C11F}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Debug|x64 = Debug|x64 19 | Debug|x86 = Debug|x86 20 | Release|Any CPU = Release|Any CPU 21 | Release|x64 = Release|x64 22 | Release|x86 = Release|x86 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Debug|x64.ActiveCfg = Debug|Any CPU 28 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Debug|x64.Build.0 = Debug|Any CPU 29 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Debug|x86.ActiveCfg = Debug|Any CPU 30 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Debug|x86.Build.0 = Debug|Any CPU 31 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Release|x64.ActiveCfg = Release|Any CPU 34 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Release|x64.Build.0 = Release|Any CPU 35 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Release|x86.ActiveCfg = Release|Any CPU 36 | {BD980C51-9FCF-483E-BB88-B903952FDDDD}.Release|x86.Build.0 = Release|Any CPU 37 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Debug|x64.ActiveCfg = Debug|Any CPU 40 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Debug|x64.Build.0 = Debug|Any CPU 41 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Debug|x86.ActiveCfg = Debug|Any CPU 42 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Debug|x86.Build.0 = Debug|Any CPU 43 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Release|x64.ActiveCfg = Release|Any CPU 46 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Release|x64.Build.0 = Release|Any CPU 47 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Release|x86.ActiveCfg = Release|Any CPU 48 | {D3EFF606-0562-45E4-8E0B-1DF967220A9B}.Release|x86.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {62FAB699-A5BF-4AD3-8D1C-9334107EF293} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /RELNOTES.md: -------------------------------------------------------------------------------- 1 | # v7.3.4 "The Protagonist Returns", again^6! 2 | 3 | - Recognize AV1 video tracks in the input (mkv only) 4 | - Allow drag-n-drop YAML project files into the new project window 5 | - Log more messages from the x26x encoder to aid debugging 6 | - Add bundled x264/x265 encoders (now you can left out `"Encoder"` setting in project JSON) 7 | - Support bundled vapoursynth & RPChecker 8 | 9 | # v7.3.3 "The Protagonist Returns", again^5! 10 | 11 | - Add support for mandatory VS version checks, for v3 json. 12 | - Allow arbitrary ordering of `#OKE:DEBUG` and `#OKE:INPUTFILE`. 13 | - Log all unrecognized lines on stderr for `vspipe | x265`. 14 | 15 | # v7.3.2 "The Protagonist Returns", again^4! 16 | Basically upstream v7.3, with the following additions: 17 | - Huge page support. 18 | - Configurable set of common path components to strip for working/output directory. 19 | - "Lossy" audio transcode support. 20 | - The window title is still "The Protagonist Returns". 21 | 22 | # v7.2.7 "The Protagonist Returns", again^4! 23 | 24 | - Show vspipe path in window title. 25 | - Add support for "Optional" field in AudioTracks and SubtitleTracks. All optionals tracks of the same type must satisfy that either all exist or none exists. 26 | - The window title is still "The Protagonist Returns". 27 | 28 | # v7.2.6 "The Protagonist Returns", again^3! 29 | 30 | - Strip configurable set of common path components (e.g. BDMV STREAM) in working & output directory path. 31 | - Update auto-update to check the AmusementClub fork, instead of the origin upstream. 32 | - The window title is still "The Protagonist Returns". 33 | 34 | # v7.2.5 "The Protagonist Returns", again^2! 35 | 36 | - If "跳过Numa检测" is set, do not set numa node when starting vspipe and encoder. 37 | - Also change the affinity mask from (1<<28)-1 to (1<<64)-1 to avoid wasting CPUs. 38 | - Updated eac3to-wrapper to v1.2 (built with Go 1.17). 39 | - The window title is still "The Protagonist Returns". 40 | 41 | # v7.2.4 "The Protagonist Returns", again! 42 | 43 | - Simplify temporary working directory path, useless "BDMV" and "STREAM" components are removed. 44 | - Separate output files from the working directory. Place output files into "./output/" under the project directory. 45 | - The window title is still "The Protagonist Returns". 46 | 47 | # v7.2.3 "Attack on Memory" 48 | 49 | - Change task name text alignment to left by default 50 | - Add **HUGE** page support for vs-classic and modded x265 that use mimalloc 51 | - I forget to change the AssemblyDescription, so the window title is still "The Protagonist Returns", it's a feature! 52 | 53 | # v7.2.2 "The Protagonist Returns" 54 | 55 | - Recognizes ass and srt subs in mkv inputs (though no support for muxing them into the final output though, so you have to specify `"MuxOption": "Skip"` for all those ASS/SRT tracks. 56 | 57 | # v7.2.1 "Me and You and the Student Council" 58 | 59 | Compared to upstream v7.2 release, this release introduces the following changes: 60 | 61 | - `OKE:PROJECTDIR` tag: the vpy script can access files under the project directory (where the json file locates) to import custom modules or plugins. It can only used at most once in a vpy. 62 | Example: 63 | ```python 64 | #OKE:PROJECTDIR 65 | sdir = '.' 66 | sys.path.insert(1, sdir) # some packages rely on having '' as sys.path[0] 67 | import akvsfunc as akf # imports akvsfunc from the project directory 68 | core.std.LoadPlugin(os.path.abspath(os.path.join(sdir, 'akarin.dll'))) 69 | ``` 70 | 71 | - Temporary files will no longer be created in the source m2ts file and instead will be placed under the project directory. 72 | 73 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/Demuxer/TrackInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using OKEGui.Model; 4 | 5 | namespace OKEGui 6 | { 7 | public partial class EACDemuxer 8 | { 9 | public enum TrackCodec 10 | { 11 | Unknown, 12 | MPEG2, 13 | H264_AVC, 14 | H265_HEVC, 15 | AV1, 16 | RAW_PCM, 17 | FLAC, 18 | AAC, 19 | DTSMA, 20 | TRUEHD_AC3, 21 | AC3, 22 | DTS, 23 | EAC3, 24 | OPUS, 25 | PGS, 26 | Chapter, 27 | VobSub, 28 | ASS, 29 | SRT 30 | } 31 | 32 | public class TrackInfo 33 | { 34 | public TrackCodec Codec; 35 | public int Index; 36 | public string Information; 37 | public string RawOutput; 38 | public string SourceFile; 39 | public string WorkingPathPrefix; 40 | public TrackType Type; 41 | public bool DupOrEmpty; 42 | public int Length; 43 | public long FileSize; 44 | public double MeanVolume; 45 | public double MaxVolume; 46 | 47 | public string OutFileName 48 | { 49 | get 50 | { 51 | var directory = Path.GetDirectoryName(WorkingPathPrefix); 52 | var baseName = Path.GetFileNameWithoutExtension(SourceFile); 53 | 54 | return $"{Path.Combine(directory, baseName)}_{Index}{FileExtension}"; 55 | } 56 | } 57 | 58 | public string FileExtension 59 | { 60 | get 61 | { 62 | TrackCodec type = Codec; 63 | return "." + s_eacOutputs.Find(val => val.Codec == type).FileExtension; 64 | } 65 | } 66 | 67 | public bool IsEmpty() 68 | { 69 | switch (Type) 70 | { 71 | case TrackType.Audio: 72 | return MeanVolume < -70 && MaxVolume < -30; 73 | case TrackType.Subtitle: 74 | return FileSize / Length < 3 * 1024 * 1024 / 3600; 75 | default: 76 | return FileSize < 64; 77 | } 78 | } 79 | 80 | public bool IsDuplicate(in TrackInfo other) 81 | { 82 | if (Type != other.Type) 83 | { 84 | return false; 85 | } 86 | switch (Type) 87 | { 88 | case TrackType.Audio: 89 | return Math.Abs(MeanVolume - other.MeanVolume) < 0.01 && Math.Abs(MaxVolume - other.MaxVolume) < 0.01; 90 | default: 91 | return FileSize == other.FileSize; 92 | } 93 | } 94 | 95 | public void MarkSkipping() 96 | { 97 | try 98 | { 99 | File.Move(OutFileName, Path.ChangeExtension(OutFileName, ".bak") + FileExtension); 100 | } 101 | catch (Exception) 102 | { 103 | Logger.Warn("无法备份文件,直接删除。如果是重启的任务,这很正常。"); 104 | File.Delete(OutFileName); 105 | } 106 | DupOrEmpty = true; 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /dist/windows/options.nsi: -------------------------------------------------------------------------------- 1 | Unicode true 2 | ManifestDPIAware true 3 | ;Compress the header too 4 | !packhdr "$%TEMP%\exehead.tmp" 'upx.exe -9 --best --ultra-brute "$%TEMP%\exehead.tmp"' 5 | 6 | ;Setting the compression 7 | SetCompressor /SOLID LZMA 8 | SetCompressorDictSize 64 9 | XPStyle on 10 | 11 | !include "MUI.nsh" 12 | !include "UAC.nsh" 13 | !include "FileFunc.nsh" 14 | !include "WinVer.nsh" 15 | 16 | ;For the file association 17 | !define SHCNE_ASSOCCHANGED 0x8000000 18 | !define SHCNF_IDLIST 0 19 | 20 | ;For special folder detection 21 | !define CSIDL_APPDATA '0x1A' ;Application Data path 22 | !define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path 23 | 24 | ; Program specific 25 | !define PROG_VERSION "6.8" 26 | 27 | !define MUI_FINISHPAGE_RUN 28 | !define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun 29 | !define MUI_FINISHPAGE_RUN_TEXT $(launch_str) 30 | 31 | ; The name of the installer 32 | Name "OKEGui ${PROG_VERSION}" 33 | ; The file to write 34 | OutFile "OKEGui_${PROG_VERSION}_setup.exe" 35 | 36 | ;Installer Version Information 37 | VIAddVersionKey "ProductName" "OKEGui" 38 | VIAddVersionKey "CompanyName" "VCB-Studio" 39 | VIAddVersionKey "LegalCopyright" "Copyright ©2016-2020 VCB-Studio" 40 | VIAddVersionKey "FileDescription" "OKEGui - One Key Encode GUI" 41 | VIAddVersionKey "FileVersion" "${PROG_VERSION}" 42 | 43 | VIProductVersion "${PROG_VERSION}.0.0" 44 | 45 | InstallDir $APPDATA\OKEGui 46 | 47 | ; Registry key to check for directory (so if you install again, it will 48 | ; overwrite the old one automatically) 49 | InstallDirRegKey HKLM Software\OKEGui InstallLocation 50 | 51 | ; Request application privileges for Windows Vista 52 | RequestExecutionLevel user 53 | 54 | ;-------------------------------- 55 | ;General Settings 56 | !define MUI_ABORTWARNING 57 | !define MUI_HEADERIMAGE 58 | !define MUI_COMPONENTSPAGE_NODESC 59 | !define MUI_ICON "OKEGui.ico" 60 | !define MUI_UNICON "OKEGui.ico" 61 | !define MUI_LICENSEPAGE_CHECKBOX 62 | !define MUI_LANGDLL_ALLLANGUAGES 63 | 64 | ;-------------------------------- 65 | ;Remember the unistaller/installer language 66 | !define MUI_LANGDLL_REGISTRY_ROOT "HKLM" 67 | !define MUI_LANGDLL_REGISTRY_KEY "Software\OKEGui" 68 | !define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language" 69 | 70 | ;-------------------------------- 71 | ;Installer Pages 72 | !insertmacro MUI_PAGE_WELCOME 73 | !insertmacro MUI_PAGE_LICENSE "..\..\LICENSE" 74 | !insertmacro MUI_PAGE_COMPONENTS 75 | !insertmacro MUI_PAGE_DIRECTORY 76 | !insertmacro MUI_PAGE_INSTFILES 77 | !insertmacro MUI_PAGE_FINISH 78 | 79 | ;-------------------------------- 80 | ;Uninstaller Pages 81 | !insertmacro MUI_UNPAGE_CONFIRM 82 | !insertmacro MUI_UNPAGE_COMPONENTS 83 | !insertmacro MUI_UNPAGE_INSTFILES 84 | 85 | !insertmacro MUI_RESERVEFILE_LANGDLL 86 | ReserveFile "${NSISDIR}\Plugins\x86-unicode\UAC.dll" 87 | 88 | !macro Init thing 89 | uac_tryagain: 90 | !insertmacro UAC_RunElevated 91 | ${Switch} $0 92 | ${Case} 0 93 | ${IfThen} $1 = 1 ${|} Quit ${|} ;we are the outer process, the inner process has done its work, we are done 94 | ${IfThen} $3 <> 0 ${|} ${Break} ${|} ;we are admin, let the show go on 95 | ${If} $1 = 3 ;RunAs completed successfully, but with a non-admin user 96 | MessageBox mb_YesNo|mb_IconExclamation|mb_TopMost|mb_SetForeground "This ${thing} requires admin privileges, try again" /SD IDNO IDYES uac_tryagain IDNO 0 97 | ${EndIf} 98 | ;fall-through and die 99 | ${Case} 1223 100 | MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "This ${thing} requires admin privileges, aborting!" 101 | Quit 102 | ${Case} 1062 103 | MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Logon service not running, aborting!" 104 | Quit 105 | ${Default} 106 | MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate , error $0" 107 | Quit 108 | ${EndSwitch} 109 | 110 | SetShellVarContext all 111 | !macroend 112 | -------------------------------------------------------------------------------- /dist/windows/installer.nsi: -------------------------------------------------------------------------------- 1 | Var uninstallerPath 2 | 3 | Section "-hidden" 4 | 5 | ;Search if OKEGui is already installed. 6 | FindFirst $0 $1 "$uninstallerPath\uninst.exe" 7 | FindClose $0 8 | StrCmp $1 "" done 9 | 10 | ;Run the uninstaller of the previous install. 11 | DetailPrint $(inst_unist_str) 12 | ExecWait '"$uninstallerPath\uninst.exe" /S _?=$uninstallerPath' 13 | Delete "$uninstallerPath\uninst.exe" 14 | RMDir "$uninstallerPath" 15 | 16 | done: 17 | 18 | SectionEnd 19 | 20 | 21 | Section $(inst_req_str) ;"OKEGui (required)" 22 | SectionIn RO 23 | 24 | ; Set output path to the installation directory. 25 | SetOutPath $INSTDIR 26 | ; Put file there 27 | File "..\..\OKEGui\OKEGui\bin\Release\*.dll" 28 | File "..\..\OKEGui\OKEGui\bin\Release\*.xml" 29 | File "..\..\OKEGui\OKEGui\bin\Release\OKEGui.exe" 30 | File "..\..\LICENSE" 31 | 32 | SetOutPath $INSTDIR\x86 33 | File /r "..\..\OKEGui\OKEGui\bin\Release\x86\*" 34 | 35 | SetOutPath $INSTDIR\x64 36 | File /r "..\..\OKEGui\OKEGui\bin\Release\x64\*" 37 | 38 | ; Write the installation path into the registry 39 | WriteRegStr HKLM "Software\OKEGui" "InstallLocation" "$INSTDIR" 40 | 41 | ; Write the uninstall keys for Windows 42 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "DisplayName" "OKEGui ${PROG_VERSION}" 43 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "UninstallString" '"$INSTDIR\uninst.exe"' 44 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "DisplayIcon" '"$INSTDIR\OKEGui.exe",0' 45 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "Publisher" "VCB-Studio" 46 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "URLInfoAbout" "https://github.com/vcb-s/OKEGui" 47 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "DisplayVersion" "${PROG_VERSION}" 48 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "NoModify" 1 49 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "NoRepair" 1 50 | WriteUninstaller "uninst.exe" 51 | ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 52 | IntFmt $0 "0x%08X" $0 53 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OKEGui" "EstimatedSize" "$0" 54 | 55 | SectionEnd 56 | 57 | Section $(inst_tools_str) ;"External tools (recommend)" 58 | CreateDirectory "$INSTDIR\tools" 59 | SetOutPath "$INSTDIR\tools" 60 | File /r "tools\*" 61 | SectionEnd 62 | 63 | Section /o $(inst_samples_str) ;"Example files (optional)" 64 | CreateDirectory "$INSTDIR\examples" 65 | SetOutPath "$INSTDIR\examples" 66 | File /r "examples\*" 67 | SectionEnd 68 | 69 | ; Optional section (can be disabled by the user) 70 | Section /o $(inst_dekstop_str) ;"Create Desktop Shortcut" 71 | 72 | SetOutPath "$INSTDIR" 73 | CreateShortCut "$DESKTOP\OKEGui.lnk" "$INSTDIR\OKEGui.exe" 74 | 75 | SectionEnd 76 | 77 | Section $(inst_startmenu_str) ;"Create Start Menu Shortcut" 78 | 79 | CreateDirectory "$SMPROGRAMS\OKEGui" 80 | SetOutPath "$INSTDIR" 81 | CreateShortCut "$SMPROGRAMS\OKEGui\OKEGui.lnk" "$INSTDIR\OKEGui.exe" 82 | CreateShortCut "$SMPROGRAMS\OKEGui\Uninstall.lnk" "$INSTDIR\uninst.exe" 83 | 84 | SectionEnd 85 | 86 | 87 | ;-------------------------------- 88 | 89 | Function .onInit 90 | 91 | !insertmacro Init "installer" 92 | !insertmacro MUI_LANGDLL_DISPLAY 93 | 94 | ;Search if OKEGui is already installed. 95 | FindFirst $0 $1 "$INSTDIR\uninst.exe" 96 | FindClose $0 97 | StrCmp $1 "" done 98 | 99 | ;Copy old value to var so we can call the correct uninstaller 100 | StrCpy $uninstallerPath $INSTDIR 101 | 102 | ;Inform the user 103 | MessageBox MB_OKCANCEL|MB_ICONINFORMATION $(inst_uninstall_question_str) /SD IDOK IDOK done 104 | Quit 105 | 106 | done: 107 | 108 | FunctionEnd 109 | 110 | 111 | Function PageFinishRun 112 | 113 | !insertmacro UAC_AsUser_ExecShell "" "$INSTDIR\OKEGui.exe" "" "$INSTDIR" "" 114 | 115 | FunctionEnd 116 | 117 | Function .onInstSuccess 118 | SetErrorLevel 0 119 | FunctionEnd 120 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/Video/X264Encoder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using OKEGui.Utils; 8 | using OKEGui.JobProcessor; 9 | using System.Collections.Generic; 10 | 11 | namespace OKEGui 12 | { 13 | public class X264Encoder : CommandlineVideoEncoder 14 | { 15 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("X264Encoder"); 16 | private readonly string X264Path = ""; 17 | private readonly string VspipePath = ""; 18 | 19 | public X264Encoder(VideoJob job) : base() 20 | { 21 | this.job = job; 22 | getInputProperties(job); 23 | 24 | executable = Path.Combine(Environment.SystemDirectory, "cmd.exe"); 25 | 26 | if (File.Exists(job.EncoderPath)) 27 | { 28 | this.X264Path = job.EncoderPath; 29 | } 30 | 31 | // 获取VSPipe路径 32 | this.VspipePath = Initializer.Config.vspipePath; 33 | 34 | commandLine = BuildCommandline(job.EncodeParam, job.NumaNode, job.VspipeArgs); 35 | } 36 | 37 | public override void ProcessLine(string line, StreamType stream) 38 | { 39 | if (line.Contains("x264 [error]:")) 40 | { 41 | Logger.Error(line); 42 | OKETaskException ex = new OKETaskException(Constants.x264ErrorSmr); 43 | ex.progress = 0.0; 44 | ex.Data["X264_ERROR"] = line.Substring(14); 45 | throw ex; 46 | } 47 | 48 | if (line.Contains("Error: fwrite() call failed when writing frame: ")) 49 | { 50 | Logger.Error(line); 51 | OKETaskException ex = new OKETaskException(Constants.x264CrashSmr); 52 | throw ex; 53 | } 54 | 55 | if (line.ToLowerInvariant().Contains("encoded")) 56 | { 57 | Logger.Debug(line); 58 | Regex rf = new Regex("encoded ([0-9]+) frames, ([0-9]+.[0-9]+) fps, ([0-9]+.[0-9]+) kb/s"); 59 | 60 | var result = rf.Split(line); 61 | 62 | ulong reportedFrames = ulong.Parse(result[1]); 63 | 64 | // 这里是平均速度 65 | if (!base.setSpeed(result[2])) 66 | { 67 | return; 68 | } 69 | 70 | Debugger.Log(0, "EncodeFinish", result[2] + "fps\n"); 71 | 72 | base.encodeFinish(reportedFrames); 73 | } 74 | 75 | Regex r = new Regex("([0-9]+) frames: ([0-9]+.[0-9]+) fps, ([0-9]+.[0-9]+) kb/s", RegexOptions.IgnoreCase); 76 | 77 | var status = r.Split(line); 78 | if (status.Length < 3) 79 | { 80 | Logger.Debug(line); 81 | return; 82 | } 83 | 84 | if (!base.setFrameNumber(status[1], true)) 85 | { 86 | return; 87 | } 88 | 89 | base.setBitrate(status[3], "kb/s"); 90 | 91 | if (!base.setSpeed(status[2])) 92 | { 93 | return; 94 | } 95 | } 96 | 97 | private string BuildCommandline(string extractParam, int numaNode, List vspipeArgs) 98 | { 99 | StringBuilder sb = new StringBuilder(); 100 | 101 | sb.Append("/c \"start \"foo\" /b /wait "); 102 | if (!Initializer.Config.singleNuma) 103 | { 104 | sb.Append("/affinity 0xFFFFFFFFFFFFFFFF /node "); 105 | sb.Append(numaNode.ToString()); 106 | } 107 | // 构建vspipe参数 108 | sb.Append(" \"" + VspipePath + "\""); 109 | sb.Append(" --y4m"); 110 | foreach (string arg in vspipeArgs) 111 | { 112 | sb.Append($" --arg \"{arg}\""); 113 | } 114 | sb.Append(" \"" + job.Input + "\""); 115 | sb.Append(" - |"); 116 | 117 | // 构建X264参数 118 | sb.Append(" \"" + X264Path + "\""); 119 | sb.Append(" --demuxer y4m " + extractParam + " -o"); 120 | sb.Append(" \"" + job.Output + "\" -"); 121 | sb.Append("\""); 122 | 123 | return sb.ToString(); 124 | } 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/Constants.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace OKEGui.Utils 5 | { 6 | public static class Constants 7 | { 8 | //QAAC encoder. 9 | public const string QAACPath = ".\\tools\\qaac\\qaac64.exe"; 10 | public const int QAACBitrate = 192; 11 | 12 | //ffmpeg 13 | public const string ffmpegPath = ".\\tools\\ffmpeg\\ffmpeg.exe"; 14 | 15 | //eac3to-wrapper 16 | public const string eac3toWrapperPath = ".\\tools\\eac3to\\eac3to-wrapper.exe"; 17 | 18 | //vspipe 19 | public const string vspipePath = ".\\tools\\vapoursynth\\vspipe.exe"; 20 | 21 | //x264 22 | public const string x264Path = ".\\tools\\x26x\\x264.exe"; 23 | 24 | //x265 25 | public const string x265Path = ".\\tools\\x26x\\x265.exe"; 26 | 27 | //rpchecker 28 | public const string rpcPath = ".\\tools\\rpc\\rpchecker.exe"; 29 | 30 | //Audio & sub language. 31 | public const string language = "jpn"; 32 | 33 | //Error messages and summaries 34 | 35 | public const string audioNumMismatchMsg = "当前的视频含有轨道数{0},与json中指定的数量{1}必须+{2}可选不符合。该文件{3}将跳过处理。请转告技术总监复查。"; 36 | public const string audioNumMismatchSmr = "音轨数不一致"; 37 | 38 | public const string subNumMismatchMsg = "当前的视频含有字幕数{0},与json中指定的数量{1}必须+{2}可选不符合。该文件{3}将跳过处理。请转告技术总监复查。"; 39 | public const string subNumMismatchSmr = "字幕数不一致"; 40 | 41 | public const string fpsMismatchMsg = "输出FPS和指定FPS不一致。json里指定帧率为{0},vs输出帧率为{1}。该文件{2}将跳过处理。请转告技术总监复查。"; 42 | public const string fpsMismatchSmr = "FPS不一致"; 43 | 44 | public const string x264ErrorMsg = "x264出错:{0}。该文件{1}将跳过处理。请转告技术总监复查。"; 45 | public const string x264ErrorSmr = "x264出错"; 46 | 47 | public const string x265ErrorMsg = "x265出错:{0}。该文件{1}将跳过处理。请转告技术总监复查。"; 48 | public const string x265ErrorSmr = "x265出错"; 49 | 50 | public const string svtav1ErrorMsg = "svt-av1出错:{0}。该文件{1}将跳过处理。请转告技术总监复查。"; 51 | public const string svtav1ErrorSmr = "svt-av1出错"; 52 | 53 | public const string vpyErrorMsg = "vpy出错:{0}。该文件{1}将跳过处理。请转告技术总监复查。"; 54 | public const string vpyErrorSmr = "vpy出错"; 55 | 56 | public const string unknownErrorMsg = "未知错误。该文件{0}将跳过处理。请转告技术总监复查。"; 57 | public const string unknownErrorSmr = "未知错误"; 58 | 59 | public const string vsCrashMsg = "压制未能完成,预计是vs崩溃。该文件{0}将跳过处理,半成品以HEVC形式保留在目录中。请转告技术总监复查。"; 60 | public const string vsCrashSmr = "vs崩溃"; 61 | 62 | public const string x264CrashMsg = "压制未能完成,预计是x264崩溃。该文件{0}将跳过处理,如果是MKV输出,半成品以_.mkv形式保留在目录中。请转告技术总监复查。"; 63 | public const string x264CrashSmr = "x264崩溃"; 64 | 65 | public const string x265CrashMsg = "压制未能完成,预计是x265崩溃。该文件{0}将跳过处理,半成品以HEVC形式保留在目录中。请转告技术总监复查。"; 66 | public const string x265CrashSmr = "x265崩溃"; 67 | 68 | public const string svtav1CrashMsg = "压制未能完成,预计是svt-av1崩溃。该文件{0}将跳过处理,半成品以HEVC形式保留在目录中。请转告技术总监复查。"; 69 | public const string svtav1CrashSmr = "svt-av1崩溃"; 70 | 71 | public const string qaacErrorMsg = "QAAC无法正常运行。请确保你安装了Apple Application Support 64bit"; 72 | public const string qaacErrorSmr = "QAAC无法运行"; 73 | 74 | public const string audioFormatMistachMsg = "无法将{0}格式的音轨转为{1}格式。该文件{2}将跳过处理。请转告技术总监复查。"; 75 | public const string audioFormatMistachSmr = "音轨格式不匹配"; 76 | 77 | public const string rpcErrorMsg = "RPC出错:{0}。请手动检查{1},并请转告技术总监复查。"; 78 | public const string rpcErrorSmr = "RPC出错"; 79 | 80 | //Application configuration file 81 | public const string configFile = "OKEGuiConfig.json"; 82 | 83 | //Input and Debug, Memory regex 84 | public static readonly Regex inputRegex = new Regex("# *OKE:INPUTFILE([\\s]+\\w+[ ]*=[ ]*)([r]*[\"'].*[\"'])", RegexOptions.Multiline | RegexOptions.IgnoreCase); 85 | public static readonly Regex projectDirRegex = new Regex("# *OKE:PROJECTDIR([\\s]+\\w+[ ]*=[ ]*)([r]*[\"'].*[\"'])", RegexOptions.Multiline | RegexOptions.IgnoreCase); 86 | public static readonly Regex memoryRegex = new Regex("# *OKE:MEMORY([\\s]+core.max_cache_size+[ ]*=[ ]*)(\\d+)", RegexOptions.Multiline | RegexOptions.IgnoreCase); 87 | public static readonly Regex debugRegex = new Regex("# *OKE:DEBUG([\\s]+\\w+[ ]*=[ ]*)(\\w+)", RegexOptions.Multiline | RegexOptions.IgnoreCase); 88 | 89 | //Deprecated option list 90 | public static readonly List deprecatedOptions = new List { "SkipMuxing", "IncludeSub", "SubtitleLanguage" }; 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Task/SubProcessService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Management; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace OKEGui.Task 11 | { 12 | public class SubProcessService 13 | { 14 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("SubProcessService"); 15 | 16 | [Flags] 17 | public enum ThreadAccess : int 18 | { 19 | TERMINATE = (0x0001), 20 | SUSPEND_RESUME = (0x0002), 21 | GET_CONTEXT = (0x0008), 22 | SET_CONTEXT = (0x0010), 23 | SET_INFORMATION = (0x0020), 24 | QUERY_INFORMATION = (0x0040), 25 | SET_THREAD_TOKEN = (0x0080), 26 | IMPERSONATE = (0x0100), 27 | DIRECT_IMPERSONATION = (0x0200) 28 | } 29 | 30 | [DllImport("kernel32.dll")] 31 | static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); 32 | [DllImport("kernel32.dll")] 33 | static extern uint SuspendThread(IntPtr hThread); 34 | [DllImport("kernel32.dll")] 35 | static extern int ResumeThread(IntPtr hThread); 36 | [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] 37 | static extern bool CloseHandle(IntPtr handle); 38 | 39 | 40 | private static void SuspendProcess(Process process) 41 | { 42 | foreach (ProcessThread pT in process.Threads) 43 | { 44 | IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id); 45 | 46 | if (pOpenThread == IntPtr.Zero) 47 | { 48 | continue; 49 | } 50 | 51 | SuspendThread(pOpenThread); 52 | CloseHandle(pOpenThread); 53 | } 54 | } 55 | 56 | private static void ResumeProcess(Process process) 57 | { 58 | foreach (ProcessThread pT in process.Threads) 59 | { 60 | IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id); 61 | 62 | if (pOpenThread == IntPtr.Zero) 63 | { 64 | continue; 65 | } 66 | 67 | int suspendCount = 2; 68 | while (suspendCount > 1) 69 | { 70 | suspendCount = ResumeThread(pOpenThread); 71 | } 72 | 73 | CloseHandle(pOpenThread); 74 | } 75 | } 76 | 77 | private static List getChildProcesses(Process process) 78 | { 79 | ManagementObjectSearcher searcher = new ManagementObjectSearcher( 80 | "SELECT *" + 81 | " FROM Win32_Process" + 82 | " WHERE ParentProcessId=" + process.Id.ToString()); 83 | ManagementObjectCollection collection = searcher.Get(); 84 | List res = new List(); 85 | if (collection.Count > 0) 86 | { 87 | foreach (ManagementBaseObject item in collection) 88 | { 89 | UInt32 childProcessId = (UInt32)item["ProcessId"]; 90 | if ((int)childProcessId != Process.GetCurrentProcess().Id) 91 | { 92 | Process childProcess = Process.GetProcessById((int)childProcessId); 93 | res.Add(childProcess); 94 | res.AddRange(getChildProcesses(childProcess)); 95 | } 96 | } 97 | } 98 | return res; 99 | } 100 | 101 | public static void PauseAll() 102 | { 103 | List allProcesses = getChildProcesses(Process.GetCurrentProcess()); 104 | foreach (Process i in allProcesses) 105 | { 106 | SuspendProcess(i); 107 | } 108 | } 109 | 110 | public static void ResumeAll() 111 | { 112 | List allProcesses = getChildProcesses(Process.GetCurrentProcess()); 113 | foreach (Process i in allProcesses) 114 | { 115 | ResumeProcess(i); 116 | } 117 | } 118 | 119 | public static void KillAll() 120 | { 121 | List allProcesses = getChildProcesses(Process.GetCurrentProcess()); 122 | foreach (Process i in allProcesses) 123 | { 124 | i.Kill(); 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: OKEGui Mod Release 2 | 3 | on: 4 | push: 5 | tags: v* 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-2019 11 | #if: github.event.base_ref == 'refs/heads/mod' 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | submodules: recursive 17 | 18 | - name: Setup MSBuild 19 | uses: microsoft/setup-msbuild@v1.1 20 | 21 | - name: Setup NuGet 22 | uses: NuGet/setup-nuget@v1 23 | 24 | - name: Setup Go 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: '1.17' 28 | 29 | - name: Navigate to Workspace 30 | run: cd $GITHUB_WORKSPACE 31 | 32 | - name: Clone & Build eac3to-wrapper 33 | shell: bash 34 | run: | 35 | git clone https://github.com/AkarinVS/eac3to-wrapper 36 | cd eac3to-wrapper 37 | go build -ldflags "-X main.version=$(git describe --tags)" 38 | cd .. 39 | 40 | - name: Restore Packages 41 | run: nuget restore OKEGui/OKEGui.sln 42 | 43 | - name: Build Solution 44 | run: | 45 | msbuild.exe OKEGui/OKEGui.sln /nologo /p:DeleteExistingFiles=True /p:platform="Any CPU" /p:configuration="Release" 46 | 47 | - name: Integrate tools pack from previous release 48 | shell: bash 49 | run: | 50 | set -ex 51 | curl -s -o tools.zip -L https://github.com/vcb-s/OKEGui/releases/download/7.3/OKEGui_v7.3.zip 52 | 7z x -otmp tools.zip 53 | # clean up unnecessary files 54 | rm -f tmp/OKEGui/tools/mkvtoolnix/cache/fileIdentifier/* 55 | # tweak mkvtoolnix setting 56 | sed -i -e 's|chapterNameTemplate=.*|chapterNameTemplate=Chapter |g' tmp/OKEGui/tools/mkvtoolnix/mkvtoolnix-gui.ini 57 | sed -i -e 's|updates\\checkForUpdates=true|updates\\checkForUpdates=false|g' tmp/OKEGui/tools/mkvtoolnix/mkvtoolnix-gui.ini 58 | # update eac3to's libdcadec.dll, see https://forum.videohelp.com/threads/400707-UsEac3to-1-3-0-DTS-HD-MA-to-FLAC#post2651323 59 | curl -s -o dcadec.zip -L https://github.com/foo86/dcadec/releases/download/v0.2.0/dcadec-0.2.0-win32.zip 60 | 7z x -odcadec dcadec.zip 61 | mv dcadec/libdcadec.dll tmp/OKEGui/tools/eac3to/libdcadec.dll 62 | # integrate into release 63 | mv tmp/OKEGui/tools "./OKEGui/OKEGui/bin/Release/" 64 | rm -rf tmp 65 | 66 | - name: Integrate eac3to-wrapper 67 | shell: bash 68 | run: | 69 | mkdir -p "./OKEGui/OKEGui/bin/Release/tools/eac3to" 70 | cp eac3to-wrapper/eac3to-wrapper.exe "./OKEGui/OKEGui/bin/Release/tools/eac3to" 71 | 72 | - name: Integrate x264 tmod and x265 Yuuki 73 | shell: bash 74 | run: | 75 | set -ex 76 | mkdir -p "./OKEGui/OKEGui/bin/Release/tools/x26x" 77 | # 78 | curl -s -o x264.7z -L https://github.com/jpsdr/x264/releases/download/r3075/x264_tmod_r3075.7z 79 | 7z x -otmp x264.7z 80 | cp -a tmp/posix/x264_x64.exe "./OKEGui/OKEGui/bin/Release/tools/x26x/x264.exe" 81 | rm -rf tmp 82 | # 83 | curl -s -o x265.7z -L https://github.com/AmusementClub/x265/releases/download/Kyouko-3.5-AC2/x265-win64-skylake-clang.Kyouko-3.5-AC2.7z 84 | 7z x -otmp x265.7z 85 | cp -a tmp/x265.exe "./OKEGui/OKEGui/bin/Release/tools/x26x/x265.exe" 86 | rm -rf tmp 87 | 88 | - name: Integrate RP-Checker 89 | shell: bash 90 | run: | 91 | set -ex 92 | mkdir -p "./OKEGui/OKEGui/bin/Release/tools/rpc" 93 | # 94 | curl -s -o RPChecker.exe -L https://github.com/vcb-s/rp-checker/releases/download/1.0.7.2/RPChecker.exe 95 | mv RPChecker.exe "./OKEGui/OKEGui/bin/Release/tools/rpc/" 96 | # 97 | curl -s -o RpcTemplate.vpy https://raw.githubusercontent.com/AmusementClub/vapoursynth-script/master/RpcTemplate.vpy 98 | mv "./OKEGui/OKEGui/bin/Release/tools/rpc/RpcTemplate.vpy" "./OKEGui/OKEGui/bin/Release/tools/rpc/RpcTemplate.vpy.old" 99 | mv RpcTemplate.vpy "./OKEGui/OKEGui/bin/Release/tools/rpc/" 100 | 101 | - name: Package release 102 | shell: bash 103 | run: | 104 | git clone https://github.com/AkarinVS/exe 105 | export PATH=`pwd`/exe/:$PATH 106 | cp -r dist/windows/examples ./OKEGui/OKEGui/bin/Release 107 | pushd ./OKEGui/OKEGui/bin 108 | mv Release OKEGui-mod 109 | zip -9r ../../../"OKEGui-mod-$(git describe --tags).zip" OKEGui-mod 110 | 111 | - name: Upload artifact 112 | uses: actions/upload-artifact@v3 113 | with: 114 | name: release 115 | path: | 116 | OKEGui-*.zip 117 | 118 | - name: Release 119 | uses: softprops/action-gh-release@v1 120 | with: 121 | files: OKEGui-*.zip 122 | body_path: RELNOTES.md 123 | draft: true 124 | env: 125 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ 246 | 247 | dist/windows/OKEGui*.exe 248 | dist/windows/tools/ 249 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/ExceptionParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using OKEGui.Utils; 7 | using OKEGui; 8 | using System.IO; 9 | 10 | namespace OKEGui.JobProcessor 11 | { 12 | struct ExceptionMsg 13 | { 14 | public string errorMsg; 15 | public string fileName; 16 | 17 | public override string ToString() 18 | { 19 | if (string.IsNullOrWhiteSpace(fileName)) 20 | { 21 | return errorMsg; 22 | } 23 | else 24 | { 25 | return fileName + " : " + errorMsg; 26 | } 27 | } 28 | } 29 | 30 | public class OKETaskException : OperationCanceledException 31 | { 32 | public readonly string summary; 33 | public double? progress = null; 34 | 35 | public OKETaskException() 36 | { 37 | summary = Constants.unknownErrorSmr; 38 | } 39 | 40 | public OKETaskException(string message) 41 | : base(message) 42 | { 43 | summary = message; 44 | } 45 | 46 | public OKETaskException(string message, Exception inner) 47 | : base(message, inner) 48 | { 49 | summary = message; 50 | } 51 | } 52 | 53 | static class ExceptionParser 54 | { 55 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("ExceptionParser"); 56 | 57 | public static ExceptionMsg Parse(OKETaskException ex, TaskDetail task) 58 | { 59 | Logger.Warn("收到异常" + ex.StackTrace); 60 | ExceptionMsg msg; 61 | if (task != null) 62 | { 63 | FileInfo fileinfo = new FileInfo(task.InputFile); 64 | msg.fileName = fileinfo.Name; 65 | } 66 | else 67 | { 68 | msg.fileName = ""; 69 | } 70 | switch (ex.summary) 71 | { 72 | case Constants.audioNumMismatchSmr: 73 | msg.errorMsg = string.Format(Constants.audioNumMismatchMsg, ex.Data["SRC_TRACK"], ex.Data["DST_REQ_TRACK"], ex.Data["DST_OPT_TRACK"], task.InputFile); 74 | break; 75 | 76 | case Constants.subNumMismatchSmr: 77 | msg.errorMsg = string.Format(Constants.subNumMismatchMsg, ex.Data["SRC_TRACK"], ex.Data["DST_REQ_TRACK"], ex.Data["DST_OPT_TRACK"], task.InputFile); 78 | break; 79 | 80 | case Constants.fpsMismatchSmr: 81 | msg.errorMsg = string.Format(Constants.fpsMismatchMsg, ex.Data["SRC_FPS"], ex.Data["DST_FPS"], task.InputFile); 82 | break; 83 | 84 | case Constants.x264ErrorSmr: 85 | msg.errorMsg = string.Format(Constants.x264ErrorMsg, ex.Data["X264_ERROR"], task.InputFile); 86 | break; 87 | 88 | case Constants.x265ErrorSmr: 89 | msg.errorMsg = string.Format(Constants.x265ErrorMsg, ex.Data["X265_ERROR"], task.InputFile); 90 | break; 91 | 92 | case Constants.svtav1ErrorSmr: 93 | msg.errorMsg = string.Format(Constants.svtav1ErrorMsg, ex.Data["SVTAV1_ERROR"], task.InputFile); 94 | break; 95 | 96 | case Constants.vpyErrorSmr: 97 | msg.errorMsg = string.Format(Constants.vpyErrorMsg, ex.Data["VPY_ERROR"], task.InputFile); 98 | break; 99 | 100 | case Constants.vsCrashSmr: 101 | msg.errorMsg = string.Format(Constants.vsCrashMsg, task.InputFile); 102 | break; 103 | 104 | case Constants.x264CrashSmr: 105 | msg.errorMsg = string.Format(Constants.x264CrashMsg, task.InputFile); 106 | break; 107 | 108 | case Constants.x265CrashSmr: 109 | msg.errorMsg = string.Format(Constants.x265CrashMsg, task.InputFile); 110 | break; 111 | 112 | case Constants.svtav1CrashSmr: 113 | msg.errorMsg = string.Format(Constants.svtav1CrashMsg, task.InputFile); 114 | break; 115 | 116 | case Constants.qaacErrorSmr: 117 | msg.errorMsg = string.Format(Constants.qaacErrorMsg); 118 | break; 119 | 120 | case Constants.audioFormatMistachSmr: 121 | msg.errorMsg = string.Format(Constants.audioFormatMistachMsg, ex.Data["SRC_FMT"], ex.Data["DST_FMT"], task.InputFile); 122 | break; 123 | 124 | case Constants.rpcErrorSmr: 125 | msg.errorMsg = string.Format(Constants.rpcErrorMsg, ex.Data["RPC_ERROR"], task.InputFile); 126 | break; 127 | 128 | case Constants.unknownErrorSmr: 129 | default: 130 | msg.errorMsg = string.Format(Constants.unknownErrorMsg, task.InputFile); 131 | break; 132 | } 133 | return msg; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Model/OKEFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | 9 | namespace OKEGui.Model 10 | { 11 | public class OKEFile 12 | { 13 | private FileInfo fi; 14 | 15 | public OKEFile(string path) 16 | { 17 | fi = new FileInfo(path); 18 | } 19 | 20 | public OKEFile(FileInfo fileInfo) 21 | { 22 | fi = fileInfo; 23 | } 24 | 25 | public bool ChangeExtension(string newExt) 26 | { 27 | return this.Rename(Path.ChangeExtension(fi.FullName, newExt)); 28 | } 29 | 30 | public bool ChangeExtension(string newExt, bool overwrite) 31 | { 32 | return this.Rename(Path.ChangeExtension(fi.FullName, newExt), overwrite); 33 | } 34 | 35 | public bool AddExtension(string newExt) 36 | { 37 | return this.Rename(fi.FullName + newExt); 38 | } 39 | 40 | public bool AddExtension(string newExt, bool overwrite) 41 | { 42 | return this.Rename(fi.FullName + newExt, overwrite); 43 | } 44 | 45 | public OKEFile CopyTo(string dstDirectory) 46 | { 47 | try { 48 | return new OKEFile(fi.CopyTo(dstDirectory + this.GetFileName())); 49 | } catch (Exception) { 50 | return null; 51 | } 52 | } 53 | 54 | public OKEFile CopyTo(string dstDirectory, bool overwrite) 55 | { 56 | if (!overwrite && new FileInfo(dstDirectory + this.GetFileName()).Exists) { 57 | // 文件已经存在且不覆盖 58 | return null; 59 | } 60 | 61 | try { 62 | return new OKEFile(fi.CopyTo(dstDirectory + this.GetFileName())); 63 | } catch (Exception) { 64 | return null; 65 | } 66 | } 67 | 68 | public bool Delete() 69 | { 70 | try { 71 | File.Delete(fi.FullName); 72 | return true; 73 | } catch (Exception) { 74 | return false; 75 | } 76 | } 77 | 78 | public string GetDirectory() 79 | { 80 | return fi.Directory.FullName; 81 | } 82 | 83 | public string GetExtension() 84 | { 85 | return fi.Extension; 86 | } 87 | 88 | public string GetFileName() 89 | { 90 | return fi.Name; 91 | } 92 | 93 | public string GetFileNameWithoutExtension() 94 | { 95 | return Path.GetFileNameWithoutExtension(fi.FullName); 96 | } 97 | 98 | public long GetFileSize() 99 | { 100 | return fi.Length; 101 | } 102 | 103 | public string GetFullPath() 104 | { 105 | return fi.FullName; 106 | } 107 | 108 | public bool IsPathCharSave() 109 | { 110 | const string pattern = "[a-zA-Z]:(\\\\([\\&\\[\\]\\ 0-9a-zA-Z-]+))+(\\.?)([a-zA-Z0-9]*)"; 111 | 112 | return Regex.Match(fi.FullName, pattern).Value == fi.FullName; 113 | } 114 | 115 | public bool MoveTo(string dstDirectory) 116 | { 117 | try { 118 | fi.MoveTo(dstDirectory + fi.Name); 119 | return true; 120 | } catch (Exception) { 121 | return false; 122 | } 123 | } 124 | 125 | public bool MoveTo(string dstDirectory, bool overwrite) 126 | { 127 | if (!overwrite && new FileInfo(dstDirectory + this.GetFileName()).Exists) { 128 | // 文件已经存在且不覆盖 129 | return false; 130 | } 131 | 132 | try { 133 | fi.MoveTo(dstDirectory + fi.Name); 134 | return true; 135 | } catch (Exception) { 136 | return false; 137 | } 138 | } 139 | 140 | public bool Rename(string newName) 141 | { 142 | try { 143 | fi.MoveTo(newName); 144 | return true; 145 | } catch (Exception) { 146 | return false; 147 | } 148 | } 149 | 150 | public bool Rename(string newName, bool overwrite) 151 | { 152 | if (!overwrite && new FileInfo(newName).Exists) { 153 | // 文件已经存在且不覆盖 154 | return false; 155 | } 156 | 157 | try { 158 | fi.MoveTo(newName); 159 | return true; 160 | } catch (Exception) { 161 | return false; 162 | } 163 | } 164 | 165 | public bool Exists() 166 | { 167 | return fi.Exists; 168 | } 169 | 170 | public bool AddCRC32() 171 | { 172 | string CRC32Code = Utils.CRC32.ComputeChecksumString(fi.FullName); 173 | string newName = GetDirectory() + "\\" + GetFileNameWithoutExtension() + " [" + CRC32Code + "]" + GetExtension(); 174 | return Rename(newName); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/Video/svtav1Encoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using OKEGui.Utils; 7 | using OKEGui.JobProcessor; 8 | using System.Collections.Generic; 9 | 10 | namespace OKEGui 11 | { 12 | public class SVTAV1Encoder : CommandlineVideoEncoder 13 | { 14 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("SVTAV1Encoder"); 15 | private readonly string svtav1Path = ""; 16 | private readonly string vspipePath = ""; 17 | private bool expectTotalFrames = false; 18 | 19 | public SVTAV1Encoder(VideoJob job) : base() 20 | { 21 | this.job = job; 22 | getInputProperties(job); 23 | 24 | executable = Path.Combine(Environment.SystemDirectory, "cmd.exe"); 25 | 26 | if (File.Exists(job.EncoderPath)) 27 | { 28 | this.svtav1Path = job.EncoderPath; 29 | } 30 | 31 | // 获取VSPipe路径 32 | this.vspipePath = Initializer.Config.vspipePath; 33 | 34 | commandLine = BuildCommandline(job.EncodeParam, job.NumaNode, job.VspipeArgs); 35 | } 36 | 37 | public override void ProcessLine(string line, StreamType stream) 38 | { 39 | if (line.Contains("Svt[error]: ") || line.Contains("[SVT-Error]: ") || line.Contains("Error: ")) 40 | { 41 | Logger.Error(line); 42 | OKETaskException ex = new OKETaskException(Constants.svtav1ErrorSmr); 43 | ex.progress = 0.0; 44 | ex.Data["SVTAV1_ERROR"] = line.Substring(line.IndexOf(':')+2); 45 | throw ex; 46 | } 47 | 48 | if (line.Contains("Error: fwrite() call failed when writing frame: ")) 49 | { 50 | Logger.Error(line); 51 | OKETaskException ex = new OKETaskException(Constants.svtav1CrashSmr); 52 | throw ex; 53 | } 54 | 55 | if (line.ToLowerInvariant().Contains("all_done_encoding")) // svt-av1 must be built with -DLOG_ENC_DONE=1. 56 | { 57 | Logger.Debug(line); 58 | Regex rf = new Regex("all_done_encoding *([0-9]+) frames"); 59 | 60 | var result = rf.Split(line); 61 | 62 | ulong reportedFrames = ulong.Parse(result[1]); 63 | 64 | Debugger.Log(0, "EncodeFinish", result[1] + " frames\n"); 65 | 66 | base.encodeFinish(reportedFrames); 67 | } 68 | if (line.StartsWith("Total Frames\t")) 69 | { 70 | Logger.Debug(line); 71 | expectTotalFrames = true; 72 | return; 73 | } 74 | if (expectTotalFrames) 75 | { 76 | Logger.Debug(line); 77 | Regex rf = new Regex("[\t ]*([0-9]+)[\t ]"); 78 | var result = rf.Split(line); 79 | if (result.Length < 2) 80 | return; 81 | ulong reportedFrames = ulong.Parse(result[1]); 82 | Debugger.Log(0, "EncodeFinish", result[1] + " frames\n"); 83 | base.encodeFinish(reportedFrames); 84 | expectTotalFrames = false; 85 | } 86 | 87 | Regex regOfficial = new Regex("Encoding frame *([0-9]+) *([0-9]+.[0-9]+) *kbps *([0-9]+.[0-9]+) *(fp[sm])", RegexOptions.IgnoreCase); 88 | 89 | string[] status; 90 | 91 | if (regOfficial.Split(line).Length >= 4) 92 | { 93 | status = regOfficial.Split(line); 94 | } 95 | else 96 | { 97 | Logger.Debug(line); 98 | return; 99 | } 100 | 101 | if (!base.setFrameNumber(status[1], true)) 102 | { 103 | return; 104 | } 105 | 106 | base.setBitrate(status[2], "kb/s"); 107 | 108 | if (!base.setSpeed(status[3], status[4])) 109 | { 110 | return; 111 | } 112 | } 113 | 114 | private string BuildCommandline(string extractParam, int numaNode, List vspipeArgs) 115 | { 116 | StringBuilder sb = new StringBuilder(); 117 | sb.Append("/c \"start \"foo\" /b /wait "); 118 | if (!Initializer.Config.singleNuma) 119 | { 120 | sb.Append("/affinity 0xFFFFFFFFFFFFFFFF /node "); 121 | sb.Append(numaNode.ToString()); 122 | } 123 | // 构建vspipe参数 124 | sb.Append(" \"" + vspipePath + "\""); 125 | sb.Append(" --y4m"); 126 | foreach (string arg in vspipeArgs) 127 | { 128 | sb.Append($" --arg \"{arg}\""); 129 | } 130 | sb.Append(" \"" + job.Input + "\""); 131 | sb.Append(" - |"); 132 | 133 | // 构建svtav1参数 134 | sb.Append(" \"" + svtav1Path + "\""); 135 | sb.Append(" --progress 2 " + extractParam + " -b"); 136 | sb.Append(" \"" + job.Output + "\" -i -"); 137 | sb.Append(" \""); // leave extra spaces for ApppendParameter. 138 | 139 | return sb.ToString(); 140 | } 141 | 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/Video/x265Encoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using OKEGui.Utils; 7 | using OKEGui.JobProcessor; 8 | using System.Collections.Generic; 9 | 10 | namespace OKEGui 11 | { 12 | public class X265Encoder : CommandlineVideoEncoder 13 | { 14 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("X265Encoder"); 15 | private readonly string X265Path = ""; 16 | private readonly string vspipePath = ""; 17 | 18 | public X265Encoder(VideoJob job) : base() 19 | { 20 | this.job = job; 21 | getInputProperties(job); 22 | 23 | executable = Path.Combine(Environment.SystemDirectory, "cmd.exe"); 24 | 25 | if (File.Exists(job.EncoderPath)) 26 | { 27 | this.X265Path = job.EncoderPath; 28 | } 29 | 30 | // 获取VSPipe路径 31 | this.vspipePath = Initializer.Config.vspipePath; 32 | 33 | commandLine = BuildCommandline(job.EncodeParam, job.NumaNode, job.VspipeArgs); 34 | } 35 | 36 | public override void ProcessLine(string line, StreamType stream) 37 | { 38 | if (line.Contains("x265 [error]:")) 39 | { 40 | Logger.Error(line); 41 | OKETaskException ex = new OKETaskException(Constants.x265ErrorSmr); 42 | ex.progress = 0.0; 43 | ex.Data["X265_ERROR"] = line.Substring(14); 44 | throw ex; 45 | } 46 | 47 | if (line.Contains("Error: fwrite() call failed when writing frame: ")) 48 | { 49 | Logger.Error(line); 50 | OKETaskException ex = new OKETaskException(Constants.x265CrashSmr); 51 | throw ex; 52 | } 53 | 54 | if (line.ToLowerInvariant().Contains("encoded")) 55 | { 56 | Logger.Debug(line); 57 | Regex rf = new Regex("encoded ([0-9]+) frames in ([0-9]+.[0-9]+)s \\(([0-9]+.[0-9]+) fps\\), ([0-9]+.[0-9]+) kb/s, Avg QP:(([0-9]+.[0-9]+))"); 58 | 59 | var result = rf.Split(line); 60 | 61 | ulong reportedFrames = ulong.Parse(result[1]); 62 | 63 | // 这里是平均速度 64 | if (!base.setSpeed(result[3])) 65 | { 66 | return; 67 | } 68 | 69 | Debugger.Log(0, "EncodeFinish", result[3] + "fps\n"); 70 | 71 | base.encodeFinish(reportedFrames); 72 | } 73 | 74 | Regex regOfficial = new Regex("([0-9]+) frames: ([0-9]+.[0-9]+) fps, ([0-9]+.[0-9]+) kb/s", RegexOptions.IgnoreCase); 75 | Regex regAsuna = new Regex("([0-9]+)/[0-9]+ frames, ([0-9]+.[0-9]+) fps, ([0-9]+.[0-9]+) kb/s", RegexOptions.IgnoreCase); 76 | 77 | string[] status; 78 | 79 | if (regOfficial.Split(line).Length >= 3) 80 | { 81 | status = regOfficial.Split(line); 82 | } 83 | else if (regAsuna.Split(line).Length >= 3) 84 | { 85 | status = regAsuna.Split(line); 86 | } 87 | else 88 | { 89 | Logger.Debug(line); 90 | return; 91 | } 92 | 93 | if (!base.setFrameNumber(status[1], true)) 94 | { 95 | return; 96 | } 97 | 98 | base.setBitrate(status[3], "kb/s"); 99 | 100 | if (!base.setSpeed(status[2])) 101 | { 102 | return; 103 | } 104 | } 105 | 106 | private string BuildCommandline(string extractParam, int numaNode, List vspipeArgs) 107 | { 108 | StringBuilder sb = new StringBuilder(); 109 | sb.Append("/v /c \""); 110 | if (Initializer.Config.hugePage) 111 | { 112 | int targetgigs = (int)Math.Floor((double)Initializer.Config.memoryLimit / 2 / 1024) + 1; 113 | sb.Append("set MIMALLOC_RESERVE_HUGE_OS_PAGES=" + targetgigs + "&& "); 114 | sb.Append("set MIMALLOC_VERBOSE=1&& "); 115 | } 116 | sb.Append("start \"foo\" /b /wait "); 117 | if (!Initializer.Config.singleNuma) 118 | { 119 | sb.Append("/affinity 0xFFFFFFFFFFFFFFFF /node "); 120 | sb.Append(numaNode.ToString()); 121 | } 122 | // 构建vspipe参数 123 | sb.Append(" \"" + vspipePath + "\""); 124 | sb.Append(" --y4m"); 125 | foreach (string arg in vspipeArgs) 126 | { 127 | sb.Append($" --arg \"{arg}\""); 128 | } 129 | sb.Append(" \"" + job.Input + "\""); 130 | sb.Append(" - |"); 131 | 132 | // 构建x265参数 133 | sb.Append(" \"" + X265Path + "\""); 134 | if (Initializer.Config.avx512 && !extractParam.ToLower().Contains("--asm")) 135 | { 136 | sb.Append(" --asm avx512"); 137 | } 138 | sb.Append(" --y4m " + extractParam + " -o"); 139 | sb.Append(" \"" + job.Output + "\" -"); 140 | sb.Append("\""); 141 | 142 | return sb.ToString(); 143 | } 144 | 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Gui/ConfigPanel.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/RpChecker/RpChecker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Newtonsoft.Json; 5 | using OKEGui.JobProcessor; 6 | using OKEGui.Utils; 7 | 8 | namespace OKEGui 9 | { 10 | // To be deprecated. 11 | public class LogBuffer 12 | { 13 | public bool Inf = false; 14 | } 15 | public class RpcResult 16 | { 17 | public List<(int index, double value)> Data = new List<(int index, double value)>(); 18 | public (string src, string opt) FileNamePair; 19 | public LogBuffer Logs = new LogBuffer(); 20 | } 21 | public class RpcResult3 22 | { 23 | public List<(int index, double value, double valueU, double valueV)> Data = new List<(int index, double value, double valueU, double valueV)>(); 24 | public (string src, string opt) FileNamePair; 25 | public LogBuffer Logs = new LogBuffer(); 26 | } 27 | 28 | public class RpChecker : CommandlineJobProcessor 29 | { 30 | public enum RpcStatus { 等待中, 跳过, 错误, 未通过, 通过 }; 31 | 32 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("RpChecker"); 33 | private static readonly double psnr_threashold = 30.0; 34 | private static readonly double psnrUV_threshold = 40.0; 35 | 36 | private RpcStatus status = RpcStatus.等待中; 37 | public RpcStatus Status 38 | { 39 | get 40 | { 41 | finishMre.WaitOne(); 42 | return status; 43 | } 44 | private set 45 | { 46 | status = value; 47 | } 48 | } 49 | 50 | private RpcResult result = new RpcResult(); 51 | private RpcResult3 result3 = new RpcResult3(); 52 | 53 | private const string TemplateFile = ".\\tools\\rpc\\RpcTemplate.vpy"; 54 | private RpcJob job; 55 | private ulong frameCount = 0; 56 | 57 | public RpChecker(RpcJob job) : base() 58 | { 59 | executable = Initializer.Config.vspipePath; 60 | commandLine = $" \"{GetRpcScript(job)}\" ."; 61 | this.job = job; 62 | result.FileNamePair = (job.Input, job.RippedFile); 63 | } 64 | 65 | public string GetRpcScript(RpcJob job) 66 | { 67 | string argsClauses = ""; 68 | foreach (KeyValuePair itr in job.Args) 69 | { 70 | argsClauses += $"setattr(mod, '{itr.Key}', b'{itr.Value}')" + Environment.NewLine; 71 | } 72 | string scriptContent = File.ReadAllText(TemplateFile); 73 | scriptContent = scriptContent 74 | .Replace("OKE:SOURCE_SCRIPT", job.Input) 75 | .Replace("OKE:VIDEO_FILE", job.RippedFile) 76 | .Replace("OKE:VSPIPE_ARGS", argsClauses); 77 | string fileName = job.RippedFile.Replace(Path.GetExtension(job.RippedFile), "_rpc.vpy"); 78 | File.WriteAllText(fileName, scriptContent); 79 | 80 | return fileName; 81 | } 82 | 83 | public override void ProcessLine(string line, StreamType stream) 84 | { 85 | base.ProcessLine(line, stream); 86 | if (line.Contains("Python exception: ")) 87 | { 88 | status = RpcStatus.错误; 89 | finishMre.Set(); 90 | OKETaskException ex = new OKETaskException(Constants.rpcErrorSmr); 91 | ex.Data["RPC_ERROR"] = line.Substring(18); 92 | throw ex; 93 | } 94 | else if (line.Contains("RPCOUT:")) 95 | { 96 | frameCount++; 97 | job.Progress = 100.0 * frameCount / job.TotalFrame; 98 | string[] strNumbers = line.Substring(8).Split(new char[] { ' ' }); 99 | int frameNo = int.Parse(strNumbers[0]); 100 | double psnr = double.Parse(strNumbers[1]), psnrU = psnrUV_threshold, psnrV = psnrUV_threshold; 101 | if (strNumbers.Length > 3) 102 | { 103 | psnrU = double.Parse(strNumbers[2]); 104 | psnrV = double.Parse(strNumbers[3]); 105 | result3.Data.Add((frameNo, psnr, psnrU, psnrV)); 106 | } 107 | else 108 | result.Data.Add((frameNo, psnr)); 109 | if (psnr < psnr_threashold || psnrU < psnrUV_threshold || psnrV < psnrUV_threshold) 110 | { 111 | status = RpcStatus.未通过; 112 | } 113 | } 114 | else if (line.StartsWith("Output ")) 115 | { 116 | if (status == RpcStatus.等待中) 117 | { 118 | status = RpcStatus.通过; 119 | } 120 | finishMre.Set(); 121 | } 122 | } 123 | 124 | public override void waitForFinish() 125 | { 126 | base.waitForFinish(); 127 | job.RpcStatus = Status; 128 | JsonSerializer serializer = new JsonSerializer 129 | { 130 | Formatting = Formatting.None 131 | }; 132 | if (Status != RpcStatus.通过) 133 | { 134 | job.Output = job.FailedRPCOutputFile; 135 | } 136 | job.Output = job.Output.Replace(".rpc", $"-{Status.ToString()}.rpc"); 137 | using (StreamWriter fileWriter = new StreamWriter(job.Output)) 138 | using (JsonTextWriter writer = new JsonTextWriter(fileWriter)) 139 | { 140 | if (result.Data.Count > 0) 141 | serializer.Serialize(writer, new RpcResult[] { result }); 142 | else 143 | serializer.Serialize(writer, new RpcResult3[] { result3 }); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/SystemMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Windows; 5 | using System.Windows.Forms; 6 | using System.Windows.Interop; 7 | 8 | 9 | namespace OKEGui.Utils 10 | { 11 | class SystemMenu 12 | { 13 | #region Native methods 14 | 15 | private const int WM_SYSCOMMAND = 0x112; 16 | private const int MF_STRING = 0x0; 17 | private const int MF_SEPARATOR = 0x800; 18 | 19 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 20 | private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); 21 | 22 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 23 | private static extern bool AppendMenu(IntPtr hMenu, int uFlags, int uIDNewItem, string lpNewItem); 24 | 25 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 26 | private static extern bool InsertMenu(IntPtr hMenu, int uPosition, int uFlags, int uIDNewItem, string lpNewItem); 27 | 28 | #endregion Native methods 29 | 30 | #region Private data 31 | 32 | private Window window; 33 | private IntPtr hSysMenu; 34 | private bool isHandleCreated; 35 | private int lastId; 36 | private List actions = new List(); 37 | private List pendingCommands; 38 | 39 | #endregion Private data 40 | 41 | #region Constructors 42 | 43 | /// 44 | /// Initializes a new instance of the class for the specified 45 | /// . 46 | /// 47 | /// The window for which the system menu is expanded. 48 | public SystemMenu(Window window) 49 | { 50 | isHandleCreated = false; 51 | this.window = window; 52 | this.window.Loaded += OnHandleCreated; 53 | } 54 | 55 | #endregion Constructors 56 | 57 | #region Public methods 58 | 59 | /// 60 | /// Adds a command to the system menu. 61 | /// 62 | /// The displayed command text. 63 | /// The action that is executed when the user clicks on the command. 64 | /// Indicates whether a separator is inserted before the command. 65 | public void AddCommand(string text, Action action, bool separatorBeforeCommand) 66 | { 67 | var id = ++lastId; 68 | if (!isHandleCreated) 69 | { 70 | // The form is not yet created, queue the command for later addition 71 | if (pendingCommands == null) 72 | { 73 | pendingCommands = new List(); 74 | } 75 | pendingCommands.Add(new CommandInfo 76 | { 77 | Id = id, 78 | Text = text, 79 | Action = action, 80 | Separator = separatorBeforeCommand 81 | }); 82 | } 83 | else 84 | { 85 | // The form is created, add the command now 86 | if (separatorBeforeCommand) 87 | { 88 | AppendMenu(hSysMenu, MF_SEPARATOR, 0, ""); 89 | } 90 | AppendMenu(hSysMenu, MF_STRING, id, text); 91 | } 92 | actions.Add(action); 93 | } 94 | 95 | /// 96 | /// Tests a window message for system menu commands and executes the associated action. This 97 | /// method must be called from within the Form's overridden WndProc method because it is not 98 | /// publicly accessible. 99 | /// 100 | /// The window message to test. 101 | public IntPtr HandleMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 102 | { 103 | // This method is kept short and simple to allow inlining (verified) for improving 104 | // performance (unverified). It will be called for every single message that is sent to 105 | // the window. 106 | if (msg == WM_SYSCOMMAND) 107 | { 108 | OnSysCommandMessage(wParam, ref handled); 109 | } 110 | return IntPtr.Zero; 111 | } 112 | 113 | #endregion Public methods 114 | 115 | #region Private methods 116 | 117 | private void OnHandleCreated(object sender, EventArgs args) 118 | { 119 | isHandleCreated = true; 120 | window.Loaded -= OnHandleCreated; 121 | var hwnd = new WindowInteropHelper(this.window).Handle; 122 | hSysMenu = GetSystemMenu(hwnd, false); 123 | 124 | // Add all queued commands now 125 | if (pendingCommands != null) 126 | { 127 | foreach (var command in pendingCommands) 128 | { 129 | if (command.Separator) 130 | { 131 | AppendMenu(hSysMenu, MF_SEPARATOR, 0, ""); 132 | } 133 | AppendMenu(hSysMenu, MF_STRING, command.Id, command.Text); 134 | } 135 | pendingCommands = null; 136 | } 137 | } 138 | 139 | private void OnSysCommandMessage(IntPtr WParam, ref bool handled) 140 | { 141 | if ((long)WParam > 0 && (long)WParam <= lastId) 142 | { 143 | handled = true; 144 | actions[(int)WParam - 1](); 145 | } 146 | } 147 | 148 | #endregion Private methods 149 | 150 | #region Classes 151 | 152 | private class CommandInfo 153 | { 154 | public int Id { get; set; } 155 | public string Text { get; set; } 156 | public Action Action { get; set; } 157 | public bool Separator { get; set; } 158 | } 159 | 160 | #endregion Classes 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/CRC32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | using System.Threading.Tasks; 5 | 6 | namespace OKEGui.Utils 7 | { 8 | /// 9 | /// Implementation of CRC-32. 10 | /// This class supports several convenient static methods returning the CRC as UInt32. 11 | /// 12 | public class CRC32 : HashAlgorithm 13 | { 14 | private uint _currentCrc; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public CRC32() 20 | { 21 | #if !NETCORE 22 | HashSizeValue = 32; 23 | #endif 24 | } 25 | 26 | /// 27 | /// Computes CRC-32 from multiple buffers. 28 | /// Call this method multiple times to chain multiple buffers. 29 | /// 30 | /// 31 | /// Initial CRC value for the algorithm. It is zero for the first buffer. 32 | /// Subsequent buffers should have their initial value set to CRC value returned by previous call to this method. 33 | /// 34 | /// Input buffer with data to be checksummed. 35 | /// Offset of the input data within the buffer. 36 | /// Length of the input data in the buffer. 37 | /// Accumulated CRC-32 of all buffers processed so far. 38 | public static uint Append(uint initial, byte[] input, int offset, int length) 39 | { 40 | if (input == null) 41 | throw new ArgumentNullException(); 42 | if (offset < 0 || length < 0 || offset + length > input.Length) 43 | throw new ArgumentOutOfRangeException("Selected range is outside the bounds of the input array"); 44 | return AppendInternal(initial, input, offset, length); 45 | } 46 | 47 | /// 48 | /// Computes CRC-3C from multiple buffers. 49 | /// Call this method multiple times to chain multiple buffers. 50 | /// 51 | /// 52 | /// Initial CRC value for the algorithm. It is zero for the first buffer. 53 | /// Subsequent buffers should have their initial value set to CRC value returned by previous call to this method. 54 | /// 55 | /// Input buffer containing data to be checksummed. 56 | /// Accumulated CRC-32 of all buffers processed so far. 57 | public static uint Append(uint initial, byte[] input) 58 | { 59 | if (input == null) 60 | throw new ArgumentNullException(); 61 | return AppendInternal(initial, input, 0, input.Length); 62 | } 63 | 64 | /// 65 | /// Computes CRC-32 from input buffer. 66 | /// 67 | /// Input buffer with data to be checksummed. 68 | /// Offset of the input data within the buffer. 69 | /// Length of the input data in the buffer. 70 | /// CRC-32 of the data in the buffer. 71 | public static uint Compute(byte[] input, int offset, int length) 72 | { 73 | return Append(0, input, offset, length); 74 | } 75 | 76 | /// 77 | /// Computes CRC-32 from input buffer. 78 | /// 79 | /// Input buffer containing data to be checksummed. 80 | /// CRC-32 of the buffer. 81 | public static uint Compute(byte[] input) 82 | { 83 | return Append(0, input); 84 | } 85 | 86 | /// 87 | /// Resets internal state of the algorithm. Used internally. 88 | /// 89 | public override void Initialize() 90 | { 91 | _currentCrc = 0; 92 | } 93 | 94 | /// 95 | /// Appends CRC-32 from given buffer 96 | /// 97 | protected override void HashCore(byte[] input, int offset, int length) 98 | { 99 | _currentCrc = AppendInternal(_currentCrc, input, offset, length); 100 | } 101 | 102 | /// 103 | /// Computes CRC-32 from 104 | /// 105 | protected override byte[] HashFinal() 106 | { 107 | // Crc32 by dariogriffo uses big endian, so, we need to be compatible and return big endian too 108 | return new[] { (byte)(_currentCrc >> 24), (byte)(_currentCrc >> 16), (byte)(_currentCrc >> 8), (byte)_currentCrc }; 109 | } 110 | 111 | private static readonly SafeProxy Proxy = new SafeProxy(); 112 | 113 | private static uint AppendInternal(uint initial, byte[] input, int offset, int length) 114 | { 115 | return length > 0 ? SafeProxy.Append(initial, input, offset, length) : initial; 116 | } 117 | 118 | /// 119 | /// Calculate file's CRC32 value 120 | /// 121 | /// 122 | /// 123 | public static async Task FileCRC(string filePath) 124 | { 125 | if (!File.Exists(filePath)) return 0; 126 | var hash = new CRC32(); 127 | const int capacity = 1024 * 1024; 128 | var buffer = new byte[capacity]; 129 | using (var file = File.OpenRead(filePath)) 130 | { 131 | int cbSize; 132 | do 133 | { 134 | cbSize = await file.ReadAsync(buffer, 0, capacity).ConfigureAwait(false); 135 | if (cbSize > 0) hash.HashCore(buffer, 0, cbSize); 136 | } while (cbSize > 0); 137 | return hash._currentCrc; 138 | } 139 | } 140 | 141 | /// 142 | /// Calculate file's CRC32 string value 143 | /// 144 | /// 145 | /// 146 | public static string ComputeChecksumString(string filePath) 147 | { 148 | uint numberInUint = FileCRC(filePath).Result; 149 | return numberInUint.ToString("X8"); 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/Video/VSPipeProcessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading; 7 | using System.Windows.Forms; 8 | using OKEGui.JobProcessor; 9 | using OKEGui.Utils; 10 | 11 | namespace OKEGui 12 | { 13 | public class VSPipeProcessor : CommandlineJobProcessor 14 | { 15 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("VSPipeProcessor"); 16 | private VSVideoInfo videoInfo; 17 | private bool videoInfoOk; 18 | private string lastStderrLine; 19 | private ManualResetEvent retrieved = new ManualResetEvent(false); 20 | private VideoJob job; 21 | 22 | public VSPipeProcessor(VideoInfoJob j) : base() 23 | { 24 | // 获取VSPipe路径 25 | executable = Initializer.Config.vspipePath; 26 | videoInfo = new VSVideoInfo(); 27 | videoInfoOk = false; 28 | job = j.vJob; 29 | 30 | StringBuilder sb = new StringBuilder(); 31 | 32 | sb.Append("--info"); 33 | foreach (string arg in j.Args) 34 | { 35 | sb.Append($" --arg \"{arg}\""); 36 | } 37 | sb.Append(" \"" + j.Input + "\""); 38 | sb.Append(" -"); 39 | 40 | commandLine = sb.ToString(); 41 | } 42 | 43 | public override void ProcessLine(string line, StreamType stream) 44 | { 45 | Logger.Debug(line); 46 | if (stream == StreamType.Stderr) 47 | lastStderrLine = line; 48 | Regex rWidth = new Regex("Width: ([0-9]+)"); 49 | Regex rHeight = new Regex("Height: ([0-9]+)"); 50 | Regex rFrames = new Regex("Frames: ([0-9]+)"); 51 | Regex rFPS = new Regex("FPS: ([0-9]+)/([0-9]+) \\(([0-9]+.[0-9]+) fps\\)"); 52 | Regex rFormatName = new Regex("Format Name: ([a-zA-Z0-9]+)"); 53 | Regex rColorFamily = new Regex("Color Family: ([a-zA-Z]+)"); 54 | Regex rBits = new Regex("Bits: ([0-9]+)"); 55 | Regex rlwiProgress = new Regex("Creating lwi index file ([0-9]+)%"); 56 | 57 | if (line.Contains("Python exception: ")) 58 | { 59 | OKETaskException ex = new OKETaskException(Constants.vpyErrorSmr); 60 | ex.progress = 0.0; 61 | ex.Data["VPY_ERROR"] = line.Substring(18); 62 | throw ex; 63 | } 64 | else if (line.Contains("Width")) 65 | { 66 | var s = rWidth.Split(line); 67 | int w; 68 | int.TryParse(s[1], out w); 69 | if (w > 0) 70 | { 71 | videoInfo.width = w; 72 | } 73 | } 74 | else if (line.Contains("Height")) 75 | { 76 | var s = rHeight.Split(line); 77 | int h; 78 | int.TryParse(s[1], out h); 79 | if (h > 0) 80 | { 81 | videoInfo.height = h; 82 | } 83 | } 84 | else if (line.Contains("Frames")) 85 | { 86 | var s = rFrames.Split(line); 87 | int f; 88 | int.TryParse(s[1], out f); 89 | if (f > 0) 90 | { 91 | videoInfo.numFrames = f; 92 | } 93 | } 94 | else if (line.Contains("FPS")) 95 | { 96 | if (line.Contains("Variable")) 97 | { 98 | throw new Exception("VFR output not supported, even for VFR jobs."); 99 | } 100 | var s = rFPS.Split(line); 101 | 102 | int n; 103 | int.TryParse(s[1], out n); 104 | if (n > 0) 105 | { 106 | videoInfo.fpsNum = n; 107 | } 108 | 109 | int.TryParse(s[2], out n); 110 | if (n > 0) 111 | { 112 | videoInfo.fpsDen = n; 113 | } 114 | 115 | double f; 116 | double.TryParse(s[3], out f); 117 | if (f > 0) 118 | { 119 | videoInfo.fps = f; 120 | } 121 | } 122 | else if (line.Contains("Format Name:")) 123 | { 124 | var s = rFormatName.Split(line); 125 | videoInfo.format.name = s[1]; 126 | } 127 | else if (line.Contains("Color Family")) 128 | { 129 | var s = rColorFamily.Split(line); 130 | videoInfo.format.colorFamilyName = s[1]; 131 | } 132 | else if (line.Contains("Bits")) 133 | { 134 | var s = rBits.Split(line); 135 | int w; 136 | int.TryParse(s[1], out w); 137 | if (w > 0) 138 | { 139 | videoInfo.format.bitsPerSample = w; 140 | } 141 | 142 | // 假设到这里已经获取完毕了 143 | videoInfoOk = true; 144 | retrieved.Set(); 145 | } 146 | else if (line.Contains("SubSampling")) 147 | { 148 | //目前还没有要处理subsampling的 149 | } 150 | else if (rlwiProgress.IsMatch(line)) 151 | { 152 | var s = rlwiProgress.Split(line); 153 | int p; 154 | int.TryParse(s[1], out p); 155 | job.Progress = p; 156 | } 157 | } 158 | 159 | public override void waitForFinish() 160 | { 161 | retrieved.WaitOne(); 162 | } 163 | 164 | protected override void onExited(int exitCode) 165 | { 166 | if (exitCode != 0) 167 | { 168 | if (lastStderrLine == "") 169 | lastStderrLine = "exitcode is " + exitCode.ToString(); 170 | retrieved.Set(); 171 | } 172 | } 173 | 174 | public VSVideoInfo VideoInfo 175 | { 176 | get { 177 | retrieved.WaitOne(); 178 | if (!videoInfoOk) 179 | throw new Exception("vspipe -i failed: " + lastStderrLine); 180 | return videoInfo; 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/JobProcessor/Video/CommandlineVideoEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using OKEGui.Utils; 4 | using OKEGui.JobProcessor; 5 | using System.Diagnostics; 6 | 7 | namespace OKEGui 8 | { 9 | public delegate void EncoderOutputCallback(string line, int type); 10 | 11 | public abstract class CommandlineVideoEncoder : CommandlineJobProcessor 12 | { 13 | #region variables 14 | 15 | public ulong NumberOfFrames { get; protected set; } 16 | private ulong currentFrameNumber; 17 | protected long fps_n = 0, fps_d = 0; 18 | 19 | protected bool usesSAR = false; 20 | protected double speed; 21 | protected double bitrate; 22 | protected string unit; 23 | 24 | protected VideoJob job; 25 | 26 | #endregion variables 27 | 28 | #region helper methods 29 | 30 | /// 31 | /// tries to open the video source and gets the number of frames from it, or 32 | /// exits with an error 33 | /// 34 | /// the AviSynth script 35 | /// return parameter for all errors 36 | /// true if the file could be opened, false if not 37 | protected void getInputProperties(VideoJob job) 38 | { 39 | //VapourSynthHelper vsHelper = new VapourSynthHelper(); 40 | //vsHelper.LoadScriptFile(job.Input); 41 | VSPipeInfo vsHelper = new VSPipeInfo(job); 42 | fps_n = vsHelper.FpsNum; 43 | fps_d = vsHelper.FpsDen; 44 | NumberOfFrames = (ulong)vsHelper.TotalFreams; 45 | job.NumberOfFrames = NumberOfFrames; 46 | if (fps_n == job.FpsNum && fps_d == job.FpsDen) return; 47 | if (job.Vfr) 48 | { 49 | job.FpsNum = (uint)fps_n; 50 | job.FpsDen = (uint)fps_d; 51 | job.Fps = (fps_n + 0.0) / fps_d; 52 | return; 53 | } 54 | OKETaskException ex = new OKETaskException(Constants.fpsMismatchSmr); 55 | ex.progress = 0.0; 56 | ex.Data["SRC_FPS"] = ((double)job.FpsNum / job.FpsDen).ToString("F3"); 57 | ex.Data["DST_FPS"] = ((double)fps_n / fps_d).ToString("F3"); 58 | throw ex; 59 | } 60 | 61 | #endregion helper methods 62 | 63 | protected bool setFrameNumber(string frameString, bool isUpdateSpeed = false) 64 | { 65 | int currentFrame; 66 | if (int.TryParse(frameString, out currentFrame)) 67 | { 68 | if (currentFrame < 0) 69 | { 70 | currentFrameNumber = 0; 71 | return false; 72 | } 73 | else 74 | { 75 | currentFrameNumber = (ulong)currentFrame; 76 | } 77 | 78 | Update(); 79 | return true; 80 | } 81 | return false; 82 | } 83 | 84 | protected bool setSpeed(string speed, string unit = "fps") 85 | { 86 | double fps, factor = 1; 87 | if (unit == "fpm") 88 | factor = 60; 89 | if (double.TryParse(speed, out fps)) 90 | { 91 | if (fps > 0) 92 | { 93 | this.speed = fps / factor; 94 | } 95 | else 96 | { 97 | this.speed = 0; 98 | } 99 | 100 | Update(); 101 | return true; 102 | } 103 | 104 | return false; 105 | } 106 | 107 | protected bool setBitrate(string bitrate, string unit) 108 | { 109 | double rate; 110 | this.unit = unit; 111 | if (double.TryParse(bitrate, out rate)) 112 | { 113 | if (rate > 0) 114 | { 115 | this.bitrate = rate; 116 | } 117 | else 118 | { 119 | this.bitrate = 0; 120 | } 121 | 122 | Update(); 123 | return true; 124 | } 125 | 126 | return false; 127 | } 128 | 129 | protected void Update() 130 | { 131 | if (speed == 0) 132 | { 133 | job.TimeRemain = TimeSpan.FromDays(30); 134 | } 135 | else 136 | { 137 | job.TimeRemain = TimeSpan.FromSeconds((double)(NumberOfFrames - currentFrameNumber) / speed); 138 | } 139 | 140 | job.Speed = speed.ToString("0.00") + " fps"; 141 | job.Progress = (double)currentFrameNumber / (double)NumberOfFrames * 100; 142 | 143 | if (bitrate == 0) 144 | { 145 | job.BitRate = "未知"; 146 | } 147 | else 148 | { 149 | job.BitRate = bitrate.ToString("0.00") + " " + unit; 150 | } 151 | 152 | // su.NbFramesDone = currentFrameNumber; 153 | } 154 | 155 | public static String HumanReadableFilesize(double size, int digit) 156 | { 157 | String[] units = new String[] { "B", "KB", "MB", "GB", "TB", "PB" }; 158 | double mod = 1024.0; 159 | int i = 0; 160 | while (size >= mod) 161 | { 162 | size /= mod; 163 | i++; 164 | } 165 | 166 | return Math.Round(size * Math.Pow(10, digit)) / Math.Pow(10, digit) + " " + units[i]; 167 | } 168 | 169 | protected void encodeFinish(ulong reportedFrames) 170 | { 171 | if (reportedFrames < NumberOfFrames) 172 | { 173 | OKETaskException ex = new OKETaskException(Constants.vsCrashSmr); 174 | throw ex; 175 | } 176 | job.TimeRemain = TimeSpan.Zero; 177 | job.Progress = 100; 178 | job.Status = "压制完成"; 179 | 180 | // TODO: 计算最终码率 181 | // 这里显示文件最终大小 182 | FileInfo vinfo = new FileInfo(job.Output); 183 | job.BitRate = HumanReadableFilesize(vinfo.Length, 2); 184 | 185 | base.SetFinish(); 186 | } 187 | 188 | public void AppendParameter(string param) 189 | { 190 | int pos = commandLine.Length - 2; 191 | commandLine = commandLine.Insert(pos, param + " "); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/Initializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.CompilerServices; 8 | using System.Net; 9 | using System.Reflection; 10 | using System.Windows; 11 | using Newtonsoft.Json; 12 | using NLog; 13 | using NLog.Config; 14 | using NLog.Targets; 15 | 16 | namespace OKEGui.Utils 17 | { 18 | public class OKEGuiConfig : INotifyPropertyChanged, ICloneable 19 | { 20 | public event PropertyChangedEventHandler PropertyChanged; 21 | private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") 22 | { 23 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 24 | } 25 | 26 | public object Clone() 27 | { 28 | return this.MemberwiseClone(); 29 | } 30 | 31 | private string _vspipePath; 32 | public string vspipePath 33 | { 34 | get => _vspipePath; 35 | set 36 | { 37 | _vspipePath = value; 38 | NotifyPropertyChanged(); 39 | } 40 | } 41 | 42 | private string _logLevel = "DEBUG"; 43 | public string logLevel 44 | { 45 | get => _logLevel; 46 | set 47 | { 48 | _logLevel = value; 49 | NotifyPropertyChanged(); 50 | } 51 | } 52 | 53 | private bool _singleNuma = false; 54 | public bool singleNuma 55 | { 56 | get => _singleNuma; 57 | set 58 | { 59 | _singleNuma = value; 60 | NotifyPropertyChanged(); 61 | } 62 | } 63 | 64 | private string _rpCheckerPath; 65 | public string rpCheckerPath 66 | { 67 | get => _rpCheckerPath; 68 | set 69 | { 70 | _rpCheckerPath = value; 71 | NotifyPropertyChanged(); 72 | } 73 | } 74 | 75 | private bool _avx512; 76 | public bool avx512 77 | { 78 | get => _avx512; 79 | set 80 | { 81 | _avx512 = value; 82 | NotifyPropertyChanged(); 83 | } 84 | } 85 | 86 | private bool _hugePage; 87 | public bool hugePage 88 | { 89 | get => _hugePage; 90 | set 91 | { 92 | _hugePage = value; 93 | NotifyPropertyChanged(); 94 | } 95 | } 96 | 97 | private int _memoryLimit; 98 | public int memoryLimit 99 | { 100 | get => _memoryLimit; 101 | set 102 | { 103 | _memoryLimit = value; 104 | NotifyPropertyChanged(); 105 | } 106 | } 107 | 108 | private int _memoryTotal; 109 | public int memoryTotal 110 | { 111 | get => _memoryTotal; 112 | set 113 | { 114 | _memoryTotal = value; 115 | NotifyPropertyChanged(); 116 | } 117 | } 118 | 119 | private string _stripCommonPathCompnents = "BDBOX/BDROM/BD/BDMV/STREAM/BD_VIDEO"; 120 | public string stripCommonPathCompnents 121 | { 122 | get => _stripCommonPathCompnents; 123 | set 124 | { 125 | _stripCommonPathCompnents = value; 126 | NotifyPropertyChanged(); 127 | } 128 | } 129 | } 130 | 131 | static class Initializer 132 | { 133 | private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); 134 | public static OKEGuiConfig Config = new OKEGuiConfig(); 135 | 136 | public static OKEGuiConfig LoadConfig() 137 | { 138 | FileInfo configInfo = new FileInfo(Constants.configFile); 139 | if (configInfo.Exists) 140 | { 141 | string strConfig = File.ReadAllText(Constants.configFile); 142 | try 143 | { 144 | Config = JsonConvert.DeserializeObject(strConfig); 145 | } 146 | catch (Exception) 147 | { 148 | MessageBoxResult result = MessageBox.Show( 149 | "程序目录下的OKEGuiConfig.json已损坏。点击Yes重置所有之前设置,点击No退出程序以人工修复。", 150 | "无法读取配置文件", 151 | MessageBoxButton.YesNo, 152 | MessageBoxImage.Error); 153 | 154 | if (result == MessageBoxResult.No) 155 | { 156 | Environment.Exit(0); 157 | } 158 | } 159 | } 160 | return Config; 161 | } 162 | 163 | public static bool WriteConfig() 164 | { 165 | JsonSerializer serializer = new JsonSerializer 166 | { 167 | Formatting = Formatting.Indented 168 | }; 169 | using (StreamWriter fileWriter = new StreamWriter(Constants.configFile)) 170 | using (JsonTextWriter writer = new JsonTextWriter(fileWriter)) 171 | { 172 | writer.Indentation = 4; 173 | serializer.Serialize(writer, Config); 174 | } 175 | return true; 176 | } 177 | 178 | public static bool ConfigLogger() 179 | { 180 | string time = DateTime.Now.ToString("yyyyMMdd-HHmm"); 181 | string pid = Process.GetCurrentProcess().Id.ToString("X"); 182 | LogLevel level = LogLevel.FromString(Config.logLevel); // default is DEBUG 183 | 184 | LoggingConfiguration config = new LoggingConfiguration(); 185 | 186 | FileTarget logfile = new FileTarget("logfile") { FileName = $"log\\OKE_{time}_{pid}.log" }; 187 | DebuggerTarget logconsole = new DebuggerTarget("logconsole"); 188 | 189 | // Rules for mapping loggers to targets 190 | config.AddRule(LogLevel.Trace, LogLevel.Fatal, logconsole); 191 | config.AddRule(level, LogLevel.Fatal, logfile); 192 | 193 | // Apply config 194 | LogManager.Configuration = config; 195 | return true; 196 | } 197 | 198 | public static void ClearOldLogs() 199 | { 200 | if (Directory.Exists("log")) 201 | { 202 | Directory.GetFiles("log") 203 | .Select(f => new FileInfo(f)) 204 | .Where(f => f.LastWriteTime < DateTime.Now.AddMonths(-6) || (f.LastWriteTime < DateTime.Now.AddDays(-7) && f.Length < 1024)) 205 | .ToList() 206 | .ForEach(f => f.Delete()); 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Worker/WorkerManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | 5 | 6 | namespace OKEGui.Worker 7 | { 8 | public enum WorkerType 9 | { 10 | Normal, 11 | Temporary, 12 | } 13 | 14 | public struct WorkerArgs 15 | { 16 | public string Name; 17 | public WorkerType RunningType; 18 | public TaskManager taskManager; 19 | public BackgroundWorker bgWorker; 20 | public int numaNode; 21 | } 22 | 23 | // 类似MeGUI的worker概念。每一个task(以及分离的Job)由worker来执行。多个Worker允许多开处理。 24 | // Worker执行Task的具体实现,见ExecuteTaskService里的WorkerDoWork() 25 | public partial class WorkerManager 26 | { 27 | public MainWindow MainWindow; 28 | public TaskManager tm; 29 | 30 | private List workerList; 31 | private ConcurrentDictionary workerType; 32 | 33 | // dummy object for locking. 34 | private object o = new object(); 35 | 36 | private ConcurrentDictionary bgworkerlist; 37 | private int tempCounter; 38 | public bool IsRunning { get; protected set; } 39 | 40 | public delegate void Callback(MainWindow window); 41 | 42 | public Callback AfterFinish = null; 43 | 44 | public WorkerManager(MainWindow mainWindow, TaskManager taskManager) 45 | { 46 | workerList = new List(); 47 | bgworkerlist = new ConcurrentDictionary(); 48 | workerType = new ConcurrentDictionary(); 49 | MainWindow = mainWindow; 50 | tm = taskManager; 51 | IsRunning = false; 52 | tempCounter = 0; 53 | } 54 | 55 | public void AddTask(TaskDetail detail) 56 | { 57 | tm.AddTask(detail); 58 | if (IsRunning) 59 | { 60 | foreach (string worker in workerList) 61 | { 62 | if (!bgworkerlist.ContainsKey(worker)) 63 | { 64 | CreateWorker(worker); 65 | StartWorker(worker); 66 | break; 67 | } 68 | } 69 | } 70 | } 71 | 72 | public bool Start() 73 | { 74 | lock (o) 75 | { 76 | if (workerList.Count == 0) 77 | { 78 | return false; 79 | } 80 | 81 | int activeTaskCount = tm.GetActiveTaskCount(); 82 | IsRunning = true; 83 | 84 | foreach (string worker in workerList) 85 | { 86 | if (activeTaskCount == 0) 87 | { 88 | break; 89 | } 90 | if (!bgworkerlist.ContainsKey(worker)) 91 | { 92 | CreateWorker(worker); 93 | StartWorker(worker); 94 | activeTaskCount--; 95 | } 96 | } 97 | 98 | return true; 99 | } 100 | } 101 | 102 | public bool CreateWorker(string name) 103 | { 104 | BackgroundWorker worker = new BackgroundWorker(); 105 | worker.WorkerSupportsCancellation = true; 106 | worker.WorkerReportsProgress = true; 107 | worker.DoWork += new DoWorkEventHandler(WorkerDoWork); 108 | worker.ProgressChanged += new ProgressChangedEventHandler(WorkerProgressChanged); 109 | worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(WorkerCompleted); 110 | 111 | return bgworkerlist.TryAdd(name, worker); 112 | } 113 | 114 | public bool StartWorker(string name) 115 | { 116 | if (!bgworkerlist.ContainsKey(name)) 117 | { 118 | return false; 119 | } 120 | 121 | var worker = bgworkerlist[name]; 122 | 123 | WorkerArgs args; 124 | args.Name = name; 125 | args.RunningType = workerType[name]; 126 | args.taskManager = tm; 127 | args.bgWorker = worker; 128 | args.numaNode = NumaNode.NextNuma(); 129 | Logger.Trace(name + "所拥有的Numa Node编号是" + args.numaNode.ToString()); 130 | 131 | worker.RunWorkerAsync(args); 132 | return true; 133 | } 134 | 135 | public int GetWorkerCount() 136 | { 137 | lock (o) 138 | { 139 | return workerList.Count; 140 | } 141 | } 142 | 143 | public string AddTempWorker() 144 | { 145 | // 临时Worker只运行一次任务 146 | tempCounter++; 147 | string name = "Temp-" + tempCounter.ToString(); 148 | 149 | lock (o) 150 | { 151 | workerList.Add(name); 152 | workerType.TryAdd(name, WorkerType.Temporary); 153 | } 154 | 155 | if (IsRunning) 156 | { 157 | CreateWorker(name); 158 | StartWorker(name); 159 | } 160 | 161 | return name; 162 | } 163 | 164 | public bool AddWorker(string name) 165 | { 166 | lock (o) 167 | { 168 | if (workerList.Contains(name)) 169 | { 170 | return false; 171 | } 172 | 173 | workerList.Add(name); 174 | workerType.TryAdd(name, WorkerType.Normal); 175 | } 176 | 177 | if (IsRunning) 178 | { 179 | CreateWorker(name); 180 | StartWorker(name); 181 | } 182 | 183 | return true; 184 | } 185 | 186 | public bool DeleteWorker(string name) 187 | { 188 | if (IsRunning) 189 | { 190 | return false; 191 | } 192 | 193 | lock (o) 194 | { 195 | bgworkerlist.TryRemove(name, out BackgroundWorker v); 196 | workerType.TryRemove(name, out WorkerType w); 197 | return workerList.Remove(name); 198 | } 199 | } 200 | 201 | public void StopWorker(string name) 202 | { 203 | // TODO 204 | IsRunning = false; 205 | 206 | if (bgworkerlist.ContainsKey(name)) 207 | { 208 | if (bgworkerlist[name].IsBusy) 209 | { 210 | bgworkerlist[name].CancelAsync(); 211 | } 212 | } 213 | } 214 | 215 | private void WorkerProgressChanged(object sender, ProgressChangedEventArgs e) 216 | { 217 | } 218 | 219 | private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 220 | { 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Task/TaskStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using static OKEGui.RpChecker; 4 | 5 | namespace OKEGui 6 | { 7 | /// 8 | /// 继承自INotifyPropertyChanged接口来实现数据的实时显示 9 | /// 每一个域都与MainWindow里的显示挂钩。 10 | /// 11 | public class TaskStatus : INotifyPropertyChanged 12 | { 13 | /// 14 | /// 任务是否启用 15 | /// 16 | private bool isEnabled; 17 | public bool IsEnabled 18 | { 19 | get { return isEnabled; } 20 | set 21 | { 22 | isEnabled = value; 23 | OnPropertyChanged(new PropertyChangedEventArgs("IsEnabled")); 24 | } 25 | } 26 | 27 | /// 28 | /// 任务名称 29 | /// 30 | private string taskName; 31 | public string TaskName 32 | { 33 | get { return taskName; } 34 | set 35 | { 36 | taskName = value; 37 | OnPropertyChanged(new PropertyChangedEventArgs("TaskName")); 38 | } 39 | } 40 | 41 | /// 42 | /// 输入文件 43 | /// 44 | private string inputFile; 45 | public string InputFile 46 | { 47 | get { return inputFile; } 48 | set 49 | { 50 | inputFile = value; 51 | OnPropertyChanged(new PropertyChangedEventArgs("InputFile")); 52 | } 53 | } 54 | 55 | /// 56 | /// 章节信息 57 | /// 58 | private ChapterStatus chapterStatus; 59 | public ChapterStatus ChapterStatus 60 | { 61 | get => chapterStatus; 62 | set 63 | { 64 | chapterStatus = value; 65 | OnPropertyChanged(new PropertyChangedEventArgs("ChapterStatus")); 66 | } 67 | } 68 | 69 | /// 70 | /// 输出文件 71 | /// 72 | private string outputFile; 73 | public string OutputFile 74 | { 75 | get { return outputFile; } 76 | set 77 | { 78 | outputFile = value; 79 | OnPropertyChanged(new PropertyChangedEventArgs("OutputFile")); 80 | } 81 | } 82 | 83 | /// 84 | /// 任务总体进度 85 | /// 86 | 87 | public enum TaskProgress : int { WAITING = 0, RUNNING, ERROR, FINISHED } 88 | public TaskProgress Progress; 89 | 90 | 91 | /// 92 | /// 任务执行状态 93 | /// 94 | private string currentStatus; 95 | public string CurrentStatus 96 | { 97 | get { return currentStatus; } 98 | set 99 | { 100 | currentStatus = value; 101 | OnPropertyChanged(new PropertyChangedEventArgs("CurrentStatus")); 102 | } 103 | } 104 | 105 | /// 106 | /// 当前进度(子任务进度) 107 | /// 108 | private double progressValue; 109 | public double ProgressValue 110 | { 111 | get { return progressValue; } 112 | set 113 | { 114 | progressValue = value; 115 | // Indetermate 116 | if (progressValue < 0) 117 | { 118 | ProgressStr = ""; 119 | IsUnKnowProgress = true; 120 | } 121 | else 122 | { 123 | ProgressStr = progressValue.ToString("0.00") + "%"; 124 | IsUnKnowProgress = false; 125 | } 126 | 127 | OnPropertyChanged(new PropertyChangedEventArgs("ProgressValue")); 128 | } 129 | } 130 | 131 | private string progressStr; 132 | public string ProgressStr 133 | { 134 | get { return progressStr; } 135 | set 136 | { 137 | progressStr = value; 138 | OnPropertyChanged(new PropertyChangedEventArgs("ProgressStr")); 139 | } 140 | } 141 | 142 | /// 143 | /// 任务进度状态(进度未知) 144 | /// 145 | private bool isUnKnowProgress; 146 | public bool IsUnKnowProgress 147 | { 148 | get { return isUnKnowProgress; } 149 | set 150 | { 151 | isUnKnowProgress = value; 152 | OnPropertyChanged(new PropertyChangedEventArgs("IsUnKnowProgress")); 153 | } 154 | } 155 | 156 | /// 157 | /// 任务速度 158 | /// 159 | private string speed; 160 | public string Speed 161 | { 162 | get { return speed; } 163 | set 164 | { 165 | speed = value; 166 | OnPropertyChanged(new PropertyChangedEventArgs("Speed")); 167 | } 168 | } 169 | 170 | /// 171 | /// 码率 172 | /// 173 | private string bitrate; 174 | public string BitRate 175 | { 176 | get { return bitrate; } 177 | set 178 | { 179 | bitrate = value; 180 | OnPropertyChanged(new PropertyChangedEventArgs("BitRate")); 181 | } 182 | } 183 | 184 | /// 185 | /// 剩余时间 186 | /// 187 | private TimeSpan timeRemain; 188 | public TimeSpan TimeRemain 189 | { 190 | get { return timeRemain; } 191 | set 192 | { 193 | timeRemain = value; 194 | TimeRemainStr = (int)value.TotalHours + value.ToString(@"\:mm\:ss"); 195 | if (value.TotalHours > 24.0*7) 196 | { 197 | TimeRemainStr = "大于一周"; 198 | } 199 | } 200 | } 201 | 202 | private string timeRemainStr; 203 | public string TimeRemainStr 204 | { 205 | get { return timeRemainStr; } 206 | set 207 | { 208 | timeRemainStr = value; 209 | OnPropertyChanged(new PropertyChangedEventArgs("TimeRemainStr")); 210 | } 211 | } 212 | 213 | /// 214 | /// 正在执行的工作单元名称 215 | /// 216 | private string workerName; 217 | public string WorkerName 218 | { 219 | get { return workerName; } 220 | set 221 | { 222 | workerName = value; 223 | OnPropertyChanged(new PropertyChangedEventArgs("WorkerName")); 224 | } 225 | } 226 | 227 | /// 228 | /// 花屏检测信息 229 | /// 230 | private RpcStatus rpcStatus; 231 | public string RpcStatus 232 | { 233 | get { return rpcStatus.ToString(); } 234 | set 235 | { 236 | rpcStatus = (RpcStatus)Enum.Parse(typeof(RpcStatus), value); 237 | OnPropertyChanged(new PropertyChangedEventArgs("RpcStatus")); 238 | OnPropertyChanged(new PropertyChangedEventArgs("RpcButtonEnabled")); 239 | } 240 | } 241 | 242 | private string rpcOutput; 243 | public string RpcOutput 244 | { 245 | get { return rpcOutput; } 246 | set 247 | { 248 | rpcOutput = value; 249 | OnPropertyChanged(new PropertyChangedEventArgs("RpcOutput")); 250 | } 251 | } 252 | 253 | public bool RpcButtonEnabled 254 | { 255 | get { return rpcStatus == RpChecker.RpcStatus.未通过 || rpcStatus == RpChecker.RpcStatus.通过; } 256 | } 257 | 258 | public event PropertyChangedEventHandler PropertyChanged; 259 | public void OnPropertyChanged(PropertyChangedEventArgs e) 260 | { 261 | PropertyChanged?.Invoke(this, e); 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Utils/EnvironmentChecker.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Security; 6 | using System.Security.Permissions; 7 | using System.Windows; 8 | using OKEGui.JobProcessor; 9 | 10 | namespace OKEGui.Utils 11 | { 12 | static class EnvironmentChecker 13 | { 14 | static OKEGuiConfig Config; 15 | public static bool CheckEnviornment() 16 | { 17 | if (!CheckRootFolderWriteAccess()) 18 | { 19 | MessageBox.Show("没有权限控制OKEGui所在的文件夹,请保证当前用户获取了目录权限,或者以管理员模式运行。", "没有权限控制OKEGui所在的文件夹"); 20 | return false; 21 | } 22 | Config = Initializer.LoadConfig(); 23 | 24 | return CheckVspipe() && CheckQAAC() && CheckFfmpeg() && CheckRPChecker() && CheckEac3toWrapper() && CheckEncoders(); 25 | } 26 | 27 | static bool CheckRootFolderWriteAccess() 28 | { 29 | FileIOPermission f = new FileIOPermission(FileIOPermissionAccess.AllAccess, AppDomain.CurrentDomain.BaseDirectory); 30 | try 31 | { 32 | f.Demand(); 33 | } 34 | catch (SecurityException) 35 | { 36 | return false; 37 | } 38 | return true; 39 | } 40 | 41 | static bool CheckVspipe() 42 | { 43 | string vspipePath = Config.vspipePath; 44 | FileInfo vspipeInfo; 45 | 46 | if (!string.IsNullOrWhiteSpace(vspipePath)) 47 | { 48 | vspipeInfo = new FileInfo(vspipePath); 49 | if (vspipeInfo.Exists) 50 | { 51 | return true; 52 | } 53 | } 54 | 55 | vspipeInfo = new FileInfo(Constants.vspipePath); 56 | if (vspipeInfo.Exists) 57 | { 58 | Config.vspipePath = vspipeInfo.FullName; 59 | return true; 60 | } 61 | 62 | RegistryKey key = Registry.LocalMachine.OpenSubKey("software\\vapoursynth"); 63 | if (key == null) 64 | { 65 | key = Registry.CurrentUser.OpenSubKey("software\\vapoursynth"); 66 | } 67 | if (key != null) 68 | { 69 | vspipePath = key.GetValue("Path") as string; 70 | } 71 | 72 | if (!string.IsNullOrWhiteSpace(vspipePath)) 73 | { 74 | foreach (var subPath in new[] {"core", "core64"}) 75 | { 76 | vspipeInfo = new FileInfo(vspipePath + $"\\{subPath}\\vspipe.exe"); 77 | 78 | if (vspipeInfo.Exists) 79 | { 80 | vspipePath += $"\\{subPath}\\vspipe.exe"; 81 | Config.vspipePath = vspipePath; 82 | return true; 83 | } 84 | } 85 | } 86 | 87 | MessageBox.Show( 88 | "无法找到vspipe.exe。请手动指定其位置,否则程序将退出。", 89 | "无法找到vspipe.exe", 90 | MessageBoxButton.OK, 91 | MessageBoxImage.Error); 92 | 93 | OpenFileDialog ofd = new OpenFileDialog 94 | { 95 | Multiselect = false, 96 | Filter = "vspipe.exe (vspipe.exe)|vspipe.exe" 97 | }; 98 | bool result = ofd.ShowDialog().GetValueOrDefault(false); 99 | if (!result) 100 | { 101 | return false; 102 | } 103 | vspipePath = ofd.FileName; 104 | vspipeInfo = new FileInfo(vspipePath); 105 | 106 | if (vspipeInfo.Exists) 107 | { 108 | Config.vspipePath = vspipePath; 109 | return true; 110 | } 111 | else 112 | { 113 | MessageBox.Show("请尝试重新安装VapourSynth,程序将退出。", "此文件无法读取"); 114 | return false; 115 | } 116 | } 117 | 118 | static bool CheckQAAC() 119 | { 120 | FileInfo qaacInfo = new FileInfo(Constants.QAACPath); 121 | if (!qaacInfo.Exists) 122 | { 123 | MessageBox.Show("请更新tools工具包。", "无法找到qaac"); 124 | return false; 125 | } 126 | QAACEncoder e = new QAACEncoder("--check"); 127 | try 128 | { 129 | e.start(); 130 | } 131 | catch (OKETaskException ex) 132 | { 133 | ExceptionMsg msg = ExceptionParser.Parse(ex, null); 134 | MessageBox.Show(msg.errorMsg, ex.Message); 135 | return false; 136 | } 137 | catch (Exception ex) 138 | { 139 | MessageBox.Show(ex.Message, "qaac检查失败"); 140 | return false; 141 | } 142 | return true; 143 | } 144 | 145 | static bool CheckFfmpeg() 146 | { 147 | FileInfo ffmpegInfo = new FileInfo(Constants.ffmpegPath); 148 | 149 | if (ffmpegInfo.Exists) 150 | { 151 | return true; 152 | } 153 | else 154 | { 155 | MessageBox.Show("请更新tools工具包。", "无法找到ffmpeg"); 156 | return false; 157 | } 158 | } 159 | 160 | static bool CheckRPChecker() 161 | { 162 | string rpCheckerPath = Config.rpCheckerPath; 163 | FileInfo rpCheckerInfo; 164 | 165 | if (!string.IsNullOrWhiteSpace(rpCheckerPath)) 166 | { 167 | rpCheckerInfo = new FileInfo(rpCheckerPath); 168 | if (rpCheckerInfo.Exists) 169 | { 170 | return true; 171 | } 172 | } 173 | 174 | rpCheckerInfo = new FileInfo(Constants.rpcPath); 175 | if (rpCheckerInfo.Exists) 176 | { 177 | Config.rpCheckerPath = rpCheckerInfo.FullName; 178 | return true; 179 | } 180 | 181 | MessageBox.Show( 182 | "无法找到RPChecker.exe。请手动指定其位置,否则程序将退出。", 183 | "无法找到RPChecker.exe", 184 | MessageBoxButton.OK, 185 | MessageBoxImage.Error); 186 | 187 | OpenFileDialog ofd = new OpenFileDialog 188 | { 189 | Multiselect = false, 190 | Filter = "RPChecker.exe (RPChecker*.exe)|RPChecker*.exe" 191 | }; 192 | bool result = ofd.ShowDialog().GetValueOrDefault(false); 193 | if (!result) 194 | { 195 | return false; 196 | } 197 | rpCheckerPath = ofd.FileName; 198 | rpCheckerInfo = new FileInfo(rpCheckerPath); 199 | 200 | if (rpCheckerInfo.Exists) 201 | { 202 | Config.rpCheckerPath = rpCheckerPath; 203 | return true; 204 | } 205 | else 206 | { 207 | MessageBox.Show("请准备RPChecker最新版,程序将退出。", "此文件无法读取"); 208 | return false; 209 | } 210 | } 211 | 212 | static bool CheckEac3toWrapper() 213 | { 214 | FileInfo eac3toWrapperInfo = new FileInfo(Constants.eac3toWrapperPath); 215 | 216 | if (eac3toWrapperInfo.Exists) 217 | { 218 | return true; 219 | } 220 | else 221 | { 222 | MessageBox.Show("请更新tools工具包。", "无法找到eac3to-wrapper"); 223 | return false; 224 | } 225 | } 226 | 227 | static bool CheckEncoders() 228 | { 229 | var encoders = new List { 230 | new FileInfo(Constants.x264Path), 231 | new FileInfo(Constants.x265Path), 232 | }; 233 | foreach (FileInfo fi in encoders) 234 | { 235 | if (!fi.Exists) 236 | { 237 | MessageBox.Show("请更新tools工具包。", "无法找到" + fi.Name); 238 | return false; 239 | } 240 | } 241 | return true; 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /OKEGui/OKEGui/Task/ChapterService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using OKEGui.Utils; 7 | using TChapter.Chapters; 8 | using TChapter.Parsing; 9 | using MediaInfo; 10 | 11 | namespace OKEGui 12 | { 13 | public enum ChapterStatus 14 | { 15 | No, 16 | Yes, 17 | Added, 18 | Maybe, 19 | MKV, 20 | Warn 21 | }; 22 | 23 | public class ChapterService 24 | { 25 | private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("ChapterService"); 26 | 27 | public static ChapterStatus UpdateChapterStatus(TaskDetail task) 28 | { 29 | return HasChapterFile(task) ? ChapterStatus.Yes : 30 | HasBlurayStructure(task) ? ChapterStatus.Maybe : 31 | HasMatroskaChapter(task) ? ChapterStatus.MKV : ChapterStatus.No; 32 | } 33 | 34 | private static bool HasChapterFile(TaskDetail task) 35 | { 36 | return FindChapterFile(task); 37 | } 38 | 39 | private static bool HasBlurayStructure(TaskDetail task) 40 | { 41 | FileInfo inputFile = new FileInfo(task.InputFile); 42 | if (inputFile.Extension.ToLower() != ".m2ts") 43 | { 44 | Logger.Warn($"{task.InputFile}不是蓝光原盘文件。"); 45 | return false; 46 | } 47 | 48 | if (inputFile.Directory.Name.ToUpper() != "STREAM") 49 | { 50 | Logger.Warn($"{task.InputFile}不在BDMV文件夹结构内。"); 51 | return false; 52 | } 53 | 54 | DirectoryInfo playlist = new DirectoryInfo(Path.Combine(inputFile.Directory.Parent.FullName, "PLAYLIST")); 55 | if (!playlist.Exists) 56 | { 57 | Logger.Warn($"{task.InputFile}没有上级的PLAYLIST文件夹"); 58 | return false; 59 | } 60 | 61 | return playlist.GetFiles("*.mpls").Length > 0; 62 | } 63 | 64 | private static bool HasMatroskaChapter(TaskDetail task) 65 | { 66 | FileInfo inputFile = new FileInfo(task.InputFile); 67 | if (inputFile.Extension.ToLower() != ".mkv") 68 | { 69 | Logger.Warn($"{task.InputFile}不是Matroska文件。"); 70 | return false; 71 | } 72 | 73 | MediaInfo.MediaInfo MI = new MediaInfo.MediaInfo(); 74 | MI.Open(inputFile.FullName); 75 | MI.Option("Complete"); 76 | int.TryParse(MI.Get(StreamKind.General, 0, "MenuCount"), out var MenuCount); 77 | 78 | if (MenuCount == 0) 79 | { 80 | Logger.Warn($"{task.InputFile}内不含有章节。"); 81 | MI?.Close(); 82 | return false; 83 | } 84 | 85 | MI?.Close(); 86 | return true; 87 | } 88 | 89 | public static bool FindChapterFile(TaskDetail task) 90 | { 91 | if (!string.IsNullOrEmpty(task.ChapterFileName)) 92 | return true; 93 | FileInfo inputFile = new FileInfo(task.InputFile); 94 | string inputPath = Path.GetFullPath(inputFile.FullName); 95 | string basename = Path.GetFileNameWithoutExtension(inputFile.FullName); 96 | string[] files = Directory.GetFiles(Path.GetDirectoryName(inputPath), basename + ".*txt"); 97 | if (files.Length > 0) 98 | Logger.Warn($"ChapterFile: found {String.Join(",", files)}."); 99 | if (files.Length > 1) 100 | throw new Exception("More than one chapter files found for " + task.InputFile + ": " + String.Join(",", files)); 101 | if (files.Length == 1) 102 | { 103 | task.ChapterFileName = files[0]; 104 | string ext = Path.GetFileNameWithoutExtension(task.ChapterFileName); 105 | if (ext.Length > basename.Length) 106 | task.ChapterLanguage = ext.Substring(basename.Length + 1); 107 | Logger.Warn($"ChapterFile {task.ChapterFileName}, language \"{task.ChapterLanguage}\"."); 108 | return true; 109 | } 110 | return false; 111 | } 112 | 113 | public static ChapterInfo LoadChapter(TaskDetail task) 114 | { 115 | FileInfo inputFile = new FileInfo(task.InputFile); 116 | ChapterInfo chapterInfo; 117 | switch (task.ChapterStatus) 118 | { 119 | case ChapterStatus.Yes: 120 | if (!FindChapterFile(task)) return null; 121 | chapterInfo = new OGMParser().Parse(task.ChapterFileName).FirstOrDefault(); 122 | break; 123 | case ChapterStatus.Maybe: 124 | DirectoryInfo playlistDirectory = 125 | new DirectoryInfo(Path.Combine(inputFile.Directory.Parent.FullName, "PLAYLIST")); 126 | chapterInfo = GetChapterFromMPLS(playlistDirectory.GetFiles("*.mpls"), inputFile); 127 | break; 128 | case ChapterStatus.MKV: 129 | FileInfo mkvExtract = new FileInfo(".\\tools\\mkvtoolnix\\mkvextract.exe"); 130 | chapterInfo = new MATROSKAParser(mkvExtract.FullName).Parse(inputFile.FullName).FirstOrDefault(); 131 | break; 132 | default: 133 | return null; 134 | } 135 | 136 | if (chapterInfo == null) return null; 137 | 138 | chapterInfo.Chapters.Sort((a, b) => a.Time.CompareTo(b.Time)); 139 | chapterInfo.Chapters = chapterInfo.Chapters 140 | .Where(x => task.LengthInMiliSec - x.Time.TotalMilliseconds > 1001).ToList(); 141 | 142 | if (task.Taskfile.RenumberChapters) 143 | { 144 | for (int i = 0; i < chapterInfo.Chapters.Count; i++) 145 | { 146 | chapterInfo.Chapters[i].Name = string.Format("Chapter {0,2:0#}", i+1); 147 | } 148 | } 149 | 150 | if (chapterInfo.Chapters.Count > 1 || 151 | chapterInfo.Chapters.Count == 1 && chapterInfo.Chapters[0].Time.Ticks > 0) 152 | { 153 | double lastChapterInMiliSec = chapterInfo.Chapters[chapterInfo.Chapters.Count - 1].Time.TotalMilliseconds; 154 | if (task.LengthInMiliSec - lastChapterInMiliSec < 3003) 155 | { 156 | task.ChapterStatus = ChapterStatus.Warn; 157 | } 158 | return chapterInfo; 159 | } 160 | 161 | Logger.Info(inputFile.Name + "对应章节为空,跳过封装。"); 162 | return null; 163 | } 164 | 165 | private static ChapterInfo GetChapterFromMPLS(IEnumerable playlists, FileInfo inputFile) 166 | { 167 | MPLSParser parser = new MPLSParser(); 168 | foreach (FileInfo playlistFile in playlists) 169 | { 170 | IChapterData allChapters = parser.Parse(playlistFile.FullName); 171 | foreach (ChapterInfo chapter in allChapters) 172 | { 173 | if (chapter.SourceName + ".m2ts" == inputFile.Name) 174 | { 175 | return chapter; 176 | } 177 | } 178 | } 179 | 180 | return null; 181 | } 182 | 183 | public static string GenerateQpFile(ChapterInfo chapterInfo, double fps) 184 | { 185 | StringBuilder qpFile = new StringBuilder(); 186 | 187 | foreach (var chapter in chapterInfo.Chapters) 188 | { 189 | long miliSec = (long)chapter.Time.TotalMilliseconds; 190 | int frameNo = (int)(miliSec / 1000.0 * fps + 0.5); 191 | qpFile.AppendLine($"{frameNo} I"); 192 | } 193 | 194 | return qpFile.ToString(); 195 | } 196 | 197 | public static string GenerateQpFile(ChapterInfo chapterInfo, Timecode timecode) 198 | { 199 | StringBuilder qpFile = new StringBuilder(); 200 | 201 | foreach (var chapter in chapterInfo.Chapters) 202 | { 203 | qpFile.AppendLine($"{timecode.GetFrameNumberFromTimeSpan(chapter.Time)} I"); 204 | } 205 | 206 | return qpFile.ToString(); 207 | } 208 | } 209 | } 210 | --------------------------------------------------------------------------------