From 9adaa9d6187bd4769b7a3587f0694d9eb722e373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bonavent?= <56730254+LoicBonavent@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:51:07 +0200 Subject: [PATCH] [DONE] Remove BBB module and add sipmediagw feature (#1190) * Removal of the old BBB module (replaced by the meetings module) * Delete MEETING_WEBINAR_SIPMEDIAGW_URL and MEETING_WEBINAR_SIPMEDIAGW_TOKEN parameters management (replaced by live gateway feature) * Add sipmediagw_server_url and sipmediagw_server_password to live gateway class (meeting/models.py) * Manage request URLs from SIPMediaGW server (hashkey with a / before the last 10 characters) --- .coveragerc | 1 - .gitignore | 1 - pod/bbb/__init__.py | 0 pod/bbb/admin.py | 85 -- pod/bbb/apps.py | 6 - pod/bbb/bbb.py | 86 -- pod/bbb/forms.py | 91 -- pod/bbb/management/__init__.py | 0 pod/bbb/management/commands/__init__.py | 0 pod/bbb/management/commands/bbb.py | 734 --------- pod/bbb/migrations/__init__.py | 0 pod/bbb/models.py | 287 ---- pod/bbb/plugins/__init__.py | 0 pod/bbb/plugins/type_bbb.py | 23 - pod/bbb/rest_views.py | 66 - pod/bbb/static/css/bbb.css | 22 - pod/bbb/templates/bbb/card.html | 40 - pod/bbb/templates/bbb/list_meeting.html | 93 -- pod/bbb/templates/bbb/live_card.html | 53 - pod/bbb/templates/bbb/live_list_meeting.html | 65 - .../templates/bbb/live_publish_meeting.html | 98 -- pod/bbb/templates/bbb/live_record_list.html | 20 - pod/bbb/templates/bbb/publish_meeting.html | 89 -- pod/bbb/templates/bbb/record_list.html | 20 - pod/bbb/tests/__init__.py | 0 pod/bbb/tests/test_models.py | 207 --- pod/bbb/tests/test_views.py | 137 -- pod/bbb/urls.py | 25 - pod/bbb/views.py | 306 ---- pod/import_video/static/css/import_video.css | 57 +- pod/live/templates/live/event-script.html | 2 +- pod/locale/fr/LC_MESSAGES/django.po | 731 ++------- pod/locale/fr/LC_MESSAGES/djangojs.po | 2 +- pod/locale/nl/LC_MESSAGES/django.po | 650 ++------ pod/locale/nl/LC_MESSAGES/djangojs.po | 2 +- pod/main/configuration.json | 38 +- pod/main/context_processors.py | 6 - pod/main/rest_router.py | 8 - pod/main/tasks.py | 9 - pod/main/templates/navbar.html | 6 - pod/main/test_settings.py | 3 - pod/main/utils.py | 6 +- pod/meeting/admin.py | 6 +- pod/meeting/models.py | 28 +- pod/meeting/static/css/meeting.css | 51 +- pod/meeting/urls.py | 1 + pod/meeting/views.py | 13 +- pod/meeting/webinar.py | 133 +- pod/settings.py | 1 - pod/urls.py | 8 - scripts/bbb-pod-live/bbb-pod-live.php | 1327 ----------------- .../bbb-pod-live/docker-compose.default.yml | 58 - 52 files changed, 447 insertions(+), 5254 deletions(-) delete mode 100644 pod/bbb/__init__.py delete mode 100644 pod/bbb/admin.py delete mode 100644 pod/bbb/apps.py delete mode 100644 pod/bbb/bbb.py delete mode 100644 pod/bbb/forms.py delete mode 100644 pod/bbb/management/__init__.py delete mode 100644 pod/bbb/management/commands/__init__.py delete mode 100644 pod/bbb/management/commands/bbb.py delete mode 100644 pod/bbb/migrations/__init__.py delete mode 100644 pod/bbb/models.py delete mode 100644 pod/bbb/plugins/__init__.py delete mode 100644 pod/bbb/plugins/type_bbb.py delete mode 100644 pod/bbb/rest_views.py delete mode 100644 pod/bbb/static/css/bbb.css delete mode 100644 pod/bbb/templates/bbb/card.html delete mode 100644 pod/bbb/templates/bbb/list_meeting.html delete mode 100644 pod/bbb/templates/bbb/live_card.html delete mode 100644 pod/bbb/templates/bbb/live_list_meeting.html delete mode 100644 pod/bbb/templates/bbb/live_publish_meeting.html delete mode 100644 pod/bbb/templates/bbb/live_record_list.html delete mode 100644 pod/bbb/templates/bbb/publish_meeting.html delete mode 100644 pod/bbb/templates/bbb/record_list.html delete mode 100644 pod/bbb/tests/__init__.py delete mode 100644 pod/bbb/tests/test_models.py delete mode 100644 pod/bbb/tests/test_views.py delete mode 100644 pod/bbb/urls.py delete mode 100644 pod/bbb/views.py delete mode 100644 scripts/bbb-pod-live/bbb-pod-live.php delete mode 100644 scripts/bbb-pod-live/docker-compose.default.yml diff --git a/.coveragerc b/.coveragerc index f3ec4743f8..2693a47e11 100755 --- a/.coveragerc +++ b/.coveragerc @@ -24,5 +24,4 @@ omit = pod/*settings*.py */migrations/* pod/recorder/plugins/type_*.py pod/*/forms.py - scripts/bbb-pod-live/*.* pod/live/pilotingInterface.py diff --git a/.gitignore b/.gitignore index bb869c0a8f..9f02003adc 100644 --- a/.gitignore +++ b/.gitignore @@ -49,7 +49,6 @@ pod/custom/* pod/main/static/custom/img !pod/custom/settings_local.py.example settings_local.py -scripts/bbb-pod-live/docker-compose.yml transcription/* # Unit test utilities # diff --git a/pod/bbb/__init__.py b/pod/bbb/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pod/bbb/admin.py b/pod/bbb/admin.py deleted file mode 100644 index 8ae5a0a74d..0000000000 --- a/pod/bbb/admin.py +++ /dev/null @@ -1,85 +0,0 @@ -from django.contrib import admin -from .models import BBB_Meeting, Attendee, Livestream -from .forms import MeetingForm -from django.utils.translation import ugettext_lazy as _ - - -class MeetingAdminForm(MeetingForm): - is_staff = True - is_superuser = False - is_admin = True - - -class MeetingSuperAdminForm(MeetingAdminForm): - is_superuser = True - encoding_step = 1 - - -class MeetingAdmin(admin.ModelAdmin): - list_display = ( - "id", - "session_date", - "meeting_name", - "encoding_step", - "recorded", - "recording_available", - "encoded_by", - ) - list_display_links = ("id", "meeting_name") - ordering = ("-id", "-session_date") - readonly_fields = [] - search_fields = [ - "id", - "meeting_name", - "encoded_by__username", - "encoded_by__first_name", - "encoded_by__last_name", - ] - - actions = ["encode_meeting"] - - # Re-encode a BBB presentation Web - def encode_meeting(self, request, queryset): - for item in queryset: - item.launch_encode = True - item.save() - - encode_meeting.short_description = _("Encode selected") - - -class AttendeeAdmin(admin.ModelAdmin): - list_display = ("id", "full_name", "role", "username", "meeting", "user") - list_display_links = ("id", "full_name") - ordering = ("full_name",) - readonly_fields = [] - search_fields = ["id", "full_name", "meeting__meeting_name"] - - -class LivestreamAdmin(admin.ModelAdmin): - list_display = ( - "id", - "meeting", - "start_date", - "end_date", - "show_chat", - "download_meeting", - "enable_chat", - "server", - "status", - "user", - ) - list_display_links = ("id", "meeting") - ordering = ("-id", "-start_date") - readonly_fields = [] - search_fields = [ - "id", - "meeting__meeting_name", - "user__username", - "user__first_name", - "user__last_name", - ] - - -admin.site.register(BBB_Meeting, MeetingAdmin) -admin.site.register(Attendee, AttendeeAdmin) -admin.site.register(Livestream, LivestreamAdmin) diff --git a/pod/bbb/apps.py b/pod/bbb/apps.py deleted file mode 100644 index 54a5d1d33c..0000000000 --- a/pod/bbb/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class BbbConfig(AppConfig): - name = "pod.bbb" - default_auto_field = "django.db.models.BigAutoField" diff --git a/pod/bbb/bbb.py b/pod/bbb/bbb.py deleted file mode 100644 index b218b2b81a..0000000000 --- a/pod/bbb/bbb.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -from django.conf import settings -from pod.main.tasks import task_start_bbb_encode -from pod.bbb.models import BBB_Meeting as Meeting - -import subprocess -import time - -import threading -import logging - -# Use of BigBlueButton -USE_BBB = getattr(settings, "USE_BBB", False) -# Directory of bbb-recorder plugin -DEFAULT_BBB_PLUGIN = getattr(settings, "DEFAULT_BBB_PLUGIN", "/data/bbb-recorder/") -# Directory that will contain the video files generated by bbb-recorder -DEFAULT_BBB_PATH = getattr(settings, "DEFAULT_BBB_PATH", "/data/bbb-recorder/media/") -# The last caracter of DEFAULT_BBB_PATH must be an OS separator -if not DEFAULT_BBB_PATH.endswith(os.path.sep): - DEFAULT_BBB_PATH += os.path.sep - -# BigBlueButton or Scalelite server URL, where BBB Web presentation and API are -BBB_SERVER_URL = getattr(settings, "BBB_SERVER_URL", "https://bbb.univ-test.fr/") -# The last caracter of BBB_SERVER_URL must be / -if not BBB_SERVER_URL.endswith("/"): - BBB_SERVER_URL += "/" - -# Debug mode -DEBUG = getattr(settings, "DEBUG", False) -# Use of Celery to encode -CELERY_TO_ENCODE = getattr(settings, "CELERY_TO_ENCODE", False) - -log = logging.getLogger(__name__) - - -def start_bbb_encode(id) -> None: - if CELERY_TO_ENCODE: - task_start_bbb_encode.delay(id) - else: - log.info("START BBB ENCODE MEETING %s" % id) - t = threading.Thread(target=bbb_encode_meeting, args=[id]) - t.setDaemon(True) - t.start() - - -def bbb_encode_meeting(id) -> None: - msg = "" - - # Get the meeting - meeting_to_encode = Meeting.objects.get(id=id) - - # Update this meeting and put - # encoding_step to 2 (Encoding in progress) - meeting_to_encode.encoding_step = 2 - meeting_to_encode.save() - - # Encode in webm (not mp4, less data in log) - command = "" - # Put on the bbb-recorder plugin directory - command += "cd %s; " % (DEFAULT_BBB_PLUGIN) - # The command looks like: - # node export.js https://bbb.univ.fr/playback/presentation/2.0/ - # playback.html?meetingId=INTERNAL_MEETING_ID INTERNAL_MEETING_ID.webm - # > /data/www/USERPOD/bbb-recorder/logs/INTERNAL_MEETING_ID.log - # 2>&1 < /dev/null - # Recording_URL can be like https://bbb.univ.fr/playback/presentation/2.3/ - # INTERNAL_MEETING_ID/?meetingId=INTERNAL_MEETING_ID/ - # Check BBB_VERSION_IS_23 parameter. - command += "node export.js " + str(meeting_to_encode.recording_url) - command += " " + str(meeting_to_encode.internal_meeting_id) + ".webm" - command += " > " + DEFAULT_BBB_PATH + "logs/" - command += str(meeting_to_encode.internal_meeting_id) - command += ".log 2>&1 < /dev/null" - - # if you want to reuse the command: print(command) - msg = "\nBBBEncodeCommand:\n%s" % command - msg += "\n- Encoding: %s" % time.ctime() - # Execute the process - subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - msg += "\n- meeting_to_encode: %s" % meeting_to_encode - msg += "\n- End Encoding: %s" % time.ctime() - - # Update this meeting and put - # encoding_step to 3 (Encoded) - meeting_to_encode.encoding_step = 3 - meeting_to_encode.save() diff --git a/pod/bbb/forms.py b/pod/bbb/forms.py deleted file mode 100644 index 0a727a7521..0000000000 --- a/pod/bbb/forms.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Esup-Pod BBB forms.""" - -from django import forms -from django.conf import settings -from .models import BBB_Meeting -from .models import Livestream -from pod.main.forms_utils import add_placeholder_and_asterisk -from django.dispatch import receiver -from django.db.models.signals import post_save -import importlib -from django.core.exceptions import ValidationError -from django.utils.translation import ugettext_lazy as _ - -USE_BBB = getattr(settings, "USE_BBB", False) -USE_BBB_LIVE_DOWNLOADING = getattr(settings, "USE_BBB_LIVE_DOWNLOADING", False) - - -class MeetingForm(forms.ModelForm): - def __init__(self, request, *args, **kwargs) -> None: - super(MeetingForm, self).__init__(*args, **kwargs) - - # All fields are hidden. This form is like a Confirm prompt - self.fields = add_placeholder_and_asterisk(self.fields) - hidden_fields = ( - "meeting_id", - "internal_meeting_id", - "session_date", - "encoding_step", - "recorded", - "recording_available", - "recording_url", - "thumbnail_url", - "encoded_by", - "last_date_in_progress", - ) - for field in hidden_fields: - if self.fields.get(field, None): - self.fields[field].widget = forms.HiddenInput() - - class Meta: - model = BBB_Meeting - exclude = () - - -@receiver(post_save, sender=BBB_Meeting) -def launch_encode(sender, instance, created, **kwargs) -> None: - # Useful when an administrator send a re-encode task - if hasattr(instance, "launch_encode") and instance.launch_encode is True: - instance.launch_encode = False - # Re-encode is only possible when an user has already tried it - # Re-encode is not depending on the encoding_step - if instance.encoded_by is not None: - mod = importlib.import_module("%s.plugins.type_%s" % (__package__, "bbb")) - mod.process(instance) - else: - raise ValidationError( - _( - "It is not possible to re-encode a recording " - "that was not originally encoded by an user." - ) - ) - - -class LivestreamForm(forms.ModelForm): - def __init__(self, request, *args, **kwargs) -> None: - super(LivestreamForm, self).__init__(*args, **kwargs) - - # All fields are hidden. This form is like a Confirm prompt - self.fields = add_placeholder_and_asterisk(self.fields) - hidden_fields = ( - "meeting", - "start_date", - "end_date", - "status", - "user", - "server", - "broadcaster_id", - "redis_hostname", - "redis_port", - "redis_channel", - ) - for field in hidden_fields: - if self.fields.get(field, None): - self.fields[field].widget = forms.HiddenInput() - - if not USE_BBB_LIVE_DOWNLOADING: - self.fields["download_meeting"].widget = forms.HiddenInput() - - class Meta: - model = Livestream - exclude = () diff --git a/pod/bbb/management/__init__.py b/pod/bbb/management/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pod/bbb/management/commands/__init__.py b/pod/bbb/management/commands/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pod/bbb/management/commands/bbb.py b/pod/bbb/management/commands/bbb.py deleted file mode 100644 index 567612002f..0000000000 --- a/pod/bbb/management/commands/bbb.py +++ /dev/null @@ -1,734 +0,0 @@ -""" -Esup-Pod BBB command. - -This command is used to manage the recordings made by BigBlueButton. -To achieve this, this command performs the following tasks: - -- Connect to BBB / Scalelite server to get informations about the -current meetings and save then in Pod database. -This is useful to obtain the actuel meetings -and the moderators list of theses meetings. -Be careful: in BBB, we only have the firstname and last name -of these moderators. - -- Search for recordings available for meetings. -Search for meetings, made since 4 days, with their presentation recorded -where the recording is not available for the moment. -The idea of the 4 days is to avoid to process recordings that were deleted -or with bad data in the database (in fact, the recording tag in BBB -seems always true even if not recorded). - -- Search to matching BBB users as Pod users. -This allows to try if BBB user (known with firstname and lastname) is matching -a Pod user. You can parameter the BBB username format via the use -of BBB_USERNAME_FORMAT setting. -At each use of this script, we search to matching BBB users -- not already known - as Pod users. -Be careful: tested with the Moodle plugin, mod_bigbluebuttonbn, -not with Greenlight (should be the same if use of LDAP with givenName -and lastName). - -- Then, we check directory (DEFAULT_BBB_PATH) to publish video files that were -generated by bbb-recorder (DEFAULT_BBB_PLUGIN). If video files found, this -script encode them as Pod video. - -- If you have set BBB_NUMBER_DAYS_BEFORE_DELETE to a number different than 0, -all meetings - with associated users - not already published, older than -BBB_NUMBER_DAYS_BEFORE_DELETE days, will be deleted. - -Finally, if there was at least one live started or one error, -an email is sent to Pod admins. - -This script must be executed regurlaly (for an example, with a CRON task). -Example: crontab -e */2 * * * * /usr/bin/bash -c 'export -WORKON_HOME=/data/www/%userpod%/.virtualenvs; export -VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3.6; cd -/data/www/%userpod%/django_projects/podv3; source -/usr/bin/virtualenvwrapper.sh; workon django_pod; python manage.py bbb -main' -""" - -import os -import traceback -from django.utils import translation -from django.core.management.base import BaseCommand -from django.conf import settings -from pod.bbb.models import BBB_Meeting as Meeting, Attendee -import hashlib -import requests -import datetime -import dateutil.parser -from django.core.mail import mail_admins -from django.utils import timezone -from defusedxml import minidom -import urllib.parse - -from pod.video.models import Video, Type, get_storage_path_video -from pod.video_encode_transcript import encode -from django.contrib.auth.models import User - -from django.db.models import Value -from django.db.models.functions import Concat - -LANGUAGE_CODE = getattr(settings, "LANGUAGE_CODE", "fr") - -# Use of BigBlueButton -USE_BBB = getattr(settings, "USE_BBB", False) - -# Directory that will contain the video files generated by bbb-recorder -DEFAULT_BBB_PATH = getattr(settings, "DEFAULT_BBB_PATH", "/data/bbb-recorder/media/") -# The last caracter of DEFAULT_BBB_PATH must be an OS separator -if not DEFAULT_BBB_PATH.endswith(os.path.sep): - DEFAULT_BBB_PATH += os.path.sep - -# BigBlueButton or Scalelite server URL, where BBB Web presentation and API are -BBB_SERVER_URL = getattr(settings, "BBB_SERVER_URL", "https://bbb.univ.fr/") -# The last caracter of BBB_SERVER_URL must be / -if not BBB_SERVER_URL.endswith("/"): - BBB_SERVER_URL += "/" - -# BigBlueButton key or Scalelite LOADBALANCER_SECRET -BBB_SECRET_KEY = getattr(settings, "BBB_SECRET_KEY", "") - -# Default type of the generated video -DEFAULT_BBB_TYPE_ID = getattr(settings, "DEFAULT_BBB_TYPE_ID", 1) - -# Username format of the user in BBB -BBB_USERNAME_FORMAT = getattr(settings, "BBB_USERNAME_FORMAT", "first_name last_name") - -# Number of days before removal the meetings -BBB_NUMBER_DAYS_BEFORE_DELETE = getattr(settings, "BBB_NUMBER_DAYS_BEFORE_DELETE", 0) - -# BBB or Scalelite version is 2.3 (useful for recording URL in 2.0 or 2.3 format) -BBB_VERSION_IS_23 = getattr(settings, "BBB_VERSION_IS_23", False) - -# Allowed extensions -VIDEO_ALLOWED_EXTENSIONS = getattr( - settings, - "VIDEO_ALLOWED_EXTENSIONS", - ( - "3gp", - "avi", - "divx", - "flv", - "m2p", - "m4v", - "mkv", - "mov", - "mp4", - "mpeg", - "mpg", - "mts", - "wmv", - "mp3", - "ogg", - "wav", - "wma", - "webm", - "ts", - ), -) - -# Mode debug (0: False, 1: True) -DEBUG = getattr(settings, "DEBUG", False) -# Encode video -ENCODE_VIDEO = getattr(settings, "ENCODE_VIDEO", "start_encode") - - -def print_if_debug(str): - if DEBUG: - print(str) - - -def encode_file_exist(filename, extension, message_error, html_message_error): - # A video file exist in the BBB directory: encode it - print_if_debug(" - Encode BBB video file: " + filename) - # Absolute path of the video - source_file = os.path.join(DEFAULT_BBB_PATH, filename) - - # Filename corresponds to: internal_meeting_id.webm - internalMeetingId = filename.replace("." + extension, "") - # Check if the meeting already exists in Pod database - oMeeting = Meeting.objects.filter(internal_meeting_id=internalMeetingId).first() - if oMeeting: - # Set video properties with meetng informations - video = Video() - video.title = oMeeting.meeting_name - if oMeeting.encoded_by_id: - video.owner = User.objects.get(id=oMeeting.encoded_by_id) - video.type = Type.objects.get(id=DEFAULT_BBB_TYPE_ID) - video.date_evt = oMeeting.session_date - # Video management - storage_path = get_storage_path_video(video, os.path.basename(source_file)) - dt = str(datetime.datetime.now()).replace(":", "-") - nom, ext = os.path.splitext(os.path.basename(source_file)) - ext = ext.lower() - # Video name - video_name = nom + "_" + dt.replace(" ", "_") + ext - video.video = os.path.join(os.path.dirname(storage_path), video_name) - # Move source file to destination - os.makedirs(os.path.dirname(video.video.path), exist_ok=True) - os.rename(source_file, video.video.path) - video.save() - # Encode - encode_video = getattr(encode, ENCODE_VIDEO) - encode_video(video.id) - else: - # Meeting was certainly deleted in Pod database - print_if_debug( - " - WARNING: It seems that this meeting was deleted " - "from Pod database. " - "internal_meeting_id: " + internalMeetingId - ) - - return html_message_error, message_error - - -def process_directory(files, root, html_message_error, message_error): - # Search files in the BBB directory - for filename in files: - # Name of the directory - dirname = root.split(os.path.sep)[-1] - print_if_debug( - "\n*** Process the file " - + os.path.join(DEFAULT_BBB_PATH, dirname, filename) - + " ***" - ) - # Check if extension is a good extension (videos extensions) - extension = filename.split(".")[-1] - - valid_ext = VIDEO_ALLOWED_EXTENSIONS - if not (extension in valid_ext and filename != extension): - print_if_debug( - " - WARNING: " + extension + "is not a valid video " - "extension. If it should " - "be, add it to the setting " - "VIDEO_ALLOWED_EXTENSIONS" - ) - continue - html_message_error, message_error = encode_file_exist( - filename, extension, message_error, html_message_error - ) - - return html_message_error, message_error - - -def get_bbb_meetings_by_xml(html_message_error, message_error): - print_if_debug("\n*** Check BBB/Scalelite actual meetings ***") - try: - # See https://docs.bigbluebutton.org/dev/api.html#usage - # for checksum and security - checksum = hashlib.sha1( - str("getMeetings" + BBB_SECRET_KEY).encode("utf-8") - ).hexdigest() - # Request on BBB/Scalelite server (API) - # URL example: - # https://bbb.univ.fr/bigbluebutton/api/getMeetings?checksum=xxxx - urlToRequest = BBB_SERVER_URL - urlToRequest += "bigbluebutton/api/getMeetings?checksum=" + checksum - addr = requests.get(urlToRequest) - print_if_debug( - "Request on URL: " + urlToRequest + ", status: " + str(addr.status_code) - ) - # XML result to parse - xmldoc = minidom.parseString(addr.text) - returncode = xmldoc.getElementsByTagName("returncode")[0].firstChild.data - # Management of FAILED error (basically error in checksum) - if returncode == "FAILED": - err = "Return code = FAILED for: " + urlToRequest - err += " => : " + xmldoc.toxml() + "" - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - # Actual meetings - meetings = xmldoc.getElementsByTagName("meeting") - for meeting in meetings: - get_meeting(meeting, html_message_error, message_error) - - except Exception as e: - err = ( - "Problem to parse XML meetings on the BBB/Scalelite server " - "or save in Pod database: " + str(e) + ". " + traceback.format_exc() - ) - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -def get_meeting(meeting, html_message_error, message_error): - try: - # Get meeting informations - meetingName = meeting.getElementsByTagName("meetingName")[0].firstChild.data - meetingID = meeting.getElementsByTagName("meetingID")[0].firstChild.data - internalMeetingID = meeting.getElementsByTagName("internalMeetingID")[ - 0 - ].firstChild.data - date = meeting.getElementsByTagName("createDate")[0].firstChild.data - # Recording seems useless (~always True) - recording = meeting.getElementsByTagName("recording")[0].firstChild.data - - print_if_debug("\n - Meeting: " + internalMeetingID) - - # Id of the current meeting - idActualMeeting = 0 - # Search if the meeting already exists in Pod database - oMeeting = Meeting.objects.filter(internal_meeting_id=internalMeetingID).first() - if oMeeting: - idActualMeeting = oMeeting.id - print_if_debug(" + Meeting already exists in Pod database.") - # Check if meeting is recorded now - if oMeeting.recorded is False and recording == "true": - print_if_debug(" + Recording this meeting. ") - oMeeting.recorded = True - # In all case, save the last date where BBB session was in progress - lastDateInProgress = timezone.now() - oMeeting.last_date_in_progress = lastDateInProgress - oMeeting.save() - else: - # Create the meeting in Pod database - print_if_debug( - " + Create the meeting in Pod database. " - "internal_meeting_id: " + internalMeetingID - ) - meetingToCreate = Meeting() - meetingToCreate.meeting_id = meetingID - meetingToCreate.internal_meeting_id = internalMeetingID - meetingToCreate.meeting_name = meetingName - # Convert the date in the database format - dateForSql = dateutil.parser.parse(date, ignoretz=False) - meetingToCreate.session_date = dateForSql - # Initially encoding_step = 0 (very important) - meetingToCreate.encoding_step = 0 - # Recording tag seems ~always true, so seems useless - if recording == "true": - meetingToCreate.recorded = True - else: - meetingToCreate.recorded = False - meetingToCreate.recording_available = False - meetingToCreate.save() - idActualMeeting = meetingToCreate.id - - # Management of the participants - for attendee in meeting.getElementsByTagName("attendee"): - get_attendee(attendee, idActualMeeting, html_message_error, message_error) - - except Exception as e: - err = ( - "Problem to get BBB meeting " - "and save in Pod database: " + str(e) + ". " + traceback.format_exc() - ) - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -def get_attendee(attendee, idActualMeeting, html_message_error, message_error): - try: - # In BigBlueButton, we have only the full name - # Full name format: "first_name last_name" - fullName = attendee.getElementsByTagName("fullName")[0].firstChild.data - role = attendee.getElementsByTagName("role")[0].firstChild.data - # We save only the BBB moderator - if role == "MODERATOR": - # Search if the BBB user already exists in Pod - oAttendee = Attendee.objects.filter( - full_name=fullName, meeting_id=idActualMeeting - ).first() - if oAttendee: - print_if_debug( - " + User already exists " - "in Pod database: " - "" + oAttendee.full_name - ) - else: - # Create the meeting user in Pod database - print_if_debug( - " + Create the meeting user in Pod database: " + fullName - ) - attendeeToCreate = Attendee() - attendeeToCreate.full_name = fullName - attendeeToCreate.role = "MODERATOR" - attendeeToCreate.meeting_id = idActualMeeting - - attendeeToCreate.save() - except Exception as e: - err = ( - "Problem to get BBB attendee " - "and save in Pod database: " + str(e) + ". " + traceback.format_exc() - ) - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -def matching_bbb_pod_user(html_message_error, message_error): - print_if_debug("\n*** Search if BBB users matching to Pod users ***") - try: - # Search for BBB users already in Pod database, without matching - # By security: take only the 500 last BBB users, to avoid process - # too long. Usefull when users are not known in Pod. - attendees = Attendee.objects.filter(user_id__isnull=True).order_by("-id")[:500] - - # Use the BBB_USERNAME_FORMAT setting to make the matching. - if BBB_USERNAME_FORMAT == "last_name first_name": - bbbUsernameFormat = Concat("last_name", Value(" "), "first_name") - else: - bbbUsernameFormat = Concat("first_name", Value(" "), "last_name") - - for attendee in attendees: - # Search if this BBB user matching to a Pod user. - # Take the first one (This can cause an error in case of namesake!) - podUser = ( - User.objects.annotate( - name=bbbUsernameFormat, - ) - .filter(name__icontains=attendee.full_name) - .first() - ) - if podUser: - # Update the id and the username of this user - print_if_debug( - " - A Pod user matching a BBB user " - "was found in Pod database. " - "BBB user: " + attendee.full_name + ". " - "Pod user: " + podUser.username - ) - attendee.username = podUser.username - attendee.user_id = podUser.id - attendee.save() - else: - print_if_debug( - " - A Pod user matching a BBB user " - "was NOT found in Pod database. " - "BBB user: " + attendee.full_name - ) - - except Exception as e: - err = ( - "Problem to matching BBB user to Pod user: " - + str(e) - + ". " - + traceback.format_exc() - ) - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -def get_bbb_meetings_recorded(html_message_error, message_error): - print_if_debug("\n*** Check BBB meetings recorded in Pod, not already available ***") - - try: - # Search for meetings, made since 4 days, with their presentation - # recorded where the recording is not available for the moment. - # The idea of the 4 days is to avoid to process recordings that - # were deleted or with bad data in the database. - # For informations: parameter Recorded seems useless (~always True) - dateSince4d = timezone.now() - timezone.timedelta(days=4) - meetings = Meeting.objects.filter( - recorded=True, - recording_available=False, - session_date__gte=dateSince4d, - ).order_by("id") - for meeting in meetings: - # Search recording on BBB/Scalelite server - html_message_error, message_error = get_bbb_recording_by_xml( - meeting.meeting_id, - meeting.internal_meeting_id, - html_message_error, - message_error, - ) - - except Exception as e: - err = ( - "Problem to get recorded meetings " - "in Pod database: " + str(e) + ". " + traceback.format_exc() - ) - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -def get_bbb_recording_by_xml( - meeting_id, internal_meeting_id, html_message_error, message_error -): - print_if_debug(" - Check BBB/Scalelite recording.") - try: - # See https://docs.bigbluebutton.org/dev/api.html#usage - # for checksum and security - uri = "getRecordingsmeetingID=" - uri += urllib.parse.quote_plus(meeting_id) + BBB_SECRET_KEY - checksum = hashlib.sha1(str(uri).encode("utf-8")).hexdigest() - # Request on BBB/Scalelite server (API) - # URL example: https://bbb.univ.fr/bigbluebutton/api/getRecordings? - # meetingID=xxxxxxxxxxxxxx&checksum=yyyyyyyyyyyyyyy - urlToRequest = BBB_SERVER_URL - urlToRequest += "bigbluebutton/api/getRecordings?meetingID=" - urlToRequest += urllib.parse.quote_plus(meeting_id) - urlToRequest += "&checksum=" + checksum - addr = requests.get(urlToRequest) - print_if_debug( - " + Request on URL: " + urlToRequest + "" - ", status: " + str(addr.status_code) - ) - # XML result to parse - xmldoc = minidom.parseString(addr.text) - returncode = xmldoc.getElementsByTagName("returncode")[0].firstChild.data - # Management of FAILED error (basically error in checksum) - if returncode == "FAILED": - err = "Return code = FAILED for: " + urlToRequest - err += " => : " + xmldoc.toxml() + "" - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - # Actual recordings - recordings = xmldoc.getElementsByTagName("recording") - for recording in recordings: - get_recording( - recording, - internal_meeting_id, - html_message_error, - message_error, - ) - - except Exception as e: - err = ( - "Problem to parse XML recording on the BBB/Scalelite server " - "or save in Pod database: " + str(e) + ". " + traceback.format_exc() - ) - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -def get_recording(recording, internal_meeting_id, html_message_error, message_error): - try: - # Get recording informations - # meetingID = recording.getElementsByTagName( - # "meetingID")[0].firstChild.data - internalMeetingID = recording.getElementsByTagName("internalMeetingID")[ - 0 - ].firstChild.data - - # We only process the correct recording, - # set by the internal_meeting_id - print_if_debug(internalMeetingID + " -- " + internal_meeting_id) - if internalMeetingID == internal_meeting_id: - # Check if the meeting already exists in Pod database - oMeeting = Meeting.objects.filter( - internal_meeting_id=internal_meeting_id - ).first() - if oMeeting: - get_and_save_recording_url( - recording, - internal_meeting_id, - oMeeting, - html_message_error, - message_error, - ) - else: - # Meeting was certainly deleted in Pod database - print_if_debug( - " + WARNING: It seems that this " - "meeting was deleted from Pod database: " + internalMeetingID - ) - - except Exception as e: - err = "Problem to get BBB recording: " + str(e) + ". " + traceback.format_exc() - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -def get_and_save_recording_url( - recording, internal_meeting_id, oMeeting, html_message_error, message_error -): - try: - # Get recording URL that corresponds to the presentation URL - # Save this information, if found, in database - # Take only the "presentation" format - # Not other format like "screenshare" or "podcast" - recording_url = "" - # Check playback data - for playback in recording.getElementsByTagName("playback"): - # Depends on BBB parameters, we can have multiple format - for format in playback.getElementsByTagName("format"): - type = format.getElementsByTagName("type")[0].firstChild.data - # For bbb-recorder, we need URL of presentation format - if type == "presentation": - # Recording URL is the BBB presentation URL - recording_url = format.getElementsByTagName("url")[0].firstChild.data - # We take the first thumbnail found - thumbnail_url = playback.getElementsByTagName("image")[ - 0 - ].firstChild.data - - if recording_url != "": - print_if_debug( - " + The recording was found. " - "internal_meeting_id: " + internal_meeting_id + ". " - "recording_url: " + recording_url - ) - # Convert recording_url format (2.0 to 2.3) if necessary - recording_url, html_message_error, message_error = convert_format( - recording_url, internal_meeting_id, html_message_error, message_error - ) - oMeeting.recording_available = True - oMeeting.recording_url = recording_url - oMeeting.thumbnail_url = thumbnail_url - oMeeting.save() - - except Exception as e: - err = ( - "Problem to get BBB recording url " - "and save in Pod database: " + str(e) + ". " + traceback.format_exc() - ) - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -def convert_format(recording_url, internal_meeting_id, html_message_error, message_error): - print_if_debug("\n*** Convert recording_url format (if necessary) ***") - try: - # Conversion - if necessary - from - # https://xxx/playback/presentation/2.0/playback.html?meetingId=ID - # to https://xxx/playback/presentation/2.3/ID?meetingId=ID - if BBB_VERSION_IS_23 and recording_url.find("/2.0/") >= 0: - recording_url = recording_url.replace( - "/2.0/playback.html", "/2.3/" + internal_meeting_id - ) - print_if_debug( - " + Converting recording_url " - "from 2.0 format to 2.3 format. " - "New recording_url: " + recording_url - ) - except Exception as e: - err = "Problem to convert format: " + str(e) + ". " + traceback.format_exc() - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return recording_url, html_message_error, message_error - - -def delete_old_meetings(html_message_error, message_error): - print_if_debug("\n*** Delete old meetings and BBB users ***") - try: - # Delete only old meetings if parameter set - if BBB_NUMBER_DAYS_BEFORE_DELETE == 0: - print_if_debug(" - BBB_NUMBER_DAYS_BEFORE_DELETE = 0: nothing to remove.") - else: - # Search for BBB meetings, not already published, older than - # BBB_NUMBER_DAYS_BEFORE_DELETE days - delay = timezone.timedelta(days=BBB_NUMBER_DAYS_BEFORE_DELETE) - date_removal = timezone.now() - delay - meetings = Meeting.objects.filter( - encoding_step__lt=3, session_date__lte=date_removal - ).order_by("id") - for meeting in meetings: - print_if_debug( - " - Removal of this meeting. " - "internal_meeting_id: " + meeting.internal_meeting_id + "." - ) - meeting.delete() - except Exception as e: - err = "Problem to delete old meetings: " + str(e) + ". " + traceback.format_exc() - message_error += err + "\n" - html_message_error += "
  • " + err + "
  • " - print_if_debug(err) - return html_message_error, message_error - - return html_message_error, message_error - - -class Command(BaseCommand): - # First possible argument: main - args = "main" - help = "Manage the BigBlueButton presentation " - valid_args = ["main"] - - def add_arguments(self, parser): - parser.add_argument("task") - - def handle(self, *args, **options): - # Activate a fixed locale fr - translation.activate(LANGUAGE_CODE) - if options["task"] and options["task"] in self.valid_args: - html_message_error = "" - message_error = "" - # Connect to BBB / Scalelite server to get infos - # about the current meetings - html_message_error, message_error = get_bbb_meetings_by_xml( - html_message_error, message_error - ) - - # Search for recording available for meetings - html_message_error, message_error = get_bbb_meetings_recorded( - html_message_error, message_error - ) - - # Search to matching BBB users as Pod users - html_message_error, message_error = matching_bbb_pod_user( - html_message_error, message_error - ) - - # Check directory to publish video files - for root, dirs, files in os.walk(DEFAULT_BBB_PATH): - if "logs" in dirs: - dirs.remove("logs") - html_message_error, message_error = process_directory( - files, root, html_message_error, message_error - ) - - # Delete old meetings and users - html_message_error, message_error = delete_old_meetings( - html_message_error, message_error - ) - - # If USE_BBB = True, if there was at least one error, - # send an email to Pod admins - if message_error != "": - if USE_BBB: - print_if_debug( - "\n\n*** An email BBB job [Error(s) " - "encountered] was sent to Pod admins, with message: " - "***\n\n" + message_error - ) - mail_admins( - "BBB job [Error(s) encountered]", - message_error, - fail_silently=False, - html_message=html_message_error, - ) - else: - print_if_debug( - "\n\n*** Error(s) encountered but no email sent " - "\n\n*** (USE_BBB = false) " - "***\n\nMessage: " + message_error - ) - else: - print("*** Warning: you must give some arguments: %s ***" % self.valid_args) diff --git a/pod/bbb/migrations/__init__.py b/pod/bbb/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pod/bbb/models.py b/pod/bbb/models.py deleted file mode 100644 index 1f94e1b258..0000000000 --- a/pod/bbb/models.py +++ /dev/null @@ -1,287 +0,0 @@ -"""Esup-Pod BBB models.""" - -import importlib - -from django.db import models -from django.conf import settings -from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth.models import User -from django.dispatch import receiver -from django.db.models.signals import post_save -from django.utils import timezone - - -USE_BBB = getattr(settings, "USE_BBB", False) - - -class BBB_Meeting(models.Model): - # Meeting id for the BBB session - meeting_id = models.CharField( - _("Meeting id"), max_length=200, help_text=_("Id of the BBB meeting.") - ) - # Internal meeting id for the BBB session - internal_meeting_id = models.CharField( - _("Internal meeting id"), - max_length=200, - help_text=_("Internal id of the BBB meeting."), - ) - # Meeting name for the BBB session - meeting_name = models.CharField( - _("Meeting name"), - max_length=200, - help_text=_("Name of the BBB meeting."), - ) - # Date of the BBB session - session_date = models.DateTimeField(_("Session date"), default=timezone.now) - # Encoding step / status of the process - ENCODING_STEP = ( - (0, _("Publish is possible")), - (1, _("Waiting for encoding")), - (2, _("Encoding in progress")), - (3, _("Already published")), - ) - encoding_step = models.IntegerField( - _("Encoding step"), - choices=ENCODING_STEP, - help_text=_( - "Encoding step for conversion of the BBB presentation to video file." - ), - default=0, - ) - # Is this meeting was recorded in BigBlueButton? - recorded = models.BooleanField( - _("Recorded"), help_text=_("BBB presentation recorded?"), default=False - ) - # Is the recording of the presentation is available in BigBlueButton? - recording_available = models.BooleanField( - _("Recording available"), - help_text=_("BBB presentation recording is available?"), - default=False, - ) - # URL of the recording of the BigBlueButton presentation - recording_url = models.CharField( - _("Recording url"), - help_text=_("URL of the recording of the BBB presentation."), - max_length=200, - ) - # URL of the recording thumbnail of the BigBlueButton presentation - thumbnail_url = models.CharField( - _("Thumbnail url"), - help_text=_("URL of the recording thumbnail of the BBB presentation."), - max_length=200, - ) - # User who converted the BigBlueButton presentation to video file - encoded_by = models.ForeignKey( - User, - on_delete=models.CASCADE, - limit_choices_to={"is_staff": True}, - verbose_name=_("User"), - null=True, - blank=True, - help_text=_("User who converted the BBB presentation to video file."), - ) - # Last date of the BBB session in progress - last_date_in_progress = models.DateTimeField( - _("Last date in progress"), - default=timezone.now, - help_text=_("Last date where BBB session was in progress."), - ) - - def __unicode__(self) -> str: - return "%s - %s" % (self.meeting_name, self.meeting_id) - - def __str__(self) -> str: - return "%s - %s" % (self.meeting_name, self.meeting_id) - - def save(self, *args, **kwargs) -> None: - super(BBB_Meeting, self).save(*args, **kwargs) - - class Meta: - verbose_name = _("Meeting") - verbose_name_plural = _("Meetings") - ordering = ["session_date"] - - -@receiver(post_save, sender=BBB_Meeting) -def process_recording(sender, instance, created, **kwargs) -> None: - # Convert the BBB presentation only one time - # Be careful: this is the condition to create a video of the - # BigBlueButton presentation only one time. - if instance.encoding_step == 1 and instance.encoded_by is not None: - mod = importlib.import_module("%s.plugins.type_%s" % (__package__, "bbb")) - mod.process(instance) - - -# Management of the BigBlueButton users, -# with role = MODERATOR in BigBlueButton -class Attendee(models.Model): - # Meeting for which this user was a moderator - meeting = models.ForeignKey( - BBB_Meeting, on_delete=models.CASCADE, verbose_name=_("Meeting") - ) - # Full name (First_name Last_name) of the user from BigBlueButton - full_name = models.CharField( - _("Full name"), - max_length=200, - help_text=_("Full name of the user from BBB."), - ) - # Role of this user (only MODERATOR for the moment) - role = models.CharField( - _("User role"), - max_length=200, - help_text=_("Role of the user from BBB."), - ) - # Username of this user, if the BBB user was translated with a Pod user - # Redundant information with user, but can be useful. - username = models.CharField( - _("Username / User id"), - max_length=150, - help_text=_("Username / User id, if the BBB user was matching a Pod user."), - ) - - # Pod user, if the BBB user was translated with a Pod user - user = models.ForeignKey( - User, - on_delete=models.CASCADE, - limit_choices_to={"is_staff": True}, - verbose_name=_("User"), - null=True, - blank=True, - help_text=_("User from the Pod database, if user found."), - ) - - def __unicode__(self) -> str: - return "%s - %s" % (self.full_name, self.role) - - def __str__(self) -> str: - return "%s - %s" % (self.full_name, self.role) - - def save(self, *args, **kwargs) -> None: - super(Attendee, self).save(*args, **kwargs) - - class Meta: - verbose_name = _("Attendee") - verbose_name_plural = _("Attendees") - ordering = ["full_name"] - - -# Management of BigBlueButton sessions for live -# See bbb-pod-live.php for more explanations. -class Livestream(models.Model): - # Meeting - meeting = models.ForeignKey( - BBB_Meeting, on_delete=models.CASCADE, verbose_name=_("Meeting") - ) - # Start date of the live - start_date = models.DateTimeField( - _("Start date"), - default=timezone.now, - help_text=_("Start date of the live."), - ) - # End date of the live - end_date = models.DateTimeField( - _("End date"), - null=True, - blank=True, - help_text=_("End date of the live."), - ) - # Live status - STATUS = ( - (0, _("Live not started")), - (1, _("Live in progress")), - (2, _("Live stopped")), - ) - status = models.IntegerField(_("Live status"), choices=STATUS, default=0) - # Server/Process performing the live - server = models.CharField( - _("Server"), - max_length=20, - null=True, - blank=True, - help_text=_("Server/process performing the live."), - ) - # User that want to perform the live - user = models.ForeignKey( - User, - on_delete=models.CASCADE, - limit_choices_to={"is_staff": True}, - verbose_name=_("User"), - null=True, - blank=True, - help_text=_("Username / User id, that want to perform the live."), - ) - # Restricted access to the created live - is_restricted = models.BooleanField( - verbose_name=_("Restricted access"), - help_text=_("Is live only accessible to authenticated users?"), - default=False, - ) - # Broadcaster in charge to perform the live - broadcaster_id = models.IntegerField( - null=True, - blank=True, - verbose_name=_("Broadcaster"), - help_text=_("Broadcaster in charge to perform live."), - ) - # If the user wants that show the public chat in the live - show_chat = models.BooleanField( - verbose_name=_("Show public chat"), - help_text=_("Do you want to show the public chat in the live?"), - default=True, - ) - # If the user wants to download the video of this meeting after the live - download_meeting = models.BooleanField( - verbose_name=_("Save meeting in dashboard"), - help_text=_( - "Do you want to save the video of " - "this meeting, at the end of the live, directly in “Dashboard”?" - ), - default=False, - ) - # If the user wants that students have a chat in the live page - enable_chat = models.BooleanField( - verbose_name=_("Enable chat"), - help_text=_( - "Do you want a chat on the live page " - "for students? Messages sent in this live page’s chat will " - "end up in BigBlueButton’s public chat." - ), - default=False, - ) - # Redis hostname, useful for chat - redis_hostname = models.CharField( - _("Redis hostname"), - max_length=200, - null=True, - blank=True, - help_text=_("Redis hostname, useful for chat"), - ) - # Redis port, useful for chat - redis_port = models.IntegerField( - _("Redis port"), - null=True, - blank=True, - help_text=_("Redis port, useful for chat"), - ) - # Redis channel, useful for chat - redis_channel = models.CharField( - _("Redis channel"), - max_length=200, - null=True, - blank=True, - help_text=_("Redis channel, useful for chat"), - ) - - def __unicode__(self) -> str: - return "%s - %s" % (self.meeting, self.status) - - def __str__(self) -> str: - return "%s - %s" % (self.meeting, self.status) - - def save(self, *args, **kwargs) -> None: - super(Livestream, self).save(*args, **kwargs) - - class Meta: - verbose_name = _("Livestream") - verbose_name_plural = _("Livestreams") - ordering = ["start_date"] diff --git a/pod/bbb/plugins/__init__.py b/pod/bbb/plugins/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pod/bbb/plugins/type_bbb.py b/pod/bbb/plugins/type_bbb.py deleted file mode 100644 index c467a64542..0000000000 --- a/pod/bbb/plugins/type_bbb.py +++ /dev/null @@ -1,23 +0,0 @@ -import threading -import logging - -from django.conf import settings -from pod.bbb.bbb import start_bbb_encode - -BBB_ENCODE_MEETING = getattr(settings, "BBB_ENCODE_MEETING", start_bbb_encode) - -log = logging.getLogger(__name__) - - -# Process that allows to create a video file -# from a BigBlueButton presentation -def process(recording): - log.info("START PROCESS OF RECORDING %s" % recording) - t = threading.Thread(target=bbb_encode_recording, args=[recording]) - t.setDaemon(True) - t.start() - - -# Create a video file from a BigBlueButton presentation -def bbb_encode_recording(recording): - BBB_ENCODE_MEETING(recording.id) diff --git a/pod/bbb/rest_views.py b/pod/bbb/rest_views.py deleted file mode 100644 index 5b7f147f1d..0000000000 --- a/pod/bbb/rest_views.py +++ /dev/null @@ -1,66 +0,0 @@ -from rest_framework import serializers, viewsets -from .models import BBB_Meeting, Attendee, Livestream - - -class MeetingModelSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = BBB_Meeting - fields = ( - "id", - "meeting_id", - "internal_meeting_id", - "meeting_name", - "encoding_step", - "recorded", - "recording_available", - "recording_url", - "thumbnail_url", - "encoded_by", - "session_date", - "last_date_in_progress", - ) - - -class MeetingModelViewSet(viewsets.ModelViewSet): - queryset = BBB_Meeting.objects.all() - serializer_class = MeetingModelSerializer - - -class AttendeeModelSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = Attendee - fields = ("id", "full_name", "role", "username", "meeting", "user") - - -class AttendeeModelViewSet(viewsets.ModelViewSet): - queryset = Attendee.objects.all() - serializer_class = AttendeeModelSerializer - - -class LivestreamModelSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = Livestream - fields = ( - "id", - "meeting", - "start_date", - "end_date", - "show_chat", - "download_meeting", - "enable_chat", - "is_restricted", - "broadcaster_id", - "redis_hostname", - "redis_port", - "redis_channel", - "status", - "server", - "user", - ) - filterset_fields = ["status", "server"] - - -class LivestreamModelViewSet(viewsets.ModelViewSet): - queryset = Livestream.objects.all() - serializer_class = LivestreamModelSerializer - filterset_fields = ["status", "server"] diff --git a/pod/bbb/static/css/bbb.css b/pod/bbb/static/css/bbb.css deleted file mode 100644 index cd388c7916..0000000000 --- a/pod/bbb/static/css/bbb.css +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Esup-Pod BBB styles - */ - -#bbb_meetings_list .card-header { - background: var(--color-black-alpha); - padding: 0 0.25rem; - z-index: 9; -} - -#bbb_meetings_list .card-header .text-muted { - color: var(--color-white) !important; -} - -/* To always see publish button */ -#bbb_meetings_list .infinite-item .card-body { - height: auto !important; -} - -#bbb_meetings_list .infinite-item .card-body footer a { - width: 100% !important; -} diff --git a/pod/bbb/templates/bbb/card.html b/pod/bbb/templates/bbb/card.html deleted file mode 100644 index ef2782b8eb..0000000000 --- a/pod/bbb/templates/bbb/card.html +++ /dev/null @@ -1,40 +0,0 @@ -{% load i18n %} -{% spaceless %} - -
    -
    -
    - {{record.meeting_name}} ({{ record.session_date }}) -
    -
    -
    -
    - - {% trans 'BigBlueButton presentation preview' %} - -
    - - -
    -
    -{% endspaceless %} \ No newline at end of file diff --git a/pod/bbb/templates/bbb/list_meeting.html b/pod/bbb/templates/bbb/list_meeting.html deleted file mode 100644 index 5e60cb96e9..0000000000 --- a/pod/bbb/templates/bbb/list_meeting.html +++ /dev/null @@ -1,93 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} - -{% block page_extra_head %} - -{% endblock %} - -{% block breadcrumbs %} -{{ block.super }} - -{% endblock %} - -{% block page_content %} - -

    {% trans "Create a video from a BigBlueButton presentation" %}

    - {% if records.paginator.count == 0 %} -

    {% trans "No record found" %}

    - - {% else %} -

    {% blocktrans count counter=records.paginator.count %}{{ counter }} record found{% plural %}{{ counter }} records found{% endblocktrans %}

    - {% trans "This is the list of the recorded BigBlueButton sessions for which you were moderator. This module allows you to create a video from a BigBlueButton presentation." %}
    - {% trans "Shortly after the presentation is published, the corresponding video will appear in your videos." %}
    - - - {% trans "Please note: a BigBlueButton presentation can be encoded by another moderator." %} - {% trans "If such a case occurs, this information will be displayed directly in the list. You can then contact this user directly to ask him/her to share the video with you, or even to put you as additional owner of the video." %} - - -
    - {% include "bbb/record_list.html" %} - {% endif %} - - - - {% trans "Please note: this page refreshes automatically every 30 seconds." %} - -{% endblock page_content %} - -{% block collapse_page_aside %}{% endblock collapse_page_aside %} -{% block page_aside %}{% endblock page_aside %} - -{% block more_script %} - - - - -{% endblock more_script %} diff --git a/pod/bbb/templates/bbb/live_card.html b/pod/bbb/templates/bbb/live_card.html deleted file mode 100644 index f0bc97ed0a..0000000000 --- a/pod/bbb/templates/bbb/live_card.html +++ /dev/null @@ -1,53 +0,0 @@ -{% load i18n %} -{% spaceless %} - -
    -
    -
    - {{record.meeting_name}} ({{ record.session_date }}) -
    -
    -
    -
    - {% if not record.live %} - -
    - {% if not max_limit_reached %} - - {% trans "Perform a BigBlueButton live"%} - - {% else %} - - {% trans "Impossible to perform a BigBlueButton live for the moment (all resources are busy)"%}> - {% endif %} -
    - {% else %} - {% if record.live.status == 0 %} - - {% endif %} - {% if record.live.status == 1 %} - - {% endif %} - {% if record.live.status == 2 %} - - {% endif %} - {% endif %} -
    - -
    -
    -{% endspaceless %} diff --git a/pod/bbb/templates/bbb/live_list_meeting.html b/pod/bbb/templates/bbb/live_list_meeting.html deleted file mode 100644 index c643ea79d4..0000000000 --- a/pod/bbb/templates/bbb/live_list_meeting.html +++ /dev/null @@ -1,65 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} - -{% block page_extra_head %} - -{% endblock %} - -{% block breadcrumbs %}{{ block.super }} - -{% endblock %} - -{% block page_content %} - {% if records.paginator.count == 0 %} -

    {% trans "No BigBlueButton session in progress found"%}

    - {% else %} -

    {% blocktrans count counter=records.paginator.count %}{{ counter }} BigBlueButton session in progress found{% plural %}{{ counter }} BigBlueButton sessions in progress found{% endblocktrans %}

    - {% endif %} -

    - {% trans "This is the list of current BigBlueButton sessions for which you are moderator. This module allows you to make a live stream from this BigBlueButton session (useful if there are more than 100 users)." %} - {% trans "Remember to not use breakout rooms in this case and end the meeting once it is over." %} -

    - {% blocktrans %}Shortly after clicking the “Perform a BigBlueButton live” button, and select the desired options, the live stream will be available to users on the  Lives page.{% endblocktrans %} -

    - - {% if records.paginator.count == 0 %} - - {% else %} - {% include "bbb/live_record_list.html" %} - {% endif %} - -

    {% trans "Please note: this page refreshes automatically every 30 seconds." %}

    -{% endblock page_content %} - -{% block collapse_page_aside %}{% endblock collapse_page_aside %} -{% block page_aside %}{% endblock page_aside %} - -{% block more_script %} - - - - -{% endblock more_script %} diff --git a/pod/bbb/templates/bbb/live_publish_meeting.html b/pod/bbb/templates/bbb/live_publish_meeting.html deleted file mode 100644 index 579556d251..0000000000 --- a/pod/bbb/templates/bbb/live_publish_meeting.html +++ /dev/null @@ -1,98 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load thumbnail %} -{% block page_extra_head %} - -{% endblock page_extra_head %} - -{% block breadcrumbs %}{{ block.super }} - - -{% endblock %} - -{% block page_title %} - {% trans "Confirmation of performing a BigBlueButton live" %} -{% endblock %} - - -{% block page_content %} -

    {% trans "Are you sure you want to perform a BigBlueButton live?" %}

    - -
    - {% csrf_token %} - -
    - {% trans 'Select the desired options than validate this form by clicking "Perform a BigBlueButton live".' %}
    -

    - {% trans "This live will be stopped automatically when BigBlueButton session ends." %} -

    -
    - {% if form.errors %} -

    {% trans "One or more errors have been found in the form." %}

    - {% endif %} - {% for field_hidden in form.hidden_fields %} - {{field_hidden.errors}} - {{field_hidden}} - {% endfor %} - {% if form.errors %} - {% for error in form.errors %} - {{ error }} - {% endfor %} - {% endif %} - {% for field in form.visible_fields %} - {% spaceless %} -
    -
    -
    - {{ field.errors }} - {% if "form-check-input" in field.field.widget.attrs.class %} -
    - {{ field }} -
    - {% else %} - - {{ field }} - {% endif %} - {% if field.help_text %} - {{ field.help_text|safe }} - {% endif %} - {% if field.field.required %}
    {% trans "Please provide a valid value for this field." %}
    {% endif %} -
    -
    -
    - {% endspaceless %} - {% endfor %} -
    - - {% trans 'Please note: shortly after clicking the “Perform a BigBlueButton live” button, the live stream will be available to users on the  Lives page.' %} -
    -
    -
    - -
    -
    -{% endblock page_content %} - -{% block collapse_page_aside %} -{% endblock collapse_page_aside %} - -{% block page_aside %} -{% endblock page_aside %} - -{% block more_script %} -{{form.media}} -{% endblock more_script %} diff --git a/pod/bbb/templates/bbb/live_record_list.html b/pod/bbb/templates/bbb/live_record_list.html deleted file mode 100644 index 86d1e662ba..0000000000 --- a/pod/bbb/templates/bbb/live_record_list.html +++ /dev/null @@ -1,20 +0,0 @@ -{% load i18n %} -{% spaceless %} -
    - {% for record in records %} -
    - {% include "bbb/live_card.html" %} -
    - {% empty %} -
    -

    {% trans "Sorry, no BigBlueButton session in progress found" %}

    -
    - {% endfor %} -
    - {% if records.has_next %} - {% trans "More" %} - {% endif %} - -{% endspaceless %} diff --git a/pod/bbb/templates/bbb/publish_meeting.html b/pod/bbb/templates/bbb/publish_meeting.html deleted file mode 100644 index 7acf1a6def..0000000000 --- a/pod/bbb/templates/bbb/publish_meeting.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% load static %} -{% load thumbnail %} -{% block page_extra_head %} - -{% endblock page_extra_head %} - -{% block breadcrumbs %}{{ block.super }} - - -{% endblock %} - -{% block page_title %} - {% trans "Publish the BigBlueButton presentation" %} -{% endblock %} - - -{% block page_content %} -

    {% trans "Are you sure you want to publish this BigBlueButton presentation?" %}

    - -
    - {% csrf_token %} - -
    -
    - {% if form.errors %} -

    {% trans "One or more errors have been found in the form." %}

    - {% endif %} - {% for field_hidden in form.hidden_fields %} - {{field_hidden.errors}} - {{field_hidden}} - {% endfor %} - {% if form.errors %} - {% for error in form.errors %} - {{ error }} - {% endfor %} - {% endif %} - {% for field in form.visible_fields %} - {% spaceless %} -
    -
    -
    - {{ field.errors }} - {% if "form-check-input" in field.field.widget.attrs.class %} -
    - {{ field }} -
    - {% else %} - - {{ field }} - {% endif %} - {% if field.help_text %} - {{ field.help_text|safe }} - {% endif %} - {% if field.field.required %}
    {% trans "Please provide a valid value for this field." %}
    {% endif %} -
    -
    -
    - {% endspaceless %} - {% endfor %} -
    - {% trans "A video will be created from the BigBlueButton presentation and will be available on this platform." %}
    - {% trans "Please note: this treatment can take a long time. You will receive an email when this treatment is completed." %} -
    -
    - -
    -
    -{% endblock page_content %} - -{% block collapse_page_aside %} -{% endblock collapse_page_aside %} - -{% block page_aside %} -{% endblock page_aside %} - -{% block more_script %} -{{form.media}} -{% endblock more_script %} diff --git a/pod/bbb/templates/bbb/record_list.html b/pod/bbb/templates/bbb/record_list.html deleted file mode 100644 index e156591359..0000000000 --- a/pod/bbb/templates/bbb/record_list.html +++ /dev/null @@ -1,20 +0,0 @@ -{% load i18n %} -{% spaceless %} -
    - {% for record in records %} -
    - {% include "bbb/card.html" %} -
    - {% empty %} -
    -

    {% trans "Sorry, no record found" %}

    -
    - {% endfor %} -
    -{% if records.has_next %} - {% trans "More" %} -{% endif %} - -{% endspaceless %} diff --git a/pod/bbb/tests/__init__.py b/pod/bbb/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pod/bbb/tests/test_models.py b/pod/bbb/tests/test_models.py deleted file mode 100644 index e4a5e7a63a..0000000000 --- a/pod/bbb/tests/test_models.py +++ /dev/null @@ -1,207 +0,0 @@ -"""Test BBB models.""" - -from django.test import TestCase -from django.contrib.auth.models import User -from django.utils import timezone -from ..models import BBB_Meeting as Meeting, Attendee, Livestream - - -class MeetingTestCase(TestCase): - fixtures = [ - "initial_data.json", - ] - - def setUp(self): - """Set up MeetingTestCase.""" - Meeting.objects.create( - id=1, - meeting_id="id1", - internal_meeting_id="internalid1", - meeting_name="Session BBB1", - encoding_step=0, - ) - - user = User.objects.create(username="pod") - Meeting.objects.create( - id=2, - meeting_id="id2", - internal_meeting_id="internalid2", - meeting_name="Session BBB2", - encoding_step=1, - encoded_by=user, - ) - - print(" ---> SetUp of MeetingTestCase: OK!") - - def test_attributes(self): - """Test Meeting attributes.""" - meeting = Meeting.objects.get(id=1) - self.assertEqual(meeting.meeting_name, "Session BBB1") - self.assertEqual(meeting.internal_meeting_id, "internalid1") - self.assertEqual(meeting.meeting_name, "Session BBB1") - self.assertEqual(meeting.encoding_step, 0) - date = timezone.now() - self.assertEqual(meeting.session_date.year, date.year) - self.assertEqual(meeting.session_date.month, date.month) - self.assertEqual(meeting.session_date.day, date.day) - - user = User.objects.get(username="pod") - meeting2 = Meeting.objects.get(id=2) - self.assertEqual(meeting2.meeting_name, "Session BBB2") - self.assertEqual(meeting2.internal_meeting_id, "internalid2") - self.assertEqual(meeting2.meeting_name, "Session BBB2") - self.assertEqual(meeting2.encoding_step, 1) - self.assertEqual(meeting2.encoded_by, user) - print(" ---> test_attributs of MeetingTestCase: OK!") - - # Test update attributes - def test_update_attributes(self): - meeting = Meeting.objects.get(id=1) - user = User.objects.get(username="pod") - meeting.encoding_step = 1 - meeting.encoded_by = user - meeting.save() - self.assertEqual(meeting.encoding_step, 1) - self.assertEqual(meeting.encoded_by, user) - print(" ---> test_update_attributes of MeetingTestCase: OK!") - - # Test delete object - def test_delete_object(self): - Meeting.objects.filter(meeting_id="id1").delete() - self.assertEqual(Meeting.objects.all().count(), 1) - Meeting.objects.filter(meeting_id="id2").delete() - self.assertEqual(Meeting.objects.all().count(), 0) - - print(" ---> test_delete_object of MeetingTestCase: OK!") - - -class AttendeeTestCase(TestCase): - fixtures = [ - "initial_data.json", - ] - - def setUp(self): - meeting1 = Meeting.objects.create( - id=1, - meeting_id="id1", - internal_meeting_id="internalid1", - meeting_name="Session BBB1", - encoding_step=0, - ) - - Attendee.objects.create( - id=1, full_name="John Doe", role="MODERATOR", meeting=meeting1 - ) - - User.objects.create(username="pod") - userJaneDoe = User.objects.create(username="pod2") - Attendee.objects.create( - id=2, - full_name="Jane Doe", - role="MODERATOR", - username="pod2", - meeting=meeting1, - user=userJaneDoe, - ) - - print(" ---> SetUp of AttendeeTestCase: OK!") - - # Test attributes - def test_attributes(self): - attendee = Attendee.objects.get(id=1) - meeting = Meeting.objects.get(id=1) - self.assertEqual(attendee.full_name, "John Doe") - self.assertEqual(attendee.role, "MODERATOR") - self.assertEqual(attendee.meeting, meeting) - - userJaneDoe = User.objects.get(username="pod2") - attendee2 = Attendee.objects.get(id=2) - self.assertEqual(attendee2.full_name, "Jane Doe") - self.assertEqual(attendee2.role, "MODERATOR") - self.assertEqual(attendee2.meeting, meeting) - self.assertEqual(attendee2.username, "pod2") - self.assertEqual(attendee2.user, userJaneDoe) - print(" ---> test_attributs of AttendeeTestCase: OK!") - - # Test update attributes - def test_update_attributes(self): - attendee = Attendee.objects.get(id=1) - userJohnDoe = User.objects.get(username="pod") - attendee.username = "pod" - attendee.user = userJohnDoe - attendee.save() - self.assertEqual(attendee.username, "pod") - self.assertEqual(attendee.user, userJohnDoe) - print(" ---> test_update_attributes of AttendeeTestCase: OK!") - - # Test delete object - def test_delete_object(self): - Attendee.objects.filter(id=1).delete() - self.assertEqual(Attendee.objects.all().count(), 1) - Attendee.objects.filter(id=2).delete() - self.assertEqual(Attendee.objects.all().count(), 0) - - print(" ---> test_delete_object of AttendeeTestCase: OK!") - - -class LivestreamTestCase(TestCase): - fixtures = [ - "initial_data.json", - ] - - def setUp(self): - meeting1 = Meeting.objects.create( - id=1, - meeting_id="id1", - internal_meeting_id="internalid1", - meeting_name="Session BBB1", - encoding_step=0, - ) - - user1 = User.objects.create(username="pod") - - Livestream.objects.create( - id=1, start_date=timezone.now(), meeting=meeting1, user=user1 - ) - - print(" ---> SetUp of LivestreamTestCase: OK!") - - # Test attributes - def test_attributes(self): - meeting = Meeting.objects.get(id=1) - user = User.objects.get(username="pod") - livestream = Livestream.objects.get(id=1) - self.assertEqual(livestream.status, 0) - self.assertEqual(livestream.meeting, meeting) - self.assertEqual(livestream.user, user) - date = timezone.now() - self.assertEqual(livestream.start_date.year, date.year) - self.assertEqual(livestream.start_date.month, date.month) - self.assertEqual(livestream.start_date.day, date.day) - - print(" ---> test_attributs of LivestreamTestCase: OK!") - - # Test update attributes - def test_update_attributes(self): - livestream = Livestream.objects.get(id=1) - livestream.status = 1 - livestream.download_meeting = 1 - livestream.enable_chat = 1 - livestream.is_restricted = 1 - livestream.redis_hostname = "localhost" - livestream.redis_port = 6379 - livestream.broadcaster_id = 1 - livestream.save() - self.assertEqual(livestream.status, 1) - self.assertEqual(livestream.download_meeting, 1) - self.assertEqual(livestream.redis_hostname, "localhost") - self.assertEqual(livestream.redis_port, 6379) - self.assertEqual(livestream.broadcaster_id, 1) - print(" ---> test_update_attributes of LivestreamTestCase: OK!") - - # Test delete object - def test_delete_object(self): - Livestream.objects.filter(id=1).delete() - self.assertEqual(Livestream.objects.all().count(), 0) - - print(" ---> test_delete_object of LivestreamTestCase: OK!") diff --git a/pod/bbb/tests/test_views.py b/pod/bbb/tests/test_views.py deleted file mode 100644 index b1a29b72c8..0000000000 --- a/pod/bbb/tests/test_views.py +++ /dev/null @@ -1,137 +0,0 @@ -"""Unit tests for BBB views.""" - -from django.test import TestCase -from django.test import Client -from django.contrib.auth.models import User -from django.core.exceptions import PermissionDenied -from ..models import BBB_Meeting as Meeting, Attendee, Livestream -from django.utils import timezone - - -class MeetingViewsTestCase(TestCase): - """Test case for BBB MeetingViews.""" - - fixtures = [ - "initial_data.json", - ] - - def setUp(self): - meeting1 = Meeting.objects.create( - id=1, - meeting_id="id1", - internal_meeting_id="internalid1", - meeting_name="Session BBB1", - recorded=True, - recording_available=True, - encoding_step=0, - ) - userJohnDoe = User.objects.create(username="pod") - Attendee.objects.create( - id=1, - full_name="John Doe", - role="MODERATOR", - username="pod", - meeting=meeting1, - user=userJohnDoe, - ) - print(" ---> SetUp of MeetingViewsTestCase: OK!") - - def test_list_meeting(self): - self.client = Client() - response = self.client.get("/bbb/list_meeting/") - self.assertRaises(PermissionDenied) - - self.user = User.objects.get(username="pod") - self.user.is_staff = True - self.user.save() - - self.client.force_login(self.user) - response = self.client.get("/bbb/list_meeting/") - self.assertTrue(b"Session BBB1" in response.content) - self.assertEqual(response.status_code, 200) - - print(" ---> test_list_meeting of MeetingViewsTestCase: OK!") - - def test_publish_meeting(self): - self.client = Client() - response = self.client.get("/bbb/publish_meeting/1") - self.assertRaises(PermissionDenied) - - self.user = User.objects.get(username="pod") - self.user.is_staff = True - self.user.save() - - response = self.client.post("/bbb/publish_meeting/1") - # Possible status: 200 or 301 - if response.status_code == 200: - self.assertEqual(response.status_code, 200) - else: - self.assertEqual(response.status_code, 301) - print(" ---> test_publish_meeting of MeetingViewsTestCase: OK!") - - -class LivestreamViewsTestCase(TestCase): - fixtures = [ - "initial_data.json", - ] - - def setUp(self): - meeting1 = Meeting.objects.create( - id=1, - meeting_id="id1", - internal_meeting_id="internalid1", - meeting_name="Session BBB1", - session_date=timezone.now(), - encoding_step=0, - ) - userJohnDoe = User.objects.create(username="john.doe") - Attendee.objects.create( - id=1, - full_name="John Doe", - role="MODERATOR", - username="john.doe", - meeting=meeting1, - user=userJohnDoe, - ) - - Livestream.objects.create( - id=1, - start_date=timezone.now(), - meeting=meeting1, - status=0, - user=userJohnDoe, - ) - print(" ---> SetUp of LivestreamViewsTestCase: OK!") - - def test_live_list_meeting(self): - self.client = Client() - response = self.client.get("/bbb/live_list_meeting/") - self.assertRaises(PermissionDenied) - - self.user = User.objects.get(username="john.doe") - self.user.is_staff = True - self.user.save() - - self.client.force_login(self.user) - response = self.client.get("/bbb/live_list_meeting/") - self.assertTrue(b"Session BBB1" in response.content) - self.assertEqual(response.status_code, 200) - - print(" ---> test_live_list_meeting of LivestreamViewsTestCase: OK!") - - def test_live_publish_meeting(self): - self.client = Client() - response = self.client.get("/bbb/live_publish_meeting/1") - self.assertRaises(PermissionDenied) - - self.user = User.objects.get(username="john.doe") - self.user.is_staff = True - self.user.save() - - response = self.client.post("/bbb/live_publish_meeting/1") - # Possible status: 200 or 301 - if response.status_code == 200: - self.assertEqual(response.status_code, 200) - else: - self.assertEqual(response.status_code, 301) - print(" ---> test_live_publish_meeting of LivestreamViewsTestCase:OK!") diff --git a/pod/bbb/urls.py b/pod/bbb/urls.py deleted file mode 100644 index a02d6a45fd..0000000000 --- a/pod/bbb/urls.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.conf.urls import url -from .views import list_meeting, publish_meeting, live_list_meeting -from .views import live_publish_meeting, live_publish_chat - -app_name = "bbb" - -urlpatterns = [ - url(r"^list_meeting/$", list_meeting, name="list_meeting"), - url( - r"^publish_meeting/(?P[\d]+)/$", - publish_meeting, - name="publish_meeting", - ), - url(r"^live_list_meeting/$", live_list_meeting, name="live_list_meeting"), - url( - r"^live_publish_meeting/(?P[\d]+)/$", - live_publish_meeting, - name="live_publish_meeting", - ), - url( - r"^live_publish_chat/(?P[\d]+)/$", - live_publish_chat, - name="live_publish_chat", - ), -] diff --git a/pod/bbb/views.py b/pod/bbb/views.py deleted file mode 100644 index 0b6df81de4..0000000000 --- a/pod/bbb/views.py +++ /dev/null @@ -1,306 +0,0 @@ -# -*- coding: utf-8 -*- -"""Pod BBB views.""" -import json - -from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage -from django.shortcuts import render -from django.conf import settings -from django.urls import reverse -from django.core.exceptions import PermissionDenied -from django.shortcuts import redirect -from django.contrib.admin.views.decorators import staff_member_required -from django.views.decorators.csrf import csrf_protect -from django.utils.translation import ugettext_lazy as _ -from pod.bbb.models import BBB_Meeting as Meeting -from .models import Livestream -from .forms import MeetingForm, LivestreamForm -from django.contrib import messages -from django.shortcuts import get_object_or_404 -from django.utils import timezone -from django.http import JsonResponse, HttpResponseNotAllowed -from django.contrib.auth.decorators import login_required, user_passes_test -import redis - -USE_BBB = getattr(settings, "USE_BBB", False) -USE_BBB_LIVE = getattr(settings, "USE_BBB_LIVE", False) -BBB_NUMBER_MAX_LIVES = getattr(settings, "BBB_NUMBER_MAX_LIVES", 1) - - -@csrf_protect -@login_required(redirect_field_name="referrer") -@staff_member_required(redirect_field_name="referrer") -def list_meeting(request): - """Get meetings list, which recordings are available, ordered by date.""" - meetings_list = Meeting.objects.filter( - attendee__user_id=request.user.id, recording_available=True - ) - meetings_list = meetings_list.order_by("-session_date") - - page = request.GET.get("page", 1) - - full_path = "" - if page: - full_path = ( - request.get_full_path() - .replace("?page=%s" % page, "") - .replace("&page=%s" % page, "") - ) - - paginator = Paginator(meetings_list, 12) - try: - records = paginator.page(page) - except PageNotAnInteger: - records = paginator.page(1) - except EmptyPage: - records = paginator.page(paginator.num_pages) - - if request.is_ajax(): - return render( - request, - "bbb/record_list.html", - {"records": records, "full_path": full_path}, - ) - - return render( - request, - "bbb/list_meeting.html", - { - "records": records, - "full_path": full_path, - "page_title": _("My BigBlueButton records"), - }, - ) - - -@csrf_protect -@staff_member_required(redirect_field_name="referrer") -def publish_meeting(request, id=None): - """Allow you to create a video from a BigBlueButton presentation.""" - record = get_object_or_404(Meeting, id=id) - - initial = { - "id": record.id, - "meeting_id": record.meeting_id, - "internal_meeting_id": record.internal_meeting_id, - "meeting_name": record.meeting_name, - "recorded": record.recorded, - "recording_available": record.recording_available, - "recording_url": record.recording_url, - "thumbnail_url": record.thumbnail_url, - "session_date": record.session_date, - } - - form = MeetingForm(request, initial=initial) - - # Check security: a normal user can publish only a meeting - # where he was moderator - meetings_list = Meeting.objects.filter(attendee__user_id=request.user.id, id=id) - if not meetings_list and not request.user.is_superuser: - messages.add_message( - request, - messages.ERROR, - _("You aren’t the moderator of this BigBlueButton session."), - ) - raise PermissionDenied - - if request.method == "POST": - form = MeetingForm(request, request.POST) - if form.is_valid(): - meeting = form.save(commit=False) - meeting.id = record.id - # The 2 parameters are very important in the publish process: - # At this stage, we put encoding_step=1 (waiting for encoding) - # and the encoded_by = user that convert this presentation. - # See the impacts in the models.py, process_recording function. - # Waiting for encoding - meeting.encoding_step = 1 - # Save the user that convert this presentation - meeting.encoded_by = request.user - meeting.save() - messages.add_message( - request, - messages.INFO, - _("The BigBlueButton session has been published."), - ) - return redirect(reverse("bbb:list_meeting")) - else: - messages.add_message( - request, - messages.ERROR, - _("One or more errors have been found in the form."), - ) - - return render(request, "bbb/publish_meeting.html", {"record": record, "form": form}) - - -@csrf_protect -@login_required(redirect_field_name="referrer") -@staff_member_required(redirect_field_name="referrer") -def live_list_meeting(request): - """Get a list of live meetings in progress.""" - dateSince10Min = timezone.now() - timezone.timedelta(minutes=10) - meetings_list = Meeting.objects.filter( - attendee__user_id=request.user.id, - last_date_in_progress__gte=dateSince10Min, - ) - meetings_list = meetings_list.order_by("-session_date") - - meetings_list = check_meetings_have_live_in_progress(meetings_list, request) - - # Get number of lives in progress - lives_in_progress = Livestream.objects.filter(status=1) - if len(lives_in_progress) >= BBB_NUMBER_MAX_LIVES: - max_limit_reached = True - else: - max_limit_reached = False - - page = request.GET.get("page", 1) - - full_path = "" - if page: - full_path = ( - request.get_full_path() - .replace("?page=%s" % page, "") - .replace("&page=%s" % page, "") - ) - - paginator = Paginator(meetings_list, 12) - try: - records = paginator.page(page) - except PageNotAnInteger: - records = paginator.page(1) - except EmptyPage: - records = paginator.page(paginator.num_pages) - - if request.is_ajax(): - return render( - request, - "bbb/live_record_list.html", - { - "records": records, - "full_path": full_path, - "max_limit_reached": max_limit_reached, - }, - ) - - return render( - request, - "bbb/live_list_meeting.html", - { - "records": records, - "full_path": full_path, - "max_limit_reached": max_limit_reached, - "page_title": _("Perform a BigBlueButton live"), - }, - ) - - -def check_meetings_have_live_in_progress(meetings_list, request): - """Check if these meetings have a live in progress.""" - dateToday = timezone.now() - timezone.timedelta(days=1) - if len(meetings_list) > 0: - for meeting in meetings_list: - # Get the live object that corresponds to this meeting in progress - lives_list = Livestream.objects.filter( - user_id=request.user.id, - start_date__gte=dateToday, - meeting_id=meeting.id, - ) - if len(lives_list) > 0: - # Use case: only 1 live for a meeting - for live in lives_list: - # Add the information directly on the meeting - # on a specific property live - meeting.live = live - return meetings_list - - -@csrf_protect -@staff_member_required(redirect_field_name="referrer") -def live_publish_meeting(request, id=None): - """Allow you to create a live streaming from a BigBlueButton presentation.""" - record = get_object_or_404(Meeting, id=id) - - initial = {"meeting": record, "status": 0, "end_date": None, "server": None} - - form = LivestreamForm(request, initial=initial) - - # Check security: a normal user can publish only a meeting - # where he was moderator - meetings_list = Meeting.objects.filter(attendee__user_id=request.user.id, id=id) - if not meetings_list and not request.user.is_superuser: - messages.add_message( - request, - messages.ERROR, - _("You aren’t the moderator of this BigBlueButton session."), - ) - raise PermissionDenied - - if request.method == "POST": - form = LivestreamForm(request, request.POST) - if form.is_valid(): - live = form.save(commit=False) - live.meeting = record - live.user = request.user - live.save() - messages.add_message( - request, - messages.INFO, - _("The BigBlueButton live has been performed."), - ) - return redirect(reverse("bbb:live_list_meeting")) - else: - messages.add_message( - request, - messages.ERROR, - _("One or more errors have been found in the form."), - ) - - return render( - request, - "bbb/live_publish_meeting.html", - {"record": record, "form": form}, - ) - - -def live_publish_chat_if_authenticated(user): - if user.__str__() == "AnonymousUser": - return False - return True - - -@csrf_protect -@user_passes_test(live_publish_chat_if_authenticated, redirect_field_name="referrer") -def live_publish_chat(request, id=None): - if request.method != "POST": - return HttpResponseNotAllowed(["POST"]) - - """Allow an authenticated user to send chat question to BBB.""" - who_sent = "(%s %s) " % (request.user.first_name, request.user.last_name) - - body_unicode = request.body.decode("utf-8") - body_data = json.loads(body_unicode) - message = body_data["message"] - - livestreams_list = Livestream.objects.filter(broadcaster_id=id) - - data = {"message_return": "error_no_broadcaster_found", "is_sent": False} - if len(livestreams_list) > 0: - for livestream in livestreams_list: - try: - # Publish on Redis - r = redis.Redis( - host=livestream.redis_hostname, - port=str(livestream.redis_port), - db=0, - ) - r.publish(livestream.redis_channel, who_sent + message) - - data = {"message_return": "message_sent", "is_sent": True} - except Exception: - data = { - "message_return": "error_no_connection", - "is_sent": False, - } - - return JsonResponse(data) diff --git a/pod/import_video/static/css/import_video.css b/pod/import_video/static/css/import_video.css index 3f82647ca6..15e642c0cc 100644 --- a/pod/import_video/static/css/import_video.css +++ b/pod/import_video/static/css/import_video.css @@ -44,58 +44,17 @@ } /* Message error */ -div.alert.alert-dismissible { - border-radius: 6px; - display: table; - width: 100%; - padding-left: 78px; - position: relative; - padding-right: 60px; +i.bi.bi-exclamation-circle.me-2 { + color: #c82630; } - -div.alert .icon { - text-align: center; - width: 58px; - height: 100%; - position: absolute; - top: 0; - left: 0; - border: 1px solid #bdbdbd; - padding-top: 15px; - border-radius: 6px 0 0 6px; +i.bi.bi-exclamation-triangle.me-2 { + color: #f9af2c; } - -div.alert .icon i { - font-size: 20px; - color: #fff; - left: 21px; - margin-top: -15px; - position: absolute; - top: 50%; +i.bi.bi-check-circle.me-2 { + color: #00986a; } - -div.alert.alert-success .icon, -div.alert.alert-success .icon::after { - border-color: none; - background: #00986a; -} - -div.alert.alert-info .icon, -div.alert.alert-info .icon::after { - border-color: none; - background: #00b3c8; -} - -div.alert.alert-warning .icon, -div.alert.alert-warning .icon::after { - border: none; - background: #f9af2c; -} - -div.alert.alert-error .icon, -div.alert.alert-error .icon::after { - border-color: none; - background: #c82630; +i.bi.bi-info-circle.me-2 { + color: #00b3c8; } div.alert .proposition { diff --git a/pod/live/templates/live/event-script.html b/pod/live/templates/live/event-script.html index be5390bcdc..281cdc7c08 100644 --- a/pod/live/templates/live/event-script.html +++ b/pod/live/templates/live/event-script.html @@ -733,7 +733,7 @@ }).then((data) => { document.getElementById('live_meeting_chat_form').reset(); console.info(data); - if (data.res) { + if (data.is_sent) { // Message_sent displayReturnMessage("info", data.message_return); } else { diff --git a/pod/locale/fr/LC_MESSAGES/django.po b/pod/locale/fr/LC_MESSAGES/django.po index 6efefaccd0..67729f5439 100644 --- a/pod/locale/fr/LC_MESSAGES/django.po +++ b/pod/locale/fr/LC_MESSAGES/django.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Pod\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-19 07:39+0000\n" +"POT-Creation-Date: 2024-08-26 13:54+0200\n" "PO-Revision-Date: \n" "Last-Translator: obado \n" "Language-Team: Pod Team cotech-esup-pod@esup-portail.org\n" @@ -265,8 +265,6 @@ msgstr "Supprimer cette amélioration" #: pod/ai_enhancement/views.py #: pod/authentication/templates/userpicture/userpicture.html #: pod/authentication/tests/test_views.py -#: pod/bbb/templates/bbb/live_publish_meeting.html -#: pod/bbb/templates/bbb/publish_meeting.html pod/bbb/views.py #: pod/completion/templates/video_caption_maker.html #: pod/dressing/templates/dressing_delete.html #: pod/dressing/templates/dressing_edit.html pod/dressing/views.py @@ -305,8 +303,6 @@ msgstr "Une ou plusieurs erreurs ont été trouvées dans le formulaire." #: pod/ai_enhancement/templates/create_enhancement.html #: pod/ai_enhancement/templates/delete_enhancement.html #: pod/authentication/templates/userpicture/userpicture.html -#: pod/bbb/templates/bbb/live_publish_meeting.html -#: pod/bbb/templates/bbb/publish_meeting.html #: pod/completion/templates/video_caption_maker.html #: pod/dressing/templates/dressing_delete.html #: pod/dressing/templates/dressing_edit.html @@ -696,7 +692,6 @@ msgid "Change your picture" msgstr "Changer votre image de profil" #: pod/authentication/templates/userpicture/userpicture.html -#: pod/bbb/templates/bbb/list_meeting.html #: pod/completion/templates/video_caption_maker.html #: pod/import_video/templates/import_video/add_or_edit.html #: pod/live/templates/live/direct.html @@ -724,553 +719,6 @@ msgstr "Fermer" msgid "Save changes" msgstr "Sauvegarder" -#: pod/bbb/admin.py pod/video/admin.py -msgid "Encode selected" -msgstr "(Ré)encoder la sélection" - -#: pod/bbb/forms.py -msgid "" -"It is not possible to re-encode a recording that was not originally encoded " -"by an user." -msgstr "" -"Il n’est pas possible de ré-encoder un enregistrement qui n’a pas été encodé " -"initialement par un utilisateur." - -#: pod/bbb/models.py -msgid "Meeting id" -msgstr "Identifiant de la session" - -#: pod/bbb/models.py -msgid "Id of the BBB meeting." -msgstr "Identifiant de la session BBB." - -#: pod/bbb/models.py -msgid "Internal meeting id" -msgstr "Identifiant interne de la session" - -#: pod/bbb/models.py -msgid "Internal id of the BBB meeting." -msgstr "Identifiant interne de la session BBB." - -#: pod/bbb/models.py pod/meeting/views.py -msgid "Meeting name" -msgstr "Nom de la session" - -#: pod/bbb/models.py -msgid "Name of the BBB meeting." -msgstr "Nom de la session BBB." - -#: pod/bbb/models.py -msgid "Session date" -msgstr "Date de session" - -#: pod/bbb/models.py -msgid "Publish is possible" -msgstr "La publication est possible" - -#: pod/bbb/models.py pod/bbb/templates/bbb/card.html -msgid "Waiting for encoding" -msgstr "En attente d’encodage" - -#: pod/bbb/models.py pod/bbb/templates/bbb/card.html pod/video/models.py -msgid "Encoding in progress" -msgstr "Encodage en cours" - -#: pod/bbb/models.py pod/bbb/templates/bbb/card.html -msgid "Already published" -msgstr "Déjà publié" - -#: pod/bbb/models.py pod/video/models.py pod/video_encode_transcript/models.py -msgid "Encoding step" -msgstr "Étape de l’encodage" - -#: pod/bbb/models.py -msgid "Encoding step for conversion of the BBB presentation to video file." -msgstr "" -"Étape d’encodage pour la conversion de la présentation BBB en fichier vidéo." - -#: pod/bbb/models.py -msgid "Recorded" -msgstr "Enregistrée" - -#: pod/bbb/models.py -msgid "BBB presentation recorded?" -msgstr "La présentation BBB a-t-elle été enregistrée ?" - -#: pod/bbb/models.py -msgid "Recording available" -msgstr "Enregistrement disponible" - -#: pod/bbb/models.py -msgid "BBB presentation recording is available?" -msgstr "Un enregistrement de la présentation BBB est-il disponible ?" - -#: pod/bbb/models.py -msgid "Recording url" -msgstr "URL de l’enregistrement" - -#: pod/bbb/models.py -msgid "URL of the recording of the BBB presentation." -msgstr "URL de l’enregistrement de la présentation BBB." - -#: pod/bbb/models.py -msgid "Thumbnail url" -msgstr "URL de la vignette" - -#: pod/bbb/models.py -msgid "URL of the recording thumbnail of the BBB presentation." -msgstr "" -"URL de la vignette correspondant à l’enregistrement de la présentation BBB." - -#: pod/bbb/models.py pod/import_video/models.py pod/meeting/models.py -#: pod/playlist/models.py pod/recorder/models.py pod/video/models.py -msgid "User" -msgstr "Utilisateur" - -#: pod/bbb/models.py -msgid "User who converted the BBB presentation to video file." -msgstr "Utilisateur ayant converti la présentation BBB en fichier vidéo." - -#: pod/bbb/models.py -msgid "Last date in progress" -msgstr "Dernière date en cours" - -#: pod/bbb/models.py -msgid "Last date where BBB session was in progress." -msgstr "Dernière date à laquelle la session BBB était en cours." - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Meeting" -msgstr "Session" - -#: pod/bbb/models.py pod/meeting/apps.py -msgid "Meetings" -msgstr "Sessions" - -#: pod/bbb/models.py pod/meeting/views.py -msgid "Full name" -msgstr "Nom complet" - -#: pod/bbb/models.py -msgid "Full name of the user from BBB." -msgstr "Nom complet de l’utilisateur dans BBB." - -#: pod/bbb/models.py -msgid "User role" -msgstr "Rôle de l’utilisateur" - -#: pod/bbb/models.py -msgid "Role of the user from BBB." -msgstr "Rôle de l’utilisateur dans BBB." - -#: pod/bbb/models.py -msgid "Username / User id" -msgstr "Username / Identifiant utilisateur" - -#: pod/bbb/models.py -msgid "Username / User id, if the BBB user was matching a Pod user." -msgstr "" -"Nom d’utilisateur / ID utilisateur, si l’utilisateur BBB correspond à un " -"utilisateur existant de Pod." - -#: pod/bbb/models.py -msgid "User from the Pod database, if user found." -msgstr "" -"Utilisateur dans la base de données de Pod, si l’utilisateur est trouvé." - -#: pod/bbb/models.py pod/meeting/views.py -msgid "Attendee" -msgstr "Participant" - -#: pod/bbb/models.py pod/meeting/views.py -msgid "Attendees" -msgstr "Participants" - -#: pod/bbb/models.py pod/import_video/models.py pod/live/forms.py -#: pod/live/models.py pod/meeting/forms.py pod/meeting/models.py -#: pod/video_search/forms.py -msgid "Start date" -msgstr "Date de début" - -#: pod/bbb/models.py -msgid "Start date of the live." -msgstr "Date de début du direct." - -#: pod/bbb/models.py pod/live/forms.py pod/live/models.py -#: pod/video_search/forms.py -msgid "End date" -msgstr "Date de fin" - -#: pod/bbb/models.py -msgid "End date of the live." -msgstr "Date de fin du direct." - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Live not started" -msgstr "Le direct n’a pas encore démarré" - -#: pod/bbb/models.py pod/bbb/templates/bbb/live_card.html pod/meeting/models.py -msgid "Live in progress" -msgstr "Le direct est en cours" - -#: pod/bbb/models.py pod/bbb/templates/bbb/live_card.html pod/meeting/models.py -msgid "Live stopped" -msgstr "Le direct a été arrêté" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Live status" -msgstr "Statut du direct" - -#: pod/bbb/models.py -msgid "Server" -msgstr "Serveur" - -#: pod/bbb/models.py -msgid "Server/process performing the live." -msgstr "Serveur / processus effectuant le direct." - -#: pod/bbb/models.py -msgid "Username / User id, that want to perform the live." -msgstr "" -"Nom d’utilisateur / ID d’utilisateur, qui souhaite effectuer le direct." - -#: pod/bbb/models.py pod/live/models.py pod/live/templates/live/event_card.html -#: pod/meeting/models.py pod/recorder/models.py pod/video/forms.py -msgid "Restricted access" -msgstr "Accès restreint" - -#: pod/bbb/models.py -msgid "Is live only accessible to authenticated users?" -msgstr "Le direct est-il uniquement accessible aux utilisateurs authentifiés ?" - -#: pod/bbb/models.py pod/live/admin.py pod/live/models.py pod/meeting/models.py -msgid "Broadcaster" -msgstr "Diffuseur" - -#: pod/bbb/models.py -msgid "Broadcaster in charge to perform live." -msgstr "Diffuseur en charge de réaliser le direct." - -#: pod/bbb/models.py -msgid "Show public chat" -msgstr "Affichage du chat public" - -#: pod/bbb/models.py -msgid "Do you want to show the public chat in the live?" -msgstr "Souhaitez-vous montrer le chat public en direct ?" - -#: pod/bbb/models.py -msgid "Save meeting in dashboard" -msgstr "Enregistrer la réunion dans le tableau de bord" - -#: pod/bbb/models.py -msgid "" -"Do you want to save the video of this meeting, at the end of the live, " -"directly in “Dashboard”?" -msgstr "" -"Souhaitez-vous enregistrer la vidéo de cette session, à la fin du direct, " -"directement dans le « Tableau de bord » ?" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Enable chat" -msgstr "Activer le chat" - -#: pod/bbb/models.py -msgid "" -"Do you want a chat on the live page for students? Messages sent in this live " -"page’s chat will end up in BigBlueButton’s public chat." -msgstr "" -"Voulez-vous un chat sur la page de direct pour les étudiants ? Les messages " -"envoyés dans le chat de cette page de direct se retrouveront dans le chat " -"public de BigBlueButton." - -#: pod/bbb/models.py -msgid "Redis hostname" -msgstr "Nom d’hôte REDIS" - -#: pod/bbb/models.py -msgid "Redis hostname, useful for chat" -msgstr "Nom d’hôte REDIS, utile pour le chat" - -#: pod/bbb/models.py -msgid "Redis port" -msgstr "Port REDIS" - -#: pod/bbb/models.py -msgid "Redis port, useful for chat" -msgstr "Port REDIS, utile pour le chat" - -#: pod/bbb/models.py -msgid "Redis channel" -msgstr "Channel REDIS" - -#: pod/bbb/models.py -msgid "Redis channel, useful for chat" -msgstr "Channel REDIS, utile pour le chat" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Livestream" -msgstr "Direct" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Livestreams" -msgstr "Directs" - -#: pod/bbb/templates/bbb/card.html pod/bbb/templates/bbb/list_meeting.html -msgid "BigBlueButton presentation preview" -msgstr "Aperçu de la présentation BigBlueButton" - -#: pod/bbb/templates/bbb/card.html -msgid "Publish the BigBlueButton presentation on this platform" -msgstr "Publier la présentation BigBlueButton sur cette plateforme" - -#: pod/bbb/templates/bbb/card.html pod/bbb/templates/bbb/publish_meeting.html -msgid "Publish this presentation" -msgstr "Publier cette présentation" - -#: pod/bbb/templates/bbb/card.html -#: pod/meeting/templates/meeting/internal_recordings.html -msgid "by" -msgstr "par" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "Create a video from a BigBlueButton presentation" -msgstr "Créer une vidéo à partir d’une présentation BigBlueButton" - -#: pod/bbb/templates/bbb/list_meeting.html -#: pod/recorder/templates/recorder/claim_record.html -msgid "No record found" -msgstr "Aucun enregistrement trouvé" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "There are no BigBlueButton records." -msgstr "Il n’y a pas de sessions BigBlueButton." - -#: pod/bbb/templates/bbb/list_meeting.html -#: pod/recorder/templates/recorder/claim_record.html -#, python-format -msgid "%(counter)s record found" -msgid_plural "%(counter)s records found" -msgstr[0] "%(counter)s enregistrement trouvé" -msgstr[1] "%(counter)s enregistrements trouvés" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "" -"This is the list of the recorded BigBlueButton sessions for which you were " -"moderator. This module allows you to create a video from a BigBlueButton " -"presentation." -msgstr "" -"Voici la liste des sessions BigBlueButton enregistrées pour lesquelles vous " -"étiez modérateur. Ce module vous permet de créer une vidéo à partir d’une " -"présentation BigBlueButton." - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "" -"Shortly after the presentation is published, the corresponding video will " -"appear in your videos." -msgstr "" -"Peu de temps après la publication de la présentation, la vidéo " -"correspondante apparaîtra dans vos vidéos." - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "" -"Please note: a BigBlueButton presentation can be encoded by another " -"moderator." -msgstr "" -"Remarque: une présentation BigBlueButton peut être encodée par un autre " -"modérateur." - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "" -"If such a case occurs, this information will be displayed directly in the " -"list. You can then contact this user directly to ask him/her to share the " -"video with you, or even to put you as additional owner of the video." -msgstr "" -"Si un tel cas se produit, ces informations seront affichées directement dans " -"la liste. Vous pouvez alors contacter directement cet utilisateur pour lui " -"demander de partager la vidéo avec vous, voire de vous mettre en tant que " -"propriétaire additionnel de la vidéo." - -#: pod/bbb/templates/bbb/list_meeting.html -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "Please note: this page refreshes automatically every 30 seconds." -msgstr "" -"Remarque : cette page s’actualise automatiquement toutes les 30 secondes." - -#: pod/bbb/templates/bbb/live_card.html -#: pod/bbb/templates/bbb/live_publish_meeting.html pod/bbb/views.py -#: pod/main/templates/navbar.html -msgid "Perform a BigBlueButton live" -msgstr "Réaliser un direct BigBlueButton" - -#: pod/bbb/templates/bbb/live_card.html -msgid "" -"Impossible to perform a BigBlueButton live for the moment (all resources are " -"busy)" -msgstr "" -"Impossible de réaliser un direct BigBlueButton pour le moment (toutes les " -"ressources sont occupées)" - -#: pod/bbb/templates/bbb/live_card.html -msgid "Live not published" -msgstr "Le direct n’a pas été publié" - -#: pod/bbb/templates/bbb/live_card.html -msgid "Live not already started" -msgstr "Le direct n’a pas encore démarré" - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "No BigBlueButton session in progress found" -msgstr "Aucune session BigBlueButton en cours trouvée" - -#: pod/bbb/templates/bbb/live_list_meeting.html -#, python-format -msgid "%(counter)s BigBlueButton session in progress found" -msgid_plural "%(counter)s BigBlueButton sessions in progress found" -msgstr[0] "%(counter)s session BigBlueButton en cours trouvée" -msgstr[1] "%(counter)s sessions BigBlueButton en cours trouvées" - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "" -"This is the list of current BigBlueButton sessions for which you are " -"moderator. This module allows you to make a live stream from this " -"BigBlueButton session (useful if there are more than 100 users)." -msgstr "" -"Voici la liste des sessions BigBlueButton en cours pour lesquelles vous êtes " -"modérateur. Ce module vous permet de réaliser un direct de cette session " -"BigBlueButton (utile s’il y a plus de 100 utilisateurs)." - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "" -"Remember to not use breakout rooms in this case and end the meeting once it " -"is over." -msgstr "" -"N’oubliez pas de ne pas utiliser les salles privées dans ce cas et de mettre " -"fin à la réunion une fois celle-ci terminée." - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "" -"Shortly after clicking the “Perform a BigBlueButton live” button, and select " -"the desired options, the live stream will be available to users on the  Lives " -"page." -msgstr "" -"Peu de temps après avoir cliqué sur le bouton « Réaliser un direct " -"BigBlueButton » et sélectionné les options souhaitées, le direct sera " -"disponible pour les utilisateurs sur la page  Directs." - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "There are no BigBlueButton sessions in progress." -msgstr "Aucune session BigBlueButton n’est en cours." - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "Confirmation of performing a BigBlueButton live" -msgstr "Confirmation de la réalisation d’un direct BigBlueButton" - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "Are you sure you want to perform a BigBlueButton live?" -msgstr "Êtes-vous sûr de vouloir réaliser un direct BigBlueButton ?" - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "" -"Select the desired options than validate this form by clicking \"Perform a " -"BigBlueButton live\"." -msgstr "" -"Sélectionnez les options souhaitées puis validez ce formulaire en cliquant " -"sur « Réaliser un direct BigBlueButton »." - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "" -"This live will be stopped automatically when BigBlueButton session ends." -msgstr "" -"Ce direct sera arrêté automatiquement à la fin de la session BigBlueButton." - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "" -"Please note: shortly after clicking the “Perform a BigBlueButton live” " -"button, the live stream will be available to users on the  Lives page." -msgstr "" -"Remarque : peu de temps après avoir cliqué sur le bouton « Réaliser un " -"direct BigBlueButton » le direct sera disponible pour les utilisateurs sur " -"la page  Directs." - -#: pod/bbb/templates/bbb/live_record_list.html -msgid "Sorry, no BigBlueButton session in progress found" -msgstr "Désolé, aucune session BigBlueButton en cours n’a été trouvée" - -#: pod/bbb/templates/bbb/live_record_list.html -#: pod/bbb/templates/bbb/record_list.html -#: pod/playlist/templates/playlist/playlist-videos-list.html -#: pod/recorder/templates/recorder/record_list.html -#: pod/video/templates/videos/video_list.html -#: pod/video/templates/videos/video_list_grid_selectable.html -#: pod/video/templates/videos/video_list_table_selectable.html -msgid "More" -msgstr "Plus" - -#: pod/bbb/templates/bbb/live_record_list.html -#: pod/bbb/templates/bbb/record_list.html pod/main/templates/loader.html -#: pod/playlist/templates/playlist/playlist-videos-list.html -#: pod/podfile/templates/podfile/home_content.html -#: pod/podfile/templates/podfile/list_folder_files.html -#: pod/recorder/templates/recorder/record_list.html -#: pod/video/templates/videos/dashboard_modal.html -#: pod/video/templates/videos/video_edit.html -#: pod/video/templates/videos/video_list.html -#: pod/video/templates/videos/video_list_grid_selectable.html -#: pod/video/templates/videos/video_list_table_selectable.html -msgid "Loading…" -msgstr "Chargement en cours…" - -#: pod/bbb/templates/bbb/publish_meeting.html pod/bbb/views.py -#: pod/main/templates/navbar.html -msgid "My BigBlueButton records" -msgstr "Mes sessions BigBlueButton" - -#: pod/bbb/templates/bbb/publish_meeting.html -msgid "Publish the BigBlueButton presentation" -msgstr "Publier la présentation BigBlueButton" - -#: pod/bbb/templates/bbb/publish_meeting.html -msgid "Are you sure you want to publish this BigBlueButton presentation?" -msgstr "Voulez-vous vraiment publier cette présentation BigBlueButton ?" - -#: pod/bbb/templates/bbb/publish_meeting.html -msgid "" -"A video will be created from the BigBlueButton presentation and will be " -"available on this platform." -msgstr "" -"Une vidéo sera créée à partir de la présentation BigBlueButton et sera " -"disponible sur cette plateforme." - -#: pod/bbb/templates/bbb/publish_meeting.html -msgid "" -"Please note: this treatment can take a long time. You will receive an email " -"when this treatment is completed." -msgstr "" -"Attention: ce traitement peut durer longtemps. Vous recevrez un e-mail " -"lorsque ce traitement sera terminé." - -#: pod/bbb/templates/bbb/record_list.html -#: pod/recorder/templates/recorder/record_list.html -msgid "Sorry, no record found" -msgstr "Désolé, aucun enregistrement trouvé" - -#: pod/bbb/views.py -msgid "You aren’t the moderator of this BigBlueButton session." -msgstr "Vous n’êtes pas le modérateur de cette session BigBlueButton." - -#: pod/bbb/views.py -msgid "The BigBlueButton session has been published." -msgstr "La session BigBlueButton a été publiée." - -#: pod/bbb/views.py -msgid "The BigBlueButton live has been performed." -msgstr "Le direct BigBlueButton a été publié et va bientôt démarrer." - #: pod/chapter/apps.py pod/chapter/models.py #: pod/chapter/templates/video_chapter.html #: pod/video/templates/videos/video-element.html @@ -3009,6 +2457,16 @@ msgstr "" "Veuillez entrer un nom qui vous permettra de retrouver facilement cet " "enregistrement." +#: pod/import_video/models.py pod/live/forms.py pod/live/models.py +#: pod/meeting/forms.py pod/meeting/models.py pod/video_search/forms.py +msgid "Start date" +msgstr "Date de début" + +#: pod/import_video/models.py pod/meeting/models.py pod/playlist/models.py +#: pod/recorder/models.py pod/video/models.py +msgid "User" +msgstr "Utilisateur" + #: pod/import_video/models.py pod/meeting/models.py msgid "User who create this recording" msgstr "Utilisateur qui a créé cet enregistrement" @@ -3610,6 +3068,10 @@ msgstr "Oui" msgid "No" msgstr "Non" +#: pod/live/admin.py pod/live/models.py pod/meeting/models.py +msgid "Broadcaster" +msgstr "Diffuseur" + #: pod/live/admin.py msgid "Auto start admin" msgstr "Enregistrement auto" @@ -3634,6 +3096,10 @@ msgstr "" "Sélectionner l’implémentation de pilotage des enregistrements pour ce " "diffuseur." +#: pod/live/forms.py pod/live/models.py pod/video_search/forms.py +msgid "End date" +msgstr "Date de fin" + #: pod/live/forms.py msgid "End should not be in the past" msgstr "La fin d’un évènement ne peut pas être planifiée dans le passé" @@ -3733,6 +3199,11 @@ msgstr "Activer le compteur de spectateurs" msgid "Enable viewers count on live." msgstr "Active le compteur de spectateurs sur le direct." +#: pod/live/models.py pod/live/templates/live/event_card.html +#: pod/meeting/models.py pod/recorder/models.py pod/video/forms.py +msgid "Restricted access" +msgstr "Accès restreint" + #: pod/live/models.py msgid "Live is accessible only to authenticated users." msgstr "" @@ -5586,6 +5057,19 @@ msgstr "Version" msgid "videos available" msgstr "vidéos disponibles" +#: pod/main/templates/loader.html +#: pod/playlist/templates/playlist/playlist-videos-list.html +#: pod/podfile/templates/podfile/home_content.html +#: pod/podfile/templates/podfile/list_folder_files.html +#: pod/recorder/templates/recorder/record_list.html +#: pod/video/templates/videos/dashboard_modal.html +#: pod/video/templates/videos/video_edit.html +#: pod/video/templates/videos/video_list.html +#: pod/video/templates/videos/video_list_grid_selectable.html +#: pod/video/templates/videos/video_list_table_selectable.html +msgid "Loading…" +msgstr "Chargement en cours…" + #: pod/main/templates/mail/mail.html pod/main/templates/mail/mail_sender.html msgid "Hello" msgstr "Bonjour" @@ -5882,6 +5366,10 @@ msgstr "Modérateurs" msgid "Viewers" msgstr "Spectateurs" +#: pod/meeting/apps.py +msgid "Meetings" +msgstr "Sessions" + #: pod/meeting/forms.py pod/video/feeds.py pod/video/models.py #: pod/video/templates/videos/video_row_select.html #: pod/video/templates/videos/video_sort_select.html @@ -6297,6 +5785,10 @@ msgstr "" "BigBlueButton, et les auditeurs auront un accès via un direct dans la page " "des directs." +#: pod/meeting/models.py +msgid "Enable chat" +msgstr "Activer le chat" + #: pod/meeting/models.py msgid "" "Do you want a chat on the live page for listeners? Messages sent in this " @@ -6328,6 +5820,10 @@ msgstr "Impossible d’obtenir les enregistrements de la réunion !" msgid "Unable to delete recording!" msgstr "Impossible de supprimer l’enregistrement !" +#: pod/meeting/models.py +msgid "Meeting" +msgstr "Session" + #: pod/meeting/models.py msgid "meeting" msgstr "reunion" @@ -6369,6 +5865,22 @@ msgstr "Exemple de format : rtmp://live.univ.fr/live/name" msgid "Broadcaster in charge to perform lives." msgstr "Diffuseur chargé de réaliser des directs." +#: pod/meeting/models.py +msgid "URL of the SIPMediaGW server" +msgstr "Adresse URL du serveur SIPMediaGW" + +#: pod/meeting/models.py +msgid "Example format: https://sipmediagw.univ.fr" +msgstr "Exemple de format : https://sipmediagw.univ.fr" + +#: pod/meeting/models.py +msgid "Bearer token for the SIPMediaGW server." +msgstr "Jeton Bearer pour le serveur SIPMediaGW." + +#: pod/meeting/models.py +msgid "Example format: 1234" +msgstr "Exemple de format : 1234" + #: pod/meeting/models.py msgid "Live gateway" msgstr "Passerelle de live" @@ -6377,6 +5889,22 @@ msgstr "Passerelle de live" msgid "Live gateways" msgstr "Passerelles de live" +#: pod/meeting/models.py +msgid "Live not started" +msgstr "Le direct n’a pas encore démarré" + +#: pod/meeting/models.py +msgid "Live in progress" +msgstr "Le direct est en cours" + +#: pod/meeting/models.py +msgid "Live stopped" +msgstr "Le direct a été arrêté" + +#: pod/meeting/models.py +msgid "Live status" +msgstr "Statut du direct" + #: pod/meeting/models.py msgid "Event managed for this live" msgstr "Gestion de l’événement pour ce direct" @@ -6393,6 +5921,14 @@ msgstr "Passerelle de live utilisé pour ce direct" msgid "Live gateway (encoder and broadcaster) that perform the livestream" msgstr "Passerelle de live (encodeur et diffuseur) qui réalise la diffusion" +#: pod/meeting/models.py +msgid "Livestream" +msgstr "Direct" + +#: pod/meeting/models.py +msgid "Livestreams" +msgstr "Directs" + #: pod/meeting/templates/meeting/add_or_edit.html #: pod/meeting/templates/meeting/link_meeting.html pod/meeting/views.py msgid "Edit the meeting" @@ -6616,6 +6152,10 @@ msgstr "" msgid "Toolbar" msgstr "Barre d’outils" +#: pod/meeting/templates/meeting/internal_recordings.html +msgid "by" +msgstr "par" + #: pod/meeting/templates/meeting/internal_recordings.html msgid "Please confirm you want to upload the recording To Pod" msgstr "" @@ -6826,6 +6366,10 @@ msgstr "" "Un nouvel enregistrement Big Blue Button pour la réunion “%(content_title)s” " "est maintenant disponible sur %(site_title)s." +#: pod/meeting/views.py +msgid "Meeting name" +msgstr "Nom de la session" + #: pod/meeting/views.py msgid "Has user joined?" msgstr "Utilisateurs connectés ?" @@ -6846,6 +6390,18 @@ msgstr "Nombre d’auditeurs" msgid "Moderator count" msgstr "Nombre de modérateurs" +#: pod/meeting/views.py +msgid "Attendees" +msgstr "Participants" + +#: pod/meeting/views.py +msgid "Attendee" +msgstr "Participant" + +#: pod/meeting/views.py +msgid "Full name" +msgstr "Nom complet" + #: pod/meeting/views.py msgid "You cannot edit this meeting." msgstr "Vous ne pouvez pas éditer cette réunion." @@ -7428,6 +6984,14 @@ msgstr "Gérer la liste de lecture" msgid "Sorry, no video found." msgstr "Désolé, aucune vidéo trouvée." +#: pod/playlist/templates/playlist/playlist-videos-list.html +#: pod/recorder/templates/recorder/record_list.html +#: pod/video/templates/videos/video_list.html +#: pod/video/templates/videos/video_list_grid_selectable.html +#: pod/video/templates/videos/video_list_table_selectable.html +msgid "More" +msgstr "Plus" + #: pod/playlist/templates/playlist/playlist.html #: pod/playlist/templates/playlist/playlist_content.html #: pod/video/templates/channel/channel.html @@ -8631,6 +8195,17 @@ msgstr "Ajouter un nouvel enregistrement" msgid "Date of recording." msgstr "Date de l’enregistrement." +#: pod/recorder/templates/recorder/claim_record.html +#, python-format +msgid "%(counter)s record found" +msgid_plural "%(counter)s records found" +msgstr[0] "%(counter)s enregistrement trouvé" +msgstr[1] "%(counter)s enregistrements trouvés" + +#: pod/recorder/templates/recorder/claim_record.html +msgid "No record found" +msgstr "Aucun enregistrement trouvé" + #: pod/recorder/templates/recorder/claim_record.html msgid "There is no unassigned records" msgstr "Il n’y a pas d’enregistrement non assigné" @@ -8688,6 +8263,10 @@ msgstr "" "Pour supprimer l’enregistrement, veuillez cocher la case et cliquer sur " "« Supprimer »." +#: pod/recorder/templates/recorder/record_list.html +msgid "Sorry, no record found" +msgstr "Désolé, aucun enregistrement trouvé" + #: pod/recorder/views.py msgid "Recorder should be indicated." msgstr "L’enregistreur doit être indiqué." @@ -8873,6 +8452,10 @@ msgstr "Encodé ?" msgid "Set as draft" msgstr "Définir comme brouillon" +#: pod/video/admin.py +msgid "Encode selected" +msgstr "(Ré)encoder la sélection" + #: pod/video/admin.py msgid "Transcript selected" msgstr "Transcrire la sélection" @@ -9510,6 +9093,10 @@ msgstr "La chaîne où vous voulez que votre contenu apparaisse." msgid "Overview" msgstr "Vue d’ensemble" +#: pod/video/models.py +msgid "Encoding in progress" +msgstr "Encodage en cours" + #: pod/video/models.py msgid "Is Video" msgstr "Est une vidéo" @@ -9535,6 +9122,10 @@ msgstr "Somme des vues" msgid "Sum of view of last %(ndays)s days" msgstr "Somme des vues des %(ndays)s derniers jours" +#: pod/video/models.py pod/video_encode_transcript/models.py +msgid "Encoding step" +msgstr "Étape de l’encodage" + #: pod/video/models.py msgid "Is the video encoded?" msgstr "Est-ce que la vidéo est encodée ?" @@ -11237,37 +10828,3 @@ msgstr "Résultats de la recherche" #: pod/xapi/apps.py msgid "Esup-Pod xAPI" msgstr "xAPI Esup-Pod" - -#~ msgid "The video has been deleted." -#~ msgstr "La vidéo a été supprimée." - -#~ msgid "You cannot delete this video." -#~ msgstr "Vous ne pouvez pas supprimer cette vidéo." - -#~ msgid "Long answer" -#~ msgstr "Réponse longue" - -#~ msgid "Long answer question" -#~ msgstr "Question à réponse longue" - -#~ msgid "Write a long answer." -#~ msgstr "Écrivez une réponse longue." - -#~ msgid "Please choose an answer." -#~ msgstr "Veuillez choisir une réponse." - -#~ msgid "Long answer questions" -#~ msgstr "Questions à réponse longue" - -#~ msgid "Restricted" -#~ msgstr "Restreint" - -#~ msgid "" -#~ "The video was treated by Aristote. You must verify and validate the " -#~ "processing by pressing the robot icon." -#~ msgstr "" -#~ "La vidéo a été traitée par Aristote. Vous devez vérifier et valider le " -#~ "traitement en appuyant sur l’icône du robot." - -#~ msgid "Quiz(zes)" -#~ msgstr "Quiz" diff --git a/pod/locale/fr/LC_MESSAGES/djangojs.po b/pod/locale/fr/LC_MESSAGES/djangojs.po index 747c22368b..dbba910bef 100644 --- a/pod/locale/fr/LC_MESSAGES/djangojs.po +++ b/pod/locale/fr/LC_MESSAGES/djangojs.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Esup-Pod\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-19 07:39+0000\n" +"POT-Creation-Date: 2024-08-26 13:54+0200\n" "PO-Revision-Date: \n" "Last-Translator: obado \n" "Language-Team: \n" diff --git a/pod/locale/nl/LC_MESSAGES/django.po b/pod/locale/nl/LC_MESSAGES/django.po index 5b7b837fd5..9e92bc7c3b 100644 --- a/pod/locale/nl/LC_MESSAGES/django.po +++ b/pod/locale/nl/LC_MESSAGES/django.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Pod\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-19 07:39+0000\n" +"POT-Creation-Date: 2024-08-26 13:54+0200\n" "PO-Revision-Date: 2024-07-04 17:54+0200\n" "Last-Translator: obado \n" "Language-Team: \n" @@ -254,8 +254,6 @@ msgstr "" #: pod/ai_enhancement/views.py #: pod/authentication/templates/userpicture/userpicture.html #: pod/authentication/tests/test_views.py -#: pod/bbb/templates/bbb/live_publish_meeting.html -#: pod/bbb/templates/bbb/publish_meeting.html pod/bbb/views.py #: pod/completion/templates/video_caption_maker.html #: pod/dressing/templates/dressing_delete.html #: pod/dressing/templates/dressing_edit.html pod/dressing/views.py @@ -294,8 +292,6 @@ msgstr "" #: pod/ai_enhancement/templates/create_enhancement.html #: pod/ai_enhancement/templates/delete_enhancement.html #: pod/authentication/templates/userpicture/userpicture.html -#: pod/bbb/templates/bbb/live_publish_meeting.html -#: pod/bbb/templates/bbb/publish_meeting.html #: pod/completion/templates/video_caption_maker.html #: pod/dressing/templates/dressing_delete.html #: pod/dressing/templates/dressing_edit.html @@ -670,7 +666,6 @@ msgid "Change your picture" msgstr "" #: pod/authentication/templates/userpicture/userpicture.html -#: pod/bbb/templates/bbb/list_meeting.html #: pod/completion/templates/video_caption_maker.html #: pod/import_video/templates/import_video/add_or_edit.html #: pod/live/templates/live/direct.html @@ -698,506 +693,6 @@ msgstr "" msgid "Save changes" msgstr "" -#: pod/bbb/admin.py pod/video/admin.py -msgid "Encode selected" -msgstr "" - -#: pod/bbb/forms.py -msgid "" -"It is not possible to re-encode a recording that was not originally encoded " -"by an user." -msgstr "" - -#: pod/bbb/models.py -msgid "Meeting id" -msgstr "" - -#: pod/bbb/models.py -msgid "Id of the BBB meeting." -msgstr "" - -#: pod/bbb/models.py -msgid "Internal meeting id" -msgstr "" - -#: pod/bbb/models.py -msgid "Internal id of the BBB meeting." -msgstr "" - -#: pod/bbb/models.py pod/meeting/views.py -msgid "Meeting name" -msgstr "" - -#: pod/bbb/models.py -msgid "Name of the BBB meeting." -msgstr "" - -#: pod/bbb/models.py -msgid "Session date" -msgstr "" - -#: pod/bbb/models.py -msgid "Publish is possible" -msgstr "" - -#: pod/bbb/models.py pod/bbb/templates/bbb/card.html -msgid "Waiting for encoding" -msgstr "" - -#: pod/bbb/models.py pod/bbb/templates/bbb/card.html pod/video/models.py -msgid "Encoding in progress" -msgstr "" - -#: pod/bbb/models.py pod/bbb/templates/bbb/card.html -msgid "Already published" -msgstr "" - -#: pod/bbb/models.py pod/video/models.py pod/video_encode_transcript/models.py -msgid "Encoding step" -msgstr "" - -#: pod/bbb/models.py -msgid "Encoding step for conversion of the BBB presentation to video file." -msgstr "" - -#: pod/bbb/models.py -msgid "Recorded" -msgstr "" - -#: pod/bbb/models.py -msgid "BBB presentation recorded?" -msgstr "" - -#: pod/bbb/models.py -msgid "Recording available" -msgstr "" - -#: pod/bbb/models.py -msgid "BBB presentation recording is available?" -msgstr "" - -#: pod/bbb/models.py -msgid "Recording url" -msgstr "" - -#: pod/bbb/models.py -msgid "URL of the recording of the BBB presentation." -msgstr "" - -#: pod/bbb/models.py -msgid "Thumbnail url" -msgstr "" - -#: pod/bbb/models.py -msgid "URL of the recording thumbnail of the BBB presentation." -msgstr "" - -#: pod/bbb/models.py pod/import_video/models.py pod/meeting/models.py -#: pod/playlist/models.py pod/recorder/models.py pod/video/models.py -msgid "User" -msgstr "" - -#: pod/bbb/models.py -msgid "User who converted the BBB presentation to video file." -msgstr "" - -#: pod/bbb/models.py -msgid "Last date in progress" -msgstr "" - -#: pod/bbb/models.py -msgid "Last date where BBB session was in progress." -msgstr "" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Meeting" -msgstr "" - -#: pod/bbb/models.py pod/meeting/apps.py -msgid "Meetings" -msgstr "" - -#: pod/bbb/models.py pod/meeting/views.py -msgid "Full name" -msgstr "" - -#: pod/bbb/models.py -msgid "Full name of the user from BBB." -msgstr "" - -#: pod/bbb/models.py -msgid "User role" -msgstr "" - -#: pod/bbb/models.py -msgid "Role of the user from BBB." -msgstr "" - -#: pod/bbb/models.py -msgid "Username / User id" -msgstr "" - -#: pod/bbb/models.py -msgid "Username / User id, if the BBB user was matching a Pod user." -msgstr "" - -#: pod/bbb/models.py -msgid "User from the Pod database, if user found." -msgstr "" - -#: pod/bbb/models.py pod/meeting/views.py -msgid "Attendee" -msgstr "" - -#: pod/bbb/models.py pod/meeting/views.py -msgid "Attendees" -msgstr "" - -#: pod/bbb/models.py pod/import_video/models.py pod/live/forms.py -#: pod/live/models.py pod/meeting/forms.py pod/meeting/models.py -#: pod/video_search/forms.py -msgid "Start date" -msgstr "" - -#: pod/bbb/models.py -msgid "Start date of the live." -msgstr "" - -#: pod/bbb/models.py pod/live/forms.py pod/live/models.py -#: pod/video_search/forms.py -msgid "End date" -msgstr "" - -#: pod/bbb/models.py -msgid "End date of the live." -msgstr "" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Live not started" -msgstr "" - -#: pod/bbb/models.py pod/bbb/templates/bbb/live_card.html pod/meeting/models.py -msgid "Live in progress" -msgstr "" - -#: pod/bbb/models.py pod/bbb/templates/bbb/live_card.html pod/meeting/models.py -msgid "Live stopped" -msgstr "" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Live status" -msgstr "" - -#: pod/bbb/models.py -msgid "Server" -msgstr "" - -#: pod/bbb/models.py -msgid "Server/process performing the live." -msgstr "" - -#: pod/bbb/models.py -msgid "Username / User id, that want to perform the live." -msgstr "" - -#: pod/bbb/models.py pod/live/models.py pod/live/templates/live/event_card.html -#: pod/meeting/models.py pod/recorder/models.py pod/video/forms.py -msgid "Restricted access" -msgstr "" - -#: pod/bbb/models.py -msgid "Is live only accessible to authenticated users?" -msgstr "" - -#: pod/bbb/models.py pod/live/admin.py pod/live/models.py pod/meeting/models.py -msgid "Broadcaster" -msgstr "" - -#: pod/bbb/models.py -msgid "Broadcaster in charge to perform live." -msgstr "" - -#: pod/bbb/models.py -msgid "Show public chat" -msgstr "" - -#: pod/bbb/models.py -msgid "Do you want to show the public chat in the live?" -msgstr "" - -#: pod/bbb/models.py -msgid "Save meeting in dashboard" -msgstr "" - -#: pod/bbb/models.py -msgid "" -"Do you want to save the video of this meeting, at the end of the live, " -"directly in “Dashboard”?" -msgstr "" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Enable chat" -msgstr "" - -#: pod/bbb/models.py -msgid "" -"Do you want a chat on the live page for students? Messages sent in this live " -"page’s chat will end up in BigBlueButton’s public chat." -msgstr "" - -#: pod/bbb/models.py -msgid "Redis hostname" -msgstr "" - -#: pod/bbb/models.py -msgid "Redis hostname, useful for chat" -msgstr "" - -#: pod/bbb/models.py -msgid "Redis port" -msgstr "" - -#: pod/bbb/models.py -msgid "Redis port, useful for chat" -msgstr "" - -#: pod/bbb/models.py -msgid "Redis channel" -msgstr "" - -#: pod/bbb/models.py -msgid "Redis channel, useful for chat" -msgstr "" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Livestream" -msgstr "" - -#: pod/bbb/models.py pod/meeting/models.py -msgid "Livestreams" -msgstr "" - -#: pod/bbb/templates/bbb/card.html pod/bbb/templates/bbb/list_meeting.html -msgid "BigBlueButton presentation preview" -msgstr "" - -#: pod/bbb/templates/bbb/card.html -msgid "Publish the BigBlueButton presentation on this platform" -msgstr "" - -#: pod/bbb/templates/bbb/card.html pod/bbb/templates/bbb/publish_meeting.html -msgid "Publish this presentation" -msgstr "" - -#: pod/bbb/templates/bbb/card.html -#: pod/meeting/templates/meeting/internal_recordings.html -msgid "by" -msgstr "" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "Create a video from a BigBlueButton presentation" -msgstr "" - -#: pod/bbb/templates/bbb/list_meeting.html -#: pod/recorder/templates/recorder/claim_record.html -msgid "No record found" -msgstr "" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "There are no BigBlueButton records." -msgstr "" - -#: pod/bbb/templates/bbb/list_meeting.html -#: pod/recorder/templates/recorder/claim_record.html -#, python-format -msgid "%(counter)s record found" -msgid_plural "%(counter)s records found" -msgstr[0] "" -msgstr[1] "" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "" -"This is the list of the recorded BigBlueButton sessions for which you were " -"moderator. This module allows you to create a video from a BigBlueButton " -"presentation." -msgstr "" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "" -"Shortly after the presentation is published, the corresponding video will " -"appear in your videos." -msgstr "" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "" -"Please note: a BigBlueButton presentation can be encoded by another " -"moderator." -msgstr "" - -#: pod/bbb/templates/bbb/list_meeting.html -msgid "" -"If such a case occurs, this information will be displayed directly in the " -"list. You can then contact this user directly to ask him/her to share the " -"video with you, or even to put you as additional owner of the video." -msgstr "" - -#: pod/bbb/templates/bbb/list_meeting.html -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "Please note: this page refreshes automatically every 30 seconds." -msgstr "" - -#: pod/bbb/templates/bbb/live_card.html -#: pod/bbb/templates/bbb/live_publish_meeting.html pod/bbb/views.py -#: pod/main/templates/navbar.html -msgid "Perform a BigBlueButton live" -msgstr "" - -#: pod/bbb/templates/bbb/live_card.html -msgid "" -"Impossible to perform a BigBlueButton live for the moment (all resources are " -"busy)" -msgstr "" - -#: pod/bbb/templates/bbb/live_card.html -msgid "Live not published" -msgstr "" - -#: pod/bbb/templates/bbb/live_card.html -msgid "Live not already started" -msgstr "" - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "No BigBlueButton session in progress found" -msgstr "" - -#: pod/bbb/templates/bbb/live_list_meeting.html -#, python-format -msgid "%(counter)s BigBlueButton session in progress found" -msgid_plural "%(counter)s BigBlueButton sessions in progress found" -msgstr[0] "" -msgstr[1] "" - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "" -"This is the list of current BigBlueButton sessions for which you are " -"moderator. This module allows you to make a live stream from this " -"BigBlueButton session (useful if there are more than 100 users)." -msgstr "" - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "" -"Remember to not use breakout rooms in this case and end the meeting once it " -"is over." -msgstr "" - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "" -"Shortly after clicking the “Perform a BigBlueButton live” button, and select " -"the desired options, the live stream will be available to users on the  Lives " -"page." -msgstr "" - -#: pod/bbb/templates/bbb/live_list_meeting.html -msgid "There are no BigBlueButton sessions in progress." -msgstr "" - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "Confirmation of performing a BigBlueButton live" -msgstr "" - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "Are you sure you want to perform a BigBlueButton live?" -msgstr "" - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "" -"Select the desired options than validate this form by clicking \"Perform a " -"BigBlueButton live\"." -msgstr "" - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "" -"This live will be stopped automatically when BigBlueButton session ends." -msgstr "" - -#: pod/bbb/templates/bbb/live_publish_meeting.html -msgid "" -"Please note: shortly after clicking the “Perform a BigBlueButton live” " -"button, the live stream will be available to users on the  Lives page." -msgstr "" - -#: pod/bbb/templates/bbb/live_record_list.html -msgid "Sorry, no BigBlueButton session in progress found" -msgstr "" - -#: pod/bbb/templates/bbb/live_record_list.html -#: pod/bbb/templates/bbb/record_list.html -#: pod/playlist/templates/playlist/playlist-videos-list.html -#: pod/recorder/templates/recorder/record_list.html -#: pod/video/templates/videos/video_list.html -#: pod/video/templates/videos/video_list_grid_selectable.html -#: pod/video/templates/videos/video_list_table_selectable.html -msgid "More" -msgstr "" - -#: pod/bbb/templates/bbb/live_record_list.html -#: pod/bbb/templates/bbb/record_list.html pod/main/templates/loader.html -#: pod/playlist/templates/playlist/playlist-videos-list.html -#: pod/podfile/templates/podfile/home_content.html -#: pod/podfile/templates/podfile/list_folder_files.html -#: pod/recorder/templates/recorder/record_list.html -#: pod/video/templates/videos/dashboard_modal.html -#: pod/video/templates/videos/video_edit.html -#: pod/video/templates/videos/video_list.html -#: pod/video/templates/videos/video_list_grid_selectable.html -#: pod/video/templates/videos/video_list_table_selectable.html -msgid "Loading…" -msgstr "" - -#: pod/bbb/templates/bbb/publish_meeting.html pod/bbb/views.py -#: pod/main/templates/navbar.html -msgid "My BigBlueButton records" -msgstr "" - -#: pod/bbb/templates/bbb/publish_meeting.html -msgid "Publish the BigBlueButton presentation" -msgstr "" - -#: pod/bbb/templates/bbb/publish_meeting.html -msgid "Are you sure you want to publish this BigBlueButton presentation?" -msgstr "" - -#: pod/bbb/templates/bbb/publish_meeting.html -msgid "" -"A video will be created from the BigBlueButton presentation and will be " -"available on this platform." -msgstr "" - -#: pod/bbb/templates/bbb/publish_meeting.html -msgid "" -"Please note: this treatment can take a long time. You will receive an email " -"when this treatment is completed." -msgstr "" - -#: pod/bbb/templates/bbb/record_list.html -#: pod/recorder/templates/recorder/record_list.html -msgid "Sorry, no record found" -msgstr "" - -#: pod/bbb/views.py -msgid "You aren’t the moderator of this BigBlueButton session." -msgstr "" - -#: pod/bbb/views.py -msgid "The BigBlueButton session has been published." -msgstr "" - -#: pod/bbb/views.py -msgid "The BigBlueButton live has been performed." -msgstr "" - #: pod/chapter/apps.py pod/chapter/models.py #: pod/chapter/templates/video_chapter.html #: pod/video/templates/videos/video-element.html @@ -2854,6 +2349,16 @@ msgstr "" msgid "Please enter a name that will allow you to easily find this recording." msgstr "" +#: pod/import_video/models.py pod/live/forms.py pod/live/models.py +#: pod/meeting/forms.py pod/meeting/models.py pod/video_search/forms.py +msgid "Start date" +msgstr "" + +#: pod/import_video/models.py pod/meeting/models.py pod/playlist/models.py +#: pod/recorder/models.py pod/video/models.py +msgid "User" +msgstr "" + #: pod/import_video/models.py pod/meeting/models.py msgid "User who create this recording" msgstr "" @@ -3376,6 +2881,10 @@ msgstr "" msgid "No" msgstr "" +#: pod/live/admin.py pod/live/models.py pod/meeting/models.py +msgid "Broadcaster" +msgstr "" + #: pod/live/admin.py msgid "Auto start admin" msgstr "" @@ -3398,6 +2907,10 @@ msgstr "" msgid "Select the piloting implementation for to this broadcaster." msgstr "" +#: pod/live/forms.py pod/live/models.py pod/video_search/forms.py +msgid "End date" +msgstr "" + #: pod/live/forms.py msgid "End should not be in the past" msgstr "" @@ -3497,6 +3010,11 @@ msgstr "" msgid "Enable viewers count on live." msgstr "" +#: pod/live/models.py pod/live/templates/live/event_card.html +#: pod/meeting/models.py pod/recorder/models.py pod/video/forms.py +msgid "Restricted access" +msgstr "" + #: pod/live/models.py msgid "Live is accessible only to authenticated users." msgstr "" @@ -5283,6 +4801,19 @@ msgstr "" msgid "videos available" msgstr "" +#: pod/main/templates/loader.html +#: pod/playlist/templates/playlist/playlist-videos-list.html +#: pod/podfile/templates/podfile/home_content.html +#: pod/podfile/templates/podfile/list_folder_files.html +#: pod/recorder/templates/recorder/record_list.html +#: pod/video/templates/videos/dashboard_modal.html +#: pod/video/templates/videos/video_edit.html +#: pod/video/templates/videos/video_list.html +#: pod/video/templates/videos/video_list_grid_selectable.html +#: pod/video/templates/videos/video_list_table_selectable.html +msgid "Loading…" +msgstr "" + #: pod/main/templates/mail/mail.html pod/main/templates/mail/mail_sender.html msgid "Hello" msgstr "" @@ -5574,6 +5105,10 @@ msgstr "" msgid "Viewers" msgstr "" +#: pod/meeting/apps.py +msgid "Meetings" +msgstr "" + #: pod/meeting/forms.py pod/video/feeds.py pod/video/models.py #: pod/video/templates/videos/video_row_select.html #: pod/video/templates/videos/video_sort_select.html @@ -5958,6 +5493,10 @@ msgid "" "direct access to a livestream in the livestreams page." msgstr "" +#: pod/meeting/models.py +msgid "Enable chat" +msgstr "" + #: pod/meeting/models.py msgid "" "Do you want a chat on the live page for listeners? Messages sent in this " @@ -5983,6 +5522,10 @@ msgstr "" msgid "Unable to delete recording!" msgstr "" +#: pod/meeting/models.py +msgid "Meeting" +msgstr "" + #: pod/meeting/models.py msgid "meeting" msgstr "" @@ -6024,6 +5567,22 @@ msgstr "" msgid "Broadcaster in charge to perform lives." msgstr "" +#: pod/meeting/models.py +msgid "URL of the SIPMediaGW server" +msgstr "" + +#: pod/meeting/models.py +msgid "Example format: https://sipmediagw.univ.fr" +msgstr "" + +#: pod/meeting/models.py +msgid "Bearer token for the SIPMediaGW server." +msgstr "" + +#: pod/meeting/models.py +msgid "Example format: 1234" +msgstr "" + #: pod/meeting/models.py msgid "Live gateway" msgstr "" @@ -6032,6 +5591,22 @@ msgstr "" msgid "Live gateways" msgstr "" +#: pod/meeting/models.py +msgid "Live not started" +msgstr "" + +#: pod/meeting/models.py +msgid "Live in progress" +msgstr "" + +#: pod/meeting/models.py +msgid "Live stopped" +msgstr "" + +#: pod/meeting/models.py +msgid "Live status" +msgstr "" + #: pod/meeting/models.py msgid "Event managed for this live" msgstr "" @@ -6048,6 +5623,14 @@ msgstr "" msgid "Live gateway (encoder and broadcaster) that perform the livestream" msgstr "" +#: pod/meeting/models.py +msgid "Livestream" +msgstr "" + +#: pod/meeting/models.py +msgid "Livestreams" +msgstr "" + #: pod/meeting/templates/meeting/add_or_edit.html #: pod/meeting/templates/meeting/link_meeting.html pod/meeting/views.py msgid "Edit the meeting" @@ -6223,6 +5806,10 @@ msgstr "" msgid "Toolbar" msgstr "" +#: pod/meeting/templates/meeting/internal_recordings.html +msgid "by" +msgstr "" + #: pod/meeting/templates/meeting/internal_recordings.html msgid "Please confirm you want to upload the recording To Pod" msgstr "" @@ -6420,6 +6007,10 @@ msgid "" "available on %(site_title)s." msgstr "" +#: pod/meeting/views.py +msgid "Meeting name" +msgstr "" + #: pod/meeting/views.py msgid "Has user joined?" msgstr "" @@ -6440,6 +6031,18 @@ msgstr "" msgid "Moderator count" msgstr "" +#: pod/meeting/views.py +msgid "Attendees" +msgstr "" + +#: pod/meeting/views.py +msgid "Attendee" +msgstr "" + +#: pod/meeting/views.py +msgid "Full name" +msgstr "" + #: pod/meeting/views.py msgid "You cannot edit this meeting." msgstr "" @@ -6935,6 +6538,14 @@ msgstr "" msgid "Sorry, no video found." msgstr "" +#: pod/playlist/templates/playlist/playlist-videos-list.html +#: pod/recorder/templates/recorder/record_list.html +#: pod/video/templates/videos/video_list.html +#: pod/video/templates/videos/video_list_grid_selectable.html +#: pod/video/templates/videos/video_list_table_selectable.html +msgid "More" +msgstr "" + #: pod/playlist/templates/playlist/playlist.html #: pod/playlist/templates/playlist/playlist_content.html #: pod/video/templates/channel/channel.html @@ -8071,6 +7682,17 @@ msgstr "" msgid "Date of recording." msgstr "" +#: pod/recorder/templates/recorder/claim_record.html +#, python-format +msgid "%(counter)s record found" +msgid_plural "%(counter)s records found" +msgstr[0] "" +msgstr[1] "" + +#: pod/recorder/templates/recorder/claim_record.html +msgid "No record found" +msgstr "" + #: pod/recorder/templates/recorder/claim_record.html msgid "There is no unassigned records" msgstr "" @@ -8123,6 +7745,10 @@ msgstr "" msgid "To delete the record, please check the box and click “Delete”." msgstr "" +#: pod/recorder/templates/recorder/record_list.html +msgid "Sorry, no record found" +msgstr "" + #: pod/recorder/views.py msgid "Recorder should be indicated." msgstr "" @@ -8302,6 +7928,10 @@ msgstr "" msgid "Set as draft" msgstr "" +#: pod/video/admin.py +msgid "Encode selected" +msgstr "" + #: pod/video/admin.py msgid "Transcript selected" msgstr "" @@ -8839,6 +8469,10 @@ msgstr "" msgid "Overview" msgstr "" +#: pod/video/models.py +msgid "Encoding in progress" +msgstr "" + #: pod/video/models.py msgid "Is Video" msgstr "" @@ -8864,6 +8498,10 @@ msgstr "" msgid "Sum of view of last %(ndays)s days" msgstr "" +#: pod/video/models.py pod/video_encode_transcript/models.py +msgid "Encoding step" +msgstr "" + #: pod/video/models.py msgid "Is the video encoded?" msgstr "" diff --git a/pod/locale/nl/LC_MESSAGES/djangojs.po b/pod/locale/nl/LC_MESSAGES/djangojs.po index c871566d78..5e1929ec30 100644 --- a/pod/locale/nl/LC_MESSAGES/djangojs.po +++ b/pod/locale/nl/LC_MESSAGES/djangojs.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Esup-Pod\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-19 07:39+0000\n" +"POT-Creation-Date: 2024-08-26 13:54+0200\n" "PO-Revision-Date: 2024-06-04 16:20+0200\n" "Last-Translator: obado \n" "Language-Team: \n" diff --git a/pod/main/configuration.json b/pod/main/configuration.json index 1ca08a4413..ae5cf1d903 100644 --- a/pod/main/configuration.json +++ b/pod/main/configuration.json @@ -1412,40 +1412,42 @@ "default_value": false, "description": { "en": [ - "" + "Using BigBlueButton", + "Withdrawn from Pod version 3.8.2 (replaced by the meetings module)" ], "fr": [ "Utilisation de BigBlueButton", - "[TODO] À retirer dans les futures versions de Pod" + "Retiré à partir de la version 3.8.2 de Pod (remplacé par le module des réunions)" ] }, - "pod_version_end": "", + "pod_version_end": "3.8.2", "pod_version_init": "3.1" }, "USE_BBB_LIVE": { "default_value": false, "description": { "en": [ - "" + "Using the BigBlueButton webinar delivery system", + "Withdrawn from Pod version 3.8.2 (replaced by the meetings module)" ], "fr": [ "Utilisation du système de diffusion de Webinaires en lien avec BigBlueButton", - "[TODO] À retirer dans les futures versions de Pod" + "Retiré à partir de la version 3.8.2 de Pod (remplacé par le module des réunions)" ] }, - "pod_version_end": "", + "pod_version_end": "3.8.2", "pod_version_init": "3.1" }, "USE_LIVE_TRANSCRIPTION": { "default_value": false, "description": { "en": [ - "", - "Enable auto-transcritption for live events" + "Enable auto-transcription for live events", + "" ], "fr": [ - "", - "Activer l’auto-transcription pour les directs" + "Activer l’auto-transcription pour les directs", + "" ] }, "pod_version_end": "", @@ -1907,26 +1909,30 @@ "default_value": "", "description": { "en": [ - "URL of the SIPMediaGW server that manages webinars (e.g. `https://sipmediagw.univ.fr`)" + "URL of the SIPMediaGW server that manages webinars (e.g. `https://sipmediagw.univ.fr`)", + "Retired as of Pod version 3.8.2 (replaced by the meetings module, see live gateway)" ], "fr": [ - "URL du serveur SIPMediaGW qui gère les webinaires (Ex: `https://sipmediagw.univ.fr`)" + "URL du serveur SIPMediaGW qui gère les webinaires (Ex: `https://sipmediagw.univ.fr`)", + "Retiré à partir de la version 3.8.2 de Pod (remplacé par le module des réunions, cf. passerelle de live)" ] }, - "pod_version_end": "", + "pod_version_end": "3.8.2", "pod_version_init": "3.6.0" }, "MEETING_WEBINAR_SIPMEDIAGW_TOKEN": { "default_value": "", "description": { "en": [ - "Bearer token for the SIPMediaGW server that manages webinars" + "Bearer token for the SIPMediaGW server that manages webinars", + "Retired as of Pod version 3.8.2 (see live gateway)" ], "fr": [ - "Jeton bearer du serveur SIPMediaGW qui gère les webinaires" + "Jeton bearer du serveur SIPMediaGW qui gère les webinaires", + "Retiré à partir de la version 3.8.2 de Pod (cf. passerelle de live)" ] }, - "pod_version_end": "", + "pod_version_end": "3.8.2", "pod_version_init": "3.6.0" }, "MEETING_WEBINAR_FIELDS": { diff --git a/pod/main/context_processors.py b/pod/main/context_processors.py index 6f7ca5fec4..eea1502933 100644 --- a/pod/main/context_processors.py +++ b/pod/main/context_processors.py @@ -64,10 +64,6 @@ HIDE_TYPES = getattr(django_settings, "HIDE_TYPES", False) -USE_BBB = getattr(django_settings, "USE_BBB", False) - -USE_BBB_LIVE = getattr(django_settings, "USE_BBB_LIVE", False) - COOKIE_LEARN_MORE = getattr(django_settings, "COOKIE_LEARN_MORE", "") USE_OPENCAST_STUDIO = getattr(django_settings, "USE_OPENCAST_STUDIO", False) @@ -133,8 +129,6 @@ def context_settings(request): new_settings["MAINTENANCE_MODE"] = maintenance_mode new_settings["MAINTENANCE_TEXT_SHEDULED"] = maintenance_text_sheduled new_settings["MAINTENANCE_SHEDULED"] = maintenance_sheduled - new_settings["USE_BBB"] = USE_BBB - new_settings["USE_BBB_LIVE"] = USE_BBB_LIVE new_settings["DARKMODE_ENABLED"] = DARKMODE_ENABLED new_settings["DYSLEXIAMODE_ENABLED"] = DYSLEXIAMODE_ENABLED new_settings["USE_OPENCAST_STUDIO"] = USE_OPENCAST_STUDIO diff --git a/pod/main/rest_router.py b/pod/main/rest_router.py index 1fed02eb1f..5b99fa0864 100644 --- a/pod/main/rest_router.py +++ b/pod/main/rest_router.py @@ -21,9 +21,6 @@ if getattr(settings, "USE_PODFILE", False): from pod.podfile import rest_views as podfile_views -if getattr(settings, "USE_BBB", True): - from pod.bbb import rest_views as bbb_views - if getattr(settings, "USE_MEETING", True): from pod.meeting import rest_views as meeting_views @@ -70,11 +67,6 @@ router.register(r"files", podfile_views.CustomFileModelSerializerViewSet) router.register(r"images", podfile_views.CustomImageModelSerializerViewSet) -if getattr(settings, "USE_BBB", True): - router.register(r"bbb_meeting", bbb_views.MeetingModelViewSet) - router.register(r"bbb_attendee", bbb_views.AttendeeModelViewSet) - router.register(r"bbb_livestream", bbb_views.LivestreamModelViewSet) - if getattr(settings, "USE_MEETING", True): router.register(r"meeting_session", meeting_views.MeetingModelViewSet) router.register( diff --git a/pod/main/tasks.py b/pod/main/tasks.py index 5b7b50a3e9..8cd67a6a7b 100644 --- a/pod/main/tasks.py +++ b/pod/main/tasks.py @@ -22,15 +22,6 @@ def task_start_transcript(self, video_id): main_threaded_transcript(video_id) -@app.task(bind=True) -def task_start_bbb_encode(self, meeting_id): - """Start BBB meeting encoding with Celery.""" - print("CELERY START BBB ENCODE MEETING ID %s" % meeting_id) - from pod.bbb.bbb import bbb_encode_meeting - - bbb_encode_meeting(meeting_id) - - @app.task(bind=True) def task_start_encode_studio( self, recording_id, video_output, videos, subtime, presenter diff --git a/pod/main/templates/navbar.html b/pod/main/templates/navbar.html index 4ecde4a9b8..a9296b7d64 100644 --- a/pod/main/templates/navbar.html +++ b/pod/main/templates/navbar.html @@ -291,12 +291,6 @@
    {% if user.get_full_name != '' %}{{ user.get_ {% if request.user.is_staff and ALLOW_MANUAL_RECORDING_CLAIMING %} {% trans 'Claim a record' %} {% endif %} - {% if request.user.is_staff and USE_BBB %} - {% trans 'My BigBlueButton records' %} - {% if USE_BBB_LIVE %} - {% trans 'Perform a BigBlueButton live' %} - {% endif %} - {% endif %} {% if request.user.is_superuser and USE_SPEAKER %}  {% trans 'Speakers management' %} {% endif %} diff --git a/pod/main/test_settings.py b/pod/main/test_settings.py index ab5e42acae..61a0cdbb7b 100644 --- a/pod/main/test_settings.py +++ b/pod/main/test_settings.py @@ -66,7 +66,6 @@ USER_VIDEO_CATEGORY = True POD_ARCHIVE_AFFILIATION = ["faculty"] WARN_DEADLINES = [60, 30, 7] -USE_BBB = True ORGANIZE_BY_THEME = True SHIBBOLETH_STAFF_ALLOWED_DOMAINS = () SHIBBOLETH_ATTRIBUTE_MAP = { @@ -110,8 +109,6 @@ def get_shared_secret(): # Webinar options USE_MEETING_WEBINAR = True -MEETING_WEBINAR_SIPMEDIAGW_URL = "https://127.0.0.1" -MEETING_WEBINAR_SIPMEDIAGW_TOKEN = "token" MEETING_WEBINAR_AFFILIATION = ["faculty", "employee", "staff"] # Uniquement lors d'environnement conteneurisé diff --git a/pod/main/utils.py b/pod/main/utils.py index 0cd18985c9..89d5486ea8 100644 --- a/pod/main/utils.py +++ b/pod/main/utils.py @@ -70,8 +70,10 @@ def display_message_with_icon(request, type, message) -> None: messages.DEBUG: "code", } icon = mapp.get(type, "info-circle") - msg = "
    " - msg += message + msg = "
    " + msg += " " + msg += " " + message + "" + msg += "
    " messages.add_message(request, type, mark_safe(msg)) diff --git a/pod/meeting/admin.py b/pod/meeting/admin.py index d7b6a49b7a..dcbae4ab59 100644 --- a/pod/meeting/admin.py +++ b/pod/meeting/admin.py @@ -288,11 +288,13 @@ class LiveGatewayAdmin(admin.ModelAdmin): "id", "rtmp_stream_url", "broadcaster", + "sipmediagw_server_url" ) - list_display_links = ("id", "rtmp_stream_url") - ordering = ("-id", "rtmp_stream_url") + list_display_links = ("id",) + ordering = ("-id",) readonly_fields = [] search_fields = [ "id", "broadcaster__broadcaster_name", + "sipmediagw_server_url" ] diff --git a/pod/meeting/models.py b/pod/meeting/models.py index a53aa86c40..5caf784e3d 100644 --- a/pod/meeting/models.py +++ b/pod/meeting/models.py @@ -1243,7 +1243,7 @@ def default_site_recording(sender, instance, **kwargs): class LiveGateway(models.Model): - """Hold information about live gateways, encoders and broadcasters informations. + """Hold information about live gateways (SIPMediaGW, encoders and broadcasters informations). Useful for BigBlueButton livestreams. """ @@ -1256,6 +1256,7 @@ class LiveGateway(models.Model): max_length=200, help_text=_("Example format: rtmp://live.univ.fr/live/name"), ) + # Broadcaster in charge to perform the live broadcaster = models.ForeignKey( Broadcaster, @@ -1264,16 +1265,32 @@ class LiveGateway(models.Model): help_text=_("Broadcaster in charge to perform lives."), ) + # URL of the SIPMediaGW server that manages webinars (e.g. `https://sipmediagw.univ.fr`) + sipmediagw_server_url = models.CharField( + _("URL of the SIPMediaGW server"), + max_length=200, + help_text=_("Example format: https://sipmediagw.univ.fr"), + default="https://sipmediagw.univ.fr" + ) + + # Bearer token for the SIPMediaGW server (e.g. `1234`) + sipmediagw_server_token = models.CharField( + _("Bearer token for the SIPMediaGW server."), + max_length=25, + help_text=_("Example format: 1234"), + default="1234" + ) + # LiveGateway's site site = models.ForeignKey( Site, verbose_name=_("Site"), on_delete=models.CASCADE, default=SITE_ID ) def __unicode__(self): - return "%s - %s" % (self.rtmp_stream_url, self.broadcaster) + return "%s - %s" % (self.rtmp_stream_url, self.sipmediagw_server_url) def __str__(self): - return "%s - %s" % (self.rtmp_stream_url, self.broadcaster) + return "%s - %s" % (self.rtmp_stream_url, self.sipmediagw_server_url) def save(self, *args, **kwargs): super(LiveGateway, self).save(*args, **kwargs) @@ -1281,6 +1298,11 @@ def save(self, *args, **kwargs): class Meta: verbose_name = _("Live gateway") verbose_name_plural = _("Live gateways") + constraints = [ + models.UniqueConstraint( + fields=["sipmediagw_server_url"], name="livegateway_unique_sipmediagw_server_url" + ), + ] @receiver(pre_save, sender=LiveGateway) diff --git a/pod/meeting/static/css/meeting.css b/pod/meeting/static/css/meeting.css index fb9238dcd8..3967cc5cd4 100644 --- a/pod/meeting/static/css/meeting.css +++ b/pod/meeting/static/css/meeting.css @@ -2,7 +2,7 @@ * Esup-Pod Meeting styles */ -.meeting-card-inactive { + .meeting-card-inactive { border: 1px solid #f5a391; } @@ -64,50 +64,17 @@ } /* Message error */ - -div.alert .icon { - text-align: center; - width: 58px; - height: 100%; - position: absolute; - top: 0; - left: 0; - border: 1px solid #bdbdbd; - padding-top: 15px; - border-radius: 6px 0 0 6px; -} - -div.alert .icon i { - font-size: 20px; - color: #fff; - left: 21px; - margin-top: -15px; - position: absolute; - top: 50%; -} - -div.alert.alert-success .icon, -div.alert.alert-success .icon::after { - border-color: none; - background: #00986a; +i.bi.bi-exclamation-circle.me-2 { + color: #c82630; } - -div.alert.alert-info .icon, -div.alert.alert-info .icon::after { - border-color: none; - background: #00b3c8; +i.bi.bi-exclamation-triangle.me-2 { + color: #f9af2c; } - -div.alert.alert-warning .icon, -div.alert.alert-warning .icon::after { - border: none; - background: #f9af2c; +i.bi.bi-check-circle.me-2 { + color: #00986a; } - -div.alert.alert-error .icon, -div.alert.alert-error .icon::after { - border-color: none; - background: #c82630; +i.bi.bi-info-circle.me-2 { + color: #00b3c8; } div.alert .proposition { diff --git a/pod/meeting/urls.py b/pod/meeting/urls.py index 47088f3258..7a480dcbd4 100644 --- a/pod/meeting/urls.py +++ b/pod/meeting/urls.py @@ -52,6 +52,7 @@ urlpatterns += [ path("restart_live//", views.restart_live, name="restart_live"), path("end_live//", views.end_live, name="end_live"), + path("//", views.join, name="join"), url( r"^live_publish_chat/(?P[\d]+)/$", views.live_publish_chat, diff --git a/pod/meeting/views.py b/pod/meeting/views.py index 0f8c9b09ed..ba4523ec24 100644 --- a/pod/meeting/views.py +++ b/pod/meeting/views.py @@ -308,7 +308,7 @@ def save_meeting_form(request: WSGIRequest, form: MeetingForm) -> Meeting: # Manage webinar for event and livestream manage_webinar(meeting, created, live_gateway) display_message_with_icon( - request, messages.INFO, _("The changes have been saved.") + request, messages.SUCCESS, _("The changes have been saved.") ) else: # Disable webinar mode if no live gateway available @@ -325,7 +325,7 @@ def save_meeting_form(request: WSGIRequest, form: MeetingForm) -> Meeting: ) else: display_message_with_icon( - request, messages.INFO, _("The changes have been saved.") + request, messages.SUCCESS, _("The changes have been saved.") ) return meeting @@ -382,7 +382,9 @@ def delete(request: WSGIRequest, meeting_id: str) -> HttpResponse: @csrf_protect @ensure_csrf_cookie -def join(request: WSGIRequest, meeting_id: str, direct_access=None) -> HttpResponse: +def join( + request: WSGIRequest, meeting_id: str, direct_access=None, room=None +) -> HttpResponse: """Join a meeting.""" try: id = int(meeting_id[: meeting_id.find("-")]) @@ -395,6 +397,11 @@ def join(request: WSGIRequest, meeting_id: str, direct_access=None) -> HttpRespo ): return join_as_moderator(request, meeting) + # Manage room (last 10 characters of meeting_id) for SIPMediaGW + # In such a case, we need to compute direct access and room + if room: + direct_access += room + if direct_access and direct_access != meeting.get_hashkey(): raise SuspiciousOperation("Invalid access") diff --git a/pod/meeting/webinar.py b/pod/meeting/webinar.py index 9f961f6722..44f7ad98f7 100644 --- a/pod/meeting/webinar.py +++ b/pod/meeting/webinar.py @@ -6,7 +6,6 @@ import threading import time -from django.conf import settings from django.contrib import messages from django.core.handlers.wsgi import WSGIRequest from django.utils.html import mark_safe @@ -15,13 +14,6 @@ from pod.meeting.models import Meeting, Livestream from pod.meeting.utils import slash_join -# URL of the SIPMediaGW server that manages webinars -MEETING_WEBINAR_SIPMEDIAGW_URL = getattr(settings, "MEETING_WEBINAR_SIPMEDIAGW_URL", "") -# Bearer token for the SIPMediaGW server that manages webinars -MEETING_WEBINAR_SIPMEDIAGW_TOKEN = getattr( - settings, "MEETING_WEBINAR_SIPMEDIAGW_TOKEN", "" -) - log = logging.getLogger("webinar") @@ -95,6 +87,7 @@ def start_webinar_livestream(pod_host: str, meet_id: int): "(localhost) for this functionality." ) ) + # Get the current meeting meeting = Meeting.objects.get(id=meet_id) @@ -128,7 +121,7 @@ def stop_webinar_livestream(meet_id: int, force: bool): if livestream_in_progress: # Stop RTMP Gateway for SIPMediaGW - stop_rtmp_gateway(meet_id) + stop_rtmp_gateway(meet_id, livestream_in_progress.id) # Change livestream status livestream_in_progress.status = 2 @@ -198,7 +191,8 @@ def start_rtmp_gateway(pod_host: str, meet_id: int, livestream_id: int): # Get the current meeting meeting = Meeting.objects.get(id=meet_id) livestream = Livestream.objects.get(id=livestream_id) - # Base URL; example format: pod.univ.fr/meeting/##id##/##hashkey## + # Base URL; example format: pod.univ.fr/meeting/##id##/##hash/key## + # with a / before the last 10 characters meeting_base_url = slash_join( pod_host, "meeting", meeting.meeting_id, meeting.get_hashkey() ) @@ -209,11 +203,11 @@ def start_rtmp_gateway(pod_host: str, meet_id: int, livestream_id: int): # RTMP stream URL rtmp_stream_url = livestream.live_gateway.rtmp_stream_url # Start URL on SIPMediaGW server - sipmediagw_url = slash_join(MEETING_WEBINAR_SIPMEDIAGW_URL, "start") + sipmediagw_url = slash_join(livestream.live_gateway.sipmediagw_server_url, "start") # SIPMediaGW start request headers = { - "Authorization": "Bearer %s" % MEETING_WEBINAR_SIPMEDIAGW_TOKEN, + "Authorization": "Bearer %s" % livestream.live_gateway.sipmediagw_server_token, } params = { "room": room, @@ -229,23 +223,24 @@ def start_rtmp_gateway(pod_host: str, meet_id: int, livestream_id: int): % (meeting.id, meeting.name, response.text) ) - if json_response["res"] != "ok": - message = json_response["type"] + if json_response["status"] != "success": + message = json_response["details"] raise ValueError(mark_safe(message)) -def stop_rtmp_gateway(meet_id: int): +def stop_rtmp_gateway(meet_id: int, livestream_id: int): """Run the stop command for SIPMediaGW RTMP gateway.""" # Get the current meeting meeting = Meeting.objects.get(id=meet_id) + livestream = Livestream.objects.get(id=livestream_id) # Room used (last 10 characters) room = meeting.get_hashkey()[-10:] # Stop URL on SIPMediaGW server - sipmediagw_url = slash_join(MEETING_WEBINAR_SIPMEDIAGW_URL, "stop") + sipmediagw_url = slash_join(livestream.live_gateway.sipmediagw_server_url, "stop") # SIPMediaGW stop request headers = { - "Authorization": "Bearer %s" % MEETING_WEBINAR_SIPMEDIAGW_TOKEN, + "Authorization": "Bearer %s" % livestream.live_gateway.sipmediagw_server_token, } params = { "room": room, @@ -259,8 +254,8 @@ def stop_rtmp_gateway(meet_id: int): % (meeting.id, meeting.name, response.text) ) - if json_response["res"].find("Warning") != -1: - message = json_response["res"] + if json_response["status"] != "success": + message = json_response["details"] raise ValueError(mark_safe(message)) @@ -270,26 +265,35 @@ def toggle_rtmp_gateway(meet_id: int): meeting = Meeting.objects.get(id=meet_id) # Room used (last 10 characters) room = meeting.get_hashkey()[-10:] - # Toogle URL on SIPMediaGW server - sipmediagw_url = slash_join(MEETING_WEBINAR_SIPMEDIAGW_URL, "chat") - - # SIPMediaGW toogle request - headers = { - "Authorization": "Bearer %s" % MEETING_WEBINAR_SIPMEDIAGW_TOKEN, - } - params = {"room": room, "toggle": True} - response = requests.get(sipmediagw_url, params=params, headers=headers, verify=False) - # Specific error message when not started - message = response.text - # Output in JSON (ex: {"res": "ok"}) - json_response = json.loads(response.text) - if json_response["res"] != "ok": - message = "Toogle was sent before SIPMediaGW start (%s)" % response.text + # Search for the livestream used for this webinar + livestream = Livestream.objects.filter( + meeting=meeting, status=1 + ).first() + if livestream: + # Toogle URL on SIPMediaGW server + sipmediagw_url = slash_join( + livestream.live_gateway.sipmediagw_server_url, "chat" + ) - log.info( - "toggle_rtmp_gateway for meeting %s “%s”: %s" - % (meeting.id, meeting.name, message) - ) + # SIPMediaGW toogle request + headers = { + "Authorization": "Bearer %s" % livestream.live_gateway.sipmediagw_server_token, + } + params = {"room": room, "toggle": True} + response = requests.get(sipmediagw_url, params=params, headers=headers, verify=False) + # Specific error message when not started + message = response.text + # Output in JSON (ex: {"res": "ok"}) + json_response = json.loads(response.text) + if json_response["res"] != "ok": + message = "Toogle was sent before SIPMediaGW start (%s)" % response.text + + log.info( + "toggle_rtmp_gateway for meeting %s “%s”: %s" + % (meeting.id, meeting.name, message) + ) + else: + log.error("No livestream object found for webinar id %s" % meet_id) def chat_rtmp_gateway(meet_id: int, msg: str): @@ -298,29 +302,38 @@ def chat_rtmp_gateway(meet_id: int, msg: str): meeting = Meeting.objects.get(id=meet_id) # Room used (last 10 characters) room = meeting.get_hashkey()[-10:] - # Toogle URL on SIPMediaGW server - sipmediagw_url = slash_join(MEETING_WEBINAR_SIPMEDIAGW_URL, "chat") + # Search for the livestream used for this webinar + livestream = Livestream.objects.filter( + meeting=meeting, status=1 + ).first() + if livestream: + # Toogle URL on SIPMediaGW server + sipmediagw_url = slash_join( + livestream.live_gateway.sipmediagw_server_url, "chat" + ) - # SIPMediaGW toogle request - headers = { - "Content-Type": "application/json", - } - # Manage quotes in msg - msg = msg.replace("'", "’") - msg = msg.replace('"', "’") - json_data = {"room": room, "msg": msg} - response = requests.post( - sipmediagw_url, headers=headers, json=json_data, verify=False - ) + # SIPMediaGW toogle request + headers = { + "Content-Type": "application/json", + } + # Manage quotes in msg + msg = msg.replace("'", "’") + msg = msg.replace('"', "’") + json_data = {"room": room, "msg": msg} + response = requests.post( + sipmediagw_url, headers=headers, json=json_data, verify=False + ) - message = response.text - # Output in JSON (ex: {"res": "ok"}) - json_response = json.loads(response.text) + message = response.text + # Output in JSON (ex: {"res": "ok"}) + json_response = json.loads(response.text) - log.info( - "chat_rtmp_gateway for meeting %s “%s”: %s" % (meeting.id, meeting.name, message) - ) + log.info( + "chat_rtmp_gateway for meeting %s “%s”: %s" % (meeting.id, meeting.name, message) + ) - if json_response["res"].find("ok") == -1: - message = json_response["res"] - raise ValueError(mark_safe(message)) + if json_response["res"].find("ok") == -1: + message = json_response["res"] + raise ValueError(mark_safe(message)) + else: + log.error("No livestream object found for webinar id %s" % meet_id) diff --git a/pod/settings.py b/pod/settings.py index 1766d4180a..2590e3c855 100644 --- a/pod/settings.py +++ b/pod/settings.py @@ -62,7 +62,6 @@ "pod.live", "pod.recorder", "pod.lti", - "pod.bbb", "pod.meeting", "pod.cut", "pod.xapi", diff --git a/pod/urls.py b/pod/urls.py index a63ca7be84..6a66b8afed 100644 --- a/pod/urls.py +++ b/pod/urls.py @@ -31,7 +31,6 @@ USE_CUT = getattr(settings, "USE_CUT", True) USE_MEETING = getattr(settings, "USE_MEETING", False) USE_XAPI = getattr(settings, "USE_XAPI", False) -USE_BBB = getattr(settings, "USE_BBB", False) USE_OPENCAST_STUDIO = getattr(settings, "USE_OPENCAST_STUDIO", False) USE_PODFILE = getattr(settings, "USE_PODFILE", False) USE_PLAYLIST = getattr(settings, "USE_PLAYLIST", True) @@ -124,7 +123,6 @@ ), ] -# BBB: TODO REPLACE BBB BY MEETING if USE_MEETING: urlpatterns += [ url(r"^meeting/", include("pod.meeting.urls")), @@ -135,12 +133,6 @@ url(r"^xapi/", include("pod.xapi.urls")), ] -# BBB -if USE_BBB: - urlpatterns += [ - url(r"^bbb/", include("pod.bbb.urls")), - ] - # RECORDER if USE_OPENCAST_STUDIO: urlpatterns += [ diff --git a/scripts/bbb-pod-live/bbb-pod-live.php b/scripts/bbb-pod-live/bbb-pod-live.php deleted file mode 100644 index 49ef168c8a..0000000000 --- a/scripts/bbb-pod-live/bbb-pod-live.php +++ /dev/null @@ -1,1327 +0,0 @@ -getMessage(); -} - -// Envoi d'un message à l'administrateur en cas d'erreur de script. -if ($GLOBALS["txtErrorInScript"] !== "") { - sendEmail("[BBB-POD-LIVE] Erreur rencontrée", $GLOBALS["txtErrorInScript"]); -} -// Fin de la phase principale. - - -/** - * Procédure de création et de configuration initiale - * des différents plugin BigBlueButton-liveStreaming. - * Un répertoire, par nombre de directs gérés par ce serveur (cf. NUMBER_LIVES), - * sera créé sous la forme bbb-live-streaming+incrémental. - * Le fichier compose.yml sera copié depuis le répertoire courant - * (fichier docker-compose.default.yml). - */ -function configureBBBLiveStreaming() -{ - writeLog( - "----- Configuration des plugins nécessaires : configureBBBLiveStreaming()-----", - "DEBUG" - ); - - /* - Création des répertoires et des fichiers compose - pour le plugin BigBlueButton-liveStreaming. - */ - - for ($i = 1; $i <= NUMBER_LIVES; $i++) { - // Définition du répertoire. - $dirLiveStreaming = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming$i/"; - writeLog( - "Vérification pour le direct $i : $dirLiveStreaming", - "DEBUG" - ); - // Définition du fichier compose.yml dans ce répertoire. - $fichierCompose = $dirLiveStreaming."docker-compose.yml"; - // Création du répertoire et du fichier la 1° fois. - if (file_exists($fichierCompose) === false) { - // Création du répertoire. - writeLog( - " + Création du répertoire $dirLiveStreaming", - "DEBUG" - ); - @mkdir("$dirLiveStreaming", 0755); - // Téléchargement du fichier compose depuis Github. - writeLog( - " + Copie du fichier docker-compose.default.yml du répertoire courant", - "DEBUG" - ); - $cmdCp = "cp ./docker-compose.default.yml $fichierCompose"; - exec("$cmdCp 2>&1", $aVerifCp, $sVerifCp); - if ($sVerifCp === 0) { - writeLog( - " + Copie du fichier $fichierCompose réalisée", - "DEBUG" - ); - } else { - writeLog( - " - Commande '$cmdCp' : $aVerifCp[0]", - "ERROR", - __FILE__, __LINE__ - ); - } - } - } - -} -// end configureBBBLiveStreaming() - - -/** - * Procédure permettant de démarrer des directs, - * si des usagers en ont fait la demande dans Pod. - * Pour cela, on utilise l'API Rest de Pod. - * Cette procédure permet d'identifier si un slot est disponible - * pour être utilisé pour lancer un direct. - */ -function startLives() -{ - writeLog( - "-----Démarrage des directs : startLives()-----", - "DEBUG" - ); - - // Recherche si des lives sont en cours. - $cmdStatus1 = "curl --silent -H 'Content-Type: application/json' "; - $cmdStatus1 .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdStatus1 .= "-X GET ".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/?status=1"; - - exec("$cmdStatus1 2>&1", $aVerifStatus1, $sVerifStatus1); - - writeLog( - "Recherche si des lives sont en cours", - "DEBUG" - ); - - // En cas d'erreur, le code de retour est différent de 0. - if ($sVerifStatus1 === 0) { - writeLog( - " + Commande '$cmdStatus1' : $aVerifStatus1[0]", - "DEBUG" - ); - - $oListeSessions = json_decode($aVerifStatus1[0]); - // Recherche des lives existants en cours, sauvegardés dans Pod. - for ($i = 0; $i < $oListeSessions->count; $i++) { - // Identifiant du live dans Pod. - $idLive = $oListeSessions->results[$i]->id; - // Dans Pod, l'information est sauvegardé sous la forme - // NUMERO_SERVEUR/NUMERO_REPERTOIRE_bbb_live_streaming. - $server = $oListeSessions->results[$i]->server; - // Le live est il déjà en cours sur un des serveurs BBB-POD-LIVE ? - $status = $oListeSessions->results[$i]->status; - // Utilisateur ayant lancé ce live. - $user = $oListeSessions->results[$i]->user; - // Prise en compte seulement des lives en cours de ce serveur. - if (($status === 1) - && (strpos("$server", SERVER_NUMBER."/") !== false) - ) { - // Sauvegarde du NUMERO_REPERTOIRE_bbb_live_streaming. - $processInProgress = str_replace(SERVER_NUMBER."/", "", $server); - // Utilisation d'une classe standard. - $liveInProgress = new stdClass(); - $liveInProgress->id = $idLive; - $liveInProgress->idBbbLiveStreaming = $processInProgress; - // Ajout de cet objet au tableau des lives en cours sur ce serveur. - $GLOBALS["livesInProgressOnThisServer"][] = $liveInProgress; - writeLog( - " => Le live $idLive de $user est toujours en cours sur le serveur/bbb_live_streaming : $server.", - "DEBUG" - ); - } - } - } else { - writeLog( - " + Commande '$cmdStatus1' : $sVerifStatus1[0]", - "ERROR", __FILE__, __LINE__ - ); - } - - // Recherche si des utilisateurs ont lancé des lives depuis Pod. - $cmdStatus0 = "curl --silent -H 'Content-Type: application/json' "; - $cmdStatus0 .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdStatus0 .= "-X GET ".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/?status=0"; - - exec("$cmdStatus0 2>&1", $aVerifStatus0, $sVerifStatus0); - - writeLog( - "Recherche si des utilisateurs ont lancé des lives depuis Pod", - "DEBUG" - ); - - // En cas d'erreur, le code de retour est différent de 0. - if ($sVerifStatus0 === 0) { - writeLog( - " + Commande '$cmdStatus0' : $aVerifStatus0[0]", - "DEBUG" - ); - - $oListeSessions = json_decode($aVerifStatus0[0]); - // Recherche des nouvelles demandes de lives, sauvegardées dans Pod. - for ($i = 0; $i < $oListeSessions->count; $i++) { - // Identifiant du live BBB dans Pod. - $idLive = $oListeSessions->results[$i]->id; - // Adresse de la session dans Pod. - $urlMeeting = $oListeSessions->results[$i]->meeting; - // Nom du serveur/processus déjà en charge de ce live. - $server = $oListeSessions->results[$i]->server; - // Le live est il déjà en cours sur un des serveurs BBB-POD-LIVE ? - $status = $oListeSessions->results[$i]->status; - // Utilisateur ayant lancé ce live. - $user = $oListeSessions->results[$i]->user; - /* - Identifiant du répertoire bbb-live-streaming qui s'occupera de - réaliser le live, si disponible. - */ - - $idBbbLiveStreaming = 0; - // Recherche si ce serveur peut encore lancer un direct. - for ($j = 1; $j <= NUMBER_LIVES; $j++) { - // Variable de travail. - $idBbbLiveStreamingUsed = false; - foreach ($GLOBALS["livesInProgressOnThisServer"] as $ligneLiveInProgressOnThisServer) { - // Cet idBbbLiveStreaming est déjà utilisé. - if ($ligneLiveInProgressOnThisServer->idBbbLiveStreaming === $j) { - $idBbbLiveStreamingUsed = true; - } - } - // Le slot idBbbLiveStreaming est non utilisé. - if ($idBbbLiveStreamingUsed === false) { - // Un slot est disponible. - $idBbbLiveStreaming = $j; - // Ajout de l'information aux lives en cours sur ce serveur. - $liveInProgress2 = new stdClass(); - $liveInProgress2->id = $idLive; - $liveInProgress2->idBbbLiveStreaming = $idBbbLiveStreaming; - $GLOBALS["livesInProgressOnThisServer"][] = $liveInProgress2; - break; - } - } - // Un slot est disponible sur ce serveur pour réaliser un live ? - if ($idBbbLiveStreaming === 0) { - writeLog( - " => Impossible de lancer le live $idLive de $user sur ce serveur : il y a déjà ".NUMBER_LIVES." directs qui sont gérés par ce serveur.", - "INFO" - ); - } else { - writeLog( - " => Lancement du live $idLive de $user, via bbb-live-streaming$idBbbLiveStreaming", - "INFO" - ); - configureAndStartLive($idLive, $urlMeeting, $idBbbLiveStreaming); - } - } - } else { - writeLog( - " + Commande '$cmdStatus0' : $sVerifStatus0[0]", - "ERROR", __FILE__, __LINE__ - ); - } - -} - -/** - * Procédure permettant de configurer, puis de lancer, - * le plugin nécessaire au démarrage d'un direct. - * Cette procédure créé également le diffuseur nécessaire - * à l'affichage du live dans Pod. - * - * @param string $idLive - Identifiant du live BBB de Pod à démarrer - * (cf. table bbb_meeting) - * @param string $urlMeeting - URL de la session BBB de Pod à démarrer - * (cf. table bbb_livestream) - * @param string $idBbbLiveStreaming - Identifiant du répertoire bbb-live-streaming - * qui va être utilisé pour lancer ce direct - */ -function configureAndStartLive($idLive, $urlMeeting, $idBbbLiveStreaming) -{ - writeLog( - "-----Configuration et démarrage du direct : configureAndStartLive($idLive, '$urlMeeting', $idBbbLiveStreaming)-----", - "DEBUG" - ); - - $cmd = "curl --silent -H 'Content-Type: application/json' "; - $cmd .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmd .= "-X GET $urlMeeting"; - exec("$cmd 2>&1", $aVerif, $sVerif); - - writeLog( - "Récupération de l'objet meeting ($urlMeeting) depuis Pod", - "DEBUG" - ); - - if ($sVerif === 0) { - writeLog( - " + Commande '$cmd' : $aVerif[0]", - "DEBUG" - ); - - // Récupération de l'objet meeting. - $oMeeting = json_decode($aVerif[0]); - /* - Nom de la session, sans caractères problématiques ni espaces, et la - chaîne bbb- en premier pour éviter toute confusion - avec un diffuseur existant. - */ - - $nameMeeting = "bbb-".formatString($oMeeting->meeting_name); - /* - Nom de la session, sans caractères problématiques avec espaces, - et la chaîne [BBB] en premier pour éviter toute confusion - avec un diffuseur existant. - */ - - $nameMeetingToDisplay = "[BBB] ".formatStringToDisplay($oMeeting->meeting_name); - // Id de la session. - $idMeeting = $oMeeting->meeting_id; - - /* - Récupération des informations concernant - les options saisies par l'utilisateur. - */ - - $cmdOptions = "curl --silent -H 'Content-Type: application/json' "; - $cmdOptions .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdOptions .= "-X GET ".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/$idLive/"; - exec("$cmdOptions 2>&1", $aVerifOptions, $sVerifOptions); - - writeLog( - "Récupération des options de l'objet bbb_livestream (/rest/bbb_livestream/$idLive/) depuis Pod", - "DEBUG" - ); - - $isRestricted = "false"; - $enableChat = "false"; - $showChat = "true"; - $downloadMeeting = "false"; - if ($sVerifOptions === 0) { - // Récupération de l'objet live. - $oLive = json_decode($aVerifOptions[0]); - // Accès restreint. - if ($oLive->is_restricted === 1) { - $isRestricted = "true"; - } else { - $isRestricted = "false"; - } - // Utilisation du chat. - if ($oLive->enable_chat === 1) { - $enableChat = "true"; - } else { - $enableChat = "false"; - } - // Affichage du chat dans la vidéo. - if ($oLive->show_chat === 1) { - $showChat = "true"; - } else { - $showChat = "false"; - } - // Téléchargement de la vidéo en fin de live. - if ($oLive->download_meeting === 1) { - $downloadMeeting = "true"; - } else { - $downloadMeeting = "false"; - } - } else { - writeLog( - " + Commande '$cmdOptions' : $sVerifOptions[0]", - "ERROR", __FILE__, __LINE__ - ); - } - - // Modification de la configuration du docker-compose.yml. - writeLog( - " + Modification de la configuration du docker-compose.yml", - "DEBUG" - ); - $dockerFile = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$idBbbLiveStreaming."/docker-compose.yml"; - // Configuration du nom du container (container_name). - $nameContainer = "liveStreaming".$idBbbLiveStreaming; - $cmdSed0 = "sed -i \"s/^.*container_name:.*/ container_name: $nameContainer/\" $dockerFile"; - exec("$cmdSed0 2>&1", $aVerifSed0, $sVerifSed0); - if ($sVerifSed0 !== 0) { - writeLog( - " - Commande '$cmdSed0' : $aVerifSed0[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Configuration du port utilisé par le host (ligne sous ports:), - de la forme - "6379:6379" pour 1°, - "6380:6379" pour le 2°... - */ - - $port = (6378 + $idBbbLiveStreaming); - $cmdSed01 = "sed -i \"s/^.*:6379:.*/ - \"$port:6379\"/\" $dockerFile"; - exec("$cmdSed01 2>&1", $aVerifSed01, $sVerifSed01); - if ($sVerifSed01 !== 0) { - writeLog( - " - Commande '$cmdSed01' : $aVerifSed01[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Configuration du serveur BBB/Scalelite (BBB_URL) - Gestion des caractères / de BBB_URL pour être utilisé via sed. - */ - - $bbbURL = str_replace("/", "\/", checkEndWithoutSlash(BBB_URL)); - $cmdSed1 = "sed -i \"s/^.*BBB_URL=.*/ - BBB_URL=$bbbURL/\" $dockerFile"; - exec("$cmdSed1 2>&1", $aVerifSed1, $sVerifSed1); - if ($sVerifSed1 !== 0) { - writeLog( - " - Commande '$cmdSed1' : $aVerifSed1[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Configuration de la clé secrète (BBB_SECRET). - $cmdSed2 = "sed -i \"s/^.*BBB_SECRET=.*/ - BBB_SECRET=".BBB_SECRET."/\" $dockerFile"; - exec("$cmdSed2 2>&1", $aVerifSed2, $sVerifSed2); - if ($sVerifSed2 !== 0) { - writeLog( - " - Commande '$cmdSed2' : $aVerifSed2[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Configuration de la timezone (TZ). - $cmdSed3 = "sed -i \"s/^.*TZ=.*/ - TZ=Europe\/Paris/\" $dockerFile"; - exec("$cmdSed3 2>&1", $aVerifSed3, $sVerifSed3); - if ($sVerifSed3 !== 0) { - writeLog( - " - Commande '$cmdSed3' : $aVerifSed3[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Configuration de la résolution (BBB_RESOLUTION). - $cmdSed4 = "sed -i \"s/^.*BBB_RESOLUTION=.*/ - BBB_RESOLUTION=".BBB_RESOLUTION."/\" $dockerFile"; - exec("$cmdSed4 2>&1", $aVerifSed4, $sVerifSed4); - if ($sVerifSed4 !== 0) { - writeLog( - " - Commande '$cmdSed4' : $aVerifSed4[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Configuration du bitrate de la vidéo (FFMPEG_STREAM_VIDEO_BITRATE). - $cmdSed5 = "sed -i \"s/^.*FFMPEG_STREAM_VIDEO_BITRATE=.*/ - FFMPEG_STREAM_VIDEO_BITRATE=".FFMPEG_STREAM_VIDEO_BITRATE."/\" $dockerFile"; - exec("$cmdSed5 2>&1", $aVerifSed5, $sVerifSed5); - if ($sVerifSed5 !== 0) { - writeLog( - " - Commande '$cmdSed5' : $aVerifSed5[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Configuration du nombre de threads (FFMPEG_STREAM_THREADS). - $cmdSed6 = "sed -i \"s/^.*FFMPEG_STREAM_THREADS=.*/ - FFMPEG_STREAM_THREADS=".FFMPEG_STREAM_THREADS."/\" $dockerFile"; - exec("$cmdSed6 2>&1", $aVerifSed6, $sVerifSed6); - if ($sVerifSed6 !== 0) { - writeLog( - " - Commande '$cmdSed6' : $aVerifSed6[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Modification de la ligne concernant l'id - de la session à streamer (BBB_MEETING_ID). - */ - - $cmdSed7 = "sed -i \"s/^.*BBB_MEETING_ID=.*/ - BBB_MEETING_ID=$idMeeting/\" ".$dockerFile; - exec("$cmdSed7 2>&1", $aVerifSed7, $sVerifSed7); - if ($sVerifSed7 !== 0) { - writeLog( - " - Commande '$cmdSed7' : $aVerifSed7[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Modification de la ligne concernant le flux RTMP (BBB_STREAM_URL). - // Gestion des caractères / du serveur RTMP pour être utilisé via sed. - $rtmpServer = str_replace("/", "\/", checkEndSlash(BBB_STREAM_URL)); - $cmdSed8 = "sed -i \"s/^.*BBB_STREAM_URL=.*/ - BBB_STREAM_URL=".$rtmpServer."$nameMeeting/\" ".$dockerFile; - exec("$cmdSed8 2>&1", $aVerifSed8, $sVerifSed8); - if ($sVerifSed8 !== 0) { - writeLog( - " - Commande '$cmdSed8' : $aVerifSed8[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Modification de la ligne concernant - l'utilisation de chat (BBB_ENABLE_CHAT). - */ - - $cmdSed9 = "sed -i \"s/^.*BBB_ENABLE_CHAT=.*/ - BBB_ENABLE_CHAT=$enableChat/\" ".$dockerFile; - exec("$cmdSed9 2>&1", $aVerifSed9, $sVerifSed9); - if ($sVerifSed9 !== 0) { - writeLog( - " - Commande '$cmdSed9' : $aVerifSed9[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Modification de la ligne concernant l'affichage - du chat dans la vidéo (BBB_SHOW_CHAT). - */ - - $cmdSed10 = "sed -i \"s/^.*BBB_SHOW_CHAT=.*/ - BBB_SHOW_CHAT=$showChat/\" ".$dockerFile; - exec("$cmdSed10 2>&1", $aVerifSed10, $sVerifSed10); - if ($sVerifSed10 !== 0) { - writeLog( - " - Commande '$cmdSed10' : $aVerifSed10[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Modification de la ligne concernant l'enregistrement - de la vidéo du live (BBB_DOWNLOAD_MEETING). - */ - - $cmdSed11 = "sed -i \"s/^.*BBB_DOWNLOAD_MEETING=.*/ - BBB_DOWNLOAD_MEETING=$downloadMeeting/\" ".$dockerFile; - exec("$cmdSed11 2>&1", $aVerifSed11, $sVerifSed11); - if ($sVerifSed11 !== 0) { - writeLog( - " - Commande '$cmdSed11' : $aVerifSed11[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Modification de la ligne concernant le titre - de la session (BBB_MEETING_TITLE). - */ - - $cmdSed12 = "sed -i \"s/^.*BBB_MEETING_TITLE=.*/ - BBB_MEETING_TITLE=$nameMeetingToDisplay/\" ".$dockerFile; - exec("$cmdSed12 2>&1", $aVerifSed12, $sVerifSed12); - if ($sVerifSed12 !== 0) { - writeLog( - " - Commande '$cmdSed12' : $aVerifSed12[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Modification de la ligne concernant le mot de passe - d'un participant de la session (BBB_ATTENDEE_PASSWORD). - */ - - $cmdSed13 = "sed -i \"s/^.*BBB_ATTENDEE_PASSWORD=.*/ - BBB_ATTENDEE_PASSWORD=".BBB_ATTENDEE_PASSWORD."/\" ".$dockerFile; - exec("$cmdSed13 2>&1", $aVerifSed13, $sVerifSed13); - if ($sVerifSed13 !== 0) { - writeLog( - " - Commande '$cmdSed13' : $aVerifSed13[0]", - "ERROR", __FILE__, __LINE__ - ); - } - /* - Modification de la ligne concernant le mot de passe - d'un modérateur de la session (BBB_MODERATOR_PASSWORD). - */ - - $cmdSed14 = "sed -i \"s/^.*BBB_MODERATOR_PASSWORD=.*/ - BBB_MODERATOR_PASSWORD=".BBB_MODERATOR_PASSWORD."/\" ".$dockerFile; - exec("$cmdSed14 2>&1", $aVerifSed14, $sVerifSed14); - if ($sVerifSed14 !== 0) { - writeLog( - " - Commande '$cmdSed14' : $aVerifSed14[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Modification de la ligne concernant le channel pour REDIS, - // en cas d'utilisation du chat (BBB_REDIS_CHANNEL) - // Typiquement pour le répertoire 1 => chat1, 2 => chat2, 3 => chat3... - $channelRedis = "chat".$idBbbLiveStreaming; - $cmdSed15 = "sed -i \"s/^.*BBB_REDIS_CHANNEL=.*/ - BBB_REDIS_CHANNEL=$channelRedis/\" ".$dockerFile; - exec("$cmdSed15 2>&1", $aVerifSed15, $sVerifSed15); - if ($sVerifSed15 !== 0) { - writeLog( - " - Commande '$cmdSed15' : $aVerifSed15[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Modification de la ligne concernant le mode DEBUG (DEBUG). - if (DEBUG === true) { - $debug = "true"; - } else { - $debug = "false"; - } - $cmdSed16 = "sed -i \"s/^.*DEBUG=.*/ - DEBUG=$debug/\" ".$dockerFile; - exec("$cmdSed16 2>&1", $aVerifSed16, $sVerifSed16); - if ($sVerifSed16 !== 0) { - writeLog( - " - Commande '$cmdSed16' : $aVerifSed16[0]", - "ERROR", __FILE__, __LINE__ - ); - } - - // Création du diffuseur correspondant dans Pod. - $cmdBroadcaster = "curl --silent -H 'Content-Type: multipart/form-data' "; - $cmdBroadcaster .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdBroadcaster .= "-F 'url=".checkEndSlash(POD_HLS_STREAM)."$nameMeeting.m3u8' "; - $cmdBroadcaster .= "-F 'building=".checkEndWithoutSlash(POD_URL)."/rest/buildings/".POD_ID_BUILDING."/' "; - $cmdBroadcaster .= "-F 'name=$nameMeetingToDisplay' -F 'status=true' -F 'is_restricted=$isRestricted' '".checkEndWithoutSlash(POD_URL)."/rest/broadcasters/'"; - exec("$cmdBroadcaster 2>&1", $aVerifBroadcaster, $sVerifBroadcaster); - - writeLog( - " + Création du diffuseur correspondant dans Pod", - "DEBUG" - ); - - if ($sVerifBroadcaster === 0) { - writeLog( - " - Commande '$cmdBroadcaster' : $aVerifBroadcaster[0]", - "DEBUG" - ); - // Id du diffuseur. - $idBroadcaster = 0; - - // Récupération du diffuseur créé. - $oBroadcaster = json_decode($aVerifBroadcaster[0]); - - /* - Si le diffuseur existe déjà, - $aVerifBroadcaster[0] contiendra un message d'avertissement du type : - {"url":["Un objet Diffuseur avec ce champ URL existe déjà."], - "name":["Un objet Diffuseur avec ce champ nom existe déjà."]} - */ - - if (strpos($aVerifBroadcaster[0], "Un objet Diffuseur avec ce champ nom existe déjà.") !== false) { - $cmdBroadcaster2 = "curl --silent -H 'Content-Type: application/json' "; - $cmdBroadcaster2 .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdBroadcaster2 .= "-X GET ".checkEndWithoutSlash(POD_URL)."/rest/broadcasters/$nameMeeting/"; - exec("$cmdBroadcaster2 2>&1", $aVerifBroadcaster2, $sVerifBroadcaster2); - - writeLog( - " + Récupération du diffuseur déjà existant dans Pod", - "DEBUG" - ); - if ($sVerifBroadcaster2 === 0) { - writeLog( - " - Commande '$cmdBroadcaster2' : $aVerifBroadcaster2[0]", - "DEBUG" - ); - $oBroadcaster2 = json_decode($aVerifBroadcaster2[0]); - $idBroadcaster = $oBroadcaster2->id; - } else { - writeLog( - " + Commande '$cmdBroadcaster2' : $aVerifBroadcaster2[0]", - "ERROR", __FILE__, __LINE__ - ); - } - } else { - $idBroadcaster = $oBroadcaster->id; - } - - if ($idBroadcaster !== 0) { - writeLog( - " + Utilisation du diffuseur $idBroadcaster", - "DEBUG" - ); - - // Démarrage du live, si nécessaire. - startLive( - $idLive, checkEndSlash(BBB_STREAM_URL)."$nameMeeting", - $idBbbLiveStreaming, $idBroadcaster - ); - } else { - writeLog( - " + Démarrage impossible du live : aucun identifiant du diffuseur défini.", - "ERROR", __FILE__, __LINE__ - ); - } - } else { - writeLog( - " + Commande '$cmdBroadcaster' : $aVerifBroadcaster[0]", - "ERROR", __FILE__, __LINE__ - ); - } - } else { - writeLog( - " + Commande '$cmd' : $sVerif[0]", - "ERROR", __FILE__, __LINE__ - ); - } -} - -/** - * Procédure permettant de démarrer un direct. - * - * @param string $idLive - Identifiant du live BBB de Pod à démarrer - * (cf. table bbb_meeting) - * @param string $streamName - Nom du stream utilisé, en correspondance - * avec le diffuseur créé précédemment - * @param string $idBbbLiveStreaming - Identifiant du répertoire bbb-live-streaming - * qui va être utilisé pour lancer ce direct - * @param string $idBroadcaster - Identifiant du diffuseur qui va être utilisé - * pour lancer ce direct - */ -function startLive($idLive, $streamName, $idBbbLiveStreaming, $idBroadcaster) -{ - writeLog( - "-----Démarrage du direct : startLive($idLive, '$streamName', $idBbbLiveStreaming, $idBroadcaster)-----", - "DEBUG" - ); - - if (DEBUG) { - // Avec gestions des logs, dans le répertoire des logs. - // Le nom du fichier correspond à l'id du live BBB de Pod. - // ((cf. table bbb_meeting). - $cmd = "cd ".checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$idBbbLiveStreaming." ; docker compose up 1>".checkEndSlash(PHYSICAL_LOG_ROOT)."$idLive.log"; - exec("$cmd 2>&1 &", $aVerif, $sVerif); - } else { - // En mode daemon. - $cmd = "cd ".checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$idBbbLiveStreaming." ; docker compose up -d"; - exec("$cmd 2>&1", $aVerif, $sVerif); - } - - writeLog( - "Démarrage du live", - "DEBUG" - ); - - if ($sVerif === 0) { - writeLog( - " + Commande '$cmd'", - "DEBUG" - ); - - // Définition du port pour REDIS (en cas d'utilisation du chat) - // Typiquement pour le répertoire 1 => 6379, 2 => 6380, 3 => 6381... - $portRedis = (6378 + $idBbbLiveStreaming); - - // Définition du channel pour REDIS (en cas d'utilisation du chat) - // Typiquement pour le répertoire 1 => chat1, 2 => chat2, 3 => chat3... - $channelRedis = "chat".$idBbbLiveStreaming; - - // Mise a jour de l'information dans Pod, via l'API Rest. - $cmdMajPod = "curl --silent -H 'Content-Type: application/json' "; - $cmdMajPod .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdMajPod .= "-X PATCH -d '{\"server\":\"".SERVER_NUMBER."/".$idBbbLiveStreaming."\", \"status\":1, \"broadcaster_id\": $idBroadcaster, \"redis_hostname\":\"".SERVER_HOSTNAME."\", \"redis_port\": $portRedis, \"redis_channel\":\"$channelRedis\"}' "; - $cmdMajPod .= "".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/$idLive/"; - exec("$cmdMajPod", $aVerifMajPod, $sVerifMajPod); - - writeLog( - " + Mise à jour de l'information du bbb_livestream dans Pod", - "DEBUG" - ); - - if ($sVerifMajPod === 0) { - writeLog( - " - Commande '$cmdMajPod' : $aVerifMajPod[0]", - "DEBUG" - ); - } else { - writeLog( - " - Commande '$cmdMajPod' : $sVerifMajPod[0]", - "ERROR", __FILE__, __LINE__ - ); - } - } else { - writeLog( - " + Commande '$cmd' : $sVerif[0]", - "ERROR", __FILE__, __LINE__ - ); - } - sendEmail("[BBB-POD-LIVE] Démarrage d'un live", "Démarrage d'un direct (id : $idLive, stream : $streamName) sur le serveur ".SERVER_NUMBER); -} - -/** - * Procédure permettant d'identifier et d'arrêter des directs - * dont la session BigBlueButton a été arrêtée. - */ -function stopLives() -{ - writeLog( - "-----Arrêt des directs : stopLives()-----", - "DEBUG" - ); - - // Checksum utile pour récupérer les informations des sessions - // en cours sur BigBlueButton/Scalelite. - $checksum = sha1("getMeetings".BBB_SECRET); - // Adresse utile pour récupérer les informations des sessions - // en cours sur BigBlueButton/Scalelite. - $bbbUrlGetMeetings = checkEndWithoutSlash(BBB_URL)."/getMeetings?checksum=".$checksum; - // Variable permettant de connaitre les sessions - // en cours sur le serveur BigBlueButton/Scalelite. - $meetingsInProgressOnBBB = array(); - - // On ne récupère les sessions du serveur BigBlueButton/Scalelite - // que s'il existe des lives en cours sur ce serveur. - if (count($GLOBALS["livesInProgressOnThisServer"]) > 0) { - $xml = simplexml_load_file($bbbUrlGetMeetings); - writeLog( - "Récupération des sessions depuis le serveur BigBlueButton/Scalelite", - "DEBUG" - ); - if ($xml === false) { - writeLog( - " + Impossible de se connecter au serveur BBB/Scalelite : $bbbUrlGetMeetings", - "ERROR", __FILE__, __LINE__ - ); - } else { - writeLog( - " + Requête sur le serveur BBB/Scalelite : $bbbUrlGetMeetings", - "DEBUG" - ); - foreach ($xml->meetings->meeting as $meeting) { - // Ajout du meetingID au tableau des sessions BBB en cours. - $meetingsInProgressOnBBB[] = $meeting->meetingID; - } - } - - // Recherche de tous les directs marqués comme étant en cours. - foreach ($GLOBALS["livesInProgressOnThisServer"] as $ligneLiveInProgressOnThisServer) { - /* - Récupération du BBB_MEETING_ID - correspondant dans le docker-compose.yml. - */ - - $dockerFile = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$ligneLiveInProgressOnThisServer->idBbbLiveStreaming."/docker-compose.yml"; - $dockerDirectory = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$ligneLiveInProgressOnThisServer->idBbbLiveStreaming; - $cmdGrep1 = "grep BBB_MEETING_ID $dockerFile| cut -d\"=\" -f2"; - exec("$cmdGrep1 2>&1", $aVerifGrep1, $sVerifGrep1); - if ($sVerifGrep1 === 0) { - writeLog( - " + Commande '$cmdGrep1' : $aVerifGrep1[0]", - "DEBUG" - ); - // Meeting ID correspondant. - $bbbMeetingId = $aVerifGrep1[0]; - - // Recherche du nom du diffuseur correspondant. - // (sauvegardé aussi dans BBB_MEETING_TITLE du fichier compose). - $broadcasterName = ""; - $cmdGrep2 = "grep BBB_MEETING_TITLE $dockerFile| cut -d\"=\" -f2"; - exec("$cmdGrep2 2>&1", $aVerifGrep2, $sVerifGrep2); - if ($sVerifGrep2 === 0) { - writeLog( - " + Commande '$cmdGrep2' : $aVerifGrep2[0]", - "DEBUG" - ); - // Nom du diffuseur correspondant. - $broadcasterName = formatString($aVerifGrep2[0]); - } else { - writeLog( - " + Commande '$cmdGrep2' : $sVerifGrep2[0]", - "ERROR", __FILE__, __LINE__ - ); - } - - /* - * Cet ID n'est plus dans la liste des sessions en cours sur BBB : - * - on arrête le container docker correspondant - * - on supprime le diffuseur correspondant - * - on copie, pour permettre l'encodage, le fichier vidéo - * si l'utilisateur a enregistré la session - */ - - if (in_array($bbbMeetingId, $meetingsInProgressOnBBB) === false) { - writeLog( - " + La session BigBlueButton $bbbMeetingId est arrêtée. Arrêt du container docker $dockerFile, suppression du diffuseur correspondant, copie du fichier vidéo généré selon le souhait de l'utilisateur", - "INFO" - ); - $cmdStop = "cd $dockerDirectory; docker compose down"; - // exec($command, $output, $result_code): string|false - exec("$cmdStop 2>&1", null, $sVerifStop); - if ($sVerifStop === 0) { - writeLog( - " - Le container docker $dockerDirectory est bien arrêté", - "DEBUG" - ); - // Formatage de la date d'arrêt dans le bon format. - $endDate = date('Y-m-d H:i:s'); - // On sauvegarde cette information dans la base de Pod - // via l'appel à l'API Rest. - $cmdMajPod1 = "curl --silent -H 'Content-Type: application/json' "; - $cmdMajPod1 .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdMajPod1 .= "-X PATCH -d '{\"end_date\":\"$endDate\", \"status\":2}' "; - $cmdMajPod1 .= "".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/".$ligneLiveInProgressOnThisServer->id."/"; - exec("$cmdMajPod1", $aVerifMajPod1, $sVerifMajPod1); - - writeLog( - " + Mise à jour de l'information du bbb_livestream dans Pod", - "DEBUG" - ); - - // URL de l'API Rest du meeting en cours d'arrêt. - $urlApiRestMeeting = ""; - // URL de l'API Rest du user qui a réalisé le live en cours d'arrêt. - $urlApiRestUser = ""; - if ($sVerifMajPod1 === 0) { - writeLog( - " - Commande '$cmdMajPod1' : $aVerifMajPod1[0]", - "DEBUG" - ); - $oLive = json_decode($aVerifMajPod1[0]); - if (isset($oLive->meeting) === true) { - $urlApiRestMeeting = $oLive->meeting; - $urlApiRestUser = $oLive->user; - } - } else { - writeLog( - " - Commande '$cmdMajPod1' : $sVerifMajPod1[0]", - "ERROR", __FILE__, __LINE__ - ); - } - // Suppression du diffuseur. - if ($broadcasterName !== "") { - deleteBroadcaster($broadcasterName); - } - - // Recherche si l'utilisateur a souhaité cet enregistrement. - // (sauvegardé aussi dans BBB_DOWNLOAD_MEETING du fichier compose). - $downloadMeeting = false; - $cmdGrep3 = "grep BBB_DOWNLOAD_MEETING $dockerFile| cut -d\"=\" -f2"; - exec("$cmdGrep3 2>&1", $aVerifGrep3, $sVerifGrep3); - if ($sVerifGrep3 === 0) { - writeLog( - " + Commande '$cmdGrep3' : $aVerifGrep3[0]", - "DEBUG" - ); - // Nom du diffuseur correspondant. - if ($aVerifGrep3[0] === "true") { - $downloadMeeting = true; - } - } else { - writeLog( - " + Commande '$cmdGrep3' : $sVerifGrep3[0]", - "ERROR", __FILE__, __LINE__ - ); - } - - // Copie du fichier vidéo créé : si c'est configuré pour - // et que l'utilisateur a souhaité cet enregistrement. - if (POD_DEFAULT_BBB_PATH !== "" && $downloadMeeting === true) { - /* - Recherche de internal_meeting_id - correspondant à cette session. - */ - - $cmdMajPod2 = "curl --silent -H 'Content-Type: application/json' "; - $cmdMajPod2 .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdMajPod2 .= "-X PATCH -d '{\"encoded_by\":\"$urlApiRestUser\", \"encoding_step\":3}' "; - $cmdMajPod2 .= "$urlApiRestMeeting"; - exec("$cmdMajPod2 2>&1", $aVerifMajPod2, $sVerifMajPod2); - - writeLog( - " + Récupération de l'internal_meeting_id correspondant à l'objet bbb_meeting $bbbMeetingId depuis Pod", - "DEBUG" - ); - $internalMeetingId = ""; - if ($sVerifMajPod2 === 0) { - writeLog( - " - Commande '$cmdMajPod2' : $aVerifMajPod2[0]", - "DEBUG" - ); - - /* - Recherche de l'internal_meeting_id - correspondant au meeting. - */ - - $oMeeting = json_decode($aVerifMajPod2[0]); - if (isset($oMeeting->internal_meeting_id) === true) { - $internalMeetingId = $oMeeting->internal_meeting_id; - } - } else { - writeLog( - " - Commande '$cmdMajPod2' : $sVerifMajPod2[0]", - "ERROR", __FILE__, __LINE__ - ); - } - - if ($internalMeetingId !== "") { - processDirectory( - $ligneLiveInProgressOnThisServer->idBbbLiveStreaming, - $internalMeetingId - ); - } else { - writeLog( - " - Impossible de récupérer l'internal meeting id pour le direct ".$ligneLiveInProgressOnThisServer->idBbbLiveStreaming, - "ERROR", __FILE__, __LINE__ - ); - } - } - } else { - writeLog( - " + Commande '$cmdStop' : $sVerifStop[0]", - "ERROR", __FILE__, __LINE__ - ); - } - } - } else { - writeLog( - " + Commande '$cmdGrep1' : $sVerifGrep1[0]", - "ERROR", __FILE__, __LINE__ - ); - } - } - } -} - -/** - * Procédure permettant de supprimer un diffuseur dans Pod. - * - * @param string $broadcasterName - Nom du diffuseur à supprimer - */ -function deleteBroadcaster($broadcasterName) -{ - // Via l'API, il faut utiliser le slug et non le nom. - $slug = str_replace("[BBB]", "bbb", $broadcasterName); - // Suppression du diffuseur correspondant dans Pod. - $cmdBroadcaster = "curl --silent "; - $cmdBroadcaster .= "-H 'Authorization: Token ".POD_TOKEN."' "; - $cmdBroadcaster .= "-X DELETE '".checkEndWithoutSlash(POD_URL)."/rest/broadcasters/$slug/'"; - exec("$cmdBroadcaster 2>&1", $aVerifBroadcaster, $sVerifBroadcaster); - - writeLog( - " + Suppression du diffuseur $slug dans Pod", - "DEBUG" - ); - - if ($sVerifBroadcaster === 0) { - writeLog( - " - Commande '$cmdBroadcaster' exécutée", - "DEBUG" - ); - } else { - writeLog( - " + Commande '$cmdBroadcaster' : $aVerifBroadcaster[0]", - "ERROR", __FILE__, __LINE__ - ); - } -} - -/** - * Procédure permettant de copier le fichier vidéo créé, - * si un enregistrement existe de ce dernier. - * Le fichier vidéo est créé dans le répertoire videodata - * du répertoire BigBlueButton-liveStreaming concerné. - * Ce fichier vidéo sera copié dans le POD_DEFAULT_BBB_PATH - * et renommé sous la forme internalMeetingId.mkv. - * Ce nommage est très important et permet au CRON Job bbb de Pod - * d'assigner le bon propriétaire à cette vidéo. - * - * @param string $idBbbLiveStreaming - Identifiant du répertoire - * BigBlueButton-liveStreaming concerné. - * @param string $internalMeetingId - Identifiant interne de la - * session BBB enregistrée. - */ -function processDirectory($idBbbLiveStreaming, $internalMeetingId) -{ - writeLog( - "-----Copie des fichiers vidéos enregistrées sur le partage NFS, pour traitement automatique par POD : processDirectory($idBbbLiveStreaming, $internalMeetingId)-----", - "DEBUG" - ); - // Parcours du répertoire videodata du répertoire - // BigBlueButton-liveStreaming concerné - // Définition du répertoire. - $dirLiveStreaming = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming$idBbbLiveStreaming/videodata"; - writeLog( - "Recherche de fichiers vidéos pour le direct $idBbbLiveStreaming : $dirLiveStreaming", - "DEBUG" - ); - - if (is_dir($dirLiveStreaming) === true) { - $listFiles = scandir("$dirLiveStreaming"); - // Mise en place d'une boucle, - // mais il ne doit y avoir qu'un seul fichier au maximum. - foreach ($listFiles as $key => $value) { - if (strrpos($value, ".mkv") === true) { - // Déplacer et renommer le fichier avec l'internalMeetingId. - $oldFilename = "$dirLiveStreaming/$value"; - $newFilename = checkEndSlash(POD_DEFAULT_BBB_PATH).$internalMeetingId.".mkv"; - writeLog( - " + Déplacement du fichier $oldFilename vers $newFilename", - "DEBUG" - ); - @rename("$oldFilename", "$newFilename"); - // Positionnement de droits adéquats pour pouvoir être encodé par Pod. - // Normalement, il n'y en a pas besoin : - // le fichier généré a les droits 644, ce qui est suffisant. - @chmod("$newFilename", 0755); - } - } - } -} - -/** - * Fonction d'écriture dans le fichier de logs. - * Les messages au niveau debug ne seront écris que - * si l'application est configuré en mode DEBUG (DEBUG = true). - * - * @param string $message - Message à écrire - * @param string $level - Niveau de log de ce message (debug, info, warning, error) - * @param string $file - Nom du fichier PHP concerné (en cas d'erreur) - * @param int $line - Ligne dans le fichier PHP concerné (en cas d'erreur) - * - * @return nombre d'octets écris, false sinon - */ -function writeLog($message, $level, $file=null, $line=null) -{ - // Ecriture des lignes de debug seulement en cas de mode DEBUG. - if (($level === "DEBUG") && (DEBUG === false)) { - return false; - } - - // Création du répertoire des logs si besoin. - if (is_dir(checkEndSlash(PHYSICAL_LOG_ROOT)) === false) { - // Création du répertoire. - @mkdir(checkEndSlash(PHYSICAL_LOG_ROOT), 0755); - } - - // Configuration du fichier de log, 1 par jour. - $logFile = checkEndSlash(PHYSICAL_LOG_ROOT).gmdate("Y-m-d")."_bbb-pod-live.log"; - - // En cas de non existence, on créé ce fichier. - if (file_exists($logFile) === false) { - $file = fopen($logFile, "x+"); - // Une exception est levée en cas de non existence du fichier. - // (problème manifeste de droits utilisateurs). - if (file_exists($logFile) === false) { - print("Erreur de configuration : impossible de créer le fichier $logFile."); - throw new Exception("Impossible de créer le fichier $logFile."); - } - } - - // Une exception est levée en cas de problème d'écriture. - // (problème manifeste de droits utilisateurs) - if (!is_writeable($logFile)) { - throw new Exception("$logFile n'a pas les droits en écriture."); - } - - $message = gmdate("Y-m-d H:i:s")." - [$level] - ".$message; - $message .= is_null($file) ? '' : " - Fichier [$file]"; - $message .= is_null($line) ? '' : " - Ligne [$line]."; - $message .= "\n"; - - // Surcharge de la variable globale signifiant une erreur dans le script. - if ($level === "ERROR") { - $GLOBALS["txtErrorInScript"] .= "$message"; - } - - return file_put_contents($logFile, $message, FILE_APPEND); -} - -/** - * Fonction permettant de vérifier que la chaîne de caractères finit par un /. - * En ajoute un si nécessaire. - * - * @param string $string - Chaîne de caractères. - * - * @return Chaîne de caractères identique à celle en entrée, - * mais avec un / comme dernier caractère. - */ -function checkEndSlash($string) -{ - if (substr($string, -1) !== "/") { - $string .= "/"; - } - return $string; -} - -/** - * Fonction permettant de vérifier que la chaîne de caractères - * ne finit pas par un /. Supprime ce / un si nécessaire. - * - * @param string $string - Chaîne de caractères. - * - * @return Chaîne de caractères identique à celle en entrée, mais sans / à la fin. - */ -function checkEndWithoutSlash($string) -{ - if (substr($string, -1) === "/") { - $string = substr($string, 0, -1); - } - return $string; -} - -/** - * Fonction permettant de supprimer les caractères accentués et autres caractères - * problématiques d'une chaîne de caractères. - * Remplace aussi les espaces par des tirets - * - * @param $string - chaîne avec accents - * - * @return chaîne sans accents - */ -function formatString($string) -{ - $string = htmlentities($string, ENT_NOQUOTES, 'utf-8'); - $string = preg_replace( - '#&([A-za-z])(?:uml|circ|tilde|acute|grave|cedil|ring);#', - '\1', $string - ); - $string = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $string); - $string = preg_replace('#&[^;]+;".()\'#', '', $string); - $string = preg_replace('/\s+/', '-', $string); - $string = str_replace("'", "-", $string); - return $string; -} - -/** - * Fonction permettant de supprimer les caractères accentués et autres caractéres - * problématiques d'une chaîne de caractères. - * Ne replace pas les espaces. - * - * @param $string - chaîne avec accents - * - * @return chaîne sans accents - */ -function formatStringToDisplay($string) -{ - $string = htmlentities($string, ENT_NOQUOTES, 'utf-8'); - $string = preg_replace( - '#&([A-za-z])(?:uml|circ|tilde|acute|grave|cedil|ring);#', - '\1', $string - ); - $string = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $string); - $string = preg_replace('#&[^;]+;".()\'#', '', $string); - $string = str_replace("'", "-", $string); - return $string; -} - -/** - * Procédure permettant d'envoyer un email à l'administrateur. - * - * @param $subject - sujet du mail - * @param $message - message du mail - */ -function sendEmail($subject, $message) -{ - $dest = ADMIN_EMAIL; - $message = nl2br($message); - - $headers = "MIME-Version: 1.0\r\n"; - $headers .= "Content-type: text/html; charset=utf-8\r\n"; - - mail($dest, $subject, $message, $headers); -} diff --git a/scripts/bbb-pod-live/docker-compose.default.yml b/scripts/bbb-pod-live/docker-compose.default.yml deleted file mode 100644 index 3b83606370..0000000000 --- a/scripts/bbb-pod-live/docker-compose.default.yml +++ /dev/null @@ -1,58 +0,0 @@ -version: '3.3' -services: - redis: - image: redis:6.2 - expose: - - "6379" - networks: - - app-tier - bbb-streamer: - image: aauzid/bigbluebutton-livestreaming - container_name: liveStreaming1 - shm_size: '2gb' - environment: - # BigBlueButton Server url: - - BBB_URL=https://bbb.univ.fr/bigbluebutton/api - # BigBlueButton secret: - - BBB_SECRET=abcd - # BigBlueButton meetingID: - - BBB_MEETING_ID=abcd - # start meeting (optional): - - BBB_START_MEETING=false - # attendee password (optional - has to be set to the attendee password of moodle/greenlight or any other frontend to allow joining via their links): - - BBB_ATTENDEE_PASSWORD=xxxxxxxxxx - # moderator password (optional - has to be set to the moderator password of moodle/greenlight or any other frontend to allow joining via their links): - - BBB_MODERATOR_PASSWORD=xxxxxxxxxx - # meeting title (optional): - - BBB_MEETING_TITLE=(BBB) Webinaire de test 01 - # download / save BigBlueButton meeting - - BBB_DOWNLOAD_MEETING=false - # Media server url: - - BBB_STREAM_URL=rtmp://live.univ.fr/live/bbb-Webinaire-de-test-01 - # Resolution to be streamed/downloaded in format WxH (default 1920x1080) - - BBB_RESOLUTION=1280x720 - # stream video bitrate - - FFMPEG_STREAM_VIDEO_BITRATE=3000 - # threads used for stream (0=auto) - - FFMPEG_STREAM_THREADS=0 - # Enable chat functionality - - BBB_ENABLE_CHAT=true - # shows the chat on the left side of the window (Default: false) - - BBB_SHOW_CHAT=true - # Timezone (default: Europe/Vienna): - - TZ=Europe/Paris - # Set REDIS host (default: 'redis') - - BBB_REDIS_HOST=redis - # Set REDIS channel to subscribe (default: 'chat') - - BBB_REDIS_CHANNEL=chat1 - # Username for the chat (default: 'Chat') - - BBB_CHAT_NAME=Chat - # DEBUG - - DEBUG=true - networks: - - app-tier - volumes: - - ./videodata:/video -networks: - app-tier: - driver: bridge