2023-05-30 01:40:02 +03:00
#!/usr/bin/env python3
2023-05-30 14:47:44 +03:00
import platform
2023-05-31 22:03:44 +03:00
import signal
2023-05-29 16:34:00 +03:00
import sys
2023-05-29 21:05:29 +03:00
import shutil
2023-05-28 17:49:40 +03:00
import glob
import argparse
import multiprocessing as mp
import os
2023-05-31 17:30:24 +03:00
import torch
2023-05-28 17:49:40 +03:00
from pathlib import Path
import tkinter as tk
from tkinter import filedialog
2023-05-31 20:06:19 +03:00
from opennsfw2 import predict_video_frames , predict_image
2023-05-29 11:09:44 +03:00
from tkinter . filedialog import asksaveasfilename
2023-05-28 17:49:40 +03:00
import webbrowser
import psutil
2023-05-29 15:20:42 +03:00
import cv2
2023-05-29 22:07:43 +03:00
import threading
from PIL import Image , ImageTk
2023-05-31 17:30:24 +03:00
import core . globals
2023-05-31 22:17:01 +03:00
from core . swapper import process_video , process_img
2023-05-31 17:30:24 +03:00
from core . utils import is_img , detect_fps , set_fps , create_video , add_audio , extract_frames , rreplace
2023-06-01 22:09:12 +03:00
from core . analyser import get_face_single
2023-05-28 17:49:40 +03:00
2023-05-31 17:30:24 +03:00
if ' ROCMExecutionProvider ' in core . globals . providers :
del torch
2023-05-31 16:42:13 +03:00
2023-05-28 17:49:40 +03:00
pool = None
args = { }
2023-05-31 22:03:44 +03:00
signal . signal ( signal . SIGINT , lambda signal_number , frame : quit ( ) )
2023-05-28 17:49:40 +03:00
parser = argparse . ArgumentParser ( )
parser . add_argument ( ' -f ' , ' --face ' , help = ' use this face ' , dest = ' source_img ' )
parser . add_argument ( ' -t ' , ' --target ' , help = ' replace this face ' , dest = ' target_path ' )
2023-05-29 16:12:06 +03:00
parser . add_argument ( ' -o ' , ' --output ' , help = ' save output to this file ' , dest = ' output_file ' )
2023-05-30 04:30:37 +03:00
parser . add_argument ( ' --gpu ' , help = ' use gpu ' , dest = ' gpu ' , action = ' store_true ' , default = False )
2023-05-30 21:42:54 +03:00
parser . add_argument ( ' --keep-fps ' , help = ' maintain original fps ' , dest = ' keep_fps ' , action = ' store_true ' , default = False )
2023-05-28 17:49:40 +03:00
parser . add_argument ( ' --keep-frames ' , help = ' keep frames directory ' , dest = ' keep_frames ' , action = ' store_true ' , default = False )
2023-05-31 20:37:33 +03:00
parser . add_argument ( ' --max-memory ' , help = ' maximum amount of RAM in GB to be used ' , type = int )
parser . add_argument ( ' --max-cores ' , help = ' number of cores to be use for CPU mode ' , dest = ' cores_count ' , type = int , default = max ( psutil . cpu_count ( ) - 2 , 2 ) )
2023-05-31 20:50:33 +03:00
parser . add_argument ( ' --all-faces ' , help = ' swap all faces in frame ' , dest = ' all_faces ' , action = ' store_true ' , default = False )
2023-05-28 17:49:40 +03:00
for name , value in vars ( parser . parse_args ( ) ) . items ( ) :
args [ name ] = value
2023-05-29 20:25:47 +03:00
sep = " / "
if os . name == " nt " :
sep = " \\ "
2023-05-30 11:15:25 +03:00
def limit_resources ( ) :
2023-05-31 16:17:20 +03:00
if args [ ' max_memory ' ] :
2023-05-30 11:59:23 +03:00
memory = args [ ' max_memory ' ] * 1024 * 1024 * 1024
2023-05-30 14:47:44 +03:00
if str ( platform . system ( ) ) . lower ( ) == ' windows ' :
2023-05-30 16:19:47 +03:00
import ctypes
kernel32 = ctypes . windll . kernel32
kernel32 . SetProcessWorkingSetSize ( - 1 , ctypes . c_size_t ( memory ) , ctypes . c_size_t ( memory ) )
2023-05-30 14:53:59 +03:00
else :
import resource
resource . setrlimit ( resource . RLIMIT_DATA , ( memory , memory ) )
2023-05-30 11:15:25 +03:00
2023-05-30 10:01:03 +03:00
def pre_check ( ) :
2023-06-01 00:27:26 +03:00
if sys . version_info < ( 3 , 9 ) :
quit ( ' Python version is not supported - please upgrade to 3.9 or higher ' )
2023-05-30 10:01:03 +03:00
if not shutil . which ( ' ffmpeg ' ) :
quit ( ' ffmpeg is not installed! ' )
2023-05-30 22:52:39 +03:00
model_path = os . path . join ( os . path . abspath ( os . path . dirname ( __file__ ) ) , ' inswapper_128.onnx ' )
if not os . path . isfile ( model_path ) :
2023-05-30 10:01:03 +03:00
quit ( ' File " inswapper_128.onnx " does not exist! ' )
if ' --gpu ' in sys . argv :
2023-06-01 12:37:44 +03:00
NVIDIA_PROVIDERS = [ ' CUDAExecutionProvider ' , ' TensorrtExecutionProvider ' ]
if len ( list ( set ( core . globals . providers ) - set ( NVIDIA_PROVIDERS ) ) ) == 1 :
2023-05-31 16:17:20 +03:00
CUDA_VERSION = torch . version . cuda
CUDNN_VERSION = torch . backends . cudnn . version ( )
2023-05-30 20:28:39 +03:00
if not torch . cuda . is_available ( ) or not CUDA_VERSION :
quit ( " You are using --gpu flag but CUDA isn ' t available or properly installed on your system. " )
2023-05-30 10:01:03 +03:00
if CUDA_VERSION > ' 11.8 ' :
2023-05-31 11:32:40 +03:00
quit ( f " CUDA version { CUDA_VERSION } is not supported - please downgrade to 11.8 " )
2023-05-30 10:07:12 +03:00
if CUDA_VERSION < ' 11.4 ' :
2023-05-30 16:19:47 +03:00
quit ( f " CUDA version { CUDA_VERSION } is not supported - please upgrade to 11.8 " )
2023-05-30 10:01:03 +03:00
if CUDNN_VERSION < 8220 :
quit ( f " CUDNN version { CUDNN_VERSION } is not supported - please upgrade to 8.9.1 " )
if CUDNN_VERSION > 8910 :
quit ( f " CUDNN version { CUDNN_VERSION } is not supported - please downgrade to 8.9.1 " )
else :
core . globals . providers = [ ' CPUExecutionProvider ' ]
2023-05-31 18:35:39 +03:00
if ' --all-faces ' in sys . argv or ' -a ' in sys . argv :
core . globals . all_faces = True
2023-05-30 10:01:03 +03:00
2023-05-28 17:49:40 +03:00
def start_processing ( ) :
if args [ ' gpu ' ] :
process_video ( args [ ' source_img ' ] , args [ " frame_paths " ] )
return
frame_paths = args [ " frame_paths " ]
2023-05-30 18:00:35 +03:00
n = len ( frame_paths ) / / ( args [ ' cores_count ' ] )
2023-05-28 17:49:40 +03:00
processes = [ ]
for i in range ( 0 , len ( frame_paths ) , n ) :
p = pool . apply_async ( process_video , args = ( args [ ' source_img ' ] , frame_paths [ i : i + n ] , ) )
processes . append ( p )
for p in processes :
p . get ( )
pool . close ( )
pool . join ( )
2023-05-30 10:01:03 +03:00
2023-05-29 22:07:43 +03:00
def preview_image ( image_path ) :
img = Image . open ( image_path )
2023-05-30 04:10:46 +03:00
img = img . resize ( ( 180 , 180 ) , Image . ANTIALIAS )
2023-05-29 22:07:43 +03:00
photo_img = ImageTk . PhotoImage ( img )
left_frame = tk . Frame ( window )
2023-05-30 04:10:46 +03:00
left_frame . place ( x = 60 , y = 100 )
2023-05-29 22:07:43 +03:00
img_label = tk . Label ( left_frame , image = photo_img )
img_label . image = photo_img
img_label . pack ( )
def preview_video ( video_path ) :
cap = cv2 . VideoCapture ( video_path )
if not cap . isOpened ( ) :
print ( " Error opening video file " )
return
ret , frame = cap . read ( )
if ret :
frame = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2RGB )
img = Image . fromarray ( frame )
2023-05-30 04:10:46 +03:00
img = img . resize ( ( 180 , 180 ) , Image . ANTIALIAS )
2023-05-29 22:07:43 +03:00
photo_img = ImageTk . PhotoImage ( img )
right_frame = tk . Frame ( window )
2023-05-30 04:10:46 +03:00
right_frame . place ( x = 360 , y = 100 )
2023-05-29 22:07:43 +03:00
img_label = tk . Label ( right_frame , image = photo_img )
img_label . image = photo_img
img_label . pack ( )
cap . release ( )
2023-05-28 17:49:40 +03:00
def select_face ( ) :
args [ ' source_img ' ] = filedialog . askopenfilename ( title = " Select a face " )
2023-05-29 22:07:43 +03:00
preview_image ( args [ ' source_img ' ] )
2023-05-28 17:49:40 +03:00
def select_target ( ) :
args [ ' target_path ' ] = filedialog . askopenfilename ( title = " Select a target " )
2023-05-29 22:07:43 +03:00
threading . Thread ( target = preview_video , args = ( args [ ' target_path ' ] , ) ) . start ( )
2023-05-28 17:49:40 +03:00
def toggle_fps_limit ( ) :
2023-06-01 01:20:18 +03:00
args [ ' keep_fps ' ] = int ( limit_fps . get ( ) != True )
2023-05-28 17:49:40 +03:00
2023-05-31 20:54:41 +03:00
def toggle_all_faces ( ) :
core . globals . all_faces = True if all_faces . get ( ) == 1 else False
2023-05-30 01:57:42 +03:00
def toggle_keep_frames ( ) :
2023-06-01 00:57:21 +03:00
args [ ' keep_frames ' ] = int ( keep_frames . get ( ) )
2023-05-30 01:57:42 +03:00
2023-05-29 11:09:44 +03:00
def save_file ( ) :
2023-05-30 16:37:22 +03:00
filename , ext = ' output.mp4 ' , ' .mp4 '
if is_img ( args [ ' target_path ' ] ) :
filename , ext = ' output.png ' , ' .png '
args [ ' output_file ' ] = asksaveasfilename ( initialfile = filename , defaultextension = ext , filetypes = [ ( " All Files " , " *.* " ) , ( " Videos " , " *.mp4 " ) ] )
2023-05-29 11:09:44 +03:00
2023-05-29 23:58:31 +03:00
def status ( string ) :
2023-05-30 16:23:31 +03:00
if ' cli_mode ' in args :
2023-05-30 06:26:50 +03:00
print ( " Status: " + string )
else :
status_label [ " text " ] = " Status: " + string
window . update ( )
2023-05-30 16:23:31 +03:00
2023-05-29 23:58:31 +03:00
2023-05-28 17:49:40 +03:00
def start ( ) :
2023-05-29 10:31:05 +03:00
if not args [ ' source_img ' ] or not os . path . isfile ( args [ ' source_img ' ] ) :
print ( " \n [WARNING] Please select an image containing a face. " )
return
elif not args [ ' target_path ' ] or not os . path . isfile ( args [ ' target_path ' ] ) :
print ( " \n [WARNING] Please select a video/image to swap face in. " )
return
2023-05-30 16:37:22 +03:00
if not args [ ' output_file ' ] :
2023-05-31 12:13:21 +03:00
target_path = args [ ' target_path ' ]
args [ ' output_file ' ] = rreplace ( target_path , " / " , " /swapped- " , 1 ) if " / " in target_path else " swapped- " + target_path
2023-05-28 17:49:40 +03:00
global pool
2023-05-30 18:00:35 +03:00
pool = mp . Pool ( args [ ' cores_count ' ] )
2023-05-28 17:49:40 +03:00
target_path = args [ ' target_path ' ]
2023-06-01 22:09:12 +03:00
test_face = get_face_single ( cv2 . imread ( args [ ' source_img ' ] ) )
2023-05-29 15:20:42 +03:00
if not test_face :
print ( " \n [WARNING] No face detected in source image. Please try with another one. \n " )
return
2023-05-28 17:49:40 +03:00
if is_img ( target_path ) :
2023-05-31 20:27:31 +03:00
if predict_image ( target_path ) > 0.7 :
2023-05-31 20:06:19 +03:00
quit ( )
2023-05-30 16:37:22 +03:00
process_img ( args [ ' source_img ' ] , target_path , args [ ' output_file ' ] )
2023-05-30 01:57:42 +03:00
status ( " swap successful! " )
2023-05-28 17:49:40 +03:00
return
2023-06-01 00:04:37 +03:00
seconds , probabilities = predict_video_frames ( video_path = args [ ' target_path ' ] , frame_interval = 100 )
2023-06-01 09:10:25 +03:00
if any ( probability > 0.85 for probability in probabilities ) :
2023-05-31 20:06:19 +03:00
quit ( )
2023-05-31 16:17:20 +03:00
video_name_full = target_path . split ( " / " ) [ - 1 ]
video_name = os . path . splitext ( video_name_full ) [ 0 ]
2023-05-31 18:22:43 +03:00
output_dir = os . path . dirname ( target_path ) + " / " + video_name
2023-05-28 17:49:40 +03:00
Path ( output_dir ) . mkdir ( exist_ok = True )
2023-05-30 01:57:42 +03:00
status ( " detecting video ' s FPS... " )
2023-05-31 16:17:20 +03:00
fps , exact_fps = detect_fps ( target_path )
2023-05-28 17:49:40 +03:00
if not args [ ' keep_fps ' ] and fps > 30 :
2023-05-29 18:54:40 +03:00
this_path = output_dir + " / " + video_name + " .mp4 "
2023-05-28 17:49:40 +03:00
set_fps ( target_path , this_path , 30 )
2023-05-31 16:17:20 +03:00
target_path , exact_fps = this_path , 30
2023-05-28 17:49:40 +03:00
else :
shutil . copy ( target_path , output_dir )
2023-05-30 01:57:42 +03:00
status ( " extracting frames... " )
2023-05-28 17:49:40 +03:00
extract_frames ( target_path , output_dir )
args [ ' frame_paths ' ] = tuple ( sorted (
2023-05-31 11:32:40 +03:00
glob . glob ( output_dir + " /*.png " ) ,
2023-05-29 20:25:47 +03:00
key = lambda x : int ( x . split ( sep ) [ - 1 ] . replace ( " .png " , " " ) )
2023-05-28 17:49:40 +03:00
) )
2023-05-30 01:57:42 +03:00
status ( " swapping in progress... " )
2023-05-28 17:49:40 +03:00
start_processing ( )
2023-05-30 01:57:42 +03:00
status ( " creating video... " )
2023-05-31 16:17:20 +03:00
create_video ( video_name , exact_fps , output_dir )
2023-05-30 01:57:42 +03:00
status ( " adding audio... " )
2023-05-31 16:17:20 +03:00
add_audio ( output_dir , target_path , video_name_full , args [ ' keep_frames ' ] , args [ ' output_file ' ] )
2023-05-29 18:54:40 +03:00
save_path = args [ ' output_file ' ] if args [ ' output_file ' ] else output_dir + " / " + video_name + " .mp4 "
2023-05-29 12:57:52 +03:00
print ( " \n \n Video saved as: " , save_path , " \n \n " )
2023-05-30 01:57:42 +03:00
status ( " swap successful! " )
2023-05-28 17:49:40 +03:00
if __name__ == " __main__ " :
2023-05-29 23:58:31 +03:00
global status_label , window
2023-05-30 10:01:03 +03:00
pre_check ( )
2023-05-30 11:15:25 +03:00
limit_resources ( )
2023-05-28 17:49:40 +03:00
if args [ ' source_img ' ] :
2023-05-30 16:23:31 +03:00
args [ ' cli_mode ' ] = True
2023-05-28 17:49:40 +03:00
start ( )
quit ( )
window = tk . Tk ( )
2023-05-30 04:10:46 +03:00
window . geometry ( " 600x700 " )
2023-05-28 17:49:40 +03:00
window . title ( " roop " )
2023-05-30 01:57:42 +03:00
window . configure ( bg = " #2d3436 " )
window . resizable ( width = False , height = False )
2023-05-28 17:49:40 +03:00
# Contact information
2023-05-30 01:57:42 +03:00
support_link = tk . Label ( window , text = " Donate to project <3 " , fg = " #fd79a8 " , bg = " #2d3436 " , cursor = " hand2 " , font = ( " Arial " , 8 ) )
support_link . place ( x = 180 , y = 20 , width = 250 , height = 30 )
2023-05-28 17:49:40 +03:00
support_link . bind ( " <Button-1> " , lambda e : webbrowser . open ( " https://github.com/sponsors/s0md3v " ) )
# Select a face button
2023-05-30 01:57:42 +03:00
face_button = tk . Button ( window , text = " Select a face " , command = select_face , bg = " #2d3436 " , fg = " #74b9ff " , highlightthickness = 4 , relief = " flat " , highlightbackground = " #74b9ff " , activebackground = " #74b9ff " , borderwidth = 4 )
2023-05-30 04:10:46 +03:00
face_button . place ( x = 60 , y = 320 , width = 180 , height = 80 )
2023-05-28 17:49:40 +03:00
# Select a target button
2023-05-30 04:10:46 +03:00
target_button = tk . Button ( window , text = " Select a target " , command = select_target , bg = " #2d3436 " , fg = " #74b9ff " , highlightthickness = 4 , relief = " flat " , highlightbackground = " #74b9ff " , activebackground = " #74b9ff " , borderwidth = 4 )
target_button . place ( x = 360 , y = 320 , width = 180 , height = 80 )
2023-05-28 17:49:40 +03:00
2023-05-31 20:54:41 +03:00
# All faces checkbox
all_faces = tk . IntVar ( )
2023-06-01 20:39:16 +03:00
all_faces_checkbox = tk . Checkbutton ( window , anchor = " w " , relief = " groove " , activebackground = " #2d3436 " , activeforeground = " #74b9ff " , selectcolor = " black " , text = " Process all faces in frame " , fg = " #dfe6e9 " , borderwidth = 0 , highlightthickness = 0 , bg = " #2d3436 " , variable = all_faces , command = toggle_all_faces )
all_faces_checkbox . place ( x = 60 , y = 500 , width = 240 , height = 31 )
2023-05-31 20:54:41 +03:00
2023-05-28 17:49:40 +03:00
# FPS limit checkbox
2023-06-01 00:57:21 +03:00
limit_fps = tk . IntVar ( None , not args [ ' keep_fps ' ] )
2023-06-01 20:39:16 +03:00
fps_checkbox = tk . Checkbutton ( window , anchor = " w " , relief = " groove " , activebackground = " #2d3436 " , activeforeground = " #74b9ff " , selectcolor = " black " , text = " Limit FPS to 30 " , fg = " #dfe6e9 " , borderwidth = 0 , highlightthickness = 0 , bg = " #2d3436 " , variable = limit_fps , command = toggle_fps_limit )
fps_checkbox . place ( x = 60 , y = 475 , width = 240 , height = 31 )
2023-05-28 17:49:40 +03:00
2023-05-30 01:57:42 +03:00
# Keep frames checkbox
2023-06-01 00:57:21 +03:00
keep_frames = tk . IntVar ( None , args [ ' keep_frames ' ] )
2023-06-01 20:39:16 +03:00
frames_checkbox = tk . Checkbutton ( window , anchor = " w " , relief = " groove " , activebackground = " #2d3436 " , activeforeground = " #74b9ff " , selectcolor = " black " , text = " Keep frames dir " , fg = " #dfe6e9 " , borderwidth = 0 , highlightthickness = 0 , bg = " #2d3436 " , variable = keep_frames , command = toggle_keep_frames )
frames_checkbox . place ( x = 60 , y = 450 , width = 240 , height = 31 )
2023-05-30 04:10:46 +03:00
2023-05-28 17:49:40 +03:00
# Start button
2023-05-30 04:10:46 +03:00
start_button = tk . Button ( window , text = " Start " , bg = " #f1c40f " , relief = " flat " , borderwidth = 0 , highlightthickness = 0 , command = lambda : [ save_file ( ) , start ( ) ] )
start_button . place ( x = 240 , y = 560 , width = 120 , height = 49 )
2023-05-29 23:58:31 +03:00
# Status label
2023-05-30 04:10:46 +03:00
status_label = tk . Label ( window , width = 580 , justify = " center " , text = " Status: waiting for input... " , fg = " #2ecc71 " , bg = " #2d3436 " )
status_label . place ( x = 10 , y = 640 , width = 580 , height = 30 )
2023-05-31 20:54:41 +03:00
2023-05-28 17:49:40 +03:00
window . mainloop ( )