fffiloni commited on
Commit
ca4b227
·
verified ·
1 Parent(s): bf0fa2b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +332 -1271
app.py CHANGED
@@ -1,1326 +1,387 @@
1
- import spaces
2
-
3
- APP_BUILD_ID = "relit-live-zerogpu-te-shim-2026-06-29-1908"
4
- import gradio as gr
5
  import os
 
6
  import subprocess
7
  import sys
8
- import uuid
9
- import shutil
10
- import signal
11
- import threading
12
  from pathlib import Path
13
- from PIL import Image
14
- from datetime import datetime
15
- import socket
16
-
17
-
18
- # -------------------------- Core Configuration --------------------------
19
- BASE_UPLOAD_DIR = "./datasets/gradio_data/upload_data"
20
- BASE_RESULT_DIR = "./datasets/gradio_data/results"
21
- DEMO_IMAGES_DIR = "./datasets/gradio_data/assets/images_demo"
22
- BASH_SCRIPT_PATH = "./tools/full_inference_modules_gradio.sh"
23
-
24
- SUPPORTED_IMAGE_FORMATS = [".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".hdr"]
25
- SUPPORTED_VIDEO_FORMATS = [".mp4", ".mov", ".avi", ".mkv"]
26
- SUPPORTED_HDR_FORMATS = [".hdr", ".exr", ".jpg", ".png"]
27
-
28
- ASSETS_ENV_DIR = "./datasets/gradio_data/assets/envs_demo"
29
- BUILTIN_ENV_OPTIONS = []
30
- if os.path.exists(ASSETS_ENV_DIR):
31
- for ext in SUPPORTED_HDR_FORMATS:
32
- BUILTIN_ENV_OPTIONS.extend([f.stem for f in Path(ASSETS_ENV_DIR).glob(f"*{ext}")])
33
- BUILTIN_ENV_OPTIONS = sorted(set(BUILTIN_ENV_OPTIONS))
34
- DEFAULT_ENV = "Pink_Sunrise" if "Pink_Sunrise" in BUILTIN_ENV_OPTIONS else (BUILTIN_ENV_OPTIONS[0] if BUILTIN_ENV_OPTIONS else None)
35
-
36
- MODULE1_RESULT_TYPES = {
37
- "base_color": {"name": "Base Color", "dir": "Base Color", "glob_pattern": "frame_*"},
38
- "normal": {"name": "Normal Map", "dir": "normal", "glob_pattern": "frame_*"},
39
- "roughness": {"name": "Roughness Map", "dir": "Roughness", "glob_pattern": "frame_*"},
40
- }
41
-
42
- INPUT_PREVIEW_HEIGHT = 240
43
- MODULE1_VIS_HEIGHT = 180
44
- MODULE2_VIS_HEIGHT = 200
45
- MODULE3_VIS_HEIGHT = 400
46
- DEMO_IMAGE_HEIGHT = 200
47
-
48
- RUNNING_PROCESSES = {}
49
- RUNNING_PROCESSES_LOCK = threading.RLock()
50
-
51
-
52
- def patch_bash_script_for_hf_spaces():
53
- """Disable hard-coded conda activation lines that do not exist in HF Spaces."""
54
- script = Path(BASH_SCRIPT_PATH)
55
- if not script.exists():
56
- return
57
- try:
58
- text = script.read_text(encoding="utf-8")
59
- except Exception as exc:
60
- print(f"Warning: could not read {script}: {exc}")
61
- return
62
-
63
- original = text
64
- patched_lines = []
65
- changed = False
66
- for line in text.splitlines():
67
- stripped = line.strip()
68
- if "/home/user/miniconda3/bin/activate" in stripped or stripped.startswith("conda activate"):
69
- patched_lines.append("# Disabled by app.py for Hugging Face Spaces: " + line)
70
- patched_lines.append("true")
71
- changed = True
72
- else:
73
- patched_lines.append(line)
74
-
75
- if changed:
76
- try:
77
- script.write_text("\n".join(patched_lines) + "\n", encoding="utf-8")
78
- print(f"Patched {script}: disabled hard-coded conda activation.")
79
- except Exception as exc:
80
- print(f"Warning: could not patch {script}: {exc}")
81
- RUNTIME_SETUP_LOCK = threading.RLock()
82
- RUNTIME_SETUP_DONE = False
83
- RUNTIME_SETUP_ATTEMPTED = False
84
- RUNTIME_SETUP_LOG = ""
85
-
86
- RELIT_CHECKPOINT_REPO = os.environ.get("RELIT_CHECKPOINT_REPO", "weiqingXiao/Relit-LiVE")
87
- WAN_REPO = os.environ.get("RELIT_WAN_REPO", "Wan-AI/Wan2.1-T2V-1.3B")
88
- COSMOS_REPO = os.environ.get("RELIT_COSMOS_REPO", "https://github.com/nv-tlabs/cosmos-transfer1-diffusion-renderer.git")
89
-
90
- DOWNLOAD_RELIT_CHECKPOINTS = os.environ.get("RELIT_DOWNLOAD_CHECKPOINTS", "1") != "0"
91
- DOWNLOAD_WAN = os.environ.get("RELIT_DOWNLOAD_WAN", "1") != "0"
92
- CLONE_COSMOS = os.environ.get("RELIT_CLONE_COSMOS", "1") != "0"
93
- STARTUP_RUNTIME_SETUP = os.environ.get("RELIT_STARTUP_SETUP", "1") != "0"
94
- INSTALL_COSMOS_DEPS = os.environ.get("RELIT_INSTALL_COSMOS_DEPS", "1") != "0"
95
- INSTALL_TRANSFORMER_ENGINE = os.environ.get("RELIT_INSTALL_TRANSFORMER_ENGINE", "0") == "1"
96
- INSTALL_NVDIFFRAST = os.environ.get("RELIT_INSTALL_NVDIFFRAST", "0") == "1"
97
- DOWNLOAD_COSMOS_CHECKPOINTS = os.environ.get("RELIT_DOWNLOAD_COSMOS_CHECKPOINTS", "1") != "0"
98
- STRICT_COSMOS_DEPS = os.environ.get("RELIT_STRICT_COSMOS_DEPS", "0") == "1"
99
- USE_TRANSFORMER_ENGINE_SHIM = os.environ.get("RELIT_USE_TRANSFORMER_ENGINE_SHIM", "1") != "0"
100
-
101
-
102
- # -------------------------- Utility Functions --------------------------
103
- def get_server_ip():
104
- try:
105
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
106
- s.connect(("8.8.8.8", 80))
107
- local_ip = s.getsockname()[0]
108
- s.close()
109
- return local_ip
110
- except Exception:
111
- return "Unknown IP"
112
-
113
-
114
- def generate_test_id(flag):
115
- timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
116
- random_str = uuid.uuid4().hex[:8]
117
- return f"{flag}_{timestamp}_{random_str}"
118
-
119
-
120
- def clear_test_dir(test_id):
121
- upload_dir = os.path.join(BASE_UPLOAD_DIR, test_id)
122
- result_dir = os.path.join(BASE_RESULT_DIR, test_id)
123
- if os.path.exists(upload_dir):
124
- shutil.rmtree(upload_dir)
125
- if os.path.exists(result_dir):
126
- shutil.rmtree(result_dir)
127
- os.makedirs(upload_dir, exist_ok=True)
128
- os.makedirs(result_dir, exist_ok=True)
129
-
130
 
131
- def convert_to_jpg(file_path, save_path):
132
- try:
133
- with Image.open(file_path) as img:
134
- if img.mode in ("RGBA", "P"):
135
- img = img.convert("RGB")
136
- img.save(save_path, "JPEG", quality=95)
137
- return True
138
- except Exception as e:
139
- print(f"Image format conversion failed: {e}")
140
- return False
141
-
142
-
143
- def save_uploaded_file(file, test_id, is_env=False):
144
- if file is None:
145
- return None, None, None
146
-
147
- upload_dir = os.path.join(BASE_UPLOAD_DIR, test_id)
148
- os.makedirs(upload_dir, exist_ok=True)
149
-
150
- original_path = file.name if hasattr(file, "name") else file
151
- file_suffix = Path(original_path).suffix.lower()
152
-
153
- if is_env:
154
- env_filename = f"env_{uuid.uuid4().hex[:4]}"
155
- save_path = os.path.join(upload_dir, f"{env_filename}{file_suffix}")
156
- shutil.copy(original_path, save_path)
157
- return file_suffix, save_path, env_filename
158
-
159
- base_filename = test_id
160
- if file_suffix in [".jpg", ".jpeg", ".png"]:
161
- save_path = os.path.join(upload_dir, f"{base_filename}.jpg")
162
- if not convert_to_jpg(original_path, save_path):
163
- shutil.copy(original_path, save_path)
164
- return ".jpg", save_path, None
165
-
166
- save_path = os.path.join(upload_dir, f"{base_filename}{file_suffix}")
167
- shutil.copy(original_path, save_path)
168
- return file_suffix, save_path, None
169
-
170
-
171
- def clear_module_results(test_id, module_num, test_env=""):
172
- try:
173
- if module_num == 1:
174
- for path in [
175
- os.path.join(BASE_RESULT_DIR, test_id, "frames"),
176
- os.path.join(BASE_RESULT_DIR, test_id, "frames_delighting"),
177
- os.path.join(BASE_RESULT_DIR, test_id, "rego"),
178
- ]:
179
- if os.path.exists(path):
180
- shutil.rmtree(path)
181
- elif module_num == 2:
182
- path = os.path.join(BASE_RESULT_DIR, test_id, "envs", test_env)
183
- if os.path.exists(path):
184
- shutil.rmtree(path)
185
- return True
186
- except Exception as e:
187
- print(f"Failed to clear Module {module_num} results: {e}")
188
- return False
189
-
190
-
191
- def find_first_file(root_dir, glob_pattern, formats):
192
- if not os.path.exists(root_dir):
193
- return None
194
- matches = []
195
- for fmt in formats:
196
- matches.extend(Path(root_dir).rglob(f"{glob_pattern}{fmt}"))
197
- return str(sorted(matches)[0]) if matches else None
198
-
199
-
200
- def find_first_keyword_file(root_dir, keywords):
201
- if not os.path.exists(root_dir):
202
- return None
203
- matches = []
204
- for fmt in SUPPORTED_IMAGE_FORMATS:
205
- matches.extend(Path(root_dir).rglob(f"*{fmt}"))
206
- for path in sorted(matches):
207
- path_lower = str(path).lower()
208
- if any(keyword in path_lower for keyword in keywords):
209
- return str(path)
210
- return None
211
-
212
-
213
- def get_result_files(test_id, params):
214
- input_ext = ".jpg" if params["test_type"] == 1 else ".mp4"
215
- results = {
216
- "input_file": os.path.join(BASE_UPLOAD_DIR, test_id, f"{test_id}{input_ext}"),
217
- "input_file_type": "image" if params["test_type"] == 1 else "video",
218
- "module1": {"base_color": None, "normal": None, "roughness": None, "status": "Not Executed", "exists": False, "missing_types": []},
219
- "module2": {"ldr_video": None, "env_dir_video": None, "status": "Not Executed", "exists": False},
220
- "module3": {"final": None, "status": "Not Executed", "exists": False, "file_type": None},
221
- "test_id": test_id,
222
- "test_env": params["test_env"],
223
- }
224
-
225
- module1_root_candidates = [
226
- os.path.join(BASE_RESULT_DIR, test_id, "rego", f"{test_id}.0"),
227
- os.path.join(BASE_RESULT_DIR, test_id, "frames_delighting", "gbuffer_frames", test_id),
228
- os.path.join(BASE_RESULT_DIR, test_id, "frames_delighting", "gbuffer_frames"),
229
- os.path.join(BASE_RESULT_DIR, test_id, "frames"),
230
- ]
231
- existing_module1_roots = [path for path in module1_root_candidates if os.path.exists(path)]
232
-
233
- if params["infer_1"] == 1 or existing_module1_roots:
234
- if existing_module1_roots:
235
- for result_type, config in MODULE1_RESULT_TYPES.items():
236
- found_file = None
237
- for root_dir in existing_module1_roots:
238
- exact_dir = os.path.join(root_dir, config["dir"])
239
- found_file = find_first_file(exact_dir, config["glob_pattern"], SUPPORTED_IMAGE_FORMATS)
240
- if found_file:
241
- break
242
-
243
- if not found_file:
244
- keywords = {
245
- "base_color": ["base", "albedo", "color"],
246
- "normal": ["normal"],
247
- "roughness": ["rough", "roughness"],
248
- }[result_type]
249
- for root_dir in existing_module1_roots:
250
- found_file = find_first_keyword_file(root_dir, keywords)
251
- if found_file:
252
- break
253
-
254
- if found_file:
255
- results["module1"][result_type] = found_file
256
- else:
257
- results["module1"]["missing_types"].append(config["name"])
258
-
259
- has_result = any(results["module1"][k] for k in ["base_color", "normal", "roughness"])
260
- results["module1"]["exists"] = has_result
261
- if not results["module1"]["missing_types"]:
262
- results["module1"]["status"] = "Execution Successful (All 3 results generated)"
263
- elif has_result:
264
- missing = ", ".join(results["module1"]["missing_types"])
265
- results["module1"]["status"] = f"Execution Successful (Partial results missing: {missing})"
266
- else:
267
- results["module1"]["status"] = "Execution Failed: No results generated"
268
- else:
269
- checked = "\n".join(f"- {path}" for path in module1_root_candidates)
270
- results["module1"]["status"] = f"Execution Failed: Module 1 output directory not found. Checked:\n{checked}"
271
-
272
- env_dir = os.path.join(BASE_RESULT_DIR, test_id, "envs", params["test_env"])
273
- if params["infer_2"] == 1 or os.path.exists(env_dir):
274
- if os.path.exists(env_dir):
275
- for fmt in SUPPORTED_VIDEO_FORMATS:
276
- candidate = os.path.join(env_dir, f"ldr_video_fix_first_frame{fmt}")
277
- if os.path.exists(candidate):
278
- results["module2"]["ldr_video"] = candidate
279
- break
280
- for fmt in SUPPORTED_VIDEO_FORMATS:
281
- candidate = os.path.join(env_dir, f"env_dir_video_fix_first_frame{fmt}")
282
- if os.path.exists(candidate):
283
- results["module2"]["env_dir_video"] = candidate
284
- break
285
- results["module2"]["exists"] = bool(results["module2"]["ldr_video"] or results["module2"]["env_dir_video"])
286
- if results["module2"]["ldr_video"]:
287
- results["module2"]["status"] = "Execution Successful (LDR video generated)"
288
- elif results["module2"]["env_dir_video"]:
289
- results["module2"]["status"] = "Execution Successful (Only environment direction video generated)"
290
- else:
291
- results["module2"]["status"] = "Execution Failed: No video files generated"
292
- else:
293
- results["module2"]["status"] = "Execution Failed: Directory not found"
294
-
295
- final_files = list(Path(BASE_RESULT_DIR, test_id).glob(f"{test_id}.{params['test_env']}.*"))
296
- if params["infer_3"] == 1 or final_files:
297
- if final_files:
298
- final_file = str(sorted(final_files)[0])
299
- suffix = Path(final_file).suffix.lower()
300
- results["module3"]["final"] = final_file
301
- results["module3"]["exists"] = True
302
- if suffix in SUPPORTED_IMAGE_FORMATS:
303
- results["module3"]["file_type"] = "image"
304
- elif suffix in SUPPORTED_VIDEO_FORMATS:
305
- results["module3"]["file_type"] = "video"
306
- results["module3"]["status"] = "Execution Successful"
307
- else:
308
- results["module3"]["status"] = "Execution Failed: No image/video results generated"
309
 
310
- return results
311
 
 
 
 
 
 
 
 
 
312
 
313
- def estimate_gpu_duration(params, module_num, process_state=None):
314
- if not params:
315
- return 60
 
316
 
317
- test_type = int(params.get("test_type", 1))
318
- frames = 1 if test_type == 1 else int(params.get("frame", 25))
319
- steps = int(params.get("num_infer_steps", 20))
 
 
 
 
320
 
321
- if module_num == 1:
322
- return min(600, max(90, 90 + frames * 6))
323
- if module_num == 2:
324
- return min(600, max(90, 90 + frames * 4))
325
- if module_num == 3:
326
- return min(1200, max(120, int(120 + frames * steps * 0.7)))
327
- return 300
328
 
329
 
330
- def path_has_files(path, patterns=None):
331
- root = Path(path)
332
- if not root.exists():
333
- return False
334
- if not patterns:
335
- return any(item.is_file() for item in root.rglob("*"))
336
- for pattern in patterns:
337
- if any(root.glob(pattern)):
338
- return True
339
- return False
340
 
341
 
342
- def run_setup_command(command, cwd=None, env_extra=None):
343
- print(f"Runtime setup command: {' '.join(command)}")
344
  env = os.environ.copy()
345
- if env_extra:
346
- env.update(env_extra)
347
- completed = subprocess.run(
348
- command,
349
- cwd=cwd,
 
 
350
  env=env,
351
  text=True,
352
  stdout=subprocess.PIPE,
353
- stderr=subprocess.STDOUT,
 
354
  )
355
- output = completed.stdout or ""
356
- if completed.returncode != 0:
357
- raise RuntimeError(f"Command failed: {' '.join(command)}\n{output}")
358
- return output.strip()
359
-
360
-
361
- def pip_install(args, cwd=None):
362
- return run_setup_command([sys.executable, "-m", "pip", "install", *args], cwd=cwd)
363
-
364
-
365
- def snapshot_download_runtime(repo_id, local_dir, allow_patterns=None):
366
- try:
367
- from huggingface_hub import snapshot_download
368
- except Exception as e:
369
- raise RuntimeError(
370
- "Missing `huggingface_hub`. Add `huggingface_hub` to requirements.txt."
371
- ) from e
372
-
373
- kwargs = {
374
- "repo_id": repo_id,
375
- "local_dir": local_dir,
376
- "local_dir_use_symlinks": False,
377
- }
378
- if allow_patterns:
379
- kwargs["allow_patterns"] = allow_patterns
380
-
381
- token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
382
- if token:
383
- kwargs["token"] = token
384
-
385
- snapshot_download(**kwargs)
386
-
387
-
388
-
389
-
390
-
391
- def _write_transformer_engine_shim(package_parent):
392
- """Write a small PyTorch fallback for the subset of Transformer Engine used by Cosmos."""
393
- package_parent = Path(package_parent)
394
- shim_root = package_parent / "transformer_engine"
395
- pytorch_dir = shim_root / "pytorch"
396
- pytorch_dir.mkdir(parents=True, exist_ok=True)
397
-
398
- root_init = "from . import pytorch\n__all__ = ['pytorch']\n"
399
-
400
- pytorch_init = r'''import torch
401
- from torch import nn
402
-
403
-
404
- class RMSNorm(nn.Module):
405
- def __init__(self, hidden_size, eps=1e-6, **kwargs):
406
- super().__init__()
407
- self.weight = nn.Parameter(torch.ones(hidden_size))
408
- self.eps = eps
409
-
410
- def forward(self, x):
411
- dtype = x.dtype
412
- variance = x.to(torch.float32).pow(2).mean(dim=-1, keepdim=True)
413
- x = x * torch.rsqrt(variance + self.eps).to(dtype)
414
- return x * self.weight.to(dtype)
415
-
416
-
417
- LayerNorm = nn.LayerNorm
418
- Linear = nn.Linear
419
-
420
-
421
- class LayerNormLinear(nn.Module):
422
- def __init__(self, hidden_size, output_size, eps=1e-5, bias=True, return_layernorm_output=False, **kwargs):
423
- super().__init__()
424
- self.norm = nn.LayerNorm(hidden_size, eps=eps)
425
- self.linear = nn.Linear(hidden_size, output_size, bias=bias)
426
- self.return_layernorm_output = return_layernorm_output
427
-
428
- def forward(self, x):
429
- y = self.norm(x)
430
- out = self.linear(y)
431
- if self.return_layernorm_output:
432
- return out, y
433
- return out
434
-
435
-
436
- try:
437
- from .attention import DotProductAttention, apply_rotary_pos_emb
438
- except Exception:
439
- DotProductAttention = None
440
-
441
- def apply_rotary_pos_emb(tensor, *args, **kwargs):
442
- return tensor
443
- '''
444
-
445
- attention_code = r'''import torch
446
- from torch import nn
447
- from torch.nn import functional as F
448
-
449
 
450
- def apply_rotary_pos_emb(tensor, *args, **kwargs):
451
- # Fallback for the fused Transformer Engine RoPE helper.
452
- # Keeping identity preserves shapes and lets the ZeroGPU demo run without compiling TE.
453
- return tensor
454
 
 
 
 
455
 
456
- class DotProductAttention(nn.Module):
457
- def __init__(
458
- self,
459
- num_attention_heads=None,
460
- kv_channels=None,
461
- *args,
462
- qkv_format="bshd",
463
- attention_dropout=0.0,
464
- **kwargs,
465
- ):
466
- super().__init__()
467
- self.qkv_format = qkv_format or "bshd"
468
- self.attention_dropout = float(attention_dropout or 0.0)
469
 
470
- def _to_bhsd(self, tensor):
471
- if tensor.ndim != 4:
472
- raise ValueError(f"TE shim expected a 4D qkv tensor, got {tuple(tensor.shape)}")
473
- fmt = self.qkv_format
474
- if fmt == "bshd":
475
- return tensor.permute(0, 2, 1, 3), "bshd" # B,H,S,D
476
- if fmt == "sbhd":
477
- return tensor.permute(1, 2, 0, 3), "sbhd" # B,H,S,D
478
- # Cosmos normally uses bshd. Keep a sane fallback for similar 4D layouts.
479
- return tensor.permute(0, 2, 1, 3), "bshd"
480
 
481
- def _restore_and_flatten(self, tensor, fmt):
482
- if fmt == "bshd":
483
- tensor = tensor.permute(0, 2, 1, 3).contiguous() # B,S,H,D
484
- elif fmt == "sbhd":
485
- tensor = tensor.permute(2, 0, 1, 3).contiguous() # S,B,H,D
486
- else:
487
- raise ValueError(f"Unsupported qkv_format for TE shim: {fmt}")
488
- return tensor.reshape(*tensor.shape[:-2], tensor.shape[-2] * tensor.shape[-1])
489
 
490
- def forward(self, query_layer, key_layer, value_layer, *args, **kwargs):
491
- q, fmt = self._to_bhsd(query_layer)
492
- k, _ = self._to_bhsd(key_layer)
493
- v, _ = self._to_bhsd(value_layer)
494
- out = F.scaled_dot_product_attention(
495
- q,
496
- k,
497
- v,
498
- dropout_p=self.attention_dropout if self.training else 0.0,
499
- is_causal=False,
500
  )
501
- return self._restore_and_flatten(out, fmt)
502
- '''
503
-
504
- (shim_root / "__init__.py").write_text(root_init, encoding="utf-8")
505
- (pytorch_dir / "__init__.py").write_text(pytorch_init, encoding="utf-8")
506
- (pytorch_dir / "attention.py").write_text(attention_code, encoding="utf-8")
507
- return shim_root
508
-
509
-
510
- def ensure_transformer_engine_shim(cosmos_dir, logs):
511
- if not USE_TRANSFORMER_ENGINE_SHIM:
512
- logs.append("Transformer Engine shim disabled. Set RELIT_USE_TRANSFORMER_ENGINE_SHIM=1 to enable it.")
513
- return
514
-
515
- repo_root = Path.cwd().resolve()
516
- cosmos_dir = Path(cosmos_dir).resolve()
517
-
518
- written = []
519
- # Root copy wins because run_bash_script puts the app root first in PYTHONPATH.
520
- written.append(_write_transformer_engine_shim(repo_root))
521
- # Cosmos-local copy is a fallback when a Cosmos script mutates sys.path/cwd.
522
- if cosmos_dir.exists():
523
- written.append(_write_transformer_engine_shim(cosmos_dir))
524
-
525
- pythonpath = f"{repo_root}:{cosmos_dir}:{os.environ.get('PYTHONPATH', '')}".rstrip(":")
526
- try:
527
- run_setup_command(
528
- [
529
- sys.executable,
530
- "-c",
531
- (
532
- "import transformer_engine as te; "
533
- "from transformer_engine.pytorch.attention import DotProductAttention; "
534
- "print('TE shim import ok', te.__file__, DotProductAttention)"
535
- ),
536
- ],
537
- env_extra={"PYTHONPATH": pythonpath},
538
- )
539
- logs.append("Transformer Engine compatibility shim ready and import-tested.")
540
- except Exception as e:
541
- logs.append(f"Transformer Engine shim was written but import test failed: {e}")
542
- if STRICT_COSMOS_DEPS:
543
- raise
544
-
545
- logs.append("Transformer Engine shim locations: " + ", ".join(str(path) for path in written))
546
-
547
- def ensure_cosmos_dependencies(cosmos_dir, logs):
548
- cosmos_dir = Path(cosmos_dir)
549
- if not cosmos_dir.exists():
550
- return
551
-
552
- if not INSTALL_COSMOS_DEPS:
553
- logs.append("Cosmos dependency installation disabled.")
554
- ensure_transformer_engine_shim(cosmos_dir, logs)
555
- return
556
-
557
- requirements_marker = cosmos_dir / ".relit_requirements_installed"
558
- requirements_path = cosmos_dir / "requirements.txt"
559
-
560
- if requirements_path.exists() and not requirements_marker.exists():
561
- logs.append("Installing Cosmos Python requirements...")
562
- try:
563
- pip_install(["-r", str(requirements_path)])
564
- requirements_marker.write_text("ok\n")
565
- logs.append("Cosmos Python requirements installed.")
566
- except Exception as e:
567
- logs.append(f"Cosmos requirements install failed: {e}")
568
- logs.append("Installing minimal fallback dependency: omegaconf...")
569
- pip_install(["omegaconf"])
570
- logs.append("Minimal fallback dependency installed.")
571
- elif requirements_marker.exists():
572
- logs.append("Cosmos Python requirements already installed.")
573
  else:
574
- logs.append("No Cosmos requirements.txt found; installing omegaconf fallback...")
575
- pip_install(["omegaconf"])
576
- logs.append("OmegaConf installed.")
577
-
578
- ensure_transformer_engine_shim(cosmos_dir, logs)
579
-
580
- te_marker = cosmos_dir / ".relit_transformer_engine_installed"
581
- if INSTALL_TRANSFORMER_ENGINE and not te_marker.exists():
582
- logs.append("Installing transformer-engine[pytorch]==1.12.0...")
583
- try:
584
- pip_install(["--no-build-isolation", "transformer-engine[pytorch]==1.12.0"])
585
- te_marker.write_text("ok\n")
586
- logs.append("Transformer Engine installed.")
587
- except Exception as e:
588
- logs.append(f"Transformer Engine install failed: {e}")
589
- logs.append("Continuing setup. Set RELIT_STRICT_COSMOS_DEPS=1 to make this fatal.")
590
- if STRICT_COSMOS_DEPS:
591
- raise
592
- elif INSTALL_TRANSFORMER_ENGINE:
593
- logs.append("Transformer Engine already installed.")
594
- else:
595
- logs.append("Transformer Engine install disabled. Using shim unless RELIT_USE_TRANSFORMER_ENGINE_SHIM=0.")
596
-
597
- nvdiffrast_marker = cosmos_dir / ".relit_nvdiffrast_installed"
598
- if INSTALL_NVDIFFRAST and not nvdiffrast_marker.exists():
599
- logs.append("Installing nvdiffrast...")
600
- try:
601
- pip_install(["--no-build-isolation", "git+https://github.com/NVlabs/nvdiffrast.git"])
602
- nvdiffrast_marker.write_text("ok\n")
603
- logs.append("nvdiffrast installed.")
604
- except Exception as e:
605
- logs.append(f"nvdiffrast install failed: {e}")
606
- logs.append("Continuing setup. Set RELIT_STRICT_COSMOS_DEPS=1 to make this fatal.")
607
- if STRICT_COSMOS_DEPS:
608
- raise
609
- elif INSTALL_NVDIFFRAST:
610
- logs.append("nvdiffrast already installed.")
611
  else:
612
- logs.append("nvdiffrast install disabled. Set RELIT_INSTALL_NVDIFFRAST=1 if the renderer asks for it.")
613
-
614
-
615
- def ensure_cosmos_checkpoints(cosmos_dir, logs):
616
- if not DOWNLOAD_COSMOS_CHECKPOINTS:
617
- logs.append("Cosmos checkpoint download disabled. Set RELIT_DOWNLOAD_COSMOS_CHECKPOINTS=1 to enable it.")
618
- return
619
-
620
- cosmos_dir = Path(cosmos_dir).resolve()
621
- checkpoints_dir = cosmos_dir / "checkpoints"
622
- inverse_dir = checkpoints_dir / "Diffusion_Renderer_Inverse_Cosmos_7B"
623
- forward_dir = checkpoints_dir / "Diffusion_Renderer_Forward_Cosmos_7B"
624
-
625
- if inverse_dir.exists() and forward_dir.exists():
626
- logs.append("Cosmos DiffusionRenderer checkpoints already present.")
627
- return
628
-
629
- # Recover checkpoints that were downloaded to a nested path by an older runtime setup.
630
- nested_inverse_dirs = [
631
- path
632
- for path in cosmos_dir.rglob("Diffusion_Renderer_Inverse_Cosmos_7B")
633
- if path.is_dir() and path != inverse_dir
634
- ]
635
- nested_forward_dirs = [
636
- path
637
- for path in cosmos_dir.rglob("Diffusion_Renderer_Forward_Cosmos_7B")
638
- if path.is_dir() and path != forward_dir
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
  ]
640
- if nested_inverse_dirs or nested_forward_dirs:
641
- checkpoints_dir.mkdir(parents=True, exist_ok=True)
642
- for found, expected in [
643
- (nested_inverse_dirs[0] if nested_inverse_dirs else None, inverse_dir),
644
- (nested_forward_dirs[0] if nested_forward_dirs else None, forward_dir),
645
- ]:
646
- if found and not expected.exists():
647
- try:
648
- expected.symlink_to(found, target_is_directory=True)
649
- logs.append(f"Linked misplaced Cosmos checkpoint: {expected} -> {found}")
650
- except Exception:
651
- shutil.copytree(found, expected)
652
- logs.append(f"Copied misplaced Cosmos checkpoint: {found} -> {expected}")
653
 
654
- if inverse_dir.exists() and forward_dir.exists():
655
- logs.append("Cosmos DiffusionRenderer checkpoints recovered.")
656
- return
657
-
658
- download_script = cosmos_dir / "scripts" / "download_diffusion_renderer_checkpoints.py"
659
- if not download_script.exists():
660
- logs.append("Cosmos checkpoint download script not found; skipping checkpoint download.")
661
- return
662
-
663
- checkpoints_dir.mkdir(parents=True, exist_ok=True)
664
- logs.append(
665
- "Downloading Cosmos DiffusionRenderer checkpoints to "
666
- f"`{checkpoints_dir}`. This is large and may take a while..."
667
- )
668
- cosmos_pythonpath = str(cosmos_dir.resolve())
669
- existing_pythonpath = os.environ.get("PYTHONPATH")
670
- if existing_pythonpath:
671
- cosmos_pythonpath = f"{cosmos_pythonpath}:{existing_pythonpath}"
672
-
673
- run_setup_command(
674
- [
675
- sys.executable,
676
- str(download_script.resolve()),
677
- "--checkpoint_dir",
678
- str(checkpoints_dir.resolve()),
679
- ],
680
- cwd=str(cosmos_dir),
681
- env_extra={"PYTHONPATH": cosmos_pythonpath},
682
  )
683
- logs.append("Cosmos DiffusionRenderer checkpoints ready.")
684
-
685
-
686
- def ensure_runtime_assets():
687
- global RUNTIME_SETUP_DONE, RUNTIME_SETUP_ATTEMPTED, RUNTIME_SETUP_LOG
688
-
689
- with RUNTIME_SETUP_LOCK:
690
- RUNTIME_SETUP_ATTEMPTED = True
691
- if RUNTIME_SETUP_DONE:
692
- return True, RUNTIME_SETUP_LOG
693
-
694
- logs = []
695
-
696
- try:
697
- os.makedirs("checkpoints", exist_ok=True)
698
- os.makedirs("models/Wan-AI", exist_ok=True)
699
- os.makedirs("third_party", exist_ok=True)
700
-
701
- checkpoint_patterns = [
702
- "model_frame25_480_832.ckpt",
703
- "model_frame57_480_832.ckpt",
704
- "model_frame1_1024_1472.ckpt",
705
- ]
706
- checkpoints_ready = path_has_files("checkpoints", checkpoint_patterns)
707
- if DOWNLOAD_RELIT_CHECKPOINTS and not checkpoints_ready:
708
- logs.append(f"Downloading Relit-LiVE checkpoints from {RELIT_CHECKPOINT_REPO}...")
709
- snapshot_download_runtime(
710
- repo_id=RELIT_CHECKPOINT_REPO,
711
- local_dir=".",
712
- allow_patterns=["checkpoints/*.ckpt"],
713
- )
714
- logs.append("Relit-LiVE checkpoints ready.")
715
- elif checkpoints_ready:
716
- logs.append("Relit-LiVE checkpoints already present.")
717
- else:
718
- logs.append("Relit-LiVE checkpoint download disabled.")
719
-
720
- wan_dir = Path("models/Wan-AI/Wan2.1-T2V-1.3B")
721
- if DOWNLOAD_WAN and not path_has_files(wan_dir):
722
- logs.append(f"Downloading Wan2.1 base model from {WAN_REPO}...")
723
- snapshot_download_runtime(
724
- repo_id=WAN_REPO,
725
- local_dir=str(wan_dir),
726
- )
727
- logs.append("Wan2.1 base model ready.")
728
- elif path_has_files(wan_dir):
729
- logs.append("Wan2.1 base model already present.")
730
- else:
731
- logs.append("Wan2.1 download disabled.")
732
-
733
- cosmos_dir = Path("third_party/cosmos-transfer1-diffusion-renderer")
734
- if CLONE_COSMOS and not cosmos_dir.exists():
735
- logs.append(f"Cloning Cosmos Transfer renderer from {COSMOS_REPO}...")
736
- run_setup_command(["git", "clone", "--depth", "1", COSMOS_REPO, str(cosmos_dir)])
737
- logs.append("Cosmos Transfer renderer cloned.")
738
- elif cosmos_dir.exists():
739
- logs.append("Cosmos Transfer renderer already present.")
740
- else:
741
- logs.append("Cosmos renderer clone disabled.")
742
-
743
- if cosmos_dir.exists():
744
- ensure_cosmos_dependencies(cosmos_dir, logs)
745
- ensure_cosmos_checkpoints(cosmos_dir, logs)
746
-
747
- RUNTIME_SETUP_DONE = True
748
- RUNTIME_SETUP_LOG = "\n".join(logs)
749
- print(RUNTIME_SETUP_LOG)
750
- return True, RUNTIME_SETUP_LOG
751
-
752
- except Exception as e:
753
- logs.append(f"Runtime setup failed: {e}")
754
- RUNTIME_SETUP_LOG = "\n".join(logs)
755
- print(RUNTIME_SETUP_LOG)
756
- return False, RUNTIME_SETUP_LOG
757
-
758
-
759
- def get_runtime_setup_status_for_execution():
760
- if STARTUP_RUNTIME_SETUP and RUNTIME_SETUP_ATTEMPTED and not RUNTIME_SETUP_DONE:
761
- return False, RUNTIME_SETUP_LOG
762
- return ensure_runtime_assets()
763
-
764
-
765
- def get_missing_module_prerequisites(module_num):
766
- missing = []
767
-
768
- if module_num == 1:
769
- renderer_dir = Path("third_party/cosmos-transfer1-diffusion-renderer")
770
- if not renderer_dir.exists():
771
- missing.append(
772
- "Missing `third_party/cosmos-transfer1-diffusion-renderer`. "
773
- "Module 1 needs the Cosmos Transfer renderer to generate gbuffer frames."
774
- )
775
- inverse_checkpoint_dir = renderer_dir / "checkpoints" / "Diffusion_Renderer_Inverse_Cosmos_7B"
776
- has_inverse_checkpoint = inverse_checkpoint_dir.exists() or any(
777
- path.is_dir() for path in renderer_dir.rglob("Diffusion_Renderer_Inverse_Cosmos_7B")
778
- )
779
- if renderer_dir.exists() and not has_inverse_checkpoint:
780
- missing.append(
781
- "Missing Cosmos inverse renderer checkpoints under "
782
- "`third_party/cosmos-transfer1-diffusion-renderer/checkpoints/Diffusion_Renderer_Inverse_Cosmos_7B`. "
783
- "Set `RELIT_DOWNLOAD_COSMOS_CHECKPOINTS=1` or provide the checkpoints manually."
784
- )
785
-
786
- if module_num in [2, 3]:
787
- wan_dir = Path("models/Wan-AI/Wan2.1-T2V-1.3B")
788
- if not wan_dir.exists():
789
- missing.append("Missing `models/Wan-AI/Wan2.1-T2V-1.3B`.")
790
-
791
- if module_num == 3:
792
- checkpoints = [
793
- Path("checkpoints/model_frame25_480_832.ckpt"),
794
- Path("checkpoints/model_frame57_480_832.ckpt"),
795
- Path("checkpoints/model_frame1_1024_1472.ckpt"),
796
- ]
797
- if not any(path.exists() for path in checkpoints):
798
- missing.append("Missing Relit-LiVE checkpoints under `checkpoints/`.")
799
-
800
- return missing
801
-
802
-
803
- @spaces.GPU(duration=estimate_gpu_duration)
804
- def run_bash_script(params, module_num, process_state):
805
- patch_bash_script_for_hf_spaces()
806
- test_id = params["test_id"]
807
- env = os.environ.copy()
808
- repo_path = os.getcwd()
809
- cosmos_path = str(Path("third_party/cosmos-transfer1-diffusion-renderer").resolve())
810
 
811
- # Last-mile guard: make the Transformer Engine shim visible even if the
812
- # startup setup was skipped or the Space was restarted from a stale image.
813
- if USE_TRANSFORMER_ENGINE_SHIM:
814
- try:
815
- _write_transformer_engine_shim(Path(repo_path))
816
- if Path(cosmos_path).exists():
817
- _write_transformer_engine_shim(Path(cosmos_path))
818
- print("Transformer Engine shim ensured before module subprocess.")
819
- except Exception as shim_error:
820
- print(f"Warning: failed to ensure Transformer Engine shim: {shim_error}")
821
 
822
- pythonpath_parts = [repo_path]
823
- if Path(cosmos_path).exists():
824
- pythonpath_parts.append(cosmos_path)
825
- if env.get("PYTHONPATH"):
826
- pythonpath_parts.append(env["PYTHONPATH"])
827
 
828
- env.update({
829
- "TEST_ID": test_id,
830
- "TEST_TYPE": str(params["test_type"]),
831
- "TEST_ENV": params["test_env"],
832
- "USE_OFFICE_ENV": str(params["use_office_env"]),
833
- "FRAME": str(params["frame"]),
834
- "FRAME_RATE": str(params["frame_rate"]),
835
- "INFER_1": str(params["infer_1"]),
836
- "INFER_2": str(params["infer_2"]),
837
- "INFER_3": str(params["infer_3"]),
838
- "ENV_STRENGTH": str(params["env_strength"]),
839
- "NUM_INFER_STEPS": str(params["num_infer_steps"]),
840
- "WORW": str(params["worw"]),
841
- "LIGHT_TYPE": str(params["light_type"]),
842
- "REPO_PATH": repo_path,
843
- "PYTHONPATH": ":".join(pythonpath_parts),
844
- })
845
 
 
846
  try:
847
- process = subprocess.Popen(
848
- ["bash", BASH_SCRIPT_PATH],
849
- env=env,
850
- stdout=subprocess.PIPE,
851
- stderr=subprocess.PIPE,
852
- text=True,
853
- preexec_fn=os.setsid,
854
- )
855
-
856
- with RUNNING_PROCESSES_LOCK:
857
- RUNNING_PROCESSES[module_num] = process
858
- process_state[module_num] = process.pid
859
-
860
- print(f"Module {module_num} started with PID: {process.pid}")
861
- stdout, stderr = process.communicate()
862
- combined_logs = "\n".join(part for part in [stdout, stderr] if part)
863
-
864
- with RUNNING_PROCESSES_LOCK:
865
- RUNNING_PROCESSES.pop(module_num, None)
866
- process_state.pop(module_num, None)
867
-
868
- if process.returncode == 0:
869
- print(f"Module {module_num} execution succeeded (PID: {process.pid})")
870
- print(f"Script stdout: {stdout}")
871
- if stderr:
872
- print(f"Script stderr: {stderr}")
873
- return True, test_id, "", combined_logs
874
-
875
- error_msg = f"Module {module_num} execution failed (PID: {process.pid}): {stderr}\n{stdout}"
876
- print(error_msg)
877
- return False, test_id, error_msg, ""
878
- except Exception as e:
879
- error_msg = f"Module {module_num} execution error: {e}"
880
- print(error_msg)
881
- with RUNNING_PROCESSES_LOCK:
882
- RUNNING_PROCESSES.pop(module_num, None)
883
- process_state.pop(module_num, None)
884
- return False, test_id, error_msg, ""
885
-
886
-
887
- def stop_module_execution(module_num, process_state, current_status):
888
- with RUNNING_PROCESSES_LOCK:
889
- process = RUNNING_PROCESSES.get(module_num)
890
- pid = process.pid if process else process_state.get(module_num)
891
 
892
- if not pid:
893
- return process_state, current_status + "\nNo running process found for this module"
894
 
 
895
  try:
896
- os.killpg(os.getpgid(pid), signal.SIGTERM)
897
- with RUNNING_PROCESSES_LOCK:
898
- RUNNING_PROCESSES.pop(module_num, None)
899
- process_state.pop(module_num, None)
900
- return process_state, current_status + f"\nModule {module_num} execution stopped successfully (PID: {pid})"
901
- except ProcessLookupError:
902
- with RUNNING_PROCESSES_LOCK:
903
- RUNNING_PROCESSES.pop(module_num, None)
904
- process_state.pop(module_num, None)
905
- return process_state, current_status + "\nProcess already completed"
906
- except Exception as e:
907
- return process_state, current_status + f"\nFailed to stop module: {e}"
908
-
909
-
910
- def get_demo_images():
911
- demo_images = []
912
- if os.path.exists(DEMO_IMAGES_DIR):
913
- for ext in SUPPORTED_IMAGE_FORMATS:
914
- demo_images.extend(list(Path(DEMO_IMAGES_DIR).glob(f"*{ext}")))
915
- return [str(path) for path in sorted(demo_images)]
916
-
917
-
918
- def update_carousel(index, direction, total_images):
919
- if total_images <= 0:
920
- return -1
921
- if direction == "next":
922
- return (index + 1) % total_images
923
- if direction == "prev":
924
- return (index - 1) % total_images
925
- return index
926
-
927
-
928
- def init_input_file(input_file):
929
- if input_file is None:
930
- return gr.update(value=None), gr.update(value=None), "Error: Please upload an input image/video file!", gr.update(value={})
931
-
932
- test_id = generate_test_id("demo")
933
- clear_test_dir(test_id)
934
-
935
- input_suffix, input_path, _ = save_uploaded_file(input_file, test_id, is_env=False)
936
- test_type = 1 if input_suffix in [".jpg", ".jpeg", ".png"] else 0
937
- input_file_type = "image" if test_type == 1 else "video"
938
-
939
- base_params = {
940
- "test_id": test_id,
941
- "test_type": test_type,
942
- "test_env": DEFAULT_ENV or "",
943
- "use_office_env": 1,
944
- "frame": 25 if test_type == 0 else 1,
945
- "frame_rate": 24,
946
- "infer_1": 0,
947
- "infer_2": 0,
948
- "infer_3": 0,
949
- "env_strength": 3.0,
950
- "num_infer_steps": 20,
951
- "worw": 0.0,
952
- "light_type": 0,
953
- }
954
-
955
- return (
956
- input_path if input_file_type == "image" else None,
957
- input_path if input_file_type == "video" else None,
958
- f"Input file initialized successfully!\nFile type: {input_file_type}\nEnvironment: {DEFAULT_ENV or 'not configured'}",
959
- base_params,
960
- )
961
-
962
-
963
- def select_demo_image(image_path):
964
- if not image_path or not os.path.exists(image_path):
965
- return gr.update(value=None, visible=False), gr.update(value=None, visible=False), "Error: Demo image not found!", gr.update(value={})
966
-
967
- class MockFile:
968
- def __init__(self, path):
969
- self.name = path
970
-
971
- image_preview, video_preview, status, params = init_input_file(MockFile(image_path))
972
- return (
973
- gr.update(value=image_preview, visible=image_preview is not None, height=INPUT_PREVIEW_HEIGHT),
974
- gr.update(value=video_preview, visible=video_preview is not None, height=INPUT_PREVIEW_HEIGHT),
975
- f"Selected demo image: {os.path.basename(image_path)}\n{status}",
976
- params,
977
- )
978
-
979
-
980
- def update_env_config(base_params, use_builtin_env, builtin_env_choice, env_file):
981
- if not base_params:
982
- return base_params, "Error: Please initialize input file first!"
983
-
984
- test_id = base_params["test_id"]
985
- if not use_builtin_env and env_file is not None:
986
- _, _, test_env = save_uploaded_file(env_file, test_id, is_env=True)
987
- use_office_env = 0
988
- env_type = "Custom Environment"
989
- else:
990
- test_env = builtin_env_choice
991
- use_office_env = 1
992
- env_type = f"Built-in Environment ({builtin_env_choice})"
993
-
994
- if not test_env:
995
- return base_params, "Error: Please select or upload an environment first!"
996
-
997
- base_params.update({"test_env": test_env, "use_office_env": use_office_env})
998
- return base_params, f"Environment configuration updated successfully!\nType: {env_type}"
999
-
1000
-
1001
- def update_advanced_params(base_params, frame, frame_rate, env_strength, num_infer_steps, worw, light_type):
1002
- if not base_params:
1003
- return base_params, "Error: Please initialize input file first!"
1004
-
1005
- if light_type not in [0, 1, 2]:
1006
- light_type = 0
1007
-
1008
- base_params.update({
1009
- "frame": frame if base_params["test_type"] == 0 else 1,
1010
- "frame_rate": frame_rate,
1011
- "env_strength": env_strength,
1012
- "num_infer_steps": num_infer_steps,
1013
- "worw": worw,
1014
- "light_type": light_type,
1015
- })
1016
-
1017
- desc = {
1018
- 0: "Original Scene + Static Light",
1019
- 1: "Original Scene + Dynamic Light",
1020
- 2: "Fixed First Frame + Dynamic Light",
1021
- }
1022
- return base_params, f"Advanced parameters updated successfully!\nFrame rate: {frame_rate} | Light Type: {desc[light_type]} | Inference Steps: {num_infer_steps} | Env Strength: {env_strength}"
1023
-
1024
-
1025
- def run_single_module(module_num, params, process_state, re_run=True):
1026
- if not params:
1027
- if module_num == 1:
1028
- return None, None, None, process_state, "Error: Please initialize input file first!"
1029
- if module_num == 2:
1030
- return None, None, process_state, "Error: Please initialize input file first!"
1031
- return gr.update(value=None), gr.update(value=None), process_state, "Error: Please initialize input file first!"
1032
-
1033
- if module_num in process_state:
1034
- pid = process_state[module_num]
1035
- msg = f"Module {module_num} is already running (PID: {pid}). Please stop it first."
1036
- if module_num == 1:
1037
- return None, None, None, process_state, msg
1038
- if module_num == 2:
1039
- return None, None, process_state, msg
1040
- return gr.update(value=None), gr.update(value=None), process_state, msg
1041
-
1042
- if not re_run:
1043
- results = get_result_files(params["test_id"], params)
1044
- if module_num == 1:
1045
- return results["module1"]["base_color"], results["module1"]["normal"], results["module1"]["roughness"], process_state, f"Show existing results: {results['module1']['status']}"
1046
- if module_num == 2:
1047
- return results["module2"]["ldr_video"], results["module2"]["env_dir_video"], process_state, f"Show existing results: {results['module2']['status']}"
1048
- image_result = results["module3"]["final"] if results["module3"]["file_type"] == "image" else None
1049
- video_result = results["module3"]["final"] if results["module3"]["file_type"] == "video" else None
1050
- return gr.update(value=image_result), gr.update(value=video_result), process_state, f"Show existing results: {results['module3']['status']}"
1051
-
1052
- setup_ok, setup_log = get_runtime_setup_status_for_execution()
1053
- if not setup_ok:
1054
- msg = "Runtime setup failed:\n" + setup_log
1055
- if module_num == 1:
1056
- return None, None, None, process_state, msg
1057
- if module_num == 2:
1058
- return None, None, process_state, msg
1059
- return gr.update(value=None), gr.update(value=None), process_state, msg
1060
-
1061
- missing_prerequisites = get_missing_module_prerequisites(module_num)
1062
- if missing_prerequisites:
1063
- msg = "Missing prerequisites after runtime setup:\n"
1064
- msg += "\n".join(f"- {item}" for item in missing_prerequisites)
1065
- if setup_log:
1066
- msg += "\n\nRuntime setup log:\n" + setup_log
1067
- if module_num == 1:
1068
- return None, None, None, process_state, msg
1069
- if module_num == 2:
1070
- return None, None, process_state, msg
1071
- return gr.update(value=None), gr.update(value=None), process_state, msg
1072
-
1073
- if params["test_env"] == "" and module_num in [2, 3]:
1074
- if module_num == 2:
1075
- return None, None, process_state, "Error: Please configure environment first!"
1076
- return gr.update(value=None), gr.update(value=None), process_state, "Error: Please configure environment first!"
1077
-
1078
- clear_success = clear_module_results(params["test_id"], module_num, params["test_env"])
1079
- status_msg = f"Cleared old results for Module {module_num}, starting execution..." if clear_success else f"Could not clear old results for Module {module_num}, attempting execution..."
1080
- if setup_log:
1081
- status_msg = f"Runtime setup ready:\n{setup_log}\n\n{status_msg}"
1082
-
1083
- params["infer_1"] = 1 if module_num == 1 else 0
1084
- params["infer_2"] = 1 if module_num == 2 else 0
1085
- params["infer_3"] = 1 if module_num == 3 else 0
1086
-
1087
- success, test_id, error_msg, stdout = run_bash_script(params, module_num, process_state)
1088
- results = get_result_files(test_id, params)
1089
- stdout_tail = ""
1090
- if stdout:
1091
- stdout_lines = stdout.strip().splitlines()
1092
- stdout_tail = "\nScript stdout tail:\n" + "\n".join(stdout_lines[-12:])
1093
-
1094
- if module_num == 1:
1095
- final_status = f"{status_msg}\n{results['module1']['status']}"
1096
- if error_msg:
1097
- final_status += f"\n{error_msg}"
1098
- elif success and not results["module1"]["exists"] and stdout_tail:
1099
- final_status += stdout_tail
1100
- return results["module1"]["base_color"], results["module1"]["normal"], results["module1"]["roughness"], process_state, final_status
1101
-
1102
- if module_num == 2:
1103
- final_status = f"{status_msg}\n{results['module2']['status']}"
1104
- if error_msg:
1105
- final_status += f"\n{error_msg}"
1106
- elif success and not results["module2"]["exists"] and stdout_tail:
1107
- final_status += stdout_tail
1108
- return results["module2"]["ldr_video"], results["module2"]["env_dir_video"], process_state, final_status
1109
-
1110
- final_status = f"{status_msg}\n{results['module3']['status']}"
1111
- if error_msg:
1112
- final_status += f"\n{error_msg}"
1113
- elif success and not results["module3"]["exists"] and stdout_tail:
1114
- final_status += stdout_tail
1115
- image_result = results["module3"]["final"] if results["module3"]["file_type"] == "image" else None
1116
- video_result = results["module3"]["final"] if results["module3"]["file_type"] == "video" else None
1117
- return gr.update(value=image_result), gr.update(value=video_result), process_state, final_status
1118
-
1119
-
1120
- def one_click_run_all(params, process_state, stop_flag):
1121
- stop_flag = False
1122
- if not params:
1123
- msg = "One-click run failed: Input file not initialized"
1124
- return None, None, None, None, None, gr.update(), gr.update(), process_state, msg, msg, msg, stop_flag
1125
- if params["test_env"] == "":
1126
- msg = "One-click run failed: Environment not configured"
1127
- return None, None, None, None, None, gr.update(), gr.update(), process_state, msg, msg, msg, stop_flag
1128
-
1129
- m1_base, m1_normal, m1_rough, process_state, m1_status = run_single_module(1, params, process_state, re_run=True)
1130
- if "Failed" in m1_status or "Error" in m1_status:
1131
- return m1_base, m1_normal, m1_rough, None, None, gr.update(), gr.update(), process_state, m1_status + "\nModule1 failed, one-click run terminated", "Module2 not executed", "Module3 not executed", stop_flag
1132
-
1133
- m2_ldr, m2_env, process_state, m2_status = run_single_module(2, params, process_state, re_run=True)
1134
- if "Failed" in m2_status or "Error" in m2_status:
1135
- return m1_base, m1_normal, m1_rough, m2_ldr, m2_env, gr.update(), gr.update(), process_state, m1_status, m2_status + "\nModule2 failed, one-click run terminated", "Module3 not executed", stop_flag
1136
-
1137
- m3_img, m3_video, process_state, m3_status = run_single_module(3, params, process_state, re_run=True)
1138
- m3_status += "\nOne-click run completed successfully!" if "Failed" not in m3_status and "Error" not in m3_status else "\nOne-click run completed with Module3 failure"
1139
- return m1_base, m1_normal, m1_rough, m2_ldr, m2_env, m3_img, m3_video, process_state, m1_status, m2_status, m3_status, stop_flag
1140
-
1141
-
1142
- def stop_one_click_run(process_state, stop_flag, m1_status, m2_status, m3_status):
1143
- stop_flag = True
1144
- for module_num in list(process_state.keys()):
1145
- process_state, _ = stop_module_execution(module_num, process_state, "")
1146
- return process_state, stop_flag, m1_status + "\nOne-click run manually stopped", m2_status + "\nOne-click run manually stopped", m3_status + "\nOne-click run manually stopped"
1147
-
1148
 
1149
- def init_and_show_preview(input_file):
1150
- image_preview, video_preview, status, params = init_input_file(input_file)
1151
- return (
1152
- gr.update(value=image_preview, visible=image_preview is not None, height=INPUT_PREVIEW_HEIGHT),
1153
- gr.update(value=video_preview, visible=video_preview is not None, height=INPUT_PREVIEW_HEIGHT),
1154
- status,
1155
- params,
1156
- )
1157
 
 
 
 
 
1158
 
1159
  css = """
1160
- .carousel-container { width: 100%; max-width: 600px; margin: 20px auto; overflow: hidden; border-radius: 12px; }
1161
- .select-image-btn { margin-top: 15px; width: 100%; max-width: 200px; }
1162
  """
1163
 
 
 
 
 
 
 
 
1164
 
1165
- with gr.Blocks(title="Relit-LiVE: Relighting Model Interactive Inference Tool") as demo:
1166
- gr.Markdown("""
1167
- # Relit-LiVE: Relighting Model Interactive Inference Tool
1168
- > Left Panel (Input + Parameter Configuration) | Right Panel (Module 1 + Module 2 + Module 3)
1169
- """)
1170
-
1171
- base_params = gr.State(value={})
1172
- process_state = gr.State(value={})
1173
- one_click_stop_flag = gr.State(value=False)
1174
- demo_image_paths = get_demo_images()
1175
- total_images = len(demo_image_paths)
1176
- current_index = gr.State(value=0 if total_images > 0 else -1)
1177
-
1178
- with gr.Row():
1179
- with gr.Column(scale=1, min_width=400):
1180
- gr.Markdown("## Input & Parameter Configuration")
1181
- input_file = gr.File(label="Upload Input File (Supports jpg/png/mp4)", file_types=[".jpg", ".jpeg", ".png", ".mp4"])
1182
- init_input_btn = gr.Button("Initialize Input File", variant="primary")
1183
-
1184
- with gr.Row():
1185
- input_image_preview = gr.Image(label="Image Preview", height=INPUT_PREVIEW_HEIGHT, visible=False)
1186
- input_video_preview = gr.Video(label="Video Preview", height=INPUT_PREVIEW_HEIGHT, visible=False)
1187
- input_status = gr.Textbox(label="Initialization Status", lines=2)
1188
-
1189
- gr.Markdown("### Demo Images")
1190
- if demo_image_paths:
1191
- carousel_image = gr.Image(value=demo_image_paths[0], label="Demo Image", height=DEMO_IMAGE_HEIGHT, interactive=False)
1192
- with gr.Row():
1193
- prev_btn = gr.Button("Previous")
1194
- next_btn = gr.Button("Next")
1195
- select_current_btn = gr.Button("Select This Image", variant="primary", elem_classes=["select-image-btn"])
1196
-
1197
- def update_carousel_ui(index):
1198
- if index < 0 or index >= len(demo_image_paths):
1199
- return None
1200
- return demo_image_paths[index]
1201
-
1202
- prev_btn.click(lambda idx: update_carousel(idx, "prev", total_images), inputs=[current_index], outputs=[current_index]).then(update_carousel_ui, inputs=[current_index], outputs=[carousel_image])
1203
- next_btn.click(lambda idx: update_carousel(idx, "next", total_images), inputs=[current_index], outputs=[current_index]).then(update_carousel_ui, inputs=[current_index], outputs=[carousel_image])
1204
- select_current_btn.click(lambda idx: select_demo_image(demo_image_paths[idx] if 0 <= idx < len(demo_image_paths) else None), inputs=[current_index], outputs=[input_image_preview, input_video_preview, input_status, base_params])
1205
- else:
1206
- gr.Markdown("*No demo images found in the specified directory*")
1207
-
1208
- gr.Markdown("---\n## Environment Configuration")
1209
- use_builtin_env = gr.Checkbox(label="Use Built-in Environment (Uncheck to upload custom)", value=True)
1210
- with gr.Row():
1211
- builtin_env_choice = gr.Dropdown(label="Built-in Environment Selection", choices=BUILTIN_ENV_OPTIONS, value=DEFAULT_ENV)
1212
- env_file = gr.File(label="Custom Environment (hdr/jpg/png)", file_types=[".hdr", ".jpg", ".jpeg", ".png"], visible=False)
1213
- update_env_btn = gr.Button("Update Environment", variant="primary")
1214
- env_status = gr.Textbox(label="Environment Config Status", lines=1)
1215
-
1216
- gr.Markdown("---\n## Advanced Parameters")
1217
- frame = gr.Slider(label="Video Frames (Video only, 1-57, 4n+1)", minimum=1, maximum=57, step=4, value=25)
1218
- frame_rate = gr.Slider(label="Sample Rate of Video Frames (Video only, 10-24)", minimum=10, maximum=24, step=1, value=24)
1219
- env_strength = gr.Slider(label="Environment Strength (0-5)", minimum=0, maximum=5, step=0.1, value=3.0)
1220
- num_infer_steps = gr.Slider(label="Inference Steps (1-50)", minimum=1, maximum=50, step=1, value=20)
1221
- worw = gr.Slider(label="Reference Image Weight (0-5, smaller = more influence. Increase it when the light fails.)", minimum=0, maximum=5, step=0.1, value=0.0)
1222
- light_type = gr.Radio(label="Light Type", choices=[0, 1, 2], value=0)
1223
- update_advanced_btn = gr.Button("Update Parameters", variant="primary")
1224
- advanced_status = gr.Textbox(label="Parameter Update Status", lines=2)
1225
-
1226
- gr.Markdown("---\n## One-Click Run All Modules")
1227
- with gr.Row():
1228
- one_click_run_btn = gr.Button("Run Module1 -> Module2 -> Module3", variant="primary", size="lg")
1229
- one_click_stop_btn = gr.Button("Stop All Running Modules", variant="stop", size="lg")
1230
- one_click_status = gr.Textbox(label="One-Click Run Status", lines=3)
1231
-
1232
- with gr.Column(scale=2, min_width=600):
1233
- gr.Markdown("## Module 1: Inverse Rendering")
1234
- with gr.Row():
1235
- run_module1_btn = gr.Button("Start Execution", variant="primary")
1236
- stop_module1_btn = gr.Button("Stop Execution", variant="stop")
1237
- show_module1_btn = gr.Button("Show Results", variant="secondary")
1238
- module1_status = gr.Textbox(label="Execution Status", lines=2)
1239
- with gr.Row(equal_height=True):
1240
- module1_base_color = gr.Image(label="Base Color", height=MODULE1_VIS_HEIGHT, scale=1)
1241
- module1_normal = gr.Image(label="Normal Map", height=MODULE1_VIS_HEIGHT, scale=1)
1242
- module1_roughness = gr.Image(label="Roughness Map", height=MODULE1_VIS_HEIGHT, scale=1)
1243
-
1244
- gr.Markdown("---\n## Module 2: Environment Processing")
1245
- with gr.Row():
1246
- run_module2_btn = gr.Button("Start Execution", variant="primary")
1247
- stop_module2_btn = gr.Button("Stop Execution", variant="stop")
1248
- show_module2_btn = gr.Button("Show Results", variant="secondary")
1249
- module2_status = gr.Textbox(label="Execution Status", lines=2)
1250
- with gr.Row(equal_height=True):
1251
- module2_ldr_video = gr.Video(label="LDR Video (Core Result)", height=MODULE2_VIS_HEIGHT, scale=1)
1252
- module2_env_video = gr.Video(label="Environment Direction Video", height=MODULE2_VIS_HEIGHT, scale=1)
1253
-
1254
- gr.Markdown("---\n## Module 3: Relighting")
1255
- with gr.Row():
1256
- run_module3_btn = gr.Button("Start Execution", variant="primary")
1257
- stop_module3_btn = gr.Button("Stop Execution", variant="stop")
1258
- show_module3_btn = gr.Button("Show Results", variant="secondary")
1259
- module3_status = gr.Textbox(label="Execution Status", lines=2)
1260
- with gr.Row():
1261
- module3_image_result = gr.Image(label="Relighting Result (Image)", height=MODULE3_VIS_HEIGHT, visible=False, scale=1)
1262
- module3_video_result = gr.Video(label="Relighting Result (Video)", height=MODULE3_VIS_HEIGHT, visible=False, scale=1)
1263
-
1264
- init_input_btn.click(init_and_show_preview, inputs=[input_file], outputs=[input_image_preview, input_video_preview, input_status, base_params])
1265
- use_builtin_env.change(lambda x: gr.update(visible=not x), inputs=[use_builtin_env], outputs=[env_file])
1266
- update_env_btn.click(update_env_config, inputs=[base_params, use_builtin_env, builtin_env_choice, env_file], outputs=[base_params, env_status])
1267
- update_advanced_btn.click(update_advanced_params, inputs=[base_params, frame, frame_rate, env_strength, num_infer_steps, worw, light_type], outputs=[base_params, advanced_status])
1268
-
1269
- run_module1_btn.click(lambda params, ps: run_single_module(1, params, ps, True), inputs=[base_params, process_state], outputs=[module1_base_color, module1_normal, module1_roughness, process_state, module1_status])
1270
- stop_module1_btn.click(lambda ps, cs: stop_module_execution(1, ps, cs), inputs=[process_state, module1_status], outputs=[process_state, module1_status], queue=False)
1271
- show_module1_btn.click(lambda params, ps: run_single_module(1, params, ps, False), inputs=[base_params, process_state], outputs=[module1_base_color, module1_normal, module1_roughness, process_state, module1_status])
1272
-
1273
- run_module2_btn.click(lambda params, ps: run_single_module(2, params, ps, True), inputs=[base_params, process_state], outputs=[module2_ldr_video, module2_env_video, process_state, module2_status])
1274
- stop_module2_btn.click(lambda ps, cs: stop_module_execution(2, ps, cs), inputs=[process_state, module2_status], outputs=[process_state, module2_status], queue=False)
1275
- show_module2_btn.click(lambda params, ps: run_single_module(2, params, ps, False), inputs=[base_params, process_state], outputs=[module2_ldr_video, module2_env_video, process_state, module2_status])
1276
 
1277
- def update_module3_visibility(image_val, video_val, status):
1278
- return (
1279
- gr.update(value=image_val, visible=image_val is not None, height=MODULE3_VIS_HEIGHT),
1280
- gr.update(value=video_val, visible=video_val is not None, height=MODULE3_VIS_HEIGHT),
1281
- status,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1282
  )
1283
 
1284
- run_module3_btn.click(lambda params, ps: run_single_module(3, params, ps, True), inputs=[base_params, process_state], outputs=[module3_image_result, module3_video_result, process_state, module3_status]).then(update_module3_visibility, inputs=[module3_image_result, module3_video_result, module3_status], outputs=[module3_image_result, module3_video_result, module3_status])
1285
- stop_module3_btn.click(lambda ps, cs: stop_module_execution(3, ps, cs), inputs=[process_state, module3_status], outputs=[process_state, module3_status], queue=False)
1286
- show_module3_btn.click(lambda params, ps: run_single_module(3, params, ps, False), inputs=[base_params, process_state], outputs=[module3_image_result, module3_video_result, process_state, module3_status]).then(update_module3_visibility, inputs=[module3_image_result, module3_video_result, module3_status], outputs=[module3_image_result, module3_video_result, module3_status])
1287
-
1288
- one_click_run_btn.click(
1289
- one_click_run_all,
1290
- inputs=[base_params, process_state, one_click_stop_flag],
1291
- outputs=[module1_base_color, module1_normal, module1_roughness, module2_ldr_video, module2_env_video, module3_image_result, module3_video_result, process_state, module1_status, module2_status, module3_status, one_click_stop_flag],
1292
- ).then(
1293
- lambda image_val, video_val: (gr.update(visible=image_val is not None, height=MODULE3_VIS_HEIGHT), gr.update(visible=video_val is not None, height=MODULE3_VIS_HEIGHT)),
1294
- inputs=[module3_image_result, module3_video_result],
1295
- outputs=[module3_image_result, module3_video_result],
1296
  )
1297
- one_click_stop_btn.click(stop_one_click_run, inputs=[process_state, one_click_stop_flag, module1_status, module2_status, module3_status], outputs=[process_state, one_click_stop_flag, module1_status, module2_status, module3_status], queue=False)
1298
-
1299
 
1300
  if __name__ == "__main__":
1301
- os.makedirs(BASE_UPLOAD_DIR, exist_ok=True)
1302
- os.makedirs(BASE_RESULT_DIR, exist_ok=True)
1303
-
1304
- print("=" * 60)
1305
- print("Relit-LiVE: Relighting Model Interactive Inference Tool")
1306
- print(f"App build: {APP_BUILD_ID}")
1307
- print("ZeroGPU allocation wraps the module subprocess execution")
1308
- print("=" * 60)
1309
-
1310
- if STARTUP_RUNTIME_SETUP:
1311
- print("Preparing runtime assets before launching the app...")
1312
- setup_ok, setup_log = ensure_runtime_assets()
1313
- print(setup_log)
1314
- if not setup_ok:
1315
- print("Runtime setup failed. The app will still launch and show the setup error in module status.")
1316
- else:
1317
- print("Startup runtime setup disabled with RELIT_STARTUP_SETUP=0")
1318
-
1319
- demo.queue(default_concurrency_limit=1, max_size=10)
1320
  demo.launch(
1321
- css=css,
1322
  server_name="0.0.0.0",
1323
- server_port=int(os.environ.get("PORT", 7860)),
1324
- share=False,
1325
  show_error=True,
1326
- )
 
1
+ import hashlib
 
 
 
2
  import os
3
+ import shutil
4
  import subprocess
5
  import sys
6
+ import traceback
 
 
 
7
  from pathlib import Path
8
+ from typing import List, Tuple
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ import gradio as gr
11
+ import spaces
12
+ from huggingface_hub import snapshot_download
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ APP_BUILD = "relit-live-readme-lite-2026-06-29"
15
 
16
+ ROOT = Path(__file__).resolve().parent
17
+ DATASETS_DEMOS = ROOT / "datasets" / "demos"
18
+ DATASETS_ENVS = ROOT / "datasets" / "envs"
19
+ RUNTIME_DIR = ROOT / "runtime_zerogpu_lite"
20
+ CACHE_DIR = RUNTIME_DIR / "cache"
21
+ TMP_DATASETS_DIR = RUNTIME_DIR / "datasets"
22
+ OUTPUT_DIR = RUNTIME_DIR / "outputs"
23
+ PREVIEW_DIR = RUNTIME_DIR / "previews"
24
 
25
+ RELIT_REPO_ID = os.environ.get("RELIT_HF_REPO", "weiqingXiao/Relit-LiVE")
26
+ WAN_REPO_ID = os.environ.get("RELIT_WAN_REPO", "Wan-AI/Wan2.1-T2V-1.3B")
27
+ DOWNLOAD_WEIGHTS = os.environ.get("RELIT_DOWNLOAD_WEIGHTS", "1") != "0"
28
+ ZEROGPU_SIZE = os.environ.get("RELIT_ZEROGPU_SIZE", "large")
29
 
30
+ CKPT_25 = ROOT / "checkpoints" / "model_frame25_480_832.ckpt"
31
+ WAN_DIR = ROOT / "models" / "Wan-AI" / "Wan2.1-T2V-1.3B"
32
+ WAN_REQUIRED = [
33
+ WAN_DIR / "diffusion_pytorch_model.safetensors",
34
+ WAN_DIR / "models_t5_umt5-xxl-enc-bf16.pth",
35
+ WAN_DIR / "Wan2.1_VAE.pth",
36
+ ]
37
 
38
+ VIDEO_EXTS = {".mp4", ".mov", ".mkv", ".avi"}
39
+ IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".bmp"}
 
 
 
 
 
40
 
41
 
42
+ def _tail(text: str, max_chars: int = 5000) -> str:
43
+ if not text:
44
+ return ""
45
+ text = text.strip()
46
+ return text[-max_chars:]
 
 
 
 
 
47
 
48
 
49
+ def run_command(cmd: List[str], timeout: int | None = None) -> subprocess.CompletedProcess:
 
50
  env = os.environ.copy()
51
+ env["PYTHONUNBUFFERED"] = "1"
52
+ env["PYTHONPATH"] = f"{ROOT}:{env.get('PYTHONPATH', '')}"
53
+ env.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
54
+ env.setdefault("TOKENIZERS_PARALLELISM", "false")
55
+ return subprocess.run(
56
+ cmd,
57
+ cwd=str(ROOT),
58
  env=env,
59
  text=True,
60
  stdout=subprocess.PIPE,
61
+ stderr=subprocess.PIPE,
62
+ timeout=timeout,
63
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
 
 
 
 
65
 
66
+ def ensure_dirs() -> None:
67
+ for path in [RUNTIME_DIR, CACHE_DIR, TMP_DATASETS_DIR, OUTPUT_DIR, PREVIEW_DIR, ROOT / "checkpoints", WAN_DIR]:
68
+ path.mkdir(parents=True, exist_ok=True)
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ def ensure_weights() -> str:
72
+ ensure_dirs()
73
+ logs: List[str] = [f"App build: {APP_BUILD}"]
 
 
 
 
 
 
 
74
 
75
+ if not DOWNLOAD_WEIGHTS:
76
+ logs.append("Weight download disabled with RELIT_DOWNLOAD_WEIGHTS=0.")
77
+ return "\n".join(logs)
 
 
 
 
 
78
 
79
+ if not CKPT_25.exists():
80
+ logs.append(f"Downloading Relit-LiVE 25-frame checkpoint from {RELIT_REPO_ID}...")
81
+ snapshot_download(
82
+ repo_id=RELIT_REPO_ID,
83
+ local_dir=str(ROOT),
84
+ allow_patterns=["checkpoints/model_frame25_480_832.ckpt"],
85
+ token=os.environ.get("HF_TOKEN"),
 
 
 
86
  )
87
+ logs.append("Relit-LiVE 25-frame checkpoint ready.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  else:
89
+ logs.append("Relit-LiVE 25-frame checkpoint already present.")
90
+
91
+ if not all(path.exists() for path in WAN_REQUIRED):
92
+ logs.append(f"Downloading Wan2.1 base model from {WAN_REPO_ID}...")
93
+ snapshot_download(
94
+ repo_id=WAN_REPO_ID,
95
+ local_dir=str(WAN_DIR),
96
+ token=os.environ.get("HF_TOKEN"),
97
+ )
98
+ logs.append("Wan2.1 base model ready.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  else:
100
+ logs.append("Wan2.1 base model already present.")
101
+
102
+ return "\n".join(logs)
103
+
104
+
105
+ def list_samples() -> List[str]:
106
+ if not DATASETS_DEMOS.exists():
107
+ return []
108
+ samples = []
109
+ for item in sorted(DATASETS_DEMOS.iterdir()):
110
+ if not item.is_dir():
111
+ continue
112
+ # relit_inference.py expects at least images_4 plus intrinsic buffers.
113
+ if (item / "images_4").exists():
114
+ samples.append(item.name)
115
+ return samples
116
+
117
+
118
+ def list_envs() -> List[str]:
119
+ if not DATASETS_ENVS.exists():
120
+ return []
121
+ envs = []
122
+ for item in sorted(DATASETS_ENVS.iterdir()):
123
+ if not item.is_dir():
124
+ continue
125
+ if (item / "ldr_video_fix_first_frame.mp4").exists() or (item / "hdr_log_video_fix_first_frame.mp4").exists():
126
+ envs.append(item.name)
127
+ return envs
128
+
129
+
130
+ def sample_frame_paths(sample_name: str, limit: int = 8) -> List[str]:
131
+ images_dir = DATASETS_DEMOS / sample_name / "images_4"
132
+ if not images_dir.exists():
133
+ return []
134
+ files = []
135
+ for ext in sorted(IMAGE_EXTS):
136
+ files.extend(images_dir.glob(f"*{ext}"))
137
+ files = sorted(files)
138
+ if len(files) <= limit:
139
+ return [str(f) for f in files]
140
+ # Spread frames across the sequence.
141
+ idxs = sorted(set(round(i * (len(files) - 1) / (limit - 1)) for i in range(limit)))
142
+ return [str(files[i]) for i in idxs]
143
+
144
+
145
+ def sample_info(sample_name: str) -> str:
146
+ sample_dir = DATASETS_DEMOS / sample_name
147
+ if not sample_dir.exists():
148
+ return "Sample not found."
149
+ parts = []
150
+ for sub in ["images_4", "Base Color", "normal", "Roughness", "depth", "Metallic"]:
151
+ d = sample_dir / sub
152
+ if d.exists():
153
+ count = sum(1 for p in d.iterdir() if p.suffix.lower() in IMAGE_EXTS | {".exr"})
154
+ parts.append(f"{sub}: {count} files")
155
+ return "\n".join(parts) or "No recognized Relit-LiVE sample folders found."
156
+
157
+
158
+ def update_preview(sample_name: str) -> Tuple[List[str], str]:
159
+ if not sample_name:
160
+ return [], "No sample selected."
161
+ return sample_frame_paths(sample_name), sample_info(sample_name)
162
+
163
+
164
+ def validate_runtime(sample_name: str, env_name: str) -> None:
165
+ missing = []
166
+ if not (ROOT / "relit_inference.py").exists():
167
+ missing.append("relit_inference.py")
168
+ if not CKPT_25.exists():
169
+ missing.append(str(CKPT_25.relative_to(ROOT)))
170
+ for path in WAN_REQUIRED:
171
+ if not path.exists():
172
+ missing.append(str(path.relative_to(ROOT)))
173
+ sample_dir = DATASETS_DEMOS / sample_name
174
+ env_dir = DATASETS_ENVS / env_name
175
+ if not sample_dir.exists():
176
+ missing.append(f"datasets/demos/{sample_name}")
177
+ if not (sample_dir / "images_4").exists():
178
+ missing.append(f"datasets/demos/{sample_name}/images_4")
179
+ if not env_dir.exists():
180
+ missing.append(f"datasets/envs/{env_name}")
181
+ if missing:
182
+ raise RuntimeError("Missing required files:\n- " + "\n- ".join(missing))
183
+
184
+
185
+ def make_single_sample_dataset(sample_name: str, job_dir: Path) -> Path:
186
+ dataset_dir = job_dir / "dataset"
187
+ dataset_dir.mkdir(parents=True, exist_ok=True)
188
+ src = DATASETS_DEMOS / sample_name
189
+ dst = dataset_dir / sample_name
190
+ if dst.exists() or dst.is_symlink():
191
+ if dst.is_symlink() or dst.is_file():
192
+ dst.unlink()
193
+ else:
194
+ shutil.rmtree(dst)
195
+ try:
196
+ dst.symlink_to(src.resolve(), target_is_directory=True)
197
+ except Exception:
198
+ shutil.copytree(src, dst)
199
+ return dataset_dir
200
+
201
+
202
+ def cache_key(sample_name: str, env_name: str, mode: str, num_frames: int, steps: int, cfg_scale: float, quality: int) -> str:
203
+ raw = f"{APP_BUILD}|{sample_name}|{env_name}|{mode}|{num_frames}|{steps}|{cfg_scale}|{quality}"
204
+ return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
205
+
206
+
207
+ def result_path_for(key: str, num_frames: int) -> Path:
208
+ suffix = ".png" if int(num_frames) == 1 else ".mp4"
209
+ return OUTPUT_DIR / key / f"relit_live_result{suffix}"
210
+
211
+
212
+ def estimate_duration(sample_name: str, env_name: str, mode: str, num_frames: int, steps: int, cfg_scale: float, quality: int) -> int:
213
+ # Includes model load time because we intentionally follow the official README CLI path.
214
+ frames = int(num_frames)
215
+ steps = int(steps)
216
+ return min(1800, max(180, int(180 + frames * steps * 1.2)))
217
+
218
+
219
+ @spaces.GPU(duration=estimate_duration, size=ZEROGPU_SIZE)
220
+ def run_relit_cli(sample_name: str, env_name: str, mode: str, num_frames: int, steps: int, cfg_scale: float, quality: int) -> Tuple[str, str]:
221
+ validate_runtime(sample_name, env_name)
222
+ key = cache_key(sample_name, env_name, mode, int(num_frames), int(steps), float(cfg_scale), int(quality))
223
+ output_path = result_path_for(key, int(num_frames))
224
+ if output_path.exists() and output_path.stat().st_size > 0:
225
+ return str(output_path), f"Cache hit: {key}"
226
+
227
+ job_dir = TMP_DATASETS_DIR / key
228
+ if job_dir.exists():
229
+ shutil.rmtree(job_dir)
230
+ job_dir.mkdir(parents=True, exist_ok=True)
231
+ dataset_dir = make_single_sample_dataset(sample_name, job_dir)
232
+ output_path.parent.mkdir(parents=True, exist_ok=True)
233
+
234
+ cmd = [
235
+ sys.executable,
236
+ str(ROOT / "relit_inference.py"),
237
+ "--dataset_path", str(dataset_dir),
238
+ "--ckpt_path", str(CKPT_25),
239
+ "--output_dir", str(output_path.parent),
240
+ "--output_path", str(output_path),
241
+ "--cfg_scale", str(float(cfg_scale)),
242
+ "--height", "480",
243
+ "--width", "832",
244
+ "--num_frames", str(int(num_frames)),
245
+ "--padding_resolution",
246
+ "--use_ref_image",
247
+ "--env_map_path", str(DATASETS_ENVS / env_name),
248
+ "--frame_interval", "1",
249
+ "--num_inference_steps", str(int(steps)),
250
+ "--quality", str(int(quality)),
251
+ "--dataloader_num_workers", "0",
252
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
+ if mode == "Rotating light":
255
+ cmd.append("--use_rotate_light")
256
+ elif mode == "Fixed first frame + width light rotation":
257
+ cmd.append("--use_fixed_frame_and_w_rotate_light")
258
+ elif mode == "Fixed first frame + height light rotation":
259
+ cmd.append("--use_fixed_frame_and_h_rotate_light")
260
+
261
+ proc = run_command(cmd, timeout=3600)
262
+ stdout_tail = _tail(proc.stdout)
263
+ stderr_tail = _tail(proc.stderr)
264
+ log = (
265
+ "Command:\n" + " ".join(cmd) +
266
+ "\n\nSTDOUT tail:\n" + stdout_tail +
267
+ "\n\nSTDERR tail:\n" + stderr_tail
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
+ if proc.returncode != 0:
271
+ raise RuntimeError(f"relit_inference.py failed with exit code {proc.returncode}.\n\n{log}")
272
+ if not output_path.exists() or output_path.stat().st_size == 0:
273
+ produced = sorted(output_path.parent.glob("*.mp4")) + sorted(output_path.parent.glob("*.png"))
274
+ if produced:
275
+ output_path = produced[-1]
276
+ else:
277
+ raise RuntimeError("relit_inference.py finished but no output file was produced.\n\n" + log)
 
 
278
 
279
+ return str(output_path), log
 
 
 
 
280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
+ def run_demo(sample_name: str, env_name: str, mode: str, num_frames: int, steps: int, cfg_scale: float, quality: int):
283
  try:
284
+ ensure_weights()
285
+ key = cache_key(sample_name, env_name, mode, int(num_frames), int(steps), float(cfg_scale), int(quality))
286
+ cached = result_path_for(key, int(num_frames))
287
+ if cached.exists() and cached.stat().st_size > 0:
288
+ status = f"✅ Cached result loaded.\nKey: {key}"
289
+ if int(num_frames) == 1:
290
+ return gr.update(value=str(cached), visible=True), gr.update(value=None, visible=False), status
291
+ return gr.update(value=None, visible=False), gr.update(value=str(cached), visible=True), status
292
+
293
+ output, log = run_relit_cli(sample_name, env_name, mode, int(num_frames), int(steps), float(cfg_scale), int(quality))
294
+ status = "✅ Relit-LiVE inference completed.\n\n" + _tail(log, 2500)
295
+ if int(num_frames) == 1 or output.lower().endswith(".png"):
296
+ return gr.update(value=output, visible=True), gr.update(value=None, visible=False), status
297
+ return gr.update(value=None, visible=False), gr.update(value=output, visible=True), status
298
+ except Exception as exc:
299
+ status = "❌ Inference failed.\n\n" + str(exc) + "\n\n" + traceback.format_exc()[-3000:]
300
+ return gr.update(value=None, visible=False), gr.update(value=None, visible=False), status
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
 
 
302
 
303
+ def startup_message() -> str:
304
  try:
305
+ return ensure_weights()
306
+ except Exception as exc:
307
+ return "Runtime setup failed:\n" + str(exc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
 
 
 
 
 
 
 
 
309
 
310
+ SAMPLES = list_samples()
311
+ ENVS = list_envs()
312
+ DEFAULT_SAMPLE = SAMPLES[0] if SAMPLES else None
313
+ DEFAULT_ENV = "Pink_Sunrise" if "Pink_Sunrise" in ENVS else (ENVS[0] if ENVS else None)
314
 
315
  css = """
316
+ #notice {border: 1px solid #ddd; border-radius: 12px; padding: 12px;}
 
317
  """
318
 
319
+ with gr.Blocks(title="Relit-LiVE ZeroGPU README Demo", css=css) as demo:
320
+ gr.Markdown(
321
+ "# Relit-LiVE ZeroGPU Demo\n"
322
+ "This Space follows the repository README inference path: `python relit_inference.py`. "
323
+ "It uses curated samples already present in `datasets/demos` and environments from `datasets/envs`. "
324
+ "The full Gradio inverse-rendering pipeline is intentionally not used here."
325
+ )
326
 
327
+ with gr.Accordion("Runtime setup", open=False):
328
+ setup_box = gr.Textbox(value=startup_message(), label="Setup log", lines=10)
329
+ refresh_setup = gr.Button("Refresh setup check")
330
+ refresh_setup.click(fn=startup_message, inputs=[], outputs=[setup_box], queue=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
332
+ if not SAMPLES or not ENVS:
333
+ gr.Markdown(
334
+ "## Missing demo assets\n"
335
+ "This app expects the cloned repository to contain `datasets/demos/` and `datasets/envs/`."
336
+ )
337
+ else:
338
+ with gr.Row():
339
+ with gr.Column(scale=1):
340
+ sample = gr.Dropdown(SAMPLES, value=DEFAULT_SAMPLE, label="Demo sample")
341
+ env = gr.Dropdown(ENVS, value=DEFAULT_ENV, label="Lighting environment")
342
+ mode = gr.Radio(
343
+ [
344
+ "Basic relighting",
345
+ "Rotating light",
346
+ "Fixed first frame + width light rotation",
347
+ "Fixed first frame + height light rotation",
348
+ ],
349
+ value="Basic relighting",
350
+ label="README inference mode",
351
+ )
352
+ num_frames = gr.Radio([1, 9, 17, 25], value=9, label="Frames")
353
+ steps = gr.Slider(8, 50, value=16, step=1, label="Inference steps")
354
+ cfg_scale = gr.Slider(1.0, 5.0, value=1.0, step=0.1, label="CFG scale")
355
+ quality = gr.Slider(5, 10, value=8, step=1, label="Video quality")
356
+ run_btn = gr.Button("Run README inference", variant="primary")
357
+
358
+ with gr.Column(scale=1):
359
+ preview = gr.Gallery(label="Input sample frames", columns=4, height=320, value=sample_frame_paths(DEFAULT_SAMPLE) if DEFAULT_SAMPLE else [])
360
+ info = gr.Textbox(label="Sample contents", lines=8, value=sample_info(DEFAULT_SAMPLE) if DEFAULT_SAMPLE else "")
361
+
362
+ with gr.Row():
363
+ result_image = gr.Image(label="Relit result", visible=False)
364
+ result_video = gr.Video(label="Relit result", visible=False)
365
+
366
+ status = gr.Textbox(label="Status / logs", lines=16)
367
+
368
+ sample.change(fn=update_preview, inputs=[sample], outputs=[preview, info], queue=False)
369
+ run_btn.click(
370
+ fn=run_demo,
371
+ inputs=[sample, env, mode, num_frames, steps, cfg_scale, quality],
372
+ outputs=[result_image, result_video, status],
373
  )
374
 
375
+ gr.Markdown(
376
+ "---\n"
377
+ "**ZeroGPU scope:** this demo does not accept arbitrary uploads because arbitrary inputs require the full inverse-rendering pipeline and Cosmos. "
378
+ "Here we run the README `relit_inference.py` path on prepared Relit-LiVE samples."
 
 
 
 
 
 
 
 
379
  )
 
 
380
 
381
  if __name__ == "__main__":
382
+ demo.queue(default_concurrency_limit=1, max_size=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  demo.launch(
 
384
  server_name="0.0.0.0",
385
+ server_port=int(os.environ.get("PORT", "7860")),
 
386
  show_error=True,
387
+ )