fffiloni commited on
Commit
127a080
·
verified ·
1 Parent(s): 58c6efb

Refactor app.py for Spaces compatibility and safer startup

Browse files
Files changed (1) hide show
  1. app.py +308 -221
app.py CHANGED
@@ -1,15 +1,22 @@
1
  import os
2
  import random
 
 
3
  from pathlib import Path
 
 
4
  import numpy as np
5
  import torch
6
-
7
- is_shared_ui = True if "fffiloni/echomimic-v2" in os.environ['SPACE_ID'] else False
8
- is_gpu_associated = torch.cuda.is_available()
9
-
10
 
11
  from diffusers import AutoencoderKL, DDIMScheduler
12
  from PIL import Image
 
 
 
 
 
13
  from src.models.unet_2d_condition import UNet2DConditionModel
14
  from src.models.unet_3d_emo import EMOUNet3DConditionModel
15
  from src.models.whisper.audio2feature import load_audio_model
@@ -17,209 +24,227 @@ from src.pipelines.pipeline_echomimicv2 import EchoMimicV2Pipeline
17
  from src.utils.util import save_videos_grid
18
  from src.models.pose_encoder import PoseEncoder
19
  from src.utils.dwpose_util import draw_pose_select_v2
20
- from moviepy.editor import VideoFileClip, AudioFileClip
21
 
22
- import gradio as gr
23
- from datetime import datetime
24
- from torchao.quantization import quantize_, int8_weight_only
25
- import gc
26
 
27
- import tempfile
28
- from pydub import AudioSegment
 
 
29
 
30
  def cut_audio_to_5_seconds(audio_path):
31
  try:
32
- # Load the audio file
33
  audio = AudioSegment.from_file(audio_path)
34
-
35
- # Trim to a maximum of 5 seconds (5000 milliseconds)
36
  trimmed_audio = audio[:5000]
37
 
38
- # Create a temporary directory
39
  temp_dir = tempfile.mkdtemp()
40
  output_path = os.path.join(temp_dir, "trimmed_audio.wav")
41
-
42
- # Export the trimmed audio
43
  trimmed_audio.export(output_path, format="wav")
44
 
45
  return output_path
46
  except Exception as e:
47
- return f"An error occurred while trying to trim audio: {str(e)}"
48
-
49
- import requests
50
- import tarfile
51
-
52
- def download_and_setup_ffmpeg():
53
- url = "https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-4.4-amd64-static.tar.xz"
54
- download_path = "ffmpeg-4.4-amd64-static.tar.xz"
55
- extract_dir = "ffmpeg-4.4-amd64-static"
56
-
57
- try:
58
- # Download the file
59
- response = requests.get(url, stream=True)
60
- response.raise_for_status() # Check for HTTP request errors
61
- with open(download_path, "wb") as file:
62
- for chunk in response.iter_content(chunk_size=8192):
63
- file.write(chunk)
64
-
65
- # Extract the tar.xz file
66
- with tarfile.open(download_path, "r:xz") as tar:
67
- tar.extractall(path=extract_dir)
68
-
69
- # Set the FFMPEG_PATH environment variable
70
- ffmpeg_binary_path = os.path.join(extract_dir, "ffmpeg-4.4-amd64-static", "ffmpeg")
71
- os.environ["FFMPEG_PATH"] = ffmpeg_binary_path
72
-
73
- return f"FFmpeg downloaded and setup successfully! Path: {ffmpeg_binary_path}"
74
- except Exception as e:
75
- return f"An error occurred: {str(e)}"
76
 
77
- download_and_setup_ffmpeg()
78
 
79
- from huggingface_hub import snapshot_download
80
-
81
- # Create the main "pretrained_weights" folder
82
  os.makedirs("pretrained_weights", exist_ok=True)
83
 
84
- # List of subdirectories to create inside "pretrained_weights"
85
  subfolders = [
86
  "sd-vae-ft-mse",
87
  "sd-image-variations-diffusers",
88
- "audio_processor"
89
  ]
90
 
91
- # Create each subdirectory
92
  for subfolder in subfolders:
93
  os.makedirs(os.path.join("pretrained_weights", subfolder), exist_ok=True)
94
-
95
- snapshot_download(
96
- repo_id = "BadToBest/EchoMimicV2",
97
- local_dir="./pretrained_weights"
98
- )
99
- snapshot_download(
100
- repo_id = "stabilityai/sd-vae-ft-mse",
101
- local_dir="./pretrained_weights/sd-vae-ft-mse"
102
- )
103
- snapshot_download(
104
- repo_id = "lambdalabs/sd-image-variations-diffusers",
105
- local_dir="./pretrained_weights/sd-image-variations-diffusers"
106
- )
107
 
108
- is_shared_ui = True if "fffiloni/echomimic-v2" in os.environ['SPACE_ID'] else False
109
 
110
- # Download and place the Whisper model in the "audio_processor" folder
 
 
 
 
 
 
 
 
 
111
  def download_whisper_model():
112
- url = "https://openaipublic.azureedge.net/main/whisper/models/65147644a518d12f04e32d6f3b26facc3f8dd46e5390956a9424a650c0ce22b9/tiny.pt"
 
 
 
113
  save_path = os.path.join("pretrained_weights", "audio_processor", "tiny.pt")
114
-
 
 
 
 
115
  try:
116
- # Download the file
117
- response = requests.get(url, stream=True)
118
- response.raise_for_status() # Check for HTTP request errors
 
119
  with open(save_path, "wb") as file:
120
  for chunk in response.iter_content(chunk_size=8192):
121
- file.write(chunk)
 
 
122
  print(f"Whisper model downloaded and saved to {save_path}")
 
123
  except Exception as e:
124
- print(f"An error occurred while downloading the model: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
 
 
 
 
 
126
 
127
  if torch.cuda.is_available():
128
  device = "cuda"
 
129
 
130
- # Download the Whisper model
131
  download_whisper_model()
132
 
133
  total_vram_in_gb = torch.cuda.get_device_properties(0).total_memory / 1073741824
134
- print(f'\033[32mCUDA版本:{torch.version.cuda}\033[0m')
135
- print(f'\033[32mPytorch版本:{torch.__version__}\033[0m')
136
- print(f'\033[32m显卡型号:{torch.cuda.get_device_name()}\033[0m')
137
- print(f'\033[32m显存大小:{total_vram_in_gb:.2f}GB\033[0m')
138
- print(f'\033[32m精度:float16\033[0m')
139
-
140
- dtype = torch.float16
141
-
142
  else:
143
- print("cuda not available, using cpu")
144
  device = "cpu"
145
-
146
- ffmpeg_path = os.getenv('FFMPEG_PATH')
147
- if ffmpeg_path is None:
148
- print("please download ffmpeg-static and export to FFMPEG_PATH. \nFor example: export FFMPEG_PATH=./ffmpeg-4.4-amd64-static")
149
- elif ffmpeg_path not in os.getenv('PATH'):
150
- print("add ffmpeg to path")
151
- os.environ["PATH"] = f"{ffmpeg_path}:{os.environ['PATH']}"
152
-
153
-
154
- def generate(image_input, audio_input, pose_input, width, height, length, steps, sample_rate, cfg, fps, context_frames, context_overlap, quantization_input, seed, progress=gr.Progress(track_tqdm=True)):
 
 
 
 
 
 
 
 
 
 
155
  gc.collect()
156
- torch.cuda.empty_cache()
157
- torch.cuda.ipc_collect()
 
 
158
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
159
  save_dir = Path("outputs")
160
  save_dir.mkdir(exist_ok=True, parents=True)
161
 
162
- ############# model_init started #############
163
- ## vae init
164
- vae = AutoencoderKL.from_pretrained("./pretrained_weights/sd-vae-ft-mse").to(device, dtype=dtype)
 
 
 
 
 
 
 
 
 
 
 
165
  if quantization_input:
166
  quantize_(vae, int8_weight_only())
167
- print("Use int8 quantization.")
168
 
169
- ## reference net init
170
- reference_unet = UNet2DConditionModel.from_pretrained("./pretrained_weights/sd-image-variations-diffusers", subfolder="unet", use_safetensors=False).to(dtype=dtype, device=device)
171
- reference_unet.load_state_dict(torch.load("./pretrained_weights/reference_unet.pth", weights_only=True))
 
 
 
 
 
 
172
  if quantization_input:
173
  quantize_(reference_unet, int8_weight_only())
 
 
 
 
 
 
174
 
175
- ## denoising net init
176
- if os.path.exists("./pretrained_weights/motion_module.pth"):
177
- print('using motion module')
178
- else:
179
- exit("motion module not found")
180
- ### stage1 + stage2
181
  denoising_unet = EMOUNet3DConditionModel.from_pretrained_2d(
182
  "./pretrained_weights/sd-image-variations-diffusers",
183
- "./pretrained_weights/motion_module.pth",
184
  subfolder="unet",
185
- unet_additional_kwargs = {
186
  "use_inflated_groupnorm": True,
187
  "unet_use_cross_frame_attention": False,
188
  "unet_use_temporal_attention": False,
189
  "use_motion_module": True,
190
  "cross_attention_dim": 384,
191
- "motion_module_resolutions": [
192
- 1,
193
- 2,
194
- 4,
195
- 8
196
- ],
197
- "motion_module_mid_block": True ,
198
  "motion_module_decoder_only": False,
199
  "motion_module_type": "Vanilla",
200
- "motion_module_kwargs":{
201
  "num_attention_heads": 8,
202
  "num_transformer_block": 1,
203
  "attention_block_types": [
204
- 'Temporal_Self',
205
- 'Temporal_Self'
206
  ],
207
  "temporal_position_encoding": True,
208
  "temporal_position_encoding_max_len": 32,
209
  "temporal_attention_dim_div": 1,
210
- }
211
  },
212
  ).to(dtype=dtype, device=device)
213
- denoising_unet.load_state_dict(torch.load("./pretrained_weights/denoising_unet.pth", weights_only=True),strict=False)
214
 
215
- # pose net init
216
- pose_net = PoseEncoder(320, conditioning_channels=3, block_out_channels=(16, 32, 96, 256)).to(dtype=dtype, device=device)
217
- pose_net.load_state_dict(torch.load("./pretrained_weights/pose_encoder.pth", weights_only=True))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
- ### load audio processor params
220
- audio_processor = load_audio_model(model_path="./pretrained_weights/audio_processor/tiny.pt", device=device)
221
-
222
- ############# model_init finished #############
223
  sched_kwargs = {
224
  "beta_start": 0.00085,
225
  "beta_end": 0.012,
@@ -228,7 +253,7 @@ def generate(image_input, audio_input, pose_input, width, height, length, steps,
228
  "steps_offset": 1,
229
  "prediction_type": "v_prediction",
230
  "rescale_betas_zero_snr": True,
231
- "timestep_spacing": "trailing"
232
  }
233
  scheduler = DDIMScheduler(**sched_kwargs)
234
 
@@ -240,13 +265,12 @@ def generate(image_input, audio_input, pose_input, width, height, length, steps,
240
  pose_encoder=pose_net,
241
  scheduler=scheduler,
242
  )
243
-
244
  pipe = pipe.to(device, dtype=dtype)
245
 
246
- if seed is not None and seed > -1:
247
  generator = torch.manual_seed(seed)
248
  else:
249
- seed = random.randint(100, 1000000)
250
  generator = torch.manual_seed(seed)
251
 
252
  if is_shared_ui:
@@ -259,40 +283,51 @@ def generate(image_input, audio_input, pose_input, width, height, length, steps,
259
  "pose": pose_input,
260
  }
261
 
262
- print('Pose:', inputs_dict['pose'])
263
- print('Reference:', inputs_dict['refimg'])
264
- print('Audio:', inputs_dict['audio'])
265
 
266
  save_name = f"{save_dir}/{timestamp}"
267
-
268
- ref_image_pil = Image.open(inputs_dict['refimg']).resize((width, height))
269
- audio_clip = AudioFileClip(inputs_dict['audio'])
270
-
271
- length = min(length, int(audio_clip.duration * fps), len(os.listdir(inputs_dict['pose'])))
 
 
 
 
272
 
273
  start_idx = 0
274
 
275
  pose_list = []
276
  for index in range(start_idx, start_idx + length):
277
- tgt_musk = np.zeros((width, height, 3)).astype('uint8')
278
- tgt_musk_path = os.path.join(inputs_dict['pose'], "{}.npy".format(index))
279
- detected_pose = np.load(tgt_musk_path, allow_pickle=True).tolist()
280
- imh_new, imw_new, rb, re, cb, ce = detected_pose['draw_pose_params']
 
281
  im = draw_pose_select_v2(detected_pose, imh_new, imw_new, ref_w=800)
282
- im = np.transpose(np.array(im),(1, 2, 0))
283
- tgt_musk[rb:re,cb:ce,:] = im
 
 
 
 
 
 
 
 
284
 
285
- tgt_musk_pil = Image.fromarray(np.array(tgt_musk)).convert('RGB')
286
- pose_list.append(torch.Tensor(np.array(tgt_musk_pil)).to(dtype=dtype, device=device).permute(2,0,1) / 255.0)
287
-
288
  poses_tensor = torch.stack(pose_list, dim=1).unsqueeze(0)
289
- audio_clip = AudioFileClip(inputs_dict['audio'])
290
-
291
  audio_clip = audio_clip.set_duration(length / fps)
 
292
  video = pipe(
293
  ref_image_pil,
294
- inputs_dict['audio'],
295
- poses_tensor[:,:,:length,...],
296
  width,
297
  height,
298
  length,
@@ -304,11 +339,11 @@ def generate(image_input, audio_input, pose_input, width, height, length, steps,
304
  fps=fps,
305
  context_overlap=context_overlap,
306
  start_idx=start_idx,
307
- ).videos
308
-
309
  final_length = min(video.shape[2], poses_tensor.shape[2], length)
310
  video_sig = video[:, :, :final_length, :, :]
311
-
312
  save_videos_grid(
313
  video_sig,
314
  save_name + "_woa_sig.mp4",
@@ -316,13 +351,20 @@ def generate(image_input, audio_input, pose_input, width, height, length, steps,
316
  fps=fps,
317
  )
318
 
319
- video_clip_sig = VideoFileClip(save_name + "_woa_sig.mp4",)
320
  video_clip_sig = video_clip_sig.set_audio(audio_clip)
321
- video_clip_sig.write_videofile(save_name + "_sig.mp4", codec="libx264", audio_codec="aac", threads=2)
 
 
 
 
 
 
322
  video_output = save_name + "_sig.mp4"
323
  seed_text = gr.update(visible=True, value=seed)
324
  return video_output, seed_text
325
 
 
326
  css = """
327
  div#warning-duplicate {
328
  background-color: #ebf5ff;
@@ -381,100 +423,131 @@ div#warning-ready > .gr-prose > h2, div#warning-ready > .gr-prose > p {
381
  }
382
  """
383
 
 
384
  with gr.Blocks(css=css) as demo:
385
- gr.Markdown("""
386
- # EchoMimicV2
387
-
388
- ⚠️ This demonstration is for academic research and experiential use only.
389
- """)
390
- gr.HTML("""
 
 
 
 
391
  <div style="display:flex;column-gap:4px;">
392
  <a href="https://github.com/antgroup/echomimic_v2">
393
  <img src='https://img.shields.io/badge/GitHub-Repo-blue'>
394
- </a>
395
  <a href="https://antgroup.github.io/ai/echomimic_v2/">
396
  <img src='https://img.shields.io/badge/Project-Page-green'>
397
  </a>
398
- <a href="https://arxiv.org/abs/2411.10061">
399
  <img src='https://img.shields.io/badge/ArXiv-Paper-red'>
400
  </a>
401
  <a href="https://huggingface.co/spaces/fffiloni/echomimic-v2?duplicate=true">
402
- <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-sm.svg" alt="Duplicate this Space">
403
- </a>
404
- <a href="https://huggingface.co/fffiloni">
405
- <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/follow-me-on-HF-sm-dark.svg" alt="Follow me on HF">
406
- </a>
407
  </div>
408
- """)
 
 
409
  with gr.Column():
410
  with gr.Row():
411
  with gr.Column():
412
  with gr.Group():
413
  image_input = gr.Image(label="Image Input (Auto Scaling)", type="filepath")
414
  audio_input = gr.Audio(label="Audio Input - max 5 seconds on shared UI", type="filepath")
415
- pose_input = gr.Textbox(label="Pose Input (Directory Path)", placeholder="Please enter the directory path for pose data.", value="assets/halfbody_demo/pose/01", interactive=False, visible=False)
 
 
 
 
 
 
 
416
  with gr.Accordion("Advanced Settings", open=False):
417
  with gr.Row():
418
  width = gr.Number(label="Width (multiple of 16, recommended: 768)", value=768)
419
  height = gr.Number(label="Height (multiple of 16, recommended: 768)", value=768)
420
- length = gr.Number(label="Video Length (recommended: 240", value=240)
 
421
  with gr.Row():
422
  steps = gr.Number(label="Steps (recommended: 30)", value=20)
423
  sample_rate = gr.Number(label="Sampling Rate (recommended: 16000)", value=16000)
424
  cfg = gr.Number(label="CFG (recommended: 2.5)", value=2.5, step=0.1)
 
425
  with gr.Row():
426
  fps = gr.Number(label="Frame Rate (recommended: 24)", value=24)
427
  context_frames = gr.Number(label="Context Frames (recommended: 12)", value=12)
428
  context_overlap = gr.Number(label="Context Overlap (recommended: 3)", value=3)
 
429
  with gr.Row():
430
- quantization_input = gr.Checkbox(label="Int8 Quantization (recommended for users with 12GB VRAM, use audio no longer than 5 seconds)", value=False)
 
 
 
431
  seed = gr.Number(label="Seed (-1 for random)", value=-1)
432
- generate_button = gr.Button("🎬 Generate Video", interactive=False if is_shared_ui else True)
433
- with gr.Column():
434
 
 
 
 
435
  if is_shared_ui:
436
- top_description = gr.HTML(f'''
437
- <div class="gr-prose">
438
- <h2 class="custom-color"><svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" style="margin-right: 0px;display: inline-block;"fill="none"><path fill="#fff" d="M7 13.2a6.3 6.3 0 0 0 4.4-10.7A6.3 6.3 0 0 0 .6 6.9 6.3 6.3 0 0 0 7 13.2Z"/><path fill="#fff" fill-rule="evenodd" d="M7 0a6.9 6.9 0 0 1 4.8 11.8A6.9 6.9 0 0 1 0 7 6.9 6.9 0 0 1 7 0Zm0 0v.7V0ZM0 7h.6H0Zm7 6.8v-.6.6ZM13.7 7h-.6.6ZM9.1 1.7c-.7-.3-1.4-.4-2.2-.4a5.6 5.6 0 0 0-4 1.6 5.6 5.6 0 0 0-1.6 4 5.6 5.6 0 0 0 1.6 4 5.6 5.6 0 0 0 4 1.7 5.6 5.6 0 0 0 4-1.7 5.6 5.6 0 0 0 1.7-4 5.6 5.6 0 0 0-1.7-4c-.5-.5-1.1-.9-1.8-1.2Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M7 2.9a.8.8 0 1 1 0 1.5A.8.8 0 0 1 7 3ZM5.8 5.7c0-.4.3-.6.6-.6h.7c.3 0 .6.2.6.6v3.7h.5a.6.6 0 0 1 0 1.3H6a.6.6 0 0 1 0-1.3h.4v-3a.6.6 0 0 1-.6-.7Z" clip-rule="evenodd"/></svg>
439
- Attention: this Space need to be duplicated to work</h2>
440
- <p class="main-message custom-color">
441
- To make it work, <strong>duplicate the Space</strong> and run it on your own profile using a <strong>private</strong> GPU (L40s recommended).<br />
442
- A L40s costs <strong>US$1.80/h</strong>.
443
- </p>
444
- <p class="actions custom-color">
445
- <a href="https://huggingface.co/spaces/{os.environ['SPACE_ID']}?duplicate=true">
446
- <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-lg-dark.svg" alt="Duplicate this Space" />
447
- </a>
448
- to start experimenting with this demo
449
- </p>
450
- </div>
451
- ''', elem_id="warning-duplicate")
452
- else:
453
- if(is_gpu_associated):
454
- top_description = gr.HTML(f'''
455
- <div class="gr-prose">
456
- <h2 class="custom-color"><svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" style="margin-right: 0px;display: inline-block;"fill="none"><path fill="#fff" d="M7 13.2a6.3 6.3 0 0 0 4.4-10.7A6.3 6.3 0 0 0 .6 6.9 6.3 6.3 0 0 0 7 13.2Z"/><path fill="#fff" fill-rule="evenodd" d="M7 0a6.9 6.9 0 0 1 4.8 11.8A6.9 6.9 0 0 1 0 7 6.9 6.9 0 0 1 7 0Zm0 0v.7V0ZM0 7h.6H0Zm7 6.8v-.6.6ZM13.7 7h-.6.6ZM9.1 1.7c-.7-.3-1.4-.4-2.2-.4a5.6 5.6 0 0 0-4 1.6 5.6 5.6 0 0 0-1.6 4 5.6 5.6 0 0 0 1.6 4 5.6 5.6 0 0 0 4 1.7 5.6 5.6 0 0 0 4-1.7 5.6 5.6 0 0 0 1.7-4 5.6 5.6 0 0 0-1.7-4c-.5-.5-1.1-.9-1.8-1.2Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M7 2.9a.8.8 0 1 1 0 1.5A.8.8 0 0 1 7 3ZM5.8 5.7c0-.4.3-.6.6-.6h.7c.3 0 .6.2.6.6v3.7h.5a.6.6 0 0 1 0 1.3H6a.6.6 0 0 1 0-1.3h.4v-3a.6.6 0 0 1-.6-.7Z" clip-rule="evenodd"/></svg>
457
- You have successfully associated a GPU to this Space 🎉</h2>
458
- <p class="custom-color">
459
- You will be billed by the minute from when you activated the GPU until when it is turned off.
460
- </p>
461
- </div>
462
- ''', elem_id="warning-ready")
463
- else:
464
- top_description = gr.HTML(f'''
465
  <div class="gr-prose">
466
- <h2 class="custom-color"><svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" style="margin-right: 0px;display: inline-block;"fill="none"><path fill="#fff" d="M7 13.2a6.3 6.3 0 0 0 4.4-10.7A6.3 6.3 0 0 0 .6 6.9 6.3 6.3 0 0 0 7 13.2Z"/><path fill="#fff" fill-rule="evenodd" d="M7 0a6.9 6.9 0 0 1 4.8 11.8A6.9 6.9 0 0 1 0 7 6.9 6.9 0 0 1 7 0Zm0 0v.7V0ZM0 7h.6H0Zm7 6.8v-.6.6ZM13.7 7h-.6.6ZM9.1 1.7c-.7-.3-1.4-.4-2.2-.4a5.6 5.6 0 0 0-4 1.6 5.6 5.6 0 0 0-1.6 4 5.6 5.6 0 0 0 1.6 4 5.6 5.6 0 0 0 4 1.7 5.6 5.6 0 0 0 4-1.7 5.6 5.6 0 0 0 1.7-4 5.6 5.6 0 0 0-1.7-4c-.5-.5-1.1-.9-1.8-1.2Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M7 2.9a.8.8 0 1 1 0 1.5A.8.8 0 0 1 7 3ZM5.8 5.7c0-.4.3-.6.6-.6h.7c.3 0 .6.2.6.6v3.7h.5a.6.6 0 0 1 0 1.3H6a.6.6 0 0 1 0-1.3h.4v-3a.6.6 0 0 1-.6-.7Z" clip-rule="evenodd"/></svg>
467
- You have successfully duplicated the MimicMotion Space 🎉</h2>
468
- <p class="custom-color">There's only one step left before you can properly play with this demo: <a href="https://huggingface.co/spaces/{os.environ['SPACE_ID']}/settings" style="text-decoration: underline" target="_blank">attribute a GPU</b> to it (via the Settings tab)</a> and run the app below.
469
- You will be billed by the minute from when you activate the GPU until when it is turned off.</p>
 
 
470
  <p class="actions custom-color">
471
- <a href="https://huggingface.co/spaces/{os.environ['SPACE_ID']}/settings">🔥 &nbsp; Set recommended GPU</a>
 
 
 
472
  </p>
473
  </div>
474
- ''', elem_id="warning-setgpu")
475
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  video_output = gr.Video(label="Output Video")
477
  seed_text = gr.Textbox(label="Seed", interactive=False, visible=False)
 
478
  gr.Examples(
479
  examples=[
480
  ["EMTD_dataset/ref_imgs_by_FLUX/man/0001.png", "assets/halfbody_demo/audio/chinese/echomimicv2_man.wav"],
@@ -483,20 +556,34 @@ with gr.Blocks(css=css) as demo:
483
  ["EMTD_dataset/ref_imgs_by_FLUX/woman/0033.png", "assets/halfbody_demo/audio/chinese/good.wav"],
484
  ["EMTD_dataset/ref_imgs_by_FLUX/man/0010.png", "assets/halfbody_demo/audio/chinese/news.wav"],
485
  ["EMTD_dataset/ref_imgs_by_FLUX/man/1168.png", "assets/halfbody_demo/audio/chinese/no_smoking.wav"],
486
- ["EMTD_dataset/ref_imgs_by_FLUX/woman/0057.png", "assets/halfbody_demo/audio/chinese/ultraman.wav"]
487
  ],
488
- inputs=[image_input, audio_input],
489
  label="Preset Characters and Audio",
490
  )
491
 
492
  generate_button.click(
493
  generate,
494
- inputs=[image_input, audio_input, pose_input, width, height, length, steps, sample_rate, cfg, fps, context_frames, context_overlap, quantization_input, seed],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  outputs=[video_output, seed_text],
496
  )
497
 
498
 
499
-
500
  if __name__ == "__main__":
501
  demo.queue()
502
- demo.launch(show_api=False, show_error=True, ssr_mode=False)
 
1
  import os
2
  import random
3
+ import gc
4
+ import tempfile
5
  from pathlib import Path
6
+ from datetime import datetime
7
+
8
  import numpy as np
9
  import torch
10
+ import gradio as gr
11
+ import requests
 
 
12
 
13
  from diffusers import AutoencoderKL, DDIMScheduler
14
  from PIL import Image
15
+ from moviepy.editor import VideoFileClip, AudioFileClip
16
+ from pydub import AudioSegment
17
+ from huggingface_hub import snapshot_download
18
+ from torchao.quantization import quantize_, int8_weight_only
19
+
20
  from src.models.unet_2d_condition import UNet2DConditionModel
21
  from src.models.unet_3d_emo import EMOUNet3DConditionModel
22
  from src.models.whisper.audio2feature import load_audio_model
 
24
  from src.utils.util import save_videos_grid
25
  from src.models.pose_encoder import PoseEncoder
26
  from src.utils.dwpose_util import draw_pose_select_v2
 
27
 
 
 
 
 
28
 
29
+ space_id = os.getenv("SPACE_ID", "")
30
+ is_shared_ui = "fffiloni/echomimic-v2" in space_id
31
+ is_gpu_associated = torch.cuda.is_available()
32
+
33
 
34
  def cut_audio_to_5_seconds(audio_path):
35
  try:
 
36
  audio = AudioSegment.from_file(audio_path)
 
 
37
  trimmed_audio = audio[:5000]
38
 
 
39
  temp_dir = tempfile.mkdtemp()
40
  output_path = os.path.join(temp_dir, "trimmed_audio.wav")
 
 
41
  trimmed_audio.export(output_path, format="wav")
42
 
43
  return output_path
44
  except Exception as e:
45
+ raise RuntimeError(f"Failed to trim audio: {e}") from e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
 
47
 
48
+ # Create the main pretrained_weights folder
 
 
49
  os.makedirs("pretrained_weights", exist_ok=True)
50
 
51
+ # Create expected subfolders
52
  subfolders = [
53
  "sd-vae-ft-mse",
54
  "sd-image-variations-diffusers",
55
+ "audio_processor",
56
  ]
57
 
 
58
  for subfolder in subfolders:
59
  os.makedirs(os.path.join("pretrained_weights", subfolder), exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
 
61
 
62
+ def ensure_snapshot(repo_id, local_dir, check_exists=None):
63
+ if check_exists is not None and os.path.exists(check_exists):
64
+ print(f"Skipping download for {repo_id}, found: {check_exists}")
65
+ return
66
+
67
+ print(f"Downloading {repo_id} to {local_dir} ...")
68
+ snapshot_download(repo_id=repo_id, local_dir=local_dir)
69
+ print(f"Downloaded {repo_id}")
70
+
71
+
72
  def download_whisper_model():
73
+ url = (
74
+ "https://openaipublic.azureedge.net/main/whisper/models/"
75
+ "65147644a518d12f04e32d6f3b26facc3f8dd46e5390956a9424a650c0ce22b9/tiny.pt"
76
+ )
77
  save_path = os.path.join("pretrained_weights", "audio_processor", "tiny.pt")
78
+
79
+ if os.path.exists(save_path):
80
+ print(f"Whisper model already present at {save_path}")
81
+ return save_path
82
+
83
  try:
84
+ print("Downloading Whisper tiny model...")
85
+ response = requests.get(url, stream=True, timeout=60)
86
+ response.raise_for_status()
87
+
88
  with open(save_path, "wb") as file:
89
  for chunk in response.iter_content(chunk_size=8192):
90
+ if chunk:
91
+ file.write(chunk)
92
+
93
  print(f"Whisper model downloaded and saved to {save_path}")
94
+ return save_path
95
  except Exception as e:
96
+ raise RuntimeError(f"Failed to download Whisper model: {e}") from e
97
+
98
+
99
+ # Download only when missing
100
+ ensure_snapshot(
101
+ repo_id="BadToBest/EchoMimicV2",
102
+ local_dir="./pretrained_weights",
103
+ check_exists="./pretrained_weights/reference_unet.pth",
104
+ )
105
+
106
+ ensure_snapshot(
107
+ repo_id="stabilityai/sd-vae-ft-mse",
108
+ local_dir="./pretrained_weights/sd-vae-ft-mse",
109
+ check_exists="./pretrained_weights/sd-vae-ft-mse/config.json",
110
+ )
111
 
112
+ ensure_snapshot(
113
+ repo_id="lambdalabs/sd-image-variations-diffusers",
114
+ local_dir="./pretrained_weights/sd-image-variations-diffusers",
115
+ check_exists="./pretrained_weights/sd-image-variations-diffusers/unet/config.json",
116
+ )
117
 
118
  if torch.cuda.is_available():
119
  device = "cuda"
120
+ dtype = torch.float16
121
 
 
122
  download_whisper_model()
123
 
124
  total_vram_in_gb = torch.cuda.get_device_properties(0).total_memory / 1073741824
125
+ print(f"\033[32mCUDA version: {torch.version.cuda}\033[0m")
126
+ print(f"\033[32mPyTorch version: {torch.__version__}\033[0m")
127
+ print(f"\033[32mGPU: {torch.cuda.get_device_name()}\033[0m")
128
+ print(f"\033[32mVRAM: {total_vram_in_gb:.2f} GB\033[0m")
129
+ print(f"\033[32mPrecision: float16\033[0m")
 
 
 
130
  else:
131
+ print("CUDA not available, using CPU")
132
  device = "cpu"
133
+ dtype = torch.float32
134
+
135
+
136
+ def generate(
137
+ image_input,
138
+ audio_input,
139
+ pose_input,
140
+ width,
141
+ height,
142
+ length,
143
+ steps,
144
+ sample_rate,
145
+ cfg,
146
+ fps,
147
+ context_frames,
148
+ context_overlap,
149
+ quantization_input,
150
+ seed,
151
+ progress=gr.Progress(track_tqdm=True),
152
+ ):
153
  gc.collect()
154
+ if torch.cuda.is_available():
155
+ torch.cuda.empty_cache()
156
+ torch.cuda.ipc_collect()
157
+
158
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
159
  save_dir = Path("outputs")
160
  save_dir.mkdir(exist_ok=True, parents=True)
161
 
162
+ width = int(width)
163
+ height = int(height)
164
+ length = int(length)
165
+ steps = int(steps)
166
+ sample_rate = int(sample_rate)
167
+ fps = int(fps)
168
+ context_frames = int(context_frames)
169
+ context_overlap = int(context_overlap)
170
+ seed = int(seed) if seed is not None else -1
171
+
172
+ # VAE
173
+ vae = AutoencoderKL.from_pretrained("./pretrained_weights/sd-vae-ft-mse").to(
174
+ device, dtype=dtype
175
+ )
176
  if quantization_input:
177
  quantize_(vae, int8_weight_only())
178
+ print("Using int8 quantization for VAE.")
179
 
180
+ # Reference UNet
181
+ reference_unet = UNet2DConditionModel.from_pretrained(
182
+ "./pretrained_weights/sd-image-variations-diffusers",
183
+ subfolder="unet",
184
+ use_safetensors=False,
185
+ ).to(dtype=dtype, device=device)
186
+ reference_unet.load_state_dict(
187
+ torch.load("./pretrained_weights/reference_unet.pth", map_location=device, weights_only=True)
188
+ )
189
  if quantization_input:
190
  quantize_(reference_unet, int8_weight_only())
191
+ print("Using int8 quantization for reference UNet.")
192
+
193
+ # Denoising UNet
194
+ motion_module_path = "./pretrained_weights/motion_module.pth"
195
+ if not os.path.exists(motion_module_path):
196
+ raise FileNotFoundError(f"Motion module not found: {motion_module_path}")
197
 
 
 
 
 
 
 
198
  denoising_unet = EMOUNet3DConditionModel.from_pretrained_2d(
199
  "./pretrained_weights/sd-image-variations-diffusers",
200
+ motion_module_path,
201
  subfolder="unet",
202
+ unet_additional_kwargs={
203
  "use_inflated_groupnorm": True,
204
  "unet_use_cross_frame_attention": False,
205
  "unet_use_temporal_attention": False,
206
  "use_motion_module": True,
207
  "cross_attention_dim": 384,
208
+ "motion_module_resolutions": [1, 2, 4, 8],
209
+ "motion_module_mid_block": True,
 
 
 
 
 
210
  "motion_module_decoder_only": False,
211
  "motion_module_type": "Vanilla",
212
+ "motion_module_kwargs": {
213
  "num_attention_heads": 8,
214
  "num_transformer_block": 1,
215
  "attention_block_types": [
216
+ "Temporal_Self",
217
+ "Temporal_Self",
218
  ],
219
  "temporal_position_encoding": True,
220
  "temporal_position_encoding_max_len": 32,
221
  "temporal_attention_dim_div": 1,
222
+ },
223
  },
224
  ).to(dtype=dtype, device=device)
 
225
 
226
+ denoising_unet.load_state_dict(
227
+ torch.load("./pretrained_weights/denoising_unet.pth", map_location=device, weights_only=True),
228
+ strict=False,
229
+ )
230
+
231
+ # Pose net
232
+ pose_net = PoseEncoder(
233
+ 320,
234
+ conditioning_channels=3,
235
+ block_out_channels=(16, 32, 96, 256),
236
+ ).to(dtype=dtype, device=device)
237
+
238
+ pose_net.load_state_dict(
239
+ torch.load("./pretrained_weights/pose_encoder.pth", map_location=device, weights_only=True)
240
+ )
241
+
242
+ # Audio processor
243
+ audio_processor = load_audio_model(
244
+ model_path="./pretrained_weights/audio_processor/tiny.pt",
245
+ device=device,
246
+ )
247
 
 
 
 
 
248
  sched_kwargs = {
249
  "beta_start": 0.00085,
250
  "beta_end": 0.012,
 
253
  "steps_offset": 1,
254
  "prediction_type": "v_prediction",
255
  "rescale_betas_zero_snr": True,
256
+ "timestep_spacing": "trailing",
257
  }
258
  scheduler = DDIMScheduler(**sched_kwargs)
259
 
 
265
  pose_encoder=pose_net,
266
  scheduler=scheduler,
267
  )
 
268
  pipe = pipe.to(device, dtype=dtype)
269
 
270
+ if seed > -1:
271
  generator = torch.manual_seed(seed)
272
  else:
273
+ seed = random.randint(100, 1_000_000)
274
  generator = torch.manual_seed(seed)
275
 
276
  if is_shared_ui:
 
283
  "pose": pose_input,
284
  }
285
 
286
+ print("Pose:", inputs_dict["pose"])
287
+ print("Reference:", inputs_dict["refimg"])
288
+ print("Audio:", inputs_dict["audio"])
289
 
290
  save_name = f"{save_dir}/{timestamp}"
291
+
292
+ ref_image_pil = Image.open(inputs_dict["refimg"]).convert("RGB").resize((width, height))
293
+ audio_clip = AudioFileClip(inputs_dict["audio"])
294
+
295
+ length = min(
296
+ length,
297
+ int(audio_clip.duration * fps),
298
+ len(os.listdir(inputs_dict["pose"])),
299
+ )
300
 
301
  start_idx = 0
302
 
303
  pose_list = []
304
  for index in range(start_idx, start_idx + length):
305
+ tgt_mask = np.zeros((height, width, 3), dtype="uint8")
306
+ tgt_mask_path = os.path.join(inputs_dict["pose"], f"{index}.npy")
307
+ detected_pose = np.load(tgt_mask_path, allow_pickle=True).tolist()
308
+
309
+ imh_new, imw_new, rb, re, cb, ce = detected_pose["draw_pose_params"]
310
  im = draw_pose_select_v2(detected_pose, imh_new, imw_new, ref_w=800)
311
+ im = np.transpose(np.array(im), (1, 2, 0))
312
+ tgt_mask[rb:re, cb:ce, :] = im
313
+
314
+ tgt_mask_pil = Image.fromarray(tgt_mask).convert("RGB")
315
+ pose_tensor = (
316
+ torch.tensor(np.array(tgt_mask_pil), device=device, dtype=dtype)
317
+ .permute(2, 0, 1)
318
+ / 255.0
319
+ )
320
+ pose_list.append(pose_tensor)
321
 
 
 
 
322
  poses_tensor = torch.stack(pose_list, dim=1).unsqueeze(0)
323
+
324
+ audio_clip = AudioFileClip(inputs_dict["audio"])
325
  audio_clip = audio_clip.set_duration(length / fps)
326
+
327
  video = pipe(
328
  ref_image_pil,
329
+ inputs_dict["audio"],
330
+ poses_tensor[:, :, :length, ...],
331
  width,
332
  height,
333
  length,
 
339
  fps=fps,
340
  context_overlap=context_overlap,
341
  start_idx=start_idx,
342
+ ).videos
343
+
344
  final_length = min(video.shape[2], poses_tensor.shape[2], length)
345
  video_sig = video[:, :, :final_length, :, :]
346
+
347
  save_videos_grid(
348
  video_sig,
349
  save_name + "_woa_sig.mp4",
 
351
  fps=fps,
352
  )
353
 
354
+ video_clip_sig = VideoFileClip(save_name + "_woa_sig.mp4")
355
  video_clip_sig = video_clip_sig.set_audio(audio_clip)
356
+ video_clip_sig.write_videofile(
357
+ save_name + "_sig.mp4",
358
+ codec="libx264",
359
+ audio_codec="aac",
360
+ threads=2,
361
+ )
362
+
363
  video_output = save_name + "_sig.mp4"
364
  seed_text = gr.update(visible=True, value=seed)
365
  return video_output, seed_text
366
 
367
+
368
  css = """
369
  div#warning-duplicate {
370
  background-color: #ebf5ff;
 
423
  }
424
  """
425
 
426
+
427
  with gr.Blocks(css=css) as demo:
428
+ gr.Markdown(
429
+ """
430
+ # EchoMimicV2
431
+
432
+ ⚠️ This demonstration is for academic research and experiential use only.
433
+ """
434
+ )
435
+
436
+ gr.HTML(
437
+ """
438
  <div style="display:flex;column-gap:4px;">
439
  <a href="https://github.com/antgroup/echomimic_v2">
440
  <img src='https://img.shields.io/badge/GitHub-Repo-blue'>
441
+ </a>
442
  <a href="https://antgroup.github.io/ai/echomimic_v2/">
443
  <img src='https://img.shields.io/badge/Project-Page-green'>
444
  </a>
445
+ <a href="https://arxiv.org/abs/2411.10061">
446
  <img src='https://img.shields.io/badge/ArXiv-Paper-red'>
447
  </a>
448
  <a href="https://huggingface.co/spaces/fffiloni/echomimic-v2?duplicate=true">
449
+ <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-sm.svg" alt="Duplicate this Space">
450
+ </a>
451
+ <a href="https://huggingface.co/fffiloni">
452
+ <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/follow-me-on-HF-sm-dark.svg" alt="Follow me on HF">
453
+ </a>
454
  </div>
455
+ """
456
+ )
457
+
458
  with gr.Column():
459
  with gr.Row():
460
  with gr.Column():
461
  with gr.Group():
462
  image_input = gr.Image(label="Image Input (Auto Scaling)", type="filepath")
463
  audio_input = gr.Audio(label="Audio Input - max 5 seconds on shared UI", type="filepath")
464
+ pose_input = gr.Textbox(
465
+ label="Pose Input (Directory Path)",
466
+ placeholder="Please enter the directory path for pose data.",
467
+ value="assets/halfbody_demo/pose/01",
468
+ interactive=False,
469
+ visible=False,
470
+ )
471
+
472
  with gr.Accordion("Advanced Settings", open=False):
473
  with gr.Row():
474
  width = gr.Number(label="Width (multiple of 16, recommended: 768)", value=768)
475
  height = gr.Number(label="Height (multiple of 16, recommended: 768)", value=768)
476
+ length = gr.Number(label="Video Length (recommended: 240)", value=240)
477
+
478
  with gr.Row():
479
  steps = gr.Number(label="Steps (recommended: 30)", value=20)
480
  sample_rate = gr.Number(label="Sampling Rate (recommended: 16000)", value=16000)
481
  cfg = gr.Number(label="CFG (recommended: 2.5)", value=2.5, step=0.1)
482
+
483
  with gr.Row():
484
  fps = gr.Number(label="Frame Rate (recommended: 24)", value=24)
485
  context_frames = gr.Number(label="Context Frames (recommended: 12)", value=12)
486
  context_overlap = gr.Number(label="Context Overlap (recommended: 3)", value=3)
487
+
488
  with gr.Row():
489
+ quantization_input = gr.Checkbox(
490
+ label="Int8 Quantization (recommended for users with 12GB VRAM, use audio no longer than 5 seconds)",
491
+ value=False,
492
+ )
493
  seed = gr.Number(label="Seed (-1 for random)", value=-1)
 
 
494
 
495
+ generate_button = gr.Button("🎬 Generate Video", interactive=not is_shared_ui)
496
+
497
+ with gr.Column():
498
  if is_shared_ui:
499
+ top_description = gr.HTML(
500
+ f'''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  <div class="gr-prose">
502
+ <h2 class="custom-color"><svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" style="margin-right: 0px;display: inline-block;" fill="none"><path fill="#fff" d="M7 13.2a6.3 6.3 0 0 0 4.4-10.7A6.3 6.3 0 0 0 .6 6.9 6.3 6.3 0 0 0 7 13.2Z"/><path fill="#fff" fill-rule="evenodd" d="M7 0a6.9 6.9 0 0 1 4.8 11.8A6.9 6.9 0 0 1 0 7 6.9 6.9 0 0 1 7 0Zm0 0v.7V0ZM0 7h.6H0Zm7 6.8v-.6.6ZM13.7 7h-.6.6ZM9.1 1.7c-.7-.3-1.4-.4-2.2-.4a5.6 5.6 0 0 0-4 1.6 5.6 5.6 0 0 0-1.6 4 5.6 5.6 0 0 0 1.6 4 5.6 5.6 0 0 0 4 1.7 5.6 5.6 0 0 0 4-1.7 5.6 5.6 0 0 0 1.7-4 5.6 5.6 0 0 0-1.7-4c-.5-.5-1.1-.9-1.8-1.2Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M7 2.9a.8.8 0 1 1 0 1.5A.8.8 0 0 1 7 3ZM5.8 5.7c0-.4.3-.6.6-.6h.7c.3 0 .6.2.6.6v3.7h.5a.6.6 0 0 1 0 1.3H6a.6.6 0 0 1 0-1.3h.4v-3a.6.6 0 0 1-.6-.7Z" clip-rule="evenodd"/></svg>
503
+ Attention: this Space needs to be duplicated to work</h2>
504
+ <p class="main-message custom-color">
505
+ To make it work, <strong>duplicate the Space</strong> and run it on your own profile using a <strong>private</strong> GPU (L40s recommended).<br />
506
+ An L40s costs <strong>US$1.80/h</strong>.
507
+ </p>
508
  <p class="actions custom-color">
509
+ <a href="https://huggingface.co/spaces/{space_id}?duplicate=true">
510
+ <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-lg-dark.svg" alt="Duplicate this Space" />
511
+ </a>
512
+ to start experimenting with this demo
513
  </p>
514
  </div>
515
+ ''',
516
+ elem_id="warning-duplicate",
517
+ )
518
+ else:
519
+ if is_gpu_associated:
520
+ top_description = gr.HTML(
521
+ '''
522
+ <div class="gr-prose">
523
+ <h2 class="custom-color"><svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" style="margin-right: 0px;display: inline-block;" fill="none"><path fill="#fff" d="M7 13.2a6.3 6.3 0 0 0 4.4-10.7A6.3 6.3 0 0 0 .6 6.9 6.3 6.3 0 0 0 7 13.2Z"/><path fill="#fff" fill-rule="evenodd" d="M7 0a6.9 6.9 0 0 1 4.8 11.8A6.9 6.9 0 0 1 0 7 6.9 6.9 0 0 1 7 0Zm0 0v.7V0ZM0 7h.6H0Zm7 6.8v-.6.6ZM13.7 7h-.6.6ZM9.1 1.7c-.7-.3-1.4-.4-2.2-.4a5.6 5.6 0 0 0-4 1.6 5.6 5.6 0 0 0-1.6 4 5.6 5.6 0 0 0 1.6 4 5.6 5.6 0 0 0 4 1.7 5.6 5.6 0 0 0 4-1.7 5.6 5.6 0 0 0 1.7-4 5.6 5.6 0 0 0-1.7-4c-.5-.5-1.1-.9-1.8-1.2Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M7 2.9a.8.8 0 1 1 0 1.5A.8.8 0 0 1 7 3ZM5.8 5.7c0-.4.3-.6.6-.6h.7c.3 0 .6.2.6.6v3.7h.5a.6.6 0 0 1 0 1.3H6a.6.6 0 0 1 0-1.3h.4v-3a.6.6 0 0 1-.6-.7Z" clip-rule="evenodd"/></svg>
524
+ You have successfully associated a GPU to this Space 🎉</h2>
525
+ <p class="custom-color">
526
+ You will be billed by the minute from when you activated the GPU until when it is turned off.
527
+ </p>
528
+ </div>
529
+ ''',
530
+ elem_id="warning-ready",
531
+ )
532
+ else:
533
+ top_description = gr.HTML(
534
+ f'''
535
+ <div class="gr-prose">
536
+ <h2 class="custom-color"><svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" style="margin-right: 0px;display: inline-block;" fill="none"><path fill="#fff" d="M7 13.2a6.3 6.3 0 0 0 4.4-10.7A6.3 6.3 0 0 0 .6 6.9 6.3 6.3 0 0 0 7 13.2Z"/><path fill="#fff" fill-rule="evenodd" d="M7 0a6.9 6.9 0 0 1 4.8 11.8A6.9 6.9 0 0 1 0 7 6.9 6.9 0 0 1 7 0Zm0 0v.7V0ZM0 7h.6H0Zm7 6.8v-.6.6ZM13.7 7h-.6.6ZM9.1 1.7c-.7-.3-1.4-.4-2.2-.4a5.6 5.6 0 0 0-4 1.6 5.6 5.6 0 0 0-1.6 4 5.6 5.6 0 0 0 1.6 4 5.6 5.6 0 0 0 4 1.7 5.6 5.6 0 0 0 4-1.7 5.6 5.6 0 0 0 1.7-4 5.6 5.6 0 0 0-1.7-4c-.5-.5-1.1-.9-1.8-1.2Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M7 2.9a.8.8 0 1 1 0 1.5A.8.8 0 0 1 7 3ZM5.8 5.7c0-.4.3-.6.6-.6h.7c.3 0 .6.2.6.6v3.7h.5a.6.6 0 0 1 0 1.3H6a.6.6 0 0 1 0-1.3h.4v-3a.6.6 0 0 1-.6-.7Z" clip-rule="evenodd"/></svg>
537
+ You have successfully duplicated the MimicMotion Space 🎉</h2>
538
+ <p class="custom-color">There's only one step left before you can properly play with this demo: <a href="https://huggingface.co/spaces/{space_id}/settings" style="text-decoration: underline" target="_blank">attach a GPU</a> to it (via the Settings tab) and run the app below.
539
+ You will be billed by the minute from when you activate the GPU until when it is turned off.</p>
540
+ <p class="actions custom-color">
541
+ <a href="https://huggingface.co/spaces/{space_id}/settings">🔥 &nbsp; Set recommended GPU</a>
542
+ </p>
543
+ </div>
544
+ ''',
545
+ elem_id="warning-setgpu",
546
+ )
547
+
548
  video_output = gr.Video(label="Output Video")
549
  seed_text = gr.Textbox(label="Seed", interactive=False, visible=False)
550
+
551
  gr.Examples(
552
  examples=[
553
  ["EMTD_dataset/ref_imgs_by_FLUX/man/0001.png", "assets/halfbody_demo/audio/chinese/echomimicv2_man.wav"],
 
556
  ["EMTD_dataset/ref_imgs_by_FLUX/woman/0033.png", "assets/halfbody_demo/audio/chinese/good.wav"],
557
  ["EMTD_dataset/ref_imgs_by_FLUX/man/0010.png", "assets/halfbody_demo/audio/chinese/news.wav"],
558
  ["EMTD_dataset/ref_imgs_by_FLUX/man/1168.png", "assets/halfbody_demo/audio/chinese/no_smoking.wav"],
559
+ ["EMTD_dataset/ref_imgs_by_FLUX/woman/0057.png", "assets/halfbody_demo/audio/chinese/ultraman.wav"],
560
  ],
561
+ inputs=[image_input, audio_input],
562
  label="Preset Characters and Audio",
563
  )
564
 
565
  generate_button.click(
566
  generate,
567
+ inputs=[
568
+ image_input,
569
+ audio_input,
570
+ pose_input,
571
+ width,
572
+ height,
573
+ length,
574
+ steps,
575
+ sample_rate,
576
+ cfg,
577
+ fps,
578
+ context_frames,
579
+ context_overlap,
580
+ quantization_input,
581
+ seed,
582
+ ],
583
  outputs=[video_output, seed_text],
584
  )
585
 
586
 
 
587
  if __name__ == "__main__":
588
  demo.queue()
589
+ demo.launch(show_error=True, ssr_mode=False)