FROM nvidia/cuda:12.9.0-runtime-ubuntu24.04

# Avoid interactive prompts during package installation
ENV DEBIAN_FRONTEND=noninteractive

# Install Python and system dependencies. espeak-ng is REQUIRED: Kokoro's English G2P
# (misaki) uses it as the out-of-dictionary fallback; without it OOD words are dropped. It is also
# the WHOLE G2P for Spanish/French/Italian/Portuguese/Hindi, which go through EspeakG2P.
# build-essential + cmake are needed only for the Japanese G2P: misaki[ja] pulls pyopenjtalk, which
# publishes NO wheels (sdist only), so pip builds its OpenJTalk C++ engine from source with CMake.
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 \
    python3-pip \
    python3-dev \
    build-essential \
    cmake \
    espeak-ng \
    libsndfile1 \
    ffmpeg \
    && rm -rf /var/lib/apt/lists/*

# Set Python alias (Ubuntu 24.04 ships Python 3.12)
RUN ln -sf /usr/bin/python3 /usr/bin/python
ENV PIP_BREAK_SYSTEM_PACKAGES=1

WORKDIR /app

# Install PyTorch (cu128 wheels for CUDA 12.8+/12.9 compat). Kokoro leaves torch unpinned.
RUN pip install --no-cache-dir \
    torch==2.8.0 \
    torchaudio==2.8.0 \
    --index-url https://download.pytorch.org/whl/cu128

# Kokoro (brings misaki[en] G2P, huggingface_hub, transformers, etc.) + eval-loop deps.
RUN pip install --no-cache-dir "kokoro>=0.9.4" soundfile datasets tqdm

# G2P backends for the non-espeak languages: Japanese (lang_code 'j') and Mandarin ('z').
# `pip install kokoro` brings misaki[en] ONLY, so KPipeline's `from misaki import ja` / `zh` raises
# ImportError for those two and the job dies at pipeline construction.
#   ja -> pyopenjtalk (compiled, see cmake above), fugashi, jaconv, mojimoji, unidic
#   zh -> jieba, pypinyin, pypinyin-dict, cn2an, ordered-set   (all pure Python)
# The version is pinned to the misaki ALREADY INSTALLED above, so adding extras cannot silently
# upgrade misaki out from under the pinned kokoro.
RUN pip install --no-cache-dir \
    "misaki[ja,zh]==$(python -c 'import importlib.metadata as m; print(m.version("misaki"))')"

# UniDic dictionary — REQUIRED for Japanese, not optional. Kokoro constructs misaki's `ja.JAG2P()`,
# whose signature is `__init__(self, version='cutlet', ...)`: the DEFAULT path builds Cutlet(), which
# calls `fugashi.Tagger()` with no arguments. fugashi then resolves its dictionary to the installed
# `unidic` package — a downloader stub that ships EMPTY — so without this the job dies at pipeline
# construction with "Failed initializing MeCab ... no such file: .../unidic/dicdir/mecabrc".
# Costs ~770 MB unpacked. Lighter alternative, at the price of an older dictionary (UniDic 2.1.2)
# and slightly different readings, which fugashi picks up only when `unidic` is absent:
#     RUN pip uninstall -y unidic && pip install --no-cache-dir unidic-lite
RUN python -m unidic download

# Build-time smoke test of the two G2P backends Kokoro actually constructs, so a missing dictionary
# fails the BUILD rather than a GPU job hours later. JAG2P() exercises the cutlet path (fugashi +
# UniDic) and ZHG2P() the jieba/pypinyin one. The jieba cache and pyopenjtalk dictionary are warmed
# here too: both are fetched/built on first use, which inside the eval loop would land in the
# *timed* generation of the first sample and corrupt its RTFx.
RUN python -c "from misaki.ja import JAG2P; print(JAG2P()('今日はいい天気ですね'))" \
    && python -c "from misaki.zh import ZHG2P; print(ZHG2P()('今天天气很好'))" \
    && python -c "import pyopenjtalk; print(pyopenjtalk.g2p('こんにちは'))" \
    && python -c "import jieba; print('/'.join(jieba.lcut('预热结巴缓存')))"

# Copy the full repository
COPY . /app

# Default entrypoint
ENTRYPOINT ["bash"]

# Keep-alive CMD so the Space runtime stays healthy; `docker run` overrides it.
EXPOSE 7860
CMD ["-c", "python3 -m http.server 7860"]
