22Queue management routes.
33"""
44
5+ import asyncio
56import logging
67import threading
78from typing import List
@@ -70,18 +71,7 @@ def get_current_queue():
7071 """Get the current queue."""
7172 try :
7273 queue = get_queue ()
73- queue_dicts = [item .to_dict () for item in queue ]
74-
75- # Log queue items for debugging
76- for item_dict in queue_dicts :
77- logger .info (
78- f"GET /queue returning item: id={ item_dict ['id' ]} , "
79- f"type={ item_dict .get ('type' , 'MISSING' )} , "
80- f"week_year={ item_dict .get ('week_year' , 'MISSING' )} , "
81- f"title={ item_dict ['title' ][:50 ]} "
82- )
83-
84- return JSONResponse ({"queue" : queue_dicts })
74+ return JSONResponse ({"queue" : [item .to_dict () for item in queue ]})
8575 except Exception as e :
8676 logger .error (f"Error fetching queue: { e } " )
8777 raise HTTPException (status_code = 500 , detail = str (e ))
@@ -136,14 +126,8 @@ def play_next_in_queue():
136126 # Add type-specific fields
137127 if next_item .type == "summary" :
138128 response ["week_year" ] = next_item .week_year
139- logger .info (
140- f"Playing next item (summary): { next_item .title } - week_year: { next_item .week_year } "
141- )
142129 else :
143130 response ["youtube_id" ] = next_item .youtube_id
144- logger .info (
145- f"Playing next item (youtube): { next_item .title } - video_id: { next_item .youtube_id } "
146- )
147131
148132 return JSONResponse (response )
149133 except Exception as e :
@@ -225,11 +209,92 @@ def _prefetch_worker():
225209 return JSONResponse ({"status" : "started" , "video_id" : video_id })
226210
227211
212+ def _run_suggestions_sync () -> dict :
213+ """
214+ Run the entire suggestion pipeline synchronously.
215+
216+ This is designed to be called via asyncio.to_thread() so it doesn't
217+ block the event loop. All operations here (LLM calls, subprocess calls,
218+ database writes) are synchronous.
219+ """
220+ from services .book_suggestions import get_video_suggestions
221+
222+ logger .info ("Generating video suggestions based on recently watched content..." )
223+
224+ suggestions = get_video_suggestions ()
225+
226+ if not suggestions :
227+ return {
228+ "status" : "no_suggestions" ,
229+ "message" : "No suggestions could be generated. Check logs for details." ,
230+ "added" : [],
231+ }
232+
233+ added = []
234+ failed = []
235+
236+ for suggestion in suggestions :
237+ try :
238+ video_id = suggestion ["video_id" ]
239+ metadata = get_video_metadata (video_id )
240+
241+ if metadata :
242+ queue_id = add_to_queue (
243+ video_id ,
244+ metadata ["title" ],
245+ metadata .get ("channel" ),
246+ metadata .get ("thumbnail_url" ),
247+ )
248+ added .append (
249+ {
250+ "queue_id" : queue_id ,
251+ "video_id" : video_id ,
252+ "title" : metadata ["title" ],
253+ "channel" : suggestion .get ("channel" , "Unknown" ),
254+ }
255+ )
256+ logger .info (f"Added suggestion to queue: { metadata ['title' ]} " )
257+ else :
258+ queue_id = add_to_queue (
259+ video_id ,
260+ suggestion ["title" ],
261+ suggestion .get ("channel" ),
262+ )
263+ added .append (
264+ {
265+ "queue_id" : queue_id ,
266+ "video_id" : video_id ,
267+ "title" : suggestion ["title" ],
268+ "channel" : suggestion .get ("channel" , "Unknown" ),
269+ }
270+ )
271+ logger .warning (
272+ f"Could not fetch YouTube metadata for { video_id } , using search result"
273+ )
274+
275+ except Exception as e :
276+ logger .error (f"Failed to add suggestion to queue: { e } " )
277+ failed .append (
278+ {"title" : suggestion .get ("title" , "Unknown" ), "error" : str (e )}
279+ )
280+
281+ return {
282+ "status" : "success" ,
283+ "message" : f"Added { len (added )} video suggestions to queue" ,
284+ "added" : added ,
285+ "failed" : failed ,
286+ "total_suggestions" : len (suggestions ),
287+ }
288+
289+
228290@router .post ("/queue/suggestions" )
229291async def generate_and_queue_suggestions ():
230292 """
231293 Generate video suggestions based on recently watched content
232294 and automatically add them to the queue.
295+
296+ Runs the blocking suggestion pipeline in a thread so the event loop
297+ stays free to handle other requests (queue removal, playback, etc.).
233298 """
234299 if not config .book_suggestions_enabled :
235300 raise HTTPException (
@@ -238,83 +303,8 @@ async def generate_and_queue_suggestions():
238303 )
239304
240305 try :
241- from services .book_suggestions import get_video_suggestions
242-
243- logger .info ("Generating video suggestions based on recently watched content..." )
244-
245- # Get suggestions
246- suggestions = await get_video_suggestions ()
247-
248- if not suggestions :
249- return JSONResponse (
250- {
251- "status" : "no_suggestions" ,
252- "message" : "No suggestions could be generated. Check logs for details." ,
253- "added" : [],
254- }
255- )
256-
257- # Add suggestions to queue
258- added = []
259- failed = []
260-
261- for suggestion in suggestions :
262- try :
263- video_id = suggestion ["video_id" ]
264-
265- # Try to get metadata from YouTube (validate the URL)
266- metadata = get_video_metadata (video_id )
267-
268- if metadata :
269- queue_id = add_to_queue (
270- video_id ,
271- metadata ["title" ],
272- metadata .get ("channel" ),
273- metadata .get ("thumbnail_url" ),
274- )
275- added .append (
276- {
277- "queue_id" : queue_id ,
278- "video_id" : video_id ,
279- "title" : metadata ["title" ],
280- "channel" : suggestion .get ("channel" , "Unknown" ),
281- }
282- )
283- logger .info (f"Added suggestion to queue: { metadata ['title' ]} " )
284- else :
285- # Fall back to suggestion-provided title
286- queue_id = add_to_queue (
287- video_id ,
288- suggestion ["title" ],
289- suggestion .get ("channel" ),
290- )
291- added .append (
292- {
293- "queue_id" : queue_id ,
294- "video_id" : video_id ,
295- "title" : suggestion ["title" ],
296- "channel" : suggestion .get ("channel" , "Unknown" ),
297- }
298- )
299- logger .warning (
300- f"Could not fetch YouTube metadata for { video_id } , using search result"
301- )
302-
303- except Exception as e :
304- logger .error (f"Failed to add suggestion to queue: { e } " )
305- failed .append (
306- {"title" : suggestion .get ("title" , "Unknown" ), "error" : str (e )}
307- )
308-
309- return JSONResponse (
310- {
311- "status" : "success" ,
312- "message" : f"Added { len (added )} video suggestions to queue" ,
313- "added" : added ,
314- "failed" : failed ,
315- "total_suggestions" : len (suggestions ),
316- }
317- )
306+ result = await asyncio .to_thread (_run_suggestions_sync )
307+ return JSONResponse (result )
318308
319309 except Exception as e :
320310 logger .error (f"Error generating suggestions: { e } " , exc_info = True )
0 commit comments