2023-09-24 21:36:57 +08:00
import os
import webbrowser
import customtkinter as ctk
2024-08-16 21:03:14 +08:00
from typing import Callable , Tuple
2023-09-24 21:36:57 +08:00
import cv2
from PIL import Image , ImageOps
import modules . globals
import modules . metadata
from modules . face_analyser import get_one_face
from modules . capturer import get_video_frame , get_video_frame_total
from modules . processors . frame . core import get_frame_processors_modules
2024-08-21 03:02:00 +08:00
from modules . utilities import is_image , is_video , resolve_relative_path , has_image_extension
2023-09-24 21:36:57 +08:00
ROOT = None
2024-08-16 21:03:14 +08:00
ROOT_HEIGHT = 700
2024-08-16 13:47:12 +08:00
ROOT_WIDTH = 600
2023-09-24 21:36:57 +08:00
PREVIEW = None
PREVIEW_MAX_HEIGHT = 700
2024-08-16 21:03:14 +08:00
PREVIEW_MAX_WIDTH = 1200
2023-09-24 21:36:57 +08:00
RECENT_DIRECTORY_SOURCE = None
RECENT_DIRECTORY_TARGET = None
RECENT_DIRECTORY_OUTPUT = None
preview_label = None
preview_slider = None
source_label = None
target_label = None
status_label = None
img_ft , vid_ft = modules . globals . file_types
def init ( start : Callable [ [ ] , None ] , destroy : Callable [ [ ] , None ] ) - > ctk . CTk :
global ROOT , PREVIEW
ROOT = create_root ( start , destroy )
PREVIEW = create_preview ( ROOT )
return ROOT
def create_root ( start : Callable [ [ ] , None ] , destroy : Callable [ [ ] , None ] ) - > ctk . CTk :
global source_label , target_label , status_label
ctk . deactivate_automatic_dpi_awareness ( )
ctk . set_appearance_mode ( ' system ' )
ctk . set_default_color_theme ( resolve_relative_path ( ' ui.json ' ) )
root = ctk . CTk ( )
root . minsize ( ROOT_WIDTH , ROOT_HEIGHT )
root . title ( f ' { modules . metadata . name } { modules . metadata . version } { modules . metadata . edition } ' )
2024-08-16 21:03:14 +08:00
root . configure ( )
2023-09-24 21:36:57 +08:00
root . protocol ( ' WM_DELETE_WINDOW ' , lambda : destroy ( ) )
source_label = ctk . CTkLabel ( root , text = None )
2024-08-16 21:03:14 +08:00
source_label . place ( relx = 0.1 , rely = 0.1 , relwidth = 0.3 , relheight = 0.25 )
2023-09-24 21:36:57 +08:00
target_label = ctk . CTkLabel ( root , text = None )
2024-08-16 21:03:14 +08:00
target_label . place ( relx = 0.6 , rely = 0.1 , relwidth = 0.3 , relheight = 0.25 )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
select_face_button = ctk . CTkButton ( root , text = ' Select a face ' , cursor = ' hand2 ' , command = lambda : select_source_path ( ) )
select_face_button . place ( relx = 0.1 , rely = 0.4 , relwidth = 0.3 , relheight = 0.1 )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
select_target_button = ctk . CTkButton ( root , text = ' Select a target ' , cursor = ' hand2 ' , command = lambda : select_target_path ( ) )
select_target_button . place ( relx = 0.6 , rely = 0.4 , relwidth = 0.3 , relheight = 0.1 )
2023-09-24 21:36:57 +08:00
keep_fps_value = ctk . BooleanVar ( value = modules . globals . keep_fps )
keep_fps_checkbox = ctk . CTkSwitch ( root , text = ' Keep fps ' , variable = keep_fps_value , cursor = ' hand2 ' , command = lambda : setattr ( modules . globals , ' keep_fps ' , not modules . globals . keep_fps ) )
2024-08-16 21:03:14 +08:00
keep_fps_checkbox . place ( relx = 0.1 , rely = 0.6 )
2023-09-24 21:36:57 +08:00
keep_frames_value = ctk . BooleanVar ( value = modules . globals . keep_frames )
keep_frames_switch = ctk . CTkSwitch ( root , text = ' Keep frames ' , variable = keep_frames_value , cursor = ' hand2 ' , command = lambda : setattr ( modules . globals , ' keep_frames ' , keep_frames_value . get ( ) ) )
2024-08-16 21:03:14 +08:00
keep_frames_switch . place ( relx = 0.1 , rely = 0.65 )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
# for FRAME PROCESSOR ENHANCER tumbler:
2023-09-24 21:36:57 +08:00
enhancer_value = ctk . BooleanVar ( value = modules . globals . fp_ui [ ' face_enhancer ' ] )
2024-08-16 21:03:14 +08:00
enhancer_switch = ctk . CTkSwitch ( root , text = ' Face Enhancer ' , variable = enhancer_value , cursor = ' hand2 ' , command = lambda : update_tumbler ( ' face_enhancer ' , enhancer_value . get ( ) ) )
enhancer_switch . place ( relx = 0.1 , rely = 0.7 )
2023-09-24 21:36:57 +08:00
keep_audio_value = ctk . BooleanVar ( value = modules . globals . keep_audio )
keep_audio_switch = ctk . CTkSwitch ( root , text = ' Keep audio ' , variable = keep_audio_value , cursor = ' hand2 ' , command = lambda : setattr ( modules . globals , ' keep_audio ' , keep_audio_value . get ( ) ) )
2024-08-16 21:03:14 +08:00
keep_audio_switch . place ( relx = 0.6 , rely = 0.6 )
2023-09-24 21:36:57 +08:00
many_faces_value = ctk . BooleanVar ( value = modules . globals . many_faces )
many_faces_switch = ctk . CTkSwitch ( root , text = ' Many faces ' , variable = many_faces_value , cursor = ' hand2 ' , command = lambda : setattr ( modules . globals , ' many_faces ' , many_faces_value . get ( ) ) )
2024-08-16 21:03:14 +08:00
many_faces_switch . place ( relx = 0.6 , rely = 0.65 )
2023-09-24 21:36:57 +08:00
2024-08-21 03:02:00 +08:00
nsfw_value = ctk . BooleanVar ( value = modules . globals . nsfw_filter )
nsfw_switch = ctk . CTkSwitch ( root , text = ' NSFW filter ' , variable = nsfw_value , cursor = ' hand2 ' , command = lambda : setattr ( modules . globals , ' nsfw_filter ' , nsfw_value . get ( ) ) )
nsfw_switch . place ( relx = 0.6 , rely = 0.7 )
2023-09-24 21:36:57 +08:00
start_button = ctk . CTkButton ( root , text = ' Start ' , cursor = ' hand2 ' , command = lambda : select_output_path ( start ) )
2024-08-16 21:03:14 +08:00
start_button . place ( relx = 0.15 , rely = 0.80 , relwidth = 0.2 , relheight = 0.05 )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
stop_button = ctk . CTkButton ( root , text = ' Destroy ' , cursor = ' hand2 ' , command = lambda : destroy ( ) )
stop_button . place ( relx = 0.4 , rely = 0.80 , relwidth = 0.2 , relheight = 0.05 )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
preview_button = ctk . CTkButton ( root , text = ' Preview ' , cursor = ' hand2 ' , command = lambda : toggle_preview ( ) )
preview_button . place ( relx = 0.65 , rely = 0.80 , relwidth = 0.2 , relheight = 0.05 )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
live_button = ctk . CTkButton ( root , text = ' Live ' , cursor = ' hand2 ' , command = lambda : webcam_preview ( ) )
live_button . place ( relx = 0.40 , rely = 0.86 , relwidth = 0.2 , relheight = 0.05 )
2023-09-24 21:36:57 +08:00
status_label = ctk . CTkLabel ( root , text = None , justify = ' center ' )
2024-08-16 21:03:14 +08:00
status_label . place ( relx = 0.1 , rely = 0.9 , relwidth = 0.8 )
2023-09-24 21:36:57 +08:00
2023-09-24 21:59:24 +08:00
donate_label = ctk . CTkLabel ( root , text = ' Deep Live Cam ' , justify = ' center ' , cursor = ' hand2 ' )
2023-09-24 21:36:57 +08:00
donate_label . place ( relx = 0.1 , rely = 0.95 , relwidth = 0.8 )
donate_label . configure ( text_color = ctk . ThemeManager . theme . get ( ' URL ' ) . get ( ' text_color ' ) )
2024-08-16 21:03:14 +08:00
donate_label . bind ( ' <Button> ' , lambda event : webbrowser . open ( ' https://paypal.me/hacksider ' ) )
2023-09-24 21:36:57 +08:00
return root
2024-08-16 21:03:14 +08:00
def create_preview ( parent : ctk . CTkToplevel ) - > ctk . CTkToplevel :
2023-09-24 21:36:57 +08:00
global preview_label , preview_slider
preview = ctk . CTkToplevel ( parent )
preview . withdraw ( )
preview . title ( ' Preview ' )
2024-08-16 21:03:14 +08:00
preview . configure ( )
preview . protocol ( ' WM_DELETE_WINDOW ' , lambda : toggle_preview ( ) )
preview . resizable ( width = False , height = False )
2023-09-24 21:36:57 +08:00
preview_label = ctk . CTkLabel ( preview , text = None )
preview_label . pack ( fill = ' both ' , expand = True )
2024-08-16 21:03:14 +08:00
preview_slider = ctk . CTkSlider ( preview , from_ = 0 , to = 0 , command = lambda frame_value : update_preview ( frame_value ) )
2023-09-24 21:36:57 +08:00
return preview
def update_status ( text : str ) - > None :
status_label . configure ( text = text )
ROOT . update ( )
def update_tumbler ( var : str , value : bool ) - > None :
modules . globals . fp_ui [ var ] = value
def select_source_path ( ) - > None :
2024-08-16 21:03:14 +08:00
global RECENT_DIRECTORY_SOURCE , img_ft , vid_ft
2023-09-24 21:36:57 +08:00
PREVIEW . withdraw ( )
2024-08-16 21:03:14 +08:00
source_path = ctk . filedialog . askopenfilename ( title = ' select an source image ' , initialdir = RECENT_DIRECTORY_SOURCE , filetypes = [ img_ft ] )
2023-09-24 21:36:57 +08:00
if is_image ( source_path ) :
modules . globals . source_path = source_path
RECENT_DIRECTORY_SOURCE = os . path . dirname ( modules . globals . source_path )
image = render_image_preview ( modules . globals . source_path , ( 200 , 200 ) )
source_label . configure ( image = image )
else :
modules . globals . source_path = None
source_label . configure ( image = None )
def select_target_path ( ) - > None :
2024-08-16 21:03:14 +08:00
global RECENT_DIRECTORY_TARGET , img_ft , vid_ft
2023-09-24 21:36:57 +08:00
PREVIEW . withdraw ( )
2024-08-16 21:03:14 +08:00
target_path = ctk . filedialog . askopenfilename ( title = ' select an target image or video ' , initialdir = RECENT_DIRECTORY_TARGET , filetypes = [ img_ft , vid_ft ] )
2023-09-24 21:36:57 +08:00
if is_image ( target_path ) :
modules . globals . target_path = target_path
RECENT_DIRECTORY_TARGET = os . path . dirname ( modules . globals . target_path )
image = render_image_preview ( modules . globals . target_path , ( 200 , 200 ) )
target_label . configure ( image = image )
elif is_video ( target_path ) :
modules . globals . target_path = target_path
RECENT_DIRECTORY_TARGET = os . path . dirname ( modules . globals . target_path )
video_frame = render_video_preview ( target_path , ( 200 , 200 ) )
target_label . configure ( image = video_frame )
else :
modules . globals . target_path = None
target_label . configure ( image = None )
def select_output_path ( start : Callable [ [ ] , None ] ) - > None :
2024-08-16 21:03:14 +08:00
global RECENT_DIRECTORY_OUTPUT , img_ft , vid_ft
2023-09-24 21:36:57 +08:00
if is_image ( modules . globals . target_path ) :
2024-08-16 21:03:14 +08:00
output_path = ctk . filedialog . asksaveasfilename ( title = ' save image output file ' , filetypes = [ img_ft ] , defaultextension = ' .png ' , initialfile = ' output.png ' , initialdir = RECENT_DIRECTORY_OUTPUT )
2023-09-24 21:36:57 +08:00
elif is_video ( modules . globals . target_path ) :
2024-08-16 21:03:14 +08:00
output_path = ctk . filedialog . asksaveasfilename ( title = ' save video output file ' , filetypes = [ vid_ft ] , defaultextension = ' .mp4 ' , initialfile = ' output.mp4 ' , initialdir = RECENT_DIRECTORY_OUTPUT )
2023-09-24 21:36:57 +08:00
else :
output_path = None
if output_path :
modules . globals . output_path = output_path
RECENT_DIRECTORY_OUTPUT = os . path . dirname ( modules . globals . output_path )
start ( )
2024-08-21 03:02:00 +08:00
def check_and_ignore_nsfw ( target , destroy : Callable = None ) - > bool :
''' Check the target is NSFW or not.
TODO : Consider to make blur the target .
'''
from numpy import ndarray
from modules . predicter import predict_image , predict_video , predict_frame
if type ( target ) is str : # image/video file path
check_nsfw = predict_image if has_image_extension ( target ) else predict_video
elif type ( target ) is ndarray : # frame object
check_nsfw = predict_frame
if check_nsfw and check_nsfw ( target ) :
if destroy : destroy ( to_quit = False ) # Do not need to destroy the window frame if the target is NSFW
update_status ( ' Processing ignored! ' )
return True
else : return False
2023-09-24 21:36:57 +08:00
def render_image_preview ( image_path : str , size : Tuple [ int , int ] ) - > ctk . CTkImage :
image = Image . open ( image_path )
if size :
image = ImageOps . fit ( image , size , Image . LANCZOS )
return ctk . CTkImage ( image , size = image . size )
def render_video_preview ( video_path : str , size : Tuple [ int , int ] , frame_number : int = 0 ) - > ctk . CTkImage :
capture = cv2 . VideoCapture ( video_path )
if frame_number :
capture . set ( cv2 . CAP_PROP_POS_FRAMES , frame_number )
has_frame , frame = capture . read ( )
if has_frame :
image = Image . fromarray ( cv2 . cvtColor ( frame , cv2 . COLOR_BGR2RGB ) )
if size :
image = ImageOps . fit ( image , size , Image . LANCZOS )
return ctk . CTkImage ( image , size = image . size )
2024-08-16 21:03:14 +08:00
capture . release ( )
cv2 . destroyAllWindows ( )
2023-09-24 21:36:57 +08:00
def toggle_preview ( ) - > None :
if PREVIEW . state ( ) == ' normal ' :
PREVIEW . withdraw ( )
elif modules . globals . source_path and modules . globals . target_path :
init_preview ( )
update_preview ( )
def init_preview ( ) - > None :
if is_image ( modules . globals . target_path ) :
preview_slider . pack_forget ( )
2024-08-16 21:03:14 +08:00
if is_video ( modules . globals . target_path ) :
2023-09-24 21:36:57 +08:00
video_frame_total = get_video_frame_total ( modules . globals . target_path )
preview_slider . configure ( to = video_frame_total )
preview_slider . pack ( fill = ' x ' )
preview_slider . set ( 0 )
def update_preview ( frame_number : int = 0 ) - > None :
if modules . globals . source_path and modules . globals . target_path :
2024-08-21 03:02:00 +08:00
update_status ( ' Processing... ' )
2023-09-24 21:36:57 +08:00
temp_frame = get_video_frame ( modules . globals . target_path , frame_number )
2024-08-21 03:02:00 +08:00
if modules . globals . nsfw_filter and check_and_ignore_nsfw ( temp_frame ) :
return
2023-09-24 21:36:57 +08:00
for frame_processor in get_frame_processors_modules ( modules . globals . frame_processors ) :
temp_frame = frame_processor . process_frame (
get_one_face ( cv2 . imread ( modules . globals . source_path ) ) ,
temp_frame
)
image = Image . fromarray ( cv2 . cvtColor ( temp_frame , cv2 . COLOR_BGR2RGB ) )
image = ImageOps . contain ( image , ( PREVIEW_MAX_WIDTH , PREVIEW_MAX_HEIGHT ) , Image . LANCZOS )
image = ctk . CTkImage ( image , size = image . size )
preview_label . configure ( image = image )
2024-08-21 03:02:00 +08:00
update_status ( ' Processing succeed! ' )
PREVIEW . deiconify ( )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
def webcam_preview ( ) :
2023-09-24 21:36:57 +08:00
if modules . globals . source_path is None :
2024-08-16 21:03:14 +08:00
# No image selected
2023-09-24 21:36:57 +08:00
return
2024-08-14 23:52:42 +08:00
2023-09-24 21:36:57 +08:00
global preview_label , PREVIEW
2024-08-16 21:03:14 +08:00
cap = cv2 . VideoCapture ( 0 ) # Use index for the webcam (adjust the index accordingly if necessary)
cap . set ( cv2 . CAP_PROP_FRAME_WIDTH , 960 ) # Set the width of the resolution
cap . set ( cv2 . CAP_PROP_FRAME_HEIGHT , 540 ) # Set the height of the resolution
cap . set ( cv2 . CAP_PROP_FPS , 60 ) # Set the frame rate of the webcam
PREVIEW_MAX_WIDTH = 960
PREVIEW_MAX_HEIGHT = 540
2024-08-16 00:15:53 +08:00
2024-08-16 21:03:14 +08:00
preview_label . configure ( image = None ) # Reset the preview image before startup
2024-08-16 13:47:12 +08:00
2024-08-16 21:03:14 +08:00
PREVIEW . deiconify ( ) # Open preview window
2023-09-24 21:36:57 +08:00
2024-08-16 00:15:53 +08:00
frame_processors = get_frame_processors_modules ( modules . globals . frame_processors )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
source_image = None # Initialize variable for the selected face image
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
while True :
ret , frame = cap . read ( )
if not ret :
break
2024-08-16 00:15:53 +08:00
2024-08-16 21:03:14 +08:00
# Select and save face image only once
if source_image is None and modules . globals . source_path :
source_image = get_one_face ( cv2 . imread ( modules . globals . source_path ) )
2024-08-16 00:15:53 +08:00
2024-08-16 21:03:14 +08:00
temp_frame = frame . copy ( ) #Create a copy of the frame
2024-08-16 00:15:53 +08:00
2024-08-16 21:03:14 +08:00
for frame_processor in frame_processors :
temp_frame = frame_processor . process_frame ( source_image , temp_frame )
image = cv2 . cvtColor ( temp_frame , cv2 . COLOR_BGR2RGB ) # Convert the image to RGB format to display it with Tkinter
image = Image . fromarray ( image )
image = ImageOps . contain ( image , ( PREVIEW_MAX_WIDTH , PREVIEW_MAX_HEIGHT ) , Image . LANCZOS )
image = ctk . CTkImage ( image , size = image . size )
preview_label . configure ( image = image )
ROOT . update ( )
2023-09-24 21:36:57 +08:00
2024-08-16 21:03:14 +08:00
if PREVIEW . state ( ) == ' withdrawn ' :
break
2024-08-14 23:52:42 +08:00
2024-08-16 21:03:14 +08:00
cap . release ( )
PREVIEW . withdraw ( ) # Close preview window when loop is finished