Commit 95b66e1f authored by seb-b's avatar seb-b Committed by GitHub
Browse files

Merge pull request #3 from takeflight/quality

Add quality field to transcodes
parents 5a809ce4 e98607a2
...@@ -10,7 +10,7 @@ from wagtail.wagtailadmin.forms import (BaseCollectionMemberForm, ...@@ -10,7 +10,7 @@ from wagtail.wagtailadmin.forms import (BaseCollectionMemberForm,
collection_member_permission_formset_factory) collection_member_permission_formset_factory)
from wagtailvideos.fields import WagtailVideoField from wagtailvideos.fields import WagtailVideoField
from wagtailvideos.models import MediaFormats, Video from wagtailvideos.models import MediaFormats, Video, VideoQuality
from wagtailvideos.permissions import \ from wagtailvideos.permissions import \
permission_policy as video_permission_policy permission_policy as video_permission_policy
...@@ -55,6 +55,7 @@ def get_video_form(model): ...@@ -55,6 +55,7 @@ def get_video_form(model):
class VideoTranscodeAdminForm(forms.Form): class VideoTranscodeAdminForm(forms.Form):
media_format = EnumField(MediaFormats) media_format = EnumField(MediaFormats)
quality = EnumField(VideoQuality)
def __init__(self, video, data=None, **kwargs): def __init__(self, video, data=None, **kwargs):
super(VideoTranscodeAdminForm, self).__init__(data=data, **kwargs) super(VideoTranscodeAdminForm, self).__init__(data=data, **kwargs)
...@@ -62,7 +63,8 @@ class VideoTranscodeAdminForm(forms.Form): ...@@ -62,7 +63,8 @@ class VideoTranscodeAdminForm(forms.Form):
def save(self): def save(self):
media_format = self.cleaned_data['media_format'] media_format = self.cleaned_data['media_format']
self.video.do_transcode(media_format) quality = self.cleaned_data['quality']
self.video.do_transcode(media_format, quality)
GroupVideoPermissionFormSet = collection_member_permission_formset_factory( GroupVideoPermissionFormSet = collection_member_permission_formset_factory(
......
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-01 04:35
from __future__ import unicode_literals
from django.db import migrations
import enumchoicefield.fields
import wagtailvideos.models
class Migration(migrations.Migration):
dependencies = [
('wagtailvideos', '0008_auto_20160728_1523'),
]
operations = [
migrations.AddField(
model_name='videotranscode',
name='quality',
field=enumchoicefield.fields.EnumChoiceField(default=wagtailvideos.models.VideoQuality(1), enum_class=wagtailvideos.models.VideoQuality, max_length=7),
),
]
...@@ -31,11 +31,36 @@ from wagtail.wagtailsearch.queryset import SearchableQuerySetMixin ...@@ -31,11 +31,36 @@ from wagtail.wagtailsearch.queryset import SearchableQuerySetMixin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class VideoQuality(ChoiceEnum):
default = 'Default'
lowest = 'Low'
highest = 'High'
class MediaFormats(ChoiceEnum): class MediaFormats(ChoiceEnum):
webm = 'VP8 and Vorbis in WebM' webm = 'VP8 and Vorbis in WebM'
mp4 = 'H.264 and MP3 in Mp4' mp4 = 'H.264 and MP3 in Mp4'
ogg = 'Theora and Voris in Ogg' ogg = 'Theora and Voris in Ogg'
def get_quality_param(self, quality):
if self is MediaFormats.webm:
return {
VideoQuality.lowest: '50',
VideoQuality.default: '22',
VideoQuality.highest: '4'
}[quality]
elif self is MediaFormats.mp4:
return {
VideoQuality.lowest: '28',
VideoQuality.default: '24',
VideoQuality.highest: '18'
}[quality]
elif self is MediaFormats.ogg:
return {
VideoQuality.lowest: '5',
VideoQuality.default: '7',
VideoQuality.highest: '9'
}[quality]
class VideoQuerySet(SearchableQuerySetMixin, models.QuerySet): class VideoQuerySet(SearchableQuerySetMixin, models.QuerySet):
pass pass
...@@ -200,14 +225,17 @@ class AbstractVideo(CollectionMember, TagSearchable): ...@@ -200,14 +225,17 @@ class AbstractVideo(CollectionMember, TagSearchable):
except Transcode.DoesNotExist: except Transcode.DoesNotExist:
return self.do_transcode(media_format) return self.do_transcode(media_format)
def do_transcode(self, media_format, force=False): def do_transcode(self, media_format, quality):
transcode, created = self.transcodes.get_or_create( transcode, created = self.transcodes.get_or_create(
media_format=media_format, media_format=media_format,
) )
if transcode.processing is False: if transcode.processing is False:
transcode.processing = True transcode.processing = True
transcode.error_messages = '' transcode.error_messages = ''
transcode.save(update_fields=['processing', 'error_message']) # Lock the transcode model transcode.quality = quality
# Lock the transcode model
transcode.save(update_fields=['processing', 'error_message',
'quality'])
TranscodingThread(transcode).start() TranscodingThread(transcode).start()
else: else:
pass # TODO Queue? pass # TODO Queue?
...@@ -242,12 +270,13 @@ class TranscodingThread(threading.Thread): ...@@ -242,12 +270,13 @@ class TranscodingThread(threading.Thread):
output_file = os.path.join(output_dir, transcode_name) output_file = os.path.join(output_dir, transcode_name)
FNULL = open(os.devnull, 'r') FNULL = open(os.devnull, 'r')
quality_param = media_format.get_quality_param(self.transcode.quality)
args = ['ffmpeg', '-hide_banner', '-i', input_file] args = ['ffmpeg', '-hide_banner', '-i', input_file]
try: try:
if media_format is MediaFormats.ogg: if media_format is MediaFormats.ogg:
subprocess.check_output(args + [ subprocess.check_output(args + [
'-codec:v', 'libtheora', '-codec:v', 'libtheora',
'-qscale:v', '7', '-qscale:v', quality_param,
'-codec:a', 'libvorbis', '-codec:a', 'libvorbis',
'-qscale:a', '5', '-qscale:a', '5',
output_file, output_file,
...@@ -256,15 +285,14 @@ class TranscodingThread(threading.Thread): ...@@ -256,15 +285,14 @@ class TranscodingThread(threading.Thread):
subprocess.check_output(args + [ subprocess.check_output(args + [
'-codec:v', 'libx264', '-codec:v', 'libx264',
'-preset', 'slow', # TODO Checkout other presets '-preset', 'slow', # TODO Checkout other presets
'-crf', '22', '-crf', quality_param,
'-codec:a', 'copy', '-codec:a', 'copy',
output_file, output_file,
], stdin=FNULL, stderr=subprocess.STDOUT) ], stdin=FNULL, stderr=subprocess.STDOUT)
elif media_format is MediaFormats.webm: elif media_format is MediaFormats.webm:
subprocess.check_output(args + [ subprocess.check_output(args + [
'-codec:v', 'libvpx', '-codec:v', 'libvpx',
'-crf', '10', '-crf', quality_param,
'-b:v', '1M',
'-codec:a', 'libvorbis', '-codec:a', 'libvorbis',
output_file, output_file,
], stdin=FNULL, stderr=subprocess.STDOUT) ], stdin=FNULL, stderr=subprocess.STDOUT)
...@@ -286,6 +314,7 @@ def video_delete(sender, instance, **kwargs): ...@@ -286,6 +314,7 @@ def video_delete(sender, instance, **kwargs):
instance.thumbnail.delete(False) instance.thumbnail.delete(False)
instance.file.delete(False) instance.file.delete(False)
# Fields that need the actual video file to create # Fields that need the actual video file to create
@receiver(post_save, sender=Video) @receiver(post_save, sender=Video)
def video_saved(sender, instance, **kwargs): def video_saved(sender, instance, **kwargs):
...@@ -298,11 +327,13 @@ def video_saved(sender, instance, **kwargs): ...@@ -298,11 +327,13 @@ def video_saved(sender, instance, **kwargs):
instance.save() instance.save()
del instance._from_signal del instance._from_signal
class AbstractVideoTranscode(models.Model): class AbstractVideoTranscode(models.Model):
media_format = EnumChoiceField(MediaFormats) media_format = EnumChoiceField(MediaFormats)
quality = EnumChoiceField(VideoQuality, default=VideoQuality.default)
processing = models.BooleanField(default=False) processing = models.BooleanField(default=False)
file = models.FileField(null=True, blank=True, file = models.FileField(null=True, blank=True, verbose_name=_('file'),
verbose_name=_('file'), upload_to=get_upload_to) upload_to=get_upload_to)
error_message = models.TextField(blank=True) error_message = models.TextField(blank=True)
@property @property
...@@ -326,6 +357,7 @@ class VideoTranscode(AbstractVideoTranscode): ...@@ -326,6 +357,7 @@ class VideoTranscode(AbstractVideoTranscode):
('video', 'media_format') ('video', 'media_format')
) )
# Delete files when model is deleted # Delete files when model is deleted
@receiver(pre_delete, sender=VideoTranscode) @receiver(pre_delete, sender=VideoTranscode)
def transcode_delete(sender, instance, **kwargs): def transcode_delete(sender, instance, **kwargs):
......
{% extends "wagtailadmin/base.html" %} {% extends "wagtailadmin/base.html" %} {% load staticfiles i18n wagtailvideos_tags %} {% block titletag %}{% blocktrans with title=video.title %}Editing video {{ title }}{% endblocktrans %}{% endblock %} {% block extra_css %}
{% load staticfiles i18n wagtailvideos_tags %} <link rel="stylesheet" href="{% static 'wagtailvideos/css/edit-video.css' %}" type="text/css" /> {% endblock %} {% block extra_js %} {{ block.super }} {% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
{% block titletag %}{% blocktrans with title=video.title %}Editing video {{ title }}{% endblocktrans %}{% endblock %} <script>
$(function() {
{% block extra_css %} $('#id_tags').tagit({
<link rel="stylesheet" href="{% static 'wagtailvideos/css/edit-video.css' %}" type="text/css" /> autocomplete: {
source: "{{ autocomplete_url|addslashes }}"
{% endblock %} }
{% block extra_js %}
{{ block.super }}
{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
<script>
$(function() {
$('#id_tags').tagit({
autocomplete: {source: "{{ autocomplete_url|addslashes }}"}
});
}); });
</script> });
{% endblock %} </script>
{% endblock %} {% block content %} {% trans "Editing" as editing_str %} {% include "wagtailadmin/shared/header.html" with title=editing_str subtitle=video.title icon="media" usage_object=video %}
{% block content %}
{% trans "Editing" as editing_str %}
{% include "wagtailadmin/shared/header.html" with title=editing_str subtitle=video.title icon="media" usage_object=video %}
<div class="row row-flush nice-padding"> <div class="row row-flush nice-padding">
<div class="col5"> <div class="col5">
<form action="{% url 'wagtailvideos:edit' video.id %}" method="POST" enctype="multipart/form-data"> <form action="{% url 'wagtailvideos:edit' video.id %}" method="POST" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<ul class="fields"> <ul class="fields">
{% for field in form %} {% for field in form %} {% if field.name == 'file' %} {% include "wagtailvideos/videos/_file_field_as_li.html" %} {% elif field.is_hidden %} {{ field }} {% else %} {% include "wagtailadmin/shared/field_as_li.html" %} {% endif %} {% endfor %}
{% if field.name == 'file' %} <li>
{% include "wagtailvideos/videos/_file_field_as_li.html" %} <input type="submit" class="button" value="{% trans 'Save' %}" /> {% if user_can_delete %}
{% elif field.is_hidden %} <a href="{% url 'wagtailvideos:delete' video.id %}" class="button button-secondary no">{% trans "Delete video" %}</a> {% endif %}
{{ field }} </li>
{% else %} </ul>
{% include "wagtailadmin/shared/field_as_li.html" %} </form>
{% endif %} </div>
{% endfor %} <div class="col5 divider-after">
<li> <h2 class="label">{% trans "Video preview" %}</h2> {% video video controls style=max-width:100% %}
<input type="submit" class="button" value="{% trans 'Save' %}" /> <h3 class="label">Transcodes</h3>
{% if user_can_delete %} <p>If you wish to generate HTML5 compliant transcodes use the form below. This may take a while depending on the length of the video.</p>
<a href="{% url 'wagtailvideos:delete' video.id %}" class="button button-secondary no">{% trans "Delete video" %}</a> {% if transcodes %}
{% endif %} <h3 class="label">Available Transcodes</h3>
</li> <ul>
</ul> {% for transcode in transcodes %}
</form> <li>
</div> {{ transcode.media_format }} ({{ transcode.quality }} quality) {% if transcode.processing %} <span class='processing'>(Processing... hold tight) </span>{% endif %} {% if transcode.error_message %}
<div class="col5 divider-after"> <span class='transcode-error'>ERROR:</span>
<h2 class="label">{% trans "Video preview" %}</h2> <div class='transcode-error'>
{% video video controls style=max-width:100% %} <pre> {{ transcode.error_message }}</pre>
<h3 class="label">Transcodes</h3> </div>
<p>If you wish to generate HTML5 compliant transcodes use the form below. This may take a while dependung in the length of the video.</p> {% endif %}
{% if transcodes %} </li>
<h3 class="label">Available Transcodes</h3> {% endfor %}
<ul> </ul>
{% for transcode in transcodes %} {% endif %}
<li> <h3 class="label">Create transcode</h3>
{{ transcode.media_format }} <form action="{% url 'wagtailvideos:create_transcode' video.id %}" method="POST">
{% if transcode.processing %} <span class='processing'>(Processing... hold tight) </span>{% endif %} <ul class="fields">
{% if transcode.error_message %} {% csrf_token %} {% include "wagtailadmin/shared/field_as_li.html" with field=transcode_form.media_format %} {% include "wagtailadmin/shared/field_as_li.html" with field=transcode_form.quality %}
<span class='transcode-error'>ERROR:</span> <li>
<div class='transcode-error'> <input class="button" type='submit' value="Start" />
<pre> {{ transcode.error_message }}</pre> </li>
</div> </ul>
{% endif %} </form>
</li> </div>
{% endfor %} <div class="col2 ">
</ul> <dl>
{% endif %} <dt>{% trans "Thumbnail" %}</dt>
<h3 class="label">Create transcode</h3> <dd><img src='{{ video.thumbnail.url }}' /></dd>
<form action="{% url 'wagtailvideos:create_transcode' video.id %}" method="POST"> <dt>{% trans "Filesize" %}</dt>
<ul class="fields"> <dd>{% if filesize %}{{ filesize|filesizeformat }}{% else %}{% trans "File not found" %}{% endif %}</dd>
{% csrf_token %} <dt>{% trans "Duration" %}</dt>
{% include "wagtailadmin/shared/field_as_li.html" with field=transcode_form.media_format %} <dd>{{ video.formatted_duration }}</dd>
<li> </dl>
<input class="button" type='submit' value="Start" />
</li>
</ul>
</form>
</div>
<div class="col2 ">
<dl>
<dt>{% trans "Thumbnail" %}</dt>
<dd><img src='{{ video.thumbnail.url }}' /></dd>
<dt>{% trans "Filesize" %}</dt>
<dd>{% if filesize %}{{ filesize|filesizeformat }}{% else %}{% trans "File not found" %}{% endif %}</dd>
<dt>{% trans "Duration" %}</dt>
<dd>{{ video.formatted_duration }}</dd>
</dl>
</div>
</div> </div>
</div>
{% endblock %} {% endblock %}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment