diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 5f46d8f1df2..fce6212b224 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -15,6 +15,7 @@ from base64 import b64decode, b64encode from odoo.http import request from odoo import http, tools, _, SUPERUSER_ID from odoo.addons.http_routing.models.ir_http import slug, unslug +from odoo.addons.web_editor.tools import get_video_url_data from odoo.exceptions import UserError from odoo.modules.module import get_resource_path from odoo.tools.mimetypes import guess_mimetype @@ -174,6 +175,19 @@ class Web_Editor(http.Controller): return True + @http.route('/web_editor/video_url/data', type='json', auth='user', website=True) + def video_url_data(self, video_url, autoplay=False, loop=False, + hide_controls=False, hide_fullscreen=False, hide_yt_logo=False, + hide_dm_logo=False, hide_dm_share=False): + if not request.env.user.has_group('base.group_user'): + raise werkzeug.exceptions.Forbidden() + return get_video_url_data( + video_url, autoplay=autoplay, loop=loop, + hide_controls=hide_controls, hide_fullscreen=hide_fullscreen, + hide_yt_logo=hide_yt_logo, hide_dm_logo=hide_dm_logo, + hide_dm_share=hide_dm_share + ) + @http.route('/web_editor/attachment/add_data', type='json', auth='user', methods=['POST'], website=True) def add_data(self, name, data, is_image, quality=0, width=0, height=0, res_id=False, res_model='ir.ui.view', **kwargs): if is_image: diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js index 97bd2abceca..966c1e747bd 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js @@ -1228,7 +1228,8 @@ var VideoWidget = MediaWidget.extend({ /** * @override */ - start: function () { + start: async function () { + const _super = this._super.bind(this); this.$content = this.$('.o_video_dialog_iframe'); if (this.media) { @@ -1244,7 +1245,7 @@ var VideoWidget = MediaWidget.extend({ this.$('input#o_video_hide_dm_logo').prop('checked', src.indexOf('ui-logo=0') >= 0); this.$('input#o_video_hide_dm_share').prop('checked', src.indexOf('sharing-enable=0') >= 0); - this._updateVideo(); + await this._updateVideo(); } // loads the thumbnail of vimeo video previews. @@ -1264,7 +1265,7 @@ var VideoWidget = MediaWidget.extend({ }); }); - return this._super.apply(this, arguments); + return _super(...arguments); }, //-------------------------------------------------------------------------- @@ -1274,8 +1275,8 @@ var VideoWidget = MediaWidget.extend({ /** * @override */ - save: function () { - this._updateVideo(); + save: async function () { + await this._updateVideo(); const videoSrc = this.$content.attr('src'); if (this.isForBgVideo) { return Promise.resolve({bgVideoSrc: videoSrc}); @@ -1337,28 +1338,28 @@ var VideoWidget = MediaWidget.extend({ * errorCode -> if defined, either '0' for invalid URL or '1' for * unsupported video provider */ - _createVideoNode: function (url, options) { + _createVideoNode: async function (url, options) { options = options || {}; - const videoData = this._getVideoURLData(url, options); + const videoData = await this._getVideoURLData(url, options); if (videoData.error) { return {errorCode: 0}; } - if (!videoData.type) { + if (!videoData.platform) { return {errorCode: 1}; } const $video = $('') % data['embed_url'] + + +def get_video_thumbnail(video_url): + """ Computes the valid thumbnail image from given URL + (or None in case of invalid URL). + """ + source = get_video_source_data(video_url) + if source is None: + return None + + response = None + platform, video_id = source[:2] + if platform == 'youtube': + response = requests.get(f'https://img.youtube.com/vi/{video_id}/0.jpg', timeout=10) + elif platform == 'vimeo': + res = requests.get(f'http://vimeo.com/api/oembed.json?url={video_url}', timeout=10) + if res.ok: + data = res.json() + response = requests.get(data['thumbnail_url'], timeout=10) + elif platform == 'dailymotion': + response = requests.get(f'https://www.dailymotion.com/thumbnail/video/{video_id}', timeout=10) + elif platform == 'instagram': + response = requests.get(f'https://www.instagram.com/p/{video_id}/media/?size=t', timeout=10) + + if response and response.ok: + return image_process(base64.b64encode(response.content)) + return None diff --git a/addons/website/tools.py b/addons/website/tools.py index 6b47811e386..546757463df 100644 --- a/addons/website/tools.py +++ b/addons/website/tools.py @@ -1,9 +1,7 @@ # -*- encoding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import contextlib -import re from lxml import etree -from psycopg2 import sql from unittest.mock import Mock, MagicMock, patch import werkzeug @@ -12,53 +10,6 @@ import odoo from odoo.tools.misc import DotDict -def get_video_embed_code(video_url): - ''' Computes the valid iframe from given URL that can be embedded - (or False in case of invalid URL). - ''' - - if not video_url: - return False - - # To detect if we have a valid URL or not - validURLRegex = r'^(http:\/\/|https:\/\/|\/\/)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$' - - # Regex for few of the widely used video hosting services - ytRegex = r'^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube(-nocookie)?\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((?:\w|-){11})(?:\S+)?$' - vimeoRegex = r'\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*' - dmRegex = r'.+dailymotion.com\/(video|hub|embed)\/([^_?]+)[^#]*(#video=([^_&]+))?' - igRegex = r'(.*)instagram.com\/p\/(.[a-zA-Z0-9]*)' - ykuRegex = r'(.*).youku\.com\/(v_show\/id_|embed\/)(.+)' - - if not re.search(validURLRegex, video_url): - return False - else: - embedUrl = False - ytMatch = re.search(ytRegex, video_url) - vimeoMatch = re.search(vimeoRegex, video_url) - dmMatch = re.search(dmRegex, video_url) - igMatch = re.search(igRegex, video_url) - ykuMatch = re.search(ykuRegex, video_url) - - if ytMatch and len(ytMatch.groups()[1]) == 11: - embedUrl = '//www.youtube%s.com/embed/%s?rel=0' % (ytMatch.groups()[0] or '', ytMatch.groups()[1]) - elif vimeoMatch: - embedUrl = '//player.vimeo.com/video/%s' % (vimeoMatch.groups()[2]) - elif dmMatch: - embedUrl = '//www.dailymotion.com/embed/video/%s' % (dmMatch.groups()[1]) - elif igMatch: - embedUrl = '//www.instagram.com/p/%s/embed/' % (igMatch.groups()[1]) - elif ykuMatch: - ykuLink = ykuMatch.groups()[2] - if '.html?' in ykuLink: - ykuLink = ykuLink.split('.html?')[0] - embedUrl = '//player.youku.com/embed/%s' % (ykuLink) - else: - # We directly use the provided URL as it is - embedUrl = video_url - return '' % embedUrl - - def werkzeugRaiseNotFound(*args, **kwargs): raise werkzeug.exceptions.NotFound() diff --git a/addons/website_sale/models/product_image.py b/addons/website_sale/models/product_image.py index c8cdb4aaf06..526256e9ca1 100644 --- a/addons/website_sale/models/product_image.py +++ b/addons/website_sale/models/product_image.py @@ -4,7 +4,7 @@ from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError -from odoo.addons.website.tools import get_video_embed_code +from odoo.addons.web_editor.tools import get_video_embed_code, get_video_thumbnail class ProductImage(models.Model): @@ -16,7 +16,7 @@ class ProductImage(models.Model): name = fields.Char("Name", required=True) sequence = fields.Integer(default=10, index=True) - image_1920 = fields.Image(required=True) + image_1920 = fields.Image() product_tmpl_id = fields.Many2one('product.template', "Product Template", index=True, ondelete='cascade') product_variant_id = fields.Many2one('product.product', "Product Variant", index=True, ondelete='cascade') @@ -31,10 +31,15 @@ class ProductImage(models.Model): for image in self: image.can_image_1024_be_zoomed = image.image_1920 and tools.is_image_size_above(image.image_1920, image.image_1024) + @api.onchange('video_url') + def _onchange_video_url(self): + if not self.image_1920: + self.image_1920 = get_video_thumbnail(self.video_url) or False + @api.depends('video_url') def _compute_embed_code(self): for image in self: - image.embed_code = get_video_embed_code(image.video_url) + image.embed_code = get_video_embed_code(image.video_url) or False @api.constrains('video_url') def _check_valid_video_url(self):