Skip to content

kokoro-onnx-nix-package

Packages

Package kokoro-onnx — an ONNX-runtime text-to-speech engine — for Nix, when neither it nor several of its dependencies exist in nixpkgs.

The problem

kokoro-onnx is not in nixpkgs. Neither are three packages in its dependency closure:

  • phonemizer-fork — a maintained fork of phonemizer (grapheme → phoneme)
  • espeakng-loader — locates and loads the espeak-ng shared library
  • dlinfo — a tiny ctypes helper that phonemizer-fork depends on

There is no overlay to lean on, so all four are hand-rolled with buildPythonPackage straight from PyPI. Three of them (kokoro-onnx, dlinfo, phonemizer-fork) are ordinary pure-Python pyproject sdists and build the boring way.

The trap: espeakng-loader must be fetched as a wheel, not an sdist

espeakng-loader bundles a precompiled native libespeak-ng inside its wheel. There is one wheel per platform. The PyPI sdist does not build the native library — so the reflexive fetchPypi { inherit pname version; } (which grabs the sdist) gives you a package that imports but has no working espeak-ng behind it.

The fix is to fetch the platform-matching wheel explicitly:

espeakng-loader = python3Packages.buildPythonPackage {
  pname = "espeakng-loader";
  version = "0.2.4";
  format = "wheel";                 # <- not an sdist build
  src = pkgs.fetchurl {
    url  = ".../espeakng_loader-0.2.4-py3-none-<platform>.whl";
    hash = "sha256-...";
  };
};

Because the wheel is platform-specific, you must pick the wheel that matches the machine you build/run on. The default in default.nix is the macOS arm64 wheel — override it for Linux x86_64, Linux aarch64, etc.

Two other small gotchas

  • PyPI name normalization. phonemizer-fork and kokoro-onnx are published on PyPI under underscored dist names (phonemizer_fork, kokoro_onnx). fetchPypi defaults pname to the value you pass, so pass the underscored form to pname (or it 404s).
  • doCheck = false on all of them. Their test suites want network access and extra tooling that add nothing to a package build.

Usage

let
  kokoro-onnx = import ./default.nix {
    inherit pkgs;
    inherit (pkgs) python3Packages;
  };
in
  # e.g. pkgs.python3.withPackages (ps: [ kokoro-onnx ])

Selecting the wheel for your platform

Override espeakngWheel. Get the URL and hash from the "Download files" tab on https://pypi.org/project/espeakng-loader/#files; compute the hash with nix store prefetch-file <url>.

import ./default.nix {
  inherit pkgs;
  inherit (pkgs) python3Packages;
  espeakngWheel = {
    url  = "https://files.pythonhosted.org/.../espeakng_loader-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl";
    hash = "sha256-...";
  };
}

You can also drive this off pkgs.stdenv.hostPlatform.system with an attrset mapping each system to its wheel, so a multi-arch build picks the right one automatically.

Caveats

  • Versions and hashes are pinned to a point in time (kokoro-onnx 0.5.0, espeakng-loader 0.2.4, phonemizer-fork 3.3.2, dlinfo 2.0.0). Bump and re-hash as upstream moves; the wheel URL changes with every espeakng-loader release.
  • If any of these land in nixpkgs later, drop the corresponding let binding and use the nixpkgs attribute instead.
  • The Kokoro voice/model ONNX files themselves are not packaged here — this is just the inference library.

Source

packages/kokoro-onnx-nix-package/default.nix
# kokoro-onnx — an ONNX-based TTS inference engine, packaged for Nix.
#
# kokoro-onnx is not in nixpkgs, and neither are three of its dependencies:
#   - phonemizer-fork   (a maintained fork of `phonemizer`)
#   - espeakng-loader   (locates/loads the espeak-ng shared library)
#   - dlinfo            (tiny ctypes helper phonemizer-fork pulls in)
#
# So we hand-roll all four with buildPythonPackage straight from PyPI.
#
# THE TRAP (see README): espeakng-loader ships a *precompiled* libespeak-ng
# bundled inside its wheel, one wheel per platform. Its PyPI sdist does NOT
# build the native library, so a plain fetchPypi sdist is useless — you must
# fetch the platform-matching wheel (format = "wheel"). Pick the URL/hash for
# YOUR platform from https://pypi.org/project/espeakng-loader/#files
# (linux x86_64, linux aarch64, macos arm64, macos x86_64, windows, …).
#
# Usage:
#   kokoro-onnx = import ./default.nix {
#     inherit pkgs;
#     inherit (pkgs) python3Packages;
#   };
#
# The `espeakngWheel` argument lets a caller override the wheel without
# editing this file (e.g. select per pkgs.stdenv.hostPlatform.system).

{
  pkgs,
  python3Packages,

  # Platform-pinned espeakng-loader wheel. Default is the macOS arm64 wheel;
  # OVERRIDE this for your platform. Grab url+hash from PyPI "Download files".
  # To compute the hash: nix store prefetch-file <url>
  espeakngWheel ? {
    url = "https://files.pythonhosted.org/packages/a8/26/258c0cd43b9bc1043301c5f61767d6a6c3b679df82790c9cb43a3277b865/espeakng_loader-0.2.4-py3-none-macosx_11_0_arm64.whl";
    hash = "sha256-0nzcoxESIm5ymdhWLoidPjih5IBVye44G0XWaQcu5Z8=";
  },
}:
let
  # Fetched as a WHEEL, not an sdist: the wheel carries the precompiled
  # libespeak-ng native library. `format = "wheel"` skips the build step.
  espeakng-loader = python3Packages.buildPythonPackage rec {
    pname = "espeakng-loader";
    version = "0.2.4";
    format = "wheel";
    src = pkgs.fetchurl {
      inherit (espeakngWheel) url hash;
    };
  };

  dlinfo = python3Packages.buildPythonPackage rec {
    pname = "dlinfo";
    version = "2.0.0";
    pyproject = true;
    src = python3Packages.fetchPypi {
      inherit pname version;
      hash = "sha256-iKK8BPUdAbxgTNyescPMC96JBXUyymo+caQfYjVDPhc=";
    };
    build-system = with python3Packages; [
      setuptools
      setuptools-scm
    ];
    doCheck = false;
  };

  phonemizer-fork = python3Packages.buildPythonPackage rec {
    pname = "phonemizer-fork";
    version = "3.3.2";
    pyproject = true;
    src = python3Packages.fetchPypi {
      # PyPI normalizes the dist name with an underscore; pname here is the
      # human-readable name, so fetchPypi's pname is overridden explicitly.
      pname = "phonemizer_fork";
      inherit version;
      hash = "sha256-EOFugn0EQ7CHBi4htV6AXACYnPE0Oy6B5zTK5fbAz2k=";
    };
    build-system = [ python3Packages.hatchling ];
    dependencies = with python3Packages; [
      espeakng-loader
      attrs
      dlinfo
      joblib
      segments
      typing-extensions
    ];
    doCheck = false;
  };
in
python3Packages.buildPythonPackage rec {
  pname = "kokoro-onnx";
  version = "0.5.0";
  pyproject = true;

  src = python3Packages.fetchPypi {
    pname = "kokoro_onnx";
    inherit version;
    hash = "sha256-W+sV8IXigo7Y1JP3ksB5r4VxA6stzqoeESsXYFh6yWo=";
  };

  build-system = [ python3Packages.hatchling ];

  dependencies = with python3Packages; [
    numpy
    onnxruntime
    phonemizer-fork
  ];

  doCheck = false;

  meta = with pkgs.lib; {
    description = "TTS inference engine using ONNX runtime (Kokoro)";
    homepage = "https://github.com/thewh1teagle/kokoro-onnx";
    license = licenses.mit;
  };
}