Refactor UI

This commit is contained in:
henryruhs
2023-06-07 00:00:26 +02:00
parent 4194584854
commit f77df69553
6 changed files with 145 additions and 345 deletions

View File

@@ -13,7 +13,7 @@ def get_face_analyser() -> Any:
return FACE_ANALYSER return FACE_ANALYSER
def get_face_single(image_data) -> Any: def get_one_face(image_data) -> Any:
face = get_face_analyser().get(image_data) face = get_face_analyser().get(image_data)
try: try:
return min(face, key=lambda x: x.bbox[0]) return min(face, key=lambda x: x.bbox[0])
@@ -21,7 +21,7 @@ def get_face_single(image_data) -> Any:
return None return None
def get_face_many(image_data) -> Any: def get_many_faces(image_data) -> Any:
try: try:
return get_face_analyser().get(image_data) return get_face_analyser().get(image_data)
except IndexError: except IndexError:

View File

@@ -24,7 +24,7 @@ import roop.globals
import roop.ui as ui import roop.ui as ui
from roop.swapper import process_video, process_img from roop.swapper import process_video, process_img
from roop.utilities import has_image_extention, is_image, is_video, detect_fps, create_video, extract_frames, get_temp_frames_paths, restore_audio, create_temp, move_temp, clean_temp from roop.utilities import has_image_extention, is_image, is_video, detect_fps, create_video, extract_frames, get_temp_frames_paths, restore_audio, create_temp, move_temp, clean_temp
from roop.analyser import get_face_single from roop.analyser import get_one_face
if 'ROCMExecutionProvider' in roop.globals.providers: if 'ROCMExecutionProvider' in roop.globals.providers:
del torch del torch
@@ -41,11 +41,11 @@ def parse_args() -> None:
parser.add_argument('--keep-fps', help='maintain original fps', dest='keep_fps', action='store_true', default=False) parser.add_argument('--keep-fps', help='maintain original fps', dest='keep_fps', action='store_true', default=False)
parser.add_argument('--keep-audio', help='maintain original audio', dest='keep_audio', action='store_true', default=True) parser.add_argument('--keep-audio', help='maintain original audio', dest='keep_audio', action='store_true', default=True)
parser.add_argument('--keep-frames', help='keep frames directory', dest='keep_frames', action='store_true', default=False) parser.add_argument('--keep-frames', help='keep frames directory', dest='keep_frames', action='store_true', default=False)
parser.add_argument('--all-faces', help='swap all faces in frame', dest='all_faces', action='store_true', default=False) parser.add_argument('--many-faces', help='swap every face in the frame', dest='many_faces', action='store_true', default=False)
parser.add_argument('--max-memory', help='maximum amount of RAM in GB to be used', dest='max_memory', type=int) parser.add_argument('--max-memory', help='maximum amount of RAM in GB to be used', dest='max_memory', type=int)
parser.add_argument('--cpu-cores', help='number of CPU cores to use', dest='cpu_cores', type=int, default=max(psutil.cpu_count() / 2, 1)) parser.add_argument('--cpu-cores', help='number of CPU cores to use', dest='cpu_cores', type=int, default=max(psutil.cpu_count() / 2, 1))
parser.add_argument('--gpu-threads', help='number of threads to be use for the GPU', dest='gpu_threads', type=int, default=8) parser.add_argument('--gpu-threads', help='number of threads to be use for the GPU', dest='gpu_threads', type=int, default=8)
parser.add_argument('--gpu-vendor', help='select your GPU vendor', dest='gpu_vendor', choices=['apple', 'amd', 'intel', 'nvidia']) parser.add_argument('--gpu-vendor', help='select your GPU vendor', dest='gpu_vendor', choices=['apple', 'amd', 'nvidia'])
args = parser.parse_known_args()[0] args = parser.parse_known_args()[0]
@@ -56,7 +56,7 @@ def parse_args() -> None:
roop.globals.keep_fps = args.keep_fps roop.globals.keep_fps = args.keep_fps
roop.globals.keep_audio = args.keep_audio roop.globals.keep_audio = args.keep_audio
roop.globals.keep_frames = args.keep_frames roop.globals.keep_frames = args.keep_frames
roop.globals.all_faces = args.all_faces roop.globals.many_faces = args.many_faces
if args.cpu_cores: if args.cpu_cores:
roop.globals.cpu_cores = int(args.cpu_cores) roop.globals.cpu_cores = int(args.cpu_cores)
@@ -154,7 +154,7 @@ def start() -> None:
elif not roop.globals.target_path or not os.path.isfile(roop.globals.target_path): elif not roop.globals.target_path or not os.path.isfile(roop.globals.target_path):
update_status('Please select a video/image target!') update_status('Please select a video/image target!')
return return
test_face = get_face_single(cv2.imread(roop.globals.source_path)) test_face = get_one_face(cv2.imread(roop.globals.source_path))
if not test_face: if not test_face:
update_status('No face detected in source image. Please try with another one!') update_status('No face detected in source image. Please try with another one!')
return return
@@ -217,5 +217,5 @@ def run() -> None:
if roop.globals.headless: if roop.globals.headless:
start() start()
else: else:
window = ui.init(start) window = ui.init(start, destroy)
window.mainloop() window.mainloop()

View File

@@ -6,7 +6,7 @@ output_path = None
keep_fps = None keep_fps = None
keep_audio = None keep_audio = None
keep_frames = None keep_frames = None
all_faces = None many_faces = None
cpu_cores = None cpu_cores = None
gpu_threads = None gpu_threads = None
gpu_vendor = None gpu_vendor = None

View File

@@ -5,7 +5,7 @@ import cv2
import insightface import insightface
import threading import threading
import roop.globals import roop.globals
from roop.analyser import get_face_single, get_face_many from roop.analyser import get_one_face, get_many_faces
FACE_SWAPPER = None FACE_SWAPPER = None
THREAD_LOCK = threading.Lock() THREAD_LOCK = threading.Lock()
@@ -27,20 +27,20 @@ def swap_face_in_frame(source_face, target_face, frame):
def process_faces(source_face, target_frame): def process_faces(source_face, target_frame):
if roop.globals.all_faces: if roop.globals.many_faces:
many_faces = get_face_many(target_frame) many_faces = get_many_faces(target_frame)
if many_faces: if many_faces:
for face in many_faces: for face in many_faces:
target_frame = swap_face_in_frame(source_face, face, target_frame) target_frame = swap_face_in_frame(source_face, face, target_frame)
else: else:
face = get_face_single(target_frame) face = get_one_face(target_frame)
if face: if face:
target_frame = swap_face_in_frame(source_face, face, target_frame) target_frame = swap_face_in_frame(source_face, face, target_frame)
return target_frame return target_frame
def process_frames(source_img, frame_paths, progress=None): def process_frames(source_img, frame_paths, progress=None):
source_face = get_face_single(cv2.imread(source_img)) source_face = get_one_face(cv2.imread(source_img))
for frame_path in frame_paths: for frame_path in frame_paths:
frame = cv2.imread(frame_path) frame = cv2.imread(frame_path)
try: try:
@@ -77,9 +77,9 @@ def multi_process_frame(source_img, frame_paths, progress):
def process_img(source_img, target_path, output_file): def process_img(source_img, target_path, output_file):
frame = cv2.imread(target_path) frame = cv2.imread(target_path)
face = get_face_single(frame) target_frame = get_one_face(frame)
source_face = get_face_single(cv2.imread(source_img)) source_face = get_one_face(cv2.imread(source_img))
result = get_face_swapper().get(frame, face, source_face, paste_back=True) result = get_face_swapper().get(frame, target_frame, source_face, paste_back=True)
cv2.imwrite(output_file, result) cv2.imwrite(output_file, result)

View File

@@ -1,358 +1,157 @@
import tkinter as tk import tkinter as tk
from typing import Any, Callable, Tuple from tkinter import filedialog
from typing import Callable, Any
import cv2 import cv2
from PIL import Image, ImageTk, ImageOps from PIL import Image, ImageTk, ImageOps
import webbrowser
from tkinter import filedialog
from tkinter.filedialog import asksaveasfilename
import threading
import roop.globals import roop.globals
from roop.analyser import get_face_single
from roop.swapper import process_faces
from roop.utilities import is_image from roop.utilities import is_image
max_preview_size = 800 PRIMARY_COLOR = '#2d3436'
SECONDARY_COLOR = '#74b9ff'
TERTIARY_COLOR = '#f1c40f'
ACCENT_COLOR = '#2ecc71'
WINDOW_HEIGHT = 700
WINDOW_WIDTH = 600
MAX_PREVIEW_SIZE = 800
def create_preview(parent): def init(start: Callable, destroy: Callable):
global preview_image_frame, preview_frame_slider, test_button global WINDOW, source_label, target_label, status_label
preview_window = tk.Toplevel(parent) WINDOW = tk.Tk()
# Override close button WINDOW.minsize(WINDOW_WIDTH, WINDOW_HEIGHT)
preview_window.protocol("WM_DELETE_WINDOW", hide_preview) WINDOW.title('roop')
preview_window.withdraw() WINDOW.configure(bg=PRIMARY_COLOR)
preview_window.title("Preview") WINDOW.option_add('*Font', ('Arial', 11))
preview_window.configure(bg="red")
preview_window.resizable(width=False, height=False)
frame = tk.Frame(preview_window, background="#2d3436") source_label = tk.Label(bg=PRIMARY_COLOR)
frame.pack(fill='both', side='left', expand='True') source_label.place(relx=0.1, rely=0.1, relwidth=0.3, relheight=0.25)
# Preview image
preview_image_frame = tk.Label(frame)
preview_image_frame.pack(side='top')
# Bottom frame target_label = tk.Label(bg=PRIMARY_COLOR)
buttons_frame = tk.Frame(frame, background="#2d3436") target_label.place(relx=0.6, rely=0.1, relwidth=0.3, relheight=0.25)
buttons_frame.pack(fill='both', side='bottom')
current_frame = tk.IntVar() source_button = create_primary_button(WINDOW, 'Select a face', lambda: select_source_path())
preview_frame_slider = tk.Scale( source_button.place(relx=0.1, rely=0.4, relwidth=0.3, relheight=0.1)
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) target_button = create_primary_button(WINDOW, 'Select a target', lambda: select_target_path())
test_button.pack(side='right', fill='y') target_button.place(relx=0.6, rely=0.4, relwidth=0.3, relheight=0.1)
return preview_window
keep_fps_value = tk.BooleanVar(value=roop.globals.keep_fps)
keep_fps_checkbox = create_checkbox(WINDOW, 'Limit to 30 fps', keep_fps_value, lambda: setattr(roop.globals, 'keep_fps', not roop.globals.keep_fps))
keep_fps_checkbox.place(relx=0.1, rely=0.6)
keep_frames_value = tk.BooleanVar(value=roop.globals.keep_frames)
keep_frames_checkbox = create_checkbox(WINDOW, 'Keep frames dir', keep_frames_value, lambda: setattr(roop.globals, 'keep_frames', keep_frames_value.get()))
keep_frames_checkbox.place(relx=0.1, rely=0.65)
keep_audio_value = tk.BooleanVar(value=roop.globals.keep_audio)
keep_audio_checkbox = create_checkbox(WINDOW, 'Keep original audio', keep_frames_value, lambda: setattr(roop.globals, 'keep_audio', keep_audio_value.get()))
keep_audio_checkbox.place(relx=0.6, rely=0.6)
many_faces_value = tk.BooleanVar(value=roop.globals.many_faces)
many_faces_checkbox = create_checkbox(WINDOW, 'Replace all faces', many_faces_value, lambda: setattr(roop.globals, 'many_faces', keep_audio_value.get()))
many_faces_checkbox.place(relx=0.6, rely=0.65)
start_button = create_secondary_button(WINDOW, 'Start', lambda: select_output_path(start))
start_button.place(relx=0.15, rely=0.75, relwidth=0.2, relheight=0.05)
stop_button = create_secondary_button(WINDOW, 'Destroy', lambda: destroy())
stop_button.place(relx=0.4, rely=0.75, relwidth=0.2, relheight=0.05)
preview_button = create_secondary_button(WINDOW, 'Preview', lambda: None)
preview_button.place(relx=0.65, rely=0.75, relwidth=0.2, relheight=0.05)
preview_button.config(state='disabled')
status_label = tk.Label(WINDOW, justify='center', text='Status: UI under heavy development, more features will soon be (re)added', fg=ACCENT_COLOR, bg=PRIMARY_COLOR)
status_label.place(relx=0.1, rely=0.9)
return WINDOW
def show_preview(): def create_primary_button(parent: Any, text: str, command: Callable) -> tk.Button:
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)
img = ImageOps.contain(img, (max_preview_size, max_preview_size), Image.LANCZOS)
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]):
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: update_preview(get_video_frame(video_path, frame_number))
def test_preview(create_test_preview):
frame = create_test_preview(preview_frame_slider.get())
update_preview(frame)
def update_slider(get_video_frame, create_test_preview, video_path, frames_amount):
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):
path = filedialog.askopenfilename(title="Select a target")
target_path.set(path)
amount, frame = select_target_handler(path)
frames_amount.set(amount)
preview_target(frame)
update_preview(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_image(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( return tk.Button(
parent, parent,
text=text, text=text,
command=command, command=command,
bg="#f1c40f", bg=PRIMARY_COLOR,
relief="flat", fg=SECONDARY_COLOR,
borderwidth=0, relief='flat',
highlightthickness=4,
highlightbackground=SECONDARY_COLOR,
activebackground=SECONDARY_COLOR,
borderwidth=4
)
def create_secondary_button(parent: Any, text: str, command: Callable) -> tk.Button:
return tk.Button(
parent,
text=text,
command=command,
bg=TERTIARY_COLOR,
relief='flat',
borderwidth=0,
highlightthickness=0 highlightthickness=0
) )
def create_background_button(parent, text, command): def create_checkbox(parent: Any, text: str, variable: tk.BooleanVar, command: Callable) -> tk.Checkbutton:
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( return tk.Checkbutton(
parent, parent,
anchor="w", text=text,
relief="groove", variable=variable,
activebackground="#2d3436", command=command,
activeforeground="#74b9ff", relief='flat',
selectcolor="black", bg=PRIMARY_COLOR,
text=text, activebackground=PRIMARY_COLOR,
fg="#dfe6e9", activeforeground=SECONDARY_COLOR,
borderwidth=0, selectcolor=PRIMARY_COLOR,
highlightthickness=0, fg=SECONDARY_COLOR,
bg="#2d3436", borderwidth=0,
variable=variable, highlightthickness=0
command=command
) )
def preview_thread(thread_function): def update_status(text: str) -> None:
threading.Thread(target=thread_function).start() status_label['text'] = text
WINDOW.update()
def open_preview_window(get_video_frame, target_path): def select_source_path():
if preview_visible.get(): path = filedialog.askopenfilename(title='Select a face')
hide_preview() if is_image(path):
roop.globals.source_path = path
image = render_frame_image(roop.globals.source_path)
source_label.configure(image=image)
source_label.image = image
else: else:
show_preview() roop.globals.source_path = None
if target_path: source_label.configure(image=None)
frame = get_video_frame(target_path) source_label.image = None
update_preview(frame)
def preview_face(path): def select_target_path():
img = Image.open(path) path = filedialog.askopenfilename(title='Select a target')
img = ImageOps.fit(img, (180, 180), Image.LANCZOS) if is_image(path):
photo_img = ImageTk.PhotoImage(img) roop.globals.target_path = path
face_label.configure(image=photo_img) image = render_frame_image(roop.globals.target_path)
face_label.image = photo_img target_label.configure(image=image)
target_label.image = image
else:
roop.globals.target_path = None
target_label.configure(image=None)
target_label.image = None
def preview_target(frame): def select_output_path(start):
img = Image.fromarray(frame) roop.globals.output_path = filedialog.askdirectory(title='Select a target')
img = ImageOps.fit(img, (180, 180), Image.LANCZOS) start()
photo_img = ImageTk.PhotoImage(img)
target_label.configure(image=photo_img)
target_label.image = photo_img
def update_status(value): def render_frame_image(image_path: str) -> ImageTk.PhotoImage:
status_label["text"] = value image = Image.open(image_path)
window.update() image = ImageOps.fit(image, (200, 200), Image.LANCZOS)
return ImageTk.PhotoImage(image)
def init(start: Callable[[], None]):
global window, preview, preview_visible, 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)
preview_visible = tk.BooleanVar(window, False)
target_path = tk.StringVar()
frames_amount = tk.IntVar()
# Preview 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))
support_link.place(x=180,y=20,width=250,height=30)
support_link.bind("<Button-1>", lambda e: webbrowser.open("https://github.com/sponsors/s0md3v"))
left_frame = tk.Frame(window)
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, width=180, height=180)
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, roop.globals.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 roop.globals.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, roop.globals.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(lambda: start(update_preview))])
start_button = create_button(window, "Start", lambda: [save_file(save_file_handler, target_path.get()), 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
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():
update_status('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():
update_status('Error opening video file')
return 0
amount_of_frames = cap.get(cv2.CAP_PROP_FRAME_COUNT)
ret, frame = cap.read()
if ret:
frame = get_video_frame(video_path)
cap.release()
return (amount_of_frames, frame)
def select_face_handler(path: str):
roop.globals.source_path = path
def select_target_handler(target_path: str) -> None:
roop.globals.target_path = target_path
return preview_video(roop.globals.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):
roop.globals.keep_fps = int(value != 1)
def toggle_keep_frames_handler(value: int):
roop.globals.keep_frames = value
def save_file_handler(path: str):
roop.globals.output_path = path
def create_test_preview(frame_number):
return process_faces(
get_face_single(cv2.imread(roop.globals.source_path)),
get_video_frame(roop.globals.target_path, frame_number)
)

View File

@@ -70,13 +70,13 @@ def clean_temp(target_path: str) -> None:
def has_image_extention(image_path: str) -> bool: def has_image_extention(image_path: str) -> bool:
return image_path.lower().endswith(('png', 'jpg', 'jpeg', 'bmp')) return image_path.lower().endswith(('png', 'jpg', 'jpeg'))
def is_image(path: str) -> bool: def is_image(image_path: str) -> bool:
if os.path.isfile(path): if image_path and os.path.isfile(image_path):
try: try:
image = Image.open(path) image = Image.open(image_path)
image.verify() image.verify()
return True return True
except Exception: except Exception:
@@ -84,10 +84,11 @@ def is_image(path: str) -> bool:
return False return False
def is_video(path: str) -> bool: def is_video(video_path: str) -> bool:
try: try:
run_ffmpeg(['-v', 'error', '-i', path, '-f', 'null', '-']) if video_path and os.path.isfile(video_path):
return True run_ffmpeg(['-v', 'error', '-i', video_path, '-f', 'null', '-'])
return True
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
pass pass
return False return False