affaf12 commited on
Commit
3919971
·
verified ·
1 Parent(s): b563f84

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +35 -97
app.py CHANGED
@@ -48,66 +48,17 @@ def get_duration(path):
48
  return -1.0
49
 
50
 
51
- def _concat_audios(audio_paths, per_clip_trim_seconds=0):
52
  """
53
- Normalize each uploaded audio to the same format (mono, 16kHz, pcm_s16le),
54
- OPTIONALLY trim each one to a fixed length (per_clip_trim_seconds, e.g.
55
- 10/15/20s — 0 means "no trim, use full clip"), then concatenate them in
56
- the order the user uploaded them, using ffmpeg's concat demuxer.
57
- Normalizing first is required — concat demuxer with `-c copy` only works
58
- reliably when every input already shares the same codec/sample-rate/
59
- channel-layout.
60
- """
61
- os.makedirs("temp", exist_ok=True)
62
- norm_paths = []
63
- for i, p in enumerate(audio_paths):
64
- norm_path = f"temp/norm_{i}.wav"
65
- cmd = ["ffmpeg", "-y", "-i", p]
66
- if per_clip_trim_seconds and per_clip_trim_seconds > 0:
67
- cmd += ["-t", str(per_clip_trim_seconds)]
68
- cmd += ["-ac", "1", "-ar", "16000", "-c:a", "pcm_s16le", norm_path]
69
- subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
70
- norm_paths.append(norm_path)
71
-
72
- if len(norm_paths) == 1:
73
- return norm_paths[0]
74
-
75
- list_file = "temp/concat_list.txt"
76
- with open(list_file, "w") as f:
77
- for p in norm_paths:
78
- f.write(f"file '{os.path.abspath(p)}'\n")
79
-
80
- combined_path = "temp/combined_audio.wav"
81
- subprocess.check_call(
82
- ["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", list_file, "-c", "copy", combined_path],
83
- stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
84
- )
85
- return combined_path
86
-
87
-
88
- def clean_audio(audio_paths, per_clip_trim_seconds, max_total_seconds):
89
- """
90
- Cleaning pipeline (supports one OR multiple uploaded audio files):
91
- - if multiple files given, concatenates them in upload order first
92
- - if per_clip_trim_seconds > 0, each individual clip is trimmed to that
93
- length BEFORE joining (e.g. 3 clips capped at 15s each -> ~45s total)
94
  - trims ONLY leading/trailing silence (mid-audio pauses/breaths are kept)
95
  - loudness-normalizes
96
  - reduces background noise
97
- - THEN hard-caps the final combined result to max_total_seconds as a
98
- safety limit (protects the free daily ZeroGPU quota from an
99
- accidental huge upload — longer combined audio = proportionally more
100
- GPU time).
101
  """
102
- if not audio_paths:
103
- raise gr.Error("Pehle kam az kam ek audio file upload karein.")
104
- if isinstance(audio_paths, str):
105
- audio_paths = [audio_paths]
106
-
107
- try:
108
- combined = _concat_audios(audio_paths, per_clip_trim_seconds)
109
- except subprocess.CalledProcessError as e:
110
- raise gr.Error(f"Audio files jodte waqt error aayi: {e}")
111
 
112
  os.makedirs("temp", exist_ok=True)
113
  ffmpeg_out = "temp/ffmpeg_stage.wav"
@@ -117,7 +68,7 @@ def clean_audio(audio_paths, per_clip_trim_seconds, max_total_seconds):
117
  try:
118
  subprocess.check_call(
119
  [
120
- "ffmpeg", "-y", "-i", combined,
121
  "-af",
122
  "silenceremove=start_periods=1:start_threshold=-45dB:start_silence=0.1,"
123
  "areverse,"
@@ -132,15 +83,16 @@ def clean_audio(audio_paths, per_clip_trim_seconds, max_total_seconds):
132
  except subprocess.CalledProcessError as e:
133
  raise gr.Error(f"Audio clean karte waqt error aayi: {e}")
134
 
135
- # Safety cap — combined length can't exceed the selected max
136
- try:
137
- subprocess.check_call(
138
- ["ffmpeg", "-y", "-i", ffmpeg_out, "-t", str(max_total_seconds), trimmed_out],
139
- stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
140
- )
141
- trim_source = trimmed_out
142
- except subprocess.CalledProcessError:
143
- trim_source = ffmpeg_out
 
144
 
145
  try:
146
  data, sr = sf.read(trim_source)
@@ -150,26 +102,24 @@ def clean_audio(audio_paths, per_clip_trim_seconds, max_total_seconds):
150
  out_path = trim_source
151
 
152
  final_dur = get_duration(out_path)
153
- print(f"[INFO] combined+cleaned audio duration: {final_dur:.2f}s (cap={max_total_seconds}s, files={len(audio_paths)})")
154
 
155
  return out_path, gr.update(interactive=True)
156
 
157
 
158
- def estimate_gpu_duration(avatar_image, cleaned_audio, enhance_face, still_mode, max_total_seconds, framing, progress=None):
159
  """
160
- Uses the ACTUAL final cleaned/combined audio duration (via ffprobe),
161
- since with multi-audio concatenation the real length can vary up to
162
- the safety cap rather than always being a fixed 10/15/20s value.
163
  """
164
  base_overhead = 40 # model load / warmup, roughly fixed
165
  audio_len = get_duration(cleaned_audio) if cleaned_audio else 10
166
  if audio_len <= 0:
167
- audio_len = float(max_total_seconds) if max_total_seconds else 10
168
 
169
  per_second_cost = 9 if enhance_face else 4.5
170
  estimated = (base_overhead + audio_len * per_second_cost) * 1.25
171
 
172
- # Hard cap raised to comfortably cover longer multi-audio combos.
173
  return int(min(max(estimated, 50), 280))
174
 
175
 
@@ -293,7 +243,7 @@ def _build_attempt_plan(enhance_face, still_mode):
293
 
294
 
295
  @spaces.GPU(duration=estimate_gpu_duration)
296
- def run(avatar_image, cleaned_audio, enhance_face, still_mode, max_total_seconds, framing, progress=gr.Progress()):
297
  if avatar_image is None:
298
  raise gr.Error("Pehle avatar photo upload karein.")
299
  if cleaned_audio is None:
@@ -334,38 +284,26 @@ def run(avatar_image, cleaned_audio, enhance_face, still_mode, max_total_seconds
334
 
335
  with gr.Blocks(title="NextGen Analytics — Avatar Talking Video (SadTalker)") as demo:
336
  gr.Markdown(
337
- "# NextGen Analytics — Avatar Talking Video Generator (v4, SadTalker)\n"
338
  "Head movement, eye blink, aur natural expression shamil hain.\n\n"
339
  "1) Avatar photo upload karein (clear, front-facing, shoulders tak visible ho to behtar)\n"
340
- "2) Voice audio upload karein — **ek ya zyada files** (multiple select karein to woh order mein jud kar ek lambi video banegi)\n"
341
- "3) Chahain to **har audio ko ek fix length tak trim** karein (10/15/20 sec — 'No Trim' se poori clip use hogi)\n"
342
- "4) Final combined length ka safety cap choose karein (30/45/60 sec)\n"
343
- "5) **Clean Audio** dabayein\n"
344
- "6) **Generate Video** dabayein\n\n"
345
  "✅ **Reliability upgrade**: Video ka duration/metadata ffprobe se verify hota hai (NaN:NaN wala issue "
346
  "fix), aur agar generation fail ho ya corrupt nikle to system khud-ba-khud safer settings ke saath "
347
- "dobara try karta hai.\n\n"
348
- "⚠️ **Note**: Lambi video (jaise 45-60 sec, kai audios jod ke) zyada ZeroGPU time leti hai — free daily "
349
- "quota jaldi khatam ho sakta hai."
350
  )
351
  with gr.Row():
352
  with gr.Column():
353
  avatar_input = gr.Image(label="Avatar Photo", type="filepath", sources=["upload"])
354
- audio_input = gr.File(
355
- label="Voice Audio(s) — ek ya zyada files select karein (order wahi rahega jis order mein select karenge)",
356
- file_count="multiple",
357
- file_types=["audio"],
358
- )
359
- per_clip_trim = gr.Radio(
360
- label="Trim Har Audio Ko (join karne se pehle)",
361
- choices=[("No Trim (poori clip use karein)", 0), ("10 sec", 10), ("15 sec", 15), ("20 sec", 20)],
362
  value=0,
363
  )
364
- max_total_seconds = gr.Radio(
365
- label="Max Total Length (final safety cap — sab clips jud ne ke baad)",
366
- choices=[("30 sec", 30), ("45 sec", 45), ("60 sec", 60)],
367
- value=30,
368
- )
369
  framing = gr.Radio(
370
  label="Framing",
371
  choices=[("Full Body / Shoulders (jaisa original photo mein hai)", "full"), ("Face Close-up (crop)", "crop")],
@@ -384,12 +322,12 @@ with gr.Blocks(title="NextGen Analytics — Avatar Talking Video (SadTalker)") a
384
 
385
  clean_btn.click(
386
  fn=clean_audio,
387
- inputs=[audio_input, per_clip_trim, max_total_seconds],
388
  outputs=[cleaned_audio_preview, generate_btn],
389
  )
390
  generate_btn.click(
391
  fn=run,
392
- inputs=[avatar_input, cleaned_audio_preview, enhance_face, still_mode, max_total_seconds, framing],
393
  outputs=[video_output],
394
  api_name="run",
395
  )
 
48
  return -1.0
49
 
50
 
51
+ def clean_audio(audio_path, trim_seconds):
52
  """
53
+ Cleaning pipeline for a single uploaded audio clip:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  - trims ONLY leading/trailing silence (mid-audio pauses/breaths are kept)
55
  - loudness-normalizes
56
  - reduces background noise
57
+ - if trim_seconds > 0, hard-trims the final result to that length
58
+ (e.g. 10/15/20s). trim_seconds = 0 means "no trim, keep full clip".
 
 
59
  """
60
+ if not audio_path:
61
+ raise gr.Error("Pehle audio upload karein.")
 
 
 
 
 
 
 
62
 
63
  os.makedirs("temp", exist_ok=True)
64
  ffmpeg_out = "temp/ffmpeg_stage.wav"
 
68
  try:
69
  subprocess.check_call(
70
  [
71
+ "ffmpeg", "-y", "-i", audio_path,
72
  "-af",
73
  "silenceremove=start_periods=1:start_threshold=-45dB:start_silence=0.1,"
74
  "areverse,"
 
83
  except subprocess.CalledProcessError as e:
84
  raise gr.Error(f"Audio clean karte waqt error aayi: {e}")
85
 
86
+ trim_source = ffmpeg_out
87
+ if trim_seconds and trim_seconds > 0:
88
+ try:
89
+ subprocess.check_call(
90
+ ["ffmpeg", "-y", "-i", ffmpeg_out, "-t", str(trim_seconds), trimmed_out],
91
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
92
+ )
93
+ trim_source = trimmed_out
94
+ except subprocess.CalledProcessError:
95
+ trim_source = ffmpeg_out # if trim fails, fall back to un-trimmed cleaned audio
96
 
97
  try:
98
  data, sr = sf.read(trim_source)
 
102
  out_path = trim_source
103
 
104
  final_dur = get_duration(out_path)
105
+ print(f"[INFO] cleaned audio duration: {final_dur:.2f}s (trim={trim_seconds or 'none'}s)")
106
 
107
  return out_path, gr.update(interactive=True)
108
 
109
 
110
+ def estimate_gpu_duration(avatar_image, cleaned_audio, enhance_face, still_mode, trim_seconds, framing, progress=None):
111
  """
112
+ Uses the ACTUAL final cleaned audio duration (via ffprobe) to size the
113
+ GPU time request accurately.
 
114
  """
115
  base_overhead = 40 # model load / warmup, roughly fixed
116
  audio_len = get_duration(cleaned_audio) if cleaned_audio else 10
117
  if audio_len <= 0:
118
+ audio_len = float(trim_seconds) if trim_seconds else 10
119
 
120
  per_second_cost = 9 if enhance_face else 4.5
121
  estimated = (base_overhead + audio_len * per_second_cost) * 1.25
122
 
 
123
  return int(min(max(estimated, 50), 280))
124
 
125
 
 
243
 
244
 
245
  @spaces.GPU(duration=estimate_gpu_duration)
246
+ def run(avatar_image, cleaned_audio, enhance_face, still_mode, trim_seconds, framing, progress=gr.Progress()):
247
  if avatar_image is None:
248
  raise gr.Error("Pehle avatar photo upload karein.")
249
  if cleaned_audio is None:
 
284
 
285
  with gr.Blocks(title="NextGen Analytics — Avatar Talking Video (SadTalker)") as demo:
286
  gr.Markdown(
287
+ "# NextGen Analytics — Avatar Talking Video Generator (v5, SadTalker)\n"
288
  "Head movement, eye blink, aur natural expression shamil hain.\n\n"
289
  "1) Avatar photo upload karein (clear, front-facing, shoulders tak visible ho to behtar)\n"
290
+ "2) Voice audio upload karein\n"
291
+ "3) Chahain to audio ko ek fix length tak **trim** karein (10/15/20 sec — 'No Trim' se poori audio use hogi)\n"
292
+ "4) **Clean Audio** dabayein\n"
293
+ "5) **Generate Video** dabayein\n\n"
 
294
  "✅ **Reliability upgrade**: Video ka duration/metadata ffprobe se verify hota hai (NaN:NaN wala issue "
295
  "fix), aur agar generation fail ho ya corrupt nikle to system khud-ba-khud safer settings ke saath "
296
+ "dobara try karta hai."
 
 
297
  )
298
  with gr.Row():
299
  with gr.Column():
300
  avatar_input = gr.Image(label="Avatar Photo", type="filepath", sources=["upload"])
301
+ audio_input = gr.Audio(label="Voice Audio", type="filepath", sources=["upload"])
302
+ trim_seconds = gr.Radio(
303
+ label="Trim Audio To",
304
+ choices=[("No Trim (poori audio use karein)", 0), ("10 sec", 10), ("15 sec", 15), ("20 sec", 20)],
 
 
 
 
305
  value=0,
306
  )
 
 
 
 
 
307
  framing = gr.Radio(
308
  label="Framing",
309
  choices=[("Full Body / Shoulders (jaisa original photo mein hai)", "full"), ("Face Close-up (crop)", "crop")],
 
322
 
323
  clean_btn.click(
324
  fn=clean_audio,
325
+ inputs=[audio_input, trim_seconds],
326
  outputs=[cleaned_audio_preview, generate_btn],
327
  )
328
  generate_btn.click(
329
  fn=run,
330
+ inputs=[avatar_input, cleaned_audio_preview, enhance_face, still_mode, trim_seconds, framing],
331
  outputs=[video_output],
332
  api_name="run",
333
  )