ghostai1 commited on
Commit
3fc0a09
·
verified ·
1 Parent(s): 8576125

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +112 -1345
app.py CHANGED
@@ -1,1377 +1,144 @@
1
  #!/usr/bin/env python3
2
- # FILE: app.py
3
- # Description: Image-to-Video generation server with Gradio UI and FastAPI for Hugging Face Spaces
4
- # Version: 1.2.8
5
- # Timestamp: 2025-07-02 03:20 CDT
6
- # Author: Grok 3, built by xAI (based on GhostAI's ghostpack_gradio_f1.py)
7
- # NOTE: Optimized for Hugging Face Spaces with H200 GPU, 25 min/day render time
8
- # Loads models from /data/models (pre-downloaded)
9
- # Uses /data for persistent storage, /tmp for temporary files
10
- # API key authentication for /generate endpoint (off-site use)
11
- # Base64-encoded video responses
12
- # Gradio UI matches original ghostpack_gradio_f1.py
13
- # Idle until triggered by API or Gradio
14
- # Requires custom diffusers_helper package in /diffusers_helper
15
 
16
  import os
17
  import sys
18
- import time
19
- import json
20
- import argparse
21
- import importlib.util
22
  import subprocess
23
- import traceback
24
- import torch
25
- import einops
26
- import numpy as np
27
- from PIL import Image
28
- import io
29
- import gradio as gr
30
- import asyncio
31
- import queue
32
- from threading import Thread
33
- import re
34
  import logging
35
- import base64
36
- import socket
37
- import requests
38
- import shutil
39
- import uuid
40
- from fastapi import FastAPI, HTTPException, UploadFile, File, Form, Depends, Security, status
41
- from fastapi.security import APIKeyHeader
42
- from fastapi.middleware.cors import CORSMiddleware
43
- from fastapi.responses import JSONResponse
44
- from pydantic import BaseModel
45
- from diffusers import AutoencoderKLHunyuanVideo
46
- from transformers import (
47
- LlamaModel, CLIPTextModel, LlamaTokenizerFast, CLIPTokenizer,
48
- SiglipImageProcessor, SiglipVisionModel
49
- )
50
- from diffusers_helper.hunyuan import (
51
- encode_prompt_conds, vae_decode, vae_encode, vae_decode_fake
52
- )
53
- from diffusers_helper.utils import (
54
- save_bcthw_as_mp4, crop_or_pad_yield_mask, soft_append_bcthw
55
- )
56
- from diffusers_helper.models.hunyuan_video_packed import HunyuanVideoTransformer3DModelPacked
57
- from diffusers_helper.memory import (
58
- gpu, get_cuda_free_memory_gb, move_model_to_device_with_memory_preservation,
59
- offload_model_from_device_for_memory_preservation, fake_diffusers_current_device,
60
- DynamicSwapInstaller, unload_complete_models, load_model_as_complete
61
- )
62
- from diffusers_helper.clip_vision import hf_clip_vision_encode
63
- from diffusers_helper.bucket_tools import find_nearest_bucket
64
- from diffusers_helper.thread_utils import AsyncStream
65
- from diffusers_helper.gradio.progress_bar import make_progress_bar_css, make_progress_bar_html
66
-
67
- # Optional: Colorama for colored console output
68
- try:
69
- from colorama import init, Fore, Style
70
- init(autoreset=True)
71
- COLORAMA_AVAILABLE = True
72
- def red(s): return Fore.RED + s + Style.RESET_ALL
73
- def green(s): return Fore.GREEN + s + Style.RESET_ALL
74
- def yellow(s): return Fore.YELLOW + s + Style.RESET_ALL
75
- def reset_all(s): return Style.RESET_ALL + s
76
- except ImportError:
77
- COLORAMA_AVAILABLE = False
78
- def red(s): return s
79
- def green(s): return s
80
- def yellow(s): return s
81
- def reset_all(s): return s
82
 
83
  # Set up logging
84
  logging.basicConfig(
85
- filename="/data/ghostpack.log",
86
  level=logging.DEBUG,
87
  format="%(asctime)s %(levelname)s:%(message)s",
88
  )
89
  logger = logging.getLogger(__name__)
90
- logger.info("Starting GhostPack F1 Pro")
91
- print(f"{green('Using /data/video_info.json for metadata')}")
92
-
93
- VERSION = "1.2.8"
94
- HF_TOKEN = os.getenv('HF_TOKEN', 'your-hf-token') # Set in Spaces secrets
95
- API_KEY_NAME = "X-API-Key"
96
- API_KEY = os.getenv('API_KEY', 'key_temp_1234567890') # Set in Spaces secrets
97
- api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
98
 
99
- # Global job registry
100
- active_jobs = {} # {job_id: AsyncStream}
101
- job_status = {} # {job_id: {"status": str, "progress": float, "render_time": float}}
 
102
 
103
- # CLI
104
- parser = argparse.ArgumentParser(description="GhostPack F1 Pro")
105
- parser.add_argument("--share", action="store_true", help="Share Gradio UI publicly")
106
- parser.add_argument("--server", type=str, default="0.0.0.0", help="Server host")
107
- parser.add_argument("--port", type=int, default=7860, help="FastAPI port")
108
- parser.add_argument("--gradio", action="store_true", help="Enable Gradio UI")
109
- parser.add_argument("--inbrowser", action="store_true", help="Open in browser")
110
- parser.add_argument("--cli", action="store_true", help="Show CLI help")
111
- args = parser.parse_args()
112
-
113
- # Global state
114
- render_on_off = True
115
-
116
- BASE = os.path.abspath(os.path.dirname(__file__))
117
- MODEL_DIR = "/data/models"
118
-
119
- # Check if ports are available
120
- def is_port_in_use(port):
121
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
122
- return s.connect_ex(('0.0.0.0', port)) == 0
123
-
124
- if args.cli:
125
- print(f"{green('👻 GhostPack F1 Pro CLI')}")
126
- print("python app.py # Launch API")
127
- print("python app.py --gradio # Launch API + Gradio UI")
128
- print("python app.py --cli # Show help")
129
- sys.exit(0)
130
-
131
- # Paths
132
- DATA_DIR = "/data"
133
- TMP_DIR = "/tmp/ghostpack"
134
- VIDEO_OUTPUT_DIR = "/tmp/ghostpack/vid"
135
- VIDEO_IMG_DIR = "/tmp/ghostpack/img"
136
- VIDEO_TMP_DIR = "/tmp/ghostpack/tmp_vid"
137
- VIDEO_INFO_FILE = "/data/video_info.json"
138
- PROMPT_LOG_FILE = "/data/prompts.txt"
139
- SAVED_PROMPTS_FILE = "/data/saved_prompts.json"
140
- INSTALL_LOG_FILE = "/data/install_logs.txt"
141
- LAST_CLEANUP_FILE = "/data/last_cleanup.txt"
142
-
143
- # Initialize directories
144
- for d in (DATA_DIR, TMP_DIR, VIDEO_OUTPUT_DIR, VIDEO_IMG_DIR, VIDEO_TMP_DIR, MODEL_DIR):
145
- if not os.path.exists(d):
146
- try:
147
- os.makedirs(d, exist_ok=True)
148
- os.chmod(d, 0o775)
149
- logger.debug(f"Created {d}")
150
- except Exception as e:
151
- logger.error(f"Failed to create {d}: {e}")
152
- print(f"{red(f'Error: Failed to create {d}: {e}')}")
153
- sys.exit(1)
154
-
155
- # Initialize files
156
- for f in (VIDEO_INFO_FILE, SAVED_PROMPTS_FILE, PROMPT_LOG_FILE, INSTALL_LOG_FILE, LAST_CLEANUP_FILE):
157
- if not os.path.exists(f):
158
- try:
159
- if f == LAST_CLEANUP_FILE:
160
- with open(f, "w") as fd:
161
- fd.write(str(time.time()))
162
- elif f in (VIDEO_INFO_FILE, SAVED_PROMPTS_FILE):
163
- with open(f, "w") as fd:
164
- json.dump([], fd)
165
- else:
166
- open(f, "w").close()
167
- os.chmod(f, 0o664)
168
- logger.debug(f"Created {f}")
169
- except Exception as e:
170
- logger.error(f"Failed to create/chmod {f}: {e}")
171
- print(f"{red(f'Error: Failed to create/chmod {f}: {e}')}")
172
- sys.exit(1)
173
-
174
- # Clear VIDEO_INFO_FILE on startup
175
- try:
176
- with open(VIDEO_INFO_FILE, "w") as f:
177
- json.dump([], f)
178
- os.chmod(VIDEO_INFO_FILE, 0o664)
179
- logger.debug(f"Cleared {VIDEO_INFO_FILE}")
180
- except Exception as e:
181
- logger.error(f"Failed to clear {VIDEO_INFO_FILE}: {e}")
182
- print(f"{red(f'Error: Failed to clear {VIDEO_INFO_FILE}: {e}')}")
183
- sys.exit(1)
184
-
185
- # Queue clearing utility
186
- def clear_queue(q):
187
- try:
188
- while True:
189
- if hasattr(q, "get_nowait"):
190
- q.get_nowait()
191
- else:
192
- break
193
- except queue.Empty:
194
- pass
195
-
196
- # Prompt utilities
197
- def get_last_prompts():
198
  try:
199
- return json.load(open(SAVED_PROMPTS_FILE))[-5:][::-1]
200
- except Exception as e:
201
- logger.error(f"Failed to load prompts from {SAVED_PROMPTS_FILE}: {e}")
202
- print(f"{red(f'Error: Failed to load prompts: {e}')}")
203
- return []
204
-
205
- def save_prompt_fn(prompt, n_p):
206
- if not prompt:
207
- return f"{red('❌ No prompt')}"
208
- try:
209
- data = json.load(open(SAVED_PROMPTS_FILE))
210
- entry = {"prompt": prompt, "negative": n_p}
211
- if entry not in data:
212
- data.append(entry)
213
- with open(SAVED_PROMPTS_FILE, "w") as f:
214
- json.dump(data, f, indent=2)
215
- os.chmod(SAVED_PROMPTS_FILE, 0o664)
216
- return f"{green('✅ Saved')}"
217
- except Exception as e:
218
- logger.error(f"Failed to save prompt to {SAVED_PROMPTS_FILE}: {e}")
219
- print(f"{red(f'Error: Failed to save prompt: {e}')}")
220
- return f"{red('❌ Save failed')}"
221
-
222
- def load_prompt_fn(idx):
223
- lst = get_last_prompts()
224
- return lst[idx]["prompt"] if idx < len(lst) else ""
225
-
226
- # Cleanup utilities
227
- def clear_temp_videos():
228
- try:
229
- for f in os.listdir(VIDEO_TMP_DIR):
230
- os.remove(os.path.join(VIDEO_TMP_DIR, f))
231
- return f"{green('✅ Temp cleared')}"
232
- except Exception as e:
233
- logger.error(f"Failed to clear temp videos in {VIDEO_TMP_DIR}: {e}")
234
- print(f"{red(f'Error: Failed to clear temp videos: {e}')}")
235
- return f"{red('❌ Clear failed')}"
236
-
237
- def clear_old_files():
238
- cutoff = time.time() - 7 * 24 * 3600
239
- c = 0
240
- try:
241
- for d in (VIDEO_TMP_DIR, VIDEO_IMG_DIR, VIDEO_OUTPUT_DIR):
242
- for f in os.listdir(d):
243
- p = os.path.join(d, f)
244
- if os.path.isfile(p) and os.path.getmtime(p) < cutoff:
245
- os.remove(p)
246
- c += 1
247
- with open(LAST_CLEANUP_FILE, "w") as f:
248
- f.write(str(time.time()))
249
- os.chmod(LAST_CLEANUP_FILE, 0o664)
250
- return f"{green(f'✅ {c} old files removed')}"
251
- except Exception as e:
252
- logger.error(f"Failed to clear old files: {e}")
253
- print(f"{red(f'Error: Failed to clear old files: {e}')}")
254
- return f"{red('❌ Clear failed')}"
255
-
256
- def clear_images():
257
- try:
258
- for f in os.listdir(VIDEO_IMG_DIR):
259
- os.remove(os.path.join(VIDEO_IMG_DIR, f))
260
- return f"{green('✅ Images cleared')}"
261
- except Exception as e:
262
- logger.error(f"Failed to clear images in {VIDEO_IMG_DIR}: {e}")
263
- print(f"{red(f'Error: Failed to clear images: {e}')}")
264
- return f"{red('❌ Clear failed')}"
265
-
266
- def clear_videos():
267
- try:
268
- for f in os.listdir(VIDEO_OUTPUT_DIR):
269
- os.remove(os.path.join(VIDEO_OUTPUT_DIR, f))
270
- return f"{green('✅ Videos cleared')}"
271
- except Exception as e:
272
- logger.error(f"Failed to clear videos in {VIDEO_OUTPUT_DIR}: {e}")
273
- print(f"{red(f'Error: Failed to clear videos: {e}')}")
274
- return f"{red('❌ Clear failed')}"
275
-
276
- def check_and_run_weekly_cleanup():
277
- try:
278
- with open(LAST_CLEANUP_FILE, "r") as f:
279
- last_cleanup = float(f.read().strip())
280
- except (FileNotFoundError, ValueError):
281
- last_cleanup = 0
282
- if time.time() - last_cleanup > 7 * 24 * 3600:
283
- return clear_old_files()
284
- return ""
285
-
286
- # Video metadata utilities
287
- def save_video_info(prompt, n_p, filename, seed, secs, additional_info, completed=False):
288
- if not completed:
289
- return
290
- try:
291
- video_info = json.load(open(VIDEO_INFO_FILE))
292
- except (FileNotFoundError, json.JSONDecodeError):
293
- video_info = []
294
- entry = {
295
- "prompt": prompt or "",
296
- "negative_prompt": n_p or "",
297
- "filename": filename,
298
- "location": os.path.join(VIDEO_OUTPUT_DIR, filename),
299
- "seed": seed,
300
- "duration_secs": secs,
301
- "timestamp": time.strftime("%Y%m%d_%H%M%S"),
302
- "completed": completed,
303
- "additional_info": additional_info or {},
304
- }
305
- video_info.append(entry)
306
- try:
307
- with open(VIDEO_INFO_FILE, "w") as f:
308
- json.dump(video_info, f, indent=2)
309
- os.chmod(VIDEO_INFO_FILE, 0o664)
310
- logger.debug(f"Saved video info to {VIDEO_INFO_FILE}")
311
- except Exception as e:
312
- logger.error(f"Failed to save video info to {VIDEO_INFO_FILE}: {e}")
313
- print(f"{red(f'Error: Failed to save video info to {VIDEO_INFO_FILE}: {e}')}")
314
- raise
315
-
316
- # Gallery helpers
317
- def list_images():
318
- return sorted(
319
- [os.path.join(VIDEO_IMG_DIR, f) for f in os.listdir(VIDEO_IMG_DIR) if f.lower().endswith((".png", ".jpg"))],
320
- key=os.path.getmtime,
321
- )
322
-
323
- def list_videos():
324
- return sorted(
325
- [os.path.join(VIDEO_OUTPUT_DIR, f) for f in os.listdir(VIDEO_OUTPUT_DIR) if f.lower().endswith(".mp4")],
326
- key=os.path.getmtime,
327
- )
328
-
329
- def load_image(sel):
330
- imgs = list_images()
331
- if sel in [os.path.basename(p) for p in imgs]:
332
- pth = imgs[[os.path.basename(p) for p in imgs].index(sel)]
333
- return gr.update(value=pth), gr.update(value=os.path.basename(pth))
334
- return gr.update(), gr.update()
335
-
336
- def load_video(sel):
337
- vids = list_videos()
338
- if sel in [os.path.basename(p) for p in vids]:
339
- pth = vids[[os.path.basename(p) for p in vids].index(sel)]
340
- return gr.update(value=pth), gr.update(value=os.path.basename(pth))
341
- return gr.update(), gr.update()
342
-
343
- def next_image_and_load(sel):
344
- imgs = list_images()
345
- if not imgs:
346
- return gr.update(), gr.update()
347
- names = [os.path.basename(i) for i in imgs]
348
- idx = (names.index(sel) + 1) % len(names) if sel in names else 0
349
- pth = imgs[idx]
350
- return gr.update(value=pth), gr.update(value=os.path.basename(pth))
351
-
352
- def next_video_and_load(sel):
353
- vids = list_videos()
354
- if not vids:
355
- return gr.update(), gr.update()
356
- names = [os.path.basename(v) for v in vids]
357
- idx = (names.index(sel) + 1) % len(names) if sel in names else 0
358
- pth = vids[idx]
359
- return gr.update(value=pth), gr.update(value=os.path.basename(pth))
360
-
361
- def gallery_image_select(evt: gr.SelectData):
362
- imgs = list_images()
363
- if evt.index is not None and evt.index < len(imgs):
364
- pth = imgs[evt.index]
365
- return gr.update(value=pth), gr.update(value=os.path.basename(pth))
366
- return gr.update(), gr.update()
367
-
368
- def gallery_video_select(evt: gr.SelectData):
369
- vids = list_videos()
370
- if evt.index is not None and evt.index < len(vids):
371
- pth = vids[evt.index]
372
- return gr.update(value=pth), gr.update(value=os.path.basename(pth))
373
- return gr.update(), gr.update()
374
-
375
- # Install status
376
- def check_mod(n):
377
- return importlib.util.find_spec(n) is not None
378
-
379
- def status_xformers():
380
- print(f"{green('✅ Xformers is installed!')}" if check_mod("xformers") else f"{red('❌ Xformers is not installed!')}")
381
- return f"{green('✅ xformers')}" if check_mod("xformers") else f"{red('❌ xformers')}"
382
-
383
- def status_sage():
384
- print(f"{green('✅ Sage Attn is installed!')}" if check_mod("sageattention") else f"{red('❌ Sage Attn is not installed!')}")
385
- return f"{green('✅ sage-attn')}" if check_mod("sageattention") else f"{red('❌ sage-attn')}"
386
-
387
- def status_flash():
388
- print(f"{yellow('⚠️ Flash Attn is not installed, performance may be reduced!')}" if not check_mod("flash_attn") else f"{green('✅ Flash Attn is installed!')}")
389
- return f"{yellow('⚠️ flash-attn')}" if not check_mod("flash_attn") else f"{green('✅ flash-attn')}"
390
-
391
- def status_colorama():
392
- return f"{green('✅ colorama')}" if COLORAMA_AVAILABLE else f"{red('❌ colorama')}"
393
-
394
- def install_pkg(pkg, warn=None):
395
- if warn:
396
- print(f"{yellow(warn)}")
397
- time.sleep(1)
398
- try:
399
- out = subprocess.check_output(
400
- [sys.executable, "-m", "pip", "install", pkg], stderr=subprocess.STDOUT, text=True
401
- )
402
- res = f"{green(f'✅ {pkg}')}\n{out}\n"
403
  except subprocess.CalledProcessError as e:
404
- res = f"{red(f'❌ {pkg}')}\n{e.output}\n"
405
- with open(INSTALL_LOG_FILE, "a") as f:
406
- f.write(f"[{pkg}] {res}")
407
- return res
408
-
409
- install_xformers = lambda: install_pkg("xformers")
410
- install_sage_attn = lambda: install_pkg("sage-attn")
411
- install_flash_attn = lambda: install_pkg("flash-attn", "⚠️ long compile, optional for performance")
412
- install_colorama = lambda: install_pkg("colorama")
413
- refresh_logs = lambda: open(INSTALL_LOG_FILE).read()
414
- clear_logs = lambda: open(INSTALL_LOG_FILE, "w").close() or f"{green('✅ Logs cleared')}"
415
-
416
- # Model load
417
- free_mem = get_cuda_free_memory_gb(gpu)
418
- hv = free_mem > 60
419
- logger.info(f"VRAM available: {free_mem:.2f} GB, High VRAM mode: {hv}")
420
- print(f"{yellow(f'VRAM available: {free_mem:.2f} GB, High VRAM mode: {hv}')}")
421
-
422
- try:
423
- print(f"{yellow('Loading models from /data/models...')}")
424
- text_encoder = LlamaModel.from_pretrained(
425
- os.path.join(MODEL_DIR, "hunyuanvideo-community/HunyuanVideo/text_encoder"), torch_dtype=torch.float16
426
- ).cpu().eval()
427
- text_encoder_2 = CLIPTextModel.from_pretrained(
428
- os.path.join(MODEL_DIR, "hunyuanvideo-community/HunyuanVideo/text_encoder_2"), torch_dtype=torch.float16
429
- ).cpu().eval()
430
- tokenizer = LlamaTokenizerFast.from_pretrained(
431
- os.path.join(MODEL_DIR, "hunyuanvideo-community/HunyuanVideo/tokenizer")
432
- )
433
- tokenizer_2 = CLIPTokenizer.from_pretrained(
434
- os.path.join(MODEL_DIR, "hunyuanvideo-community/HunyuanVideo/tokenizer_2")
435
- )
436
- vae = AutoencoderKLHunyuanVideo.from_pretrained(
437
- os.path.join(MODEL_DIR, "hunyuanvideo-community/HunyuanVideo/vae"), torch_dtype=torch.float16
438
- ).cpu().eval()
439
- feature_extractor = SiglipImageProcessor.from_pretrained(
440
- os.path.join(MODEL_DIR, "lllyasviel/flux_redux_bfl/feature_extractor")
441
- )
442
- image_encoder = SiglipVisionModel.from_pretrained(
443
- os.path.join(MODEL_DIR, "lllyasviel/flux_redux_bfl/image_encoder"), torch_dtype=torch.float16
444
- ).cpu().eval()
445
- transformer = HunyuanVideoTransformer3DModelPacked.from_pretrained(
446
- os.path.join(MODEL_DIR, "lllyasviel/FramePack_F1_I2V_HY_20250503"), torch_dtype=torch.bfloat16
447
- ).cpu().eval()
448
- logger.info("Models loaded successfully from /data/models")
449
- print(f"{green('Models loaded successfully from /data/models')}")
450
- except Exception as e:
451
- logger.error(f"Failed to load models: {e}", exc_info=True)
452
- print(f"{red(f'Error: Failed to load models from /data/models: {e}')}")
453
- raise
454
-
455
- if not hv:
456
- vae.enable_slicing()
457
- vae.enable_tiling()
458
-
459
- transformer.high_quality_fp32_output_for_inference = True
460
- transformer.to(dtype=torch.bfloat16)
461
- for m in (vae, image_encoder, text_encoder, text_encoder_2):
462
- m.to(dtype=torch.float16)
463
- for m in (vae, image_encoder, text_encoder, text_encoder_2, transformer):
464
- m.requires_grad_(False)
465
-
466
- if not hv:
467
- DynamicSwapInstaller.install_model(transformer, device=gpu)
468
- DynamicSwapInstaller.install_model(text_encoder, device=gpu)
469
- else:
470
- for m in (vae, image_encoder, text_encoder, text_encoder_2, transformer):
471
- m.to(gpu)
472
- logger.debug("Models configured and moved to device")
473
- print(f"{green('Models configured and moved to device')}")
474
 
475
- # FastAPI Setup
476
- app = FastAPI(title="GhostPack F1 Pro API")
477
- app.add_middleware(
478
- CORSMiddleware,
479
- allow_origins=["*"],
480
- allow_credentials=True,
481
- allow_methods=["*"],
482
- allow_headers=["*"],
483
  )
484
 
485
- async def verify_api_key(api_key: str = Security(api_key_header)):
486
- if api_key != API_KEY:
487
- raise HTTPException(
488
- status_code=status.HTTP_401_UNAUTHORIZED,
489
- detail="Invalid API key"
490
- )
491
- return api_key
492
-
493
- class GenerateRequest(BaseModel):
494
- prompt: str
495
- negative_prompt: str
496
- seed: int
497
- video_length: float
498
- latent_window: int
499
- steps: int
500
- cfg: float
501
- distilled_cfg: float
502
- cfg_rescale: float
503
- gpu_keep: float
504
- crf: int
505
- use_teacache: bool
506
- camera_action: str
507
- disable_prompt_mods: bool
508
- link_steps_window: bool
509
-
510
- @app.get("/health")
511
- async def health_check():
512
- try:
513
- return JSONResponse(content={"status": "healthy"})
514
- except Exception as e:
515
- logger.error(f"Health check failed: {e}", exc_info=True)
516
- return JSONResponse(content={"error": str(e), "status": "error"}, status_code=500)
517
-
518
- @app.get("/test")
519
- async def test_server():
520
- try:
521
- report = {
522
- "server_status": {
523
- "version": VERSION,
524
- "host": args.server,
525
- "port": args.port,
526
- "uptime": time.time() - time.time() if job_status else 0,
527
- "active_jobs": len(active_jobs),
528
- "api_status": "running",
529
- },
530
- "system": {
531
- "vram_total": free_mem,
532
- "vram_free": get_cuda_free_memory_gb(gpu),
533
- "high_vram_mode": hv,
534
- "cuda_available": torch.cuda.is_available(),
535
- "cuda_device": torch.cuda.get_device_name(gpu) if torch.cuda.is_available() else "N/A",
536
- },
537
- "models": {
538
- "text_encoder": text_encoder is not None,
539
- "text_encoder_2": text_encoder_2 is not None,
540
- "vae": vae is not None,
541
- "image_encoder": image_encoder is not None,
542
- "transformer": transformer is not None,
543
- "tokenizer": tokenizer is not None,
544
- "tokenizer_2": tokenizer_2 is not None,
545
- "feature_extractor": feature_extractor is not None,
546
- },
547
- "paths": {
548
- "base": BASE,
549
- "models": MODEL_DIR,
550
- "images": VIDEO_IMG_DIR,
551
- "videos": VIDEO_OUTPUT_DIR,
552
- "temp": VIDEO_TMP_DIR,
553
- "data": DATA_DIR,
554
- "prompt_log": PROMPT_LOG_FILE,
555
- "saved_prompts": SAVED_PROMPTS_FILE,
556
- "install_log": INSTALL_LOG_FILE,
557
- "video_info": VIDEO_INFO_FILE,
558
- },
559
- "file_system": {
560
- "images_writable": os.access(VIDEO_IMG_DIR, os.W_OK),
561
- "videos_writable": os.access(VIDEO_OUTPUT_DIR, os.W_OK),
562
- "temp_writable": os.access(VIDEO_TMP_DIR, os.W_OK),
563
- "data_writable": os.access(DATA_DIR, os.W_OK),
564
- "models_writable": os.access(MODEL_DIR, os.W_OK),
565
- },
566
- "dependencies": {
567
- "xformers": status_xformers(),
568
- "sage_attn": status_sage(),
569
- "flash_attn": status_flash(),
570
- "colorama": status_colorama(),
571
- },
572
- "health_check": {"status": "pass", "details": ""}
573
- }
574
-
575
- try:
576
- dummy_img = np.zeros((64, 64, 3), dtype=np.uint8)
577
- img_pt = (torch.from_numpy(dummy_img).float() / 127.5 - 1).permute(2, 0, 1)[None, :, None]
578
- if not hv:
579
- load_model_as_complete(vae, gpu)
580
- _ = vae_encode(img_pt, vae)
581
- report["health_check"]["status"] = "pass"
582
- except Exception as e:
583
- report["health_check"]["status"] = "fail"
584
- report["health_check"]["details"] = str(e)
585
- logger.error(f"Health check failed: {e}", exc_info=True)
586
-
587
- logger.info("Test endpoint accessed successfully")
588
- print(f"{green(f'Test endpoint accessed: API running on {args.server}:{args.port}')}")
589
- return JSONResponse(content=report)
590
- except Exception as e:
591
- logger.error(f"Test endpoint error: {e}", exc_info=True)
592
- print(f"{red(f'Test endpoint error: {e}')}")
593
- return JSONResponse(
594
- content={"error": str(e), "status": "fail"},
595
- status_code=500
596
- )
597
-
598
- @app.get("/status/{job_id}")
599
- async def get_status(job_id: str, api_key: str = Depends(verify_api_key)):
600
- try:
601
- status = job_status.get(job_id, {"status": "not_found", "progress": 0.0, "render_time": 0})
602
- return JSONResponse(
603
- content={
604
- "job_id": job_id,
605
- "render_status": status["status"],
606
- "render_progress": status["progress"],
607
- "render_time": status["render_time"],
608
- "active_jobs": len(active_jobs),
609
- "api_status": "running",
610
- }
611
- )
612
- except Exception as e:
613
- logger.error(f"Status check failed for job {job_id}: {e}", exc_info=True)
614
- return JSONResponse(
615
- content={"error": str(e), "job_id": job_id, "status": "error"},
616
- status_code=500
617
- )
618
-
619
- @app.post("/stop/{job_id}")
620
- async def stop_render(job_id: str, api_key: str = Depends(verify_api_key)):
621
- if job_id not in active_jobs:
622
- logger.info(f"No active job {job_id} to stop")
623
- print(f"{yellow(f'No active job {job_id} to stop')}")
624
- return JSONResponse(content={"message": f"No active job {job_id}"})
625
- stream = active_jobs[jid]
626
- stream.stop()
627
- active_jobs.pop(job_id, None)
628
- job_status[job_id]["status"] = "stopped"
629
- job_status[job_id]["progress"] = 0.0
630
- logger.info(f"Stopped job {job_id}")
631
- print(f"{yellow(f'Stopped job {job_id}')}")
632
- return JSONResponse(content={"message": f"Job {job_id} stopped"})
633
-
634
- @app.get("/videos")
635
- async def get_videos(api_key: str = Depends(verify_api_key)):
636
- try:
637
- videos = [f for f in os.listdir(VIDEO_OUTPUT_DIR) if f.lower().endswith(".mp4")]
638
- return JSONResponse(content={"status": "success", "videos": videos})
639
- except Exception as e:
640
- logger.error(f"Failed to list videos: {e}", exc_info=True)
641
- return JSONResponse(content={"error": str(e), "status": "error"}, status_code=500)
642
 
643
- @app.post("/generate")
644
- async def generate_video(
645
- image_file: UploadFile = File(...),
646
- prompt: str = Form(""),
647
- negative_prompt: str = Form(""),
648
- seed: int = Form(31337),
649
- video_length: float = Form(8.0),
650
- latent_window: int = Form(3),
651
- steps: int = Form(12),
652
- cfg: float = Form(1.0),
653
- distilled_cfg: float = Form(7.0),
654
- cfg_rescale: float = Form(0.5),
655
- gpu_keep: float = Form(6.0),
656
- crf: int = Form(20),
657
- use_teacache: bool = Form(True),
658
- camera_action: str = Form("Static Camera"),
659
- disable_prompt_mods: bool = Form(False),
660
- link_steps_window: bool = Form(True),
661
- api_key: str = Depends(verify_api_key)
662
- ):
663
- params = {
664
- "prompt": prompt,
665
- "negative_prompt": negative_prompt,
666
- "seed": seed,
667
- "video_length": video_length,
668
- "latent_window": latent_window,
669
- "steps": steps,
670
- "cfg": cfg,
671
- "distilled_cfg": distilled_cfg,
672
- "cfg_rescale": cfg_rescale,
673
- "gpu_keep": gpu_keep,
674
- "crf": crf,
675
- "use_teacache": use_teacache,
676
- "camera_action": camera_action,
677
- "disable_prompt_mods": disable_prompt_mods,
678
- "link_steps_window": link_steps_window
679
- }
680
- logger.info(f"Received /generate request with parameters: {json.dumps(params, indent=2)}")
681
- print(f"{green(f'API: Received /generate request with parameters: {json.dumps(params, indent=2)}')}")
682
 
683
- if not render_on_off:
684
- logger.info("Render disabled by client")
685
- print(f"{red('API: Render disabled by client')}")
686
- return JSONResponse(content={"status": "render_disabled", "error": "Rendering disabled"}, status_code=403)
 
 
 
 
 
 
 
 
 
 
 
 
 
687
 
688
- jid = str(uuid.uuid4())
689
- logger.info(f"Starting job {jid} with prompt: {prompt}")
690
- print(f"{green(f'API: Starting job ID: {jid}')}")
691
 
692
- stream = AsyncStream()
693
- active_jobs[jid] = stream
694
- job_status[jid] = {"status": "rendering", "progress": 0.0, "render_time": 0}
695
 
 
696
  try:
697
- logger.debug("Processing uploaded image file")
698
- print(f"{yellow('API: Processing uploaded image file')}")
699
- img_data = await image_file.read()
700
- if not img_data:
701
- logger.error("Empty image file")
702
- print(f"{red('API: Empty image file')}")
703
- raise HTTPException(status_code=400, detail="Empty image file")
704
-
705
- try:
706
- img = Image.open(io.BytesIO(img_data)).convert('RGB')
707
- img_np = np.array(img)
708
- if img_np.shape[0] < 64 or img_np.shape[1] < 64:
709
- logger.error("Image dimensions too small")
710
- print(f"{red('API: Image dimensions too small (minimum 64x64)')}")
711
- raise HTTPException(status_code=400, detail="Image dimensions must be at least 64x64")
712
- except Exception as e:
713
- logger.error(f"Invalid image: {str(e)}")
714
- print(f"{red(f'API: Invalid image: {str(e)}')}")
715
- raise HTTPException(status_code=400, detail=f"Invalid image: {str(e)}")
716
-
717
- if get_cuda_free_memory_gb(gpu) < 2:
718
- logger.error("Insufficient VRAM for processing")
719
- print(f"{red('API: Insufficient VRAM (<2GB). Lower gpu_keep or latent_window.')}")
720
- raise HTTPException(status_code=500, detail="Low VRAM (<2GB). Lower 'gpu_keep' or 'latent_window'.")
721
-
722
- logger.info(f"Passing to worker: seed={seed}, video_length={video_length}, latent_window={latent_window}, steps={steps}, cfg={cfg}, distilled_cfg={distilled_cfg}")
723
- print(f"{yellow(f'API: Passing to worker: seed={seed}, video_length={video_length}, latent_window={latent_window}, steps={steps}, cfg={cfg}, distilled_cfg={distilled_cfg}')}")
724
-
725
- final_video_path = worker(
726
- img_np=img_np,
727
- prompt=prompt,
728
- negative_prompt=negative_prompt,
729
- seed=seed,
730
- secs=video_length,
731
- win=latent_window,
732
- stp=steps,
733
- cfg=cfg,
734
- gsc=distilled_cfg,
735
- rsc=cfg_rescale,
736
- keep=gpu_keep,
737
- tea=use_teacache,
738
- crf=crf,
739
- camera_action=camera_action,
740
- disable_prompt_mods=disable_prompt_mods,
741
- link_steps_window=link_steps_window,
742
- stream=stream,
743
- jid=jid
744
  )
745
-
746
- if final_video_path is None:
747
- logger.error("Render stopped or failed")
748
- print(f"{red('API: Render stopped or failed')}")
749
- raise HTTPException(status_code=500, detail="Render stopped or failed")
750
-
751
- final_filename = os.path.basename(final_video_path)
752
- with open(final_video_path, "rb") as f:
753
- video_data = base64.b64encode(f.read()).decode("utf-8")
754
-
755
- save_video_info(
756
- prompt=prompt,
757
- n_p=negative_prompt,
758
- filename=final_filename,
759
- seed=seed,
760
- secs=video_length,
761
- additional_info={"camera_action": camera_action, "job_id": jid},
762
- completed=True
763
- )
764
-
765
- response_info = {
766
- "status": "success",
767
- "job_id": jid,
768
- "video_data": video_data,
769
- "metadata": {
770
- "prompt": prompt,
771
- "negative_prompt": negative_prompt,
772
- "seed": seed,
773
- "duration_secs": video_length,
774
- "timestamp": time.strftime("%Y%m%d_%H%M%S"),
775
- "render_time_secs": job_status[jid]["render_time"],
776
- "camera_action": camera_action,
777
- "latent_window": latent_window,
778
- "steps": steps,
779
- "cfg": cfg,
780
- "distilled_cfg": distilled_cfg,
781
- "cfg_rescale": cfg_rescale,
782
- "gpu_keep": gpu_keep,
783
- "crf": crf,
784
- "use_teacache": use_teacache,
785
- "disable_prompt_mods": disable_prompt_mods,
786
- "link_steps_window": link_steps_window
787
- }
788
- }
789
-
790
- logger.info(f"Video generated: {final_video_path}")
791
- print(f"{green(f'API: Video generated: {final_video_path}')}")
792
- return JSONResponse(content=response_info)
793
-
794
- except Exception as e:
795
- logger.error(f"Generate failed: {e}", exc_info=True)
796
- print(f"{red(f'API: Error during /generate: {str(e)}')}")
797
- job_status[jid]["status"] = "error"
798
- job_status[jid]["progress"] = 0.0
799
- stream.output_queue.push(("end", str(e)))
800
- return JSONResponse(
801
- content={"error": str(e), "job_id": jid, "status": "error"},
802
- status_code=500
803
- )
804
- finally:
805
- active_jobs.pop(jid, None)
806
- clear_queue(stream.input_queue)
807
- clear_queue(stream.output_queue)
808
- if job_status.get(jid, {}).get("status") not in ["complete", "error", "stopped"]:
809
- job_status[jid]["status"] = "complete"
810
- torch.cuda.empty_cache()
811
-
812
- @torch.no_grad()
813
- def worker(img_np, prompt, negative_prompt, seed, secs, win, stp, cfg, gsc, rsc, keep, tea, crf, camera_action, disable_prompt_mods, link_steps_window, stream, jid):
814
- start_time = time.time()
815
- job_status[jid] = {"status": "rendering", "progress": 0.0, "render_time": 0}
816
- max_sections = 100
817
-
818
- logger.info(f"Worker started for job {jid} with secs={secs}, win={win}, cfg={cfg}, distilled_cfg={gsc}")
819
- print(f"{green(f'API: Starting video generation, job ID: {jid}, secs={secs}, win={win}, cfg={cfg}, distilled_cfg={gsc}')}")
820
-
821
- try:
822
- if img_np.shape[0] < 64 or img_np.shape[1] < 64:
823
- raise ValueError("Image dimensions too small (minimum 64x64)")
824
- if secs > 10:
825
- logger.warning("Video length > 10s capped at 10s")
826
- print(f"{yellow('API: Video length > 10s capped at 10s')}")
827
- secs = min(secs, 10)
828
- if win > 10:
829
- logger.warning("Latent window > 10 capped at 10")
830
- print(f"{yellow('API: Latent window > 10 capped at 10')}")
831
- win = min(win, 10)
832
- if get_cuda_free_memory_gb(gpu) < 2:
833
- raise ValueError("Low VRAM (<2GB). Lower 'gpu_keep' or 'latent_window'.")
834
-
835
- try:
836
- if hasattr(stream.input_queue, "qsize") and stream.input_queue.qsize() > 0:
837
- if stream.input_queue.get_nowait() == "end":
838
- stream.output_queue.push(("end", "Job stopped by client"))
839
- job_status[jid]["status"] = "stopped"
840
- return None
841
- except queue.Empty:
842
- pass
843
-
844
- if not disable_prompt_mods:
845
- if "stop" not in prompt.lower() and secs > 3:
846
- prompt += " The subject stops moving after 3 seconds."
847
- if "smooth" not in prompt.lower():
848
- prompt = f"Smooth animation: {prompt}"
849
- if "silent" not in prompt.lower():
850
- prompt += ", silent"
851
- prompt = update_prompt(prompt, camera_action)
852
- if len(prompt.split()) > 50:
853
- logger.warning("Complex prompt may slow rendering")
854
- print(f"{yellow('API: Warning: Complex prompt may slow rendering')}")
855
-
856
- try:
857
- with open(PROMPT_LOG_FILE, "a") as f:
858
- f.write(f"{jid}\t{prompt}\t{negative_prompt}\n")
859
- os.chmod(PROMPT_LOG_FILE, 0o664)
860
- except Exception as e:
861
- logger.error(f"Failed to write to {PROMPT_LOG_FILE}: {e}")
862
- print(f"{red(f'API: Failed to write prompt log: {e}')}")
863
- raise
864
-
865
- stream.output_queue.push(('progress', (None, "", make_progress_bar_html(0, "Start"))))
866
-
867
- if not hv:
868
- unload_complete_models(text_encoder, text_encoder_2, image_encoder, vae, transformer)
869
- fake_diffusers_current_device(text_encoder, gpu)
870
- load_model_as_complete(text_encoder_2, gpu)
871
- lv, cp = encode_prompt_conds(prompt, text_encoder, text_encoder_2, tokenizer, tokenizer_2)
872
- if cfg == 1:
873
- lv_n = torch.zeros_like(lv)
874
- cp_n = torch.zeros_like(cp)
875
- else:
876
- lv_n, cp_n = encode_prompt_conds(negative_prompt, text_encoder, text_encoder_2, tokenizer, tokenizer_2)
877
- lv, m = crop_or_pad_yield_mask(lv, 512)
878
- lv_n, m_n = crop_or_pad_yield_mask(lv_n, 512)
879
- lv, cp, lv_n, cp_n = [x.to(torch.bfloat16) for x in (lv, cp, lv_n, cp_n)]
880
- logger.debug(f"Prompt embeddings: lv={lv.shape}, cp={cp.shape}, lv_n={lv_n.shape}, cp_n={cp_n.shape}")
881
- torch.cuda.empty_cache()
882
-
883
- H, W, _ = img_np.shape
884
- h, w = H, W
885
- img_filename = f"{jid}.png"
886
- try:
887
- Image.fromarray(img_np).save(os.path.join(VIDEO_IMG_DIR, img_filename))
888
- os.chmod(os.path.join(VIDEO_IMG_DIR, img_filename), 0o664)
889
- except Exception as e:
890
- logger.error(f"Failed to save image {img_filename}: {e}")
891
- print(f"{red(f'API: Failed to save image: {e}')}")
892
- raise
893
-
894
- img_pt = (torch.from_numpy(img_np).float() / 127.5 - 1).permute(2, 0, 1)[None, :, None]
895
- logger.debug(f"Image tensor shape: {img_pt.shape}")
896
-
897
- if not hv:
898
- load_model_as_complete(vae, gpu)
899
- start_lat = vae_encode(img_pt, vae)
900
- logger.debug(f"VAE encoded latent shape: {start_lat.shape}")
901
- if not hv:
902
- load_model_as_complete(image_encoder, gpu)
903
- img_emb = hf_clip_vision_encode(img_np, feature_extractor, image_encoder).last_hidden_state.to(torch.bfloat16)
904
- logger.debug(f"Image embedding shape: {img_emb.shape}")
905
- torch.cuda.empty_cache()
906
-
907
- gen = torch.Generator("cpu").manual_seed(seed)
908
- sections = max(round((secs * 30) / (win * 4)), 1)
909
- if sections > max_sections:
910
- logger.error(f"Too many sections ({sections}) for job {jid}")
911
- print(f"{red(f'API: Too many sections ({sections}) for job {jid}')}")
912
- raise ValueError(f"Too many sections ({sections})")
913
- logger.info(f"Job {jid} sections: {sections}, pad_seq: {[3] + [2] * (sections - 3) + [1, 0] if sections > 4 else list(reversed(range(sections)))}")
914
- hist_lat = torch.zeros((1, 16, 1 + 2 + 16, h // 8, w // 8), dtype=torch.float16).cpu()
915
- hist_px = None
916
- total = 0
917
- pad_seq = [3] + [2] * (sections - 3) + [1, 0] if sections > 4 else list(reversed(range(sections)))
918
- section_count = 0
919
- for pad in pad_seq:
920
- section_count += 1
921
- if section_count > max_sections:
922
- logger.error(f"Max sections ({max_sections}) exceeded for job {jid}")
923
- print(f"{red(f'API: Max sections ({max_sections}) exceeded for job {jid}')}")
924
- raise ValueError(f"Max sections ({max_sections}) exceeded")
925
- last = pad == 0
926
- logger.info(f"Job {jid} processing pad: {pad}, last: {last}")
927
-
928
- def cb(d):
929
- if job_status[jid]["status"] == "complete":
930
- return
931
- pv = vae_decode_fake(d["denoised"])
932
- pv = (pv * 255).cpu().numpy().clip(0, 255).astype(np.uint8)
933
- pv = einops.rearrange(pv, "b c t h w -> (b h) (t w) c")
934
- cur = d["i"] + 1
935
- job_status[jid]["progress"] = (cur / stp) * 100
936
- progress_message = f"API: Job {jid} Progress {cur}/{stp} ({job_status[jid]['progress']:.1f}%)"
937
- logger.info(progress_message)
938
- print(yellow(progress_message))
939
- stream.output_queue.push(('progress', (pv, f"{cur}/{stp}", make_progress_bar_html(int(100 * cur / stp), f"{cur}/{stp}"))))
940
- try:
941
- if hasattr(stream.input_queue, "qsize") and stream.input_queue.qsize() > 0:
942
- if stream.input_queue.get_nowait() == "end":
943
- stream.output_queue.push(("end", "Job stopped by client"))
944
- raise KeyboardInterrupt
945
- except queue.Empty:
946
- pass
947
-
948
- idx = torch.arange(0, sum([1, pad * win, win, 1, 2, 16]))[None].to(device=gpu)
949
- a, b, c, d, e, f = idx.split([1, pad * win, win, 1, 2, 16], 1)
950
- clean_idx = torch.cat([a, d], 1)
951
- pre = start_lat.to(hist_lat)
952
- post, two, four = hist_lat[:, :, :1 + 2 + 16].split([1, 2, 16], 2)
953
- clean = torch.cat([pre, post], 2)
954
- if not hv:
955
- unload_complete_models()
956
- move_model_to_device_with_memory_preservation(transformer, gpu, keep)
957
- transformer.initialize_teacache(tea, stp)
958
- new_lat = sample_hunyuan(
959
- transformer=transformer, sampler="unipc", width=w, height=h, frames=win * 4 - 3,
960
- real_guidance_scale=cfg, distilled_guidance_scale=gsc, guidance_rescale=rsc,
961
- num_inference_steps=stp, generator=gen,
962
- prompt_embeds=lv, prompt_embeds_mask=m, prompt_poolers=cp,
963
- negative_prompt_embeds=lv_n, negative_prompt_embeds_mask=m_n, negative_prompt_poolers=cp_n,
964
- device=gpu, dtype=torch.bfloat16, image_embeddings=img_emb,
965
- latent_indices=c, clean_latents=clean, clean_latent_indices=clean_idx,
966
- clean_latents_2x=two, clean_latent_2x_indices=e,
967
- clean_latents_4x=four, clean_latent_4x_indices=f, callback=cb
968
- )
969
- if last:
970
- new_lat = torch.cat([start_lat.to(new_lat), new_lat], 2)
971
- total += new_lat.shape[2]
972
- hist_lat = torch.cat([new_lat.to(hist_lat), hist_lat], 2)
973
- if not hv:
974
- offload_model_from_device_for_memory_preservation(transformer, gpu, 8)
975
- load_model_as_complete(vae, gpu)
976
- real = hist_lat[:, :, :total]
977
- if hist_px is None:
978
- hist_px = vae_decode(real, vae).cpu()
979
- else:
980
- overlap = win * 4 - 3
981
- curr = vae_decode(real[:, :, :win * 2], vae).cpu()
982
- hist_px = soft_append_bcthw(curr, hist_px, overlap)
983
- if not hv:
984
- unload_complete_models()
985
- tmp_path = os.path.join(VIDEO_TMP_DIR, f"{jid}_{total}.mp4")
986
- save_bcthw_as_mp4(hist_px, tmp_path, fps=30, crf=crf)
987
- os.chmod(tmp_path, 0o664)
988
- stream.output_queue.push(('file', tmp_path))
989
- if last:
990
- fin_path = os.path.join(VIDEO_OUTPUT_DIR, f"{jid}_{total}.mp4")
991
- try:
992
- os.replace(tmp_path, fin_path)
993
- os.chmod(fin_path, 0o664)
994
- job_status[jid]["status"] = "complete"
995
- job_status[jid]["render_time"] = time.time() - start_time
996
- stream.output_queue.push(('complete', fin_path))
997
- clear_queue(stream.input_queue)
998
- clear_queue(stream.output_queue)
999
- logger.info(f"Final video saved: {fin_path}, render time: {job_status[jid]['render_time']:.2f}s")
1000
- print(f"{green(f'API: Final video saved: {fin_path}')}")
1001
- return fin_path
1002
- except Exception as e:
1003
- logger.error(f"Failed to save final video: {e}")
1004
- print(f"{red(f'API: Failed to save final video: {e}')}")
1005
- raise
1006
- torch.cuda.empty_cache()
1007
- except Exception as e:
1008
- logger.error(f"Worker failed: {e}", exc_info=True)
1009
- print(f"{red(f'API: Worker error: {e}')}")
1010
- traceback.print_exc()
1011
- job_status[jid]["status"] = "error"
1012
- stream.output_queue.push(("end", str(e)))
1013
- return None
1014
- finally:
1015
- if jid in active_jobs:
1016
- active_jobs.pop(jid, None)
1017
- clear_queue(stream.input_queue)
1018
- clear_queue(stream.output_queue)
1019
- if job_status.get(jid, {}).get("status") not in ["complete", "error", "stopped"]:
1020
- job_status[jid]["status"] = "complete"
1021
- torch.cuda.empty_cache()
1022
-
1023
- @torch.no_grad()
1024
- def process(img, prm, npr, sd, sec, win, stp, cfg, gsc, rsc, kee, tea, crf, disable_prompt_mods, link_steps_window):
1025
- if img is None:
1026
- raise gr.Error("Upload an image")
1027
- yield None, None, "", "", gr.update(interactive=False), gr.update(interactive=True)
1028
- stream = AsyncStream()
1029
- jid = str(uuid.uuid4())
1030
- asyncio.run(worker(img_np=img, prompt=prm, negative_prompt=npr, seed=sd, secs=sec, win=win, stp=stp, cfg=cfg, gsc=gsc, rsc=rsc, keep=kee, tea=tea, crf=crf, camera_action="Static Camera", disable_prompt_mods=disable_prompt_mods, link_steps_window=link_steps_window, stream=stream, jid=jid))
1031
- out, log = None, ""
1032
- try:
1033
- while True:
1034
- flag, data = stream.output_queue.next()
1035
- if job_status.get(jid, {}).get("status") == "complete":
1036
- break
1037
- if flag == "file":
1038
- out = data
1039
- yield out, gr.update(), gr.update(), log, gr.update(interactive=False), gr.update(interactive=True)
1040
- if flag == "progress":
1041
- pv, desc, html = data
1042
- log = desc
1043
- yield gr.update(), gr.update(visible=True, value=pv), desc, html, gr.update(interactive=False), gr.update(interactive=True)
1044
- if flag == "complete":
1045
- yield data, gr.update(visible=False), "Generation complete", "", gr.update(interactive=True), gr.update(interactive=False)
1046
- break
1047
- if flag == "end":
1048
- yield out, gr.update(visible=False), f"Error: {data}", "", gr.update(interactive=True), gr.update(interactive=False)
1049
- break
1050
  except Exception as e:
1051
- logger.error(f"Process loop failed: {e}")
1052
- yield out, gr.update(visible=False), f"Error: {str(e)}", "", gr.update(interactive=True), gr.update(interactive=False)
1053
- job_status[jid]["status"] = "error"
1054
- finally:
1055
- clear_queue(stream.input_queue)
1056
- clear_queue(stream.output_queue)
1057
- torch.cuda.empty_cache()
1058
-
1059
- def end_process():
1060
- global stream
1061
- if stream:
1062
- stream.input_queue.push("end")
1063
- logger.info("Gradio: Render stop requested")
1064
- print(f"{red('Gradio: Render stop requested')}")
1065
-
1066
- # Gradio UI (same as original)
1067
- quick_prompts = [
1068
- ["Smooth animation: A character waves for 3 seconds, then stands still for 2 seconds, static camera, silent."],
1069
- ["Smooth animation: A character moves for 5 seconds, static camera, silent."]
1070
- ]
1071
- css = make_progress_bar_css() + """
1072
- .orange-button{background:#ff6200;color:#fff;border-color:#ff6200;}
1073
- .load-button{background:#4CAF50;color:#fff;border-color:#4CAF50;margin-left:10px;}
1074
- .big-setting-button{background:#0066cc;color:#fff;border:none;padding:14px 24px;font-size:18px;width:100%;border-radius:6px;margin:8px 0;}
1075
- .styled-dropdown{width:250px;padding:5px;border-radius:4px;}
1076
- .viewer-column{width:100%;max-width:900px;margin:0 auto;}
1077
- .media-preview img,.media-preview video{max-width:100%;height:380px;object-fit:contain;border:1px solid #444;border-radius:6px;}
1078
- .media-container{display:flex;gap:20px;align-items:flex-start;}
1079
- .control-box{min-width:220px;}
1080
- .control-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
1081
- .image-gallery{display:grid!important;grid-template-columns:repeat(auto-fit,minmax(300px,1fr))!important;gap:10px;padding:10px!important;overflow-y:auto!important;max-height:360px!important;}
1082
- .image-gallery .gallery-item{padding:10px;height:360px!important;width:300px!important;}
1083
- .image-gallery img{object-fit:contain;height:360px!important;width:300px!important;}
1084
- .video-gallery{display:grid!important;grid-template-columns:repeat(auto-fit,minmax(300px,1fr))!important;gap:10px;padding:10px!important;overflow-y:auto!important;max-height:360px!important;}
1085
- .video-gallery .gallery-item{padding:10px;height:360px!important;width:300px!important;}
1086
- .video-gallery video{object-fit:contain;height:360px!important;width:300px!important;}
1087
- .stop-button {background-color: #ff4d4d !important; color: white !important;}
1088
- """
1089
-
1090
- blk = gr.Blocks(css=css, title="GhostPack F1 Pro").queue()
1091
- with blk:
1092
- gr.Markdown("# 👻 GhostPack F1 Pro")
1093
- with gr.Tabs():
1094
- with gr.TabItem("👻 Generate"):
1095
- with gr.Row():
1096
- with gr.Column():
1097
- img_in = gr.Image(sources="upload", type="numpy", label="Image", height=320)
1098
- generate_button = gr.Button("Generate Video", elem_id="generate_button")
1099
- stop_button = gr.Button("Stop Generation", elem_id="stop_button", elem_classes="stop-button")
1100
- prm = gr.Textbox(
1101
- label="Prompt",
1102
- value="Smooth animation: A female stands with subtle, sensual micro-movements, breathing gently, slight head tilt, static camera, silent",
1103
- elem_id="prompt_input",
1104
- )
1105
- npr = gr.Textbox(
1106
- label="Negative Prompt",
1107
- value="low quality, blurry, speaking, talking, moaning, vocalizing, lip movement, mouth animation, sound, dialogue, speech, whispering, shouting, lip sync, facial animation, expressive face, verbal expression, animated mouth",
1108
- elem_id="negative_prompt_input",
1109
- )
1110
- save_msg = gr.Markdown("")
1111
- disable_prompt_mods = gr.Checkbox(label="Disable Prompt Modifications", value=False)
1112
- link_steps_window = gr.Checkbox(label="Link Steps and Latent Window", value=True)
1113
- btn_save = gr.Button("Save Prompt")
1114
- btn1, btn2, btn3 = (
1115
- gr.Button("Load Most Recent"),
1116
- gr.Button("Load 2nd Recent"),
1117
- gr.Button("Load 3rd Recent"),
1118
- )
1119
- ds = gr.Dataset(samples=quick_prompts, label="Quick List", components=[prm])
1120
- ds.click(lambda x: x[0], [ds], [prm])
1121
- btn_save.click(save_prompt_fn, [prm, npr], [save_msg])
1122
- btn1.click(lambda: load_prompt_fn(0), [], [prm])
1123
- btn2.click(lambda: load_prompt_fn(1), [], [prm])
1124
- btn3.click(lambda: load_prompt_fn(2), [], [prm])
1125
- camera_action_input = gr.Dropdown(
1126
- choices=[
1127
- "Static Camera", "Slight Orbit Left", "Slight Orbit Right",
1128
- "Slight Orbit Up", "Slight Orbit Down", "Top-Down View",
1129
- "Slight Zoom In", "Slight Zoom Out",
1130
- ],
1131
- label="Camera Action",
1132
- value="Static Camera",
1133
- elem_id="camera_action_input",
1134
- info="Select a camera movement to append to the prompt.",
1135
- )
1136
- camera_action_input.change(
1137
- fn=lambda prompt, camera_action: update_prompt(prompt, camera_action),
1138
- inputs=[prm, camera_action_input],
1139
- outputs=prm,
1140
- )
1141
- with gr.Column():
1142
- pv = gr.Image(label="Next Latents", height=200, visible=False)
1143
- vid = gr.Video(label="Finished", autoplay=True, height=500, loop=True, show_share_button=False)
1144
- log_md = gr.Markdown("")
1145
- bar = gr.HTML("")
1146
- with gr.Column():
1147
- se = gr.Number(label="Seed", value=31337, precision=0, elem_id="seed_input")
1148
- sec = gr.Slider(label="Video Length (s)", minimum=1, maximum=10, value=8.0, step=0.1, elem_id="video_length_input")
1149
- win = gr.Slider(label="Latent Window", minimum=1, maximum=10, value=3, step=1, elem_id="latent_window_input")
1150
- stp = gr.Slider(label="Steps", minimum=1, maximum=100, value=12, step=1, elem_id="steps_input")
1151
- cfg = gr.Slider(label="CFG", minimum=1, maximum=32, value=1.7, step=0.01, elem_id="cfg_input")
1152
- gsc = gr.Slider(label="Distilled CFG", minimum=1, maximum=32, value=4.0, step=0.01, elem_id="distilled_cfg_input")
1153
- rsc = gr.Slider(label="CFG Re-Scale", minimum=0, maximum=1, value=0.5, step=0.01, elem_id="cfg_rescale_input")
1154
- kee = gr.Slider(label="GPU Keep (GB)", minimum=6, maximum=free_mem, value=6.5, step=0.1, elem_id="gpu_keep_input")
1155
- crf = gr.Slider(label="MP4 CRF", minimum=0, maximum=100, value=20, step=1, elem_id="mp4_crf_input")
1156
- tea = gr.Checkbox(label="Use TeaCache", value=True, elem_id="use_teacache_input")
1157
- generate_button.click(
1158
- fn=process,
1159
- inputs=[img_in, prm, npr, se, sec, win, stp, cfg, gsc, rsc, kee, tea, crf, disable_prompt_mods, link_steps_window],
1160
- outputs=[vid, pv, log_md, bar, generate_button, stop_button],
1161
- )
1162
- stop_button.click(fn=end_process)
1163
- gr.Button("Update Progress").click(fn=lambda: get_progress(), outputs=[log_md, bar])
1164
-
1165
- with gr.TabItem("🖼️ Image Gallery"):
1166
- with gr.Row(elem_classes="media-container"):
1167
- with gr.Column(scale=3):
1168
- image_preview = gr.Image(
1169
- label="Viewer", value=(list_images()[0] if list_images() else None),
1170
- interactive=False, elem_classes="media-preview",
1171
- )
1172
- with gr.Column(elem_classes="control-box"):
1173
- image_dropdown = gr.Dropdown(
1174
- choices=[os.path.basename(i) for i in list_images()],
1175
- value=(os.path.basename(list_images()[0]) if list_images() else None),
1176
- label="Select", elem_classes="styled-dropdown",
1177
- )
1178
- with gr.Row(elem_classes="control-grid"):
1179
- load_btn = gr.Button("Load", elem_classes="load-button")
1180
- next_btn = gr.Button("Next", elem_classes="load-button")
1181
- with gr.Row(elem_classes="control-grid"):
1182
- refresh_btn = gr.Button("Refresh")
1183
- delete_btn = gr.Button("Delete", elem_classes="orange-button")
1184
- image_gallery = gr.Gallery(
1185
- value=list_images(), label="Thumbnails", columns=6, height=360,
1186
- allow_preview=False, type="filepath", elem_classes="image-gallery",
1187
- )
1188
- load_btn.click(load_image, [image_dropdown], [image_preview, image_dropdown])
1189
- next_btn.click(next_image_and_load, [image_dropdown], [image_preview, image_dropdown])
1190
- refresh_btn.click(
1191
- lambda: (
1192
- gr.update(choices=[os.path.basename(i) for i in list_images()], value=os.path.basename(list_images()[0]) if list_images() else None),
1193
- gr.update(value=list_images()[0] if list_images() else None),
1194
- gr.update(value=list_images()),
1195
- ),
1196
- [], [image_dropdown, image_preview, image_gallery],
1197
- )
1198
- delete_btn.click(
1199
- lambda sel: (
1200
- os.remove(os.path.join(VIDEO_IMG_DIR, sel)) if sel and os.path.exists(os.path.join(VIDEO_IMG_DIR, sel)) else None
1201
- ) or load_image(""),
1202
- [image_dropdown], [image_preview, image_dropdown],
1203
- )
1204
- image_gallery.select(gallery_image_select, [], [image_preview, image_dropdown])
1205
-
1206
- with gr.TabItem("🎬 Video Gallery"):
1207
- with gr.Row(elem_classes="media-container"):
1208
- with gr.Column(scale=3):
1209
- video_preview = gr.Video(
1210
- label="Viewer", value=(list_videos()[0] if list_videos() else None),
1211
- autoplay=True, loop=True, interactive=False, elem_classes="media-preview",
1212
- )
1213
- with gr.Column(elem_classes="control-box"):
1214
- video_dropdown = gr.Dropdown(
1215
- choices=[os.path.basename(v) for v in list_videos()],
1216
- value=(os.path.basename(list_videos()[0]) if list_videos() else None),
1217
- label="Select", elem_classes="styled-dropdown",
1218
- )
1219
- with gr.Row(elem_classes="control-grid"):
1220
- load_vbtn = gr.Button("Load", elem_classes="load-button")
1221
- next_vbtn = gr.Button("Next", elem_classes="load-button")
1222
- with gr.Row(elem_classes="control-grid"):
1223
- refresh_v = gr.Button("Refresh")
1224
- delete_v = gr.Button("Delete", elem_classes="orange-button")
1225
- video_gallery = gr.Gallery(
1226
- value=list_videos(), label="Thumbnails", columns=6, height=360,
1227
- allow_preview=False, type="filepath", elem_classes="video-gallery",
1228
- )
1229
- load_vbtn.click(load_video, [video_dropdown], [video_preview, video_dropdown])
1230
- next_vbtn.click(next_video_and_load, [video_dropdown], [video_preview, video_dropdown])
1231
- refresh_v.click(
1232
- lambda: (
1233
- gr.update(choices=[os.path.basename(v) for v in list_videos()], value=os.path.basename(list_videos()[0]) if list_videos() else None),
1234
- gr.update(value=list_videos()[0] if list_videos() else None),
1235
- gr.update(value=list_videos()),
1236
- ),
1237
- [], [video_dropdown, video_preview, video_gallery],
1238
- )
1239
- delete_v.click(
1240
- lambda sel: (
1241
- os.remove(os.path.join(VIDEO_OUTPUT_DIR, sel)) if sel and os.path.exists(os.path.join(VIDEO_OUTPUT_DIR, sel)) else None
1242
- ) or load_video(""),
1243
- [video_dropdown], [video_preview, video_dropdown],
1244
- )
1245
- video_gallery.select(gallery_video_select, [], [video_preview, video_dropdown])
1246
-
1247
- with gr.TabItem("👻 About"):
1248
- gr.Markdown("## GhostPack F1 Pro")
1249
- with gr.Row():
1250
- with gr.Column():
1251
- gr.Markdown("**🛠️ Description**\nImage-to-Video toolkit powered by HunyuanVideo & FramePack-F1")
1252
- with gr.Column():
1253
- gr.Markdown(f"**📦 Version**\n{VERSION}")
1254
- with gr.Column():
1255
- gr.Markdown("**✍️ Author**\nGhostAI")
1256
- with gr.Column():
1257
- gr.Markdown("**🔗 Repo**\nhttps://huggingface.co/spaces/ghostai1/ghostvidspace")
1258
-
1259
- with gr.TabItem("⚙️ Settings"):
1260
- ct = gr.Button("Clear Temp", elem_classes="big-setting-button")
1261
- ctmsg = gr.Markdown("")
1262
- co = gr.Button("Clear Old", elem_classes="big-setting-button")
1263
- comsg = gr.Markdown("")
1264
- ci = gr.Button("Clear Images", elem_classes="big-setting-button")
1265
- cimg = gr.Markdown("")
1266
- cv = gr.Button("Clear Videos", elem_classes="big-setting-button")
1267
- cvid = gr.Markdown("")
1268
- ct.click(clear_temp_videos, [], ctmsg)
1269
- co.click(clear_old_files, [], comsg)
1270
- ci.click(clear_images, [], cimg)
1271
- cv.click(clear_videos, [], cvid)
1272
-
1273
- with gr.TabItem("🛠️ Install"):
1274
- xs = gr.Textbox(value=status_xformers(), interactive=False, label="xformers")
1275
- bx = gr.Button("Install xformers", elem_classes="big-setting-button")
1276
- ss = gr.Textbox(value=status_sage(), interactive=False, label="sage-attn")
1277
- bs = gr.Button("Install sage-attn", elem_classes="big-setting-button")
1278
- fs = gr.Textbox(value=status_flash(), interactive=False, label="flash-attn")
1279
- bf = gr.Button("Install flash-attn", elem_classes="big-setting-button")
1280
- cs = gr.Textbox(value=status_colorama(), interactive=False, label="colorama")
1281
- bc = gr.Button("Install colorama", elem_classes="big-setting-button")
1282
- bx.click(install_xformers, [], xs)
1283
- bs.click(install_sage_attn, [], ss)
1284
- bf.click(install_flash_attn, [], fs)
1285
- bc.click(install_colorama, [], cs)
1286
-
1287
- with gr.TabItem("📜 Logs"):
1288
- logs = gr.Textbox(lines=20, interactive=False, label="Install Logs")
1289
- rl = gr.Button("Refresh", elem_classes="big-setting-button")
1290
- cl = gr.Button("Clear", elem_classes="big-setting-button")
1291
- rl.click(refresh_logs, [], logs)
1292
- cl.click(clear_logs, [], logs)
1293
-
1294
- gr.HTML(
1295
- """
1296
- <script>
1297
- document.querySelectorAll('.video-gallery video').forEach(v => {
1298
- v.addEventListener('loadedmetadata', () => {
1299
- if (v.duration > 2) v.currentTime = 2;
1300
- });
1301
- });
1302
- </script>
1303
- """
1304
- )
1305
-
1306
- def update_prompt(prompt, camera_action):
1307
- camera_actions = [
1308
- "static camera", "slight camera orbit left", "slight camera orbit right",
1309
- "slight camera orbit up", "slight camera orbit down", "top-down view",
1310
- "slight camera zoom in", "slight camera zoom out",
1311
- ]
1312
- for action in camera_actions:
1313
- prompt = re.sub(rf",\s*{re.escape(action)}\b", "", prompt, flags=re.IGNORECASE).strip()
1314
- if camera_action and camera_action != "None":
1315
- camera_phrase = f", {camera_action.lower()}"
1316
- if len(prompt.split()) + len(camera_phrase.split()) <= 50:
1317
- return prompt + camera_phrase
1318
- else:
1319
- logger.warning(f"Prompt exceeds 50 words after adding camera action: {prompt}")
1320
- print(f"{yellow(f'API: Warning: Prompt exceeds 50 words with camera action')}")
1321
- return prompt
1322
-
1323
- def get_progress():
1324
- return f"Status: {job_status.get('latest', {'status': 'idle'})['status']}\nProgress: {job_status.get('latest', {'progress': 0.0})['progress']:.1f}%\nLast Render Time: {job_status.get('latest', {'render_time': 0})['render_time']:.1f}s"
1325
 
1326
- # Check for port conflicts
1327
- if is_port_in_use(args.port):
1328
- logger.error(f"Port {args.port} is already in use")
1329
- print(f"{red(f'Error: Port {args.port} is already in use. Please stop other instances or change ports.')}")
 
 
 
 
 
 
1330
  sys.exit(1)
1331
 
1332
- # Run FastAPI and optional Gradio
1333
- def run_api():
1334
- try:
1335
- logger.info(f"Starting FastAPI on {args.server}:{args.port}")
1336
- print(f"{green(f'Starting FastAPI on {args.server}:{args.port}')}")
1337
- uvicorn.run(app, host=args.server, port=args.port)
1338
- except Exception as e:
1339
- logger.error(f"Failed to start FastAPI: {e}", exc_info=True)
1340
- print(f"{red(f'Error: Failed to start FastAPI: {e}')}")
1341
- sys.exit(1)
1342
-
1343
- if __name__ == "__main__":
1344
- try:
1345
- logger.info(f"Starting GhostPack F1 Pro Server version {VERSION}")
1346
- print(f"Starting GhostPack F1 Pro Server version {VERSION}")
1347
- api_thread = Thread(target=run_api)
1348
- api_thread.daemon = True
1349
- api_thread.start()
1350
- time.sleep(5)
1351
- try:
1352
- response = requests.get(f"http://{args.server}:{args.port}/health", timeout=10)
1353
- if response.status_code != 200:
1354
- raise RuntimeError("FastAPI health check failed")
1355
- logger.info("FastAPI health check passed")
1356
- print(f"{green('FastAPI health check passed')}")
1357
- except Exception as e:
1358
- logger.error(f"FastAPI not ready: {e}")
1359
- print(f"{red(f'Error: FastAPI not ready: {e}')}")
1360
- sys.exit(1)
1361
 
1362
- if args.gradio:
1363
- logger.info(f"Starting Gradio UI on {args.server}:7860")
1364
- print(f"{green(f'Starting Gradio UI on {args.server}:7860')}")
1365
- server = blk.launch(
1366
- server_name=args.server,
1367
- server_port=7860,
1368
- share=args.share,
1369
- inbrowser=args.inbrowser,
1370
- prevent_thread_lock=True,
1371
- allowed_paths=["/"]
1372
- )
1373
- if args.share and server.share_url:
1374
- logger.info(f"Public Gradio URL: {server.share_url}")
1375
- print(f"{yellow(f'Public Gradio URL: {server.share_url}')}")
1376
- logger.info(f"Gradio UI running on http://{args.server}:7860")
1377
- print
 
1
  #!/usr/bin/env python3
2
+ # FILE: install_deps.py
3
+ # Description: Installs all dependencies and downloads models for GhostPack F1 Pro on Hugging Face Spaces with H200 GPU
4
+ # Version: 1.0.0
5
+ # Timestamp: 2025-07-02 04:20 CDT
6
+ # Author: Grok 3, built by xAI
7
+ # NOTE: Installs PyTorch 2.4.1 with CUDA 12.1, dependencies, and downloads models to /data/models
8
+ # Requires HF_TOKEN set as environment variable or secret
9
+ # Run this before app.py
10
+ # Logs to /data/install_deps.log
 
 
 
 
11
 
12
  import os
13
  import sys
 
 
 
 
14
  import subprocess
 
 
 
 
 
 
 
 
 
 
 
15
  import logging
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  # Set up logging
18
  logging.basicConfig(
19
+ filename="/data/install_deps.log",
20
  level=logging.DEBUG,
21
  format="%(asctime)s %(levelname)s:%(message)s",
22
  )
23
  logger = logging.getLogger(__name__)
 
 
 
 
 
 
 
 
24
 
25
+ # Create log directory
26
+ os.makedirs("/data", exist_ok=True)
27
+ os.chmod("/data", 0o775)
28
+ logger.info("Starting dependency installation and model download")
29
 
30
+ # Function to run shell commands
31
+ def run_command(command, error_message):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  try:
33
+ result = subprocess.run(command, check=True, capture_output=True, text=True)
34
+ logger.info(f"Command succeeded: {' '.join(command)}\n{result.stdout}")
35
+ print(f"Command succeeded: {' '.join(command)}")
36
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  except subprocess.CalledProcessError as e:
38
+ logger.error(f"{error_message}: {e}\n{e.stderr}")
39
+ print(f"{error_message}: {e}")
40
+ sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ # Update pip
43
+ logger.info("Upgrading pip...")
44
+ run_command(
45
+ [sys.executable, "-m", "pip", "install", "--upgrade", "pip"],
46
+ "ERROR: Failed to upgrade pip"
 
 
 
47
  )
48
 
49
+ # Install PyTorch with CUDA 12.1
50
+ logger.info("Installing PyTorch 2.4.1 with CUDA 12.1...")
51
+ run_command(
52
+ [sys.executable, "-m", "pip", "install", "torch==2.4.1+cu121", "--index-url", "https://download.pytorch.org/whl/cu121"],
53
+ "ERROR: Failed to install PyTorch"
54
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
+ # Install dependencies
57
+ logger.info("Installing required dependencies...")
58
+ with open("requirements.txt", "w") as f:
59
+ f.write("""transformers==4.44.2
60
+ fastapi==0.115.0
61
+ uvicorn==0.30.6
62
+ gradio==4.44.0
63
+ python-multipart==0.0.9
64
+ diffusers==0.30.3
65
+ pydantic==2.9.2
66
+ einops==0.8.0
67
+ numpy==1.26.4
68
+ pillow==10.4.0
69
+ requests==2.32.3
70
+ colorama==0.4.6
71
+ # Note: diffusers_helper must be included as a folder in the Space's root directory (/diffusers_helper)
72
+ """)
73
+ run_command(
74
+ [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
75
+ "ERROR: Failed to install dependencies"
76
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ # Install optional dependencies
79
+ logger.info("Installing optional dependencies (xformers, sage-attn, flash-attn)...")
80
+ for pkg, warn in [
81
+ ("xformers", "WARNING: Failed to install xformers"),
82
+ ("sage-attn", "WARNING: Failed to install sage-attn"),
83
+ ("flash-attn", "WARNING: Failed to install flash-attn")
84
+ ]:
85
+ try:
86
+ run_command([sys.executable, "-m", "pip", "install", pkg], warn)
87
+ except SystemExit:
88
+ logger.warning(warn)
89
+ print(warn)
90
+
91
+ # Download models
92
+ logger.info("Downloading models to /data/models...")
93
+ MODEL_DIR = "/data/models"
94
+ HF_TOKEN = os.getenv('HF_TOKEN', 'your-hf-token') # Set in Spaces secrets
95
 
96
+ os.makedirs(MODEL_DIR, exist_ok=True)
97
+ os.chmod(MODEL_DIR, 0o775)
98
+ logger.info(f"Created model directory: {MODEL_DIR}")
99
 
100
+ from transformers import LlamaModel, CLIPTextModel, LlamaTokenizerFast, CLIPTokenizer, SiglipImageProcessor, SiglipVisionModel
101
+ from diffusers import AutoencoderKLHunyuanVideo
102
+ from diffusers_helper.models.hunyuan_video_packed import HunyuanVideoTransformer3DModelPacked
103
 
104
+ def download_model(model_class, model_name, subfolder=None, **kwargs):
105
  try:
106
+ logger.info(f"Downloading {model_name} (subfolder: {subfolder}) to {MODEL_DIR}")
107
+ model = model_class.from_pretrained(
108
+ model_name,
109
+ subfolder=subfolder,
110
+ token=HF_TOKEN,
111
+ cache_dir=MODEL_DIR,
112
+ local_files_only=False,
113
+ **kwargs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  )
115
+ logger.info(f"Successfully downloaded {model_name} (subfolder: {subfolder})")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  except Exception as e:
117
+ logger.error(f"Failed to download {model_name} (subfolder: {subfolder}): {e}")
118
+ print(f"Error: Failed to download {model_name} (subfolder: {subfolder}): {e}")
119
+ sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
+ # Download HunyuanVideo components
122
+ try:
123
+ download_model(LlamaModel, "hunyuanvideo-community/HunyuanVideo", subfolder="text_encoder", torch_dtype=torch.float16)
124
+ download_model(CLIPTextModel, "hunyuanvideo-community/HunyuanVideo", subfolder="text_encoder_2", torch_dtype=torch.float16)
125
+ download_model(LlamaTokenizerFast, "hunyuanvideo-community/HunyuanVideo", subfolder="tokenizer")
126
+ download_model(CLIPTokenizer, "hunyuanvideo-community/HunyuanVideo", subfolder="tokenizer_2")
127
+ download_model(AutoencoderKLHunyuanVideo, "hunyuanvideo-community/HunyuanVideo", subfolder="vae", torch_dtype=torch.float16)
128
+ except Exception as e:
129
+ logger.error(f"Failed to download HunyuanVideo components: {e}")
130
+ print(f"Error: Failed to download HunyuanVideo components: {e}")
131
  sys.exit(1)
132
 
133
+ # Download FramePack components
134
+ try:
135
+ download_model(SiglipImageProcessor, "lllyasviel/flux_redux_bfl", subfolder="feature_extractor")
136
+ download_model(SiglipVisionModel, "lllyasviel/flux_redux_bfl", subfolder="image_encoder", torch_dtype=torch.float16)
137
+ download_model(HunyuanVideoTransformer3DModelPacked, "lllyasviel/FramePack_F1_I2V_HY_20250503", torch_dtype=torch.bfloat16)
138
+ except Exception as e:
139
+ logger.error(f"Failed to download FramePack components: {e}")
140
+ print(f"Error: Failed to download FramePack components: {e}")
141
+ sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
+ logger.info("Dependency installation and model download completed successfully")
144
+ print("Dependency installation and model download completed successfully")