bugdown: Add preview for vimeo videos.

This also amends a commit from Brock Whittaker <brock@zulipchat.com>
that merges two separate functions for YouTube videos and Vimeo videos
into a generic video recall function.

Fixes #7550.
This commit is contained in:
Shreyansh Dwivedi
2017-12-15 02:47:00 +05:30
committed by showell
parent 9fe284b442
commit b0fb7aa6b2
3 changed files with 87 additions and 11 deletions

View File

@@ -53,19 +53,28 @@ function display_image(payload, options) {
$(".image-actions .open, .image-actions .download").attr("href", payload.source); $(".image-actions .open, .image-actions .download").attr("href", payload.source);
} }
function display_youtube_video(payload) { function display_video(payload) {
render_lightbox_list_images(payload.preview); render_lightbox_list_images(payload.preview);
$("#lightbox_overlay .image-preview, .image-description, .download, .lightbox-canvas-trigger").hide(); $("#lightbox_overlay .image-preview, .image-description, .download, .lightbox-canvas-trigger").hide();
var source;
if (payload.type === "youtube-video") {
source = "https://www.youtube.com/embed/" + payload.source;
} else if (payload.type === "vimeo-video") {
source = "https://player.vimeo.com/video/" + payload.source;
}
var iframe = $("<iframe></iframe>", { var iframe = $("<iframe></iframe>", {
src: "https://www.youtube.com/embed/" + payload.source, src: source,
frameborder: 0, frameborder: 0,
allowfullscreen: true, allowfullscreen: true,
}); });
$("#lightbox_overlay .player-container").html(iframe).show(); $("#lightbox_overlay .player-container").html(iframe).show();
$(".image-actions .open").attr("href", "https://youtu.be/" + payload.source);
var url = (payload.type === "youtube-video" ? "https://youtu.be/" : "https://vimeo.com/") + payload.source;
$(".image-actions .open").attr("href", url);
} }
// the image param is optional, but required on the first preview of an image. // the image param is optional, but required on the first preview of an image.
@@ -84,6 +93,7 @@ exports.open = function (image, options) {
// if wrapped in the .youtube-video class, it will be length = 1, and therefore // if wrapped in the .youtube-video class, it will be length = 1, and therefore
// cast to true. // cast to true.
var is_youtube_video = !!$image.closest(".youtube-video").length; var is_youtube_video = !!$image.closest(".youtube-video").length;
var is_vimeo_video = !!$image.closest(".vimeo-video").length;
var payload; var payload;
// if the asset_map already contains the metadata required to display the // if the asset_map already contains the metadata required to display the
@@ -94,20 +104,32 @@ exports.open = function (image, options) {
} else { } else {
var $parent = $image.parent(); var $parent = $image.parent();
var $message = $parent.closest("[zid]"); var $message = $parent.closest("[zid]");
var $type;
var $source;
if (is_youtube_video) {
$type = "youtube-video";
$source = $parent.attr("data-id");
} else if (is_vimeo_video) {
$type = "vimeo-video";
$source = $parent.attr("data-id");
} else {
$type = "image";
$source = $image.attr("src");
}
payload = { payload = {
user: message_store.get($message.attr("zid")).sender_full_name, user: message_store.get($message.attr("zid")).sender_full_name,
title: $image.parent().attr("title"), title: $image.parent().attr("title"),
type: is_youtube_video ? "youtube-video" : "image", type: $type,
preview: $image.attr("src"), preview: $image.attr("src"),
source: is_youtube_video ? $parent.attr("data-id") : $image.attr("src"), source: $source,
}; };
asset_map[payload.preview] = payload; asset_map[payload.preview] = payload;
} }
if (payload.type === "youtube-video") { if (payload.type.match("-video")) {
display_youtube_video(payload); display_video(payload);
} else if (payload.type === "image") { } else if (payload.type === "image") {
display_image(payload, options); display_image(payload, options);
} }

View File

@@ -253,13 +253,31 @@ def add_embed(root: Element, link: Text, extracted_data: Dict[Text, Any]) -> Non
a.set("target", "_blank") a.set("target", "_blank")
a.set("title", title) a.set("title", title)
a.text = title a.text = title
description = extracted_data.get('description') description = extracted_data.get('description')
if description: if description:
description_elm = markdown.util.etree.SubElement(data_container, "div") description_elm = markdown.util.etree.SubElement(data_container, "div")
description_elm.set("class", "message_embed_description") description_elm.set("class", "message_embed_description")
description_elm.text = description description_elm.text = description
def add_vimeo_preview(root: Element, link: Text, extracted_data: Dict[Text, Any], vm_id: Text) -> None:
container = markdown.util.etree.SubElement(root, "div")
container.set("class", "vimeo-video message_inline_image")
img_link = extracted_data.get('image')
if img_link:
parsed_img_link = urllib.parse.urlparse(img_link)
# Append domain where relative img_link url is given
if not parsed_img_link.netloc:
parsed_url = urllib.parse.urlparse(link)
domain = '{url.scheme}://{url.netloc}/'.format(url=parsed_url)
img_link = urllib.parse.urljoin(domain, img_link)
anchor = markdown.util.etree.SubElement(container, "a")
anchor.set("href", link)
anchor.set("target", "_blank")
anchor.set("data-id", vm_id)
anchor.set("title", link)
img = markdown.util.etree.SubElement(anchor, "img")
img.set("src", img_link)
@cache_with_key(lambda tweet_id: tweet_id, cache_name="database", with_statsd_key="tweet_data") @cache_with_key(lambda tweet_id: tweet_id, cache_name="database", with_statsd_key="tweet_data")
def fetch_tweet_data(tweet_id: Text) -> Optional[Dict[Text, Any]]: def fetch_tweet_data(tweet_id: Text) -> Optional[Dict[Text, Any]]:
@@ -327,13 +345,11 @@ def fetch_open_graph_image(url: Text) -> Optional[Dict[str, Any]]:
# a closing tag if it has not been closed yet. # a closing tag if it has not been closed yet.
last_closed = True last_closed = True
head = [] head = []
# TODO: What if response content is huge? Should we get headers first? # TODO: What if response content is huge? Should we get headers first?
try: try:
content = requests.get(url, timeout=1).text content = requests.get(url, timeout=1).text
except Exception: except Exception:
return None return None
# Extract the head and meta tags # Extract the head and meta tags
# All meta tags are self closing, have no children or are closed # All meta tags are self closing, have no children or are closed
# automatically. # automatically.
@@ -529,6 +545,27 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor):
return "https://i.ytimg.com/vi/%s/default.jpg" % (yt_id,) return "https://i.ytimg.com/vi/%s/default.jpg" % (yt_id,)
return None return None
def vimeo_id(self, url: Text) -> Optional[Text]:
if not image_preview_enabled_for_realm():
return None
#(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)
# If it matches, match.group('id') is the video id.
vimeo_re = r'^((http|https)?:\/\/(www\.)?vimeo.com\/' + \
r'(?:channels\/(?:\w+\/)?|groups\/' + \
r'([^\/]*)\/videos\/|)(\d+)(?:|\/\?))$'
match = re.match(vimeo_re, url)
if match is None:
return None
return match.group(5)
def vimeo_image(self, url: Text) -> Optional[Text]:
vm_id = self.vimeo_id(url)
if vm_id is not None:
return "http://i.vimeocdn.com/video/%s.jpg" % (vm_id,)
return None
def twitter_text(self, text: Text, def twitter_text(self, text: Text,
urls: List[Dict[Text, Text]], urls: List[Dict[Text, Text]],
user_mentions: List[Dict[Text, Any]], user_mentions: List[Dict[Text, Any]],
@@ -841,8 +878,14 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor):
except NotFoundInCache: except NotFoundInCache:
current_message.links_for_preview.add(url) current_message.links_for_preview.add(url)
continue continue
vimeo = self.vimeo_image(url)
if extracted_data: if extracted_data:
add_embed(root, url, extracted_data) if vimeo is not None:
vm_id = self.vimeo_id(url)
add_vimeo_preview(root, url, extracted_data, vm_id)
continue
else:
add_embed(root, url, extracted_data)
class Avatar(markdown.inlinepatterns.Pattern): class Avatar(markdown.inlinepatterns.Pattern):

View File

@@ -290,6 +290,17 @@ class BugdownTest(ZulipTestCase):
self.assertEqual(converted, '<p><a href="http://www.youtube.com/watch?v=hx1mjT73xYE" target="_blank" title="http://www.youtube.com/watch?v=hx1mjT73xYE">http://www.youtube.com/watch?v=hx1mjT73xYE</a></p>\n<div class="youtube-video message_inline_image"><a data-id="hx1mjT73xYE" href="http://www.youtube.com/watch?v=hx1mjT73xYE" target="_blank" title="http://www.youtube.com/watch?v=hx1mjT73xYE"><img src="https://i.ytimg.com/vi/hx1mjT73xYE/default.jpg"></a></div>') self.assertEqual(converted, '<p><a href="http://www.youtube.com/watch?v=hx1mjT73xYE" target="_blank" title="http://www.youtube.com/watch?v=hx1mjT73xYE">http://www.youtube.com/watch?v=hx1mjT73xYE</a></p>\n<div class="youtube-video message_inline_image"><a data-id="hx1mjT73xYE" href="http://www.youtube.com/watch?v=hx1mjT73xYE" target="_blank" title="http://www.youtube.com/watch?v=hx1mjT73xYE"><img src="https://i.ytimg.com/vi/hx1mjT73xYE/default.jpg"></a></div>')
def test_inline_vimeo(self) -> None:
msg = 'Check out the debate: https://vimeo.com/246979354'
converted = bugdown_convert(msg)
self.assertEqual(converted, '<p>Check out the debate: <a href="https://vimeo.com/246979354" target="_blank" title="https://vimeo.com/246979354">https://vimeo.com/246979354</a></p>')
msg = 'https://vimeo.com/246979354'
converted = bugdown_convert(msg)
self.assertEqual(converted, '<p><a href="https://vimeo.com/246979354" target="_blank" title="https://vimeo.com/246979354">https://vimeo.com/246979354</a></p>')
@override_settings(INLINE_IMAGE_PREVIEW=True) @override_settings(INLINE_IMAGE_PREVIEW=True)
def test_inline_image_preview(self) -> None: def test_inline_image_preview(self) -> None:
with_preview = '<p>Test: <a href="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg" target="_blank" title="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg">http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg</a></p>\n<div class="message_inline_image"><a href="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg" target="_blank" title="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg"><img src="https://external-content.zulipcdn.net/389b5d7148a0cbc7475ed564e1b03ceb476bdacb/687474703a2f2f63646e2e77616c6c70617065727361666172692e636f6d2f31332f362f313665566a782e6a706567"></a></div>' with_preview = '<p>Test: <a href="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg" target="_blank" title="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg">http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg</a></p>\n<div class="message_inline_image"><a href="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg" target="_blank" title="http://cdn.wallpapersafari.com/13/6/16eVjx.jpeg"><img src="https://external-content.zulipcdn.net/389b5d7148a0cbc7475ed564e1b03ceb476bdacb/687474703a2f2f63646e2e77616c6c70617065727361666172692e636f6d2f31332f362f313665566a782e6a706567"></a></div>'