Commit 5bacfcc3 by Anton Stupak

Merge pull request #2045 from edx/anton/video-merge-fields

Merge "video sources" and "download video" into the same field
parents 636122c0 645007f9
...@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes, ...@@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected. the top. Include a label indicating the component affected.
Blades: Change the download video field to a dropdown that will allow students
to download the first source listed in the alternate sources. BLD-364.
Blades: Change the track field to a dropdown that will allow students Blades: Change the track field to a dropdown that will allow students
to download the transcript of the video without timecodes. BLD-368. to download the transcript of the video without timecodes. BLD-368.
......
...@@ -40,12 +40,12 @@ def correct_video_settings(_step): ...@@ -40,12 +40,12 @@ def correct_video_settings(_step):
# advanced # advanced
['Display Name', 'Video', False], ['Display Name', 'Video', False],
['Download Video', '', False],
['End Time', '00:00:00', False], ['End Time', '00:00:00', False],
['HTML5 Transcript', '', False], ['HTML5 Transcript', '', False],
['Show Transcript', 'True', False], ['Show Transcript', 'True', False],
['Start Time', '00:00:00', False], ['Start Time', '00:00:00', False],
['Transcript Download Allowed', 'False', False], ['Transcript Download Allowed', 'False', False],
['Video Download Allowed', 'False', False],
['Video Sources', '', False], ['Video Sources', '', False],
['Youtube ID', 'OEoXaMPEzfM', False], ['Youtube ID', 'OEoXaMPEzfM', False],
['Youtube ID for .75x speed', '', False], ['Youtube ID for .75x speed', '', False],
......
...@@ -106,7 +106,6 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList) { ...@@ -106,7 +106,6 @@ function(BaseView, _, MetadataModel, AbstractEditor, VideoList) {
} }
}, },
getValueFromEditor : function () { getValueFromEditor : function () {
return this.$el.find('#' + this.uniqueId).val(); return this.$el.find('#' + this.uniqueId).val();
}, },
......
...@@ -187,6 +187,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -187,6 +187,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8" youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
show_captions="false" show_captions="false"
download_track="true" download_track="true"
download_video="true"
start_time="00:00:01" start_time="00:00:01"
end_time="00:01:00"> end_time="00:01:00">
<source src="http://www.example.com/source.mp4"/> <source src="http://www.example.com/source.mp4"/>
...@@ -207,6 +208,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -207,6 +208,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'youtube_id_1_0': 'p2Q6BrNhdh8', 'youtube_id_1_0': 'p2Q6BrNhdh8',
'youtube_id_1_25': '1EeWXzPdhSA', 'youtube_id_1_25': '1EeWXzPdhSA',
'youtube_id_1_5': 'rABDYkeK0x8', 'youtube_id_1_5': 'rABDYkeK0x8',
'download_video': True,
'show_captions': False, 'show_captions': False,
'start_time': datetime.timedelta(seconds=1), 'start_time': datetime.timedelta(seconds=1),
'end_time': datetime.timedelta(seconds=60), 'end_time': datetime.timedelta(seconds=60),
...@@ -224,6 +226,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -224,6 +226,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
show_captions="false" show_captions="false"
download_track="false" download_track="false"
start_time="00:00:01" start_time="00:00:01"
download_video="false"
end_time="00:01:00"> end_time="00:01:00">
<source src="http://www.example.com/source.mp4"/> <source src="http://www.example.com/source.mp4"/>
<track src="http://www.example.com/track"/> <track src="http://www.example.com/track"/>
...@@ -240,7 +243,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -240,7 +243,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'end_time': datetime.timedelta(seconds=60), 'end_time': datetime.timedelta(seconds=60),
'track': 'http://www.example.com/track', 'track': 'http://www.example.com/track',
'download_track': False, 'download_track': False,
'source': 'http://www.example.com/source.mp4', 'download_video': False,
'html5_sources': ['http://www.example.com/source.mp4'], 'html5_sources': ['http://www.example.com/source.mp4'],
'data': '' 'data': ''
}) })
...@@ -269,7 +272,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -269,7 +272,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'end_time': datetime.timedelta(seconds=0.0), 'end_time': datetime.timedelta(seconds=0.0),
'track': '', 'track': '',
'download_track': False, 'download_track': False,
'source': 'http://www.example.com/source.mp4', 'download_video': False,
'html5_sources': ['http://www.example.com/source.mp4'], 'html5_sources': ['http://www.example.com/source.mp4'],
'data': '' 'data': ''
}) })
...@@ -291,7 +294,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -291,7 +294,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'end_time': datetime.timedelta(seconds=0.0), 'end_time': datetime.timedelta(seconds=0.0),
'track': '', 'track': '',
'download_track': False, 'download_track': False,
'source': '', 'download_video': False,
'html5_sources': [], 'html5_sources': [],
'data': '' 'data': ''
}) })
...@@ -306,7 +309,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -306,7 +309,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
<video display_name="&quot;display_name&quot;" <video display_name="&quot;display_name&quot;"
html5_sources="[&quot;source_1&quot;, &quot;source_2&quot;]" html5_sources="[&quot;source_1&quot;, &quot;source_2&quot;]"
show_captions="false" show_captions="false"
source="&quot;http://download_video&quot;" download_video="true"
sub="&quot;html5_subtitles&quot;" sub="&quot;html5_subtitles&quot;"
track="&quot;http://download_track&quot;" track="&quot;http://download_track&quot;"
download_track="true" download_track="true"
...@@ -327,7 +330,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -327,7 +330,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'end_time': datetime.timedelta(seconds=0.0), 'end_time': datetime.timedelta(seconds=0.0),
'track': 'http://download_track', 'track': 'http://download_track',
'download_track': True, 'download_track': True,
'source': 'http://download_video', 'download_video': True,
'html5_sources': ["source_1", "source_2"], 'html5_sources': ["source_1", "source_2"],
'data': '' 'data': ''
}) })
...@@ -350,7 +353,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -350,7 +353,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'end_time': datetime.timedelta(seconds=0.0), 'end_time': datetime.timedelta(seconds=0.0),
'track': '', 'track': '',
'download_track': False, 'download_track': False,
'source': '', 'download_video': False,
'html5_sources': [], 'html5_sources': [],
'data': '' 'data': ''
}) })
...@@ -364,6 +367,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -364,6 +367,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
<video display_name="Test Video" <video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8" youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
show_captions="false" show_captions="false"
source="http://www.example.com/source.mp4"
from="00:00:01" from="00:00:01"
to="00:01:00"> to="00:01:00">
<source src="http://www.example.com/source.mp4"/> <source src="http://www.example.com/source.mp4"/>
...@@ -382,7 +386,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase): ...@@ -382,7 +386,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'track': 'http://www.example.com/track', 'track': 'http://www.example.com/track',
'download_track': True, 'download_track': True,
'html5_sources': ['http://www.example.com/source.mp4'], 'html5_sources': ['http://www.example.com/source.mp4'],
'data': '' 'data': '',
}) })
def test_old_video_data(self): def test_old_video_data(self):
...@@ -473,10 +477,11 @@ class VideoExportTestCase(unittest.TestCase): ...@@ -473,10 +477,11 @@ class VideoExportTestCase(unittest.TestCase):
desc.track = 'http://www.example.com/track' desc.track = 'http://www.example.com/track'
desc.download_track = True desc.download_track = True
desc.html5_sources = ['http://www.example.com/source.mp4', 'http://www.example.com/source.ogg'] desc.html5_sources = ['http://www.example.com/source.mp4', 'http://www.example.com/source.ogg']
desc.download_video = True
xml = desc.definition_to_xml(None) # We don't use the `resource_fs` parameter xml = desc.definition_to_xml(None) # We don't use the `resource_fs` parameter
expected = etree.fromstring('''\ expected = etree.fromstring('''\
<video url_name="SampleProblem1" start_time="0:00:01" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false" end_time="0:01:00" download_track="true"> <video url_name="SampleProblem1" start_time="0:00:01" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false" end_time="0:01:00" download_video="true" download_track="true">
<source src="http://www.example.com/source.mp4"/> <source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.ogg"/> <source src="http://www.example.com/source.ogg"/>
<track src="http://www.example.com/track"/> <track src="http://www.example.com/track"/>
...@@ -501,10 +506,11 @@ class VideoExportTestCase(unittest.TestCase): ...@@ -501,10 +506,11 @@ class VideoExportTestCase(unittest.TestCase):
desc.track = 'http://www.example.com/track' desc.track = 'http://www.example.com/track'
desc.download_track = True desc.download_track = True
desc.html5_sources = ['http://www.example.com/source.mp4', 'http://www.example.com/source.ogg'] desc.html5_sources = ['http://www.example.com/source.mp4', 'http://www.example.com/source.ogg']
desc.download_video = True
xml = desc.definition_to_xml(None) # We don't use the `resource_fs` parameter xml = desc.definition_to_xml(None) # We don't use the `resource_fs` parameter
expected = etree.fromstring('''\ expected = etree.fromstring('''\
<video url_name="SampleProblem1" start_time="0:00:05" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false" download_track="true"> <video url_name="SampleProblem1" start_time="0:00:05" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false" download_video="true" download_track="true">
<source src="http://www.example.com/source.mp4"/> <source src="http://www.example.com/source.mp4"/>
<source src="http://www.example.com/source.ogg"/> <source src="http://www.example.com/source.ogg"/>
<track src="http://www.example.com/track"/> <track src="http://www.example.com/track"/>
......
...@@ -98,12 +98,20 @@ class VideoFields(object): ...@@ -98,12 +98,20 @@ class VideoFields(object):
) )
#front-end code of video player checks logical validity of (start_time, end_time) pair. #front-end code of video player checks logical validity of (start_time, end_time) pair.
# `source` is deprecated field and should not be used in future.
# `download_video` is used instead.
source = String( source = String(
help="The external URL to download the video. This appears as a link beneath the video.", help="The external URL to download the video.",
display_name="Download Video", display_name="Download Video",
scope=Scope.settings, scope=Scope.settings,
default="" default=""
) )
download_video = Boolean(
help="Show a link beneath the video to allow students to download the video. Note: You must add at least one video source below.",
display_name="Video Download Allowed",
scope=Scope.settings,
default=False
)
html5_sources = List( html5_sources = List(
help="A list of filenames to be used with HTML5 video. The first supported filetype will be displayed.", help="A list of filenames to be used with HTML5 video. The first supported filetype will be displayed.",
display_name="Video Sources", display_name="Video Sources",
...@@ -181,7 +189,12 @@ class VideoModule(VideoFields, XModule): ...@@ -181,7 +189,12 @@ class VideoModule(VideoFields, XModule):
get_ext = lambda filename: filename.rpartition('.')[-1] get_ext = lambda filename: filename.rpartition('.')[-1]
sources = {get_ext(src): src for src in self.html5_sources} sources = {get_ext(src): src for src in self.html5_sources}
sources['main'] = self.source
if self.download_video:
if self.source:
sources['main'] = self.source
elif self.html5_sources:
sources['main'] = self.html5_sources[0]
if self.download_track: if self.download_track:
if self.track: if self.track:
...@@ -281,6 +294,14 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor ...@@ -281,6 +294,14 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor
If `track` field exists show `track` field on front-end as not-editable If `track` field exists show `track` field on front-end as not-editable
but clearable. Dropdown `download_track` is a new field and it has value but clearable. Dropdown `download_track` is a new field and it has value
True. True.
`source` is deprecated field.
a) If `source` exists and `source` is not `html5_sources`: show `source`
field on front-end as not-editable but clearable. Dropdown is a new
field `download_video` and it has value True.
b) If `source` is cleared it is not shown anymore.
c) If `source` exists and `source` in `html5_sources`, do not show `source`
field. `download_video` field has value True.
''' '''
super(VideoDescriptor, self).__init__(*args, **kwargs) super(VideoDescriptor, self).__init__(*args, **kwargs)
# For backwards compatibility -- if we've got XML data, parse # For backwards compatibility -- if we've got XML data, parse
...@@ -290,21 +311,43 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor ...@@ -290,21 +311,43 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor
self._field_data.set_many(self, field_data) self._field_data.set_many(self, field_data)
del self.data del self.data
editable_fields = self.editable_metadata_fields
self.track_visible = False self.track_visible = False
if self.track: if self.track:
self.track_visible = True self.track_visible = True
download_track = self.editable_metadata_fields['download_track'] download_track = editable_fields['download_track']
if not download_track['explicitly_set']: if not download_track['explicitly_set']:
self.download_track = True self.download_track = True
self.source_visible = False
if self.source:
# If `source` field value exist in the `html5_sources` field values,
# then delete `source` field value and use value from `html5_sources` field.
if self.source in self.html5_sources:
self.source = '' # Delete source field value.
self.download_video = True
else: # Otherwise, `source` field value will be used.
self.source_visible = True
download_video = editable_fields['download_video']
if not download_video['explicitly_set']:
self.download_video = True
@property @property
def editable_metadata_fields(self): def editable_metadata_fields(self):
editable_fields = super(VideoDescriptor, self).editable_metadata_fields editable_fields = super(VideoDescriptor, self).editable_metadata_fields
if self.track_visible: if hasattr(self, 'track_visible'):
editable_fields['track']['non_editable'] = True if self.track_visible:
else: editable_fields['track']['non_editable'] = True
editable_fields.pop('track') else:
editable_fields.pop('track')
if hasattr(self, 'source_visible'):
if self.source_visible:
editable_fields['source']['non_editable'] = True
else:
editable_fields.pop('source')
return editable_fields return editable_fields
...@@ -359,6 +402,7 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor ...@@ -359,6 +402,7 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor
'end_time': self.end_time, 'end_time': self.end_time,
'sub': self.sub, 'sub': self.sub,
'download_track': json.dumps(self.download_track), 'download_track': json.dumps(self.download_track),
'download_video': json.dumps(self.download_video),
} }
for key, value in attrs.items(): for key, value in attrs.items():
# Mild workaround to ensure that tests pass -- if a field # Mild workaround to ensure that tests pass -- if a field
...@@ -468,7 +512,6 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor ...@@ -468,7 +512,6 @@ class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor
sources = xml.findall('source') sources = xml.findall('source')
if sources: if sources:
field_data['html5_sources'] = [ele.get('src') for ele in sources] field_data['html5_sources'] = [ele.get('src') for ele in sources]
field_data['source'] = field_data['html5_sources'][0]
track = xml.find('track') track = xml.find('track')
if track is not None: if track is not None:
......
...@@ -47,7 +47,7 @@ class TestVideoYouTube(TestVideo): ...@@ -47,7 +47,7 @@ class TestVideoYouTube(TestVideo):
METADATA = {} METADATA = {}
def test_video_constructor(self): def test_video_constructor(self):
"""Make sure that all parameters extracted correclty from xml""" """Make sure that all parameters extracted correctly from xml"""
context = self.item_module.render('student_view').content context = self.item_module.render('student_view').content
sources = { sources = {
...@@ -70,7 +70,7 @@ class TestVideoYouTube(TestVideo): ...@@ -70,7 +70,7 @@ class TestVideoYouTube(TestVideo):
'youtube_streams': _create_youtube_string(self.item_module), 'youtube_streams': _create_youtube_string(self.item_module),
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/' 'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/',
} }
self.assertEqual( self.assertEqual(
...@@ -81,11 +81,11 @@ class TestVideoYouTube(TestVideo): ...@@ -81,11 +81,11 @@ class TestVideoYouTube(TestVideo):
class TestVideoNonYouTube(TestVideo): class TestVideoNonYouTube(TestVideo):
"""Integration tests: web client + mongo.""" """Integration tests: web client + mongo."""
DATA = """ DATA = """
<video show_captions="true" <video show_captions="true"
display_name="A Name" display_name="A Name"
sub="a_sub_file.srt.sjson" sub="a_sub_file.srt.sjson"
download_video="true"
start_time="01:00:03" end_time="01:00:10" start_time="01:00:03" end_time="01:00:10"
> >
<source src="example.mp4"/> <source src="example.mp4"/>
...@@ -96,7 +96,6 @@ class TestVideoNonYouTube(TestVideo): ...@@ -96,7 +96,6 @@ class TestVideoNonYouTube(TestVideo):
'data': DATA 'data': DATA
} }
METADATA = {} METADATA = {}
def test_video_constructor(self): def test_video_constructor(self):
"""Make sure that if the 'youtube' attribute is omitted in XML, then """Make sure that if the 'youtube' attribute is omitted in XML, then
the template generates an empty string for the YouTube streams. the template generates an empty string for the YouTube streams.
...@@ -123,7 +122,7 @@ class TestVideoNonYouTube(TestVideo): ...@@ -123,7 +122,7 @@ class TestVideoNonYouTube(TestVideo):
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/' 'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/',
} }
self.assertEqual( self.assertEqual(
...@@ -132,104 +131,10 @@ class TestVideoNonYouTube(TestVideo): ...@@ -132,104 +131,10 @@ class TestVideoNonYouTube(TestVideo):
) )
class TestVideoGetTranscriptsMethod(TestVideo):
"""
Make sure that `get_transcript` method works correctly
"""
DATA = """
<video show_captions="true"
display_name="A Name"
>
<source src="example.mp4"/>
<source src="example.webm"/>
</video>
"""
MODEL_DATA = {
'data': DATA
}
METADATA = {}
def test_good_transcript(self):
self.item_module.render('student_view')
item = self.item_descriptor.xmodule_runtime.xmodule_instance
good_sjson = _create_file(content="""
{
"start": [
270,
2720
],
"end": [
2720,
5430
],
"text": [
"Hi, welcome to Edx.",
"Let&#39;s start with what is on your screen right now."
]
}
""")
_upload_file(good_sjson, self.item_module.location)
subs_id = _get_subs_id(good_sjson.name)
text = item.get_transcript(subs_id)
expected_text = "Hi, welcome to Edx.\nLet's start with what is on your screen right now."
self.assertEqual(
text, expected_text
)
def test_not_found_error(self):
self.item_module.render('student_view')
item = self.item_descriptor.xmodule_runtime.xmodule_instance
with self.assertRaises(NotFoundError):
item.get_transcript('wrong')
def test_value_error(self):
self.item_module.render('student_view')
item = self.item_descriptor.xmodule_runtime.xmodule_instance
good_sjson = _create_file(content="""
bad content
""")
_upload_file(good_sjson, self.item_module.location)
subs_id = _get_subs_id(good_sjson.name)
with self.assertRaises(ValueError):
item.get_transcript(subs_id)
def test_key_error(self):
self.item_module.render('student_view')
item = self.item_descriptor.xmodule_runtime.xmodule_instance
good_sjson = _create_file(content="""
{
"start": [
270,
2720
],
"end": [
2720,
5430
]
}
""")
_upload_file(good_sjson, self.item_module.location)
subs_id = _get_subs_id(good_sjson.name)
with self.assertRaises(KeyError):
item.get_transcript(subs_id)
class TestGetHtmlMethod(BaseTestXmodule): class TestGetHtmlMethod(BaseTestXmodule):
""" '''
Make sure that `get_html` works correctly. Make sure that `get_html` works correctly.
""" '''
CATEGORY = "video" CATEGORY = "video"
DATA = SOURCE_XML DATA = SOURCE_XML
METADATA = {} METADATA = {}
...@@ -274,7 +179,7 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -274,7 +179,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
'track': u'<track src="http://www.example.com/track"/>', 'track': u'<track src="http://www.example.com/track"/>',
'sub': u'a_sub_file.srt.sjson', 'sub': u'a_sub_file.srt.sjson',
'expected_track_url': None, 'expected_track_url': None,
} },
] ]
expected_context = { expected_context = {
...@@ -285,7 +190,6 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -285,7 +190,6 @@ class TestGetHtmlMethod(BaseTestXmodule):
'end': 3610.0, 'end': 3610.0,
'id': None, 'id': None,
'sources': { 'sources': {
'main': u'example.mp4',
u'mp4': u'example.mp4', u'mp4': u'example.mp4',
u'webm': u'example.webm' u'webm': u'example.webm'
}, },
...@@ -295,14 +199,14 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -295,14 +199,14 @@ class TestGetHtmlMethod(BaseTestXmodule):
'youtube_streams': '1.00:OEoXaMPEzfM', 'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True), 'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500, 'yt_test_timeout': 1500,
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/' 'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/',
} }
for data in cases: for data in cases:
DATA = SOURCE_XML.format( DATA = SOURCE_XML.format(
download_track=data['download_track'], download_track=data['download_track'],
track=data['track'], track=data['track'],
sub=data['sub'], sub=data['sub']
) )
self.initialize_module(data=DATA) self.initialize_module(data=DATA)
...@@ -320,6 +224,104 @@ class TestGetHtmlMethod(BaseTestXmodule): ...@@ -320,6 +224,104 @@ class TestGetHtmlMethod(BaseTestXmodule):
self.item_module.xmodule_runtime.render_template('video.html', expected_context) self.item_module.xmodule_runtime.render_template('video.html', expected_context)
) )
def test_get_html_source(self):
SOURCE_XML = """
<video show_captions="true"
display_name="A Name"
sub="a_sub_file.srt.sjson" source="{source}"
download_video="{download_video}"
start_time="01:00:03" end_time="01:00:10"
>
{sources}
</video>
"""
cases = [
# self.download_video == True
{
'download_video': 'true',
'source': 'example_source.mp4',
'sources': """
<source src="example.mp4"/>
<source src="example.webm"/>
""",
'result': {
'main': u'example_source.mp4',
u'mp4': u'example.mp4',
u'webm': u'example.webm',
},
},
{
'download_video': 'true',
'source': '',
'sources': """
<source src="example.mp4"/>
<source src="example.webm"/>
""",
'result': {
'main': u'example.mp4',
u'mp4': u'example.mp4',
u'webm': u'example.webm',
},
},
{
'download_video': 'true',
'source': '',
'sources': [],
'result': {},
},
# self.download_video == False
{
'download_video': 'false',
'source': 'example_source.mp4',
'sources': """
<source src="example.mp4"/>
<source src="example.webm"/>
""",
'result': {
u'mp4': u'example.mp4',
u'webm': u'example.webm',
},
},
]
expected_context = {
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': '/static/subs/',
'show_captions': 'true',
'display_name': u'A Name',
'end': 3610.0,
'id': None,
'sources': None,
'start': 3603.0,
'sub': u'a_sub_file.srt.sjson',
'track': None,
'youtube_streams': '1.00:OEoXaMPEzfM',
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
'yt_test_timeout': 1500,
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/',
}
for data in cases:
DATA = SOURCE_XML.format(
download_video=data['download_video'],
source=data['source'],
sources=data['sources']
)
self.initialize_module(data=DATA)
expected_context.update({
'sources': data['result'],
'id': self.item_module.location.html_id(),
})
context = self.item_module.render('student_view').content
self.assertEqual(
context,
self.item_module.xmodule_runtime.render_template('video.html', expected_context)
)
class TestVideoDescriptorInitialization(BaseTestXmodule): class TestVideoDescriptorInitialization(BaseTestXmodule):
""" """
...@@ -332,6 +334,105 @@ class TestVideoDescriptorInitialization(BaseTestXmodule): ...@@ -332,6 +334,105 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
def setUp(self): def setUp(self):
self.setup_course(); self.setup_course();
def test_source_not_in_html5sources(self):
metadata = {
'source': 'http://example.org/video.mp4',
'html5_sources': ['http://youtu.be/OEoXaMPEzfM.mp4'],
}
self.initialize_module(metadata=metadata)
fields = self.item_descriptor.editable_metadata_fields
self.assertIn('source', fields)
self.assertEqual(self.item_module.source, 'http://example.org/video.mp4')
self.assertTrue(self.item_module.download_video)
self.assertTrue(self.item_module.source_visible)
def test_source_in_html5sources(self):
metadata = {
'source': 'http://example.org/video.mp4',
'html5_sources': ['http://example.org/video.mp4'],
}
self.initialize_module(metadata=metadata)
fields = self.item_descriptor.editable_metadata_fields
self.assertNotIn('source', fields)
self.assertTrue(self.item_module.download_video)
self.assertFalse(self.item_module.source_visible)
@patch('xmodule.x_module.XModuleDescriptor.editable_metadata_fields', new_callable=PropertyMock)
def test_download_video_is_explicitly_set(self, mock_editable_fields):
mock_editable_fields.return_value = {
'download_video': {
'default_value': False,
'explicitly_set': True,
'display_name': 'Video Download Allowed',
'help': 'Show a link beneath the video to allow students to download the video.',
'type': 'Boolean',
'value': False,
'field_name': 'download_video',
'options': [
{'display_name': "True", "value": True},
{'display_name': "False", "value": False}
],
},
'html5_sources': {
'default_value': [],
'explicitly_set': False,
'display_name': 'Video Sources',
'help': 'A list of filenames to be used with HTML5 video.',
'type': 'List',
'value': [u'http://youtu.be/OEoXaMPEzfM.mp4'],
'field_name': 'html5_sources',
'options': [],
},
'source': {
'default_value': '',
'explicitly_set': False,
'display_name': 'Download Video',
'help': 'The external URL to download the video.',
'type': 'Generic',
'value': u'http://example.org/video.mp4',
'field_name': 'source',
'options': [],
},
'track': {
'default_value': '',
'explicitly_set': False,
'display_name': 'Download Transcript',
'help': 'The external URL to download the timed transcript track.',
'type': 'Generic',
'value': u'',
'field_name': 'track',
'options': [],
},
}
metadata = {
'track': '',
'source': 'http://example.org/video.mp4',
'html5_sources': ['http://youtu.be/OEoXaMPEzfM.mp4'],
}
self.initialize_module(metadata=metadata)
fields = self.item_descriptor.editable_metadata_fields
self.assertIn('source', fields)
self.assertFalse(self.item_module.download_video)
self.assertTrue(self.item_module.source_visible)
def test_source_is_empty(self):
metadata = {
'source': '',
'html5_sources': ['http://youtu.be/OEoXaMPEzfM.mp4'],
}
self.initialize_module(metadata=metadata)
fields = self.item_descriptor.editable_metadata_fields
self.assertNotIn('source', fields)
self.assertFalse(self.item_module.download_video)
def test_track_is_not_empty(self): def test_track_is_not_empty(self):
metatdata = { metatdata = {
'track': 'http://example.org/track', 'track': 'http://example.org/track',
...@@ -359,7 +460,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule): ...@@ -359,7 +460,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
'options': [ 'options': [
{'display_name': "True", "value": True}, {'display_name': "True", "value": True},
{'display_name': "False", "value": False} {'display_name': "False", "value": False}
] ],
}, },
'track': { 'track': {
'default_value': '', 'default_value': '',
...@@ -369,10 +470,21 @@ class TestVideoDescriptorInitialization(BaseTestXmodule): ...@@ -369,10 +470,21 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
'type': 'Generic', 'type': 'Generic',
'value': u'http://example.org/track', 'value': u'http://example.org/track',
'field_name': 'track', 'field_name': 'track',
'options': [] 'options': [],
},
'source': {
'default_value': '',
'explicitly_set': False,
'display_name': 'Download Video',
'help': 'The external URL to download the video.',
'type': 'Generic',
'value': u'',
'field_name': 'source',
'options': [],
}, },
} }
metadata = { metadata = {
'source': '',
'track': 'http://example.org/track', 'track': 'http://example.org/track',
} }
...@@ -399,6 +511,96 @@ class TestVideoDescriptorInitialization(BaseTestXmodule): ...@@ -399,6 +511,96 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
self.assertFalse(self.item_module.track_visible) self.assertFalse(self.item_module.track_visible)
class TestVideoGetTranscriptsMethod(TestVideo):
"""
Make sure that `get_transcript` method works correctly
"""
DATA = """
<video show_captions="true"
display_name="A Name"
>
<source src="example.mp4"/>
<source src="example.webm"/>
</video>
"""
MODEL_DATA = {
'data': DATA
}
METADATA = {}
def test_good_transcript(self):
self.item_module.render('student_view')
item = self.item_descriptor.xmodule_runtime.xmodule_instance
good_sjson = _create_file(content="""
{
"start": [
270,
2720
],
"end": [
2720,
5430
],
"text": [
"Hi, welcome to Edx.",
"Let&#39;s start with what is on your screen right now."
]
}
""")
_upload_file(good_sjson, self.item_module.location)
subs_id = _get_subs_id(good_sjson.name)
text = item.get_transcript(subs_id)
expected_text = "Hi, welcome to Edx.\nLet's start with what is on your screen right now."
self.assertEqual(text, expected_text)
def test_not_found_error(self):
self.item_module.render('student_view')
item = self.item_descriptor.xmodule_runtime.xmodule_instance
with self.assertRaises(NotFoundError):
item.get_transcript('wrong')
def test_value_error(self):
self.item_module.render('student_view')
item = self.item_descriptor.xmodule_runtime.xmodule_instance
good_sjson = _create_file(content='bad content')
_upload_file(good_sjson, self.item_module.location)
subs_id = _get_subs_id(good_sjson.name)
with self.assertRaises(ValueError):
item.get_transcript(subs_id)
def test_key_error(self):
self.item_module.render('student_view')
item = self.item_descriptor.xmodule_runtime.xmodule_instance
good_sjson = _create_file(content="""
{
"start": [
270,
2720
],
"end": [
2720,
5430
]
}
""")
_upload_file(good_sjson, self.item_module.location)
subs_id = _get_subs_id(good_sjson.name)
with self.assertRaises(KeyError):
item.get_transcript(subs_id)
def _clear_assets(location): def _clear_assets(location):
store = contentstore() store = contentstore()
......
...@@ -31,6 +31,7 @@ SOURCE_XML = """ ...@@ -31,6 +31,7 @@ SOURCE_XML = """
display_name="A Name" display_name="A Name"
youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg" youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg"
sub="a_sub_file.srt.sjson" sub="a_sub_file.srt.sjson"
download_video="true"
start_time="01:00:03" end_time="01:00:10" start_time="01:00:03" end_time="01:00:10"
> >
<source src="example.mp4"/> <source src="example.mp4"/>
......
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