fffiloni commited on
Commit
0ba33c3
·
verified ·
1 Parent(s): 9888f7b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +883 -270
app.py CHANGED
@@ -1,375 +1,988 @@
1
- import gradio as gr
 
2
  import os
3
- import sys
4
  import shutil
5
- import hashlib
6
  import subprocess
 
 
 
7
  from pathlib import Path
8
- from typing import Tuple, Optional
9
 
 
10
  import spaces
11
  from huggingface_hub import snapshot_download
12
 
13
- BUILD_ID = "relit-live-zerogpu-simple-gradio-v2-transformers-patch"
 
14
 
15
  ROOT = Path(__file__).resolve().parent
16
  DEMO_ROOT = ROOT / "datasets" / "demos"
17
  ENV_ROOT = ROOT / "datasets" / "envs"
18
- CHECKPOINT_PATH = ROOT / "checkpoints" / "model_frame25_480_832.ckpt"
19
  WAN_DIR = ROOT / "models" / "Wan-AI" / "Wan2.1-T2V-1.3B"
20
- RUNTIME_ROOT = ROOT / "runtime_simple_demo"
 
 
 
 
 
 
21
  JOBS_ROOT = RUNTIME_ROOT / "jobs"
22
  OUTPUTS_ROOT = RUNTIME_ROOT / "outputs"
 
23
 
 
24
  VIDEO_EXTS = {".mp4", ".mov", ".avi", ".mkv"}
25
- IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".bmp", ".webp"}
26
 
27
- for p in [RUNTIME_ROOT, JOBS_ROOT, OUTPUTS_ROOT]:
28
- p.mkdir(parents=True, exist_ok=True)
29
-
30
-
31
- def list_demo_samples():
32
- if not DEMO_ROOT.exists():
33
- return []
34
- return sorted([p.name for p in DEMO_ROOT.iterdir() if p.is_dir()])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
 
37
- def list_envs():
38
- if not ENV_ROOT.exists():
39
- return []
40
- return sorted([p.name for p in ENV_ROOT.iterdir() if p.is_dir()])
41
 
42
 
43
- def safe_rmtree(path: Path):
44
  if path.exists():
45
  shutil.rmtree(path, ignore_errors=True)
46
 
47
 
48
- def hash_key(*items) -> str:
49
- raw = "||".join(map(str, items))
50
- return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
51
 
52
 
53
- def tail_text(text: str, max_chars: int = 16000) -> str:
54
- if not text:
55
- return ""
56
- if len(text) <= max_chars:
57
- return text
58
- return text[-max_chars:]
 
 
59
 
 
 
 
 
 
60
 
61
- def patch_transformers_compat() -> list[str]:
62
- """Patch the local DiffSynth import that breaks with modern Transformers.
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- The repo imports PretrainedConfig from transformers.modeling_utils. In current
65
- Transformers, PretrainedConfig lives in transformers.configuration_utils / public API.
66
- We patch the source file and also write sitecustomize.py as a subprocess fallback.
67
- """
68
- logs = []
69
 
70
- bad = "from transformers.modeling_utils import PretrainedConfig, PreTrainedModel"
71
- good = "from transformers.configuration_utils import PretrainedConfig\nfrom transformers.modeling_utils import PreTrainedModel"
72
-
73
- patched_files = []
74
- diffsynth_dir = ROOT / "diffsynth"
75
- if diffsynth_dir.exists():
76
- for py_file in diffsynth_dir.rglob("*.py"):
77
- try:
78
- text = py_file.read_text(encoding="utf-8")
79
- except Exception:
80
- continue
81
- if bad in text:
82
- py_file.write_text(text.replace(bad, good), encoding="utf-8")
83
- patched_files.append(str(py_file.relative_to(ROOT)))
84
-
85
- if patched_files:
86
- logs.append("Patched DiffSynth Transformers import in:\n- " + "\n- ".join(patched_files))
87
- else:
88
- logs.append("DiffSynth Transformers import patch: no direct source replacement needed or already patched.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
- # Fallback for any remaining legacy import in subprocesses.
91
- sitecustomize = ROOT / "sitecustomize.py"
92
- marker = "# relit-live transformers compatibility patch"
93
- snippet = f'''{marker}\ntry:\n import transformers.modeling_utils as _relit_mu\n from transformers.configuration_utils import PretrainedConfig as _RelitPretrainedConfig\n if not hasattr(_relit_mu, "PretrainedConfig"):\n _relit_mu.PretrainedConfig = _RelitPretrainedConfig\nexcept Exception:\n pass\n'''
94
  try:
95
- existing = sitecustomize.read_text(encoding="utf-8") if sitecustomize.exists() else ""
96
- if marker not in existing:
97
- sitecustomize.write_text(snippet + "\n" + existing, encoding="utf-8")
98
- logs.append(f"Wrote subprocess fallback: {sitecustomize.relative_to(ROOT)}")
99
- else:
100
- logs.append("Subprocess fallback sitecustomize.py already present.")
101
- except Exception as e:
102
- logs.append(f"Warning: failed to write sitecustomize.py fallback: {repr(e)}")
103
 
104
- return logs
 
105
 
 
106
 
107
- def make_env() -> dict:
108
- env = os.environ.copy()
109
- env["PYTHONPATH"] = str(ROOT) + os.pathsep + env.get("PYTHONPATH", "")
110
- env.setdefault("TRANSFORMERS_NO_ADVISORY_WARNINGS", "1")
111
- env.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
112
- return env
113
 
 
 
 
 
114
 
115
- def run_preflight_import() -> str:
116
- code = r'''
117
- import sys
118
- print("python", sys.version)
119
- import torch
 
 
 
 
 
120
  print("torch", torch.__version__)
121
  print("torch_cuda", torch.version.cuda)
122
  print("cuda_available", torch.cuda.is_available())
123
  print("device", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU")
124
- print("capability", torch.cuda.get_device_capability(0) if torch.cuda.is_available() else "N/A")
125
  from transformers.modeling_utils import PretrainedConfig, PreTrainedModel
126
  print("legacy_transformers_import_ok", PretrainedConfig.__name__, PreTrainedModel.__name__)
127
  from diffsynth import ModelManager, WanVideoRelitlivePipeline
128
  print("diffsynth_import_ok", ModelManager.__name__, WanVideoRelitlivePipeline.__name__)
129
- '''
 
 
130
  proc = subprocess.run(
131
  [sys.executable, "-c", code],
132
  cwd=str(ROOT),
133
- env=make_env(),
134
  capture_output=True,
135
  text=True,
136
  )
137
- return (
138
- f"Relit/DiffSynth preflight exit code: {proc.returncode}\n"
139
- f"STDOUT:\n{tail_text(proc.stdout)}\n"
140
- f"STDERR:\n{tail_text(proc.stderr)}"
141
- )
142
 
143
 
144
- def ensure_runtime_assets() -> Tuple[bool, str]:
145
- logs = [f"Build: {BUILD_ID}", "Mode: README relit_inference.py only. Cosmos is not used."]
146
- logs.extend(patch_transformers_compat())
147
- token = os.getenv("HF_TOKEN", None)
 
 
 
148
 
149
- try:
150
- if CHECKPOINT_PATH.exists():
151
- logs.append("Relit-LiVE 25-frame checkpoint already present.")
152
- else:
153
- logs.append("Downloading Relit-LiVE 25-frame checkpoint...")
154
- snapshot_download(
155
- repo_id="weiqingXiao/Relit-LiVE",
156
- local_dir=str(ROOT),
157
- allow_patterns=["checkpoints/model_frame25_480_832.ckpt"],
158
- token=token,
159
- )
160
- if not CHECKPOINT_PATH.exists():
161
- return False, "\n".join(logs + ["❌ Failed to fetch model_frame25_480_832.ckpt"])
162
- logs.append("Relit-LiVE 25-frame checkpoint ready.")
163
-
164
- if WAN_DIR.exists() and any(WAN_DIR.rglob("*")):
165
- logs.append("Wan2.1 base model already present.")
166
- else:
167
- logs.append("Downloading Wan2.1 base model...")
168
- WAN_DIR.mkdir(parents=True, exist_ok=True)
169
- snapshot_download(
170
- repo_id="Wan-AI/Wan2.1-T2V-1.3B",
171
- local_dir=str(WAN_DIR),
172
- token=token,
173
- )
174
- logs.append("Wan2.1 base model ready.")
175
 
176
- if not (ROOT / "relit_inference.py").exists():
177
- return False, "\n".join(logs + ["❌ relit_inference.py not found at repo root"])
178
- if not DEMO_ROOT.exists():
179
- return False, "\n".join(logs + [f"❌ Missing demo root: {DEMO_ROOT}"])
180
- if not ENV_ROOT.exists():
181
- return False, "\n".join(logs + [f"❌ Missing env root: {ENV_ROOT}"])
182
 
183
- samples = list_demo_samples()
184
- envs = list_envs()
185
- logs.append(f"Found {len(samples)} demo sample(s).")
186
- logs.append(f"Found {len(envs)} environment(s).")
187
- if not samples:
188
- return False, "\n".join(logs + ["❌ No demo samples found in datasets/demos"])
189
- if not envs:
190
- return False, "\n".join(logs + ["❌ No environments found in datasets/envs"])
191
-
192
- # Do an import preflight at startup so dependency errors appear before inference.
193
- logs.append(run_preflight_import())
194
-
195
- return True, "\n".join(logs)
196
- except Exception as e:
197
- logs.append(f"❌ Runtime asset preparation failed: {repr(e)}")
198
- return False, "\n".join(logs)
199
 
 
 
 
200
 
201
- STARTUP_OK, STARTUP_LOG = ensure_runtime_assets()
202
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
- def detect_output(output_dir: Path) -> Tuple[Optional[Path], Optional[str]]:
205
- if not output_dir.exists():
206
- return None, None
207
- files = [p for p in output_dir.rglob("*") if p.is_file()]
208
- if not files:
209
- return None, None
210
- video_files = sorted([p for p in files if p.suffix.lower() in VIDEO_EXTS])
211
- if video_files:
212
- return video_files[0], "video"
213
- image_files = sorted([p for p in files if p.suffix.lower() in IMAGE_EXTS])
214
- if image_files:
215
- return image_files[0], "image"
216
- return None, None
217
-
218
-
219
- def build_command(dataset_path: Path, env_path: Path, output_dir: Path, num_frames: int, steps: int, cfg_scale: float):
220
- return [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  sys.executable,
222
  str(ROOT / "relit_inference.py"),
223
- "--dataset_path", str(dataset_path),
224
- "--ckpt_path", str(CHECKPOINT_PATH),
225
- "--output_dir", str(output_dir),
226
- "--cfg_scale", str(cfg_scale),
227
- "--height", "480",
228
- "--width", "832",
229
- "--num_frames", str(num_frames),
 
 
 
 
 
 
 
 
 
230
  "--padding_resolution",
231
  "--use_ref_image",
232
- "--env_map_path", str(env_path),
233
- "--frame_interval", "1",
234
- "--num_inference_steps", str(steps),
235
- "--quality", "10",
 
 
 
 
 
 
 
 
236
  ]
237
 
 
238
 
239
- def make_job_dataset(sample_name: str, job_dataset_root: Path) -> Path:
240
- src = DEMO_ROOT / sample_name
241
- if not src.exists():
242
- raise FileNotFoundError(f"Sample not found: {src}")
243
- safe_rmtree(job_dataset_root)
244
- job_dataset_root.mkdir(parents=True, exist_ok=True)
245
- shutil.copytree(src, job_dataset_root / sample_name)
246
- return job_dataset_root
247
 
 
248
 
249
- @spaces.GPU(duration=600, size="xlarge")
250
- def run_relit_cli(sample_name: str, env_name: str, num_frames: int, steps: int, cfg_scale: float):
251
- # Re-apply the patch inside the worker process. Some ZeroGPU workers are separate processes.
252
- local_logs = []
253
- local_logs.extend(patch_transformers_compat())
254
- logs = [STARTUP_LOG, "\n".join(local_logs)]
255
 
256
- if not STARTUP_OK:
257
- logs.append("❌ Startup preparation failed. Aborting inference.")
258
- return None, None, "\n\n".join(logs)
259
 
260
- if not sample_name:
261
- return None, None, "\n\n".join(logs + ["❌ No sample selected."])
262
- if not env_name:
263
- return None, None, "\n\n".join(logs + ["❌ No environment selected."])
264
 
265
- env_path = ENV_ROOT / env_name
266
- if not env_path.exists():
267
- return None, None, "\n\n".join(logs + [f"❌ Environment not found: {env_path}"])
268
 
269
- key = hash_key(sample_name, env_name, num_frames, steps, cfg_scale, BUILD_ID)
270
- logs.append(f"Cache key: {key}")
271
 
272
- output_dir = OUTPUTS_ROOT / key
273
- existing_output, existing_kind = detect_output(output_dir)
274
- if existing_output is not None:
275
- logs.append("✅ Returning cached result.")
276
- return (None, str(existing_output), "\n\n".join(logs)) if existing_kind == "video" else (str(existing_output), None, "\n\n".join(logs))
277
 
278
- job_root = JOBS_ROOT / key
279
- dataset_root = job_root / "dataset"
280
- safe_rmtree(job_root)
281
- safe_rmtree(output_dir)
282
- job_root.mkdir(parents=True, exist_ok=True)
283
- output_dir.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
  try:
286
- make_job_dataset(sample_name, dataset_root)
287
- except Exception as e:
288
- return None, None, "\n\n".join(logs + [f"❌ Failed to prepare job dataset: {repr(e)}"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
 
290
- cmd = build_command(dataset_root, env_path, output_dir, int(num_frames), int(steps), float(cfg_scale))
291
- logs.append("Command:\n" + " ".join(cmd))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
- preflight = run_preflight_import()
294
- logs.append(preflight)
295
- if "Relit/DiffSynth preflight exit code: 0" not in preflight:
296
- return None, None, "❌ Dependency preflight failed.\n\n" + "\n\n".join(logs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
- proc = subprocess.run(
299
- cmd,
300
- cwd=str(ROOT),
301
- env=make_env(),
302
- capture_output=True,
303
- text=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  )
305
 
306
- logs.append(f"relit_inference.py exit code: {proc.returncode}")
307
- logs.append("STDOUT tail:\n" + tail_text(proc.stdout))
308
- logs.append("STDERR tail:\n" + tail_text(proc.stderr))
 
 
309
 
310
- if proc.returncode != 0:
311
- return None, None, "❌ Inference failed.\n\n" + "\n\n".join(logs)
 
 
312
 
313
- output_path, output_kind = detect_output(output_dir)
314
- if output_path is None:
315
- logs.append(f"❌ Inference finished but no output file found under {output_dir}")
316
- return None, None, "❌ Inference failed.\n\n" + "\n\n".join(logs)
 
 
 
317
 
318
- logs.append(f"✅ Output detected: {output_path}")
319
- return (None, str(output_path), "\n\n".join(logs)) if output_kind == "video" else (str(output_path), None, "\n\n".join(logs))
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
322
- def run_demo(sample_name, env_name, num_frames, steps, cfg_scale):
323
- try:
324
- image_path, video_path, log = run_relit_cli(sample_name, env_name, int(num_frames), int(steps), float(cfg_scale))
325
- return (
326
- gr.update(value=image_path, visible=image_path is not None),
327
- gr.update(value=video_path, visible=video_path is not None),
328
- log,
329
- )
330
- except Exception as e:
331
- return (
332
- gr.update(value=None, visible=False),
333
- gr.update(value=None, visible=False),
334
- f"❌ Gradio wrapper failed.\n\n{STARTUP_LOG}\n\n{repr(e)}",
335
- )
 
 
 
 
336
 
337
 
338
  SAMPLES = list_demo_samples()
339
  ENVS = list_envs()
 
 
 
340
 
341
- DESCRIPTION = f"""
342
- # Relit-LiVE ZeroGPU Demo
343
- **Build:** `{BUILD_ID}`
344
 
345
- This Space runs the simple README path with `relit_inference.py` on curated samples from the cloned repo.
 
 
 
 
 
 
 
 
 
 
346
 
347
- - Uses `datasets/demos/`
348
- - Uses `datasets/envs/`
349
- - Uses Relit-LiVE + Wan2.1
350
- - Does not use Cosmos
351
- - Does not support arbitrary uploads
 
 
352
  """
 
353
 
354
- with gr.Blocks(title="Relit-LiVE ZeroGPU Demo") as demo:
355
- gr.Markdown(DESCRIPTION)
356
  with gr.Row():
357
  with gr.Column(scale=1):
358
- sample_name = gr.Dropdown(label="Demo sample", choices=SAMPLES, value=SAMPLES[0] if SAMPLES else None)
359
- env_name = gr.Dropdown(label="Environment", choices=ENVS, value=ENVS[0] if ENVS else None)
360
- num_frames = gr.Radio(label="Number of frames", choices=[1, 9, 17, 25], value=1)
361
- steps = gr.Slider(label="Inference steps", minimum=4, maximum=30, step=1, value=8)
362
- cfg_scale = gr.Slider(label="CFG scale", minimum=0.5, maximum=3.0, step=0.1, value=1.0)
363
- run_btn = gr.Button("Run relighting", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  with gr.Column(scale=1):
365
- result_image = gr.Image(label="Result image", visible=False)
366
- result_video = gr.Video(label="Result video", visible=False)
367
- logs = gr.Textbox(label="Logs", value=STARTUP_LOG, lines=30, max_lines=50, autoscroll=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  run_btn.click(
369
- fn=run_demo,
370
- inputs=[sample_name, env_name, num_frames, steps, cfg_scale],
371
- outputs=[result_image, result_video, logs],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  )
373
 
 
374
  if __name__ == "__main__":
375
- demo.queue(default_concurrency_limit=1).launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)), show_error=True)
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import hashlib
3
  import os
 
4
  import shutil
 
5
  import subprocess
6
+ import sys
7
+ import tempfile
8
+ import zipfile
9
  from pathlib import Path
10
+ from typing import Any, Dict, Optional, Tuple
11
 
12
+ import gradio as gr
13
  import spaces
14
  from huggingface_hub import snapshot_download
15
 
16
+
17
+ BUILD_ID = "relit-live-readme-gradio-v4"
18
 
19
  ROOT = Path(__file__).resolve().parent
20
  DEMO_ROOT = ROOT / "datasets" / "demos"
21
  ENV_ROOT = ROOT / "datasets" / "envs"
22
+
23
  WAN_DIR = ROOT / "models" / "Wan-AI" / "Wan2.1-T2V-1.3B"
24
+ CHECKPOINTS = {
25
+ "model_frame25_480_832.ckpt": ROOT / "checkpoints" / "model_frame25_480_832.ckpt",
26
+ "model_frame57_480_832.ckpt": ROOT / "checkpoints" / "model_frame57_480_832.ckpt",
27
+ "model_frame1_1024_1472.ckpt": ROOT / "checkpoints" / "model_frame1_1024_1472.ckpt",
28
+ }
29
+
30
+ RUNTIME_ROOT = ROOT / "runtime_readme_gradio"
31
  JOBS_ROOT = RUNTIME_ROOT / "jobs"
32
  OUTPUTS_ROOT = RUNTIME_ROOT / "outputs"
33
+ UPLOADS_ROOT = RUNTIME_ROOT / "uploads"
34
 
35
+ IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".bmp", ".webp", ".exr"}
36
  VIDEO_EXTS = {".mp4", ".mov", ".avi", ".mkv"}
 
37
 
38
+ REQUIRED_SAMPLE_DIRS = ["images_4", "Base Color", "depth", "normal"]
39
+ OPTIONAL_SAMPLE_DIRS = ["Metallic", "Roughness"]
40
+ REQUIRED_ENV_FILES = [
41
+ "ldr_video_fix_first_frame.mp4",
42
+ "hdr_log_video_fix_first_frame.mp4",
43
+ "env_dir_video_fix_first_frame.mp4",
44
+ ]
45
+
46
+ PRESETS: Dict[str, Dict[str, Any]] = {
47
+ "Basic 25-frame relighting": {
48
+ "checkpoint": "model_frame25_480_832.ckpt",
49
+ "height": 480,
50
+ "width": 832,
51
+ "frames": 25,
52
+ "flags": [],
53
+ "output_kind": "video",
54
+ },
55
+ "25-frame rotating-light relighting": {
56
+ "checkpoint": "model_frame25_480_832.ckpt",
57
+ "height": 480,
58
+ "width": 832,
59
+ "frames": 25,
60
+ "flags": ["--use_rotate_light"],
61
+ "output_kind": "video",
62
+ },
63
+ "Fixed-frame relighting, width-axis light rotation": {
64
+ "checkpoint": "model_frame25_480_832.ckpt",
65
+ "height": 480,
66
+ "width": 832,
67
+ "frames": 25,
68
+ "flags": ["--use_fixed_frame_and_w_rotate_light"],
69
+ "output_kind": "video",
70
+ },
71
+ "Fixed-frame relighting, height-axis light rotation": {
72
+ "checkpoint": "model_frame25_480_832.ckpt",
73
+ "height": 480,
74
+ "width": 832,
75
+ "frames": 25,
76
+ "flags": ["--use_fixed_frame_and_h_rotate_light"],
77
+ "output_kind": "video",
78
+ },
79
+ "57-frame video relighting": {
80
+ "checkpoint": "model_frame57_480_832.ckpt",
81
+ "height": 480,
82
+ "width": 832,
83
+ "frames": 57,
84
+ "flags": [],
85
+ "output_kind": "video",
86
+ },
87
+ "Single-frame high-resolution relighting": {
88
+ "checkpoint": "model_frame1_1024_1472.ckpt",
89
+ "height": 1024,
90
+ "width": 1472,
91
+ "frames": 1,
92
+ "flags": [],
93
+ "output_kind": "image",
94
+ },
95
+ }
96
+
97
+
98
+ for folder in [RUNTIME_ROOT, JOBS_ROOT, OUTPUTS_ROOT, UPLOADS_ROOT]:
99
+ folder.mkdir(parents=True, exist_ok=True)
100
+
101
+
102
+ def tail_text(text: str, max_chars: int = 20000) -> str:
103
+ text = text or ""
104
+ if len(text) <= max_chars:
105
+ return text
106
+ return text[-max_chars:]
107
 
108
 
109
+ def hash_key(*items: Any) -> str:
110
+ raw = "||".join(map(str, items))
111
+ return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
 
112
 
113
 
114
+ def safe_rmtree(path: Path) -> None:
115
  if path.exists():
116
  shutil.rmtree(path, ignore_errors=True)
117
 
118
 
119
+ def run_cmd(cmd: list[str], cwd: Path = ROOT) -> Tuple[int, str, str]:
120
+ proc = subprocess.run(cmd, cwd=str(cwd), capture_output=True, text=True)
121
+ return proc.returncode, proc.stdout, proc.stderr
122
 
123
 
124
+ def patch_transformers_imports() -> list[str]:
125
+ patched = []
126
+ target = ROOT / "diffsynth" / "models" / "stepvideo_text_encoder.py"
127
+ bad = "from transformers.modeling_utils import PretrainedConfig, PreTrainedModel"
128
+ good = (
129
+ "from transformers.configuration_utils import PretrainedConfig\n"
130
+ "from transformers.modeling_utils import PreTrainedModel"
131
+ )
132
 
133
+ if target.exists():
134
+ text = target.read_text(encoding="utf-8")
135
+ if bad in text:
136
+ target.write_text(text.replace(bad, good), encoding="utf-8")
137
+ patched.append(str(target.relative_to(ROOT)))
138
 
139
+ sitecustomize = ROOT / "sitecustomize.py"
140
+ sitecustomize.write_text(
141
+ """
142
+ try:
143
+ import transformers.modeling_utils as _modeling_utils
144
+ from transformers.configuration_utils import PretrainedConfig as _PretrainedConfig
145
+ if not hasattr(_modeling_utils, "PretrainedConfig"):
146
+ _modeling_utils.PretrainedConfig = _PretrainedConfig
147
+ except Exception:
148
+ pass
149
+ """.lstrip(),
150
+ encoding="utf-8",
151
+ )
152
+ patched.append(str(sitecustomize.relative_to(ROOT)))
153
+ return patched
154
 
 
 
 
 
 
155
 
156
+ def ensure_wan_model(logs: list[str]) -> None:
157
+ if WAN_DIR.exists() and any(WAN_DIR.rglob("*")):
158
+ logs.append("Wan2.1 base model already present.")
159
+ return
160
+
161
+ logs.append("Downloading Wan2.1 base model...")
162
+ WAN_DIR.mkdir(parents=True, exist_ok=True)
163
+ snapshot_download(
164
+ repo_id="Wan-AI/Wan2.1-T2V-1.3B",
165
+ local_dir=str(WAN_DIR),
166
+ token=os.getenv("HF_TOKEN"),
167
+ )
168
+ logs.append("Wan2.1 base model ready.")
169
+
170
+
171
+ def ensure_checkpoint(checkpoint_name: str, logs: list[str]) -> Path:
172
+ checkpoint_path = CHECKPOINTS[checkpoint_name]
173
+ if checkpoint_path.exists():
174
+ logs.append(f"Checkpoint already present: {checkpoint_name}")
175
+ return checkpoint_path
176
+
177
+ logs.append(f"Downloading checkpoint: {checkpoint_name}")
178
+ snapshot_download(
179
+ repo_id="weiqingXiao/Relit-LiVE",
180
+ local_dir=str(ROOT),
181
+ allow_patterns=[f"checkpoints/{checkpoint_name}"],
182
+ token=os.getenv("HF_TOKEN"),
183
+ )
184
+ if not checkpoint_path.exists():
185
+ raise FileNotFoundError(f"Checkpoint download failed: {checkpoint_path}")
186
+
187
+ logs.append(f"Checkpoint ready: {checkpoint_name}")
188
+ return checkpoint_path
189
+
190
+
191
+ def startup_preflight() -> Tuple[bool, str]:
192
+ logs = [
193
+ f"Build: {BUILD_ID}",
194
+ "Mode: README relit_inference.py presets.",
195
+ "Full Cosmos inverse pipeline is not used in this app.",
196
+ ]
197
 
 
 
 
 
198
  try:
199
+ patched = patch_transformers_imports()
200
+ logs.append("Compatibility patch files:")
201
+ for path in patched:
202
+ logs.append(f"- {path}")
 
 
 
 
203
 
204
+ if not (ROOT / "relit_inference.py").exists():
205
+ return False, "\n".join(logs + ["Missing relit_inference.py at repo root."])
206
 
207
+ ensure_wan_model(logs)
208
 
209
+ samples = list_demo_samples()
210
+ envs = list_envs()
211
+ logs.append(f"Found {len(samples)} repo demo sample(s).")
212
+ logs.append(f"Found {len(envs)} repo environment(s).")
 
 
213
 
214
+ code, out, err = run_python_preflight()
215
+ logs.append(f"Python/DiffSynth preflight exit code: {code}")
216
+ logs.append("STDOUT:\n" + tail_text(out, 6000))
217
+ logs.append("STDERR:\n" + tail_text(err, 6000))
218
 
219
+ return code == 0, "\n".join(logs)
220
+ except Exception as exc:
221
+ logs.append(f"Startup preflight failed: {repr(exc)}")
222
+ return False, "\n".join(logs)
223
+
224
+
225
+ def run_python_preflight() -> Tuple[int, str, str]:
226
+ code = """
227
+ import sys, torch
228
+ print("python", sys.version.replace("\\n", " "))
229
  print("torch", torch.__version__)
230
  print("torch_cuda", torch.version.cuda)
231
  print("cuda_available", torch.cuda.is_available())
232
  print("device", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU")
 
233
  from transformers.modeling_utils import PretrainedConfig, PreTrainedModel
234
  print("legacy_transformers_import_ok", PretrainedConfig.__name__, PreTrainedModel.__name__)
235
  from diffsynth import ModelManager, WanVideoRelitlivePipeline
236
  print("diffsynth_import_ok", ModelManager.__name__, WanVideoRelitlivePipeline.__name__)
237
+ """
238
+ env = os.environ.copy()
239
+ env["PYTHONPATH"] = str(ROOT) + os.pathsep + env.get("PYTHONPATH", "")
240
  proc = subprocess.run(
241
  [sys.executable, "-c", code],
242
  cwd=str(ROOT),
243
+ env=env,
244
  capture_output=True,
245
  text=True,
246
  )
247
+ return proc.returncode, proc.stdout, proc.stderr
 
 
 
 
248
 
249
 
250
+ def has_images(path: Path) -> bool:
251
+ if not path.exists() or not path.is_dir():
252
+ return False
253
+ for ext in IMAGE_EXTS:
254
+ if any(path.glob(f"*{ext}")):
255
+ return True
256
+ return False
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
+ def is_valid_sample_dir(path: Path) -> Tuple[bool, list[str]]:
260
+ missing = []
261
+ for name in REQUIRED_SAMPLE_DIRS:
262
+ if not has_images(path / name):
263
+ missing.append(name)
264
+ return not missing, missing
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
+ def is_valid_env_dir(path: Path) -> Tuple[bool, list[str]]:
268
+ missing = [name for name in REQUIRED_ENV_FILES if not (path / name).exists()]
269
+ return not missing, missing
270
 
 
271
 
272
+ def list_demo_samples() -> list[str]:
273
+ if not DEMO_ROOT.exists():
274
+ return []
275
+ samples = []
276
+ for path in sorted(DEMO_ROOT.iterdir()):
277
+ if not path.is_dir():
278
+ continue
279
+ valid, _ = is_valid_sample_dir(path)
280
+ if valid:
281
+ samples.append(path.name)
282
+ return samples
283
 
284
+
285
+ def list_envs() -> list[str]:
286
+ if not ENV_ROOT.exists():
287
+ return []
288
+ envs = []
289
+ for path in sorted(ENV_ROOT.iterdir()):
290
+ if not path.is_dir():
291
+ continue
292
+ valid, _ = is_valid_env_dir(path)
293
+ if valid:
294
+ envs.append(path.name)
295
+ return envs
296
+
297
+
298
+ def first_image(path: Path) -> Optional[str]:
299
+ if not path.exists():
300
+ return None
301
+ files = []
302
+ for ext in IMAGE_EXTS:
303
+ files.extend(path.glob(f"*{ext}"))
304
+ files = sorted(files)
305
+ return str(files[0]) if files else None
306
+
307
+
308
+ def sample_preview(sample_name: Optional[str]) -> Optional[str]:
309
+ if not sample_name:
310
+ return None
311
+ return first_image(DEMO_ROOT / sample_name / "images_4")
312
+
313
+
314
+ def env_preview(env_name: Optional[str]) -> Optional[str]:
315
+ if not env_name:
316
+ return None
317
+ path = ENV_ROOT / env_name / "ldr_video_fix_first_frame.mp4"
318
+ return str(path) if path.exists() else None
319
+
320
+
321
+ def normalize_zip_dataset(extracted_root: Path, dataset_root: Path) -> Tuple[Path, str]:
322
+ candidates = [p for p in extracted_root.iterdir() if p.is_dir()]
323
+
324
+ valid_root, _ = is_valid_sample_dir(extracted_root)
325
+ if valid_root:
326
+ sample_dir = dataset_root / "uploaded_sample"
327
+ shutil.copytree(extracted_root, sample_dir)
328
+ return dataset_root, "ZIP root is one sample. Wrapped as uploaded_sample."
329
+
330
+ valid_samples = []
331
+ for candidate in candidates:
332
+ valid, _ = is_valid_sample_dir(candidate)
333
+ if valid:
334
+ valid_samples.append(candidate)
335
+
336
+ if not valid_samples:
337
+ required = ", ".join(REQUIRED_SAMPLE_DIRS)
338
+ raise ValueError(
339
+ "No valid Relit-LiVE sample found in ZIP. "
340
+ f"Expected a sample directory containing: {required}."
341
+ )
342
+
343
+ for sample in valid_samples:
344
+ shutil.copytree(sample, dataset_root / sample.name)
345
+
346
+ return dataset_root, f"Loaded {len(valid_samples)} sample(s) from ZIP."
347
+
348
+
349
+ def normalize_zip_env(extracted_root: Path, env_root: Path) -> Tuple[Path, str]:
350
+ valid_root, _ = is_valid_env_dir(extracted_root)
351
+ if valid_root:
352
+ shutil.copytree(extracted_root, env_root)
353
+ return env_root, "ZIP root is a valid environment directory."
354
+
355
+ for candidate in extracted_root.iterdir():
356
+ if not candidate.is_dir():
357
+ continue
358
+ valid, _ = is_valid_env_dir(candidate)
359
+ if valid:
360
+ shutil.copytree(candidate, env_root)
361
+ return env_root, f"Using environment directory from ZIP: {candidate.name}"
362
+
363
+ raise ValueError(
364
+ "No valid environment directory found in ZIP. "
365
+ f"Expected: {', '.join(REQUIRED_ENV_FILES)}."
366
+ )
367
+
368
+
369
+ def extract_zip_to(zip_file: Any, target_root: Path) -> Path:
370
+ safe_rmtree(target_root)
371
+ target_root.mkdir(parents=True, exist_ok=True)
372
+
373
+ zip_path = Path(zip_file.name if hasattr(zip_file, "name") else zip_file)
374
+ with zipfile.ZipFile(zip_path, "r") as archive:
375
+ for member in archive.infolist():
376
+ member_path = target_root / member.filename
377
+ if not str(member_path.resolve()).startswith(str(target_root.resolve())):
378
+ raise ValueError("Unsafe path found in ZIP archive.")
379
+ archive.extractall(target_root)
380
+
381
+ return target_root
382
+
383
+
384
+ def prepare_dataset(
385
+ source_mode: str,
386
+ sample_name: Optional[str],
387
+ dataset_zip: Optional[Any],
388
+ dataset_root: Path,
389
+ extract_root: Path,
390
+ ) -> Tuple[Path, str]:
391
+ safe_rmtree(dataset_root)
392
+ dataset_root.mkdir(parents=True, exist_ok=True)
393
+
394
+ if source_mode == "Repo demo sample":
395
+ if not sample_name:
396
+ raise ValueError("Select a repo demo sample.")
397
+ src = DEMO_ROOT / sample_name
398
+ valid, missing = is_valid_sample_dir(src)
399
+ if not valid:
400
+ raise ValueError(f"Invalid repo sample. Missing: {', '.join(missing)}")
401
+ shutil.copytree(src, dataset_root / sample_name)
402
+ return dataset_root, f"Using repo sample: {sample_name}"
403
+
404
+ if source_mode == "Prepared dataset ZIP":
405
+ if dataset_zip is None:
406
+ raise ValueError("Upload a prepared Relit-LiVE dataset ZIP.")
407
+ extracted = extract_zip_to(dataset_zip, extract_root)
408
+ dataset_path, msg = normalize_zip_dataset(extracted, dataset_root)
409
+ return dataset_path, msg
410
+
411
+ raise ValueError("Raw image/video upload is not supported by relit_inference.py alone.")
412
+
413
+
414
+ def prepare_env(
415
+ env_mode: str,
416
+ env_name: Optional[str],
417
+ env_zip: Optional[Any],
418
+ env_root: Path,
419
+ extract_root: Path,
420
+ ) -> Tuple[Path, str]:
421
+ safe_rmtree(env_root)
422
+
423
+ if env_mode == "Repo environment":
424
+ if not env_name:
425
+ raise ValueError("Select a repo environment.")
426
+ path = ENV_ROOT / env_name
427
+ valid, missing = is_valid_env_dir(path)
428
+ if not valid:
429
+ raise ValueError(f"Invalid environment. Missing: {', '.join(missing)}")
430
+ return path, f"Using repo environment: {env_name}"
431
+
432
+ if env_mode == "Custom environment ZIP":
433
+ if env_zip is None:
434
+ raise ValueError("Upload a prepared environment ZIP.")
435
+ extracted = extract_zip_to(env_zip, extract_root)
436
+ path, msg = normalize_zip_env(extracted, env_root)
437
+ return path, msg
438
+
439
+ raise ValueError("Invalid environment mode.")
440
+
441
+
442
+ def build_command(
443
+ preset_name: str,
444
+ dataset_path: Path,
445
+ env_path: Path,
446
+ output_dir: Path,
447
+ output_path: Path,
448
+ steps: int,
449
+ cfg_scale: float,
450
+ quality: int,
451
+ wo_ref_weight: float,
452
+ drop_mr: bool,
453
+ use_multi_ref: bool,
454
+ ) -> list[str]:
455
+ preset = PRESETS[preset_name]
456
+ checkpoint_path = CHECKPOINTS[preset["checkpoint"]]
457
+
458
+ cmd = [
459
  sys.executable,
460
  str(ROOT / "relit_inference.py"),
461
+ "--dataset_path",
462
+ str(dataset_path),
463
+ "--ckpt_path",
464
+ str(checkpoint_path),
465
+ "--output_dir",
466
+ str(output_dir),
467
+ "--output_path",
468
+ str(output_path),
469
+ "--cfg_scale",
470
+ str(cfg_scale),
471
+ "--height",
472
+ str(preset["height"]),
473
+ "--width",
474
+ str(preset["width"]),
475
+ "--num_frames",
476
+ str(preset["frames"]),
477
  "--padding_resolution",
478
  "--use_ref_image",
479
+ "--env_map_path",
480
+ str(env_path),
481
+ "--frame_interval",
482
+ "1",
483
+ "--num_inference_steps",
484
+ str(steps),
485
+ "--quality",
486
+ str(quality),
487
+ "--wo_ref_weight",
488
+ str(wo_ref_weight),
489
+ "--dataloader_num_workers",
490
+ "0",
491
  ]
492
 
493
+ cmd.extend(preset["flags"])
494
 
495
+ if drop_mr:
496
+ cmd.append("--drop_mr")
497
+ if use_multi_ref:
498
+ cmd.append("--use_muti_ref_image")
 
 
 
 
499
 
500
+ return cmd
501
 
 
 
 
 
 
 
502
 
503
+ def find_diagnostic(output_dir: Path, output_path: Path, kind: str) -> Optional[str]:
504
+ if not output_dir.exists():
505
+ return None
506
 
507
+ if kind == "image":
508
+ candidates = sorted(p for p in output_dir.rglob("*.png") if p != output_path and "_render" not in p.name)
509
+ else:
510
+ candidates = sorted(p for p in output_dir.rglob("*.mp4") if p != output_path and "_video" not in p.name)
511
 
512
+ return str(candidates[0]) if candidates else None
 
 
513
 
 
 
514
 
515
+ STARTUP_OK, STARTUP_LOG = startup_preflight()
 
 
 
 
516
 
517
+
518
+ @spaces.GPU(duration=3600, size="xlarge")
519
+ def run_inference(
520
+ preset_name: str,
521
+ source_mode: str,
522
+ sample_name: Optional[str],
523
+ dataset_zip: Optional[Any],
524
+ env_mode: str,
525
+ env_name: Optional[str],
526
+ env_zip: Optional[Any],
527
+ steps: int,
528
+ cfg_scale: float,
529
+ quality: int,
530
+ wo_ref_weight: float,
531
+ drop_mr: bool,
532
+ use_multi_ref: bool,
533
+ ) -> Dict[str, Any]:
534
+ logs = [STARTUP_LOG]
535
 
536
  try:
537
+ if not STARTUP_OK:
538
+ raise RuntimeError("Startup preflight failed. See startup log above.")
539
+
540
+ if preset_name not in PRESETS:
541
+ raise ValueError("Select a README inference preset.")
542
+
543
+ preset = PRESETS[preset_name]
544
+ kind = preset["output_kind"]
545
+
546
+ patch_transformers_imports()
547
+ ensure_checkpoint(preset["checkpoint"], logs)
548
+
549
+ key = hash_key(
550
+ BUILD_ID,
551
+ preset_name,
552
+ source_mode,
553
+ sample_name or "uploaded",
554
+ env_mode,
555
+ env_name or "custom-env",
556
+ steps,
557
+ cfg_scale,
558
+ quality,
559
+ wo_ref_weight,
560
+ drop_mr,
561
+ use_multi_ref,
562
+ )
563
 
564
+ job_root = JOBS_ROOT / key
565
+ dataset_root = job_root / "dataset"
566
+ dataset_extract_root = job_root / "dataset_extract"
567
+ env_root = job_root / "custom_env"
568
+ env_extract_root = job_root / "env_extract"
569
+ output_dir = OUTPUTS_ROOT / key
570
+ output_path = output_dir / ("result.png" if kind == "image" else "result.mp4")
571
+
572
+ logs.append(f"Cache key: {key}")
573
+ logs.append(f"Preset: {preset_name}")
574
+ logs.append(f"Expected pure output: {output_path}")
575
+
576
+ if output_path.exists() and output_path.stat().st_size > 0:
577
+ logs.append("Returning cached result.")
578
+ diagnostic = find_diagnostic(output_dir, output_path, kind)
579
+ return {
580
+ "ok": True,
581
+ "kind": kind,
582
+ "output": str(output_path),
583
+ "diagnostic": diagnostic,
584
+ "log": "\n\n".join(logs),
585
+ }
586
+
587
+ safe_rmtree(job_root)
588
+ safe_rmtree(output_dir)
589
+ job_root.mkdir(parents=True, exist_ok=True)
590
+ output_dir.mkdir(parents=True, exist_ok=True)
591
+
592
+ dataset_path, dataset_msg = prepare_dataset(
593
+ source_mode=source_mode,
594
+ sample_name=sample_name,
595
+ dataset_zip=dataset_zip,
596
+ dataset_root=dataset_root,
597
+ extract_root=dataset_extract_root,
598
+ )
599
+ env_path, env_msg = prepare_env(
600
+ env_mode=env_mode,
601
+ env_name=env_name,
602
+ env_zip=env_zip,
603
+ env_root=env_root,
604
+ extract_root=env_extract_root,
605
+ )
606
 
607
+ logs.append(dataset_msg)
608
+ logs.append(env_msg)
609
+
610
+ code, out, err = run_python_preflight()
611
+ logs.append(f"GPU preflight exit code: {code}")
612
+ logs.append("STDOUT:\n" + tail_text(out, 8000))
613
+ logs.append("STDERR:\n" + tail_text(err, 8000))
614
+ if code != 0:
615
+ raise RuntimeError("GPU preflight failed.")
616
+
617
+ cmd = build_command(
618
+ preset_name=preset_name,
619
+ dataset_path=dataset_path,
620
+ env_path=env_path,
621
+ output_dir=output_dir,
622
+ output_path=output_path,
623
+ steps=int(steps),
624
+ cfg_scale=float(cfg_scale),
625
+ quality=int(quality),
626
+ wo_ref_weight=float(wo_ref_weight),
627
+ drop_mr=bool(drop_mr),
628
+ use_multi_ref=bool(use_multi_ref),
629
+ )
630
 
631
+ logs.append("Command:")
632
+ logs.append(" ".join(cmd))
633
+
634
+ env = os.environ.copy()
635
+ env["PYTHONPATH"] = str(ROOT) + os.pathsep + env.get("PYTHONPATH", "")
636
+ proc = subprocess.run(
637
+ cmd,
638
+ cwd=str(ROOT),
639
+ env=env,
640
+ capture_output=True,
641
+ text=True,
642
+ )
643
+
644
+ logs.append(f"relit_inference.py exit code: {proc.returncode}")
645
+ logs.append("STDOUT tail:\n" + tail_text(proc.stdout))
646
+ logs.append("STDERR tail:\n" + tail_text(proc.stderr))
647
+
648
+ if proc.returncode != 0:
649
+ raise RuntimeError("relit_inference.py failed.")
650
+
651
+ if not output_path.exists() or output_path.stat().st_size == 0:
652
+ logs.append("Files under output_dir:")
653
+ for path in sorted(output_dir.rglob("*")):
654
+ if path.is_file():
655
+ logs.append(f"- {path.relative_to(output_dir)} ({path.stat().st_size} bytes)")
656
+ raise RuntimeError("Explicit --output_path was not created.")
657
+
658
+ diagnostic = find_diagnostic(output_dir, output_path, kind)
659
+ logs.append(f"Pure output ready: {output_path}")
660
+ if diagnostic:
661
+ logs.append(f"Diagnostic sheet ready: {diagnostic}")
662
+
663
+ return {
664
+ "ok": True,
665
+ "kind": kind,
666
+ "output": str(output_path),
667
+ "diagnostic": diagnostic,
668
+ "log": "\n\n".join(logs),
669
+ }
670
+
671
+ except Exception as exc:
672
+ logs.append(f"Error: {repr(exc)}")
673
+ return {
674
+ "ok": False,
675
+ "kind": "video",
676
+ "output": None,
677
+ "diagnostic": None,
678
+ "log": "\n\n".join(logs),
679
+ }
680
+
681
+
682
+ def run_ui(
683
+ preset_name,
684
+ source_mode,
685
+ sample_name,
686
+ dataset_zip,
687
+ env_mode,
688
+ env_name,
689
+ env_zip,
690
+ steps,
691
+ cfg_scale,
692
+ quality,
693
+ wo_ref_weight,
694
+ drop_mr,
695
+ use_multi_ref,
696
+ ):
697
+ result = run_inference(
698
+ preset_name,
699
+ source_mode,
700
+ sample_name,
701
+ dataset_zip,
702
+ env_mode,
703
+ env_name,
704
+ env_zip,
705
+ steps,
706
+ cfg_scale,
707
+ quality,
708
+ wo_ref_weight,
709
+ drop_mr,
710
+ use_multi_ref,
711
  )
712
 
713
+ output = result.get("output")
714
+ diagnostic = result.get("diagnostic")
715
+ kind = result.get("kind")
716
+ ok = result.get("ok")
717
+ log = ("OK\n\n" if ok else "FAILED\n\n") + result.get("log", "")
718
 
719
+ image_value = output if kind == "image" else None
720
+ video_value = output if kind == "video" else None
721
+ diag_image = diagnostic if diagnostic and diagnostic.endswith(".png") else None
722
+ diag_video = diagnostic if diagnostic and diagnostic.endswith(".mp4") else None
723
 
724
+ return (
725
+ gr.update(value=image_value, visible=image_value is not None),
726
+ gr.update(value=video_value, visible=video_value is not None),
727
+ gr.update(value=diag_image, visible=diag_image is not None),
728
+ gr.update(value=diag_video, visible=diag_video is not None),
729
+ log,
730
+ )
731
 
 
 
732
 
733
+ def update_preset_info(preset_name: str):
734
+ preset = PRESETS[preset_name]
735
+ ext = "PNG" if preset["output_kind"] == "image" else "MP4"
736
+ flags = " ".join(preset["flags"]) if preset["flags"] else "none"
737
+ text = (
738
+ f"Checkpoint: {preset['checkpoint']}\n"
739
+ f"Resolution: {preset['height']}x{preset['width']}\n"
740
+ f"Frames: {preset['frames']}\n"
741
+ f"README flags: {flags}\n"
742
+ f"Pure output: {ext}"
743
+ )
744
+ return text
745
+
746
+
747
+ def update_sample_preview(sample_name):
748
+ value = sample_preview(sample_name)
749
+ return gr.update(value=value, visible=value is not None)
750
 
751
+
752
+ def update_env_preview(env_name):
753
+ value = env_preview(env_name)
754
+ return gr.update(value=value, visible=value is not None)
755
+
756
+
757
+ def update_source_mode(source_mode):
758
+ return (
759
+ gr.update(visible=source_mode == "Repo demo sample"),
760
+ gr.update(visible=source_mode == "Prepared dataset ZIP"),
761
+ )
762
+
763
+
764
+ def update_env_mode(env_mode):
765
+ return (
766
+ gr.update(visible=env_mode == "Repo environment"),
767
+ gr.update(visible=env_mode == "Custom environment ZIP"),
768
+ )
769
 
770
 
771
  SAMPLES = list_demo_samples()
772
  ENVS = list_envs()
773
+ DEFAULT_SAMPLE = SAMPLES[0] if SAMPLES else None
774
+ DEFAULT_ENV = "Pink_Sunrise" if "Pink_Sunrise" in ENVS else (ENVS[0] if ENVS else None)
775
+ DEFAULT_PRESET = "Basic 25-frame relighting"
776
 
 
 
 
777
 
778
+ CSS = """
779
+ .gradio-container { max-width: 1280px !important; }
780
+ .small-note { color: #6b7280; font-size: 0.92rem; }
781
+ textarea { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace !important; }
782
+ """
783
+
784
+
785
+ with gr.Blocks(title="Relit-LiVE README Inference", css=CSS) as demo:
786
+ gr.Markdown(
787
+ f"""
788
+ # Relit-LiVE README Inference
789
 
790
+ Build `{BUILD_ID}`. This app exposes the inference cases documented in the repository README:
791
+ basic 25-frame, rotating light, fixed-frame width/height light rotation, 57-frame video,
792
+ and single-frame high-resolution inference.
793
+
794
+ Raw image/video upload is intentionally not treated as a valid direct input. `relit_inference.py`
795
+ requires a prepared Relit-LiVE sample with RGB frames plus base color, depth, normal, and optional
796
+ metallic/roughness maps. Upload a prepared dataset ZIP for custom content.
797
  """
798
+ )
799
 
 
 
800
  with gr.Row():
801
  with gr.Column(scale=1):
802
+ gr.Markdown("## Inference preset")
803
+ preset = gr.Dropdown(
804
+ label="README case",
805
+ choices=list(PRESETS.keys()),
806
+ value=DEFAULT_PRESET,
807
+ )
808
+ preset_info = gr.Textbox(
809
+ label="Preset details",
810
+ value=update_preset_info(DEFAULT_PRESET),
811
+ lines=6,
812
+ interactive=False,
813
+ )
814
+
815
+ gr.Markdown("## Input sample")
816
+ source_mode = gr.Radio(
817
+ label="Source",
818
+ choices=["Repo demo sample", "Prepared dataset ZIP", "Raw image/video upload"],
819
+ value="Repo demo sample",
820
+ )
821
+ with gr.Group(visible=True) as repo_sample_group:
822
+ sample_name = gr.Dropdown(
823
+ label="Repo demo sample",
824
+ choices=SAMPLES,
825
+ value=DEFAULT_SAMPLE,
826
+ )
827
+ sample_img = gr.Image(
828
+ label="Sample preview",
829
+ value=sample_preview(DEFAULT_SAMPLE) if DEFAULT_SAMPLE else None,
830
+ visible=DEFAULT_SAMPLE is not None,
831
+ height=220,
832
+ )
833
+ with gr.Group(visible=False) as dataset_zip_group:
834
+ dataset_zip = gr.File(
835
+ label="Prepared Relit-LiVE dataset ZIP",
836
+ file_types=[".zip"],
837
+ )
838
+ gr.Markdown(
839
+ """
840
+ Expected ZIP layout: either a sample folder at the ZIP root, or one or more sample folders.
841
+ Each sample must contain `images_4`, `Base Color`, `depth`, and `normal`.
842
+ `Metallic` and `Roughness` are recommended.
843
+ """,
844
+ elem_classes=["small-note"],
845
+ )
846
+ raw_note = gr.Markdown(
847
+ """
848
+ Raw video upload needs inverse rendering first. Use the full Cosmos/Module1 pipeline, or upload a
849
+ prepared dataset ZIP containing the required maps.
850
+ """,
851
+ visible=False,
852
+ elem_classes=["small-note"],
853
+ )
854
+
855
+ gr.Markdown("## Environment")
856
+ env_mode = gr.Radio(
857
+ label="Environment source",
858
+ choices=["Repo environment", "Custom environment ZIP"],
859
+ value="Repo environment",
860
+ )
861
+ with gr.Group(visible=True) as repo_env_group:
862
+ env_name = gr.Dropdown(
863
+ label="Repo environment",
864
+ choices=ENVS,
865
+ value=DEFAULT_ENV,
866
+ )
867
+ env_video = gr.Video(
868
+ label="Environment preview",
869
+ value=env_preview(DEFAULT_ENV) if DEFAULT_ENV else None,
870
+ visible=DEFAULT_ENV is not None,
871
+ height=170,
872
+ )
873
+ with gr.Group(visible=False) as custom_env_group:
874
+ env_zip = gr.File(
875
+ label="Custom environment ZIP",
876
+ file_types=[".zip"],
877
+ )
878
+ gr.Markdown(
879
+ "Expected files: `ldr_video_fix_first_frame.mp4`, "
880
+ "`hdr_log_video_fix_first_frame.mp4`, `env_dir_video_fix_first_frame.mp4`.",
881
+ elem_classes=["small-note"],
882
+ )
883
+
884
  with gr.Column(scale=1):
885
+ gr.Markdown("## Controls")
886
+ steps = gr.Slider(
887
+ label="Inference steps",
888
+ minimum=8,
889
+ maximum=50,
890
+ step=1,
891
+ value=50,
892
+ )
893
+ cfg_scale = gr.Slider(
894
+ label="CFG scale",
895
+ minimum=0.5,
896
+ maximum=5.0,
897
+ step=0.1,
898
+ value=1.0,
899
+ )
900
+ quality = gr.Slider(
901
+ label="MP4 quality",
902
+ minimum=5,
903
+ maximum=10,
904
+ step=1,
905
+ value=10,
906
+ )
907
+ wo_ref_weight = gr.Slider(
908
+ label="Without-reference branch weight",
909
+ minimum=0.0,
910
+ maximum=5.0,
911
+ step=0.1,
912
+ value=0.0,
913
+ )
914
+ drop_mr = gr.Checkbox(
915
+ label="Drop metallic/roughness conditioning",
916
+ value=False,
917
+ )
918
+ use_multi_ref = gr.Checkbox(
919
+ label="Use multi-reference image mode",
920
+ value=False,
921
+ )
922
+ run_btn = gr.Button("Run inference", variant="primary", size="lg")
923
+
924
+ gr.Markdown("## Pure result")
925
+ result_image = gr.Image(label="Result image", visible=False, height=430)
926
+ result_video = gr.Video(label="Result video", visible=False, height=430)
927
+
928
+ gr.Markdown("## Diagnostic sheet")
929
+ diag_image = gr.Image(label="Diagnostic image", visible=False, height=260)
930
+ diag_video = gr.Video(label="Diagnostic video", visible=False, height=260)
931
+
932
+ logs = gr.Textbox(
933
+ label="Logs",
934
+ value=STARTUP_LOG,
935
+ lines=26,
936
+ max_lines=60,
937
+ autoscroll=True,
938
+ )
939
+
940
+ preset.change(update_preset_info, inputs=[preset], outputs=[preset_info])
941
+ sample_name.change(update_sample_preview, inputs=[sample_name], outputs=[sample_img])
942
+ env_name.change(update_env_preview, inputs=[env_name], outputs=[env_video])
943
+ source_mode.change(
944
+ update_source_mode,
945
+ inputs=[source_mode],
946
+ outputs=[repo_sample_group, dataset_zip_group],
947
+ ).then(
948
+ lambda mode: gr.update(visible=mode == "Raw image/video upload"),
949
+ inputs=[source_mode],
950
+ outputs=[raw_note],
951
+ )
952
+ env_mode.change(
953
+ update_env_mode,
954
+ inputs=[env_mode],
955
+ outputs=[repo_env_group, custom_env_group],
956
+ )
957
+
958
  run_btn.click(
959
+ run_ui,
960
+ inputs=[
961
+ preset,
962
+ source_mode,
963
+ sample_name,
964
+ dataset_zip,
965
+ env_mode,
966
+ env_name,
967
+ env_zip,
968
+ steps,
969
+ cfg_scale,
970
+ quality,
971
+ wo_ref_weight,
972
+ drop_mr,
973
+ use_multi_ref,
974
+ ],
975
+ outputs=[result_image, result_video, diag_image, diag_video, logs],
976
  )
977
 
978
+
979
  if __name__ == "__main__":
980
+ parser = argparse.ArgumentParser()
981
+ parser.add_argument("--server-name", default="0.0.0.0")
982
+ parser.add_argument("--server-port", type=int, default=int(os.getenv("PORT", "7860")))
983
+ args = parser.parse_args()
984
+ demo.queue(default_concurrency_limit=1, max_size=8).launch(
985
+ server_name=args.server_name,
986
+ server_port=args.server_port,
987
+ show_error=True,
988
+ )