From 22ce9c3f5854af97eb2e456ab0e2bbb3e718ddd3 Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 00:01:44 +0300 Subject: [PATCH 1/9] Preview window --- roop/core.py | 70 +++++++++++++++++++++++++++++++++++++++++++----- roop/swapper.py | 16 ++++++----- roop/ui.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 roop/ui.py diff --git a/roop/core.py b/roop/core.py index b9aee40..744d765 100755 --- a/roop/core.py +++ b/roop/core.py @@ -21,9 +21,10 @@ import threading from PIL import Image, ImageTk import roop.globals -from roop.swapper import process_video, process_img +from roop.swapper import process_video, process_img, process_faces from roop.utils import is_img, detect_fps, set_fps, create_video, add_audio, extract_frames, rreplace from roop.analyser import get_face_single +import roop.ui as ui if 'ROCMExecutionProvider' in roop.globals.providers: del torch @@ -100,13 +101,13 @@ def start_processing(): n = len(frame_paths) // (args['cores_count']) # single thread if args['gpu'] or n < 2: - process_video(args['source_img'], args["frame_paths"]) + process_video(args['source_img'], args["frame_paths"], preview.update) return # multithread if total frames to cpu cores ratio is greater than 2 if n > 2: 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],)) + p = pool.apply_async(process_video, args=(args['source_img'], frame_paths[i:i+n], preview.update,)) processes.append(p) for p in processes: p.get() @@ -125,6 +126,20 @@ def preview_image(image_path): img_label.pack() +def get_video_frame(video_path, frame_number = 1): + cap = cv2.VideoCapture(video_path) + amount_of_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT) + cap.set(cv2.CAP_PROP_POS_FRAMES, min(amount_of_frames, frame_number-1)) + if not cap.isOpened(): + print("Error opening video file") + return + ret, frame = cap.read() + if ret: + return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + cap.release() + + def preview_video(video_path): cap = cv2.VideoCapture(video_path) if not cap.isOpened(): @@ -132,7 +147,7 @@ def preview_video(video_path): return ret, frame = cap.read() if ret: - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + frame = get_video_frame(video_path) img = Image.fromarray(frame) img = img.resize((180, 180), Image.ANTIALIAS) photo_img = ImageTk.PhotoImage(img) @@ -142,6 +157,26 @@ def preview_video(video_path): img_label.image = photo_img img_label.pack() + # Preview + preview.update(frame) + amount_of_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT) + + def update_slider(frame_number): + preview.update(get_video_frame(video_path, frame_number)) + + preview.init_slider(amount_of_frames, update_slider) + + def test_handler(): + test_frame = process_faces( + get_face_single(cv2.imread(args['source_img'])), + get_video_frame(video_path, preview.current_frame.get()), + None, + roop.globals.all_faces + ) + preview.update(test_frame) + + preview.set_test_handler(lambda: preview_thread(test_handler)) + cap.release() @@ -237,8 +272,22 @@ def start(): status("swap successful!") +def preview_thread(thread_function): + threading.Thread(target=thread_function).start() + + +def open_preview(): + if (preview.visible): + preview.hide() + else: + preview.show() + if args['target_path']: + frame = get_video_frame(args['target_path']) + preview.update(frame) + + def run(): - global all_faces, keep_frames, limit_fps, status_label, window + global all_faces, keep_frames, limit_fps, status_label, window, preview pre_check() limit_resources() @@ -253,6 +302,9 @@ def run(): window.configure(bg="#2d3436") window.resizable(width=False, height=False) + # Preview window + preview = ui.PreviewWindow(window) + # Contact information 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) @@ -282,8 +334,12 @@ def run(): frames_checkbox.place(x=60,y=450,width=240,height=31) # Start button - 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) + start_button = tk.Button(window, text="Start", bg="#f1c40f", relief="flat", borderwidth=0, highlightthickness=0, command=lambda: [save_file(), preview_thread(start)]) + start_button.place(x=170,y=560,width=120,height=49) + + # Preview button + preview_button = tk.Button(window, text="Preview", bg="#f1c40f", relief="flat", borderwidth=0, highlightthickness=0, command=lambda: [open_preview()]) + preview_button.place(x=310,y=560,width=120,height=49) # Status label status_label = tk.Label(window, width=580, justify="center", text="Status: waiting for input...", fg="#2ecc71", bg="#2d3436") diff --git a/roop/swapper.py b/roop/swapper.py index bfc4d63..ba19c23 100644 --- a/roop/swapper.py +++ b/roop/swapper.py @@ -23,25 +23,25 @@ def swap_face_in_frame(source_face, target_face, frame): def process_faces(source_face, frame, progress, all_faces=False): + progress_status = 'S' if all_faces: many_faces = get_face_many(frame) if many_faces: for face in many_faces: frame = swap_face_in_frame(source_face, face, frame) - progress.set_postfix(status='.', refresh=True) - else: - progress.set_postfix(status='S', refresh=True) + progress_status='.' else: face = get_face_single(frame) if face: frame = swap_face_in_frame(source_face, face, frame) - progress.set_postfix(status='.', refresh=True) - else: - progress.set_postfix(status='S', refresh=True) + progress_status='.' + + if progress: + progress.set_postfix(status=progress_status, refresh=True) return frame -def process_video(source_img, frame_paths): +def process_video(source_img, frame_paths, preview_callback): source_face = get_face_single(cv2.imread(source_img)) progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]' @@ -51,6 +51,8 @@ def process_video(source_img, frame_paths): try: result = process_faces(source_face, frame, progress, roop.globals.all_faces) cv2.imwrite(frame_path, result) + if preview_callback: + preview_callback(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) except Exception: progress.set_postfix(status='E', refresh=True) pass diff --git a/roop/ui.py b/roop/ui.py new file mode 100644 index 0000000..e2c3c5b --- /dev/null +++ b/roop/ui.py @@ -0,0 +1,71 @@ +import tkinter as tk +from PIL import Image, ImageTk + + +class PreviewWindow: + def __init__(self, master): + self.master = master + self.window = tk.Toplevel(self.master) + # Override close button + self.window.protocol("WM_DELETE_WINDOW", self.hide) + self.window.withdraw() + self.window.geometry("600x700") + self.window.title("Preview") + self.window.configure(bg="red") + self.window.resizable(width=False, height=False) + + self.visible = False + self.frame = tk.Frame(self.window, background="#2d3436") + self.frame.pack_propagate(0) + self.frame.pack(fill='both', side='left', expand='True') + + # Bottom frame + buttons_frame = tk.Frame(self.frame, background="#2d3436") + buttons_frame.pack(fill='both', side='bottom') + + self.current_frame = tk.IntVar() + self.frame_slider = tk.Scale( + buttons_frame, + from_=0, + to=0, + orient='horizontal', + variable=self.current_frame, + command=self.slider_changed + ) + self.frame_slider.pack(fill='both', side='left', expand='True') + + self.test_button = tk.Button(buttons_frame, text="Test", bg="#f1c40f", relief="flat", width=15, borderwidth=0, highlightthickness=0) + self.test_button.pack( side='right', fill='y') + + def init_slider(self, frames_count, change_handler): + self.frame_change = change_handler + self.frame_slider.configure(to=frames_count) + + def slider_changed(self, event): + self.frame_change(self.frame_slider.get()) + + def set_test_handler(self, test_handler): + self.test_button.config(command = test_handler) + + # Show the window + def show(self): + self.visible = True + self.window.deiconify() + + # Hide the window + def hide(self): + self.visible = False + self.window.withdraw() + + def update(self, frame): + if not self.visible: + return + + img = Image.fromarray(frame) + img = img.resize((600, 650), Image.ANTIALIAS) + photo_img = ImageTk.PhotoImage(img) + img_frame = tk.Frame(self.frame) + img_frame.place(x=0, y=0) + img_label = tk.Label(img_frame, image=photo_img) + img_label.image = photo_img + img_label.pack(side='top') \ No newline at end of file From 86e9b3c6005190e0e5516a499f472137a68c475a Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 00:23:11 +0300 Subject: [PATCH 2/9] Aspect ratio for preview --- roop/ui.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/roop/ui.py b/roop/ui.py index e2c3c5b..8c42745 100644 --- a/roop/ui.py +++ b/roop/ui.py @@ -3,7 +3,12 @@ from PIL import Image, ImageTk class PreviewWindow: + def __init__(self, master): + self.preview_width = 600 + self.preview_height = 650 + + self.master = master self.window = tk.Toplevel(self.master) # Override close button @@ -19,6 +24,10 @@ class PreviewWindow: self.frame.pack_propagate(0) self.frame.pack(fill='both', side='left', expand='True') + # Preview image + self.img_label = tk.Label(self.frame) + self.img_label.pack(side='top') + # Bottom frame buttons_frame = tk.Frame(self.frame, background="#2d3436") buttons_frame.pack(fill='both', side='bottom') @@ -60,12 +69,21 @@ class PreviewWindow: def update(self, frame): if not self.visible: return - + img = Image.fromarray(frame) - img = img.resize((600, 650), Image.ANTIALIAS) + width, height = img.size + aspect_ratio = 1 + if width > height: + aspect_ratio = self.preview_width / width + else: + aspect_ratio = self.preview_height / height + img = img.resize( + ( + int(width * aspect_ratio), + int(height * aspect_ratio) + ), + Image.ANTIALIAS + ) photo_img = ImageTk.PhotoImage(img) - img_frame = tk.Frame(self.frame) - img_frame.place(x=0, y=0) - img_label = tk.Label(img_frame, image=photo_img) - img_label.image = photo_img - img_label.pack(side='top') \ No newline at end of file + self.img_label.configure(image=photo_img) + self.img_label.image = photo_img \ No newline at end of file From af5b68f52955fcc3e131df19341ea872bfe9cb44 Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 00:28:53 +0300 Subject: [PATCH 3/9] lint --- roop/ui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/roop/ui.py b/roop/ui.py index 8c42745..69cf5ee 100644 --- a/roop/ui.py +++ b/roop/ui.py @@ -8,7 +8,6 @@ class PreviewWindow: self.preview_width = 600 self.preview_height = 650 - self.master = master self.window = tk.Toplevel(self.master) # Override close button From 1ed00554db54680e2d83eaebc01f15e680fa2d47 Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 09:57:53 +0300 Subject: [PATCH 4/9] Auto adjust window size to preview --- roop/ui.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/roop/ui.py b/roop/ui.py index 69cf5ee..46cf9a2 100644 --- a/roop/ui.py +++ b/roop/ui.py @@ -5,22 +5,19 @@ from PIL import Image, ImageTk class PreviewWindow: def __init__(self, master): - self.preview_width = 600 - self.preview_height = 650 + self.max_preview_size = 800 self.master = master self.window = tk.Toplevel(self.master) # Override close button self.window.protocol("WM_DELETE_WINDOW", self.hide) self.window.withdraw() - self.window.geometry("600x700") self.window.title("Preview") self.window.configure(bg="red") self.window.resizable(width=False, height=False) self.visible = False self.frame = tk.Frame(self.window, background="#2d3436") - self.frame.pack_propagate(0) self.frame.pack(fill='both', side='left', expand='True') # Preview image @@ -73,9 +70,9 @@ class PreviewWindow: width, height = img.size aspect_ratio = 1 if width > height: - aspect_ratio = self.preview_width / width + aspect_ratio = self.max_preview_size / width else: - aspect_ratio = self.preview_height / height + aspect_ratio = self.max_preview_size / height img = img.resize( ( int(width * aspect_ratio), From ee9406a17e0be43f99c1ca4d5d7c4fed361c374a Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 10:18:54 +0300 Subject: [PATCH 5/9] Set slider to zero --- roop/ui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/roop/ui.py b/roop/ui.py index 46cf9a2..c257c64 100644 --- a/roop/ui.py +++ b/roop/ui.py @@ -45,6 +45,7 @@ class PreviewWindow: def init_slider(self, frames_count, change_handler): self.frame_change = change_handler self.frame_slider.configure(to=frames_count) + self.frame_slider.set(0) def slider_changed(self, event): self.frame_change(self.frame_slider.get()) From b8a158d3aabc4b0280cbf24ce747ad9bac21615d Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 12:01:22 +0300 Subject: [PATCH 6/9] Fix callback --- roop/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roop/core.py b/roop/core.py index 79350a6..e3ef2d8 100755 --- a/roop/core.py +++ b/roop/core.py @@ -249,7 +249,7 @@ def start(): key=lambda x: int(x.split(sep)[-1].replace(".png", "")) )) status("swapping in progress...") - process_video(args['source_img'], args["frame_paths"], preview.update) + process_video(args['source_img'], args["frame_paths"], preview.update if preview else None) status("creating video...") create_video(video_name, exact_fps, output_dir) status("adding audio...") From 6288cdce65e9da922128dca3d2067e33ebff52c4 Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 16:02:51 +0300 Subject: [PATCH 7/9] Move ui from core --- roop/core.py | 195 ++++++++++++------------------------------- roop/ui.py | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 280 insertions(+), 143 deletions(-) diff --git a/roop/core.py b/roop/core.py index e3ef2d8..adbc4c9 100755 --- a/roop/core.py +++ b/roop/core.py @@ -9,14 +9,8 @@ import argparse import os import torch from pathlib import Path -import tkinter as tk -from tkinter import filedialog from opennsfw2 import predict_video_frames, predict_image -from tkinter.filedialog import asksaveasfilename -import webbrowser import cv2 -import threading -from PIL import Image, ImageTk import roop.globals from roop.swapper import process_video, process_img, process_faces @@ -101,17 +95,6 @@ def pre_check(): roop.globals.providers = ['CPUExecutionProvider'] -def preview_image(image_path): - img = Image.open(image_path) - img = img.resize((180, 180), Image.ANTIALIAS) - photo_img = ImageTk.PhotoImage(img) - left_frame = tk.Frame(window) - left_frame.place(x=60, y=100) - img_label = tk.Label(left_frame, image=photo_img) - img_label.image = photo_img - img_label.pack() - - def get_video_frame(video_path, frame_number = 1): cap = cv2.VideoCapture(video_path) amount_of_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT) @@ -126,84 +109,25 @@ def get_video_frame(video_path, frame_number = 1): cap.release() -def update_slider(video_path): - return lambda frame_number: preview.update(get_video_frame(video_path, frame_number)) - - -def process_test_preview(video_path): - test_frame = process_faces( - get_face_single(cv2.imread(args['source_img'])), - get_video_frame(video_path, preview.current_frame.get()), - None - ) - preview.update(test_frame) - - -def preview_handler(video_path): - return lambda: preview_thread(process_test_preview(video_path)) - - def preview_video(video_path): cap = cv2.VideoCapture(video_path) if not cap.isOpened(): print("Error opening video file") - return + return 0 + amount_of_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT) ret, frame = cap.read() if ret: frame = get_video_frame(video_path) - img = Image.fromarray(frame) - img = img.resize((180, 180), Image.ANTIALIAS) - photo_img = ImageTk.PhotoImage(img) - right_frame = tk.Frame(window) - right_frame.place(x=360, y=100) - img_label = tk.Label(right_frame, image=photo_img) - img_label.image = photo_img - img_label.pack() - - # Preview - preview.update(frame) - amount_of_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT) - preview.init_slider(amount_of_frames, update_slider(video_path)) - preview.set_preview_handler(preview_handler(video_path)) cap.release() - - -def select_face(): - args['source_img'] = filedialog.askopenfilename(title="Select a face") - preview_image(args['source_img']) - - -def select_target(): - args['target_path'] = filedialog.askopenfilename(title="Select a target") - threading.Thread(target=preview_video, args=(args['target_path'],)).start() - - -def toggle_fps_limit(): - args['keep_fps'] = int(limit_fps.get() != True) - - -def toggle_all_faces(): - roop.globals.all_faces = True if all_faces.get() == 1 else False - - -def toggle_keep_frames(): - args['keep_frames'] = int(keep_frames.get()) - - -def save_file(): - 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")]) - + return (amount_of_frames, frame) def status(string): + value = "Status: " + string if 'cli_mode' in args: - print("Status: " + string) + print(value) else: - status_label["text"] = "Status: " + string - window.update() + ui.update_status_label(value) def start(): @@ -249,7 +173,7 @@ def start(): key=lambda x: int(x.split(sep)[-1].replace(".png", "")) )) status("swapping in progress...") - process_video(args['source_img'], args["frame_paths"], preview.update if preview else None) + process_video(args['source_img'], args["frame_paths"], ui.preview.update if ui.preview else None) status("creating video...") create_video(video_name, exact_fps, output_dir) status("adding audio...") @@ -259,22 +183,40 @@ def start(): status("swap successful!") -def preview_thread(thread_function): - threading.Thread(target=thread_function).start() +def select_face_handler(path: str): + args['source_img'] = path -def open_preview(): - if (preview.visible): - preview.hide() - else: - preview.show() - if args['target_path']: - frame = get_video_frame(args['target_path']) - preview.update(frame) +def select_target_handler(path: str): + args['target_path'] = path + return preview_video(args['target_path']) +def toggle_all_faces_handler(value: int): + roop.globals.all_faces = True if value == 1 else False + + +def toggle_fps_limit_handler(value: int): + args['keep_fps'] = int(value != 1) + + +def toggle_keep_frames_handler(value: int): + args['keep_frames'] = value + + +def save_file_handler(path: str): + args['output_file'] = path + + +def create_test_preview(frame_number): + return process_faces( + get_face_single(cv2.imread(args['source_img'])), + get_video_frame(args['target_path'], frame_number), + None + ) + def run(): - global all_faces, keep_frames, limit_fps, status_label, window, preview + global all_faces, keep_frames, limit_fps pre_check() limit_resources() @@ -282,53 +224,22 @@ def run(): args['cli_mode'] = True start() quit() - window = tk.Tk() - window.geometry("600x700") - window.title("roop") - window.configure(bg="#2d3436") - window.resizable(width=False, height=False) - # Preview window - preview = ui.PreviewWindow(window) + window = ui.init( + { + 'all_faces': roop.globals.all_faces, + 'keep_fps': args['keep_fps'], + 'keep_frames': args['keep_frames'] + }, + select_face_handler, + select_target_handler, + toggle_all_faces_handler, + toggle_fps_limit_handler, + toggle_keep_frames_handler, + save_file_handler, + start, + get_video_frame, + create_test_preview + ) - # Contact information - 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) - support_link.bind("", lambda e: webbrowser.open("https://github.com/sponsors/s0md3v")) - - # Select a face button - 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) - face_button.place(x=60,y=320,width=180,height=80) - - # Select a target button - 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) - - # All faces checkbox - all_faces = tk.IntVar() - 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) - - # FPS limit checkbox - limit_fps = tk.IntVar(None, not args['keep_fps']) - 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) - - # Keep frames checkbox - keep_frames = tk.IntVar(None, args['keep_frames']) - 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) - - # Start button - start_button = tk.Button(window, text="Start", bg="#f1c40f", relief="flat", borderwidth=0, highlightthickness=0, command=lambda: [save_file(), preview_thread(start)]) - start_button.place(x=170,y=560,width=120,height=49) - - # Preview button - preview_button = tk.Button(window, text="Preview", bg="#f1c40f", relief="flat", borderwidth=0, highlightthickness=0, command=lambda: [open_preview()]) - preview_button.place(x=310,y=560,width=120,height=49) - - # Status label - 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) - - window.mainloop() + window.mainloop() \ No newline at end of file diff --git a/roop/ui.py b/roop/ui.py index 399878f..8b2bcac 100644 --- a/roop/ui.py +++ b/roop/ui.py @@ -1,6 +1,12 @@ import tkinter as tk +from typing import Any, Callable, Tuple from PIL import Image, ImageTk +import webbrowser +from tkinter import filedialog +from tkinter.filedialog import asksaveasfilename +import threading +from roop.utils import is_img class PreviewWindow: @@ -83,4 +89,224 @@ class PreviewWindow: ) photo_img = ImageTk.PhotoImage(img) self.img_label.configure(image=photo_img) - self.img_label.image = photo_img \ No newline at end of file + self.img_label.image = photo_img + + +def select_face(select_face_handler: Callable[[str], None]): + if select_face_handler: + path = filedialog.askopenfilename(title="Select a face") + preview_face(path) + return select_face_handler(path) + return None + + +def update_slider_handler(get_video_frame, video_path): + return lambda frame_number: preview.update(get_video_frame(video_path, frame_number)) + +def test_preview(create_test_preview): + frame = create_test_preview(preview.current_frame.get()) + preview.update(frame) + +def update_slider(get_video_frame, create_test_preview, video_path, frames_amount): + preview.init_slider(frames_amount, update_slider_handler(get_video_frame, video_path)) + preview.set_preview_handler(lambda: preview_thread(test_preview(create_test_preview))) + + +def analyze_target(select_target_handler: Callable[[str], Tuple[int, Any]], target_path: tk.StringVar, frames_amount: tk.IntVar): + path = filedialog.askopenfilename(title="Select a target") + target_path.set(path) + amount, frame = select_target_handler(path) + frames_amount.set(amount) + preview_target(frame) + preview.update(frame) + + +def select_target(select_target_handler: Callable[[str], Tuple[int, Any]], target_path: tk.StringVar, frames_amount: tk.IntVar): + if select_target_handler: + analyze_target(select_target_handler, target_path, frames_amount) + + +def save_file(save_file_handler: Callable[[str], None], target_path: str): + filename, ext = 'output.mp4', '.mp4' + + if is_img(target_path): + filename, ext = 'output.png', '.png' + + if save_file_handler: + return save_file_handler(asksaveasfilename(initialfile=filename, defaultextension=ext, filetypes=[("All Files","*.*"),("Videos","*.mp4")])) + return None + +def toggle_all_faces(toggle_all_faces_handler: Callable[[int], None], variable: tk.IntVar): + if toggle_all_faces_handler: + return lambda: toggle_all_faces_handler(variable.get()) + return None + + +def toggle_fps_limit(toggle_all_faces_handler: Callable[[int], None], variable: tk.IntVar): + if toggle_all_faces_handler: + return lambda: toggle_all_faces_handler(variable.get()) + return None + + +def toggle_keep_frames(toggle_keep_frames_handler: Callable[[int], None], variable: tk.IntVar): + if toggle_keep_frames_handler: + return lambda: toggle_keep_frames_handler(variable.get()) + return None + + +def create_button(parent, text, command): + return tk.Button( + parent, + text=text, + command=command, + bg="#f1c40f", + relief="flat", + borderwidth=0, + highlightthickness=0 + ) + + +def create_background_button(parent, text, command): + button = create_button(parent, text, command) + button.configure( + bg="#2d3436", + fg="#74b9ff", + highlightthickness=4, + highlightbackground="#74b9ff", + activebackground="#74b9ff", + borderwidth=4 + ) + return button + + +def create_check(parent, text, variable, command): + return tk.Checkbutton( + parent, + anchor="w", + relief="groove", + activebackground="#2d3436", + activeforeground="#74b9ff", + selectcolor="black", + text=text, + fg="#dfe6e9", + borderwidth=0, + highlightthickness=0, + bg="#2d3436", + variable=variable, + command=command + ) + + +def preview_thread(thread_function): + threading.Thread(target=thread_function).start() + + +def open_preview_window(get_video_frame, target_path): + if (preview.visible): + preview.hide() + else: + preview.show() + if target_path: + frame = get_video_frame(target_path) + preview.update(frame) + +def preview_face(path): + img = Image.open(path) + img = img.resize((180, 180), Image.ANTIALIAS) + photo_img = ImageTk.PhotoImage(img) + face_label.configure(image=photo_img) + face_label.image = photo_img + +def preview_target(frame): + img = Image.fromarray(frame) + img = img.resize((180, 180), Image.ANTIALIAS) + photo_img = ImageTk.PhotoImage(img) + target_label.configure(image=photo_img) + target_label.image = photo_img + +def update_status_label(value): + status_label["text"] = value + window.update() + +def init( + initial_values: dict, + select_face_handler: Callable[[str], None], + select_target_handler: Callable[[str], Tuple[int, Any]], + toggle_all_faces_handler: Callable[[int], None], + toggle_fps_limit_handler: Callable[[int], None], + toggle_keep_frames_handler: Callable[[int], None], + save_file_handler: Callable[[str], None], + start: Callable[[], None], + get_video_frame: Callable[[str, int], None], + create_test_preview: Callable[[int], Any], +): + global window, preview, face_label, target_label, status_label + + window = tk.Tk() + window.geometry("600x700") + window.title("roop") + window.configure(bg="#2d3436") + window.resizable(width=False, height=False) + + target_path = tk.StringVar() + frames_amount = tk.IntVar() + + # Preview window + preview = PreviewWindow(window) + + # Contact information + 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) + support_link.bind("", lambda e: webbrowser.open("https://github.com/sponsors/s0md3v")) + + left_frame = tk.Frame(window) + left_frame.place(x=60, y=100) + face_label = tk.Label(left_frame) + face_label.pack(fill='both', side='top', expand=True) + + right_frame = tk.Frame(window) + right_frame.place(x=360, y=100) + target_label = tk.Label(right_frame) + target_label.pack(fill='both', side='top', expand=True) + + # Select a face button + face_button = create_background_button(window, "Select a face", lambda: [ + select_face(select_face_handler) + ]) + face_button.place(x=60,y=320,width=180,height=80) + + # Select a target button + target_button = create_background_button(window, "Select a target", lambda: [ + select_target(select_target_handler, target_path, frames_amount), + update_slider(get_video_frame, create_test_preview, target_path.get(), frames_amount.get()) + ]) + target_button.place(x=360,y=320,width=180,height=80) + + # All faces checkbox + all_faces = tk.IntVar(None, initial_values['all_faces']) + all_faces_checkbox = create_check(window, "Process all faces in frame", all_faces, toggle_all_faces(toggle_all_faces_handler, all_faces)) + all_faces_checkbox.place(x=60,y=500,width=240,height=31) + + # FPS limit checkbox + limit_fps = tk.IntVar(None, not initial_values['keep_fps']) + fps_checkbox = create_check(window, "Limit FPS to 30", limit_fps, toggle_fps_limit(toggle_fps_limit_handler, limit_fps)) + fps_checkbox.place(x=60,y=475,width=240,height=31) + + # Keep frames checkbox + keep_frames = tk.IntVar(None, initial_values['keep_frames']) + frames_checkbox = create_check(window, "Keep frames dir", keep_frames, toggle_keep_frames(toggle_keep_frames_handler, keep_frames)) + frames_checkbox.place(x=60,y=450,width=240,height=31) + + # Start button + start_button = create_button(window, "Start", lambda: [save_file(save_file_handler, target_path.get()), preview_thread(start)]) + start_button.place(x=170,y=560,width=120,height=49) + + # Preview button + preview_button = create_button(window, "Preview", lambda: open_preview_window(get_video_frame, target_path.get())) + preview_button.place(x=310,y=560,width=120,height=49) + + # Status label + 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) + + return window \ No newline at end of file From 2c9127009098a8906683a81d95c4131667aad306 Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 17:47:59 +0300 Subject: [PATCH 8/9] Functionional style ui --- roop/core.py | 6 +- roop/ui.py | 179 ++++++++++++++++++++++++++------------------------- 2 files changed, 95 insertions(+), 90 deletions(-) diff --git a/roop/core.py b/roop/core.py index adbc4c9..1759c3b 100755 --- a/roop/core.py +++ b/roop/core.py @@ -122,6 +122,7 @@ def preview_video(video_path): cap.release() return (amount_of_frames, frame) + def status(string): value = "Status: " + string if 'cli_mode' in args: @@ -130,7 +131,7 @@ def status(string): ui.update_status_label(value) -def start(): +def start(preview_callback = None): if not args['source_img'] or not os.path.isfile(args['source_img']): print("\n[WARNING] Please select an image containing a face.") return @@ -173,7 +174,7 @@ def start(): key=lambda x: int(x.split(sep)[-1].replace(".png", "")) )) status("swapping in progress...") - process_video(args['source_img'], args["frame_paths"], ui.preview.update if ui.preview else None) + process_video(args['source_img'], args["frame_paths"], preview_callback) status("creating video...") create_video(video_name, exact_fps, output_dir) status("adding audio...") @@ -215,6 +216,7 @@ def create_test_preview(frame_number): None ) + def run(): global all_faces, keep_frames, limit_fps diff --git a/roop/ui.py b/roop/ui.py index 8b2bcac..52d2e9f 100644 --- a/roop/ui.py +++ b/roop/ui.py @@ -8,88 +8,83 @@ import threading from roop.utils import is_img -class PreviewWindow: +max_preview_size = 800 - def __init__(self, master): - self.max_preview_size = 800 - self.master = master - self.window = tk.Toplevel(self.master) - # Override close button - self.window.protocol("WM_DELETE_WINDOW", self.hide) - self.window.withdraw() - self.window.title("Preview") - self.window.configure(bg="red") - self.window.resizable(width=False, height=False) +def create_preview(parent): + global preview_image_frame, preview_frame_slider, test_button - self.visible = False - self.frame = tk.Frame(self.window, background="#2d3436") - self.frame.pack(fill='both', side='left', expand='True') - - # Preview image - self.img_label = tk.Label(self.frame) - self.img_label.pack(side='top') + preview_window = tk.Toplevel(parent) + # Override close button + preview_window.protocol("WM_DELETE_WINDOW", hide_preview) + preview_window.withdraw() + preview_window.title("Preview") + preview_window.configure(bg="red") + preview_window.resizable(width=False, height=False) - # Bottom frame - buttons_frame = tk.Frame(self.frame, background="#2d3436") - buttons_frame.pack(fill='both', side='bottom') - - self.current_frame = tk.IntVar() - self.frame_slider = tk.Scale( - buttons_frame, - from_=0, - to=0, - orient='horizontal', - variable=self.current_frame, - command=self.slider_changed - ) - self.frame_slider.pack(fill='both', side='left', expand='True') - - self.test_button = tk.Button(buttons_frame, text="Test", bg="#f1c40f", relief="flat", width=15, borderwidth=0, highlightthickness=0) - self.test_button.pack( side='right', fill='y') - - def init_slider(self, frames_count, change_handler): - self.frame_change = change_handler - self.frame_slider.configure(to=frames_count) - self.frame_slider.set(0) - - def slider_changed(self, event): - self.frame_change(self.frame_slider.get()) - - def set_preview_handler(self, test_handler): - self.test_button.config(command = test_handler) - - # Show the window - def show(self): - self.visible = True - self.window.deiconify() + frame = tk.Frame(preview_window, background="#2d3436") + frame.pack(fill='both', side='left', expand='True') - # Hide the window - def hide(self): - self.visible = False - self.window.withdraw() + # Preview image + preview_image_frame = tk.Label(frame) + preview_image_frame.pack(side='top') - def update(self, frame): - if not self.visible: - return + # Bottom frame + buttons_frame = tk.Frame(frame, background="#2d3436") + buttons_frame.pack(fill='both', side='bottom') - img = Image.fromarray(frame) - width, height = img.size - aspect_ratio = 1 - if width > height: - aspect_ratio = self.max_preview_size / width - else: - aspect_ratio = self.max_preview_size / height - img = img.resize( - ( - int(width * aspect_ratio), - int(height * aspect_ratio) - ), - Image.ANTIALIAS - ) - photo_img = ImageTk.PhotoImage(img) - self.img_label.configure(image=photo_img) - self.img_label.image = photo_img + current_frame = tk.IntVar() + preview_frame_slider = tk.Scale( + buttons_frame, + from_=0, + to=0, + orient='horizontal', + variable=current_frame + ) + preview_frame_slider.pack(fill='both', side='left', expand='True') + + test_button = tk.Button(buttons_frame, text="Test", bg="#f1c40f", relief="flat", width=15, borderwidth=0, highlightthickness=0) + test_button.pack(side='right', fill='y') + return preview_window + + +def show_preview(): + preview.deiconify() + preview_visible.set(True) + + +def hide_preview(): + preview.withdraw() + preview_visible.set(False) + + +def set_preview_handler(test_handler): + test_button.config(command = test_handler) + + +def init_slider(frames_count, change_handler): + preview_frame_slider.configure(to=frames_count, command=lambda value: change_handler(preview_frame_slider.get())) + preview_frame_slider.set(0) + + +def update_preview(frame): + img = Image.fromarray(frame) + width, height = img.size + aspect_ratio = 1 + if width > height: + aspect_ratio = max_preview_size / width + else: + aspect_ratio = max_preview_size / height + img = img.resize( + ( + int(width * aspect_ratio), + int(height * aspect_ratio) + ), + Image.ANTIALIAS + ) + photo_img = ImageTk.PhotoImage(img) + preview_image_frame.configure(image=photo_img) + preview_image_frame.image = photo_img def select_face(select_face_handler: Callable[[str], None]): @@ -101,15 +96,17 @@ def select_face(select_face_handler: Callable[[str], None]): def update_slider_handler(get_video_frame, video_path): - return lambda frame_number: preview.update(get_video_frame(video_path, frame_number)) + return lambda frame_number: update_preview(get_video_frame(video_path, frame_number)) + def test_preview(create_test_preview): - frame = create_test_preview(preview.current_frame.get()) - preview.update(frame) + frame = create_test_preview(preview_frame_slider.get()) + update_preview(frame) + def update_slider(get_video_frame, create_test_preview, video_path, frames_amount): - preview.init_slider(frames_amount, update_slider_handler(get_video_frame, video_path)) - preview.set_preview_handler(lambda: preview_thread(test_preview(create_test_preview))) + init_slider(frames_amount, update_slider_handler(get_video_frame, video_path)) + set_preview_handler(lambda: preview_thread(lambda: test_preview(create_test_preview))) def analyze_target(select_target_handler: Callable[[str], Tuple[int, Any]], target_path: tk.StringVar, frames_amount: tk.IntVar): @@ -118,7 +115,7 @@ def analyze_target(select_target_handler: Callable[[str], Tuple[int, Any]], targ amount, frame = select_target_handler(path) frames_amount.set(amount) preview_target(frame) - preview.update(frame) + update_preview(frame) def select_target(select_target_handler: Callable[[str], Tuple[int, Any]], target_path: tk.StringVar, frames_amount: tk.IntVar): @@ -136,6 +133,7 @@ def save_file(save_file_handler: Callable[[str], None], target_path: str): return save_file_handler(asksaveasfilename(initialfile=filename, defaultextension=ext, filetypes=[("All Files","*.*"),("Videos","*.mp4")])) return None + def toggle_all_faces(toggle_all_faces_handler: Callable[[int], None], variable: tk.IntVar): if toggle_all_faces_handler: return lambda: toggle_all_faces_handler(variable.get()) @@ -202,13 +200,14 @@ def preview_thread(thread_function): def open_preview_window(get_video_frame, target_path): - if (preview.visible): - preview.hide() + if preview_visible.get(): + hide_preview() else: - preview.show() + show_preview() if target_path: frame = get_video_frame(target_path) - preview.update(frame) + update_preview(frame) + def preview_face(path): img = Image.open(path) @@ -217,6 +216,7 @@ def preview_face(path): face_label.configure(image=photo_img) face_label.image = photo_img + def preview_target(frame): img = Image.fromarray(frame) img = img.resize((180, 180), Image.ANTIALIAS) @@ -224,10 +224,12 @@ def preview_target(frame): target_label.configure(image=photo_img) target_label.image = photo_img + def update_status_label(value): status_label["text"] = value window.update() + def init( initial_values: dict, select_face_handler: Callable[[str], None], @@ -240,7 +242,7 @@ def init( get_video_frame: Callable[[str, int], None], create_test_preview: Callable[[int], Any], ): - global window, preview, face_label, target_label, status_label + global window, preview, preview_visible, face_label, target_label, status_label window = tk.Tk() window.geometry("600x700") @@ -248,11 +250,12 @@ def init( window.configure(bg="#2d3436") window.resizable(width=False, height=False) + preview_visible = tk.BooleanVar(window, False) target_path = tk.StringVar() frames_amount = tk.IntVar() # Preview window - preview = PreviewWindow(window) + preview = create_preview(window) # Contact information support_link = tk.Label(window, text="Donate to project <3", fg="#fd79a8", bg="#2d3436", cursor="hand2", font=("Arial", 8)) @@ -298,7 +301,7 @@ def init( frames_checkbox.place(x=60,y=450,width=240,height=31) # Start button - start_button = create_button(window, "Start", lambda: [save_file(save_file_handler, target_path.get()), preview_thread(start)]) + start_button = create_button(window, "Start", lambda: [save_file(save_file_handler, target_path.get()), preview_thread(lambda: start(update_preview))]) start_button.place(x=170,y=560,width=120,height=49) # Preview button From 0d624e8a9b94d90d720d1f19719498407505c0cf Mon Sep 17 00:00:00 2001 From: "K1llM@n" Date: Sat, 3 Jun 2023 17:57:40 +0300 Subject: [PATCH 9/9] Fix frames size --- roop/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roop/ui.py b/roop/ui.py index 52d2e9f..d8891fb 100644 --- a/roop/ui.py +++ b/roop/ui.py @@ -263,12 +263,12 @@ def init( support_link.bind("", lambda e: webbrowser.open("https://github.com/sponsors/s0md3v")) left_frame = tk.Frame(window) - left_frame.place(x=60, y=100) + left_frame.place(x=60, y=100, width=180, height=180) face_label = tk.Label(left_frame) face_label.pack(fill='both', side='top', expand=True) right_frame = tk.Frame(window) - right_frame.place(x=360, y=100) + right_frame.place(x=360, y=100, width=180, height=180) target_label = tk.Label(right_frame) target_label.pack(fill='both', side='top', expand=True)