daKhosa commited on
Commit
4bd6143
·
1 Parent(s): 499128d

Add 10Eros Space (TenStrip/LTX2.3-10Eros layer-scaled merge)

Browse files
10Eros/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
10Eros/README.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: LTX2.3-10Eros
3
+ emoji: 🔥
4
+ colorFrom: orange
5
+ colorTo: red
6
+ sdk: gradio
7
+ sdk_version: "6.14.0"
8
+ python_version: "3.12"
9
+ app_file: app.py
10
+ pinned: false
11
+ license: mit
12
+ ---
13
+
14
+ # 10Eros — Image to Video
15
+
16
+ Upload an image and prompt, and 10Eros will animate it into a short video clip.
17
+
18
+ **Model**
19
+ - **10Eros** — LTX-2.3 layer-scaled merge built on Sulphur-2-base
20
+
21
+ **Tips**
22
+ - Landscape images work best at 832×480; portrait at 480×832
23
+ - Keep prompts focused on motion (e.g. "hair blowing in the wind, slow pan")
24
+ - 8 steps is fast; raise to 20–30 for finer detail
10Eros/app.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 10Eros — Image to Video (HF Spaces).
3
+ Clones Wan2GP and downloads models on first run.
4
+ Generation is handled by generate.py called as a subprocess inside @spaces.GPU.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import subprocess
10
+ import shutil
11
+ import tempfile
12
+ import threading
13
+ import json
14
+ from pathlib import Path
15
+
16
+ import gradio as gr
17
+ import spaces
18
+
19
+ _HF_TOKEN = os.environ.get("HF_TOKEN")
20
+ _PERSISTENT = Path("/data") if Path("/data").exists() else Path(tempfile.gettempdir())
21
+ WAN2GP_ROOT = _PERSISTENT / "Wan2GP"
22
+ CKPTS_DIR = WAN2GP_ROOT / "ckpts"
23
+ LORAS_DIR = WAN2GP_ROOT / "loras" / "ltx2"
24
+ FINETUNES_DIR = WAN2GP_ROOT / "finetunes"
25
+ GENERATE_PY = Path(__file__).parent / "generate.py"
26
+
27
+ MODEL_ASSETS = [
28
+ ("TenStrip/LTX2.3-10Eros", "10Eros_v1_bf16.safetensors", CKPTS_DIR),
29
+ ]
30
+ LTX_ASSETS = [
31
+ ("SulphurAI/Sulphur-2-base", "distill_loras/ltx-2.3-22b-distilled-lora-1.1_fro90_ceil72_condsafe.safetensors", LORAS_DIR),
32
+ ("DeepBeepMeep/LTX-2", "ltx-2.3-22b_vae.safetensors", CKPTS_DIR),
33
+ ("DeepBeepMeep/LTX-2", "ltx-2.3-22b_text_embedding_projection.safetensors", CKPTS_DIR),
34
+ ("DeepBeepMeep/LTX-2", "ltx-2.3-22b_embeddings_connector.safetensors", CKPTS_DIR),
35
+ ]
36
+
37
+ MODEL_FINETUNE = {
38
+ "model": {
39
+ "name": "10Eros",
40
+ "visible": True,
41
+ "architecture": "ltx2_22B",
42
+ "parent_model_type": "ltx2_22B",
43
+ "description": "LTX-2.3 layer-scaled merge i2v. Built on Sulphur-2-base.",
44
+ "URLs": [str(CKPTS_DIR / "10Eros_v1_bf16.safetensors")],
45
+ "preload_URLs": [],
46
+ },
47
+ "num_inference_steps": 8,
48
+ "video_length": 81,
49
+ "resolution": "832x480",
50
+ "guidance_scale": 3.5,
51
+ "alt_guidance_scale": 3.5,
52
+ }
53
+
54
+ _setup_lock = threading.Lock()
55
+ _setup_done = False
56
+
57
+
58
+ def _download(repo_id, filename, dest_dir):
59
+ from huggingface_hub import hf_hub_download
60
+ dest_dir.mkdir(parents=True, exist_ok=True)
61
+ dest = dest_dir / Path(filename).name # flat — strip any subfolder
62
+ if dest.exists():
63
+ print(f"[download] cached: {dest.name}")
64
+ return
65
+ print(f"[download] {repo_id}/{filename}")
66
+ hf_hub_download(repo_id=repo_id, filename=filename,
67
+ local_dir=str(dest_dir), token=_HF_TOKEN)
68
+ # hf_hub_download preserves subfolder structure; flatten to dest_dir root
69
+ downloaded = dest_dir / filename
70
+ if downloaded.exists() and not dest.exists():
71
+ shutil.move(str(downloaded), str(dest))
72
+
73
+
74
+ def setup():
75
+ global _setup_done
76
+ with _setup_lock:
77
+ if _setup_done:
78
+ return
79
+ _setup_done = True
80
+
81
+ if not (WAN2GP_ROOT / "shared" / "api.py").exists():
82
+ WAN2GP_ROOT.mkdir(parents=True, exist_ok=True)
83
+ print("[setup] Cloning Wan2GP...")
84
+ subprocess.run(
85
+ ["git", "clone", "--depth=1",
86
+ "https://github.com/deepbeepmeep/Wan2GP.git", str(WAN2GP_ROOT)],
87
+ check=True,
88
+ )
89
+
90
+ for repo, fname, dest in MODEL_ASSETS + LTX_ASSETS:
91
+ _download(repo, fname, dest)
92
+
93
+ # Gemma text encoder — must stay in its subfolder (Wan2GP looks there by name)
94
+ _gemma_folder = "gemma-3-12b-it-qat-q4_0-unquantized"
95
+ _gemma_file = f"{_gemma_folder}_quanto_bf16_int8.safetensors"
96
+ gemma_dest = CKPTS_DIR / _gemma_folder / _gemma_file
97
+ if not gemma_dest.exists():
98
+ from huggingface_hub import hf_hub_download
99
+ print("[download] Gemma text encoder...")
100
+ hf_hub_download(
101
+ repo_id="DeepBeepMeep/LTX-2",
102
+ filename=f"{_gemma_folder}/{_gemma_file}",
103
+ local_dir=str(CKPTS_DIR),
104
+ token=_HF_TOKEN,
105
+ )
106
+ else:
107
+ print("[download] cached: Gemma text encoder")
108
+
109
+ FINETUNES_DIR.mkdir(parents=True, exist_ok=True)
110
+ (FINETUNES_DIR / "ten_eros.json").write_text(json.dumps(MODEL_FINETUNE, indent=2))
111
+ print("[setup] Done.")
112
+
113
+
114
+ setup()
115
+
116
+ RESOLUTIONS = ["832x480", "480x832", "640x640", "1024x576", "576x1024"]
117
+
118
+
119
+ @spaces.GPU(duration=120)
120
+ def generate_video(image, prompt, resolution, steps, guidance_scale, frames, seed):
121
+ if image is None:
122
+ raise gr.Error("Please upload an image.")
123
+ if not prompt.strip():
124
+ raise gr.Error("Please enter a prompt.")
125
+
126
+ out_file = Path(tempfile.mkdtemp()) / "output.mp4"
127
+ env = {**os.environ, "WAN2GP_ROOT": str(WAN2GP_ROOT)}
128
+
129
+ cmd = [
130
+ sys.executable, str(GENERATE_PY),
131
+ "--image", image,
132
+ "--prompt", prompt,
133
+ "--output", str(out_file),
134
+ "--model", "10eros",
135
+ "--seed", str(int(seed)),
136
+ "--resolution", resolution,
137
+ "--steps", str(int(steps)),
138
+ "--guidance_scale", str(float(guidance_scale)),
139
+ "--frames", str(int(frames)),
140
+ ]
141
+
142
+ log_lines = []
143
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
144
+ text=True, bufsize=0, env=env)
145
+
146
+ buf = ""
147
+ while True:
148
+ chunk = proc.stdout.read(256)
149
+ if not chunk:
150
+ break
151
+ buf += chunk
152
+ # Split on \r or \n — tqdm uses \r to overwrite progress lines
153
+ parts = buf.replace("\r", "\n").split("\n")
154
+ buf = parts[-1]
155
+ for part in parts[:-1]:
156
+ stripped = part.strip()
157
+ if not stripped:
158
+ continue
159
+ # Overwrite last line if it looks like a progress bar update
160
+ if log_lines and ("%" in stripped or "it/s" in stripped or "step" in stripped.lower()):
161
+ log_lines[-1] = stripped
162
+ else:
163
+ log_lines.append(stripped)
164
+ print(stripped)
165
+ yield None, "\n".join(log_lines[-30:])
166
+
167
+ proc.wait()
168
+ log = "\n".join(log_lines)
169
+
170
+ if proc.returncode != 0 or not out_file.exists():
171
+ yield None, log + "\n\n[ERROR] Generation failed."
172
+ return
173
+
174
+ final = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
175
+ shutil.copy2(out_file, final.name)
176
+ yield final.name, log + "\n\n[DONE]"
177
+
178
+
179
+ with gr.Blocks(title="10Eros — Image to Video") as demo:
180
+ gr.Markdown("# 10Eros — Image to Video\nPowered by Wan2GP · LTX-2.3 layer-scaled merge")
181
+ with gr.Row():
182
+ with gr.Column(scale=1):
183
+ image_in = gr.Image(type="filepath", label="Input Image")
184
+ prompt_in = gr.Textbox(label="Prompt", placeholder="Describe the motion…", lines=3)
185
+ with gr.Accordion("Advanced", open=False):
186
+ resolution_dd = gr.Dropdown(RESOLUTIONS, value="832x480", label="Resolution")
187
+ steps_sl = gr.Slider(1, 50, value=8, step=1, label="Steps")
188
+ guidance_sl = gr.Slider(1.0, 10.0, value=5.0, step=0.5, label="Guidance Scale")
189
+ frames_sl = gr.Slider(17, 257, value=81, step=8, label="Frames")
190
+ seed_num = gr.Number(value=-1, label="Seed (-1 = random)", precision=0)
191
+ run_btn = gr.Button("Generate", variant="primary")
192
+ with gr.Column(scale=1):
193
+ video_out = gr.Video(label="Output Video")
194
+ log_out = gr.Textbox(label="Log", lines=10, interactive=False)
195
+
196
+ run_btn.click(
197
+ fn=generate_video,
198
+ inputs=[image_in, prompt_in, resolution_dd, steps_sl, guidance_sl, frames_sl, seed_num],
199
+ outputs=[video_out, log_out],
200
+ )
201
+
202
+ if __name__ == "__main__":
203
+ demo.launch(theme=gr.themes.Soft())
10Eros/generate.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ HF Spaces version of generate.py — same logic, paths adapted for /tmp/Wan2GP.
3
+ Called as a subprocess from app.py inside @spaces.GPU.
4
+ """
5
+
6
+ import argparse
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ WAN2GP_ROOT = Path(os.environ.get("WAN2GP_ROOT", "/tmp/Wan2GP"))
12
+
13
+ MODEL_SHORTHANDS = {
14
+ "10eros": "ten_eros",
15
+ }
16
+
17
+ DEFAULTS = {
18
+ "ten_eros": {
19
+ "num_inference_steps": 8,
20
+ "guidance_scale": 5.0,
21
+ "resolution": "832x480",
22
+ "video_length": 81,
23
+ },
24
+ }
25
+
26
+
27
+ def p(*args, **kwargs):
28
+ print(*args, **kwargs, flush=True)
29
+
30
+
31
+ def parse_args():
32
+ ap = argparse.ArgumentParser()
33
+ ap.add_argument("--image", required=True)
34
+ ap.add_argument("--prompt", required=True)
35
+ ap.add_argument("--output", required=True)
36
+ ap.add_argument("--model", default="10eros")
37
+ ap.add_argument("--steps", type=int, default=None)
38
+ ap.add_argument("--guidance_scale", type=float, default=None)
39
+ ap.add_argument("--frames", type=int, default=None)
40
+ ap.add_argument("--resolution", default=None)
41
+ ap.add_argument("--seed", type=int, default=-1)
42
+ return ap.parse_args()
43
+
44
+
45
+ def main():
46
+ args = parse_args()
47
+
48
+ model_type = MODEL_SHORTHANDS.get(args.model, args.model)
49
+ defaults = DEFAULTS.get(model_type, DEFAULTS["ten_eros"])
50
+
51
+ image_path = str(Path(args.image.strip()).resolve())
52
+ if not Path(image_path).exists():
53
+ print(f"Fatal: image not found: {image_path}", flush=True)
54
+ sys.exit(1)
55
+
56
+ resolution = args.resolution or defaults["resolution"]
57
+ if not args.resolution:
58
+ try:
59
+ from PIL import Image as _PIL
60
+ img = _PIL.open(image_path)
61
+ iw, ih = img.size
62
+ if ih > iw:
63
+ tw = 480
64
+ th = round(ih / iw * tw / 32) * 32
65
+ else:
66
+ th = 480
67
+ tw = round(iw / ih * th / 32) * 32
68
+ resolution = f"{tw}x{th}"
69
+ p(f"Auto-detected resolution: {resolution} (from {iw}x{ih} input)")
70
+ except Exception:
71
+ pass
72
+
73
+ task = {
74
+ "model_type": model_type,
75
+ "base_model_type": model_type,
76
+ "prompt": args.prompt,
77
+ "image_start": image_path,
78
+ "num_inference_steps": args.steps or defaults["num_inference_steps"],
79
+ "guidance_scale": args.guidance_scale or defaults["guidance_scale"],
80
+ "resolution": resolution,
81
+ "video_length": args.frames or defaults["video_length"],
82
+ "seed": args.seed,
83
+ "image_prompt_type": "S",
84
+ "input_video_strength": 1.0,
85
+ "activated_loras": [
86
+ "ltx-2.3-22b-distilled-lora-1.1_fro90_ceil72_condsafe.safetensors",
87
+ ],
88
+ "loras_multipliers": ["0.5"],
89
+ }
90
+
91
+ p(f"Model: {model_type}")
92
+ p(f"Image: {image_path}")
93
+ p(f"Steps: {task['num_inference_steps']} Guidance: {task['guidance_scale']}")
94
+ p(f"Resolution: {task['resolution']} Frames: {task['video_length']}")
95
+ p(f"Prompt: {args.prompt[:80]}")
96
+
97
+ sys.path.insert(0, str(WAN2GP_ROOT))
98
+ os.chdir(WAN2GP_ROOT)
99
+
100
+ from shared.api import WanGPSession
101
+
102
+ output_dir = Path(args.output).parent
103
+ output_dir.mkdir(parents=True, exist_ok=True)
104
+
105
+ p("Starting session...")
106
+ session = WanGPSession(root=WAN2GP_ROOT, output_dir=output_dir, console_output=True)
107
+
108
+ p("Running generation...")
109
+ result = session.run_task(task)
110
+
111
+ output_file = None
112
+
113
+ if result.artifacts:
114
+ src = result.artifacts[0].path
115
+ if src and Path(src).exists():
116
+ output_file = src
117
+
118
+ # Fallback: scan the output dir for any video file Wan2GP may have written
119
+ if output_file is None:
120
+ candidates = sorted(output_dir.glob("**/*.mp4"), key=lambda f: f.stat().st_mtime, reverse=True)
121
+ if candidates:
122
+ output_file = str(candidates[0])
123
+ p(f"Found output via dir scan: {output_file}")
124
+
125
+ if output_file:
126
+ import shutil
127
+ shutil.copy2(output_file, args.output)
128
+ p(f"Done: {args.output}")
129
+ else:
130
+ p(f"No output found in {output_dir}")
131
+ if result.errors:
132
+ p(f"Errors: {result.errors}")
133
+ sys.exit(1)
134
+
135
+ session.close()
136
+
137
+
138
+ if __name__ == "__main__":
139
+ main()
10Eros/requirements.txt ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HF Spaces runtime
2
+ gradio>=6.0.0
3
+ spaces
4
+ huggingface_hub>=0.24.0
5
+
6
+ # Wan2GP deps (from requirements.txt — all packages that install on Linux)
7
+ mmgp==3.7.6
8
+ diffusers==0.36.0
9
+ transformers==4.54.0
10
+ tokenizers>=0.20.3
11
+ accelerate>=1.1.1
12
+ tqdm
13
+ imageio
14
+ imageio-ffmpeg
15
+ einops
16
+ sentencepiece
17
+ open_clip_torch>=2.29.0
18
+ numpy==2.1.2
19
+ num2words==0.5.14
20
+ moviepy==1.0.3
21
+ av
22
+ ffmpeg-python
23
+ pygame>=2.1.0
24
+ sounddevice>=0.4.0
25
+ soundfile
26
+ mutagen
27
+ pyloudnorm
28
+ librosa==0.11.0
29
+ speechbrain==1.0.3
30
+ openai-whisper==20250625
31
+ audio-separator==0.36.1
32
+ pyannote.audio==3.3.2
33
+ loguru
34
+ dashscope
35
+ s3tokenizer
36
+ conformer==0.3.2
37
+ spacy_pkuseg
38
+ spacy
39
+ opencv-python-headless>=4.12.0.88
40
+ pycocotools
41
+ segment-anything
42
+ rembg==2.0.65
43
+ onnxruntime>=1.20.0
44
+ decord
45
+ timm
46
+ iopath>=0.1.10
47
+ insightface==0.7.3
48
+ facexlib==0.3.0
49
+ vector_quantize_pytorch==1.27.19
50
+ omegaconf
51
+ hydra-core
52
+ easydict
53
+ torchdiffeq>=0.2.5
54
+ tensordict>=0.6.1
55
+ peft==0.17.0
56
+ vector-quantize-pytorch
57
+ matplotlib
58
+ gguf==0.17.1
59
+ flash-linear-attention==0.4.1
60
+ ftfy
61
+ piexif
62
+ nvidia-ml-py
63
+ misaki
64
+ gitdb==4.0.12
65
+ gitpython==3.1.45
66
+ stringzilla==4.0.14
67
+ xxhash
68
+ munch
69
+ wetext==0.1.2
70
+ markdown
71
+ Pillow
72
+ safetensors
73
+ https://github.com/deepbeepmeep/smplfitter/releases/download/v0.2.10/smplfitter-0.2.10-py3-none-any.whl
74
+ https://github.com/deepbeepmeep/chumpy/releases/download/v0.71/chumpy-0.71-py3-none-any.whl