From b42e3d2c6463b795d737b7bf2a2f62fd1f3f662b Mon Sep 17 00:00:00 2001 From: yuyuyzl Date: Fri, 10 Jan 2025 03:52:00 +0800 Subject: [PATCH] feat: launcher2.py --- launcher.py | 2 +- launcher2.py | 458 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 launcher2.py diff --git a/launcher.py b/launcher.py index dd09747..984cdbc 100644 --- a/launcher.py +++ b/launcher.py @@ -255,7 +255,7 @@ def inputChange(): ifmEnt.pack_forget() osfLbl.pack_forget() osfEnt.pack_forget() - frameLTxt.configure(height=0) + input = tk.IntVar(value=args['input']) ttk.Label(frameL, text="Face Data Source").pack(fill='x', expand=True) ttk.Radiobutton(frameL, text='iFacialMocap', value=0, variable=input, command=inputChange).pack(fill='x', expand=True) diff --git a/launcher2.py b/launcher2.py new file mode 100644 index 0000000..72582c1 --- /dev/null +++ b/launcher2.py @@ -0,0 +1,458 @@ +import ctypes +import os +import subprocess + +import wx +import json +import sys + +ctypes.windll.shcore.SetProcessDpiAwareness(1) +p = None +cache_simplify_map = { + 'Off':0, + 'Low':1, + 'Medium':2, + 'High':3, + 'Higher':4, + 'Highest':6, + 'Gaming':8 +} + + +cache_simplify_quality_map = { + 'Off':100, + 'Low':99, + 'Medium':95, + 'High':90, + 'Higher':85, + 'Highest':80, + 'Gaming':75 +} +default_arg = { + 'character': 'lambda_00', + 'input': 2, + 'output': 2, + 'ifm': None, + 'osf': '127.0.0.1:11573', + 'is_extend_movement': False, + 'is_alpha_split': False, + 'is_bongo': False, + 'is_eyebrow': False, + 'cache_simplify': 'High', + 'cache_compression': 'High', + 'ram_cache_size': '2gb', + 'vram_cache_size': '2gb', + 'model_select': 'seperable_half', + 'interpolation': 'x2_half', + 'frame_rate_limit': '30', + 'sr': 'Off', + 'device_id': '0', + 'use_tensorrt': True, + 'preset': 'Medium' +} + +try: + f = open('launcher.json') + args = json.load(f) + default_arg.update(args) + f.close() +except: + pass +finally: + args = default_arg + +p = None +dirPath = 'data/images' +characterList = [] +for item in sorted(os.listdir(dirPath), key=lambda x: -os.path.getmtime(os.path.join(dirPath, x))): + if '.png' == item[-4:]: + characterList.append(item[:-4]) + + +class OptionPanel(wx.Panel): + def __init__(self, parent, title='', desc='', choices=None, mapping=None, type=0, default=None): + wx.Panel.__init__(self, parent) + self.type = type + if mapping is not None: + self.mapping = mapping + else: + self.mapping = choices + mainSizer = wx.BoxSizer(wx.HORIZONTAL) + leftSizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(mainSizer) + titleText = wx.StaticText(self, wx.ID_ANY, title) + titleFont = titleText.GetFont() + titleFont.SetWeight(wx.FONTWEIGHT_SEMIBOLD) + titleText.SetFont(titleFont) + leftSizer.Add(titleText, 0, wx.ALL, 0) + descText = wx.StaticText(self, wx.ID_ANY, desc) + descFont = descText.GetFont() + descFont.SetWeight(wx.FONTWEIGHT_EXTRALIGHT) + descText.SetFont(descFont) + leftSizer.Add(descText, 0, wx.ALL, 0) + mainSizer.Add(leftSizer, 1, wx.EXPAND | wx.ALL, 0) + if self.type == 0: + self.control = wx.Choice(self, wx.ID_ANY, choices=choices) + self.control.SetMinSize(wx.Size(300, -1)) + try: + if default is not None: + if self.mapping: + self.control.SetSelection(self.mapping.index(default)) + else: + self.control.SetSelection(default) + except: + pass + elif self.type == 1: + self.control = wx.CheckBox(self, wx.ID_ANY) + try: + if default is not None: + if self.mapping: + self.control.SetValue(self.mapping[default]) + else: + self.control.SetValue(default) + + except: + pass + elif self.type == 2: + self.control = wx.TextCtrl(self, wx.ID_ANY) + self.control.SetMinSize(wx.Size(300, -1)) + try: + if default is not None: + if self.mapping: + self.control.SetValue(self.mapping[default]) + else: + self.control.SetValue(default) + except: + pass + + mainSizer.Add(self.control, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 20) + # self.SetBackgroundColour('#000000') + + def GetValue(self): + if self.type == 0: + ret = self.control.GetSelection() + elif self.type == 1: + ret = self.control.GetValue() + elif self.type == 2: + ret = self.control.GetValue() + if self.mapping is not None: + return self.mapping[ret] + else: + return ret + + +class LauncherPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + self.number_of_buttons = 0 + self.frame = parent + self.optionDict = {} + self.mainSizer = wx.BoxSizer(wx.VERTICAL) + controlSizer = wx.BoxSizer(wx.HORIZONTAL) + self.widgetSizer = wx.BoxSizer(wx.VERTICAL) + + stEasy = wx.StaticText(self, wx.ID_ANY, "Easy") + f = stEasy.GetFont() + f.SetWeight(wx.FONTWEIGHT_HEAVY) + f = f.MakeLarger() + stEasy.SetFont(f) + stVtuber = wx.StaticText(self, wx.ID_ANY, "Vtuber") + f = stVtuber.GetFont() + f.SetWeight(wx.FONTWEIGHT_LIGHT) + f = f.MakeLarger() + stVtuber.SetFont(f) + controlSizer.Add(stEasy, 0, wx.ALL | wx.CENTER, 0) + controlSizer.Add(stVtuber, 1, wx.RIGHT | wx.CENTER, 30) + + self.btnLaunch = wx.Button(self, label="Save && Launch") + self.btnLaunch.Bind(wx.EVT_BUTTON, self.OnLaunch) + controlSizer.Add(self.btnLaunch, 0, wx.CENTER | wx.ALL, 10) + # self.btnAdd = wx.Button(self, label="添加") + # self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAddWidget) + # controlSizer.Add(self.btnAdd, 0, wx.CENTER | wx.ALL, 5) + # self.btnRemove = wx.Button(self, label="删除") + # self.btnRemove.Bind(wx.EVT_BUTTON, self.OnRemoveWidget) + # controlSizer.Add(self.btnRemove, 0, wx.CENTER | wx.ALL, 5) + self.mainSizer.Add(self.widgetSizer, 0, wx.CENTER | wx.ALL, 10) + self.mainSizer.Add(controlSizer, 0, wx.CENTER | wx.EXPAND | wx.LEFT, 10) + self.mainSizer.Add(wx.StaticLine(self), 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 8) + self.SetSizer(self.mainSizer) + self.optionSizer = wx.BoxSizer(wx.VERTICAL) + self.mainSizer.Add(self.optionSizer, 0, wx.EXPAND | wx.CENTER | wx.ALL, 2) + + def addOption(key, **kwargs): + kwargs['default'] = args[key] + t = OptionPanel(self, **kwargs) + self.optionSizer.Add(t, 0, wx.EXPAND | wx.ALL, 5) + self.optionDict[key] = t + return t + + addOption('character', title='Character', desc='选择需要使用的角色图片,位于data/images内', + choices=characterList) + + addOption('input', title='Input Device', desc='选择希望使用的面捕数据源', + choices=['iFacialMocap', 'OpenSeeFace', 'OpenCV(Webcam)', 'Mouse Input', 'Debug Input'], + mapping=[0, 4, 1, 3, 2]) + addOption('ifm', title='iFacialMocap IP:Port', desc='输入iFacialMocap连接使用的IP:端口号', type=2) + addOption('is_eyebrow', title='Eyebrow', desc='使用眉毛输入,对性能有一定影响\n仅支持iFacialMocap', type=1, + default=True) + addOption('osf', title='OpenSeeFace IP:Port', desc='输入OpenSeeFace连接使用的IP:端口号', type=2) + + addOption('output', title='Output', desc='选择输出目标', + choices=['Spout2', 'OBS VirtualCam', 'UnityCapture', 'Debug Output'], + mapping=[3, 1, 0, 2]) + + addOption('device_id', title='GPU Device', desc='选择需要使用的计算设备', choices=['0']) + addOption('use_tensorrt', title='TensorRT', + desc='开启TensorRT后可使用预编译模型\n进一步提升性能(仅NVIDIA显卡支持)', type=1) + + addOption('frame_rate_limit', title='FPS Limit', desc='选择帧率限制目标', + choices=['10', '15', '20', '30', '60']) + addOption('preset', title='Performance Preset', desc='性能预设,注意修改后会覆盖后续配置', + choices=['Low', 'Medium', 'High', 'Ultra', 'Custom']) + addOption('model_select', title='Model Select', desc='选择使用的模型\nStandard Full精度较高性能较低', + choices=['Seperable Half', 'Seperable Full', 'Standard Half', 'Standard Full'], + mapping=['seperable_half', 'seperable_full', 'standard_half', 'standard_full']) + addOption('ram_cache_size', title='RAM Cache Size', desc='分配内存缓存大小\n用于存储最终运算结果', + choices=['OFF', '1GB', '2GB', '4GB', '8GB', '16GB'], + mapping=['0b', '1gb', '2gb', '4gb', '8gb', '16gb']) + addOption('vram_cache_size', title='VRAM Cache Size', desc='分配显存缓存大小\n用于存储中间结果', + choices=['OFF', '1GB', '2GB', '4GB', '8GB', '16GB'], + mapping=['0b', '1gb', '2gb', '4gb', '8gb', '16gb']) + addOption('cache_simplify', title='Input Simplify', + desc='设置输入简化级别\n输入越简化,缓存命中率越高,动作越不平滑', + choices=['OFF', 'Low', 'Medium', 'High', 'Higher', 'Highest', 'Gaming']) + addOption('cache_compression', title='JPEG Compression', + desc='设置内存缓存压缩等级\n压缩等级越高,缓存命中率越高,输出质量越差', + choices=['OFF', 'Low', 'Medium', 'High']) + + + addOption('sr', title='SuperResolution', desc='选择使用的超分模型', + choices=['Off', 'anime4k_x2', 'waifu2x_x2_half', 'real-esrgan_x4_half', 'waifu2x_x2_full', + 'real-esrgan_x4_full']) + addOption('interpolation', title='Frame Interpolation', desc='选择使用的补帧模型', + choices=['off', 'x2_half', 'x3_half', 'x4_half', 'x2_full', 'x3_full', 'x4_full']) + + addOption('is_extend_movement', title='Extend Movement', desc='基于面捕的XY输入移动、缩放输出图像提升可动性', + type=1) + addOption('is_bongo', title='Bongocat Mode', desc='适当旋转输出以适配Bongocat桌宠', type=1) + addOption('is_alpha_split', title='Alpha Split', desc='分离透明通道到视频流右侧\n你需要自行进行后续处理', + type=1) + + def inputChoice(e=None): + s=self.optionDict['input'].GetValue() + if s!=0: + self.optionSizer.Hide(self.optionDict['ifm']) + self.optionSizer.Hide(self.optionDict['is_eyebrow']) + else: + self.optionSizer.Show(self.optionDict['ifm']) + self.optionSizer.Show(self.optionDict['is_eyebrow']) + if s!=4: + self.optionSizer.Hide(self.optionDict['osf']) + else: + self.optionSizer.Show(self.optionDict['osf']) + + self.frame.fSizer.Layout() + self.frame.Fit() + self.optionDict['input'].Bind(wx.EVT_CHOICE,inputChoice) + inputChoice() + + def presetChoice(e=None): + s = self.optionDict['preset'].GetValue() + presetControls=[ + self.optionDict['model_select'], + self.optionDict['ram_cache_size'], + self.optionDict['vram_cache_size'], + self.optionDict['cache_simplify'], + self.optionDict['cache_compression'], + ] + if s=='Custom': + for c in presetControls: self.optionSizer.Show(c) + else: + for c in presetControls: self.optionSizer.Hide(c) + if s=='Low': + opt=[0,1,1,5,3] + for i in range(5): presetControls[i].control.SetSelection(opt[i]) + elif s=='Medium': + opt=[0,1,1,4,2] + for i in range(5): presetControls[i].control.SetSelection(opt[i]) + elif s=='High': + opt=[0,2,2,2,2] + for i in range(5): presetControls[i].control.SetSelection(opt[i]) + elif s=='Ultra': + opt=[0,3,3,1,1] + for i in range(5): presetControls[i].control.SetSelection(opt[i]) + + self.frame.fSizer.Layout() + self.frame.Fit() + self.optionDict['preset'].Bind(wx.EVT_CHOICE,presetChoice) + presetChoice() + # def OnAddWidget(self, e): + # self.number_of_buttons += 1 + # label = "按钮 %s" % self.number_of_buttons + # name = "button%s" % self.number_of_buttons + # new_button = wx.Button(self, label=label, name=name) + # self.widgetSizer.Add(new_button, 0, wx.ALL, 5) + # self.frame.fSizer.Layout() + # self.frame.Fit() + # + # def OnRemoveWidget(self, e): + # if self.widgetSizer.GetChildren(): + # sizer_item = self.widgetSizer.GetItem(self.number_of_buttons - 1) + # widget = sizer_item.GetWindow() + # self.widgetSizer.Hide(widget) + # widget.Destroy() + # self.number_of_buttons -= 1 + # self.frame.fSizer.Layout() + # self.frame.Fit() + + def OnLaunch(self, e): + global p + args = {} + for k in self.optionDict.keys(): + args[k] = self.optionDict[k].GetValue() + f = open('launcher.json', mode='w') + json.dump(args, f) + f.close() + self.btnLaunch.SetLabelText('Working...') + + if p is not None: + subprocess.run(['taskkill', '/F', '/PID', str(p.pid), '/T'], stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + p = None + self.btnLaunch.SetLabelText("Save & Launch") + else: + run_args = [sys.executable, 'main.py'] + if len(args['character']): + run_args.append('--character') + run_args.append(args['character']) + + if args['input'] == 0: + if len(args['ifm']): + run_args.append('--ifm') + run_args.append(args['ifm']) + elif args['input'] == 1: + run_args.append('--input') + run_args.append('cam') + elif args['input'] == 2: + run_args.append('--debug_input') + elif args['input'] == 3: + run_args.append('--mouse_input') + run_args.append('0,0,' + str(wx.GetDisplaySize().width) + ',' + str(wx.GetDisplaySize().height)) + elif args['input'] == 4: + if len(args['osf']): + run_args.append('--osf') + run_args.append(args['osf']) + + if args['output'] == 0: + run_args.append('--output_webcam') + run_args.append('unitycapture') + elif args['output'] == 1: + run_args.append('--output_webcam') + run_args.append('obs') + elif args['output'] == 3: + run_args.append('--output_webcam') + run_args.append('spout') + elif args['output'] == 2: + run_args.append('--debug') + if args['is_alpha_split']: + run_args.append('--alpha_split') + if args['is_extend_movement']: + run_args.append('--extend_movement') + run_args.append('1') + if args['is_bongo']: + run_args.append('--bongo') + if args['is_eyebrow']: + run_args.append('--eyebrow') + if args['cache_simplify'] is not None: + run_args.append('--simplify') + run_args.append(str(cache_simplify_map[args['cache_simplify']])) + run_args.append('--cacher_quality') + run_args.append(str(cache_simplify_quality_map[args['cache_compression']])) + if args['cache_simplify'] != 'Off': + run_args.append('--use_cacher') + if args['ram_cache_size'] is not None: + run_args.append('--cache') + run_args.append(args['ram_cache_size']) + run_args.append('--gpu_cache') + run_args.append(args['vram_cache_size']) + + if args['interpolation'] is not None: + if not 'off' == args['interpolation']: + run_args.append('--use_interpolation') + if 'half' in args['interpolation']: + run_args.append('--interpolation_half') + + if 'x2' in args['interpolation']: + run_args.append('--interpolation_scale') + run_args.append('2') + elif 'x3' in args['interpolation']: + run_args.append('--interpolation_scale') + run_args.append('3') + elif 'x4' in args['interpolation']: + run_args.append('--interpolation_scale') + run_args.append('4') + + if args['model_select'] is not None: + if 'seperable' in args['model_select']: + run_args.append('--model_seperable') + if 'half' in args['model_select']: + run_args.append('--model_half') + + if args['frame_rate_limit'] is not None: + run_args.append('--frame_rate_limit') + run_args.append(args['frame_rate_limit']) + + if args['sr'] is not None and args['sr'] != 'Off': + if 'anime4k' in args['sr']: + run_args.append('--anime4k') + else: + run_args.append('--use_sr') + if 'x4' in args['sr']: + run_args.append('--sr_x4') + if 'half' in args['sr']: + run_args.append('--sr_half') + + if args['device_id'] is not None: + run_args.append('--device_id') + run_args.append(args['device_id']) + + if args['use_tensorrt'] is not None and args['use_tensorrt']: + run_args.append('--use_tensorrt') + run_args.append('--model_cache') + run_args.append('--model_vram_cache') + + run_args.append('--output_size') + run_args.append('512x512') + print('Launched: ' + ' '.join(run_args)) + p = subprocess.Popen(run_args) + self.btnLaunch.SetLabelText('Stop') + + + +class MainFrame(wx.Frame): + def __init__(self, *args, **kw): + super(MainFrame, self).__init__(*args, **kw) + self.InitUi() + + def InitUi(self): + self.SetTitle("EasyVtuber Launcher") + self.fSizer = wx.BoxSizer(wx.VERTICAL) + panel = LauncherPanel(self) + self.fSizer.Add(panel, 1, wx.EXPAND) + self.SetSizer(self.fSizer) + self.SetMinSize(wx.Size(600, 0)) + self.Fit() + self.Centre() + + +def main(): + app = wx.App() + sample = MainFrame(None) + + sample.Show() + app.MainLoop() + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt index 29c0c78..be68fc0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ Pillow==9.1.0 pyanime4k==2.5.2 pyvirtualcam==0.9.1 pyopengl +wxpython onnx onnxruntime-directml turbojpeg