File size: 6,553 Bytes
f66643d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# syntax=docker/dockerfile:1
# E2E PDF translator (OCR -> Translate -> Render) for a GPU Hugging Face Space.
# Base: CUDA 13 runtime + Ubuntu 22.04 (Python 3.10) β€” the config that builds cleanly.
# Only surya-ocr/paddleocr are pinned (in requirements.txt); torch/paddle/numpy stay
# unpinned so pip resolves a mutually compatible CUDA stack. T4 = Turing sm_75 (OK on CUDA 13).
# NOTE: if this tag 404s at build, pick an existing one from
#   https://hub.docker.com/r/nvidia/cuda/tags  (e.g. 13.0.1-cudnn-runtime-ubuntu22.04).
FROM nvidia/cuda:13.0.0-cudnn-runtime-ubuntu22.04

ENV DEBIAN_FRONTEND=noninteractive \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    TORCH_DEVICE=cuda \
    TYPST_BIN=typst \
    PDF2ZH_FONT_DIR=/app/fonts \
    HF_HOME=/app/.cache/huggingface \
    TRANSFORMERS_CACHE=/app/.cache/huggingface \
    TYPST_PACKAGE_CACHE_PATH=/app/.cache/typst \
    MODEL_CACHE_DIR=/app/.cache/datalab/models \
    PADDLE_PDX_CACHE_HOME=/app/.cache/paddlex

WORKDIR /app
EXPOSE 7860

# ── System deps ───────────────────────────────────────────────────────────────
#  - python 3.10 + pip (Ubuntu 22.04 default)
#  - OpenCV / PyMuPDF runtime libs (libgl1, libglib2.0-0, ...)
#  - fonts: Noto Sans/Serif + Noto CJK (covers Vietnamese + CJK), fontconfig
#  - wget/xz to fetch the typst binary
RUN apt-get update && apt-get install --no-install-recommends -y \
        python3 python3-pip python3-dev \
        libgl1 libglib2.0-0 libxext6 libsm6 libxrender1 \
        fontconfig fonts-noto-core fonts-noto-cjk \
        wget xz-utils ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# ── Typst binary ────────────────────────────────────────────────────────────────
ARG TYPST_VERSION=v0.14.2
RUN wget -qO /tmp/typst.tar.xz \
        "https://github.com/typst/typst/releases/download/${TYPST_VERSION}/typst-x86_64-unknown-linux-musl.tar.xz" && \
    tar -xJf /tmp/typst.tar.xz -C /tmp && \
    install -m 0755 /tmp/typst-x86_64-unknown-linux-musl/typst /usr/local/bin/typst && \
    rm -rf /tmp/typst* && typst --version

# ── Extra fonts (Be Vietnam Pro β€” open-source Google Font) ───────────────────────
RUN mkdir -p /app/fonts && \
    for w in Regular Bold Italic; do \
        wget -qO "/app/fonts/BeVietnamPro-${w}.ttf" \
          "https://github.com/google/fonts/raw/main/ofl/bevietnampro/BeVietnamPro-${w}.ttf" || true; \
    done && \
    # also surface the system Noto fonts to the typst --font-path dir
    cp -n /usr/share/fonts/truetype/noto/*.ttf /app/fonts/ 2>/dev/null || true && \
    cp -n /usr/share/fonts/opentype/noto/*.otf /app/fonts/ 2>/dev/null || true && \
    fc-cache -f

# ── Python deps ──────────────────────────────────────────────────────────────────
COPY requirements.txt .
RUN python3 -m pip install --upgrade pip && \
    python3 -m pip install -r requirements.txt

# ── App code ─────────────────────────────────────────────────────────────────────
# Running from /app puts the pdf2zh package on sys.path, so no editable install is
# needed (and it avoids pulling pyproject's heavier optional deps like babeldoc).
COPY . .

# ── Seed .env from the tracked template ────────────────────────────────────────
# .env is gitignored (absent from the image) but Settings reads env_file=".env".
# Copy it here as root: the Space runs the container as a non-root user that cannot
# write to /app at runtime, so seeding only in the entrypoint fails with EACCES.
RUN cp -n .env.example .env 2>/dev/null || true

# ── Pre-cache typst packages (cmarker + mitex) so runtime needs no network ─────────
RUN mkdir -p /app/.cache/typst && \
    printf '#import "@preview/cmarker:0.1.8"\n#import "@preview/mitex:0.2.6": *\n#cmarker.render("ok")\n' \
        > /tmp/warm.typ && \
    typst compile /tmp/warm.typ /tmp/warm.pdf || echo "typst package pre-cache skipped"

# OCR models (~3-5GB) are NOT baked in β€” they download on the first request:
#   - Surya (layout/detection/recognition) -> Datalab's servers, cached in MODEL_CACHE_DIR
#   - Paddle table-cell model              -> PaddleX model server, cached in PADDLE_PDX_CACHE_HOME
# Make caches writable in case the Space runs the container as a non-root user.
RUN mkdir -p /app/.cache/datalab/models /app/.cache/paddlex /app/.cache/huggingface \
        /app/.cache/typst && chmod -R 777 /app/.cache

# Force UTF-8 so the app can write Vietnamese text / .typ files on a minimal locale
# (placed after pip so the heavy install layer stays cached). Avoids
# UnicodeEncodeError: 'ascii' codec can't encode ... at render time.
ENV PYTHONUTF8=1 \
    LANG=C.UTF-8 \
    LC_ALL=C.UTF-8

# Entrypoint: prefer HF Persistent Storage (/data) for the model caches so they
# survive sleep/restart and download only once. Falls back to /app/.cache (ephemeral)
# when persistent storage is not enabled.
RUN cat > /usr/local/bin/entrypoint.sh <<'EOF'
#!/usr/bin/env bash
set -e
if mkdir -p /data 2>/dev/null && [ -w /data ]; then
  CACHE_ROOT=/data
else
  CACHE_ROOT=/app/.cache
fi
export MODEL_CACHE_DIR="$CACHE_ROOT/datalab/models"
export PADDLE_PDX_CACHE_HOME="$CACHE_ROOT/paddlex"
export HF_HOME="$CACHE_ROOT/huggingface"
export TRANSFORMERS_CACHE="$CACHE_ROOT/huggingface"
mkdir -p "$MODEL_CACHE_DIR" "$PADDLE_PDX_CACHE_HOME" "$HF_HOME"
echo "[entrypoint] model cache root = $CACHE_ROOT"
# Fallback seed for writable-/app environments (local runs). On the Space /app is
# read-only at runtime, so .env is already baked at build time; keep this non-fatal
# (|| true) so a failed copy never aborts the entrypoint under `set -e`.
if [ ! -f /app/.env ] && [ -f /app/.env.example ]; then
  cp /app/.env.example /app/.env 2>/dev/null \
    && echo "[entrypoint] seeded /app/.env from .env.example" \
    || echo "[entrypoint] /app not writable; using build-time .env"
fi
exec python3 app.py
EOF
RUN chmod +x /usr/local/bin/entrypoint.sh

CMD ["/usr/local/bin/entrypoint.sh"]