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 Bonjour,\n"
-" %(owner)s vous invite à une réunion récurrente "
-"%(meeting_title)s. %(owner)s vous invite à une réunion récurrente "
+"%(meeting_title)s. Date de début : %(start_date_time)s Récurrent jusqu’à la date : %(end_date)s La réunion se tiendra tou(te)s les %(frequency)s %(recurrence)s "
"p>\n"
" Voici le lien pour rejoindre la réunion :\n"
" %(join_link)s Vous avez besoin de ce mot de passe pour entrer : "
-"%(password)s Vous avez besoin de ce mot de passe pour entrer : "
+"%(password)s Cordialement Date de fin : %(end_date)s Voici le lien pour rejoindre la réunion :\n"
" %(join_link)s Vous avez besoin de ce mot de passe pour entrer : "
-"%(password)s Vous avez besoin de ce mot de passe pour entrer : "
+"%(password)s Cordialement
%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
Pour "
-"l’ajouter, cliquez sur le lien ci dessous.
Cordialement
" +"l’ajouter, cliquez sur le lien ci dessous." +"%(link_url)sCordialement
" #: 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" -"\n" +"\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
+ {% 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." %} +
+ +{{ record.get_start_time }} | {% if record.presentationUrl != "" %} - - + + {% endif %} {% if record.videoUrl != "" %} - - + + {% endif %} | @@ -64,16 +75,15 @@ - {% csrf_token %} - + {% endif %} {% if record.canDelete %} {% trans "Please confirm you want to delete the recording" as confirmDelete %} {% 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/