carm5333 commited on
Commit
2bdaa49
Β·
verified Β·
1 Parent(s): f188534

S190: WPE runs in a timeout-guarded subprocess instead of unbounded in-process

Browse files
Files changed (1) hide show
  1. engine_safaa_v4.py +56 -25
engine_safaa_v4.py CHANGED
@@ -41,7 +41,7 @@ from __future__ import annotations
41
 
42
  __version__ = 'v4'
43
 
44
- import os, shutil, subprocess, tempfile, warnings
45
  from concurrent.futures import ThreadPoolExecutor, as_completed
46
  from dataclasses import dataclass, field
47
  from pathlib import Path
@@ -397,23 +397,13 @@ def _lra_overlapping(s, frame_s=0.4, hop_s=0.2, sr=SR):
397
  for i in range(0, len(s) - fn, hn)]
398
  return float(np.percentile(db, 95) - np.percentile(db, 10)) if len(db) >= 4 else 0.0
399
 
400
- def _wpe(wav, rt60, st):
401
- if not WPE_OK:
402
- _L(st, ' [S3-WPE] nara_wpe not installed β†’ pip install nara_wpe soundfile')
403
- return wav
404
- if rt60 < RT60_WPE_MIN:
405
- _L(st, f' [S3-WPE] RT60={rt60:.2f}s < {RT60_WPE_MIN}s β€” skip (Β§109.6)')
406
- return wav
407
 
408
- _L(st, f' [S3-WPE] RT60={rt60:.2f}s β€” running WPE')
409
- d = tempfile.mkdtemp(prefix='safaa3_wpe_')
 
 
410
  try:
411
- mi = os.path.join(d, 'in.wav')
412
- rc, _, _ = _run(['ffmpeg', '-y', '-i', wav,
413
- '-acodec', 'pcm_f32le', '-ar', str(SR), '-ac', '1',
414
- '-loglevel', 'error', mi])
415
- if rc or not os.path.exists(mi):
416
- return wav
417
  if SF_OK:
418
  y, _ = SF.read(mi, dtype='float32', always_2d=False)
419
  else:
@@ -421,10 +411,10 @@ def _wpe(wav, rt60, st):
421
  with _w.open(mi, 'rb') as f: raw = f.readframes(f.getnframes())
422
  y = np.frombuffer(raw, dtype=np.float32).copy()
423
 
424
- if st.mujawwad_conf > 0.6: taps, iters = 5, 2
425
- elif rt60 > 4.0: taps, iters = 12, 3
426
- elif rt60 > 2.0: taps, iters = 10, 3
427
- else: taps, iters = 8, 3
428
  delay = 3
429
 
430
  Y = _wpe_stft(y, size=512, shift=128)
@@ -434,17 +424,15 @@ def _wpe(wav, rt60, st):
434
 
435
  ld = abs(_lra_overlapping(y) - _lra_overlapping(z)) # [I5]
436
  if ld > _LRA_MAX_DELTA:
437
- _L(st, f' [S3-WPE] LRA Ξ”={ld:.2f}LU β€” retry iters-1')
438
  Z2 = wpe_v8(Y[..., np.newaxis], taps=taps, delay=delay, iterations=max(1, iters - 1))
439
  z2 = _wpe_istft(Z2[..., 0], size=512, shift=128)
440
  z2 = z2[:len(y)] if len(z2) > len(y) else np.pad(z2, (0, len(y) - len(z2)))
441
  ld2 = abs(_lra_overlapping(y) - _lra_overlapping(z2))
442
  if ld2 > _LRA_MAX_DELTA:
443
- st.guard_reverts += 1
444
- _L(st, f' [S3-WPE] LRA {ld2:.2f}LU after retry β€” REVERT'); return wav
445
  z = z2; ld = ld2
446
 
447
- mo = os.path.join(d, 'out.wav')
448
  if SF_OK:
449
  SF.write(mo, z.astype(np.float32), SR, subtype='FLOAT')
450
  else:
@@ -453,11 +441,54 @@ def _wpe(wav, rt60, st):
453
  with _w.open(mo, 'wb') as f:
454
  f.setnchannels(1); f.setsampwidth(2); f.setframerate(SR)
455
  f.writeframes(b16.tobytes())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  out = _tmp('s3', st)
457
  if not _enc_mono(mo, out):
458
  return wav
459
  st.wpe = True
460
- _L(st, f' [S3-WPE] βœ“ taps={taps} delay={delay} iters={iters} LRA Ξ”={ld:.2f}LU')
 
461
  return out
462
  except Exception as e:
463
  _L(st, f' [S3-WPE] exception: {e}'); return wav
 
41
 
42
  __version__ = 'v4'
43
 
44
+ import os, shutil, subprocess, tempfile, warnings, multiprocessing as mp
45
  from concurrent.futures import ThreadPoolExecutor, as_completed
46
  from dataclasses import dataclass, field
47
  from pathlib import Path
 
397
  for i in range(0, len(s) - fn, hn)]
398
  return float(np.percentile(db, 95) - np.percentile(db, 10)) if len(db) >= 4 else 0.0
399
 
400
+ WPE_TIMEOUT_S = 240 # S190: hard cap so a slow/atypical file can't eat the whole 600s job budget
 
 
 
 
 
 
401
 
402
+ def _wpe_compute_worker(mi, mo, rt60, mujawwad_conf, q):
403
+ """Runs in a separate forked process so a slow/hung wpe_v8 call can be
404
+ killed without taking down the whole engine. Writes result wav to `mo`
405
+ on success; only a small dict goes back through the queue."""
406
  try:
 
 
 
 
 
 
407
  if SF_OK:
408
  y, _ = SF.read(mi, dtype='float32', always_2d=False)
409
  else:
 
411
  with _w.open(mi, 'rb') as f: raw = f.readframes(f.getnframes())
412
  y = np.frombuffer(raw, dtype=np.float32).copy()
413
 
414
+ if mujawwad_conf > 0.6: taps, iters = 5, 2
415
+ elif rt60 > 4.0: taps, iters = 12, 3
416
+ elif rt60 > 2.0: taps, iters = 10, 3
417
+ else: taps, iters = 8, 3
418
  delay = 3
419
 
420
  Y = _wpe_stft(y, size=512, shift=128)
 
424
 
425
  ld = abs(_lra_overlapping(y) - _lra_overlapping(z)) # [I5]
426
  if ld > _LRA_MAX_DELTA:
 
427
  Z2 = wpe_v8(Y[..., np.newaxis], taps=taps, delay=delay, iterations=max(1, iters - 1))
428
  z2 = _wpe_istft(Z2[..., 0], size=512, shift=128)
429
  z2 = z2[:len(y)] if len(z2) > len(y) else np.pad(z2, (0, len(y) - len(z2)))
430
  ld2 = abs(_lra_overlapping(y) - _lra_overlapping(z2))
431
  if ld2 > _LRA_MAX_DELTA:
432
+ q.put({'ok': False, 'reason': f'LRA {ld2:.2f}LU after retry β€” REVERT'})
433
+ return
434
  z = z2; ld = ld2
435
 
 
436
  if SF_OK:
437
  SF.write(mo, z.astype(np.float32), SR, subtype='FLOAT')
438
  else:
 
441
  with _w.open(mo, 'wb') as f:
442
  f.setnchannels(1); f.setsampwidth(2); f.setframerate(SR)
443
  f.writeframes(b16.tobytes())
444
+ q.put({'ok': True, 'taps': taps, 'delay': delay, 'iters': iters, 'ld': float(ld)})
445
+ except Exception as e:
446
+ q.put({'ok': False, 'reason': f'exception: {e}'})
447
+
448
+ def _wpe(wav, rt60, st):
449
+ if not WPE_OK:
450
+ _L(st, ' [S3-WPE] nara_wpe not installed β†’ pip install nara_wpe soundfile')
451
+ return wav
452
+ if rt60 < RT60_WPE_MIN:
453
+ _L(st, f' [S3-WPE] RT60={rt60:.2f}s < {RT60_WPE_MIN}s β€” skip (Β§109.6)')
454
+ return wav
455
+
456
+ _L(st, f' [S3-WPE] RT60={rt60:.2f}s β€” running WPE (cap {WPE_TIMEOUT_S}s)')
457
+ d = tempfile.mkdtemp(prefix='safaa3_wpe_')
458
+ try:
459
+ mi = os.path.join(d, 'in.wav')
460
+ rc, _, _ = _run(['ffmpeg', '-y', '-i', wav,
461
+ '-acodec', 'pcm_f32le', '-ar', str(SR), '-ac', '1',
462
+ '-loglevel', 'error', mi])
463
+ if rc or not os.path.exists(mi):
464
+ return wav
465
+
466
+ mo = os.path.join(d, 'out.wav')
467
+ ctx = mp.get_context('fork')
468
+ q = ctx.Queue()
469
+ proc = ctx.Process(target=_wpe_compute_worker, args=(mi, mo, rt60, st.mujawwad_conf, q))
470
+ proc.start()
471
+ proc.join(WPE_TIMEOUT_S)
472
+ if proc.is_alive():
473
+ proc.terminate(); proc.join(5)
474
+ if proc.is_alive():
475
+ proc.kill(); proc.join(5)
476
+ st.guard_reverts += 1
477
+ _L(st, f' [S3-WPE] TIMED OUT after {WPE_TIMEOUT_S}s β€” using source for this stage only')
478
+ return wav
479
+
480
+ result = q.get() if not q.empty() else {'ok': False, 'reason': 'worker died with no result'}
481
+ if not result.get('ok'):
482
+ st.guard_reverts += 1
483
+ _L(st, f" [S3-WPE] {result.get('reason', 'failed')} β€” REVERT")
484
+ return wav
485
+
486
  out = _tmp('s3', st)
487
  if not _enc_mono(mo, out):
488
  return wav
489
  st.wpe = True
490
+ _L(st, f" [S3-WPE] βœ“ taps={result['taps']} delay={result['delay']} "
491
+ f"iters={result['iters']} LRA Ξ”={result['ld']:.2f}LU")
492
  return out
493
  except Exception as e:
494
  _L(st, f' [S3-WPE] exception: {e}'); return wav