├── .gitignore ├── LICENCE ├── MANIFEST.in ├── README.textile ├── demoproject ├── bootstrap.py ├── buildout.cfg ├── setup.py └── src │ └── videodemo │ ├── __init__.py │ ├── manage.py │ ├── settings.py │ └── urls.py ├── setup.py └── src └── videostream ├── LICENCE ├── __init__.py ├── admin.py ├── feeds.py ├── fixtures └── videostream_test_fixtures.json ├── management ├── __init__.py └── commands │ ├── __init__.py │ └── encode.py ├── models.py ├── templates └── videostream │ ├── include │ └── render_video.html │ ├── video_archive.html │ ├── video_archive_day.html │ ├── video_archive_month.html │ ├── video_archive_year.html │ ├── video_detail.html │ ├── videocategory_detail.html │ └── videocategory_list.html ├── templatetags ├── __init__.py └── videostream_tags.py ├── tests.py ├── urls.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.egg-info 4 | *.db 5 | *~ 6 | *.swp 7 | .*.cfg 8 | media 9 | bin 10 | develop-eggs 11 | dist 12 | downloads 13 | nginx 14 | eggs 15 | parts 16 | build -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Andre Engelbrecht 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * Neither the name of django-video nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include src/videostream/templates * -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. django-video Documentation 2 | 3 | © Copyright 2009 Andre Engelbrecht. All Rights Reserved. 4 | 5 | h2. LICENSE 6 | 7 | Please see the LICENCE file for information on the License 8 | 9 | h2. django-video Use Cases 10 | 11 | Before you decide to use django-video on any commercial project, please be aware that this application only does some cms management and conversion into flv, and is perfect to personal sites, small company sites that want to incorporate video etc. Note however that if you want to build a large community driven site, of which video will be a big part, you will have to do a little more research into commercial, high volume streaming media. You may have to modify this app to hook into those solutions. 12 | 13 | h2. Requirements 14 | 15 | * The full version of FFMPEG, if you want to use @python manage.py encode@, to encode a video clip from avi, mov or ogv to flv. Note that, some operating systems, like Ubuntu, by default does not have the full version of FFMPEG installed, so make sure to install the full version. 16 | * django-oembed 17 | * django-tagging (optional but recommended if you want to use it's tagging features.) 18 | 19 | h2. Installing 20 | 21 | # Check that videostream folder is on your python path 22 | # Next, add the videostream and oembed apps to your django INSTALLED_APPS 23 | @'videostream',@ 24 | @'oembed,@ 25 | # If you're using the app directories template loader (enabled by default) django should find the default templates on its own. 26 | # Override the default settings if you want to. 27 | See the settings below. 28 | # Add the urls 29 | @( r'^videos/', include( 'videostream.urls' ) ),@ 30 | 31 | h2. Settings you may want to override. 32 | 33 | @VIDEOSTREAM_SIZE = "320x240"@ 34 | 35 | @VIDEOSTREAM_THUMBNAIL_SIZE = "320x240"@ 36 | 37 | @VIDEOSTREAM_FEED_TITLE = "Video Feeds"@ 38 | 39 | @VIDEOSTREAM_FEED_DESCRIPTION = "Video Feeds"@ 40 | 41 | @VIDEOSTREAM_FEED_LINK = ""@ 42 | -------------------------------------------------------------------------------- /demoproject/bootstrap.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2006 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Bootstrap a buildout-based project 15 | 16 | Simply run this script in a directory containing a buildout.cfg. 17 | The script accepts buildout command-line options, so you can 18 | use the -c option to specify an alternate configuration file. 19 | """ 20 | 21 | import os, shutil, sys, tempfile, urllib, urllib2, subprocess 22 | from optparse import OptionParser 23 | 24 | if sys.platform == 'win32': 25 | def quote(c): 26 | if ' ' in c: 27 | return '"%s"' % c # work around spawn lamosity on windows 28 | else: 29 | return c 30 | else: 31 | quote = str 32 | 33 | # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. 34 | stdout, stderr = subprocess.Popen( 35 | [sys.executable, '-Sc', 36 | 'try:\n' 37 | ' import ConfigParser\n' 38 | 'except ImportError:\n' 39 | ' print 1\n' 40 | 'else:\n' 41 | ' print 0\n'], 42 | stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 43 | has_broken_dash_S = bool(int(stdout.strip())) 44 | 45 | # In order to be more robust in the face of system Pythons, we want to 46 | # run without site-packages loaded. This is somewhat tricky, in 47 | # particular because Python 2.6's distutils imports site, so starting 48 | # with the -S flag is not sufficient. However, we'll start with that: 49 | if not has_broken_dash_S and 'site' in sys.modules: 50 | # We will restart with python -S. 51 | args = sys.argv[:] 52 | args[0:0] = [sys.executable, '-S'] 53 | args = map(quote, args) 54 | os.execv(sys.executable, args) 55 | # Now we are running with -S. We'll get the clean sys.path, import site 56 | # because distutils will do it later, and then reset the path and clean 57 | # out any namespace packages from site-packages that might have been 58 | # loaded by .pth files. 59 | clean_path = sys.path[:] 60 | import site # imported because of its side effects 61 | sys.path[:] = clean_path 62 | for k, v in sys.modules.items(): 63 | if k in ('setuptools', 'pkg_resources') or ( 64 | hasattr(v, '__path__') and 65 | len(v.__path__) == 1 and 66 | not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))): 67 | # This is a namespace package. Remove it. 68 | sys.modules.pop(k) 69 | 70 | is_jython = sys.platform.startswith('java') 71 | 72 | setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' 73 | distribute_source = 'http://python-distribute.org/distribute_setup.py' 74 | 75 | 76 | # parsing arguments 77 | def normalize_to_url(option, opt_str, value, parser): 78 | if value: 79 | if '://' not in value: # It doesn't smell like a URL. 80 | value = 'file://%s' % ( 81 | urllib.pathname2url( 82 | os.path.abspath(os.path.expanduser(value))),) 83 | if opt_str == '--download-base' and not value.endswith('/'): 84 | # Download base needs a trailing slash to make the world happy. 85 | value += '/' 86 | else: 87 | value = None 88 | name = opt_str[2:].replace('-', '_') 89 | setattr(parser.values, name, value) 90 | 91 | usage = '''\ 92 | [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] 93 | 94 | Bootstraps a buildout-based project. 95 | 96 | Simply run this script in a directory containing a buildout.cfg, using the 97 | Python that you want bin/buildout to use. 98 | 99 | Note that by using --setup-source and --download-base to point to 100 | local resources, you can keep this script from going over the network. 101 | ''' 102 | 103 | parser = OptionParser(usage=usage) 104 | parser.add_option("-v", "--version", dest="version", 105 | help="use a specific zc.buildout version") 106 | parser.add_option("-d", "--distribute", 107 | action="store_true", dest="use_distribute", default=False, 108 | help="Use Distribute rather than Setuptools.") 109 | parser.add_option("--setup-source", action="callback", dest="setup_source", 110 | callback=normalize_to_url, nargs=1, type="string", 111 | help=("Specify a URL or file location for the setup file. " 112 | "If you use Setuptools, this will default to " + 113 | setuptools_source + "; if you use Distribute, this " 114 | "will default to " + distribute_source + ".")) 115 | parser.add_option("--download-base", action="callback", dest="download_base", 116 | callback=normalize_to_url, nargs=1, type="string", 117 | help=("Specify a URL or directory for downloading " 118 | "zc.buildout and either Setuptools or Distribute. " 119 | "Defaults to PyPI.")) 120 | parser.add_option("--eggs", 121 | help=("Specify a directory for storing eggs. Defaults to " 122 | "a temporary directory that is deleted when the " 123 | "bootstrap script completes.")) 124 | parser.add_option("-t", "--accept-buildout-test-releases", 125 | dest='accept_buildout_test_releases', 126 | action="store_true", default=False, 127 | help=("Normally, if you do not specify a --version, the " 128 | "bootstrap script and buildout gets the newest " 129 | "*final* versions of zc.buildout and its recipes and " 130 | "extensions for you. If you use this flag, " 131 | "bootstrap and buildout will get the newest releases " 132 | "even if they are alphas or betas.")) 133 | parser.add_option("-c", None, action="store", dest="config_file", 134 | help=("Specify the path to the buildout configuration " 135 | "file to be used.")) 136 | 137 | options, args = parser.parse_args() 138 | 139 | # if -c was provided, we push it back into args for buildout's main function 140 | if options.config_file is not None: 141 | args += ['-c', options.config_file] 142 | 143 | if options.eggs: 144 | eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) 145 | else: 146 | eggs_dir = tempfile.mkdtemp() 147 | 148 | if options.setup_source is None: 149 | if options.use_distribute: 150 | options.setup_source = distribute_source 151 | else: 152 | options.setup_source = setuptools_source 153 | 154 | if options.accept_buildout_test_releases: 155 | args.append('buildout:accept-buildout-test-releases=true') 156 | args.append('bootstrap') 157 | 158 | try: 159 | import pkg_resources 160 | import setuptools # A flag. Sometimes pkg_resources is installed alone. 161 | if not hasattr(pkg_resources, '_distribute'): 162 | raise ImportError 163 | except ImportError: 164 | ez_code = urllib2.urlopen( 165 | options.setup_source).read().replace('\r\n', '\n') 166 | ez = {} 167 | exec ez_code in ez 168 | setup_args = dict(to_dir=eggs_dir, download_delay=0) 169 | if options.download_base: 170 | setup_args['download_base'] = options.download_base 171 | if options.use_distribute: 172 | setup_args['no_fake'] = True 173 | ez['use_setuptools'](**setup_args) 174 | if 'pkg_resources' in sys.modules: 175 | reload(sys.modules['pkg_resources']) 176 | import pkg_resources 177 | # This does not (always?) update the default working set. We will 178 | # do it. 179 | for path in sys.path: 180 | if path not in pkg_resources.working_set.entries: 181 | pkg_resources.working_set.add_entry(path) 182 | 183 | cmd = [quote(sys.executable), 184 | '-c', 185 | quote('from setuptools.command.easy_install import main; main()'), 186 | '-mqNxd', 187 | quote(eggs_dir)] 188 | 189 | if not has_broken_dash_S: 190 | cmd.insert(1, '-S') 191 | 192 | find_links = options.download_base 193 | if not find_links: 194 | find_links = os.environ.get('bootstrap-testing-find-links') 195 | if find_links: 196 | cmd.extend(['-f', quote(find_links)]) 197 | 198 | if options.use_distribute: 199 | setup_requirement = 'distribute' 200 | else: 201 | setup_requirement = 'setuptools' 202 | ws = pkg_resources.working_set 203 | setup_requirement_path = ws.find( 204 | pkg_resources.Requirement.parse(setup_requirement)).location 205 | env = dict( 206 | os.environ, 207 | PYTHONPATH=setup_requirement_path) 208 | 209 | requirement = 'zc.buildout' 210 | version = options.version 211 | if version is None and not options.accept_buildout_test_releases: 212 | # Figure out the most recent final version of zc.buildout. 213 | import setuptools.package_index 214 | _final_parts = '*final-', '*final' 215 | 216 | def _final_version(parsed_version): 217 | for part in parsed_version: 218 | if (part[:1] == '*') and (part not in _final_parts): 219 | return False 220 | return True 221 | index = setuptools.package_index.PackageIndex( 222 | search_path=[setup_requirement_path]) 223 | if find_links: 224 | index.add_find_links((find_links,)) 225 | req = pkg_resources.Requirement.parse(requirement) 226 | if index.obtain(req) is not None: 227 | best = [] 228 | bestv = None 229 | for dist in index[req.project_name]: 230 | distv = dist.parsed_version 231 | if _final_version(distv): 232 | if bestv is None or distv > bestv: 233 | best = [dist] 234 | bestv = distv 235 | elif distv == bestv: 236 | best.append(dist) 237 | if best: 238 | best.sort() 239 | version = best[-1].version 240 | if version: 241 | requirement = '=='.join((requirement, version)) 242 | cmd.append(requirement) 243 | 244 | if is_jython: 245 | import subprocess 246 | exitcode = subprocess.Popen(cmd, env=env).wait() 247 | else: # Windows prefers this, apparently; otherwise we would prefer subprocess 248 | exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) 249 | if exitcode != 0: 250 | sys.stdout.flush() 251 | sys.stderr.flush() 252 | print ("An error occurred when trying to install zc.buildout. " 253 | "Look above this message for any errors that " 254 | "were output by easy_install.") 255 | sys.exit(exitcode) 256 | 257 | ws.add_entry(eggs_dir) 258 | ws.require(requirement) 259 | import zc.buildout.buildout 260 | zc.buildout.buildout.main(args) 261 | if not options.eggs: # clean up temporary egg directory 262 | shutil.rmtree(eggs_dir) 263 | -------------------------------------------------------------------------------- /demoproject/buildout.cfg: -------------------------------------------------------------------------------- 1 | [buildout] 2 | versions = versions 3 | include-site-packages = false 4 | extensions = mr.developer 5 | 6 | parts = 7 | python 8 | django 9 | libmp3lame 10 | ffmpeg 11 | rubygems 12 | 13 | # auto-checkout = * 14 | 15 | develop = 16 | . 17 | ${buildout:directory}/../ 18 | 19 | eggs = 20 | pillow 21 | django 22 | django-oembed 23 | videostream 24 | videodemo 25 | 26 | [versions] 27 | pillow = 1.7.5 28 | django = 1.3.1 29 | 30 | [sources] 31 | 32 | [rubygems] 33 | recipe = rubygemsrecipe 34 | gems = flvtool2 35 | 36 | [python] 37 | recipe = zc.recipe.egg 38 | interpreter = python 39 | eggs = ${buildout:eggs} 40 | 41 | ## Site Configurations 42 | [django] 43 | recipe = djangorecipe 44 | projectegg = videodemo 45 | control-script = videodemo 46 | settings = settings 47 | eggs = ${buildout:eggs} 48 | 49 | [libmp3lame] 50 | recipe = zc.recipe.cmmi 51 | url = http://sourceforge.net/projects/lame/files/lame/3.99/lame-3.99.5.tar.gz/download 52 | 53 | [ffmpeg] 54 | recipe = zc.recipe.cmmi 55 | url = http://ffmpeg.org/releases/ffmpeg-0.10.2.tar.gz 56 | extra_options = --disable-yasm --enable-libmp3lame --extra-cflags=-I${buildout:directory}/parts/libmp3lame/include/ --extra-ldflags=-L${buildout:directory}/parts/libmp3lame/lib/ 57 | 58 | -------------------------------------------------------------------------------- /demoproject/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="videodemo", 5 | version="0.1", 6 | url="", 7 | description="A demo project for django-video tests etc.", 8 | author="Andre Engelbrecht", 9 | packages=find_packages('src'), 10 | package_dir={'': 'src'}, 11 | install_requires=[ 12 | 'setuptools', 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /demoproject/src/videodemo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewebdev/django-video/9b58ebca44695d37c2974f188b2dc42f268ca810/demoproject/src/videodemo/__init__.py -------------------------------------------------------------------------------- /demoproject/src/videodemo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /demoproject/src/videodemo/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for videodemo project. 2 | 3 | import os 4 | from os.path import dirname 5 | 6 | PROJECT_DIR = dirname(__file__) 7 | BUILDOUT_DIR = dirname(dirname(dirname(__file__))) 8 | 9 | projectdir = lambda p: os.path.join(PROJECT_DIR, p) 10 | buildoutdir = lambda p: os.path.join(BUILDOUT_DIR, p) 11 | 12 | DEBUG = True 13 | TEMPLATE_DEBUG = DEBUG 14 | 15 | ADMINS = ( 16 | # ('Your Name', 'your_email@example.com'), 17 | ) 18 | 19 | MANAGERS = ADMINS 20 | 21 | DATABASES = { 22 | 'default': { 23 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 24 | 'NAME': projectdir('dev.db'), # Or path to database file if using sqlite3. 25 | 'USER': '', # Not used with sqlite3. 26 | 'PASSWORD': '', # Not used with sqlite3. 27 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 28 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 29 | } 30 | } 31 | 32 | # Local time zone for this installation. Choices can be found here: 33 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 34 | # although not all choices may be available on all operating systems. 35 | # On Unix systems, a value of None will cause Django to use the same 36 | # timezone as the operating system. 37 | # If running in a Windows environment this must be set to the same as your 38 | # system time zone. 39 | TIME_ZONE = 'America/Chicago' 40 | 41 | # Language code for this installation. All choices can be found here: 42 | # http://www.i18nguy.com/unicode/language-identifiers.html 43 | LANGUAGE_CODE = 'en-us' 44 | 45 | SITE_ID = 1 46 | 47 | # If you set this to False, Django will make some optimizations so as not 48 | # to load the internationalization machinery. 49 | USE_I18N = True 50 | 51 | # If you set this to False, Django will not format dates, numbers and 52 | # calendars according to the current locale 53 | USE_L10N = True 54 | 55 | # Absolute filesystem path to the directory that will hold user-uploaded files. 56 | # Example: "/home/media/media.lawrence.com/media/" 57 | MEDIA_ROOT = projectdir('media') 58 | 59 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 60 | # trailing slash. 61 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 62 | MEDIA_URL = '/media/' 63 | 64 | # Absolute path to the directory static files should be collected to. 65 | # Don't put anything in this directory yourself; store your static files 66 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 67 | # Example: "/home/media/media.lawrence.com/static/" 68 | STATIC_ROOT = projectdir('static') 69 | 70 | # URL prefix for static files. 71 | # Example: "http://media.lawrence.com/static/" 72 | STATIC_URL = '/static/' 73 | 74 | # URL prefix for admin static files -- CSS, JavaScript and images. 75 | # Make sure to use a trailing slash. 76 | # Examples: "http://foo.com/static/admin/", "/static/admin/". 77 | ADMIN_MEDIA_PREFIX = '/static/admin/' 78 | 79 | # Additional locations of static files 80 | STATICFILES_DIRS = ( 81 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 82 | # Always use forward slashes, even on Windows. 83 | # Don't forget to use absolute paths, not relative paths. 84 | ) 85 | 86 | # List of finder classes that know how to find static files in 87 | # various locations. 88 | STATICFILES_FINDERS = ( 89 | 'django.contrib.staticfiles.finders.FileSystemFinder', 90 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 91 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 92 | ) 93 | 94 | # Make this unique, and don't share it with anybody. 95 | SECRET_KEY = '*#wwjo1sqy97iq%^-*q=lcd-hijx@bl&*t&cfg-!knrr5)gd9x' 96 | 97 | # List of callables that know how to import templates from various sources. 98 | TEMPLATE_LOADERS = ( 99 | 'django.template.loaders.filesystem.Loader', 100 | 'django.template.loaders.app_directories.Loader', 101 | # 'django.template.loaders.eggs.Loader', 102 | ) 103 | 104 | MIDDLEWARE_CLASSES = ( 105 | 'django.middleware.common.CommonMiddleware', 106 | 'django.contrib.sessions.middleware.SessionMiddleware', 107 | 'django.middleware.csrf.CsrfViewMiddleware', 108 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 109 | 'django.contrib.messages.middleware.MessageMiddleware', 110 | ) 111 | 112 | ROOT_URLCONF = 'videodemo.urls' 113 | 114 | TEMPLATE_DIRS = ( 115 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 116 | # Always use forward slashes, even on Windows. 117 | # Don't forget to use absolute paths, not relative paths. 118 | ) 119 | 120 | INSTALLED_APPS = ( 121 | 'django.contrib.auth', 122 | 'django.contrib.contenttypes', 123 | 'django.contrib.sessions', 124 | 'django.contrib.sites', 125 | 'django.contrib.messages', 126 | 'django.contrib.staticfiles', 127 | 'django.contrib.admin', 128 | 'django.contrib.admindocs', 129 | 'videostream', 130 | 'oembed', 131 | ) 132 | 133 | 134 | FFMPEG_BINARY_PATH = buildoutdir('parts/ffmpeg/bin/ffmpeg') 135 | FLVTOOL_PATH = buildoutdir('bin/flvtool2') 136 | 137 | # A sample logging configuration. The only tangible logging 138 | # performed by this configuration is to send an email to 139 | # the site admins on every HTTP 500 error. 140 | # See http://docs.djangoproject.com/en/dev/topics/logging for 141 | # more details on how to customize your logging configuration. 142 | LOGGING = { 143 | 'version': 1, 144 | 'disable_existing_loggers': False, 145 | 'handlers': { 146 | 'mail_admins': { 147 | 'level': 'ERROR', 148 | 'class': 'django.utils.log.AdminEmailHandler' 149 | } 150 | }, 151 | 'loggers': { 152 | 'django.request': { 153 | 'handlers': ['mail_admins'], 154 | 'level': 'ERROR', 155 | 'propagate': True, 156 | }, 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /demoproject/src/videodemo/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | from django.contrib import admin 3 | from django.conf import settings 4 | 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 9 | url(r'^admin/', include(admin.site.urls)), 10 | 11 | (r'^', include('videostream.urls')), 12 | 13 | (r'^media/(?P.*)$', 'django.views.static.serve', 14 | {'document_root': settings.MEDIA_ROOT}), 15 | ) 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="videostream", 5 | version="0.2", 6 | url="http://github.com/andrewebdev/django-video", 7 | description="A simple video streaming application for django", 8 | author="Andre Engelbrech", 9 | author_email="andre@teh-node.co.za", 10 | package_dir={'': 'src'}, 11 | packages=find_packages('src'), 12 | include_package_data=True, 13 | ) 14 | -------------------------------------------------------------------------------- /src/videostream/LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Andre Engelbrecht 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * Neither the name of django-video nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /src/videostream/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # © Copyright 2009 Andre Engelbrecht. All Rights Reserved. 4 | # This script is licensed under the BSD Open Source Licence 5 | # Please see the text file LICENCE for more information 6 | # If this script is distributed, it must be accompanied by the Licence 7 | -------------------------------------------------------------------------------- /src/videostream/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.contrib import admin 4 | 5 | from videostream.models import * 6 | from videostream.utils import encode_video_set 7 | 8 | ## Admin Actions 9 | def encode_videos(modeladmin, request, queryset): 10 | """ Encode all selected videos """ 11 | encode_video_set(queryset) 12 | encode_videos.short_description = "Encode selected videos into Flash flv videos" 13 | 14 | def mark_for_encoding(modeladmin, request, queryset): 15 | """ Mark selected videos for encoding """ 16 | queryset.update(encode=True) 17 | mark_for_encoding.short_description = "Mark videos for encoding" 18 | 19 | def unmark_for_encoding(modeladmin, request, queryset): 20 | """ Unmark selected videos for encoding """ 21 | queryset.update(encode=False) 22 | unmark_for_encoding.short_description = "Unmark videos for encoding" 23 | 24 | def enable_video_comments(modeladmin, request, queryset): 25 | """ Enable Comments on selected videos """ 26 | queryset.update(allow_comments=True) 27 | enable_video_comments.short_description = "Enable comments on selected videos" 28 | 29 | def disable_video_comments(modeladmin, request, queryset): 30 | """ Disable comments on selected Videos """ 31 | queryset.update(allow_comments=False) 32 | disable_video_comments.short_description = "Disable comments on selected videos" 33 | 34 | def publish_videos(modeladmin, request, queryset): 35 | """ Mark selected videos as public """ 36 | queryset.update(is_public=True) 37 | # Quickly call the save() method for every video so that the dates are updated 38 | for video in queryset: 39 | video.save() 40 | publish_videos.short_description = "Publish selected videos" 41 | 42 | def unpublish_videos(modeladmin, request, queryset): 43 | """ Unmark selected videos as public """ 44 | queryset.update(is_public=False) 45 | unpublish_videos.short_description = "Unpublish selected Videos" 46 | 47 | ## Inline Model Classes 48 | class HTML5VideoInline(admin.TabularInline): 49 | model = HTML5Video 50 | 51 | 52 | ## ModelAdmin Classes 53 | class VideoCategoryAdmin(admin.ModelAdmin): 54 | prepopulated_fields = {'slug': ('title',)} 55 | list_display = ['title', 'slug'] 56 | 57 | 58 | class VideoAdmin(admin.ModelAdmin): 59 | prepopulated_fields = {'slug': ('title',)} 60 | date_hierarchy = 'publish_date' 61 | list_display = ['title', 'slug', 'publish_date', 'is_public', 62 | 'allow_comments', 'author'] 63 | list_filter = ['created_date', 'publish_date', 'modified_date', 64 | 'is_public', 'allow_comments'] 65 | search_fields = ['title', 'description', 'tags'] 66 | fieldsets = ( 67 | ('Video Details', {'fields': [ 68 | 'title', 'slug', 'description', 'tags', 'categories', 'is_public', 69 | 'allow_comments', 'publish_date', 'author', 70 | ]}), 71 | ) 72 | actions = [publish_videos, unpublish_videos, 73 | enable_video_comments, disable_video_comments] 74 | 75 | 76 | class FlashVideoAdmin(VideoAdmin): 77 | list_display = VideoAdmin.list_display + ['encode'] 78 | list_filter = VideoAdmin.list_filter + ['encode'] 79 | fieldsets = VideoAdmin.fieldsets + ( 80 | ('Video Source', {'fields': [ 81 | 'original_file', 82 | 'flv_file', 83 | 'thumbnail', 84 | 'encode' 85 | ]}), 86 | ) 87 | actions = VideoAdmin.actions + [mark_for_encoding, 88 | unmark_for_encoding, encode_videos] 89 | 90 | 91 | class EmbedVideoAdmin(VideoAdmin): 92 | list_display = VideoAdmin.list_display + ['video_url'] 93 | fieldsets = VideoAdmin.fieldsets + ( 94 | ('Video Source', {'fields': [ 95 | 'video_url', 96 | 'video_code', 97 | ]}), 98 | ) 99 | 100 | 101 | class BasicVideoAdmin(VideoAdmin): 102 | inlines = [HTML5VideoInline] 103 | 104 | 105 | admin.site.register(VideoCategory, VideoCategoryAdmin) 106 | admin.site.register(FlashVideo, FlashVideoAdmin) 107 | admin.site.register(EmbedVideo, EmbedVideoAdmin) 108 | admin.site.register(BasicVideo, BasicVideoAdmin) 109 | -------------------------------------------------------------------------------- /src/videostream/feeds.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # © Copyright 2009 Andre Engelbrecht. All Rights Reserved. 4 | # This script is licensed under the BSD Open Source Licence 5 | # Please see the text file LICENCE for more information 6 | # If this script is distributed, it must be accompanied by the Licence 7 | 8 | from django.contrib.syndication.feeds import Feed 9 | from django.conf import settings 10 | from videostream.models import VideoStream 11 | 12 | class LatestStream(Feed): 13 | title = getattr(settings, 'VIDEOSTREAM_FEED_TITLE', 'Video Feeds') 14 | description = getattr(settings, 'VIDEOSTREAM_FEED_DESCRIPTION', 'Video Feeds') 15 | link = getattr(settings, 'VIDEOSTREAM_FEED_LINK', '') 16 | 17 | def items(self): 18 | return VideoStream.objects.all().filter(is_public=True)[:5] 19 | -------------------------------------------------------------------------------- /src/videostream/fixtures/videostream_test_fixtures.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "auth.user", 5 | "fields": { 6 | "username": "tester", 7 | "password": "", 8 | "email": "test@example.com" 9 | } 10 | }, 11 | { 12 | "pk": 1, 13 | "model": "videostream.videocategory", 14 | "fields": { 15 | "title": "Category 1", 16 | "slug": "category-1", 17 | "description": "Cateogory 1 Description" 18 | } 19 | }, 20 | { 21 | "pk": 2, 22 | "model": "videostream.videocategory", 23 | "fields": { 24 | "title": "Cateogory 2", 25 | "slug": "category-2", 26 | "description": "Cateogory 2 Description" 27 | } 28 | }, 29 | { 30 | "pk": 1, 31 | "model": "videostream.video", 32 | "fields": { 33 | "title": "Video 1", 34 | "slug": "video-1", 35 | "description": "Video 1 Description", 36 | "tags": "tag1 tag2", 37 | "categories": [1, 2], 38 | "author": 1 39 | } 40 | }, 41 | { 42 | "pk": 2, 43 | "model": "videostream.video", 44 | "fields": { 45 | "title": "Video 2 (Basic Video)", 46 | "slug": "video-2", 47 | "description": "Video 2 Description", 48 | "tags": "tag1 tag2", 49 | "categories": [1], 50 | "author": 1 51 | } 52 | }, 53 | { 54 | "pk": 3, 55 | "model": "videostream.video", 56 | "fields": { 57 | "title": "Video 3 (Flash Video)", 58 | "slug": "video-3", 59 | "description": "Video 3 Description", 60 | "tags": "tag1 tag2", 61 | "categories": [2], 62 | "author": 1 63 | } 64 | }, 65 | { 66 | "pk": 1, 67 | "model": "videostream.basicvideo", 68 | "fields": {} 69 | }, 70 | { 71 | "pk": 1, 72 | "model": "videostream.html5video", 73 | "fields": { 74 | "video_type": 1, 75 | "video_file": "temp.ogg", 76 | "basic_video": 1 77 | } 78 | }, 79 | { 80 | "pk": 2, 81 | "model": "videostream.html5video", 82 | "fields": { 83 | "video_type": 2, 84 | "video_file": "temp.webm", 85 | "basic_video": 1 86 | } 87 | }, 88 | { 89 | "pk": 3, 90 | "model": "videostream.html5video", 91 | "fields": { 92 | "video_type": 3, 93 | "video_file": "temp.mp4", 94 | "basic_video": 1 95 | } 96 | }, 97 | { 98 | "pk": 1, 99 | "model": "videostream.flashvideo", 100 | "fields": { 101 | "original_file": "original.mp4", 102 | "flv_file": "video.flv", 103 | "thumbnail": "video_thumb.png", 104 | "encode": false 105 | } 106 | } 107 | ] -------------------------------------------------------------------------------- /src/videostream/management/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # © Copyright 2009 Andre Engelbrecht. All Rights Reserved. 4 | # This script is licensed under the BSD Open Source Licence 5 | # Please see the text file LICENCE for more information 6 | # If this script is distributed, it must be accompanied by the Licence 7 | -------------------------------------------------------------------------------- /src/videostream/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # © Copyright 2009 Andre Engelbrecht. All Rights Reserved. 4 | # This script is licensed under the BSD Open Source Licence 5 | # Please see the text file LICENCE for more information 6 | # If this script is distributed, it must be accompanied by the Licence 7 | -------------------------------------------------------------------------------- /src/videostream/management/commands/encode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import commands 4 | import os 5 | 6 | from django.core.management.base import NoArgsCommand 7 | 8 | from videostream.utils import encode_video_set 9 | 10 | 11 | class Command(NoArgsCommand): 12 | 13 | def handle_noargs(self, **options): 14 | """ Encode all pending streams """ 15 | encode_video_set() 16 | -------------------------------------------------------------------------------- /src/videostream/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | 5 | from django.db import models 6 | from django.conf import settings 7 | from django.contrib.auth.models import User 8 | 9 | 10 | # use Django-tagging for tags. If Django-tagging cannot be found, create our own 11 | # I did not author this little snippet, I found it somewhere on the web, 12 | # and cannot remember where exactly it was. 13 | try: 14 | from tagging.fields import TagField 15 | tagfield_help_text = 'Separate tags with spaces, put quotes around multiple-word tags.' 16 | except ImportError: 17 | class TagField(models.CharField): 18 | def __init__(self, **kwargs): 19 | default_kwargs = {'max_length': 255, 'blank': True} 20 | default_kwargs.update(kwargs) 21 | super(TagField, self).__init__(**default_kwargs) 22 | def get_internal_type(self): 23 | return 'CharField' 24 | tagfield_help_text = 'Django-tagging was not found, tags will be treated as plain text.' 25 | # End tagging snippet 26 | 27 | 28 | class VideoCategory(models.Model): 29 | """ A model to help categorize videos """ 30 | title = models.CharField(max_length=255) 31 | slug = models.SlugField( 32 | unique=True, 33 | help_text="A url friendly slug for the category", 34 | ) 35 | description = models.TextField(null=True, blank=True) 36 | 37 | class Meta: 38 | verbose_name_plural = "Video Categories" 39 | 40 | def __unicode__(self): 41 | return "%s" % self.title 42 | 43 | @models.permalink 44 | def get_absolute_url(self): 45 | return ('videostream_category_detail', [self.slug]) 46 | 47 | 48 | class Video(models.Model): 49 | """ 50 | This is our Base Video Class, with fields that will be available 51 | to all other Video models. 52 | """ 53 | title = models.CharField(max_length=255) 54 | slug = models.SlugField(unique=True, 55 | help_text="A url friendly slug for the video clip.") 56 | description = models.TextField(null=True, blank=True) 57 | 58 | tags = TagField(help_text=tagfield_help_text) 59 | categories = models.ManyToManyField(VideoCategory) 60 | allow_comments = models.BooleanField(default=False) 61 | 62 | ## TODO: 63 | ## In future we may want to allow for more control over publication 64 | is_public = models.BooleanField(default=False) 65 | 66 | created_date = models.DateTimeField(auto_now_add=True) 67 | modified_date = models.DateTimeField(auto_now=True) 68 | publish_date = models.DateTimeField(null=True, blank=True) 69 | 70 | author = models.ForeignKey(User, null=True, blank=True) 71 | 72 | class Meta: 73 | ordering = ('-publish_date', '-created_date') 74 | get_latest_by = 'publish_date' 75 | 76 | def __unicode__(self): 77 | return "%s" % self.title 78 | 79 | @models.permalink 80 | def get_absolute_url(self): 81 | return ('videostream_video_detail', (), { 82 | 'year': self.publish_date.strftime("%Y"), 83 | 'month': self.publish_date.strftime("%b"), 84 | 'day': self.publish_date.strftime("%d"), 85 | 'slug': self.slug 86 | }) 87 | 88 | def save(self, *args, **kwargs): 89 | self.modified_date = datetime.now() 90 | if self.publish_date == None and self.is_public: 91 | self.publish_date = datetime.now() 92 | super(Video, self).save(*args, **kwargs) 93 | 94 | 95 | class BasicVideo(Video): 96 | """ 97 | This is our basic HTML5 Video type. BasicVideo can have more than 98 | one HTML5 Video as a 'video type'. This allows us to create different 99 | video formats, one for each type format. 100 | """ 101 | pass 102 | 103 | 104 | class HTML5Video(models.Model): 105 | OGG = 0 106 | WEBM = 1 107 | MP4 = 2 108 | FLASH = 3 109 | VIDEO_TYPE = ( 110 | (OGG, 'video/ogg'), 111 | (WEBM, 'video/webm'), 112 | (MP4, 'video/mp4'), 113 | (FLASH, 'video/flv'), 114 | ) 115 | 116 | video_type = models.IntegerField( 117 | choices=VIDEO_TYPE, 118 | default=WEBM, 119 | help_text="The Video type" 120 | ) 121 | video_file = models.FileField( 122 | upload_to="videos/html5/", 123 | help_text="The file you wish to upload. Make sure that it's the correct format.", 124 | ) 125 | 126 | # Allow for multiple video types for a single video 127 | basic_video = models.ForeignKey(BasicVideo) 128 | 129 | class Meta: 130 | verbose_name = "Html 5 Video" 131 | verbose_name_plural = "Html 5 Videos" 132 | 133 | 134 | class EmbedVideo(Video): 135 | video_url = models.URLField(null=True, blank=True) 136 | video_code = models.TextField( 137 | null=True, 138 | blank=True, 139 | help_text="Use the video embed code instead of the url if your frontend does not support embedding with the URL only." 140 | ) 141 | 142 | 143 | class FlashVideo(Video): 144 | """ 145 | This model is what was once called "VideoStream". Since we want to support 146 | videos from other sources as well, this model was renamed to FlashVideo. 147 | """ 148 | original_file = models.FileField( 149 | upload_to="videos/flash/source/", 150 | null=True, 151 | blank=True, 152 | help_text="Make sure that the video you are uploading has a audo bitrate of at least 16. The encoding wont function on a lower audio bitrate." 153 | ) 154 | 155 | flv_file = models.FileField( 156 | upload_to="videos/flash/flv/", 157 | null=True, 158 | blank=True, 159 | help_text="If you already have an encoded flash video, upload it here (no encoding needed)." 160 | ) 161 | 162 | thumbnail = models.ImageField( 163 | blank=True, 164 | null=True, 165 | upload_to="videos/flash/thumbnails/", 166 | help_text="If you uploaded a flv clip that was already encoded, you will need to upload a thumbnail as well. If you are planning use django-video to encode, you dont have to upload a thumbnail, as django-video will create it for you" 167 | ) 168 | 169 | # This option allows us to specify whether we need to encode the clip 170 | encode = models.BooleanField( 171 | default=False, 172 | help_text="Encode or Re-Encode the clip. If you only wanted to change some information on the item, and do not want to encode the clip again, make sure this option is not selected." 173 | ) 174 | 175 | def get_player_size(self): 176 | """ this method returns the styles for the player size """ 177 | size = getattr(settings, 'VIDEOSTREAM_SIZE', '320x240').split('x') 178 | return "width: %spx; height: %spx;" % (size[0], size[1]) 179 | -------------------------------------------------------------------------------- /src/videostream/templates/videostream/include/render_video.html: -------------------------------------------------------------------------------- 1 | {% load oembed_tags %} 2 | 3 | 4 | {% if video_type == 'basicvideo' %} 5 | 11 | {% endif %} 12 | 13 | 14 | {% if video_type == 'embedvideo' %} 15 | 16 | {% comment %} 17 | 18 | If we have the video_code, then we use that to embed the video. 19 | If not then we will use django-oembed to display the video directly 20 | from the url. 21 | Make sure you have django-oembed installed. 22 | 23 | {% endcomment %} 24 | 25 | {% if video_instance.embedvideo.video_code %} 26 | {{ video_instance.embedvideo.video_code|safe }} 27 | {% else %} 28 | {% oembed %}{{ video_instance.embedvideo.video_url }}{% endoembed %} 29 | {% endif %} 30 | 31 | {% endif %} 32 | 33 | 34 | {% if video_type == 'flashvideo' %} 35 | 36 | {% comment %} 37 | To use the flashplayer option, you need to have a flashplayer installed 38 | for your site. FlowPlayer is a good choice (http://flowplayer.org/). 39 | 40 | If you have a flashplayer installed, you need to customize this 41 | template and add the neccesarry HTML required for the flash 42 | player you chose. 43 | 44 | Here is a example, using flowplayer: 45 | 46 | 49 | {% endcomment %} 50 | 51 |

The player has been disabled in the demo app. See the template, 52 | videostream/include/render_video.html. 53 | 54 |

Just for reference, the flv file is: {{ video_instance.flashvideo.flv_file.url }} 55 | 56 | {% endif %} 57 | -------------------------------------------------------------------------------- /src/videostream/templates/videostream/video_archive.html: -------------------------------------------------------------------------------- 1 | 2 |

Latest Videos

3 | 4 | 21 | -------------------------------------------------------------------------------- /src/videostream/templates/videostream/video_archive_day.html: -------------------------------------------------------------------------------- 1 | 2 |

Videos for, {{ day|date:"d M Y" }}

3 | 4 | 21 | -------------------------------------------------------------------------------- /src/videostream/templates/videostream/video_archive_month.html: -------------------------------------------------------------------------------- 1 | 2 |

Videos published in, {{ month|date:"M Y" }}

3 | 4 | 21 | -------------------------------------------------------------------------------- /src/videostream/templates/videostream/video_archive_year.html: -------------------------------------------------------------------------------- 1 | 2 |

Videos published in, {{ year }}

3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /src/videostream/templates/videostream/video_detail.html: -------------------------------------------------------------------------------- 1 | 2 | {% load videostream_tags %} 3 | 4 | {% if video %} 5 | 6 |

{{ object.title }}

7 |

{{ object.description }}

8 | 9 | {% render_video object %} 10 | 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /src/videostream/templates/videostream/videocategory_detail.html: -------------------------------------------------------------------------------- 1 | 2 |

{{ category }}

3 | -------------------------------------------------------------------------------- /src/videostream/templates/videostream/videocategory_list.html: -------------------------------------------------------------------------------- 1 | 2 | {% for cat in category_list %} 3 |

{{ cat }}

4 | {% endfor %} 5 | -------------------------------------------------------------------------------- /src/videostream/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewebdev/django-video/9b58ebca44695d37c2974f188b2dc42f268ca810/src/videostream/templatetags/__init__.py -------------------------------------------------------------------------------- /src/videostream/templatetags/videostream_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.contrib.contenttypes.models import ContentType 3 | 4 | from videostream.models import BasicVideo, EmbedVideo, FlashVideo 5 | 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.inclusion_tag('videostream/include/render_video.html') 11 | def render_video(video_instance, width=320, height=240): 12 | """ 13 | This is a intelligent inclusion tag that will try do determine what kind 14 | of video ``video_instance`` is, and then render the correct HTML for this 15 | video. 16 | 17 | ``width`` and ``height`` refers to the width and height of the video. 18 | 19 | Example Usage: 20 | {% render_video video 640 480 %} 21 | 22 | """ 23 | try: 24 | if video_instance.basicvideo: 25 | video_type = 'basicvideo' 26 | except: 27 | pass 28 | 29 | try: 30 | if video_instance.embedvideo: 31 | video_type = 'embedvideo' 32 | except: 33 | pass 34 | 35 | try: 36 | if video_instance.flashvideo: 37 | video_type = 'flashvideo' 38 | except: 39 | pass 40 | 41 | return locals() 42 | -------------------------------------------------------------------------------- /src/videostream/tests.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.test import TestCase 4 | from django.test.client import Client, RequestFactory 5 | from django.contrib.auth.models import User 6 | from django.core.urlresolvers import reverse 7 | 8 | from videostream.models import (VideoCategory, Video, BasicVideo, HTML5Video, 9 | EmbedVideo, FlashVideo) 10 | 11 | 12 | ## Models (including basic urls for permalink lookups) 13 | class VideoCategoryTestCase(TestCase): 14 | 15 | fixtures = ['videostream_test_fixtures.json'] 16 | urls = 'videostream.urls' 17 | 18 | def test_model_exists(self): 19 | cat = VideoCategory.objects.create( 20 | title='test', slug='test', description='test category') 21 | 22 | def test_unicode(self): 23 | self.assertEqual('Category 1', 24 | VideoCategory.objects.get(id=1).__unicode__()) 25 | 26 | def test_verbose_name_plural(self): 27 | self.assertEqual('Video Categories', 28 | VideoCategory._meta.verbose_name_plural) 29 | 30 | def test_categories_exist(self): 31 | self.assertEqual(2, VideoCategory.objects.all().count()) 32 | 33 | def test_absolute_url(self): 34 | self.assertEqual('/category/category-1/', 35 | VideoCategory.objects.get(id=1).get_absolute_url()) 36 | 37 | 38 | class VideoTestCase(TestCase): 39 | 40 | fixtures = ['videostream_test_fixtures.json'] 41 | urls = 'videostream.urls' 42 | 43 | def test_model(self): 44 | v = Video.objects.create( 45 | title='test video 1', 46 | slug='test-video-1', 47 | description='test video description', 48 | tags='tag1 tag2', 49 | author=User.objects.get(id=1), # Use our default user 50 | ) 51 | 52 | def test_unicode(self): 53 | self.assertEqual('Video 1', Video.objects.get(id=1).__unicode__()) 54 | 55 | def test_visible_video_has_publish_date(self): 56 | v = Video.objects.get(id=1) 57 | self.assertIsNone(v.publish_date) 58 | 59 | v.is_public = True 60 | v.save() 61 | self.assertIsNotNone(v.publish_date) 62 | 63 | def test_video_has_categories(self): 64 | v = Video.objects.get(id=1) 65 | self.assertEqual(2, v.categories.all().count()) 66 | 67 | def test_absolute_url(self): 68 | v = Video.objects.get(id=1) 69 | v.is_public = True 70 | v.save() 71 | 72 | now = datetime.now() 73 | expected_url = '/%s/%s/%s/%s/' % ( 74 | now.strftime('%Y'), now.strftime('%b'), now.strftime('%d'), 75 | 'video-1') 76 | 77 | self.assertEqual(expected_url, v.get_absolute_url()) 78 | 79 | def test_is_parent_class(self): 80 | # Basically since this is a parent class all other videos that 81 | # inherrits from this class can also be found through this model 82 | # Since we have these other videos in the fixtures, 83 | # this test should pass 84 | self.assertEqual(3, Video.objects.all().count()) 85 | 86 | 87 | class BasicVideoTestCase(TestCase): 88 | 89 | fixtures = ['videostream_test_fixtures.json'] 90 | 91 | def test_model_exists(self): 92 | v = BasicVideo() # No need to test other fields since it inherrits 93 | 94 | def test_has_html5videos(self): 95 | v = BasicVideo.objects.get(id=1) 96 | self.assertEqual(3, v.html5video_set.all().count()) 97 | 98 | 99 | class HTML5VideoTestCase(TestCase): 100 | 101 | fixtures = ['videostream_test_fixtures.json'] 102 | 103 | def test_model(self): 104 | v = HTML5Video(video_type=1, video_file='test.ogg') 105 | 106 | def test_html5videos_exists(self): 107 | self.assertEqual(3, HTML5Video.objects.all().count()) 108 | 109 | 110 | class EmbedVideoTestCase(TestCase): 111 | 112 | fixtures = ['videostream_test_fixtures.json'] 113 | 114 | def test_model(self): 115 | v = EmbedVideo.objects.create( 116 | video_url='http://test.example.com/video/', 117 | video_code='[video code]' 118 | ) 119 | 120 | 121 | class FlashVideoTestCase(TestCase): 122 | 123 | fixtures = ['videostream_test_fixtures.json'] 124 | 125 | def test_model(self): 126 | v = FlashVideo( 127 | original_file='original.mp4', 128 | flv_file='video.flv', 129 | thumbnail='thumb.png', 130 | encode=False 131 | ) 132 | 133 | def test_get_player_size(self): 134 | self.assertEqual('width: 320px; height: 240px;', 135 | FlashVideo.objects.get(id=1).get_player_size()) 136 | 137 | 138 | class VideoStreamViewsTestCase(TestCase): 139 | 140 | fixtures = ['videostream_test_fixtures.json'] 141 | urls = 'videostream.urls' 142 | 143 | def setUp(self): 144 | now = datetime.now() 145 | self.day = now.strftime('%d') 146 | self.month = now.strftime('%b') 147 | self.year = now.strftime('%Y') 148 | 149 | for v in Video.objects.all(): 150 | v.is_public = True 151 | v.save() 152 | 153 | def test_category_list_view(self): 154 | c = Client() 155 | response = c.get('/categories/') 156 | self.assertEqual(200, response.status_code) 157 | self.assertIn('object_list', response.context) 158 | self.assertEqual(2, response.context['object_list'].count()) 159 | 160 | def test_category_detail_view(self): 161 | c = Client() 162 | response = c.get('/category/category-1/') 163 | self.assertEqual(200, response.status_code) 164 | self.assertIn('category', response.context) 165 | 166 | def test_archive_year_view(self): 167 | c = Client() 168 | response = c.get('/%s/' % self.year) 169 | self.assertEqual(200, response.status_code) 170 | self.assertIn('date_list', response.context) 171 | self.assertEqual(1, len(response.context['date_list'])) 172 | 173 | def test_archive_month_view(self): 174 | c = Client() 175 | response = c.get('/%s/%s/' % (self.year, self.month)) 176 | self.assertEqual(200, response.status_code) 177 | self.assertIn('object_list', response.context) 178 | self.assertEqual(3, response.context['object_list'].count()) 179 | 180 | def test_archive_day_view(self): 181 | c = Client() 182 | response = c.get('/%s/%s/%s/' % (self.year, self.month, self.day)) 183 | self.assertEqual(200, response.status_code) 184 | self.assertIn('object_list', response.context) 185 | self.assertEqual(3, response.context['object_list'].count()) 186 | 187 | def test_video_detail_view(self): 188 | c = Client() 189 | response = c.get('/%s/%s/%s/%s/' % ( 190 | self.year, self.month, self.day, 'video-1')) 191 | self.assertEqual(200, response.status_code) 192 | self.assertIn('video', response.context) 193 | self.assertEqual('Video 1', response.context['video'].title) 194 | 195 | -------------------------------------------------------------------------------- /src/videostream/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf.urls import * 4 | from django.views.generic import DetailView, ListView 5 | from django.views.generic.dates import * 6 | 7 | from videostream.models import VideoCategory, Video 8 | #from videostream.feeds import LatestStream 9 | 10 | 11 | urlpatterns = patterns('', 12 | 13 | url(r'^category/(?P[-\w]+)/$', DetailView.as_view( 14 | model=VideoCategory, context_object_name='category' 15 | ), name='videostream_category_detail'), 16 | 17 | url(r'^categories/$', ListView.as_view( 18 | model=VideoCategory, 19 | ), name='videostream_category_list'), 20 | 21 | 22 | ## Date Based Views 23 | url(r'^latest/$', ArchiveIndexView.as_view( 24 | queryset=Video.objects.filter(is_public=True), 25 | date_field='publish_date', 26 | ), name='videostream_video_archive'), 27 | 28 | url(r'^(?P\d{4})/(?P\w+)/(?P\d{1,2})/(?P[-\w]+)/$', 29 | DateDetailView.as_view( 30 | queryset=Video.objects.filter(is_public=True), 31 | date_field='publish_date', 32 | ), 33 | name='videostream_video_detail'), 34 | 35 | url(r'^(?P\d{4})/(?P\w+)/(?P\d{1,2})/$', 36 | DayArchiveView.as_view( 37 | queryset=Video.objects.filter(is_public=True), 38 | date_field='publish_date', 39 | ), name='videostream_video_day'), 40 | 41 | url(r'^(?P\d{4})/(?P\w+)/$', 42 | MonthArchiveView.as_view( 43 | queryset=Video.objects.filter(is_public=True), 44 | date_field='publish_date', 45 | ), name='videostream_video_month'), 46 | 47 | url(r'^(?P\d{4})/$', YearArchiveView.as_view( 48 | queryset=Video.objects.filter(is_public=True), 49 | date_field='publish_date', 50 | ), name='videostream_video_year'), 51 | 52 | ) 53 | 54 | # feeds = { 55 | # 'latest': LatestStream, 56 | # } 57 | 58 | # urlpatterns += patterns('django.contrib.syndication.views', 59 | # (r'^feeds/(?P.*)/$', 'feed', {'feed_dict': feeds}), 60 | # ) 61 | -------------------------------------------------------------------------------- /src/videostream/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import commands 4 | import os 5 | 6 | from django.conf import settings 7 | 8 | from videostream.models import FlashVideo 9 | 10 | 11 | # This allows the developer to override the binary path for ffmpeg 12 | FFMPEG_BINARY_PATH = getattr(settings, 'FFMPEG_BINARY_PATH', 'ffmpeg') 13 | FLVTOOL_PATH = getattr(settings, 'FLVTOOL_PATH', 'flvtool2') 14 | 15 | 16 | def encode_video(flashvideo): 17 | """ 18 | Encode a single Video where ``flashvideo`` is an instance of 19 | videostream.models.FlashVideo 20 | """ 21 | MEDIA_ROOT = getattr(settings, 'MEDIA_ROOT') 22 | VIDEOSTREAM_SIZE = getattr(settings, 'VIDEOSTREAM_SIZE', '320x240') 23 | VIDEOSTREAM_THUMBNAIL_SIZE = getattr(settings, 24 | 'VIDEOSTREAM_THUMBNAIL_SIZE', '320x240') 25 | 26 | flvfilename = "%s.flv" % flashvideo.slug 27 | infile = "%s/%s" % (MEDIA_ROOT, flashvideo.original_file) 28 | outfile = "%s/videos/flash/flv/%s" % (MEDIA_ROOT, flvfilename) 29 | thumbnailfilename = "%s/videos/flash/thumbnails/%s.png" % ( 30 | MEDIA_ROOT, flashvideo.slug) 31 | 32 | # Final Results 33 | flvurl = "videos/flash/flv/%s" % flvfilename 34 | thumburl = "videos/flash/thumbnails/%s.png" % flashvideo.slug 35 | 36 | # Check if flv and thumbnail folder exists and create if not 37 | if not(os.access("%s/videos/flash/flv/" % MEDIA_ROOT, os.F_OK)): 38 | os.makedirs("%s/videos/flash/flv" % MEDIA_ROOT) 39 | 40 | if not(os.access("%s/videos/flash/thumbnails/" % MEDIA_ROOT, os.F_OK)): 41 | os.makedirs("%s/videos/flash/thumbnails" % MEDIA_ROOT) 42 | 43 | # ffmpeg command to create flv video 44 | ffmpeg = "%s -y -i %s -acodec libmp3lame -ar 22050 -ab 32000 -f flv -s %s %s" % ( 45 | FFMPEG_BINARY_PATH, infile, VIDEOSTREAM_SIZE, outfile) 46 | 47 | # ffmpeg command to create the video thumbnail 48 | getThumb = "%s -y -i %s -vframes 1 -ss 00:00:02 -an -vcodec png -f rawvideo -s %s %s" % ( 49 | FFMPEG_BINARY_PATH, infile, VIDEOSTREAM_THUMBNAIL_SIZE, thumbnailfilename) 50 | 51 | # flvtool command to get the metadata 52 | flvtool = "%s -U %s" % (FLVTOOL_PATH, outfile) 53 | 54 | # Lets do the conversion 55 | ffmpegresult = commands.getoutput(ffmpeg) 56 | print 80*"~" 57 | print ffmpegresult 58 | 59 | if os.access(outfile, os.F_OK): # outfile exists 60 | 61 | # There was a error cause the outfile size is zero 62 | if (os.stat(outfile).st_size==0): 63 | # We remove the file so that it does not cause confusion 64 | os.remove(outfile) 65 | 66 | else: 67 | # there does not seem to be errors, follow the rest of the procedures 68 | flvtoolresult = commands.getoutput(flvtool) 69 | print flvtoolresult 70 | 71 | thumbresult = commands.getoutput(getThumb) 72 | print thumbresult 73 | 74 | flashvideo.encode = False 75 | flashvideo.flv_file = flvurl 76 | flashvideo.thumbnail = thumburl 77 | 78 | print 80*"~" 79 | flashvideo.save() 80 | 81 | 82 | def encode_video_set(queryset=None): 83 | 84 | if not queryset: 85 | queryset = FlashVideo.objects.filter(encode=True) 86 | 87 | for flashvideo in queryset: 88 | encode_video(flashvideo) 89 | 90 | --------------------------------------------------------------------------------