Skip to content

[DONE] Feature import external video, add mediacad platform #1014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pod/import_video/templates/import_video/add_or_edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ <h2 class="h4 card-header card-title pod-card__title ps-2">{% trans "Uploading"
</div>
</div>

<div class="card">
<h2 class="h4 card-header card-title pod-card__title ps-2">{% trans "Useful tips" %}</h2>
<div class="card-body card-text">
<p>{% trans "The address entered 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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Une petite faute au début, voici son corrigé :

The entered address ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Salut Aymeric, merci pour la review.
C'est corrigé

<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>
</div>
</div>

<div class="card" id="card-helpfields">
<h2 class="card-header card-title pod-card__title h4">{% trans "Terms of Service"%}</h2>
<div class="card-body" id="formfields">
Expand Down
34 changes: 32 additions & 2 deletions pod/import_video/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ def setUp(self):
type="video",
source_url="https://video.url",
)
ExternalRecording.objects.create(
id=6,
name="test mediacad video recording1",
site=site,
owner=user2,
type="video",
source_url="https://mediacad.url",
)
user.owner.sites.add(Site.objects.get_current())
user.owner.save()
user2.owner.sites.add(Site.objects.get_current())
Expand Down Expand Up @@ -223,7 +231,7 @@ def test_recording_upload_post_request(self):
)
self.assertEqual(response.status_code, HTTPStatus.OK)
# Message for a bad URL
self.assertTrue(b"Impossible to upload to Pod the PeerTube" in response.content)
self.assertTrue(b"Impossible to upload to Pod the video" in response.content)

# External BBB type
recordingBBB = ExternalRecording.objects.get(name="test external bbb recording1")
Expand Down Expand Up @@ -258,7 +266,29 @@ def test_recording_upload_post_request(self):
response = self.client.post(
url,
{
"recording_name": "test video recording1",
"recording_name": "test direct video recording1",
"source_url": "https://video.url",
},
follow=True,
)
self.assertEqual(response.status_code, HTTPStatus.OK)
# Message for a bad URL
self.assertTrue(b"Impossible to upload to Pod the video" in response.content)

# Video type - Mediacad video
recordingMediacad = ExternalRecording.objects.get(
name="test mediacad video recording1"
)
self.user = User.objects.get(username="pod2")
self.client.force_login(self.user)
url = reverse(
"import_video:upload_external_recording_to_pod",
kwargs={"record_id": recordingMediacad.id},
)
response = self.client.post(
url,
{
"recording_name": "test mediacad video recording1",
"source_url": "https://video.url",
},
follow=True,
Expand Down
217 changes: 191 additions & 26 deletions pod/import_video/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Utils for Meeting and Import_video module."""
import json
import os
import requests
import shutil

Expand Down Expand Up @@ -42,6 +43,8 @@
),
)

VIDEOS_DIR = getattr(settings, "VIDEOS_DIR", "videos")


def secure_request_for_upload(request):
"""Check that the request is correct for uploading a recording.
Expand Down Expand Up @@ -81,20 +84,15 @@ def parse_remote_file(session, source_html_url):
if response.status_code != 200:
msg = {}
msg["error"] = _(
"The HTML file for this recording was not found on the BBB server."
"The HTML file for this recording was not found on the 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)

# Parse the BBB video HTML file
parser = video_parser()
# Manage the encoding
if response.encoding == "ISO-8859-1":
parser.feed(response.text.encode("ISO-8859-1").decode("utf-8"))
else:
parser.feed(response.text)
parser = create_parser(response)

# Video file found
if parser.video_check:
Expand All @@ -111,12 +109,17 @@ def parse_remote_file(session, source_html_url):
# Returns the name of the video (if necessary, title is parser.title)
return parser.video_file
else:
msg = {}
msg["error"] = _(
"The video file for this recording was not found in the HTML file."
)
msg["message"] = _("No video file found.")
raise ValueError(msg)
msg = ""
# Useful tips for Pod links
if (
source_html_url.find("/video/") != -1
or source_html_url.find("/media/videos/") != -1
):
msg = _(
"In the case of a video from a Pod platform, please enter "
"the source file address, available in the video edition."
)
raise ValueError("<div class='alert alert-info'>%s</div>" % msg)
except Exception as exc:
msg = {}
msg["error"] = _(
Expand All @@ -126,6 +129,16 @@ def parse_remote_file(session, source_html_url):
raise ValueError(msg)


def create_parser(response):
"""Parse the BBB video HTML file and manage its encoding."""
parser = video_parser()
if response.encoding == "ISO-8859-1":
parser.feed(response.text.encode("ISO-8859-1").decode("utf-8"))
else:
parser.feed(response.text)
return parser


def manage_recording_url(source_url, video_file_add):
"""Generate the BBB video URL.

Expand All @@ -136,7 +149,7 @@ def manage_recording_url(source_url, video_file_add):
video_file_add (String): Name of the video file to add to the URL

Returns:
String: good URL of a BBB recording video
String: good URL of a BBB recording video or of the video file
"""
try:
bbb_playback_video = "/video/"
Expand Down Expand Up @@ -201,7 +214,7 @@ def manage_download(session, source_url, video_file_add, dest_file):


def download_video_file(session, source_video_url, dest_file):
"""Download BBB video file.
"""Download video file.

Args:
session (Session) : session useful to achieve requests (and keep cookies between)
Expand All @@ -220,7 +233,7 @@ def download_video_file(session, source_video_url, dest_file):
raise ValueError(
_(
"The video file for this recording "
"was not found on the BBB server."
"was not found on the server."
)
)

Expand Down Expand Up @@ -281,10 +294,13 @@ def check_file_exists(source_url):
Returns:
Boolean: file exists (True) or not (False)
"""
response = requests.head(source_url, timeout=2)
if response.status_code < 400:
return True
else:
try:
response = requests.head(source_url, timeout=2)
if response.status_code < 400:
return True
else:
return False
except Exception:
return False


Expand All @@ -297,12 +313,18 @@ def verify_video_exists_and_size(video_url):
Raises:
ValueError: exception raised if no video found in this URL or video oversized
"""
response = requests.head(video_url, timeout=2)
if response.status_code < 400:
# Video file size
size = int(response.headers.get("Content-Length", "0"))
check_video_size(size)
else:
try:
response = requests.head(video_url, timeout=2)
if response.status_code < 400:
# Video file size
size = int(response.headers.get("Content-Length", "0"))
check_video_size(size)
else:
msg = {}
msg["error"] = _("No video file found.")
msg["message"] = _("No video file found for this address.")
raise ValueError(msg)
except Exception:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem, préciser l'exception quand c'est possible.

msg = {}
msg["error"] = _("No video file found.")
msg["message"] = _("No video file found for this address.")
Expand All @@ -329,6 +351,149 @@ def check_video_size(video_size):
raise ValueError(msg)


def check_source_url(source_url): # noqa: C901
"""Check the source URL to identify the used platform.

Platforms managed :
- Mediacad platform (Médiathèque académique) : rewrite source URL if required
and manage JSON API.
"""
base_url = ""
media_id = ""
format = ""
url_api_video = ""
source_video_url = ""
platform = ""
platform_type = None
# Source URL array
array_url = source_url.split('/')
# Parse for parameters
url = urlparse(source_url)
if url.query:
query = parse_qs(url.query, keep_blank_values=True)

try:
if source_url.find("/download.php?t=") != -1 and url.query:
# Mediacad direct video link (with ##format## in: mp4, source, webm)
# ##mediacadBaseUrl##/download.php?t=##token##&e=##format##&m=##mediaId##
base_url = source_url[:source_url.find("/download.php?t=")]
media_id = query["m"][0]
format = query["e"][0].replace("source", "mp4")
# Force to download mp4 file if source
source_video_url = source_url.replace("&e=source", "&e=mp4")
platform = "Mediacad"
elif (
source_url.find("/d/d") != -1
and source_url.find("/default/media/display/m") != -1
):
# Mediacad direct video link (with ##format## in: mp4, source, webm)
# ##mediacadBaseUrl##/default/media/display/m/##mediaId##/e/##format##/d/d
base_url = source_url[:source_url.find("/default/media/display/m/")]
media_id = array_url[-5]
format = array_url[-3].replace("source", "mp4")
# Force to download mp4 file if source
source_video_url = source_url.replace("/e/source", "/e/mp4")
platform = "Mediacad"
elif source_url.find("/d/m/e") != -1:
# Mediacad direct video link (with ##format## in: mp4, source, webm)
# ##mediacadBaseUrl##/m/##mediaId##/d/m/e/##format##
base_url = source_url[:source_url.find("/m/")]
media_id = array_url[-5]
format = array_url[-1].replace("source", "mp4")
source_video_url = source_url.replace("/e/source", "/e/mp4")
platform = "Mediacad"
elif source_url.find("/default/media/display/") != -1:
# Mediacad page link
# ##mediacadBaseUrl##/default/media/display/m/##mediaId##
# ##mediacadBaseUrl##/default/media/display/page/1/sort/date/m/##mediaId##
base_url = source_url[:source_url.find("/default/media/display/")]
media_id = array_url[-1]
format = "mp4"
source_video_url = source_url + "/e/mp4/d/d"
platform = "Mediacad"
elif source_url.find("/m/") != -1:
# Mediacad page link
# ##mediacadBaseUrl##/m/##mediaId##
base_url = source_url[:source_url.find("/m/")]
media_id = array_url[-1]
format = "mp4"
# Download possible on all Mediacad platform with such an URL
source_video_url = "%s/default/media/display/m/%s/e/mp4/d/d" % (
base_url, media_id
)
platform = "Mediacad"

# Platform's URL identified
if platform == "Mediacad":
# Mediacad API (JSON format) is available at :
# ##mediacadBaseUrl##/default/media/display/m/##mediaId##/d/j
url_api_video = "%s/default/media/display/m/%s/d/j" % (base_url, media_id)
# Platform type
platform_type = TypeSourceURL(
platform,
source_video_url,
format,
url_api_video
)

return platform_type
except Exception as exc:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type d'exception à préciser

msg = {}
msg["error"] = _(
"The video file for this recording was not found."
)
msg["message"] = mark_safe(str(exc))
raise ValueError(msg)


def define_dest_file(request, id, extension):
""" Define standard destination filename for an external recording."""
# Set a discriminant
discrim = dt.now().strftime("%Y%m%d%H%M%S")
dest_file = os.path.join(
settings.MEDIA_ROOT,
VIDEOS_DIR,
request.user.owner.hashkey,
os.path.basename("%s-%s.%s" % (discrim, id, extension)),
)
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
return dest_file


def define_dest_path(request, id, extension):
""" Define standard destination path for an external recording."""
# Set a discriminant
discrim = dt.now().strftime("%Y%m%d%H%M%S")
dest_path = os.path.join(
VIDEOS_DIR,
request.user.owner.hashkey,
os.path.basename("%s-%s.%s" % (discrim, id, extension)),
)
return dest_path


class TypeSourceURL:
"""Manage external recording source URL.

Define context, and platform used, about a source URL.
"""
# Source URL type, like Mediacad, Pod...
type = ""
# Source video file URL
url = ""
# Video extension (mp4, webm...)
extension = ""
# API URL if supplied
api_url = ""

def __init__(self, type, url, extension, api_url):
"""Initialize."""
self.type = type
self.url = url
self.extension = extension
self.api_url = api_url


class video_parser(HTMLParser):
"""Useful to parse the BBB Web page and search for video file.

Expand Down
Loading