inventwithdean commited on
Commit
7b58f25
·
1 Parent(s): 3ed46b6

add dynamic cameras and youtube chat

Browse files
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: The Emergent Show
3
- emoji: 🌃
4
  colorFrom: green
5
  colorTo: purple
6
  sdk: gradio
 
1
  ---
2
  title: The Emergent Show
3
+ emoji: 📺
4
  colorFrom: green
5
  colorTo: purple
6
  sdk: gradio
__pycache__/guard.cpython-310.pyc CHANGED
Binary files a/__pycache__/guard.cpython-310.pyc and b/__pycache__/guard.cpython-310.pyc differ
 
__pycache__/host.cpython-310.pyc CHANGED
Binary files a/__pycache__/host.cpython-310.pyc and b/__pycache__/host.cpython-310.pyc differ
 
__pycache__/image_api.cpython-310.pyc CHANGED
Binary files a/__pycache__/image_api.cpython-310.pyc and b/__pycache__/image_api.cpython-310.pyc differ
 
__pycache__/prompts.cpython-310.pyc ADDED
Binary file (1.35 kB). View file
 
__pycache__/timing.cpython-310.pyc CHANGED
Binary files a/__pycache__/timing.cpython-310.pyc and b/__pycache__/timing.cpython-310.pyc differ
 
__pycache__/tv_crew.cpython-310.pyc CHANGED
Binary files a/__pycache__/tv_crew.cpython-310.pyc and b/__pycache__/tv_crew.cpython-310.pyc differ
 
__pycache__/yt_chat.cpython-310.pyc ADDED
Binary file (4.17 kB). View file
 
app.py CHANGED
@@ -11,8 +11,10 @@ import random
11
  import threading
12
  from timing import TimeManager
13
  from validity import is_valid_rpm_url
 
 
14
 
15
- # Remove these to upload on Spaces
16
  # import dotenv
17
 
18
  # dotenv.load_dotenv()
@@ -27,20 +29,34 @@ client_guard = OpenAI(base_url=guard_api_base, api_key=guard_api_key)
27
  unreal_orchestrator_url = os.getenv("ORCHESTRATOR_URL")
28
  api_key_unreal = os.getenv("API_KEY_UNREAL")
29
 
30
- show_state = {"current_guest": None, "time_since_last_guest_message": 0}
 
 
 
 
31
  turn_limit = 7
32
 
33
  host = Host(client, show_state)
34
- tv_crew = TVCrew(client)
 
 
 
35
  audience = Audience(client)
36
  guardian = Guardian(client_guard)
37
 
38
  timeManager = TimeManager(host, show_state)
 
39
 
40
- t = threading.Thread(target=timeManager.guest_time_limit, daemon=True)
41
- t.start()
42
 
43
- in_construction = True
 
 
 
 
 
 
44
 
45
 
46
  def join_show(avatar_url):
@@ -77,6 +93,7 @@ def join_show(avatar_url):
77
  host.clear_context()
78
  audience.clear_context()
79
  tv_crew.clear_context()
 
80
  return f"You have joined the show. Your guest_name: {name}"
81
 
82
 
@@ -96,7 +113,6 @@ def speak(guest_name, text):
96
  return "Join the show first! And then use the guest_name you get"
97
 
98
  headers = {"Content-Type": "application/json", "x-api-key": api_key_unreal}
99
-
100
  is_safe, categories = guardian.moderate_message(text)
101
  if not is_safe:
102
  requests.post(url=f"{unreal_orchestrator_url}/wrapup", json={}, headers=headers)
@@ -114,21 +130,19 @@ def speak(guest_name, text):
114
  # **************************************************************************
115
 
116
  # Get TV suggestion
117
- image_url, current_tv_image_caption = None, None
118
 
119
  # Update TV 50% of the time
120
- if random.random() < 0.7:
121
- image_url, current_tv_image_caption = tv_crew.suggest_image()
122
- if image_url:
123
  # Let host know that Television has been updated
124
  tv_info_suffix = (
125
  f"\n[Crew Updated Television to: {current_tv_image_caption}]\n"
126
  )
127
  text = text + tv_info_suffix
128
-
129
- # Pass in image_url which will be displayed just after the guest has finished speaking
130
- if image_url:
131
- payload["image_url"] = image_url
132
 
133
  # **************************************************************************
134
 
@@ -171,6 +185,9 @@ def speak(guest_name, text):
171
 
172
  if shouldWrapUp:
173
  requests.post(url=f"{unreal_orchestrator_url}/wrapup", json={}, headers=headers)
 
 
 
174
  show_state["current_guest"] = None
175
  output = output + "\nThe show has been wrapped up. Thank you for joining!"
176
 
@@ -182,9 +199,7 @@ with gr.Blocks() as demo:
182
  gr.Markdown("# The Emergent Show 🍻")
183
  gr.Markdown("### Join the Live Stream with your LLM and let's have a chat")
184
  if in_construction:
185
- gr.Markdown(
186
- "# 🚧 Under construction. Please check back later! 🚧"
187
- )
188
  with gr.Tab("Join"):
189
  gr.HTML(
190
  """
@@ -253,7 +268,7 @@ with gr.Blocks() as demo:
253
  left:0%;
254
  width:100%;
255
  height:100%;"
256
- src="https://www.youtube.com/embed/VhvLqnE1k5M?si=WSmI4Ypo8Bm3JbEO" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
257
  </div>
258
  """
259
  )
 
11
  import threading
12
  from timing import TimeManager
13
  from validity import is_valid_rpm_url
14
+ from yt_chat import StreamChatHost
15
+ from prompts import system_prompt_tv_crew_guest, system_prompt_tv_crew_chat
16
 
17
+ # Remove these before uploading on Spaces
18
  # import dotenv
19
 
20
  # dotenv.load_dotenv()
 
29
  unreal_orchestrator_url = os.getenv("ORCHESTRATOR_URL")
30
  api_key_unreal = os.getenv("API_KEY_UNREAL")
31
 
32
+
33
+ show_state = {
34
+ "current_guest": None,
35
+ "time_since_last_guest_message": 0,
36
+ }
37
  turn_limit = 7
38
 
39
  host = Host(client, show_state)
40
+
41
+ tv_crew = TVCrew(client, system_prompt_tv_crew_guest)
42
+ tv_crew_chat = TVCrew(client, system_prompt_tv_crew_chat)
43
+
44
  audience = Audience(client)
45
  guardian = Guardian(client_guard)
46
 
47
  timeManager = TimeManager(host, show_state)
48
+ streamChatHost = StreamChatHost(client, tv_crew_chat, guardian)
49
 
50
+ thread_time_manager = threading.Thread(target=timeManager.guest_time_limit, daemon=True)
51
+ thread_time_manager.start()
52
 
53
+ thread_stream_chat = threading.Thread(
54
+ target=streamChatHost.chat_interaction_loop, daemon=True
55
+ )
56
+ thread_stream_chat.start()
57
+
58
+
59
+ in_construction = False
60
 
61
 
62
  def join_show(avatar_url):
 
93
  host.clear_context()
94
  audience.clear_context()
95
  tv_crew.clear_context()
96
+
97
  return f"You have joined the show. Your guest_name: {name}"
98
 
99
 
 
113
  return "Join the show first! And then use the guest_name you get"
114
 
115
  headers = {"Content-Type": "application/json", "x-api-key": api_key_unreal}
 
116
  is_safe, categories = guardian.moderate_message(text)
117
  if not is_safe:
118
  requests.post(url=f"{unreal_orchestrator_url}/wrapup", json={}, headers=headers)
 
130
  # **************************************************************************
131
 
132
  # Get TV suggestion
133
+ image_base64, current_tv_image_caption = None, None
134
 
135
  # Update TV 50% of the time
136
+ if random.random() < 0.5:
137
+ image_base64, current_tv_image_caption = tv_crew.suggest_image()
138
+ if image_base64:
139
  # Let host know that Television has been updated
140
  tv_info_suffix = (
141
  f"\n[Crew Updated Television to: {current_tv_image_caption}]\n"
142
  )
143
  text = text + tv_info_suffix
144
+ # Pass in image_url which will be displayed just after the guest has finished speaking
145
+ payload["base64"] = image_base64
 
 
146
 
147
  # **************************************************************************
148
 
 
185
 
186
  if shouldWrapUp:
187
  requests.post(url=f"{unreal_orchestrator_url}/wrapup", json={}, headers=headers)
188
+ tv_crew.clear_context()
189
+ audience.clear_context()
190
+ host.clear_context()
191
  show_state["current_guest"] = None
192
  output = output + "\nThe show has been wrapped up. Thank you for joining!"
193
 
 
199
  gr.Markdown("# The Emergent Show 🍻")
200
  gr.Markdown("### Join the Live Stream with your LLM and let's have a chat")
201
  if in_construction:
202
+ gr.Markdown("# 🚧 Under construction. Please check back later! 🚧")
 
 
203
  with gr.Tab("Join"):
204
  gr.HTML(
205
  """
 
268
  left:0%;
269
  width:100%;
270
  height:100%;"
271
+ src="https://www.youtube.com/embed/xBVJIJQg1FY?si=tsCm1DdajNCP45ho" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
272
  </div>
273
  """
274
  )
guard.py CHANGED
@@ -1,4 +1,6 @@
1
 
 
 
2
  class Guardian:
3
  def __init__(self, client):
4
  self.model = "Qwen/Qwen3Guard-Gen-4B"
@@ -8,12 +10,16 @@ class Guardian:
8
  messages = [
9
  {"role": "user", "content": prompt}
10
  ]
11
- chat_completion = self.client.chat.completions.create(
12
- messages=messages,
13
- model=self.model)
14
- response = chat_completion.choices[0].message.content
15
- safety, categories = response.split("\n")
16
- safety = safety.replace("Safety:", "").strip()
17
- categories = categories.replace("Categories:", "").strip()
18
- is_safe = True if "Safe" in safety else False
19
- return is_safe, categories
 
 
 
 
 
1
 
2
+
3
+
4
  class Guardian:
5
  def __init__(self, client):
6
  self.model = "Qwen/Qwen3Guard-Gen-4B"
 
10
  messages = [
11
  {"role": "user", "content": prompt}
12
  ]
13
+ try:
14
+ chat_completion = self.client.chat.completions.create(
15
+ messages=messages,
16
+ model=self.model)
17
+ response = chat_completion.choices[0].message.content
18
+ safety, categories = response.split("\n")
19
+ safety = safety.replace("Safety:", "").strip()
20
+ categories = categories.replace("Categories:", "").strip()
21
+ is_safe = True if "Safe" in safety else False
22
+ return is_safe, categories
23
+ except Exception as e:
24
+ print(f"Error in Guardian moderate_message: {e}")
25
+ return False, "Guard down!"
host.py CHANGED
@@ -19,7 +19,6 @@ Keep your responses short, like two or three sentences, and be witty."""
19
  self.show_state["time_since_last_guest_message"] = 0
20
  shouldWrapUp = False
21
  if len(self.messages) == 1:
22
- print(self.messages)
23
  msg_prefix = f"<Production Instructions>\nA guest entered the show, now they will talk to you\n</Production Instructions>\n"
24
  user_message = msg_prefix + user_message + "\n"
25
 
 
19
  self.show_state["time_since_last_guest_message"] = 0
20
  shouldWrapUp = False
21
  if len(self.messages) == 1:
 
22
  msg_prefix = f"<Production Instructions>\nA guest entered the show, now they will talk to you\n</Production Instructions>\n"
23
  user_message = msg_prefix + user_message + "\n"
24
 
image_api.py CHANGED
@@ -6,6 +6,7 @@ load_dotenv()
6
  base_url = "https://api.pexels.com/v1"
7
  api_key = os.getenv("API_KEY_PEXELS")
8
 
 
9
  def get_random_image(topic: str) -> tuple[str, str] | tuple[None, None]:
10
  # Returns the url of a random image on a topic
11
  params = {"query": topic, "per_page": 1}
@@ -18,8 +19,6 @@ def get_random_image(topic: str) -> tuple[str, str] | tuple[None, None]:
18
  photo = response_json["photos"][0]
19
  landscape_url = photo["src"]["landscape"]
20
  alt = photo["alt"]
21
- print("Found landscape URL:")
22
- print(landscape_url)
23
  except:
24
  landscape_url, alt = None, None
25
  return landscape_url, alt
 
6
  base_url = "https://api.pexels.com/v1"
7
  api_key = os.getenv("API_KEY_PEXELS")
8
 
9
+
10
  def get_random_image(topic: str) -> tuple[str, str] | tuple[None, None]:
11
  # Returns the url of a random image on a topic
12
  params = {"query": topic, "per_page": 1}
 
19
  photo = response_json["photos"][0]
20
  landscape_url = photo["src"]["landscape"]
21
  alt = photo["alt"]
 
 
22
  except:
23
  landscape_url, alt = None, None
24
  return landscape_url, alt
prompts.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TV Crew
2
+
3
+ system_prompt_tv_crew_guest = """You are a crew member of a Live Talk show who has the responsibility to update Television Images based on the conversation going on between host and the guest.
4
+ You will be provided the history of conversation, and you have to output in json, a specific keyword like "green forest" or "haunted mansion" etc. which can be used to fetch related image and show them on the Television.
5
+ Format: {"image_keyword": "Desired keyword"}
6
+ If you think guest is talking about previous image or an image isn't necessary for the conversation then just output none
7
+ {"image_keyword": "none"}
8
+ """
9
+
10
+ system_prompt_tv_crew_chat = """You are a crew member of a Live Talk show who has the responsibility to update Television Images based on the conversation going on between host and live chat.
11
+ You will be provided the history of conversation, and you have to output in json, a specific keyword like "green forest" or "haunted mansion" etc. which can be used to fetch related image and show them on the Television.
12
+ Format: {"image_keyword": "Desired keyword"}
13
+ If you think people are talking about previous image or an image isn't necessary for the conversation then just output none
14
+ {"image_keyword": "none"}
15
+ """
requirements.txt CHANGED
@@ -1 +1,2 @@
1
- openai
 
 
1
+ openai
2
+ google-api-python-client
timing.py CHANGED
@@ -13,9 +13,6 @@ class TimeManager:
13
  def __init__(self, host: Host, show_state: dict):
14
  self.show_state = show_state
15
  self.single_response_time_limit = 90 # seconds
16
- self.unreal_orchestrator_url = (
17
- unreal_orchestrator_url # Replace with actual URL
18
- )
19
  self.headers = {"Content-Type": "application/json", "x-api-key": api_key_unreal}
20
  self.host = host
21
 
 
13
  def __init__(self, host: Host, show_state: dict):
14
  self.show_state = show_state
15
  self.single_response_time_limit = 90 # seconds
 
 
 
16
  self.headers = {"Content-Type": "application/json", "x-api-key": api_key_unreal}
17
  self.host = host
18
 
tv_crew.py CHANGED
@@ -1,6 +1,8 @@
1
  from pydantic import BaseModel
2
  import json
3
  from image_api import get_random_image
 
 
4
 
5
 
6
  class TVCrewOutput(BaseModel):
@@ -8,14 +10,9 @@ class TVCrewOutput(BaseModel):
8
 
9
 
10
  class TVCrew:
11
- def __init__(self, client):
12
  self.model = "google/gemma-3-12b-it"
13
- self.system_prompt = """You are a crew member of a Live Talk show who has the responsibility to update Television Images based on the conversation going on between host and the guest.
14
- You will be provided the history of conversation, and you have to output in json, a specific keyword like "green forest" or "haunted mansion" etc. which can be used to fetch related image and show them on the Television.
15
- Format: {"image_keyword": "Desired keyword"}
16
- If you think guest is talking about previous image or an image isn't necessary for the conversation then just output none
17
- {"image_keyword": "none"}
18
- """
19
  self.context = ""
20
  self.client = client
21
 
@@ -23,14 +20,10 @@ class TVCrew:
23
  """
24
  Finds a image related to conversation and returns the url and caption
25
  """
26
- # print("*************************** TV Crew ***************************")
27
- # print(self.context)
28
- # print("***************************************************************")
29
  messages_tv_crew = [
30
  {"role": "system", "content": self.system_prompt},
31
  {"role": "user", "content": self.context},
32
  ]
33
- print("Crew is on it...")
34
  response_tv_crew = self.client.chat.completions.parse(
35
  model=self.model,
36
  messages=messages_tv_crew,
@@ -46,8 +39,9 @@ class TVCrew:
46
  if image_url:
47
  caption = alt if len(alt) > 0 else image_keyword
48
  self.add_context(f"TV Shows: {caption}\n")
49
-
50
- return image_url, caption
 
51
  return None, None
52
 
53
  def add_context(self, content):
@@ -55,4 +49,4 @@ class TVCrew:
55
  self.context += content
56
 
57
  def clear_context(self):
58
- self.context = ""
 
1
  from pydantic import BaseModel
2
  import json
3
  from image_api import get_random_image
4
+ import base64
5
+ import requests
6
 
7
 
8
  class TVCrewOutput(BaseModel):
 
10
 
11
 
12
  class TVCrew:
13
+ def __init__(self, client, system_prompt: str):
14
  self.model = "google/gemma-3-12b-it"
15
+ self.system_prompt = system_prompt
 
 
 
 
 
16
  self.context = ""
17
  self.client = client
18
 
 
20
  """
21
  Finds a image related to conversation and returns the url and caption
22
  """
 
 
 
23
  messages_tv_crew = [
24
  {"role": "system", "content": self.system_prompt},
25
  {"role": "user", "content": self.context},
26
  ]
 
27
  response_tv_crew = self.client.chat.completions.parse(
28
  model=self.model,
29
  messages=messages_tv_crew,
 
39
  if image_url:
40
  caption = alt if len(alt) > 0 else image_keyword
41
  self.add_context(f"TV Shows: {caption}\n")
42
+ img_bytes = requests.get(image_url).content
43
+ image_base64 = base64.b64encode(img_bytes).decode("utf-8")
44
+ return image_base64, caption
45
  return None, None
46
 
47
  def add_context(self, content):
 
49
  self.context += content
50
 
51
  def clear_context(self):
52
+ self.context = ""
yt_chat.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ # from dotenv import load_dotenv
3
+ from guard import Guardian
4
+ from tv_crew import TVCrew
5
+ import requests
6
+ import time
7
+ import googleapiclient.discovery
8
+
9
+ # load_dotenv()
10
+ unreal_orchestrator_url = os.getenv("ORCHESTRATOR_URL")
11
+ api_key_unreal = os.getenv("API_KEY_UNREAL")
12
+
13
+ API_KEY = os.getenv("YOUTUBE_API_KEY")
14
+ VIDEO_ID="xBVJIJQg1FY"
15
+ POLL_INTERVAL = 10 # seconds
16
+
17
+
18
+ class StreamChatHost:
19
+ def __init__(self, client, tv_crew: TVCrew, guardian: Guardian):
20
+ self.client = client
21
+ self.guardian = guardian
22
+ self.tv_crew = tv_crew
23
+ self.model = "deepseek/deepseek-v3.2-exp"
24
+ system_prompt = """You are the Host of a Talk Show, where users come and have a chat.
25
+ There is a TV Display near you which the crew members use to display images based on your conversation.
26
+ The TV is not in control of the guest. It is controlled by crew members and is only for graphic uplifting of the conversation.
27
+ You will be told when TV shows something, otherwise, don't talk about it.
28
+ Keep your responses short, like two or three sentences, and be witty."""
29
+ self.system_message = {"role": "system", "content": system_prompt}
30
+
31
+ self.messages = [self.system_message]
32
+ self.headers = {"Content-Type": "application/json", "x-api-key": api_key_unreal}
33
+ self.youtube = googleapiclient.discovery.build(
34
+ "youtube", "v3", developerKey=API_KEY
35
+ )
36
+ self.chat_id = self.get_live_chat_id(self.youtube, VIDEO_ID)
37
+ self.next_page_token = None
38
+
39
+ def chat_interaction_loop(self):
40
+ """Checks if the Host is in lobby, then pulls chat messages from the Live Stream. The Host then replies to the message."""
41
+ while True:
42
+ time.sleep(POLL_INTERVAL)
43
+ request = requests.get(
44
+ f"{unreal_orchestrator_url}/status", headers=self.headers
45
+ )
46
+ in_lobby = request.json().get("in_lobby", False)
47
+ if not in_lobby:
48
+ continue
49
+ if len(self.messages) > 20:
50
+ self.messages = [self.system_message]
51
+ self.tv_crew.clear_context()
52
+ try:
53
+ request = self.youtube.liveChatMessages().list(
54
+ liveChatId=self.chat_id,
55
+ part="snippet,authorDetails",
56
+ maxResults=20,
57
+ pageToken=self.next_page_token,
58
+ )
59
+ response = request.execute()
60
+ user_messages = []
61
+ for item in response.get("items", []):
62
+ author_name = item["authorDetails"]["displayName"]
63
+ message_text = item["snippet"]["displayMessage"]
64
+ user_messages.append(f"{author_name}: {message_text}")
65
+
66
+ self.next_page_token = response.get("nextPageToken")
67
+ if len(user_messages) == 0:
68
+ continue
69
+ except Exception as e:
70
+ continue
71
+ # **************************************************************
72
+
73
+ user_message = "[YouTube Chat]\n"
74
+ for msg in user_messages:
75
+ if len(msg) <= 500:
76
+ user_message += f"{msg}\n"
77
+ is_safe, _ = self.guardian.moderate_message(user_message)
78
+
79
+ if not is_safe:
80
+ continue
81
+
82
+ self.tv_crew.add_context(user_message)
83
+ image_base64, caption = self.tv_crew.suggest_image()
84
+ if image_base64:
85
+ is_safe, _ = self.guardian.moderate_message(caption)
86
+ if not is_safe:
87
+ image_base64 = None
88
+ caption = None
89
+ else:
90
+ user_message += f"[Crew Updated Television to: {caption}]\n"
91
+
92
+ self.messages.append({"role": "user", "content": user_message})
93
+ try:
94
+ response = self.client.responses.create(
95
+ model=self.model, input=self.messages
96
+ )
97
+ host_response = response.output_text
98
+ self.messages.append({"role": "assistant", "content": host_response})
99
+ except Exception as e:
100
+ print(f"Error generating host response: {e}")
101
+ continue
102
+
103
+ self.tv_crew.add_context(f"Host: {host_response}\n")
104
+ # Just to make sure, that we're in the lobby
105
+ request = requests.get(
106
+ f"{unreal_orchestrator_url}/status", headers=self.headers
107
+ )
108
+ in_lobby = request.json().get("in_lobby", False)
109
+ if not in_lobby:
110
+ continue
111
+
112
+ if image_base64:
113
+ payload_tv = {"base64": image_base64}
114
+ response = requests.post(
115
+ url=f"{unreal_orchestrator_url}/television",
116
+ json=payload_tv,
117
+ headers=self.headers,
118
+ )
119
+
120
+ payload = {"text": host_response, "is_host": True}
121
+ response = requests.post(
122
+ url=f"{unreal_orchestrator_url}/tts",
123
+ json=payload,
124
+ headers=self.headers,
125
+ )
126
+
127
+ def get_live_chat_id(self, youtube, video_id):
128
+ try:
129
+ request = youtube.videos().list(part="liveStreamingDetails", id=video_id)
130
+ response = request.execute()
131
+ if not response["items"]:
132
+ print("No video found with the given ID.")
133
+ return None
134
+
135
+ details = response["items"][0]["liveStreamingDetails"]
136
+ if not details:
137
+ print("No live streaming details found for this video.")
138
+ return None
139
+ return details.get("activeLiveChatId", None)
140
+ except Exception as e:
141
+ print(f"An error occurred: {e}")
142
+ return None