Commit 9d09e446 authored by Seb's avatar Seb
Browse files

Rudimentry transcoding of videos working, attaching to Video model as related field

parent fc0f8eed
...@@ -18,7 +18,8 @@ setup( ...@@ -18,7 +18,8 @@ setup(
url='https://github.com/takeflight/wagtailvideos', url='https://github.com/takeflight/wagtailvideos',
install_requires=[ install_requires=[
'wagtail>=1.0', 'wagtail>=1.4',
'django-enumchoicefield==0.6.0',
], ],
zip_safe=False, zip_safe=False,
license='BSD License', license='BSD License',
...@@ -28,6 +29,8 @@ setup( ...@@ -28,6 +29,8 @@ setup(
include_package_data=True, include_package_data=True,
package_data={}, package_data={},
classifiers=[ classifiers=[
'Environment :: Web Environment', 'Environment :: Web Environment',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
......
...@@ -7,8 +7,7 @@ import subprocess ...@@ -7,8 +7,7 @@ import subprocess
import tempfile import tempfile
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from PIL import Image import django
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
...@@ -16,6 +15,8 @@ from django.core.urlresolvers import reverse ...@@ -16,6 +15,8 @@ from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from enum import Enum
from PIL import Image
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from unidecode import unidecode from unidecode import unidecode
from wagtail.wagtailadmin.taggable import TagSearchable from wagtail.wagtailadmin.taggable import TagSearchable
...@@ -24,6 +25,13 @@ from wagtail.wagtailcore.models import CollectionMember ...@@ -24,6 +25,13 @@ from wagtail.wagtailcore.models import CollectionMember
from wagtail.wagtailsearch import index from wagtail.wagtailsearch import index
from wagtail.wagtailsearch.queryset import SearchableQuerySetMixin from wagtail.wagtailsearch.queryset import SearchableQuerySetMixin
from enumchoicefield import ChoiceEnum, EnumChoiceField
class MediaFormats(ChoiceEnum):
webm = 'VP8 and Vorbis in WebM'
mp4 = 'H.264 and MP3 in Mp4'
ogg = 'Theora and Voris in Ogg'
class VideoQuerySet(SearchableQuerySetMixin, models.QuerySet): class VideoQuerySet(SearchableQuerySetMixin, models.QuerySet):
pass pass
...@@ -76,7 +84,7 @@ class AbstractVideo(CollectionMember, TagSearchable): ...@@ -76,7 +84,7 @@ class AbstractVideo(CollectionMember, TagSearchable):
return self.file_size return self.file_size
def get_upload_to(self, filename): def get_upload_to(self, filename):
folder_name = 'original_images' folder_name = 'original_videos'
filename = self.file.field.storage.get_valid_name(filename) filename = self.file.field.storage.get_valid_name(filename)
# do a unidecode in the filename and then # do a unidecode in the filename and then
...@@ -105,9 +113,6 @@ class AbstractVideo(CollectionMember, TagSearchable): ...@@ -105,9 +113,6 @@ class AbstractVideo(CollectionMember, TagSearchable):
def __str__(self): def __str__(self):
return self.title return self.title
def get_rendition(self, filter):
pass # TODO
def get_thumbnail(self): def get_thumbnail(self):
file_path = self.file.path file_path = self.file.path
try: try:
...@@ -151,6 +156,85 @@ class AbstractVideo(CollectionMember, TagSearchable): ...@@ -151,6 +156,85 @@ class AbstractVideo(CollectionMember, TagSearchable):
from wagtail.wagtailimages.permissions import permission_policy from wagtail.wagtailimages.permissions import permission_policy
return permission_policy.user_has_permission_for_instance(user, 'change', self) return permission_policy.user_has_permission_for_instance(user, 'change', self)
@classmethod
def get_transcode_model(cls):
if django.VERSION >= (1, 9):
return cls.transcodes.rel.related_model
else:
return cls.transcodes.related.related_model
def get_transcode(self, media_format):
# TODO check media_format is MediaFormat
Transcode = self.get_transcode_model()
try:
return self.transcodes.get(media_format=media_format)
except Transcode.DoesNotExist:
output_dir = tempfile.mkdtemp()
transcode_filename = os.path.splitext(
os.path.basename(self.file.path))[0] + '.' + media_format.name
transcoded_file = self.do_transcode(
media_format, self.file.path, output_dir, transcode_filename)
if transcoded_file:
transcode, created = self.transcodes.get_or_create(
media_format=media_format,
defaults={'file': transcoded_file}
)
return transcode
else:
# TODO handle transcode failure
return None
def do_transcode(self, media_format, input_file, output_dir, transcode_name):
output_file = os.path.join(output_dir, transcode_name)
FNULL = open(os.devnull, 'r')
try:
if media_format is MediaFormats.ogg:
subprocess.check_call([
'ffmpeg',
'-i', input_file,
'-codec:v', 'libtheora',
'-qscale:v', '7',
'-codec:a', 'libvorbis',
'-qscale:a', '5',
output_file,
], stdin=FNULL)
elif media_format is MediaFormats.mp4:
subprocess.check_call([
'ffmpeg',
'-i', input_file,
'-codec:v', 'libx264',
'-preset', 'slow', # TODO Checkout other presets
'-crf', '22',
'-codec:a', 'copy',
output_file,
])
elif media_format is MediaFormats.webm:
subprocess.check_call([
'ffmpeg',
'-i', input_file,
'-codec:v', 'libvpx',
'-crf', '10',
'-b:v', '1M',
'-codec:a', 'libvorbis',
output_file,
])
else:
return None
transcoded_file = ContentFile(open(output_file, 'rb').read(), transcode_name)
except subprocess.CalledProcessError:
return None
finally:
shutil.rmtree(output_dir, ignore_errors=True)
return transcoded_file
class Meta: class Meta:
abstract = True abstract = True
...@@ -182,3 +266,30 @@ def get_video_model(): ...@@ -182,3 +266,30 @@ def get_video_model():
settings.WAGTAILIMAGES_IMAGE_MODEL settings.WAGTAILIMAGES_IMAGE_MODEL
) )
return image_model return image_model
class AbstractVideoTranscode(models.Model):
media_format = EnumChoiceField(MediaFormats)
file = models.FileField(
verbose_name=_('file'), upload_to=get_upload_to) # FIXME get_transcode_upload_to
@property
def url(self):
return self.file.url
def get_upload_to(self, filename):
folder_name = 'video_transcodes'
filename = self.file.field.storage.get_valid_name(filename)
return os.path.join(folder_name, filename)
class Meta:
abstract = True
class VideoTranscode(AbstractVideoTranscode):
video = models.ForeignKey(Video, related_name='transcodes')
class Meta:
unique_together = (
('video', 'media_format')
)
...@@ -51,7 +51,6 @@ ...@@ -51,7 +51,6 @@
<video style='max-width:100%;height:auto;' {# FIXME Inline styles #} preload="auto" controls="true" poster='{{video.thumbnail.url}}'> <video style='max-width:100%;height:auto;' {# FIXME Inline styles #} preload="auto" controls="true" poster='{{video.thumbnail.url}}'>
<source src="{{video.file.url}}" type='video/mp4'/> <source src="{{video.file.url}}" type='video/mp4'/>
</video> </video>
<!--img src='{{video.thumbnail.url}}' /-->
<div class="focal-point-chooser" <div class="focal-point-chooser"
style="max-width: {{ rendition.width }}px; max-height: {{ rendition.height }}px;" style="max-width: {{ rendition.width }}px; max-height: {{ rendition.height }}px;"
......
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