Compare commits

..

12 Commits

Author SHA1 Message Date
KRSHH 48c83151a4 size 2025-02-03 20:59:06 +05:30
KRSHH bb3502d9bd update branch
update branch
2025-01-31 22:32:47 +05:30
KRSHH a101a1f3f1 Reorganized switches 2025-01-30 20:20:04 +05:30
KRSHH 01ef955372 Improve eyebrow mask 2025-01-30 20:14:16 +05:30
KRSHH ab3b73631b Shift masking features to face_masking.py 2025-01-30 20:04:31 +05:30
KRSHH d8fc1ffa04 Eyebrow Mask 2025-01-30 19:58:15 +05:30
KRSHH 5dfd1c0ced Eye Mask 2025-01-30 19:20:58 +05:30
KRSHH 59cd3be0f9 Simplify Privacy mode switch state 2025-01-27 00:47:34 +05:30
KRSHH ccb676ac17 FPS Switch re-enable 2025-01-26 23:41:44 +05:30
KRSHH f0c66732e7 Use Logo 2025-01-26 23:20:08 +05:30
KRSHH 8055d79daf Privacy Mode 2025-01-26 23:10:45 +05:30
KRSHH 3c7dd1a574 Hardcoded Keep FPS and Keep Frames 2025-01-23 18:45:43 +05:30
42 changed files with 990 additions and 3722 deletions
-2
View File
@@ -25,5 +25,3 @@ models/DMDNet.pth
faceswap/
.vscode/
switch_states.json
/models
install.bat
+1
View File
@@ -0,0 +1 @@
3.10.0
+42 -108
View File
@@ -1,4 +1,4 @@
<h1 align="center">Deep-Live-Cam 2.0.5c</h1>
<h1 align="center">Deep-Live-Cam</h1>
<p align="center">
Real-time face swap and video deepfake with a single click and only a single image.
@@ -30,13 +30,18 @@ By using this software, you agree to these terms and commit to using it in a man
Users are expected to use this software responsibly and legally. If using a real person's face, obtain their consent and clearly label any output as a deepfake when sharing online. We are not responsible for end-user actions.
## Exclusive v2.6d Quick Start - Pre-built (Windows/Mac Silicon)
<a href="https://deeplivecam.net/index.php/quickstart"> <img src="media/Download.png" width="285" height="77" />
## Quick Start - Pre-built (Windows / Nvidia)
##### This is the fastest build you can get if you have a discrete NVIDIA or AMD GPU or Mac Silicon, And you'll receive special priority support.
<a href="https://hacksider.gumroad.com/l/vccdmm"> <img src="https://github.com/user-attachments/assets/7d993b32-e3e8-4cd3-bbfb-a549152ebdd5" width="285" height="77" />
##### This is the fastest build you can get if you have a discrete NVIDIA GPU.
## Quick Start - Pre-built (Mac / Silicon)
<a href="https://krshh.gumroad.com/l/Deep-Live-Cam-Mac"> <img src="https://github.com/user-attachments/assets/d5d913b5-a7de-4609-96b9-979a5749a703" width="285" height="77" />
###### These Pre-builts are perfect for non-technical users or those who don't have time to, or can't manually install all the requirements. Just a heads-up: this is an open-source project, so you can also install it manually.
###### These Pre-builts are perfect for non-technical users or those who dont have time to, or can't manually install all the requirements. Just a heads-up: this is an open-source project, so you can also install it manually.
## TLDR; Live Deepfake in just 3 Clicks
![easysteps](https://github.com/user-attachments/assets/af825228-852c-411b-b787-ffd9aac72fc6)
@@ -44,7 +49,7 @@ Users are expected to use this software responsibly and legally. If using a real
2. Select which camera to use
3. Press live!
## Features & Uses - Everything is in real-time
## Features & Uses - Everything is real-time
### Mouth Mask
@@ -80,7 +85,7 @@ Users are expected to use this software responsibly and legally. If using a real
### Memes
**Create Your Most Viral Meme Yet**
**Create Your most viral meme yet**
<p align="center">
<img src="media/meme.gif" alt="show" width="450">
@@ -88,17 +93,10 @@ Users are expected to use this software responsibly and legally. If using a real
<sub>Created using Many Faces feature in Deep-Live-Cam</sub>
</p>
### Omegle
**Surprise people on Omegle**
<p align="center">
<video src="https://github.com/user-attachments/assets/2e9b9b82-fa04-4b70-9f56-b1f68e7672d0" width="450" controls></video>
</p>
## Installation (Manual)
**Please be aware that the installation requires technical skills and is not for beginners. Consider downloading the quickstart version.**
**Please be aware that the installation requires technical skills and is not for beginners. Consider downloading the prebuilt version.**
<details>
<summary>Click to see the process</summary>
@@ -109,7 +107,7 @@ This is more likely to work on your computer but will be slower as it utilizes t
**1. Set up Your Platform**
- Python (3.11 recommended)
- Python (3.10 recommended)
- pip
- git
- [ffmpeg](https://www.youtube.com/watch?v=OlNWCpFdVMA) - ```iex (irm ffmpeg.tc.ht)```
@@ -118,13 +116,12 @@ This is more likely to work on your computer but will be slower as it utilizes t
**2. Clone the Repository**
```bash
git clone https://github.com/hacksider/Deep-Live-Cam.git
cd Deep-Live-Cam
https://github.com/hacksider/Deep-Live-Cam.git
```
**3. Download the Models**
1. [GFPGANv1.4](https://huggingface.co/hacksider/deep-live-cam/resolve/main/GFPGANv1.4.onnx)
1. [GFPGANv1.4](https://huggingface.co/hacksider/deep-live-cam/resolve/main/GFPGANv1.4.pth)
2. [inswapper\_128\_fp16.onnx](https://huggingface.co/hacksider/deep-live-cam/resolve/main/inswapper_128_fp16.onnx)
Place these files in the "**models**" folder.
@@ -133,57 +130,14 @@ Place these files in the "**models**" folder.
We highly recommend using a `venv` to avoid issues.
For Windows:
```bash
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
```
For Linux:
```bash
# Ensure you use the installed Python 3.10
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
**For macOS:**
Apple Silicon (M1/M2/M3) requires specific setup:
**For macOS:** Install or upgrade the `python-tk` package:
```bash
# Install Python 3.11 (specific version is important)
brew install python@3.11
# Install tkinter package (required for the GUI)
brew install python-tk@3.10
# Create and activate virtual environment with Python 3.11
python3.11 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
```
** In case something goes wrong and you need to reinstall the virtual environment **
```bash
# Deactivate the virtual environment
rm -rf venv
# Reinstall the virtual environment
python -m venv venv
source venv/bin/activate
# install the dependencies again
pip install -r requirements.txt
# gfpgan and basicsrs issue fix
pip install git+https://github.com/xinntao/BasicSR.git@master
pip uninstall gfpgan -y
pip install git+https://github.com/TencentARC/GFPGAN.git@master
```
**Run:** If you don't have a GPU, you can run Deep-Live-Cam using `python run.py`. Note that initial execution will download models (~300MB).
@@ -192,16 +146,12 @@ pip install git+https://github.com/TencentARC/GFPGAN.git@master
**CUDA Execution Provider (Nvidia)**
1. Install [CUDA Toolkit 12.8.0](https://developer.nvidia.com/cuda-12-8-0-download-archive)
2. Install [cuDNN v8.9.7 for CUDA 12.x](https://developer.nvidia.com/rdp/cudnn-archive) (required for onnxruntime-gpu):
- Download cuDNN v8.9.7 for CUDA 12.x
- Make sure the cuDNN bin directory is in your system PATH
3. Install dependencies:
1. Install [CUDA Toolkit 11.8](https://developer.nvidia.com/cuda-11-8-0-download-archive) or [CUDA Toolkit 12.1.1](https://developer.nvidia.com/cuda-12-1-1-download-archive)
2. Install dependencies:
```bash
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128
pip uninstall onnxruntime onnxruntime-gpu
pip install onnxruntime-gpu==1.21.0
pip install onnxruntime-gpu==1.16.3
```
3. Usage:
@@ -212,46 +162,26 @@ python run.py --execution-provider cuda
**CoreML Execution Provider (Apple Silicon)**
Apple Silicon (M1/M2/M3) specific installation:
1. Make sure you've completed the macOS setup above using Python 3.10.
2. Install dependencies:
1. Install dependencies:
```bash
pip uninstall onnxruntime onnxruntime-silicon
pip install onnxruntime-silicon==1.13.1
```
3. Usage (important: specify Python 3.10):
2. Usage:
```bash
python3.10 run.py --execution-provider coreml
python run.py --execution-provider coreml
```
**Important Notes for macOS:**
- You **must** use Python 3.10, not newer versions like 3.11 or 3.13
- Always run with `python3.10` command not just `python` if you have multiple Python versions installed
- If you get error about `_tkinter` missing, reinstall the tkinter package: `brew reinstall python-tk@3.10`
- If you get model loading errors, check that your models are in the correct folder
- If you encounter conflicts with other Python versions, consider uninstalling them:
```bash
# List all installed Python versions
brew list | grep python
# Uninstall conflicting versions if needed
brew uninstall --ignore-dependencies python@3.11 python@3.13
# Keep only Python 3.11
brew cleanup
```
**CoreML Execution Provider (Apple Legacy)**
1. Install dependencies:
```bash
pip uninstall onnxruntime onnxruntime-coreml
pip install onnxruntime-coreml==1.21.0
pip install onnxruntime-coreml==1.13.1
```
2. Usage:
@@ -266,7 +196,7 @@ python run.py --execution-provider coreml
```bash
pip uninstall onnxruntime onnxruntime-directml
pip install onnxruntime-directml==1.21.0
pip install onnxruntime-directml==1.15.1
```
2. Usage:
@@ -281,7 +211,7 @@ python run.py --execution-provider directml
```bash
pip uninstall onnxruntime onnxruntime-openvino
pip install onnxruntime-openvino==1.21.0
pip install onnxruntime-openvino==1.15.0
```
2. Usage:
@@ -289,6 +219,7 @@ pip install onnxruntime-openvino==1.21.0
```bash
python run.py --execution-provider openvino
```
</details>
## Usage
@@ -338,22 +269,24 @@ Looking for a CLI mode? Using the -s/--source argument will make the run program
## Press
- [**Ars Technica**](https://arstechnica.com/information-technology/2024/08/new-ai-tool-enables-real-time-face-swapping-on-webcams-raising-fraud-concerns/) - *"Deep-Live-Cam goes viral, allowing anyone to become a digital doppelganger"*
- [**Yahoo!**](https://www.yahoo.com/tech/ok-viral-ai-live-stream-080041056.html) - *"OK, this viral AI live stream software is truly terrifying"*
- [**CNN Brasil**](https://www.cnnbrasil.com.br/tecnologia/ia-consegue-clonar-rostos-na-webcam-entenda-funcionamento/) - *"AI can clone faces on webcam; understand how it works"*
- [**Bloomberg Technoz**](https://www.bloombergtechnoz.com/detail-news/71032/kenalan-dengan-teknologi-deep-live-cam-bisa-jadi-alat-menipu) - *"Get to know Deep Live Cam technology, it can be used as a tool for deception."*
- [**TrendMicro**](https://www.trendmicro.com/vinfo/gb/security/news/cyber-attacks/ai-vs-ai-deepfakes-and-ekyc) - *"AI vs AI: DeepFakes and eKYC"*
- [**PetaPixel**](https://petapixel.com/2024/08/14/deep-live-cam-deepfake-ai-tool-lets-you-become-anyone-in-a-video-call-with-single-photo-mark-zuckerberg-jd-vance-elon-musk/) - *"Deepfake AI Tool Lets You Become Anyone in a Video Call With Single Photo"*
- [**SomeOrdinaryGamers**](https://www.youtube.com/watch?time_continue=1074&v=py4Tc-Y8BcY) - *"That's Crazy, Oh God. That's Fucking Freaky Dude... That's So Wild Dude"*
- [**IShowSpeed**](https://www.youtube.com/live/mFsCe7AIxq8?feature=shared&t=2686) - *"Alright look look look, now look chat, we can do any face we want to look like chat"*
- [**TechLinked (Linus Tech Tips)**](https://www.youtube.com/watch?v=wnCghLjqv3s&t=551s) - *"They do a pretty good job matching poses, expression and even the lighting"*
- [**IShowSpeed**](https://youtu.be/JbUPRmXRUtE?t=3964) - *"What the F***! Why do I look like Vinny Jr? I look exactly like Vinny Jr!? No, this shit is crazy! Bro This is F*** Crazy!"*
**We are always open to criticism and are ready to improve, that's why we didn't cherry-pick anything.**
- [*"Deep-Live-Cam goes viral, allowing anyone to become a digital doppelganger"*](https://arstechnica.com/information-technology/2024/08/new-ai-tool-enables-real-time-face-swapping-on-webcams-raising-fraud-concerns/) - Ars Technica
- [*"Thanks Deep Live Cam, shapeshifters are among us now"*](https://dataconomy.com/2024/08/15/what-is-deep-live-cam-github-deepfake/) - Dataconomy
- [*"This free AI tool lets you become anyone during video-calls"*](https://www.newsbytesapp.com/news/science/deep-live-cam-ai-impersonation-tool-goes-viral/story) - NewsBytes
- [*"OK, this viral AI live stream software is truly terrifying"*](https://www.creativebloq.com/ai/ok-this-viral-ai-live-stream-software-is-truly-terrifying) - Creative Bloq
- [*"Deepfake AI Tool Lets You Become Anyone in a Video Call With Single Photo"*](https://petapixel.com/2024/08/14/deep-live-cam-deepfake-ai-tool-lets-you-become-anyone-in-a-video-call-with-single-photo-mark-zuckerberg-jd-vance-elon-musk/) - PetaPixel
- [*"Deep-Live-Cam Uses AI to Transform Your Face in Real-Time, Celebrities Included"*](https://www.techeblog.com/deep-live-cam-ai-transform-face/) - TechEBlog
- [*"An AI tool that "makes you look like anyone" during a video call is going viral online"*](https://telegrafi.com/en/a-tool-that-makes-you-look-like-anyone-during-a-video-call-is-going-viral-on-the-Internet/) - Telegrafi
- [*"This Deepfake Tool Turning Images Into Livestreams is Topping the GitHub Charts"*](https://decrypt.co/244565/this-deepfake-tool-turning-images-into-livestreams-is-topping-the-github-charts) - Emerge
- [*"New Real-Time Face-Swapping AI Allows Anyone to Mimic Famous Faces"*](https://www.digitalmusicnews.com/2024/08/15/face-swapping-ai-real-time-mimic/) - Digital Music News
- [*"This real-time webcam deepfake tool raises alarms about the future of identity theft"*](https://www.diyphotography.net/this-real-time-webcam-deepfake-tool-raises-alarms-about-the-future-of-identity-theft/) - DIYPhotography
- [*"That's Crazy, Oh God. That's Fucking Freaky Dude... That's So Wild Dude"*](https://www.youtube.com/watch?time_continue=1074&v=py4Tc-Y8BcY) - SomeOrdinaryGamers
- [*"Alright look look look, now look chat, we can do any face we want to look like chat"*](https://www.youtube.com/live/mFsCe7AIxq8?feature=shared&t=2686) - IShowSpeed
## Credits
- [ffmpeg](https://ffmpeg.org/): for making video-related operations easy
- [Henry](https://github.com/henryruhs): One of the major contributor in this repo
- [deepinsight](https://github.com/deepinsight): for their [insightface](https://github.com/deepinsight/insightface) project which provided a well-made library and models. Please be reminded that the [use of the model is for non-commercial research purposes only](https://github.com/deepinsight/insightface?tab=readme-ov-file#license).
- [havok2-htwo](https://github.com/havok2-htwo): for sharing the code for webcam
- [GosuDRM](https://github.com/GosuDRM): for the open version of roop
@@ -361,7 +294,6 @@ Looking for a CLI mode? Using the -s/--source argument will make the run program
- [vic4key](https://github.com/vic4key): For supporting/contributing to this project
- [kier007](https://github.com/kier007): for improving the user experience
- [qitianai](https://github.com/qitianai): for multi-lingual support
- [laurigates](https://github.com/laurigates): Decoupling stuffs to make everything faster!
- and [all developers](https://github.com/hacksider/Deep-Live-Cam/graphs/contributors) behind libraries used in this project.
- Footnote: Please be informed that the base author of the code is [s0md3v](https://github.com/s0md3v/roop)
- All the wonderful users who helped make this project go viral by starring the repo ❤️
@@ -381,3 +313,5 @@ Looking for a CLI mode? Using the -s/--source argument will make the run program
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=hacksider/deep-live-cam&type=Date" />
</picture>
</a>
-46
View File
@@ -1,46 +0,0 @@
{
"Source x Target Mapper": "Quelle x Ziel Zuordnung",
"select a source image": "Wähle ein Quellbild",
"Preview": "Vorschau",
"select a target image or video": "Wähle ein Zielbild oder Video",
"save image output file": "Bildausgabedatei speichern",
"save video output file": "Videoausgabedatei speichern",
"select a target image": "Wähle ein Zielbild",
"source": "Quelle",
"Select a target": "Wähle ein Ziel",
"Select a face": "Wähle ein Gesicht",
"Keep audio": "Audio beibehalten",
"Face Enhancer": "Gesichtsverbesserung",
"Many faces": "Mehrere Gesichter",
"Show FPS": "FPS anzeigen",
"Keep fps": "FPS beibehalten",
"Keep frames": "Frames beibehalten",
"Fix Blueish Cam": "Bläuliche Kamera korrigieren",
"Mouth Mask": "Mundmaske",
"Show Mouth Mask Box": "Mundmaskenrahmen anzeigen",
"Start": "Starten",
"Live": "Live",
"Destroy": "Beenden",
"Map faces": "Gesichter zuordnen",
"Processing...": "Verarbeitung läuft...",
"Processing succeed!": "Verarbeitung erfolgreich!",
"Processing ignored!": "Verarbeitung ignoriert!",
"Failed to start camera": "Kamera konnte nicht gestartet werden",
"Please complete pop-up or close it.": "Bitte das Pop-up komplettieren oder schließen.",
"Getting unique faces": "Einzigartige Gesichter erfassen",
"Please select a source image first": "Bitte zuerst ein Quellbild auswählen",
"No faces found in target": "Keine Gesichter im Zielbild gefunden",
"Add": "Hinzufügen",
"Clear": "Löschen",
"Submit": "Absenden",
"Select source image": "Quellbild auswählen",
"Select target image": "Zielbild auswählen",
"Please provide mapping!": "Bitte eine Zuordnung angeben!",
"At least 1 source with target is required!": "Mindestens eine Quelle mit einem Ziel ist erforderlich!",
"At least 1 source with target is required!": "Mindestens eine Quelle mit einem Ziel ist erforderlich!",
"Face could not be detected in last upload!": "Im letzten Upload konnte kein Gesicht erkannt werden!",
"Select Camera:": "Kamera auswählen:",
"All mappings cleared!": "Alle Zuordnungen gelöscht!",
"Mappings successfully submitted!": "Zuordnungen erfolgreich übermittelt!",
"Source x Target Mapper is already open.": "Quell-zu-Ziel-Zuordnung ist bereits geöffnet."
}
-46
View File
@@ -1,46 +0,0 @@
{
"Source x Target Mapper": "Mapeador de fuente x destino",
"select a source image": "Seleccionar imagen fuente",
"Preview": "Vista previa",
"select a target image or video": "elegir un video o una imagen fuente",
"save image output file": "guardar imagen final",
"save video output file": "guardar video final",
"select a target image": "elegir una imagen objetiva",
"source": "fuente",
"Select a target": "Elegir un destino",
"Select a face": "Elegir una cara",
"Keep audio": "Mantener audio original",
"Face Enhancer": "Potenciador de caras",
"Many faces": "Varias caras",
"Show FPS": "Mostrar fps",
"Keep fps": "Mantener fps",
"Keep frames": "Mantener frames",
"Fix Blueish Cam": "Corregir tono azul de video",
"Mouth Mask": "Máscara de boca",
"Show Mouth Mask Box": "Mostrar área de la máscara de boca",
"Start": "Iniciar",
"Live": "En vivo",
"Destroy": "Borrar",
"Map faces": "Mapear caras",
"Processing...": "Procesando...",
"Processing succeed!": "¡Proceso terminado con éxito!",
"Processing ignored!": "¡Procesamiento omitido!",
"Failed to start camera": "No se pudo iniciar la cámara",
"Please complete pop-up or close it.": "Complete o cierre el pop-up",
"Getting unique faces": "Buscando caras únicas",
"Please select a source image first": "Primero, seleccione una imagen fuente",
"No faces found in target": "No se encontró una cara en el destino",
"Add": "Agregar",
"Clear": "Limpiar",
"Submit": "Enviar",
"Select source image": "Seleccionar imagen fuente",
"Select target image": "Seleccionar imagen destino",
"Please provide mapping!": "Por favor, proporcione un mapeo",
"At least 1 source with target is required!": "Se requiere al menos una fuente con un destino.",
"At least 1 source with target is required!": "Se requiere al menos una fuente con un destino.",
"Face could not be detected in last upload!": "¡No se pudo encontrar una cara en el último video o imagen!",
"Select Camera:": "Elegir cámara:",
"All mappings cleared!": "¡Todos los mapeos fueron borrados!",
"Mappings successfully submitted!": "Mapeos enviados con éxito!",
"Source x Target Mapper is already open.": "El mapeador de fuente x destino ya está abierto."
}
-46
View File
@@ -1,46 +0,0 @@
{
"Source x Target Mapper": "Source x Target Kartoitin",
"select an source image": "Valitse lähde kuva",
"Preview": "Esikatsele",
"select an target image or video": "Valitse kohde kuva tai video",
"save image output file": "tallenna kuva",
"save video output file": "tallenna video",
"select an target image": "Valitse kohde kuva",
"source": "lähde",
"Select a target": "Valitse kohde",
"Select a face": "Valitse kasvot",
"Keep audio": "Säilytä ääni",
"Face Enhancer": "Kasvojen Parantaja",
"Many faces": "Useampia kasvoja",
"Show FPS": "Näytä FPS",
"Keep fps": "Säilytä FPS",
"Keep frames": "Säilytä ruudut",
"Fix Blueish Cam": "Korjaa Sinertävä Kamera",
"Mouth Mask": "Suu Maski",
"Show Mouth Mask Box": "Näytä Suu Maski Laatiko",
"Start": "Aloita",
"Live": "Live",
"Destroy": "Tuhoa",
"Map faces": "Kartoita kasvot",
"Processing...": "Prosessoi...",
"Processing succeed!": "Prosessointi onnistui!",
"Processing ignored!": "Prosessointi lopetettu!",
"Failed to start camera": "Kameran käynnistäminen epäonnistui",
"Please complete pop-up or close it.": "Viimeistele tai sulje ponnahdusikkuna",
"Getting unique faces": "Hankitaan uniikkeja kasvoja",
"Please select a source image first": "Valitse ensin lähde kuva",
"No faces found in target": "Kasvoja ei löydetty kohteessa",
"Add": "Lisää",
"Clear": "Tyhjennä",
"Submit": "Lähetä",
"Select source image": "Valitse lähde kuva",
"Select target image": "Valitse kohde kuva",
"Please provide mapping!": "Tarjoa kartoitus!",
"Atleast 1 source with target is required!": "Vähintään 1 lähde kohteen kanssa on vaadittu!",
"At least 1 source with target is required!": "Vähintään 1 lähde kohteen kanssa on vaadittu!",
"Face could not be detected in last upload!": "Kasvoja ei voitu tunnistaa edellisessä latauksessa!",
"Select Camera:": "Valitse Kamera:",
"All mappings cleared!": "Kaikki kartoitukset tyhjennetty!",
"Mappings successfully submitted!": "Kartoitukset lähetety onnistuneesti!",
"Source x Target Mapper is already open.": "Lähde x Kohde Kartoittaja on jo auki."
}
-45
View File
@@ -1,45 +0,0 @@
{
"Source x Target Mapper": "Pemetaan Sumber x Target",
"select a source image": "Pilih gambar sumber",
"Preview": "Pratinjau",
"select a target image or video": "Pilih gambar atau video target",
"save image output file": "Simpan file keluaran gambar",
"save video output file": "Simpan file keluaran video",
"select a target image": "Pilih gambar target",
"source": "Sumber",
"Select a target": "Pilih target",
"Select a face": "Pilih wajah",
"Keep audio": "Pertahankan audio",
"Face Enhancer": "Peningkat wajah",
"Many faces": "Banyak wajah",
"Show FPS": "Tampilkan FPS",
"Keep fps": "Pertahankan FPS",
"Keep frames": "Pertahankan frame",
"Fix Blueish Cam": "Perbaiki kamera kebiruan",
"Mouth Mask": "Masker mulut",
"Show Mouth Mask Box": "Tampilkan kotak masker mulut",
"Start": "Mulai",
"Live": "Langsung",
"Destroy": "Hentikan",
"Map faces": "Petakan wajah",
"Processing...": "Sedang memproses...",
"Processing succeed!": "Pemrosesan berhasil!",
"Processing ignored!": "Pemrosesan diabaikan!",
"Failed to start camera": "Gagal memulai kamera",
"Please complete pop-up or close it.": "Harap selesaikan atau tutup pop-up.",
"Getting unique faces": "Mengambil wajah unik",
"Please select a source image first": "Silakan pilih gambar sumber terlebih dahulu",
"No faces found in target": "Tidak ada wajah ditemukan pada target",
"Add": "Tambah",
"Clear": "Bersihkan",
"Submit": "Kirim",
"Select source image": "Pilih gambar sumber",
"Select target image": "Pilih gambar target",
"Please provide mapping!": "Harap tentukan pemetaan!",
"At least 1 source with target is required!": "Minimal 1 sumber dengan target diperlukan!",
"Face could not be detected in last upload!": "Wajah tidak dapat terdeteksi pada unggahan terakhir!",
"Select Camera:": "Pilih Kamera:",
"All mappings cleared!": "Semua pemetaan telah dibersihkan!",
"Mappings successfully submitted!": "Pemetaan berhasil dikirim!",
"Source x Target Mapper is already open.": "Pemetaan Sumber x Target sudah terbuka."
}
-45
View File
@@ -1,45 +0,0 @@
{
"Source x Target Mapper": "ប្រភប x បន្ថែម Mapper",
"select a source image": "ជ្រើសរើសប្រភពរូបភាព",
"Preview": "បង្ហាញ",
"select a target image or video": "ជ្រើសរើសគោលដៅរូបភាពឬវីដេអូ",
"save image output file": "រក្សាទុកលទ្ធផលឯកសាររូបភាព",
"save video output file": "រក្សាទុកលទ្ធផលឯកសារវីដេអូ",
"select a target image": "ជ្រើសរើសគោលដៅរូបភាព",
"source": "ប្រភព",
"Select a target": "ជ្រើសរើសគោលដៅ",
"Select a face": "ជ្រើសរើសមុខ",
"Keep audio": "រម្លងសម្លេង",
"Face Enhancer": "ឧបករណ៍ពង្រឹងមុខ",
"Many faces": "ទម្រង់មុខច្រើន",
"Show FPS": "បង្ហាញ FPS",
"Keep fps": "រម្លង fps",
"Keep frames": "រម្លងទម្រង់",
"Fix Blueish Cam": "ជួសជុល Cam Blueish",
"Mouth Mask": "របាំងមាត់",
"Show Mouth Mask Box": "បង្ហាញប្រអប់របាំងមាត់",
"Start": "ចាប់ផ្ដើម",
"Live": "ផ្សាយផ្ទាល់",
"Destroy": "លុប",
"Map faces": "ផែនទីមុខ",
"Processing...": "កំពុងដំណើរការ...",
"Processing succeed!": "ការដំណើរការទទួលបានជោគជ័យ!",
"Processing ignored!": "ការដំណើរការមិនទទួលបានជោគជ័យ!",
"Failed to start camera": "បរាជ័យដើម្បីចាប់ផ្ដើមបើកកាមេរ៉ា",
"Please complete pop-up or close it.": "សូមបញ្ចប់ផ្ទាំងផុស ឬបិទវា.",
"Getting unique faces": "ការចាប់ផ្ដើមទម្រង់មុខប្លែក",
"Please select a source image first": "សូមជ្រើសរើសប្រភពរូបភាពដំបូង",
"No faces found in target": "រកអត់ឃើញមុខនៅក្នុងគោលដៅ",
"Add": "បន្ថែម",
"Clear": "សម្អាត",
"Submit": "បញ្ចូន",
"Select source image": "ជ្រើសរើសប្រភពរូបភាព",
"Select target image": "ជ្រើសរើសគោលដៅរូបភាព",
"Please provide mapping!": "សូមផ្ដល់នៅផែនទី",
"At least 1 source with target is required!": "ត្រូវការប្រភពយ៉ាងហោចណាស់ ១ ដែលមានគោលដៅ!",
"Face could not be detected in last upload!": "មុខមិនអាចភ្ជាប់នៅក្នុងការបង្ហេាះចុងក្រោយ!",
"Select Camera:": "ជ្រើសរើសកាមេរ៉ា",
"All mappings cleared!": "ផែនទីទាំងអស់ត្រូវបានសម្អាត!",
"Mappings successfully submitted!": "ផែនទីត្រូវបានបញ្ជូនជោគជ័យ!",
"Source x Target Mapper is already open.": "ប្រភព x Target Mapper បានបើករួចហើយ។"
}
-45
View File
@@ -1,45 +0,0 @@
{
"Source x Target Mapper": "소스 x 타겟 매퍼",
"select a source image": "소스 이미지 선택",
"Preview": "미리보기",
"select a target image or video": "타겟 이미지 또는 영상 선택",
"save image output file": "이미지 출력 파일 저장",
"save video output file": "영상 출력 파일 저장",
"select a target image": "타겟 이미지 선택",
"source": "소스",
"Select a target": "타겟 선택",
"Select a face": "얼굴 선택",
"Keep audio": "오디오 유지",
"Face Enhancer": "얼굴 향상",
"Many faces": "여러 얼굴",
"Show FPS": "FPS 표시",
"Keep fps": "FPS 유지",
"Keep frames": "프레임 유지",
"Fix Blueish Cam": "푸른빛 카메라 보정",
"Mouth Mask": "입 마스크",
"Show Mouth Mask Box": "입 마스크 박스 표시",
"Start": "시작",
"Live": "라이브",
"Destroy": "종료",
"Map faces": "얼굴 매핑",
"Processing...": "처리 중...",
"Processing succeed!": "처리 성공!",
"Processing ignored!": "처리 무시됨!",
"Failed to start camera": "카메라 시작 실패",
"Please complete pop-up or close it.": "팝업을 완료하거나 닫아주세요.",
"Getting unique faces": "고유 얼굴 가져오는 중",
"Please select a source image first": "먼저 소스 이미지를 선택해주세요",
"No faces found in target": "타겟에서 얼굴을 찾을 수 없음",
"Add": "추가",
"Clear": "지우기",
"Submit": "제출",
"Select source image": "소스 이미지 선택",
"Select target image": "타겟 이미지 선택",
"Please provide mapping!": "매핑을 입력해주세요!",
"At least 1 source with target is required!": "최소 하나의 소스와 타겟이 필요합니다!",
"Face could not be detected in last upload!": "최근 업로드에서 얼굴을 감지할 수 없습니다!",
"Select Camera:": "카메라 선택:",
"All mappings cleared!": "모든 매핑이 삭제되었습니다!",
"Mappings successfully submitted!": "매핑이 성공적으로 제출되었습니다!",
"Source x Target Mapper is already open.": "소스 x 타겟 매퍼가 이미 열려 있습니다."
}
-46
View File
@@ -1,46 +0,0 @@
{
"Source x Target Mapper": "Mapeador de Origem x Destino",
"select an source image": "Escolha uma imagem de origem",
"Preview": "Prévia",
"select an target image or video": "Escolha uma imagem ou vídeo de destino",
"save image output file": "Salvar imagem final",
"save video output file": "Salvar vídeo final",
"select an target image": "Escolha uma imagem de destino",
"source": "Origem",
"Select a target": "Escolha o destino",
"Select a face": "Escolha um rosto",
"Keep audio": "Manter o áudio original",
"Face Enhancer": "Melhorar rosto",
"Many faces": "Vários rostos",
"Show FPS": "Mostrar FPS",
"Keep fps": "Manter FPS",
"Keep frames": "Manter frames",
"Fix Blueish Cam": "Corrigir tom azulado da câmera",
"Mouth Mask": "Máscara da boca",
"Show Mouth Mask Box": "Mostrar área da máscara da boca",
"Start": "Começar",
"Live": "Ao vivo",
"Destroy": "Destruir",
"Map faces": "Mapear rostos",
"Processing...": "Processando...",
"Processing succeed!": "Tudo certo!",
"Processing ignored!": "Processamento ignorado!",
"Failed to start camera": "Não foi possível iniciar a câmera",
"Please complete pop-up or close it.": "Finalize ou feche o pop-up",
"Getting unique faces": "Buscando rostos diferentes",
"Please select a source image first": "Selecione primeiro uma imagem de origem",
"No faces found in target": "Nenhum rosto encontrado na imagem de destino",
"Add": "Adicionar",
"Clear": "Limpar",
"Submit": "Enviar",
"Select source image": "Escolha a imagem de origem",
"Select target image": "Escolha a imagem de destino",
"Please provide mapping!": "Você precisa realizar o mapeamento!",
"Atleast 1 source with target is required!": "É necessária pelo menos uma origem com um destino!",
"At least 1 source with target is required!": "É necessária pelo menos uma origem com um destino!",
"Face could not be detected in last upload!": "Não conseguimos detectar o rosto na última imagem!",
"Select Camera:": "Escolher câmera:",
"All mappings cleared!": "Todos os mapeamentos foram removidos!",
"Mappings successfully submitted!": "Mapeamentos enviados com sucesso!",
"Source x Target Mapper is already open.": "O Mapeador de Origem x Destino já está aberto."
}
-45
View File
@@ -1,45 +0,0 @@
{
"Source x Target Mapper": "Сопоставитель Источник x Цель",
"select a source image": "выберите исходное изображение",
"Preview": "Предпросмотр",
"select a target image or video": "выберите целевое изображение или видео",
"save image output file": "сохранить выходной файл изображения",
"save video output file": "сохранить выходной файл видео",
"select a target image": "выберите целевое изображение",
"source": "источник",
"Select a target": "Выберите целевое изображение",
"Select a face": "Выберите лицо",
"Keep audio": "Сохранить аудио",
"Face Enhancer": "Улучшение лица",
"Many faces": "Несколько лиц",
"Show FPS": "Показать FPS",
"Keep fps": "Сохранить FPS",
"Keep frames": "Сохранить кадры",
"Fix Blueish Cam": "Исправить синеву камеры",
"Mouth Mask": "Маска рта",
"Show Mouth Mask Box": "Показать рамку маски рта",
"Start": "Старт",
"Live": "В реальном времени",
"Destroy": "Остановить",
"Map faces": "Сопоставить лица",
"Processing...": "Обработка...",
"Processing succeed!": "Обработка успешна!",
"Processing ignored!": "Обработка проигнорирована!",
"Failed to start camera": "Не удалось запустить камеру",
"Please complete pop-up or close it.": "Пожалуйста, заполните всплывающее окно или закройте его.",
"Getting unique faces": "Получение уникальных лиц",
"Please select a source image first": "Сначала выберите исходное изображение, пожалуйста",
"No faces found in target": "В целевом изображении не найдено лиц",
"Add": "Добавить",
"Clear": "Очистить",
"Submit": "Отправить",
"Select source image": "Выбрать исходное изображение",
"Select target image": "Выбрать целевое изображение",
"Please provide mapping!": "Пожалуйста, укажите сопоставление!",
"At least 1 source with target is required!": "Требуется хотя бы 1 источник с целью!",
"Face could not be detected in last upload!": "Лицо не обнаружено в последнем загруженном изображении!",
"Select Camera:": "Выберите камеру:",
"All mappings cleared!": "Все сопоставления очищены!",
"Mappings successfully submitted!": "Сопоставления успешно отправлены!",
"Source x Target Mapper is already open.": "Сопоставитель Источник-Цель уже открыт."
}
-45
View File
@@ -1,45 +0,0 @@
{
"Source x Target Mapper": "ตัวจับคู่ต้นทาง x ปลายทาง",
"select a source image": "เลือกรูปภาพต้นฉบับ",
"Preview": "ตัวอย่าง",
"select a target image or video": "เลือกรูปภาพหรือวิดีโอเป้าหมาย",
"save image output file": "บันทึกไฟล์รูปภาพ",
"save video output file": "บันทึกไฟล์วิดีโอ",
"select a target image": "เลือกรูปภาพเป้าหมาย",
"source": "ต้นฉบับ",
"Select a target": "เลือกเป้าหมาย",
"Select a face": "เลือกใบหน้า",
"Keep audio": "เก็บเสียง",
"Face Enhancer": "ปรับปรุงใบหน้า",
"Many faces": "หลายใบหน้า",
"Show FPS": "แสดง FPS",
"Keep fps": "คงค่า FPS",
"Keep frames": "คงค่าเฟรม",
"Fix Blueish Cam": "แก้ไขภาพอมฟ้าจากกล้อง",
"Mouth Mask": "มาสก์ปาก",
"Show Mouth Mask Box": "แสดงกรอบมาสก์ปาก",
"Start": "เริ่ม",
"Live": "สด",
"Destroy": "หยุด",
"Map faces": "จับคู่ใบหน้า",
"Processing...": "กำลังประมวลผล...",
"Processing succeed!": "ประมวลผลสำเร็จแล้ว!",
"Processing ignored!": "การประมวลผลถูกละเว้น",
"Failed to start camera": "ไม่สามารถเริ่มกล้องได้",
"Please complete pop-up or close it.": "โปรดดำเนินการในป๊อปอัปให้เสร็จสิ้น หรือปิด",
"Getting unique faces": "กำลังค้นหาใบหน้าที่ไม่ซ้ำกัน",
"Please select a source image first": "โปรดเลือกภาพต้นฉบับก่อน",
"No faces found in target": "ไม่พบใบหน้าในภาพเป้าหมาย",
"Add": "เพิ่ม",
"Clear": "ล้าง",
"Submit": "ส่ง",
"Select source image": "เลือกภาพต้นฉบับ",
"Select target image": "เลือกภาพเป้าหมาย",
"Please provide mapping!": "โปรดระบุการจับคู่!",
"At least 1 source with target is required!": "ต้องมีการจับคู่ต้นฉบับกับเป้าหมายอย่างน้อย 1 คู่!",
"Face could not be detected in last upload!": "ไม่สามารถตรวจพบใบหน้าในไฟล์อัปโหลดล่าสุด!",
"Select Camera:": "เลือกกล้อง:",
"All mappings cleared!": "ล้างการจับคู่ทั้งหมดแล้ว!",
"Mappings successfully submitted!": "ส่งการจับคู่สำเร็จแล้ว!",
"Source x Target Mapper is already open.": "ตัวจับคู่ต้นทาง x ปลายทาง เปิดอยู่แล้ว"
}
+5 -5
View File
@@ -1,11 +1,11 @@
{
"Source x Target Mapper": "Source x Target Mapper",
"select a source image": "选择一个源图像",
"select an source image": "选择一个源图像",
"Preview": "预览",
"select a target image or video": "选择一个目标图像或视频",
"select an target image or video": "选择一个目标图像或视频",
"save image output file": "保存图像输出文件",
"save video output file": "保存视频输出文件",
"select a target image": "选择一个目标图像",
"select an target image": "选择一个目标图像",
"source": "源",
"Select a target": "选择一个目标",
"Select a face": "选择一张脸",
@@ -36,11 +36,11 @@
"Select source image": "请选取源图像",
"Select target image": "请选取目标图像",
"Please provide mapping!": "请提供映射",
"At least 1 source with target is required!": "至少需要一个来源图像与目标图像相关!",
"Atleast 1 source with target is required!": "至少需要一个来源图像与目标图像相关!",
"At least 1 source with target is required!": "至少需要一个来源图像与目标图像相关!",
"Face could not be detected in last upload!": "最近上传的图像中没有检测到人脸!",
"Select Camera:": "选择摄像头",
"All mappings cleared!": "所有映射均已清除!",
"Mappings successfully submitted!": "成功提交映射!",
"Source x Target Mapper is already open.": "源 x 目标映射器已打开。"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

-18
View File
@@ -1,18 +0,0 @@
import os
import cv2
import numpy as np
# Utility function to support unicode characters in file paths for reading
def imread_unicode(path, flags=cv2.IMREAD_COLOR):
return cv2.imdecode(np.fromfile(path, dtype=np.uint8), flags)
# Utility function to support unicode characters in file paths for writing
def imwrite_unicode(path, img, params=None):
root, ext = os.path.splitext(path)
if not ext:
ext = ".png"
result, encoded_img = cv2.imencode(ext, img, params if params else [])
result, encoded_img = cv2.imencode(f".{ext}", img, params if params is not None else [])
encoded_img.tofile(path)
return True
return False
+1 -2
View File
@@ -1,7 +1,6 @@
from typing import Any
import cv2
import modules.globals # Import the globals to check the color correction toggle
from modules.gpu_processing import gpu_cvt_color
def get_video_frame(video_path: str, frame_number: int = 0) -> Any:
@@ -20,7 +19,7 @@ def get_video_frame(video_path: str, frame_number: int = 0) -> Any:
if has_frame and modules.globals.color_correction:
# Convert the frame color if necessary
frame = gpu_cvt_color(frame, cv2.COLOR_BGR2RGB)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
capture.release()
return frame if has_frame else None
+17 -57
View File
@@ -11,11 +11,7 @@ import platform
import signal
import shutil
import argparse
try:
import torch
HAS_TORCH = True
except ImportError:
HAS_TORCH = False
import torch
import onnxruntime
import tensorflow
@@ -24,13 +20,13 @@ import modules.metadata
import modules.ui as ui
from modules.processors.frame.core import get_frame_processors_modules
from modules.utilities import has_image_extension, is_image, is_video, detect_fps, create_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clean_temp, normalize_output_path
from modules.fake_face_handler import cleanup_fake_face
if HAS_TORCH and 'ROCMExecutionProvider' in modules.globals.execution_providers:
if 'ROCMExecutionProvider' in modules.globals.execution_providers:
del torch
warnings.filterwarnings('ignore', category=FutureWarning, module='insightface')
if HAS_TORCH:
warnings.filterwarnings('ignore', category=UserWarning, module='torchvision')
warnings.filterwarnings('ignore', category=UserWarning, module='torchvision')
def parse_args() -> None:
@@ -39,10 +35,8 @@ def parse_args() -> None:
program.add_argument('-s', '--source', help='select an source image', dest='source_path')
program.add_argument('-t', '--target', help='select an target image or video', dest='target_path')
program.add_argument('-o', '--output', help='select output file or directory', dest='output_path')
program.add_argument('--frame-processor', help='pipeline of frame processors', dest='frame_processor', default=['face_swapper'], choices=['face_swapper', 'face_enhancer', 'face_enhancer_gpen256', 'face_enhancer_gpen512'], nargs='+')
program.add_argument('--keep-fps', help='keep original fps', dest='keep_fps', action='store_true', default=False)
program.add_argument('--frame-processor', help='pipeline of frame processors', dest='frame_processor', default=['face_swapper'], choices=['face_swapper', 'face_enhancer'], nargs='+')
program.add_argument('--keep-audio', help='keep original audio', dest='keep_audio', action='store_true', default=True)
program.add_argument('--keep-frames', help='keep temporary frames', dest='keep_frames', action='store_true', default=False)
program.add_argument('--many-faces', help='process every face', dest='many_faces', action='store_true', default=False)
program.add_argument('--nsfw-filter', help='filter the NSFW image or video', dest='nsfw_filter', action='store_true', default=False)
program.add_argument('--map-faces', help='map source target faces', dest='map_faces', action='store_true', default=False)
@@ -70,9 +64,9 @@ def parse_args() -> None:
modules.globals.output_path = normalize_output_path(modules.globals.source_path, modules.globals.target_path, args.output_path)
modules.globals.frame_processors = args.frame_processor
modules.globals.headless = args.source_path or args.target_path or args.output_path
modules.globals.keep_fps = args.keep_fps
modules.globals.keep_fps = True
modules.globals.keep_frames = True
modules.globals.keep_audio = args.keep_audio
modules.globals.keep_frames = args.keep_frames
modules.globals.many_faces = args.many_faces
modules.globals.mouth_mask = args.mouth_mask
modules.globals.nsfw_filter = args.nsfw_filter
@@ -86,9 +80,11 @@ def parse_args() -> None:
modules.globals.execution_threads = args.execution_threads
modules.globals.lang = args.lang
#for ENHANCER tumblers:
for enhancer_key in ('face_enhancer', 'face_enhancer_gpen256', 'face_enhancer_gpen512'):
modules.globals.fp_ui[enhancer_key] = enhancer_key in args.frame_processor
#for ENHANCER tumbler:
if 'face_enhancer' in args.frame_processor:
modules.globals.fp_ui['face_enhancer'] = True
else:
modules.globals.fp_ui['face_enhancer'] = False
# translate deprecated args
if args.source_path_deprecated:
@@ -132,22 +128,11 @@ def suggest_execution_providers() -> List[str]:
def suggest_execution_threads() -> int:
"""Suggest optimal thread count based on hardware and execution provider."""
import os
# Get CPU count
cpu_count = os.cpu_count() or 4
if 'DmlExecutionProvider' in modules.globals.execution_providers:
return 1
if 'ROCMExecutionProvider' in modules.globals.execution_providers:
return 1
if 'CUDAExecutionProvider' in modules.globals.execution_providers:
# For CUDA, use more threads for parallel frame processing
return min(cpu_count, 16)
# For CPU execution, use most cores but leave some for system
return max(4, min(cpu_count - 2, 16))
return 8
def limit_resources() -> None:
@@ -170,7 +155,7 @@ def limit_resources() -> None:
def release_resources() -> None:
if 'CUDAExecutionProvider' in modules.globals.execution_providers and HAS_TORCH:
if 'CUDAExecutionProvider' in modules.globals.execution_providers:
torch.cuda.empty_cache()
@@ -190,16 +175,10 @@ def update_status(message: str, scope: str = 'DLC.CORE') -> None:
ui.update_status(message)
def start() -> None:
"""Start processing with performance monitoring."""
import time
start_time = time.time()
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
if not frame_processor.pre_start():
return
update_status('Processing...')
# process image to image
if has_image_extension(modules.globals.target_path):
if modules.globals.nsfw_filter and ui.check_and_ignore_nsfw(modules.globals.target_path, destroy):
@@ -213,40 +192,26 @@ def start() -> None:
frame_processor.process_image(modules.globals.source_path, modules.globals.output_path, modules.globals.output_path)
release_resources()
if is_image(modules.globals.target_path):
elapsed = time.time() - start_time
update_status(f'Processing to image succeed! (Time: {elapsed:.2f}s)')
update_status('Processing to image succeed!')
else:
update_status('Processing to image failed!')
return
# process image to videos
if modules.globals.nsfw_filter and ui.check_and_ignore_nsfw(modules.globals.target_path, destroy):
return
extraction_start = time.time()
if not modules.globals.map_faces:
update_status('Creating temp resources...')
create_temp(modules.globals.target_path)
update_status('Extracting frames...')
extract_frames(modules.globals.target_path)
extraction_time = time.time() - extraction_start
update_status(f'Frame extraction completed in {extraction_time:.2f}s')
temp_frame_paths = get_temp_frame_paths(modules.globals.target_path)
total_frames = len(temp_frame_paths)
update_status(f'Processing {total_frames} frames with {modules.globals.execution_threads} threads...')
processing_start = time.time()
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
update_status('Progressing...', frame_processor.NAME)
frame_processor.process_video(modules.globals.source_path, temp_frame_paths)
release_resources()
processing_time = time.time() - processing_start
fps_processing = total_frames / processing_time if processing_time > 0 else 0
update_status(f'Frame processing completed in {processing_time:.2f}s ({fps_processing:.2f} fps)')
# handles fps
encoding_start = time.time()
if modules.globals.keep_fps:
update_status('Detecting fps...')
fps = detect_fps(modules.globals.target_path)
@@ -255,9 +220,6 @@ def start() -> None:
else:
update_status('Creating video with 30.0 fps...')
create_video(modules.globals.target_path)
encoding_time = time.time() - encoding_start
update_status(f'Video encoding completed in {encoding_time:.2f}s')
# handle audio
if modules.globals.keep_audio:
if modules.globals.keep_fps:
@@ -267,13 +229,10 @@ def start() -> None:
restore_audio(modules.globals.target_path, modules.globals.output_path)
else:
move_temp(modules.globals.target_path, modules.globals.output_path)
# clean and validate
clean_temp(modules.globals.target_path)
total_time = time.time() - start_time
if is_video(modules.globals.target_path):
update_status(f'Processing to video succeed! Total time: {total_time:.2f}s')
update_status('Processing to video succeed!')
else:
update_status('Processing to video failed!')
@@ -281,6 +240,7 @@ def start() -> None:
def destroy(to_quit=True) -> None:
if modules.globals.target_path:
clean_temp(modules.globals.target_path)
cleanup_fake_face()
if to_quit: quit()
-7
View File
@@ -1,7 +0,0 @@
from typing import Any
from insightface.app.common import Face
import numpy
Face = Face
Frame = numpy.ndarray[Any, Any]
Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

+14 -24
View File
@@ -2,7 +2,6 @@ import os
import shutil
from typing import Any
import insightface
import threading
import cv2
import numpy as np
@@ -14,23 +13,14 @@ from modules.utilities import get_temp_directory_path, create_temp, extract_fram
from pathlib import Path
FACE_ANALYSER = None
FACE_ANALYSER_LOCK = threading.Lock()
def get_face_analyser() -> Any:
"""Get face analyser with thread-safe initialization."""
global FACE_ANALYSER
if FACE_ANALYSER is None:
with FACE_ANALYSER_LOCK:
# Double-check after acquiring lock
if FACE_ANALYSER is None:
FACE_ANALYSER = insightface.app.FaceAnalysis(
name='buffalo_l',
providers=modules.globals.execution_providers,
allowed_modules=['detection', 'recognition']
)
FACE_ANALYSER.prepare(ctx_id=0, det_size=(320, 320))
FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=modules.globals.execution_providers)
FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640))
return FACE_ANALYSER
@@ -49,13 +39,13 @@ def get_many_faces(frame: Frame) -> Any:
return None
def has_valid_map() -> bool:
for map in modules.globals.source_target_map:
for map in modules.globals.souce_target_map:
if "source" in map and "target" in map:
return True
return False
def default_source_face() -> Any:
for map in modules.globals.source_target_map:
for map in modules.globals.souce_target_map:
if "source" in map:
return map['source']['face']
return None
@@ -63,7 +53,7 @@ def default_source_face() -> Any:
def simplify_maps() -> Any:
centroids = []
faces = []
for map in modules.globals.source_target_map:
for map in modules.globals.souce_target_map:
if "source" in map and "target" in map:
centroids.append(map['target']['face'].normed_embedding)
faces.append(map['source']['face'])
@@ -74,10 +64,10 @@ def simplify_maps() -> Any:
def add_blank_map() -> Any:
try:
max_id = -1
if len(modules.globals.source_target_map) > 0:
max_id = max(modules.globals.source_target_map, key=lambda x: x['id'])['id']
if len(modules.globals.souce_target_map) > 0:
max_id = max(modules.globals.souce_target_map, key=lambda x: x['id'])['id']
modules.globals.source_target_map.append({
modules.globals.souce_target_map.append({
'id' : max_id + 1
})
except ValueError:
@@ -85,14 +75,14 @@ def add_blank_map() -> Any:
def get_unique_faces_from_target_image() -> Any:
try:
modules.globals.source_target_map = []
modules.globals.souce_target_map = []
target_frame = cv2.imread(modules.globals.target_path)
many_faces = get_many_faces(target_frame)
i = 0
for face in many_faces:
x_min, y_min, x_max, y_max = face['bbox']
modules.globals.source_target_map.append({
modules.globals.souce_target_map.append({
'id' : i,
'target' : {
'cv2' : target_frame[int(y_min):int(y_max), int(x_min):int(x_max)],
@@ -106,7 +96,7 @@ def get_unique_faces_from_target_image() -> Any:
def get_unique_faces_from_target_video() -> Any:
try:
modules.globals.source_target_map = []
modules.globals.souce_target_map = []
frame_face_embeddings = []
face_embeddings = []
@@ -137,7 +127,7 @@ def get_unique_faces_from_target_video() -> Any:
face['target_centroid'] = closest_centroid_index
for i in range(len(centroids)):
modules.globals.source_target_map.append({
modules.globals.souce_target_map.append({
'id' : i
})
@@ -145,7 +135,7 @@ def get_unique_faces_from_target_video() -> Any:
for frame in tqdm(frame_face_embeddings, desc=f"Mapping frame embeddings to centroids-{i}"):
temp.append({'frame': frame['frame'], 'faces': [face for face in frame['faces'] if face['target_centroid'] == i], 'location': frame['location']})
modules.globals.source_target_map[i]['target_faces_in_frame'] = temp
modules.globals.souce_target_map[i]['target_faces_in_frame'] = temp
# dump_faces(centroids, frame_face_embeddings)
default_target_face()
@@ -154,7 +144,7 @@ def get_unique_faces_from_target_video() -> Any:
def default_target_face():
for map in modules.globals.source_target_map:
for map in modules.globals.souce_target_map:
best_face = None
best_frame = None
for frame in map['target_faces_in_frame']:
+120
View File
@@ -0,0 +1,120 @@
import os
import requests
import tempfile
from pathlib import Path
import cv2
import numpy as np
import modules.globals
def add_padding_to_face(image, padding_ratio=0.3):
"""Add padding around the face image
Args:
image: The input face image
padding_ratio: Amount of padding to add as a ratio of image dimensions
Returns:
Padded image with background padding added
"""
if image is None:
return None
height, width = image.shape[:2]
pad_x = int(width * padding_ratio)
pad_y = int(height * padding_ratio)
# Create larger image with padding
padded_height = height + 2 * pad_y
padded_width = width + 2 * pad_x
padded_image = np.zeros((padded_height, padded_width, 3), dtype=np.uint8)
# Fill padded area with blurred and darkened edge pixels
edge_color = cv2.blur(image, (15, 15))
edge_color = (edge_color * 0.6).astype(np.uint8) # Darken the padding
# Fill the padded image with original face
padded_image[pad_y:pad_y+height, pad_x:pad_x+width] = image
# Fill padding areas with edge color
# Top padding - repeat first row
top_edge = edge_color[0, :, :]
for i in range(pad_y):
padded_image[i, pad_x:pad_x+width] = top_edge
# Bottom padding - repeat last row
bottom_edge = edge_color[-1, :, :]
for i in range(pad_y):
padded_image[pad_y+height+i, pad_x:pad_x+width] = bottom_edge
# Left padding - repeat first column
left_edge = edge_color[:, 0, :]
for i in range(pad_x):
padded_image[pad_y:pad_y+height, i] = left_edge
# Right padding - repeat last column
right_edge = edge_color[:, -1, :]
for i in range(pad_x):
padded_image[pad_y:pad_y+height, pad_x+width+i] = right_edge
# Fill corners with nearest edge colors
# Top-left corner
padded_image[:pad_y, :pad_x] = edge_color[0, 0, :]
# Top-right corner
padded_image[:pad_y, pad_x+width:] = edge_color[0, -1, :]
# Bottom-left corner
padded_image[pad_y+height:, :pad_x] = edge_color[-1, 0, :]
# Bottom-right corner
padded_image[pad_y+height:, pad_x+width:] = edge_color[-1, -1, :]
return padded_image
def get_fake_face() -> str:
"""Fetch a face from thispersondoesnotexist.com and save it temporarily"""
try:
# Create temp directory if it doesn't exist
temp_dir = Path(tempfile.gettempdir()) / "deep-live-cam"
temp_dir.mkdir(parents=True, exist_ok=True)
# Generate temp file path
temp_file = temp_dir / "fake_face.jpg"
# Basic headers to mimic a browser request
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
# Fetch the image
response = requests.get('https://thispersondoesnotexist.com', headers=headers)
if response.status_code == 200:
# Read image from response
image_array = np.asarray(bytearray(response.content), dtype=np.uint8)
image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
# Add padding around the face
padded_image = add_padding_to_face(image)
# Save the padded image
cv2.imwrite(str(temp_file), padded_image)
return str(temp_file)
else:
print(f"Failed to fetch fake face: {response.status_code}")
return None
except Exception as e:
print(f"Error fetching fake face: {str(e)}")
return None
def cleanup_fake_face():
"""Clean up the temporary fake face image"""
try:
if modules.globals.fake_face_path and os.path.exists(modules.globals.fake_face_path):
os.remove(modules.globals.fake_face_path)
modules.globals.fake_face_path = None
except Exception as e:
print(f"Error cleaning up fake face: {str(e)}")
def refresh_fake_face():
"""Refresh the fake face image"""
cleanup_fake_face()
modules.globals.fake_face_path = get_fake_face()
return modules.globals.fake_face_path is not None
+39 -59
View File
@@ -1,5 +1,3 @@
# --- START OF FILE globals.py ---
import os
from typing import List, Dict, Any
@@ -11,62 +9,44 @@ file_types = [
("Video", ("*.mp4", "*.mkv")),
]
# Face Mapping Data
source_target_map: List[Dict[str, Any]] = [] # Stores detailed map for image/video processing
simple_map: Dict[str, Any] = {} # Stores simplified map (embeddings/faces) for live/simple mode
souce_target_map = []
simple_map = {}
# Paths
source_path: str | None = None
target_path: str | None = None
output_path: str | None = None
# Processing Options
source_path = None
target_path = None
output_path = None
frame_processors: List[str] = []
keep_fps: bool = True
keep_audio: bool = True
keep_frames: bool = False
many_faces: bool = False # Process all detected faces with default source
map_faces: bool = False # Use source_target_map or simple_map for specific swaps
poisson_blend: bool = False # Enable Poisson Blending for smoother face swaps
color_correction: bool = False # Enable color correction (implementation specific)
nsfw_filter: bool = False
# Video Output Options
video_encoder: str | None = None
video_quality: int | None = None # Typically a CRF value or bitrate
# Live Mode Options
live_mirror: bool = False
live_resizable: bool = True
camera_input_combobox: Any | None = None # Placeholder for UI element if needed
webcam_preview_running: bool = False
show_fps: bool = False
# System Configuration
max_memory: int | None = None # Memory limit in GB? (Needs clarification)
execution_providers: List[str] = [] # e.g., ['CUDAExecutionProvider', 'CPUExecutionProvider']
execution_threads: int | None = None # Number of threads for CPU execution
headless: bool | None = None # Run without UI?
log_level: str = "error" # Logging level (e.g., 'debug', 'info', 'warning', 'error')
# Face Processor UI Toggles (Example)
fp_ui: Dict[str, bool] = {"face_enhancer": False, "face_enhancer_gpen256": False, "face_enhancer_gpen512": False}
# Face Swapper Specific Options
face_swapper_enabled: bool = True # General toggle for the swapper processor
opacity: float = 1.0 # Blend factor for the swapped face (0.0-1.0)
sharpness: float = 0.0 # Sharpness enhancement for swapped face (0.0-1.0+)
# Mouth Mask Options
mouth_mask: bool = False # Enable mouth area masking/pasting
show_mouth_mask_box: bool = False # Visualize the mouth mask area (for debugging)
mask_feather_ratio: int = 12 # Denominator for feathering calculation (higher = smaller feather)
mask_down_size: float = 0.1 # Expansion factor for lower lip mask (relative)
mask_size: float = 1.0 # Expansion factor for upper lip mask (relative)
# --- START: Added for Frame Interpolation ---
enable_interpolation: bool = True # Toggle temporal smoothing
interpolation_weight: float = 0 # Blend weight for current frame (0.0-1.0). Lower=smoother.
# --- END: Added for Frame Interpolation ---
# --- END OF FILE globals.py ---
keep_fps = True
keep_audio = True
keep_frames = False
many_faces = False
map_faces = False
color_correction = False
nsfw_filter = False
video_encoder = None
video_quality = None
live_mirror = False
live_resizable = True
max_memory = None
execution_providers: List[str] = []
execution_threads = None
headless = None
log_level = "error"
fp_ui: Dict[str, bool] = {"face_enhancer": False}
camera_input_combobox = None
webcam_preview_running = False
show_fps = False
mouth_mask = False
show_mouth_mask_box = False
mask_feather_ratio = 8
mask_down_size = 0.50
mask_size = 1
mouth_mask_size = 1.0
eyes_mask = False
show_eyes_mask_box = False
eyebrows_mask = False
show_eyebrows_mask_box = False
eyes_mask_size = 1.0
eyebrows_mask_size = 1.0
use_fake_face = False
fake_face_path = None
-286
View File
@@ -1,286 +0,0 @@
# --- START OF FILE gpu_processing.py ---
"""
GPU-accelerated image processing using OpenCV CUDA (cv2.cuda.GpuMat).
Provides drop-in replacements for common cv2 functions. When OpenCV is built
with CUDA support the functions transparently upload → process → download via
GpuMat; otherwise they fall back to the regular CPU path so the rest of the
codebase never has to care whether CUDA is available.
Usage
-----
from modules.gpu_processing import (
gpu_gaussian_blur, gpu_sharpen, gpu_add_weighted,
gpu_resize, gpu_cvt_color, gpu_flip,
is_gpu_accelerated,
)
"""
from __future__ import annotations
import cv2
import numpy as np
from typing import Tuple, Optional
# ---------------------------------------------------------------------------
# CUDA availability detection (evaluated once at import time)
# ---------------------------------------------------------------------------
CUDA_AVAILABLE: bool = False
try:
# cv2.cuda.GpuMat is only present when OpenCV is compiled with CUDA
_test_mat = cv2.cuda.GpuMat()
# Verify we have the required filter / image-processing functions
_has_gauss = hasattr(cv2.cuda, "createGaussianFilter")
_has_resize = hasattr(cv2.cuda, "resize")
_has_cvt = hasattr(cv2.cuda, "cvtColor")
if _has_gauss and _has_resize and _has_cvt:
CUDA_AVAILABLE = True
print("[gpu_processing] OpenCV CUDA support detected GPU-accelerated processing enabled.")
else:
missing = []
if not _has_gauss:
missing.append("createGaussianFilter")
if not _has_resize:
missing.append("resize")
if not _has_cvt:
missing.append("cvtColor")
print(f"[gpu_processing] cv2.cuda.GpuMat exists but missing: {', '.join(missing)} falling back to CPU.")
except Exception:
print("[gpu_processing] OpenCV CUDA not available using CPU fallback for all operations.")
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
def _ensure_uint8(img: np.ndarray) -> np.ndarray:
"""Clip and convert to uint8 if necessary."""
if img.dtype != np.uint8:
return np.clip(img, 0, 255).astype(np.uint8)
return img
def _ksize_odd(ksize: Tuple[int, int]) -> Tuple[int, int]:
"""Ensure kernel dimensions are positive and odd (required by GaussianBlur)."""
kw = max(1, ksize[0] // 2 * 2 + 1) if ksize[0] > 0 else 0
kh = max(1, ksize[1] // 2 * 2 + 1) if ksize[1] > 0 else 0
return (kw, kh)
def _cv_type_for(img: np.ndarray) -> int:
"""Return the OpenCV type constant matching *img* (uint8 only)."""
channels = 1 if img.ndim == 2 else img.shape[2]
if channels == 1:
return cv2.CV_8UC1
elif channels == 3:
return cv2.CV_8UC3
elif channels == 4:
return cv2.CV_8UC4
return cv2.CV_8UC3 # fallback
# ---------------------------------------------------------------------------
# Public API Gaussian Blur
# ---------------------------------------------------------------------------
def gpu_gaussian_blur(
src: np.ndarray,
ksize: Tuple[int, int],
sigma_x: float,
sigma_y: float = 0,
) -> np.ndarray:
"""Drop-in replacement for ``cv2.GaussianBlur`` with CUDA acceleration.
Parameters match ``cv2.GaussianBlur(src, ksize, sigmaX, sigmaY)``.
When *ksize* is ``(0, 0)`` OpenCV computes the kernel size from *sigma_x*.
"""
if CUDA_AVAILABLE:
try:
src_u8 = _ensure_uint8(src)
cv_type = _cv_type_for(src_u8)
ks = _ksize_odd(ksize) if ksize != (0, 0) else ksize
gauss = cv2.cuda.createGaussianFilter(cv_type, cv_type, ks, sigma_x, sigma_y)
gpu_src = cv2.cuda.GpuMat()
gpu_src.upload(src_u8)
gpu_dst = gauss.apply(gpu_src)
return gpu_dst.download()
except cv2.error:
pass
return cv2.GaussianBlur(src, ksize, sigma_x, sigmaY=sigma_y)
# ---------------------------------------------------------------------------
# Public API addWeighted
# ---------------------------------------------------------------------------
def gpu_add_weighted(
src1: np.ndarray,
alpha: float,
src2: np.ndarray,
beta: float,
gamma: float,
) -> np.ndarray:
"""Drop-in replacement for ``cv2.addWeighted`` with CUDA acceleration."""
if CUDA_AVAILABLE:
try:
s1 = _ensure_uint8(src1)
s2 = _ensure_uint8(src2)
g1 = cv2.cuda.GpuMat()
g2 = cv2.cuda.GpuMat()
g1.upload(s1)
g2.upload(s2)
gpu_dst = cv2.cuda.addWeighted(g1, alpha, g2, beta, gamma)
return gpu_dst.download()
except cv2.error:
pass
return cv2.addWeighted(src1, alpha, src2, beta, gamma)
# ---------------------------------------------------------------------------
# Public API Unsharp-mask sharpening
# ---------------------------------------------------------------------------
def gpu_sharpen(
src: np.ndarray,
strength: float,
sigma: float = 3,
) -> np.ndarray:
"""Unsharp-mask sharpening, optionally GPU-accelerated.
Equivalent to::
blurred = GaussianBlur(src, (0,0), sigma)
result = addWeighted(src, 1+strength, blurred, -strength, 0)
"""
if strength <= 0:
return src
if CUDA_AVAILABLE:
try:
src_u8 = _ensure_uint8(src)
cv_type = _cv_type_for(src_u8)
gauss = cv2.cuda.createGaussianFilter(cv_type, cv_type, (0, 0), sigma)
gpu_src = cv2.cuda.GpuMat()
gpu_src.upload(src_u8)
gpu_blurred = gauss.apply(gpu_src)
gpu_sharp = cv2.cuda.addWeighted(gpu_src, 1.0 + strength, gpu_blurred, -strength, 0)
result = gpu_sharp.download()
return np.clip(result, 0, 255).astype(np.uint8)
except cv2.error:
pass
blurred = cv2.GaussianBlur(src, (0, 0), sigma)
sharpened = cv2.addWeighted(src, 1.0 + strength, blurred, -strength, 0)
return np.clip(sharpened, 0, 255).astype(np.uint8)
# ---------------------------------------------------------------------------
# Public API Resize
# ---------------------------------------------------------------------------
# Map common cv2 interpolation flags to their CUDA equivalents
_INTERP_MAP = {
cv2.INTER_NEAREST: cv2.INTER_NEAREST,
cv2.INTER_LINEAR: cv2.INTER_LINEAR,
cv2.INTER_CUBIC: cv2.INTER_CUBIC,
cv2.INTER_AREA: cv2.INTER_AREA,
cv2.INTER_LANCZOS4: cv2.INTER_LANCZOS4,
}
def gpu_resize(
src: np.ndarray,
dsize: Tuple[int, int],
fx: float = 0,
fy: float = 0,
interpolation: int = cv2.INTER_LINEAR,
) -> np.ndarray:
"""Drop-in replacement for ``cv2.resize`` with CUDA acceleration.
Parameters match ``cv2.resize(src, dsize, fx=fx, fy=fy, interpolation=...)``.
"""
if CUDA_AVAILABLE:
try:
src_u8 = _ensure_uint8(src)
gpu_src = cv2.cuda.GpuMat()
gpu_src.upload(src_u8)
interp = _INTERP_MAP.get(interpolation, cv2.INTER_LINEAR)
if dsize and dsize[0] > 0 and dsize[1] > 0:
gpu_dst = cv2.cuda.resize(gpu_src, dsize, interpolation=interp)
else:
gpu_dst = cv2.cuda.resize(gpu_src, (0, 0), fx=fx, fy=fy, interpolation=interp)
return gpu_dst.download()
except cv2.error:
pass
return cv2.resize(src, dsize, fx=fx, fy=fy, interpolation=interpolation)
# ---------------------------------------------------------------------------
# Public API Color conversion
# ---------------------------------------------------------------------------
def gpu_cvt_color(
src: np.ndarray,
code: int,
) -> np.ndarray:
"""Drop-in replacement for ``cv2.cvtColor`` with CUDA acceleration.
Parameters match ``cv2.cvtColor(src, code)``.
"""
if CUDA_AVAILABLE:
try:
src_u8 = _ensure_uint8(src)
gpu_src = cv2.cuda.GpuMat()
gpu_src.upload(src_u8)
gpu_dst = cv2.cuda.cvtColor(gpu_src, code)
return gpu_dst.download()
except cv2.error:
pass
return cv2.cvtColor(src, code)
# ---------------------------------------------------------------------------
# Public API Flip
# ---------------------------------------------------------------------------
def gpu_flip(
src: np.ndarray,
flip_code: int,
) -> np.ndarray:
"""Drop-in replacement for ``cv2.flip`` with CUDA acceleration.
Parameters match ``cv2.flip(src, flipCode)``.
*flip_code*: 0 = vertical, 1 = horizontal, -1 = both.
"""
if CUDA_AVAILABLE:
try:
src_u8 = _ensure_uint8(src)
gpu_src = cv2.cuda.GpuMat()
gpu_src.upload(src_u8)
gpu_dst = cv2.cuda.flip(gpu_src, flip_code)
return gpu_dst.download()
except cv2.error:
pass
return cv2.flip(src, flip_code)
# ---------------------------------------------------------------------------
# Convenience: check at runtime whether GPU path is active
# ---------------------------------------------------------------------------
def is_gpu_accelerated() -> bool:
"""Return ``True`` when the CUDA path will be used."""
return CUDA_AVAILABLE
# --- END OF FILE gpu_processing.py ---
+2 -2
View File
@@ -1,3 +1,3 @@
name = 'Deep-Live-Cam'
version = '2.0.3c'
edition = 'GitHub Edition'
version = '1.8'
edition = 'GitHub Edition'
-6
View File
@@ -1,6 +0,0 @@
"""Shared path constants for the Deep-Live-Cam project."""
import os
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MODELS_DIR = os.path.join(ROOT_DIR, "models")
+1 -2
View File
@@ -3,7 +3,6 @@ import opennsfw2
from PIL import Image
import cv2 # Add OpenCV import
import modules.globals # Import globals to access the color correction toggle
from modules.gpu_processing import gpu_cvt_color
from modules.typing import Frame
@@ -15,7 +14,7 @@ model = None
def predict_frame(target_frame: Frame) -> bool:
# Convert the frame to RGB before processing if color correction is enabled
if modules.globals.color_correction:
target_frame = gpu_cvt_color(target_frame, cv2.COLOR_BGR2RGB)
target_frame = cv2.cvtColor(target_frame, cv2.COLOR_BGR2RGB)
image = Image.fromarray(target_frame)
image = opennsfw2.preprocess_image(image, opennsfw2.Preprocessing.YAHOO)
-145
View File
@@ -1,145 +0,0 @@
"""Shared ONNX-based face enhancement utilities for GPEN-BFR models.
Provides session creation, pre/post processing, and the core
enhance-face-via-ONNX pipeline.
"""
import os
import platform
import threading
from typing import Any
import cv2
import numpy as np
import onnxruntime
import modules.globals
IS_APPLE_SILICON = platform.system() == "Darwin" and platform.machine() == "arm64"
# Limit concurrent ONNX calls to avoid VRAM exhaustion on multi-face frames
THREAD_SEMAPHORE = threading.Semaphore(min(max(1, (os.cpu_count() or 1)), 8))
def create_onnx_session(model_path: str) -> onnxruntime.InferenceSession:
"""Create an ONNX Runtime session using the configured execution providers."""
providers = modules.globals.execution_providers
session = onnxruntime.InferenceSession(model_path, providers=providers)
return session
def warmup_session(session: onnxruntime.InferenceSession) -> None:
"""Run a dummy inference pass to trigger JIT / compile caching."""
try:
input_feed = {
inp.name: np.zeros(
[d if isinstance(d, int) and d > 0 else 1 for d in inp.shape],
dtype=np.float32,
)
for inp in session.get_inputs()
}
session.run(None, input_feed)
except Exception as e:
print(f"ONNX enhancer warmup skipped (non-fatal): {e}")
def preprocess_face(face_img: np.ndarray, input_size: int) -> np.ndarray:
"""Resize, normalize, and convert a BGR face crop to ONNX input blob.
GPEN-BFR expects [1, 3, H, W] float32 in RGB, normalized to [-1, 1].
"""
resized = cv2.resize(face_img, (input_size, input_size), interpolation=cv2.INTER_LINEAR)
rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
blob = rgb.astype(np.float32) / 255.0 * 2.0 - 1.0
blob = np.transpose(blob, (2, 0, 1))[np.newaxis, ...]
return blob
def postprocess_face(output: np.ndarray) -> np.ndarray:
"""Convert ONNX output [1, 3, H, W] float32 back to BGR uint8 image."""
img = output[0].transpose(1, 2, 0)
img = ((img + 1.0) / 2.0 * 255.0)
img = np.clip(img, 0, 255).astype(np.uint8)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
return img
def _get_face_affine(face: Any, input_size: int):
"""Compute affine transform to align a face to GPEN input space.
Returns (M, inv_M) — forward and inverse affine matrices.
"""
template = np.array([
[0.31556875, 0.4615741],
[0.68262291, 0.4615741],
[0.50009375, 0.6405054],
[0.34947187, 0.8246919],
[0.65343645, 0.8246919],
], dtype=np.float32) * input_size
landmarks = None
if hasattr(face, "kps") and face.kps is not None:
landmarks = face.kps.astype(np.float32)
elif hasattr(face, "landmark_2d_106") and face.landmark_2d_106 is not None:
lm106 = face.landmark_2d_106
landmarks = np.array([
lm106[38], # left eye
lm106[88], # right eye
lm106[86], # nose tip
lm106[52], # left mouth
lm106[61], # right mouth
], dtype=np.float32)
if landmarks is None or len(landmarks) < 5:
return None, None
M = cv2.estimateAffinePartial2D(landmarks, template, method=cv2.LMEDS)[0]
if M is None:
return None, None
inv_M = cv2.invertAffineTransform(M)
return M, inv_M
def enhance_face_onnx(
frame: np.ndarray,
face: Any,
session: onnxruntime.InferenceSession,
input_size: int,
) -> np.ndarray:
"""Enhance a single face in the frame using an ONNX face restoration model."""
M, inv_M = _get_face_affine(face, input_size)
if M is None:
return frame
face_crop = cv2.warpAffine(
frame, M, (input_size, input_size),
flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE,
)
blob = preprocess_face(face_crop, input_size)
with THREAD_SEMAPHORE:
output = session.run(None, {session.get_inputs()[0].name: blob})[0]
enhanced = postprocess_face(output)
# Create mask for blending (feathered edges)
mask = np.ones((input_size, input_size), dtype=np.float32)
border = max(1, input_size // 16)
mask[:border, :] = np.linspace(0, 1, border)[:, np.newaxis]
mask[-border:, :] = np.linspace(1, 0, border)[:, np.newaxis]
mask[:, :border] = np.minimum(mask[:, :border], np.linspace(0, 1, border)[np.newaxis, :])
mask[:, -border:] = np.minimum(mask[:, -border:], np.linspace(1, 0, border)[np.newaxis, :])
h, w = frame.shape[:2]
warped_enhanced = cv2.warpAffine(
enhanced, inv_M, (w, h),
flags=cv2.INTER_LINEAR, borderValue=(0, 0, 0),
)
warped_mask = cv2.warpAffine(
mask, inv_M, (w, h),
flags=cv2.INTER_LINEAR, borderValue=0,
)
mask_3ch = warped_mask[:, :, np.newaxis]
result = (warped_enhanced.astype(np.float32) * mask_3ch +
frame.astype(np.float32) * (1.0 - mask_3ch))
return np.clip(result, 0, 255).astype(np.uint8)
+16 -52
View File
@@ -17,17 +17,8 @@ FRAME_PROCESSORS_INTERFACE = [
'process_video'
]
ALLOWED_PROCESSORS = {
'face_swapper',
'face_enhancer',
'face_enhancer_gpen256',
'face_enhancer_gpen512'
}
def load_frame_processor_module(frame_processor: str) -> Any:
if frame_processor not in ALLOWED_PROCESSORS:
print(f"Frame processor {frame_processor} is not allowed")
sys.exit()
try:
frame_processor_module = importlib.import_module(f'modules.processors.frame.{frame_processor}')
for method_name in FRAME_PROCESSORS_INTERFACE:
@@ -51,54 +42,27 @@ def get_frame_processors_modules(frame_processors: List[str]) -> List[ModuleType
def set_frame_processors_modules_from_ui(frame_processors: List[str]) -> None:
global FRAME_PROCESSORS_MODULES
current_processor_names = [proc.__name__.split('.')[-1] for proc in FRAME_PROCESSORS_MODULES]
for frame_processor, state in modules.globals.fp_ui.items():
if state == True and frame_processor not in current_processor_names:
if state == True and frame_processor not in frame_processors:
frame_processor_module = load_frame_processor_module(frame_processor)
FRAME_PROCESSORS_MODULES.append(frame_processor_module)
modules.globals.frame_processors.append(frame_processor)
if state == False:
try:
frame_processor_module = load_frame_processor_module(frame_processor)
FRAME_PROCESSORS_MODULES.append(frame_processor_module)
if frame_processor not in modules.globals.frame_processors:
modules.globals.frame_processors.append(frame_processor)
except SystemExit:
print(f"Warning: Failed to load frame processor {frame_processor} requested by UI state.")
except Exception as e:
print(f"Warning: Error loading frame processor {frame_processor} requested by UI state: {e}")
elif state == False and frame_processor in current_processor_names:
try:
module_to_remove = next((mod for mod in FRAME_PROCESSORS_MODULES if mod.__name__.endswith(f'.{frame_processor}')), None)
if module_to_remove:
FRAME_PROCESSORS_MODULES.remove(module_to_remove)
if frame_processor in modules.globals.frame_processors:
modules.globals.frame_processors.remove(frame_processor)
except Exception as e:
print(f"Warning: Error removing frame processor {frame_processor}: {e}")
FRAME_PROCESSORS_MODULES.remove(frame_processor_module)
modules.globals.frame_processors.remove(frame_processor)
except:
pass
def multi_process_frame(source_path: str, temp_frame_paths: List[str], process_frames: Callable[[str, List[str], Any], None], progress: Any = None) -> None:
"""Process frames in parallel with optimized batching and memory management."""
max_workers = modules.globals.execution_threads
# Determine optimal batch size based on available memory and thread count
# Process frames in batches to avoid memory overflow
batch_size = max(1, min(32, len(temp_frame_paths) // max(1, max_workers)))
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# Process in batches to manage memory better
for i in range(0, len(temp_frame_paths), batch_size):
batch = temp_frame_paths[i:i + batch_size]
futures = []
for path in batch:
future = executor.submit(process_frames, source_path, [path], progress)
futures.append(future)
# Wait for batch to complete before starting next batch
for future in futures:
try:
future.result()
except Exception as e:
print(f"Error processing frame: {e}")
with ThreadPoolExecutor(max_workers=modules.globals.execution_threads) as executor:
futures = []
for path in temp_frame_paths:
future = executor.submit(process_frames, source_path, [path], progress)
futures.append(future)
for future in futures:
future.result()
def process_video(source_path: str, frame_paths: list[str], process_frames: Callable[[str, List[str], Any], None]) -> None:
+44 -307
View File
@@ -1,20 +1,18 @@
# --- START OF FILE face_enhancer.py ---
# Uses ONNX Runtime for GFPGAN face enhancement (no torch/gfpgan dependency)
from typing import Any, List
import cv2
import threading
import numpy as np
import gfpgan
import os
import onnxruntime
import modules.globals
import modules.processors.frame.core
from modules.core import update_status
from modules.face_analyser import get_one_face, get_many_faces
from modules.face_analyser import get_one_face
from modules.typing import Frame, Face
import platform
import torch
from modules.utilities import (
conditional_download,
is_image,
is_video,
)
@@ -29,29 +27,15 @@ models_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models"
)
# Standard FFHQ 5-point face template for 512x512 resolution
# Points: left_eye, right_eye, nose, left_mouth, right_mouth
FFHQ_TEMPLATE_512 = np.array(
[
[192.98138, 239.94708],
[318.90277, 240.19366],
[256.63416, 314.01935],
[201.26117, 371.41043],
[313.08905, 371.15118],
],
dtype=np.float32,
)
def pre_check() -> bool:
model_path = os.path.join(models_dir, "gfpgan-1024.onnx")
if not os.path.exists(model_path):
update_status(
f"GFPGAN ONNX model not found at {model_path}. "
"Please place gfpgan-1024.onnx in the models folder.",
NAME,
)
return False
download_directory_path = models_dir
conditional_download(
download_directory_path,
[
"https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth"
],
)
return True
@@ -64,309 +48,62 @@ def pre_start() -> bool:
return True
def get_face_enhancer() -> onnxruntime.InferenceSession:
"""
Initializes and returns the GFPGAN ONNX Runtime inference session,
using the execution providers configured in modules.globals.
"""
def get_face_enhancer() -> Any:
global FACE_ENHANCER
with THREAD_LOCK:
if FACE_ENHANCER is None:
model_path = os.path.join(models_dir, "gfpgan-1024.onnx")
if not os.path.exists(model_path):
raise FileNotFoundError(
f"{NAME}: Model not found at {model_path}"
)
try:
providers = modules.globals.execution_providers
session_options = onnxruntime.SessionOptions()
session_options.graph_optimization_level = (
onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
)
FACE_ENHANCER = onnxruntime.InferenceSession(
model_path,
sess_options=session_options,
providers=providers,
)
input_info = FACE_ENHANCER.get_inputs()[0]
output_info = FACE_ENHANCER.get_outputs()[0]
active_providers = FACE_ENHANCER.get_providers()
print(
f"{NAME}: GFPGAN ONNX model loaded successfully."
)
print(
f"{NAME}: Input: {input_info.name}, "
f"shape: {input_info.shape}, type: {input_info.type}"
)
print(
f"{NAME}: Output: {output_info.name}, "
f"shape: {output_info.shape}, type: {output_info.type}"
)
print(f"{NAME}: Active providers: {active_providers}")
except Exception as e:
print(f"{NAME}: Error loading GFPGAN ONNX model: {e}")
FACE_ENHANCER = None
raise RuntimeError(
f"{NAME}: Failed to load GFPGAN ONNX model: {e}"
)
if FACE_ENHANCER is None:
raise RuntimeError(
f"{NAME}: Failed to initialize GFPGAN ONNX session. Check logs."
)
model_path = os.path.join(models_dir, "GFPGANv1.4.pth")
match platform.system():
case "Darwin": # Mac OS
if torch.backends.mps.is_available():
mps_device = torch.device("mps")
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1, device=mps_device) # type: ignore[attr-defined]
else:
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined]
case _: # Other OS
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined]
return FACE_ENHANCER
def _align_face(
frame: Frame, landmarks_5: np.ndarray, output_size: int
) -> tuple:
"""
Align and crop a face from the frame using 5-point landmarks and the
standard FFHQ template.
Returns:
(aligned_face, affine_matrix) or (None, None) on failure.
"""
# Scale the 512-base template to the desired output size
scale = output_size / 512.0
template = FFHQ_TEMPLATE_512 * scale
# Estimate a similarity transform (4 DOF: rotation, scale, tx, ty)
affine_matrix, _ = cv2.estimateAffinePartial2D(
landmarks_5, template, method=cv2.LMEDS
)
if affine_matrix is None:
return None, None
# Warp the face to the aligned position
aligned_face = cv2.warpAffine(
frame,
affine_matrix,
(output_size, output_size),
borderMode=cv2.BORDER_CONSTANT,
borderValue=(135, 133, 132),
)
return aligned_face, affine_matrix
def _paste_back(
frame: Frame,
enhanced_face: np.ndarray,
affine_matrix: np.ndarray,
output_size: int,
) -> Frame:
"""
Paste an enhanced (aligned) face back onto the original frame using the
inverse affine transform with feathered-edge blending.
"""
h, w = frame.shape[:2]
# Inverse the affine warp
inv_matrix = cv2.invertAffineTransform(affine_matrix)
inv_restored = cv2.warpAffine(
enhanced_face,
inv_matrix,
(w, h),
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0),
)
# Build a soft feathered mask in aligned space for edge blending
face_mask = np.ones((output_size, output_size), dtype=np.float32)
# Feather the border (5 % of the size on each edge)
border = max(1, int(output_size * 0.05))
ramp_up = np.linspace(0.0, 1.0, border, dtype=np.float32)
ramp_down = np.linspace(1.0, 0.0, border, dtype=np.float32)
# Top / bottom rows
face_mask[:border, :] *= ramp_up[:, None]
face_mask[-border:, :] *= ramp_down[:, None]
# Left / right columns
face_mask[:, :border] *= ramp_up[None, :]
face_mask[:, -border:] *= ramp_down[None, :]
# Expand to 3-channel
face_mask_3c = np.stack([face_mask] * 3, axis=-1)
# Warp mask back to original frame space
inv_mask = cv2.warpAffine(
face_mask_3c,
inv_matrix,
(w, h),
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0),
)
inv_mask = np.clip(inv_mask, 0.0, 1.0)
# Alpha-blend
result = (
frame.astype(np.float32) * (1.0 - inv_mask)
+ inv_restored.astype(np.float32) * inv_mask
)
return np.clip(result, 0, 255).astype(np.uint8)
def _preprocess_face(aligned_face: np.ndarray) -> np.ndarray:
"""
Convert an aligned BGR uint8 face image to the ONNX model input tensor.
Format: NCHW float32, normalised to [-1, 1].
"""
# BGR -> RGB
rgb = cv2.cvtColor(aligned_face, cv2.COLOR_BGR2RGB).astype(np.float32)
# [0, 255] -> [0, 1] -> [-1, 1]
rgb = rgb / 255.0
rgb = (rgb - 0.5) / 0.5
# HWC -> CHW, add batch dim
chw = np.transpose(rgb, (2, 0, 1))
return np.expand_dims(chw, axis=0) # shape: (1, 3, H, W)
def _postprocess_face(output: np.ndarray) -> np.ndarray:
"""
Convert the ONNX model output tensor back to a BGR uint8 image.
Expects input in NCHW format with values in [-1, 1].
"""
face = np.squeeze(output) # remove batch dim -> (3, H, W)
face = np.transpose(face, (1, 2, 0)) # CHW -> HWC
# [-1, 1] -> [0, 1] -> [0, 255]
face = (face + 1.0) / 2.0
face = np.clip(face * 255.0, 0, 255).astype(np.uint8)
# RGB -> BGR
return cv2.cvtColor(face, cv2.COLOR_RGB2BGR)
def enhance_face(temp_frame: Frame) -> Frame:
"""Enhances all faces in a frame using the GFPGAN ONNX model."""
session = get_face_enhancer()
# Determine model input resolution from the session metadata
input_info = session.get_inputs()[0]
input_name = input_info.name
input_shape = input_info.shape # e.g. [1, 3, 512, 512]
# Safely extract input size (handle dynamic / symbolic dimensions)
try:
align_size = int(input_shape[2])
if align_size <= 0:
align_size = 512
except (ValueError, TypeError, IndexError):
align_size = 512
# Detect faces using InsightFace (already a project dependency)
faces = get_many_faces(temp_frame)
if not faces:
return temp_frame
result_frame = temp_frame.copy()
for face in faces:
# Need the 5-point key-points for alignment
if not hasattr(face, "kps") or face.kps is None:
continue
landmarks_5 = face.kps.astype(np.float32)
if landmarks_5.shape[0] < 5:
continue
# Align / crop the face at the model's INPUT resolution
aligned_face, affine_matrix = _align_face(
temp_frame, landmarks_5, output_size=align_size
)
if aligned_face is None or affine_matrix is None:
continue
try:
with THREAD_SEMAPHORE:
input_tensor = _preprocess_face(aligned_face)
output_tensor = session.run(None, {input_name: input_tensor})[0]
enhanced_bgr = _postprocess_face(output_tensor)
# The model may output at a different resolution than its input
# (e.g. input 512x512 → output 1024x1024). Resize the enhanced
# face back to the alignment size so the inverse affine maps
# correctly.
eh, ew = enhanced_bgr.shape[:2]
if eh != align_size or ew != align_size:
enhanced_bgr = cv2.resize(
enhanced_bgr,
(align_size, align_size),
interpolation=cv2.INTER_LANCZOS4,
)
# Paste enhanced face back onto the frame
result_frame = _paste_back(
result_frame, enhanced_bgr, affine_matrix, output_size=align_size
)
except Exception as e:
print(f"{NAME}: Error enhancing a face: {e}")
continue
return result_frame
with THREAD_SEMAPHORE:
_, _, temp_frame = get_face_enhancer().enhance(temp_frame, paste_back=True)
return temp_frame
def process_frame(source_face: Face | None, temp_frame: Frame) -> Frame:
"""Processes a frame: enhances face if detected."""
temp_frame = enhance_face(temp_frame)
def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
target_face = get_one_face(temp_frame)
if target_face:
temp_frame = enhance_face(temp_frame)
return temp_frame
def process_frames(
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
source_path: str, temp_frame_paths: List[str], progress: Any = None
) -> None:
"""Processes multiple frames from file paths."""
for temp_frame_path in temp_frame_paths:
if not os.path.exists(temp_frame_path):
print(
f"{NAME}: Warning: Frame path not found {temp_frame_path}, skipping."
)
if progress:
progress.update(1)
continue
temp_frame = cv2.imread(temp_frame_path)
if temp_frame is None:
print(
f"{NAME}: Warning: Failed to read frame {temp_frame_path}, skipping."
)
if progress:
progress.update(1)
continue
result_frame = process_frame(None, temp_frame)
cv2.imwrite(temp_frame_path, result_frame)
result = process_frame(None, temp_frame)
cv2.imwrite(temp_frame_path, result)
if progress:
progress.update(1)
def process_image(
source_path: str | None, target_path: str, output_path: str
) -> None:
"""Processes a single image file."""
def process_image(source_path: str, target_path: str, output_path: str) -> None:
target_frame = cv2.imread(target_path)
if target_frame is None:
print(f"{NAME}: Error: Failed to read target image {target_path}")
return
result_frame = process_frame(None, target_frame)
cv2.imwrite(output_path, result_frame)
print(f"{NAME}: Enhanced image saved to {output_path}")
result = process_frame(None, target_frame)
cv2.imwrite(output_path, result)
def process_video(
source_path: str | None, temp_frame_paths: List[str]
) -> None:
"""Processes video frames using the frame processor core."""
modules.processors.frame.core.process_video(
source_path, temp_frame_paths, process_frames
)
def process_video(source_path: str, temp_frame_paths: List[str]) -> None:
modules.processors.frame.core.process_video(None, temp_frame_paths, process_frames)
# --- END OF FILE face_enhancer.py ---
def process_frame_v2(temp_frame: Frame) -> Frame:
target_face = get_one_face(temp_frame)
if target_face:
temp_frame = enhance_face(temp_frame)
return temp_frame
@@ -1,125 +0,0 @@
"""GPEN-BFR-256 face enhancer — ONNX-based face restoration at 256x256."""
from typing import Any, List
import os
import threading
import cv2
import numpy as np
import modules.globals
import modules.processors.frame.core
from modules.core import update_status
from modules.face_analyser import get_one_face
from modules.typing import Frame, Face
from modules.utilities import (
is_image,
is_video,
)
from modules.processors.frame._onnx_enhancer import (
create_onnx_session,
warmup_session,
enhance_face_onnx,
)
NAME = "DLC.FACE-ENHANCER-GPEN256"
INPUT_SIZE = 256
MODEL_URL = "https://github.com/harisreedhar/Face-Upscalers-ONNX/releases/download/GPEN-BFR/GPEN-BFR-256.onnx"
MODEL_FILE = "GPEN-BFR-256.onnx"
ENHANCER = None
THREAD_LOCK = threading.Lock()
abs_dir = os.path.dirname(os.path.abspath(__file__))
models_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models"
)
def pre_check() -> bool:
model_path = os.path.join(models_dir, MODEL_FILE)
if not os.path.exists(model_path):
update_status(f"Downloading {MODEL_FILE}...", NAME)
from modules.utilities import conditional_download
conditional_download(models_dir, [MODEL_URL])
return True
def pre_start() -> bool:
if not is_image(modules.globals.target_path) and not is_video(modules.globals.target_path):
update_status("Select an image or video for target path.", NAME)
return False
return True
def get_enhancer() -> Any:
global ENHANCER
with THREAD_LOCK:
if ENHANCER is None:
model_path = os.path.join(models_dir, MODEL_FILE)
if not os.path.exists(model_path):
from modules.utilities import conditional_download
conditional_download(models_dir, [MODEL_URL])
if not os.path.exists(model_path):
raise FileNotFoundError(f"Model file not found: {model_path}")
print(f"{NAME}: Loading ONNX model from {model_path}")
ENHANCER = create_onnx_session(model_path)
warmup_session(ENHANCER)
print(f"{NAME}: Model loaded successfully.")
return ENHANCER
def enhance_face(temp_frame: Frame, face: Face) -> Frame:
try:
session = get_enhancer()
except Exception as e:
print(f"{NAME}: {e}")
return temp_frame
try:
return enhance_face_onnx(temp_frame, face, session, INPUT_SIZE)
except Exception as e:
print(f"{NAME}: Error during face enhancement: {e}")
return temp_frame
def process_frame(source_face: Face | None, temp_frame: Frame) -> Frame:
target_face = get_one_face(temp_frame)
if target_face is None:
return temp_frame
return enhance_face(temp_frame, target_face)
def process_frame_v2(temp_frame: Frame) -> Frame:
target_face = get_one_face(temp_frame)
if target_face:
temp_frame = enhance_face(temp_frame, target_face)
return temp_frame
def process_frames(
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
) -> None:
for temp_frame_path in temp_frame_paths:
temp_frame = cv2.imread(temp_frame_path)
if temp_frame is None:
if progress:
progress.update(1)
continue
result = process_frame(None, temp_frame)
cv2.imwrite(temp_frame_path, result)
if progress:
progress.update(1)
def process_image(source_path: str | None, target_path: str, output_path: str) -> None:
target_frame = cv2.imread(target_path)
if target_frame is None:
print(f"{NAME}: Error: Failed to read target image {target_path}")
return
result_frame = process_frame(None, target_frame)
cv2.imwrite(output_path, result_frame)
print(f"{NAME}: Enhanced image saved to {output_path}")
def process_video(source_path: str | None, temp_frame_paths: List[str]) -> None:
modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames)
@@ -1,125 +0,0 @@
"""GPEN-BFR-512 face enhancer — ONNX-based face restoration at 512x512."""
from typing import Any, List
import os
import threading
import cv2
import numpy as np
import modules.globals
import modules.processors.frame.core
from modules.core import update_status
from modules.face_analyser import get_one_face
from modules.typing import Frame, Face
from modules.utilities import (
is_image,
is_video,
)
from modules.processors.frame._onnx_enhancer import (
create_onnx_session,
warmup_session,
enhance_face_onnx,
)
NAME = "DLC.FACE-ENHANCER-GPEN512"
INPUT_SIZE = 512
MODEL_URL = "https://github.com/harisreedhar/Face-Upscalers-ONNX/releases/download/GPEN-BFR/GPEN-BFR-512.onnx"
MODEL_FILE = "GPEN-BFR-512.onnx"
ENHANCER = None
THREAD_LOCK = threading.Lock()
abs_dir = os.path.dirname(os.path.abspath(__file__))
models_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models"
)
def pre_check() -> bool:
model_path = os.path.join(models_dir, MODEL_FILE)
if not os.path.exists(model_path):
update_status(f"Downloading {MODEL_FILE}...", NAME)
from modules.utilities import conditional_download
conditional_download(models_dir, [MODEL_URL])
return True
def pre_start() -> bool:
if not is_image(modules.globals.target_path) and not is_video(modules.globals.target_path):
update_status("Select an image or video for target path.", NAME)
return False
return True
def get_enhancer() -> Any:
global ENHANCER
with THREAD_LOCK:
if ENHANCER is None:
model_path = os.path.join(models_dir, MODEL_FILE)
if not os.path.exists(model_path):
from modules.utilities import conditional_download
conditional_download(models_dir, [MODEL_URL])
if not os.path.exists(model_path):
raise FileNotFoundError(f"Model file not found: {model_path}")
print(f"{NAME}: Loading ONNX model from {model_path}")
ENHANCER = create_onnx_session(model_path)
warmup_session(ENHANCER)
print(f"{NAME}: Model loaded successfully.")
return ENHANCER
def enhance_face(temp_frame: Frame, face: Face) -> Frame:
try:
session = get_enhancer()
except Exception as e:
print(f"{NAME}: {e}")
return temp_frame
try:
return enhance_face_onnx(temp_frame, face, session, INPUT_SIZE)
except Exception as e:
print(f"{NAME}: Error during face enhancement: {e}")
return temp_frame
def process_frame(source_face: Face | None, temp_frame: Frame) -> Frame:
target_face = get_one_face(temp_frame)
if target_face is None:
return temp_frame
return enhance_face(temp_frame, target_face)
def process_frame_v2(temp_frame: Frame) -> Frame:
target_face = get_one_face(temp_frame)
if target_face:
temp_frame = enhance_face(temp_frame, target_face)
return temp_frame
def process_frames(
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
) -> None:
for temp_frame_path in temp_frame_paths:
temp_frame = cv2.imread(temp_frame_path)
if temp_frame is None:
if progress:
progress.update(1)
continue
result = process_frame(None, temp_frame)
cv2.imwrite(temp_frame_path, result)
if progress:
progress.update(1)
def process_image(source_path: str | None, target_path: str, output_path: str) -> None:
target_frame = cv2.imread(target_path)
if target_frame is None:
print(f"{NAME}: Error: Failed to read target image {target_path}")
return
result_frame = process_frame(None, target_frame)
cv2.imwrite(output_path, result_frame)
print(f"{NAME}: Enhanced image saved to {output_path}")
def process_video(source_path: str | None, temp_frame_paths: List[str]) -> None:
modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames)
+135 -75
View File
@@ -2,35 +2,27 @@ import cv2
import numpy as np
from modules.typing import Face, Frame
import modules.globals
from modules.gpu_processing import gpu_gaussian_blur, gpu_resize, gpu_cvt_color
def apply_color_transfer(source, target):
"""
Apply color transfer from target to source image using LAB color space.
Uses float32 throughout for performance (sufficient precision for 8-bit images).
Apply color transfer from target to source image
"""
# Convert to float32 [0,1] range for proper LAB conversion
source_f32 = source.astype(np.float32) / 255.0
target_f32 = target.astype(np.float32) / 255.0
source = cv2.cvtColor(source, cv2.COLOR_BGR2LAB).astype("float32")
target = cv2.cvtColor(target, cv2.COLOR_BGR2LAB).astype("float32")
source_lab = cv2.cvtColor(source_f32, cv2.COLOR_BGR2LAB)
target_lab = cv2.cvtColor(target_f32, cv2.COLOR_BGR2LAB)
source_mean, source_std = cv2.meanStdDev(source)
target_mean, target_std = cv2.meanStdDev(target)
source_mean, source_std = cv2.meanStdDev(source_lab)
target_mean, target_std = cv2.meanStdDev(target_lab)
# Reshape mean and std to be broadcastable
source_mean = source_mean.reshape(1, 1, 3)
source_std = source_std.reshape(1, 1, 3)
target_mean = target_mean.reshape(1, 1, 3)
target_std = target_std.reshape(1, 1, 3)
# Reshape mean and std to be broadcastable (already float64 from meanStdDev, cast to f32)
source_mean = source_mean.reshape(1, 1, 3).astype(np.float32)
source_std = np.maximum(source_std.reshape(1, 1, 3), 1e-6).astype(np.float32)
target_mean = target_mean.reshape(1, 1, 3).astype(np.float32)
target_std = target_std.reshape(1, 1, 3).astype(np.float32)
# Perform the color transfer
source = (source - source_mean) * (target_std / source_std) + target_mean
# Perform the color transfer in LAB space
result_lab = (source_lab - source_mean) * (target_std / source_std) + target_mean
# Convert back to BGR and uint8
result_bgr = cv2.cvtColor(result_lab, cv2.COLOR_LAB2BGR)
return np.clip(result_bgr * 255.0, 0, 255).astype(np.uint8)
return cv2.cvtColor(np.clip(source, 0, 255).astype("uint8"), cv2.COLOR_LAB2BGR)
def create_face_mask(face: Face, frame: Frame) -> np.ndarray:
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
@@ -47,28 +39,54 @@ def create_face_mask(face: Face, frame: Frame) -> np.ndarray:
left_eye = landmarks[87:96]
left_eye_brow = landmarks[97:105]
# Calculate forehead extension
right_eyebrow_top = np.min(right_eye_brow[:, 1])
left_eyebrow_top = np.min(left_eye_brow[:, 1])
eyebrow_top = min(right_eyebrow_top, left_eyebrow_top)
face_top = np.min([right_side_face[0, 1], left_side_face[-1, 1]])
forehead_height = face_top - eyebrow_top
extended_forehead_height = int(forehead_height * 5.0) # Extend by 50%
# Create forehead points
forehead_left = right_side_face[0].copy()
forehead_right = left_side_face[-1].copy()
forehead_left[1] -= extended_forehead_height
forehead_right[1] -= extended_forehead_height
# Combine all points to create the face outline
face_outline = np.vstack(
[
[forehead_left],
right_side_face,
left_side_face[::-1], # Reverse left side to create a continuous outline
[forehead_right],
]
)
# Calculate padding
padding = int(
np.linalg.norm(right_side_face[0] - left_side_face[-1]) * 0.05
) # 5% of face width
# Create a slightly larger convex hull for padding
face_outline = landmarks[0:33]
hull = cv2.convexHull(face_outline)
# Vectorized hull padding — expand each point outward from center
center = np.mean(face_outline, axis=0, dtype=np.float32)
hull_pts = hull.reshape(-1, 2).astype(np.float32)
directions = hull_pts - center
norms = np.linalg.norm(directions, axis=1, keepdims=True)
norms = np.maximum(norms, 1e-6) # avoid division by zero
directions /= norms
hull_padded = (hull_pts + directions * padding).astype(np.int32)
hull_padded = []
for point in hull:
x, y = point[0]
center = np.mean(face_outline, axis=0)
direction = np.array([x, y]) - center
direction = direction / np.linalg.norm(direction)
padded_point = np.array([x, y]) + direction * padding
hull_padded.append(padded_point)
hull_padded = np.array(hull_padded, dtype=np.int32)
# Fill the padded convex hull
cv2.fillConvexPoly(mask, hull_padded, 255)
# Smooth the mask edges (GPU-accelerated when available)
mask = gpu_gaussian_blur(mask, (5, 5), 3)
# Smooth the mask edges
mask = cv2.GaussianBlur(mask, (5, 5), 3)
return mask
@@ -77,30 +95,77 @@ def create_lower_mouth_mask(
) -> (np.ndarray, np.ndarray, tuple, np.ndarray):
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
mouth_cutout = None
lower_lip_polygon = None
mouth_box = (0,0,0,0)
landmarks = face.landmark_2d_106
if landmarks is not None:
# Use outer mouth landmarks (52-63) to capture the lips only
lower_lip_order = list(range(52, 64))
if max(lower_lip_order) >= landmarks.shape[0]:
return mask, mouth_cutout, mouth_box, lower_lip_polygon
lower_lip_landmarks = landmarks[lower_lip_order].astype(np.float32)
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
lower_lip_order = [
65,
66,
62,
70,
69,
18,
19,
20,
21,
22,
23,
24,
0,
8,
7,
6,
5,
4,
3,
2,
65,
]
lower_lip_landmarks = landmarks[lower_lip_order].astype(
np.float32
) # Use float for precise calculations
# Calculate the center of the landmarks
center = np.mean(lower_lip_landmarks, axis=0)
# Expand the landmarks outward using the mouth_mask_size
# Use a more conservative expansion to avoid affecting face shape
expansion_factor = (
1 + modules.globals.mask_down_size * modules.globals.mouth_mask_size
)
) # Adjust expansion based on slider
expanded_landmarks = (lower_lip_landmarks - center) * expansion_factor + center
# Removed specific top/chin extensions to preserve face shape
# Extend the top lip part
toplip_indices = [
20,
0,
1,
2,
3,
4,
5,
] # Indices for landmarks 2, 65, 66, 62, 70, 69, 18
toplip_extension = (
modules.globals.mask_size * modules.globals.mouth_mask_size * 0.5
) # Adjust extension based on slider
for idx in toplip_indices:
direction = expanded_landmarks[idx] - center
direction = direction / np.linalg.norm(direction)
expanded_landmarks[idx] += direction * toplip_extension
# Extend the bottom part (chin area)
chin_indices = [
11,
12,
13,
14,
15,
16,
] # Indices for landmarks 21, 22, 23, 24, 0, 8
chin_extension = 2 * 0.2 # Adjust this factor to control the extension
for idx in chin_indices:
expanded_landmarks[idx][1] += (
expanded_landmarks[idx][1] - center[1]
) * chin_extension
# Convert back to integer coordinates
expanded_landmarks = expanded_landmarks.astype(np.int32)
@@ -125,12 +190,10 @@ def create_lower_mouth_mask(
# Create the mask
mask_roi = np.zeros((max_y - min_y, max_x - min_x), dtype=np.uint8)
# Shift polygon coordinates relative to the ROI's top-left corner
polygon_relative_to_roi = expanded_landmarks - [min_x, min_y]
cv2.fillPoly(mask_roi, [polygon_relative_to_roi], 255)
cv2.fillPoly(mask_roi, [expanded_landmarks - [min_x, min_y]], 255)
# Apply Gaussian blur to soften the mask edges (GPU-accelerated when available)
mask_roi = gpu_gaussian_blur(mask_roi, (15, 15), 5)
# Apply Gaussian blur to soften the mask edges
mask_roi = cv2.GaussianBlur(mask_roi, (15, 15), 5)
# Place the mask ROI in the full-sized mask
mask[min_y:max_y, min_x:max_x] = mask_roi
@@ -140,9 +203,8 @@ def create_lower_mouth_mask(
# Return the expanded lower lip polygon in original frame coordinates
lower_lip_polygon = expanded_landmarks
mouth_box = (min_x, min_y, max_x, max_y)
return mask, mouth_cutout, mouth_box, lower_lip_polygon
return mask, mouth_cutout, (min_x, min_y, max_x, max_y), lower_lip_polygon
def create_eyes_mask(face: Face, frame: Frame) -> (np.ndarray, np.ndarray, tuple, np.ndarray):
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
@@ -198,8 +260,8 @@ def create_eyes_mask(face: Face, frame: Frame) -> (np.ndarray, np.ndarray, tuple
cv2.ellipse(mask_roi, left_center, left_axes, 0, 0, 360, 255, -1)
cv2.ellipse(mask_roi, right_center, right_axes, 0, 0, 360, 255, -1)
# Apply Gaussian blur to soften mask edges (GPU-accelerated when available)
mask_roi = gpu_gaussian_blur(mask_roi, (15, 15), 5)
# Apply Gaussian blur to soften mask edges
mask_roi = cv2.GaussianBlur(mask_roi, (15, 15), 5)
# Place the mask ROI in the full-sized mask
mask[min_y:max_y, min_x:max_x] = mask_roi
@@ -380,15 +442,15 @@ def create_eyebrows_mask(face: Face, frame: Frame) -> (np.ndarray, np.ndarray, t
left_shape = create_curved_eyebrow(left_local)
right_shape = create_curved_eyebrow(right_local)
# Apply multi-stage blurring for natural feathering (GPU-accelerated when available)
# Apply multi-stage blurring for natural feathering
# First, strong Gaussian blur for initial softening
mask_roi = gpu_gaussian_blur(mask_roi, (21, 21), 7)
mask_roi = cv2.GaussianBlur(mask_roi, (21, 21), 7)
# Second, medium blur for transition areas
mask_roi = gpu_gaussian_blur(mask_roi, (11, 11), 3)
mask_roi = cv2.GaussianBlur(mask_roi, (11, 11), 3)
# Finally, light blur for fine details
mask_roi = gpu_gaussian_blur(mask_roi, (5, 5), 1)
mask_roi = cv2.GaussianBlur(mask_roi, (5, 5), 1)
# Normalize mask values
mask_roi = cv2.normalize(mask_roi, None, 0, 255, cv2.NORM_MINMAX)
@@ -411,7 +473,7 @@ def create_eyebrows_mask(face: Face, frame: Frame) -> (np.ndarray, np.ndarray, t
right_local = right_eyebrow - [min_x, min_y]
cv2.fillPoly(mask_roi, [left_local.astype(np.int32)], 255)
cv2.fillPoly(mask_roi, [right_local.astype(np.int32)], 255)
mask_roi = gpu_gaussian_blur(mask_roi, (21, 21), 7)
mask_roi = cv2.GaussianBlur(mask_roi, (21, 21), 7)
mask[min_y:max_y, min_x:max_x] = mask_roi
eyebrows_cutout = frame[min_y:max_y, min_x:max_x].copy()
eyebrows_polygon = np.vstack([left_eyebrow, right_eyebrow]).astype(np.int32)
@@ -439,11 +501,11 @@ def apply_mask_area(
return frame
try:
resized_cutout = gpu_resize(cutout, (box_width, box_height))
resized_cutout = cv2.resize(cutout, (box_width, box_height))
roi = frame[min_y:max_y, min_x:max_x]
if roi.shape != resized_cutout.shape:
resized_cutout = gpu_resize(
resized_cutout = cv2.resize(
resized_cutout, (roi.shape[1], roi.shape[0])
)
@@ -463,8 +525,8 @@ def apply_mask_area(
adjusted_polygon = polygon - [min_x, min_y]
cv2.fillPoly(polygon_mask, [adjusted_polygon], 255)
# Apply strong initial feathering (GPU-accelerated when available)
polygon_mask = gpu_gaussian_blur(polygon_mask, (21, 21), 7)
# Apply strong initial feathering
polygon_mask = cv2.GaussianBlur(polygon_mask, (21, 21), 7)
# Apply additional feathering
feather_amount = min(
@@ -473,28 +535,26 @@ def apply_mask_area(
box_height // modules.globals.mask_feather_ratio,
)
feathered_mask = cv2.GaussianBlur(
polygon_mask.astype(np.float32), (0, 0), feather_amount
polygon_mask.astype(float), (0, 0), feather_amount
)
max_val = feathered_mask.max()
if max_val > 1e-6:
feathered_mask *= np.float32(1.0 / max_val)
feathered_mask = feathered_mask / feathered_mask.max()
# Apply additional smoothing to the mask edges
feathered_mask = cv2.GaussianBlur(feathered_mask, (5, 5), 1)
face_mask_roi = face_mask[min_y:max_y, min_x:max_x]
combined_mask = feathered_mask * (face_mask_roi.astype(np.float32) * np.float32(1.0 / 255.0))
combined_mask = feathered_mask * (face_mask_roi / 255.0)
combined_mask_3ch = combined_mask[:, :, np.newaxis]
inv_mask = np.float32(1.0) - combined_mask_3ch
combined_mask = combined_mask[:, :, np.newaxis]
blended = (
color_corrected_area * combined_mask_3ch + roi * inv_mask
color_corrected_area * combined_mask + roi * (1 - combined_mask)
).astype(np.uint8)
# Apply face mask to blended result
face_mask_f32 = face_mask_roi[:, :, np.newaxis].astype(np.float32) * np.float32(1.0 / 255.0)
face_mask_3channel = np.broadcast_to(face_mask_f32, blended.shape)
final_blend = blended * face_mask_3channel + roi * (np.float32(1.0) - face_mask_3channel)
face_mask_3channel = (
np.repeat(face_mask_roi[:, :, np.newaxis], 3, axis=2) / 255.0
)
final_blend = blended * face_mask_3channel + roi * (1 - face_mask_3channel)
frame[min_y:max_y, min_x:max_x] = final_blend.astype(np.uint8)
except Exception as e:
@@ -571,4 +631,4 @@ def draw_mask_visualization(
1,
)
return vis_frame
return vis_frame
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
#!/usr/bin/env python3
# Import the tkinter fix to patch the ScreenChanged error
import tkinter_fix
import core
if __name__ == '__main__':
core.run()
-26
View File
@@ -1,26 +0,0 @@
import tkinter
# Only needs to be imported once at the beginning of the application
def apply_patch():
# Create a monkey patch for the internal _tkinter module
original_init = tkinter.Tk.__init__
def patched_init(self, *args, **kwargs):
# Call the original init
original_init(self, *args, **kwargs)
# Define the missing ::tk::ScreenChanged procedure
self.tk.eval("""
if {[info commands ::tk::ScreenChanged] == ""} {
proc ::tk::ScreenChanged {args} {
# Do nothing
return
}
}
""")
# Apply the monkey patch
tkinter.Tk.__init__ = patched_init
# Apply the patch automatically when this module is imported
apply_patch()
+305 -503
View File
File diff suppressed because it is too large Load Diff
-74
View File
@@ -1,74 +0,0 @@
"""Lightweight hover tooltip for CustomTkinter widgets."""
import customtkinter as ctk
class ToolTip:
"""Show a floating tooltip popup when the user hovers over a widget.
Usage:
ToolTip(my_button, "Helpful description text")
"""
def __init__(self, widget: ctk.CTkBaseClass, text: str, delay: int = 500):
self._widget = widget
self._text = text
self._delay = delay
self._tooltip_window = None
self._after_id = None
widget.bind("<Enter>", self._schedule_show, add="+")
widget.bind("<Leave>", self._hide, add="+")
def _schedule_show(self, event=None):
self._cancel()
self._after_id = self._widget.after(self._delay, self._show)
def _show(self):
if self._tooltip_window is not None:
return
x = self._widget.winfo_rootx() + 20
y = self._widget.winfo_rooty() + self._widget.winfo_height() + 5
self._tooltip_window = tw = ctk.CTkToplevel(self._widget)
tw.withdraw()
tw.overrideredirect(True)
label = ctk.CTkLabel(
tw,
text=self._text,
fg_color="#333333",
text_color="#EEEEEE",
corner_radius=6,
padx=8,
pady=4,
)
label.pack()
tw.update_idletasks()
# Clamp to screen bounds
screen_w = tw.winfo_screenwidth()
screen_h = tw.winfo_screenheight()
tip_w = tw.winfo_reqwidth()
tip_h = tw.winfo_reqheight()
if x + tip_w > screen_w:
x = screen_w - tip_w - 5
if y + tip_h > screen_h:
y = self._widget.winfo_rooty() - tip_h - 5
tw.geometry(f"+{x}+{y}")
tw.deiconify()
def _hide(self, event=None):
self._cancel()
if self._tooltip_window is not None:
self._tooltip_window.destroy()
self._tooltip_window = None
def _cancel(self):
if self._after_id is not None:
self._widget.after_cancel(self._after_id)
self._after_id = None
+30 -132
View File
@@ -15,16 +15,19 @@ import modules.globals
TEMP_FILE = "temp.mp4"
TEMP_DIRECTORY = "temp"
# monkey patch ssl for mac
if platform.system().lower() == "darwin":
ssl._create_default_https_context = ssl._create_unverified_context
def run_ffmpeg(args: List[str]) -> bool:
"""Run ffmpeg with hardware acceleration and optimized settings."""
commands = [
"ffmpeg",
"-hide_banner",
"-hwaccel", "auto", # Auto-detect hardware acceleration
"-hwaccel_output_format", "auto", # Use hardware format when possible
"-threads", str(modules.globals.execution_threads or 0), # 0 = auto-detect optimal thread count
"-loglevel", modules.globals.log_level,
"-hwaccel",
"auto",
"-loglevel",
modules.globals.log_level,
]
commands.extend(args)
try:
@@ -58,131 +61,39 @@ def detect_fps(target_path: str) -> float:
def extract_frames(target_path: str) -> None:
"""Extract frames with hardware acceleration and optimized settings."""
temp_directory_path = get_temp_directory_path(target_path)
# Use hardware-accelerated decoding and optimized pixel format
run_ffmpeg(
[
"-i", target_path,
"-vf", "format=rgb24", # Use video filter for format conversion (faster)
"-vsync", "0", # Prevent frame duplication
"-frame_pts", "1", # Preserve frame timing
"-i",
target_path,
"-pix_fmt",
"rgb24",
os.path.join(temp_directory_path, "%04d.png"),
]
)
def create_video(target_path: str, fps: float = 30.0) -> None:
"""Create video with hardware-accelerated encoding and optimized settings."""
temp_output_path = get_temp_output_path(target_path)
temp_directory_path = get_temp_directory_path(target_path)
# Determine optimal encoder based on available hardware
encoder = modules.globals.video_encoder
encoder_options = []
# GPU-accelerated encoding options
if 'CUDAExecutionProvider' in modules.globals.execution_providers:
# NVIDIA GPU encoding
if encoder == 'libx264':
encoder = 'h264_nvenc'
encoder_options = [
"-preset", "p7", # Highest quality preset for NVENC
"-tune", "hq", # High quality tuning
"-rc", "vbr", # Variable bitrate
"-cq", str(modules.globals.video_quality), # Quality level
"-b:v", "0", # Let CQ control bitrate
"-multipass", "fullres", # Two-pass encoding for better quality
]
elif encoder == 'libx265':
encoder = 'hevc_nvenc'
encoder_options = [
"-preset", "p7",
"-tune", "hq",
"-rc", "vbr",
"-cq", str(modules.globals.video_quality),
"-b:v", "0",
]
elif 'DmlExecutionProvider' in modules.globals.execution_providers:
# AMD/Intel GPU encoding (DirectML on Windows)
if encoder == 'libx264':
# Try AMD AMF encoder
encoder = 'h264_amf'
encoder_options = [
"-quality", "quality", # Quality mode
"-rc", "vbr_latency",
"-qp_i", str(modules.globals.video_quality),
"-qp_p", str(modules.globals.video_quality),
]
elif encoder == 'libx265':
encoder = 'hevc_amf'
encoder_options = [
"-quality", "quality",
"-rc", "vbr_latency",
"-qp_i", str(modules.globals.video_quality),
"-qp_p", str(modules.globals.video_quality),
]
else:
# CPU encoding with optimized settings
if encoder == 'libx264':
encoder_options = [
"-preset", "medium", # Balance speed/quality
"-crf", str(modules.globals.video_quality),
"-tune", "film", # Optimize for film content
]
elif encoder == 'libx265':
encoder_options = [
"-preset", "medium",
"-crf", str(modules.globals.video_quality),
"-x265-params", "log-level=error",
]
elif encoder == 'libvpx-vp9':
encoder_options = [
"-crf", str(modules.globals.video_quality),
"-b:v", "0", # Constant quality mode
"-cpu-used", "2", # Speed vs quality (0-5, lower=slower/better)
]
# Build ffmpeg command
ffmpeg_args = [
"-r", str(fps),
"-i", os.path.join(temp_directory_path, "%04d.png"),
"-c:v", encoder,
]
# Add encoder-specific options
ffmpeg_args.extend(encoder_options)
# Add common options
ffmpeg_args.extend([
"-pix_fmt", "yuv420p",
"-movflags", "+faststart", # Enable fast start for web playback
"-vf", "colorspace=bt709:iall=bt601-6-625:fast=1",
"-y",
temp_output_path,
])
# Try with hardware encoder first, fallback to software if it fails
success = run_ffmpeg(ffmpeg_args)
if not success and encoder in ['h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf']:
# Fallback to software encoding
print(f"Hardware encoding with {encoder} failed, falling back to software encoding...")
fallback_encoder = 'libx264' if 'h264' in encoder else 'libx265'
ffmpeg_args_fallback = [
"-r", str(fps),
"-i", os.path.join(temp_directory_path, "%04d.png"),
"-c:v", fallback_encoder,
"-preset", "medium",
"-crf", str(modules.globals.video_quality),
"-pix_fmt", "yuv420p",
"-movflags", "+faststart",
"-vf", "colorspace=bt709:iall=bt601-6-625:fast=1",
run_ffmpeg(
[
"-r",
str(fps),
"-i",
os.path.join(temp_directory_path, "%04d.png"),
"-c:v",
modules.globals.video_encoder,
"-crf",
str(modules.globals.video_quality),
"-pix_fmt",
"yuv420p",
"-vf",
"colorspace=bt709:iall=bt601-6-625:fast=1",
"-y",
temp_output_path,
]
run_ffmpeg(ffmpeg_args_fallback)
)
def restore_audio(target_path: str, output_path: str) -> None:
@@ -282,15 +193,8 @@ def conditional_download(download_directory_path: str, urls: List[str]) -> None:
download_directory_path, os.path.basename(url)
)
if not os.path.exists(download_file_path):
request = urllib.request.Request(url)
# Create a specific SSL context for macOS to avoid globally disabling verification
ctx = None
if platform.system().lower() == "darwin":
ctx = ssl._create_unverified_context()
response = urllib.request.urlopen(request, context=ctx)
total = int(response.headers.get("Content-Length", 0))
request = urllib.request.urlopen(url) # type: ignore[attr-defined]
total = int(request.headers.get("Content-Length", 0))
with tqdm(
total=total,
desc="Downloading",
@@ -298,13 +202,7 @@ def conditional_download(download_directory_path: str, urls: List[str]) -> None:
unit_scale=True,
unit_divisor=1024,
) as progress:
with open(download_file_path, "wb") as f:
while True:
buffer = response.read(8192)
if not buffer:
break
f.write(buffer)
progress.update(len(buffer))
urllib.request.urlretrieve(url, download_file_path, reporthook=lambda count, block_size, total_size: progress.update(block_size)) # type: ignore[attr-defined]
def resolve_relative_path(path: str) -> str:
+15 -7
View File
@@ -1,16 +1,24 @@
--extra-index-url https://download.pytorch.org/whl/cu118
numpy>=1.23.5,<2
typing-extensions>=4.8.0
opencv-python==4.10.0.84
cv2_enumerate_cameras==1.1.15
onnx==1.18.0
onnx==1.16.0
insightface==0.7.3
psutil==5.9.8
tk==0.1.0
customtkinter==5.2.2
pillow==12.1.1
pillow==11.1.0
torch==2.0.1+cu118; sys_platform != 'darwin'
torch==2.0.1; sys_platform == 'darwin'
torchvision==0.15.2+cu118; sys_platform != 'darwin'
torchvision==0.15.2; sys_platform == 'darwin'
onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64'
onnxruntime-gpu==1.24.2; sys_platform != 'darwin'
tensorflow; sys_platform != 'darwin'
onnxruntime-gpu==1.16.3; sys_platform != 'darwin'
tensorflow==2.12.1; sys_platform != 'darwin'
opennsfw2==0.10.2
protobuf==4.25.1
pygrabber
protobuf==4.23.2
tqdm==4.66.4
gfpgan==1.3.8
tkinterdnd2==0.4.2
pygrabber==0.2
-3
View File
@@ -1,8 +1,5 @@
#!/usr/bin/env python3
# Import the tkinter fix to patch the ScreenChanged error
import tkinter_fix
from modules import core
if __name__ == '__main__':
-26
View File
@@ -1,26 +0,0 @@
import tkinter
# Only needs to be imported once at the beginning of the application
def apply_patch():
# Create a monkey patch for the internal _tkinter module
original_init = tkinter.Tk.__init__
def patched_init(self, *args, **kwargs):
# Call the original init
original_init(self, *args, **kwargs)
# Define the missing ::tk::ScreenChanged procedure
self.tk.eval("""
if {[info commands ::tk::ScreenChanged] == ""} {
proc ::tk::ScreenChanged {args} {
# Do nothing
return
}
}
""")
# Apply the monkey patch
tkinter.Tk.__init__ = patched_init
# Apply the patch automatically when this module is imported
apply_patch()