Merge branch 'bump-project'
Work of last month to get this project running for the first release.
This commit is contained in:
34
manage.py
34
manage.py
@@ -3,20 +3,20 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "videodinges.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "videodinges.settings")
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# The above import may fail for some other reason. Ensure that the
|
# The above import may fail for some other reason. Ensure that the
|
||||||
# issue is really that Django is missing to avoid masking other
|
# issue is really that Django is missing to avoid masking other
|
||||||
# exceptions on Python 2.
|
# exceptions on Python 2.
|
||||||
try:
|
try:
|
||||||
import django
|
import django
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Couldn't import Django. Are you sure it's installed and "
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
"forget to activate a virtual environment?"
|
"forget to activate a virtual environment?"
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
execute_from_command_line(sys.argv)
|
execute_from_command_line(sys.argv)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
Django==1.11
|
Django==3.0.*
|
||||||
pkg-resources==0.0.0
|
pytz
|
||||||
pytz==2018.7
|
Jinja2==2.11.*
|
||||||
|
|||||||
0
static/.placeholder
Normal file
0
static/.placeholder
Normal file
24
static/js/video.js
Normal file
24
static/js/video.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
function init() {
|
||||||
|
|
||||||
|
var hash = document.location.hash;
|
||||||
|
var res = hash.match(/t=([0-9]+)/);
|
||||||
|
if (res) {
|
||||||
|
var vids = document.getElementsByTagName('video');
|
||||||
|
// only first video
|
||||||
|
var vid = vids[0];
|
||||||
|
vid.currentTime = res[1];
|
||||||
|
vid.autoplay = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
function vidTimeInUrl(el) {
|
||||||
|
var vids = document.getElementsByTagName('video');
|
||||||
|
// only first video
|
||||||
|
var vid = vids[0];
|
||||||
|
var currentTime = vid.currentTime;
|
||||||
|
var href = el.href + "#t=" + parseInt(currentTime);
|
||||||
|
el.href = href;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
0
tmp/.placeholder
Normal file
0
tmp/.placeholder
Normal file
0
uploads/.placeholder
Normal file
0
uploads/.placeholder
Normal file
34
videodinges/admin.py
Normal file
34
videodinges/admin.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
class TranscodingsForm(forms.ModelForm):
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
if not cleaned_data['url'] and not cleaned_data['upload']:
|
||||||
|
validation_error = ValidationError('Either url or upload must be given', code='invalid')
|
||||||
|
self.add_error('url', validation_error)
|
||||||
|
self.add_error('upload', validation_error)
|
||||||
|
if cleaned_data['url'] and cleaned_data['upload']:
|
||||||
|
validation_error = ValidationError('Cannot fill both url and upload', code='invalid')
|
||||||
|
self.add_error('url', validation_error)
|
||||||
|
self.add_error('upload', validation_error)
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
class TranscodingsInline(admin.StackedInline):
|
||||||
|
model = models.Transcoding
|
||||||
|
form = TranscodingsForm
|
||||||
|
fields = ['quality', 'type', 'url', 'upload']
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
class VideoAdmin(admin.ModelAdmin):
|
||||||
|
model = models.Video
|
||||||
|
fields = ['title', 'description', 'slug', 'poster', 'og_image', 'created_at', 'default_quality']
|
||||||
|
inlines = [TranscodingsInline]
|
||||||
|
list_display = ('title', 'slug', 'created_at', 'updated_at')
|
||||||
|
ordering = ('-created_at', )
|
||||||
|
|
||||||
|
admin.site.register(models.Video, VideoAdmin)
|
||||||
|
admin.site.register(models.Upload)
|
||||||
@@ -1,32 +1,115 @@
|
|||||||
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from collections import namedtuple
|
from typing import NamedTuple, Optional, Union
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
Quality = namedtuple('Quality', ['name', 'width', 'height', 'priority'])
|
from django.db import models
|
||||||
|
from django.db.models import constraints
|
||||||
|
from django.db.models.query_utils import Q
|
||||||
|
|
||||||
|
class Quality(NamedTuple):
|
||||||
|
name: str
|
||||||
|
width: int
|
||||||
|
height: int
|
||||||
|
priority: int
|
||||||
|
|
||||||
|
class TranscodingType(NamedTuple):
|
||||||
|
name: str
|
||||||
|
short_name: str
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
qualities = (
|
qualities = (
|
||||||
Quality(name='360p', width=640, height=360, priority=1),
|
Quality(name='360p', width=640, height=360, priority=1),
|
||||||
Quality(name='480p', width=853, height=480, priority=2),
|
Quality(name='480p', width=853, height=480, priority=2),
|
||||||
Quality(name='480p', width=1280, height=720, priority=2),
|
Quality(name='720p', width=1280, height=720, priority=2),
|
||||||
Quality(name='1080p', width=1920, height=1080, priority=1),
|
Quality(name='1080p', width=1920, height=1080, priority=1),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
transcoding_types = (
|
||||||
|
TranscodingType(name='video/webm', short_name='vp8'),
|
||||||
|
TranscodingType(name='video/webm; codecs="vp8, vorbis"', short_name='vp8'),
|
||||||
|
TranscodingType(name='video/webm; codecs="vp9, opus"', short_name='vp9'),
|
||||||
|
TranscodingType(name='video/mp4', short_name='h.264'),
|
||||||
|
TranscodingType(name='video/mp4; codecs="avc1.64001f,mp4a.40.2"', short_name='h.264'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Upload(models.Model):
|
||||||
|
id = models.AutoField(primary_key=True)
|
||||||
|
file = models.FileField()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return os.path.basename(self.file.path)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'uploads'
|
||||||
|
|
||||||
class Video(models.Model):
|
class Video(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
title = models.CharField()
|
title = models.CharField(max_length=256)
|
||||||
slug = models.CharField()
|
slug = models.CharField(max_length=256, unique=True)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
created_at = models.DateTimeField(default=datetime.now)
|
poster = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_poster')
|
||||||
updated_at = models.DateTimeField(default=datetime.now)
|
og_image = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True, related_name='video_og_image')
|
||||||
|
default_quality = models.CharField(
|
||||||
|
choices=((quality.name, quality.name) for quality in qualities),
|
||||||
|
max_length=128,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
created_at = models.DateTimeField(default=datetime.now)
|
||||||
|
updated_at = models.DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
self.updated_at = datetime.now()
|
self.updated_at = datetime.now()
|
||||||
super().save(force_insert, force_update, using, update_fields)
|
super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [models.Index(fields=['slug']), models.Index(fields=['created_at'])]
|
||||||
|
db_table = 'videos'
|
||||||
|
|
||||||
class Transcoding(models.Model):
|
class Transcoding(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
video = models.ForeignKey(Video, on_delete=models.CASCADE)
|
video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='transcodings')
|
||||||
quality = models.CharField(choices=(quality.name, quality.name) for quality in qualities)
|
quality = models.CharField(choices=((quality.name, quality.name) for quality in qualities), max_length=128)
|
||||||
file = models.FileField()
|
type = models.CharField(choices=((str(type_), str(type_)) for type_ in transcoding_types), max_length=128)
|
||||||
|
upload = models.OneToOneField(Upload, on_delete=models.PROTECT, blank=True, null=True)
|
||||||
|
url = models.CharField(max_length=256, null=True, blank=True, unique=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.quality
|
||||||
|
|
||||||
|
@property
|
||||||
|
def quality_obj(self):
|
||||||
|
return get_quality_by_name(self.quality)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('video', 'quality', 'type')
|
||||||
|
constraints = [
|
||||||
|
constraints.CheckConstraint(
|
||||||
|
check=Q(upload__isnull=False) | Q(url__isnull=False),
|
||||||
|
name='upload_or_url_is_filled'
|
||||||
|
),
|
||||||
|
constraints.CheckConstraint(
|
||||||
|
check=~(Q(upload__isnull=False) & Q(url__isnull=False)),
|
||||||
|
name='upload_and_url_cannot_both_be_filled'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
db_table = 'transcodings'
|
||||||
|
|
||||||
|
def get_quality_by_name(name: str) -> Optional[Quality]:
|
||||||
|
for quality in qualities:
|
||||||
|
if quality.name == name:
|
||||||
|
return quality
|
||||||
|
|
||||||
|
def get_short_name_of_transcoding_type(transcoding_type: Union[str, TranscodingType]) -> str:
|
||||||
|
if isinstance(transcoding_type, str):
|
||||||
|
for type_ in transcoding_types:
|
||||||
|
if type_.name == transcoding_type:
|
||||||
|
transcoding_type = type_
|
||||||
|
|
||||||
|
if isinstance(transcoding_type, TranscodingType):
|
||||||
|
return transcoding_type.short_name
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for videodinges project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 1.11.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/1.11/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/1.11/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = '_$9059r$6diz+m+z91(sn_mx3yarj%h)j_+2z$6j8yp+(-h-1c'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'videodinges.urls'
|
|
||||||
|
|
||||||
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 = 'videodinges.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
|
||||||
4
videodinges/settings/__init__.py
Normal file
4
videodinges/settings/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
try:
|
||||||
|
from .localsettings import *
|
||||||
|
except ImportError:
|
||||||
|
from .defaultsettings import *
|
||||||
141
videodinges/settings/defaultsettings.py
Normal file
141
videodinges/settings/defaultsettings.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
"""
|
||||||
|
Django settings for videodinges project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 1.11.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.11/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/1.11/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), '..', '..', '..'))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '_$9059r$6diz+m+z91(sn_mx3yarj%h)j_+2z$6j8yp+(-h-1c'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'videodinges',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'videodinges.urls'
|
||||||
|
|
||||||
|
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',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.jinja2.Jinja2',
|
||||||
|
'DIRS': [os.path.join(BASE_DIR, 'videodinges', 'templates')],
|
||||||
|
'APP_DIRS': False,
|
||||||
|
'OPTIONS': {},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'videodinges.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
|
||||||
|
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
|
||||||
|
|
||||||
|
MEDIA_URL = '/uploads/'
|
||||||
|
|
||||||
|
FILE_UPLOAD_HANDLERS = ['django.core.files.uploadhandler.TemporaryFileUploadHandler']
|
||||||
|
|
||||||
|
FILE_UPLOAD_MAX_MEMORY_SIZE = 2147483648 # 2GB
|
||||||
|
|
||||||
|
FILE_UPLOAD_TEMP_DIR = os.path.join(BASE_DIR, 'tmp') # probably default /tmp is too small for video files
|
||||||
|
|
||||||
|
URL_BASE = '' # usefull to prefix the application URL on deployment
|
||||||
15
videodinges/settings/localsettings-example.py
Normal file
15
videodinges/settings/localsettings-example.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
Copy this file to localsettings.py to make local overrides.
|
||||||
|
BEWARE to always import defaultsettings as well if activate this file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .defaultsettings import *
|
||||||
|
|
||||||
|
DATABASES['default'] = {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
|
'NAME': 'videos', # database name
|
||||||
|
'USER': 'videos',
|
||||||
|
'PASSWORD': 'v3r7s3cr3t',
|
||||||
|
'HOST': 'localhost',
|
||||||
|
'PORT': '5432',
|
||||||
|
}
|
||||||
10
videodinges/templates/index.html.j2
Normal file
10
videodinges/templates/index.html.j2
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Video's</h1>
|
||||||
|
<ul>
|
||||||
|
{% for video in videos %}
|
||||||
|
<li><a href="{{ video.slug }}.html">{{ video.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
videodinges/templates/video.html.j2
Normal file
36
videodinges/templates/video.html.j2
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{# <link rel="stylesheet" href="style.css" type="text/css" media="screen" /> #}
|
||||||
|
{% if og_image %}
|
||||||
|
<meta property="og:image" content="{{og_image}}" />
|
||||||
|
{% endif %}
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<video width="{{ width }}" height="{{ height }}" {% if poster %}poster="{{ poster }}" {% endif %}controls="controls">
|
||||||
|
{% for source in sources %}
|
||||||
|
<source src="{{ source.src }}" type='{{ source.type }}' />
|
||||||
|
{% endfor %}
|
||||||
|
You need a browser that understands HTML5 video and supports {% for i in used_codecs %}{{ i }}{% if not loop.last %} or {% endif %}{% endfor %} codecs.
|
||||||
|
</video><br />
|
||||||
|
<p>
|
||||||
|
{% for quality in qualities %}
|
||||||
|
{% if quality == current_quality %}
|
||||||
|
<strong>{{ quality }} versie</strong>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ slug }}.html?quality={{ quality }}" onclick="vidTimeInUrl(this);">{{ quality }} versie</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if not loop.last %}|{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ description|safe }}
|
||||||
|
</p>
|
||||||
|
<div id="commenter-container" data-object-id="welmers-video-{{ slug }}">
|
||||||
|
<div class="commenter-count-container"><span class="commenter-count">0</span> comments total</div>
|
||||||
|
</div>
|
||||||
|
<script data-container="commenter-container" src="//www.welmers.net/commenter/js/commenter.js" type="text/javascript"></script>
|
||||||
|
<script src="static/js/video.js" type="text/javascript"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,21 +1,35 @@
|
|||||||
"""videodinges URL Configuration
|
"""videodinges URL Configuration
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
https://docs.djangoproject.com/en/1.11/topics/http/urls/
|
https://docs.djangoproject.com/en/1.11/topics/http/urls/
|
||||||
Examples:
|
Examples:
|
||||||
Function views
|
Function views
|
||||||
1. Add an import: from my_app import views
|
1. Add an import: from my_app import views
|
||||||
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
|
||||||
Class-based views
|
Class-based views
|
||||||
1. Add an import: from other_app.views import Home
|
1. Add an import: from other_app.views import Home
|
||||||
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
|
||||||
Including another URLconf
|
Including another URLconf
|
||||||
1. Import the include() function: from django.conf.urls import url, include
|
1. Import the include() function: from django.conf.urls import url, include
|
||||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import include
|
||||||
|
|
||||||
urlpatterns = [
|
from . import views
|
||||||
url(r'^admin/', admin.site.urls),
|
|
||||||
|
_urlpatterns = [
|
||||||
|
url(r'^admin/', admin.site.urls),
|
||||||
|
url(r'^$', views.index),
|
||||||
|
url(r'^(?P<slug>[\w-]+).html', views.video)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
if settings.URL_BASE:
|
||||||
|
urlpatterns = [url(r'^{}/'.format(settings.URL_BASE), include(_urlpatterns))]
|
||||||
|
else:
|
||||||
|
urlpatterns = _urlpatterns
|
||||||
|
|||||||
77
videodinges/views.py
Normal file
77
videodinges/views.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
from django.http import HttpResponse, HttpRequest, Http404
|
||||||
|
from django.shortcuts import render
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
def video(request: HttpRequest, slug: str) -> HttpResponse:
|
||||||
|
try:
|
||||||
|
video = models.Video.objects.get(slug=slug)
|
||||||
|
except models.Video.DoesNotExist:
|
||||||
|
raise Http404('Video not found')
|
||||||
|
|
||||||
|
template_data = dict(
|
||||||
|
og_image=video.og_image.file.url if video.og_image else None,
|
||||||
|
title=video.title,
|
||||||
|
poster=video.poster.file.url if video.poster else None,
|
||||||
|
description=video.description,
|
||||||
|
slug=video.slug
|
||||||
|
)
|
||||||
|
|
||||||
|
qualities = _get_qualities(video)
|
||||||
|
try:
|
||||||
|
# find quality specified by URL param
|
||||||
|
quality = qualities[request.GET['quality']]
|
||||||
|
except:
|
||||||
|
# find quality specified by default quality specified for video
|
||||||
|
try:
|
||||||
|
quality = qualities[video.default_quality]
|
||||||
|
except:
|
||||||
|
# take default first quality
|
||||||
|
quality = next(iter(qualities.values()))
|
||||||
|
|
||||||
|
template_data.update(
|
||||||
|
width=quality[0].quality_obj.width,
|
||||||
|
height=quality[0].quality_obj.height,
|
||||||
|
current_quality=quality[0].quality_obj.name
|
||||||
|
)
|
||||||
|
|
||||||
|
template_data['sources'] = [
|
||||||
|
{
|
||||||
|
'src': _url_for(transcoding),
|
||||||
|
'type': transcoding.type,
|
||||||
|
}
|
||||||
|
for transcoding in quality ]
|
||||||
|
|
||||||
|
template_data['used_codecs'] = [
|
||||||
|
models.get_short_name_of_transcoding_type(transcoding.type)
|
||||||
|
for transcoding in quality
|
||||||
|
]
|
||||||
|
|
||||||
|
template_data['qualities'] = qualities.keys()
|
||||||
|
|
||||||
|
return render(request, 'video.html.j2', template_data, using='jinja2')
|
||||||
|
|
||||||
|
def index(request: HttpRequest) -> HttpResponse:
|
||||||
|
videos = models.Video.objects.order_by('-created_at').all()
|
||||||
|
return render(request, 'index.html.j2', dict(videos=videos), using='jinja2')
|
||||||
|
|
||||||
|
def _get_dict_from_models_with_fields(model, *fields: str) -> Dict[str, Any]:
|
||||||
|
ret = {}
|
||||||
|
for field in fields:
|
||||||
|
ret[field] = model.__dict__[field]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def _get_qualities(video: models.Video) -> Dict[str, List[models.Transcoding]]:
|
||||||
|
transcodings: List[models.Transcoding] = video.transcodings.order_by('quality').all()
|
||||||
|
qualities = defaultdict(list)
|
||||||
|
for transcoding in transcodings:
|
||||||
|
qualities[transcoding.quality_obj.name].append(transcoding)
|
||||||
|
return dict(qualities)
|
||||||
|
|
||||||
|
def _url_for(transcoding: models.Transcoding) -> str:
|
||||||
|
if transcoding.url:
|
||||||
|
return transcoding.url
|
||||||
|
elif transcoding.upload:
|
||||||
|
return transcoding.upload.file.url
|
||||||
Reference in New Issue
Block a user