added module: Face Swap (Insight)

This commit is contained in:
iperov 2023-07-09 22:01:38 +04:00
commit c4511db198
33 changed files with 567 additions and 75 deletions

View file

@ -11,18 +11,19 @@ from xlib.qt.widgets.QXLabel import QXLabel
from . import backend
from .ui.QCameraSource import QCameraSource
from .ui.QFaceAligner import QFaceAligner
from .ui.QFaceAnimator import QFaceAnimator
from .ui.QFaceDetector import QFaceDetector
from .ui.QFaceMarker import QFaceMarker
from .ui.QFaceMerger import QFaceMerger
from .ui.QFaceAnimator import QFaceAnimator
from .ui.QFaceSwapper import QFaceSwapper
from .ui.QFaceSwapInsight import QFaceSwapInsight
from .ui.QFaceSwapDFM import QFaceSwapDFM
from .ui.QFileSource import QFileSource
from .ui.QFrameAdjuster import QFrameAdjuster
from .ui.QStreamOutput import QStreamOutput
from .ui.widgets.QBCFaceAlignViewer import QBCFaceAlignViewer
from .ui.widgets.QBCFaceSwapViewer import QBCFaceSwapViewer
from .ui.widgets.QBCMergedFrameViewer import QBCMergedFrameViewer
from .ui.widgets.QBCFrameViewer import QBCFrameViewer
from .ui.widgets.QBCMergedFrameViewer import QBCMergedFrameViewer
class QLiveSwap(qtx.QXWidget):
@ -58,13 +59,13 @@ class QLiveSwap(qtx.QXWidget):
face_marker = self.face_marker = backend.FaceMarker (weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_detector_bc_out, bc_out=face_marker_bc_out, backend_db=backend_db)
face_aligner = self.face_aligner = backend.FaceAligner (weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_marker_bc_out, bc_out=face_aligner_bc_out, backend_db=backend_db )
face_animator = self.face_animator = backend.FaceAnimator (weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_aligner_bc_out, bc_out=face_merger_bc_out, animatables_path=animatables_path, backend_db=backend_db )
face_swapper = self.face_swapper = backend.FaceSwapper (weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_aligner_bc_out, bc_out=face_swapper_bc_out, dfm_models_path=dfm_models_path, backend_db=backend_db )
face_swap_insight = self.face_swap_insight = backend.FaceSwapInsight (weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_aligner_bc_out, bc_out=face_swapper_bc_out, faces_path=animatables_path, backend_db=backend_db )
face_swap_dfm = self.face_swap_dfm = backend.FaceSwapDFM (weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_aligner_bc_out, bc_out=face_swapper_bc_out, dfm_models_path=dfm_models_path, backend_db=backend_db )
frame_adjuster = self.frame_adjuster = backend.FrameAdjuster(weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_swapper_bc_out, bc_out=frame_adjuster_bc_out, backend_db=backend_db )
face_merger = self.face_merger = backend.FaceMerger (weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=frame_adjuster_bc_out, bc_out=face_merger_bc_out, backend_db=backend_db )
stream_output = self.stream_output = backend.StreamOutput (weak_heap=backend_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=face_merger_bc_out, save_default_path=userdata_path, backend_db=backend_db)
self.all_backends : List[backend.BackendHost] = [file_source, camera_source, face_detector, face_marker, face_aligner, face_animator, face_swapper, frame_adjuster, face_merger, stream_output]
self.all_backends : List[backend.BackendHost] = [file_source, camera_source, face_detector, face_marker, face_aligner, face_animator, face_swap_insight, face_swap_dfm, frame_adjuster, face_merger, stream_output]
self.q_file_source = QFileSource(self.file_source)
self.q_camera_source = QCameraSource(self.camera_source)
@ -72,7 +73,8 @@ class QLiveSwap(qtx.QXWidget):
self.q_face_marker = QFaceMarker(self.face_marker)
self.q_face_aligner = QFaceAligner(self.face_aligner)
self.q_face_animator = QFaceAnimator(self.face_animator, animatables_path=animatables_path)
self.q_face_swapper = QFaceSwapper(self.face_swapper, dfm_models_path=dfm_models_path)
self.q_face_swap_insight = QFaceSwapInsight(self.face_swap_insight, faces_path=animatables_path)
self.q_face_swap_dfm = QFaceSwapDFM(self.face_swap_dfm, dfm_models_path=dfm_models_path)
self.q_frame_adjuster = QFrameAdjuster(self.frame_adjuster)
self.q_face_merger = QFaceMerger(self.face_merger)
self.q_stream_output = QStreamOutput(self.stream_output)
@ -83,8 +85,8 @@ class QLiveSwap(qtx.QXWidget):
self.q_ds_merged_frame_viewer = QBCMergedFrameViewer(backend_weak_heap, face_merger_bc_out)
q_nodes = qtx.QXWidgetHBox([ qtx.QXWidgetVBox([self.q_file_source, self.q_camera_source], spacing=5, fixed_width=256),
qtx.QXWidgetVBox([self.q_face_detector, self.q_face_aligner,], spacing=5, fixed_width=256),
qtx.QXWidgetVBox([self.q_face_marker, self.q_face_animator, self.q_face_swapper], spacing=5, fixed_width=256),
qtx.QXWidgetVBox([self.q_face_detector, self.q_face_aligner, ], spacing=5, fixed_width=256),
qtx.QXWidgetVBox([self.q_face_marker, self.q_face_animator, self.q_face_swap_insight, self.q_face_swap_dfm], spacing=5, fixed_width=256),
qtx.QXWidgetVBox([self.q_frame_adjuster, self.q_face_merger, self.q_stream_output], spacing=5, fixed_width=256),
], spacing=5, size_policy=('fixed', 'fixed') )
@ -112,7 +114,7 @@ class QLiveSwap(qtx.QXWidget):
def initialize(self):
for bcknd in self.all_backends:
default_state = True
if isinstance(bcknd, (backend.CameraSource, backend.FaceAnimator) ):
if isinstance(bcknd, (backend.CameraSource, backend.FaceAnimator, backend.FaceSwapInsight) ):
default_state = False
bcknd.restore_on_off_state(default_state=default_state)

View file

@ -14,13 +14,13 @@ from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendWorkerState)
class FaceSwapper(BackendHost):
class FaceSwapDFM(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, dfm_models_path : Path, backend_db : BackendDB = None,
id : int = 0):
self._id = id
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FaceSwapperWorker,
worker_cls=FaceSwapDFMWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out, dfm_models_path])
@ -29,7 +29,7 @@ class FaceSwapper(BackendHost):
def _get_name(self):
return super()._get_name()# + f'{self._id}'
class FaceSwapperWorker(BackendWorker):
class FaceSwapDFMWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
@ -218,7 +218,7 @@ class FaceSwapperWorker(BackendWorker):
if events.new_status_downloading:
self.set_busy(False)
cs.model_dl_progress.enable()
cs.model_dl_progress.set_config( lib_csw.Progress.Config(title='@FaceSwapper.downloading_model') )
cs.model_dl_progress.set_config( lib_csw.Progress.Config(title='@FaceSwapDFM.downloading_model') )
cs.model_dl_progress.set_progress(0)
elif events.new_status_initialized:
@ -229,12 +229,12 @@ class FaceSwapperWorker(BackendWorker):
cs.model_info_label.enable()
cs.model_info_label.set_config( lib_csw.InfoLabel.Config(info_icon=True,
info_lines=[f'@FaceSwapper.model_information',
info_lines=[f'@FaceSwapDFM.model_information',
'',
f'@FaceSwapper.filename',
f'@FaceSwapDFM.filename',
f'{self.dfm_model.get_model_path().name}',
'',
f'@FaceSwapper.resolution',
f'@FaceSwapDFM.resolution',
f'{model_width}x{model_height}']) )
cs.swap_all_faces.enable()

View file

@ -0,0 +1,269 @@
import time
from pathlib import Path
import cv2
import numpy as np
from modelhub.onnx import InsightFace2D106, InsightFaceSwap, YoloV5Face
from xlib import cv as lib_cv2
from xlib import os as lib_os
from xlib import path as lib_path
from xlib.face import ELandmarks2D, FLandmarks2D, FRect
from xlib.image.ImageProcessor import ImageProcessor
from xlib.mp import csw as lib_csw
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
BackendSignal, BackendWeakHeap, BackendWorker,
BackendWorkerState)
class FaceSwapInsight(BackendHost):
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, faces_path : Path, backend_db : BackendDB = None,
id : int = 0):
self._id = id
super().__init__(backend_db=backend_db,
sheet_cls=Sheet,
worker_cls=FaceSwapInsightWorker,
worker_state_cls=WorkerState,
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out, faces_path])
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
def _get_name(self):
return super()._get_name()
class FaceSwapInsightWorker(BackendWorker):
def get_state(self) -> 'WorkerState': return super().get_state()
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
def on_start(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, faces_path : Path):
self.weak_heap = weak_heap
self.reemit_frame_signal = reemit_frame_signal
self.bc_in = bc_in
self.bc_out = bc_out
self.faces_path = faces_path
self.pending_bcd = None
self.swap_model : InsightFaceSwap = None
self.target_face_img = None
self.face_vector = None
lib_os.set_timer_resolution(1)
state, cs = self.get_state(), self.get_control_sheet()
cs.device.call_on_selected(self.on_cs_device)
cs.face.call_on_selected(self.on_cs_face)
cs.adjust_c.call_on_number(self.on_cs_adjust_c)
cs.adjust_x.call_on_number(self.on_cs_adjust_x)
cs.adjust_y.call_on_number(self.on_cs_adjust_y)
cs.animator_face_id.call_on_number(self.on_cs_animator_face_id)
cs.update_faces.call_on_signal(self.update_faces)
cs.device.enable()
cs.device.set_choices( InsightFaceSwap.get_available_devices(), none_choice_name='@misc.menu_select')
cs.device.select(state.device)
def update_faces(self):
state, cs = self.get_state(), self.get_control_sheet()
cs.face.set_choices([face_path.name for face_path in lib_path.get_files_paths(self.faces_path, extensions=['.jpg','.jpeg','.png'])], none_choice_name='@misc.menu_select')
def on_cs_device(self, idx, device):
state, cs = self.get_state(), self.get_control_sheet()
if device is not None and state.device == device:
self.swap_model = InsightFaceSwap(device)
self.face_detector = YoloV5Face(device)
self.face_marker = InsightFace2D106(device)
cs.face.enable()
self.update_faces()
cs.face.select(state.face)
cs.adjust_c.enable()
cs.adjust_c.set_config(lib_csw.Number.Config(min=1.0, max=2.0, step=0.01, decimals=2, allow_instant_update=True))
adjust_c = state.adjust_c
if adjust_c is None:
adjust_c = 1.55
cs.adjust_c.set_number(adjust_c)
cs.adjust_x.enable()
cs.adjust_x.set_config(lib_csw.Number.Config(min=-0.5, max=0.5, step=0.01, decimals=2, allow_instant_update=True))
adjust_x = state.adjust_x
if adjust_x is None:
adjust_x = 0.0
cs.adjust_x.set_number(adjust_x)
cs.adjust_y.enable()
cs.adjust_y.set_config(lib_csw.Number.Config(min=-0.5, max=0.5, step=0.01, decimals=2, allow_instant_update=True))
adjust_y = state.adjust_y
if adjust_y is None:
adjust_y = -0.15
cs.adjust_y.set_number(adjust_y)
cs.animator_face_id.enable()
cs.animator_face_id.set_config(lib_csw.Number.Config(min=0, max=16, step=1, decimals=0, allow_instant_update=True))
cs.animator_face_id.set_number(state.animator_face_id if state.animator_face_id is not None else 0)
cs.update_faces.enable()
else:
state.device = device
self.save_state()
self.restart()
def on_cs_face(self, idx, face):
state, cs = self.get_state(), self.get_control_sheet()
state.face = face
self.face_vector = None
self.target_face_img = None
if face is not None:
try:
self.target_face_img = lib_cv2.imread(self.faces_path / face)
except Exception as e:
cs.face.unselect()
self.save_state()
self.reemit_frame_signal.send()
def on_cs_adjust_c(self, adjust_c):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.adjust_c.get_config()
adjust_c = state.adjust_c = np.clip(adjust_c, cfg.min, cfg.max)
cs.adjust_c.set_number(adjust_c)
self.face_vector = None
self.save_state()
self.reemit_frame_signal.send()
def on_cs_adjust_x(self, adjust_x):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.adjust_x.get_config()
adjust_x = state.adjust_x = np.clip(adjust_x, cfg.min, cfg.max)
cs.adjust_x.set_number(adjust_x)
self.face_vector = None
self.save_state()
self.reemit_frame_signal.send()
def on_cs_adjust_y(self, adjust_y):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.adjust_y.get_config()
adjust_y = state.adjust_y = np.clip(adjust_y, cfg.min, cfg.max)
cs.adjust_y.set_number(adjust_y)
self.face_vector = None
self.save_state()
self.reemit_frame_signal.send()
def on_cs_animator_face_id(self, animator_face_id):
state, cs = self.get_state(), self.get_control_sheet()
cfg = cs.animator_face_id.get_config()
animator_face_id = state.animator_face_id = int(np.clip(animator_face_id, cfg.min, cfg.max))
cs.animator_face_id.set_number(animator_face_id)
self.save_state()
self.reemit_frame_signal.send()
def on_tick(self):
state, cs = self.get_state(), self.get_control_sheet()
if self.pending_bcd is None:
self.start_profile_timing()
bcd = self.bc_in.read(timeout=0.005)
if bcd is not None:
bcd.assign_weak_heap(self.weak_heap)
if self.face_vector is None and self.target_face_img is not None:
rects = self.face_detector.extract (self.target_face_img, threshold=0.5)[0]
if len(rects) > 0:
_,H,W,_ = ImageProcessor(self.target_face_img).get_dims()
u_rects = [ FRect.from_ltrb( (l/W, t/H, r/W, b/H) ) for l,t,r,b in rects ]
face_urect = FRect.sort_by_area_size(u_rects)[0] # sorted by largest
face_image, face_uni_mat = face_urect.cut(self.target_face_img, 1.6, 192)
lmrks = self.face_marker.extract(face_image)[0]
lmrks = lmrks[...,0:2] / (192,192)
face_ulmrks = FLandmarks2D.create (ELandmarks2D.L106, lmrks).transform(face_uni_mat, invert=True)
face_align_img, _ = face_ulmrks.cut(self.target_face_img, state.adjust_c,
self.swap_model.get_face_vector_input_size(),
x_offset=state.adjust_x,
y_offset=state.adjust_y)
self.face_vector = self.swap_model.get_face_vector(face_align_img)
swap_model = self.swap_model
if swap_model is not None and self.face_vector is not None:
for i, fsi in enumerate(bcd.get_face_swap_info_list()):
if state.animator_face_id == i:
face_align_image = bcd.get_image(fsi.face_align_image_name)
if face_align_image is not None:
_,H,W,_ = ImageProcessor(face_align_image).get_dims()
anim_image = swap_model.generate(face_align_image, self.face_vector)
anim_image = ImageProcessor(anim_image).resize((W,H)).get_image('HWC')
fsi.face_align_mask_name = f'{fsi.face_align_image_name}_mask'
fsi.face_swap_image_name = f'{fsi.face_align_image_name}_swapped'
fsi.face_swap_mask_name = f'{fsi.face_swap_image_name}_mask'
bcd.set_image(fsi.face_swap_image_name, anim_image)
white_mask = np.full_like(anim_image, 255, dtype=np.uint8)
bcd.set_image(fsi.face_align_mask_name, white_mask)
bcd.set_image(fsi.face_swap_mask_name, white_mask)
break
self.stop_profile_timing()
self.pending_bcd = bcd
if self.pending_bcd is not None:
if self.bc_out.is_full_read(1):
self.bc_out.write(self.pending_bcd)
self.pending_bcd = None
else:
time.sleep(0.001)
class Sheet:
class Host(lib_csw.Sheet.Host):
def __init__(self):
super().__init__()
self.device = lib_csw.DynamicSingleSwitch.Client()
self.face = lib_csw.DynamicSingleSwitch.Client()
self.animator_face_id = lib_csw.Number.Client()
self.update_faces = lib_csw.Signal.Client()
self.adjust_c = lib_csw.Number.Client()
self.adjust_x = lib_csw.Number.Client()
self.adjust_y = lib_csw.Number.Client()
class Worker(lib_csw.Sheet.Worker):
def __init__(self):
super().__init__()
self.device = lib_csw.DynamicSingleSwitch.Host()
self.face = lib_csw.DynamicSingleSwitch.Host()
self.animator_face_id = lib_csw.Number.Host()
self.update_faces = lib_csw.Signal.Host()
self.adjust_c = lib_csw.Number.Host()
self.adjust_x = lib_csw.Number.Host()
self.adjust_y = lib_csw.Number.Host()
class WorkerState(BackendWorkerState):
device = None
face : str = None
animator_face_id : int = None
adjust_c : float = None
adjust_x : float = None
adjust_y : float = None

View file

@ -7,7 +7,8 @@ from .FaceAnimator import FaceAnimator
from .FaceDetector import FaceDetector
from .FaceMarker import FaceMarker
from .FaceMerger import FaceMerger
from .FaceSwapper import FaceSwapper
from .FaceSwapInsight import FaceSwapInsight
from .FaceSwapDFM import FaceSwapDFM
from .FileSource import FileSource
from .FrameAdjuster import FrameAdjuster
from .StreamOutput import StreamOutput

View file

@ -4,7 +4,7 @@ from localization import L
from resources.gfx import QXImageDB
from xlib import qt as qtx
from ..backend import FaceSwapper
from ..backend import FaceSwapDFM
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
@ -17,8 +17,8 @@ from .widgets.QSliderCSWNumber import QSliderCSWNumber
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
class QFaceSwapper(QBackendPanel):
def __init__(self, backend : FaceSwapper, dfm_models_path : Path):
class QFaceSwapDFM(QBackendPanel):
def __init__(self, backend : FaceSwapDFM, dfm_models_path : Path):
self._dfm_models_path = dfm_models_path
cs = backend.get_control_sheet()
@ -28,7 +28,7 @@ class QFaceSwapper(QBackendPanel):
q_device_label = QLabelPopupInfo(label=L('@common.device'), popup_info_text=L('@common.help.device') )
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
q_model_label = QLabelPopupInfo(label=L('@QFaceSwapper.model'), popup_info_text=L('@QFaceSwapper.help.model') )
q_model_label = QLabelPopupInfo(label=L('@QFaceSwapDFM.model'), popup_info_text=L('@QFaceSwapDFM.help.model') )
q_model = QComboBoxCSWDynamicSingleSwitch(cs.model, reflect_state_widgets=[q_model_label, btn_open_folder])
q_model_dl_error = self._q_model_dl_error = QErrorCSWError(cs.model_dl_error)
@ -36,31 +36,31 @@ class QFaceSwapper(QBackendPanel):
q_model_info_label = self._q_model_info_label = QLabelPopupInfoCSWInfoLabel(cs.model_info_label)
q_swap_all_faces_label = QLabelPopupInfo(label=L('@QFaceSwapper.swap_all_faces') )
q_swap_all_faces_label = QLabelPopupInfo(label=L('@QFaceSwapDFM.swap_all_faces') )
q_swap_all_faces = QCheckBoxCSWFlag(cs.swap_all_faces, reflect_state_widgets=[q_swap_all_faces_label])
q_face_id_label = QLabelPopupInfo(label=L('@QFaceSwapper.face_id'), popup_info_text=L('@QFaceSwapper.help.face_id') )
q_face_id_label = QLabelPopupInfo(label=L('@common.face_id'), popup_info_text=L('@QFaceSwapDFM.help.face_id') )
q_face_id = QSpinBoxCSWNumber(cs.face_id, reflect_state_widgets=[q_face_id_label])
q_morph_factor_label = QLabelPopupInfo(label=L('@QFaceSwapper.morph_factor'), popup_info_text=L('@QFaceSwapper.help.morph_factor') )
q_morph_factor_label = QLabelPopupInfo(label=L('@QFaceSwapDFM.morph_factor'), popup_info_text=L('@QFaceSwapDFM.help.morph_factor') )
q_morph_factor = QSliderCSWNumber(cs.morph_factor, reflect_state_widgets=[q_morph_factor_label])
q_sharpen_amount_label = QLabelPopupInfo(label=L('@QFaceSwapper.presharpen_amount'), popup_info_text=L('@QFaceSwapper.help.presharpen_amount') )
q_sharpen_amount_label = QLabelPopupInfo(label=L('@QFaceSwapDFM.presharpen_amount'), popup_info_text=L('@QFaceSwapDFM.help.presharpen_amount') )
q_sharpen_amount = QSliderCSWNumber(cs.presharpen_amount, reflect_state_widgets=[q_sharpen_amount_label])
q_pre_gamma_label = QLabelPopupInfo(label=L('@QFaceSwapper.pregamma'), popup_info_text=L('@QFaceSwapper.help.pregamma') )
q_pre_gamma_label = QLabelPopupInfo(label=L('@QFaceSwapDFM.pregamma'), popup_info_text=L('@QFaceSwapDFM.help.pregamma') )
q_pre_gamma_red = QSpinBoxCSWNumber(cs.pre_gamma_red, reflect_state_widgets=[q_pre_gamma_label])
q_pre_gamma_green = QSpinBoxCSWNumber(cs.pre_gamma_green)
q_pre_gamma_blue = QSpinBoxCSWNumber(cs.pre_gamma_blue)
q_post_gamma_label = QLabelPopupInfo(label=L('@QFaceSwapper.postgamma'))
q_post_gamma_label = QLabelPopupInfo(label=L('@QFaceSwapDFM.postgamma'))
q_post_gamma_red = QSpinBoxCSWNumber(cs.post_gamma_red, reflect_state_widgets=[q_post_gamma_label])
q_post_gamma_green = QSpinBoxCSWNumber(cs.post_gamma_green)
q_post_gamma_blue = QSpinBoxCSWNumber(cs.post_gamma_blue)
q_two_pass_label = QLabelPopupInfo(label=L('@QFaceSwapper.two_pass'), popup_info_text=L('@QFaceSwapper.help.two_pass') )
q_two_pass_label = QLabelPopupInfo(label=L('@QFaceSwapDFM.two_pass'), popup_info_text=L('@QFaceSwapDFM.help.two_pass') )
q_two_pass = QCheckBoxCSWFlag(cs.two_pass, reflect_state_widgets=[q_two_pass_label])
grid_l = qtx.QXGridLayout( spacing=5)
@ -94,7 +94,7 @@ class QFaceSwapper(QBackendPanel):
grid_l.addWidget(q_two_pass, row, 1)
row += 1
super().__init__(backend, L('@QFaceSwapper.module_title'),
super().__init__(backend, L('@QFaceSwapDFM.module_title'),
layout=qtx.QXVBoxLayout([grid_l]) )

View file

@ -0,0 +1,71 @@
from pathlib import Path
from localization import L
from resources.gfx import QXImageDB
from xlib import qt as qtx
from ..backend import FaceSwapInsight
from .widgets.QBackendPanel import QBackendPanel
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
QComboBoxCSWDynamicSingleSwitch
from .widgets.QLabelPopupInfo import QLabelPopupInfo
from .widgets.QSliderCSWNumber import QSliderCSWNumber
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
class QFaceSwapInsight(QBackendPanel):
def __init__(self, backend : FaceSwapInsight, faces_path : Path):
self._faces_path = faces_path
cs = backend.get_control_sheet()
btn_open_folder = self.btn_open_folder = qtx.QXPushButton(image = QXImageDB.eye_outline('light gray'), tooltip_text='Reveal in Explorer', released=self._btn_open_folder_released, fixed_size=(24,22) )
q_device_label = QLabelPopupInfo(label=L('@common.device'), popup_info_text=L('@common.help.device') )
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
q_face_label = QLabelPopupInfo(label=L('@QFaceSwapInsight.face') )
q_face = QComboBoxCSWDynamicSingleSwitch(cs.face, reflect_state_widgets=[q_face_label, btn_open_folder])
q_adjust_c_label = QLabelPopupInfo(label='C')
q_adjust_c = QSliderCSWNumber(cs.adjust_c, reflect_state_widgets=[q_adjust_c_label])
q_adjust_x_label = QLabelPopupInfo(label='X')
q_adjust_x = QSliderCSWNumber(cs.adjust_x, reflect_state_widgets=[q_adjust_x_label])
q_adjust_y_label = QLabelPopupInfo(label='Y')
q_adjust_y = QSliderCSWNumber(cs.adjust_y, reflect_state_widgets=[q_adjust_y_label])
q_animator_face_id_label = QLabelPopupInfo(label=L('@common.face_id') )
q_animator_face_id = QSpinBoxCSWNumber(cs.animator_face_id, reflect_state_widgets=[q_animator_face_id_label])
q_update_faces = QXPushButtonCSWSignal(cs.update_faces, image=QXImageDB.reload_outline('light gray'), button_size=(24,22) )
grid_l = qtx.QXGridLayout( spacing=5)
row = 0
grid_l.addWidget(q_device_label, row, 0, alignment=qtx.AlignRight | qtx.AlignVCenter )
grid_l.addWidget(q_device, row, 1, alignment=qtx.AlignLeft )
row += 1
grid_l.addWidget(q_face_label, row, 0, alignment=qtx.AlignRight | qtx.AlignVCenter )
grid_l.addLayout(qtx.QXHBoxLayout([q_face, 2, btn_open_folder, 2, q_update_faces]), row, 1 )
row += 1
grid_l.addWidget(q_adjust_c_label, row, 0, alignment=qtx.AlignRight | qtx.AlignVCenter )
grid_l.addWidget(q_adjust_c, row, 1 )
row += 1
grid_l.addWidget(q_adjust_x_label, row, 0, alignment=qtx.AlignRight | qtx.AlignVCenter )
grid_l.addWidget(q_adjust_x, row, 1 )
row += 1
grid_l.addWidget(q_adjust_y_label, row, 0, alignment=qtx.AlignRight | qtx.AlignVCenter )
grid_l.addWidget(q_adjust_y, row, 1 )
row += 1
grid_l.addWidget(q_animator_face_id_label, row, 0, alignment=qtx.AlignRight | qtx.AlignVCenter )
grid_l.addWidget(q_animator_face_id, row, 1, alignment=qtx.AlignLeft )
row += 1
super().__init__(backend, L('@QFaceSwapInsight.module_title'),
layout=qtx.QXVBoxLayout([grid_l]) )
def _btn_open_folder_released(self):
qtx.QDesktopServices.openUrl(qtx.QUrl.fromLocalFile( str(self._faces_path) ))