Nikita Makarov commited on
Commit
c04db7b
·
1 Parent(s): 84c7402

v2.3 we did it

Browse files
Files changed (1) hide show
  1. src/app.py +130 -92
src/app.py CHANGED
@@ -230,6 +230,13 @@ def start_radio_stream():
230
  return "🎵 Starting your personalized radio show...", None, None, None, ""
231
 
232
 
 
 
 
 
 
 
 
233
  def start_and_play_first_segment():
234
  """
235
  One-shot helper for the UI:
@@ -272,15 +279,15 @@ def start_and_play_first_segment():
272
  }
273
  print("📋 Using default preferences")
274
 
275
- # Check if resuming from stopped state
276
- if radio_state.get("is_stopped") and radio_state.get("planned_show"):
277
  print("▶️ Resuming from stopped state...")
278
  radio_state["is_playing"] = True
279
  radio_state["is_stopped"] = False
280
  radio_state["stop_flag"] = False
281
 
282
  # Play next segment (continuing from where we stopped)
283
- segment_info, host_audio, music_html, progress, now_playing, llm_script = play_next_segment()
284
 
285
  return (
286
  "▶️ Resuming radio...",
@@ -290,15 +297,30 @@ def start_and_play_first_segment():
290
  progress,
291
  now_playing,
292
  llm_script,
 
293
  )
294
 
295
- if radio_state["is_playing"]:
296
- return (
297
- "📻 Radio is already playing!",
298
- "**Welcome!** Set your preferences and start the radio.",
299
- None, None, "Ready to start", "📻 AI Radio",
300
- "Model-generated script will appear here for each segment.",
301
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
  import time
304
  t0 = time.time()
@@ -326,7 +348,7 @@ def start_and_play_first_segment():
326
  # Step 2 & 3: Generate and play first segment (LLM + TTS inside play_next_segment)
327
  print(" [2/3] Generating first segment (LLM)...")
328
  t1 = time.time()
329
- segment_info, host_audio, music_html, progress, now_playing, llm_script = play_next_segment()
330
  print(f" [2/3] Done in {time.time()-t1:.1f}s")
331
  print(f" [3/3] Total time: {time.time()-t0:.1f}s")
332
  print(f" Host audio file: {host_audio}")
@@ -339,6 +361,7 @@ def start_and_play_first_segment():
339
  progress,
340
  now_playing,
341
  llm_script,
 
342
  )
343
 
344
  def play_next_segment():
@@ -356,11 +379,11 @@ def play_next_segment():
356
  radio_state["is_playing"] = True
357
  radio_state["stop_flag"] = False
358
  else:
359
- return "⏸️ No show planned. Click 'Generate & Play' to start.", None, None, None, "", ""
360
 
361
  if radio_state["stop_flag"]:
362
  radio_state["is_playing"] = False
363
- return "⏸️ Radio paused", None, None, None, "", ""
364
 
365
  # If there are remaining news TTS batches, play the next one without advancing to a new segment
366
  if radio_state.get("current_news_batches"):
@@ -395,11 +418,11 @@ def play_next_segment():
395
  radio_state["news_total_batches"] = 0
396
  radio_state["news_batches_played"] = 0
397
 
398
- return segment_info, host_audio_file, "", progress, get_now_playing(segment), display_script
399
 
400
  if radio_state["current_segment_index"] >= len(radio_state["planned_show"]):
401
  radio_state["is_playing"] = False
402
- return "🎊 Show completed! Hope you enjoyed it.", None, None, None, "", ""
403
 
404
  # Get current segment
405
  segment_index = radio_state["current_segment_index"]
@@ -750,8 +773,38 @@ def play_next_segment():
750
 
751
  threading.Thread(target=_prefetch_next, daemon=True).start()
752
 
753
- # Return: segment_info, host_audio, music_player_html, progress, now_playing, segmented LLM script for UI
754
- return segment_info, host_audio_file, music_player_html, progress, get_now_playing(segment), display_script
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
755
 
756
  def stop_radio():
757
  """Stop the radio stream - pauses at current segment without resetting"""
@@ -765,13 +818,14 @@ def stop_radio():
765
 
766
  status_msg = f"⏹️ Stopped at segment {current_idx}/{total_segments}. Click 'Generate & Play' or 'Next Segment' to continue."
767
 
768
- # Return status, clear audio, clear music player, progress, now playing
769
  return (
770
  status_msg,
771
  None, # Clear audio
772
  "", # Clear music player HTML
773
  f"Stopped at {current_idx}/{total_segments}",
774
- f"⏸️ Paused - Segment {current_idx}/{total_segments}"
 
775
  )
776
 
777
 
@@ -959,6 +1013,7 @@ def handle_voice_request():
959
  # Create music player HTML with volume fading (0→30% during host speech, then 30%→100%)
960
  music_player_html = ""
961
  player_id = f"voice_player_{int(time.time())}"
 
962
 
963
  if track.get("source") == "youtube":
964
  youtube_id = track.get("youtube_id", "")
@@ -966,53 +1021,48 @@ def handle_voice_request():
966
  youtube_id = track["url"].split("v=")[-1].split("&")[0]
967
 
968
  if youtube_id:
969
- # # COMMENTED OUT: Delayed loading mechanism
970
- # # Get host audio duration to calculate delay
971
- # delay_ms = 0
972
- # if audio_file and os.path.exists(audio_file):
973
- # try:
974
- # audio = AudioSegment.from_file(audio_file)
975
- # duration_ms = len(audio)
976
- # delay_ms = max(0, duration_ms - 3000) # Start 3 seconds before end
977
- # print(f"⏱️ Voice request audio: {duration_ms}ms, YouTube delay: {delay_ms}ms")
978
- # except Exception as e:
979
- # print(f"⚠️ Could not get audio duration: {e}")
980
-
981
- # # Store the actual iframe HTML for later (after host speech ends)
982
- # delay_s = delay_ms / 1000.0
983
- # actual_player_html = create_youtube_player_html(
984
- # youtube_id,
985
- # track.get('title', 'Unknown'),
986
- # track.get('artist', 'Unknown'),
987
- # track['url']
988
- # )
989
-
990
- # # Schedule the player to be ready after the delay
991
- # radio_state["pending_player"] = {
992
- # "html": actual_player_html,
993
- # "ready_at": time.time() + delay_s,
994
- # "type": "music",
995
- # "title": track.get('title', 'Unknown')
996
- # }
997
-
998
- # # Return placeholder initially (no iframe yet!)
999
- # music_player_html = create_placeholder_html(
1000
- # track.get('title', 'Unknown'),
1001
- # track.get('artist', 'Unknown'),
1002
- # delay_s,
1003
- # "music"
1004
- # )
1005
- # print(f"✅ Voice request YouTube player scheduled - iframe added after {delay_s:.1f}s")
1006
- # print(f"✅ YouTube player scheduled: {track.get('title', 'Unknown')} (ID: {youtube_id})")
1007
-
1008
- # Simple immediate iframe - start track with host
1009
- music_player_html = create_youtube_player_html(
1010
- youtube_id,
1011
- track.get('title', 'Unknown'),
1012
- track.get('artist', 'Unknown'),
1013
- track['url']
1014
- )
1015
- print(f"✅ Voice request YouTube player created immediately: {track.get('title', 'Unknown')} (ID: {youtube_id})")
1016
  else:
1017
  music_player_html = f"""
1018
  <div style="padding: 1rem; background: #f0f0f0; border-radius: 10px; margin: 1rem 0;">
@@ -1042,10 +1092,10 @@ def handle_voice_request():
1042
  # The browser may block autoplay - user may need to click play
1043
 
1044
  # Return with player
1045
- return f"✅ {host_response}\n\n🎵 Playing: {track['title']} by {track['artist']}", audio_file, music_player_html
1046
 
1047
  except Exception as e:
1048
- return f"❌ Error processing voice request: {e}", None, ""
1049
 
1050
  def get_now_playing(segment: Dict[str, Any]) -> str:
1051
  """Get now playing text"""
@@ -1561,26 +1611,14 @@ with gr.Blocks(css=custom_css, title="AI Radio 🎵", theme=gr.themes.Soft(), he
1561
  audio_output = gr.Audio(label="🔊 Host Speech", autoplay=True, type="filepath", elem_id="host_audio")
1562
  music_player = gr.HTML(label="🎵 Music/Podcast Player (streaming)")
1563
 
1564
- # # COMMENTED OUT: Timer to check for pending players (delayed loading mechanism)
1565
- # # Timer to check for pending players (polls every 2 seconds)
1566
- # player_timer = gr.Timer(value=2, active=True)
1567
-
1568
- # def check_pending_player():
1569
- # """Check if there's a pending player ready to show"""
1570
- # pending = radio_state.get("pending_player")
1571
- # if pending and time.time() >= pending.get("ready_at", 0):
1572
- # html = pending.get("html", "")
1573
- # title = pending.get("title", "Unknown")
1574
- # radio_state["pending_player"] = None # Clear it
1575
- # print(f"▶️ Adding iframe for: {title}")
1576
- # return html
1577
- # return gr.update() # No change
1578
-
1579
- # player_timer.tick(
1580
- # fn=check_pending_player,
1581
- # inputs=[],
1582
- # outputs=[music_player]
1583
- # )
1584
 
1585
  status_text = gr.Textbox(label="Status", value="Ready", interactive=False, visible=False)
1586
 
@@ -1588,19 +1626,19 @@ with gr.Blocks(css=custom_css, title="AI Radio 🎵", theme=gr.themes.Soft(), he
1588
  start_btn.click(
1589
  fn=start_and_play_first_segment,
1590
  inputs=[],
1591
- outputs=[status_text, segment_info, audio_output, music_player, progress_text, now_playing, llm_script]
1592
  )
1593
 
1594
  next_btn.click(
1595
  fn=play_next_segment,
1596
  inputs=[],
1597
- outputs=[segment_info, audio_output, music_player, progress_text, now_playing, llm_script]
1598
  )
1599
 
1600
  stop_btn.click(
1601
  fn=stop_radio,
1602
  inputs=[],
1603
- outputs=[status_text, audio_output, music_player, progress_text, now_playing],
1604
  js="() => { if(window.cancelNextSegment) window.cancelNextSegment(); }"
1605
  )
1606
 
@@ -1608,7 +1646,7 @@ with gr.Blocks(css=custom_css, title="AI Radio 🎵", theme=gr.themes.Soft(), he
1608
  voice_btn.click(
1609
  fn=handle_voice_request,
1610
  inputs=[],
1611
- outputs=[voice_status, audio_output, music_player]
1612
  )
1613
 
1614
  # Like/Dislike buttons
 
230
  return "🎵 Starting your personalized radio show...", None, None, None, ""
231
 
232
 
233
+ def show_pending_player():
234
+ """Called by timer to finally show the media player"""
235
+ player_html = radio_state.get("pending_player_html", "")
236
+ print("⏰ Timer fired: Showing pending media player now")
237
+ # Return player HTML and disable timer
238
+ return player_html, gr.Timer(value=0, active=False)
239
+
240
  def start_and_play_first_segment():
241
  """
242
  One-shot helper for the UI:
 
279
  }
280
  print("📋 Using default preferences")
281
 
282
+ # Check if resuming from stopped state (only if show was manually stopped, not completed)
283
+ if radio_state.get("is_stopped") and radio_state.get("planned_show") and radio_state["current_segment_index"] < len(radio_state["planned_show"]):
284
  print("▶️ Resuming from stopped state...")
285
  radio_state["is_playing"] = True
286
  radio_state["is_stopped"] = False
287
  radio_state["stop_flag"] = False
288
 
289
  # Play next segment (continuing from where we stopped)
290
+ segment_info, host_audio, music_html, progress, now_playing, llm_script, timer_config = play_next_segment()
291
 
292
  return (
293
  "▶️ Resuming radio...",
 
297
  progress,
298
  now_playing,
299
  llm_script,
300
+ timer_config
301
  )
302
 
303
+ # If show completed or no valid show to resume, start fresh
304
+ # Also check if is_playing is True but show is actually completed
305
+ if radio_state.get("is_playing") and radio_state.get("planned_show"):
306
+ # Check if show is actually completed
307
+ if radio_state["current_segment_index"] >= len(radio_state["planned_show"]):
308
+ # Show completed, reset and start fresh
309
+ print("🔄 Show completed, resetting for new show...")
310
+ reset_radio()
311
+ else:
312
+ # Show is actually playing
313
+ return (
314
+ "📻 Radio is already playing!",
315
+ "**Welcome!** Set your preferences and start the radio.",
316
+ None, None, "Ready to start", "📻 AI Radio",
317
+ "Model-generated script will appear here for each segment.",
318
+ gr.Timer(value=0, active=False)
319
+ )
320
+ elif radio_state.get("is_playing"):
321
+ # is_playing is True but no planned show - reset
322
+ print("🔄 is_playing=True but no planned_show, resetting...")
323
+ reset_radio()
324
 
325
  import time
326
  t0 = time.time()
 
348
  # Step 2 & 3: Generate and play first segment (LLM + TTS inside play_next_segment)
349
  print(" [2/3] Generating first segment (LLM)...")
350
  t1 = time.time()
351
+ segment_info, host_audio, music_html, progress, now_playing, llm_script, timer_config = play_next_segment()
352
  print(f" [2/3] Done in {time.time()-t1:.1f}s")
353
  print(f" [3/3] Total time: {time.time()-t0:.1f}s")
354
  print(f" Host audio file: {host_audio}")
 
361
  progress,
362
  now_playing,
363
  llm_script,
364
+ timer_config
365
  )
366
 
367
  def play_next_segment():
 
379
  radio_state["is_playing"] = True
380
  radio_state["stop_flag"] = False
381
  else:
382
+ return "⏸️ No show planned. Click 'Generate & Play' to start.", None, None, None, "", "", gr.Timer(value=0, active=False)
383
 
384
  if radio_state["stop_flag"]:
385
  radio_state["is_playing"] = False
386
+ return "⏸️ Radio paused", None, None, None, "", "", gr.Timer(value=0, active=False)
387
 
388
  # If there are remaining news TTS batches, play the next one without advancing to a new segment
389
  if radio_state.get("current_news_batches"):
 
418
  radio_state["news_total_batches"] = 0
419
  radio_state["news_batches_played"] = 0
420
 
421
+ return segment_info, host_audio_file, "", progress, get_now_playing(segment), display_script, gr.Timer(value=0, active=False)
422
 
423
  if radio_state["current_segment_index"] >= len(radio_state["planned_show"]):
424
  radio_state["is_playing"] = False
425
+ return "🎊 Show completed! Hope you enjoyed it.", None, None, None, "", "", gr.Timer(value=0, active=False)
426
 
427
  # Get current segment
428
  segment_index = radio_state["current_segment_index"]
 
773
 
774
  threading.Thread(target=_prefetch_next, daemon=True).start()
775
 
776
+ # Calculate segment duration and set up delayed player if needed
777
+ timer_config = gr.Timer(value=0, active=False)
778
+
779
+ # Only delay player if we have both host audio and a music/podcast player
780
+ if host_audio_file and os.path.exists(host_audio_file) and music_player_html and ("<iframe" in music_player_html):
781
+ try:
782
+ audio = AudioSegment.from_file(host_audio_file)
783
+ host_duration_s = len(audio) / 1000.0
784
+
785
+ # Start player 3 seconds before host speech ends
786
+ delay_s = max(0.5, host_duration_s - 3.0)
787
+
788
+ print(f"⏱️ Host duration: {host_duration_s:.1f}s, Scheduling player in {delay_s:.1f}s")
789
+
790
+ # Store the actual player HTML
791
+ radio_state["pending_player_html"] = music_player_html
792
+
793
+ # Return placeholder/empty player and active timer
794
+ music_player_html = create_placeholder_html(
795
+ segment.get('track', {}).get('title', 'Content') if segment.get('type') == 'music' else segment.get('podcast', {}).get('title', 'Podcast'),
796
+ segment.get('track', {}).get('artist', '') if segment.get('type') == 'music' else segment.get('podcast', {}).get('host', ''),
797
+ delay_s,
798
+ segment.get('type', 'music')
799
+ )
800
+ timer_config = gr.Timer(value=delay_s, active=True)
801
+
802
+ except Exception as e:
803
+ print(f"⚠️ Error calculating audio duration for delay: {e}")
804
+ # Fallback: play immediately (leave music_player_html as is, timer inactive)
805
+
806
+ # Return: segment_info, host_audio, music_player_html, progress, now_playing, segmented LLM script for UI, timer
807
+ return segment_info, host_audio_file, music_player_html, progress, get_now_playing(segment), display_script, timer_config
808
 
809
  def stop_radio():
810
  """Stop the radio stream - pauses at current segment without resetting"""
 
818
 
819
  status_msg = f"⏹️ Stopped at segment {current_idx}/{total_segments}. Click 'Generate & Play' or 'Next Segment' to continue."
820
 
821
+ # Return status, clear audio, clear music player, progress, now playing, timer
822
  return (
823
  status_msg,
824
  None, # Clear audio
825
  "", # Clear music player HTML
826
  f"Stopped at {current_idx}/{total_segments}",
827
+ f"⏸️ Paused - Segment {current_idx}/{total_segments}",
828
+ gr.Timer(value=0, active=False) # Stop timer
829
  )
830
 
831
 
 
1013
  # Create music player HTML with volume fading (0→30% during host speech, then 30%→100%)
1014
  music_player_html = ""
1015
  player_id = f"voice_player_{int(time.time())}"
1016
+ timer_config = gr.Timer(value=0, active=False)
1017
 
1018
  if track.get("source") == "youtube":
1019
  youtube_id = track.get("youtube_id", "")
 
1021
  youtube_id = track["url"].split("v=")[-1].split("&")[0]
1022
 
1023
  if youtube_id:
1024
+ # Delayed loading mechanism with gr.Timer
1025
+ try:
1026
+ # Get host audio duration to calculate delay
1027
+ delay_s = 0.5
1028
+ if audio_file and os.path.exists(audio_file):
1029
+ audio = AudioSegment.from_file(audio_file)
1030
+ duration_ms = len(audio)
1031
+ delay_s = max(0.5, (duration_ms / 1000.0) - 3.0) # Start 3 seconds before end
1032
+ print(f"⏱️ Voice request audio: {duration_ms}ms, YouTube delay: {delay_s:.1f}s")
1033
+
1034
+ # Create the actual player HTML
1035
+ actual_player_html = create_youtube_player_html(
1036
+ youtube_id,
1037
+ track.get('title', 'Unknown'),
1038
+ track.get('artist', 'Unknown'),
1039
+ track['url']
1040
+ )
1041
+
1042
+ # Store for timer callback
1043
+ radio_state["pending_player_html"] = actual_player_html
1044
+
1045
+ # Create placeholder
1046
+ music_player_html = create_placeholder_html(
1047
+ track.get('title', 'Unknown'),
1048
+ track.get('artist', 'Unknown'),
1049
+ delay_s,
1050
+ "music"
1051
+ )
1052
+
1053
+ # Activate timer
1054
+ timer_config = gr.Timer(value=delay_s, active=True)
1055
+ print(f"✅ Voice request: Player scheduled in {delay_s:.1f}s")
1056
+
1057
+ except Exception as e:
1058
+ print(f"⚠️ Error calculating delay for voice request: {e}")
1059
+ # Fallback: immediate play
1060
+ music_player_html = create_youtube_player_html(
1061
+ youtube_id,
1062
+ track.get('title', 'Unknown'),
1063
+ track.get('artist', 'Unknown'),
1064
+ track['url']
1065
+ )
 
 
 
 
 
1066
  else:
1067
  music_player_html = f"""
1068
  <div style="padding: 1rem; background: #f0f0f0; border-radius: 10px; margin: 1rem 0;">
 
1092
  # The browser may block autoplay - user may need to click play
1093
 
1094
  # Return with player
1095
+ return f"✅ {host_response}\n\n🎵 Playing: {track['title']} by {track['artist']}", audio_file, music_player_html, timer_config
1096
 
1097
  except Exception as e:
1098
+ return f"❌ Error processing voice request: {e}", None, "", gr.Timer(value=0, active=False)
1099
 
1100
  def get_now_playing(segment: Dict[str, Any]) -> str:
1101
  """Get now playing text"""
 
1611
  audio_output = gr.Audio(label="🔊 Host Speech", autoplay=True, type="filepath", elem_id="host_audio")
1612
  music_player = gr.HTML(label="🎵 Music/Podcast Player (streaming)")
1613
 
1614
+ # Timer for delayed player loading (start disabled)
1615
+ player_timer = gr.Timer(value=0, active=False)
1616
+
1617
+ player_timer.tick(
1618
+ fn=show_pending_player,
1619
+ inputs=[],
1620
+ outputs=[music_player, player_timer]
1621
+ )
 
 
 
 
 
 
 
 
 
 
 
 
1622
 
1623
  status_text = gr.Textbox(label="Status", value="Ready", interactive=False, visible=False)
1624
 
 
1626
  start_btn.click(
1627
  fn=start_and_play_first_segment,
1628
  inputs=[],
1629
+ outputs=[status_text, segment_info, audio_output, music_player, progress_text, now_playing, llm_script, player_timer]
1630
  )
1631
 
1632
  next_btn.click(
1633
  fn=play_next_segment,
1634
  inputs=[],
1635
+ outputs=[segment_info, audio_output, music_player, progress_text, now_playing, llm_script, player_timer]
1636
  )
1637
 
1638
  stop_btn.click(
1639
  fn=stop_radio,
1640
  inputs=[],
1641
+ outputs=[status_text, audio_output, music_player, progress_text, now_playing, player_timer],
1642
  js="() => { if(window.cancelNextSegment) window.cancelNextSegment(); }"
1643
  )
1644
 
 
1646
  voice_btn.click(
1647
  fn=handle_voice_request,
1648
  inputs=[],
1649
+ outputs=[voice_status, audio_output, music_player, player_timer]
1650
  )
1651
 
1652
  # Like/Dislike buttons