File size: 7,104 Bytes
1b2e96f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""Locate the model files used by 4DAnyone.

Every published file is anchored by one immutable Hugging Face revision and
downloaded on demand. ``fdanyone.download`` fetches missing files;
the resolvers here only locate them.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING

from fdanyone.errors import AssetError

if TYPE_CHECKING:
    from fdanyone.config import ModeSettings

HF_REPO_ID = "AntResearch/4DAnyone"
HF_REVISION = "7850985888b56aabf09e69480b73248f1a76bcbe"

BIREFNET_REPO_ID = "ZhengPeng7/BiRefNet"
BIREFNET_REVISION = "e2bf8e4460fc8fa32bba5ea4d94b3233d367b0e4"
BIREFNET_DIR = "birefnet"
BIREFNET_FILES = (
    "BiRefNet_config.py",
    "birefnet.py",
    "config.json",
    "model.safetensors",
)

CHECKPOINT = "4danyone/model.safetensors"
MHR70_REGRESSOR = "4danyone/smplx_to_goliath70.pt"
WAN_VAE = "4danyone/Wan2.2_VAE.pth"
TEXT_ENCODER = "4danyone/models_t5_umt5-xxl-enc-bf16.pth"
TOKENIZER_DIR = "4danyone/umt5-xxl"
TOKENIZER_FILES = tuple(
    f"{TOKENIZER_DIR}/{name}"
    for name in ("special_tokens_map.json", "spiece.model", "tokenizer.json", "tokenizer_config.json")
)

GVHMR_CHECKPOINT = "gvhmr/gvhmr_siga24_release.ckpt"
HMR2_CHECKPOINT = "gvhmr/epoch=10-step=25000.ckpt"
VITPOSE_CHECKPOINT = "gvhmr/vitpose-h-multi-coco.pth"
YOLO_CHECKPOINT = "gvhmr/yolov8x.pt"
PERCEPTUAL_VGG19 = "perceptual/imagenet-vgg-verydeep-19-conv.safetensors"
TAEW2_2 = "fps-assets/taehv/taew2_2.pth"
TAEW2_2_URL = (
    "https://raw.githubusercontent.com/madebyollin/taehv/"
    "e743234f3217ab3d1570f65642ab06596d1bd7c5/taew2_2.pth"
)
TAEW2_2_SHA256 = "d053e216ca50e2bb837bbcd79b85f0366bea00e5938025572382a773b74c559a"
TURBO_LORA = (
    "fps-assets/turbo-lora/LoRAs/Wan22-Turbo/"
    "Wan22_TI2V_5B_Turbo_lora_rank_64_fp16.safetensors"
)
TURBO_LORA_REPO_ID = "Kijai/WanVideo_comfy"
TURBO_LORA_REVISION = "86c2b0442e01eeee630b48fd7efc0cd37af03252"
TURBO_LORA_REPO_FILE = (
    "LoRAs/Wan22-Turbo/Wan22_TI2V_5B_Turbo_lora_rank_64_fp16.safetensors"
)
TURBO_LORA_SHA256 = "0ace5244e3d1256f884662c261b017249796cf5b95f05d5ed93cc02a478967b8"

SMPLX_MODEL = "body_models/smplx/SMPLX_NEUTRAL.npz"

# Space patch (sync_vendor.sh): the exported prompt embedding replaces the
# UMT5-XXL encoder and its tokenizer, and reconstruction never runs here.
MODEL_FILES = (
    CHECKPOINT,
    MHR70_REGRESSOR,
    WAN_VAE,
    GVHMR_CHECKPOINT,
    HMR2_CHECKPOINT,
    VITPOSE_CHECKPOINT,
    YOLO_CHECKPOINT,
)

EXAMPLE_FILES = (
    "data/source/pexels/10331522-uhd_2160_4096_25fps.mp4",
    "data/source/pexels/2785536-uhd_2160_3840_25fps.mp4",
    "data/source/pexels/5435720-uhd_2160_4096_25fps.mp4",
    "data/source/pexels/5885633-hd_1080_1920_25fps.mp4",
    "data/source/pexels/5999210-uhd_2160_4096_25fps.mp4",
    "data/source/pexels/6980035-uhd_2160_4096_30fps.mp4",
    "data/source/pexels/7080903-hd_1080_1920_30fps.mp4",
    "data/source/pexels/7480858-uhd_2160_3840_25fps.mp4",
)

# Upstream GVHMR resolves its model files relative to its own checkout, so the
# install commands link each downloaded file to the location GVHMR expects.
GVHMR_LINKS = (
    (GVHMR_CHECKPOINT, "inputs/checkpoints/gvhmr/gvhmr_siga24_release.ckpt"),
    (HMR2_CHECKPOINT, "inputs/checkpoints/hmr2/epoch=10-step=25000.ckpt"),
    (VITPOSE_CHECKPOINT, "inputs/checkpoints/vitpose/vitpose-h-multi-coco.pth"),
    (YOLO_CHECKPOINT, "inputs/checkpoints/yolo/yolov8x.pt"),
    (SMPLX_MODEL, "inputs/checkpoints/body_models/smplx/SMPLX_NEUTRAL.npz"),
)


@dataclass(frozen=True, slots=True)
class BaseAssets:
    vae: Path
    """Wan VAE checkpoint."""
    text_encoder: Path | None
    """UMT5 text-encoder checkpoint; ``None`` when a prompt embedding replaces it."""
    tokenizer: Path | None
    """UMT5 tokenizer directory; ``None`` when a prompt embedding replaces it."""
    tiny_decoder: Path | None
    """Pinned TAEW2.2 checkpoint when turbo mode needs it."""
    turbo_lora: Path | None
    """Pinned Wan2.2 Turbo-LoRA when turbo mode needs it."""


def _require_file(path: Path, label: str, command: str) -> Path:
    resolved = path.expanduser().resolve()
    if not resolved.is_file():
        raise AssetError(f"{label} does not exist: {resolved}. Run `python {command}` to install it.")
    return resolved


def resolve_checkpoint(path: str | Path | None = None, model_dir: str | Path = "models") -> Path:
    if path is not None:
        resolved = Path(path).expanduser().resolve()
        if not resolved.is_file():
            raise AssetError(f"Checkpoint override does not exist: {resolved}")
        return resolved
    return _require_file(Path(model_dir) / CHECKPOINT, "Checkpoint", "scripts/download_model.py")


def resolve_regressor(path: str | Path | None = None, model_dir: str | Path = "models") -> Path:
    if path is not None:
        resolved = Path(path).expanduser().resolve()
        if not resolved.is_file():
            raise AssetError(f"MHR70 regressor override does not exist: {resolved}")
        return resolved
    return _require_file(Path(model_dir) / MHR70_REGRESSOR, "MHR70 regressor", "scripts/download_model.py")


def resolve_foreground_model(model_dir: str | Path = "models") -> Path:
    root = Path(model_dir).expanduser() / BIREFNET_DIR
    for relative in BIREFNET_FILES:
        _require_file(root / relative, "BiRefNet file", "scripts/download_model.py")
    return root.resolve()


def resolve_perceptual_vgg19(model_dir: str | Path = "models") -> Path:
    """Resolve the converted VGG-19 weights used by perceptual reconstruction."""

    return _require_file(
        Path(model_dir) / PERCEPTUAL_VGG19,
        "Perceptual VGG-19 weights",
        "scripts/download_model.py",
    )


def resolve_base_assets(
    model_dir: str | Path,
    settings: ModeSettings,
    *,
    have_prompt_embedding: bool = False,
) -> BaseAssets:
    """Resolve the local VAE, T5 encoder, and tokenizer.

    The encoder and its tokenizer serve only the single fixed prompt, so a
    caller that already holds the exported embedding needs neither and must
    not be asked for the 11 GB checkpoint.
    """

    root = Path(model_dir).expanduser()
    text_encoder: Path | None = None
    tokenizer: Path | None = None
    if not have_prompt_embedding:
        for relative in TOKENIZER_FILES:
            _require_file(root / relative, "Tokenizer file", "scripts/download_model.py")
        text_encoder = _require_file(root / TEXT_ENCODER, "Text encoder", "scripts/download_model.py")
        tokenizer = (root / TOKENIZER_DIR).expanduser().resolve()
    return BaseAssets(
        vae=_require_file(root / WAN_VAE, "VAE", "scripts/download_model.py"),
        text_encoder=text_encoder,
        tokenizer=tokenizer,
        tiny_decoder=(
            _require_file(root / TAEW2_2, "TAEW2.2 checkpoint", "scripts/download_model.py")
            if settings.tiny_decoders
            else None
        ),
        turbo_lora=(
            _require_file(root / TURBO_LORA, "Turbo-LoRA", "scripts/download_model.py")
            if settings.turbo_lora
            else None
        ),
    )