From c941aa32a2e07ef04f38142835a0cd00e2c4daca Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 25 Apr 2022 12:33:20 +0200 Subject: [PATCH 1/8] stream.py: Hide interactive UI elements This commit hides more interactive UI elements which are unnecessary for a video stream, e.g., buttons. All elements from the top navbar were removed except the meeting's title. Furthermore, some CSS was added to hide both "fullscreen" and "hide" buttons from presentations, webcam videos, and screen shares. Other interactive elements like notification toasts and the poll window - not the results - were also marked as hidden. By adding those rules through CSS, they will also apply to elements spawned after the recording was started. --- stream.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/stream.py b/stream.py index 61fb309..0ce6f7e 100644 --- a/stream.py +++ b/stream.py @@ -191,12 +191,32 @@ def bbb_browser(): except ElementClickInterceptedException: logging.info("could not find users and messages toggle") - try: - browser.execute_script("document.querySelector('[aria-label=\"Users and messages toggle\"]').style.display='none';") - except JavascriptException: - browser.execute_script("document.querySelector('[aria-label=\"Users and messages toggle with new message notification\"]').style.display='none';") - browser.execute_script("document.querySelector('[aria-label=\"Options\"]').style.display='none';") + # Remove everything from the top bar, except the meeting's title. + browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"left\"]').style.display='none';") + browser.execute_script("document.querySelectorAll('div[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"center\"] > :not(h1)').forEach((ele) => ele.style.display='none');") + browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"right\"]').style.display='none';") + browser.execute_script("document.querySelector('[aria-label=\"Actions bar\"]').style.display='none';") + + browser.execute_script(""" + const hideDecoratorsStyle = document.createElement("style"); + hideDecoratorsStyle.innerText = ` + /* Presentation hide minus button */ + button[aria-label="Hide presentation"], + /* Fullscreen button, both for presentations and webcams */ + button[aria-label^="Make "][aria-label$=" fullscreen"], + /* Drop down menu next to user names for webcam videos */ + div[class^="videoCanvas"] span[class^="dropdownTrigger"]::after, + /* Interactive poll window */ + div[class^="pollingContainer"], + /* Notification toasts */ + div[class="Toastify"] { + display: none; + } + `; + document.head.appendChild(hideDecoratorsStyle); + """) + try: browser.execute_script("document.getElementById('container').setAttribute('style','margin-bottom:30px');") except JavascriptException: From b0adf8bc17c280f0e5e4b31478209c11d6cb5ccc Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 25 Apr 2022 13:09:31 +0200 Subject: [PATCH 2/8] stream.py: BBB_HIDE_MEETING_TITLE and BBB_HIDE_WHO_TALKS Flags to hide both the meeting's title as well as who is currently talking. Those are separate flags, as one might only want one of those, depending on the specific kind of meeting. --- README.md | 2 ++ stream.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 9c9a5c4..bd90781 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ You need to set some environment variables to run the container. * BBB_SHOW_CHAT - shows the chat on the left side of the window (Default: false) * BBB_RESOLUTION - the streamed/downloaded resolution (Default: 1920x1080) * BBB_CHAT_MESSAGE - prefix for the message that would be posted to BBB chat, while joining a conference (Default: "This meeting is streamed to") +* BBB_HIDE_MEETING_TITLE - hide the meeting title in the top bar (Default: false) +* BBB_HIDE_WHO_TALKS - hide the annotation who is currently talking (Default: false) * TZ - Timezone (Default: Europe/Vienna) #### Chat settings diff --git a/stream.py b/stream.py index 0ce6f7e..d700ad5 100644 --- a/stream.py +++ b/stream.py @@ -16,6 +16,7 @@ from selenium.webdriver.common.by import By from datetime import datetime +from distutils.util import strtobool import time browser = None @@ -72,6 +73,18 @@ '--browser-disable-dev-shm-usage', action='store_true', default=False, help='do not use /dev/shm', ) +parser.add_argument( + '--bbb-hide-meeting-title', + type=bool, + help='hide the meetings title in the top bar (can be set using env)', + default=bool(strtobool(os.environ.get('BBB_HIDE_MEETING_TITLE', '0'))) +) +parser.add_argument( + '--bbb-hide-who-talks', + type=bool, + help='hide the annotation who is currently talking (can be set using env)', + default=bool(strtobool(os.environ.get('BBB_HIDE_WHO_TALKS', '0'))) +) args = parser.parse_args() # some ugly hacks for additional options @@ -196,6 +209,11 @@ def bbb_browser(): browser.execute_script("document.querySelectorAll('div[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"center\"] > :not(h1)').forEach((ele) => ele.style.display='none');") browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"right\"]').style.display='none';") + if args.bbb_hide_meeting_title: + browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"top\"]').style.display='none';") + if args.bbb_hide_who_talks: + browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"bottom\"]').style.display='none';") + browser.execute_script("document.querySelector('[aria-label=\"Actions bar\"]').style.display='none';") browser.execute_script(""" From 8a17587f4eb9ab5159e7fbbdd0e372369edb4e8c Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 25 Apr 2022 13:27:29 +0200 Subject: [PATCH 3/8] stream.py: BBB_BACKGROUND_COLOR Override the default background color within the stream. --- README.md | 1 + stream.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index bd90781..41e0054 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ You need to set some environment variables to run the container. * BBB_CHAT_MESSAGE - prefix for the message that would be posted to BBB chat, while joining a conference (Default: "This meeting is streamed to") * BBB_HIDE_MEETING_TITLE - hide the meeting title in the top bar (Default: false) * BBB_HIDE_WHO_TALKS - hide the annotation who is currently talking (Default: false) +* BBB_BACKGROUND_COLOR - override background color by a CSS color, e.g., "black" or "#ffffff" * TZ - Timezone (Default: Europe/Vienna) #### Chat settings diff --git a/stream.py b/stream.py index d700ad5..cbcc1be 100644 --- a/stream.py +++ b/stream.py @@ -85,6 +85,11 @@ help='hide the annotation who is currently talking (can be set using env)', default=bool(strtobool(os.environ.get('BBB_HIDE_WHO_TALKS', '0'))) ) +parser.add_argument( + '--bbb-background-color', + help='override background color by a CSS color, e.g., "black" or "#ffffff" (can be set using env)', + default=os.environ.get('BBB_BACKGROUND_COLOR', '') +) args = parser.parse_args() # some ugly hacks for additional options @@ -240,6 +245,9 @@ def bbb_browser(): except JavascriptException: browser.execute_script("document.getElementById('app').setAttribute('style','margin-bottom:30px');") + if args.bbb_background_color: + browser.execute_script("document.querySelector('body').setAttribute('style','background-color: %s;');" % args.bbb_background_color) + def create_meeting(): create_params = {} if args.moderatorPassword: From f51c0263a5ca0f781357d676b090de7602f6b325 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 30 Apr 2022 11:56:00 +0200 Subject: [PATCH 4/8] stream.py: fix selenium deprecation warnings - webdriver.Chrome: a Service should be used over the executable_path[0] - find_element_*: a By selector in find_element should be used[1] [0] https://github.com/SeleniumHQ/selenium/blob/selenium-4.1.3-python/py/CHANGES#L159 [1] https://github.com/SeleniumHQ/selenium/blob/selenium-4.1.3-python/py/CHANGES#L90 --- stream.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/stream.py b/stream.py index cbcc1be..e82c03a 100644 --- a/stream.py +++ b/stream.py @@ -6,11 +6,13 @@ from bigbluebutton_api_python import BigBlueButton, exception from bigbluebutton_api_python import util as bbbUtil from selenium import webdriver +from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import ElementClickInterceptedException from selenium.common.exceptions import JavascriptException from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.chrome.options import Options +from selenium.webdriver.chrome.service import Service from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By @@ -137,7 +139,7 @@ def set_up(): logging.info('Starting browser!!') - browser = webdriver.Chrome(executable_path='./chromedriver',options=options) + browser = webdriver.Chrome(service=Service('./chromedriver'), options=options) def bbb_browser(): global browser @@ -160,8 +162,8 @@ def bbb_browser(): element = EC.presence_of_element_located((By.ID, 'message-input')) WebDriverWait(browser, selenium_timeout).until(element) - element = browser.find_element_by_id('message-input') - chat_send = browser.find_elements_by_css_selector('[aria-label="Send message"]')[0] + element = browser.find_element(By.ID, 'message-input') + chat_send = browser.find_elements(By.CSS_SELECTOR, '[aria-label="Send message"]')[0] # ensure chat is enabled (might be locked by moderator) if element.is_enabled() and chat_send.is_enabled(): tmp_chatMsg = os.environ.get('BBB_CHAT_MESSAGE', "This meeting is streamed to") @@ -188,7 +190,7 @@ def bbb_browser(): except JavaScriptException: browser.execute_script("document.querySelector('[aria-label=\"Users list\"]').parentElement.style.display='none';") else: - element = browser.find_elements_by_id('chat-toggle-button')[0] + element = browser.find_elements(By.ID, 'chat-toggle-button')[0] if element.is_enabled(): element.click() except NoSuchElementException: @@ -201,7 +203,7 @@ def bbb_browser(): time.sleep(10) if not args.chat: try: - element = browser.find_elements_by_css_selector('button[aria-label^="Users and messages toggle"]')[0] + element = browser.find_elements(By.CSS_SELECTOR, 'button[aria-label^="Users and messages toggle"]')[0] if element.is_enabled(): element.click() except NoSuchElementException: From 1a7dbb70d379716d797a39e564d03f2510be3198 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 30 Apr 2022 14:36:30 +0200 Subject: [PATCH 5/8] stream.py: include logo in video Introduces new flags to include a logo by an URL into the video. The logo will be placed in a corner, by default the top right one, as this does not collide with the list of current speakers. However, one can override this, e.g., when the bar of current speakers was disabled. The logo's style attribute places it in the background and limits its height to a tenth of the visible screen size. This prevents distorting other elements, e.g., when lots of webcams are in use. --- README.md | 2 ++ stream.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/README.md b/README.md index 41e0054..44f0a98 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ You need to set some environment variables to run the container. * BBB_HIDE_MEETING_TITLE - hide the meeting title in the top bar (Default: false) * BBB_HIDE_WHO_TALKS - hide the annotation who is currently talking (Default: false) * BBB_BACKGROUND_COLOR - override background color by a CSS color, e.g., "black" or "#ffffff" +* BBB_LOGO_URL - add a logo to the video, passed as an image URL (Default: none/disabled) +* BBB_LOGO_POS - corner where to place the logo: "top/left", "top/right", "bottom/left" or "bottom/right" (Default "top/right") * TZ - Timezone (Default: Europe/Vienna) #### Chat settings diff --git a/stream.py b/stream.py index e82c03a..3e59698 100644 --- a/stream.py +++ b/stream.py @@ -92,6 +92,16 @@ help='override background color by a CSS color, e.g., "black" or "#ffffff" (can be set using env)', default=os.environ.get('BBB_BACKGROUND_COLOR', '') ) +parser.add_argument( + '--logo', + help='add a logo to the video, passed as an image URL (can be set using env)', + default=os.environ.get('BBB_LOGO_URL', '') +) +parser.add_argument( + '--logo-position', + help='corner where to place the logo: "top/left", "top/right" (default), "bottom/left" or "bottom/right" (can be set using env)', + default=os.environ.get('BBB_LOGO_POS', 'top/right') +) args = parser.parse_args() # some ugly hacks for additional options @@ -242,6 +252,19 @@ def bbb_browser(): document.head.appendChild(hideDecoratorsStyle); """) + if args.logo != '': + [logo_pos_vertical, logo_pos_horizontal] = args.logo_position.split('/') + browser.execute_script(""" + const navbarHeader = document.querySelector('[class^="navbar"]'); + + const logoImg = document.createElement("img"); + logoImg.style = "position: absolute; %s: 5px; %s: 5px;"; + logoImg.src = "%s"; + logoImg.height = 0.1 * window.innerHeight; + + navbarHeader.appendChild(logoImg); + """ % (logo_pos_vertical, logo_pos_horizontal, args.logo)) + try: browser.execute_script("document.getElementById('container').setAttribute('style','margin-bottom:30px');") except JavascriptException: From da029bb8d2cb360a54808a4d79afe06461ad14f5 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 30 Apr 2022 14:48:05 +0200 Subject: [PATCH 6/8] stream.py: shrink navbar when empty Followup to #153. When both the meeting tile as well as the list of current speakers are hidden, the top navbar becomes empty. However, it still blocks another 112px for an empty box. As all elements still have a margin, removing this forced height will not place videos directly under the top. --- stream.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stream.py b/stream.py index 3e59698..f196c50 100644 --- a/stream.py +++ b/stream.py @@ -230,6 +230,8 @@ def bbb_browser(): browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"top\"]').style.display='none';") if args.bbb_hide_who_talks: browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"bottom\"]').style.display='none';") + if args.bbb_hide_meeting_title and args.bbb_hide_who_talks: + browser.execute_script("document.querySelector('[class^=\"navbar\"]').style.height='0px';") browser.execute_script("document.querySelector('[aria-label=\"Actions bar\"]').style.display='none';") From 9ed494408478dc187e6f30049610ad42c68973b2 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Sat, 30 Apr 2022 15:13:04 +0200 Subject: [PATCH 7/8] stream.py: attempt to fix presence of chat message In four out of five times, the chat message could not be posted on my setup. This "fix" moves the magic sleep before trying to post the message, as this increases the chance of the overlay to have already disappeared. For further issues, the logging was increased. --- stream.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/stream.py b/stream.py index f196c50..44c5291 100644 --- a/stream.py +++ b/stream.py @@ -166,6 +166,7 @@ def bbb_browser(): browser.get(join_url) + time.sleep(10) try: # Wait for the input element to appear logging.info("Waiting for chat input window to appear.") @@ -193,6 +194,8 @@ def bbb_browser(): element.send_keys(tmp_chatMsg) chat_send.click() + else: + logging.info("chat is not enabled") if args.chat: try: @@ -205,12 +208,12 @@ def bbb_browser(): element.click() except NoSuchElementException: # ignore (chat might be disabled) - logging.info("could not find chat input or chat toggle") - except ElementClickInterceptedException: + logging.warn("could not find chat input or chat toggle") + except ElementClickInterceptedException as e: # ignore (chat might be disabled) - logging.info("could not find chat input or chat toggle") + logging.warn("could not find chat input or chat toggle") + logging.warn(e, exc_info=True) - time.sleep(10) if not args.chat: try: element = browser.find_elements(By.CSS_SELECTOR, 'button[aria-label^="Users and messages toggle"]')[0] From d4feb464e35a1414a71783edcf2a81550f03ed12 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 10 May 2022 23:21:54 +0200 Subject: [PATCH 8/8] stream.py: fix querySelector for BBB 2.3 and 2.4 --- stream.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stream.py b/stream.py index 44c5291..aec9009 100644 --- a/stream.py +++ b/stream.py @@ -225,14 +225,14 @@ def bbb_browser(): logging.info("could not find users and messages toggle") # Remove everything from the top bar, except the meeting's title. - browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"left\"]').style.display='none';") - browser.execute_script("document.querySelectorAll('div[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"center\"] > :not(h1)').forEach((ele) => ele.style.display='none');") - browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"right\"]').style.display='none';") + browser.execute_script("document.querySelector('[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"left\"]').style.display='none';") + browser.execute_script("document.querySelectorAll('[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"center\"] > :not(h1)').forEach((ele) => ele.style.display='none');") + browser.execute_script("document.querySelector('[class^=\"navbar\"] > div[class^=\"top\"] > div[class^=\"right\"]').style.display='none';") if args.bbb_hide_meeting_title: - browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"top\"]').style.display='none';") + browser.execute_script("document.querySelector('[class^=\"navbar\"] > div[class^=\"top\"]').style.display='none';") if args.bbb_hide_who_talks: - browser.execute_script("document.querySelector('div[class^=\"navbar\"] > div[class^=\"bottom\"]').style.display='none';") + browser.execute_script("document.querySelector('[class^=\"navbar\"] > div[class^=\"bottom\"]').style.display='none';") if args.bbb_hide_meeting_title and args.bbb_hide_who_talks: browser.execute_script("document.querySelector('[class^=\"navbar\"]').style.height='0px';") @@ -246,7 +246,7 @@ def bbb_browser(): /* Fullscreen button, both for presentations and webcams */ button[aria-label^="Make "][aria-label$=" fullscreen"], /* Drop down menu next to user names for webcam videos */ - div[class^="videoCanvas"] span[class^="dropdownTrigger"]::after, + div[class^="videoCanvas"] [class^="dropdownTrigger"]::after, /* Interactive poll window */ div[class^="pollingContainer"], /* Notification toasts */