diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db1c93c --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Devenv +.devenv* +devenv.local.nix +devenv.local.yaml + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml +src/protestswap/__pycache__ diff --git a/flake.lock b/flake.lock index e4f40ad..4672eeb 100644 --- a/flake.lock +++ b/flake.lock @@ -1,43 +1,58 @@ { "nodes": { - "nixpkgs": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1760164275, - "narHash": "sha256-gKl2Gtro/LNf8P+4L3S2RsZ0G390ccd5MyXYrTdMCFE=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "362791944032cb532aabbeed7887a441496d5e6e", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { - "id": "nixpkgs", - "type": "indirect" + "owner": "numtide", + "repo": "flake-utils", + "type": "github" } }, - "pyproject-nix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, + "nixpkgs": { "locked": { - "lastModified": 1759739877, - "narHash": "sha256-XfcxM4nzSuuRmFGiy/MhGwYf9EennQ2+0jjR2aJqtKE=", - "owner": "pyproject-nix", - "repo": "pyproject.nix", - "rev": "966e0961f9670f847439ba90dc25ffaa112d8803", + "lastModified": 1760284886, + "narHash": "sha256-TK9Kr0BYBQ/1P5kAsnNQhmWWKgmZXwUQr4ZMjCzWf2c=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "cf3f5c4def3c7b5f1fc012b3d839575dbe552d43", "type": "github" }, "original": { - "owner": "pyproject-nix", - "repo": "pyproject.nix", + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { - "nixpkgs": "nixpkgs", - "pyproject-nix": "pyproject-nix" + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 832cf8c..2030bde 100644 --- a/flake.nix +++ b/flake.nix @@ -1,41 +1,183 @@ { description = "Flake using pyproject.toml metadata"; - inputs.pyproject-nix.url = "github:pyproject-nix/pyproject.nix"; - inputs.pyproject-nix.inputs.nixpkgs.follows = "nixpkgs"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + pyproject-nix = { + url = "github:pyproject-nix/pyproject.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-utils.url = "github:numtide/flake-utils"; + }; - outputs = - { nixpkgs, pyproject-nix, ... }: - let - inherit (nixpkgs) lib; - forAllSystems = lib.genAttrs lib.systems.flakeExposed; + outputs = { + self, + nixpkgs, + flake-utils, + ... + }@inputs: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + project = inputs.pyproject-nix.lib.project.loadPyproject { + projectRoot = ./.; + }; + projectName = project.pyproject.project.name; + python = pkgs.python3; - project = pyproject-nix.lib.project.loadPyproject { - projectRoot = ./.; + inswapperModel = pkgs.stdenv.mkDerivation { + name = "inswapper_128"; + src = pkgs.fetchurl { + url = "https://huggingface.co/thebiglaskowski/inswapper_128.onnx/resolve/main/inswapper_128.onnx"; + hash = "sha256-5KPwjHU8ty0E4Qqg99vj3uu/OVZ9Tq1tzgjpiqSeFq8="; }; - pythonAttr = "python3"; - in - { - devShells = forAllSystems (system: { - default = - let - pkgs = nixpkgs.legacyPackages.${system}; - python = pkgs.${pythonAttr}; - pythonEnv = python.withPackages (project.renderers.withPackages { inherit python; }); - in - pkgs.mkShell { packages = [ pythonEnv ]; }; - }); + phases = [ "installPhase" ]; - packages = forAllSystems ( - system: - let - pkgs = nixpkgs.legacyPackages.${system}; - python = pkgs.${pythonAttr}; - in - { - default = python.pkgs.buildPythonPackage (project.renderers.buildPythonPackage { inherit python; }); - } - ); + installPhase = '' + mkdir -p "$out/var/lib/insightface/models/" + cp $src "$out/var/lib/insightface/models/inswapper_128.onnx" + ''; }; + buffaloModel = pkgs.stdenv.mkDerivation { + name = "buffalo_l_Model"; + src = pkgs.fetchurl { + url = "https://github.com/deepinsight/insightface/releases/download/v0.7/buffalo_l.zip"; + hash = "sha256-gP/jfYpZQNWac4TCAaKjjUdB8vPFHu9G67KCGKewyi8="; + }; + + unpackPhase = /*shell*/'' + ls -l $src + echo $src + + mkdir -p "$out/var/lib/insightface/buffalo_l/" + unar -D -o $out/var/lib/insightface/buffalo_l/ $src + ''; + + nativeBuildInputs = [ pkgs.unar ]; + }; + postInstall = /*shell*/'' + mkdir -p $out/var/lib/insightface/models/buffalo_l + cp ${inswapperModel} $out/var/lib/insightface/models/inswapper_128.onnx + ''; + # ${pkgs.unar}/bin/unar -D -o $out/var/lib/insightface/models/buffalo_l ${buffaloModel} + # ''; + packageAttrs = project.renderers.buildPythonPackage { + inherit python; + }; + packageAttrsLive = project.renderers.mkPythonEditablePackage { + inherit python; + }; + protestswap = python.pkgs.buildPythonPackage ( packageAttrs # // { inherit postInstall; } + ); + in { + devShells.${system}.default = let + arg = project.renderers.mkPythonEditablePackage { inherit python; }; + pythonEnv = python.pkgs.mkPythonEditablePackage ( arg ); + in pkgs.mkShell { + packages = [ pkgs.python3 pkgs.uv + # buffaloModel + protestswap pkgs.gcc14 ]; + shellHook = /*shell*/ + '' + ls -l ${buffaloModel} + + INSIGHTFACE_ROOT_DIR=$(mktemp -d /tmp/insightface.XXXXXXXX) + export INSIGHTFACE_ROOT_DIR + + mkdir -p "$INSIGHTFACE_ROOT_DIR" + + ln -s ${inswapperModel}/var/lib/insightface/models "$INSIGHTFACE_ROOT_DIR/models" + ln -s ${buffaloModel}/var/lib/insightface/buffalo_l "$INSIGHTFACE_ROOT_DIR/buffalo_l" + + # export PROTESTSWAP_ROOT="/tmp/protestswap_models" + # mkdir -p "$PROTESTSWAP_ROOT/buffalo_l" + # + # cp ${inswapperModel} "$PROTESTSWAP_ROOT/inswapper_128.onnx" + unset PYTHONPATH + uv sync + . .venv/bin/activate + ''; + }; + + packages.${system}.default = protestswap; + }; } + + + # flake-utils.lib.eachDefaultSystem (system: let + # pyproject = builtins.fromTOML (builtins.readFile ./pyproject.toml); + # project = pyproject.project; + # + # package = pkgs.python3Packages.buildPythonPackage { + # pname = project.name; + # inherit (project) version; + # format = "pyproject"; + # + # src = ./.; + # + # build-system = with pkgs.python3Packages; [ + # uv-build + # ]; + # + # # # test dependencies + # nativeCheckInputs = with pkgs; [ + # # python3Packages.mypy + # # python3Packages.pytest + # taplo + # ]; + # + # # checkPhase = ''''; + # + # propagatedBuildInputs = with pkgs; [ + # python3Packages.click + # python3Packages.insightface + # + # ] ++ builtins.map (dep: pkgs.python3Packages.${dep}) project.dependencies; + # }; + # + # editablePackage = pkgs.python3.pkgs.mkPythonEditablePackage { + # pname = project.name; + # inherit (project) scripts version; + # root = "$PWD"; + # }; + # in { + # devShells = { + # default = pkgs.mkShell { + # inputsFrom = [ + # package + # ]; + # + # buildInputs = [ + # # our package + # editablePackage + # + # ################# + # # VARIOUS TOOLS # + # ################# + # + # pkgs.python3Packages.build + # pkgs.python3Packages.ipython + # pkgs.python3Packages.insightface + # inswapperModel + # + # #################### + # # EDITOR/LSP TOOLS # + # #################### + # + # # LSP server: + # pkgs.python3Packages.python-lsp-server + # + # # LSP server plugins of interest: + # pkgs.python3Packages.pylsp-mypy + # pkgs.python3Packages.pylsp-rope + # pkgs.python3Packages.python-lsp-ruff + # ]; + # }; + # }; + # + # packages = { + # "inswapper-model" = inswapperModel; + # "${project.name}" = package; + # default = self.packages.${system}.${project.name}; + # }; + # }); diff --git a/pyproject.toml b/pyproject.toml index 6179bdc..27da1e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,22 @@ [project] -name = "hello-python" -version = "0.1.0" +name = "protestswap" +version = "0.0.1" +license-files = ["LICEN[CS]E*"] description = "A minimal pyproject.toml" authors = [ ] requires-python = ">=3.13" -dependencies = [ "insightface", "opencv-python", "matplotlib" ] +dependencies = [ +"insightface", +"onnxruntime", +"opencv-python", "matplotlib" +] +[project.scripts] +protestswap-cli = "protestswap.cli:main" +[build-system] +requires = ["uv_build >= 0.8.17, <0.9.0"] +build-backend = "uv_build" + +[tool.setuptools.packages] +find = {namespaces = false} # Disable implicit namespaces diff --git a/src/protestswap/__init__.py b/src/protestswap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/protestswap/cli.py b/src/protestswap/cli.py new file mode 100644 index 0000000..67c4726 --- /dev/null +++ b/src/protestswap/cli.py @@ -0,0 +1,29 @@ +import os +import logging + +from protestswap.protestswap import ProtestFaceSwapper + +logger = logging.getLogger("protestswap") + +DEFAULT_INSIGHTFACE_DIR = "/var/lib/insightface" +INSIGHTFACE_VAR = "INSIGHTFACE_ROOT_DIR" + + +def main(): + insightface_dir = None + if os.path.exists(DEFAULT_INSIGHTFACE_DIR): + insightface_dir = DEFAULT_INSIGHTFACE_DIR + elif os.environ.get(INSIGHTFACE_VAR): + insightface_dir = os.environ.get(INSIGHTFACE_VAR) + else: + logger.warning( + f"No directory at '{DEFAULT_INSIGHTFACE_DIR}' and '{INSIGHTFACE_VAR}' not set yet." + ) + insightface_dir = f"{os.environ.get('HOME')}/.cache/insightface" + + # swapper = ProtestFaceSwapper(insightface_dir) + # swapper.prepare_model() + + +if __name__ == "__main__": + main() diff --git a/src/faceswap.py b/src/protestswap/protestswap.py similarity index 89% rename from src/faceswap.py rename to src/protestswap/protestswap.py index d04736c..1b7f584 100644 --- a/src/faceswap.py +++ b/src/protestswap/protestswap.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +import logging +import os + import cv2 import random # TODO: seed! @@ -9,11 +12,14 @@ from insightface.app import FaceAnalysis from insightface.model_zoo.inswapper import INSwapper from insightface.app.common import Face +logger = logging.getLogger("protestswap") + SWAPPING_MODEL = 'inswapper_128.onnx' -class Faceswapper: - def __init__(self): - self._app : FaceAnalysis = FaceAnalysis(name='buffalo_l') + +class ProtestFaceSwapper: + def __init__(self, root_dir): + self._app : FaceAnalysis = FaceAnalysis(name='buffalo_l', root=insightface_dir) self._model : INSwapper self._source : cv2.typing.MatLike|None = None self._source_faces : list[Face] = [] @@ -49,9 +55,9 @@ class Faceswapper: def main(): - swapper = Faceswapper() - swapper.prepare_model() + swapper = ProtestFaceSwapper(insightface_dir) + swapper.prepare_model() if __name__ == '__main__':