Skip to content

Commit 36bef46

Browse files
LoicBonaventvsabatie
authored andcommitted
[DONE] Feature import external video, add mediacad platform (EsupPortail#1014)
* Add documentation about video platforms * Add tests * Add functions to manage import videos from Mediacad platform, and refactor some code * Add functions to manage import videos from Mediacad platform; improved error message handling * Add translations about Pod informations and Mediacad platform * Modification of a test due to improved error message handling * Add translations about Pod informations and Mediacad platform * Add translations about Pod informations and Mediacad platform * Change translation : Impossible to upload to Pod the video => Unable to upload the video to Pod; and remove fuzzy translations * Add role='alert' to one error message
1 parent 5c5153d commit 36bef46

File tree

9 files changed

+504
-109
lines changed

9 files changed

+504
-109
lines changed

pod/import_video/templates/import_video/add_or_edit.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,14 @@ <h2 class="h4 card-header card-title pod-card__title ps-2">{% trans "Uploading"
181181
</div>
182182
</div>
183183

184+
<div class="card">
185+
<h2 class="h4 card-header card-title pod-card__title ps-2">{% trans "Useful tips" %}</h2>
186+
<div class="card-body card-text">
187+
<p>{% trans "The entered address must correspond to a video file. The platform to which you copy the link may offer several possibilities; feel free to test the different links provided." %}</p>
188+
<p>{% trans "For example, in the case of a video from a Pod platform, please enter the source file address, available in the video edition." %}</p>
189+
</div>
190+
</div>
191+
184192
<div class="card" id="card-helpfields">
185193
<h2 class="card-header card-title pod-card__title h4">{% trans "Terms of Service"%}</h2>
186194
<div class="card-body" id="formfields">

pod/import_video/tests/test_views.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ def setUp(self):
141141
type="video",
142142
source_url="https://video.url",
143143
)
144+
ExternalRecording.objects.create(
145+
id=6,
146+
name="test mediacad video recording1",
147+
site=site,
148+
owner=user2,
149+
type="video",
150+
source_url="https://mediacad.url",
151+
)
144152
user.owner.sites.add(Site.objects.get_current())
145153
user.owner.save()
146154
user2.owner.sites.add(Site.objects.get_current())
@@ -223,7 +231,7 @@ def test_recording_upload_post_request(self):
223231
)
224232
self.assertEqual(response.status_code, HTTPStatus.OK)
225233
# Message for a bad URL
226-
self.assertTrue(b"Impossible to upload to Pod the PeerTube" in response.content)
234+
self.assertTrue(b"Unable to upload the video to Pod" in response.content)
227235

228236
# External BBB type
229237
recordingBBB = ExternalRecording.objects.get(name="test external bbb recording1")
@@ -243,7 +251,7 @@ def test_recording_upload_post_request(self):
243251
)
244252
self.assertEqual(response.status_code, HTTPStatus.OK)
245253
# Message for a bad URL
246-
self.assertTrue(b"Impossible to upload to Pod the video" in response.content)
254+
self.assertTrue(b"Unable to upload the video to Pod" in response.content)
247255

248256
# Video type
249257
recordingVideo = ExternalRecording.objects.get(
@@ -258,13 +266,35 @@ def test_recording_upload_post_request(self):
258266
response = self.client.post(
259267
url,
260268
{
261-
"recording_name": "test video recording1",
269+
"recording_name": "test direct video recording1",
270+
"source_url": "https://video.url",
271+
},
272+
follow=True,
273+
)
274+
self.assertEqual(response.status_code, HTTPStatus.OK)
275+
# Message for a bad URL
276+
self.assertTrue(b"Unable to upload the video to Pod" in response.content)
277+
278+
# Video type - Mediacad video
279+
recordingMediacad = ExternalRecording.objects.get(
280+
name="test mediacad video recording1"
281+
)
282+
self.user = User.objects.get(username="pod2")
283+
self.client.force_login(self.user)
284+
url = reverse(
285+
"import_video:upload_external_recording_to_pod",
286+
kwargs={"record_id": recordingMediacad.id},
287+
)
288+
response = self.client.post(
289+
url,
290+
{
291+
"recording_name": "test mediacad video recording1",
262292
"source_url": "https://video.url",
263293
},
264294
follow=True,
265295
)
266296
self.assertEqual(response.status_code, HTTPStatus.OK)
267297
# Message for a bad URL
268-
self.assertTrue(b"Impossible to upload to Pod the video" in response.content)
298+
self.assertTrue(b"Unable to upload the video to Pod" in response.content)
269299

270300
print(" ---> test_recording_upload_get_request of RecordingUploadTestView: OK!")

pod/import_video/utils.py

Lines changed: 192 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Utils for Meeting and Import_video module."""
22
import json
3+
import os
34
import requests
45
import shutil
56

@@ -42,6 +43,8 @@
4243
),
4344
)
4445

46+
VIDEOS_DIR = getattr(settings, "VIDEOS_DIR", "videos")
47+
4548

4649
def secure_request_for_upload(request):
4750
"""Check that the request is correct for uploading a recording.
@@ -55,7 +58,7 @@ def secure_request_for_upload(request):
5558
# Source_url and recording_name are necessary
5659
if request.POST.get("source_url") == "" or request.POST.get("recording_name") == "":
5760
msg = {}
58-
msg["error"] = _("Impossible to upload to Pod the video")
61+
msg["error"] = _("Unable to upload the video to Pod")
5962
msg["message"] = _("No URL found / No recording name")
6063
msg["proposition"] = _(
6164
"Try changing the record type or address for this recording"
@@ -81,20 +84,15 @@ def parse_remote_file(session, source_html_url):
8184
if response.status_code != 200:
8285
msg = {}
8386
msg["error"] = _(
84-
"The HTML file for this recording was not found on the BBB server."
87+
"The HTML file for this recording was not found on the server."
8588
)
8689
# If we want to display the 404/500... page to the user
8790
# msg["message"] = response.content.decode("utf-8")
8891
msg["message"] = _("Error number: %s") % response.status_code
8992
raise ValueError(msg)
9093

9194
# Parse the BBB video HTML file
92-
parser = video_parser()
93-
# Manage the encoding
94-
if response.encoding == "ISO-8859-1":
95-
parser.feed(response.text.encode("ISO-8859-1").decode("utf-8"))
96-
else:
97-
parser.feed(response.text)
95+
parser = create_parser(response)
9896

9997
# Video file found
10098
if parser.video_check:
@@ -111,12 +109,17 @@ def parse_remote_file(session, source_html_url):
111109
# Returns the name of the video (if necessary, title is parser.title)
112110
return parser.video_file
113111
else:
114-
msg = {}
115-
msg["error"] = _(
116-
"The video file for this recording was not found in the HTML file."
117-
)
118-
msg["message"] = _("No video file found.")
119-
raise ValueError(msg)
112+
msg = ""
113+
# Useful tips for Pod links
114+
if (
115+
source_html_url.find("/video/") != -1
116+
or source_html_url.find("/media/videos/") != -1
117+
):
118+
msg = _(
119+
"In the case of a video from a Pod platform, please enter "
120+
"the source file address, available in the video edition."
121+
)
122+
raise ValueError("<div role='alert' class='alert alert-info'>%s</div>" % msg)
120123
except Exception as exc:
121124
msg = {}
122125
msg["error"] = _(
@@ -126,6 +129,16 @@ def parse_remote_file(session, source_html_url):
126129
raise ValueError(msg)
127130

128131

132+
def create_parser(response):
133+
"""Parse the BBB video HTML file and manage its encoding."""
134+
parser = video_parser()
135+
if response.encoding == "ISO-8859-1":
136+
parser.feed(response.text.encode("ISO-8859-1").decode("utf-8"))
137+
else:
138+
parser.feed(response.text)
139+
return parser
140+
141+
129142
def manage_recording_url(source_url, video_file_add):
130143
"""Generate the BBB video URL.
131144
@@ -136,7 +149,7 @@ def manage_recording_url(source_url, video_file_add):
136149
video_file_add (String): Name of the video file to add to the URL
137150
138151
Returns:
139-
String: good URL of a BBB recording video
152+
String: good URL of a BBB recording video or of the video file
140153
"""
141154
try:
142155
bbb_playback_video = "/video/"
@@ -201,7 +214,7 @@ def manage_download(session, source_url, video_file_add, dest_file):
201214

202215

203216
def download_video_file(session, source_video_url, dest_file):
204-
"""Download BBB video file.
217+
"""Download video file.
205218
206219
Args:
207220
session (Session) : session useful to achieve requests (and keep cookies between)
@@ -220,7 +233,7 @@ def download_video_file(session, source_video_url, dest_file):
220233
raise ValueError(
221234
_(
222235
"The video file for this recording "
223-
"was not found on the BBB server."
236+
"was not found on the server."
224237
)
225238
)
226239

@@ -281,10 +294,13 @@ def check_file_exists(source_url):
281294
Returns:
282295
Boolean: file exists (True) or not (False)
283296
"""
284-
response = requests.head(source_url, timeout=2)
285-
if response.status_code < 400:
286-
return True
287-
else:
297+
try:
298+
response = requests.head(source_url, timeout=2)
299+
if response.status_code < 400:
300+
return True
301+
else:
302+
return False
303+
except Exception:
288304
return False
289305

290306

@@ -297,12 +313,18 @@ def verify_video_exists_and_size(video_url):
297313
Raises:
298314
ValueError: exception raised if no video found in this URL or video oversized
299315
"""
300-
response = requests.head(video_url, timeout=2)
301-
if response.status_code < 400:
302-
# Video file size
303-
size = int(response.headers.get("Content-Length", "0"))
304-
check_video_size(size)
305-
else:
316+
try:
317+
response = requests.head(video_url, timeout=2)
318+
if response.status_code < 400:
319+
# Video file size
320+
size = int(response.headers.get("Content-Length", "0"))
321+
check_video_size(size)
322+
else:
323+
msg = {}
324+
msg["error"] = _("No video file found.")
325+
msg["message"] = _("No video file found for this address.")
326+
raise ValueError(msg)
327+
except Exception:
306328
msg = {}
307329
msg["error"] = _("No video file found.")
308330
msg["message"] = _("No video file found for this address.")
@@ -329,6 +351,149 @@ def check_video_size(video_size):
329351
raise ValueError(msg)
330352

331353

354+
def check_source_url(source_url): # noqa: C901
355+
"""Check the source URL to identify the used platform.
356+
357+
Platforms managed :
358+
- Mediacad platform (Médiathèque académique) : rewrite source URL if required
359+
and manage JSON API.
360+
"""
361+
base_url = ""
362+
media_id = ""
363+
format = ""
364+
url_api_video = ""
365+
source_video_url = ""
366+
platform = ""
367+
platform_type = None
368+
# Source URL array
369+
array_url = source_url.split('/')
370+
# Parse for parameters
371+
url = urlparse(source_url)
372+
if url.query:
373+
query = parse_qs(url.query, keep_blank_values=True)
374+
375+
try:
376+
if source_url.find("/download.php?t=") != -1 and url.query:
377+
# Mediacad direct video link (with ##format## in: mp4, source, webm)
378+
# ##mediacadBaseUrl##/download.php?t=##token##&e=##format##&m=##mediaId##
379+
base_url = source_url[:source_url.find("/download.php?t=")]
380+
media_id = query["m"][0]
381+
format = query["e"][0].replace("source", "mp4")
382+
# Force to download mp4 file if source
383+
source_video_url = source_url.replace("&e=source", "&e=mp4")
384+
platform = "Mediacad"
385+
elif (
386+
source_url.find("/d/d") != -1
387+
and source_url.find("/default/media/display/m") != -1
388+
):
389+
# Mediacad direct video link (with ##format## in: mp4, source, webm)
390+
# ##mediacadBaseUrl##/default/media/display/m/##mediaId##/e/##format##/d/d
391+
base_url = source_url[:source_url.find("/default/media/display/m/")]
392+
media_id = array_url[-5]
393+
format = array_url[-3].replace("source", "mp4")
394+
# Force to download mp4 file if source
395+
source_video_url = source_url.replace("/e/source", "/e/mp4")
396+
platform = "Mediacad"
397+
elif source_url.find("/d/m/e") != -1:
398+
# Mediacad direct video link (with ##format## in: mp4, source, webm)
399+
# ##mediacadBaseUrl##/m/##mediaId##/d/m/e/##format##
400+
base_url = source_url[:source_url.find("/m/")]
401+
media_id = array_url[-5]
402+
format = array_url[-1].replace("source", "mp4")
403+
source_video_url = source_url.replace("/e/source", "/e/mp4")
404+
platform = "Mediacad"
405+
elif source_url.find("/default/media/display/") != -1:
406+
# Mediacad page link
407+
# ##mediacadBaseUrl##/default/media/display/m/##mediaId##
408+
# ##mediacadBaseUrl##/default/media/display/page/1/sort/date/m/##mediaId##
409+
base_url = source_url[:source_url.find("/default/media/display/")]
410+
media_id = array_url[-1]
411+
format = "mp4"
412+
source_video_url = source_url + "/e/mp4/d/d"
413+
platform = "Mediacad"
414+
elif source_url.find("/m/") != -1:
415+
# Mediacad page link
416+
# ##mediacadBaseUrl##/m/##mediaId##
417+
base_url = source_url[:source_url.find("/m/")]
418+
media_id = array_url[-1]
419+
format = "mp4"
420+
# Download possible on all Mediacad platform with such an URL
421+
source_video_url = "%s/default/media/display/m/%s/e/mp4/d/d" % (
422+
base_url, media_id
423+
)
424+
platform = "Mediacad"
425+
426+
# Platform's URL identified
427+
if platform == "Mediacad":
428+
# Mediacad API (JSON format) is available at :
429+
# ##mediacadBaseUrl##/default/media/display/m/##mediaId##/d/j
430+
url_api_video = "%s/default/media/display/m/%s/d/j" % (base_url, media_id)
431+
# Platform type
432+
platform_type = TypeSourceURL(
433+
platform,
434+
source_video_url,
435+
format,
436+
url_api_video
437+
)
438+
439+
return platform_type
440+
except Exception as exc:
441+
msg = {}
442+
msg["error"] = _(
443+
"The video file for this recording was not found."
444+
)
445+
msg["message"] = mark_safe(str(exc))
446+
raise ValueError(msg)
447+
448+
449+
def define_dest_file(request, id, extension):
450+
""" Define standard destination filename for an external recording."""
451+
# Set a discriminant
452+
discrim = dt.now().strftime("%Y%m%d%H%M%S")
453+
dest_file = os.path.join(
454+
settings.MEDIA_ROOT,
455+
VIDEOS_DIR,
456+
request.user.owner.hashkey,
457+
os.path.basename("%s-%s.%s" % (discrim, id, extension)),
458+
)
459+
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
460+
return dest_file
461+
462+
463+
def define_dest_path(request, id, extension):
464+
""" Define standard destination path for an external recording."""
465+
# Set a discriminant
466+
discrim = dt.now().strftime("%Y%m%d%H%M%S")
467+
dest_path = os.path.join(
468+
VIDEOS_DIR,
469+
request.user.owner.hashkey,
470+
os.path.basename("%s-%s.%s" % (discrim, id, extension)),
471+
)
472+
return dest_path
473+
474+
475+
class TypeSourceURL:
476+
"""Manage external recording source URL.
477+
478+
Define context, and platform used, about a source URL.
479+
"""
480+
# Source URL type, like Mediacad, Pod...
481+
type = ""
482+
# Source video file URL
483+
url = ""
484+
# Video extension (mp4, webm...)
485+
extension = ""
486+
# API URL if supplied
487+
api_url = ""
488+
489+
def __init__(self, type, url, extension, api_url):
490+
"""Initialize."""
491+
self.type = type
492+
self.url = url
493+
self.extension = extension
494+
self.api_url = api_url
495+
496+
332497
class video_parser(HTMLParser):
333498
"""Useful to parse the BBB Web page and search for video file.
334499

0 commit comments

Comments
 (0)