multimodalart HF Staff commited on
Commit
c2c6e5f
·
1 Parent(s): cd5f627

[Admin maintenance] Migrate grant to ZeroGPU (#113)

Browse files

- [Admin maintenance] Migrate grant to ZeroGPU (f1408b83b70e1dabef03d3ff7b51a23d40260ab8)

Files changed (6) hide show
  1. Dockerfile +0 -32
  2. README.md +5 -2
  3. app.py +245 -230
  4. requirements.txt +13 -2
  5. run.sh +0 -44
  6. utils/gradio_helpers.py +0 -469
Dockerfile DELETED
@@ -1,32 +0,0 @@
1
- FROM r8.im/fofr/expression-editor@sha256:bf913bc90e1c44ba288ba3942a538693b72e8cc7df576f3beebe56adc0a92b86
2
- RUN apt-get update && apt-get install -y netcat jq
3
-
4
- RUN useradd -m -u 1000 user
5
- RUN chown -R user:user / || true
6
- RUN chown -R user:user /src/
7
- RUN chown -R user:user /root/
8
- RUN chown -R user:user /var/
9
- USER user
10
- ENV HOME=/home/user \
11
- PATH=/home/user/.local/bin:$PATH \
12
- PYTHONPATH=$HOME/app \
13
- PYTHONUNBUFFERED=1 \
14
- GRADIO_ALLOW_FLAGGING=never \
15
- GRADIO_NUM_PORTS=1 \
16
- GRADIO_SERVER_NAME=0.0.0.0 \
17
- GRADIO_THEME=huggingface \
18
- SYSTEM=spaces
19
-
20
- WORKDIR $HOME/app
21
- #COPY ./requirements.txt /code/requirements.txt
22
-
23
- # create virtual env for Gradio app
24
- RUN python -m venv $HOME/.venv && \
25
- . $HOME/.venv/bin/activate && \
26
- pip install --no-cache-dir --upgrade pip && \
27
- pip install --no-cache-dir gradio==6.12.0 prance
28
-
29
-
30
- COPY --chown=user . $HOME/app
31
- RUN chmod +x $HOME/app/run.sh
32
- CMD ["bash", "-c", "$HOME/app/run.sh"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -3,10 +3,13 @@ title: Expression Editor
3
  emoji: 🐨
4
  colorFrom: indigo
5
  colorTo: red
6
- sdk: docker
 
 
 
7
  pinned: true
8
  disable_embedding: true
9
  short_description: Quickly edit the expression of a face
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
3
  emoji: 🐨
4
  colorFrom: indigo
5
  colorTo: red
6
+ sdk: gradio
7
+ sdk_version: 6.14.0
8
+ python_version: '3.12'
9
+ app_file: app.py
10
  pinned: true
11
  disable_embedding: true
12
  short_description: Quickly edit the expression of a face
13
  ---
14
 
15
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -1,266 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
 
2
  from PIL import Image
3
- from urllib.parse import urlparse
4
- import requests
5
- import time
6
- import os
7
 
8
- from utils.gradio_helpers import parse_outputs, process_outputs
9
-
10
- # Function to verify the image file type and resize it if necessary
11
- def preprocess_image(image_path):
12
- # Check if the file exists
13
- if not os.path.exists(image_path):
14
- raise FileNotFoundError(f"No such file: '{image_path}'")
15
-
16
- # Get the file extension and make sure it's a valid image format
17
- valid_extensions = ['jpg', 'jpeg', 'png', 'webp']
18
- file_extension = image_path.split('.')[-1].lower()
19
-
20
- if file_extension not in valid_extensions:
21
- raise ValueError("Invalid file type. Only JPG, PNG, and WEBP are allowed.")
22
-
23
- # Open the image
24
- with Image.open(image_path) as img:
25
- width, height = img.size
26
-
27
- # Check if any dimension exceeds 1024 pixels
28
- if width > 1024 or height > 1024:
29
- # Calculate the new size while maintaining aspect ratio
30
- if width > height:
31
- new_width = 1024
32
- new_height = int((new_width / width) * height)
33
- else:
34
- new_height = 1024
35
- new_width = int((new_height / height) * width)
36
-
37
- # Resize the image
38
- img_resized = img.resize((new_width, new_height), Image.LANCZOS)
39
- print(f"Resized image to {new_width}x{new_height}.")
40
-
41
- # Save the resized image as 'resized_image.jpg'
42
- output_path = 'resized_image.jpg'
43
- img_resized.save(output_path, 'JPEG')
44
- print(f"Resized image saved as {output_path}")
45
- return output_path
46
- else:
47
- print("Image size is within the limit, no resizing needed.")
48
- return image_path
49
-
50
-
51
- def display_uploaded_image(image_in):
52
- return image_in
53
 
54
- def reset_parameters():
55
- return gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0)
56
-
57
- names = ['image', 'rotate_pitch', 'rotate_yaw', 'rotate_roll', 'blink', 'eyebrow', 'wink', 'pupil_x', 'pupil_y', 'aaa', 'eee', 'woo', 'smile', 'src_ratio', 'sample_ratio', 'crop_factor', 'output_format', 'output_quality']
58
-
59
- def predict(request: gr.Request, *args, progress=gr.Progress(track_tqdm=True)):
60
- headers = {'Content-Type': 'application/json'}
61
-
62
- payload = {"input": {}}
63
- base_url = "http://0.0.0.0:7860"
64
-
65
- for i, key in enumerate(names):
66
- value = args[i]
67
- if value and os.path.exists(str(value)):
68
- value = f"{base_url}/gradio_api/file=" + value
69
- if value is not None and value != "":
70
- payload["input"][key] = value
71
-
72
- time.sleep(1.0)
73
- response = requests.post("http://0.0.0.0:5000/predictions", headers=headers, json=payload)
74
-
75
- if response.status_code == 201:
76
- follow_up_url = response.json()["urls"]["get"]
77
-
78
- # ⏰ Timeout logic
79
- max_wait_seconds = 60
80
- poll_interval = 1
81
- start_time = time.time()
82
-
83
- while True:
84
- response = requests.get(follow_up_url, headers=headers)
85
- try:
86
- response_json = response.json()
87
- except ValueError:
88
- raise gr.Error("Cog server response is not valid JSON.")
89
-
90
- status = response_json.get("status")
91
- if status == "succeeded":
92
- break
93
- if status == "failed":
94
- raise gr.Error("The submission failed.")
95
- if time.time() - start_time > max_wait_seconds:
96
- raise gr.Error("Prediction timed out after 60 seconds.")
97
- time.sleep(poll_interval)
98
-
99
- if response.status_code == 200:
100
- json_response = response.json()
101
- if outputs[0].get_config()["name"] == "json":
102
- return json_response["output"]
103
-
104
- predict_outputs = parse_outputs(json_response["output"])
105
- processed_outputs = process_outputs(predict_outputs)
106
- print(f"processed_outputs: {processed_outputs}")
107
- return tuple(processed_outputs) if len(processed_outputs) > 1 else processed_outputs[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  else:
109
- if response.status_code == 409:
110
- raise gr.Error("Sorry, the Cog image is still processing. Try again in a bit.")
111
- raise gr.Error(f"The submission failed! Error: {response.status_code}")
112
 
113
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
- css = '''
116
- #col-container{max-width: 800px;margin: 0 auto;}
117
- '''
118
- with gr.Blocks() as demo:
 
119
  with gr.Column(elem_id="col-container"):
120
  gr.Markdown("# Expression Editor")
121
- gr.Markdown("Demo for expression-editor cog image by fofr")
 
122
  with gr.Row():
123
  with gr.Column():
124
- image = gr.Image(
125
  label="Input image",
126
  sources=["upload"],
127
- type="filepath"
128
  )
 
129
  with gr.Tab("HEAD"):
130
  with gr.Column():
131
- rotate_pitch = gr.Slider(
132
- label="Rotate Up-Down",
133
- value=0,
134
- minimum=-20, maximum=20
135
- )
136
- rotate_yaw = gr.Slider(
137
- label="Rotate Left-Right turn",
138
- value=0,
139
- minimum=-20, maximum=20
140
- )
141
- rotate_roll = gr.Slider(
142
- label="Rotate Left-Right tilt", value=0,
143
- minimum=-20, maximum=20
144
- )
145
  with gr.Tab("EYES"):
146
  with gr.Column():
147
- eyebrow = gr.Slider(
148
- label="Eyebrow", value=0,
149
- minimum=-10, maximum=15
150
- )
151
  with gr.Row():
152
- blink = gr.Slider(
153
- label="Blink", value=0,
154
- minimum=-20, maximum=5
155
- )
156
-
157
- wink = gr.Slider(
158
- label="Wink", value=0,
159
- minimum=0, maximum=25
160
- )
161
  with gr.Row():
162
- pupil_x = gr.Slider(
163
- label="Pupil X", value=0,
164
- minimum=-15, maximum=15
165
- )
166
- pupil_y = gr.Slider(
167
- label="Pupil Y", value=0,
168
- minimum=-15, maximum=15
169
- )
170
  with gr.Tab("MOUTH"):
171
  with gr.Column():
172
  with gr.Row():
173
- aaa = gr.Slider(
174
- label="Aaa", value=0,
175
- minimum=-30, maximum=120
176
- )
177
- eee = gr.Slider(
178
- label="Eee", value=0,
179
- minimum=-20, maximum=15
180
- )
181
- woo = gr.Slider(
182
- label="Woo", value=0,
183
- minimum=-20, maximum=15
184
- )
185
- smile = gr.Slider(
186
- label="Smile", value=0,
187
- minimum=-0.3, maximum=1.3
188
- )
189
  with gr.Tab("More Settings"):
190
  with gr.Column():
191
- src_ratio = gr.Number(
192
- label="Src Ratio", info='''Source ratio''', value=1
193
- )
194
- sample_ratio = gr.Slider(
195
- label="Sample Ratio", info='''Sample ratio''', value=1,
196
- minimum=-0.2, maximum=1.2
197
- )
198
- crop_factor = gr.Slider(
199
- label="Crop Factor", info='''Crop factor''', value=1.7,
200
- minimum=1.5, maximum=2.5
201
- )
202
- output_format = gr.Dropdown(
203
- choices=['webp', 'jpg', 'png'], label="output_format", info='''Format of the output images''', value="webp"
204
- )
205
- output_quality = gr.Number(
206
- label="Output Quality", info='''Quality of the output images, from 0 to 100. 100 is best quality, 0 is lowest quality.''', value=95
207
  )
 
 
208
  with gr.Row():
209
  reset_btn = gr.Button("Reset")
210
- submit_btn = gr.Button("Submit")
 
211
  with gr.Column():
212
- result_image = gr.Image(elem_id="top")
213
- gr.HTML("""
214
- <div style="display: flex; flex-direction: column;justify-content: center; align-items: center; text-align: center;">
215
- <p style="display: flex;gap: 6px;">
216
- <a href="https://huggingface.co/spaces/fffiloni/expression-editor?duplicate=true">
217
- <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-lg.svg" alt="Duplicate this Space">
218
- </a>
219
- </p>
220
- <p>to skip the queue and enjoy faster inference on the GPU of your choice </p>
221
- </div>
222
- """)
223
-
224
- inputs = [image, rotate_pitch, rotate_yaw, rotate_roll, blink, eyebrow, wink, pupil_x, pupil_y, aaa, eee, woo, smile, src_ratio, sample_ratio, crop_factor, output_format, output_quality]
225
  outputs = [result_image]
226
 
227
- image.upload(
228
- fn = preprocess_image,
229
- inputs = [image],
230
- outputs = [image],
231
- queue = False
 
232
  )
233
 
234
  reset_btn.click(
235
- fn = reset_parameters,
236
- inputs = None,
237
- outputs = [rotate_pitch, rotate_yaw, rotate_roll, blink, eyebrow, wink, pupil_x, pupil_y, aaa, eee, woo, smile],
238
- queue = False
239
- ).then(
240
- fn=predict,
241
- inputs=inputs,
242
- outputs=outputs,
243
- api_visibility="private"
244
- )
245
-
246
- submit_btn.click(
247
- fn=predict,
248
- inputs=inputs,
249
- outputs=outputs,
250
- api_visibility="private"
251
- )
 
 
 
252
 
253
- rotate_pitch.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
254
- rotate_yaw.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
255
- rotate_roll.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
256
- blink.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
257
- eyebrow.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
258
- wink.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
259
- pupil_x.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
260
- pupil_y.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
261
- aaa.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
262
- eee.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
263
- woo.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
264
- smile.release(fn=predict, inputs=inputs, outputs=outputs, show_progress="minimal", api_visibility="private")
265
-
266
- demo.queue(default_concurrency_limit=1, max_size=20).launch(css=css, share=False, show_error=True, ssr_mode=False)
 
1
+ """
2
+ Expression Editor on ZeroGPU.
3
+
4
+ Vendored from fofr/cog-expression-editor's underlying ComfyUI workflow
5
+ (LoadImage -> ExpressionEditor) but executed without ComfyUI: we clone the
6
+ PowerHouseMan/ComfyUI-AdvancedLivePortrait node at startup and stub the
7
+ two ComfyUI internals it imports (`folder_paths` and `comfy.utils`).
8
+
9
+ Weights auto-download to ./models on first run via the node's own loader
10
+ (Kijai/LivePortrait_safetensors + Bingsu/adetailer for the YOLO bbox).
11
+ """
12
+ import os
13
+ import sys
14
+ import types
15
+ import subprocess
16
+
17
+ # ------------------------------------------------------------------
18
+ # 1. Pull the custom node + stub ComfyUI internals BEFORE importing it
19
+ # ------------------------------------------------------------------
20
+
21
+ # Use a Python-identifier-legal directory name so we can import it as a
22
+ # package (the repo's `nodes.py` uses relative imports like
23
+ # `from .LivePortrait...` which only work inside a real package).
24
+ CUSTOM_NODE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "advanced_live_portrait")
25
+ if not os.path.exists(CUSTOM_NODE_DIR):
26
+ subprocess.check_call([
27
+ "git", "clone", "--depth=1",
28
+ "https://github.com/PowerHouseMan/ComfyUI-AdvancedLivePortrait.git",
29
+ CUSTOM_NODE_DIR,
30
+ ])
31
+
32
+ # Writable paths the node expects
33
+ MODELS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models")
34
+ TEMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp")
35
+ os.makedirs(os.path.join(MODELS_DIR, "liveportrait"), exist_ok=True)
36
+ os.makedirs(os.path.join(MODELS_DIR, "ultralytics"), exist_ok=True)
37
+ os.makedirs(TEMP_DIR, exist_ok=True)
38
+
39
+ # Minimal `folder_paths` shim
40
+ _fp = types.ModuleType("folder_paths")
41
+ _fp.models_dir = MODELS_DIR
42
+ _fp.output_directory = os.path.join(os.path.dirname(os.path.abspath(__file__)), "outputs")
43
+ os.makedirs(_fp.output_directory, exist_ok=True)
44
+ _fp.get_folder_paths = lambda m: [os.path.join(MODELS_DIR, m)]
45
+ _fp.get_save_image_path = lambda f, d, *a, **k: (d, f, 0, "", f)
46
+ _fp.get_temp_directory = lambda: TEMP_DIR
47
+ _fp.add_model_folder_path = lambda *a, **k: None
48
+ sys.modules["folder_paths"] = _fp
49
+
50
+ # Minimal `comfy.utils` shim
51
+ import torch
52
+ import safetensors.torch
53
+
54
+ _comfy = types.ModuleType("comfy")
55
+ _comfy_utils = types.ModuleType("comfy.utils")
56
+
57
+ def _load_torch_file(ckpt, *args, **kwargs):
58
+ s = str(ckpt)
59
+ if s.endswith(".safetensors"):
60
+ return safetensors.torch.load_file(s)
61
+ return torch.load(s, map_location="cpu", weights_only=False)
62
+ _comfy_utils.load_torch_file = _load_torch_file
63
+
64
+ class _ProgressBar:
65
+ def __init__(self, *a, **k):
66
+ pass
67
+ def update(self, *a, **k):
68
+ pass
69
+ def update_absolute(self, *a, **k):
70
+ pass
71
+ _comfy_utils.ProgressBar = _ProgressBar
72
+
73
+ _comfy.utils = _comfy_utils # attach as attribute too — `import comfy.utils` then `comfy.utils.X` needs both sys.modules and attribute access
74
+ sys.modules["comfy"] = _comfy
75
+ sys.modules["comfy.utils"] = _comfy_utils
76
+
77
+ # ------------------------------------------------------------------
78
+ # 2. Now import the node
79
+ # ------------------------------------------------------------------
80
+
81
+ # Parent dir is already on the path (it's the app's CWD); import as a package
82
+ import spaces
83
  import gradio as gr
84
+ import numpy as np
85
  from PIL import Image
 
 
 
 
86
 
87
+ from advanced_live_portrait.nodes import ExpressionEditor, g_engine # noqa: E402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
+ # Preload pipeline + face detector at module scope so they land in the
90
+ # ZeroGPU snapshot. ZeroGPU forks per @spaces.GPU call; with the snapshot,
91
+ # the models are already resident in GPU memory on the worker and inference
92
+ # starts immediately. Loading them lazily inside the decorated function
93
+ # would re-download / re-init on every cold worker.
94
+ print("Preloading LivePortrait pipeline + YOLO detector for ZeroGPU snapshot...")
95
+ g_engine.get_pipeline() # downloads + .to('cuda') the 5 LivePortrait modules
96
+ g_engine.get_detect_model() # downloads + loads YOLO face bbox model
97
+ print("Preload done.")
98
+
99
+ # Single global editor (state cached across calls)
100
+ _editor = ExpressionEditor()
101
+
102
+
103
+ def _pil_to_node_tensor(img: Image.Image) -> torch.Tensor:
104
+ """ComfyUI image tensors are [N, H, W, C] float32 in [0, 1]."""
105
+ if img.mode != "RGB":
106
+ img = img.convert("RGB")
107
+ arr = np.array(img, dtype=np.float32) / 255.0
108
+ return torch.from_numpy(arr).unsqueeze(0)
109
+
110
+
111
+ def _node_tensor_to_pil(t: torch.Tensor) -> Image.Image:
112
+ arr = (t.squeeze(0).detach().cpu().numpy() * 255).clip(0, 255).astype(np.uint8)
113
+ return Image.fromarray(arr)
114
+
115
+
116
+ @spaces.GPU(duration=60)
117
+ def edit_expression(
118
+ image, rotate_pitch, rotate_yaw, rotate_roll,
119
+ blink, eyebrow, wink, pupil_x, pupil_y,
120
+ aaa, eee, woo, smile,
121
+ src_ratio, sample_ratio, sample_parts, crop_factor,
122
+ ):
123
+ if image is None:
124
+ raise gr.Error("Please upload an image.")
125
+ src_t = _pil_to_node_tensor(image)
126
+ out = _editor.run(
127
+ rotate_pitch=float(rotate_pitch),
128
+ rotate_yaw=float(rotate_yaw),
129
+ rotate_roll=float(rotate_roll),
130
+ blink=float(blink),
131
+ eyebrow=float(eyebrow),
132
+ wink=float(wink),
133
+ pupil_x=float(pupil_x),
134
+ pupil_y=float(pupil_y),
135
+ aaa=float(aaa),
136
+ eee=float(eee),
137
+ woo=float(woo),
138
+ smile=float(smile),
139
+ src_ratio=float(src_ratio),
140
+ sample_ratio=float(sample_ratio),
141
+ sample_parts=sample_parts,
142
+ crop_factor=float(crop_factor),
143
+ src_image=src_t,
144
+ )
145
+ # ExpressionEditor.run returns {"ui": {...}, "result": (out_img, motion_link, exp_data)}
146
+ out_img_t = out["result"][0]
147
+ return _node_tensor_to_pil(out_img_t)
148
+
149
+
150
+ # ------------------------------------------------------------------
151
+ # 3. Image preprocess (mirrors original: resize so max side <= 1024)
152
+ # ------------------------------------------------------------------
153
+
154
+ def preprocess_image(img: Image.Image):
155
+ if img is None:
156
+ return None
157
+ if img.mode != "RGB":
158
+ img = img.convert("RGB")
159
+ w, h = img.size
160
+ if w <= 1024 and h <= 1024:
161
+ return img
162
+ if w >= h:
163
+ new_w = 1024
164
+ new_h = int(round(new_w / w * h))
165
  else:
166
+ new_h = 1024
167
+ new_w = int(round(new_h / h * w))
168
+ return img.resize((new_w, new_h), Image.LANCZOS)
169
 
170
 
171
+ def reset_parameters():
172
+ return (
173
+ gr.update(value=0), gr.update(value=0), gr.update(value=0),
174
+ gr.update(value=0), gr.update(value=0), gr.update(value=0),
175
+ gr.update(value=0), gr.update(value=0),
176
+ gr.update(value=0), gr.update(value=0), gr.update(value=0), gr.update(value=0),
177
+ )
178
+
179
+
180
+ # ------------------------------------------------------------------
181
+ # 4. Gradio UI (mirrors fffiloni/expression-editor exactly)
182
+ # ------------------------------------------------------------------
183
 
184
+ css = """
185
+ #col-container{max-width: 800px; margin: 0 auto;}
186
+ """
187
+
188
+ with gr.Blocks(css=css, title="Expression Editor") as demo:
189
  with gr.Column(elem_id="col-container"):
190
  gr.Markdown("# Expression Editor")
191
+ gr.Markdown("Edit a face's expression with sliders. Uses the <a href='https://github.com/PowerHouseMan/ComfyUI-AdvancedLivePortrait' target='_blank'>Expression Editor ComfyUI node</a>, originally packaged by <a href='https://replicate.com/fofr' target='_blank'>fofr</a>.")
192
+
193
  with gr.Row():
194
  with gr.Column():
195
+ image_in = gr.Image(
196
  label="Input image",
197
  sources=["upload"],
198
+ type="pil",
199
  )
200
+
201
  with gr.Tab("HEAD"):
202
  with gr.Column():
203
+ rotate_pitch = gr.Slider(label="Rotate Up-Down", value=0, minimum=-20, maximum=20)
204
+ rotate_yaw = gr.Slider(label="Rotate Left-Right turn", value=0, minimum=-20, maximum=20)
205
+ rotate_roll = gr.Slider(label="Rotate Left-Right tilt", value=0, minimum=-20, maximum=20)
206
+
 
 
 
 
 
 
 
 
 
 
207
  with gr.Tab("EYES"):
208
  with gr.Column():
209
+ eyebrow = gr.Slider(label="Eyebrow", value=0, minimum=-10, maximum=15)
 
 
 
210
  with gr.Row():
211
+ blink = gr.Slider(label="Blink", value=0, minimum=-20, maximum=5)
212
+ wink = gr.Slider(label="Wink", value=0, minimum=0, maximum=25)
 
 
 
 
 
 
 
213
  with gr.Row():
214
+ pupil_x = gr.Slider(label="Pupil X", value=0, minimum=-15, maximum=15)
215
+ pupil_y = gr.Slider(label="Pupil Y", value=0, minimum=-15, maximum=15)
216
+
 
 
 
 
 
217
  with gr.Tab("MOUTH"):
218
  with gr.Column():
219
  with gr.Row():
220
+ aaa = gr.Slider(label="Aaa", value=0, minimum=-30, maximum=120)
221
+ eee = gr.Slider(label="Eee", value=0, minimum=-20, maximum=15)
222
+ woo = gr.Slider(label="Woo", value=0, minimum=-20, maximum=15)
223
+ smile = gr.Slider(label="Smile", value=0, minimum=-0.3, maximum=1.3)
224
+
 
 
 
 
 
 
 
 
 
 
 
225
  with gr.Tab("More Settings"):
226
  with gr.Column():
227
+ src_ratio = gr.Number(label="Src Ratio", info="Source ratio", value=1)
228
+ sample_ratio = gr.Slider(label="Sample Ratio", info="Sample ratio", value=1, minimum=-0.2, maximum=1.2)
229
+ sample_parts = gr.Dropdown(
230
+ choices=["OnlyExpression", "OnlyRotation", "OnlyMouth", "OnlyEyes", "All"],
231
+ value="OnlyExpression",
232
+ label="Sample parts",
 
 
 
 
 
 
 
 
 
 
233
  )
234
+ crop_factor = gr.Slider(label="Crop Factor", info="Crop factor", value=1.7, minimum=1.5, maximum=2.5)
235
+
236
  with gr.Row():
237
  reset_btn = gr.Button("Reset")
238
+ submit_btn = gr.Button("Submit", variant="primary")
239
+
240
  with gr.Column():
241
+ result_image = gr.Image(label="Output", elem_id="top")
242
+
243
+ inputs = [
244
+ image_in, rotate_pitch, rotate_yaw, rotate_roll,
245
+ blink, eyebrow, wink, pupil_x, pupil_y,
246
+ aaa, eee, woo, smile,
247
+ src_ratio, sample_ratio, sample_parts, crop_factor,
248
+ ]
 
 
 
 
 
249
  outputs = [result_image]
250
 
251
+ # Resize on upload (matches original 1024-max preprocess)
252
+ image_in.upload(
253
+ fn=preprocess_image,
254
+ inputs=[image_in],
255
+ outputs=[image_in],
256
+ queue=False,
257
  )
258
 
259
  reset_btn.click(
260
+ fn=reset_parameters,
261
+ inputs=None,
262
+ outputs=[
263
+ rotate_pitch, rotate_yaw, rotate_roll,
264
+ blink, eyebrow, wink, pupil_x, pupil_y,
265
+ aaa, eee, woo, smile,
266
+ ],
267
+ queue=False,
268
+ ).then(fn=edit_expression, inputs=inputs, outputs=outputs)
269
+
270
+ submit_btn.click(fn=edit_expression, inputs=inputs, outputs=outputs)
271
+
272
+ # Regenerate on slider release (matches original's live-editing feel)
273
+ for slider in (
274
+ rotate_pitch, rotate_yaw, rotate_roll,
275
+ blink, eyebrow, wink, pupil_x, pupil_y,
276
+ aaa, eee, woo, smile,
277
+ ):
278
+ slider.release(fn=edit_expression, inputs=inputs, outputs=outputs, show_progress="minimal")
279
+
280
 
281
+ demo.queue().launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,2 +1,13 @@
1
- gradio
2
- prance
 
 
 
 
 
 
 
 
 
 
 
 
1
+ numpy>=1.26.4
2
+ opencv-python-headless
3
+ imageio-ffmpeg>=0.5.1
4
+ lmdb>=1.4.1
5
+ timm>=1.0.7
6
+ rich>=13.7.1
7
+ albumentations>=1.4.10
8
+ ultralytics
9
+ tyro
10
+ dill
11
+ PyYAML
12
+ safetensors
13
+ Pillow
run.sh DELETED
@@ -1,44 +0,0 @@
1
- # Start the cog server in the background - Ensure correct path to cog
2
- cd /src && python3 -m cog.server.http --threads=10 &
3
-
4
- # Initialize counter for the first loop
5
- counter1=0
6
-
7
- # Continuous loop for reliably checking cog server's readiness on port 5000
8
- while true; do
9
- if nc -z localhost 5000; then
10
- echo "Cog server is running on port 5000."
11
- break # Exit the loop when the server is up
12
- fi
13
- echo "Waiting for cog server to start on port 5000..."
14
- sleep 5
15
- ((counter1++))
16
- if [ $counter1 -ge 250 ]; then
17
- echo "Error: Cog server did not start on port 5000 after 250 attempts."
18
- exit 1 # Exit the script with an error status
19
- fi
20
- done
21
-
22
- # Initialize counter for the second loop
23
- counter2=0
24
-
25
- # New check: Waiting for the cog server to be fully ready
26
- while true; do
27
- response=$(curl -s http://localhost:5000/health-check) # Replace localhost:5000 with actual hostname and port if necessary
28
- status=$(echo $response | jq -r '.status') # Parse status from JSON response
29
- if [ "$status" = "READY" ]; then
30
- echo "Cog server is fully ready."
31
- break # Exit the loop when the server is fully ready
32
- else
33
- echo "Waiting for cog server (models loading) on port 5000..."
34
- sleep 5
35
- fi
36
- ((counter2++))
37
- if [ $counter2 -ge 250 ]; then
38
- echo "Error: Cog server did not become fully ready after 250 attempts."
39
- exit 1 # Exit the script with an error status
40
- fi
41
- done
42
-
43
- # Run the application - only when cog server is fully ready
44
- cd $HOME/app && . $HOME/.venv/bin/activate && python app.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/gradio_helpers.py DELETED
@@ -1,469 +0,0 @@
1
- import gradio as gr
2
- from urllib.parse import urlparse
3
- import requests
4
- import time
5
- from PIL import Image
6
- import base64
7
- import io
8
- import uuid
9
- import os
10
-
11
-
12
- def extract_property_info(prop):
13
- combined_prop = {}
14
- merge_keywords = ["allOf", "anyOf", "oneOf"]
15
-
16
- for keyword in merge_keywords:
17
- if keyword in prop:
18
- for subprop in prop[keyword]:
19
- combined_prop.update(subprop)
20
- del prop[keyword]
21
-
22
- if not combined_prop:
23
- combined_prop = prop.copy()
24
-
25
- for key in ["description", "default"]:
26
- if key in prop:
27
- combined_prop[key] = prop[key]
28
-
29
- return combined_prop
30
-
31
-
32
- def detect_file_type(filename):
33
- audio_extensions = [".mp3", ".wav", ".flac", ".aac", ".ogg", ".m4a"]
34
- image_extensions = [
35
- ".jpg",
36
- ".jpeg",
37
- ".png",
38
- ".gif",
39
- ".bmp",
40
- ".tiff",
41
- ".svg",
42
- ".webp",
43
- ]
44
- video_extensions = [
45
- ".mp4",
46
- ".mov",
47
- ".wmv",
48
- ".flv",
49
- ".avi",
50
- ".avchd",
51
- ".mkv",
52
- ".webm",
53
- ]
54
-
55
- # Extract the file extension
56
- if isinstance(filename, str):
57
- extension = filename[filename.rfind(".") :].lower()
58
-
59
- # Check the extension against each list
60
- if extension in audio_extensions:
61
- return "audio"
62
- elif extension in image_extensions:
63
- return "image"
64
- elif extension in video_extensions:
65
- return "video"
66
- else:
67
- return "string"
68
- elif isinstance(filename, list):
69
- return "list"
70
-
71
-
72
- def build_gradio_inputs(ordered_input_schema, example_inputs=None):
73
- inputs = []
74
- input_field_strings = """inputs = []\n"""
75
- names = []
76
- for index, (name, prop) in enumerate(ordered_input_schema):
77
- names.append(name)
78
- prop = extract_property_info(prop)
79
- if "enum" in prop:
80
- input_field = gr.Dropdown(
81
- choices=prop["enum"],
82
- label=prop.get("title"),
83
- info=prop.get("description"),
84
- value=prop.get("default"),
85
- )
86
- input_field_string = f"""inputs.append(gr.Dropdown(
87
- choices={prop["enum"]}, label="{prop.get("title")}", info={"'''"+prop.get("description")+"'''" if prop.get("description") else 'None'}, value="{prop.get("default")}"
88
- ))\n"""
89
- elif prop["type"] == "integer":
90
- if prop.get("minimum") and prop.get("maximum"):
91
- input_field = gr.Slider(
92
- label=prop.get("title"),
93
- info=prop.get("description"),
94
- value=prop.get("default"),
95
- minimum=prop.get("minimum"),
96
- maximum=prop.get("maximum"),
97
- step=1,
98
- )
99
- input_field_string = f"""inputs.append(gr.Slider(
100
- label="{prop.get("title")}", info={"'''"+prop.get("description")+"'''" if prop.get("description") else 'None'}, value={prop.get("default")},
101
- minimum={prop.get("minimum")}, maximum={prop.get("maximum")}, step=1,
102
- ))\n"""
103
- else:
104
- input_field = gr.Number(
105
- label=prop.get("title"),
106
- info=prop.get("description"),
107
- value=prop.get("default"),
108
- )
109
- input_field_string = f"""inputs.append(gr.Number(
110
- label="{prop.get("title")}", info={"'''"+prop.get("description")+"'''" if prop.get("description") else 'None'}, value={prop.get("default")}
111
- ))\n"""
112
- elif prop["type"] == "number":
113
- if prop.get("minimum") and prop.get("maximum"):
114
- input_field = gr.Slider(
115
- label=prop.get("title"),
116
- info=prop.get("description"),
117
- value=prop.get("default"),
118
- minimum=prop.get("minimum"),
119
- maximum=prop.get("maximum"),
120
- )
121
- input_field_string = f"""inputs.append(gr.Slider(
122
- label="{prop.get("title")}", info={"'''"+prop.get("description")+"'''" if prop.get("description") else 'None'}, value={prop.get("default")},
123
- minimum={prop.get("minimum")}, maximum={prop.get("maximum")}
124
- ))\n"""
125
- else:
126
- input_field = gr.Number(
127
- label=prop.get("title"),
128
- info=prop.get("description"),
129
- value=prop.get("default"),
130
- )
131
- input_field_string = f"""inputs.append(gr.Number(
132
- label="{prop.get("title")}", info={"'''"+prop.get("description")+"'''" if prop.get("description") else 'None'}, value={prop.get("default")}
133
- ))\n"""
134
- elif prop["type"] == "boolean":
135
- input_field = gr.Checkbox(
136
- label=prop.get("title"),
137
- info=prop.get("description"),
138
- value=prop.get("default"),
139
- )
140
- input_field_string = f"""inputs.append(gr.Checkbox(
141
- label="{prop.get("title")}", info={"'''"+prop.get("description")+"'''" if prop.get("description") else 'None'}, value={prop.get("default")}
142
- ))\n"""
143
- elif (
144
- prop["type"] == "string" and prop.get("format") == "uri" and example_inputs
145
- ):
146
- input_type_example = example_inputs.get(name, None)
147
- if input_type_example:
148
- input_type = detect_file_type(input_type_example)
149
- else:
150
- input_type = None
151
- if input_type == "image":
152
- input_field = gr.Image(label=prop.get("title"), type="filepath")
153
- input_field_string = f"""inputs.append(gr.Image(
154
- label="{prop.get("title")}", type="filepath"
155
- ))\n"""
156
- elif input_type == "audio":
157
- input_field = gr.Audio(label=prop.get("title"), type="filepath")
158
- input_field_string = f"""inputs.append(gr.Audio(
159
- label="{prop.get("title")}", type="filepath"
160
- ))\n"""
161
- elif input_type == "video":
162
- input_field = gr.Video(label=prop.get("title"))
163
- input_field_string = f"""inputs.append(gr.Video(
164
- label="{prop.get("title")}"
165
- ))\n"""
166
- else:
167
- input_field = gr.File(label=prop.get("title"))
168
- input_field_string = f"""inputs.append(gr.File(
169
- label="{prop.get("title")}"
170
- ))\n"""
171
- else:
172
- input_field = gr.Textbox(
173
- label=prop.get("title"),
174
- info=prop.get("description"),
175
- )
176
- input_field_string = f"""inputs.append(gr.Textbox(
177
- label="{prop.get("title")}", info={"'''"+prop.get("description")+"'''" if prop.get("description") else 'None'}
178
- ))\n"""
179
- inputs.append(input_field)
180
- input_field_strings += f"{input_field_string}\n"
181
-
182
- input_field_strings += f"names = {names}\n"
183
-
184
- return inputs, input_field_strings, names
185
-
186
-
187
- def build_gradio_outputs_replicate(output_types):
188
- outputs = []
189
- output_field_strings = """outputs = []\n"""
190
- if output_types:
191
- for output in output_types:
192
- if output == "image":
193
- output_field = gr.Image()
194
- output_field_string = "outputs.append(gr.Image())"
195
- elif output == "audio":
196
- output_field = gr.Audio(type="filepath")
197
- output_field_string = "outputs.append(gr.Audio(type='filepath'))"
198
- elif output == "video":
199
- output_field = gr.Video()
200
- output_field_string = "outputs.append(gr.Video())"
201
- elif output == "string":
202
- output_field = gr.Textbox()
203
- output_field_string = "outputs.append(gr.Textbox())"
204
- elif output == "json":
205
- output_field = gr.JSON()
206
- output_field_string = "outputs.append(gr.JSON())"
207
- elif output == "list":
208
- output_field = gr.JSON()
209
- output_field_string = "outputs.append(gr.JSON())"
210
- outputs.append(output_field)
211
- output_field_strings += f"{output_field_string}\n"
212
- else:
213
- output_field = gr.JSON()
214
- output_field_string = "outputs.append(gr.JSON())"
215
- outputs.append(output_field)
216
-
217
- return outputs, output_field_strings
218
-
219
-
220
- def build_gradio_outputs_cog():
221
- pass
222
-
223
-
224
- def process_outputs(outputs):
225
- output_values = []
226
- for output in outputs:
227
- if not output:
228
- continue
229
- if isinstance(output, str):
230
- if output.startswith("data:image"):
231
- base64_data = output.split(",", 1)[1]
232
- image_data = base64.b64decode(base64_data)
233
- image_stream = io.BytesIO(image_data)
234
- image = Image.open(image_stream)
235
- output_values.append(image)
236
- elif output.startswith("data:audio"):
237
- base64_data = output.split(",", 1)[1]
238
- audio_data = base64.b64decode(base64_data)
239
- audio_stream = io.BytesIO(audio_data)
240
- filename = f"{uuid.uuid4()}.wav" # Change format as needed
241
- with open(filename, "wb") as audio_file:
242
- audio_file.write(audio_stream.getbuffer())
243
- output_values.append(filename)
244
- elif output.startswith("data:video"):
245
- base64_data = output.split(",", 1)[1]
246
- video_data = base64.b64decode(base64_data)
247
- video_stream = io.BytesIO(video_data)
248
- # Here you can save the audio or return the stream for further processing
249
- filename = f"{uuid.uuid4()}.mp4" # Change format as needed
250
- with open(filename, "wb") as video_file:
251
- video_file.write(video_stream.getbuffer())
252
- output_values.append(filename)
253
- else:
254
- output_values.append(output)
255
- else:
256
- output_values.append(output)
257
- return output_values
258
-
259
-
260
- def parse_outputs(data):
261
- if isinstance(data, dict):
262
- # Handle case where data is an object
263
- dict_values = []
264
- for value in data.values():
265
- extracted_values = parse_outputs(value)
266
- # For dict, we append instead of extend to maintain list structure within objects
267
- if isinstance(value, list):
268
- dict_values += [extracted_values]
269
- else:
270
- dict_values += extracted_values
271
- return dict_values
272
- elif isinstance(data, list):
273
- # Handle case where data is an array
274
- list_values = []
275
- for item in data:
276
- # Here we extend to flatten the list since we're already in an array context
277
- list_values += parse_outputs(item)
278
- return list_values
279
- else:
280
- # Handle primitive data types directly
281
- return [data]
282
-
283
-
284
- def create_dynamic_gradio_app(
285
- inputs,
286
- outputs,
287
- api_url,
288
- api_id=None,
289
- replicate_token=None,
290
- title="",
291
- model_description="",
292
- names=[],
293
- local_base=False,
294
- hostname="0.0.0.0",
295
- ):
296
- expected_outputs = len(outputs)
297
-
298
- def predict(request: gr.Request, *args, progress=gr.Progress(track_tqdm=True)):
299
- payload = {"input": {}}
300
- if api_id:
301
- payload["version"] = api_id
302
- parsed_url = urlparse(str(request.url))
303
- if local_base:
304
- base_url = f"http://{hostname}:7860"
305
- else:
306
- base_url = parsed_url.scheme + "://" + parsed_url.netloc
307
- for i, key in enumerate(names):
308
- value = args[i]
309
- if value and (os.path.exists(str(value))):
310
- value = f"{base_url}/file=" + value
311
- if value is not None and value != "":
312
- payload["input"][key] = value
313
- print(payload)
314
- headers = {"Content-Type": "application/json"}
315
- if replicate_token:
316
- headers["Authorization"] = f"Token {replicate_token}"
317
- print(headers)
318
- response = requests.post(api_url, headers=headers, json=payload)
319
- if response.status_code == 201:
320
- follow_up_url = response.json()["urls"]["get"]
321
- response = requests.get(follow_up_url, headers=headers)
322
- while response.json()["status"] != "succeeded":
323
- if response.json()["status"] == "failed":
324
- raise gr.Error("The submission failed!")
325
- response = requests.get(follow_up_url, headers=headers)
326
- time.sleep(1)
327
- # TODO: Add a failing mechanism if the API gets stuck
328
- if response.status_code == 200:
329
- json_response = response.json()
330
- # If the output component is JSON return the entire output response
331
- if outputs[0].get_config()["name"] == "json":
332
- return json_response["output"]
333
- predict_outputs = parse_outputs(json_response["output"])
334
- processed_outputs = process_outputs(predict_outputs)
335
- difference_outputs = expected_outputs - len(processed_outputs)
336
- # If less outputs than expected, hide the extra ones
337
- if difference_outputs > 0:
338
- extra_outputs = [gr.update(visible=False)] * difference_outputs
339
- processed_outputs.extend(extra_outputs)
340
- # If more outputs than expected, cap the outputs to the expected number if
341
- elif difference_outputs < 0:
342
- processed_outputs = processed_outputs[:difference_outputs]
343
-
344
- return (
345
- tuple(processed_outputs)
346
- if len(processed_outputs) > 1
347
- else processed_outputs[0]
348
- )
349
-
350
- else:
351
- if response.status_code == 409:
352
- raise gr.Error(
353
- f"Sorry, the Cog image is still processing. Try again in a bit."
354
- )
355
- raise gr.Error(f"The submission failed! Error: {response.status_code}")
356
-
357
- app = gr.Interface(
358
- fn=predict,
359
- inputs=inputs,
360
- outputs=outputs,
361
- title=title,
362
- description=model_description,
363
- allow_flagging="never",
364
- )
365
- return app
366
-
367
-
368
- def create_gradio_app_script(
369
- inputs_string,
370
- outputs_string,
371
- api_url,
372
- api_id=None,
373
- replicate_token=None,
374
- title="",
375
- model_description="",
376
- local_base=False,
377
- hostname="0.0.0.0"
378
- ):
379
- headers = {"Content-Type": "application/json"}
380
- if replicate_token:
381
- headers["Authorization"] = f"Token {replicate_token}"
382
-
383
- if local_base:
384
- base_url = f'base_url = "http://{hostname}:7860"'
385
- else:
386
- base_url = """parsed_url = urlparse(str(request.url))
387
- base_url = parsed_url.scheme + "://" + parsed_url.netloc"""
388
- headers_string = f"""headers = {headers}\n"""
389
- api_id_value = f'payload["version"] = "{api_id}"' if api_id is not None else ""
390
- definition_string = """expected_outputs = len(outputs)
391
- def predict(request: gr.Request, *args, progress=gr.Progress(track_tqdm=True)):"""
392
- payload_string = f"""payload = {{"input": {{}}}}
393
- {api_id_value}
394
-
395
- {base_url}
396
- for i, key in enumerate(names):
397
- value = args[i]
398
- if value and (os.path.exists(str(value))):
399
- value = f"{{base_url}}/file=" + value
400
- if value is not None and value != "":
401
- payload["input"][key] = value\n"""
402
-
403
- request_string = (
404
- f"""response = requests.post("{api_url}", headers=headers, json=payload)\n"""
405
- )
406
-
407
- result_string = f"""
408
- if response.status_code == 201:
409
- follow_up_url = response.json()["urls"]["get"]
410
- response = requests.get(follow_up_url, headers=headers)
411
- while response.json()["status"] != "succeeded":
412
- if response.json()["status"] == "failed":
413
- raise gr.Error("The submission failed!")
414
- response = requests.get(follow_up_url, headers=headers)
415
- time.sleep(1)
416
- if response.status_code == 200:
417
- json_response = response.json()
418
- #If the output component is JSON return the entire output response
419
- if(outputs[0].get_config()["name"] == "json"):
420
- return json_response["output"]
421
- predict_outputs = parse_outputs(json_response["output"])
422
- processed_outputs = process_outputs(predict_outputs)
423
- difference_outputs = expected_outputs - len(processed_outputs)
424
- # If less outputs than expected, hide the extra ones
425
- if difference_outputs > 0:
426
- extra_outputs = [gr.update(visible=False)] * difference_outputs
427
- processed_outputs.extend(extra_outputs)
428
- # If more outputs than expected, cap the outputs to the expected number
429
- elif difference_outputs < 0:
430
- processed_outputs = processed_outputs[:difference_outputs]
431
-
432
- return tuple(processed_outputs) if len(processed_outputs) > 1 else processed_outputs[0]
433
- else:
434
- if(response.status_code == 409):
435
- raise gr.Error(f"Sorry, the Cog image is still processing. Try again in a bit.")
436
- raise gr.Error(f"The submission failed! Error: {{response.status_code}}")\n"""
437
-
438
- interface_string = f"""title = "{title}"
439
- model_description = "{model_description}"
440
-
441
- app = gr.Interface(
442
- fn=predict,
443
- inputs=inputs,
444
- outputs=outputs,
445
- title=title,
446
- description=model_description,
447
- allow_flagging="never",
448
- )
449
- app.launch(share=True)
450
- """
451
-
452
- app_string = f"""import gradio as gr
453
- from urllib.parse import urlparse
454
- import requests
455
- import time
456
- import os
457
-
458
- from utils.gradio_helpers import parse_outputs, process_outputs
459
-
460
- {inputs_string}
461
- {outputs_string}
462
- {definition_string}
463
- {headers_string}
464
- {payload_string}
465
- {request_string}
466
- {result_string}
467
- {interface_string}
468
- """
469
- return app_string