# Copyright 2026 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

# LocalAI TTS backend (fish-speech / OpenAudio). Upstream's install
# clones fish-speech from UNPINNED git main at install time; we pin
# the release tag below and install the source next to backend.py.
# The venv skeleton lives in local-ai-python.eclass; the parity-rule
# resolution here additionally replicates upstream's own install
# deviations (torch/torchaudio pins stripped, pyaudio dropped,
# protobuf 5 from the system overriding audiotools' stale <3.20 pin)
# plus one of ours: pydantic==2.9.2 relaxed to the tree pydantic
# (resolution- and API-verified; the tree package builds pydantic_core
# in-package). wandb and tensorboard-data-server are pending (prebuilt
# binaries in their wheels).

EAPI=8

# backend.py imports fish_speech lazily at LoadModel — smoke-test the
# real inference closure.
LOCAL_AI_PYTHON_SMOKE_IMPORTS="backend
	fish_speech.inference_engine
	fish_speech.models.dac.inference
	fish_speech.models.text2semantic.inference
	fish_speech.utils.schema"

inherit local-ai-python

# The newest fish-speech release; matches the module layout backend.py
# imports (inference_engine, models.dac, models.text2semantic).
# v2.0.0-beta at the time of pinning; fetched by commit because tags can
# move — the Manifest digest would only turn that into a fetch error.
FISH_SPEECH_COMMIT="3578e4e7099ee85464756dab27a3af86b5a21331"

DESCRIPTION="LocalAI text-to-speech backend (fish-speech gRPC server)"
SRC_URI+="
	https://github.com/fishaudio/fish-speech/archive/${FISH_SPEECH_COMMIT}.tar.gz
		-> fish-speech-${FISH_SPEECH_COMMIT}.gh.tar.gz
"
FISH_S="${WORKDIR}/fish-speech-${FISH_SPEECH_COMMIT}"

LICENSE="MIT Apache-2.0 Fish-Audio-Research"
IUSE="distributed"

# descript-audiotools touches torch.distributed.ReduceOp at import
# time. With USE=distributed the requirement is passed through to
# pytorch (full fidelity, DDP training possible); without it the
# install lazy-guards the one line instead (see src_install).
RDEPEND="distributed? ( sci-ml/pytorch[distributed] )"

RDEPEND+="
	$(python_gen_cond_dep '
		sci-ml/torchaudio[${PYTHON_SINGLE_USEDEP}]
		app-arch/brotli[python,${PYTHON_USEDEP}]
		sci-ml/hf_xet[${PYTHON_USEDEP}]
		dev-python/llvmlite[${PYTHON_USEDEP}]
		dev-python/numba[${PYTHON_USEDEP}]
		dev-python/ormsgpack[${PYTHON_USEDEP}]
		dev-python/soxr[${PYTHON_USEDEP}]
		dev-python/tiktoken[${PYTHON_USEDEP}]
		dev-python/absl-py[${PYTHON_USEDEP}]
		dev-python/aiohappyeyeballs[${PYTHON_USEDEP}]
		dev-python/aiohttp[${PYTHON_USEDEP}]
		dev-python/aiosignal[${PYTHON_USEDEP}]
		dev-python/annotated-doc[${PYTHON_USEDEP}]
		dev-python/annotated-types[${PYTHON_USEDEP}]
		dev-python/asttokens[${PYTHON_USEDEP}]
		dev-python/attrs[${PYTHON_USEDEP}]
		dev-python/cachetools[${PYTHON_USEDEP}]
		dev-python/cffi[${PYTHON_USEDEP}]
		dev-python/click[${PYTHON_USEDEP}]
		dev-python/cloudpickle[${PYTHON_USEDEP}]
		dev-python/contourpy[${PYTHON_USEDEP}]
		dev-python/cycler[${PYTHON_USEDEP}]
		dev-python/decorator[${PYTHON_USEDEP}]
		dev-python/docstring-parser[${PYTHON_USEDEP}]
		dev-python/executing[${PYTHON_USEDEP}]
		dev-python/fastapi[${PYTHON_USEDEP}]
		dev-python/fonttools[${PYTHON_USEDEP}]
		dev-python/frozenlist[${PYTHON_USEDEP}]
		dev-python/gitdb[${PYTHON_USEDEP}]
		dev-python/gitpython[${PYTHON_USEDEP}]
		dev-python/ipython[${PYTHON_USEDEP}]
		dev-python/ipython-pygments-lexers[${PYTHON_USEDEP}]
		dev-python/jedi[${PYTHON_USEDEP}]
		dev-python/jinja2[${PYTHON_USEDEP}]
		dev-python/joblib[${PYTHON_USEDEP}]
		dev-python/kiwisolver[${PYTHON_USEDEP}]
		dev-python/lazy-loader[${PYTHON_USEDEP}]
		dev-python/loguru[${PYTHON_USEDEP}]
		dev-python/markdown[${PYTHON_USEDEP}]
		dev-python/markdown-it-py[${PYTHON_USEDEP}]
		dev-python/markdown2[${PYTHON_USEDEP}]
		dev-python/markupsafe[${PYTHON_USEDEP}]
		dev-python/matplotlib[${PYTHON_USEDEP}]
		dev-python/matplotlib-inline[${PYTHON_USEDEP}]
		dev-python/mdurl[${PYTHON_USEDEP}]
		dev-python/mpmath[${PYTHON_USEDEP}]
		dev-python/msgpack[${PYTHON_USEDEP}]
		dev-python/multidict[${PYTHON_USEDEP}]
		dev-python/multiprocess[${PYTHON_USEDEP}]
		dev-python/narwhals[${PYTHON_USEDEP}]
		dev-python/natsort[${PYTHON_USEDEP}]
		dev-python/networkx[${PYTHON_USEDEP}]
		dev-python/orjson[${PYTHON_USEDEP}]
		dev-python/pandas[${PYTHON_USEDEP}]
		dev-python/parso[${PYTHON_USEDEP}]
		dev-python/pexpect[${PYTHON_USEDEP}]
		dev-python/pillow[${PYTHON_USEDEP}]
		dev-python/platformdirs[${PYTHON_USEDEP}]
		dev-python/pooch[${PYTHON_USEDEP}]
		dev-python/prompt-toolkit[${PYTHON_USEDEP}]
		dev-python/propcache[${PYTHON_USEDEP}]
		dev-python/ptyprocess[${PYTHON_USEDEP}]
		dev-python/pure-eval[${PYTHON_USEDEP}]
		dev-python/pyarrow[${PYTHON_USEDEP}]
		dev-python/pycparser[${PYTHON_USEDEP}]
		dev-python/pydantic[${PYTHON_USEDEP}]
		dev-python/pygments[${PYTHON_USEDEP}]
		dev-python/pyparsing[${PYTHON_USEDEP}]
		dev-python/python-dateutil[${PYTHON_USEDEP}]
		dev-python/python-dotenv[${PYTHON_USEDEP}]
		dev-python/python-multipart[${PYTHON_USEDEP}]
		dev-python/pytz[${PYTHON_USEDEP}]
		dev-python/rich[${PYTHON_USEDEP}]
		dev-python/scikit-learn[${PYTHON_USEDEP}]
		dev-python/scipy[${PYTHON_USEDEP}]
		dev-python/semantic-version[${PYTHON_USEDEP}]
		dev-python/setuptools[${PYTHON_USEDEP}]
		dev-python/shellingham[${PYTHON_USEDEP}]
		dev-python/six[${PYTHON_USEDEP}]
		dev-python/smmap[${PYTHON_USEDEP}]
		dev-python/soundfile[${PYTHON_USEDEP}]
		dev-python/stack-data[${PYTHON_USEDEP}]
		dev-python/starlette[${PYTHON_USEDEP}]
		dev-python/sympy[${PYTHON_USEDEP}]
		dev-python/termcolor[${PYTHON_USEDEP}]
		dev-python/threadpoolctl[${PYTHON_USEDEP}]
		dev-python/traitlets[${PYTHON_USEDEP}]
		dev-python/typer[${PYTHON_USEDEP}]
		dev-python/typing-inspection[${PYTHON_USEDEP}]
		dev-python/uvicorn[${PYTHON_USEDEP}]
		dev-python/wcwidth[${PYTHON_USEDEP}]
		dev-python/werkzeug[${PYTHON_USEDEP}]
		dev-python/xxhash[${PYTHON_USEDEP}]
		dev-python/yarl[${PYTHON_USEDEP}]
		dev-python/zstandard[${PYTHON_USEDEP}]
	')
	$(python_gen_cond_dep 'dev-python/audioop-lts[${PYTHON_USEDEP}]' python3_{13..14})
"
DEPEND="${RDEPEND}"

src_prepare() {
	default
	# The system torchaudio is 2.11 (the maintenance-mode last tag) —
	# newer than the 2.8 upstream pins; see the patch header.
	eapply -d "${FISH_S}" -- "${FILESDIR}/fish-speech-2.0.0-beta-torchaudio-2.11.patch"
}

src_unpack() {
	local-ai-python_src_unpack
	unpack "fish-speech-${FISH_SPEECH_COMMIT}.gh.tar.gz"
}

src_install() {
	exeinto "${BACKEND_DIR}"
	doexe backend.py

	# The pinned fish-speech source, importable next to backend.py
	# (python puts the script's directory on sys.path). Installed
	# UNMODIFIED, whole tree — no behaviour trimming.
	insinto "${BACKEND_DIR}"
	doins -r "${FISH_S}/fish_speech"
	# pyrootutils walks up to this marker; upstream's install.sh
	# creates the same file.
	touch "${T}/.project-root" || die
	doins "${T}/.project-root"

	local-ai-python_install_venv

	if ! use distributed; then
		# Stringified annotation + lazy default: identical semantics on
		# a full torch, importable on the distributed-less stub — only
		# an actual DDP training run would miss it.
		local decorators=( "${ED}${BACKEND_DIR}/venv/lib"/python*/site-packages/audiotools/ml/decorators.py )
		grep -q 'op: dist.ReduceOp = dist.ReduceOp.AVG,' "${decorators[@]}" \
			|| die "audiotools ReduceOp anchor moved"
		sed -i 's|op: dist.ReduceOp = dist.ReduceOp.AVG,|op: "dist.ReduceOp" = getattr(dist, "ReduceOp", None) and dist.ReduceOp.AVG,|' \
			"${decorators[@]}" || die
	fi
	python_optimize "${ED}${BACKEND_DIR}/fish_speech"
	local-ai-python_install_meta
	local-ai-python_smoke_test
}