add HTML5 audio and video backends

This commit is contained in:
Thijs Triemstra 2016-04-14 17:08:38 +02:00 committed by Cédric Carrard
parent 4cbb72a691
commit 5e472afc2a
31 changed files with 291 additions and 107 deletions

View file

@ -1,2 +1,2 @@
[report]
[run]
include = embed_video/*

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ dist
.coverage
htmlcov
.idea
.tox

View file

@ -1,27 +1,20 @@
sudo: false
language: python
python:
- "2.7"
- "3.4"
cache: pip
cache:
directories:
- $HOME/.cache/pip
env:
- DJANGO_VERSION=1.5.2
- DJANGO_VERSION=1.6.5
- DJANGO_VERSION=1.7.0
- DJANGO_VERSION=1.8.0
- DJANGO_VERSION=1.8.6
- DJANGO_VERSION=1.9.4
install:
- pip install -q Django==$DJANGO_VERSION
- pip install coveralls
- pip install mock
- pip install south
- pip install nose
- pip install testfixtures
script:
- python setup.py build
- PYTHONHASHSEED=0 python setup.py nosetests --verbosity 2 --with-coverage --cover-tests --cover-erase
after_success:
- coveralls
notifications:
email: false
- TOXENV=py27-django15
- TOXENV=py27-django16
- TOXENV=py27-django17
- TOXENV=py27-django18
- TOXENV=py27-django19
- TOXENV=py27-django110
- TOXENV=py34-django15
- TOXENV=py34-django16
- TOXENV=py34-django17
- TOXENV=py34-django18
- TOXENV=py34-django19
- TOXENV=py34-django110
install: pip install tox coveralls
script: tox
after_success: coveralls

View file

@ -3,3 +3,8 @@ include CHANGES.rst
include LICENSE
recursive-include embed_video/templates *.html
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude * *~
recursive-exclude * .coverage

View file

@ -1,4 +1,4 @@
Development
Development
===========

View file

@ -4,23 +4,22 @@ Testing
Requirements
------------
The library needs ``Django`` and ``requests`` and ``nose``, ``mock``,
``south`` and ``testfixtures`` libraries to run tests.
::
The library needs a copy of ``Django``, e.g.::
pip install Django
pip install requests
pip install nose
pip install mock
pip install south
pip install testfixtures
And several libraries that can be installed from the root of
a source checkout::
pip install -r embed_video/tests/test-requirements.txt
It's recommended to make a separate virtual environment where you
install these packages.
Running tests
-------------
Run tests with this command:
Run tests in your virtual environment with this command:
::
@ -34,7 +33,13 @@ Run tests with coverage:
::
pip install coverage
nosetests --with-coverage --cover-package=embed_video
Use tox if you want to test against all supported Django versions,
similar to the Travis build. Install it once::
pip install tox>=2.0
Run the tests with::
tox

View file

@ -1,7 +1,8 @@
Example project
===============
For easy start with using django-embed-video, you can take a look at example
project. It is located in example_project directory in root of repository.
To get started with django-embed-video, you can take a look at the example
project. It is located in the ``example_project`` directory in the root of
the repository.
.. include:: ../example_project/README.rst

View file

@ -48,8 +48,8 @@ Default sizes are ``tiny`` (420x315), ``small`` (480x360), ``medium`` (640x480),
{% video my_video '100% x 50%' %}
It is possible to set backend options via parameters in template tag. It is
useful for example to enforce HTTPS protocol or set different query appended
It is possible to set backend options via parameters in the template tag. It is
useful to enforce HTTPS protocol for example or to set different query appended
to url.
::
@ -73,8 +73,8 @@ to url.
.. tip::
You can overwrite default template of embed code located in
``templates/embed_video/embed_code.html`` or set own file for custom
You can overwrite the default template of the embed code located in
``templates/embed_video/embed_code.html`` or use your own file for a custom
backend (:py:data:`~embed_video.backends.VideoBackend.template_name`).
.. versionadded:: 0.9
@ -87,7 +87,7 @@ Model examples
.. highlight:: python
Using the ``EmbedVideoField`` provides you validation of URLs.
Using the ``EmbedVideoField`` provides validation of URLs.
::
@ -121,8 +121,8 @@ Use ``AdminVideoMixin`` in ``admin.py``.
Custom backends
###############
If you have specific needs and default backends don't suits you, you can write
your custom backend.
If you have specific needs and the default backend doesn't suit you,
you can write a custom backend.
``my_project/my_app/backends.py``::
@ -139,7 +139,7 @@ your custom backend.
template_name = 'embed_video/custombackend_embed_code.html' # added in v0.9
You can also overwrite :py:class:`~embed_video.backends.VideoBackend` methods,
if using regular expressions isn't well enough.
if using regular expressions isn't good enough.
``my_project/my_project/settings.py``::
@ -155,7 +155,7 @@ if using regular expressions isn't well enough.
Low level API examples
######################
You can get instance of :py:class:`~embed_video.backends.VideoBackend` in your
You can get an instance of :py:class:`~embed_video.backends.VideoBackend` in your
python code thanks to :py:func:`~embed_video.backends.detect_backend`:
::

View file

@ -1,12 +1,11 @@
django-embed-video's documentation
==================================
Django app for easy embeding YouTube and Vimeo videos and music from SoundCloud.
Django app for easy embedding YouTube and Vimeo videos and music from SoundCloud.
Repository is located on GitHub: https://github.com/jazzband/django-embed-video
.. toctree::
:maxdepth: 2
:glob:
@ -15,7 +14,7 @@ Repository is located on GitHub: https://github.com/jazzband/django-embed-video
examples
example-project
development/index
development/index
websites
@ -27,7 +26,7 @@ Library API
.. toctree::
:maxdepth: 2
api/index
api/index
@ -37,4 +36,3 @@ Indices and tables
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View file

@ -4,14 +4,14 @@ Installation & Setup
Installation
############
The simpliest way is to use pip to install package:
The simpliest way is to use pip to install the package:
.. code-block:: bash
pip install django-embed-video
If you want latest version, you may use Git. It is fresh, but unstable.
If you want the latest version, you may use Git. It is fresh, but unstable.
.. code-block:: bash
@ -28,12 +28,13 @@ settings.
INSTALLED_APPS = (
...
'embed_video',
)
To detect HTTP/S you must use :py:class:`~django.core.context_processors.request`
context processor:
To detect HTTP/S you must add the :py:class:`~django.template.context_processors.request`
(or :py:class:`~django.core.context_processors.request` for Django < 1.9) context
processor:
.. code-block:: python

View file

@ -14,7 +14,7 @@ class AdminVideoWidget(forms.TextInput):
.. todo::
Django 1.6 provides better parent for this widget -
Django 1.6 provides a better parent for this widget -
:py:class:`django.forms.URLInput`.
"""

View file

@ -19,17 +19,23 @@ from .settings import EMBED_VIDEO_BACKENDS, EMBED_VIDEO_TIMEOUT, \
class EmbedVideoException(Exception):
""" Parental class for all embed_video exceptions """
"""
Parental class for all embed_video exceptions.
"""
pass
class VideoDoesntExistException(EmbedVideoException):
""" Exception thrown if video doesn't exist """
"""
Exception thrown if video doesn't exist.
"""
pass
class UnknownBackendException(EmbedVideoException):
""" Exception thrown if video backend is not recognized. """
"""
Exception thrown if video backend is not recognized.
"""
pass
@ -53,7 +59,6 @@ def detect_backend(url):
:return: Returns recognized VideoBackend
:rtype: VideoBackend
"""
for backend_name in EMBED_VIDEO_BACKENDS:
backend = import_by_path(backend_name)
if backend.is_valid(url):
@ -97,7 +102,7 @@ class VideoBackend(object):
re_detect = None
"""
Compilede regec (:py:func:`re.compile`) to detect, if input URL is valid
Compiled regex (:py:func:`re.compile`) to detect, if input URL is valid
for current backend.
Example: ``re.compile(r'^http://myvideo\.com/.*')``
@ -140,7 +145,7 @@ class VideoBackend(object):
default_query = ''
"""
Default query string or `QueryDict` appended to url
Default query string or ``QueryDict`` appended to url.
:type: str
"""
@ -154,7 +159,7 @@ class VideoBackend(object):
def __init__(self, url):
"""
First it tries to load data from cache and if it don't succeed, run
First it tries to load data from cache and if it doesn't succeed, run
:py:meth:`init` and then save it to cache.
:type url: str
@ -201,7 +206,7 @@ class VideoBackend(object):
@property
def query(self):
"""
String transformed to QueryDict appended to url.
String transformed to ``QueryDict`` appended to url.
"""
return self._query
@ -218,7 +223,7 @@ class VideoBackend(object):
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.
default this is done using the :py:data:`re_detect` regex.
:type url: str
"""
@ -280,6 +285,75 @@ class VideoBackend(object):
setattr(self, key, options[key])
class Html5Backend(VideoBackend):
"""
Backend for HTML5 media.
"""
def get_url(self):
"""
Returns URL.
"""
return self._url
def get_code(self):
"""
Returns video code.
:rtype: str
"""
return None
def get_thumbnail_url(self):
"""
Returns thumbnail URL.
:rtype: str
"""
return None
class Html5VideoBackend(Html5Backend):
"""
Backend for HTML5 video.
"""
template_name = 'embed_video/html5_video.html'
@classmethod
def is_valid(cls, url):
"""
Class method to control if passed url is valid for current backend.
:type url: str
"""
try:
r = requests.head(url, timeout=EMBED_VIDEO_TIMEOUT)
except:
raise VideoDoesntExistException()
return r.headers.get('content-type').startswith('video')
class Html5AudioBackend(Html5Backend):
"""
Backend for HTML5 audio.
"""
template_name = 'embed_video/html5_audio.html'
@classmethod
def is_valid(cls, url):
"""
Class method to control if passed url is valid for current backend.
:type url: str
"""
try:
r = requests.head(url, timeout=EMBED_VIDEO_TIMEOUT)
except:
raise VideoDoesntExistException()
return r.headers.get('content-type').startswith('audio')
class YoutubeBackend(VideoBackend):
"""
Backend for YouTube URLs.

View file

@ -33,7 +33,7 @@ class EmbedVideoField(models.URLField):
class EmbedVideoFormField(forms.URLField):
"""
Form field for embeded video. Descendant of
Form field for embedded video. Descendant of
:py:class:`django.forms.URLField`
"""

View file

@ -6,6 +6,8 @@ EMBED_VIDEO_BACKENDS = getattr(settings, 'EMBED_VIDEO_BACKENDS', (
'embed_video.backends.VimeoBackend',
'embed_video.backends.WistiaBackend',
'embed_video.backends.SoundCloudBackend',
'embed_video.backends.Html5VideoBackend',
'embed_video.backends.Html5AudioBackend',
))
""" :type: tuple[str] """

View file

@ -0,0 +1 @@
<audio src="{{ backend.url }}" controls></audio>

View file

@ -0,0 +1 @@
<video width="{{ width }}" height="{{ height }}" src="{{ backend.url }}" controls></video>

View file

@ -0,0 +1,28 @@
from unittest import TestCase
from . import BackendTestMixin
from embed_video import backends
class HTML5BackendTestCase(TestCase):
def test_url(self):
url = 'https://collab-project.github.io/videojs-wavesurfer/examples/media/heres_johnny.wav'
backend = backends.detect_backend(url)
self.assertEqual(backend.get_url(), url)
class Html5VideoBackendTestCase(BackendTestMixin, TestCase):
urls = (
('http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4', None),
('https://collab-project.github.io/videojs-wavesurfer/examples/media/example.mp4', None),
)
instance = backends.Html5VideoBackend
class Html5AudioBackendTestCase(BackendTestMixin, TestCase):
urls = (
('https://collab-project.github.io/videojs-wavesurfer/examples/media/heres_johnny.wav', None),
)
instance = backends.Html5AudioBackend

View file

@ -20,6 +20,8 @@ EMBED_VIDEO_BACKENDS = (
'embed_video.backends.VimeoBackend',
'embed_video.backends.WistiaBackend',
'embed_video.backends.SoundCloudBackend',
'embed_video.backends.Html5VideoBackend',
'embed_video.backends.Html5AudioBackend',
'embed_video.tests.backends.tests_custom_backend.CustomBackend',
)

View file

@ -0,0 +1,7 @@
requests
nose
mock
south
testfixtures
coverage
flake8

View file

@ -6,7 +6,7 @@ from django.core.exceptions import ImproperlyConfigured
def import_by_path(dotted_path, error_prefix=''):
"""
Import a dotted module path and return the attribute/class designated by
the last name in the path. Raise ImproperlyConfigured if something goes
the last name in the path. Raise ``ImproperlyConfigured`` if something goes
wrong.
.. warning::

View file

@ -8,18 +8,21 @@ Running example project
#. Create database::
python manage.py syncdb --noinput
python manage.py migrate --noinput
#. And superuser::
python manage.py createsuperuser
#. Run testing server::
python manage.py runserver
#. Take a look at http://localhost:8000 . You can log in to administration with username ``admin``
and password ``admin``.
#. Create new posts at http://localhost:8000/admin/ and view them on http://localhost:8000.
Testing HTTPS
*************
To test HTTPS on development server, `follow this instructions
To test HTTPS on a development server, `follow these instructions
<http://www.ianlewis.org/en/testing-https-djangos-development-server>`_.

View file

@ -1,7 +1,6 @@
# Django settings for example_project project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
DATABASES = {
'default': {
@ -14,24 +13,37 @@ SITE_ID = 1
SECRET_KEY = 'u%38dln@$1!7w#cxi4np504^sa3_skv5aekad)jy_u0v2mc+nr'
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.request',
)
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
APPEND_SLASH = True
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'example_project.wsgi.application'
ROOT_URLCONF = 'example_project.urls'
STATIC_URL = '/static/'
@ -42,6 +54,7 @@ DJANGO_APPS = (
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
)

View file

@ -7,7 +7,7 @@
<title>django-embed-video example project</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<style>
body {
font-size: 32px;

View file

@ -1,11 +1,8 @@
from django.conf.urls import patterns, include, url
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.conf.urls import include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = staticfiles_urlpatterns() \
+ patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^', include('posts.urls', namespace='posts')),
)
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^', include('posts.urls', namespace='posts')),
]

View file

@ -24,7 +24,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
from django.core.wsgi import get_wsgi_application # noqa
application = get_wsgi_application()
# Apply WSGI middleware here.

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-04-14 13:17
from __future__ import unicode_literals
from django.db import migrations, models
import embed_video.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=50)),
('video', embed_video.fields.EmbedVideoField(help_text=b'Link to a YouTube, Soundcloud or Vimeo clip.', verbose_name=b'My media link')),
],
),
]

View file

@ -6,8 +6,10 @@ from embed_video.fields import EmbedVideoField
class Post(models.Model):
title = models.CharField(max_length=50)
video = EmbedVideoField(verbose_name='My video',
help_text='This is a help text')
video = EmbedVideoField(
verbose_name='My media link',
help_text='Link to a YouTube, Soundcloud or Vimeo clip.'
)
def __unicode__(self):
return self.title

View file

@ -1,8 +1,8 @@
from django.conf.urls import patterns, url
from django.conf.urls import url
from .views import PostListView, PostDetailView
urlpatterns = patterns('',
urlpatterns = [
url(r'(?P<pk>\d+)/$', PostDetailView.as_view(), name='detail'),
url(r'$', PostListView.as_view(), name='list'),
)
]

3
setup.cfg Normal file
View file

@ -0,0 +1,3 @@
[flake8]
ignore = E121, E122, E123, E125, E126, E128
exclude = docs/*, embed_video/tests/*

22
tox.ini Normal file
View file

@ -0,0 +1,22 @@
[tox]
envlist = {py27,py34}-django{15,16,17,18,19,110}
[testenv]
basepython =
py27: python2.7
py34: python3.4
usedevelop = true
setenv =
DJANGO_SETTINGS_MODULE = embed_video.tests.django_settings
PYTHONPATH = {toxinidir}
PYTHONHASHSEED = 0
passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
deps =
-r{toxinidir}/embed_video/tests/test-requirements.txt
django15: Django>=1.5,<1.6
django16: Django>=1.6,<1.7
django17: Django>=1.7,<1.8
django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10
django110: Django>=1.10,<1.11
commands = nosetests -sv --with-coverage --cover-package=embed_video