Source code for embed_video.backends

import json
import re
import urllib.parse as urlparse

import requests
from django.http import QueryDict
from django.template.loader import render_to_string
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
from django.utils.safestring import mark_safe

from embed_video.settings import (
    EMBED_VIDEO_BACKENDS,
    EMBED_VIDEO_TIMEOUT,
    EMBED_VIDEO_YOUTUBE_CHECK_THUMBNAIL,
    EMBED_VIDEO_YOUTUBE_DEFAULT_QUERY,
)


[docs]class EmbedVideoException(Exception): """Parental class for all embed_video exceptions""" pass
[docs]class VideoDoesntExistException(EmbedVideoException): """Exception thrown if video doesn't exist""" pass
[docs]class UnknownBackendException(EmbedVideoException): """Exception thrown if video backend is not recognized.""" pass
[docs]class UnknownIdException(VideoDoesntExistException): """ Exception thrown if backend is detected, but video ID cannot be parsed. """ pass
[docs]def detect_backend(url): """ Detect the right backend for given URL. Goes over backends in ``settings.EMBED_VIDEO_BACKENDS``, calls :py:func:`~VideoBackend.is_valid` and returns backend instance. :param url: URL which is passed to `is_valid` methods of VideoBackends. :type url: str :return: Returns recognized VideoBackend :rtype: VideoBackend """ for backend_name in EMBED_VIDEO_BACKENDS: backend = import_string(backend_name) if backend.is_valid(url): return backend(url) raise UnknownBackendException
[docs]class VideoBackend: """ Base class used as parental class for backends. Backend variables: .. autosummary:: url code thumbnail query info is_secure protocol template_name .. code-block:: python class MyBackend(VideoBackend): ... """ re_code = None """ Compiled regex (:py:func:`re.compile`) to search code in URL. Example: ``re.compile(r'myvideo\.com/\?code=(?P<code>\w+)')`` """ re_detect = None """ Compilede regec (:py:func:`re.compile`) to detect, if input URL is valid for current backend. Example: ``re.compile(r'^http://myvideo\.com/.*')`` """ pattern_url = None """ Pattern in which the code is inserted. Example: ``http://myvideo.com?code=%s`` :type: str """ pattern_thumbnail_url = None """ Pattern in which the code is inserted to get thumbnail url. Example: ``http://static.myvideo.com/thumbs/%s`` :type: str """ allow_https = True """ Sets if HTTPS version allowed for specific backend. :type: bool """ template_name = "embed_video/embed_code.html" """ Name of embed code template used by :py:meth:`get_embed_code`. Passed template variables: ``{{ backend }}`` (instance of VideoBackend), ``{{ width }}``, ``{{ height }}`` :type: str """ default_query = "" """ Default query string or `QueryDict` appended to url :type: str """ is_secure = False """ Decides if secured protocol (HTTPS) is used. :type: bool """ def __init__(self, url): """ First it tries to load data from cache and if it don't succeed, run :py:meth:`init` and then save it to cache. :type url: str """ self.backend = self.__class__.__name__ self._url = url self.query = QueryDict(self.default_query, mutable=True) @property def code(self): """ Code of video. """ return self.get_code() @property def url(self): """ URL of video. """ return self.get_url() @property def protocol(self): """ Protocol used to generate URL. """ return "https" if self.allow_https and self.is_secure else "http" @property def thumbnail(self): """ URL of video thumbnail. """ return self.get_thumbnail_url() @property def info(self): """ Additional information about video. Not implemented in all backends. """ return self.get_info() @property def query(self): """ String transformed to QueryDict appended to url. """ return self._query @query.setter def query(self, value): """ :type value: QueryDict | str """ self._query = ( value if isinstance(value, QueryDict) else QueryDict(value, mutable=True) )
[docs] @classmethod def is_valid(cls, url): """ Class method to control if passed url is valid for current backend. By default it is done by :py:data:`re_detect` regex. :type url: str """ return True if cls.re_detect.match(url) else False
[docs] def get_code(self): """ Returns video code matched from given url by :py:data:`re_code`. :rtype: str """ match = self.re_code.search(self._url) if match: return match.group("code")
[docs] def get_url(self): """ Returns URL folded from :py:data:`pattern_url` and parsed code. """ url = self.pattern_url.format(code=self.code, protocol=self.protocol) url += "?" + self.query.urlencode() if self.query else "" return mark_safe(url)
[docs] def get_thumbnail_url(self): """ Returns thumbnail URL folded from :py:data:`pattern_thumbnail_url` and parsed code. :rtype: str """ return self.pattern_thumbnail_url.format(code=self.code, protocol=self.protocol)
[docs] def get_embed_code(self, width, height): """ Returns embed code rendered from template :py:data:`template_name`. :type width: int | str :type height: int | str :rtype: str """ return render_to_string( self.template_name, {"backend": self, "width": width, "height": height} )
[docs] def get_info(self): """ :rtype: dict """ raise NotImplementedError
[docs] def set_options(self, options): """ :type options: dict """ for key in options: setattr(self, key, options[key])
[docs]class YoutubeBackend(VideoBackend): """ Backend for YouTube URLs. """ re_detect = re.compile(r"^(http(s)?://)?(www\.|m\.)?youtu(\.?)be(\.com)?/.*", re.I) re_code = re.compile( r"""youtu(\.?)be(\.com)?/ # match youtube's domains (\#/)? # for mobile urls (embed/)? # match the embed url syntax (v/)? (shorts/)? # match youtube shorts (watch\?v=)? # match the youtube page url (ytscreeningroom\?v=)? (feeds/api/videos/)? (user\S*[^\w\-\s])? (?P<code>[\w\-]{11})[a-z0-9;:@?&%=+/\$_.-]* # match and extract """, re.I | re.X, ) pattern_url = "{protocol}://www.youtube.com/embed/{code}" pattern_thumbnail_url = "{protocol}://img.youtube.com/vi/{code}/{resolution}" default_query = EMBED_VIDEO_YOUTUBE_DEFAULT_QUERY resolutions = [ "maxresdefault.jpg", "sddefault.jpg", "hqdefault.jpg", "mqdefault.jpg", ] is_secure = True """ Decides if secured protocol (HTTPS) is used. :type: bool """
[docs] def get_code(self): code = super().get_code() if not code: parsed_url = urlparse.urlparse(self._url) parsed_qs = urlparse.parse_qs(parsed_url.query) if "v" in parsed_qs: code = parsed_qs["v"][0] elif "video_id" in parsed_qs: code = parsed_qs["video_id"][0] else: raise UnknownIdException("Cannot get ID from `{0}`".format(self._url)) return code
[docs] def get_thumbnail_url(self): """ Returns thumbnail URL folded from :py:data:`pattern_thumbnail_url` and parsed code. :rtype: str """ if not EMBED_VIDEO_YOUTUBE_CHECK_THUMBNAIL: return self.pattern_thumbnail_url.format( code=self.code, protocol=self.protocol, resolution="hqdefault.jpg" ) for resolution in self.resolutions: temp_thumbnail_url = self.pattern_thumbnail_url.format( code=self.code, protocol=self.protocol, resolution=resolution ) if int(requests.head(temp_thumbnail_url).status_code) < 400: return temp_thumbnail_url return None
[docs]class VimeoBackend(VideoBackend): """ Backend for Vimeo URLs. """ re_detect = re.compile(r"^((http(s)?:)?//)?(www\.)?(player\.)?vimeo\.com/.*", re.I) re_code = re.compile( r"""vimeo\.com/(video/)?(channels/(.*/)?)?((.+)/review/)?(manage/)?(?P<code>[0-9]+)""", re.I, ) pattern_url = "{protocol}://player.vimeo.com/video/{code}" pattern_info = "{protocol}://vimeo.com/api/v2/video/{code}.json" is_secure = True """ Decides if secured protocol (HTTPS) is used. :type: bool """
[docs] def get_info(self): try: response = requests.get( self.pattern_info.format(code=self.code, protocol=self.protocol), timeout=EMBED_VIDEO_TIMEOUT, ) return json.loads(response.text)[0] except ValueError: raise VideoDoesntExistException()
[docs] def get_thumbnail_url(self): return self.info.get("thumbnail_large")
[docs]class SoundCloudBackend(VideoBackend): """ Backend for SoundCloud URLs. """ base_url = "{protocol}://soundcloud.com/oembed" re_detect = re.compile(r"^(http(s)?://(www\.|m\.)?)?soundcloud\.com/.*", re.I) re_code = re.compile(r'src=".*%2F(?P<code>\d+)&show_artwork.*"', re.I) re_url = re.compile(r'src="(?P<url>.*?)"', re.I) is_secure = True """ Decides if secured protocol (HTTPS) is used. :type: bool """ @cached_property def width(self): """ :rtype: str """ return self.info.get("width") @cached_property def height(self): """ :rtype: str """ return self.info.get("height")
[docs] def get_info(self): params = {"format": "json", "url": self._url} r = requests.get( self.base_url.format(protocol=self.protocol), params=params, timeout=EMBED_VIDEO_TIMEOUT, ) if r.status_code != 200: raise VideoDoesntExistException( "SoundCloud returned status code `{status_code}` for URL `{url}`.".format( status_code=r.status_code, url=r.url, ) ) return json.loads(r.text)
[docs] def get_thumbnail_url(self): return self.info.get("thumbnail_url")
[docs] def get_url(self): match = self.re_url.search(self.info.get("html")) return match.group("url")
[docs] def get_code(self): match = self.re_code.search(self.info.get("html")) return match.group("code")
[docs] def get_embed_code(self, width, height): return super().get_embed_code(width=width, height=height)