jonathanagustin commited on
Commit
7dbaf21
1 Parent(s): 35e44eb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -254
app.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- This module integrates real-time object detection into live YouTube streams using the YOLO (You Only Look Once) model, and provides an interactive user interface through Gradio. It is designed to allow users to search for live YouTube streams and apply object detection to these streams in real time.
3
 
4
  Main Features:
5
  - Search for live YouTube streams using specific queries.
@@ -8,185 +8,79 @@ Main Features:
8
  - Display the live stream and object detection results through a Gradio interface.
9
 
10
  The module comprises several key components:
11
- - `SearchFilter`: An enumeration for YouTube search filters.
12
  - `SearchService`: A service class to search for YouTube videos and retrieve live stream URLs.
13
  - `LiveYouTubeObjectDetector`: The main class integrating the YOLO model and Gradio UI, handling the entire workflow of searching, streaming, and object detection.
14
 
15
  Dependencies:
16
- - cv2 (OpenCV): Used for image processing tasks.
17
  - Gradio: Provides the interactive web-based user interface.
18
- - innertube, streamlink: Used for interacting with YouTube and retrieving live stream data.
19
- - numpy: Utilized for numerical operations on image data.
20
- - PIL (Pillow): A Python Imaging Library for opening, manipulating, and saving images.
21
- - ultralytics YOLO: The YOLO model implementation for object detection.
 
 
22
 
23
  Usage:
24
  Run this file to launch the Gradio interface, which allows users to input search queries for YouTube live streams, select a stream, and perform object detection on the selected live stream.
25
 
26
  """
 
27
  import logging
28
- import os
29
- import subprocess
30
- import sys
31
- from enum import Enum
32
  from typing import Any, Dict, List, Optional, Tuple
33
 
34
- import requests
35
-
36
-
37
- # HOTFIX: https://github.com/aws/aws-cli/issues/6130#issuecomment-829659803
38
- os.environ['AWS_EC2_METADATA_DISABLED'] = 'true'
39
-
40
-
41
- def install_requirements():
42
- requirements_url = "https://raw.githubusercontent.com/aai521-group6/project/main/requirements.txt"
43
- response = requests.get(requirements_url)
44
- if response.status_code == 200:
45
- with open("requirements.txt", "wb") as file:
46
- file.write(response.content)
47
- subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
48
- else:
49
- raise Exception("Failed to download requirements.txt")
50
-
51
-
52
- try:
53
- import cv2
54
- import gradio as gr
55
- import innertube
56
- import numpy as np
57
- import streamlink
58
- from PIL import Image
59
- from ultralytics import YOLO
60
- except ImportError:
61
- install_requirements()
62
- import cv2
63
- import gradio as gr
64
- import innertube
65
- import numpy as np
66
- import streamlink
67
- from PIL import Image
68
- from ultralytics import YOLO
69
-
70
- logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
71
-
72
-
73
- class SearchFilter(Enum):
74
- """
75
- An enumeration for specifying different types of YouTube search filters.
76
-
77
- This Enum class is used to define filters for categorizing YouTube search
78
- results into either live or regular video content. It is utilized in
79
- conjunction with the `SearchService` class to refine YouTube searches
80
- based on the type of content being sought.
81
-
82
- Attributes:
83
- LIVE (str): Represents the filter code for live video content on YouTube.
84
- VIDEO (str): Represents the filter code for regular, non-live video content on YouTube.
85
 
86
- Each attribute consists of a tuple where the first element is the filter code
87
- used in YouTube search queries, and the second element is a human-readable
88
- string describing the filter.
89
- """
90
-
91
- LIVE = ("EgJAAQ%3D%3D", "Live")
92
- VIDEO = ("EgIQAQ%3D%3D", "Video")
93
-
94
- def __init__(self, code, human_readable):
95
- """Initializes the SearchFilter with a code and a human-readable string.
96
-
97
- :param code: The filter code used in YouTube search queries.
98
- :type code: str
99
- :param human_readable: A human-readable representation of the filter.
100
- :type human_readable: str
101
- """
102
- self.code = code
103
- self.human_readable = human_readable
104
-
105
- def __str__(self):
106
- """Returns the human-readable representation of the filter.
107
-
108
- :return: The human-readable representation of the filter.
109
- :rtype: str
110
- """
111
- return self.human_readable
112
 
113
 
114
  class SearchService:
115
  """
116
  SearchService provides functionality to search for YouTube videos using the
117
- InnerTube API and retrieve live stream URLs using the Streamlink library.
118
-
119
- This service allows filtering search results to either live or regular video
120
- content and parsing the search response to extract relevant video information.
121
- It also constructs YouTube URLs for given video IDs and retrieves the best
122
- available stream URL for live YouTube videos.
123
 
124
  Methods:
125
- search: Searches YouTube for videos matching a query and filter.
126
- parse: Parses raw search response data into a list of video details.
127
- _search: Performs a YouTube search with the given query and filter.
128
  get_youtube_url: Constructs a YouTube URL for a given video ID.
129
  get_stream: Retrieves the stream URL for a given YouTube video URL.
130
  """
131
 
132
  @staticmethod
133
- def search(query: Optional[str], filter: SearchFilter = SearchFilter.VIDEO):
134
- """Searches YouTube for videos matching the given query and filter.
 
135
 
136
  :param query: The search query.
137
- :type query: Optional[str]
138
- :param filter: The search filter to apply.
139
- :type filter: SearchFilter
140
  :return: A list of search results, each a dictionary with video details.
141
  :rtype: List[Dict[str, Any]]
142
  """
143
- client = innertube.InnerTube("WEB", "2.20230920.00.00")
144
- response = SearchService._search(query, filter)
145
- results = SearchService.parse(response)
146
- return results
147
-
148
- @staticmethod
149
- def parse(data: Dict[str, Any]) -> List[Dict[str, str]]:
150
- """Parses the raw search response data into a list of video details.
151
-
152
- :param data: The raw search response data from YouTube.
153
- :type data: Dict[str, Any]
154
- :return: A list of parsed video details.
155
- :rtype: List[Dict[str, str]]
156
- """
157
  results = []
158
- contents = data["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"]
159
- items = contents[0]["itemSectionRenderer"]["contents"] if contents else []
160
- for item in items:
161
- if "videoRenderer" in item:
162
- renderer = item["videoRenderer"]
163
- results.append(
164
- {
165
- "video_id": renderer["videoId"],
166
- "thumbnail_url": renderer["thumbnail"]["thumbnails"][-1]["url"],
167
- "title": "".join(run["text"] for run in renderer["title"]["runs"]),
168
- }
169
- )
170
  return results
171
 
172
- @staticmethod
173
- def _search(query: Optional[str] = None, filter: SearchFilter = SearchFilter.VIDEO) -> Dict[str, Any]:
174
- """Performs a YouTube search with the given query and filter.
175
-
176
- :param query: The search query.
177
- :type query: Optional[str]
178
- :param filter: The search filter to apply.
179
- :type filter: SearchFilter
180
- :return: The raw search response data from YouTube.
181
- :rtype: Dict[str, Any]
182
- """
183
- client = innertube.InnerTube("WEB", "2.20230920.00.00")
184
- response = client.search(query=query, params=filter.code if filter else None)
185
- return response
186
-
187
  @staticmethod
188
  def get_youtube_url(video_id: str) -> str:
189
- """Constructs a YouTube URL for the given video ID.
 
190
 
191
  :param video_id: The ID of the YouTube video.
192
  :type video_id: str
@@ -197,7 +91,8 @@ class SearchService:
197
 
198
  @staticmethod
199
  def get_stream(youtube_url: str) -> Optional[str]:
200
- """Retrieves the stream URL for a given YouTube video URL.
 
201
 
202
  :param youtube_url: The URL of the YouTube video.
203
  :type youtube_url: str
@@ -211,17 +106,13 @@ class SearchService:
211
  best_stream = streams.get("best")
212
  return best_stream.url if best_stream else None
213
  else:
214
- gr.Warning(f"No streams found for: {youtube_url}")
215
  return None
216
  except Exception as e:
217
- gr.Error(f"An error occurred while getting stream: {e}")
218
- logging.warning(f"An error occurred: {e}")
219
  return None
220
 
221
 
222
- INITIAL_STREAMS = SearchService.search("world live cams", SearchFilter.LIVE)
223
-
224
-
225
  class LiveYouTubeObjectDetector:
226
  """
227
  LiveYouTubeObjectDetector is a class that integrates object detection into live YouTube streams.
@@ -229,20 +120,6 @@ class LiveYouTubeObjectDetector:
229
  The class also provides a Gradio interface for users to interact with the object detection system,
230
  allowing them to search for live streams, view them, and detect objects in real-time.
231
 
232
- The class handles the retrieval of live stream URLs, frame capture from the streams, object detection
233
- on the frames, and updating the Gradio interface with the results.
234
-
235
- Attributes:
236
- model (YOLO): The YOLO model used for object detection.
237
- streams (list): A list of dictionaries containing information about the current live streams.
238
- gallery (gr.Gallery): A Gradio gallery widget to display live stream thumbnails.
239
- search_input (gr.Textbox): A Gradio textbox for inputting search queries.
240
- stream_input (gr.Textbox): A Gradio textbox for inputting a specific live stream URL.
241
- annotated_image (gr.AnnotatedImage): A Gradio annotated image widget to display detection results.
242
- search_button (gr.Button): A Gradio button to initiate a new search for live streams.
243
- submit_button (gr.Button): A Gradio button to start object detection on a specified live stream.
244
- page_title (gr.HTML): A Gradio HTML widget to display the page title.
245
-
246
  Methods:
247
  detect_objects: Detects objects in a live YouTube stream given its URL.
248
  get_frame: Captures a frame from a live stream URL.
@@ -256,19 +133,10 @@ class LiveYouTubeObjectDetector:
256
  """Initializes the LiveYouTubeObjectDetector with YOLO model and UI components."""
257
  logging.getLogger().setLevel(logging.DEBUG)
258
  self.model = YOLO("yolov8x.pt")
259
- self.streams = INITIAL_STREAMS
260
-
261
- # Gradio UI
262
- initial_gallery_items = [(stream["thumbnail_url"], stream["title"]) for stream in self.streams]
263
- self.gallery = gr.Gallery(label="Live YouTube Videos", value=initial_gallery_items, show_label=True, columns=[4], rows=[5], object_fit="contain", height="auto", allow_preview=False)
264
- self.search_input = gr.Textbox(label="Search Live YouTube Videos")
265
- self.stream_input = gr.Textbox(label="URL of Live YouTube Video")
266
- self.annotated_image = gr.AnnotatedImage(show_label=False)
267
- self.search_button = gr.Button("Search", size="lg")
268
- self.submit_button = gr.Button("Detect Objects", variant="primary", size="lg")
269
- self.page_title = gr.HTML("<center><h1><b>Object Detection in Live YouTube Streams</b></h1></center>")
270
-
271
- def detect_objects(self, url: str) -> Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]:
272
  """
273
  Detects objects in the given live YouTube stream URL.
274
 
@@ -279,15 +147,15 @@ class LiveYouTubeObjectDetector:
279
  """
280
  stream_url = SearchService.get_stream(url)
281
  if not stream_url:
282
- gr.Error(f"Unable to find a stream for: {stream_url}")
283
- return self.create_black_image(), []
284
- frame = self.get_frame(stream_url)
285
  if frame is None:
286
- gr.Error(f"Unable to capture frame for: {stream_url}")
287
- return self.create_black_image(), []
288
  return self.annotate(frame)
289
 
290
- def get_frame(self, stream_url: str) -> Optional[np.ndarray]:
291
  """
292
  Captures a frame from the given live stream URL.
293
 
@@ -299,14 +167,13 @@ class LiveYouTubeObjectDetector:
299
  if not stream_url:
300
  return None
301
  try:
302
- cap = cv2.VideoCapture(stream_url)
303
- ret, frame = cap.read()
304
- cap.release()
305
- if ret:
306
- return cv2.resize(frame, (1920, 1080))
307
- else:
308
- logging.warning("Unable to process the HLS stream with cv2.VideoCapture.")
309
- return None
310
  except Exception as e:
311
  logging.warning(f"An error occurred while capturing the frame: {e}")
312
  return None
@@ -320,49 +187,40 @@ class LiveYouTubeObjectDetector:
320
  :return: A tuple of the annotated PIL image and list of annotations.
321
  :rtype: Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]
322
  """
323
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
324
- predictions = self.model.predict(frame_rgb)
325
  annotations = []
326
- result = predictions._images_prediction_lst[0]
327
-
328
- for bbox, label in zip(result.prediction.bboxes_xyxy, result.prediction.labels):
329
- x1, y1, x2, y2 = bbox
330
- class_name = result.class_names[int(label)]
331
  bbox_coords = (int(x1), int(y1), int(x2), int(y2))
332
  annotations.append((bbox_coords, class_name))
333
-
334
- return Image.fromarray(frame_rgb), annotations
335
 
336
  @staticmethod
337
- def create_black_image():
338
  """
339
  Creates a black image of fixed dimensions.
340
 
341
- This method generates a black image that can be used as a placeholder or background.
342
- It is particularly useful in cases where no valid frame is available for processing.
343
-
344
- :return: A black image as a numpy array.
345
- :rtype: np.ndarray
346
  """
347
- black_image = np.zeros((1080, 1920, 3), dtype=np.uint8)
348
  pil_black_image = Image.fromarray(black_image)
349
- cv2_black_image = cv2.cvtColor(np.array(pil_black_image), cv2.COLOR_RGB2BGR)
350
- return cv2_black_image
351
 
352
- @staticmethod
353
- def get_live_streams(query=""):
354
  """
355
  Searches for live streams on YouTube based on the given query.
356
 
357
- This method utilizes the SearchService to find live YouTube streams. If no query is
358
- provided, it defaults to searching for 'world live cams'.
359
-
360
- :param query: The search query for live streams, defaults to an empty string.
361
- :type query: str, optional
362
  :return: A list of dictionaries containing information about each live stream.
363
  :rtype: List[Dict[str, str]]
364
  """
365
- return SearchService.search(query if query else "world live cams", SearchFilter.LIVE)
366
 
367
  def render(self):
368
  """
@@ -375,42 +233,76 @@ class LiveYouTubeObjectDetector:
375
  The Gradio interface allows users to search for live YouTube streams, select a stream,
376
  and run object detection on the selected live stream.
377
  """
378
- with gr.Blocks(title="Object Detection in Live YouTube Streams", css="footer {visibility: hidden}", analytics_enabled=False) as app:
379
- self.page_title.render()
380
- with gr.Column():
381
- with gr.Group():
382
  with gr.Row():
383
- self.stream_input.render()
384
- self.submit_button.render()
385
- self.annotated_image.render()
386
- with gr.Group():
387
- with gr.Row():
388
- self.search_input.render()
389
- self.search_button.render()
390
- with gr.Row():
391
- self.gallery.render()
392
-
393
- @self.gallery.select(inputs=None, outputs=[self.annotated_image, self.stream_input], scroll_to_output=True)
394
- def detect_objects_from_gallery_item(evt: gr.SelectData):
395
- if evt.index is not None and evt.index < len(self.streams):
396
- selected_stream = self.streams[evt.index]
397
- stream_url = SearchService.get_youtube_url(selected_stream["video_id"])
398
- frame_output = self.detect_objects(stream_url)
399
- return frame_output, stream_url
400
- return None, ""
401
-
402
- @self.search_button.click(inputs=[self.search_input], outputs=[self.gallery])
403
- def search_live_streams(query):
404
- self.streams = self.get_live_streams(query)
405
- gallery_items = [(stream["thumbnail_url"], stream["title"]) for stream in self.streams]
406
- return gallery_items
407
-
408
- @self.submit_button.click(inputs=[self.stream_input], outputs=[self.annotated_image])
409
- def detect_objects_from_url(url):
410
- return self.detect_objects(url)
411
-
412
- return app.queue().launch(show_api=False, debug=True)
413
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
  if __name__ == "__main__":
416
- LiveYouTubeObjectDetector().render()
 
1
  """
2
+ This module integrates real-time object detection into live YouTube streams using the YOLO (You Only Look Once) model and provides an interactive user interface through Gradio. It allows users to search for live YouTube streams and apply object detection to these streams in real time.
3
 
4
  Main Features:
5
  - Search for live YouTube streams using specific queries.
 
8
  - Display the live stream and object detection results through a Gradio interface.
9
 
10
  The module comprises several key components:
 
11
  - `SearchService`: A service class to search for YouTube videos and retrieve live stream URLs.
12
  - `LiveYouTubeObjectDetector`: The main class integrating the YOLO model and Gradio UI, handling the entire workflow of searching, streaming, and object detection.
13
 
14
  Dependencies:
15
+ - OpenCV (`cv2`): Used for image processing tasks.
16
  - Gradio: Provides the interactive web-based user interface.
17
+ - Streamlink: Used for retrieving live stream data.
18
+ - NumPy: Utilized for numerical operations on image data.
19
+ - Pillow (`PIL`): A Python Imaging Library for opening, manipulating, and saving images.
20
+ - Ultralytics YOLO: The YOLO model implementation for object detection.
21
+ - `youtube-search-python`: Used for searching YouTube without API keys.
22
+ - `imageio`: For reading frames from live streams using FFmpeg.
23
 
24
  Usage:
25
  Run this file to launch the Gradio interface, which allows users to input search queries for YouTube live streams, select a stream, and perform object detection on the selected live stream.
26
 
27
  """
28
+
29
  import logging
 
 
 
 
30
  from typing import Any, Dict, List, Optional, Tuple
31
 
32
+ import asyncio
33
+ import cv2
34
+ import gradio as gr
35
+ import numpy as np
36
+ from ultralytics import YOLO
37
+ import streamlink
38
+ from PIL import Image
39
+ from youtubesearchpython import VideosSearch
40
+ import imageio.v3 as iio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ logging.basicConfig(level=logging.DEBUG)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
 
45
  class SearchService:
46
  """
47
  SearchService provides functionality to search for YouTube videos using the
48
+ `youtube-search-python` library and retrieve live stream URLs using the Streamlink library.
 
 
 
 
 
49
 
50
  Methods:
51
+ search: Searches YouTube for videos matching a query and live filter.
 
 
52
  get_youtube_url: Constructs a YouTube URL for a given video ID.
53
  get_stream: Retrieves the stream URL for a given YouTube video URL.
54
  """
55
 
56
  @staticmethod
57
+ def search(query: str, live: bool = False) -> List[Dict[str, Any]]:
58
+ """
59
+ Searches YouTube for videos matching the given query and live filter.
60
 
61
  :param query: The search query.
62
+ :type query: str
63
+ :param live: Whether to filter for live videos.
64
+ :type live: bool
65
  :return: A list of search results, each a dictionary with video details.
66
  :rtype: List[Dict[str, Any]]
67
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  results = []
69
+ # Apply live filter if needed
70
+ search_preferences = "EgJAAQ%3D%3D" if live else None # 'Live' filter code
71
+ videos_search = VideosSearch(query, limit=20, searchPreferences=search_preferences)
72
+ for result in videos_search.result()['result']:
73
+ results.append({
74
+ 'video_id': result['id'],
75
+ 'thumbnail_url': result['thumbnails'][-1]['url'],
76
+ 'title': result['title'],
77
+ })
 
 
 
78
  return results
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  @staticmethod
81
  def get_youtube_url(video_id: str) -> str:
82
+ """
83
+ Constructs a YouTube URL for the given video ID.
84
 
85
  :param video_id: The ID of the YouTube video.
86
  :type video_id: str
 
91
 
92
  @staticmethod
93
  def get_stream(youtube_url: str) -> Optional[str]:
94
+ """
95
+ Retrieves the stream URL for a given YouTube video URL.
96
 
97
  :param youtube_url: The URL of the YouTube video.
98
  :type youtube_url: str
 
106
  best_stream = streams.get("best")
107
  return best_stream.url if best_stream else None
108
  else:
109
+ logging.warning(f"No streams found for: {youtube_url}")
110
  return None
111
  except Exception as e:
112
+ logging.warning(f"An error occurred while getting stream: {e}")
 
113
  return None
114
 
115
 
 
 
 
116
  class LiveYouTubeObjectDetector:
117
  """
118
  LiveYouTubeObjectDetector is a class that integrates object detection into live YouTube streams.
 
120
  The class also provides a Gradio interface for users to interact with the object detection system,
121
  allowing them to search for live streams, view them, and detect objects in real-time.
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  Methods:
124
  detect_objects: Detects objects in a live YouTube stream given its URL.
125
  get_frame: Captures a frame from a live stream URL.
 
133
  """Initializes the LiveYouTubeObjectDetector with YOLO model and UI components."""
134
  logging.getLogger().setLevel(logging.DEBUG)
135
  self.model = YOLO("yolov8x.pt")
136
+ self.model.fuse()
137
+ self.streams = self.get_live_streams("world live cams")
138
+
139
+ async def detect_objects(self, url: str) -> Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]:
 
 
 
 
 
 
 
 
 
140
  """
141
  Detects objects in the given live YouTube stream URL.
142
 
 
147
  """
148
  stream_url = SearchService.get_stream(url)
149
  if not stream_url:
150
+ logging.error(f"Unable to find a stream for: {url}")
151
+ return self.create_black_image()
152
+ frame = await self.get_frame(stream_url)
153
  if frame is None:
154
+ logging.error(f"Unable to capture frame for: {url}")
155
+ return self.create_black_image()
156
  return self.annotate(frame)
157
 
158
+ async def get_frame(self, stream_url: str) -> Optional[np.ndarray]:
159
  """
160
  Captures a frame from the given live stream URL.
161
 
 
167
  if not stream_url:
168
  return None
169
  try:
170
+ reader = iio.imiter(stream_url, plugin='ffmpeg', fps=1)
171
+ loop = asyncio.get_event_loop()
172
+ frame = await loop.run_in_executor(None, next, reader, None)
173
+ return frame
174
+ except StopIteration:
175
+ logging.warning("Could not read frame from stream.")
176
+ return None
 
177
  except Exception as e:
178
  logging.warning(f"An error occurred while capturing the frame: {e}")
179
  return None
 
187
  :return: A tuple of the annotated PIL image and list of annotations.
188
  :rtype: Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]
189
  """
190
+ results = self.model(frame)[0]
 
191
  annotations = []
192
+ boxes = results.boxes
193
+ for box in boxes:
194
+ x1, y1, x2, y2 = box.xyxy[0].tolist()
195
+ class_id = int(box.cls[0])
196
+ class_name = self.model.names[class_id]
197
  bbox_coords = (int(x1), int(y1), int(x2), int(y2))
198
  annotations.append((bbox_coords, class_name))
199
+ pil_image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
200
+ return pil_image, annotations
201
 
202
  @staticmethod
203
+ def create_black_image() -> Tuple[Image.Image, List]:
204
  """
205
  Creates a black image of fixed dimensions.
206
 
207
+ :return: A black image as a PIL Image and an empty list of annotations.
208
+ :rtype: Tuple[Image.Image, List]
 
 
 
209
  """
210
+ black_image = np.zeros((720, 1280, 3), dtype=np.uint8)
211
  pil_black_image = Image.fromarray(black_image)
212
+ return pil_black_image, []
 
213
 
214
+ def get_live_streams(self, query: str = "") -> List[Dict[str, Any]]:
 
215
  """
216
  Searches for live streams on YouTube based on the given query.
217
 
218
+ :param query: The search query for live streams.
219
+ :type query: str
 
 
 
220
  :return: A list of dictionaries containing information about each live stream.
221
  :rtype: List[Dict[str, str]]
222
  """
223
+ return SearchService.search(query if query else "world live cams", live=True)
224
 
225
  def render(self):
226
  """
 
233
  The Gradio interface allows users to search for live YouTube streams, select a stream,
234
  and run object detection on the selected live stream.
235
  """
236
+ with gr.Blocks(title="Object Detection in Live YouTube Streams", css=".gradio-container {background-color: #f5f5f5}", theme=gr.themes.Soft()) as app:
237
+ gr.HTML("<h1 style='text-align: center; color: #1E88E5;'>Object Detection in Live YouTube Streams</h1>")
238
+ with gr.Tabs():
239
+ with gr.TabItem("Live Stream Detection"):
240
  with gr.Row():
241
+ stream_input = gr.Textbox(label="URL of Live YouTube Video", placeholder="Enter YouTube live stream URL...", interactive=True)
242
+ submit_button = gr.Button("Detect Objects", variant="primary")
243
+ annotated_image = gr.AnnotatedImage(label="Detection Result", height=480)
244
+ status_text = gr.Markdown(value="Status: Ready", visible=False)
245
+
246
+ async def detect_objects_from_url(url):
247
+ status_text.update(value="Status: Processing...", visible=True)
248
+ try:
249
+ result = await self.detect_objects(url)
250
+ status_text.update(value="Status: Done", visible=True)
251
+ return result
252
+ except Exception as e:
253
+ logging.error(f"An error occurred: {e}")
254
+ status_text.update(value=f"Status: Error - {e}", visible=True)
255
+ return self.create_black_image()
256
+
257
+ submit_button.click(fn=detect_objects_from_url, inputs=[stream_input], outputs=[annotated_image], api_name="detect_objects")
258
+
259
+ with gr.TabItem("Search Live Streams"):
260
+ with gr.Row():
261
+ search_input = gr.Textbox(label="Search for Live YouTube Streams", placeholder="Enter search query...", interactive=True)
262
+ search_button = gr.Button("Search", variant="secondary")
263
+ gallery = gr.Gallery(label="Live YouTube Streams", show_label=False).style(grid=[4], height="auto")
264
+ gallery.style(item_height=150)
265
+ status_text_search = gr.Markdown(value="", visible=False)
266
+
267
+ def search_live_streams(query):
268
+ status_text_search.update(value="Searching...", visible=True)
269
+ self.streams = self.get_live_streams(query)
270
+ gallery_items = []
271
+ for stream in self.streams:
272
+ thumb_url = stream["thumbnail_url"]
273
+ title = stream["title"]
274
+ video_id = stream["video_id"]
275
+ gallery_items.append((thumb_url, title, video_id))
276
+ status_text_search.update(value="Search Results:", visible=True)
277
+ return gr.update(value=gallery_items)
278
+
279
+ search_button.click(fn=search_live_streams, inputs=[search_input], outputs=[gallery], api_name="search_streams")
280
+
281
+ async def detect_objects_from_gallery_item(evt: gr.SelectData):
282
+ index = evt.index
283
+ if index is not None and index < len(self.streams):
284
+ selected_stream = self.streams[index]
285
+ stream_url = SearchService.get_youtube_url(selected_stream["video_id"])
286
+ stream_input.value = stream_url
287
+ result = await self.detect_objects(stream_url)
288
+ annotated_image.update(value=result[0], annotations=result[1])
289
+ with gr.Row():
290
+ annotated_image.render()
291
+ return result
292
+
293
+ gallery.select(fn=detect_objects_from_gallery_item, inputs=None, outputs=None)
294
+
295
+ gr.Markdown(
296
+ """
297
+ **Instructions:**
298
+ - **Live Stream Detection Tab:** Enter a YouTube live stream URL and click 'Detect Objects' to view the real-time object detection.
299
+ - **Search Live Streams Tab:** Search for live streams on YouTube, select one from the gallery, and view object detection results.
300
+ """
301
+ )
302
+
303
+ gr.HTML("<footer style='text-align: center; color: gray;'>Developed using Gradio and YOLO</footer>")
304
+
305
+ app.queue(concurrency_count=3).launch()
306
 
307
  if __name__ == "__main__":
308
+ LiveYouTubeObjectDetector().render()