diff --git a/pod/import_video/utils.py b/pod/import_video/utils.py index dde51dd44f..ca947a05f3 100644 --- a/pod/import_video/utils.py +++ b/pod/import_video/utils.py @@ -1,9 +1,11 @@ """Utils for Meeting and Import_video module.""" +import json import requests import shutil from datetime import datetime as dt from django.conf import settings +from django.utils.html import mark_safe from django.utils.translation import gettext_lazy as _ from html.parser import HTMLParser from pod.video.models import Video @@ -61,40 +63,11 @@ def secure_request_for_upload(request): raise ValueError(msg) -def manage_recording_url(video_url): - """Verify the video URL and change it, if necessary (for ESR URL). - - Args: - video_url (String): URL to verify - - Returns: - String: good URL of a BBB recording video - """ - try: - bbb_playback_video = "/playback/video/" - url = urlparse(video_url) - if url.query: - query = parse_qs(url.query, keep_blank_values=True) - if query["token"][0]: - # For ESR URL - # Ex: https://_site_/recording/_uid_/video?token=_token_ - # Get recording unique identifier - uid = url.path.split("/")[2] - # New video URL - # Ex: https://_site_/playback/video/_uid_/ - return url.scheme + "://" + url.netloc + bbb_playback_video + uid + "/" - else: - return video_url - else: - return video_url - except Exception: - return video_url - - -def parse_remote_file(source_html_url): +def parse_remote_file(session, source_html_url): """Parse the remote HTML file on the BBB server. Args: + session (Session) : session useful to achieve requests (and keep cookies between) source_html_url (String): URL to parse Raises: @@ -104,7 +77,7 @@ def parse_remote_file(source_html_url): String: name of the video found in the page """ try: - response = requests.get(source_html_url) + response = session.get(source_html_url) if response.status_code != 200: msg = {} msg["error"] = _( @@ -112,7 +85,7 @@ def parse_remote_file(source_html_url): ) # If we want to display the 404/500... page to the user # msg["message"] = response.content.decode("utf-8") - msg["message"] = "Error number : %s" % response.status_code + msg["message"] = _("Error number: %s") % response.status_code raise ValueError(msg) # Parse the BBB video HTML file @@ -130,7 +103,7 @@ def parse_remote_file(source_html_url): if extension not in VIDEO_ALLOWED_EXTENSIONS: msg = {} msg["error"] = _( - "The video file for this recording was not " "found in the HTML file." + "The video file for this recording was not found in the HTML file." ) msg["message"] = _("The found file is not a valid video.") raise ValueError(msg) @@ -149,14 +122,89 @@ def parse_remote_file(source_html_url): msg["error"] = _( "The video file for this recording was not found in the HTML file." ) - msg["message"] = str(exc) + msg["message"] = mark_safe(str(exc)) raise ValueError(msg) -def download_video_file(source_video_url, dest_file): +def manage_recording_url(source_url, video_file_add): + """Generate the BBB video URL. + + See more explanations in manage_download() function. + + Args: + source_url (String): Source file URL + video_file_add (String): Name of the video file to add to the URL + + Returns: + String: good URL of a BBB recording video + """ + try: + bbb_playback_video = "/video/" + url = urlparse(source_url) + if url.query: + query = parse_qs(url.query, keep_blank_values=True) + if query["token"][0]: + # 1st case (ex: ESR URL), URL likes (ex for ESR URL:) + # https://_site_/recording/_recording_id/video?token=_token_ + # Get recording unique identifier + recording_id = url.path.split("/")[2] + # Define 2nd video URL + # Ex: https://_site_/video/_recording_id/video-0.m4v + source_video_url = "%s://%s%s%s/%s" % ( + url.scheme, + url.netloc, + bbb_playback_video, + recording_id, + video_file_add + ) + else: + # 2nd case (BBB URL standard without token) + source_video_url = source_url + video_file_add + return source_video_url + else: + return source_url + video_file_add + except Exception: + return source_url + video_file_add + + +def manage_download(session, source_url, video_file_add, dest_file): + """Manage the download of a BBB video file. + + 2 possibilities : + - Download BBB video file directly. + - Download BBB video file where source URL is protected by a single-use token. + In such a case, 2 requests are made, using the same session. + A cookie is set at the first request (the parsing one, called before) + and used for the second one. + + This function is a simple shortcut to the calls of manage_recording_url + and download_video_file. + + Args: + session (Session) : session useful to achieve requests (and keep cookies between) + source_url (String): Source file URL + video_file_add (String): Name of the video file to add to the URL + dest_file (String): Destination file of the Pod video + + Returns: + source_video_url (String) : video source file URL + + Raises: + ValueError: if impossible download + """ + try: + source_video_url = manage_recording_url(source_url, video_file_add) + download_video_file(session, source_video_url, dest_file) + return source_video_url + except Exception as exc: + raise ValueError(mark_safe(str(exc))) + + +def download_video_file(session, source_video_url, dest_file): """Download BBB video file. Args: + session (Session) : session useful to achieve requests (and keep cookies between) source_video_url (String): Video file URL dest_file (String): Destination file of the Pod video @@ -165,17 +213,14 @@ def download_video_file(source_video_url, dest_file): """ # Check if video file exists try: - with requests.get(source_video_url, timeout=(10, 180), stream=True) as response: + with session.get(source_video_url, timeout=(10, 180), stream=True) as response: + # Can be useful to debug + # print(session.cookies.get_dict()) if response.status_code != 200: - msg = {} - msg["error"] = _( + raise ValueError(_( "The video file for this recording " "was not found on the BBB server." - ) - # If we want to display the 404/500... page to the user - # msg["message"] = response.content.decode("utf-8") - msg["message"] = "Error number : %s" % response.status_code - raise ValueError(msg) + )) with open(dest_file, "wb+") as file: # Total size, in bytes, from response header @@ -189,18 +234,15 @@ def download_video_file(source_video_url, dest_file): # Method 3 : The fastest shutil.copyfileobj(response.raw, file) except Exception as exc: - msg = {} - msg["error"] = _("Impossible to download the video file from the server.") - msg["message"] = str(exc) - raise ValueError(msg) + raise ValueError(mark_safe(str(exc))) -def save_video(request, dest_file, recording_name, description, date_evt=None): +def save_video(request, dest_path, recording_name, description, date_evt=None): """Save and encode the Pod video file. Args: request (Request): HTTP request - dest_file (String): Destination file of the Pod video + dest_path (String): Destination path of the Pod video recording_name (String): recording name description (String): description added to the Pod video date_evt (Datetime, optional): Event date. Defaults to None. @@ -210,7 +252,7 @@ def save_video(request, dest_file, recording_name, description, date_evt=None): """ try: video = Video.objects.create( - video=dest_file, + video=dest_path, title=recording_name, owner=request.user, description=description, @@ -224,7 +266,7 @@ def save_video(request, dest_file, recording_name, description, date_evt=None): except Exception as exc: msg = {} msg["error"] = _("Impossible to create the Pod video") - msg["message"] = str(exc) + msg["message"] = mark_safe(str(exc)) raise ValueError(msg) @@ -279,7 +321,7 @@ def check_video_size(video_size): msg = {} msg["error"] = _("File too large.") msg["message"] = ( - _("The size of the video file exceeds " "the maximum allowed value, %s Gb.") + _("The size of the video file exceeds the maximum allowed value, %s Gb.") % MAX_UPLOAD_SIZE_ON_IMPORT ) raise ValueError(msg) @@ -372,3 +414,11 @@ def get_end_time(self): def get_duration(self): """Return duration.""" return str(self.get_end_time() - self.get_start_time()).split(".")[0] + + def to_json(self): + """Return recording data (without uploadedToPodBy) in JSON format.""" + exclusion_list = ['uploadedToPodBy'] + return json.dumps( + {key: value for key, value in self.__dict__.items() + if key not in exclusion_list} + ) diff --git a/pod/import_video/views.py b/pod/import_video/views.py index 68a3afa410..6fc6fb9d2e 100644 --- a/pod/import_video/views.py +++ b/pod/import_video/views.py @@ -25,6 +25,7 @@ from django.utils.text import get_valid_filename from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import ensure_csrf_cookie +from pod.import_video.utils import manage_download from pod.main.views import in_maintenance from pod.main.utils import secure_post_request, display_message_with_icon @@ -65,6 +66,8 @@ ), ) +VIDEOS_DIR = getattr(settings, "VIDEOS_DIR", "videos") + def secure_external_recording(request, recording): """Secure an external recording. @@ -423,21 +426,37 @@ def upload_bbb_recording_to_pod(request, record_id): Boolean: True if upload achieved """ try: + # Session useful to achieve requests (and keep cookies between) + session = requests.Session() + recording = ExternalRecording.objects.get(id=record_id) source_url = request.POST.get("source_url") # Step 1: Download and parse the remote HTML file if necessary # Check if extension is a video extension + """ extension = source_url.split(".")[-1].lower() if extension in VIDEO_ALLOWED_EXTENSIONS: # URL corresponds to a video file source_video_url = source_url else: # Download and parse the remote HTML file - video_file = parse_remote_file(source_url) + video_file = parse_remote_file(session, source_url) source_video_url = source_url + video_file + """ + + # Check if extension is a video extension + extension = source_url.split(".")[-1].lower() + # Name of the video file to add to the URL (if necessary) + video_file_add = "" + if extension not in VIDEO_ALLOWED_EXTENSIONS: + # Download and parse the remote HTML file + video_file_add = parse_remote_file(session, source_url) + # Extension overload + extension = video_file_add.split(".")[-1].lower() # Verify that video exists and not oversised + source_video_url = manage_recording_url(source_url, video_file_add) verify_video_exists_and_size(source_video_url) # Step 2: Define destination source file @@ -445,15 +464,25 @@ def upload_bbb_recording_to_pod(request, record_id): discrim = datetime.now().strftime("%Y%m%d%H%M%S") dest_file = os.path.join( settings.MEDIA_ROOT, - "videos", + VIDEOS_DIR, request.user.owner.hashkey, os.path.basename("%s-%s.%s" % (discrim, recording.id, extension)), ) - os.makedirs(os.path.dirname(dest_file), exist_ok=True) + dest_path = os.path.join( + VIDEOS_DIR, + request.user.owner.hashkey, + os.path.basename("%s-%s.%s" % (discrim, recording.id, extension)), + ) + # Step 3: Download the video file - download_video_file(source_video_url, dest_file) + source_video_url = manage_download( + session, + source_url, + video_file_add, + dest_file + ) # Step 4: Save informations about the recording recording_title = request.POST.get("recording_name") @@ -465,7 +494,7 @@ def upload_bbb_recording_to_pod(request, record_id): '%(url)s' ) % {"type": recording.get_type_display(), "url": source_video_url} - save_video(request, dest_file, recording_title, description) + save_video(request, dest_path, recording_title, description) return True except Exception as exc: @@ -524,7 +553,7 @@ def upload_youtube_recording_to_pod(request, record_id): # User directory dest_dir = os.path.join( settings.MEDIA_ROOT, - "videos", + VIDEOS_DIR, request.user.owner.hashkey, ) os.makedirs(os.path.dirname(dest_dir), exist_ok=True) @@ -532,8 +561,9 @@ def upload_youtube_recording_to_pod(request, record_id): discrim = datetime.now().strftime("%Y%m%d%H%M%S") filename = "%s-%s" % (discrim, get_valid_filename(yt_stream.default_filename)) # Video file path - dest_file = os.path.join( - dest_dir, + dest_path = os.path.join( + VIDEOS_DIR, + request.user.owner.hashkey, filename, ) @@ -549,7 +579,7 @@ def upload_youtube_recording_to_pod(request, record_id): 'its origin is Youtube: %(url)s' ) % {"name": yt_video.title, "url": source_url} recording_title = request.POST.get("recording_name") - save_video(request, dest_file, recording_title, description, date_evt) + save_video(request, dest_path, recording_title, description, date_evt) return True except VideoUnavailable: @@ -604,6 +634,9 @@ def upload_peertube_recording_to_pod(request, record_id): # noqa: C901 Boolean: True if upload achieved """ try: + # Session useful to achieve requests (and keep cookies between) + session = requests.Session() + # Manage source URL from video playback source_url = request.POST.get("source_url") @@ -678,14 +711,20 @@ def upload_peertube_recording_to_pod(request, record_id): # noqa: C901 extension = source_video_url.split(".")[-1].lower() dest_file = os.path.join( settings.MEDIA_ROOT, - "videos", + VIDEOS_DIR, request.user.owner.hashkey, os.path.basename("%s-%s.%s" % (discrim, pt_video_uuid, extension)), ) os.makedirs(os.path.dirname(dest_file), exist_ok=True) + dest_path = os.path.join( + VIDEOS_DIR, + request.user.owner.hashkey, + os.path.basename("%s-%s.%s" % (discrim, pt_video_uuid, extension)), + ) + # Step 3: Download the video file - download_video_file(source_video_url, dest_file) + download_video_file(session, source_video_url, dest_file) # Step 4: Save informations about the recording recording_title = request.POST.get("recording_name") @@ -697,7 +736,7 @@ def upload_peertube_recording_to_pod(request, record_id): # noqa: C901 "%(url)s." ) % {"name": pt_video_name, "url": pt_video_url} description = ("%s
%s") % (description, pt_video_description) - save_video(request, dest_file, recording_title, description, date_evt) + save_video(request, dest_path, recording_title, description, date_evt) return True except Exception as exc: @@ -745,8 +784,8 @@ def get_stateless_recording(request, data): # Management of the external recording type if data.type == "bigbluebutton": - # Manage BBB recording URL, if necessary - video_url = manage_recording_url(data.source_url) + # Manage BBB recording URL + video_url = data.source_url # For BBB, external URL can be the video or presentation playback if video_url.find("playback/video") != -1: # Management for standards video URLs with BBB or Scalelite server diff --git a/pod/locale/fr/LC_MESSAGES/django.mo b/pod/locale/fr/LC_MESSAGES/django.mo index 339183b744..190518175f 100644 Binary files a/pod/locale/fr/LC_MESSAGES/django.mo and b/pod/locale/fr/LC_MESSAGES/django.mo differ diff --git a/pod/locale/fr/LC_MESSAGES/django.po b/pod/locale/fr/LC_MESSAGES/django.po index 1aceb5e0c2..30a923534e 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: 2023-09-21 14:05+0000\n" +"POT-Creation-Date: 2023-09-22 16:41+0200\n" "PO-Revision-Date: \n" "Last-Translator: obado \n" "Language-Team: Pod Team pod@esup-portail.org\n" @@ -2118,8 +2118,8 @@ msgid "" msgstr "" "Les champs \"Début\" et \"Fin\" doivent contenir des valeurs en secondes. " "Lancez la lecture de la vidéo, mettez sur pause et cliquez sur \"Récupérer " -"le temps depuis le lecteur\" pour renseigner automatiquement le champ " -"\"Début\". Vous pouvez le faire également pour remplir le champ \"Fin\"." +"le temps depuis le lecteur\" pour renseigner automatiquement le champ \"Début" +"\". Vous pouvez le faire également pour remplir le champ \"Fin\"." #: pod/enrichment/templates/enrichment/edit_enrichment.html msgid "You cannot overlap enrichments." @@ -2551,6 +2551,11 @@ msgid "The HTML file for this recording was not found on the BBB server." msgstr "" "Le fichier HTML de cet enregistrement est introuvable sur le serveur BBB." +#: pod/import_video/utils.py +#, python-format +msgid "Error number: %s" +msgstr "Numéro d'erreur : %s" + #: pod/import_video/utils.py msgid "The video file for this recording was not found in the HTML file." msgstr "" @@ -2570,10 +2575,6 @@ msgstr "" "Le fichier vidéo pour cet enregistrement n’a pas été trouvé sur le serveur " "BBB." -#: pod/import_video/utils.py -msgid "Impossible to download the video file from the server." -msgstr "Impossible de télécharger le fichier vidéo depuis le serveur." - #: pod/import_video/utils.py msgid "Impossible to create the Pod video" msgstr "Impossible de créer la vidéo Pod" @@ -2623,8 +2624,8 @@ msgid "" "This video was uploaded to Pod; its origin is %(type)s: %(url)s" msgstr "" -"Cette vidéo a été téléversée sur Pod ; son origine est %(type)s : %(url)s" +"Cette vidéo a été téléversée sur Pod ; son origine est %(type)s : %(url)s" #: pod/import_video/views.py pod/meeting/views.py msgid "Try changing the record type or address for this recording." @@ -2635,8 +2636,8 @@ msgstr "" #: pod/import_video/views.py #, python-format msgid "" -"This video '%(name)s' was uploaded to Pod; its origin is Youtube: %(url)s" +"This video '%(name)s' was uploaded to Pod; its origin is Youtube: %(url)s" msgstr "" "Cette vidéo « %(name)s » a été téléversée sur Pod ; son origine est " "Youtube : %(url)s" @@ -3647,7 +3648,7 @@ msgstr "Évènements passés" msgid "Registration of event #%(content_id)s" msgstr "Programmation de l’évènement #%(content_id)s" -#: pod/live/utils.py pod/video_encode_transcript/utils.py +#: pod/live/utils.py pod/meeting/utils.py pod/video_encode_transcript/utils.py msgid "Hello," msgstr "Bonjour," @@ -3663,7 +3664,7 @@ msgstr "" "%(url_event)s). Vous pouvez retrouver les autres options de partage dans " "l’onglet dédié." -#: pod/live/utils.py pod/main/templates/mail/mail.html +#: pod/live/utils.py pod/main/templates/mail/mail.html pod/meeting/utils.py #: pod/video_encode_transcript/utils.py msgid "Regards." msgstr "Cordialement." @@ -5169,6 +5170,10 @@ msgstr "Impossible de terminer la réunion !" msgid "Unable to get meeting recordings!" msgstr "Impossible d’obtenir les enregistrements de la réunion !" +#: pod/meeting/models.py +msgid "Unable to get recording!" +msgstr "Impossible d'obtenir l'enregistrement !" + #: pod/meeting/models.py msgid "Unable to delete recording!" msgstr "Impossible de supprimer l’enregistrement !" @@ -5229,6 +5234,22 @@ msgstr "Voir toutes mes réunions" msgid "Meeting recordings" msgstr "Enregistrements de réunion" +#: pod/meeting/templates/meeting/internal_recordings.html +msgid "" +"After recording a Big Blue Button meeting, recordings of that meeting will " +"be available at that level after a while." +msgstr "" +"Après avoir enregistré une réunion Big Blue Button, les enregistrements de " +"cette réunion seront disponibles à ce niveau après un certain temps." + +#: pod/meeting/templates/meeting/internal_recordings.html +msgid "" +"When a new recording is available at this level, you will receive an email " +"to inform you." +msgstr "" +"Lorsqu'un nouvel enregistrement est disponible à ce niveau, vous recevrez un " +"courriel pour vous en informer." + #: pod/meeting/templates/meeting/internal_recordings.html msgid "Toolbar" msgstr "Barre d’outils" @@ -5406,6 +5427,26 @@ msgstr "Ou après" msgid "occurrences" msgstr "occurrences" +#: pod/meeting/utils.py +#, python-format +msgid "A new Big Blue Button recording for '%(name)s' meeting is available" +msgstr "" +"Un nouvel enregistrement de la réunion Big Blue Button '%(name)s' est " +"disponible" + +#: pod/meeting/utils.py +#, python-format +msgid "" +"A new Big Blue Button recording for “%(content_title)s” meeting is now " +"available on %(site_title)s." +msgstr "" +"Un nouvel enregistrement Big Blue Button pour la réunion “%(content_title)s” " +"est maintenant disponible sur %(site_title)s." + +#: pod/meeting/utils.py pod/video_encode_transcript/utils.py +msgid "You will find it here:" +msgstr "Vous la/le retrouverez ici :" + #: pod/meeting/views.py msgid "Has user joined?" msgstr "Utilisateurs connectés ?" @@ -5485,16 +5526,16 @@ msgid "" msgstr "" "\n" "

Bonjour,\n" -"

%(owner)s vous invite à une réunion récurrente " -"%(meeting_title)s.

\n" +"

%(owner)s vous invite à une réunion récurrente " +"%(meeting_title)s.

\n" "

Date de début : %(start_date_time)s

\n" "

Récurrent jusqu’à la date : %(end_date)s

\n" "

La réunion se tiendra tou(te)s les %(frequency)s %(recurrence)s \n" "

Voici le lien pour rejoindre la réunion :\n" " %(join_link)s

\n" -"

Vous avez besoin de ce mot de passe pour entrer : " -"%(password)s

\n" +"

Vous avez besoin de ce mot de passe pour entrer : " +"%(password)s

\n" "

Cordialement

\n" " " @@ -5520,8 +5561,8 @@ msgstr "" "

Date de fin : %(end_date)s

\n" "

Voici le lien pour rejoindre la réunion :\n" " %(join_link)s

\n" -"

Vous avez besoin de ce mot de passe pour entrer : " -"%(password)s

\n" +"

Vous avez besoin de ce mot de passe pour entrer : " +"%(password)s

\n" "

Cordialement

\n" " " @@ -6497,8 +6538,8 @@ msgstr "Prévisualisation d’enregistrement" #: pod/video/templates/videos/video-element.html msgid "" "To view this video please enable JavaScript, and consider upgrading to a web " -"browser that supports HTML5 video" +"browser that supports HTML5 video" msgstr "" "Pour visionner cette vidéo, veuillez activer JavaScript et envisager de " "passer à un navigateur Web qui un nouvel enregistrement a été ajouté sur la plateforme " "%(title_site)s à partir de l’enregistreur \"%(recorder)s\".
Pour " -"l’ajouter, cliquez sur le lien ci dessous.

%(link_url)s
Si le lien n’est pas actif, il " -"faut le copier-coller dans la barre d’adresse de votre navigateur.

Cordialement

" +"l’ajouter, cliquez sur le lien ci dessous.

" +"%(link_url)s
Si le lien n’est pas actif, il faut le copier-coller " +"dans la barre d’adresse de votre navigateur.

Cordialement

" #: pod/recorder/views.py msgid "New recording added." @@ -6928,8 +6968,8 @@ msgid "" "%(url)s

\n" msgstr "" "vous pouvez changer la date de suppression en éditant votre vidéo :

\n" -"

%(scheme)s:%(url)s

\n" +"

" +"%(scheme)s:%(url)s

\n" "\n" #: pod/video/management/commands/check_obsolete_videos.py @@ -7691,8 +7731,8 @@ msgid "" "This video is chaptered. Click the chapter button on the video player to view them." msgstr "" -"Cette vidéo est chapitrée. Cliquez sur le bouton de chapitre sur le lecteur vidéo pour les voir." +"Cette vidéo est chapitrée. Cliquez sur le bouton de chapitre sur le lecteur vidéo pour les voir." #: pod/video/templates/videos/video-all-info.html msgid "Other versions" @@ -8493,10 +8533,6 @@ msgstr "automatiquement transcrit" msgid "encoded to Web formats" msgstr "encodé aux formats Web" -#: pod/video_encode_transcript/utils.py -msgid "You will find it here:" -msgstr "Vous la/le retrouverez ici :" - #: pod/video_encode_transcript/utils.py msgid "the:" msgstr "le :" diff --git a/pod/locale/fr/LC_MESSAGES/djangojs.po b/pod/locale/fr/LC_MESSAGES/djangojs.po index 90bd3c3108..f5860ed5aa 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: 2023-09-21 14:05+0000\n" +"POT-Creation-Date: 2023-09-22 16:41+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 3ad2b96d32..2d75489c6a 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: 2023-09-21 14:05+0000\n" +"POT-Creation-Date: 2023-09-22 16:41+0200\n" "PO-Revision-Date: 2023-06-08 14:37+0200\n" "Last-Translator: obado \n" "Language-Team: \n" @@ -2397,6 +2397,11 @@ msgstr "" msgid "The HTML file for this recording was not found on the BBB server." msgstr "" +#: pod/import_video/utils.py +#, python-format +msgid "Error number: %s" +msgstr "" + #: pod/import_video/utils.py msgid "The video file for this recording was not found in the HTML file." msgstr "" @@ -2413,10 +2418,6 @@ msgstr "" msgid "The video file for this recording was not found on the BBB server." msgstr "" -#: pod/import_video/utils.py -msgid "Impossible to download the video file from the server." -msgstr "" - #: pod/import_video/utils.py msgid "Impossible to create the Pod video" msgstr "" @@ -2471,8 +2472,8 @@ msgstr "" #: pod/import_video/views.py #, python-format msgid "" -"This video '%(name)s' was uploaded to Pod; its origin is Youtube: %(url)s" +"This video '%(name)s' was uploaded to Pod; its origin is Youtube: %(url)s" msgstr "" #: pod/import_video/views.py @@ -3415,7 +3416,7 @@ msgstr "" msgid "Registration of event #%(content_id)s" msgstr "" -#: pod/live/utils.py pod/video_encode_transcript/utils.py +#: pod/live/utils.py pod/meeting/utils.py pod/video_encode_transcript/utils.py msgid "Hello," msgstr "" @@ -3427,7 +3428,7 @@ msgid "" "the other sharing options in the dedicated tab." msgstr "" -#: pod/live/utils.py pod/main/templates/mail/mail.html +#: pod/live/utils.py pod/main/templates/mail/mail.html pod/meeting/utils.py #: pod/video_encode_transcript/utils.py msgid "Regards." msgstr "" @@ -4899,6 +4900,10 @@ msgstr "" msgid "Unable to get meeting recordings!" msgstr "" +#: pod/meeting/models.py +msgid "Unable to get recording!" +msgstr "" + #: pod/meeting/models.py msgid "Unable to delete recording!" msgstr "" @@ -4956,6 +4961,18 @@ msgstr "" msgid "Meeting recordings" msgstr "" +#: pod/meeting/templates/meeting/internal_recordings.html +msgid "" +"After recording a Big Blue Button meeting, recordings of that meeting will " +"be available at that level after a while." +msgstr "" + +#: pod/meeting/templates/meeting/internal_recordings.html +msgid "" +"When a new recording is available at this level, you will receive an email " +"to inform you." +msgstr "" + #: pod/meeting/templates/meeting/internal_recordings.html msgid "Toolbar" msgstr "" @@ -5126,6 +5143,22 @@ msgstr "" msgid "occurrences" msgstr "" +#: pod/meeting/utils.py +#, python-format +msgid "A new Big Blue Button recording for '%(name)s' meeting is available" +msgstr "" + +#: pod/meeting/utils.py +#, python-format +msgid "" +"A new Big Blue Button recording for “%(content_title)s” meeting is now " +"available on %(site_title)s." +msgstr "" + +#: pod/meeting/utils.py pod/video_encode_transcript/utils.py +msgid "You will find it here:" +msgstr "" + #: pod/meeting/views.py msgid "Has user joined?" msgstr "" @@ -6139,8 +6172,8 @@ msgstr "" #: pod/video/templates/videos/video-element.html msgid "" "To view this video please enable JavaScript, and consider upgrading to a web " -"browser that supports HTML5 video" +"browser that supports HTML5 video" msgstr "" #: pod/recorder/templates/recorder/link_record.html @@ -7985,10 +8018,6 @@ msgstr "" msgid "encoded to Web formats" msgstr "" -#: pod/video_encode_transcript/utils.py -msgid "You will find it here:" -msgstr "" - #: pod/video_encode_transcript/utils.py msgid "the:" msgstr "" diff --git a/pod/locale/nl/LC_MESSAGES/djangojs.po b/pod/locale/nl/LC_MESSAGES/djangojs.po index 8e4f0e221e..30e9fcfe27 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: 2023-09-21 14:05+0000\n" +"POT-Creation-Date: 2023-09-22 16:41+0200\n" "PO-Revision-Date: 2023-02-08 15:22+0100\n" "Last-Translator: obado \n" "Language-Team: \n" diff --git a/pod/main/templates/navbar.html b/pod/main/templates/navbar.html index 00a400c7d6..7452d118b7 100644 --- a/pod/main/templates/navbar.html +++ b/pod/main/templates/navbar.html @@ -219,6 +219,10 @@
{% if user.get_full_name != '' %}{{ user.get_ {% if USE_OPENCAST_STUDIO %} {% trans 'Video Record' %} {% endif %} + {% show_meeting_button as meeting_button %} + {% if meeting_button %} + {% trans 'My meetings' %} + {% endif %} {% show_import_video_button as import_video_button %} {% if import_video_button %} {% trans 'Import an external video' %} diff --git a/pod/meeting/models.py b/pod/meeting/models.py index 29c0dd7c34..bfc0cf8cb5 100644 --- a/pod/meeting/models.py +++ b/pod/meeting/models.py @@ -480,7 +480,7 @@ def get_hashkey(self): # ############################## Meeting occurences def next_occurrence_from_today(self): - """Returns the date of the next occurrence for the meeting from today.""" + """Return the date of the next occurrence for the meeting from today.""" if self.start_at == timezone.now().date(): start_datetime = self.start_at + self.expected_duration if start_datetime > timezone.now(): @@ -584,7 +584,10 @@ def is_active(self): # ############################## BBB API def create(self, request=None): - """Make the url with goods parameters to create the meeting on the BBB instance and call it.""" + """Make the url with goods parameters to create the meeting. + + After create the meeting on the BBB instance, call it. + """ action = "create" parameters = {} for param in meeting_to_bbb: @@ -607,6 +610,14 @@ def create(self, request=None): ] ) parameters["meta_endCallbackUrl"] = endCallbackUrl + recordingReadyUrl = "".join( + [ + "https://", + get_current_site(None).domain, + reverse("meeting:recording_ready", kwargs={}), + ] + ) + parameters["meta_bbb-recording-ready-url"] = recordingReadyUrl query = urlencode(parameters) hashed = api_call(query, action) url = slash_join(BBB_API_URL, action, "?%s" % hashed) @@ -828,6 +839,7 @@ def end(self): return True def get_recordings(self): + """Get recordings for a meeting.""" action = "getRecordings" parameters = {} parameters["meetingID"] = self.meeting_id @@ -854,6 +866,35 @@ def get_recordings(self): else: return meeting_json + def get_recording(self, record_id): + """Get a specific recording.""" + action = "getRecordings" + parameters = {} + parameters["meetingID"] = self.meeting_id + parameters["recordID"] = record_id + query = urlencode(parameters) + hashed = api_call(query, action) + url = slash_join(BBB_API_URL, action, "?%s" % hashed) + response = requests.get(url) + if response.status_code != 200: + msg = {} + msg["error"] = "Unable to call BBB server." + msg["returncode"] = response.status_code + msg["message"] = response.content.decode("utf-8") + raise ValueError(msg) + result = response.content.decode("utf-8") + xmldoc = et.fromstring(result) + recording_json = parseXmlToJson(xmldoc) + if recording_json.get("returncode", "") != "SUCCESS": + msg = {} + msg["error"] = _("Unable to get recording!") + msg["returncode"] = recording_json.get("returncode", "") + msg["messageKey"] = recording_json.get("messageKey", "") + msg["message"] = recording_json.get("message", "") + raise ValueError(msg) + else: + return recording_json + def delete_recording(self, record_id): """Delete a BBB recording.""" action = "deleteRecordings" @@ -912,6 +953,19 @@ def get_all_meetings(): else: return meeting_json + def get_recordings_absolute_url(self): + """Get recordings list absolute URL.""" + return reverse("meeting:internal_recordings", args=[ + str(self.meeting_id), + ]) + + def get_recordings_full_url(self, request=None): + """Get recordings list full URL.""" + full_url = "".join( + ["//", get_current_site(request).domain, self.get_recordings_absolute_url()] + ) + return full_url + class Meta: db_table = "meeting" verbose_name = "Meeting" diff --git a/pod/meeting/templates/meeting/internal_recordings.html b/pod/meeting/templates/meeting/internal_recordings.html index c2e8f31135..c973e24799 100644 --- a/pod/meeting/templates/meeting/internal_recordings.html +++ b/pod/meeting/templates/meeting/internal_recordings.html @@ -19,6 +19,15 @@ {% trans "Do not leave the page until the video upload to Pod is complete." %} +
+

+ {% trans "After recording a Big Blue Button meeting, recordings of that meeting will be available at that level after a while." %}
+

+ {% trans "When a new recording is available at this level, you will receive an email to inform you." %} +

+

+
+ @@ -47,13 +56,15 @@ @@ -64,16 +75,15 @@ - {% csrf_token %} - + {% endif %} {% if record.canDelete %} {% trans "Please confirm you want to delete the recording" as confirmDelete %} {% csrf_token %} - + {% endif %} @@ -129,5 +139,48 @@ } return answer; } + + /** + * Function that allow to manage the link of recordings (video and presentation playback) to take into account the single-use token. + **/ + function manageLink(type, element) { + // Get important information from element + let meetingId = element.dataset.podMeetingId; + let recordingId = element.dataset.podRecordingId; + let url = element.getAttribute("data-pod-recording-" + type + "-url"); + // Open presentation/video recording on other window + window.open(url, '_blank'); + // AJAX Pod server to re-request the BBB server + urlGetRecording = "/meeting/internal_recording/" + meetingId + "/" + recordingId + "/"; + + // Get infos from the recording (new token appears here) + fetch(urlGetRecording, { + method: "GET", + headers: { + "X-CSRFToken": "{{ csrf_token }}", + "X-Requested-With": "XMLHttpRequest", + }, + dataType: "html", + cache: "no-store", + }) + .then((response) => response.text()) + .then((data) => { + // Get new playback URL + let jsonData = JSON.parse(data); + var urlNew; + if (type == "presentation") { + urlNew = jsonData.presentationUrl; + } else { + urlNew = jsonData.videoUrl; + } + // Modify element + element.setAttribute("data-pod-recording-" + type + "-url", urlNew); + }) + .catch((error) => { + console.debug(error); + // Reload the whole page + location.reload(); + }); + } {% endblock more_script %} diff --git a/pod/meeting/tests/test_views.py b/pod/meeting/tests/test_views.py index 405693388d..e1ec68eb4b 100644 --- a/pod/meeting/tests/test_views.py +++ b/pod/meeting/tests/test_views.py @@ -551,7 +551,7 @@ def setUp(self): site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") user2 = User.objects.create(username="pod2", password="pod1234pod") - meeting = Meeting.objects.create(id=1, name="test_pod#1", owner=user, site=site) + meeting = Meeting.objects.create(id=1, name="test_pod1", owner=user, site=site) user.owner.sites.add(Site.objects.get_current()) user.owner.save() user2.owner.sites.add(Site.objects.get_current()) @@ -585,7 +585,7 @@ def test_meeting_recordings_get_request(self): self.assertEqual(response.status_code, 404) # check access right with user2 - meeting = Meeting.objects.get(name="test_pod#1") + meeting = Meeting.objects.get(name="test_pod1") url = reverse( "meeting:internal_recordings", kwargs={"meeting_id": meeting.meeting_id} ) @@ -621,7 +621,7 @@ def test_meeting_delete_internal_recordings_get_request(self): self.assertEqual(response.status_code, 404) # not found # check access right with user2 - meeting = Meeting.objects.get(name="test_pod#1") + meeting = Meeting.objects.get(name="test_pod1") url = reverse( "meeting:delete_internal_recording", kwargs={ diff --git a/pod/meeting/urls.py b/pod/meeting/urls.py index 5210bdcdcc..e14c1c2cd2 100644 --- a/pod/meeting/urls.py +++ b/pod/meeting/urls.py @@ -38,6 +38,16 @@ views.delete_internal_recording, name="delete_internal_recording", ), + path( + "internal_recording///", + views.internal_recording, + name="internal_recording", + ), + path( + "recording_ready/", + views.recording_ready, + name="recording_ready" + ), ] urlpatterns += [ diff --git a/pod/meeting/utils.py b/pod/meeting/utils.py index 634b6fe315..a3fa87fa4a 100644 --- a/pod/meeting/utils.py +++ b/pod/meeting/utils.py @@ -1,11 +1,24 @@ """Utils for Meeting module.""" from datetime import date, timedelta from django.conf import settings +from django.core.mail import EmailMultiAlternatives +from django.utils.translation import ugettext_lazy as _ +from pod.main.views import TEMPLATE_VISIBLE_SETTINGS from hashlib import sha1 +import bleach + BBB_API_URL = getattr(settings, "BBB_API_URL", "") BBB_SECRET_KEY = getattr(settings, "BBB_SECRET_KEY", "") +DEBUG = getattr(settings, "DEBUG", True) + +DEFAULT_FROM_EMAIL = getattr(settings, "DEFAULT_FROM_EMAIL", "noreply@univ.fr") + +MANAGERS = getattr(settings, "MANAGERS", {}) + +SECURE_SSL_REDIRECT = getattr(settings, "SECURE_SSL_REDIRECT", False) + def api_call(query, call): """Generate checksum for BBB API call.""" @@ -70,3 +83,60 @@ def get_nth_week_number(original_date): if first_day.weekday() <= original_date.weekday(): nb_weeks += 1 return nb_weeks + + +def send_email_recording_ready(meeting): + """Send an email when a recording was saved and available on Pod.""" + if DEBUG: + print("SEND EMAIL WHEN RECORDING READY") + + url_scheme = "https" if SECURE_SSL_REDIRECT else "http" + content_url = "%s:%s" % (url_scheme, meeting.get_recordings_full_url()) + + subject = "[%s] %s" % ( + TEMPLATE_VISIBLE_SETTINGS.get("TITLE_SITE"), + _( + "A new Big Blue Button recording for '%(name)s' meeting is available" + ) + % { + "name": meeting.name + }, + ) + + from_email = DEFAULT_FROM_EMAIL + + to_email = [meeting.owner.email] + + html_message = ( + '

%s

%s

%s
%s

%s

' + % ( + _("Hello,"), + _( + "A new Big Blue Button recording for “%(content_title)s” meeting" + + " is now available on %(site_title)s." + ) + % { + "content_title": "%s" % meeting.name, + "site_title": TEMPLATE_VISIBLE_SETTINGS.get("TITLE_SITE"), + }, + _("You will find it here:"), + content_url, + content_url, + _("Regards."), + ) + ) + + text_message = bleach.clean(html_message, tags=[], strip=True) + + msg = EmailMultiAlternatives( + subject, + text_message, + from_email, + to_email + ) + msg.attach_alternative(html_message, "text/html") + + if DEBUG: + print(text_message) + + msg.send() diff --git a/pod/meeting/views.py b/pod/meeting/views.py index 67fe67c19a..9298069933 100644 --- a/pod/meeting/views.py +++ b/pod/meeting/views.py @@ -1,11 +1,15 @@ """Views of the Meeting module.""" -import os import bleach +import jwt +import logging +import os +import requests +import traceback from .forms import MeetingForm, MeetingDeleteForm, MeetingPasswordForm from .forms import MeetingInviteForm from .models import Meeting, InternalRecording -from .utils import get_nth_week_number +from .utils import get_nth_week_number, send_email_recording_ready from datetime import datetime from django.conf import settings from django.contrib import messages @@ -14,7 +18,7 @@ from django.core.exceptions import SuspiciousOperation from django.core.exceptions import PermissionDenied from django.core.mail import EmailMultiAlternatives -from django.http import JsonResponse, HttpResponse +from django.http import JsonResponse, HttpResponse, HttpResponseBadRequest from django.shortcuts import get_object_or_404 from django.shortcuts import render, redirect from django.templatetags.static import static @@ -23,10 +27,10 @@ from django.utils.html import mark_safe from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_protect -from django.views.decorators.csrf import ensure_csrf_cookie +from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt from pod.import_video.utils import StatelessRecording -from pod.import_video.utils import secure_request_for_upload, parse_remote_file -from pod.import_video.utils import download_video_file, manage_recording_url, save_video +from pod.import_video.utils import manage_download, parse_remote_file +from pod.import_video.utils import save_video, secure_request_for_upload from pod.main.views import in_maintenance, TEMPLATE_VISIBLE_SETTINGS from pod.main.utils import secure_post_request, display_message_with_icon @@ -53,6 +57,7 @@ "role": _("Role"), }, ) +BBB_SECRET_KEY = getattr(settings, "BBB_SECRET_KEY", "") MEETING_DISABLE_RECORD = getattr(settings, "MEETING_DISABLE_RECORD", True) VIDEO_ALLOWED_EXTENSIONS = getattr( @@ -81,12 +86,16 @@ ), ) +VIDEOS_DIR = getattr(settings, "VIDEOS_DIR", "videos") + __TITLE_SITE__ = ( TEMPLATE_VISIBLE_SETTINGS["TITLE_SITE"] if (TEMPLATE_VISIBLE_SETTINGS.get("TITLE_SITE")) else "Pod" ) +log = logging.getLogger(__name__) + @login_required(redirect_field_name="referrer") def my_meetings(request): @@ -525,18 +534,19 @@ def get_meeting_info(request, meeting_id): @login_required(redirect_field_name="referrer") -def internal_recordings(request, meeting_id): - """List the internal recordings. +def get_internal_recordings(request, meeting_id, recording_id=None): + """List the internal recordings, depends on parameters (core function). Args: request (Request): HTTP request meeting_id (String): meeting id (BBB format) + recording_id (String, optional): recording id (BBB format) Raises: PermissionDenied: if user not allowed Returns: - HTTPResponse: internal recordings list + recordings[]: Array of recordings corresponding to parameters """ meeting = get_object_or_404( Meeting, meeting_id=meeting_id, site=get_current_site(request) @@ -548,7 +558,9 @@ def internal_recordings(request, meeting_id): # The user can delete this recording ? can_delete = get_can_delete_recordings(request, meeting) - meeting_recordings = meeting.get_recordings() + # Get one or more recordings + meeting_recordings = get_one_or_more_recordings(request, meeting, recording_id) + recordings = [] if type(meeting_recordings.get("recordings")) is dict: for data in meeting_recordings["recordings"].values(): @@ -568,10 +580,7 @@ def internal_recordings(request, meeting_id): # Uploading to Pod is possible only for video playback if data["playback"][playback]["type"] == "video": bbb_recording.canUpload = True - # Manage BBB recording URL, if necessary - bbb_recording.videoUrl = manage_recording_url( - data["playback"][playback]["url"] - ) + bbb_recording.videoUrl = data["playback"][playback]["url"] # Only the owner can delete their recordings bbb_recording.canDelete = can_delete @@ -583,6 +592,39 @@ def internal_recordings(request, meeting_id): bbb_recording.uploadedToPodBy = recording.uploaded_to_pod_by recordings.append(bbb_recording) + return recordings + + +@login_required(redirect_field_name="referrer") +def get_one_or_more_recordings(request, meeting, recording_id=None): + """Define recordings useful for get_internal_recordings function.""" + if recording_id is None: + meeting_recordings = meeting.get_recordings() + else: + meeting_recordings = meeting.get_recording(recording_id) + return meeting_recordings + + +@login_required(redirect_field_name="referrer") +def internal_recordings(request, meeting_id): + """List the internal recordings (main function). + + Args: + request (Request): HTTP request + meeting_id (String): meeting id (BBB format) + + Raises: + PermissionDenied: if user not allowed + + Returns: + HTTPResponse: internal recordings list + """ + meeting = get_object_or_404( + Meeting, meeting_id=meeting_id, site=get_current_site(request) + ) + # Call the core function + recordings = get_internal_recordings(request, meeting_id) + return render( request, "meeting/internal_recordings.html", @@ -594,6 +636,33 @@ def internal_recordings(request, meeting_id): ) +@csrf_protect +@ensure_csrf_cookie +@login_required(redirect_field_name="referrer") +def internal_recording(request, meeting_id, recording_id): + """Get an internal recording, in JSON format (main function). + + Args: + request (Request): HTTP request + meeting_id (String): meeting id (BBB format) + recording_id (String): recording id (BBB format) + + Raises: + PermissionDenied: if user not allowed + + Returns: + HTTPResponse: internal recording (JSON format) + """ + # Call the core function + recordings = get_internal_recordings(request, meeting_id, recording_id) + # JSON format + data = recordings[0].to_json() + if request.is_ajax(): + return HttpResponse(data, content_type="application/json") + else: + return HttpResponseBadRequest() + + def secure_internal_recordings(request, meeting): """Secure the internal recordings of a meeting. @@ -944,6 +1013,21 @@ def get_rrule(meeting): return rrule +def get_video_url(request, meeting_id, recording_id): + """Get recording video URL.""" + meeting = get_object_or_404( + Meeting, meeting_id=meeting_id, site=get_current_site(request) + ) + meeting_recordings = meeting.get_recording(recording_id) + if type(meeting_recordings.get("recordings")) is dict: + for data in meeting_recordings["recordings"].values(): + for playback in data["playback"]: + # Uploading to Pod is possible only for video playback + if data["playback"][playback]["type"] == "video": + source_url = data["playback"][playback]["url"] + return source_url + + @csrf_protect @ensure_csrf_cookie @login_required(redirect_field_name="referrer") @@ -1109,34 +1193,47 @@ def upload_bbb_recording_to_pod(request, record_id, meeting_id): Boolean: True if upload achieved """ try: + # Session useful to achieve requests (and keep cookies between) + session = requests.Session() + recording = InternalRecording.objects.get(id=record_id) - source_url = request.POST.get("source_url") + + source_url = get_video_url(request, meeting_id, recording.recording_id) # Step 1: Download and parse the remote HTML file if necessary # Check if extension is a video extension extension = source_url.split(".")[-1].lower() - if extension in VIDEO_ALLOWED_EXTENSIONS: - # URL corresponds to a video file - source_video_url = source_url - else: + # Name of the video file to add to the URL (if necessary) + video_file_add = "" + if extension not in VIDEO_ALLOWED_EXTENSIONS: # Download and parse the remote HTML file - video_file = parse_remote_file(source_url) - source_video_url = source_url + video_file + video_file_add = parse_remote_file(session, source_url) + # Extension overload + extension = video_file_add.split(".")[-1].lower() # Step 2: Define destination source file - extension = source_video_url.split(".")[-1].lower() discrim = datetime.now().strftime("%Y%m%d%H%M%S") dest_file = os.path.join( settings.MEDIA_ROOT, - "videos", + VIDEOS_DIR, request.user.owner.hashkey, os.path.basename("%s-%s.%s" % (discrim, recording.id, extension)), ) - os.makedirs(os.path.dirname(dest_file), exist_ok=True) + dest_path = os.path.join( + VIDEOS_DIR, + request.user.owner.hashkey, + os.path.basename("%s-%s.%s" % (discrim, recording.id, extension)), + ) + # Step 3: Download the video file - download_video_file(source_video_url, dest_file) + source_video_url = manage_download( + session, + source_url, + video_file_add, + dest_file + ) # Step 4: Save informations about the recording recording_title = request.POST.get("recording_name") @@ -1154,7 +1251,7 @@ def upload_bbb_recording_to_pod(request, record_id, meeting_id): '%(url)s' ) % {"type": "Big Blue Button", "url": source_video_url} - save_video(request, dest_file, recording_title, description) + save_video(request, dest_path, recording_title, description) return True except Exception as exc: @@ -1172,3 +1269,52 @@ def upload_bbb_recording_to_pod(request, record_id, meeting_id): "Try changing the record type or address for this recording." ) raise ValueError(msg) + + +@csrf_exempt +def recording_ready(request): + """Make a callback when a recording is ready for viewing. + + Useful to send an email to prevent the user. + See https://docs.bigbluebutton.org/development/api/#recording-ready-callback-url + Args: + request (Request): HTTP request + + Returns: + HttpResponse: empty response + """ + meeting_id = "" + recording_id = "" + try: + if request.method == "POST": + # Get parameters, encoded in HS256 + signed_parameters = request.POST.get("signed_parameters") + # Decoded parameters with BBB secret key + # Returns JSON format like : {'meeting_id': 'xxx', 'record_id': 'xxx'} + decoded_parameters = jwt.decode( + signed_parameters, + BBB_SECRET_KEY, + algorithms=['HS256'] + ) + # Get data + meeting_id = decoded_parameters["meeting_id"] + recording_id = decoded_parameters["record_id"] + meeting = get_object_or_404( + Meeting, meeting_id=meeting_id, site=get_current_site(request) + ) + # Send email to the owner + send_email_recording_ready(meeting) + else: + raise ValueError("No POST method") + + return HttpResponse() + except Exception as exc: + log.error( + "Error when check for recording (%s %s) ready URL: %s. %s" % ( + meeting_id, + recording_id, + mark_safe(str(exc)), + traceback.format_exc() + ) + ) + return HttpResponse() diff --git a/requirements.txt b/requirements.txt index c1ef5078d3..0eab49e34c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,3 +35,4 @@ bleach==6.0.0 pytube==15.0.0 django-redis-sessions paramiko~=3.1.0 +djangorestframework-simplejwt==5.3.0
{{ record.get_start_time }} {% if record.presentationUrl != "" %} - - + + {% endif %} {% if record.videoUrl != "" %} - - + + {% endif %}