jonathanagustin commited on
Commit
13d6ce1
1 Parent(s): 94a39eb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +20 -144
app.py CHANGED
@@ -1,32 +1,26 @@
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.
6
- - Retrieve live stream URLs using the Streamlink library.
7
  - Perform real-time object detection on live streams using the YOLO model.
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
 
28
  import logging
29
- import os
30
  import sys
31
  from enum import Enum
32
  from typing import Any, Dict, List, Optional, Tuple
@@ -35,86 +29,34 @@ import cv2
35
  import gradio as gr
36
  import innertube
37
  import numpy as np
38
- import streamlink
39
  from PIL import Image
40
  from ultralytics import YOLO
 
41
 
42
- # Set up logging
43
  logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
44
 
45
 
46
  class SearchFilter(Enum):
47
- """
48
- An enumeration for specifying different types of YouTube search filters.
49
-
50
- This Enum class is used to define filters for categorizing YouTube search
51
- results into either live or regular video content. It is utilized in
52
- conjunction with the `SearchService` class to refine YouTube searches
53
- based on the type of content being sought.
54
-
55
- Attributes:
56
- LIVE (str): Represents the filter code for live video content on YouTube.
57
- VIDEO (str): Represents the filter code for regular, non-live video content on YouTube.
58
- """
59
-
60
  LIVE = ("EgJAAQ%3D%3D", "Live")
61
  VIDEO = ("EgIQAQ%3D%3D", "Video")
62
 
63
  def __init__(self, code, human_readable):
64
- """Initializes the SearchFilter with a code and a human-readable string.
65
-
66
- :param code: The filter code used in YouTube search queries.
67
- :type code: str
68
- :param human_readable: A human-readable representation of the filter.
69
- :type human_readable: str
70
- """
71
  self.code = code
72
  self.human_readable = human_readable
73
 
74
  def __str__(self):
75
- """Returns the human-readable representation of the filter.
76
-
77
- :return: The human-readable representation of the filter.
78
- :rtype: str
79
- """
80
  return self.human_readable
81
 
82
 
83
  class SearchService:
84
- """
85
- SearchService provides functionality to search for YouTube videos using the
86
- InnerTube API and retrieve live stream URLs using the Streamlink library.
87
-
88
- This service allows filtering search results to either live or regular video
89
- content and parsing the search response to extract relevant video information.
90
- It also constructs YouTube URLs for given video IDs and retrieves the best
91
- available stream URL for live YouTube videos.
92
- """
93
-
94
  @staticmethod
95
  def search(query: Optional[str], filter: SearchFilter = SearchFilter.VIDEO):
96
- """Searches YouTube for videos matching the given query and filter.
97
-
98
- :param query: The search query.
99
- :type query: Optional[str]
100
- :param filter: The search filter to apply.
101
- :type filter: SearchFilter
102
- :return: A list of search results, each a dictionary with video details.
103
- :rtype: List[Dict[str, Any]]
104
- """
105
  response = SearchService._search(query, filter)
106
  results = SearchService.parse(response)
107
  return results
108
 
109
  @staticmethod
110
  def parse(data: Dict[str, Any]) -> List[Dict[str, str]]:
111
- """Parses the raw search response data into a list of video details.
112
-
113
- :param data: The raw search response data from YouTube.
114
- :type data: Dict[str, Any]
115
- :return: A list of parsed video details.
116
- :rtype: List[Dict[str, str]]
117
- """
118
  results = []
119
  try:
120
  contents = data["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"]
@@ -141,33 +83,17 @@ class SearchService:
141
 
142
  @staticmethod
143
  def _search(query: Optional[str] = None, filter: SearchFilter = SearchFilter.VIDEO) -> Dict[str, Any]:
144
- """Performs a YouTube search with the given query and filter.
145
-
146
- :param query: The search query.
147
- :type query: Optional[str]
148
- :param filter: The search filter to apply.
149
- :type filter: SearchFilter
150
- :return: The raw search response data from YouTube.
151
- :rtype: Dict[str, Any]
152
- """
153
  client = innertube.InnerTube(client_name="WEB", client_version="2.20230920.00.00")
154
  response = client.search(query=query, params=filter.code if filter else None)
155
  return response
156
 
157
  @staticmethod
158
  def get_youtube_url(video_id: str) -> str:
159
- """Constructs a YouTube URL for the given video ID.
160
-
161
- :param video_id: The ID of the YouTube video.
162
- :type video_id: str
163
- :return: The YouTube URL for the video.
164
- :rtype: str
165
- """
166
  return f"https://www.youtube.com/watch?v={video_id}"
167
 
168
  @staticmethod
169
  def get_stream(youtube_url: str) -> Optional[str]:
170
- """Retrieves the stream URL for a given YouTube video URL.
171
 
172
  :param youtube_url: The URL of the YouTube video.
173
  :type youtube_url: str
@@ -175,13 +101,14 @@ class SearchService:
175
  :rtype: Optional[str]
176
  """
177
  try:
178
- session = streamlink.Streamlink()
179
- streams = session.streams(youtube_url)
180
- if streams:
181
- best_stream = streams.get("best")
182
- return best_stream.url if best_stream else None
 
183
  else:
184
- logging.warning(f"No streams found for: {youtube_url}")
185
  return None
186
  except Exception as e:
187
  logging.warning(f"An error occurred while getting stream: {e}")
@@ -192,15 +119,7 @@ INITIAL_STREAMS = SearchService.search("world live cams", SearchFilter.LIVE)
192
 
193
 
194
  class LiveYouTubeObjectDetector:
195
- """
196
- LiveYouTubeObjectDetector is a class that integrates object detection into live YouTube streams.
197
- It uses the YOLO model to detect objects in video frames captured from live streams.
198
- The class also provides a Gradio interface for users to interact with the object detection system,
199
- allowing them to search for live streams, view them, and detect objects in real-time.
200
- """
201
-
202
  def __init__(self):
203
- """Initializes the LiveYouTubeObjectDetector with YOLO model and UI components."""
204
  logging.getLogger().setLevel(logging.DEBUG)
205
  self.model = YOLO("yolo11n.pt")
206
  self.streams = INITIAL_STREAMS
@@ -223,14 +142,6 @@ class LiveYouTubeObjectDetector:
223
  self.page_title = gr.HTML("<center><h1><b>Object Detection in Live YouTube Streams</b></h1></center>")
224
 
225
  def detect_objects(self, url: str) -> Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]:
226
- """
227
- Detects objects in the given live YouTube stream URL.
228
-
229
- :param url: The URL of the live YouTube video.
230
- :type url: str
231
- :return: A tuple containing the annotated image and a list of annotations.
232
- :rtype: Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]
233
- """
234
  stream_url = SearchService.get_stream(url)
235
  if not stream_url:
236
  logging.error(f"Unable to find a stream for: {url}")
@@ -242,38 +153,23 @@ class LiveYouTubeObjectDetector:
242
  return self.annotate(frame)
243
 
244
  def get_frame(self, stream_url: str) -> Optional[np.ndarray]:
245
- """
246
- Captures a frame from the given live stream URL.
247
-
248
- :param stream_url: The URL of the live stream.
249
- :type stream_url: str
250
- :return: The captured frame as a numpy array, or None if capture fails.
251
- :rtype: Optional[np.ndarray]
252
- """
253
  if not stream_url:
254
  return None
255
  try:
 
256
  cap = cv2.VideoCapture(stream_url)
257
  ret, frame = cap.read()
258
  cap.release()
259
  if ret and frame is not None:
260
  return cv2.resize(frame, (1920, 1080))
261
  else:
262
- logging.warning("Unable to process the HLS stream with cv2.VideoCapture.")
263
  return None
264
  except Exception as e:
265
  logging.warning(f"An error occurred while capturing the frame: {e}")
266
  return None
267
 
268
  def annotate(self, frame: np.ndarray) -> Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]:
269
- """
270
- Annotates the given frame with detected objects and their bounding boxes.
271
-
272
- :param frame: The frame to be annotated.
273
- :type frame: np.ndarray
274
- :return: A tuple of the annotated PIL image and list of annotations.
275
- :rtype: Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]
276
- """
277
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
278
  predictions = self.model.predict(frame_rgb)
279
  annotations = []
@@ -291,37 +187,18 @@ class LiveYouTubeObjectDetector:
291
 
292
  @staticmethod
293
  def create_black_image() -> Tuple[Image.Image, List]:
294
- """
295
- Creates a black image of fixed dimensions.
296
-
297
- :return: A black image as a PIL image and an empty list of annotations.
298
- :rtype: Tuple[Image.Image, List]
299
- """
300
  black_image = np.zeros((1080, 1920, 3), dtype=np.uint8)
301
  pil_black_image = Image.fromarray(black_image)
302
  return pil_black_image, []
303
 
304
  @staticmethod
305
  def get_live_streams(query=""):
306
- """
307
- Searches for live streams on YouTube based on the given query.
308
-
309
- :param query: The search query for live streams, defaults to 'world live cams'.
310
- :type query: str
311
- :return: A list of dictionaries containing information about each live stream.
312
- :rtype: List[Dict[str, str]]
313
- """
314
  return SearchService.search(query if query else "world live cams", SearchFilter.LIVE)
315
 
316
  def render(self):
317
- """
318
- Sets up and launches the Gradio interface for the application.
319
-
320
- The Gradio interface allows users to search for live YouTube streams, select a stream,
321
- and run object detection on the selected live stream.
322
- """
323
  with gr.Blocks(title="Object Detection in Live YouTube Streams",
324
- css="footer {visibility: hidden}", analytics_enabled=False) as app:
 
325
  self.page_title.render()
326
  with gr.Column():
327
  with gr.Group():
@@ -341,10 +218,9 @@ class LiveYouTubeObjectDetector:
341
  if evt.index is not None and evt.index < len(self.streams):
342
  selected_stream = self.streams[evt.index]
343
  stream_url = SearchService.get_youtube_url(selected_stream["video_id"])
344
- annotated_image, annotations = self.detect_objects(stream_url)
345
  self.stream_input.value = stream_url
346
- return annotated_image, annotations, stream_url
347
- return None, "", ""
348
 
349
  @self.search_button.click(inputs=[self.search_input], outputs=[self.gallery])
350
  def search_live_streams(query):
@@ -360,4 +236,4 @@ class LiveYouTubeObjectDetector:
360
 
361
 
362
  if __name__ == "__main__":
363
- LiveYouTubeObjectDetector().render()
 
1
  """
2
+ This module integrates real-time object detection into live YouTube streams using the YOLO 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.
6
+ - Retrieve live stream URLs using the `pytube` library.
7
  - Perform real-time object detection on live streams using the YOLO model.
8
  - Display the live stream and object detection results through a Gradio interface.
9
 
 
 
 
 
 
10
  Dependencies:
11
  - cv2 (OpenCV): Used for image processing tasks.
12
  - Gradio: Provides the interactive web-based user interface.
13
+ - `pytube`: Used for retrieving live stream URLs from YouTube.
14
+ - innertube: Used for interacting with YouTube's internal API.
15
  - numpy: Utilized for numerical operations on image data.
16
  - PIL (Pillow): A Python Imaging Library for opening, manipulating, and saving images.
17
  - ultralytics YOLO: The YOLO model implementation for object detection.
18
 
19
  Usage:
20
  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.
 
21
  """
22
 
23
  import logging
 
24
  import sys
25
  from enum import Enum
26
  from typing import Any, Dict, List, Optional, Tuple
 
29
  import gradio as gr
30
  import innertube
31
  import numpy as np
 
32
  from PIL import Image
33
  from ultralytics import YOLO
34
+ from pytube import YouTube
35
 
 
36
  logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
37
 
38
 
39
  class SearchFilter(Enum):
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  LIVE = ("EgJAAQ%3D%3D", "Live")
41
  VIDEO = ("EgIQAQ%3D%3D", "Video")
42
 
43
  def __init__(self, code, human_readable):
 
 
 
 
 
 
 
44
  self.code = code
45
  self.human_readable = human_readable
46
 
47
  def __str__(self):
 
 
 
 
 
48
  return self.human_readable
49
 
50
 
51
  class SearchService:
 
 
 
 
 
 
 
 
 
 
52
  @staticmethod
53
  def search(query: Optional[str], filter: SearchFilter = SearchFilter.VIDEO):
 
 
 
 
 
 
 
 
 
54
  response = SearchService._search(query, filter)
55
  results = SearchService.parse(response)
56
  return results
57
 
58
  @staticmethod
59
  def parse(data: Dict[str, Any]) -> List[Dict[str, str]]:
 
 
 
 
 
 
 
60
  results = []
61
  try:
62
  contents = data["contents"]["twoColumnSearchResultsRenderer"]["primaryContents"]["sectionListRenderer"]["contents"]
 
83
 
84
  @staticmethod
85
  def _search(query: Optional[str] = None, filter: SearchFilter = SearchFilter.VIDEO) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
86
  client = innertube.InnerTube(client_name="WEB", client_version="2.20230920.00.00")
87
  response = client.search(query=query, params=filter.code if filter else None)
88
  return response
89
 
90
  @staticmethod
91
  def get_youtube_url(video_id: str) -> str:
 
 
 
 
 
 
 
92
  return f"https://www.youtube.com/watch?v={video_id}"
93
 
94
  @staticmethod
95
  def get_stream(youtube_url: str) -> Optional[str]:
96
+ """Retrieves the stream URL for a given YouTube video URL using pytube.
97
 
98
  :param youtube_url: The URL of the YouTube video.
99
  :type youtube_url: str
 
101
  :rtype: Optional[str]
102
  """
103
  try:
104
+ yt = YouTube(youtube_url)
105
+ # Live streams have a 'LIVE' attribute in the 'streaming_data' key
106
+ if 'hlsManifestUrl' in yt.watch_html:
107
+ # Extract the HLS manifest URL
108
+ hls_manifest_url = yt.vid_info['streamingData']['hlsManifestUrl']
109
+ return hls_manifest_url
110
  else:
111
+ logging.warning(f"No live stream found for: {youtube_url}")
112
  return None
113
  except Exception as e:
114
  logging.warning(f"An error occurred while getting stream: {e}")
 
119
 
120
 
121
  class LiveYouTubeObjectDetector:
 
 
 
 
 
 
 
122
  def __init__(self):
 
123
  logging.getLogger().setLevel(logging.DEBUG)
124
  self.model = YOLO("yolo11n.pt")
125
  self.streams = INITIAL_STREAMS
 
142
  self.page_title = gr.HTML("<center><h1><b>Object Detection in Live YouTube Streams</b></h1></center>")
143
 
144
  def detect_objects(self, url: str) -> Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]:
 
 
 
 
 
 
 
 
145
  stream_url = SearchService.get_stream(url)
146
  if not stream_url:
147
  logging.error(f"Unable to find a stream for: {url}")
 
153
  return self.annotate(frame)
154
 
155
  def get_frame(self, stream_url: str) -> Optional[np.ndarray]:
 
 
 
 
 
 
 
 
156
  if not stream_url:
157
  return None
158
  try:
159
+ # Use cv2 VideoCapture with the HLS manifest URL
160
  cap = cv2.VideoCapture(stream_url)
161
  ret, frame = cap.read()
162
  cap.release()
163
  if ret and frame is not None:
164
  return cv2.resize(frame, (1920, 1080))
165
  else:
166
+ logging.warning("Unable to process the live stream with cv2.VideoCapture.")
167
  return None
168
  except Exception as e:
169
  logging.warning(f"An error occurred while capturing the frame: {e}")
170
  return None
171
 
172
  def annotate(self, frame: np.ndarray) -> Tuple[Image.Image, List[Tuple[Tuple[int, int, int, int], str]]]:
 
 
 
 
 
 
 
 
173
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
174
  predictions = self.model.predict(frame_rgb)
175
  annotations = []
 
187
 
188
  @staticmethod
189
  def create_black_image() -> Tuple[Image.Image, List]:
 
 
 
 
 
 
190
  black_image = np.zeros((1080, 1920, 3), dtype=np.uint8)
191
  pil_black_image = Image.fromarray(black_image)
192
  return pil_black_image, []
193
 
194
  @staticmethod
195
  def get_live_streams(query=""):
 
 
 
 
 
 
 
 
196
  return SearchService.search(query if query else "world live cams", SearchFilter.LIVE)
197
 
198
  def render(self):
 
 
 
 
 
 
199
  with gr.Blocks(title="Object Detection in Live YouTube Streams",
200
+ css="footer {visibility: hidden}",
201
+ analytics_enabled=False) as app:
202
  self.page_title.render()
203
  with gr.Column():
204
  with gr.Group():
 
218
  if evt.index is not None and evt.index < len(self.streams):
219
  selected_stream = self.streams[evt.index]
220
  stream_url = SearchService.get_youtube_url(selected_stream["video_id"])
 
221
  self.stream_input.value = stream_url
222
+ return self.detect_objects(stream_url)
223
+ return self.create_black_image()
224
 
225
  @self.search_button.click(inputs=[self.search_input], outputs=[self.gallery])
226
  def search_live_streams(query):
 
236
 
237
 
238
  if __name__ == "__main__":
239
+ LiveYouTubeObjectDetector().render()