J Z commited on
Commit
225e7d9
·
verified ·
1 Parent(s): 93f6f1b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +38 -22
app.py CHANGED
@@ -27,6 +27,9 @@ FLOAT_ENABLED = os.environ.get("FLOAT_ENABLED", "true").lower() == "true"
27
  for d in [VIDEOS_DIR, TTS_DIR, LIPSYNC_DIR]:
28
  d.mkdir(parents=True, exist_ok=True)
29
 
 
 
 
30
  # In-memory store for chunked generation results
31
  # session_id -> { "chunks": [...], "done": bool }
32
  _sessions = {}
@@ -218,8 +221,7 @@ async def chat(request: Request):
218
  async def chat_next(request: Request):
219
  """
220
  Generate TTS + lipsync for the next sentence in the session.
221
- Called sequentially by the frontend — one chunk at a time.
222
- Returns the audio URL, lipsync video URL, duration, and sentence text.
223
  """
224
  body = await request.json()
225
  session_id = body.get("session_id", "")
@@ -236,8 +238,34 @@ async def chat_next(request: Request):
236
  sentence = session["sentences"][idx]
237
  session["next_index"] = idx + 1
238
  is_last = (idx + 1 >= session["total"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- logger.info(f"[{_ts()}] [CHUNK {idx+1}/{session['total']}] {sentence[:60]}...")
 
 
 
241
  t0 = _time.time()
242
 
243
  # 1. TTS
@@ -258,16 +286,17 @@ async def chat_next(request: Request):
258
  except Exception:
259
  audio_duration = 3.0
260
 
261
- # 2. Lipsync
262
  lipsync_video_url = None
263
  if FLOAT_ENABLED and audio_path and os.path.exists(audio_path):
264
  try:
265
  from float_lipsync import get_lipsync
266
  lipsync = get_lipsync()
267
  if lipsync.ready:
268
- t3 = _time.time()
269
- lp = lipsync.generate(audio_path)
270
- t4 = _time.time()
 
271
  logger.info(f"[{_ts()}] [TIMING] Chunk {idx+1} FLOAT: {t4-t3:.2f}s")
272
  if lp and os.path.exists(lp):
273
  lipsync_video_url = f"/api/stream/{os.path.basename(lp)}"
@@ -277,24 +306,11 @@ async def chat_next(request: Request):
277
  total_time = _time.time() - t0
278
  logger.info(f"[{_ts()}] [TIMING] Chunk {idx+1} TOTAL: {total_time:.2f}s")
279
 
280
- if is_last:
281
- session["done"] = True
282
- # Clean up old sessions (keep last 10)
283
- if len(_sessions) > 10:
284
- oldest = sorted(_sessions.keys(), key=lambda k: _sessions[k]["created"])
285
- for k in oldest[:-10]:
286
- del _sessions[k]
287
-
288
- return JSONResponse({
289
- "done": False,
290
- "chunk_index": idx,
291
- "total_chunks": session["total"],
292
- "is_last": is_last,
293
- "sentence": sentence,
294
  "audio_url": audio_url,
295
  "audio_duration": audio_duration,
296
  "lipsync_video_url": lipsync_video_url,
297
- })
298
 
299
 
300
  @app.post("/api/reset")
 
27
  for d in [VIDEOS_DIR, TTS_DIR, LIPSYNC_DIR]:
28
  d.mkdir(parents=True, exist_ok=True)
29
 
30
+ import threading
31
+ _float_lock = threading.Lock()
32
+
33
  # In-memory store for chunked generation results
34
  # session_id -> { "chunks": [...], "done": bool }
35
  _sessions = {}
 
221
  async def chat_next(request: Request):
222
  """
223
  Generate TTS + lipsync for the next sentence in the session.
224
+ Heavy work runs in a thread so the server stays responsive for prefetch requests.
 
225
  """
226
  body = await request.json()
227
  session_id = body.get("session_id", "")
 
238
  sentence = session["sentences"][idx]
239
  session["next_index"] = idx + 1
240
  is_last = (idx + 1 >= session["total"])
241
+ total = session["total"]
242
+
243
+ # Run the heavy TTS + FLOAT work in a thread
244
+ import asyncio
245
+ result = await asyncio.to_thread(_generate_chunk, sentence, idx, total)
246
+
247
+ if is_last:
248
+ session["done"] = True
249
+ if len(_sessions) > 10:
250
+ oldest = sorted(_sessions.keys(), key=lambda k: _sessions[k]["created"])
251
+ for k in oldest[:-10]:
252
+ del _sessions[k]
253
+
254
+ return JSONResponse({
255
+ "done": False,
256
+ "chunk_index": idx,
257
+ "total_chunks": total,
258
+ "is_last": is_last,
259
+ "sentence": sentence,
260
+ "audio_url": result["audio_url"],
261
+ "audio_duration": result["audio_duration"],
262
+ "lipsync_video_url": result["lipsync_video_url"],
263
+ })
264
 
265
+
266
+ def _generate_chunk(sentence, idx, total):
267
+ """Synchronous TTS + FLOAT generation (runs in thread pool)."""
268
+ logger.info(f"[{_ts()}] [CHUNK {idx+1}/{total}] {sentence[:60]}...")
269
  t0 = _time.time()
270
 
271
  # 1. TTS
 
286
  except Exception:
287
  audio_duration = 3.0
288
 
289
+ # 2. Lipsync (locked — GPU not thread-safe)
290
  lipsync_video_url = None
291
  if FLOAT_ENABLED and audio_path and os.path.exists(audio_path):
292
  try:
293
  from float_lipsync import get_lipsync
294
  lipsync = get_lipsync()
295
  if lipsync.ready:
296
+ with _float_lock:
297
+ t3 = _time.time()
298
+ lp = lipsync.generate(audio_path)
299
+ t4 = _time.time()
300
  logger.info(f"[{_ts()}] [TIMING] Chunk {idx+1} FLOAT: {t4-t3:.2f}s")
301
  if lp and os.path.exists(lp):
302
  lipsync_video_url = f"/api/stream/{os.path.basename(lp)}"
 
306
  total_time = _time.time() - t0
307
  logger.info(f"[{_ts()}] [TIMING] Chunk {idx+1} TOTAL: {total_time:.2f}s")
308
 
309
+ return {
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  "audio_url": audio_url,
311
  "audio_duration": audio_duration,
312
  "lipsync_video_url": lipsync_video_url,
313
+ }
314
 
315
 
316
  @app.post("/api/reset")