From a6c9eb3efeb474812b1077fda6e66af1807b1a30 Mon Sep 17 00:00:00 2001 From: Felipe Daragon Date: Thu, 10 Apr 2025 22:08:59 +0100 Subject: [PATCH] Add NeoRefacer code and updates --- LICENSE | 31 ++- README.md | 148 ++++++++---- app.py | 388 ++++++++++++++++++++++++++------ demo.jpg | Bin 0 -> 38265 bytes docker/Dockerfile.nvidia | 20 -- docker/run.sh | 13 -- icon.png | Bin 0 -> 47295 bytes notebooks/Refacer_colab.ipynb | 65 ------ refacer.py | 410 ++++++++++++++++++++++------------ refacer_bulk.py | 75 +++++++ refacer_video.py | 41 ---- requirements-COREML.txt | 15 +- requirements-CPU.txt | 17 +- requirements-GPU.txt | 17 +- weights/inswapper/.gitkeep | 0 15 files changed, 817 insertions(+), 423 deletions(-) create mode 100644 demo.jpg delete mode 100644 docker/Dockerfile.nvidia delete mode 100755 docker/run.sh create mode 100644 icon.png delete mode 100644 notebooks/Refacer_colab.ipynb create mode 100644 refacer_bulk.py delete mode 100644 refacer_video.py create mode 100644 weights/inswapper/.gitkeep diff --git a/LICENSE b/LICENSE index 229ef4a..b3c04f6 100644 --- a/LICENSE +++ b/LICENSE @@ -23,9 +23,9 @@ furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -- You may only use this Software with content (such as images) for which you -have the necessary rights and permissions. Unauthorized use of third-party -content is strictly prohibited. +- You may only use this Software with content (such as images and videos) +for which you have the necessary rights and permissions. Unauthorized use of +third-party content is strictly prohibited. - This Software is intended for educational and research purposes only. Use of this Software for malicious purposes, including but not limited to identity @@ -69,10 +69,33 @@ If you wish to use this project solely under the MIT License (for example, for commercial purposes), you **must remove** the `codeformer` component. Please follow the instructions provided below: -- Remove the subdirectories basicsr, facelib and weights. +- Remove the subdirectories basicsr and facelib +- Remove within weights subdirectory Codeformer and facelib. - Remove codeformer_wrapper.py - Edit refacer.py and remove the import: codeformer_wrapper import enhance_image - Within def reface_image, comment the line: output_path = enhance_image(output_path) - That's all! Failure to remove `codeformer` when required may violate the terms of its license. + +About User Outputs: +The outputs generated by this software (such as refaced images or videos) are not +subject to the CC BY-NC-SA license and may be used freely, including for commercial +purposes, regardless of whether the optional codeformer component is used. + +Explanation: + +Codeformer is a model that processes images for face enhancement. +It does not embed its own visible content into the output. + +This is very different from a case where licensed assets (such as textures, +overlays, characters, backgrounds, or artwork) appear visibly in the output. + +Codeformer simply modifies the input image without adding original copyrighted +material. + +Therefore, output images are not derivative works of Codeformer +and are not bound by the NonCommercial restrictions of its license. + +Only the Codeformer model code and weights themselves are under CC BY-NC-SA 4.0 + — not the results produced through their use. diff --git a/README.md b/README.md index e49d666..f4f4e69 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,63 @@ -# Refacer: One-Click Deepfake Multi-Face Swap Tool + -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/xaviviro/refacer/blob/master/notebooks/Refacer_colab.ipynb) +# NeoRefacer: Images. GIFs. Full-length videos. -👉 [Watch demo on Youtube](https://youtu.be/mXk1Ox7B244) +In a future where identity flows like data and reality is just another layer, NeoRefacer gives you the power to transform. -Refacer, a simple tool that allows you to create deepfakes with multiple faces with just one click! This project was inspired by [Roop](https://github.com/s0md3v/roop) and is powered by the excellent [Insightface](https://github.com/deepinsight/insightface). Refacer requires no training - just one photo and you're ready to go. +Images. GIFs. Full-length videos. -:warning: Please, before using the code from this repository, make sure to read the [disclaimer](https://github.com/xaviviro/refacer/tree/main#disclaimer). +All yours to reface and reimagine - with a single pulse of electricity. -## Demonstration +Evolved from the foundations of the [Refacer](https://github.com/xaviviro/refacer) project, NeoRefacer is a next-generation, fully open-source refacer. -![demonstration](demo.gif) + -[![Watch the video](https://img.youtube.com/vi/mXk1Ox7B244/maxresdefault.jpg)](https://youtu.be/mXk1Ox7B244) +1. Clone the repository. +2. Spin up the environment. +3. Launch the local interface. +4. Control the face of tomorrow. +[OFFICIAL WEBSITE](https://www.mechas.ai/projects-neorefacer.php) + +## Core DNA of NeoRefacer +* **Instant Identity Shift** - Swap faces in images, GIFs, and movies faster than your neural implants can blink. +* **Overclocked Engine** - Optimized for CPU rebels and GPU warlords. +* **Feature Film Reface** - Not just TikToks. Full two-hour cinematic overthrows. +* **Targeted Strike Modes** - Single-face raids, multi-face takeovers, or precision-targeted matchups. +* **Bulk Warfare** - Mass-process entire image archives with industrial-scale automation. +* **Neural Enhancement Suite** - Automatic image enhancement. + +## Use Cases + +* **Entertainment**: Rewrite memories, remix movies, animate the past. +* **Education**: Step into history, speak through new faces. +* **Content Creation**: Craft AI doubles, weave digital alter-egos. +* **Business/Marketing**: Personalize ads inside the algorithmic flood. +* **Niche Fun**: Trace ancestral echoes, forge RPG legends, hijack fame. + +## What's New (Since Refacer) + +* Image, GIF and Video reface modes +* Significantly faster processing +* Automatic image enhancing (Image mode) +* Improved video output quality +* Support for videos that have long duration +* Preview generation for videos and GIFs (skips 90% of frames) +* Multiple replacement modes: + * **Single Face** (Fast): all faces are replaced by a single face. Ideal for images, GIFs or videos with a single face + * **Multiple Faces** (Fast): faces are replaced by the faces you provide based on their order from left to right + * **Faces by Match** (Slower): faces are first detected and replaced by the faces you provide. +* Improved GPU detection +* Uses local Gradio cache with auto-cleanup on startup +* Includes a bulk image refacer utility (refacer_bulk.py) + +NeoRefacer, just like the original Refacer project, requires no training - just one photo and you're ready to go. + +:warning: Please, before using the code from this repository, make sure to read the [LICENSE](https://github.com/MechasAI/NeoRefacer/blob/main/LICENSE). ## System Compatibility -Refacer has been thoroughly tested on the following operating systems: +NeoRefacer has been tested on the following operating systems: | Operating System | CPU Support | GPU Support | | ---------------- | ----------- | ----------- | @@ -29,59 +69,59 @@ The application is compatible with both CPU and GPU (Nvidia CUDA) environments, :warning: Please note, we do not recommend using `onnxruntime-silicon` on MacOSX due to an apparent issue with memory management. If you manage to compile `onnxruntime` for Silicon, the program is prepared to use CoreML. -## Prerequisites - -Ensure that you have `ffmpeg` installed and correctly configured. There are many guides available on the internet to help with this. Here are a few (note: I did not create these guides): - -- [How to Install FFmpeg](https://www.hostinger.com/tutorials/how-to-install-ffmpeg) - - ## Installation -Refacer has been tested and is known to work with Python 3.10.9, but it is likely to work with other Python versions as well. It is recommended to use a virtual environment for setting up and running the project to avoid potential conflicts with other Python packages you may have installed. +NeoRefacer has been tested and is known to work with Python 3.11.11, but it is likely to work with other Python versions as well. It is recommended to use a virtual environment, such as [Conda](https://www.anaconda.com/download), for setting up and running the project to avoid potential conflicts with other Python packages you may have installed. -Follow these steps to install Refacer: +Follow these steps to install Refacer and its dependencies: -1. Clone the repository: ```bash - git clone https://github.com/xaviviro/refacer.git - cd refacer - ``` + # Check if ffmpeg is available (if not, you might to download it and add it to your PATH) + # Windows: download ffmpeg-git-essentials.7z from https://www.gyan.dev/ffmpeg/builds/ + # Other systems: see a tutorial https://www.hostinger.com/tutorials/how-to-install-ffmpeg + ffmpeg -2. Download the Insightface model: - You can manually download the model created by Insightface from this [link](https://huggingface.co/deepinsight/inswapper/resolve/main/inswapper_128.onnx) and add it to the project folder. Alternatively, if you have `wget` installed, you can use the following command: - ```bash - wget --content-disposition https://huggingface.co/deepinsight/inswapper/resolve/main/inswapper_128.onnx - ``` - -3. Install dependencies: - - * For CPU (compatible with Windows, MacOSX, and Linux): - ```bash - pip install -r requirements.txt - ``` - - * For GPU (compatible with Windows and Linux only, requires a NVIDIA GPU with CUDA and its libraries): - ```bash + # Clone the repository + git clone https://github.com/MechasAI/NeoRefacer.git + cd NeoRefacer + + # Windows: Create the environment + conda create -n neorefacer-env python=3.11 nomkl conda-forge::vs2015_runtime + + # Linux: Create the environment + conda create -n neorefacer-env python=3.11 nomkl + + # MacOS: Create the environment + conda create -n neorefacer-env python=3.11 + + # Activate the environment + conda activate neorefacer-env + + # Instal the dependencies: + # For CPU only (compatible with Windows, MacOSX, and Linux) + pip install -r requirements-CPU.txt + + # For NVIDIA RTX GPU only (compatible with Windows and Linux only, requires a NVIDIA GPU with CUDA and its libraries) pip install -r requirements-GPU.txt - ``` - - * For CoreML (compatible with MacOSX, requires Silicon architecture): - ```bash + + # For CoreML only (compatible with MacOSX, requires Silicon architecture): pip install -r requirements-COREML.txt ``` - For more information on installing the CUDA necessary to use `onnxruntime-gpu`, please refer directly to the official [ONNX Runtime repository](https://github.com/microsoft/onnxruntime/). +For NVIDIA GPU, make sure you have both NVIDIA GPU Computing Toolkit and NVIDIA CUDNN installed. The onnxruntime-gpu version must match your version of CUDA. This example uses onnxruntime-gpu 1.21.0, which is compatible with CUDA 12.6 and CUDNN 9.4 - Refacer.py is pre-loading both libraries. Remember to update the paths if needed in refacer.py if you have different location or versions. -For more details on using the Insightface model, you can refer to their [example](https://github.com/deepinsight/insightface/tree/master/examples/in_swapper). +For more information on installing the CUDA necessary to use `onnxruntime-gpu`, please refer directly to the official [ONNX Runtime repository](https://github.com/microsoft/onnxruntime/). ## Usage -Once you have successfully installed Refacer and its dependencies, you can run the application using the following command: +Once you have successfully installed NeoRefacer and its dependencies, you can run the application using the following command: ```bash python app.py + +# Alternatively, if you need to force CPU mode +python app.py --force_cpu ``` Then, open your web browser and navigate to the following address: @@ -90,21 +130,31 @@ Then, open your web browser and navigate to the following address: http://127.0.0.1:7680 ``` +A bulk refacer utility is also available and can be called using the following command: + +```bash +python refacer_bulk.py --input_path ./input --dest_face myface.jpg +``` + ## Questions? -If you have any questions or issues, feel free to [open an issue](https://github.com/xaviviro/refacer/issues/new) or submit a pull request. +If you have any questions or issues, feel free to [open an issue](https://github.com/MechasAI/NeoRefacer/issues/new). -## Recognition Module +## Third-Party Modules The `recognition` folder in this repository is derived from Insightface's GitHub repository. You can find the original source code here: [Insightface Recognition Source Code](https://github.com/deepinsight/insightface/tree/master/web-demos/src_recognition) -This module is used for recognizing and handling face data within the Refacer application, enabling its powerful deepfake capabilities. We are grateful to Insightface for their work and for making their code available. +This module is used for recognizing and handling face data within the NeoRefacer application. We are grateful to Insightface for their work and for making their code available. +The image enhancing capability is based on [codeformer](https://github.com/felipedaragon/codeformer/) (by Shangchen Zhou) and [BasicSR](https://github.com/XPixelGroup/BasicSR). It also borrow some codes from [Unleashing Transformers](https://github.com/samb-t/unleashing-transformers), [YOLOv5-face](https://github.com/deepcam-cn/yolov5-face), and [FaceXLib](https://github.com/xinntao/facexlib). Thanks for their awesome works. ## License -Note: This project uses a Custom MIT License. See LICENSE for full terms. +Note: This project uses a Custom MIT License, not allowing commercial use of the code unless you remove the image enhancing component. The output (refaced image or video) is not restricted by CC BY-NC-SA and may be used including for commercial purposes. See [LICENSE](https://github.com/MechasAI/NeoRefacer/blob/main/LICENSE) for full terms. -The generated content does not represent the views, beliefs, or attitudes of the authors of this Software. Please use the Software and its outputs responsibly, ethically, and with respect toward others. +The generated content (refaced images or videos) does not represent the views, beliefs, or attitudes of the authors of this Software. Please use the Software and its outputs responsibly, ethically, and with respect toward others. + +## Credits +Special thanks to Roberto Marc for the additional testing. diff --git a/app.py b/app.py index 5ba8ec1..f76dd0d 100644 --- a/app.py +++ b/app.py @@ -1,93 +1,339 @@ +import os +os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" + import gradio as gr from refacer import Refacer import argparse import ngrok +import imageio +import numpy as np +from PIL import Image +import tempfile +import base64 +import pyfiglet +import shutil +import time +print("\033[94m" + pyfiglet.Figlet(font='slant').renderText("NeoRefacer") + "\033[0m") + +def cleanup_temp(folder_path): + try: + shutil.rmtree(folder_path) + print("Gradio cache cleared successfully.") + except Exception as e: + print(f"Error: {e}") + +# Prepare temp folder +os.environ["GRADIO_TEMP_DIR"] = "./tmp" +if os.path.exists("./tmp"): + cleanup_temp(os.environ['GRADIO_TEMP_DIR']) +if not os.path.exists("./tmp"): + os.makedirs("./tmp") + +# Parse arguments parser = argparse.ArgumentParser(description='Refacer') -parser.add_argument("--max_num_faces", type=int, help="Max number of faces on UI", default=5) -parser.add_argument("--force_cpu", help="Force CPU mode", default=False, action="store_true") -parser.add_argument("--share_gradio", help="Share Gradio", default=False, action="store_true") -parser.add_argument("--server_name", type=str, help="Server IP address", default="127.0.0.1") -parser.add_argument("--server_port", type=int, help="Server port", default=7860) -parser.add_argument("--colab_performance", help="Use in colab for better performance", default=False,action="store_true") -parser.add_argument("--ngrok", type=str, help="Use ngrok", default=None) -parser.add_argument("--ngrok_region", type=str, help="ngrok region", default="us") +parser.add_argument("--max_num_faces", type=int, default=8) +parser.add_argument("--force_cpu", default=False, action="store_true") +parser.add_argument("--share_gradio", default=False, action="store_true") +parser.add_argument("--server_name", type=str, default="127.0.0.1") +parser.add_argument("--server_port", type=int, default=7860) +parser.add_argument("--colab_performance", default=False, action="store_true") +parser.add_argument("--ngrok", type=str, default=None) +parser.add_argument("--ngrok_region", type=str, default="us") args = parser.parse_args() -refacer = Refacer(force_cpu=args.force_cpu,colab_performance=args.colab_performance) +# Initialize +refacer = Refacer(force_cpu=args.force_cpu, colab_performance=args.colab_performance) +num_faces = args.max_num_faces -num_faces=args.max_num_faces +def create_dummy_image(): + dummy = Image.new('RGB', (1, 1), color=(255, 255, 255)) + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png") + dummy.save(temp_file.name) + return temp_file.name -# Connect to ngrok for ingress -def connect(token, port, options): - account = None - if token is None: - token = 'None' - else: - if ':' in token: - # token = authtoken:username:password - token, username, password = token.split(':', 2) - account = f"{username}:{password}" +def run_image(*vars): + image_path = vars[0] + origins = vars[1:(num_faces+1)] + destinations = vars[(num_faces+1):(num_faces*2)+1] + thresholds = vars[(num_faces*2)+1:-1] + face_mode = vars[-1] - # For all options see: https://github.com/ngrok/ngrok-py/blob/main/examples/ngrok-connect-full.py - if not options.get('authtoken_from_env'): - options['authtoken'] = token - if account: - options['basic_auth'] = account - - - try: - public_url = ngrok.connect(f"127.0.0.1:{port}", **options).url() - except Exception as e: - print(f'Invalid ngrok authtoken? ngrok connection aborted due to: {e}\n' - f'Your token: {token}, get the right one on https://dashboard.ngrok.com/get-started/your-authtoken') - else: - print(f'ngrok connected to localhost:{port}! URL: {public_url}\n' - 'You can use this link after the launch is complete.') - - -def run(*vars): - video_path=vars[0] - origins=vars[1:(num_faces+1)] - destinations=vars[(num_faces+1):(num_faces*2)+1] - thresholds=vars[(num_faces*2)+1:] + disable_similarity = (face_mode in ["Single Face", "Multiple Faces"]) + multiple_faces_mode = (face_mode == "Multiple Faces") faces = [] - for k in range(0,num_faces): - if origins[k] is not None and destinations[k] is not None: + for k in range(num_faces): + if destinations[k] is not None: faces.append({ - 'origin':origins[k], - 'destination':destinations[k], - 'threshold':thresholds[k] + 'origin': origins[k] if not multiple_faces_mode else None, + 'destination': destinations[k], + 'threshold': thresholds[k] if not multiple_faces_mode else 0.0 }) - return refacer.reface(video_path,faces) + return refacer.reface_image(image_path, faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode) -origin = [] -destination = [] -thresholds = [] +def run(*vars): + video_path = vars[0] + origins = vars[1:(num_faces+1)] + destinations = vars[(num_faces+1):(num_faces*2)+1] + thresholds = vars[(num_faces*2)+1:-2] + preview = vars[-2] + face_mode = vars[-1] + + disable_similarity = (face_mode in ["Single Face", "Multiple Faces"]) + multiple_faces_mode = (face_mode == "Multiple Faces") + + faces = [] + for k in range(num_faces): + if destinations[k] is not None: + faces.append({ + 'origin': origins[k] if not multiple_faces_mode else None, + 'destination': destinations[k], + 'threshold': thresholds[k] if not multiple_faces_mode else 0.0 + }) + + mp4_path, gif_path = refacer.reface(video_path, faces, preview=preview, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode) + return mp4_path, gif_path if gif_path else None + +def load_first_frame(filepath): + if filepath is None: + return None + frames = imageio.get_reader(filepath) + return frames.get_data(0) + +def extract_faces_auto(filepath, refacer_instance, max_faces=5, isvideo=False): + if filepath is None: + return [None] * max_faces + + # Check if video + if isvideo: + if os.path.getsize(filepath) > 5 * 1024 * 1024: # larger than 5MB + print("Video too large for auto-extract, skipping face extraction.") + return [None] * max_faces + + frame = load_first_frame(filepath) + if frame is None: + return [None] * max_faces + + # Create manual temp image inside ./tmp + temp_image_path = os.path.join("./tmp", f"temp_face_extract_{int(time.time() * 1000)}.png") + Image.fromarray(frame).save(temp_image_path) + + try: + faces = refacer_instance.extract_faces_from_image(temp_image_path, max_faces=max_faces) + output_faces = faces + [None] * (max_faces - len(faces)) + return output_faces + finally: + if os.path.exists(temp_image_path): + try: + os.remove(temp_image_path) + except Exception as e: + print(f"Warning: Could not delete temp file {temp_image_path}: {e}") + +def toggle_tabs_and_faces(mode, face_tabs, origin_faces): + if mode == "Single Face": + tab_updates = [gr.update(visible=(i == 0)) for i in range(len(face_tabs))] + origin_updates = [gr.update(visible=False) for _ in range(len(origin_faces))] + elif mode == "Multiple Faces": + tab_updates = [gr.update(visible=True) for _ in range(len(face_tabs))] + origin_updates = [gr.update(visible=False) for _ in range(len(origin_faces))] + else: + tab_updates = [gr.update(visible=True) for _ in range(len(face_tabs))] + origin_updates = [gr.update(visible=True) for _ in range(len(origin_faces))] + return tab_updates + origin_updates + +# --- UI --- + +theme = gr.themes.Base(primary_hue="blue", secondary_hue="cyan") + +with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo: + with open("icon.png", "rb") as f: + icon_data = base64.b64encode(f.read()).decode() + icon_html = f'' -with gr.Blocks() as demo: with gr.Row(): - gr.Markdown("# Refacer") - with gr.Row(): - video=gr.Video(label="Original video",format="mp4") - video2=gr.Video(label="Refaced video",interactive=False,format="mp4") + gr.Markdown(f""" +
+ {icon_html} + NeoRefacer +
+ """) - for i in range(0,num_faces): - with gr.Tab(f"Face #{i+1}"): - with gr.Row(): - origin.append(gr.Image(label="Face to replace")) - destination.append(gr.Image(label="Destination face")) - with gr.Row(): - thresholds.append(gr.Slider(label="Threshold",minimum=0.0,maximum=1.0,value=0.2)) - with gr.Row(): - button=gr.Button("Reface", variant="primary") + # --- IMAGE MODE --- + with gr.Tab("Image Mode"): + with gr.Row(): + image_input = gr.Image(label="Original image", type="filepath") + image_output = gr.Image(label="Refaced image", interactive=False, type="filepath") + + with gr.Row(): + face_mode_image = gr.Radio( + choices=["Single Face", "Multiple Faces", "Faces By Match"], + value="Single Face", + label="Replacement Mode" + ) + image_btn = gr.Button("Reface Image", variant="primary") + + origin_image, destination_image, thresholds_image, face_tabs_image = [], [], [], [] + + for i in range(num_faces): + with gr.Tab(f"Face #{i+1}") as tab: + with gr.Row(): + origin = gr.Image(label="Face to replace") + destination = gr.Image(label="Destination face") + threshold = gr.Slider(label="Threshold", minimum=0.0, maximum=1.0, value=0.2) + origin_image.append(origin) + destination_image.append(destination) + thresholds_image.append(threshold) + face_tabs_image.append(tab) + + face_mode_image.change( + fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_image, origin_image), + inputs=[face_mode_image], + outputs=face_tabs_image + origin_image + ) + + demo.load( + fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_image, origin_image), + inputs=None, + outputs=face_tabs_image + origin_image + ) + + image_btn.click( + fn=run_image, + inputs=[image_input] + origin_image + destination_image + thresholds_image + [face_mode_image], + outputs=[image_output] + ) + + image_input.change( + fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces), + inputs=image_input, + outputs=origin_image + ) + + # --- GIF MODE --- + with gr.Tab("GIF Mode"): + with gr.Row(): + gif_input = gr.File(label="Original GIF", file_types=[".gif"]) + gif_preview = gr.Video(label="GIF Preview", interactive=False) + gif_output = gr.Video(label="Refaced GIF (MP4)", interactive=False, format="mp4") + gif_file_output = gr.Image(label="Refaced GIF (GIF)", type="filepath") + + with gr.Row(): + face_mode_gif = gr.Radio( + choices=["Single Face", "Multiple Faces", "Faces By Match"], + value="Single Face", + label="Replacement Mode" + ) + gif_btn = gr.Button("Reface GIF", variant="primary") + + preview_checkbox_gif = gr.Checkbox(label="Preview Generation (skip 90% of frames)", value=False) + + origin_gif, destination_gif, thresholds_gif, face_tabs_gif = [], [], [], [] + + for i in range(num_faces): + with gr.Tab(f"Face #{i+1}") as tab: + with gr.Row(): + origin = gr.Image(label="Face to replace") + destination = gr.Image(label="Destination face") + threshold = gr.Slider(label="Threshold", minimum=0.0, maximum=1.0, value=0.2) + origin_gif.append(origin) + destination_gif.append(destination) + thresholds_gif.append(threshold) + face_tabs_gif.append(tab) + + face_mode_gif.change( + fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_gif, origin_gif), + inputs=[face_mode_gif], + outputs=face_tabs_gif + origin_gif + ) + + demo.load( + fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_gif, origin_gif), + inputs=None, + outputs=face_tabs_gif + origin_gif + ) + + gif_btn.click( + fn=lambda *args: run(*args), + inputs=[gif_input] + origin_gif + destination_gif + thresholds_gif + [preview_checkbox_gif, face_mode_gif], + outputs=[gif_output, gif_file_output] + ) + + gif_input.change( + fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces), + inputs=gif_input, + outputs=origin_gif + ) + gif_input.change( + fn=lambda file: file, + inputs=gif_input, + outputs=[gif_preview] + ) + + # --- VIDEO MODE --- + with gr.Tab("Video Mode"): + with gr.Row(): + video_input = gr.Video(label="Original video", format="mp4") + video_output = gr.Video(label="Refaced Video", interactive=False, format="mp4") + + with gr.Row(): + face_mode_video = gr.Radio( + choices=["Single Face", "Multiple Faces", "Faces By Match"], + value="Single Face", + label="Replacement Mode" + ) + video_btn = gr.Button("Reface Video", variant="primary") + + preview_checkbox_video = gr.Checkbox(label="Preview Generation (skip 90% of frames)", value=False) + + origin_video, destination_video, thresholds_video, face_tabs_video = [], [], [], [] + + for i in range(num_faces): + with gr.Tab(f"Face #{i+1}") as tab: + with gr.Row(): + origin = gr.Image(label="Face to replace") + destination = gr.Image(label="Destination face") + threshold = gr.Slider(label="Threshold", minimum=0.0, maximum=1.0, value=0.2) + origin_video.append(origin) + destination_video.append(destination) + thresholds_video.append(threshold) + face_tabs_video.append(tab) + + face_mode_video.change( + fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_video, origin_video), + inputs=[face_mode_video], + outputs=face_tabs_video + origin_video + ) + + demo.load( + fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_video, origin_video), + inputs=None, + outputs=face_tabs_video + origin_video + ) + + video_input.change( + fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces, isvideo=True), + inputs=video_input, + outputs=origin_video + ) + + video_btn.click( + fn=lambda *args: run(*args), + inputs=[video_input] + origin_video + destination_video + thresholds_video + [preview_checkbox_video, face_mode_video], + outputs=[video_output, gr.File(visible=False)] + ) + +# --- ngrok connect (optional) --- +if args.ngrok: + def connect(token, port, options): + try: + public_url = ngrok.connect(f"127.0.0.1:{port}", **options).url() + print(f'ngrok URL: {public_url}') + except Exception as e: + print(f'ngrok connection aborted: {e}') - button.click(fn=run,inputs=[video]+origin+destination+thresholds,outputs=[video2]) - -if args.ngrok is not None: connect(args.ngrok, args.server_port, {'region': args.ngrok_region, 'authtoken_from_env': False}) - -#demo.launch(share=True,server_name="0.0.0.0", show_error=True) -demo.queue().launch(show_error=True,share=args.share_gradio,server_name=args.server_name,server_port=args.server_port) + +# --- Launch app --- +demo.queue().launch(favicon_path="icon.png", show_error=True, share=args.share_gradio, server_name=args.server_name, server_port=args.server_port) diff --git a/demo.jpg b/demo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ac58f0fa0ea1b5e41576636c9fd95862507f0ce GIT binary patch literal 38265 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 literal 0 HcmV?d00001 diff --git a/docker/Dockerfile.nvidia b/docker/Dockerfile.nvidia deleted file mode 100644 index 407d221..0000000 --- a/docker/Dockerfile.nvidia +++ /dev/null @@ -1,20 +0,0 @@ -FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04 - -# Always use UTC on a server -RUN ln -snf /usr/share/zoneinfo/UTC /etc/localtime && echo UTC > /etc/timezone - -RUN DEBIAN_FRONTEND=noninteractive apt update && apt install -y python3 python3-pip python3-tk git ffmpeg nvidia-cuda-toolkit nvidia-container-runtime libnvidia-decode-525-server wget unzip -RUN wget https://github.com/deepinsight/insightface/releases/download/v0.7/buffalo_l.zip -O /tmp/buffalo_l.zip && \ - mkdir -p /root/.insightface/models/buffalo_l && \ - cd /root/.insightface/models/buffalo_l && \ - unzip /tmp/buffalo_l.zip && \ - rm -f /tmp/buffalo_l.zip - -RUN pip install nvidia-tensorrt -RUN git clone https://github.com/xaviviro/refacer && cd refacer && pip install -r requirements-GPU.txt - -WORKDIR /refacer - -# Test following commands in container to make sure GPU stuff works -# nvidia-smi -# python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))" diff --git a/docker/run.sh b/docker/run.sh deleted file mode 100755 index f4a4f04..0000000 --- a/docker/run.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -# Run this script from within the refacer/docker folder. -# You'll need inswrapper_128.onnx from either: -# https://drive.google.com/file/d/1eu60OrRtn4WhKrzM4mQv4F3rIuyUXqfl/view?usp=drive_link -# or https://drive.google.com/file/d/1jbDUGrADco9A1MutWjO6d_1dwizh9w9P/view?usp=sharing -# or https://mega.nz/file/9l8mGDJA#FnPxHwpdhDovDo6OvbQjhHd2nDAk8_iVEgo3mpHLG6U -# or https://1drv.ms/u/s!AsHA3Xbnj6uAgxhb_tmQ7egHACOR?e=CPoThO -# or https://civitai.com/models/80324?modelVersionId=85159 - -docker stop -t 0 refacer -docker build -t refacer -f Dockerfile.nvidia . && \ - docker run --rm --name refacer -v $(pwd)/..:/refacer -p 7860:7860 --gpus all refacer python3 app.py --server_name 0.0.0.0 & -sleep 2 && google-chrome --new-window "http://127.0.0.1:7860" & diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e032b001d216a89e31361032446343867f18fa67 GIT binary patch literal 47295 zcmcFqV|OK8(>_T~l9Nn~2_`ltwrx9^iEZ1qZQHh;2_`lswyih!^B>;TA9nZd)vI@R z;o5aoRhYc27(6UCEC2w2mk<|L1OULkPQd^msITFdeW~%+05lPh5dZ+{V!plUL4I8m z7>Fy%008b}0Dx~G0PylP<$DAGIMD+DC%OOtXDR@IVVl{m!2NXtN?%G$81VVuk=s?0 z@HGSNC?O*PeGG(wM}$IUCWiTX7$6}mpzOMO=H-$}qSEyFxDJa;c4j%1HtCjvlY*nn z&krOyohu|`1BQP8{Du`?3M<4($(1*B}O^{dx0gZT)!EM#E(e?uNP-E93WP zAX!gsev8+Vx1k2dLpTsTl>=E05Ih-1ixMi%g@J@gA|6Z#J;ZB>Q<28u`Um!nPbRBl z8$V|Kdg%O}I(G~3HiE;n^^E0fDV@RcyHcxeZ?C%h%2R#Htd@xbMb%BM?q6wHX9y8p z=GD=!=QKDb`TK{kjdW{JDl!xvYzJEa&9^_H(o!plwa*vf0t80awb!TtY5#5uZNiLOL+q>CM2k$dN3r_#`C-`CXr7SozbKx-(9-6 z8}r?PavzMrKfW|~qmdRoBSo8^Jf_!nJzcM_YA%m8$zOlAn6Eg?8#mR`kXC!JfhKwUb_@Ypx+7x_JwueEsFj_5{WsNJtBfjkMzGpILgODck(aki>774{{1p^WF8Y z%?8UlgE@~bk@;lD$*FQ=GJEMMUP_;EY1g1CrL~QrezkX0-MaPEoK7bo@O`F=n0V*= zFx-m)UOyP#=jDX>d+l5`O0E0mj|3$fZ>`3N%# zv$q+9fqE3FUF~>@r=G&j~;_bVN zYEB8&d}AcBAL=Ynx=f6|$VRC?%(8wmoqmK6h+Z9Re&R=YTp79eL(7GVP4w_c830l> zyrM=(F_v7xTWVy(?Yf~p8>kpeJ1aGh5wbC`X1?c%50Pbjuug^%ze8^`$Fk+u4x*C_ zA~wGGz!^1$ucbCztgw`bFN`dQ0!#lP8))UW@0p|Z!a;J;b90sSM~DyOP%e&z-tS6k zzZIVxn6P-Nm486e`SdT;2bh)TE{ga_B%_oJDFUp*I7Wm^@|~DI@T`qz`hdu zF5RcTM9b+<=5&Xb*}N{N1Yu-zbj%W00S+v{ZmXr7YtnhqWuK z>=ahbC5)0lkqd&JY4Jc62Nh`N62Za!GY`cJ`KfHcQK#X|EUQw3m#P441QDGx_0#LZ zcI^v6FZT2_EcqC}&bioso}9%1v~Lam`map2?()8!qty2}+rvZb)@BmPVT5!k4C7cm z4IZ$uTJB{~XXGT3BM&+LQY;6UMiAqtv4^KXLki(bU1ErE?eI#8u-9n;T_&jV+lFx zyE>k@##!*BN%Ea0aSl5R{f|!aO^%e)${9)(AnDxz*ZAg4uk)YjKXVe_)`5iO!UW1h zzSy=R`<}bMvQgy4)&moD8K^oT?uC?R^;fY#dE4#(=9|>aw5y?-()O^hK&uKC314jyD2_-$gd?&!m4VHR7v~{UCj3 z^ivFDt0CD`tg!1`N+}cpPxQ|vwKz97{aGYDvj)abk-h$u35sIwDSFQEBTNkxE_yRd zXO14nHN%K#nwHAN6Yj6ykRD4h0U?OtJxn34FNw(1+pK+ik7(rse|kjJxT`)gdUT!` zGnBX=r|fMCc0gWQ4=xPsF%!dH5tTcdiv;hAvgSWCu`dbl9;< z{$M11sFP)j_AbZ7)X$<6r#v8?eDlNVw;LrsgqY@i>D>~A!(%S=fqXOod@kDk+2<>s zJYDqUtG!8yGg>k}s)I^7-^Z0n+%mj6o+eggGrD;+5AQ3R;vrqxl!7F@lcH#}1{Y5R z<5XocVM#H;orys4k!ER(JPr ziu{$q?*p`rj(6yJqPkjZeIMi1aUo>5f}9r>iS4O8RLqVZ+IDBo`saEpX@)mzLXM%f z_zWA@w1Qi7*jciHuhJfZom9<)2VA#`QwL!pDTYw-?=x&Lf1y{#@^eDJF!R*L-AVcd z7^$V`%Yj-s9p%rc`g>Gor#s%g@vE5+m5ndgk-z(U={-8z?Vv8Fe$BS56vbmXawM)l ze(u`WupqnMvrQFFi_4y1*TMYZ@szLY*Wly4JG}k}fU?auj7nazFb+1e7${=cu~MD?CbIeO@vi3PZkMkc zmVW=wX|2a=H9nN;26UiN+yQ%>rK=usid;=!#*cK}dho~1IUQ;ddJ!JiOEUmmjCt=7 zjS$c)D=e7QXksZ;MqEy+G+3aVP$RWmS$7+A5GoABDW)I;-}o3oIS&Mf(bLlIErG}4 z^Aa+-E#H6F>gEHViLNbq8jDYQ^{v&{Nrab7o zDmPcivQUO*_rf4Ehls@H0Vdj38Omc@ojadr4ie5o(M_qfCdTU%!<$ZyUz!f1_h$8lDu#NZKjW@N0KAAM-4 z;hE%n_S8zj;H897Ru>RI3`gtpNE}uE2pO7M0I@_oOl9u*#ZPGG;Iz(U0vog@qlhz? z7ov3EGSV0D5ME^<3a8On(B&`@O@8@>N*@W)@5513EzWoE zQ&TK(kps@*zP094Scfmh?bWK4SlFWYMH@}M{7M2b@PR$%XcSZ%q;Y_1t9BNR>Jw*$ zwl@ZmoF?hjGje6Qj)z7tJ&mK)rmA%`BaT%2Bg)Zv7R*{O(w~Mj%7bL&^b0fQ>Qes7 zM!y^sD_|2fIZ4>qT%{%+I!ffS7q4>gz}`kw!2FAk*)*PuQG2DSN=pL%W>SSoXqH7P zikFkgrv%Qiuh7QY#@t;F8&i(!j>*eipX>SN#%e>-r^UeJXoC@SZOX#sFd{O-@dM%T z*dV>mtFFR%_8%*e1r-D_^XlDHvVi3x?u#-QB{xv!wuBY8(v~i+r`1*s#<*^abS+h$ zty_T3dx4(mTnFoe`Y>3kZ~&3NG0u2sB{X3tp8M|uy+Q*s47I;7fc}PI#eWl!&^#25 ztmV3+=Z(n+a!sKk8$(0k7JM-oi{@Vw+`F3iK!Hee?wT#=P|}>+br)5iY3Ti*sc5|d z>_HoEkAX(W6S46ttY{L}2~!!(kC?_>*1&r^8e0w3yRe*+pjDU+dQ`}5$NkR0ZD_|0 zK*Cl*Hv@I1;K4XbZYWr(LXe@RVQ%0xfM!i#rhXLjfEIX)dlC3oP z{4Pd!pPRDXTLmYeZ~xZVOCPKxQj$R`t8yIxK3Z={)%RXM@j>{G*{k)$ermv19KE@1 z%%zl46Xq)l*P`B^5vH_P@uxC73gMN6kGE{Qz@3L>i2e>TR(UyCAXehdGLU9$(GZm^ zA#8pKjYBUVd*;`3NVL@tT`sKifg-psgYq-Q<`BqW8(`3u2s`mGD|15#-$8rvMCuB^TiRbsVbgk-L=>ZX8$_+Bny-L+= z3CMAj@n$>u^kz^TXL$ljRW&TCs7|Ay?4pFm21^fMK^i;p!oArtg$XBX|@Z6OK*I#4~r$7kO8~)Fw#hENrueQ!dvMPMH>9&>-pn6o*ilgY``W zPnz9~FD;FW2&Hl1w=r3p9}fF=abl+Z_Y1U=5ZGv@s1y!36~I!>t&wCWQv84_3-Wyo zxgCRyvP8hKBR>={>JeK#^Eh7kZKi%tC5_Qgm{b8hr0w3hsSskI?)Rn0C5QaHu-yxB zy*#UlRpEAdJlGS1^?u^GF}A^s7N`b9<9{M#2CGS4fweCwGh*$0&W~dmnU=h$8~Fx#I_9qysev`R-FWZ#1leW^*kf zW%Nv`8o_!L7C&hA8>w|-2Xs5&yvi&eMjezOuOLD|5ctvOS70Tb5kftItM&RPJ`0}Q z-SSynE&NbZUO zfbSafN}z7G+rY=@3pRjF+-_M&&Qoqy6&HbKw|Sh-?O>sD0YzW5(j$<86zxoZs(tl* zip7=Fj3rPRdK!QP#S09f6&IMBI|E}<7r#`c)ab=wkUb%bHt}GA!xd4&z*S^rU-hUz zs4HMK6VmYoET_Ra+x!Y^JQi9wl;M05whdxXmvLYR+oBZUres2y$2>J6nWKOt-YK0Q zuwyjrTpr*1;c{vAsn-MM`V9wK=V>7?FY7g)AcBY>YA)89m{HmIvZw?&bLCUz6r#jo zE>0Wxg#dwNwQ3TL0kf9`MKetu?6^>X0lNF(d#4o^VWKSE(!hIRa2hN{#$r5AIBe{6 z6iuIUsJbx#Zdu`f0wcE zN+!2L2iy|&GG-=XHnSTi<-KBNsP*rTc3Ry1a1fYG|1P;X>f+c-nX2;7haMwe{1^}_H82zlhqg;DR|h4dJZ9IS9j?p>3U5`e}?b|B1G>f5Gx5j z?O@=(k3e_~N`DSii+5Cl1uY9lU6p9cB0&O(-p!v**y1^_w>fx)@6EVM^?oriU0vgI z2CSt$4$Hiq$Q%e;SJOJPDwxd*T33&nmh~}H6*0gq2vFfuSXTeV)7b+WqKNL=#(W{_$7Vz@oVZ9B8YE; zg=v$iq=v@!g7?N9eTz%LeS9KD^s)T2gQ4)H6bZP=wXH3qZ0swX#OReXE};+Fz|hBn zXmsI(<~tbIlh;!Xukd(zl`x&)EP@9V7K!M-@LoE?!@XsIAPR zPQIzo@k9K|6SuL!Cf30CJj>Z?+xtWT0uO!x0&mY3N@GR4c)zJW+19jdL~@qX^>IU( zgX}3gv_0@jY`m2)1L1TdqEuA@AgL~{h^`?FTsEoeZTO9-Aa9sxP!EUua&4sHAF>5l zGyRThjbMRZCyi4%)ZxfVI`Tz3GY>&Ez8`4BXL`Z@Ax`)F}sdjr| zx;(_d7s1OkRYjfniKD6vF)g;tus8;@N;)^C1G(2TNBU zg1kvIEtlD8Vv@tR;a}Fy6A3MkKYYF3Q|*QiYxRe|mGn)~0HqkAHHxh^2aOMaQ16WX zza#gQwG7bIGG%QsT{D|1E3zUT+!HtA^{hfu*?)CxZx4L7I!~5!5KQP7!CLFv{N)p< zqqq3z@2U1OTcDX}rYvJ0mfR=&$UqVtA;F?R|CZ`~#IQ}T+z>y0N|LUl8SLo=QB1>4 z6744bjzMwns4sWh4ofvnDVjoH6#BOTvFr+RmeIYO9*X)aHuVCpq`)wf@KK}*RJjwg z5>}j^m=`yK*)go-@zCsH{1}b+vgK~?NF2WJL-Czc&WvGK+#Ir0Uh~HKsybAvy^_^w z)-yp6z`U4F~5D;}z?pwtakj-dEQDZn920Hyl@jPnIJ0?!2F} z+0dPvZ*mTg@3B8vQep+8fR{{O5fDXY@*+knO3a}7(Li%8R{bl3#)t1Cgd@p&6HN|E z#>fePgD7&$f8ZP>oo8@B@Pivr)(uYjad7aXmobu)?Vd-cOLArHfj}2R#KYSbs zg?56F!)9Xb-;6gzZ45rwQVqvnLdeQ*(&g2x9ItZsqnvAUkIeff zPeFLr<16@DZHC8dm6n@-S7@%82J~b2EQTw!FtqP*#VL!0DCV3MZ!n1X>w*5WUBdhX zm1QJ>L=8r+Y|&V8$du$oG}y`Esr8?E5=XHB6mTxK>X1vwRG)P&^Y12J_Mg^tgU3QrB+xU&hi_9+&08&M*2HkJP2(e?GIE=JecCf!4BAqe9aX zL<2OARmI_WNUD;*yMhf*Yhngi;Djh=tL0DqrbTnb`=03vRj4*gMSAjct-%Ps-I*n2 zA%waUqKi|g*7C>&DZyf}3hY*a`l}I@ru*=f z(%H`EoA=jW%0}l6rdLog(lcAum~U@B8>1{AYQFBo=*!JxA^uu6tUx*wxEYiD@vq&A zuB+D4n1BH@c+r@J{2+JUX&G7U9TG4cg>RDr&e1OF6J1;~oriCx2`QjHon*VVj+Vsu zd(d!pjx<{D)?5dOJUpolIQEb{k@x1~${3k~x1fsUhiX9JLtsp7SF7?A(_8y;X9{SX z4LRf~X2Gp`e^Y6% zy|p;EiBUu>**Zyf0|BWw*^6Ff+}fR8E|z9R;40Ha-w>s=o|29x2R z27vv(eG8CFC_Si|nJ?UamV^iL_hpWaMWYeY8}yuuMaLrS?f0qv!qRa_`G^^E$?6d?JOpwgOf9*Ob#Za*L-~ zrF;#yU)_1fj`O;2m_Q&&5jys+pKJ?Fi5jw zfl01CTn97{_0204@ zXoRluU7Vx?1CZg_(im>{?LnHqz!jRqBwnIYW`dV)_`f)&E{CY63CQu>^l}&=^ptJ^ z&c2Ogx_H|slUO(llFMn#^iJGGs|^f;xRkX#B~Clb76eK`8x~GJeLL?H7jJj%MJ~Sr z!V<_deN(H5W(V!msd}Ya&Q2_&1Y9jLkpx}#U~1rPDN?F_b&G=g>QKN}1`j0057g@X zgGngi#v7N1iJg~`OP6$ENiV}^#A)*kh`tJM*412PcWV;r7F| z$Pv5!CYKoHtGA3D94Xwamusyfv5$np58kmz3*8X7Z_MC1{>IppW}(zpnylK++2b1z zp+?zF9&6Z>vMG4$xY-D$yV%hada?P>M@v?fJ~dQj;5TxD;q0BqY{v+N0%U3Qu_C(X z>VcN>N0QZ883E#y zR#9UZWms`HxS*l#i=>4T$7ZL+lnxu2v?#_?`QW|=^bI1~jNHLMh;V#z@VMSs2GoMp z4)^m_&F20|LuTS1!Gs9h@8F@5ZE{B1>60#gI{obDkMN?C`>N>(?U8tZMHtTv1W5s zK_dDt`U_+>v)AJ6iPB+~55%ZFHbIfBs(kxGuWurX#)vlC=bH@#pi|JKndZ)NkQImo zL)q02dte{pjdofaWMb8I?4aBa8ue77PO3dwZFD?G%SRH*zL z(+8)=wcKS#s>=5Ueb;OatpSeng2`!TJWn2*S|Q&C$Z;l6Ji&hmicn+atpkzs(jD9B%IzyyjpA%~m%6OSW!oJ7YnoFC%TE;` zs}b*B(#9aoNDD-KpQIUBfN9q;(g?{8Y0tb5Ikf3ue5t_DM~Yi9)%gG})p3qZT2=76 zteh|P+EK{>%%07Mh{t-xgbdyni;_nBz7xf-MK>E55r*iASI1IMAp7#KGV@lE<^BV~ zJ$Z)(M`xt=@&#Hcxtz+fWQN`xxAzg(Za*m@zjWs(B%;Kr*ay;I)w!(=w*N z)bG>MtKZ3(0E&BV8(gRFd+H?AO;koa%+ku5!!;}*X1XYJsb-J&br&LrjXYp#b*MJT zA$)>=ma9YofLenaA+)>)HYaXc*3Qa|$ot`@)~#=Tm%j?D9M_7uZ#Vg=`JwRS(ikeR z00+OHaK%yfnV6s9fKNr>*ieYtY8N=MNxiCxpQK4MIC`j>#k~TdgayNWXjpYjZg*!Z zX^ru}4sfE@ZN3}hxUw5!l95sLVLk*&CiT+Y>CVOb=r3&fM18UBPES9-8-<+#VEoSM zc)l6KkY|ke>g`hbpJbx(l)>SXFwL{M?a&th!dsD?$;7IvI;OB3o`${ze9x=>@_w)0 zz1L5Q;3MCLRnSQl-6?!T9znSHr={qyj5fV%UkN^MpoP0ZZvz;$?2Z)Z@1>ev%j-7f_} zh5}fpL0VoC5JWJXNwVT=b3Y1&ql+1sZ@S(D{or6wh~DfOq%WL8M6l5Z3C*`|9ez2n zim9`?BFtT#K>mqxlU`l8Y$~RF^)4|6qD%E~Aqqg(NJh(ZPU*aTa``77s0b4VrueNyelf;z~}D zJ6>TC2>kJDA-;Z-e=%}4fImtHOw=!<5cOk)dwJziomcsC1aeAyVR!^#Q0gbB* z;f|?)XG9P3&mF;lq`XA6;a!X3AM{n3xl;Y;K@9J;sPxw3mZS8cCS>;MWcIUxOjzP@ z4&%H=j?I>Wc@6OJLSh5IHejtZ zKLsgc^KvpXz#Ce;5XfAY;*6uUX}3`pmxRgSY>A<+N{(aB(FwQrlAQco*I0E+Eo2}t zPp|Mtpr;UVoSQ6MZY2!AD0P#w8w6kPp8~nQ!L-{xJ*533Go%Rj7WG{-SdrX7lxz)l z*8g@gSWlSj?tBJ7ASnuW)GC~Tb1oGtzrkc52bSA5A+;5u=!7Z*capwYvJFB^ypGQEM$;I{^upVEo6M%q|Z`W8p^l zlsb{ZZ6{ecka8ftR6TEVRM>t^b=wO|ZN8Ep?G>xHsaaGuc2WdZNptNIS9#qkBHxMN2wvyng7P%0-0t7)8la)~j2Ck6H*(wlt}(;tqa5D42#7w;I%+2DJztC0ft#zlHU=s>SJr$2S1vFyGJ}A z0AJR{+vt8y_C48@;dD(pFjsjU7SLQeQ$)2dcu?Edj;v04Z=G)_2}5&hU~P;QkI)0bj!Xe?hF~V{4VO+TEDgNtraFOJ&Uer+&@W% zF;e{DIU9*(VT6R))*No=pQ=|;qv5`$a>Zj4r8-`o*>mV|u96#Z|4nGn(6YX;Rqozj zJ-QI0fa7V0?g~0J1kW{WZ>~|*AFP>oODfPaZPIqY5h+L4}8S6+gD)K&~ zGPgsy!AouBTVgPR@>?_|28C%{>BMI{|p_m6AdY)yQlBwIntzzZV+*e z?p^Zv$ubE%LbsP!+pQy39(%@LnS`2a=Yk%@4>iVC9!KO=&EZfQJ(YmmULBEq!tcLOcuY4{@svdZ9d4Z zd3IkyZ_~R{C9}=hIU^}x2E&{9>>#e5GOE@JN{*2o5Q6&rrMka;_b(4%(4e*(N^nJ$ zZbJi-w$u5=v={e>Er6cnED}^qf#$+=-i||)ZFC|J+g4ELo0O$=a-by6K^TYPO36S8pI1>HSgBA*b z`_PgS8myN>XdFoV>6l!Xw5d=^mMu9l`8JO01tJ7nafrxMsja=XAL4UPRfBQ`L zCCTK^j|pm8fs7(-#aQfj_T^UVBx7#aAaJRgJB)WOSPA6KZ@KbG-+C3z<*-gwo^ zU%ddogYoJ*dR3h9=Qhs!n!#Zd5@5)*5>~b;N`Lt4MPwVj=}#j6pugzioub~gtRfZ< z?2%r_PgovY)Jafr-PF6#Q4({lAIQxw^ra&Hn1WXQun@$tVh#1XimNFkBs!yXH=#7D z{xl`CHF?~u%|k)|FXd%_CUFe3n#WsMlbJNO>;MeDJn{1kLq2dz{CvJJ&{nYAQx|D; zh1~m!3*tYaIN~_}T^fGPTgO*Kq2S0Ag09fG%wWrz%uKH=mPs>$LUKo>U#MZr*^*cL z^ypFFE1&X8^1}pAFwVM-8hoYex;Oum-fpgyFIC$rv*Wk zgex7CpUHXYe>t4vu$GkC@tOAs`e&t*nL|pv#ctaluT>4~B|#T~BSLN5rJ~4%31I|G zH28`=0X%W{4{`;Er-4JTu1o zYj<;aF)IF4+=EGFO#ECx7m}sS6KhAEI>*D_?kkZ~b@M4RdFfd1WCWKo7ock8laOzh zQq7nn4Z$8p+W@zt{?^bqjm2O$EA$<@Vuin)c0#k(^uv<++~e)~^ip7RXhg7QkfADR z6lF2f#cRi<3?YG0#)1}WbCSgz)0vD5AIjuuNryXu@~9&W%|?SNNtCAb$V0dKh}3S< zHDz=;bYc2rxf{}O1wA%_UT=yB=pD~}%C^?ZI{znwe{di2Sc*Y0CD%&du3Qv19(az-L| zDr#K#!PX&~|2@(Fhxv|qxIO_gw0GTeEOE@o{p=cFJbMkaO=n_O?xQ=F>kAqqb%xhX zoR~R6f+JL9XGr*OHV1Xu1qmPugi8~%T!zOc{MhJDa}2@m=rr_DcP-5$F4KN5+{h&U z%=}EO>&AAm-{+6-oLyAvN9}E6(_8i;Zu7bIH%^#AsQlVE>R)@>U*C3&$1`8`@r!X> zNiK~}$t5ovmrnT8BnE}Wj|c$U{Q7-kCI*}TlzO{k=2yA@UG$lw6URGBAuGFS2)9z9 zNcvJ`4hKWgFhHmt?0tn2*f;~wY*E)joboQj*HH8+*7LJt3)@jjZOp#{Uh%4sjYD&v zy14l65j0NMS-GDAbQI-Vb+Y>R4H=JU-A$I!+k2FeQ%1fCa8{pvRk)vc^=AL~R`WiVL<31~UxKMD z6}+&@{U@@duI{~QD@mqAKQ8amV0kZ%{iG9-;FRTd>dGF>e#gpTH5+0`-D1@j3#_RR?1N${%kNR=}ySA2kFfxF8Jmxe(}*mB^+|4OkjWG|L#sXkRygaN-qTUF$I>s<->1Js9xgXCne*dx z{n*e!B}ypArY6O8HrT%q{v(u!a6tbu#EK`Vx+`mq62Av^AOs#;J!N`b9H^swROQT? zv$o#(o(Dtj{r0etvFO!YU^L+dt@AbsY2tISq!4C%pSBie+U($VvAp4)OEsT&{=F$# z)Z8taAh36Mf4bKhXS8=j;F29fPhAyC5}iW;yS`eg6x=ssvUlWjf|0`}msB-)X(;Iu2 z83rGr_#xdlTfg;TECu$kLKd86qJua=Cu4TGyI{>K)^zZuh~1vZ`my4fDdVkc$j(l2 z=N$Qi%YAm2uOpwhpJ?h6!7?It%&@G*>hy0G?vTUSWH&9`K4`~~I-y$G=`}nK1za&0 z^oC+$NevQuCMx`_gbSC8{K0mS(*_o>)N*nBPf=%$L@RVQT*t?HZL@m9o80RzP>{dJ z#r~dy9u<|4;^BJv?*JK(+bv7RpTp^|`f1ax_UqTYIa`fw^bgyAFeBZUo3Hqmllv#g zFQl9g8yK}}RPt3CBXGGj9U1uV3v#`TxguYG;fpz{?M{&~A}k%|^fzL1aynZ78@V`n z`gh}Rc(aw(%-?oYpua66QZsH8oPqIZZ^&JE?FL-1T&lkS@rdr-otvdeJ=%)^S{oYY z<91ocWgt10Zkj21^nmCr#{kvM$S)7E+F{Xh+>Iy5%Uog%#LmNxl>06XINz@6>@+mf z%aeECHF{w=poYz%zG!`>bh>Kj2Ui+0nZTY394nC{1)_oFzUw_6v?7wVFpSXdt8x(*7$W~&YYY$e$Ksfq+b|rdG-b_fr@u7~V=Bw(-zPp?t@EA>P%_$E} z*13l!Isx$71`Xvk^!0YA0%4@bk8dME`-u+kxRYA77Hg`Nu1DV33}#ftIJP8EaJYSX zn2ODINYR|1jNG>+Tz~(ze?#g&+Sw7t6^sG7(Rs&BcIFpv8QNzW#qioP`ffdl0X|1| zH*!9s-k%btty`F$30IHpVc(gekXlo;I@qS%<!D#Misz>lilx<(ED{(*(*p{_t+S`Wcbt)vy;*VzKR+widsC7r0tEl;=0A47iq~||+aJYJ%56Q=C8yW4J!uh>$z-B^-2N?b;j|7GtO+%kZUYFE zrM5CI0 z%B)hD$ig-b=i~0nC1;21?2_qi)|KR222Bi>vdHmwk913|KHml*6_zbfJysf<#mx5p zUbHsse_=p^!b7y(!}@%>R>>fa;WA&u`VIMv!L>xM0TMV-#$w4QC^D8s&kdVTPsDPF z&8~o*0M~FPUT=r+_||9S#q&D^A;Wucs<|n~TD*gIEqOyN-E*(ar^@9yI(rI1te&N! zro-W+KjG)gWUJ1$8yW!@7fY*1Xzl$JX;%% zTWr;3ysRHj7C#eI4_=cKNI~Mh;!t2A-hQC|2Ft%h5I!k}$-cT1_0X{*1%{(CgiKnp z2!f&Y2;$EkvA{&$H|(lE=+xv8__3P_)!P+6f@8e=jeLt*ekHk2Za!RVHT+QPwC&wy zFi7FO*+q0&YH!ByIj56(T>qTrdG^c#NkZZ{TtHq$6eZf3NJ>P{)nKD;w_{mqe4wfF z-BL&vDQe&h7H*i&a@AF)l@fyjjV8-pDYp80HKDejQI< z1UrR7_&%bvF0HY_i2!0c*PE)Op@<7s zi+F?}XV}9H)z>hS4f33BIcYqurmx;|J6mYD+~QQ2d1Vq}K(n_&{jgR&9MHtS(r4`a5auw*2NWFsEn(@cq(2n}#gGIUx1{q+ho_gN6zGJ1fAHjwUthK{J5 z*y(g-yFf~Rs(MMW$+A?noVZ%6-2NLfI|W*M26iTd&eQpQ#i7mbG~z~$aCOQxTf*sv~V-*|jag@reI0)u9Ky_X=-=V-I-RSD4j ze3=O?^}TV)n5U~T|C=Xczam)7$IIc>8^Ew5eLY1J@-IAN-{pDVwt@kJ{;VS$E*mn0 zSd3U%jrvavB>59+;SHgMc%m}+kK#8@_M~&-}1&hy&H|bUV*I?{Oi~S<-EO zLSTTjU_VSYgP)qLl?+)u22XMTa}jveQ`Z;&t2>9prIbet2JwOs~h;T|kIHx*=41#;=r z(?nSEHTEzZTjgNZ?a=9T9O%?kqo{>=9L;E_^wbF2wRLecTGgKtFI(QRKNHG57ET^rpQT5s#cd zb{&k=$(=%8C`tDw{QT5I%{&Xai{maNJjhh^mtwZXdM>$$n8#r7pV?|U>c143#$?ui zyJcjmy}y6k^lANo3w;roM#ST}E*JTR5jm-Fd{(>so-#unbW(bA!iwJSA95x(jQ9B$ z@#7TPHy^s!JxMqxd2}_n@i%nL=fQX2L9p_TIS~QPcY4ct^*>xEB%QnA(*|Dw<=kkQ z19C8m01{Ox=f5a}`df7~AOT}LibM`jfBA_%5tHdP_a^jeyaNH72~4k!EzCv~FWW$2 znHk7I=xkoeE0p371~inh-l)Gh`dnBTH7{Y7?HHaH3asM2GV^yBE0`U0N_9$zf#zj8+y`8 zn5hd_aB3)6T~y`99IY*dHF3-6jctntFT3T+ee&i!_sh?29z1HoC#OysKhg4fKFnTm zvkZf1cUqZTrrYP+rc53A%K043LhbnBv{D_^PCZ5@pTpW`AB)li0>Hk9jN@YJf`f4K z`#*|Aw{1n>%uRV!9sX5|MBiRo90`Y&^Wtg6oa zt=^U}3_xLN1xf!m7NISW;z+pJcSVzg0W+(E<+@FC&c6XGZolSL`5yBxx)G8Y%Wu6B zbKY|U#*Q8a$F|7}0}4atJ+1A`5Lj~ahcWM>?_t?3dj}YwzxBfmpa;MY-*x@(e)P;E zkG|XY#m8a0b8II|!pYBh>ak}o0&vqSIlq7tYN-*XLOS3U@m<1_heCH7oVtM!V2qo! z0Ar@kE(3Xm0*FWp@5^yPMJyvyC1zyvbtv|BqjP&RNNFxIeQ+5RhM~ee zCo5Cu?B2fZn&AgtaOKXYZ|gSzwT=_6_@4o**R@FR@GVngvH3 zbmMQXJA>z6_&o^154(16yT|kCCSlnkWt1R-Aha#v{-3|Y$sfNHt}c6^w6NWS z#-8x;NnLyPOjy;?F(j1IwJl3Bn2FKp`N8@Lr+jLC`}Qp>X20w6kItGo{i(aYaL&sz z$A10UGvvZczW0l!y1KizZCrgq!Rz@H3HPudl<$6}27n?ejxLI;XZEB)-OP*={_Eoi ziUkZDGag=F5e$Y`EE+7wI45QXgdmU**tTjZT3fbY$A&fVihZzibr7~)Ru^WjR00Tr zSAz6B)DIeh5fdh3)Rb8mG-hHsl`wjEm`n@-QCP2*E*KygN)5=18JbG}FDY4#;b>xh z1|e04h|JnSTp0+-zGU*mNuS&KG&=W11Mr6P0A{`Gi|Fa<_yT1He3rC+Or^V@Y6AKI zfu--m4vQCVxbK^{oA1By;_sn#^SUom-M~+iuo8h`sjurF+y8#UG5_xQPkq}*r}Xvo z{C7z9=^Z4l7y6^*!A(<%l`PSeglwl9C*(4 z!?$-7R@<4n0jW}stS>l&RSL<>wk&>s{a?QNk$D$>AIon2aN;xGe(mj+<=&rl!VXCX?OcOS`LB=#zdBx?P>^ z6Z;B114^YJ(@>Xp949kq>)KUTSC2O-*U)$f$U1@@cK`!y1>Ob@y{{f~?BZK)`Q)kL zD>;XQ-uFX1_0tc%qVs+4H}4(v=ldSooy}%M=oN6xyUWc$5lO}<0h(0^ec2e*ej@nA zE-ZiWuh`kV3AU2~hXE$eS%?X97Gmi5$tV^25c(1jigX(op64U%ekcv~Syj zw(XnH*3yi@qbKmVIg2P7dStM`RBb5L(=#)QJ)L;ymp_51%o<<~OP~nwmtlbY4m}CB zo5h+({*+{&SMrCL%BhqvnWJl{H;EC;V$aUQ4n1trpMH3Cq2B-)$GGFJ`GHkG=tIns z$e6{ZrfFOw`u7XnyJyUpwBO&q_rbl2cZ@&&>MwG(>C?h+brcr24Hz_h%+lLlnE@EH z__7aWve}je2OWOThON!slAFo~XVH7VkLP~&VVw5yn;TXwTXu4Zof9CO1DSbNC}nwR zkRT#rW=RBM*R^{}#o{x}^4WaO6=BHZ{D998ZpI3|zUBFheev2qeP!vZdA%?G=Dmad zdfz`=v$-sV#R?O!RIJR_Gf{4RL?`KfHCF9nhLg$CnkOH?>Zc!p$mBpEGz}VnVPnUl zy|oS9ZEfi7>_iy&l}%Kp=aLYx-3$f{8G)u@BhWB#Fb0jChyg=Kmhpv(*nnl(2!kN* z0U&+n$7`jUf>afx2wgiib5vzXi_x80e!8goDvBH zJ-a{C{NVS$q`vRzpT5g!$YN~J1%V-1XO^*&C=v+{f{2<7aQa;?*$#-?(qkU;p-x)@&wAp;yE)?>--XsT4Qm z@ZJ}Ns<;!a|InkS{%{+*y1T2xMj$0aSRxwdr7C-|;(e|)pujqj*_>g7UU`T9U`ZSzujz>z{ICGFzw|uXn%AdneAZJ{BBO&MiCD{0_qJIJ_n-doO`q=R2LS=hIqOSkZEyRg zQ$Ns=G9K-$&6qaE{IVuf$sCl5JZ{o{H#I*9EWGGOw6$zLjhyTkmpr#@7Kn;tPyF~_ zn+DWhL)nJS$mX>-1j4>nIQA*T*|$}B%0gk(Rd^`X8N8&{tP+4Thhzx=!m)yoXH}Q5u-!}u z@uNLETV}oc3xBxsuFswRe`b~0f)Pvk89Tw$!3Xpn0gxuE#USasDgGUcJiJGXAcj;$N9%ce8OZj3>pSNqPuMmc5Ye+SOW7F zpF&!hQG+7OXIUupMQ{b1?S~x0dQBt8m&ct~xDTUe?2kR0UIJ`83j5hWJ*r#@q8bG2 zYJ}p}k6g3#xhnyDX>S}{g>W)C;ntne)>U}6XZOyK6!t_sAVd48S^q~&mBek4Xu1o--*CD;aL(H<2 zu12pr3o>fXmg680nGZkt#N+>O;U(YuKSyjKM?}{6b0Z*34GYaey#CP=V87Ty5C7sP zWoAHiV2OF7RO1qD^tkPgnGyOvWDr#72?=?2SlKLgZ{LOuE1$=PRZFpZM>EJ0Sa{s& z7&UE9Dijzi6EQ-sl#)ZLaY>o9G-&L8+AK*aLkylUUGHM7R!~_Hsf~m~q4O)0j}$_X z$Kqr2PrYWyf3yLZcE;y$!S%nieaii>lW`SdhLTTJ3dbB71_J;jR2XR8_s_fVyO?+O z^}|E5Z**+OqG*z9xx>zOGDAFxzqIdYKFoF99kG9?EZ~w(&`G{TD zbY6Esu5R10W9jH)uKafMh8I8Vd%;7lzzStCF$-cp@MP#0o|b)W*9@(97A#sccgVn| z?E9Fd9eWK7l$r6It%DuO3d-HYw=bTSi@pNF0X94NliV8h84Q{V2ctN1Y zesvRASdf*_ys9)DQ;dy#JB7bZBrtIJNF066rI@zx5U#6lEQ5h0Dxg{=VyTFVLNXc} z2H>c7zYoJFPAx|n3SC#5aa379Ua>DB;!)=EFe)RGVcRyA{rTtE)4UFrV?$6`40Xu3 zDP==WN<)xIN3K``QdmKW@dFGJPQI~u>-sN`Jp9uCTmwKLJp9m;AF;Fblg)A5APobg zgxW18C*AM(nDBRiAg%Ab-bFufq#ta`eZ1mWJw?6RQ zZ$JN=FD%{Cyj|{U?YY*nTr1|-Q4~6A3N_KpI#UIK0n4I*+%HTz;p*f6Pd9)K@XPS| zTove6b@`3F*|Hrx_vgD1s9s)%$ha;R9C-|+?<@QqHE%`=pM%i0y%|ovfyd7|h!&rJ z8IC^Zy%;fO9Av2&H-1%s11FQi!Ka^#hM^qxZ=T910<9CGpfFq69@vnLA*@jYNPH?xl?x%>9ow067OEq?7#|h)BwE z;WQrHKs6PA%-A>dOPD%$?u!IsJCi%EYMX;np(8AIT{Ldw;Q8FU=cA$Tmp~TVwl&Q6 z!`a44f#qhgYuBzpwa$0t@4vjI)Z1}N=oPw25Lku+G9+RquoM6g_&zLI{LI3G4?OP0 zKYaPg=YDosv`ofLo@`q}oGU9I>ia<$lzP4+OWoJ`ercClW#%Z>AxH*%-w&g%cxJL~ zhe~jNdcsG3R`-9f0Icd+n24Uv!e)>Om=R<9+85BeqZuqKf~U5N1;@UFkG=RR5IKNt zmqX$(z{FV#Fnrv8QHf9E>{pTrC!5Ev4XcR>@QX!c8wO#)iDzQ*c^^O~lPyOAl~_CQ zOStc+-^Kku`4(P$?0#%n`UtC$lPdd00NhLleI0wWc3&8=SH$BdhOjJbUGW4QH&afH z0v}By$7{$!QvFz+8E6P58xG8Ja@L-<&hLyl;*$SN1F&aL_YF?Ap}xBIh`9oynDg58 zz7dV8&J4#Ep>1ck2|*B!Gpp=`Lb&X`f9TlBw?2N;Rm+ErpL~Oh!s4LV*H#^ety>d@ zuxt>~#J!dfr_X&hBTL8oeLXwX+>a{flCi08@!})C@rUcrOuY8Rr=Qqgl9i8qtY5f! z)R=Ks4<9}LD>*AT+xNZHfSJIu&K2JX3O&!usQ3+tY<|q@RWDrmZ{Gk}CbF8n)M**A zdIPF7VJDNt3;(_z=#P`aNNaLVCc9hu&OOg`WkN*U@N{U$66 zUF~g3fr)i$IZMNn~etP7HK{Lbd*3Slg zT`zd0QYb4Dv65w`6d;wo*Vo%U!FDp`DgXmssjxHPGJ$ErwCT$c`iUWB41g?~db--Z z`3EgJRCql&{6GXpY^7&vk~f{OSkn??ozq|~JO zt4Ms~un}z84jARpU{eCUW=tCIl-T*c?Pig7g{msB8roVd9hth6<@xbl03%;t2PwHCauNLX&)6>10kZChLg)*81%GFo;Z4_`@-|fzGgWYrZnuGnQhA|{`t%26bFx)^xqlyUk^jC zynb!B>DecrIQ8GF0!X1uzE)88I0Q48*Q`_vc>B6lAjvrN^z$%k>TC+bFp7AE5FGeE zKm&z5pc7)N=cIw*dMF^ zvQasyF>8bfAU8Yeh3B8W_CLk|Jhyb&wN|EXDwmt5^(={Eiw~{N%t_^+$~CquVKFnd zY}`tg5ZejhuvALu_8ordx0|~Aia#JG;;8$J;&w4+vfRvmuk?F7^V7@l%+D`JcVD-zy+8Pu?}KAml1Nlg_Bf>~ch*4Hz^Kk+ zuTB`wX3(;FC5|}zVl)gHRU!FQSxrn{sYG=H2XnP^ZuPT|FX z?KS}N^#dY2B2pM)pxE7w&?_MHJxn=pF)9;*BCteMqHcoO%U}LleeJCfnT+ngD{Y1O z0Yi`f}+7?VwobL%z`u z(3l1UmLD51c<>Nm*@C^^-haq26n@CP)u$}D;Cq+SIZm1PeYA;{H* z$kmAW@4MUlXingBKmQPZ`_;3_-7V0M7;%J5Ex=0arH#& zmSm&6%4UwBT#w5CLyrA=Eyn#|YBaML|6um%svH7>d7 zats|g>@R^|;%qLa^gn;w6=?4)e!|Y=%cZ)$&J~SWYcYF;wQP6<-}CD)yy0*EmMFkq zzI$okzybBggr%OpGKGMoU z5AW0s+?=)fV!zNUEriy;ZU8`Olw{u9Ihi#IO4DL0V>VKPW*o9O8q#ULR^1PTvPAgY z#iu~Z5N5kk%bFM9`Tkq0orY&+EI38j|$sH*PcC^ct_O6%miUs*tY69ltIRL z#FOP@kjd9a-z_Nhm4}wbv&|JN2Bo?|vB+xG=#Vx#Dl#6`XIi#Hy(Rhed2hR7&|As? z%y{SLaQ5eaZIzJuf$h3vs`|wWJ&DLKwf;{=lt99!)vH#`EZ6G4w33Tm>m4^6UBuI_ z`(<7@xp#w^1K;C}g%7>>hc7(h!oOCv=+$exb2nd(WxxLHhO8C-w+t$EeS+dqVypuM_kShJg>3<=F$J z-VT&{I-(GsKwuf7Vep6ug!YPsvH?)`wk{!7T}S%paOT+Cr$<%gM1!2{kR3akzcuRc zOWryL;Kkp60Z%{r=!eM8P9uc@p6XLfC0|uIHWB`pf3mYVZg1T=yZW%3$$Y~rbWf`O zzVhj(jwL&jD}PB|x$*uR9s_{s2ORik3cWV?G8r;)jN2u=rWulPTG&RhX^0npY&OCmz=7|)5TnOWg7mz2l3=ufBy7vVVeh;E z11cmNtq+0$DJ2Guo`ld3k}W<7eK<0VRfRO<`S1(9+S9`zz>LMGVZiV)AR<_qEIM{< z(M0`nb&Y5oF}4C1Nd%=rJQP*SWhe%)nKTJ?PUiet7N8kV0(`g^G}wIP>umsDvu?n} zAH1(Jqg|aWWK-I6A-@liip_VhE;s=xPo;Uc%e zr7}%SK3aWzTXSnjfqx$|I5*0-Eep0|&scWbNA7*KzW?YWKX{9k&G%Ud|9sF9NB(X7 zrfuG!v6KHec-Vk(E1y|%cF5vX2^s~EXA$pU4jvgYbl9DCreXJ>i@%HE0~<31zZCrA z2Oq2{3i8y?KKw7A7h4t+q*8#PM2eaikwvJ9Y)26qGg>#VM&00H7&v?sihTvlJN|S` zIp7FvU%L`L9c}Ra0QC*^7&LkUMoygtuTXB%aWWZz8Qpt!A=@}mq1Th{<*P=MlaM-$ zc@Y56wW|ffcD402Wb71V8=5eDzd7jGxfQ~4Q0(cfCb zgIc{}Lp|8d)uGhe4Hlw;X_*>_@kn{g$yj?jd%l0%`@XjD??3%S_@*}i8`f>S)~*{e z5KrTV|7k__s91-NES9m%srN1+F_kfheCj}bM zRdcx@kR#?_^nEP7<-@Pa_a8BC^p)G2cia$W2K@e!N1iI#2>%WlKHb}zfAWxn_rJeb z@^3Bqfy`v-ESKdF z1S}bKgNH$SC5T+v00_$l%P=u|&UQ2CsgQ!Am8a%K6t@o>lf_}VS>!Dng`N(>)ls~@ zmGp`2%-k23KYQ7jqb~hk^FueiISs&^v%d^ic%yc86tAQT?K};1QUw?j#vsKTkwGCV zSF&i~nV-39;BUY9?sfop_U3r=N-2k0LO`-48N^cf&;RUl%zVcupX>Cz$nyXUH=CPF z;?=9hJa_wb?Eo&Fe%i;s+0)tg{=jxm3q$K2UbjyVKfaWNK;SqoGc)@^K+^YYW`>)~ zED>JM&lc>z|E+i5@ZQp^eSgZL-A4URpOvKl%Z$VK0Q8awAew6|>G zotxIurWc+^>$a^33f*PJCm}#i2D$o13>h;PgGP+S;pe;$mYW5KQk@J0RfQLdZeJ|t z$5B4cij-Ly>}2$IwX3)rhD@AM1``2TmJ^lwf>IH}vJl8H0wAk_g>gw_leUW3Ze?G?G8}?Hhu1#WlufzJd>M= z_@fL=d@Y7Jx%I(xjO`b+|Y5o?$UUa5rqpkXv|;4n-%@F=+1EPSto(Dy-> z1>1E&0K7sGvNDy2Lur&C*k~0`&M@Fqg_X*{7fYbCHEP{J;CUE6c~10XDFel+kTTa) zH*OusybVT7m79>w57^z(@)ZE@ zeN!5MTn6JioVx!qd)w0#ea$_cxc5-hn^Up@%XZ-PcAuDL03d_41UeoFK|&m;{^@sL zKCfr|$)DIDNlYdVD>u^GZ`zdc&hvo(YF>}!cWCnl)>ZzWdaQXJZa9HiC}C`^GFr5$ zQ(H0Zu-O%zhN)HV3+eUQ>c5PX`Rs8sF()8gWzuhDBQd9yAlO_KN{13f;5(4w zEf7x^D)sgt^or3&95i||I1HnwcJ6A4q{lLKjR?aa5uw_Si}tD}Ag8ZYX4a>uSJIKF z8_}`Rwq-4bO_>KLldlcVD*v^dJhkoV{7OjjHD3>}ngN)7_Lp(Ud%iN9E%(w$5YN!@ z=2)#q$>q~$eyE&_=rLk-s#N(gkr}!4xo1yIf^g&R2?{*{K(I48tF5i|N(~G1OE0J^ z*mASjx^e5ox4HocWXPmQtcfmcBH?(N9bHKJ+cB;|Tg;;UER*2?6ur>*UCZyE#e z_K*Iwo~_(}Gjk1b2~io+CZ7lcHUd6MU@SoD$Tz)e0DxE$Iqn&Uz4zN%h`;pqk6_Ty zL7xbUeS4U}ZYEpT(cSm!`R88W2mlkO%~%$e3X0>9WI36)ehL5?lCm?Cv}EIPHnj_l zGC?$%(Folb+R#C&b%RZbWF=%^HFO|0vZ#WrZ^OA=_ zmIXj!#N^qKl~Q%Y^dm-4Dxz~&bDYsfLqW{yN45U17(Js}A}kwwHopjfFnaa@<=z60 zwGKH2eHP5bmV5l)nWi5Gj=$AYK;N%C)+O@0m{&{DAc_8e}X4|$q zv{ggajJLW0sJ3ILg0uP-jPGGuu5NGxw7i_jkm-)~_}HZFQkt_!E+A6S`f4{Fr7jId zCYM87rTtij0UCyk1c4CxK2|?;cRb;Tfl)IS8fDG$&pX#Hhn>kp#)wR*dgX*;EysAR zNBBwQ=x%L65O`n)hVM5=xgX}#>i8#NXVBT*d+oTR-n(xbfD^8{naHiXQdFnIvI*q_ zez7;qySM@*P7{JfyzP_X6gt0~Qkzy1>|x8fqQR8|%yV0R-?tug!rs#Zuh>l_$jLVz z)X~+ubi=xh3pog$EMS&Cwz&(xzXl%uGfo zoYNf&XYA7kVC(9a4i8C8fys4L3nG5u`vkD%W=6p9^rWnaSS;d|jV-E%txm4KY178$ zE7P8{@$Wa@TbB_>_=T?ZV1S(L*n%$~ceA-EYW5q1Oq(`tnEO^X0J6$iukhT{sxpc6 zJjHD@R8f^$s5a?iwb@7`RHLF2>^SKSHT*GsP+@dl?OQj%&gMY`3+UV=d!6neJKo;m9?y(RwwVc8Vx z(JOQ3;_fJ6dwtz#Yu=#g zQ!*RzsX<`jN@+m5M8pQT$Bb$^RsjH?lLMP#!b9y#WRmCCdVDB2v6$t@)XaY zripR6_+=PuZvFcwpLFdJwa)kX+ppqH4}9@S zj~$!WM}iyCv?3ue{piz?Zx|FUJgpqcjNMz-qR`zDftZ~3y-fhKN@^l9V3KN}OB$BZ z(XzfGMGp*}Gz(#=7?lvYI%t%X?5LufDD3P9_DKU!@Ns%sK8X^GMFu{y6kOL=D50;< zKh<(FC_4*d;*3%z(uY}={nsA~bkU=Tro93SNrM*fn=LU{kJ7dngV;lRl5X~!|iiJOt7-hwvu$~1dwuz#V z#D_f6hK-0M7p0uSr1CBw)(1=y zF~ZU#Yw!Ke0~SISPUh5dg^)>;2V(^gg+dV}6X;XIty&d<%1XAg4f}O=b=@@gg74Hk z_hq+z1b@5nVjeka*f)HykE>q53uNQgjqB&URSf_^u=Ks1M1sk((bw5kR@{LkR?uip zI9q+oRR()2;8hg1Gb4pNfS5Lm6nsE~_tn@rjV7vT|FC8u+P1ETRo57$9@6(P^{}_W zwq1jzkGK{uENpn@9}u<`87Y%(9Ag31xpmO>392o@k%7{Y>?ph^fv%m~q9k|VnElEM zMNl-M6tMPE?T&7aY|d&kS7r-dRl3-Ge)c{{m0Z{4{5ptq_405jQ+ z)xwqdx1)7eWzADezJVUP8%QiwnRzu#15s+rpeZkw@_L;rNa?MIFluHMVjvS(GCFtf zfS`CC4&QGM+D5f6V%3{={oeUT|S; zP;betS3vf5++BrXg%ISyJ>adn{{xm>Di&U>UadXbTVPq1u`yYx1(+UM4GmOXM|F#F zN>LxlGM9#w8A=4K=7dHFn9?^wU^uRW*5-|I3vj83(bE?qRo0Lh%o3%(Zft+)IY0=N zWRwIWj7_e~j8^HtO22d>15)~6h|s;O89-SVap0Iq%2POsG@?%{^EJp1gQ;^*y?W|C zVE|VC>QjK7Jt8q!zWSxZ(vP;a>}COg$v&Ed73ELWwL_E&{-$DkrUzlb7y}vqMN}@f zupK8r_LuG3w;sH=?=oz}h(88ip$z0ONC@k|x2gdUf{@K-I;;21&StT7%}d7q*5v-u z??x>W5R&7!(tv)}rN@%u@07kMQnW~-GqP-(+qO4H2y@fO@k)x4f-;67)<1cF)H9G6 zVvIPcv>?r7+lpX$G)b5fL`Jc5RCyg2rKEAp<{-d7|`!D}zzX?`dV~sAa>- zllBP%aPp_`BHM8e(b}FW{g9zwk|Rftx_!y5SAdDhakGbO*%~EaUM;Rql7LtfRWzc} z#BxwGS=)6RL(9Ey+;LYNI{(6Nr+$}7Gv_U}iCU{CB^mS2_{1FzZ$$&}>`yO4#W0GS_qG_6@MAS_MN! zlzA{wH6Y3_DjW!<5#FtT+qh%?>y0-6h1MOjJYVJ$aT75t;)kEP?GxJpV0cr0q#yDa zn9-x@Z;ZS@#(_Cni7^$L86v>oTrd+de-O@lz zDo;4>>u3eItX9-GVqA;?s8uo}=aCSH&YU&Ne!~qw^XARdEjN?6JIoAO>ic8$_m`Hx zu)m$H0}|~HX=A98cbyGBK-xZM?A;151CHa?d$4~!?)a;2IPEjPN=(+3Uh#=){ok@( zbar(dHTON=epBq{LofbH(~Q$U+4wIYfi6KP6PX3Jta%BYdvyQXR9V^jK7Z(wJbOYNCU(p)v@@I#s3wtn4$ zH{1Y(WX-lLC-Jyn=;hI4$J`hF4zb9x?Q~{8B{IjFYa{6kxTdC{re=$nacEgKQEt#x zOP+l8xydJe^ceMBvl;6d8L{F3;bf0me#g~s3Iniu#fq=C?%aOmf(yU(%7DNisM9B{ zFf7->ibozosn8dDN)^W@gL_G7gltNZeT*BLO5@_aqJ^o3fa3EY5MWytc5PW7J*Q#V zXars{k&di+^e-s%c4}vC#M_EeyjcC+adUG?2vWU5V#Kl<&XXQCYW*LC zlyOsBJEf#FW5ZSq;^`P(3zk5Mg6|M z`4^o34Zx|NZ)F-Toit&Lc0cE*@E{glawB?s`x^9tpp_I$2t4z*d(hRsCqks7WzOmy zG(awn(>hjT=ae>LMoNo!LMXRJIGBt#qu1?R{~|=TF4|$ahQ?S%+_q8b>&A=!_#H}p zJ=*l?gc1=Oj7Cm6Do$NuagmW!1CM|R!ru7fnr?lqN)_VH5xsw% z;5Zoq=!8-jobbexOMKfB+Z<&?6|9U{@%+<^0sMKNPUo3mId+}p){S}ck;gs+;0LFD z;@0eQPdt70j!kQ)k3a76C5w+a>36q$>g}?8-vB3*-!G!AMLU=vg8)ntOCR_v<{W-B z4;(RyIIcpQtaa@-HfnUN4TIxIOp__S<*O>V0RcsXKzW`z_9v>QRcNzPqIKIQSe6y- zw_O`wLig?+Wx8`u2a4StD5tDOH;GnAA|ub>grg$uRM^0^NSPbJk&2PN9^?5K!g0~F zXD4z42EoqcA`GS4tVOI`D~d@>Li~HfY+uy~z;T_a023KaB7Uj(d>ED=IQxsg7nUWa zS6L$oxqnWo0qeni?Db=6C#3>ZwFEAWRi=cFAP|#nTV!XlE^K$A5V4wx?YN?^gyZ)0 z2*76vFuzEGEVeS=8++WWi!KIx0PUp@HXgG;8q<6~17U3_ENxmor+ zM8qG0-`9mg>t+-?THtl;Kh>EHhpQD@Z|cno7?slkwovRCE=M#EIgd_zInOpPMr z1}mss^?kGXJh!Y}1sSa)M(BI!ZQq6NJ=@_G3yAP4q6E1-O6*zrAg%#~sz87-%#ta# zhOBf}YWx+|!3hgJZ9AgYY&TmM_YiQE5s;{#lCYgAZ@2+q#vn2bD9Zr0<1Alx`;`EI zQg`>LlIIJvg_lx{L7ZbMlTy^GjnXWh$n{e(0QE*vMAlATQlq92Rk0*lj(ftsZUC0w z`Ed-$XTB8_`+Q-$#CCQT>`bGuoN`6RatLnS+@2ymxwED$sKGNZ^ISr*G{6Z2BpPT!is%O$*Q(IjRvwfZ1aPvsIA)SzYx)FaMkwWoWB=OuC!^QRut93apAXT2r=uUstJ^gfo~WH=!eXr zJYzz{UI1)aR@l|s)1#!^Ps?ii^7YSrD!O-N*4X+8kys={PgcAdlhicB06 z*vg(W?>#sCXxTn)1eV?YF$S=u@_(!?Km5YsANtYJTUISQKcwu}I8!%LP6n~YgUodnBq%6FFHav!C1-Co`KhYAgXH2$pzNn5zF)1n|fY-wz_uW9~8AwtN5h<#XanLFm^L8Jzg-6OyyAM1%PbS929CN`3;g@`7DU+lQW=-0fk`By}@lI88h}qqwPS7)l zhfxJhP`GcLhAG5hxv*_+S+|PJbTk?*9;qI4%1Vh;o3W){x6lI=HiAZC*QV}L zDQJupLP)Qd>hlfRFZK}tc*Q=r`T8f}{6fFjSLP`=F|*cjsD1jJxi3XR1f zjV%J%c4`U6gafNNq-2KcxZ4m{bW?}On47tV`jWAV*gy+XK*_l0OJfd(ma};NyT0(2 zm>bwv@Jp6uh0)TKTrqSNjsy|w<*djbliu?cqw5@1uekk6doR@h?!LBikjRP!E$qOvh`(nn3Ftz-MJ(L!YWQIu7VH<#gf-0 zgeb<1NMz_AC{qX+@CrRBcC|q`WulT~2?kb`0?}sKmNZQUk>H$VwY;GQAZuZLMNc!9 z0FW|w!qvA_@&SS)C`fp5PMJYhy}?q#vYd5$$;?wjc1klUKq|<@>10&xa+u?>95+k5 zTiY&vD;R*fj8hl*p_AH8NjlMoNN>hc_h%H@YQwzL^i18jmVrX6MYyf?T!S%>+3<|@G(k7L;YBzUZg@DoPLg6-UyZqgSAoUOV|r^ z4fw@g_@%xGgp|cTOqeofuqXXxROg7v5yBpr`zo7TwLa$IXZ*M~MM+Hm65Oo7`FS*2F)M{B{j1{q=T4NLgDZv>eBnMx?hy*yL zno#j9Zsxz%ipk+5K*bGGJRL2^Rl+3*-VO=lhIVf(}h}tHygN= z-|8jAS?;nvRd>`KuX>GM1TxJ6xw%1a$ruz^(Rc3wxaAvWp89$Dj-1M`%7P~_3-cO z0kE~kvZ%!@>ojlOHlu6#1h!F;+cxB9w!?`)M-Det{Ej$3}L&-Hx7af1MDpb(dtWNfQJTF zAwV3I`d$KwIFuw6d(i7umgAtWb5CRv!Qs+hedUs{@+qOFvn+@$$Q5t60qE=P<*={& zH|oW->6XBY!AQ$-z@ZYa>TmXHh#SIeb2KxoHUv`lU#FlCRb^C5^*

sip1_aHT001|)NklrdgR3Jp2E9?lRa_SlI0^o7N~A8ZQt3&+gqN3oylx; zZ0irMZ8v+s*plY~3>1q+ba!-M=hiL!;!}^&@bMEcaqdFYHw}iAVa!EWdEZr7iikAN zri{`i4f7@=T_bI0umU{l#dv*y=*AV#k(J3t4@B#NDy8L0{?2hLX*{r_^2F-7TDAj# z;b!v)IfNiet_rG7y;f33GLfeE+ZDUJTe7ZbWf6iMJ02y5c+#-iNT*h{gbci?vk!b= z{iAna_|Rd_%1~x3D^4F=8|&6@-nQ-yj{vGiMg-rHfgcTrWU>~`uguolxT#snz<)uR zDT6fXu8O>7oh(bQw9Xg|WjG%-NKbD9=2Y}w4MJ-`~Q6pUfYUX8i}c^cQvzgGBf9@0cty4N5j$! zP6s&`k&kdX`r&x%Sjf43juaMj-zo!rppBBXA>?b8)75_0Gbj(8D`bE^6*akLtC2pY zqLuk96MdRC-cmvqkUZRjL;q_PQ&m)|JRn-(7scj;8D%eq(&uU;kXk(daRi{-8ODL&UYmlINl zo@~pO7p|zFQ`EVb6N6cs2Grc%SBSCXQmVc=sut<%tGD+X#E&irlX}o9jhV>ry)|40 z-|BFUt{@e+5U6W?P4uSf8n!5_uXwnzzb(%7eqVGraCJYRC-Lv6U#zy>u$o7G%gMIR zIOB_viu)Z%P)7On$y=7r1o8XKX@P1cLuU-423_R6u=nMqz5maHdFG)y)}0K-L=nvJ zWZV>zqfe}4v%ZwXzG1aph|U47^0{Bnp!-|AzexOExhxv=t^Ar*lr*$}%AhGhB10@) z%%nyudBm+Ar==YZHI&!Kas-DsitnY!iS;NOv68$AMDeMzFYPpa+w=>N8#IhjmnX~B zAuG}BFv?$~4~x#YOw@EHeal7ddfR+sPsiMzHx4niYt}aC{9!&P1Mc$I`SRUt?|7e# z>Sv>VYp7B-FPP9_C@G_(G@nF;U7 zrj599{|$UUYA?6OwudX3Tpz|7;tp9myErdbS&1+F@lAv2O|sn*Q8TzOJ&v`0{i;Im zVXZ1B=P@H&!@r@5Oc}~(HD^3-nY_bPqxdkl`@_I8{I{A;Uh}(&-zS(&`?b4CaByjk zy&(dbu!*0FkI|fF%rQ!2$uvg-X4s7f)G2 z4Q;&1=Qf9UiJ6@I6QKRIDLUC!`%i!B zTXJQvgz8N(W@JqAvS;!k7fgsaIa%wACgji5Q!9oP_#r0-hh?0~hMb4}*ToYQJ(rKV z6vyE5*JL(wbe?lM`Ar$=HfM=u^T@BM+(@z!m0lA83y%*4b5feS1o7nJXR@P)nNdoN zlvKQ}Pg!e;Zr?n+1`*bsG>q!WTwixA`27N*cO3l{F){XqqS9s^b8KH9LXtuuiF@n%VLc3X zf=Mq6-{`j#WJ`H}vvYIeFXCrZ!XGh6-}S^E2~eJqPS^ZCxgV{+$c@5vNf9i1lp2-p zKgzmRq@V9X`7cdaetqgBbO?>dvt_>8GurE zQb~Dz@e+T?t-jCjnukX@TbrG=`{oh4xp-Ft>A+#ry+3Pqo_tw|?Z!I0TmNaZd+72f z*({MA35vZ?4w>zh%}ZDJ;+Xkf-`djYmx#Xxu9e(u_@d6&`4-Uf0<2(RCk)@{5F++( z?n))9-!8w)DO4D747D8wEZ-TNVH(*gZbjQtWHgm)=b=-Aiwsi3?*sOq^X8i94_8W?V=!W@r?lG8ZC1Uti7qeJ;>+NUeST4541> zi;472IqlI8d+NHgU?KeY?x|`?*!c(my=t?pDH;SBMInJSN}2TRMQPJIlTmdW%wdfL zUC~vH6|Dv*+B|oI4n>jZI3QPdry%9au2BUY#0m>qS>EU<@JeA9dg&|EDDH*_h82lB zE?st$>v?X*)=m-y;QK$o0klN+gDaHSVT(F3n#t?J{a#;57q5RF`rf6aG)_#e*i8gz zC;SUIH!BVZAiTjBc|NO`cY_gPc@pCC>b*NqRh9pmoJk1pCmiw`?66*sB|(fGeQaa^ygb1xtXP~N<*oe07Ac|pYBl<2_>#6~mS4X38+!mpgG^OU1m2EXQFz{d31H`6xQjNR58ucYv z)o~6%^IW9DcM~LyjY!bczLGzP^Tm9X+*u;TpzR*SY4G5AOJw=F$PEkGc+^8DL9S?L1H zco~vF^u#CEhnSAH9nU(#Ad)xR=`}3&hzZV}mm5{ceSO`q86Pk}>OODyr{yO-7mzX; z{%Z$~Sgp6~&D&-c8h|?XaVvURtUzQ;JtMiCRWS?pm0{nP+5aX|*y${7JIvm8`6exK zJl<{u)quCV^n&h9_RdrF%Y@T$1dspI3i>6Tz=mgU0eif;Vtugs)w3F*Rl%r3WJC{Y zJW{qw2$I#MUGxE})Q42{#|0{OOj0_jknaPj)p{-IiYKGa_18l>=DVlzx&S67lIU~h zYNX8@j)x?OphrMQjE0TZ1vv#)Mig%eAvg7E)bDSUqGDyIeY-K%ExXp>UE|1!j>PQO z<}p=5=9~Zk24pt1^fGsU=y&4^NOPEb=@J7&txE~PQ5rq@f>zpZAF8#>pE3fptUvDG zWQb0PW6r+YdVa`hevCl{38nl(@9gsj#YM(#IwZynaHiN9Y#m=r|X#UiW` zD!f)&YFuZwt8iEvCJfO;Pne8HaU;-`LllTqbcCYhdskP~%xpR|+NTSd+4mz$J9xyG!Ieb1$_BPnB|E#@^*F18<>2dow4l$A6y#(5ySJmN1=qJ zkc=u&p;@-L=E8E7{?m{eKezo2;f+ozy}*P5h~Xv0;=46Tv8tql6u}Ej{A%!x{~5)W z&#EngAi7tY8pQ{qV|;IWT9Ei{a+2V%gaBc`khz^nY|B~X^py-S@$bMNh@vCImf{a>Z+&JwGde@(Q$yStY7Jy*4hOr{ED zh&{ose}z)Ywt=S!W@?b~`ZZ@YZzM_(#1*y2Yn-Y6QjH~LvpVq2eG{rK`%|$jQ8k75 zyxP8fFD8c=Cd{8M++<)%M;KJ{_qLGx1+7SB7^a!SlzOOl^$KXJ+m*v}|Djx{t)~)l zkXC{@)b}ZMSN$(eu>O|2)J?($z9ZB4{@qNsF8;M0Y#%xL@_rxNwDLI#)t)quUhFfR zk?bGj6@Eiuvbdg8P6{UXp9BxavO&+vhW@MYmVB9DIf@j~!f z#CL<%9!h_WFkIhiDW~#N&+-(rs5n@7Bfrb!d?JKjYB{UDb|L$7PfkoC<|H%3orPk{ z?3+N)eeG>mN-VTfVD$T9;e1oFc$?_+E!y0gES_Kv|7Cc8*vB12O<-Q;F#CPF*hhL! z*m04!vE9IWz^@(uxUzW`RC^ubO26s~Ll4cg&9YHR*%yvlp-F zsj9eFv-*c^&A}d(AEYkBjz+?3#|`Nwd@?@rmeG#M9EwynPWG#*8lWV;gvkG} zAG2`I47GOSWw;to9rpI)rK$=Y!OjnUp9=v_5<%Mz2Ab-CVhdH*ZMyi6phTTbcfR4l zWtd;cyF5lCoGcQ2FBbHjVh}`{Y-TKBX=Y%5cFI56qwL;^p15I0BmW>Z#TY_>!iXto zz?T>wmfL+}YSKnHWzay3?o%Y_>Y7f3^z(^#22YUc?$jDj9dFJsbFf@7=w4`3aek$; zJKN5qk%=L^3o3H|0ARFcSK zTRmj<~Kcfzwwip@&7ePSH!*U@%dEl>VpcQ5SDQNt6t--+-Yh-?CaM1 zOYX<`;@yhK$7*#$G=cDmR%g>LA(~GBt?ZW_4vfGdCw&>Mm{Tort)50o8+?t==-U>Q zHYEzvwXhU~5b{hw7ju8ITA&U!Aa0?&+j}(O_rb%6f~-a@6m&t8F9Q3=pfE!$Pvpys zwmDAuI4En*?#l*e^ip^iv&qyFBSuQdALg7=L`NqRJO=C~st%ceI`Qx%{-oqwxZ3{h zY|zkxs@)x-t#fLiz~T%bC#eS{{9)tpT$9?cDa5I{h!bbbhyV+l8Lv zsPYV+Vy=N-OsI>`8cY9tb9r)_K~$fL5VBz>49)w}KRe?Zv9UOO@cnRNogTclOi3D? zi|S)&kHuZmUpyYJq`O%d3z=Uu!pqh8YVzqP1weweWz?@Ck<)gi7K z3+ap_|CM$Q`|RbgDQ+idSwxqMm57e}b%Bcl7V2Z%e`U2-k*e_6s$MvzTfOX|y`iYB z9nVcMF%$Ax3NiB&13zen{mQ1&>L*w3=c;p;H37y~(!Q4vKlu2pnNpSfvsx1J6UVg^ zskdBg94){eN3j{!-li`EJNBppGbyZpf5Nbh^XaASz|XTgI@$~_+K`cBrYw%?k}Km< zK|LJ!wBI*;5QgXL_^~!CB64@vR;B#$>hyYFZ}~zDZrzrrmPX6y?w_!SE81w}Fk&2z zplITXV;xD77DMz!r>0lv*|y9Hdky%>f>;<~TO9IclXjjaHptkaI7xIlWG6a5mU6cwP=b6lMHtF>`N~p?ba?-!0ps1{>^6N;p3zdA ztDXvDL~S66zz7o_dMXQBzqq=b=03T0`^bK=n#v?@ zUpi#|@=Q-oE_4hqtm6v}mRjkW91aj-BqRC;N};PQ_e-Z3GBOo$czAFKeT1?58(}02B#m;0Zjp@!sRF zG~dT*0-+xY^Wlu@?=F;sF0G;iw&7lfJ4`KDv}&khz3%S>Qj@do!bQxYw9?OW;n3v_ z(O-U_sZwuE{FCPQmp~P`g0nK;ZF;rW8bdxegDuXy!6N^@Lf=Y(a&GeS$IvY z3#=`##459wI+RE*1kWE9Q{mn%jrNaB*Fm71*BMyCB(sSfcYE(= z%}%HQ#z^+Gzpd>xBNGR}i3B*qDF(!3r_FGRoXeH4162tx$X^ihq!0aT`PtS6A zx5s4jvxb@;03zq`TXnUnfBvw^&GiA2EC4YwxY*18nTr34Doe~onRSESyw?f?FdE5{ zGDJmQ|BwxA_>NpTP@0j1-iu~Vz7F!fh0&#$-BdhC|Y+PO$to-B)eh^@SB z820Zth~Gvqv~_*Pzmxc&0|A2GIy$cijK-r|2PX%h*O}^q_5!e_M&m^EL=XF;lGbZa zcNn>^q>Q~I^Zj(teNiI+<10Il?dJSFh_d-GC4^|y(N*PWS;R`kZQ{DM<6p`1+OnmO zo0qQTCQN!uXuSHM`aHD4ebK20DG=kf(a&*c=qgv=Yfy&sd~6c+SA%{5Z4(Qzc*{>MHIS`QB5sr) zoDM3x`xWGEPqb&dVN0n>2g9q?r==C-4nIk6bLixKb z)}H5|P*~BBg)_Mgt7nu|4>8q>-&GH&Y2>K^MtAm|QyQuGC2;*66MdhJeB*?R+H(mn z%gIME4YOHzS8-Nc{WE>88|+^z#Z>1V8K!SyR-V+O$A=kfVUELvp~UhL(ATd_lZ4p$ zyOPV?e9V`2gKxu8D78c|?EQA{DkDcroeo%NEAuW7JrT$}D8Ul9p7 z@_XrLw`=qO*YQ=6rC546|BTjZS=(;fyfaz(h1)Bw%JgOJ`1p2M$dtjUioyHQ$zn;B z3~`XvAKpgu_43l{!nbyquv@S(&skfPf+@fJWUOY}!{%T)6Z7nY;!LZ1S6nxJ-12Ll zxn#Xg4@lxy08~l`Ir);hv)4%z-2QrsXRzmQ%45&7eU9MJp<5maSkzlyuRQxTfX8kslX7-y|xb4`$OU7p>{OIa10d+VLqXNEaTB;&bnkrts{}})*^cvXM z_^xXY8;35V_^$}u_-9p#?b4h0lc1~jb8m^g!x8@-e|P7TttE;=u~7ovqvPcw68p=o8XicWTiN#kx3!^}_wV#jDpLXJt&t4u-(`D-c} z)Eq3t9Ocdv=+V|+yOzJ<`BM}LeTf^xz7Ujo1^T?%L{<$?|HxK9lvhTwYKdt8x5X$j zr_!~c##?HH>x|sj4{2r5jRJ-~EwVC#^+fzOt4J-0yrv6OKaG%=n^SSbQ2s+g8E@e1&@3KCI+zD}%JB5OB;!xajirT!muILqIZKiD1d_Tf%f;c%#ihQ7j8sb( zzZ^^pOs^g?9R#6ivkfZBPk1rLU%*_Za~dD#e(M|#~*+lM@%&lwF{j7`kx$tK-bxFdCG=f7zrr-F)PKJyTd-d`;7+YL#5 zL(4y;hmrxATO3-3%_!BShn=7QV^v4CxwgsLGWG{Q$<#h2?#q#Oj*;1E)3w1#h_0cL)yDB-ZRRQEQXur4wkx2kyr(Q;9`ny-p*sTHKC zs*EF8#1IJHw-NH@ZWDA@@;$B0n!tk!5hV0w5ltq(kx$kWt_=76R*V+~YOlbkyq{BqbcBOxDxBSACV_T?Yrh~Y>@ zMcSr^D$ZLVEYZV7*N#63IhkHqHRacZOiWLsvPiHwQRDADOD!`27c84}NW&h;W9^Dw z@!9IhiMp-T;&(o-5Jlmb3~Ahii5g4YWJ#oM3=!}?KlS3Jd3VHbx9X_DcXFeD2A>7e z*lSV4qox3HxDA`N0=Y)eI;3FQ~*D z{>b1lA1?HK_@CPOXO|*Yn+5DU%Q=)$L+quQDvhby@P+(;n?hKn~lDM*3fDS@uAKAVxDzPd1eY~ zcs>RAAG`w4LjwWMc45KO_74;Kmf|-#X!)o?2gwX2DXW-Z8VyvYB@v9)_aq25FMynQBgU-1q_@aTkBrLr5_uVshT%GTYhIlG9`=~zmTY2~nDA1Aa;|RbM3&y- zOc*8xoI*%#eIWFTHmWp&zNL{0RbRy#XqJw(1Gi?Yd+Un!BDK@$j=4DV( z3tgQ!f(6R^SJ%Dtg{Se`O2XEk(5XHr`o?D>7w1dB38x+GzLSelp`aTBq%< z=D%PXn&Yqg@F(-f#5E3Hv}Jy54GVMWOCVvWB9&GOQi0>cuhxyHL7^8@Z+|fFI+bFr zzjgNv)L2BF`6Qq#LdLWQvTulJ?IX5&-QV$Xi$CBY^$TNY_+LMY!_c)p?dR~%_jV>2 zNRq_&#>c1Zd1d*O-LzBUxxsM)OfuOyX8Enx_tL#)Q4*u8=L(-POk$ zEEXXg-CMzz&puqIDU+_YA2%0pU2%a^&24&{4eiDL>mBW>+8e)_RV9o^>M%ex&aYKc zv17xG8U_y5#^?8MvxS{UE?*KHi_1!}gR@Wo7KODd)W@M6K?svR=GRXM{ZNREVc}*a z1{Qt;#V6J!hR$Z6QS#*~4f*ghHGL4buMYB0#ignAIAaK$;km2X&nbd)q5!WylNdA1 zB<)y0l{Y&VzVIx%?M~+PM##j6@9U16y%QarTCI-rAsvAUCw9VKnwhGrHSNV2Ax0ZQ z55|ig`UsbICR=b11%9h7j>Xk)#2Ll(mNEnuzUS@&YHIDzSF_EQZ-;N&a910x;8%(7 zKjnijC||fU-?n<{6aq2R$#1PY(Hg*YuoNtwx8>}IfcF-UG7P71jAfzsC%8qa2(|v* z?h8fh8j}rXjvH35Ol0}L!Wl=vYe6L3jk$Wq5hx+(*iMSC9%{wfEBWmm+w+W7n55!} z57d1Bw64Y)EB;lY@6Voo=!*PyV?!U6%m4IQ z{f@3bJpI`>UiGu?4j=Ml#o`qP_o~3FZoq{>Mo@mB=5_kVIwQRRYDKhwMZh@ENt2!w4*Cp~1p)?u=U;NGc$F z*gzDk8DFgbU5<1$CSBVG<@hQA5hvQ#;^WNI8Q*TL!MvCC&Cfxm|Im{(j79v1a&Z*( zl&`_r%3pXe=z|Zh{gUSXVgqXXN?^XVK9}PusE*}Uz=8z2{~WmI|5?g%#44d=GhG$$ zMJOlFTG$SDs?i#7s5nq{((v9V<`I#Q!Kk&y_U=j6d3|h6*p*OT@5ftt(e<7xoIfK5 zK&lzhY5UbCnhuZoGw+2N=V&1`^kDPhaRMyDRe;Kl8ye^w7R~QCoH7p4Eky8(z_$0m zv){Op$a3j*mb4LLgieH`5}j?Q&{+D`PS#q%Yqa{TE}D?7Nz+V7V*19)k0+o1;JAy) zz*etseVSBP1P~vIV#X4mJ71m04NJpYWrc3xJ3111p2^k9pHN1O=Ww828H0IwDD!%o zOs#rqo8&wS1X1oai1RcEz9R{Hu*u1}Nw2-03&3Ix|AH%X6v7KBuH@@tmL;?{i61Hv zSIOQyTwTcjJ!mHH2$$*j!>&^1t;)7^vmRN80@3*JTMaJFxf%UX`2#K7)s$sl*(ENH zB^``hH`#EZ1y^v@?yhEz^vPcDUzT~cJwfBy)mT;s)}d&?irXhqopd^Lfet*_N0@x} zIpg~);37bDHxC({JN2*C;``X`CatiD+~;SORR*zV-m$DSO%=zPB0%sZ6koQ>4aOy>n#n9C1Xg@MuBMVbcw_RLiQc(#2{l5#rBktftIw; z_FQ4iL2;*Xx;IsP_Bt|>D%@L{1D%GMCj>^P-{)} z=HYmMcE5yfow6jArP(xPdj=NNmtRhW#4lX>BKJx%Wu7Yh2T^-FlRKa6ekOL;p?E;U z9cQMrz%$f0S&KpZ=OD2>u&0Nu?5-FK*=_JlaNDQYneA>jixS(8yv=>{{rD1( zOoB$zt-dNTD;!ua9fh*YWq#DmAL-BI?1!E?_L;vWkn|Hj$|LfZsndUsNYPMce1x;g z-AXZdt&(X6SAvMWrYT2fL~+DWf6vj?pztJ>UzS_UiFDBhoSD}RfeuSk(>V731}RHT z9z;HG_)N%bB+A%^)l(r(&>hQya#^8k6q3jmV0@)>GNc$rSXIg`*V%wV$84fX06n2*)?k?SU^x%;ktCow_PM;X9*9qxO zyMsXBts6U+0!3EERyu1_4%?1nd4qQTOZ;F#={HRWr%#i^v!>O!g;`MMWzAoypki@Tx|ErxX2pt&Tg zw)6G5w5E)sgfJ+58^RUaTml!=>>3-%n@a;o*hl!GnfYJEBq5Fo^`&SQwwHr>qW%3! zd@%y@MB^rJ1LM8i%3>>Le>}3<15-%P7PUpri2zFoTWxlx{`|chyXZeURJ;R1gjXgf zV^hawmkHAZX^6hfj1K>zFehXHnIMjMqXw8~)R3PG{u8Q``^pbdTioe{Gbc5c?wx{$ z`*j*jhtKA5t_5(`lm9QEA@!>D@~mf80T`$unhEfKUYQK*3*{g1nV4VH$`5?~d3P}r zXFUb?`{1$;Gn*dvIL+0d!&RHTFp^CtB*P;7REpC}T5@%do9MNo0uHVuZk}h| z>0HrGrXOV&BhX0Kr1OHJs&=bhw4NnkW_>TN4wS7o{x!Yid%cwd{Z{L%IQQA-`NCN(o@=?NH(`hQ#yB|^N8mEbNTvpmEUi6Cc9xybxnQsVOsU z+5oYjM7&${zRRf7tig_&cwc5~7bQ81$0eL!OgU4)=QxRcK?f&my`CvhrV+e?T(Gh@ z!=q7`Q)nKMSC>OE0{$7_v9hupUFiIGDMmHLf7q_8ptlf%wWn%EQ;VXmcwtbvul32_ z>eCLTEWQSZ`7rXVbEK54i5bBKs!yJ8V%+CzEL!OR<{EQxZMujNbeUsgT!!-*;B5Y( z)w5U&51(!!b;-~m;m3~9Cxv@1x}Y#EXX=HHH>|=7MLkBIl&er;H&*ZM*eZ43JBP>s zgz3aDhFkQ~uI_+m^`rdA2vsKPb z#BxJi>*S(&HL%@or1=WifL<Mf{YL)4GUK8jfP~RUg3Mm&xx4Z8&y}By z9?+Dw@T~f~mzU+lG3;0^&Z>jTwM(8kCficZBHm--pME~VuV;E6oc9Yf=6AsXT&S?Z z3+_do>lO1i#dX&DcBpyt1)Swoi$Wq_)F;yk3wMCJ)u+k|xD<*b#`yPRKPdm=A~5PQ zHir~}jkrfnG%XN}8S-y04Qisao>Nt4F@IHL!Xmq0wK9sH4s;>rt1q=ThX8STk1ZdBa{GOF5x0E zSkA+(zWLT=V%7{fljv?E0dk!Gj70-TahjHk5Up4}UF&XkKZ zO((94@XW=y#Dtdz(PpxP)3bB4-rziUf?eXLR2jfL02`uXc9Ijs2`cGs*bA)O=ypeEq@D* z={zVSTndXxGT*)w8x$jyzUCb6rd|g*O|LEHq&uvG)?DUY0e=u`i(wAy@mdms3zN#} zfYb|Xh2mmw2MZr)e2Tgg!&M@6!0@GE@iga(7sl$ij92+8%L$;KS*m1{#Bv)n)a4`c zREJFGSP~VGhGGa?$bG}#(D$S!9>2Ln19I=A;h87U`N!I(F0fn~mdEdP=(;!Uxent% zQ8&KHq~%20J<4N+uIY7|i3i1FU|<`p=>3g9{WyS<`B2nzGyj)g@U5D$$=Z~PiFox! zyH|6{p+LYwt$)UJ{|Fb7ijjsSY680;hJev;b$!a5R%@bG z%oq)zfIi-+D0p!vS$*CV6YtENH6b;##8<@ouzB;&DuavjU7V=A91B%a5)t=S78L;J znSp;6lv30LVLP{_z3>0Bp*Ugf zhTpipfyq+i>yD7bReOJqj~UBto+X>0oo2fI29>S6@^@<(x2g#8_3FPD85etq znS3Dy{6saOa=zqEE-eojsZfKI2X%H}aX|LL9aHXw)X z7iKrtPG#9pQa-h`;lob$VW#0jd(8VhkR$jIyX?EKxoYm@bj3&df|$0PWQQ6w|DuC5 zJ*N1VIdExr2{|i3?pT%=Cyk%TV7B>N*j2@*hew6AHpIh$N6sTOTD6z|!Wnb$-&Kq) zc0%&<(x?@|ICywDO&9at?mp`eUTZu+O?kyr`XU9OH-D+bO(XcLLB!XIQ!*}kM7J+o zRO06mQl+bnNaC}zv;A2adE7nD=%*`BS+RzGH5&~Ki%@^%Zb@G1-?vLMXy5WTj&~uc zYHO+9m-fJFlq5F~yYt8pcM?jExhL2Mg#TF=ao7?0V-3s3Fmh5m!Ls#D;{O#2M}C0&AE z^5a;D8LqY6HR9=ehYXqLW)?yf8oA?uqSnKVzTuN^bjBkri~S`wWkZK*#O<`cv`vz8 zkMGi{7n-{2L@kX{sId5~N|P&cW0lUC3bMF(7GfpNH=(w|%F6ZFg^s5suSJtJ#4X~G zkl0NCtIDIO%HcNC%%xnQj$l!5MYvE$Tp67Gy9cCcaa~%^rF-jVa@gLBN+p-AM!{bVcu#0u&R_EdL-s`}O5wW=zi?ojxeu*=+1$%5^YQ2^5EyYGWo zXXPPzLD($Cw*}k?Q zpPw^*@1rrLLEY1#i`0`qWIbpHknu$9`Bx; z$enwA7oq+Qg+aphNuB*eOs0YrDA^#F{wr-gNzY0aQ=K+Ec4i93UxE}K(AEPBfzZw) zZ9TZTunMf*2`3Q{+?Ao-)cEMt%a#M(UfL^wxGa;%RW^o9EKeU444IZwu6O`YTbn5L znvhXje;8}y6;@gHczc65U@*kXw1D5u##tFzT1?kp>GA$;)|>+LUeVQ}K5*O2T*@|^ z{C^`k5(XQ5p_;WwJZYnc4ocUuiU?l{m-;nM;KFDP)i4q@2ts;n=P6dL@Yc9r)m2;Y zWKOhEi#x~y8Nvce1;AuUZs!f5F72vu0T57`WDgGVVsCh`v1Cv9XVR?!-`4Quz*~=a z;jLUGEUq>vl2}H@xtkEU?ibXV6s_W&KghWboq7=SJW}yl&&+lGZaT;r%y9cvcBHk9 zjKv6yPX~UJ_kB2C%O)J*N>>aL?Nd#xlvDf}EEQ-{x%4-Qi4!$wDV%npW{!eKnG1I) z5r)qNhCz-JmVtUe)ghWAs`H_|+{%#?X(boUi+z>vLU+1N? zOT*1rWRFw3bWPdi2hJo~-2R@UWu&We@tVvsxm01z4E80XmGbPb$8tD8yi zcP&5SwZAELeLa4aS!17Y1+aB6sbxFO2lxuQM}=MRkE;-TXPOZ*Fszf={9-p;PD|w{ zgP3b_S&#*y@u!QI62f0}t;)yKohV=|}X6D`(ceOt4 zJHY6D#^7Q<$C$}%U7P9prxj^>y}0U06^;^rR8E~>|!op>SPZ20B~_|@vw4mvvTmO zaRLQ6c?39km^nBFI5={)BrgBI0k-yL-z_}=rep_face[2]: - frame = self.face_swapper.get(frame, faces[i], rep_face[1], paste_back=True) - del faces[i] + def process_faces(self, frame): + faces = self.__get_faces(frame, max_num=0) + if not faces: + return frame + + faces = sorted(faces, key=lambda face: face.bbox[0]) # Sort left to right + + if self.multiple_faces_mode: + for idx, face in enumerate(faces): + if idx >= len(self.replacement_faces): break + frame = self.face_swapper.get(frame, face, self.replacement_faces[idx][1], paste_back=True) + elif self.disable_similarity: + for face in faces: + frame = self.face_swapper.get(frame, face, self.replacement_faces[0][1], paste_back=True) + else: + for rep_face in self.replacement_faces: + for i in range(len(faces) - 1, -1, -1): + sim = self.rec_app.compute_sim(rep_face[0], faces[i].embedding) + if sim >= rep_face[2]: + frame = self.face_swapper.get(frame, faces[i], rep_face[1], paste_back=True) + del faces[i] + break return frame - def __check_video_has_audio(self,video_path): + def reface_group(self, faces, frames, output): + with ThreadPoolExecutor(max_workers=self.use_num_cpus) as executor: + if self.first_face: + results = list(tqdm(executor.map(self.process_first_face, frames), total=len(frames), desc="Processing frames")) + else: + results = list(tqdm(executor.map(self.process_faces, frames), total=len(frames), desc="Processing frames")) + for result in results: + output.write(result) + + def __check_video_has_audio(self, video_path): self.video_has_audio = False probe = ffmpeg.probe(video_path) audio_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'audio'), None) if audio_stream is not None: self.video_has_audio = True - - def reface_group(self, faces, frames, output): - with ThreadPoolExecutor(max_workers = self.use_num_cpus) as executor: - if self.first_face: - results = list(tqdm(executor.map(self.process_first_face, frames), total=len(frames),desc="Processing frames")) - else: - results = list(tqdm(executor.map(self.process_faces, frames), total=len(frames),desc="Processing frames")) - for result in results: - output.write(result) - def reface(self, video_path, faces): + def reface(self, video_path, faces, preview=False, disable_similarity=False, multiple_faces_mode=False): + original_name = osp.splitext(osp.basename(video_path))[0] + timestamp = str(int(time.time())) + filename = f"{original_name}_preview.mp4" if preview else f"{original_name}_{timestamp}.mp4" + self.__check_video_has_audio(video_path) - output_video_path = os.path.join('out',Path(video_path).name) - self.prepare_faces(faces) + os.makedirs("output", exist_ok=True) + output_video_path = os.path.join('output', filename) + self.prepare_faces(faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode) + self.first_face = False if multiple_faces_mode else (faces[0].get("origin") is None or disable_similarity) - cap = cv2.VideoCapture(video_path) + cap = cv2.VideoCapture(video_path, cv2.CAP_FFMPEG) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - print(f"Total frames: {total_frames}") - fps = cap.get(cv2.CAP_PROP_FPS) frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fourcc = cv2.VideoWriter_fourcc(*'mp4v') output = cv2.VideoWriter(output_video_path, fourcc, fps, (frame_width, frame_height)) - - frames=[] - self.k = 1 - with tqdm(total=total_frames,desc="Extracting frames") as pbar: + + frames = [] + frame_index = 0 + skip_rate = 10 if preview else 1 + + with tqdm(total=total_frames, desc="Extracting frames") as pbar: while cap.isOpened(): flag, frame = cap.read() - if flag and len(frame)>0: - frames.append(frame.copy()) - pbar.update() - else: + if not flag: break - if (len(frames) > 1000): - self.reface_group(faces,frames,output) - frames=[] + if frame_index % skip_rate == 0: + frames.append(frame) + if len(frames) > 300: + self.reface_group(faces, frames, output) + frames = [] + gc.collect() + frame_index += 1 + pbar.update() - cap.release() - pbar.close() - - self.reface_group(faces,frames,output) - frames=[] + cap.release() + if frames: + self.reface_group(faces, frames, output) output.release() - - return self.__convert_video(video_path,output_video_path) - + + converted_path = self.__convert_video(video_path, output_video_path, preview=preview) + + if video_path.lower().endswith(".gif"): + gif_output_path = converted_path.replace(".mp4", ".gif") + self.__generate_gif(converted_path, gif_output_path) + return converted_path, gif_output_path + + return converted_path, None + + def __generate_gif(self, video_path, gif_output_path): + print(f"Generating GIF at {gif_output_path}") + ( + ffmpeg + .input(video_path) + .output(gif_output_path, vf='fps=10,scale=512:-1:flags=lanczos', loop=0) + .overwrite_output() + .run(quiet=True) + ) + + def __convert_video(self, video_path, output_video_path, preview=False): + if self.video_has_audio and not preview: + new_path = output_video_path + str(random.randint(0, 999)) + "_c.mp4" + in1 = ffmpeg.input(output_video_path) + in2 = ffmpeg.input(video_path) + out = ffmpeg.output(in1.video, in2.audio, new_path, video_bitrate=self.ffmpeg_video_bitrate, vcodec=self.ffmpeg_video_encoder) + out.run(overwrite_output=True, quiet=True) + else: + new_path = output_video_path + print(f"Refaced video saved at: {os.path.abspath(new_path)}") + return new_path + + def reface_image(self, image_path, faces, disable_similarity=False, multiple_faces_mode=False): + self.prepare_faces(faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode) + self.first_face = False if multiple_faces_mode else (faces[0].get("origin") is None or disable_similarity) + + bgr_image = cv2.imread(image_path) + if bgr_image is None: + raise ValueError("Failed to read input image") + + refaced_bgr = self.process_first_face(bgr_image.copy()) if self.first_face else self.process_faces(bgr_image.copy()) + refaced_rgb = cv2.cvtColor(refaced_bgr, cv2.COLOR_BGR2RGB) + pil_img = Image.fromarray(refaced_rgb) + os.makedirs("output", exist_ok=True) + original_name = osp.splitext(osp.basename(image_path))[0] + timestamp = str(int(time.time())) + filename = f"{original_name}_{timestamp}.jpg" + output_path = os.path.join("output", filename) + pil_img.save(output_path, format='JPEG', quality=100, subsampling=0) + output_path = enhance_image(output_path) + print(f"Saved refaced image to {output_path}") + return output_path + + def extract_faces_from_image(self, image_path, max_faces=5): + frame = cv2.imread(image_path) + if frame is None: + raise ValueError("Failed to read input image for face extraction.") + + faces = self.__get_faces(frame, max_num=max_faces) + cropped_faces = [] + + for face in faces: + x1, y1, x2, y2 = map(int, face.bbox) + x1 = max(x1, 0) + y1 = max(y1, 0) + x2 = min(x2, frame.shape[1]) + y2 = min(y2, frame.shape[0]) + + cropped = frame[y1:y2, x1:x2] + pil_img = Image.fromarray(cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB)) + + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png") + pil_img.save(temp_file.name) + cropped_faces.append(temp_file.name) + + if len(cropped_faces) >= max_faces: + break + + return cropped_faces + def __try_ffmpeg_encoder(self, vcodec): - print(f"Trying FFMPEG {vcodec} encoder") - command = ['ffmpeg', '-y', '-f','lavfi','-i','testsrc=duration=1:size=1280x720:rate=30','-vcodec',vcodec,'testsrc.mp4'] + command = ['ffmpeg', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=1:size=1280x720:rate=30', '-vcodec', vcodec, 'testsrc.mp4'] try: subprocess.run(command, check=True, capture_output=True).stderr - except subprocess.CalledProcessError as e: - print(f"FFMPEG {vcodec} encoder doesn't work -> Disabled.") + except subprocess.CalledProcessError: return False - print(f"FFMPEG {vcodec} encoder works") return True - - def __check_encoders(self): - self.ffmpeg_video_encoder='libx264' - self.ffmpeg_video_bitrate='0' + def __check_encoders(self): + self.ffmpeg_video_encoder = 'libx264' + self.ffmpeg_video_bitrate = '0' pattern = r"encoders: ([a-zA-Z0-9_]+(?: [a-zA-Z0-9_]+)*)" command = ['ffmpeg', '-codecs', '--list-encoders'] commandout = subprocess.run(command, check=True, capture_output=True).stdout result = commandout.decode('utf-8').split('\n') for r in result: - if "264" in r: - encoders = re.search(pattern, r).group(1).split(' ') - for v_c in Refacer.VIDEO_CODECS: - for v_k in encoders: - if v_c == v_k: - if self.__try_ffmpeg_encoder(v_k): - self.ffmpeg_video_encoder=v_k - self.ffmpeg_video_bitrate=Refacer.VIDEO_CODECS[v_k] - print(f"Video codec for FFMPEG: {self.ffmpeg_video_encoder}") + if "264" in r: + encoders = re.search(pattern, r) + if encoders: + for v_c in Refacer.VIDEO_CODECS: + for v_k in encoders.group(1).split(' '): + if v_c == v_k and self.__try_ffmpeg_encoder(v_k): + self.ffmpeg_video_encoder = v_k + self.ffmpeg_video_bitrate = Refacer.VIDEO_CODECS[v_k] return VIDEO_CODECS = { - 'h264_videotoolbox':'0', #osx HW acceleration - 'h264_nvenc':'0', #NVIDIA HW acceleration - #'h264_qsv', #Intel HW acceleration - #'h264_vaapi', #Intel HW acceleration - #'h264_omx', #HW acceleration - 'libx264':'0' #No HW acceleration + 'h264_videotoolbox': '0', + 'h264_nvenc': '0', + 'libx264': '0' } diff --git a/refacer_bulk.py b/refacer_bulk.py new file mode 100644 index 0000000..2628f01 --- /dev/null +++ b/refacer_bulk.py @@ -0,0 +1,75 @@ +# refacer_bulk.py +# +# Example usage: +# python refacer_bulk.py --input_path ./input --dest_face myface.jpg --facetoreplace face1.jpg --threshold 0.3 +# +# Or, to disable similarity check (i.e., just apply the destination face to all detected faces): +# python refacer_bulk.py --input_path ./input --dest_face myface.jpg + +import argparse +import os +import cv2 +from pathlib import Path +from refacer import Refacer +from PIL import Image +import time +import pyfiglet + +def parse_args(): + parser = argparse.ArgumentParser(description="Bulk Image Refacer") + parser.add_argument("--input_path", type=str, required=True, help="Directory containing input images") + parser.add_argument("--dest_face", type=str, required=True, help="Path to destination face image") + parser.add_argument("--facetoreplace", type=str, default=None, help="Path to face to replace (origin face)") + parser.add_argument("--threshold", type=float, default=0.2, help="Similarity threshold (default: 0.2)") + parser.add_argument("--force_cpu", action="store_true", help="Force CPU mode") + parser.add_argument("--colab_performance", action="store_true", help="Enable Colab performance tweaks") + return parser.parse_args() + +def main(): + print("\033[94m" + pyfiglet.Figlet(font='slant').renderText("NeoRefacer") + "\033[0m") + + args = parse_args() + + input_dir = Path(args.input_path) + + refacer = Refacer(force_cpu=args.force_cpu, colab_performance=args.colab_performance) + + # Load destination and origin face + dest_img = cv2.imread(args.dest_face) + if dest_img is None: + raise ValueError(f"Destination face image not found: {args.dest_face}") + + origin_img = None + if args.facetoreplace: + origin_img = cv2.imread(args.facetoreplace) + if origin_img is None: + raise ValueError(f"Face to replace image not found: {args.facetoreplace}") + + disable_similarity = origin_img is None + + faces_config = [{ + 'origin': origin_img, + 'destination': dest_img, + 'threshold': args.threshold + }] + + refacer.prepare_faces(faces_config, disable_similarity=disable_similarity) + + print(f"Processing images from: {input_dir}") + image_files = list(input_dir.glob("*")) + supported_exts = {'.jpg', '.jpeg', '.png', '.bmp', '.webp'} + + for image_path in image_files: + if image_path.suffix.lower() not in supported_exts: + print(f"Skipping non-image file: {image_path}") + continue + + print(f"Refacing: {image_path}") + try: + refaced_path = refacer.reface_image(str(image_path), faces_config, disable_similarity=disable_similarity) + print(f"Saved to: {refaced_path}") + except Exception as e: + print(f"Failed to process {image_path}: {e}") + +if __name__ == "__main__": + main() diff --git a/refacer_video.py b/refacer_video.py deleted file mode 100644 index 5f06ec5..0000000 --- a/refacer_video.py +++ /dev/null @@ -1,41 +0,0 @@ -from refacer import Refacer -from os.path import exists -import argparse -import cv2 - -parser = argparse.ArgumentParser(description='Refacer') -parser.add_argument("--force_cpu", help="Force CPU mode", default=False, action="store_true") -parser.add_argument("--colab_performance", help="Use in colab for better performance", default=False,action="store_true") -parser.add_argument("--face", help="Face to replace (ex: ,,)", nargs='+', action="append", required=True) -parser.add_argument("--video", help="Video to parse", required=True) -args = parser.parse_args() - -refacer = Refacer(force_cpu=args.force_cpu,colab_performance=args.colab_performance) - -def run(video_path,faces): - video_path_exists = exists(video_path) - if video_path_exists == False: - print ("Can't find " + video_path) - return - - faces_out = [] - for face in faces: - face_str = face[0].split(",") - origin = exists(face_str[0]) - if origin == False: - print ("Can't find " + face_str[0]) - return - destination = exists(face_str[1]) - if destination == False: - print ("Can't find " + face_str[1]) - return - - faces_out.append({ - 'origin':cv2.imread(face_str[0]), - 'destination':cv2.imread(face_str[1]), - 'threshold':float(face_str[2]) - }) - - return refacer.reface(video_path,faces_out) - -run(args.video, args.face) diff --git a/requirements-COREML.txt b/requirements-COREML.txt index 15908b9..d234538 100644 --- a/requirements-COREML.txt +++ b/requirements-COREML.txt @@ -1,5 +1,6 @@ ffmpeg_python==0.2.0 -gradio==3.33.1 +imageio[ffmpeg]==2.37.0 +gradio==5.22.0 insightface==0.7.3 numpy==1.24.3 onnx==1.14.0 @@ -7,6 +8,12 @@ onnxruntime-silicon opencv_python==4.7.0.72 opencv_python_headless==4.7.0.72 scikit-image==0.20.0 -tqdm -psutil -ngrok +tqdm==4.67.1 +psutil==7.0.0 +ngrok==1.4.0 +pyfiglet==1.0.2 +# codeformer dependencies +torch==2.6.0 +torchvision==0.21.0 +gdown==5.2.0 +lpips==0.1.4 \ No newline at end of file diff --git a/requirements-CPU.txt b/requirements-CPU.txt index e934c05..4462627 100644 --- a/requirements-CPU.txt +++ b/requirements-CPU.txt @@ -1,12 +1,19 @@ ffmpeg_python==0.2.0 -gradio==3.33.1 +imageio[ffmpeg]==2.37.0 +gradio==5.22.0 insightface==0.7.3 numpy==1.24.3 onnx==1.14.0 -onnxruntime==1.15.0 +onnxruntime==1.21.0 opencv_python==4.7.0.72 opencv_python_headless==4.7.0.72 scikit-image==0.20.0 -tqdm -psutil -ngrok \ No newline at end of file +tqdm==4.67.1 +psutil==7.0.0 +ngrok==1.4.0 +pyfiglet==1.0.2 +# codeformer dependencies +torch==2.6.0 +torchvision==0.21.0 +gdown==5.2.0 +lpips==0.1.4 \ No newline at end of file diff --git a/requirements-GPU.txt b/requirements-GPU.txt index 19afdcb..7e083ff 100644 --- a/requirements-GPU.txt +++ b/requirements-GPU.txt @@ -1,12 +1,19 @@ ffmpeg_python==0.2.0 -gradio==3.33.1 +imageio[ffmpeg]==2.37.0 +gradio==5.22.0 insightface==0.7.3 numpy==1.24.3 onnx==1.14.0 -onnxruntime_gpu==1.15.0 +onnxruntime_gpu==1.21.0 opencv_python==4.7.0.72 opencv_python_headless==4.7.0.72 scikit-image==0.20.0 -tqdm -psutil -ngrok \ No newline at end of file +tqdm==4.67.1 +psutil==7.0.0 +ngrok==1.4.0 +pyfiglet==1.0.2 +# codeformer dependencies +torch==2.6.0 +torchvision==0.21.0 +gdown==5.2.0 +lpips==0.1.4 \ No newline at end of file diff --git a/weights/inswapper/.gitkeep b/weights/inswapper/.gitkeep new file mode 100644 index 0000000..e69de29