Skip to content
This repository was archived by the owner on Mar 15, 2018. It is now read-only.

Commit 118e62f

Browse files
author
Andy McKay
committed
allow videos to be uploaded in the marketplace (bug 735057)
1 parent 9bce7a2 commit 118e62f

File tree

20 files changed

+292
-69
lines changed

20 files changed

+292
-69
lines changed

apps/addons/models.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,17 +1612,30 @@ def _image_url(self, url_template):
16121612
modified = int(time.mktime(self.modified.timetuple()))
16131613
else:
16141614
modified = 0
1615-
return url_template % (self.id / 1000, self.id, modified)
1615+
args = [self.id / 1000, self.id, modified]
1616+
if '.png' not in url_template:
1617+
args.insert(2, self.file_extension)
1618+
return url_template % tuple(args)
16161619

16171620
def _image_path(self, url_template):
1618-
return url_template % (self.id / 1000, self.id)
1621+
args = [self.id / 1000, self.id]
1622+
if '.png' not in url_template:
1623+
args.append(self.file_extension)
1624+
return url_template % tuple(args)
16191625

16201626
def as_dict(self, src=None):
16211627
d = {'full': urlparams(self.image_url, src=src),
16221628
'thumbnail': urlparams(self.thumbnail_url, src=src),
16231629
'caption': unicode(self.caption)}
16241630
return d
16251631

1632+
@property
1633+
def file_extension(self):
1634+
# Assume that blank is an image.
1635+
if not self.filetype:
1636+
return 'png'
1637+
return self.filetype.split('/')[1]
1638+
16261639
@property
16271640
def thumbnail_url(self):
16281641
return self._image_url(settings.PREVIEW_THUMBNAIL_URL)

apps/addons/tests/test_models.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,19 @@ def test_as_dict(self):
14181418
reality = sorted(Preview.objects.all()[0].as_dict().keys())
14191419
eq_(expect, reality)
14201420

1421+
def test_filename(self):
1422+
preview = Preview.objects.get(pk=24)
1423+
eq_(preview.file_extension, 'png')
1424+
preview.update(filetype='')
1425+
eq_(preview.file_extension, 'png')
1426+
preview.update(filetype='video/webm')
1427+
eq_(preview.file_extension, 'webm')
1428+
1429+
def test_filename_in_url(self):
1430+
preview = Preview.objects.get(pk=24)
1431+
preview.update(filetype='video/webm')
1432+
assert 'webm' in preview.thumbnail_path
1433+
14211434

14221435
class TestAddonRecommendations(amo.tests.TestCase):
14231436
fixtures = ['base/addon-recs']
@@ -1479,7 +1492,7 @@ def setUp(self):
14791492
settings.PREVIEW_THUMBNAIL_URL = (settings.STATIC_URL +
14801493
'/img/uploads/previews/thumbs/%s/%d.png?modified=%d')
14811494
settings.PREVIEW_FULL_URL = (settings.STATIC_URL +
1482-
'/img/uploads/previews/full/%s/%d.png?modified=%d')
1495+
'/img/uploads/previews/full/%s/%d.%s?modified=%d')
14831496
_connect()
14841497

14851498
def tearDown(self):

apps/amo/fixtures/base/previews.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@
165165
"model": "addons.preview",
166166
"fields": {
167167
"caption": 32680,
168-
"filetype": "image/jpeg",
168+
"filetype": "image/png",
169169
"created": "2007-03-05 09:54:15",
170170
"modified": null,
171171
"thumbtype": "image/png",

apps/constants/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@
212212

213213
# Accepted image MIME-types
214214
IMG_TYPES = ('image/png', 'image/jpeg', 'image/jpg')
215+
VIDEO_TYPES = ('video/webm',)
215216

216217
# These types don't maintain app compatibility in the db. Instead, we look at
217218
# APP.types and APP_TYPE_SUPPORT to figure out where they are compatible.

lib/settings_base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ def JINJA_CONFIG():
906906
ADDON_ICONS_DEFAULT_PATH = os.path.join(MEDIA_ROOT, 'img/addon-icons')
907907

908908
PREVIEW_THUMBNAIL_PATH = (PREVIEWS_PATH + '/thumbs/%s/%d.png')
909-
PREVIEW_FULL_PATH = (PREVIEWS_PATH + '/full/%s/%d.png')
909+
PREVIEW_FULL_PATH = (PREVIEWS_PATH + '/full/%s/%d.%s')
910910

911911
# URL paths
912912
# paths for images, e.g. mozcdn.com/amo or '/static'
@@ -918,7 +918,7 @@ def JINJA_CONFIG():
918918
PREVIEW_THUMBNAIL_URL = (STATIC_URL +
919919
'/img/uploads/previews/thumbs/%s/%d.png?modified=%d')
920920
PREVIEW_FULL_URL = (STATIC_URL +
921-
'/img/uploads/previews/full/%s/%d.png?modified=%d')
921+
'/img/uploads/previews/full/%s/%d.%s?modified=%d')
922922
USERPICS_URL = STATIC_URL + '/img/uploads/userpics/%s/%s/%s.png?modified=%d'
923923
# paths for uploaded extensions
924924
COLLECTION_ICON_URL = (STATIC_URL +
@@ -1168,6 +1168,7 @@ def read_only_mode(env):
11681168

11691169
# Uploaded file limits
11701170
MAX_ICON_UPLOAD_SIZE = 4 * 1024 * 1024
1171+
MAX_VIDEO_UPLOAD_SIZE = 4 * 1024 * 1024
11711172
MAX_PHOTO_UPLOAD_SIZE = MAX_ICON_UPLOAD_SIZE
11721173
MAX_PERSONA_UPLOAD_SIZE = 300 * 1024
11731174
MAX_WEBAPP_UPLOAD_SIZE = 2 * 1024 * 1024

lib/video/ffmpeg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def is_valid(self):
124124
assert self.meta is not None
125125
self.errors = []
126126
if 'webm' not in self.meta.get('formats', ''):
127-
self.errors.append('Must be a valid webm video')
127+
self.errors.append('Videos must be WEBM.')
128128

129129
#TODO(andym): More checks on duration, file size, bit rate?
130130
return not self.errors

lib/video/tasks.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
import amo
88
from amo.decorators import set_modified_on
99
from lib.video.ffmpeg import Video
10+
import waffle
1011

1112
log = logging.getLogger('z.devhub.task')
1213

1314

1415
@task
1516
@set_modified_on
16-
def resize_video(src, instance, *kw):
17+
def resize_video(src, instance, **kw):
1718
"""
1819
Given a preview object and a file somewhere: encode into the full
1920
preview size and generate a thumbnail.
@@ -29,26 +30,40 @@ def resize_video(src, instance, *kw):
2930
log.info('Video is not valid for %s' % instance.pk)
3031
return
3132

32-
# Do the video first, this can take a bit.
33-
try:
34-
video_file = video.get_encoded(amo.ADDON_PREVIEW_SIZES[1])
35-
except Exception:
36-
log.info('Error encoding video for %s' % instance.pk)
37-
return
33+
if waffle.switch_is_active('video-encode'):
34+
# Do the video encoding.
35+
try:
36+
video_file = video.get_encoded(amo.ADDON_PREVIEW_SIZES[1])
37+
except Exception:
38+
log.info('Error encoding video for %s' % instance.pk)
39+
return
3840

3941
# Do the thumbnail next, this will be the signal that the
4042
# encoding has finished.
4143
try:
4244
thumbnail_file = video.get_screenshot(amo.ADDON_PREVIEW_SIZES[0])
4345
except Exception:
4446
# We'll have this file floating around because the video
45-
# encoded successfully.
46-
os.remove(video_file)
47+
# encoded successfully, or something has gone wrong in which case
48+
# we don't want the file around anyway.
49+
if waffle.switch_is_active('video-encode'):
50+
os.remove(video_file)
4751
log.info('Error making thumbnail for %s' % instance.pk)
4852
return
4953

54+
for path in (instance.thumbnail_path, instance.image_path):
55+
dirs = os.path.dirname(path)
56+
if not os.path.exists(dirs):
57+
os.makedirs(dirs)
58+
5059
shutil.move(thumbnail_file, instance.thumbnail_path)
51-
shutil.move(video_file, instance.image_path)
60+
if waffle.switch_is_active('video-encode'):
61+
# Move the file over, removing the temp file.
62+
shutil.move(video_file, instance.image_path)
63+
else:
64+
# We didn't re-encode the file.
65+
shutil.copyfile(src, instance.image_path)
66+
5267
instance.sizes = {'thumbnail': amo.ADDON_PREVIEW_SIZES[0],
5368
'image': amo.ADDON_PREVIEW_SIZES[1]}
5469
instance.save()

lib/video/tests.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
import stat
33
import tempfile
44

5-
from mock import Mock
5+
from mock import Mock, patch
66

77
from nose import SkipTest
88
from nose.tools import eq_
99

10+
import waffle
11+
1012
import amo
1113
import amo.tests
1214
from amo.tests.test_helpers import get_image_path
@@ -83,13 +85,22 @@ def test_encoded(self):
8385
class TestTask(amo.tests.TestCase):
8486

8587
def setUp(self):
88+
waffle.models.Switch.objects.create(name='video-encode', active=True)
8689
self.mock = Mock()
8790
self.mock.thumbnail_path = tempfile.mkstemp()[1]
8891
self.mock.image_path = tempfile.mkstemp()[1]
8992
self.mock.pk = 1
9093
if not ffmpeg.Video('').encoder_available():
9194
raise SkipTest
9295

96+
@patch('lib.video.ffmpeg.Video.get_encoded')
97+
def test_resize_video_no_encode(self, get_encoded):
98+
waffle.models.Switch.objects.update(name='video-encode', active=False)
99+
resize_video(files['good'], self.mock)
100+
assert not get_encoded.called
101+
assert isinstance(self.mock.sizes, dict)
102+
assert self.mock.save.called
103+
93104
def test_resize_video(self):
94105
resize_video(files['good'], self.mock)
95106
assert isinstance(self.mock.sizes, dict)

media/js/common/upload-image.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,14 @@ $.fn.objectUrl = function(offset) {
9494
instance_id++;
9595
outstanding_uploads++;
9696

97-
// Make sure it's images only.
98-
if(file.type != 'image/jpeg' && file.type != 'image/png') {
97+
if($upload_field.attr('data-allowed-types').split('|').indexOf(file.type) < 0) {
9998
var errors;
10099
if (typeof $upload_field.attr('multiple') !== 'undefined') {
101100
// If we have a `multiple` attribute, assume not an icon.
102101
errors = [gettext("Images must be either PNG or JPG.")];
102+
if ($upload_field.attr('data-allowed-types').indexOf('video') > -1) {
103+
errors.push([gettext("Videos must be WEBM.")]);
104+
}
103105
} else {
104106
errors = [gettext("Icons must be either PNG or JPG.")];
105107
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
INSERT INTO waffle_switch_amo (name, active, note)
2+
VALUES ('video-encode', 0,
3+
'Encode videos when they get uploaded.');
4+
INSERT INTO waffle_switch_mkt (name, active, note)
5+
VALUES ('video-encode', 0,
6+
'Encode videos when they get uploaded.');

0 commit comments

Comments
 (0)