AlterProgramming commited on
Commit
6486052
·
verified ·
1 Parent(s): 4cf35cb

Upload studio/backends/hf_space.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. studio/backends/hf_space.py +77 -51
studio/backends/hf_space.py CHANGED
@@ -1,21 +1,4 @@
1
- """HF Spaces backend adapter (free ZeroGPU A100 via `gradio_client`).
2
-
3
- Local CLI client that calls a deployed Hugging Face Space hosting the studio's
4
- inference pipeline. The Space provides the GPU; the local machine needs only
5
- `gradio_client` + a HF token. No torch, no diffusers, no model downloads locally.
6
-
7
- Token resolution order (most-specific wins):
8
- 1. explicit `hf_token=` param
9
- 2. `HF_TOKEN` env var
10
- 3. `huggingface_hub.HfFolder.get_token()` (reads ~/.cache/huggingface/token)
11
-
12
- The deployed Space is expected to expose a Gradio function (default api_name
13
- `/infer`) with the signature:
14
- (image_pil, preset_name, num_frames, num_inference_steps, guidance_scale,
15
- prompt, negative_prompt, seed) -> video_filepath
16
-
17
- Pair this with the host-mode `app.py` at the repo root for end-to-end ownership.
18
- """
19
  from __future__ import annotations
20
  from dataclasses import dataclass
21
  import os
@@ -65,7 +48,6 @@ class HFSpaceAdapter:
65
  def bind_motion_exemplar(
66
  self, cursor: PixelCursor, exemplar: Sequence[str]
67
  ) -> PixelCursor:
68
- """Same preset semantics as AnimateDiff; the Space carries the same MotionLoRA map."""
69
  if len(exemplar) != 1:
70
  raise ValueError(
71
  "hf_space.bind_motion_exemplar expects exactly one preset name."
@@ -108,10 +90,6 @@ class HFSpaceAdapter:
108
  else "NONE"
109
  ),
110
  "gradio_client_installed": _gradio_client_available(),
111
- "note": (
112
- "Anonymous calls work but quota is tight; an HF token raises the limit. "
113
- "ZeroGPU cold-start ~10-30s; subsequent calls ~5-15s warm."
114
- ),
115
  }
116
 
117
  def write_motion(self, cursor: PixelCursor, motion_spec: dict | None = None) -> FrameStack:
@@ -152,26 +130,63 @@ class HFSpaceAdapter:
152
  return _video_path_to_framestack(video_path, preset)
153
 
154
 
155
- def _video_path_to_framestack(video_path: str | Path, preset: str) -> FrameStack:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  try:
157
- import imageio.v3 as iio
158
  except ImportError as e:
159
- raise ImportError(
160
- "Loading the Space's video response requires imageio. Install with:\n"
161
- " pip install -e '.[hf]'"
162
- ) from e
163
- frames = iio.imread(str(video_path))
164
- if frames.ndim != 4:
165
- raise ValueError(f"unexpected video shape from Space: {frames.shape}")
166
- return _new_framestack(frames.astype(np.uint8), fps=8, name=f"hf_space:{preset}")
167
 
 
 
168
 
169
- def _gradio_client_available() -> bool:
170
- try:
171
- import gradio_client # noqa: F401
172
- return True
173
- except ImportError:
174
- return False
 
 
 
 
 
 
 
175
 
176
 
177
  def generate_sprite_via_space(
@@ -188,18 +203,7 @@ def generate_sprite_via_space(
188
  hf_token: Optional[str] = None,
189
  api_name: str = "/infer_txt2img",
190
  ) -> PILImage.Image:
191
- """Call the Space's txt2img endpoint and return the generated sprite.
192
-
193
- This is a free-standing helper (not bound to the cursor/motion abstraction).
194
- A sprite is a fresh artifact, not a transform — wrapping it in a PixelCursor
195
- would add ceremony without benefit.
196
-
197
- Prompt should include the PixelArtRedmond trigger words "pixel art, PixArFK"
198
- to engage the loaded LoRA. lora_weight scales LoRA strength (0.0 disables,
199
- 1.5 is the upper end; default 0.9).
200
-
201
- Raises ImportError if gradio_client is missing locally.
202
- """
203
  try:
204
  from gradio_client import Client
205
  except ImportError as e:
@@ -219,3 +223,25 @@ def generate_sprite_via_space(
219
  api_name=api_name,
220
  )
221
  return PILImage.open(png_path).convert("RGB")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HF Spaces backend adapter extended with LTX-Video I2V endpoint."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  from __future__ import annotations
3
  from dataclasses import dataclass
4
  import os
 
48
  def bind_motion_exemplar(
49
  self, cursor: PixelCursor, exemplar: Sequence[str]
50
  ) -> PixelCursor:
 
51
  if len(exemplar) != 1:
52
  raise ValueError(
53
  "hf_space.bind_motion_exemplar expects exactly one preset name."
 
90
  else "NONE"
91
  ),
92
  "gradio_client_installed": _gradio_client_available(),
 
 
 
 
93
  }
94
 
95
  def write_motion(self, cursor: PixelCursor, motion_spec: dict | None = None) -> FrameStack:
 
130
  return _video_path_to_framestack(video_path, preset)
131
 
132
 
133
+ def generate_headlocked_via_space(
134
+ image_path: str,
135
+ *,
136
+ space_id: str,
137
+ prompt: str,
138
+ negative_prompt: str = (
139
+ "head movement, swaying, bobbing, nodding, camera shake, "
140
+ "zoom, pan, jump cut, cartoon, deformed, blurry"
141
+ ),
142
+ height: int = 576,
143
+ width: int = 320,
144
+ num_frames: int = 121,
145
+ num_inference_steps: int = 25,
146
+ guidance_scale: float = 3.0,
147
+ seed: int = 42,
148
+ hf_token: Optional[str] = None,
149
+ api_name: str = "/infer_ltx_i2v",
150
+ ) -> str:
151
+ """Call the Space's LTX I2V endpoint and return the path to the generated mp4.
152
+
153
+ Designed for hologram head-locked clip generation. Default params:
154
+ - 576×320 portrait (9:16-ish, both div-32, confirmed working on A10G)
155
+ - 121 frames = 5.04s @ 24fps (8*15+1)
156
+ - guidance_scale=3.0 (LTX-Video optimal range is 2-4)
157
+
158
+ The Space enforces div-32 and 8k+1 constraints internally — caller values
159
+ are rounded up, not rejected.
160
+
161
+ Example:
162
+ path = generate_headlocked_via_space(
163
+ "/path/to/chancellor-li-hq-smoothLIGHT-2144x3840.jpg",
164
+ space_id="AlterProgramming/venture-studio",
165
+ prompt="East-Asian man, 30s, dark navy suit ... head absolutely still ...",
166
+ )
167
+ # path is a local mp4 file → copy to v2-compatible/ and run motion_grammar
168
+ """
169
  try:
170
+ from gradio_client import Client, handle_file
171
  except ImportError as e:
172
+ raise ImportError(INSTALL_HINT) from e
 
 
 
 
 
 
 
173
 
174
+ token = _resolve_hf_token(hf_token)
175
+ client = Client(space_id, token=token)
176
 
177
+ result = client.predict(
178
+ handle_file(image_path),
179
+ prompt,
180
+ negative_prompt,
181
+ float(height),
182
+ float(width),
183
+ float(num_frames),
184
+ float(num_inference_steps),
185
+ float(guidance_scale),
186
+ float(seed),
187
+ api_name=api_name,
188
+ )
189
+ return result if isinstance(result, str) else result[0]
190
 
191
 
192
  def generate_sprite_via_space(
 
203
  hf_token: Optional[str] = None,
204
  api_name: str = "/infer_txt2img",
205
  ) -> PILImage.Image:
206
+ """Call the Space's txt2img endpoint and return the generated sprite."""
 
 
 
 
 
 
 
 
 
 
 
207
  try:
208
  from gradio_client import Client
209
  except ImportError as e:
 
223
  api_name=api_name,
224
  )
225
  return PILImage.open(png_path).convert("RGB")
226
+
227
+
228
+ def _video_path_to_framestack(video_path: str | Path, preset: str) -> FrameStack:
229
+ try:
230
+ import imageio.v3 as iio
231
+ except ImportError as e:
232
+ raise ImportError(
233
+ "Loading the Space's video response requires imageio. Install with:\n"
234
+ " pip install -e '.[hf]'"
235
+ ) from e
236
+ frames = iio.imread(str(video_path))
237
+ if frames.ndim != 4:
238
+ raise ValueError(f"unexpected video shape from Space: {frames.shape}")
239
+ return _new_framestack(frames.astype(np.uint8), fps=8, name=f"hf_space:{preset}")
240
+
241
+
242
+ def _gradio_client_available() -> bool:
243
+ try:
244
+ import gradio_client # noqa: F401
245
+ return True
246
+ except ImportError:
247
+ return False