pinder99 commited on
Commit
69939f9
·
verified ·
1 Parent(s): 97f3ebe

Build Sulphur 2 Base demo

Browse files
Files changed (4) hide show
  1. .gitignore +4 -0
  2. README.md +30 -7
  3. app.py +174 -0
  4. requirements.txt +7 -0
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .DS_Store
4
+ work/
README.md CHANGED
@@ -1,13 +1,36 @@
1
  ---
2
- title: Sulphur 2 Base Demo
3
- emoji: 📚
4
- colorFrom: blue
5
- colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.20.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Sulphur 2 Base
3
+ emoji: 🎬
4
+ colorFrom: purple
5
+ colorTo: pink
6
  sdk: gradio
7
  sdk_version: 6.20.0
 
8
  app_file: app.py
9
+ short_description: Generate short videos with Sulphur 2 Base
10
+ python_version: "3.12"
11
+ startup_duration_timeout: 1h
12
+ models:
13
+ - SulphurAI/Sulphur-2-base
14
+ - diffusers/LTX-2.3-Diffusers
15
+ tags:
16
+ - text-to-video
17
+ - ltx-2
18
+ - zero-gpu
19
  ---
20
 
21
+ # Sulphur 2 Base demo
22
+
23
+ Generate short, synchronized audio-video clips with
24
+ [SulphurAI/Sulphur-2-base](https://huggingface.co/SulphurAI/Sulphur-2-base),
25
+ a fine-tune of Lightricks LTX 2.3.
26
+
27
+ The demo uses the repository's FP8 mixed development checkpoint with the
28
+ upstream LTX 2.3 Diffusers components. It runs a compact 512×320, 49-frame
29
+ preset to keep interactive generation practical.
30
+
31
+ ## Responsible use
32
+
33
+ Do not use this demo to create sexual content, content involving minors,
34
+ non-consensual intimate imagery, graphic violence, or deceptive impersonation.
35
+ Generated media may contain artifacts and should not be treated as factual.
36
+
app.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import tempfile
4
+ import time
5
+
6
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
7
+
8
+ import spaces
9
+ import torch
10
+ import gradio as gr
11
+ from diffusers import AutoModel, LTX2Pipeline
12
+ from diffusers.pipelines.ltx2.export_utils import encode_video
13
+ from diffusers.pipelines.ltx2.utils import DEFAULT_NEGATIVE_PROMPT
14
+
15
+
16
+ MODEL_ID = "SulphurAI/Sulphur-2-base"
17
+ BASE_MODEL_ID = "diffusers/LTX-2.3-Diffusers"
18
+ CHECKPOINT_URL = (
19
+ "https://huggingface.co/SulphurAI/Sulphur-2-base/"
20
+ "blob/main/sulphur_dev_fp8mixed.safetensors"
21
+ )
22
+
23
+ WIDTH = 512
24
+ HEIGHT = 320
25
+ NUM_FRAMES = 49
26
+ FPS = 24.0
27
+
28
+ # A deliberately conservative public-demo guard. The upstream checkpoint is
29
+ # described as uncensored, but a public demo should not generate abuse content.
30
+ BLOCKED_PATTERNS = (
31
+ r"\b(child|children|kid|minor|underage|teen(?:ager)?)\b.{0,50}"
32
+ r"\b(nude|naked|sex|sexual|explicit|porn)\b",
33
+ r"\b(nude|naked|sex|sexual|explicit|porn)\b.{0,50}"
34
+ r"\b(child|children|kid|minor|underage|teen(?:ager)?)\b",
35
+ r"\b(rape|sexual assault|non[- ]consensual|revenge porn|csam)\b",
36
+ r"\b(gore|dismemberment|beheading|graphic violence)\b",
37
+ )
38
+
39
+
40
+ def _allowed(prompt: str) -> bool:
41
+ text = prompt.casefold()
42
+ return not any(re.search(pattern, text) for pattern in BLOCKED_PATTERNS)
43
+
44
+
45
+ transformer = AutoModel.from_single_file(
46
+ CHECKPOINT_URL,
47
+ torch_dtype=torch.bfloat16,
48
+ )
49
+ pipe = LTX2Pipeline.from_pretrained(
50
+ BASE_MODEL_ID,
51
+ transformer=transformer,
52
+ torch_dtype=torch.bfloat16,
53
+ ).to("cuda")
54
+ pipe.vae.enable_tiling()
55
+
56
+
57
+ def _duration(prompt: str, seed: int, steps: int, *args, **kwargs) -> int:
58
+ del prompt, seed, args, kwargs
59
+ return min(300, 90 + int(steps) * 9)
60
+
61
+
62
+ @spaces.GPU(duration=_duration, size="xlarge")
63
+ def generate(prompt: str, seed: int, steps: int) -> tuple[str, str]:
64
+ """Generate a short 512×320 video with synchronized audio from a text prompt."""
65
+ prompt = (prompt or "").strip()
66
+ if len(prompt) < 8:
67
+ raise gr.Error("Please enter a more descriptive prompt.")
68
+ if len(prompt) > 1_500:
69
+ raise gr.Error("Please keep the prompt under 1,500 characters.")
70
+ if not _allowed(prompt):
71
+ raise gr.Error(
72
+ "This public demo cannot process sexual, exploitative, or graphic-violence prompts."
73
+ )
74
+
75
+ started = time.perf_counter()
76
+ generator = torch.Generator(device="cuda").manual_seed(int(seed))
77
+ video, audio = pipe(
78
+ prompt=prompt,
79
+ negative_prompt=DEFAULT_NEGATIVE_PROMPT,
80
+ width=WIDTH,
81
+ height=HEIGHT,
82
+ num_frames=NUM_FRAMES,
83
+ frame_rate=FPS,
84
+ num_inference_steps=int(steps),
85
+ guidance_scale=3.0,
86
+ generator=generator,
87
+ output_type="np",
88
+ return_dict=False,
89
+ )
90
+
91
+ output = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
92
+ output.close()
93
+ encode_video(
94
+ video[0],
95
+ fps=FPS,
96
+ audio=audio[0].float().cpu(),
97
+ audio_sample_rate=pipe.vocoder.config.output_sampling_rate,
98
+ output_path=output.name,
99
+ )
100
+ elapsed = time.perf_counter() - started
101
+ return output.name, f"Finished in {elapsed:.1f}s · seed {int(seed)}"
102
+
103
+
104
+ CSS = """
105
+ .gradio-container { max-width: 1120px !important; }
106
+ .hero { text-align: center; margin: 1.5rem auto 1rem; }
107
+ .hero h1 { font-size: clamp(2rem, 5vw, 4rem); margin-bottom: .2rem; }
108
+ .hero p { color: #a1a1aa; font-size: 1.05rem; }
109
+ """
110
+
111
+ with gr.Blocks(css=CSS, theme=gr.themes.Soft(primary_hue="purple")) as demo:
112
+ gr.HTML(
113
+ """
114
+ <div class="hero">
115
+ <h1>🎬 Sulphur 2 Base</h1>
116
+ <p>Text-to-video with synchronized audio, powered by LTX 2.3.</p>
117
+ </div>
118
+ """
119
+ )
120
+ with gr.Row():
121
+ with gr.Column(scale=5):
122
+ prompt = gr.Textbox(
123
+ label="Describe your shot",
124
+ placeholder=(
125
+ "A cinematic tracking shot of a tiny moss-covered robot "
126
+ "walking through a rain-soaked neon market..."
127
+ ),
128
+ lines=7,
129
+ max_lines=12,
130
+ )
131
+ with gr.Row():
132
+ seed = gr.Number(label="Seed", value=42, precision=0)
133
+ steps = gr.Slider(
134
+ label="Inference steps", minimum=12, maximum=30, value=20, step=1
135
+ )
136
+ run = gr.Button("Generate video", variant="primary", size="lg")
137
+ gr.Markdown(
138
+ "Public demo guardrails apply. Avoid sexual, exploitative, "
139
+ "graphic, or deceptive content."
140
+ )
141
+ with gr.Column(scale=7):
142
+ video = gr.Video(label="Generated clip", autoplay=True)
143
+ status = gr.Markdown()
144
+
145
+ gr.Examples(
146
+ examples=[
147
+ [
148
+ "A macro cinematic shot of a glass terrarium at dawn. A tiny "
149
+ "clockwork hummingbird unfolds its brass wings, dew glints on "
150
+ "fern leaves, soft mechanical clicks and distant birdsong."
151
+ ],
152
+ [
153
+ "A wide aerial shot over black volcanic sand at blue hour. "
154
+ "Bioluminescent waves roll ashore under a star-filled sky, with "
155
+ "wind and gentle surf in the soundtrack."
156
+ ],
157
+ [
158
+ "Stop-motion style: a paper astronaut plants a small sunflower "
159
+ "on a handcrafted moon, warm studio lighting, subtle paper "
160
+ "rustling and a whimsical music-box melody."
161
+ ],
162
+ ],
163
+ inputs=[prompt],
164
+ cache_examples=False,
165
+ )
166
+ run.click(
167
+ fn=generate,
168
+ inputs=[prompt, seed, steps],
169
+ outputs=[video, status],
170
+ api_name="generate",
171
+ )
172
+
173
+ demo.queue(default_concurrency_limit=1).launch(mcp_server=True)
174
+
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ git+https://github.com/huggingface/diffusers.git
2
+ transformers>=4.57.0
3
+ accelerate>=1.10.0
4
+ safetensors>=0.6.2
5
+ sentencepiece>=0.2.1
6
+ imageio-ffmpeg>=0.6.0
7
+