Skip to content

Commit

Permalink
pkl to safetensors
Browse files Browse the repository at this point in the history
  • Loading branch information
glucauze committed Jul 29, 2023
1 parent be02fdc commit 31635d3
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 44 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# 1.1.2 :

+ BREAKING CHANGE : enforce face checkpoint format from pkl to safetensors

Using pkl files to store faces is dangerous from a security point of view. For the same reason that models are now stored in safetensors, We are switching to safetensors for the storage format.

A script with instructions for converting existing pkl files can be found here:
https://gist.github.com/glucauze/4a3c458541f2278ad801f6625e5b9d3d


## 1.1.1 :

+ Add settings for default inpainting prompts
Expand Down
2 changes: 1 addition & 1 deletion client_api/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class FaceSwapUnit(BaseModel):
# The checkpoint file
source_face: str = Field(
description="face checkpoint (from models/faceswaplab/faces)",
examples=["my_face.pkl"],
examples=["my_face.safetensors"],
default=None,
)
# base64 batch source images
Expand Down
2 changes: 1 addition & 1 deletion docs/faq.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ A face checkpoint is a saved embedding of a face, generated from multiple images

The primary advantage of face checkpoints is their size. An embedding is only around 2KB, meaning it's lightweight and can be reused later without requiring additional calculations.

Face checkpoints are saved as `.pkl` files. Please be aware that exchanging `.pkl` files carries potential security risks. These files, by default, are not secure and could potentially execute malicious code when opened. Therefore, extreme caution should be exercised when sharing or receiving this type of file.
Face checkpoints are saved as `.safetensors` files. Please be aware that exchanging `.safetensors` files carries potential security risks. These files, by default, are not secure and could potentially execute malicious code when opened. Therefore, extreme caution should be exercised when sharing or receiving this type of file.

#### How is similarity determined?

Expand Down
11 changes: 11 additions & 0 deletions preload.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,14 @@ def preload(parser: ArgumentParser) -> None:
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
help="Set the log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
)

print("FACESWAPLAB================================================================")
print("BREAKING CHANGE: enforce face checkpoint format from pkl to safetensors\n")
print("Using pkl files to store faces is dangerous from a security point of view.")
print("For the same reason that models are now stored in safetensors,")
print("We are switching to safetensors for the storage format.")
print(
"A script with instructions for converting existing pkl files can be found here:"
)
print("https://gist.github.com/glucauze/4a3c458541f2278ad801f6625e5b9d3d")
print("==========================================================================")
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
cython
dill==0.3.6
ifnude
insightface==0.7.3
onnx==1.14.0
Expand Down
2 changes: 1 addition & 1 deletion scripts/faceswaplab_globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references"
)

VERSION_FLAG: str = "v1.1.1"
VERSION_FLAG: str = "v1.1.2"
EXTENSION_PATH = os.path.join("extensions", "sd-webui-faceswaplab")

# The NSFW score threshold. If any part of the image has a score greater than this threshold, the image will be considered NSFW.
Expand Down
37 changes: 18 additions & 19 deletions scripts/faceswaplab_ui/faceswaplab_tab.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import os
from pprint import pformat, pprint

import dill as pickle
from scripts.faceswaplab_utils import face_utils
import gradio as gr
import modules.scripts as scripts
import onnx
import pandas as pd
from scripts.faceswaplab_ui.faceswaplab_unit_ui import faceswap_unit_ui
from scripts.faceswaplab_ui.faceswaplab_postprocessing_ui import postprocessing_ui
from insightface.app.common import Face
from modules import scripts
from PIL import Image
from modules.shared import opts
Expand Down Expand Up @@ -128,10 +126,17 @@ def analyse_faces(image: Image.Image, det_threshold: float = 0.5) -> Optional[st


def sanitize_name(name: str) -> str:
logger.debug(f"Sanitize name {name}")
"""
Sanitize the input name by removing special characters and replacing spaces with underscores.
Parameters:
name (str): The input name to be sanitized.
Returns:
str: The sanitized name with special characters removed and spaces replaced by underscores.
"""
name = re.sub("[^A-Za-z0-9_. ]+", "", name)
name = name.replace(" ", "_")
logger.debug(f"Sanitized name {name[:255]}")
return name[:255]


Expand Down Expand Up @@ -185,25 +190,19 @@ def build_face_checkpoint_and_save(
),
)

file_path = os.path.join(faces_path, f"{name}.pkl")
file_path = os.path.join(faces_path, f"{name}.safetensors")
file_number = 1
while os.path.exists(file_path):
file_path = os.path.join(faces_path, f"{name}_{file_number}.pkl")
file_path = os.path.join(
faces_path, f"{name}_{file_number}.safetensors"
)
file_number += 1
result_image.save(file_path + ".png")
with open(file_path, "wb") as file:
pickle.dump(
{
"embedding": blended_face.embedding,
"gender": blended_face.gender,
"age": blended_face.age,
},
file,
)

face_utils.save_face(filename=file_path, face=blended_face)
try:
with open(file_path, "rb") as file:
data = Face(pickle.load(file))
print(data)
data = face_utils.load_face(filename=file_path)
print(data)
except Exception as e:
print(e)
return result_image
Expand Down
6 changes: 3 additions & 3 deletions scripts/faceswaplab_ui/faceswaplab_unit_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import io
from dataclasses import dataclass, fields
from typing import Any, List, Optional, Set, Union
import dill as pickle
import gradio as gr
from insightface.app.common import Face
from PIL import Image
from scripts.faceswaplab_utils.imgutils import pil_to_cv2
from scripts.faceswaplab_utils.faceswaplab_logging import logger
from scripts.faceswaplab_utils import face_utils


@dataclass
Expand Down Expand Up @@ -94,8 +94,8 @@ def reference_face(self) -> Optional[Face]:
if self.source_face and self.source_face != "None":
with open(self.source_face, "rb") as file:
try:
logger.info(f"loading pickle {file.name}")
face = Face(pickle.load(file))
logger.info(f"loading face {file.name}")
face = face_utils.load_face(file.name)
self._reference_face = face
except Exception as e:
logger.error("Failed to load checkpoint : %s", e)
Expand Down
2 changes: 1 addition & 1 deletion scripts/faceswaplab_ui/faceswaplab_unit_ui.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import List
from scripts.faceswaplab_utils.models_utils import get_face_checkpoints
from scripts.faceswaplab_utils.face_utils import get_face_checkpoints
import gradio as gr


Expand Down
48 changes: 48 additions & 0 deletions scripts/faceswaplab_utils/face_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import glob
import os
from typing import List
from insightface.app.common import Face
from safetensors.torch import save_file, safe_open
import torch

import modules.scripts as scripts
from modules import scripts
from scripts.faceswaplab_utils.faceswaplab_logging import logger


def save_face(face: Face, filename: str) -> None:
tensors = {
"embedding": torch.tensor(face["embedding"]),
"gender": torch.tensor(face["gender"]),
"age": torch.tensor(face["age"]),
}
save_file(tensors, filename)


def load_face(filename: str) -> Face:
face = {}
logger.debug("Try to load face from %s", filename)
with safe_open(filename, framework="pt", device="cpu") as f:
logger.debug("File contains %s keys", f.keys())
for k in f.keys():
logger.debug("load key %s", k)
face[k] = f.get_tensor(k).numpy()
logger.debug("face : %s", face)
return Face(face)


def get_face_checkpoints() -> List[str]:
"""
Retrieve a list of face checkpoint paths.
This function searches for face files with the extension ".safetensors" in the specified directory and returns a list
containing the paths of those files.
Returns:
list: A list of face paths, including the string "None" as the first element.
"""
faces_path = os.path.join(
scripts.basedir(), "models", "faceswaplab", "faces", "*.safetensors"
)
faces = glob.glob(faces_path)
return ["None"] + faces
17 changes: 0 additions & 17 deletions scripts/faceswaplab_utils/models_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,3 @@ def get_current_model() -> str:
"No faceswap model found. Please add it to the faceswaplab directory."
)
return model


def get_face_checkpoints() -> List[str]:
"""
Retrieve a list of face checkpoint paths.
This function searches for face files with the extension ".pkl" in the specified directory and returns a list
containing the paths of those files.
Returns:
list: A list of face paths, including the string "None" as the first element.
"""
faces_path = os.path.join(
scripts.basedir(), "models", "faceswaplab", "faces", "*.pkl"
)
faces = glob.glob(faces_path)
return ["None"] + faces

0 comments on commit 31635d3

Please sign in to comment.