code release
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
*
|
||||
!*.py
|
||||
!*.md
|
||||
!*.txt
|
||||
!*.jpg
|
||||
!*.png
|
||||
!requirements*
|
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2018 The Python Packaging Authority
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -37,7 +37,7 @@ You can train your own face model using <a href="https://github.com/iperov/DeepF
|
|||
|
||||
## Minimum system requirements
|
||||
|
||||
NVIDIA GTX 750 or higher (AMD is not supported)
|
||||
NVIDIA GTX 750 or higher (AMD is not supported yet)
|
||||
|
||||
Modern CPU with AVX instructions
|
||||
|
||||
|
|
3982
__dev_archived/_trash.txt
Normal file
BIN
__dev_archived/archived.zip
Normal file
309
apps/DeepFaceLive/DeepFaceLiveApp.py
Normal file
|
@ -0,0 +1,309 @@
|
|||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from localization import L, Localization
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from resources.gfx import QXImageDB
|
||||
from xlib import os as lib_os
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.qt.widgets.QXLabel import QXLabel
|
||||
|
||||
from . import backend
|
||||
from .ui.QCameraSource import QCameraSource
|
||||
from .ui.QFaceAligner import QFaceAligner
|
||||
from .ui.QFaceDetector import QFaceDetector
|
||||
from .ui.QFaceMarker import QFaceMarker
|
||||
from .ui.QFaceMerger import QFaceMerger
|
||||
from .ui.QFaceSwapper import QFaceSwapper
|
||||
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.QBCFinalFrameViewer import QBCFinalFrameViewer
|
||||
from .ui.widgets.QBCFrameViewer import QBCFrameViewer
|
||||
|
||||
|
||||
class QLiveSwap(lib_qt.QXWidget):
|
||||
def __init__(self, userdata_path : Path,
|
||||
settings_dirpath : Path):
|
||||
super().__init__()
|
||||
|
||||
dfm_models_path = userdata_path / 'dfm_models'
|
||||
dfm_models_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
output_sequence_path = userdata_path / 'output_sequence'
|
||||
output_sequence_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Construct backend config
|
||||
|
||||
backend_db = self.backend_db = backend.BackendDB( settings_dirpath / 'states.dat' )
|
||||
backed_weak_heap = self.backed_weak_heap = backend.BackendWeakHeap(size_mb=1024)
|
||||
reemit_frame_signal = self.reemit_frame_signal = backend.BackendSignal()
|
||||
|
||||
multi_sources_bc_out = backend.BackendConnection(multi_producer=True)
|
||||
face_detector_bc_out = backend.BackendConnection()
|
||||
face_marker_bc_out = backend.BackendConnection()
|
||||
face_aligner_bc_out = backend.BackendConnection()
|
||||
face_swapper_bc_out = backend.BackendConnection()
|
||||
frame_adjuster_bc_out = backend.BackendConnection()
|
||||
face_merger_bc_out = backend.BackendConnection()
|
||||
|
||||
file_source = self.file_source = backend.FileSource (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_out=multi_sources_bc_out, backend_db=backend_db)
|
||||
camera_source = self.camera_source = backend.CameraSource (weak_heap=backed_weak_heap, bc_out=multi_sources_bc_out, backend_db=backend_db)
|
||||
face_detector = self.face_detector = backend.FaceDetector (weak_heap=backed_weak_heap, reemit_frame_signal=reemit_frame_signal, bc_in=multi_sources_bc_out, bc_out=face_detector_bc_out, backend_db=backend_db )
|
||||
face_marker = self.face_marker = backend.FaceMarker (weak_heap=backed_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=backed_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_swapper = self.face_swapper = backend.FaceSwapper (weak_heap=backed_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=backed_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=backed_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=backed_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_swapper, frame_adjuster, face_merger, stream_output]
|
||||
|
||||
self.q_file_source = QFileSource(self.file_source)
|
||||
self.q_camera_source = QCameraSource(self.camera_source)
|
||||
self.q_face_detector = QFaceDetector(self.face_detector)
|
||||
self.q_face_marker = QFaceMarker(self.face_marker)
|
||||
self.q_face_aligner = QFaceAligner(self.face_aligner)
|
||||
self.q_face_swapper = QFaceSwapper(self.face_swapper, 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)
|
||||
|
||||
self.q_ds_frame_viewer = QBCFrameViewer(backed_weak_heap, multi_sources_bc_out)
|
||||
self.q_ds_fa_viewer = QBCFaceAlignViewer(backed_weak_heap, face_aligner_bc_out, preview_width=256)
|
||||
self.q_ds_fc_viewer = QBCFaceSwapViewer(backed_weak_heap, face_swapper_bc_out, preview_width=256)
|
||||
self.q_ds_merged_frame_viewer = QBCFinalFrameViewer(backed_weak_heap, face_merger_bc_out)
|
||||
|
||||
q_nodes = lib_qt.QXWidget(size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed),
|
||||
layout=lib_qt.QXHBoxLayout([
|
||||
lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_file_source, self.q_camera_source], spacing=5), fixed_width=256 ),
|
||||
lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_face_detector, self.q_face_aligner,], spacing=5), fixed_width=256),
|
||||
lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_face_marker, self.q_face_swapper], spacing=5), fixed_width=256),
|
||||
lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_frame_adjuster, self.q_face_merger, self.q_stream_output, ], spacing=5), fixed_width=256),
|
||||
], spacing=5))
|
||||
|
||||
q_view_nodes = lib_qt.QXWidget(size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed),
|
||||
layout=lib_qt.QXHBoxLayout([
|
||||
(lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_ds_frame_viewer]), fixed_width=256 ), Qt.AlignmentFlag.AlignTop),
|
||||
(lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_ds_fa_viewer]), fixed_width=256 ), Qt.AlignmentFlag.AlignTop),
|
||||
(lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_ds_fc_viewer]), fixed_width=256 ), Qt.AlignmentFlag.AlignTop),
|
||||
(lib_qt.QXWidget(layout=lib_qt.QXVBoxLayout([self.q_ds_merged_frame_viewer]), fixed_width=256 ), Qt.AlignmentFlag.AlignTop),
|
||||
], spacing=5))
|
||||
|
||||
self.setLayout(lib_qt.QXVBoxLayout(
|
||||
[ (lib_qt.QXWidget( layout=lib_qt.QXVBoxLayout([
|
||||
(q_nodes, Qt.AlignmentFlag.AlignTop),
|
||||
(q_view_nodes, Qt.AlignmentFlag.AlignHCenter),
|
||||
], spacing=5)),
|
||||
Qt.AlignmentFlag.AlignCenter),
|
||||
]))
|
||||
|
||||
self._timer = lib_qt.QXTimer(interval=5, timeout=self._on_timer_5ms, start=True)
|
||||
|
||||
|
||||
def _process_messages(self):
|
||||
self.backend_db.process_messages()
|
||||
for backend in self.all_backends:
|
||||
backend.process_messages()
|
||||
|
||||
def _on_timer_5ms(self):
|
||||
self._process_messages()
|
||||
|
||||
def clear_backend_db(self):
|
||||
self.backend_db.clear()
|
||||
|
||||
def initialize(self):
|
||||
for backend in self.all_backends:
|
||||
backend.restore_on_off_state()
|
||||
|
||||
def finalize(self):
|
||||
# Gracefully stop the backend
|
||||
for backend in self.all_backends:
|
||||
while backend.is_starting() or backend.is_stopping():
|
||||
self._process_messages()
|
||||
|
||||
backend.save_on_off_state()
|
||||
backend.stop()
|
||||
|
||||
while not all( x.is_stopped() for x in self.all_backends ):
|
||||
self._process_messages()
|
||||
|
||||
self.backend_db.finish_pending_jobs()
|
||||
|
||||
self.q_ds_frame_viewer.clear()
|
||||
self.q_ds_fa_viewer.clear()
|
||||
|
||||
class QDFLAppWindow(lib_qt.QXWindow):
|
||||
|
||||
def __init__(self, userdata_path, settings_dirpath):
|
||||
super().__init__(save_load_state=True, size_policy=(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) )
|
||||
|
||||
self._userdata_path = userdata_path
|
||||
self._settings_dirpath = settings_dirpath
|
||||
|
||||
menu_bar = lib_qt.QXMenuBar( font=QXFontDB.get_default_font(size=10), size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.MinimumExpanding) )
|
||||
menu_file = menu_bar.addMenu( L('@QDFLAppWindow.file') )
|
||||
menu_language = menu_bar.addMenu( L('@QDFLAppWindow.language') )
|
||||
|
||||
menu_file_action_reinitialize = menu_file.addAction( L('@QDFLAppWindow.reinitialize') )
|
||||
menu_file_action_reinitialize.triggered.connect(lambda: lib_qt.QXMainApplication.get_singleton().reinitialize() )
|
||||
|
||||
menu_file_action_reset_settings = menu_file.addAction( L('@QDFLAppWindow.reset_modules_settings') )
|
||||
menu_file_action_reset_settings.triggered.connect(self._on_reset_modules_settings)
|
||||
|
||||
menu_file_action_quit = menu_file.addAction( L('@QDFLAppWindow.quit') )
|
||||
menu_file_action_quit.triggered.connect(lambda: lib_qt.QXMainApplication.quit() )
|
||||
|
||||
menu_language_action_english = menu_language.addAction('English' )
|
||||
menu_language_action_english.triggered.connect(lambda: (lib_qt.QXMainApplication.get_singleton().set_language('en-US'), lib_qt.QXMainApplication.get_singleton().reinitialize()) )
|
||||
|
||||
menu_language_action_russian = menu_language.addAction('Русский')
|
||||
menu_language_action_russian.triggered.connect(lambda: (lib_qt.QXMainApplication.get_singleton().set_language('ru-RU'), lib_qt.QXMainApplication.get_singleton().reinitialize()) )
|
||||
|
||||
menu_language_action_chinesse = menu_language.addAction('汉语')
|
||||
menu_language_action_chinesse.triggered.connect(lambda: (lib_qt.QXMainApplication.get_singleton().set_language('zh-CN'), lib_qt.QXMainApplication.get_singleton().reinitialize()) )
|
||||
|
||||
menu_help = menu_bar.addMenu( L('@QDFLAppWindow.help') )
|
||||
menu_help_action_github = menu_help.addAction( L('@QDFLAppWindow.visit_github_page') )
|
||||
menu_help_action_github.triggered.connect(lambda: QDesktopServices.openUrl(QUrl('https://github.com/iperov/DeepFaceLive' )))
|
||||
|
||||
self.q_live_swap = None
|
||||
self.q_live_swap_container = lib_qt.QXWidget()
|
||||
|
||||
self.content_l = lib_qt.QXVBoxLayout()
|
||||
|
||||
cb_process_priority = self._cb_process_priority = lib_qt.QXSaveableComboBox(
|
||||
db_key = '_QDFLAppWindow_process_priority',
|
||||
choices=[lib_os.ProcessPriority.NORMAL, lib_os.ProcessPriority.IDLE],
|
||||
default_choice=lib_os.ProcessPriority.NORMAL,
|
||||
choices_names=[ L('@QDFLAppWindow.process_priority.normal'), L('@QDFLAppWindow.process_priority.lowest') ],
|
||||
on_choice_selected=self._on_cb_process_priority_choice)
|
||||
|
||||
menu_bar_tail = lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([
|
||||
10,
|
||||
QXLabel(text=L('@QDFLAppWindow.process_priority')),
|
||||
4,
|
||||
cb_process_priority,
|
||||
]),
|
||||
size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
|
||||
|
||||
self.setLayout( lib_qt.QXVBoxLayout([ lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([menu_bar, menu_bar_tail, lib_qt.QXFrame() ]), size_policy=(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Fixed)),
|
||||
5,
|
||||
lib_qt.QXWidget(layout=self.content_l)
|
||||
] ))
|
||||
|
||||
self.call_on_closeEvent(self._on_closeEvent)
|
||||
|
||||
q_live_swap = self.q_live_swap = QLiveSwap(userdata_path=self._userdata_path, settings_dirpath=self._settings_dirpath)
|
||||
q_live_swap.initialize()
|
||||
self.content_l.addWidget(q_live_swap)
|
||||
|
||||
def _on_reset_modules_settings(self):
|
||||
if self.q_live_swap is not None:
|
||||
self.q_live_swap.clear_backend_db()
|
||||
lib_qt.QXMainApplication.get_singleton().reinitialize()
|
||||
|
||||
def _on_cb_process_priority_choice(self, prio : lib_os.ProcessPriority, _):
|
||||
lib_os.set_process_priority(prio)
|
||||
|
||||
if self.q_live_swap is not None:
|
||||
lib_qt.QXMainApplication.get_singleton().reinitialize()
|
||||
|
||||
def finalize(self):
|
||||
self.q_live_swap.finalize()
|
||||
|
||||
def _on_closeEvent(self):
|
||||
self.finalize()
|
||||
|
||||
class QSplashWindow(lib_qt.QXWindow):
|
||||
def __init__(self, next_window=None):
|
||||
super().__init__()
|
||||
self.setWindowFlags(Qt.WindowType.SplashScreen)
|
||||
|
||||
logo_deepfacelive = lib_qt.QXLabel(image=QXImageDB.splash_deepfacelive())
|
||||
self.setLayout( lib_qt.QXVBoxLayout([
|
||||
( lib_qt.QXHBoxLayout([logo_deepfacelive]), Qt.AlignmentFlag.AlignCenter),
|
||||
], spacing=0, contents_margins=20))
|
||||
|
||||
class DeepFaceLiveApp(lib_qt.QXMainApplication):
|
||||
def __init__(self, userdata_path):
|
||||
self.userdata_path = userdata_path
|
||||
settings_dirpath = self.settings_dirpath = userdata_path / 'settings'
|
||||
if not settings_dirpath.exists():
|
||||
settings_dirpath.mkdir(parents=True)
|
||||
super().__init__(app_name='DeepFaceLive', settings_dirpath=settings_dirpath)
|
||||
|
||||
self.setFont( QXFontDB.get_default_font() )
|
||||
self.setWindowIcon( QXImageDB.app_icon().as_QIcon() )
|
||||
|
||||
splash_wnd = self.splash_wnd = QSplashWindow()
|
||||
splash_wnd.show()
|
||||
splash_wnd.center_on_screen()
|
||||
|
||||
self._dfl_wnd = None
|
||||
self.initialize()
|
||||
self._t = lib_qt.QXTimer(interval=1666, timeout=self._on_splash_wnd_expired, single_shot=True, start=True)
|
||||
|
||||
def on_reinitialize(self):
|
||||
self.finalize()
|
||||
|
||||
import gc
|
||||
gc.collect()
|
||||
gc.collect()
|
||||
|
||||
self.initialize()
|
||||
self._dfl_wnd.show()
|
||||
|
||||
def initialize(self):
|
||||
Localization.set_language( self.get_language() )
|
||||
|
||||
if self._dfl_wnd is None:
|
||||
self._dfl_wnd = QDFLAppWindow(userdata_path=self.userdata_path, settings_dirpath=self.settings_dirpath)
|
||||
|
||||
def finalize(self):
|
||||
if self._dfl_wnd is not None:
|
||||
self._dfl_wnd.close()
|
||||
self._dfl_wnd.deleteLater()
|
||||
self._dfl_wnd = None
|
||||
|
||||
def _on_splash_wnd_expired(self):
|
||||
self._dfl_wnd.show()
|
||||
|
||||
if self.splash_wnd is not None:
|
||||
self.splash_wnd.hide()
|
||||
self.splash_wnd.deleteLater()
|
||||
self.splash_wnd = None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#import gc
|
||||
#gc.collect()
|
||||
#gc.collect()
|
||||
|
||||
# def _reinitialize(self, init_new=True):
|
||||
# if self.q_live_swap is not None:
|
||||
# self.content_l.removeWidget(self.q_live_swap)
|
||||
# self.q_live_swap.finalize()
|
||||
|
||||
# self.q_live_swap.deleteLater()
|
||||
# self.q_live_swap = None
|
||||
|
||||
# import gc
|
||||
# gc.collect()
|
||||
# gc.collect()
|
||||
|
||||
# LStrings.initialize()
|
||||
|
||||
# if init_new:
|
||||
# self.q_live_swap = QLiveSwap(userdata_path=self._userdata_path, settings_dirpath=self._settings_dirpath)
|
||||
# self.q_live_swap.initialize()
|
||||
# self.content_l.addWidget(self.q_live_swap)
|
||||
#self._reinitialize(init_new=False)
|
200
apps/DeepFaceLive/backend/BackendBase.py
Normal file
|
@ -0,0 +1,200 @@
|
|||
import multiprocessing
|
||||
import pickle
|
||||
from typing import List, Union
|
||||
|
||||
import numpy as np
|
||||
from xlib import mp as lib_mp
|
||||
from xlib import time as lib_time
|
||||
from xlib.facemeta import FaceMark
|
||||
from xlib.mp import csw as lib_csw
|
||||
from xlib.python.EventListener import EventListener
|
||||
|
||||
|
||||
class BackendConnectionData:
|
||||
"""
|
||||
data class for BackendConnection
|
||||
|
||||
Should not contain large buffers.
|
||||
Large buffers are stored via MPWeakHeap
|
||||
"""
|
||||
|
||||
def __init__(self, uid ):
|
||||
super().__init__()
|
||||
self._weak_heap = None
|
||||
self._weak_heap_refs = {}
|
||||
self._weak_heap_image_infos = {}
|
||||
|
||||
self._errors = []
|
||||
|
||||
self._uid = uid
|
||||
self._is_frame_reemitted = None
|
||||
|
||||
self._frame_name = None
|
||||
self._frame_count = None
|
||||
self._frame_num = None
|
||||
self._frame_fps = None
|
||||
self._frame_timestamp = None
|
||||
self._merged_frame_name = None
|
||||
self._face_mark_list = []
|
||||
|
||||
def __getstate__(self, ):
|
||||
d = self.__dict__.copy()
|
||||
d['_weak_heap'] = None
|
||||
return d
|
||||
|
||||
def assign_weak_heap(self, weak_heap : lib_mp.MPWeakHeap):
|
||||
self._weak_heap = weak_heap
|
||||
|
||||
def set_file(self, name : str, data : Union[bytes, bytearray, memoryview]):
|
||||
self._weak_heap_refs[name] = self._weak_heap.add_data(data)
|
||||
|
||||
def get_file(self, name : str) -> Union[bytes, None]:
|
||||
ref = self._weak_heap_refs.get(name, None)
|
||||
if ref is not None:
|
||||
return self._weak_heap.get_data(ref)
|
||||
return None
|
||||
|
||||
def set_image(self, name : str, image : np.ndarray):
|
||||
"""
|
||||
store image to weak heap
|
||||
|
||||
name str
|
||||
|
||||
image np.ndarray
|
||||
"""
|
||||
self.set_file(name, image.data)
|
||||
self._weak_heap_image_infos[name] = (image.shape, image.dtype)
|
||||
|
||||
def get_image(self, name : str) -> Union[np.ndarray, None]:
|
||||
if name is None:
|
||||
return None
|
||||
image_info = self._weak_heap_image_infos.get(name, None)
|
||||
buffer = self.get_file(name)
|
||||
|
||||
if image_info is not None and buffer is not None:
|
||||
shape, dtype = image_info
|
||||
return np.ndarray(shape, dtype=dtype, buffer=buffer)
|
||||
return None
|
||||
|
||||
def add_error(self, err : str):
|
||||
self._errors.append(err)
|
||||
|
||||
def get_uid(self) -> int: return self._uid
|
||||
|
||||
def get_is_frame_reemitted(self) -> Union[bool, None]: return self._is_frame_reemitted
|
||||
def set_is_frame_reemitted(self, is_frame_reemitted : bool): self._is_frame_reemitted = is_frame_reemitted
|
||||
|
||||
def get_frame_name(self) -> Union[str, None]: return self._frame_name
|
||||
def set_frame_name(self, frame_name : str): self._frame_name = frame_name
|
||||
|
||||
def get_frame_count(self) -> Union[int, None]: return self._frame_count
|
||||
def set_frame_count(self, frame_count : int): self._frame_count = frame_count
|
||||
def get_frame_num(self) -> Union[int, None]: return self._frame_num
|
||||
def set_frame_num(self, frame_num : int): self._frame_num = frame_num
|
||||
def get_frame_fps(self) -> Union[float, None]: return self._frame_fps
|
||||
def set_frame_fps(self, frame_fps : float): self._frame_fps = frame_fps
|
||||
def get_frame_timestamp(self) -> Union[float, None]: return self._frame_timestamp
|
||||
def set_frame_timestamp(self, frame_timestamp : float): self._frame_timestamp = frame_timestamp
|
||||
|
||||
def get_merged_frame_name(self) -> Union[str, None]: return self._merged_frame_name
|
||||
def set_merged_frame_name(self, merged_frame_name : str): self._merged_frame_name = merged_frame_name
|
||||
|
||||
def get_face_mark_list(self) -> List[FaceMark]: return self._face_mark_list
|
||||
def add_face_mark(self, face_mark : FaceMark):
|
||||
if not isinstance(face_mark, FaceMark):
|
||||
raise ValueError(f'face_mark must be an instance of FaceMark')
|
||||
self._face_mark_list.append(face_mark)
|
||||
|
||||
|
||||
class BackendConnection:
|
||||
def __init__(self, multi_producer=False):
|
||||
self._rd = lib_mp.MPSPSCMRRingData(table_size=8192, heap_size_mb=8, multi_producer=multi_producer)
|
||||
|
||||
def write(self, bcd : BackendConnectionData):
|
||||
self._rd.write( pickle.dumps(bcd) )
|
||||
|
||||
def read(self, timeout : float = 0) -> Union[BackendConnectionData, None]:
|
||||
b = self._rd.read(timeout=timeout)
|
||||
if b is not None:
|
||||
return pickle.loads(b)
|
||||
return None
|
||||
|
||||
def get_write_id(self) -> int:
|
||||
return self._rd.get_write_id()
|
||||
|
||||
def get_by_id(self, id) -> Union[BackendConnectionData, None]:
|
||||
b = self._rd.get_by_id(id)
|
||||
if b is not None:
|
||||
return pickle.loads(b)
|
||||
return None
|
||||
|
||||
def wait_for_read(self, timeout : float) -> bool:
|
||||
"""
|
||||
returns True if ready to .read()
|
||||
"""
|
||||
return self._rd.wait_for_read(timeout)
|
||||
|
||||
def is_full_read(self, buffer_size=0) -> bool:
|
||||
"""
|
||||
if fully readed by receiver side minus buffer_size
|
||||
"""
|
||||
return self._rd.get_read_id() == self._rd.get_write_id()
|
||||
|
||||
|
||||
class BackendSignal:
|
||||
def __init__(self):
|
||||
self._ev = multiprocessing.Event()
|
||||
|
||||
def send(self):
|
||||
self._ev.set()
|
||||
|
||||
def recv(self):
|
||||
is_set = self._ev.is_set()
|
||||
if is_set:
|
||||
self._ev.clear()
|
||||
return is_set
|
||||
|
||||
class BackendWeakHeap(lib_mp.MPWeakHeap):
|
||||
...
|
||||
|
||||
class BackendDB(lib_csw.DB):
|
||||
...
|
||||
|
||||
class BackendWorkerState(lib_csw.WorkerState):
|
||||
...
|
||||
|
||||
class BackendHost(lib_csw.Host):
|
||||
def __init__(self, backend_db : BackendDB = None,
|
||||
sheet_cls = None,
|
||||
worker_cls = None,
|
||||
worker_state_cls : BackendWorkerState = None,
|
||||
worker_start_args = None,
|
||||
worker_start_kwargs = None):
|
||||
|
||||
super().__init__(db=backend_db,
|
||||
sheet_cls = sheet_cls,
|
||||
worker_cls = worker_cls,
|
||||
worker_state_cls = worker_state_cls,
|
||||
worker_start_args = worker_start_args,
|
||||
worker_start_kwargs = worker_start_kwargs)
|
||||
|
||||
self._profile_timing_evl = EventListener()
|
||||
self.call_on_msg('_profile_timing', self._on_profile_timing_msg)
|
||||
|
||||
def _on_profile_timing_msg(self, timing : float):
|
||||
self._profile_timing_evl.call(timing)
|
||||
|
||||
def call_on_profile_timing(self, func_or_list):
|
||||
self._profile_timing_evl.add(func_or_list)
|
||||
|
||||
class BackendWorker(lib_csw.Worker):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._profile_timing_measurer = lib_time.AverageMeasurer(samples=120)
|
||||
|
||||
def start_profile_timing(self):
|
||||
self._profile_timing_measurer.start()
|
||||
|
||||
def stop_profile_timing(self):
|
||||
self.send_msg('_profile_timing', self._profile_timing_measurer.stop() )
|
334
apps/DeepFaceLive/backend/CameraSource.py
Normal file
|
@ -0,0 +1,334 @@
|
|||
import platform
|
||||
import time
|
||||
from datetime import datetime
|
||||
from enum import IntEnum
|
||||
from typing import List
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from xlib import os as lib_os
|
||||
from xlib.image import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendConnectionData, BackendDB,
|
||||
BackendHost, BackendWeakHeap, BackendWorker,
|
||||
BackendWorkerState)
|
||||
|
||||
|
||||
class CameraSource(BackendHost):
|
||||
def __init__(self, weak_heap : BackendWeakHeap, bc_out : BackendConnection, backend_db : BackendDB = None):
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=CameraSourceWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, bc_out] )
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
class _ResolutionType(IntEnum):
|
||||
RES_320x240 = 0
|
||||
RES_640x480 = 1
|
||||
RES_720x480 = 2
|
||||
RES_1280x720 = 3
|
||||
RES_1280x960 = 4
|
||||
RES_1366x768 = 5
|
||||
RES_1920x1080 = 6
|
||||
|
||||
_ResolutionType_names = {_ResolutionType.RES_320x240 : '320x240',
|
||||
_ResolutionType.RES_640x480 : '640x480',
|
||||
_ResolutionType.RES_720x480 : '720x480',
|
||||
_ResolutionType.RES_1280x720 : '1280x720',
|
||||
_ResolutionType.RES_1280x960 : '1280x960',
|
||||
_ResolutionType.RES_1366x768 : '1366x768',
|
||||
_ResolutionType.RES_1920x1080 : '1920x1080',
|
||||
}
|
||||
|
||||
_ResolutionType_wh = {_ResolutionType.RES_320x240: (320,240),
|
||||
_ResolutionType.RES_640x480: (640,480),
|
||||
_ResolutionType.RES_720x480: (720,480),
|
||||
_ResolutionType.RES_1280x720: (1280,720),
|
||||
_ResolutionType.RES_1280x960: (1280,960),
|
||||
_ResolutionType.RES_1366x768: (1366,768),
|
||||
_ResolutionType.RES_1920x1080: (1920,1080),
|
||||
}
|
||||
class _DriverType(IntEnum):
|
||||
DSHOW = 0
|
||||
MSMF = 1
|
||||
GSTREAMER = 2
|
||||
|
||||
_DriverType_names = { _DriverType.DSHOW : 'DirectShow',
|
||||
_DriverType.MSMF : 'Microsoft Media Foundation',
|
||||
_DriverType.GSTREAMER : 'GStreamer',
|
||||
}
|
||||
|
||||
class _RotationType(IntEnum):
|
||||
ROTATION_0 = 0
|
||||
ROTATION_90 = 1
|
||||
ROTATION_180 = 2
|
||||
ROTATION_270 = 3
|
||||
|
||||
_RotationType_names = ['0 degrees', '90 degrees', '180 degrees', '270 degrees']
|
||||
|
||||
class CameraSourceWorker(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, bc_out : BackendConnection):
|
||||
self.weak_heap = weak_heap
|
||||
self.bc_out = bc_out
|
||||
self.bcd_uid = 0
|
||||
self.pending_bcd = None
|
||||
self.vcap = None
|
||||
self.last_timestamp = 0
|
||||
lib_os.set_timer_resolution(4)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
cs.device_idx.call_on_selected(self.on_cs_device_idx_selected)
|
||||
cs.driver.call_on_selected(self.on_cs_driver_selected)
|
||||
cs.resolution.call_on_selected(self.on_cs_resolution_selected)
|
||||
cs.fps.call_on_number(self.on_cs_fps)
|
||||
cs.rotation.call_on_selected(self.on_cs_rotation_selected)
|
||||
cs.flip_horizontal.call_on_flag(self.on_cs_flip_horizontal)
|
||||
cs.open_settings.call_on_signal(self.on_cs_open_settings)
|
||||
cs.load_settings.call_on_signal(self.on_cs_load_settings)
|
||||
cs.save_settings.call_on_signal(self.on_cs_save_settings)
|
||||
|
||||
cs.device_idx.enable()
|
||||
cs.device_idx.set_choices([0,1,2,3], none_choice_name='@misc.menu_select')
|
||||
cs.device_idx.select(state.device_idx)
|
||||
|
||||
cs.driver.enable()
|
||||
cs.driver.set_choices(_DriverType, _DriverType_names, none_choice_name='@misc.menu_select')
|
||||
cs.driver.select(state.driver)
|
||||
|
||||
cs.resolution.enable()
|
||||
cs.resolution.set_choices(_ResolutionType, _ResolutionType_names, none_choice_name=None)
|
||||
cs.resolution.select(state.resolution if state.resolution is not None else _ResolutionType.RES_640x480)
|
||||
|
||||
if state.device_idx is not None and \
|
||||
state.driver is not None:
|
||||
|
||||
cv_api = {_DriverType.DSHOW: cv2.CAP_DSHOW,
|
||||
_DriverType.MSMF: cv2.CAP_MSMF,
|
||||
_DriverType.GSTREAMER: cv2.CAP_GSTREAMER,
|
||||
}[state.driver]
|
||||
|
||||
vcap = cv2.VideoCapture(state.device_idx, cv_api)
|
||||
if vcap.isOpened():
|
||||
self.vcap = vcap
|
||||
w, h = _ResolutionType_wh[state.resolution]
|
||||
|
||||
vcap.set(cv2.CAP_PROP_FRAME_WIDTH, w)
|
||||
vcap.set(cv2.CAP_PROP_FRAME_HEIGHT, h)
|
||||
|
||||
if vcap.isOpened():
|
||||
cs.fps.enable()
|
||||
cs.fps.set_config(lib_csw.Number.Config(min=0, max=240, step=1.0, decimals=2, zero_is_auto=True, allow_instant_update=False))
|
||||
cs.fps.set_number(state.fps if state.fps is not None else 0)
|
||||
|
||||
cs.rotation.enable()
|
||||
cs.rotation.set_choices(_RotationType, _RotationType_names, none_choice_name=None)
|
||||
cs.rotation.select(state.rotation if state.rotation is not None else _RotationType.ROTATION_0)
|
||||
|
||||
cs.flip_horizontal.enable()
|
||||
cs.flip_horizontal.set_flag(state.flip_horizontal if state.flip_horizontal is not None else False)
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
cs.open_settings.enable()
|
||||
|
||||
cs.load_settings.enable()
|
||||
cs.save_settings.enable()
|
||||
else:
|
||||
cs.device_idx.unselect()
|
||||
cs.driver.unselect()
|
||||
|
||||
def on_cs_device_idx_selected(self, idx, device_idx):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
if state.device_idx != device_idx:
|
||||
state.device_idx = device_idx
|
||||
self.save_state()
|
||||
if self.is_started():
|
||||
self.restart()
|
||||
|
||||
def on_cs_driver_selected(self, idx, driver):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
if state.driver != driver:
|
||||
state.driver = driver
|
||||
self.save_state()
|
||||
if self.is_started():
|
||||
self.restart()
|
||||
|
||||
def on_cs_resolution_selected(self, idx, resolution : _ResolutionType):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
if state.resolution != resolution:
|
||||
state.resolution = resolution
|
||||
self.save_state()
|
||||
if self.is_started():
|
||||
self.restart()
|
||||
|
||||
def on_cs_fps(self, fps):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.fps.get_config()
|
||||
fps = state.fps = np.clip(fps, cfg.min, cfg.max)
|
||||
cs.fps.set_number(fps)
|
||||
self.save_state()
|
||||
|
||||
def on_cs_rotation_selected(self, idx, _rot_type : _RotationType):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
state.rotation = _rot_type
|
||||
self.save_state()
|
||||
|
||||
def on_cs_flip_horizontal(self, flip_horizontal):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
state.flip_horizontal = flip_horizontal
|
||||
self.save_state()
|
||||
|
||||
def on_cs_open_settings(self):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
if self.vcap is not None and self.vcap.isOpened():
|
||||
self.vcap.set(cv2.CAP_PROP_SETTINGS, 0)
|
||||
|
||||
def on_cs_load_settings(self):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
|
||||
vcap = self.vcap
|
||||
if vcap is not None:
|
||||
settings = state.settings_by_idx.get(state.device_idx, None)
|
||||
if settings is not None:
|
||||
for setting_name, value in settings.items():
|
||||
setting_id = getattr(cv2, setting_name, None)
|
||||
if setting_id is not None:
|
||||
vcap.set(setting_id, value)
|
||||
|
||||
def on_cs_save_settings(self):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
|
||||
vcap = self.vcap
|
||||
if vcap is not None:
|
||||
settings = {}
|
||||
for setting_name in self._get_vcap_setting_name_list():
|
||||
setting_id = getattr(cv2, setting_name, None)
|
||||
if setting_id is not None:
|
||||
settings[setting_name] = vcap.get(setting_id)
|
||||
state.settings_by_idx[state.device_idx] = settings
|
||||
self.save_state()
|
||||
|
||||
def on_tick(self):
|
||||
if self.vcap is not None and not self.vcap.isOpened():
|
||||
self.set_vcap(None)
|
||||
|
||||
if self.vcap is not None:
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
self.start_profile_timing()
|
||||
|
||||
ret, img = self.vcap.read()
|
||||
if ret:
|
||||
timestamp = datetime.now().timestamp()
|
||||
fps = state.fps
|
||||
if fps == 0 or ((timestamp - self.last_timestamp) > 1.0 / fps):
|
||||
self.last_timestamp = timestamp
|
||||
|
||||
ip = ImageProcessor(img)
|
||||
#if state.target_width != 0:
|
||||
# ip.fit_in(TW=state.target_width)
|
||||
|
||||
ip.ch(3).to_uint8()
|
||||
|
||||
rotation = state.rotation
|
||||
if rotation == _RotationType.ROTATION_90:
|
||||
ip.rotate90()
|
||||
elif rotation == _RotationType.ROTATION_180:
|
||||
ip.rotate180()
|
||||
elif rotation == _RotationType.ROTATION_270:
|
||||
ip.rotate270()
|
||||
|
||||
if state.flip_horizontal:
|
||||
ip.flip_horizontal()
|
||||
|
||||
img = ip.get_image('HWC')
|
||||
|
||||
bcd_uid = self.bcd_uid = self.bcd_uid + 1
|
||||
bcd = BackendConnectionData(uid=bcd_uid)
|
||||
|
||||
bcd.assign_weak_heap(self.weak_heap)
|
||||
frame_name = f'Camera_{state.device_idx}_{bcd_uid:06}'
|
||||
bcd.set_frame_name(frame_name)
|
||||
bcd.set_frame_num(bcd_uid)
|
||||
bcd.set_frame_timestamp(timestamp)
|
||||
bcd.set_image(frame_name, img)
|
||||
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
|
||||
|
||||
time.sleep(0.001)
|
||||
|
||||
def set_vcap(self, vcap):
|
||||
if self.vcap is not None:
|
||||
if self.vcap.isOpened():
|
||||
self.vcap.release()
|
||||
self.vcap = None
|
||||
self.vcap = vcap
|
||||
|
||||
def on_stop(self):
|
||||
if self.vcap is not None:
|
||||
if self.vcap.isOpened():
|
||||
self.vcap.release()
|
||||
self.vcap = None
|
||||
|
||||
def _get_vcap_setting_name_list(self) -> List[str]:
|
||||
return ['CAP_PROP_BRIGHTNESS',
|
||||
'CAP_PROP_CONTRAST',
|
||||
'CAP_PROP_SATURATION',
|
||||
'CAP_PROP_HUE',
|
||||
'CAP_PROP_SHARPNESS',
|
||||
'CAP_PROP_GAMMA',
|
||||
'CAP_PROP_AUTO_WB',
|
||||
'CAP_PROP_XI_AUTO_WB',
|
||||
'CAP_PROP_XI_MANUAL_WB',
|
||||
'CAP_PROP_WB_TEMPERATURE',
|
||||
'CAP_PROP_BACKLIGHT',
|
||||
'CAP_PROP_GAIN',
|
||||
'CAP_PROP_AUTO_EXPOSURE',
|
||||
'CAP_PROP_EXPOSURE']
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
def __init__(self):
|
||||
self.device_idx : int = None
|
||||
self.driver : _DriverType = None
|
||||
self.resolution : _ResolutionType = None
|
||||
self.fps : float = None
|
||||
self.rotation : _RotationType = None
|
||||
self.flip_horizontal : bool = None
|
||||
self.settings_by_idx = {}
|
||||
|
||||
class Sheet:
|
||||
class Host(lib_csw.Sheet.Host):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.device_idx = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.driver = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.resolution = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.fps = lib_csw.Number.Client()
|
||||
self.rotation = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.flip_horizontal = lib_csw.Flag.Client()
|
||||
self.open_settings = lib_csw.Signal.Client()
|
||||
self.save_settings = lib_csw.Signal.Client()
|
||||
self.load_settings = lib_csw.Signal.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.device_idx = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.driver = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.resolution = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.fps = lib_csw.Number.Host()
|
||||
self.rotation = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.flip_horizontal = lib_csw.Flag.Host()
|
||||
self.open_settings = lib_csw.Signal.Host()
|
||||
self.save_settings = lib_csw.Signal.Host()
|
||||
self.load_settings = lib_csw.Signal.Host()
|
140
apps/DeepFaceLive/backend/FaceAligner.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
import time
|
||||
|
||||
import numpy as np
|
||||
from xlib import os as lib_os
|
||||
from xlib.facemeta import FaceAlign, FaceULandmarks
|
||||
from xlib.mp import csw as lib_csw
|
||||
from xlib.python import all_is_not_None
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||
BackendWorkerState)
|
||||
|
||||
|
||||
class FaceAligner(BackendHost):
|
||||
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, backend_db : BackendDB = None):
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=FaceAlignerWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out])
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
class FaceAlignerWorker(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):
|
||||
self.weak_heap = weak_heap
|
||||
self.reemit_frame_signal = reemit_frame_signal
|
||||
self.bc_in = bc_in
|
||||
self.bc_out = bc_out
|
||||
self.pending_bcd = None
|
||||
lib_os.set_timer_resolution(1)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cs.face_coverage.call_on_number(self.on_cs_face_coverage)
|
||||
cs.resolution.call_on_number(self.on_cs_resolution)
|
||||
cs.exclude_moving_parts.call_on_flag(self.on_cs_exclude_moving_parts)
|
||||
|
||||
cs.face_coverage.enable()
|
||||
cs.face_coverage.set_config(lib_csw.Number.Config(min=0.1, max=4.0, step=0.1, decimals=1, allow_instant_update=True))
|
||||
cs.face_coverage.set_number(state.face_coverage if state.face_coverage is not None else 2.2)
|
||||
|
||||
cs.resolution.enable()
|
||||
cs.resolution.set_config(lib_csw.Number.Config(min=16, max=1024, step=16, decimals=0, allow_instant_update=True))
|
||||
cs.resolution.set_number(state.resolution if state.resolution is not None else 224)
|
||||
|
||||
cs.exclude_moving_parts.enable()
|
||||
|
||||
cs.exclude_moving_parts.set_flag(state.exclude_moving_parts if state.exclude_moving_parts is not None else True)
|
||||
|
||||
def on_cs_face_coverage(self, face_coverage):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.face_coverage.get_config()
|
||||
face_coverage = state.face_coverage = np.clip(face_coverage, cfg.min, cfg.max)
|
||||
cs.face_coverage.set_number(face_coverage)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_resolution(self, resolution):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.resolution.get_config()
|
||||
resolution = state.resolution = int(np.clip( (resolution//16)*16, cfg.min, cfg.max))
|
||||
cs.resolution.set_number(resolution)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_exclude_moving_parts(self, exclude_moving_parts):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
state.exclude_moving_parts = exclude_moving_parts
|
||||
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)
|
||||
|
||||
frame_name = bcd.get_frame_name()
|
||||
frame_image = bcd.get_image(frame_name)
|
||||
|
||||
if all_is_not_None(state.face_coverage, state.resolution, frame_name, frame_image):
|
||||
for face_id,face_mark in enumerate( bcd.get_face_mark_list() ):
|
||||
face_ulmrks = face_mark.get_face_ulandmarks_by_type(FaceULandmarks.Type.LANDMARKS_2D_468)
|
||||
if face_ulmrks is None:
|
||||
face_ulmrks = face_mark.get_face_ulandmarks_by_type(FaceULandmarks.Type.LANDMARKS_2D_68)
|
||||
|
||||
if face_ulmrks is not None:
|
||||
face_image, uni_mat = face_ulmrks.cut(frame_image, state.face_coverage, state.resolution, exclude_moving_parts=state.exclude_moving_parts)
|
||||
|
||||
face_align_image_name = f'{frame_name}_{face_id}_aligned'
|
||||
|
||||
face_align = FaceAlign()
|
||||
face_align.set_image_name(face_align_image_name)
|
||||
face_align.set_coverage(state.face_coverage)
|
||||
face_align.set_source_face_ulandmarks_type(face_ulmrks.get_type())
|
||||
face_align.set_source_to_aligned_uni_mat(uni_mat)
|
||||
|
||||
for face_ulmrks in face_mark.get_face_ulandmarks_list():
|
||||
face_align.add_face_ulandmarks( face_ulmrks.transform(uni_mat) )
|
||||
face_mark.set_face_align(face_align)
|
||||
|
||||
bcd.set_image(face_align_image_name, face_image)
|
||||
|
||||
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.face_coverage = lib_csw.Number.Client()
|
||||
self.resolution = lib_csw.Number.Client()
|
||||
self.exclude_moving_parts = lib_csw.Flag.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.face_coverage = lib_csw.Number.Host()
|
||||
self.resolution = lib_csw.Number.Host()
|
||||
self.exclude_moving_parts = lib_csw.Flag.Host()
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
face_coverage : float = None
|
||||
resolution : int = None
|
||||
exclude_moving_parts : bool = None
|
326
apps/DeepFaceLive/backend/FaceDetector.py
Normal file
|
@ -0,0 +1,326 @@
|
|||
import time
|
||||
from enum import IntEnum
|
||||
|
||||
import numpy as np
|
||||
from modelhub import onnx as onnx_models
|
||||
from xlib import os as lib_os
|
||||
from xlib.facemeta import FaceMark, FaceURect
|
||||
from xlib.image import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
from xlib.python import all_is_not_None
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||
BackendWorkerState)
|
||||
|
||||
|
||||
class DetectorType(IntEnum):
|
||||
CENTER_FACE = 0
|
||||
S3FD = 1
|
||||
YOLOV5 = 2
|
||||
|
||||
DetectorTypeNames = ['CenterFace', 'S3FD', 'YoloV5']
|
||||
|
||||
class FaceSortBy(IntEnum):
|
||||
LARGEST = 0
|
||||
DIST_FROM_CENTER = 1
|
||||
|
||||
FaceSortByNames = ['@FaceDetector.largest', '@FaceDetector.dist_from_center']
|
||||
|
||||
class FaceDetector(BackendHost):
|
||||
def __init__(self, weak_heap : BackendWeakHeap,
|
||||
reemit_frame_signal : BackendSignal,
|
||||
bc_in : BackendConnection,
|
||||
bc_out : BackendConnection,
|
||||
backend_db : BackendDB = None):
|
||||
self._weak_heap = weak_heap
|
||||
self._bc_out = bc_out
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=FaceDetectorWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out] )
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
def get_weak_heap(self) -> BackendWeakHeap: return self._weak_heap
|
||||
def get_bc_out(self) -> BackendConnection: return self._bc_out
|
||||
|
||||
class FaceDetectorWorker(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):
|
||||
|
||||
self.weak_heap = weak_heap
|
||||
self.reemit_frame_signal = reemit_frame_signal
|
||||
self.bc_in = bc_in
|
||||
self.bc_out = bc_out
|
||||
self.pending_bcd = None
|
||||
|
||||
self.temporal_rects = []
|
||||
self.CenterFace = None
|
||||
self.S3FD = None
|
||||
self.YoloV5Face = None
|
||||
|
||||
lib_os.set_timer_resolution(1)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cs.detector_type.call_on_selected(self.on_cs_detector_type)
|
||||
cs.device.call_on_selected(self.on_cs_devices)
|
||||
cs.fixed_window_size.call_on_number(self.on_cs_fixed_window_size)
|
||||
cs.threshold.call_on_number(self.on_cs_threshold)
|
||||
cs.max_faces.call_on_number(self.on_cs_max_faces)
|
||||
cs.sort_by.call_on_selected(self.on_cs_sort_by)
|
||||
cs.temporal_smoothing.call_on_number(self.on_cs_temporal_smoothing)
|
||||
|
||||
cs.detector_type.enable()
|
||||
cs.detector_type.set_choices(DetectorType, DetectorTypeNames, none_choice_name='@misc.menu_select')
|
||||
cs.detector_type.select(state.detector_type)
|
||||
|
||||
|
||||
def on_cs_detector_type(self, idx, detector_type):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
if state.detector_type == detector_type:
|
||||
if detector_type == DetectorType.CENTER_FACE:
|
||||
cs.device.enable()
|
||||
cs.device.set_choices( onnx_models.CenterFace.get_available_devices(), none_choice_name='@misc.menu_select')
|
||||
cs.device.select(state.center_face_state.device)
|
||||
elif detector_type == DetectorType.S3FD:
|
||||
cs.device.enable()
|
||||
cs.device.set_choices (onnx_models.S3FD.get_available_devices(), none_choice_name='@misc.menu_select')
|
||||
cs.device.select(state.S3FD_state.device)
|
||||
elif detector_type == DetectorType.YOLOV5:
|
||||
cs.device.enable()
|
||||
cs.device.set_choices (onnx_models.YoloV5Face.get_available_devices(), none_choice_name='@misc.menu_select')
|
||||
cs.device.select(state.YoloV5_state.device)
|
||||
else:
|
||||
state.detector_type = detector_type
|
||||
self.save_state()
|
||||
self.restart()
|
||||
|
||||
|
||||
|
||||
def on_cs_devices(self, idx, device):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
detector_type = state.detector_type
|
||||
|
||||
if device is not None and \
|
||||
(detector_type == DetectorType.CENTER_FACE and state.center_face_state.device == device) or \
|
||||
(detector_type == DetectorType.S3FD and state.S3FD_state.device == device) or \
|
||||
(detector_type == DetectorType.YOLOV5 and state.YoloV5_state.device == device):
|
||||
|
||||
detector_state = state.get_detector_state()
|
||||
|
||||
cs.max_faces.enable()
|
||||
cs.max_faces.set_config(lib_csw.Number.Config(min=0, max=16, step=1, decimals=0, allow_instant_update=True))
|
||||
cs.max_faces.set_number(detector_state.max_faces or 1)
|
||||
|
||||
if detector_type in [DetectorType.CENTER_FACE, DetectorType.S3FD, DetectorType.YOLOV5]:
|
||||
cs.fixed_window_size.enable()
|
||||
cs.fixed_window_size.set_config(lib_csw.Number.Config(min=0, max=4096, step=32, decimals=0, zero_is_auto=True, allow_instant_update=True))
|
||||
cs.fixed_window_size.set_number(detector_state.fixed_window_size or 0)
|
||||
|
||||
cs.threshold.enable()
|
||||
cs.threshold.set_config(lib_csw.Number.Config(min=0.01, max=1.0, step=0.01, decimals=2, allow_instant_update=True))
|
||||
cs.threshold.set_number(detector_state.threshold or 0.5)
|
||||
|
||||
cs.sort_by.enable()
|
||||
cs.sort_by.set_choices(FaceSortBy, FaceSortByNames)
|
||||
cs.sort_by.select(detector_state.sort_by if detector_state.sort_by is not None else FaceSortBy.LARGEST)
|
||||
|
||||
cs.temporal_smoothing.enable()
|
||||
cs.temporal_smoothing.set_config(lib_csw.Number.Config(min=1, max=10, step=1, allow_instant_update=True))
|
||||
cs.temporal_smoothing.set_number(detector_state.temporal_smoothing or 1)
|
||||
|
||||
if detector_type == DetectorType.CENTER_FACE:
|
||||
self.CenterFace = onnx_models.CenterFace(device)
|
||||
elif detector_type == DetectorType.S3FD:
|
||||
self.S3FD = onnx_models.S3FD(device)
|
||||
elif detector_type == DetectorType.YOLOV5:
|
||||
self.YoloV5Face = onnx_models.YoloV5Face(device)
|
||||
else:
|
||||
if detector_type == DetectorType.CENTER_FACE:
|
||||
state.center_face_state.device = device
|
||||
elif detector_type == DetectorType.S3FD:
|
||||
state.S3FD_state.device = device
|
||||
elif detector_type == DetectorType.YOLOV5:
|
||||
state.YoloV5_state.device = device
|
||||
|
||||
self.save_state()
|
||||
self.restart()
|
||||
|
||||
|
||||
def on_cs_fixed_window_size(self, fixed_window_size):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.fixed_window_size.get_config()
|
||||
fixed_window_size = state.get_detector_state().fixed_window_size = int(np.clip(fixed_window_size, cfg.min, cfg.max))
|
||||
cs.fixed_window_size.set_number(fixed_window_size)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_threshold(self, threshold):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.threshold.get_config()
|
||||
threshold = state.get_detector_state().threshold = np.clip(threshold, cfg.min, cfg.max)
|
||||
cs.threshold.set_number(threshold)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_max_faces(self, max_faces):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.max_faces.get_config()
|
||||
max_faces = state.get_detector_state().max_faces = np.clip(max_faces, cfg.min, cfg.max)
|
||||
cs.max_faces.set_number(max_faces)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_sort_by(self, idx, sort_by):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
state.get_detector_state().sort_by = sort_by
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_temporal_smoothing(self, temporal_smoothing):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.temporal_smoothing.get_config()
|
||||
temporal_smoothing = state.get_detector_state().temporal_smoothing = int(np.clip(temporal_smoothing, cfg.min, cfg.max))
|
||||
if temporal_smoothing == 1:
|
||||
self.temporal_rects = []
|
||||
cs.temporal_smoothing.set_number(temporal_smoothing)
|
||||
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)
|
||||
is_frame_reemitted = bcd.get_is_frame_reemitted()
|
||||
|
||||
detector_type = state.detector_type
|
||||
if (detector_type == DetectorType.CENTER_FACE and self.CenterFace is not None) or \
|
||||
(detector_type == DetectorType.S3FD and self.S3FD is not None) or \
|
||||
(detector_type == DetectorType.YOLOV5 and self.YoloV5Face is not None):
|
||||
|
||||
detector_state = state.get_detector_state()
|
||||
|
||||
frame_name = bcd.get_frame_name()
|
||||
frame_image = bcd.get_image(frame_name)
|
||||
|
||||
if all_is_not_None(frame_image, frame_name):
|
||||
_,H,W,_ = ImageProcessor(frame_image).get_dims()
|
||||
|
||||
rects = []
|
||||
if detector_type == DetectorType.CENTER_FACE:
|
||||
rects = self.CenterFace.extract (frame_image, threshold=detector_state.threshold, fixed_window=detector_state.fixed_window_size)[0]
|
||||
elif detector_type == DetectorType.S3FD:
|
||||
rects = self.S3FD.extract (frame_image, threshold=detector_state.threshold, fixed_window=detector_state.fixed_window_size)[0]
|
||||
elif detector_type == DetectorType.YOLOV5:
|
||||
rects = self.YoloV5Face.extract (frame_image, threshold=detector_state.threshold, fixed_window=detector_state.fixed_window_size)[0]
|
||||
|
||||
# to list of FaceURect
|
||||
rects = [ FaceURect.from_ltrb( (l/W, t/H, r/W, b/H) ) for l,t,r,b in rects ]
|
||||
|
||||
# sort
|
||||
if detector_state.sort_by == FaceSortBy.LARGEST:
|
||||
rects = FaceURect.sort_by_area_size(rects)
|
||||
elif detector_state.sort_by == FaceSortBy.DIST_FROM_CENTER:
|
||||
rects = FaceURect.sort_by_dist_from_center(rects)
|
||||
|
||||
if len(rects) != 0:
|
||||
max_faces = detector_state.max_faces
|
||||
if max_faces != 0 and len(rects) > max_faces:
|
||||
rects = rects[:max_faces]
|
||||
|
||||
if detector_state.temporal_smoothing != 1:
|
||||
if len(self.temporal_rects) != len(rects):
|
||||
self.temporal_rects = [ [] for _ in range(len(rects)) ]
|
||||
|
||||
for face_id, face_rect in enumerate(rects):
|
||||
if detector_state.temporal_smoothing != 1:
|
||||
if not is_frame_reemitted or len(self.temporal_rects[face_id]) == 0:
|
||||
self.temporal_rects[face_id].append( face_rect.as_4pts() )
|
||||
|
||||
self.temporal_rects[face_id] = self.temporal_rects[face_id][-detector_state.temporal_smoothing:]
|
||||
|
||||
face_rect = FaceURect.from_4pts ( np.mean(self.temporal_rects[face_id],0 ) )
|
||||
|
||||
if face_rect.get_area() != 0:
|
||||
face_mark = FaceMark()
|
||||
face_mark.set_image_name(frame_name)
|
||||
face_mark.set_face_urect ( face_rect )
|
||||
bcd.add_face_mark(face_mark)
|
||||
|
||||
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.detector_type = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.device = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.sort_by = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.fixed_window_size = lib_csw.Number.Client()
|
||||
self.threshold = lib_csw.Number.Client()
|
||||
self.max_faces = lib_csw.Number.Client()
|
||||
self.temporal_smoothing = lib_csw.Number.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.detector_type = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.device = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.sort_by = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.fixed_window_size = lib_csw.Number.Host()
|
||||
self.threshold = lib_csw.Number.Host()
|
||||
self.max_faces = lib_csw.Number.Host()
|
||||
self.temporal_smoothing = lib_csw.Number.Host()
|
||||
|
||||
class DetectorState(BackendWorkerState):
|
||||
fixed_window_size : int = None
|
||||
threshold : float = None
|
||||
max_faces : int = None
|
||||
sort_by : FaceSortBy = None
|
||||
temporal_smoothing : int = None
|
||||
|
||||
class CenterFaceState(BackendWorkerState):
|
||||
device = None
|
||||
|
||||
class S3FDState(BackendWorkerState):
|
||||
device = None
|
||||
|
||||
class YoloV5FaceState(BackendWorkerState):
|
||||
device = None
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
def __init__(self):
|
||||
self.detector_type : DetectorType = None
|
||||
self.detector_state = {}
|
||||
self.center_face_state = CenterFaceState()
|
||||
self.S3FD_state = S3FDState()
|
||||
self.YoloV5_state = YoloV5FaceState()
|
||||
|
||||
def get_detector_state(self) -> DetectorState:
|
||||
state = self.detector_state.get(self.detector_type, None)
|
||||
if state is None:
|
||||
state = self.detector_state[self.detector_type] = DetectorState()
|
||||
return state
|
299
apps/DeepFaceLive/backend/FaceMarker.py
Normal file
|
@ -0,0 +1,299 @@
|
|||
import time
|
||||
from enum import IntEnum
|
||||
import numpy as np
|
||||
from modelhub import onnx as onnx_models
|
||||
from xlib import cv as lib_cv
|
||||
from xlib import os as lib_os
|
||||
from xlib.facemeta import FaceULandmarks
|
||||
from xlib.image import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
from xlib.python import all_is_not_None
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||
BackendWorkerState)
|
||||
|
||||
|
||||
class MarkerType(IntEnum):
|
||||
OPENCV_LBF = 0
|
||||
GOOGLE_FACEMESH = 1
|
||||
|
||||
MarkerTypeNames = ['OpenCV LBF','Google FaceMesh']
|
||||
|
||||
class FaceMarker(BackendHost):
|
||||
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, backend_db : BackendDB = None):
|
||||
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=FaceMarkerWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out, ] )
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
|
||||
class FaceMarkerWorker(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,
|
||||
):
|
||||
self.weak_heap = weak_heap
|
||||
self.reemit_frame_signal = reemit_frame_signal
|
||||
self.bc_in = bc_in
|
||||
self.bc_out = bc_out
|
||||
self.pending_bcd = None
|
||||
self.opencv_lbf = None
|
||||
self.google_facemesh = None
|
||||
self.temporal_lmrks = []
|
||||
|
||||
lib_os.set_timer_resolution(1)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cs.marker_type.call_on_selected(self.on_cs_marker_type)
|
||||
cs.device.call_on_selected(self.on_cs_devices)
|
||||
cs.marker_coverage.call_on_number(self.on_cs_marker_coverage)
|
||||
cs.temporal_smoothing.call_on_number(self.on_cs_temporal_smoothing)
|
||||
|
||||
cs.marker_type.enable()
|
||||
cs.marker_type.set_choices(MarkerType, MarkerTypeNames, none_choice_name='@misc.menu_select')
|
||||
cs.marker_type.select(state.marker_type)
|
||||
|
||||
def on_cs_marker_type(self, idx, marker_type):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
if state.marker_type == marker_type:
|
||||
cs.device.enable()
|
||||
if marker_type == MarkerType.OPENCV_LBF:
|
||||
cs.device.set_choices(['CPU'], none_choice_name='@misc.menu_select')
|
||||
cs.device.select(state.opencv_lbf_state.device)
|
||||
elif marker_type == MarkerType.GOOGLE_FACEMESH:
|
||||
cs.device.set_choices(onnx_models.FaceMesh.get_available_devices(), none_choice_name='@misc.menu_select')
|
||||
cs.device.select(state.google_facemesh_state.device)
|
||||
else:
|
||||
state.marker_type = marker_type
|
||||
self.save_state()
|
||||
self.restart()
|
||||
|
||||
def on_cs_devices(self, idx, device):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
marker_type = state.marker_type
|
||||
|
||||
if device is not None and \
|
||||
( (marker_type == MarkerType.OPENCV_LBF and state.opencv_lbf_state.device == device) or \
|
||||
(marker_type == MarkerType.GOOGLE_FACEMESH and state.google_facemesh_state.device == device) ):
|
||||
marker_state = state.get_marker_state()
|
||||
|
||||
if state.marker_type == MarkerType.OPENCV_LBF:
|
||||
self.opencv_lbf = lib_cv.FaceMarkerLBF()
|
||||
elif state.marker_type == MarkerType.GOOGLE_FACEMESH:
|
||||
self.google_facemesh = onnx_models.FaceMesh(state.google_facemesh_state.device)
|
||||
|
||||
cs.marker_coverage.enable()
|
||||
cs.marker_coverage.set_config(lib_csw.Number.Config(min=0.1, max=3.0, step=0.1, decimals=1, allow_instant_update=True))
|
||||
|
||||
marker_coverage = marker_state.marker_coverage
|
||||
if marker_coverage is None:
|
||||
if marker_type == MarkerType.OPENCV_LBF:
|
||||
marker_coverage = 1.1
|
||||
elif marker_type == MarkerType.GOOGLE_FACEMESH:
|
||||
marker_coverage = 1.3
|
||||
cs.marker_coverage.set_number(marker_coverage)
|
||||
|
||||
cs.temporal_smoothing.enable()
|
||||
cs.temporal_smoothing.set_config(lib_csw.Number.Config(min=1, max=10, step=1, allow_instant_update=True))
|
||||
cs.temporal_smoothing.set_number(marker_state.temporal_smoothing if marker_state.temporal_smoothing is not None else 1)
|
||||
|
||||
else:
|
||||
if marker_type == MarkerType.OPENCV_LBF:
|
||||
state.opencv_lbf_state.device = device
|
||||
elif marker_type == MarkerType.GOOGLE_FACEMESH:
|
||||
state.google_facemesh_state.device = device
|
||||
self.save_state()
|
||||
self.restart()
|
||||
|
||||
|
||||
def on_cs_marker_coverage(self, marker_coverage):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.marker_coverage.get_config()
|
||||
marker_coverage = state.get_marker_state().marker_coverage = np.clip(marker_coverage, cfg.min, cfg.max)
|
||||
cs.marker_coverage.set_number(marker_coverage)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
|
||||
def on_cs_temporal_smoothing(self, temporal_smoothing):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.temporal_smoothing.get_config()
|
||||
temporal_smoothing = state.get_marker_state().temporal_smoothing = int(np.clip(temporal_smoothing, cfg.min, cfg.max))
|
||||
if temporal_smoothing == 1:
|
||||
self.temporal_lmrks = []
|
||||
cs.temporal_smoothing.set_number(temporal_smoothing)
|
||||
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)
|
||||
is_frame_reemitted = bcd.get_is_frame_reemitted()
|
||||
|
||||
marker_type = state.marker_type
|
||||
marker_state = state.get_marker_state()
|
||||
|
||||
is_opencv_lbf = marker_type == MarkerType.OPENCV_LBF and self.opencv_lbf is not None
|
||||
is_google_facemesh = marker_type == MarkerType.GOOGLE_FACEMESH and self.google_facemesh is not None
|
||||
|
||||
if marker_type is not None:
|
||||
frame_name = bcd.get_frame_name()
|
||||
frame_image = bcd.get_image(frame_name)
|
||||
|
||||
if all_is_not_None(frame_image) and (is_opencv_lbf or is_google_facemesh):
|
||||
face_mark_list = bcd.get_face_mark_list()
|
||||
if marker_state.temporal_smoothing != 1 and \
|
||||
len(self.temporal_lmrks) != len(face_mark_list):
|
||||
self.temporal_lmrks = [ [] for _ in range(len(face_mark_list)) ]
|
||||
|
||||
for face_id, face_mark in enumerate(face_mark_list):
|
||||
face_mark_rect = face_mark.get_face_urect()
|
||||
if face_mark_rect is not None:
|
||||
# Cut the face to feed to the face marker
|
||||
face_image, face_uni_mat = face_mark_rect.cut(frame_image, marker_state.marker_coverage, 256 if is_opencv_lbf else \
|
||||
192 if is_google_facemesh else 0 )
|
||||
_,H,W,_ = ImageProcessor(face_image).get_dims()
|
||||
|
||||
if is_opencv_lbf:
|
||||
lmrks = self.opencv_lbf.extract(face_image)[0]
|
||||
elif is_google_facemesh:
|
||||
lmrks = self.google_facemesh.extract(face_image)[0][...,0:2]
|
||||
|
||||
lmrks /= (W,H)
|
||||
|
||||
if marker_state.temporal_smoothing != 1:
|
||||
if not is_frame_reemitted or len(self.temporal_lmrks[face_id]) == 0:
|
||||
self.temporal_lmrks[face_id].append(lmrks)
|
||||
self.temporal_lmrks[face_id] = self.temporal_lmrks[face_id][-marker_state.temporal_smoothing:]
|
||||
|
||||
lmrks = np.mean(self.temporal_lmrks[face_id],0 )
|
||||
|
||||
face_ulmrks = FaceULandmarks.create (FaceULandmarks.Type.LANDMARKS_2D_68 if is_opencv_lbf else \
|
||||
FaceULandmarks.Type.LANDMARKS_2D_468 if is_google_facemesh else None, lmrks)
|
||||
|
||||
face_ulmrks = face_ulmrks.transform(face_uni_mat, invert=True)
|
||||
face_mark.add_face_ulandmarks (face_ulmrks)
|
||||
|
||||
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 MarkerState(BackendWorkerState):
|
||||
marker_coverage : float = None
|
||||
temporal_smoothing : int = None
|
||||
|
||||
class OpenCVLBFState(BackendWorkerState):
|
||||
device = None
|
||||
|
||||
class GoogleFaceMeshState(BackendWorkerState):
|
||||
device = None
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
def __init__(self):
|
||||
self.marker_type : MarkerType = None
|
||||
self.marker_state = {}
|
||||
self.opencv_lbf_state = OpenCVLBFState()
|
||||
self.google_facemesh_state = GoogleFaceMeshState()
|
||||
|
||||
def get_marker_state(self) -> MarkerState:
|
||||
state = self.marker_state.get(self.marker_type, None)
|
||||
if state is None:
|
||||
state = self.marker_state[self.marker_type] = MarkerState()
|
||||
return state
|
||||
|
||||
class Sheet:
|
||||
class Host(lib_csw.Sheet.Host):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.marker_type = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.device = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.marker_coverage = lib_csw.Number.Client()
|
||||
self.temporal_smoothing = lib_csw.Number.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.marker_type = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.device = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.marker_coverage = lib_csw.Number.Host()
|
||||
self.temporal_smoothing = lib_csw.Number.Host()
|
||||
|
||||
# lmrks_list = []
|
||||
|
||||
# offsets = [ (0,0), (-2,0), (0,-2), (2,0), (0,2) ]
|
||||
|
||||
# for x_off, y_off in offsets:
|
||||
# feed_image = face_image.copy()
|
||||
|
||||
# if x_off > 0:
|
||||
# feed_image[:-x_off] = feed_image[x_off:]
|
||||
# elif x_off < 0:
|
||||
# feed_image[x_off:] = feed_image[:-x_off]
|
||||
|
||||
# if y_off > 0:
|
||||
# feed_image[:,:-y_off] = feed_image[:,y_off:]
|
||||
# elif y_off < 0:
|
||||
# feed_image[:,y_off:] = feed_image[:,:-y_off]
|
||||
|
||||
# if marker_type == MarkerType.OPENCV_LBF:
|
||||
# lmrks = self.opencv_lbf.extract(feed_image)
|
||||
|
||||
# lmrks_list.append(lmrks)
|
||||
|
||||
# #print(lmrks_list)
|
||||
# lmrks = np.mean(lmrks_list, 0)
|
||||
# self.temporal_lmrks.append(lmrks)
|
||||
# if len(self.temporal_lmrks) >= 5:
|
||||
# self.temporal_lmrks.pop(0)
|
||||
# lmrks = np.mean(self.temporal_lmrks,0 )
|
||||
|
||||
|
||||
|
||||
# x = 4
|
||||
# spatial_offsets = [ (0,0), (-x,0), (0,-x), (x,0), (0,x) ]
|
||||
|
||||
# lmrks_list = []
|
||||
# for i in range(temporal_smoothing):
|
||||
# if temporal_smoothing == 1:
|
||||
# feed_image = face_image
|
||||
# else:
|
||||
# feed_image = face_image.copy()
|
||||
|
||||
# x_off,y_off = spatial_offsets[i]
|
||||
|
||||
# if x_off > 0:
|
||||
# feed_image[:-x_off] = feed_image[x_off:]
|
||||
# elif x_off < 0:
|
||||
# feed_image[x_off:] = feed_image[:-x_off]
|
||||
|
||||
# if y_off > 0:
|
||||
# feed_image[:,:-y_off] = feed_image[:,y_off:]
|
||||
# elif y_off < 0:
|
||||
# feed_image[:,y_off:] = feed_image[:,:-y_off]
|
||||
|
||||
|
||||
# lmrks_list.append(lmrks)
|
||||
|
||||
# lmrks = np.mean(lmrks_list, 0)
|
303
apps/DeepFaceLive/backend/FaceMerger.py
Normal file
|
@ -0,0 +1,303 @@
|
|||
import time
|
||||
from enum import IntEnum
|
||||
|
||||
import cupy as cp
|
||||
import numexpr as ne
|
||||
import numpy as np
|
||||
from xlib import cupy as lib_cp
|
||||
from xlib import os as lib_os
|
||||
from xlib.image import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
from xlib.python import all_is_not_None
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||
BackendWorkerState)
|
||||
|
||||
|
||||
class FaceMaskType(IntEnum):
|
||||
SRC = 0
|
||||
CELEB = 1
|
||||
SRC_M_CELEB = 2
|
||||
|
||||
FaceMaskTypeNames = ['@FaceMerger.FaceMaskType.SRC','@FaceMerger.FaceMaskType.CELEB','@FaceMerger.FaceMaskType.SRC_M_CELEB']
|
||||
|
||||
class FaceMerger(BackendHost):
|
||||
|
||||
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, backend_db : BackendDB = None):
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=FaceMergerWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out] )
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
class FaceMergerWorker(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):
|
||||
self.weak_heap = weak_heap
|
||||
self.reemit_frame_signal = reemit_frame_signal
|
||||
self.bc_in = bc_in
|
||||
self.bc_out = bc_out
|
||||
self.pending_bcd = None
|
||||
self.is_gpu = False
|
||||
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_x_offset.call_on_number(self.on_cs_face_x_offset)
|
||||
cs.face_y_offset.call_on_number(self.on_cs_face_y_offset)
|
||||
cs.face_scale.call_on_number(self.on_cs_face_scale)
|
||||
cs.face_mask_erode.call_on_number(self.on_cs_mask_erode)
|
||||
cs.face_mask_blur.call_on_number(self.on_cs_mask_blur)
|
||||
cs.face_opacity.call_on_number(self.on_cs_face_opacity)
|
||||
|
||||
cs.device.enable()
|
||||
cs.device.set_choices( ['CPU'] + lib_cp.get_available_devices(), none_choice_name='@misc.menu_select')
|
||||
|
||||
cs.device.select(state.device if state.device is not None else 'CPU')
|
||||
|
||||
|
||||
def on_cs_device(self, idxs, device : lib_cp.CuPyDeviceInfo):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
if device is not None and state.device == device:
|
||||
cs.face_x_offset.enable()
|
||||
cs.face_x_offset.set_config(lib_csw.Number.Config(min=-0.5, max=0.5, step=0.001, decimals=3, allow_instant_update=True))
|
||||
cs.face_x_offset.set_number(state.face_x_offset if state.face_x_offset is not None else 0.0)
|
||||
|
||||
cs.face_y_offset.enable()
|
||||
cs.face_y_offset.set_config(lib_csw.Number.Config(min=-0.5, max=0.5, step=0.001, decimals=3, allow_instant_update=True))
|
||||
cs.face_y_offset.set_number(state.face_y_offset if state.face_y_offset is not None else 0.0)
|
||||
|
||||
cs.face_scale.enable()
|
||||
cs.face_scale.set_config(lib_csw.Number.Config(min=0.5, max=1.5, step=0.01, decimals=2, allow_instant_update=True))
|
||||
cs.face_scale.set_number(state.face_scale if state.face_scale is not None else 1.0)
|
||||
|
||||
cs.face_mask_type.call_on_selected(self.on_cs_face_mask_type)
|
||||
cs.face_mask_type.enable()
|
||||
cs.face_mask_type.set_choices(FaceMaskType, FaceMaskTypeNames, none_choice_name=None)
|
||||
cs.face_mask_type.select(state.face_mask_type if state.face_mask_type is not None else FaceMaskType.SRC_M_CELEB)
|
||||
|
||||
cs.face_mask_erode.enable()
|
||||
cs.face_mask_erode.set_config(lib_csw.Number.Config(min=-400, max=400, step=1, decimals=0, allow_instant_update=True))
|
||||
cs.face_mask_erode.set_number(state.face_mask_erode if state.face_mask_erode is not None else 5.0)
|
||||
|
||||
cs.face_mask_blur.enable()
|
||||
cs.face_mask_blur.set_config(lib_csw.Number.Config(min=0, max=400, step=1, decimals=0, allow_instant_update=True))
|
||||
cs.face_mask_blur.set_number(state.face_mask_blur if state.face_mask_blur is not None else 25.0)
|
||||
|
||||
cs.face_opacity.enable()
|
||||
cs.face_opacity.set_config(lib_csw.Number.Config(min=0.0, max=1.0, step=0.01, decimals=2, allow_instant_update=True))
|
||||
cs.face_opacity.set_number(state.face_opacity if state.face_opacity is not None else 1.0)
|
||||
|
||||
if device != 'CPU':
|
||||
self.is_gpu = True
|
||||
cp.cuda.Device( device.get_index() ).use()
|
||||
|
||||
self.cp_mask_clip_kernel = cp.ElementwiseKernel('T x', 'T z', 'z = x < 0.004 ? 0 : x > 1.0 ? 1.0 : x', 'mask_clip_kernel')
|
||||
self.cp_merge_kernel = cp.ElementwiseKernel('T bg, T face, T mask', 'T z', 'z = bg*(1.0-mask) + face*mask', 'merge_kernel')
|
||||
self.cp_merge_kernel_opacity = cp.ElementwiseKernel('T bg, T face, T mask, T opacity', 'T z', 'z = bg*(1.0-mask) + bg*mask*(1.0-opacity) + face*mask*opacity', 'merge_kernel_opacity')
|
||||
|
||||
else:
|
||||
state.device = device
|
||||
self.save_state()
|
||||
self.restart()
|
||||
|
||||
|
||||
def on_cs_face_x_offset(self, face_x_offset):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.face_x_offset.get_config()
|
||||
face_x_offset = state.face_x_offset = float(np.clip(face_x_offset, cfg.min, cfg.max))
|
||||
cs.face_x_offset.set_number(face_x_offset)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_face_y_offset(self, face_y_offset):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.face_y_offset.get_config()
|
||||
face_y_offset = state.face_y_offset = float(np.clip(face_y_offset, cfg.min, cfg.max))
|
||||
cs.face_y_offset.set_number(face_y_offset)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_face_scale(self, face_scale):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.face_scale.get_config()
|
||||
face_scale = state.face_scale = float(np.clip(face_scale, cfg.min, cfg.max))
|
||||
cs.face_scale.set_number(face_scale)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_face_mask_type(self, idx, face_mask_type):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
state.face_mask_type = face_mask_type
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_mask_erode(self, face_mask_erode):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.face_mask_erode.get_config()
|
||||
face_mask_erode = state.face_mask_erode = int(np.clip(face_mask_erode, cfg.min, cfg.max))
|
||||
cs.face_mask_erode.set_number(face_mask_erode)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_mask_blur(self, face_mask_blur):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.face_mask_blur.get_config()
|
||||
face_mask_blur = state.face_mask_blur = int(np.clip(face_mask_blur, cfg.min, cfg.max))
|
||||
cs.face_mask_blur.set_number(face_mask_blur)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_face_opacity(self, face_opacity):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.face_opacity.get_config()
|
||||
face_opacity = state.face_opacity = float(np.clip(face_opacity, cfg.min, cfg.max))
|
||||
cs.face_opacity.set_number(face_opacity)
|
||||
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)
|
||||
|
||||
frame_name = bcd.get_frame_name()
|
||||
frame_image = bcd.get_image(frame_name)
|
||||
|
||||
if frame_image is not None:
|
||||
for face_mark in bcd.get_face_mark_list():
|
||||
face_align = face_mark.get_face_align()
|
||||
if face_align is not None:
|
||||
face_swap = face_align.get_face_swap()
|
||||
face_align_mask = face_align.get_face_mask()
|
||||
|
||||
if face_swap is not None:
|
||||
face_swap_mask = face_swap.get_face_mask()
|
||||
if face_swap_mask is not None:
|
||||
|
||||
face_align_img = bcd.get_image(face_align.get_image_name())
|
||||
face_swap_img = bcd.get_image(face_swap.get_image_name())
|
||||
|
||||
face_align_mask_img = bcd.get_image(face_align_mask.get_image_name())
|
||||
face_swap_mask_img = bcd.get_image(face_swap_mask.get_image_name())
|
||||
source_to_aligned_uni_mat = face_align.get_source_to_aligned_uni_mat()
|
||||
|
||||
face_mask_type = state.face_mask_type
|
||||
|
||||
if all_is_not_None(face_align_img, face_align_mask_img, face_swap_img, face_swap_mask_img, face_mask_type):
|
||||
face_height, face_width = face_align_img.shape[:2]
|
||||
|
||||
if self.is_gpu:
|
||||
frame_image = cp.asarray(frame_image)
|
||||
face_align_mask_img = cp.asarray(face_align_mask_img)
|
||||
face_swap_mask_img = cp.asarray(face_swap_mask_img)
|
||||
face_swap_img = cp.asarray(face_swap_img)
|
||||
|
||||
frame_image_ip = ImageProcessor(frame_image).to_ufloat32()
|
||||
frame_image, (_, frame_height, frame_width, _) = frame_image_ip.get_image('HWC'), frame_image_ip.get_dims()
|
||||
face_align_mask_img = ImageProcessor(face_align_mask_img).to_ufloat32().get_image('HW')
|
||||
face_swap_mask_img = ImageProcessor(face_swap_mask_img).to_ufloat32().get_image('HW')
|
||||
|
||||
aligned_to_source_uni_mat = source_to_aligned_uni_mat.invert()
|
||||
aligned_to_source_uni_mat = aligned_to_source_uni_mat.source_translated(-state.face_x_offset, -state.face_y_offset)
|
||||
aligned_to_source_uni_mat = aligned_to_source_uni_mat.source_scaled_around_center(state.face_scale,state.face_scale)
|
||||
aligned_to_source_uni_mat = aligned_to_source_uni_mat.to_exact_mat (face_width, face_height, frame_width, frame_height)
|
||||
|
||||
if face_mask_type == FaceMaskType.SRC:
|
||||
face_mask = face_align_mask_img
|
||||
elif face_mask_type == FaceMaskType.CELEB:
|
||||
face_mask = face_swap_mask_img
|
||||
elif face_mask_type == FaceMaskType.SRC_M_CELEB:
|
||||
face_mask = face_align_mask_img*face_swap_mask_img
|
||||
|
||||
# Combine face mask
|
||||
face_mask_ip = ImageProcessor(face_mask).erode_blur(state.face_mask_erode, state.face_mask_blur, fade_to_border=True) \
|
||||
.warpAffine(aligned_to_source_uni_mat, frame_width, frame_height)
|
||||
if self.is_gpu:
|
||||
face_mask_ip.apply( lambda img: self.cp_mask_clip_kernel(img) )
|
||||
else:
|
||||
face_mask_ip.clip2( (1.0/255.0), 0.0, 1.0, 1.0)
|
||||
frame_face_mask = face_mask_ip.get_image('HWC')
|
||||
|
||||
frame_face_swap_img = ImageProcessor(face_swap_img) \
|
||||
.to_ufloat32().warpAffine(aligned_to_source_uni_mat, frame_width, frame_height).get_image('HWC')
|
||||
|
||||
# Combine final frame
|
||||
opacity = state.face_opacity
|
||||
if self.is_gpu:
|
||||
if opacity == 1.0:
|
||||
frame_final = self.cp_merge_kernel(frame_image, frame_face_swap_img, frame_face_mask)
|
||||
else:
|
||||
frame_final = self.cp_merge_kernel_opacity(frame_image, frame_face_swap_img, frame_face_mask, opacity)
|
||||
frame_final = cp.asnumpy(frame_final)
|
||||
else:
|
||||
if opacity == 1.0:
|
||||
frame_final = ne.evaluate('frame_image*(1.0-frame_face_mask) + frame_face_swap_img*frame_face_mask')
|
||||
else:
|
||||
frame_final = ne.evaluate('frame_image*(1.0-frame_face_mask) + frame_image*frame_face_mask*(1.0-opacity) + frame_face_swap_img*frame_face_mask*opacity')
|
||||
|
||||
# keep image in float32 in order not to extra load FaceMerger
|
||||
|
||||
merged_frame_name = f'{frame_name}_merged'
|
||||
bcd.set_merged_frame_name(merged_frame_name)
|
||||
bcd.set_image(merged_frame_name, frame_final)
|
||||
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_x_offset = lib_csw.Number.Client()
|
||||
self.face_y_offset = lib_csw.Number.Client()
|
||||
self.face_scale = lib_csw.Number.Client()
|
||||
self.face_mask_type = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.face_mask_x_offset = lib_csw.Number.Client()
|
||||
self.face_mask_y_offset = lib_csw.Number.Client()
|
||||
self.face_mask_scale = lib_csw.Number.Client()
|
||||
self.face_mask_erode = lib_csw.Number.Client()
|
||||
self.face_mask_blur = lib_csw.Number.Client()
|
||||
self.face_opacity = lib_csw.Number.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.device = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.face_x_offset = lib_csw.Number.Host()
|
||||
self.face_y_offset = lib_csw.Number.Host()
|
||||
self.face_scale = lib_csw.Number.Host()
|
||||
self.face_mask_type = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.face_mask_erode = lib_csw.Number.Host()
|
||||
self.face_mask_blur = lib_csw.Number.Host()
|
||||
self.face_opacity = lib_csw.Number.Host()
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
device : lib_cp.CuPyDeviceInfo = None
|
||||
face_x_offset : float = None
|
||||
face_y_offset : float = None
|
||||
face_scale : float = None
|
||||
face_mask_type = None
|
||||
face_mask_erode : int = None
|
||||
face_mask_blur : int = None
|
||||
face_opacity : float = None
|
339
apps/DeepFaceLive/backend/FaceSwapper.py
Normal file
|
@ -0,0 +1,339 @@
|
|||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from modelhub import DFLive
|
||||
from xlib import os as lib_os
|
||||
from xlib.facemeta import FaceMask, FaceSwap
|
||||
from xlib.image.ImageProcessor import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
from xlib.python import all_is_not_None
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||
BackendWorkerState)
|
||||
|
||||
|
||||
class FaceSwapper(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):
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=FaceSwapperWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out, dfm_models_path])
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
|
||||
class FaceSwapperWorker(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, dfm_models_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.dfm_models_path = dfm_models_path
|
||||
|
||||
self.pending_bcd = None
|
||||
|
||||
self.dfm_model_initializer = None
|
||||
self.dfm_model = None
|
||||
|
||||
lib_os.set_timer_resolution(1)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
cs.model.call_on_selected(self.on_cs_model_info)
|
||||
cs.device.call_on_selected(self.on_cs_device)
|
||||
cs.face_id.call_on_number(self.on_cs_face_id)
|
||||
cs.morph_factor.call_on_number(self.on_cs_morph_factor)
|
||||
cs.presharpen_amount.call_on_number(self.on_cs_presharpen_amount)
|
||||
cs.pre_gamma_red.call_on_number(self.on_cs_gamma_red)
|
||||
cs.pre_gamma_green.call_on_number(self.on_cs_pre_gamma_green)
|
||||
cs.pre_gamma_blue.call_on_number(self.on_cs_pre_gamma_blue)
|
||||
cs.two_pass.call_on_flag(self.on_cs_two_pass)
|
||||
|
||||
cs.model.enable()
|
||||
cs.model.set_choices( DFLive.get_available_models_info(dfm_models_path), none_choice_name='@misc.menu_select')
|
||||
cs.model.select(state.model)
|
||||
|
||||
|
||||
def on_cs_model_info(self, idx, model):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
if state.model == model:
|
||||
|
||||
cs.device.enable()
|
||||
cs.device.set_choices( DFLive.get_available_devices(), none_choice_name='@misc.menu_select')
|
||||
cs.device.select(state.model_state.device)
|
||||
else:
|
||||
state.model = model
|
||||
state.model_state = ModelState()
|
||||
self.save_state()
|
||||
self.restart()
|
||||
|
||||
def on_cs_device(self, idxs, device):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
if device is not None and state.model_state.device == device:
|
||||
self.dfm_model_initializer = DFLive.DFMModel_from_info(state.model, device)
|
||||
self.set_busy(True)
|
||||
else:
|
||||
state.model_state = ModelState()
|
||||
state.model_state.device = device
|
||||
self.save_state()
|
||||
self.restart()
|
||||
|
||||
def on_cs_face_id(self, face_id):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
model_state = state.model_state
|
||||
cfg = cs.face_id.get_config()
|
||||
if model_state is not None:
|
||||
face_id = model_state.face_id = int(np.clip(face_id, cfg.min, cfg.max))
|
||||
cs.face_id.set_number(face_id)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_presharpen_amount(self, presharpen_amount):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
model_state = state.model_state
|
||||
cfg = cs.presharpen_amount.get_config()
|
||||
if model_state is not None:
|
||||
presharpen_amount = model_state.presharpen_amount = float(np.clip(presharpen_amount, cfg.min, cfg.max))
|
||||
cs.presharpen_amount.set_number(presharpen_amount)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_morph_factor(self, morph_factor):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
model_state = state.model_state
|
||||
cfg = cs.morph_factor.get_config()
|
||||
if model_state is not None:
|
||||
morph_factor = model_state.morph_factor = float(np.clip(morph_factor, cfg.min, cfg.max))
|
||||
cs.morph_factor.set_number(morph_factor)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_gamma_red(self, pre_gamma_red):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
model_state = state.model_state
|
||||
cfg = cs.pre_gamma_red.get_config()
|
||||
if model_state is not None:
|
||||
pre_gamma_red = model_state.pre_gamma_red = float(np.clip(pre_gamma_red, cfg.min, cfg.max))
|
||||
cs.pre_gamma_red.set_number(pre_gamma_red)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_pre_gamma_green(self, pre_gamma_green):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
model_state = state.model_state
|
||||
cfg = cs.pre_gamma_green.get_config()
|
||||
if model_state is not None:
|
||||
pre_gamma_green = model_state.pre_gamma_green = float(np.clip(pre_gamma_green, cfg.min, cfg.max))
|
||||
cs.pre_gamma_green.set_number(pre_gamma_green)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_pre_gamma_blue(self, pre_gamma_blue):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
model_state = state.model_state
|
||||
cfg = cs.pre_gamma_blue.get_config()
|
||||
if model_state is not None:
|
||||
pre_gamma_blue = model_state.pre_gamma_blue = float(np.clip(pre_gamma_blue, cfg.min, cfg.max))
|
||||
cs.pre_gamma_blue.set_number(pre_gamma_blue)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
|
||||
|
||||
def on_cs_two_pass(self, two_pass):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
model_state = state.model_state
|
||||
if model_state is not None:
|
||||
model_state.two_pass = two_pass
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def on_tick(self):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
if self.dfm_model_initializer is not None:
|
||||
events = self.dfm_model_initializer.process_events()
|
||||
|
||||
if events.prev_status_downloading:
|
||||
cs.model_dl_progress.disable()
|
||||
|
||||
if events.new_status_downloading:
|
||||
cs.model_dl_progress.enable()
|
||||
cs.model_dl_progress.set_config( lib_csw.Progress.Config(title='@FaceSwapper.downloading_model') )
|
||||
cs.model_dl_progress.set_progress(0)
|
||||
elif events.new_status_initialized:
|
||||
self.dfm_model = events.dfm_model
|
||||
self.dfm_model_initializer = None
|
||||
|
||||
model_width, model_height = self.dfm_model.get_input_res()
|
||||
|
||||
cs.model_info_label.enable()
|
||||
cs.model_info_label.set_config( lib_csw.InfoLabel.Config(info_icon=True,
|
||||
info_lines=[f'@FaceSwapper.model_information',
|
||||
'',
|
||||
f'@FaceSwapper.filename',
|
||||
f'{self.dfm_model.get_model_path().name}',
|
||||
'',
|
||||
f'@FaceSwapper.resolution',
|
||||
f'{model_width}x{model_height}']) )
|
||||
cs.face_id.enable()
|
||||
cs.face_id.set_config(lib_csw.Number.Config(min=0, max=16, step=1, decimals=0, allow_instant_update=True))
|
||||
cs.face_id.set_number(state.model_state.face_id if state.model_state.face_id is not None else 0)
|
||||
|
||||
if self.dfm_model.has_morph_value():
|
||||
cs.morph_factor.enable()
|
||||
cs.morph_factor.set_config(lib_csw.Number.Config(min=0, max=1, step=0.01, decimals=2, allow_instant_update=True))
|
||||
cs.morph_factor.set_number(state.model_state.morph_factor if state.model_state.morph_factor is not None else 0.75)
|
||||
|
||||
cs.presharpen_amount.enable()
|
||||
cs.presharpen_amount.set_config(lib_csw.Number.Config(min=0, max=10, step=0.1, decimals=1, allow_instant_update=True))
|
||||
cs.presharpen_amount.set_number(state.model_state.presharpen_amount if state.model_state.presharpen_amount is not None else 0)
|
||||
|
||||
cs.pre_gamma_red.enable()
|
||||
cs.pre_gamma_red.set_config(lib_csw.Number.Config(min=0.01, max=4, step=0.01, decimals=2, allow_instant_update=True))
|
||||
cs.pre_gamma_red.set_number(state.model_state.pre_gamma_red if state.model_state.pre_gamma_red is not None else 1)
|
||||
|
||||
cs.pre_gamma_green.enable()
|
||||
cs.pre_gamma_green.set_config(lib_csw.Number.Config(min=0.01, max=4, step=0.01, decimals=2, allow_instant_update=True))
|
||||
cs.pre_gamma_green.set_number(state.model_state.pre_gamma_green if state.model_state.pre_gamma_green is not None else 1)
|
||||
|
||||
cs.pre_gamma_blue.enable()
|
||||
cs.pre_gamma_blue.set_config(lib_csw.Number.Config(min=0.010, max=4, step=0.01, decimals=2, allow_instant_update=True))
|
||||
cs.pre_gamma_blue.set_number(state.model_state.pre_gamma_blue if state.model_state.pre_gamma_blue is not None else 1)
|
||||
|
||||
cs.two_pass.enable()
|
||||
cs.two_pass.set_flag(state.model_state.two_pass if state.model_state.two_pass is not None else False)
|
||||
|
||||
self.set_busy(False)
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
elif events.new_status_error:
|
||||
self.set_busy(False)
|
||||
cs.model_dl_error.enable()
|
||||
cs.model_dl_error.set_error(events.error)
|
||||
|
||||
if events.download_progress is not None:
|
||||
cs.model_dl_progress.set_progress(events.download_progress)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
model_state = state.model_state
|
||||
dfm_model = self.dfm_model
|
||||
if all_is_not_None(dfm_model, model_state):
|
||||
face_id = model_state.face_id
|
||||
if face_id is not None:
|
||||
for i, face_mark in enumerate(bcd.get_face_mark_list()):
|
||||
if face_id == i:
|
||||
face_align = face_mark.get_face_align()
|
||||
if face_align is not None:
|
||||
face_align_image_name = face_align.get_image_name()
|
||||
face_align_image = bcd.get_image(face_align_image_name)
|
||||
if face_align_image is not None:
|
||||
|
||||
pre_gamma_red = model_state.pre_gamma_red
|
||||
pre_gamma_green = model_state.pre_gamma_green
|
||||
pre_gamma_blue = model_state.pre_gamma_blue
|
||||
|
||||
fai_ip = ImageProcessor(face_align_image)
|
||||
if model_state.presharpen_amount != 0:
|
||||
fai_ip.sharpen(factor=model_state.presharpen_amount)
|
||||
|
||||
if pre_gamma_red != 1.0 or pre_gamma_green != 1.0 or pre_gamma_blue != 1.0:
|
||||
fai_ip.adjust_gamma(pre_gamma_red, pre_gamma_green, pre_gamma_blue)
|
||||
face_align_image = fai_ip.get_image('HWC')
|
||||
|
||||
celeb_face, celeb_face_mask_img, face_align_mask_img = dfm_model.convert(face_align_image, morph_factor=model_state.morph_factor)
|
||||
celeb_face, celeb_face_mask_img, face_align_mask_img = celeb_face[0], celeb_face_mask_img[0], face_align_mask_img[0]
|
||||
|
||||
if model_state.two_pass:
|
||||
celeb_face, celeb_face_mask_img, _ = dfm_model.convert(celeb_face, morph_factor=model_state.morph_factor)
|
||||
celeb_face, celeb_face_mask_img = celeb_face[0], celeb_face_mask_img[0]
|
||||
|
||||
face_align_mask = FaceMask()
|
||||
face_align_mask.set_image_name(f'{face_align_image_name}_mask')
|
||||
face_align.set_face_mask(face_align_mask)
|
||||
bcd.set_image(face_align_mask.get_image_name(), face_align_mask_img)
|
||||
|
||||
face_swap = FaceSwap()
|
||||
face_swap.set_image_name (f"{face_align_image_name}_swapped")
|
||||
face_align.set_face_swap(face_swap)
|
||||
bcd.set_image(face_swap.get_image_name(), celeb_face)
|
||||
|
||||
face_swap_mask = FaceMask()
|
||||
face_swap_mask.set_image_name(f'{face_swap.get_image_name()}_mask')
|
||||
face_swap.set_face_mask(face_swap_mask)
|
||||
bcd.set_image(face_swap_mask.get_image_name(), celeb_face_mask_img)
|
||||
|
||||
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.model = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.model_info_label = lib_csw.InfoLabel.Client()
|
||||
self.model_dl_progress = lib_csw.Progress.Client()
|
||||
self.model_dl_error = lib_csw.Error.Client()
|
||||
self.device = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.face_id = lib_csw.Number.Client()
|
||||
self.morph_factor = lib_csw.Number.Client()
|
||||
self.presharpen_amount = lib_csw.Number.Client()
|
||||
self.pre_gamma_red = lib_csw.Number.Client()
|
||||
self.pre_gamma_blue = lib_csw.Number.Client()
|
||||
self.pre_gamma_green = lib_csw.Number.Client()
|
||||
self.two_pass = lib_csw.Flag.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.model = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.model_info_label = lib_csw.InfoLabel.Host()
|
||||
self.model_dl_progress = lib_csw.Progress.Host()
|
||||
self.model_dl_error = lib_csw.Error.Host()
|
||||
self.device = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.face_id = lib_csw.Number.Host()
|
||||
self.morph_factor = lib_csw.Number.Host()
|
||||
self.presharpen_amount = lib_csw.Number.Host()
|
||||
self.pre_gamma_red = lib_csw.Number.Host()
|
||||
self.pre_gamma_blue = lib_csw.Number.Host()
|
||||
self.pre_gamma_green = lib_csw.Number.Host()
|
||||
self.two_pass = lib_csw.Flag.Host()
|
||||
|
||||
class ModelState(BackendWorkerState):
|
||||
device = None
|
||||
face_id : int = None
|
||||
morph_factor : float = None
|
||||
presharpen_amount : float = None
|
||||
pre_gamma_red : float = None
|
||||
pre_gamma_blue : float = None
|
||||
pre_gamma_green: float = None
|
||||
two_pass : bool = None
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
model : DFLive.DFMModelInfo = None
|
||||
model_state : ModelState = None
|
367
apps/DeepFaceLive/backend/FileSource.py
Normal file
|
@ -0,0 +1,367 @@
|
|||
import time
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
|
||||
from xlib import os as lib_os
|
||||
from xlib import player as lib_player
|
||||
from xlib.image import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendConnectionData, BackendDB,
|
||||
BackendHost, BackendSignal, BackendWeakHeap,
|
||||
BackendWorker, BackendWorkerState)
|
||||
|
||||
|
||||
class InputType(IntEnum):
|
||||
IMAGE_SEQUENCE = 0
|
||||
VIDEO_FILE = 1
|
||||
|
||||
InputTypeNames = ['@FileSource.image_folder', '@FileSource.video_file']
|
||||
|
||||
class FileSource(BackendHost):
|
||||
def __init__(self, weak_heap : BackendWeakHeap,
|
||||
reemit_frame_signal : BackendSignal,
|
||||
bc_out : BackendConnection, backend_db : BackendDB = None):
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=FileSourceWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, reemit_frame_signal, bc_out])
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
|
||||
class FileSourceWorker(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_out : BackendConnection):
|
||||
self.weak_heap = weak_heap
|
||||
self.reemit_frame_signal = reemit_frame_signal
|
||||
self.bc_out = bc_out
|
||||
self.bcd_uid = 0
|
||||
self.pending_bcd = None
|
||||
self.fp : lib_player.FramePlayer = None
|
||||
self.last_p_frame = None
|
||||
lib_os.set_timer_resolution(4)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
cs.input_type.call_on_selected(self.on_cs_input_type_selected)
|
||||
cs.input_paths.call_on_paths(self.on_cs_input_paths)
|
||||
cs.target_width.call_on_number(self.on_cs_target_width)
|
||||
cs.fps.call_on_number(self.on_cs_fps)
|
||||
cs.is_realtime.call_on_flag(self.on_cs_is_realtime)
|
||||
cs.is_autorewind.call_on_flag(self.on_cs_is_autorewind)
|
||||
cs.frame_index.call_on_number(self.on_cs_frame_index)
|
||||
|
||||
cs.play.call_on_signal(self.on_cs_play)
|
||||
cs.pause.call_on_signal(self.on_cs_pause)
|
||||
cs.seek_begin.call_on_signal(self.on_cs_seek_begin)
|
||||
cs.seek_backward.call_on_signal(self.on_cs_seek_backward)
|
||||
cs.seek_forward.call_on_signal(self.on_cs_seek_forward)
|
||||
cs.seek_end.call_on_signal(self.on_cs_seek_end)
|
||||
|
||||
cs.input_type.enable()
|
||||
cs.input_type.set_choices(InputType, InputTypeNames)
|
||||
cs.input_type.select(state.input_type)
|
||||
|
||||
|
||||
|
||||
def set_fp(self, fp):
|
||||
if self.fp != fp:
|
||||
if self.fp is not None:
|
||||
self.fp.dispose()
|
||||
self.fp = fp
|
||||
|
||||
#######################
|
||||
### messages
|
||||
def on_cs_input_type_selected(self, idx, input_type):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
cs.input_paths.disable()
|
||||
cs.target_width.disable()
|
||||
cs.fps.disable()
|
||||
cs.is_autorewind.disable()
|
||||
cs.is_realtime.disable()
|
||||
cs.frame_index.disable()
|
||||
cs.frame_count.disable()
|
||||
cs.play.disable()
|
||||
cs.pause.disable()
|
||||
cs.seek_backward.disable()
|
||||
cs.seek_forward.disable()
|
||||
cs.seek_begin.disable()
|
||||
cs.seek_end.disable()
|
||||
|
||||
self.set_fp(None)
|
||||
|
||||
if input_type is not None:
|
||||
cs.input_paths.enable()
|
||||
if input_type == InputType.IMAGE_SEQUENCE:
|
||||
cs.input_paths.set_config( lib_csw.Paths.Config.Directory() )
|
||||
elif input_type == InputType.VIDEO_FILE:
|
||||
cs.input_paths.set_config( lib_csw.Paths.Config.ExistingFile(caption='Video file', suffixes=lib_player.VideoFilePlayer.SUPPORTED_VIDEO_FILE_SUFFIXES ) )
|
||||
|
||||
if input_type == state.input_type:
|
||||
cs.input_paths.set_paths(state.input_path)
|
||||
else:
|
||||
state.input_type = input_type
|
||||
state.input_path = None
|
||||
cs.input_paths.set_paths(None)
|
||||
state.fp_state = None
|
||||
self.save_state()
|
||||
|
||||
def on_cs_input_paths(self, paths, prev_paths):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cs.input_paths_error.set_error(None)
|
||||
|
||||
input_type = state.input_type
|
||||
input_path = paths[0] if len(paths) != 0 else None
|
||||
|
||||
if input_path is not None:
|
||||
if input_type == InputType.IMAGE_SEQUENCE or \
|
||||
input_type == InputType.VIDEO_FILE:
|
||||
|
||||
cls_ = lib_player.ImageSequencePlayer if input_type == InputType.IMAGE_SEQUENCE \
|
||||
else lib_player.VideoFilePlayer
|
||||
|
||||
err = None
|
||||
try:
|
||||
fp = cls_(input_path)
|
||||
except Exception as e:
|
||||
err = str(e)
|
||||
|
||||
if err is None:
|
||||
self.set_fp(fp)
|
||||
fp.req_frame_seek(0, 0)
|
||||
|
||||
if input_path == state.input_path and state.fp_state is not None:
|
||||
target_width = fp.set_target_width(state.fp_state.target_width)
|
||||
fps = fp.set_fps(state.fp_state.fps)
|
||||
is_realtime = fp.set_is_realtime(state.fp_state.is_realtime)
|
||||
is_autorewind = fp.set_is_autorewind(state.fp_state.is_autorewind)
|
||||
else:
|
||||
state.input_path = input_path
|
||||
state.fp_state = FPState()
|
||||
target_width = state.fp_state.target_width = fp.get_target_width()
|
||||
fps = state.fp_state.fps = fp.get_fps()
|
||||
is_realtime = state.fp_state.is_realtime = fp.get_is_realtime()
|
||||
is_autorewind = state.fp_state.is_autorewind = fp.get_is_autorewind()
|
||||
|
||||
cs.target_width.enable()
|
||||
cs.target_width.set_config(lib_csw.Number.Config(min=0, max=4096, step=4, decimals=0, zero_is_auto=True, allow_instant_update=True))
|
||||
cs.target_width.set_number(target_width)
|
||||
|
||||
cs.fps.enable()
|
||||
cs.fps.set_config(lib_csw.Number.Config(min=0, max=240, step=1.0, decimals=2, zero_is_auto=True, allow_instant_update=True))
|
||||
cs.fps.set_number(fps)
|
||||
|
||||
cs.is_realtime.enable()
|
||||
cs.is_realtime.set_flag(is_realtime)
|
||||
|
||||
cs.is_autorewind.enable()
|
||||
cs.is_autorewind.set_flag(is_autorewind)
|
||||
|
||||
cs.frame_count.enable()
|
||||
cs.frame_count.set_number( fp.get_frame_count() )
|
||||
|
||||
cs.frame_index.enable()
|
||||
cs.frame_index.set_number( fp.get_frame_idx() )
|
||||
|
||||
cs.play.enable()
|
||||
cs.pause.freeze()
|
||||
cs.seek_backward.enable()
|
||||
cs.seek_forward.enable()
|
||||
cs.seek_begin.enable()
|
||||
cs.seek_end.enable()
|
||||
else:
|
||||
cs.input_paths_error.set_error(err)
|
||||
cs.input_paths.set_paths(prev_paths, block_event=True)
|
||||
self.save_state()
|
||||
|
||||
|
||||
def on_cs_target_width(self, target_width):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
target_width = state.fp_state.target_width = self.fp.set_target_width(target_width)
|
||||
cs.target_width.set_number(target_width)
|
||||
self.save_state()
|
||||
|
||||
def on_cs_fps(self, fps):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
fps = state.fp_state.fps = self.fp.set_fps(fps)
|
||||
cs.fps.set_number(fps)
|
||||
self.save_state()
|
||||
|
||||
def on_cs_is_realtime(self, is_realtime):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
is_realtime = state.fp_state.is_realtime = self.fp.set_is_realtime(is_realtime)
|
||||
cs.is_realtime.set_flag(is_realtime)
|
||||
if not cs.is_realtime.get_flag():
|
||||
cs.fps.freeze()
|
||||
else:
|
||||
cs.fps.enable()
|
||||
self.save_state()
|
||||
|
||||
def on_cs_is_autorewind(self, is_autorewind):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
is_autorewind = state.fp_state.is_autorewind = self.fp.set_is_autorewind(is_autorewind)
|
||||
cs.is_autorewind.set_flag(is_autorewind)
|
||||
self.save_state()
|
||||
|
||||
def on_cs_play(self):
|
||||
self.fp.req_play_start()
|
||||
|
||||
def on_cs_pause(self):
|
||||
self.fp.req_play_stop()
|
||||
|
||||
def on_cs_seek_begin(self):
|
||||
self.fp.req_frame_seek(0,0)
|
||||
|
||||
def on_cs_seek_backward(self):
|
||||
self.fp.req_frame_seek(-1,1)
|
||||
|
||||
def on_cs_seek_forward(self):
|
||||
self.fp.req_frame_seek(1,1)
|
||||
|
||||
def on_cs_seek_end(self):
|
||||
self.fp.req_frame_seek(0,2)
|
||||
|
||||
def on_cs_frame_index(self, frame_idx):
|
||||
self.fp.req_frame_seek(frame_idx, 0)
|
||||
|
||||
def on_fp_state_change(self, fp, is_playing):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
if is_playing:
|
||||
cs.play.freeze()
|
||||
cs.pause.enable()
|
||||
cs.seek_backward.freeze()
|
||||
cs.seek_forward.freeze()
|
||||
else:
|
||||
cs.play.enable()
|
||||
cs.pause.freeze()
|
||||
cs.seek_backward.enable()
|
||||
cs.seek_forward.enable()
|
||||
|
||||
def on_tick(self):
|
||||
if self.fp is not None:
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
if state.fp_state.is_realtime or self.pending_bcd is None:
|
||||
self.start_profile_timing()
|
||||
reemit_frame = self.reemit_frame_signal.recv()
|
||||
|
||||
pr = self.fp.process()
|
||||
if pr.new_is_playing is not None:
|
||||
if pr.new_is_playing:
|
||||
cs.play.freeze()
|
||||
cs.pause.enable()
|
||||
cs.seek_backward.freeze()
|
||||
cs.seek_forward.freeze()
|
||||
else:
|
||||
cs.play.enable()
|
||||
cs.pause.freeze()
|
||||
cs.seek_backward.enable()
|
||||
cs.seek_forward.enable()
|
||||
|
||||
if pr.new_frame_idx is not None:
|
||||
cs.frame_index.set_number(pr.new_frame_idx, block_event=True)
|
||||
|
||||
p_frame = pr.new_frame
|
||||
if p_frame is not None:
|
||||
self.bcd_uid += 1
|
||||
elif reemit_frame and self.last_p_frame is not None:
|
||||
p_frame = self.last_p_frame
|
||||
|
||||
if p_frame is not None:
|
||||
# Frame is changed or reemit, construct new ds
|
||||
self.last_p_frame = p_frame
|
||||
|
||||
bcd = BackendConnectionData(uid=self.bcd_uid)
|
||||
|
||||
bcd.assign_weak_heap(self.weak_heap)
|
||||
bcd.set_is_frame_reemitted(reemit_frame)
|
||||
bcd.set_frame_count(p_frame.frame_count)
|
||||
bcd.set_frame_num(p_frame.frame_num)
|
||||
bcd.set_frame_fps(p_frame.fps)
|
||||
bcd.set_frame_timestamp(p_frame.timestamp)
|
||||
|
||||
bcd.set_frame_name(p_frame.name)
|
||||
|
||||
image = ImageProcessor(p_frame.image).to_uint8().get_image('HWC')
|
||||
|
||||
bcd.set_image(p_frame.name, image)
|
||||
|
||||
if p_frame.error is not None:
|
||||
bcd.add_error(p_frame.error)
|
||||
|
||||
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
|
||||
|
||||
time.sleep(0.001)
|
||||
|
||||
def on_stop(self):
|
||||
self.set_fp(None)
|
||||
|
||||
class FPState(BackendWorkerState):
|
||||
target_width : int = None
|
||||
fps : float = None
|
||||
is_realtime : bool = None
|
||||
is_autorewind : bool = None
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
input_type : InputType = None
|
||||
input_path : Path = None
|
||||
fp_state : FPState = None
|
||||
|
||||
|
||||
class Sheet:
|
||||
class Host(lib_csw.Sheet.Host):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.input_type = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.input_paths = lib_csw.Paths.Client()
|
||||
self.input_paths_error = lib_csw.Error.Client()
|
||||
|
||||
self.target_width = lib_csw.Number.Client()
|
||||
self.fps = lib_csw.Number.Client()
|
||||
|
||||
self.is_realtime = lib_csw.Flag.Client()
|
||||
self.is_autorewind = lib_csw.Flag.Client()
|
||||
|
||||
self.frame_index = lib_csw.Number.Client()
|
||||
self.frame_count = lib_csw.Number.Client()
|
||||
|
||||
self.play = lib_csw.Signal.Client()
|
||||
self.pause = lib_csw.Signal.Client()
|
||||
self.seek_backward = lib_csw.Signal.Client()
|
||||
self.seek_forward = lib_csw.Signal.Client()
|
||||
self.seek_begin = lib_csw.Signal.Client()
|
||||
self.seek_end = lib_csw.Signal.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.input_type = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.input_paths = lib_csw.Paths.Host()
|
||||
self.input_paths_error = lib_csw.Error.Host()
|
||||
|
||||
self.target_width = lib_csw.Number.Host()
|
||||
self.fps = lib_csw.Number.Host()
|
||||
self.is_realtime = lib_csw.Flag.Host()
|
||||
self.is_autorewind = lib_csw.Flag.Host()
|
||||
|
||||
self.frame_index = lib_csw.Number.Host()
|
||||
self.frame_count = lib_csw.Number.Host()
|
||||
|
||||
self.play = lib_csw.Signal.Host()
|
||||
self.pause = lib_csw.Signal.Host()
|
||||
self.seek_backward = lib_csw.Signal.Host()
|
||||
self.seek_forward = lib_csw.Signal.Host()
|
||||
self.seek_begin = lib_csw.Signal.Host()
|
||||
self.seek_end = lib_csw.Signal.Host()
|
||||
|
111
apps/DeepFaceLive/backend/FrameAdjuster.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import time
|
||||
|
||||
import numpy as np
|
||||
from xlib import os as lib_os
|
||||
from xlib.image import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||
BackendWorkerState)
|
||||
|
||||
|
||||
class FrameAdjuster(BackendHost):
|
||||
|
||||
def __init__(self, weak_heap : BackendWeakHeap, reemit_frame_signal : BackendSignal, bc_in : BackendConnection, bc_out : BackendConnection, backend_db : BackendDB = None):
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=FrameAdjusterWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, bc_out] )
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
class FrameAdjusterWorker(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):
|
||||
self.weak_heap = weak_heap
|
||||
self.reemit_frame_signal = reemit_frame_signal
|
||||
self.bc_in = bc_in
|
||||
self.bc_out = bc_out
|
||||
self.pending_bcd = None
|
||||
|
||||
lib_os.set_timer_resolution(1)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cs.median_blur_per.call_on_number(self.on_cs_median_blur_per)
|
||||
cs.degrade_bicubic_per.call_on_number(self.on_cs_degrade_bicubic_per)
|
||||
|
||||
cs.median_blur_per.enable()
|
||||
cs.median_blur_per.set_config(lib_csw.Number.Config(min=0, max=100, step=1, decimals=0, allow_instant_update=True))
|
||||
cs.median_blur_per.set_number(state.median_blur_per if state.median_blur_per is not None else 0)
|
||||
|
||||
cs.degrade_bicubic_per.enable()
|
||||
cs.degrade_bicubic_per.set_config(lib_csw.Number.Config(min=0, max=100, step=1, decimals=0, allow_instant_update=True))
|
||||
cs.degrade_bicubic_per.set_number(state.degrade_bicubic_per if state.degrade_bicubic_per is not None else 0)
|
||||
|
||||
def on_cs_median_blur_per(self, median_blur_per):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.median_blur_per.get_config()
|
||||
median_blur_per = state.median_blur_per = int(np.clip(median_blur_per, cfg.min, cfg.max))
|
||||
cs.median_blur_per.set_number(median_blur_per)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_degrade_bicubic_per(self, degrade_bicubic_per):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.degrade_bicubic_per.get_config()
|
||||
degrade_bicubic_per = state.degrade_bicubic_per = int(np.clip(degrade_bicubic_per, cfg.min, cfg.max))
|
||||
cs.degrade_bicubic_per.set_number(degrade_bicubic_per)
|
||||
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)
|
||||
|
||||
frame_name = bcd.get_frame_name()
|
||||
frame_image = bcd.get_image(frame_name)
|
||||
|
||||
if frame_image is not None:
|
||||
frame_image_ip = ImageProcessor(frame_image)
|
||||
frame_image_ip.median_blur(5, state.median_blur_per / 100.0 )
|
||||
frame_image_ip.degrade_resize( state.degrade_bicubic_per / 100.0, interpolation=ImageProcessor.Interpolation.CUBIC)
|
||||
|
||||
frame_image = frame_image_ip.get_image('HWC')
|
||||
bcd.set_image(frame_name, frame_image)
|
||||
|
||||
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.median_blur_per = lib_csw.Number.Client()
|
||||
self.degrade_bicubic_per = lib_csw.Number.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.median_blur_per = lib_csw.Number.Host()
|
||||
self.degrade_bicubic_per = lib_csw.Number.Host()
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
median_blur_per : int = None
|
||||
degrade_bicubic_per : int = None
|
276
apps/DeepFaceLive/backend/StreamOutput.py
Normal file
|
@ -0,0 +1,276 @@
|
|||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from xlib import cv as lib_cv
|
||||
from xlib import logic as lib_logic
|
||||
from xlib import os as lib_os
|
||||
from xlib import time as lib_time
|
||||
from xlib.image import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||
BackendWorkerState)
|
||||
|
||||
|
||||
class StreamOutput(BackendHost):
|
||||
"""
|
||||
Bufferizes and shows the stream in separated window.
|
||||
"""
|
||||
def __init__(self, weak_heap : BackendWeakHeap,
|
||||
reemit_frame_signal : BackendSignal,
|
||||
bc_in : BackendConnection,
|
||||
save_default_path : Path = None,
|
||||
backend_db : BackendDB = None):
|
||||
|
||||
super().__init__(backend_db=backend_db,
|
||||
sheet_cls=Sheet,
|
||||
worker_cls=StreamOutputWorker,
|
||||
worker_state_cls=WorkerState,
|
||||
worker_start_args=[weak_heap, reemit_frame_signal, bc_in, save_default_path] )
|
||||
|
||||
def get_control_sheet(self) -> 'Sheet.Host': return super().get_control_sheet()
|
||||
|
||||
class SourceType(IntEnum):
|
||||
ALIGNED_FACE = 0
|
||||
SWAPPED_FACE = 1
|
||||
MERGED_FRAME = 2
|
||||
|
||||
ViewModeNames = ['@StreamOutput.SourceType.ALIGNED_FACE', '@StreamOutput.SourceType.SWAPPED_FACE', '@StreamOutput.SourceType.MERGED_FRAME']
|
||||
|
||||
|
||||
class StreamOutputWorker(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,
|
||||
save_default_path : Path):
|
||||
self.weak_heap = weak_heap
|
||||
self.reemit_frame_signal = reemit_frame_signal
|
||||
self.bc_in = bc_in
|
||||
|
||||
self.fps_counter = lib_time.FPSCounter()
|
||||
|
||||
self.buffered_frames = lib_logic.DelayedBuffers()
|
||||
self.is_show_window = False
|
||||
|
||||
self.prev_frame_num = -1
|
||||
|
||||
self._wnd_name = 'DeepFaceLive output'
|
||||
self._wnd_showing = False
|
||||
|
||||
lib_os.set_timer_resolution(1)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
cs.source_type.call_on_selected(self.on_cs_mode)
|
||||
cs.show_hide_window.call_on_signal(self.on_cs_show_hide_window_signal)
|
||||
cs.aligned_face_id.call_on_number(self.on_cs_aligned_face_id)
|
||||
cs.target_delay.call_on_number(self.on_cs_target_delay)
|
||||
cs.save_sequence_path.call_on_paths(self.on_cs_save_sequence_path)
|
||||
cs.save_fill_frame_gap.call_on_flag(self.on_cs_save_fill_frame_gap)
|
||||
|
||||
cs.source_type.enable()
|
||||
cs.source_type.set_choices(SourceType, ViewModeNames, none_choice_name='@misc.menu_select')
|
||||
cs.source_type.select(state.source_type)
|
||||
|
||||
cs.target_delay.enable()
|
||||
cs.target_delay.set_config(lib_csw.Number.Config(min=0, max=5000, step=100, decimals=0, allow_instant_update=True))
|
||||
cs.target_delay.set_number(state.target_delay if state.target_delay is not None else 500)
|
||||
|
||||
cs.avg_fps.enable()
|
||||
cs.avg_fps.set_config(lib_csw.Number.Config(min=0, max=240, decimals=1, read_only=True))
|
||||
cs.avg_fps.set_number(0)
|
||||
|
||||
cs.show_hide_window.enable()
|
||||
self.hide_window()
|
||||
|
||||
if state.is_showing_window is None:
|
||||
state.is_showing_window = False
|
||||
|
||||
if state.is_showing_window:
|
||||
state.is_showing_window = not state.is_showing_window
|
||||
cs.show_hide_window.signal()
|
||||
|
||||
|
||||
cs.save_sequence_path.enable()
|
||||
cs.save_sequence_path.set_config( lib_csw.Paths.Config.Directory('Choose output sequence directory', directory_path=save_default_path) )
|
||||
cs.save_sequence_path.set_paths(state.sequence_path)
|
||||
|
||||
cs.save_fill_frame_gap.enable()
|
||||
cs.save_fill_frame_gap.set_flag(state.save_fill_frame_gap if state.save_fill_frame_gap is not None else True )
|
||||
|
||||
|
||||
|
||||
def on_cs_mode(self, idx, source_type):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
if source_type == SourceType.ALIGNED_FACE:
|
||||
cs.aligned_face_id.enable()
|
||||
cs.aligned_face_id.set_config(lib_csw.Number.Config(min=0, max=16, step=1, allow_instant_update=True))
|
||||
cs.aligned_face_id.set_number(state.aligned_face_id or 0)
|
||||
else:
|
||||
cs.aligned_face_id.disable()
|
||||
|
||||
state.source_type = source_type
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def show_window(self):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cv2.namedWindow(self._wnd_name)
|
||||
self._wnd_showing = True
|
||||
|
||||
def hide_window(self):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
if self._wnd_showing:
|
||||
cv2.destroyWindow(self._wnd_name)
|
||||
self._wnd_showing = False
|
||||
|
||||
def on_cs_show_hide_window_signal(self,):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
||||
state.is_showing_window = not state.is_showing_window
|
||||
if state.is_showing_window:
|
||||
cv2.namedWindow(self._wnd_name)
|
||||
else:
|
||||
cv2.destroyWindow(self._wnd_name)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
|
||||
def on_cs_aligned_face_id(self, aligned_face_id):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.aligned_face_id.get_config()
|
||||
aligned_face_id = state.aligned_face_id = np.clip(aligned_face_id, cfg.min, cfg.max)
|
||||
cs.aligned_face_id.set_number(aligned_face_id)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_target_delay(self, target_delay):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cfg = cs.target_delay.get_config()
|
||||
target_delay = state.target_delay = int(np.clip(target_delay, cfg.min, cfg.max))
|
||||
self.buffered_frames.set_target_delay(target_delay / 1000.0)
|
||||
cs.target_delay.set_number(target_delay)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_save_sequence_path(self, paths : List[Path], prev_paths):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cs.save_sequence_path_error.set_error(None)
|
||||
sequence_path = paths[0] if len(paths) != 0 else None
|
||||
|
||||
if sequence_path is None or sequence_path.exists():
|
||||
state.sequence_path = sequence_path
|
||||
cs.save_sequence_path.set_paths(sequence_path, block_event=True)
|
||||
else:
|
||||
cs.save_sequence_path_error.set_error(f'{sequence_path} does not exist.')
|
||||
cs.save_sequence_path.set_paths(prev_paths, block_event=True)
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
def on_cs_save_fill_frame_gap(self, save_fill_frame_gap):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
state.save_fill_frame_gap = save_fill_frame_gap
|
||||
self.save_state()
|
||||
|
||||
def on_tick(self):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
|
||||
bcd = self.bc_in.read(timeout=0.005)
|
||||
if bcd is not None:
|
||||
bcd.assign_weak_heap(self.weak_heap)
|
||||
cs.avg_fps.set_number( self.fps_counter.step() )
|
||||
|
||||
prev_frame_num = self.prev_frame_num
|
||||
frame_num = self.prev_frame_num = bcd.get_frame_num()
|
||||
if frame_num < prev_frame_num:
|
||||
prev_frame_num = self.prev_frame_num = -1
|
||||
|
||||
source_type = state.source_type
|
||||
if source_type is not None and \
|
||||
(state.is_showing_window or state.sequence_path is not None):
|
||||
buffered_frames = self.buffered_frames
|
||||
|
||||
view_image = None
|
||||
|
||||
if source_type == SourceType.MERGED_FRAME:
|
||||
view_image = bcd.get_image(bcd.get_merged_frame_name())
|
||||
|
||||
elif source_type == SourceType.ALIGNED_FACE:
|
||||
aligned_face_id = state.aligned_face_id
|
||||
for i, face_mark in enumerate(bcd.get_face_mark_list()):
|
||||
if aligned_face_id == i:
|
||||
face_align = face_mark.get_face_align()
|
||||
if face_align is not None:
|
||||
view_image = bcd.get_image(face_align.get_image_name())
|
||||
break
|
||||
|
||||
elif source_type == SourceType.SWAPPED_FACE:
|
||||
for face_mark in bcd.get_face_mark_list():
|
||||
face_align = face_mark.get_face_align()
|
||||
if face_align is not None:
|
||||
face_swap = face_align.get_face_swap()
|
||||
if face_swap is not None:
|
||||
view_image = bcd.get_image(face_swap.get_image_name())
|
||||
break
|
||||
|
||||
if view_image is not None:
|
||||
buffered_frames.add_buffer( bcd.get_frame_timestamp(), view_image )
|
||||
|
||||
if state.sequence_path is not None:
|
||||
img = ImageProcessor(view_image, copy=True).to_uint8().get_image('HWC')
|
||||
|
||||
file_ext, cv_args = '.jpg', [int(cv2.IMWRITE_JPEG_QUALITY), 100]
|
||||
|
||||
frame_diff = abs(frame_num - prev_frame_num) if state.save_fill_frame_gap else 1
|
||||
for i in range(frame_diff):
|
||||
n = frame_num - i
|
||||
filename = f'{n:06}'
|
||||
lib_cv.imwrite(state.sequence_path / (filename+file_ext), img, cv_args)
|
||||
|
||||
pr = buffered_frames.process()
|
||||
|
||||
img = pr.new_data
|
||||
if state.is_showing_window and img is not None:
|
||||
cv2.imshow(self._wnd_name, img)
|
||||
|
||||
if state.is_showing_window:
|
||||
cv2.waitKey(1)
|
||||
|
||||
class Sheet:
|
||||
class Host(lib_csw.Sheet.Host):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_type = lib_csw.DynamicSingleSwitch.Client()
|
||||
self.aligned_face_id = lib_csw.Number.Client()
|
||||
self.target_delay = lib_csw.Number.Client()
|
||||
self.avg_fps = lib_csw.Number.Client()
|
||||
self.show_hide_window = lib_csw.Signal.Client()
|
||||
self.save_sequence_path = lib_csw.Paths.Client()
|
||||
self.save_sequence_path_error = lib_csw.Error.Client()
|
||||
self.save_fill_frame_gap = lib_csw.Flag.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.source_type = lib_csw.DynamicSingleSwitch.Host()
|
||||
self.aligned_face_id = lib_csw.Number.Host()
|
||||
self.target_delay = lib_csw.Number.Host()
|
||||
self.avg_fps = lib_csw.Number.Host()
|
||||
self.show_hide_window = lib_csw.Signal.Host()
|
||||
self.save_sequence_path = lib_csw.Paths.Host()
|
||||
self.save_sequence_path_error = lib_csw.Error.Host()
|
||||
self.save_fill_frame_gap = lib_csw.Flag.Host()
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
source_type : SourceType = None
|
||||
is_showing_window : bool = None
|
||||
aligned_face_id : int = None
|
||||
target_delay : int = None
|
||||
sequence_path : Path = None
|
||||
save_fill_frame_gap : bool = None
|
11
apps/DeepFaceLive/backend/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from .BackendBase import (BackendConnection, BackendConnectionData, BackendDB,
|
||||
BackendSignal, BackendWeakHeap, BackendHost, BackendWorker)
|
||||
from .CameraSource import CameraSource
|
||||
from .FaceAligner import FaceAligner
|
||||
from .FaceDetector import FaceDetector
|
||||
from .FaceMarker import FaceMarker
|
||||
from .FaceMerger import FaceMerger
|
||||
from .FaceSwapper import FaceSwapper
|
||||
from .FileSource import FileSource
|
||||
from .FrameAdjuster import FrameAdjuster
|
||||
from .StreamOutput import StreamOutput
|
79
apps/DeepFaceLive/ui/QCameraSource.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ..backend import CameraSource
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
|
||||
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
|
||||
QComboBoxCSWDynamicSingleSwitch
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
|
||||
|
||||
|
||||
class QCameraSource(QBackendPanel):
|
||||
def __init__(self, backend : CameraSource):
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
q_device_idx_label = QLabelPopupInfo(label=L('@QCameraSource.device_index') )
|
||||
q_device_idx = QComboBoxCSWDynamicSingleSwitch(cs.device_idx, reflect_state_widgets=[q_device_idx_label])
|
||||
|
||||
q_driver_label = QLabelPopupInfo(label=L('@QCameraSource.driver'), popup_info_text=L('@QCameraSource.help.driver') )
|
||||
q_driver = QComboBoxCSWDynamicSingleSwitch(cs.driver, reflect_state_widgets=[q_driver_label])
|
||||
|
||||
q_resolution_label = QLabelPopupInfo(label=L('@QCameraSource.resolution'), popup_info_text=L('@QCameraSource.help.resolution') )
|
||||
q_resolution = QComboBoxCSWDynamicSingleSwitch(cs.resolution, reflect_state_widgets=[q_resolution_label])
|
||||
|
||||
q_fps_label = QLabelPopupInfo(label=L('@QCameraSource.fps'), popup_info_text=L('@QCameraSource.help.fps') )
|
||||
q_fps = QSpinBoxCSWNumber(cs.fps, reflect_state_widgets=[q_fps_label])
|
||||
|
||||
q_rotation_label = QLabelPopupInfo(label=L('@QCameraSource.rotation') )
|
||||
q_rotation = QComboBoxCSWDynamicSingleSwitch(cs.rotation, reflect_state_widgets=[q_rotation_label])
|
||||
|
||||
q_flip_horizontal_label = QLabelPopupInfo(label=L('@QCameraSource.flip_horizontal') )
|
||||
q_flip_horizontal = QCheckBoxCSWFlag(cs.flip_horizontal, reflect_state_widgets=[q_flip_horizontal_label])
|
||||
|
||||
q_camera_settings_group_label = QLabelPopupInfo(label=L('@QCameraSource.camera_settings') )
|
||||
|
||||
q_open_settings = QXPushButtonCSWSignal(cs.open_settings, text=L('@QCameraSource.open_settings'))
|
||||
q_load_settings = QXPushButtonCSWSignal(cs.load_settings, text=L('@QCameraSource.load_settings'), reflect_state_widgets=[q_camera_settings_group_label])
|
||||
q_save_settings = QXPushButtonCSWSignal(cs.save_settings, text=L('@QCameraSource.save_settings'))
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_device_idx_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_device_idx, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_driver_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_driver, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_resolution_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_resolution, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
btn_height = 24
|
||||
grid_l.addWidget(q_camera_settings_group_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget( lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([
|
||||
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([q_open_settings]), ),
|
||||
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([q_load_settings]), ),
|
||||
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([q_save_settings]), ),
|
||||
], contents_margins=(1,0,1,0), spacing=1), fixed_height=btn_height), row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
|
||||
|
||||
grid_l.addWidget(q_fps_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_fps, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_rotation_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_rotation, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_flip_horizontal_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_flip_horizontal, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
|
||||
super().__init__(backend, L('@QCameraSource.module_title'),
|
||||
layout=lib_qt.QXVBoxLayout([grid_l], spacing=5),
|
||||
content_align_top=True)
|
||||
|
40
apps/DeepFaceLive/ui/QFaceAligner.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ..backend import FaceAligner
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||
|
||||
|
||||
class QFaceAligner(QBackendPanel):
|
||||
def __init__(self, backend : FaceAligner):
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
q_face_coverage_label = QLabelPopupInfo(label=L('@QFaceAligner.face_coverage'), popup_info_text=L('@QFaceAligner.help.face_coverage') )
|
||||
q_face_coverage = QSpinBoxCSWNumber(cs.face_coverage, reflect_state_widgets=[q_face_coverage_label])
|
||||
|
||||
q_resolution_label = QLabelPopupInfo(label=L('@QFaceAligner.resolution'), popup_info_text=L('@QFaceAligner.help.resolution') )
|
||||
q_resolution = QSpinBoxCSWNumber(cs.resolution, reflect_state_widgets=[q_resolution_label])
|
||||
|
||||
q_exclude_moving_parts_label = QLabelPopupInfo(label=L('@QFaceAligner.exclude_moving_parts'), popup_info_text=L('@QFaceAligner.help.exclude_moving_parts') )
|
||||
q_exclude_moving_parts = QCheckBoxCSWFlag(cs.exclude_moving_parts, reflect_state_widgets=[q_exclude_moving_parts_label])
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_face_coverage_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_face_coverage, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_resolution_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_resolution, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_exclude_moving_parts_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_exclude_moving_parts, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
super().__init__(backend, L('@QFaceAligner.module_title'),
|
||||
layout=lib_qt.QXVBoxLayout([grid_l]))
|
||||
|
114
apps/DeepFaceLive/ui/QFaceDetector.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
import numpy as np
|
||||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ..backend import FaceDetector
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
|
||||
QComboBoxCSWDynamicSingleSwitch
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||
|
||||
|
||||
class QFaceDetector(QBackendPanel):
|
||||
def __init__(self, backend : FaceDetector):
|
||||
if not isinstance(backend, FaceDetector):
|
||||
raise ValueError('backend must be an instance of FaceDetector')
|
||||
|
||||
self._backend = backend
|
||||
self._bc_out = backend.get_bc_out()
|
||||
self._weak_heap = backend.get_weak_heap()
|
||||
self._bcd_id = None
|
||||
self._timer = lib_qt.QXTimer(interval=10, timeout=self._on_timer_10ms, start=True)
|
||||
|
||||
face_coords_label = self._q_face_coords_label = lib_qt.QXLabel(font=QXFontDB.get_fixedwidth_font(size=7), word_wrap=False)
|
||||
q_detected_faces = self._q_detected_faces = lib_qt.QXCollapsibleSection(title=L('@QFaceDetector.detected_faces'),
|
||||
content_layout=lib_qt.QXVBoxLayout([face_coords_label]), is_opened=True)
|
||||
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
q_detector_type_label = QLabelPopupInfo(label=L('@QFaceDetector.detector_type'), popup_info_text=L('@QFaceDetector.help.detector_type') )
|
||||
q_detector_type = QComboBoxCSWDynamicSingleSwitch(cs.detector_type, reflect_state_widgets=[q_detector_type_label])
|
||||
|
||||
q_device_label = QLabelPopupInfo(label=L('@QFaceDetector.device'), popup_info_text=L('@QFaceDetector.help.device') )
|
||||
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
|
||||
|
||||
q_fixed_window_size_label = QLabelPopupInfo(label=L('@QFaceDetector.window_size'), popup_info_text=L('@QFaceDetector.help.window_size') )
|
||||
q_fixed_window_size = QSpinBoxCSWNumber(cs.fixed_window_size, reflect_state_widgets=[q_fixed_window_size_label])
|
||||
|
||||
q_threshold_label = QLabelPopupInfo(label=L('@QFaceDetector.threshold'), popup_info_text=L('@QFaceDetector.help.threshold') )
|
||||
q_threshold = QSpinBoxCSWNumber(cs.threshold, reflect_state_widgets=[q_threshold_label])
|
||||
|
||||
q_max_faces_label = QLabelPopupInfo(label=L('@QFaceDetector.max_faces'), popup_info_text=L('@QFaceDetector.help.max_faces') )
|
||||
q_max_faces = QSpinBoxCSWNumber(cs.max_faces, reflect_state_widgets=[q_max_faces_label])
|
||||
|
||||
q_sort_by_label = QLabelPopupInfo(label=L('@QFaceDetector.sort_by'), popup_info_text=L('@QFaceDetector.help.sort_by') )
|
||||
q_sort_by = QComboBoxCSWDynamicSingleSwitch(cs.sort_by, reflect_state_widgets=[q_sort_by_label])
|
||||
|
||||
q_temporal_smoothing_label = QLabelPopupInfo(label=L('@QFaceDetector.temporal_smoothing'), popup_info_text=L('@QFaceDetector.help.temporal_smoothing') )
|
||||
q_temporal_smoothing = QSpinBoxCSWNumber(cs.temporal_smoothing, reflect_state_widgets=[q_temporal_smoothing_label])
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(vertical_spacing=5, horizontal_spacing=5, )
|
||||
row = 0
|
||||
grid_l.addWidget(q_detector_type_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_detector_type, row, 1, 1, 3, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_device_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_device, row, 1, 1, 3, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_fixed_window_size_label, row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_fixed_window_size, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_threshold_label, row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_threshold, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addLayout( lib_qt.QXHBoxLayout([q_max_faces_label, 5, q_max_faces]), row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addLayout( lib_qt.QXHBoxLayout([q_sort_by_label, 5,q_sort_by]), row, 2, 1,2,alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
|
||||
row += 1
|
||||
grid_l.addLayout( lib_qt.QXHBoxLayout([q_temporal_smoothing_label, 5, q_temporal_smoothing]), row, 0, 1, 4, alignment=Qt.AlignmentFlag.AlignCenter )
|
||||
row += 1
|
||||
grid_l.addWidget(q_detected_faces, row, 0, 1, 4)
|
||||
row += 1
|
||||
super().__init__(backend, L('@QFaceDetector.module_title'), layout=lib_qt.QXVBoxLayout([grid_l]))
|
||||
|
||||
def _on_backend_state_change(self, backend, started, starting, stopping, stopped, busy):
|
||||
super()._on_backend_state_change (backend, started, starting, stopping, stopped, busy)
|
||||
|
||||
if stopped:
|
||||
self._q_face_coords_label.clear()
|
||||
|
||||
def _on_timer_10ms(self):
|
||||
|
||||
if self._q_detected_faces.is_opened() and not self.get_top_QXWindow().is_minimized():
|
||||
bcd_id = self._bc_out.get_write_id()
|
||||
if self._bcd_id != bcd_id:
|
||||
# Has new bcd version
|
||||
bcd, self._bcd_id = self._bc_out.get_by_id(bcd_id), bcd_id
|
||||
|
||||
if bcd is not None:
|
||||
bcd.assign_weak_heap(self._weak_heap)
|
||||
|
||||
frame_name = bcd.get_frame_name()
|
||||
frame_image = bcd.get_image(frame_name)
|
||||
frame_image_w_h = None
|
||||
if frame_image is not None:
|
||||
h,w = frame_image.shape[0:2]
|
||||
frame_image_w_h = (w,h)
|
||||
|
||||
info = []
|
||||
for face_num,face_mark in enumerate(bcd.get_face_mark_list()):
|
||||
info_str = f'{face_num}: '
|
||||
|
||||
rect = face_mark.get_face_urect()
|
||||
if rect is not None:
|
||||
l,t,r,b = rect.as_ltrb_bbox(frame_image_w_h).astype(np.int)
|
||||
info_str += f'[{l},{t},{r},{b}]'
|
||||
|
||||
info.append(info_str)
|
||||
|
||||
info = '\n'.join(info)
|
||||
self._q_face_coords_label.setText(info)
|
61
apps/DeepFaceLive/ui/QFaceMarker.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
|
||||
QComboBoxCSWDynamicSingleSwitch
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||
|
||||
|
||||
class QFaceMarker(QBackendPanel):
|
||||
def __init__(self, backend):
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
q_marker_type_label = QLabelPopupInfo(label=L('@QFaceMarker.marker_type'), popup_info_text=L('@QFaceMarker.help.marker_type') )
|
||||
q_marker_type = QComboBoxCSWDynamicSingleSwitch(cs.marker_type, reflect_state_widgets=[q_marker_type_label])
|
||||
|
||||
q_device_label = QLabelPopupInfo(label=L('@QFaceMarker.device'), popup_info_text=L('@QFaceMarker.help.device') )
|
||||
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
|
||||
|
||||
q_marker_coverage_label = QLabelPopupInfo(label=L('@QFaceMarker.marker_coverage'), popup_info_text=L('@QFaceMarker.help.marker_coverage') )
|
||||
q_marker_coverage = QSpinBoxCSWNumber(cs.marker_coverage, reflect_state_widgets=[q_marker_coverage_label])
|
||||
|
||||
q_temporal_smoothing_label = QLabelPopupInfo(label=L('@QFaceMarker.temporal_smoothing'), popup_info_text=L('@QFaceMarker.help.temporal_smoothing') )
|
||||
q_temporal_smoothing = QSpinBoxCSWNumber(cs.temporal_smoothing, reflect_state_widgets=[q_temporal_smoothing_label])
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_marker_type_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_marker_type, row, 1, 1, 3, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_device_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_device, row, 1, 1, 3, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
|
||||
sub_row = 0
|
||||
sub_grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
sub_grid_l.addWidget(q_marker_coverage_label, sub_row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
sub_grid_l.addWidget(q_marker_coverage, sub_row, 1, 1, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
sub_row += 1
|
||||
sub_grid_l.addWidget(q_temporal_smoothing_label, sub_row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
sub_grid_l.addWidget(q_temporal_smoothing, sub_row, 1, 1, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
sub_row += 1
|
||||
|
||||
|
||||
grid_l.addLayout(sub_grid_l, row, 0, 1, 4, alignment=Qt.AlignmentFlag.AlignCenter )
|
||||
row += 1
|
||||
|
||||
super().__init__(backend, L('@QFaceMarker.module_title'),
|
||||
layout=lib_qt.QXVBoxLayout([grid_l]) )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
66
apps/DeepFaceLive/ui/QFaceMerger.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
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
|
||||
|
||||
|
||||
class QFaceMerger(QBackendPanel):
|
||||
def __init__(self, backend):
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
q_device_label = QLabelPopupInfo(label=L('@QFaceMerger.device'), popup_info_text=L('@QFaceMerger.help.device'))
|
||||
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
|
||||
|
||||
q_face_x_offset_label = QLabelPopupInfo(label=L('@QFaceMerger.face_x_offset'))
|
||||
q_face_x_offset = QSpinBoxCSWNumber(cs.face_x_offset, reflect_state_widgets=[q_face_x_offset_label])
|
||||
|
||||
q_face_y_offset_label = QLabelPopupInfo(label=L('@QFaceMerger.face_y_offset'))
|
||||
q_face_y_offset = QSpinBoxCSWNumber(cs.face_y_offset, reflect_state_widgets=[q_face_y_offset_label])
|
||||
|
||||
q_face_scale_label = QLabelPopupInfo(label=L('@QFaceMerger.face_scale') )
|
||||
q_face_scale = QSpinBoxCSWNumber(cs.face_scale, reflect_state_widgets=[q_face_scale_label])
|
||||
|
||||
q_face_mask_type_label = QLabelPopupInfo(label=L('@QFaceMerger.face_mask_type') )
|
||||
q_face_mask_type = QComboBoxCSWDynamicSingleSwitch(cs.face_mask_type, reflect_state_widgets=[q_face_mask_type_label])
|
||||
|
||||
q_face_mask_erode_label = QLabelPopupInfo(label=L('@QFaceMerger.face_mask_erode') )
|
||||
q_face_mask_erode = QSpinBoxCSWNumber(cs.face_mask_erode, reflect_state_widgets=[q_face_mask_erode_label])
|
||||
|
||||
q_face_mask_blur_label = QLabelPopupInfo(label=L('@QFaceMerger.face_mask_blur') )
|
||||
q_face_mask_blur = QSpinBoxCSWNumber(cs.face_mask_blur, reflect_state_widgets=[q_face_mask_blur_label])
|
||||
|
||||
q_face_opacity_label = QLabelPopupInfo(label=L('@QFaceMerger.face_opacity') )
|
||||
q_face_opacity = QSliderCSWNumber(cs.face_opacity, reflect_state_widgets=[q_face_opacity_label])
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_device_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_device, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addLayout( lib_qt.QXVBoxLayout([q_face_x_offset_label, q_face_y_offset_label]), row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addLayout( lib_qt.QXHBoxLayout([q_face_x_offset, q_face_y_offset]), row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_face_scale_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_face_scale, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_face_mask_type_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_face_mask_type, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addLayout(lib_qt.QXVBoxLayout([q_face_mask_erode_label,q_face_mask_blur_label]), row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addLayout(lib_qt.QXHBoxLayout([q_face_mask_erode,q_face_mask_blur], spacing=3), row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_face_opacity_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_face_opacity, row, 1)
|
||||
row += 1
|
||||
|
||||
super().__init__(backend, L('@QFaceMerger.module_title'),
|
||||
layout=lib_qt.QXVBoxLayout([grid_l]) )
|
||||
|
95
apps/DeepFaceLive/ui/QFaceSwapper.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
from pathlib import Path
|
||||
|
||||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.gfx import QXImageDB
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ..backend import FaceSwapper
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
|
||||
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
|
||||
QComboBoxCSWDynamicSingleSwitch
|
||||
from .widgets.QErrorCSWError import QErrorCSWError
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QLabelPopupInfoCSWInfoLabel import QLabelPopupInfoCSWInfoLabel
|
||||
from .widgets.QProgressBarCSWProgress import QProgressBarCSWProgress
|
||||
from .widgets.QSliderCSWNumber import QSliderCSWNumber
|
||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||
|
||||
|
||||
class QFaceSwapper(QBackendPanel):
|
||||
def __init__(self, backend : FaceSwapper, dfm_models_path : Path):
|
||||
self._dfm_models_path = dfm_models_path
|
||||
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
btn_open_folder = self.btn_open_folder = lib_qt.QXPushButton(image = QXImageDB.eye_outline('light gray'), tooltip_text='Reveal in Explorer', released=self._btn_open_folder_released, fixed_size=(24,22) )
|
||||
|
||||
q_model_label = QLabelPopupInfo(label=L('@QFaceSwapper.model'), popup_info_text=L('@QFaceSwapper.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)
|
||||
q_model_dl_progress = self._q_model_dl_progress = QProgressBarCSWProgress(cs.model_dl_progress)
|
||||
|
||||
q_model_info_label = self._q_model_info_label = QLabelPopupInfoCSWInfoLabel(cs.model_info_label)
|
||||
|
||||
q_device_label = QLabelPopupInfo(label=L('@QFaceSwapper.device'), popup_info_text=L('@QFaceSwapper.help.device') )
|
||||
q_device = QComboBoxCSWDynamicSingleSwitch(cs.device, reflect_state_widgets=[q_device_label])
|
||||
|
||||
q_face_id_label = QLabelPopupInfo(label=L('@QFaceSwapper.face_id'), popup_info_text=L('@QFaceSwapper.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 = 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 = 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_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_two_pass_label = QLabelPopupInfo(label=L('@QFaceSwapper.two_pass'), popup_info_text=L('@QFaceSwapper.help.two_pass') )
|
||||
q_two_pass = QCheckBoxCSWFlag(cs.two_pass, reflect_state_widgets=[q_two_pass_label])
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_model_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addLayout(lib_qt.QXHBoxLayout([q_model, 2, btn_open_folder, 2, q_model_info_label]), row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_model_dl_progress, row, 0, 1, 2 )
|
||||
row += 1
|
||||
grid_l.addWidget(q_model_dl_error, row, 0, 1, 2 )
|
||||
row += 1
|
||||
grid_l.addWidget(q_device_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_device, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_face_id_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_face_id, row, 1, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(q_morph_factor_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_morph_factor, row, 1)
|
||||
row += 1
|
||||
grid_l.addWidget(q_sharpen_amount_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_sharpen_amount, row, 1)
|
||||
row += 1
|
||||
|
||||
grid_l.addWidget( q_pre_gamma_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addLayout( lib_qt.QXHBoxLayout([q_pre_gamma_red, q_pre_gamma_green, q_pre_gamma_blue ]), row, 1)
|
||||
row += 1
|
||||
|
||||
grid_l.addWidget(q_two_pass_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_two_pass, row, 1)
|
||||
row += 1
|
||||
|
||||
super().__init__(backend, L('@QFaceSwapper.module_title'),
|
||||
layout=lib_qt.QXVBoxLayout([grid_l]) )
|
||||
|
||||
|
||||
def _btn_open_folder_released(self):
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile( str(self._dfm_models_path) ))
|
77
apps/DeepFaceLive/ui/QFileSource.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.gfx import QXImageDB
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ..backend import FileSource
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
from .widgets.QButtonCSWDynamicSingleSwitch import \
|
||||
QButtonCSWDynamicSingleSwitch
|
||||
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
|
||||
from .widgets.QErrorCSWError import QErrorCSWError
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QPathEditCSWPaths import QPathEditCSWPaths
|
||||
from .widgets.QSliderCSWNumbers import QSliderCSWNumbers
|
||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
|
||||
|
||||
|
||||
class QFileSource(QBackendPanel):
|
||||
def __init__(self, backend : FileSource):
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
q_input_type = self._q_input_type = QButtonCSWDynamicSingleSwitch(cs.input_type, horizontal=True, radio_buttons=True)
|
||||
q_input_paths = self._q_input_paths = QPathEditCSWPaths(cs.input_paths)
|
||||
q_input_paths_error = self._q_input_paths_error = QErrorCSWError(cs.input_paths_error)
|
||||
|
||||
q_target_width_label = QLabelPopupInfo(label=L('@QFileSource.target_width'), popup_info_text=L('@QFileSource.help.target_width') )
|
||||
q_target_width = self._q_target_width = QSpinBoxCSWNumber(cs.target_width, reflect_state_widgets=[q_target_width_label])
|
||||
|
||||
q_fps_label = QLabelPopupInfo(label=L('@QFileSource.fps'), popup_info_text=L('@QFileSource.help.fps') )
|
||||
q_fps = self._q_fps = QSpinBoxCSWNumber(cs.fps, reflect_state_widgets=[q_fps_label])
|
||||
|
||||
q_is_realtime_label = QLabelPopupInfo(label=L('@QFileSource.is_realtime'), popup_info_text=L('@QFileSource.help.is_realtime') )
|
||||
q_is_realtime = self._q_is_realtime = QCheckBoxCSWFlag(cs.is_realtime, reflect_state_widgets=[q_is_realtime_label])
|
||||
|
||||
q_is_autorewind_label = QLabelPopupInfo(label=L('@QFileSource.is_autorewind'))
|
||||
q_is_autorewind = self._q_is_autorewind = QCheckBoxCSWFlag(cs.is_autorewind, reflect_state_widgets=[q_is_autorewind_label])
|
||||
|
||||
btn_size=(32,32)
|
||||
btn_color= '#E01010'
|
||||
btn_play = self._btn_play = QXPushButtonCSWSignal(cs.play, image=QXImageDB.play_circle_outline(btn_color), button_size=btn_size )
|
||||
btn_pause = self._btn_pause = QXPushButtonCSWSignal(cs.pause, image=QXImageDB.pause_circle_outline(btn_color), button_size=btn_size )
|
||||
btn_seek_backward = self._btn_seek_backward = QXPushButtonCSWSignal(cs.seek_backward, image=QXImageDB.play_back_circle_outline(btn_color), button_size=btn_size )
|
||||
btn_seek_forward = self._btn_seek_forward = QXPushButtonCSWSignal(cs.seek_forward, image=QXImageDB.play_forward_circle_outline(btn_color), button_size=btn_size )
|
||||
btn_seek_begin = self._btn_seek_begin = QXPushButtonCSWSignal(cs.seek_begin, image=QXImageDB.play_skip_back_circle_outline(btn_color), button_size=btn_size )
|
||||
btn_seek_end = self._btn_seek_end = QXPushButtonCSWSignal(cs.seek_end, image=QXImageDB.play_skip_forward_circle_outline(btn_color), button_size=btn_size )
|
||||
|
||||
q_frame_slider = self._q_frame_slider = QSliderCSWNumbers(cs.frame_index, cs.frame_count)
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_target_width_label, row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_target_width, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addLayout( lib_qt.QXHBoxLayout([q_is_realtime_label, 5, q_is_realtime, 5, q_fps_label]), row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget( q_fps, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget( q_is_autorewind_label, row, 0, 1, 2, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget( q_is_autorewind, row, 2, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft )
|
||||
row += 1
|
||||
grid_l.addWidget(lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([btn_seek_begin, btn_seek_backward, btn_play, btn_pause,btn_seek_forward, btn_seek_end], spacing=1), ),
|
||||
row, 0, 1, 4, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||
row += 1
|
||||
grid_l.addWidget(q_frame_slider, row, 0, 1, 4)
|
||||
row += 1
|
||||
|
||||
main_l = lib_qt.QXVBoxLayout([q_input_type,
|
||||
q_input_paths,
|
||||
q_input_paths_error,
|
||||
grid_l
|
||||
], spacing=5)
|
||||
|
||||
super().__init__(backend, L('@QFileSource.module_title'),
|
||||
layout=main_l, content_align_top=True)
|
||||
|
33
apps/DeepFaceLive/ui/QFrameAdjuster.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QSliderCSWNumber import QSliderCSWNumber
|
||||
|
||||
|
||||
class QFrameAdjuster(QBackendPanel):
|
||||
def __init__(self, backend):
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
q_median_blur_label = QLabelPopupInfo(label=L('@QFrameAdjuster.median_blur_per'), popup_info_text=L('@QFrameAdjuster.help.median_blur_per') )
|
||||
q_median_blur = QSliderCSWNumber(cs.median_blur_per, reflect_state_widgets=[q_median_blur_label])
|
||||
|
||||
q_degrade_bicubic_label = QLabelPopupInfo(label=L('@QFrameAdjuster.degrade_bicubic_per'), popup_info_text=L('@QFrameAdjuster.help.degrade_bicubic_per') )
|
||||
q_degrade_bicubic = QSliderCSWNumber(cs.degrade_bicubic_per, reflect_state_widgets=[q_degrade_bicubic_label])
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_median_blur_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_median_blur, row, 1)
|
||||
row += 1
|
||||
grid_l.addWidget(q_degrade_bicubic_label, row, 0, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_degrade_bicubic, row, 1)
|
||||
row += 1
|
||||
|
||||
super().__init__(backend, L('@QFrameAdjuster.module_title'),
|
||||
layout=lib_qt.QXVBoxLayout([grid_l]) )
|
||||
|
72
apps/DeepFaceLive/ui/QStreamOutput.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ..backend import StreamOutput
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
from .widgets.QCheckBoxCSWFlag import QCheckBoxCSWFlag
|
||||
from .widgets.QComboBoxCSWDynamicSingleSwitch import \
|
||||
QComboBoxCSWDynamicSingleSwitch
|
||||
from .widgets.QErrorCSWError import QErrorCSWError
|
||||
from .widgets.QLabelCSWNumber import QLabelCSWNumber
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QPathEditCSWPaths import QPathEditCSWPaths
|
||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
|
||||
|
||||
|
||||
class QStreamOutput(QBackendPanel):
|
||||
def __init__(self, backend : StreamOutput):
|
||||
cs = backend.get_control_sheet()
|
||||
|
||||
q_average_fps_label = QLabelPopupInfo(label=L('@QStreamOutput.avg_fps'), popup_info_text=L('@QStreamOutput.help.avg_fps'))
|
||||
q_average_fps = QLabelCSWNumber(cs.avg_fps, reflect_state_widgets=[q_average_fps_label])
|
||||
|
||||
q_source_type_label = QLabelPopupInfo(label=L('@QStreamOutput.source_type') )
|
||||
q_source_type = QComboBoxCSWDynamicSingleSwitch(cs.source_type, reflect_state_widgets=[q_source_type_label])
|
||||
|
||||
q_show_hide_window = QXPushButtonCSWSignal(cs.show_hide_window, text=L('@QStreamOutput.show_hide_window'), button_height=22)
|
||||
|
||||
q_aligned_face_id_label = QLabelPopupInfo(label=L('@QStreamOutput.aligned_face_id'), popup_info_text=L('@QStreamOutput.help.aligned_face_id'))
|
||||
q_aligned_face_id = QSpinBoxCSWNumber(cs.aligned_face_id, reflect_state_widgets=[q_aligned_face_id_label])
|
||||
|
||||
q_target_delay_label = QLabelPopupInfo(label=L('@QStreamOutput.target_delay'), popup_info_text=L('@QStreamOutput.help.target_delay'))
|
||||
q_target_delay = QSpinBoxCSWNumber(cs.target_delay, reflect_state_widgets=[q_target_delay_label])
|
||||
|
||||
q_save_sequence_path_label = QLabelPopupInfo(label=L('@QStreamOutput.save_sequence_path'), popup_info_text=L('@QStreamOutput.help.save_sequence_path'))
|
||||
q_save_sequence_path = QPathEditCSWPaths(cs.save_sequence_path, reflect_state_widgets=[q_target_delay_label])
|
||||
q_save_sequence_path_error = QErrorCSWError(cs.save_sequence_path_error)
|
||||
|
||||
q_save_fill_frame_gap_label = QLabelPopupInfo(label=L('@QStreamOutput.save_fill_frame_gap'), popup_info_text=L('@QStreamOutput.help.save_fill_frame_gap'))
|
||||
q_save_fill_frame_gap = QCheckBoxCSWFlag(cs.save_fill_frame_gap, reflect_state_widgets=[q_save_fill_frame_gap_label])
|
||||
|
||||
grid_l = lib_qt.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_average_fps_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_average_fps, row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
|
||||
row += 1
|
||||
grid_l.addWidget(q_source_type_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_source_type, row, 1, 1, 1, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_show_hide_window, row, 2, 1, 1)
|
||||
|
||||
row += 1
|
||||
grid_l.addWidget(q_aligned_face_id_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_aligned_face_id, row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
|
||||
row += 1
|
||||
grid_l.addWidget(q_target_delay_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_target_delay, row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
|
||||
row += 1
|
||||
|
||||
grid_l.addWidget(q_save_sequence_path_label, row, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter )
|
||||
grid_l.addWidget(q_save_sequence_path, row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
|
||||
row += 1
|
||||
grid_l.addLayout( lib_qt.QXHBoxLayout([q_save_fill_frame_gap, 4, q_save_fill_frame_gap_label]), row, 1, 1, 2, alignment=Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter )
|
||||
row += 1
|
||||
|
||||
grid_l.addWidget(q_save_sequence_path_error, row, 0, 1, 3)
|
||||
row += 1
|
||||
|
||||
super().__init__(backend, L('@QStreamOutput.module_title'),
|
||||
layout=grid_l)
|
86
apps/DeepFaceLive/ui/widgets/QBCFaceAlignViewer.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
import numpy as np
|
||||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.facemeta import FaceULandmarks
|
||||
from xlib.python import all_is_not_None
|
||||
|
||||
from ... import backend
|
||||
|
||||
|
||||
class QBCFaceAlignViewer(lib_qt.QXCollapsibleSection):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, backed_weak_heap : backend.BackendWeakHeap,
|
||||
bc : backend.BackendConnection,
|
||||
preview_width=256,):
|
||||
|
||||
self._preview_width = preview_width
|
||||
self._timer = lib_qt.QXTimer(interval=8, timeout=self._on_timer_8ms, start=True)
|
||||
self._backed_weak_heap = backed_weak_heap
|
||||
self._bc = bc
|
||||
self._bcd_id = None
|
||||
|
||||
layered_images = self._layered_images = lib_qt.QXFixedLayeredImages(preview_width, preview_width)
|
||||
info_label = self._info_label = lib_qt.QXLabel( font=QXFontDB.get_fixedwidth_font(size=7))
|
||||
|
||||
super().__init__(title=L('@QBCFaceAlignViewer.title'),
|
||||
content_layout=lib_qt.QXVBoxLayout(
|
||||
[ (layered_images, Qt.AlignmentFlag.AlignCenter),
|
||||
(info_label, Qt.AlignmentFlag.AlignCenter),
|
||||
]) )
|
||||
|
||||
def _on_timer_8ms(self):
|
||||
top_qx = self.get_top_QXWindow()
|
||||
if not self.is_opened() or (top_qx is not None and top_qx.is_minimized() ):
|
||||
return
|
||||
|
||||
bcd_id = self._bc.get_write_id()
|
||||
if self._bcd_id != bcd_id:
|
||||
# Has new bcd version
|
||||
bcd, self._bcd_id = self._bc.get_by_id(bcd_id), bcd_id
|
||||
if bcd is not None:
|
||||
bcd.assign_weak_heap(self._backed_weak_heap)
|
||||
|
||||
self._layered_images.clear_images()
|
||||
|
||||
for face_mark in bcd.get_face_mark_list():
|
||||
face_align = face_mark.get_face_align()
|
||||
if face_align is not None:
|
||||
face_align_image_name = face_align.get_image_name()
|
||||
|
||||
source_to_aligned_uni_mat = face_align.get_source_to_aligned_uni_mat()
|
||||
|
||||
if all_is_not_None(face_align_image_name):
|
||||
face_image = bcd.get_image(face_align_image_name).copy()
|
||||
|
||||
face_ulmrks = face_align.get_face_ulandmarks_by_type(FaceULandmarks.Type.LANDMARKS_2D_468)
|
||||
if face_ulmrks is None:
|
||||
face_ulmrks = face_align.get_face_ulandmarks_by_type(FaceULandmarks.Type.LANDMARKS_2D_68)
|
||||
|
||||
if face_ulmrks is not None:
|
||||
lmrks_layer = np.zeros( (self._preview_width, self._preview_width, 4), dtype=np.uint8)
|
||||
|
||||
face_ulmrks.draw(lmrks_layer, (0,255,0,255))
|
||||
|
||||
face_mark_rect = face_mark.get_face_urect()
|
||||
if face_mark_rect is not None:
|
||||
aligned_uni_rect = face_mark_rect.transform(source_to_aligned_uni_mat)
|
||||
aligned_uni_rect.draw(lmrks_layer, (0,0,255,255) )
|
||||
|
||||
|
||||
self._layered_images.add_image(face_image)
|
||||
self._layered_images.add_image(lmrks_layer)
|
||||
|
||||
h,w = face_image.shape[0:2]
|
||||
|
||||
self._info_label.setText(f'{w}x{h}')
|
||||
|
||||
return
|
||||
|
||||
|
||||
def clear(self):
|
||||
self._layered_images.clear_images()
|
61
apps/DeepFaceLive/ui/widgets/QBCFaceSwapViewer.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ... import backend
|
||||
|
||||
|
||||
class QBCFaceSwapViewer(lib_qt.QXCollapsibleSection):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, backed_weak_heap : backend.BackendWeakHeap,
|
||||
bc : backend.BackendConnection,
|
||||
preview_width=256,):
|
||||
self._preview_width = preview_width
|
||||
self._timer = lib_qt.QXTimer(interval=8, timeout=self._on_timer_8ms, start=True)
|
||||
|
||||
self._backed_weak_heap = backed_weak_heap
|
||||
self._bc = bc
|
||||
self._bcd_id = None
|
||||
|
||||
layered_images = self._layered_images = lib_qt.QXFixedLayeredImages(preview_width, preview_width)
|
||||
info_label = self._info_label = lib_qt.QXLabel( font=QXFontDB.get_fixedwidth_font(size=7))
|
||||
|
||||
main_l = lib_qt.QXVBoxLayout([ (layered_images, Qt.AlignmentFlag.AlignCenter),
|
||||
(info_label, Qt.AlignmentFlag.AlignCenter),
|
||||
], spacing=0)
|
||||
|
||||
super().__init__(title=L('@QBCFaceSwapViewer.title'), content_layout=main_l)
|
||||
|
||||
|
||||
def _on_timer_8ms(self):
|
||||
top_qx = self.get_top_QXWindow()
|
||||
if not self.is_opened() or (top_qx is not None and top_qx.is_minimized() ):
|
||||
return
|
||||
|
||||
bcd_id = self._bc.get_write_id()
|
||||
if self._bcd_id != bcd_id:
|
||||
# Has new bcd version
|
||||
bcd, self._bcd_id = self._bc.get_by_id(bcd_id), bcd_id
|
||||
if bcd is not None:
|
||||
bcd.assign_weak_heap(self._backed_weak_heap)
|
||||
|
||||
self._layered_images.clear_images()
|
||||
|
||||
for face_mark in bcd.get_face_mark_list():
|
||||
face_align = face_mark.get_face_align()
|
||||
if face_align is not None:
|
||||
face_swap = face_align.get_face_swap()
|
||||
if face_swap is not None:
|
||||
face_swap_image = bcd.get_image(face_swap.get_image_name())
|
||||
if face_swap_image is not None:
|
||||
self._layered_images.add_image(face_swap_image)
|
||||
h,w = face_swap_image.shape[0:2]
|
||||
self._info_label.setText(f'{w}x{h}')
|
||||
return
|
||||
|
||||
def clear(self):
|
||||
self._layered_images.clear_images()
|
60
apps/DeepFaceLive/ui/widgets/QBCFinalFrameViewer.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ... import backend
|
||||
|
||||
|
||||
class QBCFinalFrameViewer(lib_qt.QXCollapsibleSection):
|
||||
def __init__(self, backed_weak_heap : backend.BackendWeakHeap,
|
||||
bc : backend.BackendConnection,
|
||||
preview_width=256):
|
||||
self._timer = lib_qt.QXTimer(interval=8, timeout=self._on_timer_8ms, start=True)
|
||||
|
||||
self._backed_weak_heap = backed_weak_heap
|
||||
self._bc = bc
|
||||
self._bcd_id = None
|
||||
|
||||
layered_images = self._layered_images = lib_qt.QXFixedLayeredImages(preview_width, preview_width)
|
||||
|
||||
info_label = self._info_label = lib_qt.QXLabel( font=QXFontDB.get_fixedwidth_font(size=7))
|
||||
|
||||
main_l = lib_qt.QXVBoxLayout([ (layered_images, Qt.AlignmentFlag.AlignCenter),
|
||||
(info_label, Qt.AlignmentFlag.AlignCenter),
|
||||
], spacing=0)
|
||||
super().__init__(title=L('@QBCFinalFrameViewer.title'), content_layout=main_l)
|
||||
|
||||
def _on_timer_8ms(self):
|
||||
top_qx = self.get_top_QXWindow()
|
||||
if not self.is_opened() or (top_qx is not None and top_qx.is_minimized() ):
|
||||
return
|
||||
|
||||
bcd_id = self._bc.get_write_id()
|
||||
if self._bcd_id != bcd_id:
|
||||
# Has new bcd version
|
||||
bcd, self._bcd_id = self._bc.get_by_id(bcd_id), bcd_id
|
||||
if bcd is not None:
|
||||
bcd.assign_weak_heap(self._backed_weak_heap)
|
||||
|
||||
self._layered_images.clear_images()
|
||||
|
||||
merged_frame_name = bcd.get_merged_frame_name()
|
||||
|
||||
merged_frame_image = bcd.get_image(merged_frame_name)
|
||||
|
||||
if merged_frame_name is not None and merged_frame_image is not None:
|
||||
self._layered_images.add_image(merged_frame_image)
|
||||
|
||||
h,w = merged_frame_image.shape[0:2]
|
||||
|
||||
if merged_frame_name is not None:
|
||||
self._info_label.setText(f'{merged_frame_name} {w}x{h}')
|
||||
else:
|
||||
self._info_label.setText(f'{w}x{h}')
|
||||
|
||||
|
||||
def clear(self):
|
||||
self._layered_images.clear_images()
|
59
apps/DeepFaceLive/ui/widgets/QBCFrameViewer.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ... import backend
|
||||
|
||||
|
||||
class QBCFrameViewer(lib_qt.QXCollapsibleSection):
|
||||
def __init__(self, backed_weak_heap : backend.BackendWeakHeap,
|
||||
bc : backend.BackendConnection,
|
||||
preview_width=256):
|
||||
self._timer = lib_qt.QXTimer(interval=8, timeout=self._on_timer_8ms, start=True)
|
||||
|
||||
self._backed_weak_heap = backed_weak_heap
|
||||
self._bc = bc
|
||||
self._bcd_id = None
|
||||
|
||||
layered_images = self._layered_images = lib_qt.QXFixedLayeredImages(preview_width, preview_width)
|
||||
info_label = self._info_label = lib_qt.QXLabel( font=QXFontDB.get_fixedwidth_font(size=7))
|
||||
|
||||
main_l = lib_qt.QXVBoxLayout([ (layered_images, Qt.AlignmentFlag.AlignCenter),
|
||||
(info_label, Qt.AlignmentFlag.AlignCenter),
|
||||
])
|
||||
super().__init__(title=L('@QBCFrameViewer.title'), content_layout=main_l)
|
||||
|
||||
def _on_timer_8ms(self):
|
||||
top_qx = self.get_top_QXWindow()
|
||||
if not self.is_opened() or (top_qx is not None and top_qx.is_minimized() ):
|
||||
return
|
||||
|
||||
bcd_id = self._bc.get_write_id()
|
||||
if self._bcd_id != bcd_id:
|
||||
# Has new bcd version
|
||||
bcd, self._bcd_id = self._bc.get_by_id(bcd_id), bcd_id
|
||||
|
||||
if bcd is not None:
|
||||
bcd.assign_weak_heap(self._backed_weak_heap)
|
||||
|
||||
self._layered_images.clear_images()
|
||||
|
||||
frame_name = bcd.get_frame_name()
|
||||
frame_image = bcd.get_image(frame_name)
|
||||
|
||||
if frame_image is not None:
|
||||
self._layered_images.add_image(frame_image)
|
||||
|
||||
h,w = frame_image.shape[0:2]
|
||||
|
||||
if frame_name is not None:
|
||||
self._info_label.setText(f'{frame_name} {w}x{h}')
|
||||
else:
|
||||
self._info_label.setText(f'{w}x{h}')
|
||||
|
||||
|
||||
def clear(self):
|
||||
self._layered_images.clear_images()
|
104
apps/DeepFaceLive/ui/widgets/QBackendPanel.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from resources.gfx import QXImageDB, QXImageSequenceDB
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
from ...backend import BackendHost
|
||||
|
||||
|
||||
class QBackendPanel(lib_qt.QXWidget):
|
||||
"""
|
||||
Base panel for CSW backend
|
||||
"""
|
||||
def __init__(self, backend : BackendHost, name : str, layout, content_align_top=False):
|
||||
if not isinstance(backend, BackendHost):
|
||||
raise ValueError('backend must be an instance of BackendHost')
|
||||
|
||||
super().__init__()
|
||||
self._backend = backend
|
||||
self._name = name
|
||||
|
||||
backend.call_on_state_change(self._on_backend_state_change)
|
||||
backend.call_on_profile_timing(self._on_backend_profile_timing)
|
||||
|
||||
btn_on_off = self._btn_on_off = lib_qt.QXPushButton(tooltip_text=L('@QBackendPanel.start'), fixed_width=20, released=self._on_btn_on_off_released)
|
||||
|
||||
btn_reset_state = self._btn_reset_state = lib_qt.QXPushButton(image=QXImageDB.settings_reset_outline('gray'),
|
||||
#size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum),
|
||||
fixed_width=20, released=self._on_btn_reset_state_released, tooltip_text=L('@QBackendPanel.reset_settings') )
|
||||
|
||||
timing_label = self._timing_label = lib_qt.QXLabel()
|
||||
|
||||
bar_widget = self._bar_widget = \
|
||||
lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([
|
||||
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([
|
||||
btn_on_off,
|
||||
1,
|
||||
btn_reset_state,
|
||||
2,
|
||||
lib_qt.QXLabel(name, font=QXFontDB.get_default_font(10)),
|
||||
|
||||
(timing_label, Qt.AlignmentFlag.AlignRight),
|
||||
2
|
||||
]), fixed_height=24),
|
||||
]),
|
||||
size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) )
|
||||
|
||||
content_widget = self._content_widget = lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([layout], contents_margins=2),
|
||||
enabled=False )
|
||||
|
||||
l_widgets = [bar_widget, 1]
|
||||
|
||||
if not content_align_top:
|
||||
l_widgets += [ lib_qt.QXFrame(size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) ) ]
|
||||
|
||||
l_widgets += [content_widget]
|
||||
l_widgets += [ lib_qt.QXFrame(size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) ) ]
|
||||
|
||||
self.setLayout(lib_qt.QXVBoxLayout(l_widgets))
|
||||
|
||||
lib_qt.disable([self._content_widget])
|
||||
btn_on_off.set_image( QXImageDB.power_outline('red') )
|
||||
|
||||
def _on_backend_state_change(self, backend, started, starting, stopping, stopped, busy):
|
||||
btn_on_off = self._btn_on_off
|
||||
|
||||
if started or starting or stopping:
|
||||
btn_on_off.setToolTip(L('@QBackendPanel.stop'))
|
||||
if stopped:
|
||||
btn_on_off.setToolTip(L('@QBackendPanel.start'))
|
||||
|
||||
if busy or starting or stopping:
|
||||
btn_on_off.set_image_sequence(QXImageSequenceDB.icon_loading('yellow'), loop_count=0)
|
||||
elif started:
|
||||
btn_on_off.set_image( QXImageDB.power_outline('lime') )
|
||||
elif stopped:
|
||||
btn_on_off.set_image( QXImageDB.power_outline('red') )
|
||||
|
||||
if started and not busy:
|
||||
lib_qt.show_and_enable([self._content_widget, self._timing_label])
|
||||
self._timing_label.setText(None)
|
||||
else:
|
||||
lib_qt.hide_and_disable([self._content_widget, self._timing_label])
|
||||
self._timing_label.setText(None)
|
||||
|
||||
def _on_backend_profile_timing(self, timing : float):
|
||||
fps = int(1.0 / timing if timing != 0 else 0)
|
||||
if fps < 10:
|
||||
self._timing_label.set_color('red')
|
||||
else:
|
||||
self._timing_label.set_color(None)
|
||||
self._timing_label.setText(f"{fps} {L('@QBackendPanel.FPS')}")
|
||||
|
||||
def _on_btn_on_off_released(self):
|
||||
backend = self._backend
|
||||
if backend.is_stopped():
|
||||
backend.start()
|
||||
else:
|
||||
backend.stop()
|
||||
|
||||
def _on_btn_reset_state_released(self):
|
||||
self._backend.reset_state()
|
|
@ -0,0 +1,67 @@
|
|||
from typing import Union
|
||||
|
||||
from localization import Localization
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QButtonCSWDynamicSingleSwitch(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.DynamicSingleSwitch control with radiobuttons or checkboxes
|
||||
"""
|
||||
def __init__(self, csw_switch : lib_csw.DynamicSingleSwitch.Client,
|
||||
horizontal : bool, radio_buttons : bool):
|
||||
|
||||
if not isinstance(csw_switch, lib_csw.DynamicSingleSwitch.Client):
|
||||
raise ValueError('csw_switch must be an instance of DynamicSingleSwitch.Client')
|
||||
|
||||
super().__init__(csw_control=csw_switch)
|
||||
|
||||
self._csw_switch = csw_switch
|
||||
self._is_radio_buttons = radio_buttons
|
||||
|
||||
csw_switch.call_on_selected(self._on_csw_switch_selected)
|
||||
csw_switch.call_on_choices(self._on_csw_choices)
|
||||
|
||||
self._btns = []
|
||||
|
||||
main_l = self._main_l = lib_qt.QXHBoxLayout() if horizontal else lib_qt.QXVBoxLayout()
|
||||
|
||||
self.setLayout(lib_qt.QXHBoxLayout([ lib_qt.QXFrame(layout=main_l, size_policy=(QSizePolicy.Policy.Fixed,QSizePolicy.Policy.Fixed) ), ]) )
|
||||
self.hide()
|
||||
|
||||
def _on_csw_choices(self, choices, choices_names, none_choice_name : Union[str,None]):
|
||||
for btn in self._btns:
|
||||
self._main_l.removeWidget(btn)
|
||||
btn.deleteLater()
|
||||
|
||||
btns = self._btns = []
|
||||
|
||||
for idx, choice in enumerate(choices_names):
|
||||
choice = Localization.localize(choice)
|
||||
if self._is_radio_buttons:
|
||||
btn = lib_qt.QXRadioButton(text=choice, toggled=(lambda checked, idx=idx: self.on_btns_toggled(idx, checked)) )
|
||||
else:
|
||||
btn = lib_qt.QXCheckBox(text=choice, toggled=(lambda checked, idx=idx: self.on_btns_toggled(idx, checked)) )
|
||||
btns.append(btn)
|
||||
self._main_l.addWidget(btn, alignment=Qt.AlignmentFlag.AlignLeft)
|
||||
|
||||
def on_btns_toggled(self, idx, checked):
|
||||
if checked:
|
||||
if not self._csw_switch.select(idx):
|
||||
with lib_qt.BlockSignals(self._btns[idx]):
|
||||
self._btns[idx].setChecked(False)
|
||||
else:
|
||||
if not self._csw_switch.unselect():
|
||||
with lib_qt.BlockSignals(self._btns[idx]):
|
||||
self._btns[idx].setChecked(True)
|
||||
|
||||
def _on_csw_switch_selected(self, idx, choice):
|
||||
with lib_qt.BlockSignals(self._btns):
|
||||
for i,btn in enumerate(self._btns):
|
||||
btn.setChecked(i==idx)
|
55
apps/DeepFaceLive/ui/widgets/QCSWControl.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
from collections import Iterable
|
||||
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
|
||||
class QCSWControl(lib_qt.QXWidget):
|
||||
"""
|
||||
base qt widget class for CSWControl
|
||||
"""
|
||||
|
||||
def __init__(self, csw_control : lib_csw.Control, reflect_state_widgets=None):
|
||||
super().__init__()
|
||||
self._csw_control = csw_control
|
||||
|
||||
self._csw_state_widgets = []
|
||||
|
||||
csw_control.call_on_change_state(self._on_csw_state_change)
|
||||
|
||||
if reflect_state_widgets is not None:
|
||||
self.reflect_state_to_widget(reflect_state_widgets)
|
||||
|
||||
def _on_csw_state_change(self, state):
|
||||
"""overridable"""
|
||||
self._state_to_widget( self._csw_state_widgets+[self] )
|
||||
|
||||
def reflect_state_to_widget(self, widget_or_list):
|
||||
"""reflect CSW Control state to widgets"""
|
||||
if isinstance(widget_or_list, Iterable):
|
||||
widget_or_list = list(widget_or_list)
|
||||
else:
|
||||
widget_or_list = [widget_or_list]
|
||||
|
||||
self._csw_state_widgets += widget_or_list
|
||||
self._state_to_widget(widget_or_list)
|
||||
|
||||
def _state_to_widget(self, widgets):
|
||||
state = self._csw_control.get_state()
|
||||
if state == lib_csw.Control.State.ENABLED:
|
||||
for widget in widgets:
|
||||
widget.show()
|
||||
widget.setEnabled(True)
|
||||
elif state == lib_csw.Control.State.FREEZED:
|
||||
for widget in widgets:
|
||||
widget.setEnabled(False)
|
||||
widget.show()
|
||||
elif state == lib_csw.Control.State.DISABLED:
|
||||
for widget in widgets:
|
||||
widget.setEnabled(False)
|
||||
widget.hide()
|
||||
|
||||
|
34
apps/DeepFaceLive/ui/widgets/QCheckBoxCSWFlag.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QCheckBoxCSWFlag(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.Flag control as CheckBox
|
||||
"""
|
||||
def __init__(self, csw_flag : lib_csw.Flag.Client, reflect_state_widgets=None):
|
||||
if not isinstance(csw_flag, lib_csw.Flag.Client):
|
||||
raise ValueError('csw_flag must be an instance of Flag.Client')
|
||||
super().__init__(csw_control=csw_flag, reflect_state_widgets=reflect_state_widgets)
|
||||
self._csw_flag = csw_flag
|
||||
|
||||
csw_flag.call_on_flag(self.on_csw_flag)
|
||||
# Init UI
|
||||
chbox = self._chbox = lib_qt.QXCheckBox(clicked=self.on_chbox_clicked)
|
||||
|
||||
main_l = lib_qt.QXHBoxLayout([chbox])
|
||||
|
||||
self.setLayout(main_l)
|
||||
self.hide()
|
||||
|
||||
def on_csw_flag(self, flag):
|
||||
with lib_qt.BlockSignals(self._chbox):
|
||||
self._chbox.setChecked(flag)
|
||||
|
||||
def on_chbox_clicked(self):
|
||||
self._csw_flag.set_flag(self._chbox.isChecked())
|
|
@ -0,0 +1,71 @@
|
|||
from typing import Union
|
||||
|
||||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QComboBoxCSWDynamicSingleSwitch(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.DynamicSingleSwitch control with QComboBox
|
||||
"""
|
||||
def __init__(self, csw_switch : lib_csw.DynamicSingleSwitch.Client, reflect_state_widgets=None):
|
||||
if not isinstance(csw_switch, lib_csw.DynamicSingleSwitch.Client):
|
||||
raise ValueError('csw_switch must be an instance of DynamicSingleSwitch.Client')
|
||||
|
||||
super().__init__(csw_control=csw_switch, reflect_state_widgets=reflect_state_widgets)
|
||||
|
||||
self._csw_switch = csw_switch
|
||||
self._has_none_choice = True
|
||||
|
||||
csw_switch.call_on_selected(self._on_csw_switch_selected)
|
||||
csw_switch.call_on_choices(self._on_csw_choices)
|
||||
# Init UI
|
||||
self._combobox = None
|
||||
|
||||
main_l = self._main_l = lib_qt.QXHBoxLayout()
|
||||
self.setLayout(main_l)
|
||||
self.hide()
|
||||
|
||||
def _on_csw_choices(self, choices, choices_names, none_choice_name : Union[str,None]):
|
||||
if self._combobox is not None:
|
||||
self._main_l.removeWidget(self._combobox)
|
||||
self._combobox.deleteLater()
|
||||
self._choices_names = choices_names
|
||||
self._has_none_choice = none_choice_name is not None
|
||||
|
||||
combobox = self._combobox = lib_qt.QXComboBox(font=QXFontDB.get_fixedwidth_font(), on_index_changed=self.on_combobox_index_changed)
|
||||
with lib_qt.BlockSignals(self._combobox):
|
||||
if none_choice_name is not None:
|
||||
combobox.addItem( QIcon(), L(none_choice_name) )
|
||||
|
||||
for choice_name in choices_names:
|
||||
combobox.addItem( QIcon(), L(choice_name) )
|
||||
|
||||
self._main_l.addWidget(combobox)
|
||||
|
||||
def on_combobox_index_changed(self, idx):
|
||||
if self._has_none_choice and idx == 0:
|
||||
self._csw_switch.unselect()
|
||||
else:
|
||||
if self._has_none_choice:
|
||||
idx -= 1
|
||||
|
||||
if not self._csw_switch.select(idx):
|
||||
raise Exception('error on select')
|
||||
|
||||
def _on_csw_switch_selected(self, idx, choice):
|
||||
with lib_qt.BlockSignals(self._combobox):
|
||||
if idx is None:
|
||||
self._combobox.setCurrentIndex(0)
|
||||
else:
|
||||
if self._has_none_choice:
|
||||
idx += 1
|
||||
|
||||
self._combobox.setCurrentIndex(idx)
|
47
apps/DeepFaceLive/ui/widgets/QErrorCSWError.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from typing import Union
|
||||
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from resources.gfx import QXImageDB
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QErrorCSWError(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.Error control as widget
|
||||
"""
|
||||
def __init__(self, csw_error : lib_csw.Error.Client):
|
||||
if not isinstance(csw_error, lib_csw.Error.Client):
|
||||
raise ValueError('csw_error must be an instance of Error.Client')
|
||||
super().__init__(csw_control=csw_error)
|
||||
self._csw_error = csw_error
|
||||
csw_error.call_on_error(self._on_csw_error)
|
||||
|
||||
label_warning = self._label_warning = lib_qt.QXLabel(image=QXImageDB.warning_outline('red'),
|
||||
scaled_contents=True,
|
||||
size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed),
|
||||
fixed_size=(32,32),
|
||||
)
|
||||
|
||||
label = self._label = lib_qt.QXLabel(font=QXFontDB.get_default_font(size=7), word_wrap=True)
|
||||
|
||||
bar = lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout(
|
||||
[ lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([label_warning]), size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)) ,
|
||||
lib_qt.QXWidget(layout=lib_qt.QXHBoxLayout([label]))
|
||||
], spacing=0) )
|
||||
|
||||
self.setLayout(lib_qt.QXHBoxLayout([bar]))
|
||||
self.hide()
|
||||
|
||||
def _on_csw_state_change(self, state):
|
||||
super()._on_csw_state_change(state)
|
||||
if state == lib_csw.Control.State.DISABLED:
|
||||
self._label.setText(None)
|
||||
|
||||
def _on_csw_error(self, text: Union[str,None]):
|
||||
self._label.setText(text)
|
44
apps/DeepFaceLive/ui/widgets/QLabelCSWNumber.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QLabelCSWNumber(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.Number control as QXLabel read-only
|
||||
"""
|
||||
def __init__(self, csw_number : lib_csw.Number.Client, reflect_state_widgets=None):
|
||||
|
||||
if not isinstance(csw_number, lib_csw.Number.Client):
|
||||
raise ValueError('csw_number must be an instance of Number.Client')
|
||||
super().__init__(csw_control=csw_number, reflect_state_widgets=reflect_state_widgets)
|
||||
|
||||
self._csw_number = csw_number
|
||||
self._decimals = 0
|
||||
|
||||
csw_number.call_on_number(self._on_csw_number)
|
||||
csw_number.call_on_config(self._on_csw_config)
|
||||
|
||||
label = self._label = lib_qt.QXLabel( font=QXFontDB.Digital7_Mono(11, italic=True) )
|
||||
main_l = lib_qt.QXHBoxLayout([label])
|
||||
|
||||
self.setLayout(main_l)
|
||||
self.hide()
|
||||
|
||||
def _on_csw_number(self, number):
|
||||
f = (10**self._decimals)
|
||||
number = int(number * f) / f
|
||||
self._label.setText(str(number))
|
||||
|
||||
def _on_csw_config(self, cfg : lib_csw.Number.Config):
|
||||
if not cfg.read_only:
|
||||
raise Exception('QLabelCSWNumber supports only read-only Number')
|
||||
|
||||
if cfg.decimals is not None:
|
||||
self._decimals = cfg.decimals
|
||||
|
101
apps/DeepFaceLive/ui/widgets/QLabelPopupInfo.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
from typing import Union
|
||||
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from resources.gfx import QXImageDB
|
||||
from xlib import qt as lib_qt
|
||||
|
||||
|
||||
class QLabelPopupInfo(lib_qt.QXWidget):
|
||||
"""
|
||||
text label with optional popup info on click
|
||||
"""
|
||||
def __init__(self, label : str = None, popup_info_text = None):
|
||||
super().__init__()
|
||||
|
||||
self._has_info_text = False
|
||||
|
||||
self._label = lib_qt.QXLabel(text='')
|
||||
self._label.hide()
|
||||
|
||||
wnd = self._popup_wnd = lib_qt.QXWindow()
|
||||
wnd.setParent(self)
|
||||
wnd.setWindowFlags(Qt.WindowType.Popup)
|
||||
|
||||
info_btn = self._info_btn = lib_qt.QXPushButton(image=QXImageDB.information_circle_outline('light gray'), fixed_size=(24,22), released=self._on_info_btn_released)
|
||||
info_btn.hide()
|
||||
|
||||
wnd_text_label = self._popup_wnd_text_label = lib_qt.QXLabel(text='', font=QXFontDB.get_default_font() )
|
||||
|
||||
wnd_layout = lib_qt.QXHBoxLayout([
|
||||
lib_qt.QXFrame(
|
||||
bg_color= Qt.GlobalColor.black,
|
||||
layout=lib_qt.QXHBoxLayout([
|
||||
|
||||
lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([lib_qt.QXLabel(image=QXImageDB.information_circle_outline('yellow'), scaled_contents=True, fixed_size=(24,24)),
|
||||
wnd_text_label
|
||||
], contents_margins=2, spacing=2)),
|
||||
], contents_margins=2, spacing=2), size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) )
|
||||
], contents_margins=0)
|
||||
|
||||
wnd.setLayout(wnd_layout)
|
||||
|
||||
self.setLayout(lib_qt.QXHBoxLayout([self._label, info_btn]))
|
||||
|
||||
self.set_label( label )
|
||||
self.set_popup_info( popup_info_text )
|
||||
|
||||
def set_info_icon(self):
|
||||
self._label.hide()
|
||||
self._info_btn.show()
|
||||
|
||||
def set_label(self, label : Union[str, None]):
|
||||
self._info_btn.hide()
|
||||
self._label.setText(label)
|
||||
self._label.show()
|
||||
|
||||
def set_popup_info(self, text : Union[str, None]):
|
||||
if text is not None:
|
||||
self._has_info_text = True
|
||||
self._popup_wnd_text_label.setText(text)
|
||||
else:
|
||||
self._has_info_text = False
|
||||
|
||||
def enterEvent(self, ev):
|
||||
super().enterEvent(ev)
|
||||
if self.isEnabled() and self._has_info_text:
|
||||
self._label.set_color('yellow')
|
||||
|
||||
def leaveEvent(self, ev):
|
||||
super().leaveEvent(ev)
|
||||
if self.isEnabled() and self._has_info_text:
|
||||
self._label.set_color(None)
|
||||
|
||||
def mousePressEvent(self, ev):
|
||||
super().mousePressEvent(ev)
|
||||
self._show_popup_wnd()
|
||||
|
||||
def _on_info_btn_released(self):
|
||||
self._show_popup_wnd()
|
||||
|
||||
def _show_popup_wnd(self):
|
||||
|
||||
if self._has_info_text:
|
||||
popup_wnd = self._popup_wnd
|
||||
popup_wnd.show()
|
||||
|
||||
label_widget = self._label
|
||||
if label_widget.isHidden():
|
||||
label_widget = self._info_btn
|
||||
|
||||
screen_size = lib_qt.QXMainApplication.get_singleton().primaryScreen().size()
|
||||
label_size = label_widget.size()
|
||||
global_pt = label_widget.mapToGlobal( QPoint(0, label_size.height()))
|
||||
popup_wnd_size = popup_wnd.size()
|
||||
global_pt = QPoint( min(global_pt.x(), screen_size.width() - popup_wnd_size.width()),
|
||||
min(global_pt.y(), screen_size.height() - popup_wnd_size.height()) )
|
||||
|
||||
popup_wnd.move(global_pt)
|
||||
|
45
apps/DeepFaceLive/ui/widgets/QLabelPopupInfoCSWInfoLabel.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
from .QLabelPopupInfo import QLabelPopupInfo
|
||||
|
||||
|
||||
class QLabelPopupInfoCSWInfoLabel(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.InfoLabel control as QLabelPopupInfo
|
||||
"""
|
||||
def __init__(self, csw_info_label : lib_csw.InfoLabel.Client):
|
||||
if not isinstance(csw_info_label, lib_csw.InfoLabel.Client):
|
||||
raise ValueError('csw_error must be an instance of InfoLabel.Client')
|
||||
|
||||
super().__init__(csw_control=csw_info_label)
|
||||
|
||||
self._csw_info_label = csw_info_label
|
||||
csw_info_label.call_on_config(self._on_csw_config)
|
||||
|
||||
label_popup_info = self._label_popup_info = QLabelPopupInfo()
|
||||
|
||||
self.setLayout(lib_qt.QXHBoxLayout([label_popup_info]))
|
||||
self.hide()
|
||||
|
||||
def _on_csw_state_change(self, state):
|
||||
super()._on_csw_state_change(state)
|
||||
if state == lib_csw.Control.State.DISABLED:
|
||||
self._label_popup_info.set_label(None)
|
||||
self._label_popup_info.set_popup_info(None)
|
||||
|
||||
def _on_csw_config(self, cfg : lib_csw.InfoLabel.Config ):
|
||||
if cfg.info_icon:
|
||||
self._label_popup_info.set_info_icon()
|
||||
else:
|
||||
self._label_popup_info.set_label(L(cfg.label))
|
||||
|
||||
self._label_popup_info.set_popup_info( '\n'.join([L(line) for line in cfg.info_lines])
|
||||
if cfg.info_lines is not None else None)
|
||||
|
||||
|
131
apps/DeepFaceLive/ui/widgets/QPathEditCSWPaths.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
from pathlib import Path
|
||||
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from resources.gfx import QXImageDB
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QPathEditCSWPaths(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.Paths control as LineEdit with Button to manage the Path
|
||||
"""
|
||||
def __init__(self, csw_path : lib_csw.Paths.Client, reflect_state_widgets=None):
|
||||
if not isinstance(csw_path, lib_csw.Paths.Client):
|
||||
raise ValueError('csw_path must be an instance of Paths.Client')
|
||||
super().__init__(csw_control=csw_path, reflect_state_widgets=reflect_state_widgets)
|
||||
self._csw_path = csw_path
|
||||
self._dlg = None
|
||||
|
||||
csw_path.call_on_config(self.on_csw_config)
|
||||
csw_path.call_on_paths(self._on_csw_paths)
|
||||
|
||||
lineedit = self._lineedit = lib_qt.QXLineEdit(font=QXFontDB.get_fixedwidth_font(),
|
||||
placeholder_text='...',
|
||||
size_policy=(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed),
|
||||
editingFinished=self.on_lineedit_editingFinished)
|
||||
|
||||
btn_open = self._btn_open = lib_qt.QXPushButton(image=QXImageDB.folder_open_outline(color='light gray'),
|
||||
tooltip_text='Open',
|
||||
released=self._on_btn_open_released,
|
||||
fixed_size=(24,22) )
|
||||
|
||||
btn_reveal = self._btn_reveal = lib_qt.QXPushButton(image=QXImageDB.eye_outline(color='light gray'),
|
||||
tooltip_text='Reveal in explorer',
|
||||
released=self._on_btn_reveal_released,
|
||||
fixed_size=(24,22) )
|
||||
|
||||
btn_erase = self._btn_erase = lib_qt.QXPushButton(image=QXImageDB.close_outline(color='light gray'),
|
||||
tooltip_text='Erase',
|
||||
released=self._on_btn_erase_released,
|
||||
fixed_size=(24,22) )
|
||||
|
||||
main_l = lib_qt.QXHBoxLayout([lineedit, 2, btn_open, btn_reveal,btn_erase])
|
||||
|
||||
self.setLayout(lib_qt.QXHBoxLayout([
|
||||
lib_qt.QXFrame(layout=main_l, size_policy=(QSizePolicy.Policy.Expanding,QSizePolicy.Policy.Fixed) )
|
||||
]) )
|
||||
self.hide()
|
||||
|
||||
def on_csw_config(self, cfg : lib_csw.Paths.Config):
|
||||
type = cfg.get_type()
|
||||
caption = cfg.get_caption()
|
||||
if caption is None:
|
||||
caption = ""
|
||||
suffixes = cfg.get_suffixes()
|
||||
is_save = cfg.is_save()
|
||||
|
||||
file_filter = None
|
||||
if suffixes is not None:
|
||||
file_filter = f"{caption} ({' '.join(['*'+suf for suf in suffixes])})"
|
||||
|
||||
if type == lib_csw.Paths.Config.Type.ANY_FILE:
|
||||
self._dlg = lib_qt.QXFileDialog(self,
|
||||
filter=file_filter,
|
||||
existing_only=False,
|
||||
is_save=is_save,
|
||||
accepted=self._on_dlg_accepted)
|
||||
elif type == lib_csw.Paths.Config.Type.EXISTING_FILE:
|
||||
self._dlg = lib_qt.QXFileDialog(self,
|
||||
filter=file_filter,
|
||||
existing_only=True,
|
||||
is_save=is_save,
|
||||
accepted=self._on_dlg_accepted)
|
||||
|
||||
elif type == lib_csw.Paths.Config.Type.EXISTING_FILES:
|
||||
self._dlg = lib_qt.QXFileDialog(self,
|
||||
multi_files=True,
|
||||
existing_only=True,
|
||||
filter=file_filter,
|
||||
accepted=self._on_dlg_accepted)
|
||||
elif type == lib_csw.Paths.Config.Type.DIRECTORY:
|
||||
directory = cfg.get_directory_path()
|
||||
if directory is not None:
|
||||
directory = str(directory)
|
||||
self._dlg = lib_qt.QXDirDialog(self, caption=caption, directory=directory, accepted=self._on_dlg_accepted)
|
||||
|
||||
def _on_dlg_accepted(self):
|
||||
self._lineedit.setText( self._dlg.selectedFiles()[0] )
|
||||
self._lineedit.editingFinished.emit()
|
||||
|
||||
def _on_csw_paths(self, paths, prev_paths):
|
||||
if len(paths) == 0:
|
||||
text = None
|
||||
elif len(paths) == 1:
|
||||
text = str(paths[0])
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
with lib_qt.BlockSignals(self._lineedit):
|
||||
self._lineedit.setText(text)
|
||||
|
||||
def _on_btn_erase_released(self):
|
||||
self._csw_path.set_paths(None)
|
||||
|
||||
|
||||
def _on_btn_open_released(self):
|
||||
if self._dlg is not None:
|
||||
self._dlg.open()
|
||||
else:
|
||||
print('lib_csw.Paths.Config was not initialized.')
|
||||
|
||||
|
||||
def _on_btn_reveal_released(self):
|
||||
dirpath = self._lineedit.text()
|
||||
if dirpath is not None and len(dirpath) != 0:
|
||||
dirpath = Path(dirpath)
|
||||
if dirpath.is_file():
|
||||
dirpath = dirpath.parent
|
||||
|
||||
QDesktopServices.openUrl( QUrl.fromLocalFile(str(dirpath)) )
|
||||
|
||||
def on_lineedit_editingFinished(self):
|
||||
text = self._lineedit.text()
|
||||
if len(text) == 0:
|
||||
text = None
|
||||
self._csw_path.set_paths( text )
|
47
apps/DeepFaceLive/ui/widgets/QProgressBarCSWProgress.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QProgressBarCSWProgress(QCSWControl):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, csw_progress : lib_csw.Progress.Client):
|
||||
|
||||
if not isinstance(csw_progress, lib_csw.Progress.Client):
|
||||
raise ValueError('csw_progress must be an instance of Progress.Client')
|
||||
super().__init__(csw_control=csw_progress)
|
||||
|
||||
self._csw_progress = csw_progress
|
||||
|
||||
csw_progress.call_on_progress(self._on_csw_progress)
|
||||
csw_progress.call_on_config(self._on_csw_config)
|
||||
|
||||
label_title = self._label_title = lib_qt.QXLabel('', word_wrap=True, hided=True)
|
||||
progressbar = self._progressbar = lib_qt.QXProgressBar( min=0, max=100, font=QXFontDB.Digital7_Mono(11, italic=True) ) # ,, step=1, decimals=0, readonly=self._read_only, valueChanged=self._on_spinbox_valueChanged, editingFinished=self._on_spinbox_editingFinished)
|
||||
|
||||
self.setLayout( lib_qt.QXVBoxLayout([label_title, progressbar]) )
|
||||
self.hide()
|
||||
|
||||
def _on_csw_progress(self, progress):
|
||||
with lib_qt.BlockSignals(self._progressbar):
|
||||
self._progressbar.setValue(progress)
|
||||
|
||||
|
||||
def _on_csw_config(self, config : lib_csw.Progress.Config):
|
||||
title = config.get_title()
|
||||
if title is not None:
|
||||
|
||||
if len(title) != 0 and title[0] == '@':
|
||||
title = L(title[1:])
|
||||
|
||||
self._label_title.setText(title)
|
||||
self._label_title.show()
|
||||
else:
|
||||
self._label_title.hide()
|
75
apps/DeepFaceLive/ui/widgets/QSliderCSWNumber.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QSliderCSWNumber(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.Number control by Slider
|
||||
"""
|
||||
def __init__(self, csw_number: lib_csw.Number.Client, reflect_state_widgets=None):
|
||||
|
||||
if not isinstance(csw_number, lib_csw.Number.Client):
|
||||
raise ValueError('csw_number must be an instance of Number.Client')
|
||||
|
||||
super().__init__(csw_control=csw_number, reflect_state_widgets=reflect_state_widgets)
|
||||
|
||||
self._csw_number = csw_number
|
||||
|
||||
csw_number.call_on_number(self._on_csw_number)
|
||||
csw_number.call_on_config(self._on_csw_config)
|
||||
|
||||
slider = self._slider = lib_qt.QXSlider(orientation=Qt.Orientation.Horizontal,
|
||||
min=0,
|
||||
max=0,
|
||||
tick_position=QSlider.TickPosition.NoTicks,
|
||||
tick_interval=1,
|
||||
sliderReleased=self._on_slider_sliderReleased,
|
||||
valueChanged=self._on_slider_valueChanged)
|
||||
|
||||
self.setLayout(lib_qt.QXVBoxLayout([slider]))
|
||||
self.hide()
|
||||
|
||||
def _on_csw_config(self, config : lib_csw.Number.Config):
|
||||
self._config = config
|
||||
|
||||
if config.min is not None and config.max is not None:
|
||||
min = config.min
|
||||
max = config.max
|
||||
step = config.step
|
||||
|
||||
int_min = 0
|
||||
int_max = int( (max-min) / step )
|
||||
|
||||
self._slider.setMinimum(int_min)
|
||||
self._slider.setMaximum(int_max)
|
||||
self._slider.setPageStep(1)
|
||||
|
||||
self._slider.setEnabled(not config.read_only)
|
||||
self._instant_update = config.allow_instant_update
|
||||
|
||||
def _on_csw_number(self, value):
|
||||
if value is not None:
|
||||
config = self._config
|
||||
value = (value-config.min) / config.step
|
||||
with lib_qt.BlockSignals([self._slider]):
|
||||
self._slider.setValue(value)
|
||||
|
||||
def _set_csw_value(self):
|
||||
config = self._config
|
||||
value = self._slider.value()
|
||||
value = value * config.step + config.min
|
||||
self._csw_number.set_number(value)
|
||||
|
||||
def _on_slider_sliderReleased(self):
|
||||
if not self._config.allow_instant_update:
|
||||
self._set_csw_value()
|
||||
|
||||
def _on_slider_valueChanged(self):
|
||||
if not self._slider.isSliderDown() or self._config.allow_instant_update:
|
||||
self._set_csw_value()
|
||||
|
74
apps/DeepFaceLive/ui/widgets/QSliderCSWNumbers.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QSliderCSWNumbers(QCSWControl):
|
||||
"""
|
||||
Implements controlable Slider with lib_csw.Number controls
|
||||
"""
|
||||
def __init__(self, csw_idx : lib_csw.Number.Client,
|
||||
csw_idx_count : lib_csw.Number.Client,
|
||||
):
|
||||
|
||||
if not isinstance(csw_idx, lib_csw.Number.Client):
|
||||
raise ValueError('csw_idx must be an instance of Number.Client')
|
||||
if not isinstance(csw_idx_count, lib_csw.Number.Client):
|
||||
raise ValueError('csw_idx_count must be an instance of Number.Client')
|
||||
|
||||
super().__init__(csw_control=csw_idx)
|
||||
|
||||
self._csw_idx = csw_idx
|
||||
self._csw_idx_count = csw_idx_count
|
||||
|
||||
csw_idx.call_on_number(self._on_csw_idx)
|
||||
csw_idx_count.call_on_number(self._on_csw_idx_count)
|
||||
# Init UI
|
||||
slider = self._slider = lib_qt.QXSlider(orientation=Qt.Orientation.Horizontal,
|
||||
min=0,
|
||||
max=0,
|
||||
tick_position=QSlider.TickPosition.NoTicks,
|
||||
tick_interval=1,
|
||||
valueChanged=self._on_slider_valueChanged)
|
||||
|
||||
spinbox_font = QXFontDB.Digital7_Mono(11, italic=True)
|
||||
spinbox_index = self._spinbox_index = lib_qt.QXSpinBox( font=spinbox_font, min=0, max=0, step=1, alignment=Qt.AlignmentFlag.AlignRight, button_symbols=QAbstractSpinBox.ButtonSymbols.NoButtons, editingFinished=self._on_spinbox_index_editingFinished)
|
||||
spinbox_count = self._spinbox_count = lib_qt.QXSpinBox( font=spinbox_font, min=0, max=0, step=1, alignment=Qt.AlignmentFlag.AlignRight, button_symbols=QAbstractSpinBox.ButtonSymbols.NoButtons, readonly=True)
|
||||
|
||||
main_l = lib_qt.QXVBoxLayout([
|
||||
slider,
|
||||
lib_qt.QXHBoxLayout([lib_qt.QXFrame(layout=lib_qt.QXHBoxLayout([spinbox_index, lib_qt.QXLabel(text='/', ), spinbox_count]),
|
||||
size_policy=(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) )])
|
||||
])
|
||||
|
||||
self.setLayout(main_l)
|
||||
self.hide()
|
||||
|
||||
def _on_csw_idx(self, idx):
|
||||
#print('_on_csw_idx', idx)
|
||||
if idx is not None:
|
||||
with lib_qt.BlockSignals([self._slider, self._spinbox_index]):
|
||||
self._slider.setValue(idx+1)
|
||||
self._spinbox_index.setValue(idx+1)
|
||||
|
||||
def _on_csw_idx_count(self, idx_count):
|
||||
#print('_on_csw_idx_count', idx_count)
|
||||
if idx_count is not None:
|
||||
with lib_qt.BlockSignals([self._slider, self._spinbox_index, self._spinbox_count]):
|
||||
self._slider.setMinimum(1)
|
||||
self._slider.setMaximum(idx_count)
|
||||
self._spinbox_index.setMinimum(1)
|
||||
self._spinbox_index.setMaximum(idx_count)
|
||||
self._spinbox_count.setMaximum(idx_count)
|
||||
self._spinbox_count.setValue(idx_count)
|
||||
|
||||
def _on_slider_valueChanged(self):
|
||||
self._csw_idx.set_number(self._slider.value()-1)
|
||||
|
||||
def _on_spinbox_index_editingFinished(self):
|
||||
self._csw_idx.set_number(self._spinbox_index.value()-1)
|
91
apps/DeepFaceLive/ui/widgets/QSpinBoxCSWNumber.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
from localization import L
|
||||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QSpinBoxCSWNumber(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.Number control as SpinBox
|
||||
"""
|
||||
def __init__(self, csw_number : lib_csw.Number.Client, reflect_state_widgets=None):
|
||||
|
||||
if not isinstance(csw_number, lib_csw.Number.Client):
|
||||
raise ValueError('csw_number must be an instance of Number.Client')
|
||||
super().__init__(csw_control=csw_number, reflect_state_widgets=reflect_state_widgets)
|
||||
|
||||
self._csw_number = csw_number
|
||||
self._instant_update = False
|
||||
self._zero_is_auto = False
|
||||
self._read_only = True
|
||||
|
||||
csw_number.call_on_number(self._on_csw_number)
|
||||
csw_number.call_on_config(self._on_csw_config)
|
||||
|
||||
spinbox = self._spinbox = lib_qt.QXDoubleSpinBox( font=QXFontDB.Digital7_Mono(11, italic=True), min=0, max=999999999, step=1, decimals=0, readonly=self._read_only, valueChanged=self._on_spinbox_valueChanged, editingFinished=self._on_spinbox_editingFinished)
|
||||
btn_auto = self._btn_auto = lib_qt.QXPushButton(text=L('@misc.auto'), hided=True, released=self._on_btn_auto_released, fixed_height=21)
|
||||
|
||||
main_l = lib_qt.QXHBoxLayout([spinbox, 1, btn_auto])
|
||||
|
||||
self.setLayout(main_l)
|
||||
self.hide()
|
||||
|
||||
def _on_csw_number(self, number):
|
||||
with lib_qt.BlockSignals(self._spinbox):
|
||||
self._spinbox.setValue(number)
|
||||
self._btn_auto_update()
|
||||
|
||||
def _btn_auto_update(self):
|
||||
if self._zero_is_auto and self._spinbox.value() != 0:
|
||||
self._btn_auto.show()
|
||||
else:
|
||||
self._btn_auto.hide()
|
||||
|
||||
def _on_csw_config(self, cfg : lib_csw.Number.Config):
|
||||
if cfg.min is not None:
|
||||
self._spinbox.setMinimum(cfg.min)
|
||||
if cfg.max is not None:
|
||||
self._spinbox.setMaximum(cfg.max)
|
||||
if cfg.step is not None:
|
||||
self._spinbox.setSingleStep(cfg.step)
|
||||
if cfg.decimals is not None:
|
||||
self._spinbox.setDecimals(cfg.decimals)
|
||||
|
||||
self._zero_is_auto = cfg.zero_is_auto
|
||||
if cfg.zero_is_auto:
|
||||
self._spinbox.setSpecialValueText(L('@misc.auto'))
|
||||
else:
|
||||
self._spinbox.setSpecialValueText('')
|
||||
self._read_only = cfg.read_only
|
||||
self._spinbox.setReadOnly(cfg.read_only)
|
||||
if cfg.read_only:
|
||||
self._spinbox.setButtonSymbols(QAbstractSpinBox.ButtonSymbols.NoButtons)
|
||||
else:
|
||||
self._spinbox.setButtonSymbols(QAbstractSpinBox.ButtonSymbols.UpDownArrows)
|
||||
|
||||
self._btn_auto_update()
|
||||
|
||||
self._instant_update = cfg.allow_instant_update
|
||||
|
||||
def _get_spinbox_value(self):
|
||||
val = self._spinbox.value()
|
||||
if self._spinbox.decimals() == 0:
|
||||
val = int(val)
|
||||
return val
|
||||
|
||||
def _on_btn_auto_released(self):
|
||||
self._csw_number.set_number(0)
|
||||
#self._btn_auto_update()
|
||||
|
||||
def _on_spinbox_valueChanged(self):
|
||||
if self._instant_update:
|
||||
self._csw_number.set_number(self._get_spinbox_value())
|
||||
|
||||
def _on_spinbox_editingFinished(self):
|
||||
if not self._instant_update:
|
||||
self._csw_number.set_number(self._get_spinbox_value())
|
32
apps/DeepFaceLive/ui/widgets/QXPushButtonCSWSignal.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from PyQt6.QtCore import *
|
||||
from PyQt6.QtGui import *
|
||||
from PyQt6.QtWidgets import *
|
||||
from xlib import qt as lib_qt
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QXPushButtonCSWSignal(QCSWControl):
|
||||
"""
|
||||
Implements lib_csw.Signal control as PushButton
|
||||
"""
|
||||
def __init__(self, csw_signal : lib_csw.Signal.Client, reflect_state_widgets=None,
|
||||
image=None,
|
||||
text=None, button_size=None, button_height=None,):
|
||||
|
||||
if not isinstance(csw_signal, lib_csw.Signal.Client):
|
||||
raise ValueError('csw_signal must be an instance of Signal.Client')
|
||||
super().__init__(csw_control=csw_signal, reflect_state_widgets=reflect_state_widgets)
|
||||
self._csw_signal = csw_signal
|
||||
|
||||
# Init UI
|
||||
btn = self._btn = lib_qt.QXPushButton(image=image, text=text, released=self.on_btn_released, minimum_size=button_size, minimum_height=button_height)
|
||||
|
||||
main_l = lib_qt.QXHBoxLayout([btn])
|
||||
|
||||
self.setLayout(main_l)
|
||||
self.hide()
|
||||
|
||||
def on_btn_released(self):
|
||||
self._csw_signal.signal()
|
BIN
build/samples/Asian woman.mp4
Normal file
BIN
build/samples/FaceBall.mp4
Normal file
BIN
build/samples/Obama.mp4
Normal file
BIN
build/samples/Singer.mp4
Normal file
BIN
build/samples/Twitch1.mp4
Normal file
BIN
build/samples/Twitch2.mp4
Normal file
BIN
build/samples/Twitch3.mp4
Normal file
BIN
build/samples/Twitch4.mp4
Normal file
BIN
build/samples/Van Damme.mp4
Normal file
BIN
build/samples/photos/000001.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
build/samples/photos/000002.jpg
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
build/samples/photos/000003.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
build/samples/photos/000004.jpg
Normal file
After Width: | Height: | Size: 742 KiB |
BIN
build/samples/photos/000005.jpg
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
build/samples/photos/000006.jpg
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
build/samples/photos/000007.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
build/samples/photos/000008.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
build/samples/photos/000009.jpg
Normal file
After Width: | Height: | Size: 482 KiB |
BIN
build/samples/photos/000010.jpg
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
build/samples/photos/000011.jpg
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
build/samples/photos/000012.jpg
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
build/samples/photos/000013.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
build/samples/photos/000014.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
build/samples/photos/000015.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
build/samples/photos/000016.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
build/samples/photos/000017.jpg
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
build/samples/photos/000018.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
build/samples/photos/000019.jpg
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
build/samples/photos/000020.jpg
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
build/samples/photos/000021.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
build/samples/photos/000022.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
build/samples/photos/000023.jpg
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
build/samples/photos/000024.jpg
Normal file
After Width: | Height: | Size: 157 KiB |
BIN
build/samples/photos/000025.jpg
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
build/samples/photos/000026.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
build/samples/photos/000027.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
build/samples/photos/000028.jpg
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
build/samples/photos/000029.jpg
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
build/samples/photos/000030.jpg
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
build/samples/photos/000031.jpg
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
build/samples/photos/000032.jpg
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
build/samples/photos/000033.jpg
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
build/samples/photos/000034.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
build/samples/photos/000035.jpg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
build/samples/photos/000036.jpg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
build/samples/photos/000037.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
build/samples/photos/000038.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
build/samples/photos/000039.jpg
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
build/samples/photos/000040.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
build/samples/photos/000041.jpg
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
build/samples/photos/000042.jpg
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
build/samples/photos/000043.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
build/samples/photos/000044.jpg
Normal file
After Width: | Height: | Size: 618 KiB |
BIN
build/samples/photos/000045.jpg
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
build/samples/photos/000046.jpg
Normal file
After Width: | Height: | Size: 24 KiB |