Commit b33b5c7b by Vasyl Nakvasiuk Committed by Anton Stupak

Python: videoalpha -> video.

parent dad9f26a
...@@ -107,8 +107,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -107,8 +107,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
expected_types is the list of elements that should appear on the page. expected_types is the list of elements that should appear on the page.
expected_types and component_types should be similar, but not expected_types and component_types should be similar, but not
exactly the same -- for example, 'videoalpha' in exactly the same -- for example, 'video' in
component_types should cause 'Video Alpha' to be present. component_types should cause 'Video' to be present.
""" """
store = modulestore('direct') store = modulestore('direct')
import_from_xml(store, 'common/test/data/', ['simple']) import_from_xml(store, 'common/test/data/', ['simple'])
...@@ -143,7 +143,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -143,7 +143,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
'Peer Grading Interface']) 'Peer Grading Interface'])
def test_advanced_components_require_two_clicks(self): def test_advanced_components_require_two_clicks(self):
self.check_components_on_page(['videoalpha'], ['Video Alpha']) self.check_components_on_page(['video'], ['Video'])
def test_malformed_edit_unit_request(self): def test_malformed_edit_unit_request(self):
store = modulestore('direct') store = modulestore('direct')
...@@ -1624,7 +1624,7 @@ class MetadataSaveTestCase(ModuleStoreTestCase): ...@@ -1624,7 +1624,7 @@ class MetadataSaveTestCase(ModuleStoreTestCase):
constructor are correctly persisted. constructor are correctly persisted.
""" """
# We should start with a source field, from the XML's <source/> tag # We should start with a source field, from the XML's <source/> tag
self.assertIn('source', own_metadata(self.descriptor)) self.assertIn('html5_sources', own_metadata(self.descriptor))
attrs_to_strip = { attrs_to_strip = {
'show_captions', 'show_captions',
'youtube_id_1_0', 'youtube_id_1_0',
...@@ -1634,6 +1634,7 @@ class MetadataSaveTestCase(ModuleStoreTestCase): ...@@ -1634,6 +1634,7 @@ class MetadataSaveTestCase(ModuleStoreTestCase):
'start_time', 'start_time',
'end_time', 'end_time',
'source', 'source',
'html5_sources',
'track' 'track'
} }
# We strip out all metadata fields to reproduce a bug where # We strip out all metadata fields to reproduce a bug where
...@@ -1646,11 +1647,11 @@ class MetadataSaveTestCase(ModuleStoreTestCase): ...@@ -1646,11 +1647,11 @@ class MetadataSaveTestCase(ModuleStoreTestCase):
field.delete_from(self.descriptor) field.delete_from(self.descriptor)
# Assert that we correctly stripped the field # Assert that we correctly stripped the field
self.assertNotIn('source', own_metadata(self.descriptor)) self.assertNotIn('html5_sources', own_metadata(self.descriptor))
get_modulestore(self.descriptor.location).update_metadata( get_modulestore(self.descriptor.location).update_metadata(
self.descriptor.location, self.descriptor.location,
own_metadata(self.descriptor) own_metadata(self.descriptor)
) )
module = get_modulestore(self.descriptor.location).get_item(self.descriptor.location) module = get_modulestore(self.descriptor.location).get_item(self.descriptor.location)
# Assert that get_item correctly sets the metadata # Assert that get_item correctly sets the metadata
self.assertIn('source', own_metadata(module)) self.assertIn('html5_sources', own_metadata(module))
...@@ -49,7 +49,6 @@ NOTE_COMPONENT_TYPES = ['notes'] ...@@ -49,7 +49,6 @@ NOTE_COMPONENT_TYPES = ['notes']
ADVANCED_COMPONENT_TYPES = [ ADVANCED_COMPONENT_TYPES = [
'annotatable', 'annotatable',
'word_cloud', 'word_cloud',
'videoalpha',
'graphical_slider_tool' 'graphical_slider_tool'
] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES ] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES
ADVANCED_COMPONENT_CATEGORY = 'advanced' ADVANCED_COMPONENT_CATEGORY = 'advanced'
......
...@@ -40,7 +40,7 @@ setup( ...@@ -40,7 +40,7 @@ setup(
"timelimit = xmodule.timelimit_module:TimeLimitDescriptor", "timelimit = xmodule.timelimit_module:TimeLimitDescriptor",
"vertical = xmodule.vertical_module:VerticalDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor",
"video = xmodule.video_module:VideoDescriptor", "video = xmodule.video_module:VideoDescriptor",
"videoalpha = xmodule.videoalpha_module:VideoAlphaDescriptor", "videoalpha = xmodule.video_module:VideoDescriptor",
"videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor", "videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor",
"videosequence = xmodule.seq_module:SequenceDescriptor", "videosequence = xmodule.seq_module:SequenceDescriptor",
"discussion = xmodule.discussion_module:DiscussionDescriptor", "discussion = xmodule.discussion_module:DiscussionDescriptor",
......
...@@ -33,7 +33,7 @@ class TabsEditingDescriptorTestCase(unittest.TestCase): ...@@ -33,7 +33,7 @@ class TabsEditingDescriptorTestCase(unittest.TestCase):
}, },
{ {
'name': "Subtitles", 'name': "Subtitles",
'template': "videoalpha/subtitles.html", 'template': "video/subtitles.html",
}, },
{ {
'name': "Settings", 'name': "Settings",
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#pylint: disable=W0212 #pylint: disable=W0212
"""Test for Video Alpha Xmodule functional logic. """Test for Video Xmodule functional logic.
These test data read from xml, not from mongo. These test data read from xml, not from mongo.
We have a ModuleStoreTestCase class defined in We have a ModuleStoreTestCase class defined in
...@@ -17,37 +17,36 @@ import unittest ...@@ -17,37 +17,36 @@ import unittest
from . import LogicTest from . import LogicTest
from .import get_test_system from .import get_test_system
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.videoalpha_module import VideoAlphaDescriptor, _create_youtube_string from xmodule.video_module import VideoDescriptor, _create_youtube_string
from xmodule.video_module import VideoDescriptor
from .test_import import DummySystem from .test_import import DummySystem
from textwrap import dedent from textwrap import dedent
class VideoAlphaModuleTest(LogicTest): class VideoModuleTest(LogicTest):
"""Logic tests for VideoAlpha Xmodule.""" """Logic tests for Video Xmodule."""
descriptor_class = VideoAlphaDescriptor descriptor_class = VideoDescriptor
raw_model_data = { raw_model_data = {
'data': '<videoalpha />' 'data': '<video />'
} }
def test_parse_time_empty(self): def test_parse_time_empty(self):
"""Ensure parse_time returns correctly with None or empty string.""" """Ensure parse_time returns correctly with None or empty string."""
expected = '' expected = ''
self.assertEqual(VideoAlphaDescriptor._parse_time(None), expected) self.assertEqual(VideoDescriptor._parse_time(None), expected)
self.assertEqual(VideoAlphaDescriptor._parse_time(''), expected) self.assertEqual(VideoDescriptor._parse_time(''), expected)
def test_parse_time(self): def test_parse_time(self):
"""Ensure that times are parsed correctly into seconds.""" """Ensure that times are parsed correctly into seconds."""
expected = 247 expected = 247
output = VideoAlphaDescriptor._parse_time('00:04:07') output = VideoDescriptor._parse_time('00:04:07')
self.assertEqual(output, expected) self.assertEqual(output, expected)
def test_parse_youtube(self): def test_parse_youtube(self):
"""Test parsing old-style Youtube ID strings into a dict.""" """Test parsing old-style Youtube ID strings into a dict."""
youtube_str = '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg' youtube_str = '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg'
output = VideoAlphaDescriptor._parse_youtube(youtube_str) output = VideoDescriptor._parse_youtube(youtube_str)
self.assertEqual(output, {'0.75': 'jNCf2gIqpeE', self.assertEqual(output, {'0.75': 'jNCf2gIqpeE',
'1.00': 'ZwkTiUPN0mg', '1.00': 'ZwkTiUPN0mg',
'1.25': 'rsq9auxASqI', '1.25': 'rsq9auxASqI',
...@@ -59,7 +58,7 @@ class VideoAlphaModuleTest(LogicTest): ...@@ -59,7 +58,7 @@ class VideoAlphaModuleTest(LogicTest):
empty string. empty string.
""" """
youtube_str = '0.75:jNCf2gIqpeE' youtube_str = '0.75:jNCf2gIqpeE'
output = VideoAlphaDescriptor._parse_youtube(youtube_str) output = VideoDescriptor._parse_youtube(youtube_str)
self.assertEqual(output, {'0.75': 'jNCf2gIqpeE', self.assertEqual(output, {'0.75': 'jNCf2gIqpeE',
'1.00': '', '1.00': '',
'1.25': '', '1.25': '',
...@@ -72,8 +71,8 @@ class VideoAlphaModuleTest(LogicTest): ...@@ -72,8 +71,8 @@ class VideoAlphaModuleTest(LogicTest):
youtube_str = '1.00:p2Q6BrNhdh8' youtube_str = '1.00:p2Q6BrNhdh8'
youtube_str_hack = '1.0:p2Q6BrNhdh8' youtube_str_hack = '1.0:p2Q6BrNhdh8'
self.assertEqual( self.assertEqual(
VideoAlphaDescriptor._parse_youtube(youtube_str), VideoDescriptor._parse_youtube(youtube_str),
VideoAlphaDescriptor._parse_youtube(youtube_str_hack) VideoDescriptor._parse_youtube(youtube_str_hack)
) )
def test_parse_youtube_empty(self): def test_parse_youtube_empty(self):
...@@ -82,7 +81,7 @@ class VideoAlphaModuleTest(LogicTest): ...@@ -82,7 +81,7 @@ class VideoAlphaModuleTest(LogicTest):
that well. that well.
""" """
self.assertEqual( self.assertEqual(
VideoAlphaDescriptor._parse_youtube(''), VideoDescriptor._parse_youtube(''),
{'0.75': '', {'0.75': '',
'1.00': '', '1.00': '',
'1.25': '', '1.25': '',
...@@ -90,12 +89,12 @@ class VideoAlphaModuleTest(LogicTest): ...@@ -90,12 +89,12 @@ class VideoAlphaModuleTest(LogicTest):
) )
class VideoAlphaDescriptorTest(unittest.TestCase): class VideoDescriptorTest(unittest.TestCase):
"""Test for VideoAlphaDescriptor""" """Test for VideoDescriptor"""
def setUp(self): def setUp(self):
system = get_test_system() system = get_test_system()
self.descriptor = VideoAlphaDescriptor( self.descriptor = VideoDescriptor(
runtime=system, runtime=system,
model_data={}) model_data={})
...@@ -117,9 +116,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase): ...@@ -117,9 +116,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase):
back out to XML. back out to XML.
""" """
system = DummySystem(load_error_modules=True) system = DummySystem(load_error_modules=True)
location = Location(["i4x", "edX", "videoalpha", "default", "SampleProblem1"]) location = Location(["i4x", "edX", "video", "default", "SampleProblem1"])
model_data = {'location': location} model_data = {'location': location}
descriptor = VideoAlphaDescriptor(system, model_data) descriptor = VideoDescriptor(system, model_data)
descriptor.youtube_id_0_75 = 'izygArpw-Qo' descriptor.youtube_id_0_75 = 'izygArpw-Qo'
descriptor.youtube_id_1_0 = 'p2Q6BrNhdh8' descriptor.youtube_id_1_0 = 'p2Q6BrNhdh8'
descriptor.youtube_id_1_25 = '1EeWXzPdhSA' descriptor.youtube_id_1_25 = '1EeWXzPdhSA'
...@@ -133,9 +132,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase): ...@@ -133,9 +132,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase):
in the output string. in the output string.
""" """
system = DummySystem(load_error_modules=True) system = DummySystem(load_error_modules=True)
location = Location(["i4x", "edX", "videoalpha", "default", "SampleProblem1"]) location = Location(["i4x", "edX", "video", "default", "SampleProblem1"])
model_data = {'location': location} model_data = {'location': location}
descriptor = VideoAlphaDescriptor(system, model_data) descriptor = VideoDescriptor(system, model_data)
descriptor.youtube_id_0_75 = 'izygArpw-Qo' descriptor.youtube_id_0_75 = 'izygArpw-Qo'
descriptor.youtube_id_1_0 = 'p2Q6BrNhdh8' descriptor.youtube_id_1_0 = 'p2Q6BrNhdh8'
descriptor.youtube_id_1_25 = '1EeWXzPdhSA' descriptor.youtube_id_1_25 = '1EeWXzPdhSA'
...@@ -143,9 +142,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase): ...@@ -143,9 +142,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase):
self.assertEqual(_create_youtube_string(descriptor), expected) self.assertEqual(_create_youtube_string(descriptor), expected)
class VideoAlphaDescriptorImportTestCase(unittest.TestCase): class VideoDescriptorImportTestCase(unittest.TestCase):
""" """
Make sure that VideoAlphaDescriptor can import an old XML-based video correctly. Make sure that VideoDescriptor can import an old XML-based video correctly.
""" """
def assert_attributes_equal(self, video, attrs): def assert_attributes_equal(self, video, attrs):
...@@ -158,7 +157,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -158,7 +157,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
def test_constructor(self): def test_constructor(self):
sample_xml = ''' sample_xml = '''
<videoalpha 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"
start_time="00:00:01" start_time="00:00:01"
...@@ -166,14 +165,14 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -166,14 +165,14 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
<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"/>
</videoalpha> </video>
''' '''
location = Location(["i4x", "edX", "videoalpha", "default", location = Location(["i4x", "edX", "video", "default",
"SampleProblem1"]) "SampleProblem1"])
model_data = {'data': sample_xml, model_data = {'data': sample_xml,
'location': location} 'location': location}
system = DummySystem(load_error_modules=True) system = DummySystem(load_error_modules=True)
descriptor = VideoAlphaDescriptor(system, model_data) descriptor = VideoDescriptor(system, model_data)
self.assert_attributes_equal(descriptor, { self.assert_attributes_equal(descriptor, {
'youtube_id_0_75': 'izygArpw-Qo', 'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8', 'youtube_id_1_0': 'p2Q6BrNhdh8',
...@@ -190,16 +189,16 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -190,16 +189,16 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
def test_from_xml(self): def test_from_xml(self):
module_system = DummySystem(load_error_modules=True) module_system = DummySystem(load_error_modules=True)
xml_data = ''' xml_data = '''
<videoalpha 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"
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"/>
<track src="http://www.example.com/track"/> <track src="http://www.example.com/track"/>
</videoalpha> </video>
''' '''
output = VideoAlphaDescriptor.from_xml(xml_data, module_system) output = VideoDescriptor.from_xml(xml_data, module_system)
self.assert_attributes_equal(output, { self.assert_attributes_equal(output, {
'youtube_id_0_75': 'izygArpw-Qo', 'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8', 'youtube_id_1_0': 'p2Q6BrNhdh8',
...@@ -221,14 +220,14 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -221,14 +220,14 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
""" """
module_system = DummySystem(load_error_modules=True) module_system = DummySystem(load_error_modules=True)
xml_data = ''' xml_data = '''
<videoalpha display_name="Test Video" <video display_name="Test Video"
youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA" youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA"
show_captions="true"> show_captions="true">
<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"/>
</videoalpha> </video>
''' '''
output = VideoAlphaDescriptor.from_xml(xml_data, module_system) output = VideoDescriptor.from_xml(xml_data, module_system)
self.assert_attributes_equal(output, { self.assert_attributes_equal(output, {
'youtube_id_0_75': '', 'youtube_id_0_75': '',
'youtube_id_1_0': 'p2Q6BrNhdh8', 'youtube_id_1_0': 'p2Q6BrNhdh8',
...@@ -248,8 +247,8 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -248,8 +247,8 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
Make sure settings are correct if none are explicitly set in XML. Make sure settings are correct if none are explicitly set in XML.
""" """
module_system = DummySystem(load_error_modules=True) module_system = DummySystem(load_error_modules=True)
xml_data = '<videoalpha></videoalpha>' xml_data = '<video></video>'
output = VideoAlphaDescriptor.from_xml(xml_data, module_system) output = VideoDescriptor.from_xml(xml_data, module_system)
self.assert_attributes_equal(output, { self.assert_attributes_equal(output, {
'youtube_id_0_75': '', 'youtube_id_0_75': '',
'youtube_id_1_0': 'OEoXaMPEzfM', 'youtube_id_1_0': 'OEoXaMPEzfM',
...@@ -270,16 +269,16 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -270,16 +269,16 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
""" """
module_system = DummySystem(load_error_modules=True) module_system = DummySystem(load_error_modules=True)
xml_data = """ xml_data = """
<videoalpha 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"
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"/>
<track src="http://www.example.com/track"/> <track src="http://www.example.com/track"/>
</videoalpha> </video>
""" """
output = VideoAlphaDescriptor.from_xml(xml_data, module_system) output = VideoDescriptor.from_xml(xml_data, module_system)
self.assert_attributes_equal(output, { self.assert_attributes_equal(output, {
'youtube_id_0_75': 'izygArpw-Qo', 'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8', 'youtube_id_1_0': 'p2Q6BrNhdh8',
...@@ -295,7 +294,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -295,7 +294,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
def test_old_video_data(self): def test_old_video_data(self):
""" """
Ensure that Video Alpha is able to read VideoModule's model data. Ensure that Video is able to read VideoModule's model data.
""" """
module_system = DummySystem(load_error_modules=True) module_system = DummySystem(load_error_modules=True)
xml_data = """ xml_data = """
...@@ -309,8 +308,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -309,8 +308,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
</video> </video>
""" """
video = VideoDescriptor.from_xml(xml_data, module_system) video = VideoDescriptor.from_xml(xml_data, module_system)
video_alpha = VideoAlphaDescriptor(module_system, video._model_data) self.assert_attributes_equal(video, {
self.assert_attributes_equal(video_alpha, {
'youtube_id_0_75': 'izygArpw-Qo', 'youtube_id_0_75': 'izygArpw-Qo',
'youtube_id_1_0': 'p2Q6BrNhdh8', 'youtube_id_1_0': 'p2Q6BrNhdh8',
'youtube_id_1_25': '1EeWXzPdhSA', 'youtube_id_1_25': '1EeWXzPdhSA',
...@@ -324,17 +322,17 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): ...@@ -324,17 +322,17 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase):
}) })
class VideoAlphaExportTestCase(unittest.TestCase): class VideoExportTestCase(unittest.TestCase):
""" """
Make sure that VideoAlphaDescriptor can export itself to XML Make sure that VideoDescriptor can export itself to XML
correctly. correctly.
""" """
def test_export_to_xml(self): def test_export_to_xml(self):
"""Test that we write the correct XML on export.""" """Test that we write the correct XML on export."""
module_system = DummySystem(load_error_modules=True) module_system = DummySystem(load_error_modules=True)
location = Location(["i4x", "edX", "videoalpha", "default", "SampleProblem1"]) location = Location(["i4x", "edX", "video", "default", "SampleProblem1"])
desc = VideoAlphaDescriptor(module_system, {'location': location}) desc = VideoDescriptor(module_system, {'location': location})
desc.youtube_id_0_75 = 'izygArpw-Qo' desc.youtube_id_0_75 = 'izygArpw-Qo'
desc.youtube_id_1_0 = 'p2Q6BrNhdh8' desc.youtube_id_1_0 = 'p2Q6BrNhdh8'
...@@ -348,11 +346,11 @@ class VideoAlphaExportTestCase(unittest.TestCase): ...@@ -348,11 +346,11 @@ class VideoAlphaExportTestCase(unittest.TestCase):
xml = desc.export_to_xml(None) # We don't use the `resource_fs` parameter xml = desc.export_to_xml(None) # We don't use the `resource_fs` parameter
expected = dedent('''\ expected = dedent('''\
<videoalpha display_name="Video Alpha" 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"> <video display_name="Video" 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">
<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"/>
</videoalpha> </video>
''') ''')
self.assertEquals(expected, xml) self.assertEquals(expected, xml)
...@@ -360,10 +358,10 @@ class VideoAlphaExportTestCase(unittest.TestCase): ...@@ -360,10 +358,10 @@ class VideoAlphaExportTestCase(unittest.TestCase):
def test_export_to_xml_empty_parameters(self): def test_export_to_xml_empty_parameters(self):
"""Test XML export with defaults.""" """Test XML export with defaults."""
module_system = DummySystem(load_error_modules=True) module_system = DummySystem(load_error_modules=True)
location = Location(["i4x", "edX", "videoalpha", "default", "SampleProblem1"]) location = Location(["i4x", "edX", "video", "default", "SampleProblem1"])
desc = VideoAlphaDescriptor(module_system, {'location': location}) desc = VideoDescriptor(module_system, {'location': location})
xml = desc.export_to_xml(None) xml = desc.export_to_xml(None)
expected = '<videoalpha display_name="Video Alpha" youtube="1.00:OEoXaMPEzfM" show_captions="true"/>\n' expected = '<video display_name="Video" youtube="1.00:OEoXaMPEzfM" show_captions="true"/>\n'
self.assertEquals(expected, xml) self.assertEquals(expected, xml)
...@@ -16,10 +16,9 @@ from xmodule.gst_module import GraphicalSliderToolDescriptor ...@@ -16,10 +16,9 @@ from xmodule.gst_module import GraphicalSliderToolDescriptor
from xmodule.html_module import HtmlDescriptor from xmodule.html_module import HtmlDescriptor
from xmodule.peer_grading_module import PeerGradingDescriptor from xmodule.peer_grading_module import PeerGradingDescriptor
from xmodule.poll_module import PollDescriptor from xmodule.poll_module import PollDescriptor
from xmodule.video_module import VideoDescriptor
from xmodule.word_cloud_module import WordCloudDescriptor from xmodule.word_cloud_module import WordCloudDescriptor
from xmodule.crowdsource_hinter import CrowdsourceHinterDescriptor from xmodule.crowdsource_hinter import CrowdsourceHinterDescriptor
from xmodule.videoalpha_module import VideoAlphaDescriptor from xmodule.video_module import VideoDescriptor
from xmodule.seq_module import SequenceDescriptor from xmodule.seq_module import SequenceDescriptor
from xmodule.conditional_module import ConditionalDescriptor from xmodule.conditional_module import ConditionalDescriptor
from xmodule.randomize_module import RandomizeDescriptor from xmodule.randomize_module import RandomizeDescriptor
...@@ -35,9 +34,8 @@ LEAF_XMODULES = ( ...@@ -35,9 +34,8 @@ LEAF_XMODULES = (
HtmlDescriptor, HtmlDescriptor,
PeerGradingDescriptor, PeerGradingDescriptor,
PollDescriptor, PollDescriptor,
VideoDescriptor,
# This is being excluded because it has dependencies on django # This is being excluded because it has dependencies on django
#VideoAlphaDescriptor, #VideoDescriptor,
WordCloudDescriptor, WordCloudDescriptor,
) )
......
# pylint: disable=W0223 # pylint: disable=W0223
"""Video is ungraded Xmodule for support video content.""" """Video is ungraded Xmodule for support video content.
It's new improved video module, which support additional feature:
- Can play non-YouTube video sources via in-browser HTML5 video player.
- YouTube defaults to HTML5 mode from the start.
- Speed changes in both YouTube and non-YouTube videos happen via
in-browser HTML5 video method (when in HTML5 mode).
- Navigational subtitles can be disabled altogether via an attribute
in XML.
"""
import json import json
import logging import logging
from lxml import etree from lxml import etree
from pkg_resources import resource_string, resource_listdir from pkg_resources import resource_string
import datetime
import time
from django.http import Http404 from django.http import Http404
from django.conf import settings
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.editing_module import TabsEditingDescriptor
from xmodule.raw_module import EmptyDataRawDescriptor from xmodule.raw_module import EmptyDataRawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor from xmodule.modulestore.mongo import MongoModuleStore
from xblock.core import Integer, Scope, String, Float, Boolean from xmodule.modulestore.django import modulestore
from xmodule.contentstore.content import StaticContent
from xblock.core import Scope, String, Boolean, Float, List, Integer
import datetime
import time
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -22,51 +36,118 @@ log = logging.getLogger(__name__) ...@@ -22,51 +36,118 @@ log = logging.getLogger(__name__)
class VideoFields(object): class VideoFields(object):
"""Fields for `VideoModule` and `VideoDescriptor`.""" """Fields for `VideoModule` and `VideoDescriptor`."""
display_name = String( display_name = String(
display_name="Display Name", display_name="Display Name", help="Display name for this module.",
help="This name appears in the horizontal navigation at the top of the page.", default="Video",
scope=Scope.settings
)
position = Integer(
help="Current position in the video",
scope=Scope.user_state,
default=0
)
show_captions = Boolean(
help="This controls whether or not captions are shown by default.",
display_name="Show Captions",
scope=Scope.settings, scope=Scope.settings,
# it'd be nice to have a useful default but it screws up other things; so, default=True
# use display_name_with_default for those
default="Video"
) )
data = String( # TODO: This should be moved to Scope.content, but this will
help="XML data for the problem", # require data migration to support the old video module.
default='', youtube_id_1_0 = String(
scope=Scope.content help="This is the Youtube ID reference for the normal speed video.",
display_name="Youtube ID",
scope=Scope.settings,
default="OEoXaMPEzfM"
)
youtube_id_0_75 = String(
help="The Youtube ID for the .75x speed video.",
display_name="Youtube ID for .75x speed",
scope=Scope.settings,
default=""
)
youtube_id_1_25 = String(
help="The Youtube ID for the 1.25x speed video.",
display_name="Youtube ID for 1.25x speed",
scope=Scope.settings,
default=""
)
youtube_id_1_5 = String(
help="The Youtube ID for the 1.5x speed video.",
display_name="Youtube ID for 1.5x speed",
scope=Scope.settings,
default=""
)
start_time = Float(
help="Start time for the video.",
display_name="Start Time",
scope=Scope.settings,
default=0.0
)
end_time = Float(
help="End time for the video.",
display_name="End Time",
scope=Scope.settings,
default=0.0
)
source = String(
help="The external URL to download the video. This appears as a link beneath the video.",
display_name="Download Video",
scope=Scope.settings,
default=""
)
html5_sources = List(
help="A list of filenames to be used with HTML5 video. The first supported filetype will be displayed.",
display_name="Video Sources",
scope=Scope.settings,
default=[]
)
track = String(
help="The external URL to download the subtitle track. This appears as a link beneath the video.",
display_name="Download Track",
scope=Scope.settings,
default=""
)
sub = String(
help="The name of the subtitle track (for non-Youtube videos).",
display_name="HTML5 Subtitles",
scope=Scope.settings,
default=""
) )
position = Integer(help="Current position in the video", scope=Scope.user_state, default=0)
show_captions = Boolean(help="This controls whether or not captions are shown by default.", display_name="Show Captions", scope=Scope.settings, default=True)
youtube_id_1_0 = String(help="This is the Youtube ID reference for the normal speed video.", display_name="Default Speed", scope=Scope.settings, default="OEoXaMPEzfM")
youtube_id_0_75 = String(help="The Youtube ID for the .75x speed video.", display_name="Speed: .75x", scope=Scope.settings, default="")
youtube_id_1_25 = String(help="The Youtube ID for the 1.25x speed video.", display_name="Speed: 1.25x", scope=Scope.settings, default="")
youtube_id_1_5 = String(help="The Youtube ID for the 1.5x speed video.", display_name="Speed: 1.5x", scope=Scope.settings, default="")
start_time = Float(help="Time the video starts", display_name="Start Time", scope=Scope.settings, default=0.0)
end_time = Float(help="Time the video ends", display_name="End Time", scope=Scope.settings, default=0.0)
source = String(help="The external URL to download the video. This appears as a link beneath the video.", display_name="Download Video", scope=Scope.settings, default="")
track = String(help="The external URL to download the subtitle track. This appears as a link beneath the video.", display_name="Download Track", scope=Scope.settings, default="")
class VideoModule(VideoFields, XModule): class VideoModule(VideoFields, XModule):
"""Video Xmodule.""" """
XML source example:
<video show_captions="true"
youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg"
url_name="lecture_21_3" display_name="S19V3: Vacancies"
>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.mp4"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.webm"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.ogv"/>
</video>
"""
video_time = 0 video_time = 0
icon_class = 'video' icon_class = 'video'
js = { js = {
'coffee': [ 'js': [
resource_string(__name__, 'js/src/time.coffee'), resource_string(__name__, 'js/src/video/01_initialize.js'),
resource_string(__name__, 'js/src/video/display.coffee') resource_string(__name__, 'js/src/video/02_html5_video.js'),
] + resource_string(__name__, 'js/src/video/03_video_player.js'),
[resource_string(__name__, 'js/src/video/display/' + filename) resource_string(__name__, 'js/src/video/04_video_control.js'),
for filename resource_string(__name__, 'js/src/video/05_video_quality_control.js'),
in sorted(resource_listdir(__name__, 'js/src/video/display')) resource_string(__name__, 'js/src/video/06_video_progress_slider.js'),
if filename.endswith('.coffee')] resource_string(__name__, 'js/src/video/07_video_volume_control.js'),
resource_string(__name__, 'js/src/video/08_video_speed_control.js'),
resource_string(__name__, 'js/src/video/09_video_caption.js'),
resource_string(__name__, 'js/src/video/10_main.js')
]
} }
css = {'scss': [resource_string(__name__, 'css/video/display.scss')]} css = {'scss': [resource_string(__name__, 'css/video/display.scss')]}
js_module_name = "Video" js_module_name = "Video"
def __init__(self, *args, **kwargs):
XModule.__init__(self, *args, **kwargs)
def handle_ajax(self, dispatch, data): def handle_ajax(self, dispatch, data):
"""This is not being called right now and we raise 404 error.""" """This is not being called right now and we raise 404 error."""
log.debug(u"GET {0}".format(data)) log.debug(u"GET {0}".format(data))
...@@ -78,41 +159,59 @@ class VideoModule(VideoFields, XModule): ...@@ -78,41 +159,59 @@ class VideoModule(VideoFields, XModule):
return json.dumps({'position': self.position}) return json.dumps({'position': self.position})
def get_html(self): def get_html(self):
if isinstance(modulestore(), MongoModuleStore):
caption_asset_path = StaticContent.get_base_url_path_for_course_assets(self.location) + '/subs_'
else:
# VS[compat]
# cdodge: filesystem static content support.
caption_asset_path = "/static/subs/"
get_ext = lambda filename: filename.rpartition('.')[-1]
sources = {get_ext(src): src for src in self.html5_sources}
sources['main'] = self.source
return self.system.render_template('video.html', { return self.system.render_template('video.html', {
'youtube_id_0_75': self.youtube_id_0_75, 'youtube_streams': _create_youtube_string(self),
'youtube_id_1_0': self.youtube_id_1_0,
'youtube_id_1_25': self.youtube_id_1_25,
'youtube_id_1_5': self.youtube_id_1_5,
'id': self.location.html_id(), 'id': self.location.html_id(),
'position': self.position, 'sub': self.sub,
'source': self.source, 'sources': sources,
'track': self.track, 'track': self.track,
'display_name': self.display_name_with_default, 'display_name': self.display_name_with_default,
'caption_asset_path': "/static/subs/", # This won't work when we move to data that
'show_captions': 'true' if self.show_captions else 'false', # isn't on the filesystem
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': caption_asset_path,
'show_captions': json.dumps(self.show_captions),
'start': self.start_time, 'start': self.start_time,
'end': self.end_time 'end': self.end_time,
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True)
}) })
class VideoDescriptor(VideoFields, class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor):
MetadataOnlyEditingDescriptor, """Descriptor for `VideoModule`."""
EmptyDataRawDescriptor):
module_class = VideoModule module_class = VideoModule
tabs = [
# {
# 'name': "Subtitles",
# 'template': "video/subtitles.html",
# },
{
'name': "Settings",
'template': "tabs/metadata-edit-tab.html",
'current': True
}
]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(VideoDescriptor, self).__init__(*args, **kwargs) super(VideoDescriptor, self).__init__(*args, **kwargs)
# If we don't have a `youtube_id_1_0`, this is an XML course # For backwards compatibility -- if we've got XML data, parse
# and we parse out the fields. # it out and set the metadata fields
if self.data and 'youtube_id_1_0' not in self._model_data: if self.data:
_parse_video_xml(self, self.data) model_data = VideoDescriptor._parse_video_xml(self.data)
self._model_data.update(model_data)
@property del self.data
def non_editable_metadata_fields(self):
non_editable_fields = super(MetadataOnlyEditingDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([VideoModule.start_time,
VideoModule.end_time])
return non_editable_fields
@classmethod @classmethod
def from_xml(cls, xml_data, system, org=None, course=None): def from_xml(cls, xml_data, system, org=None, course=None):
...@@ -126,102 +225,143 @@ class VideoDescriptor(VideoFields, ...@@ -126,102 +225,143 @@ class VideoDescriptor(VideoFields,
org and course are optional strings that will be used in the generated modules org and course are optional strings that will be used in the generated modules
url identifiers url identifiers
""" """
# Calling from_xml of XmlDescritor, to get right Location, when importing from XML
video = super(VideoDescriptor, cls).from_xml(xml_data, system, org, course) video = super(VideoDescriptor, cls).from_xml(xml_data, system, org, course)
_parse_video_xml(video, video.data)
return video return video
def export_to_xml(self, resource_fs):
"""
Returns an xml string representing this module.
"""
xml = etree.Element('video')
attrs = {
'display_name': self.display_name,
'show_captions': json.dumps(self.show_captions),
'youtube': _create_youtube_string(self),
'start_time': datetime.timedelta(seconds=self.start_time),
'end_time': datetime.timedelta(seconds=self.end_time),
'sub': self.sub
}
for key, value in attrs.items():
if value:
xml.set(key, str(value))
for source in self.html5_sources:
ele = etree.Element('source')
ele.set('src', source)
xml.append(ele)
if self.track:
ele = etree.Element('track')
ele.set('src', self.track)
xml.append(ele)
return etree.tostring(xml, pretty_print=True)
@staticmethod
def _parse_youtube(data):
"""
Parses a string of Youtube IDs such as "1.0:AXdE34_U,1.5:VO3SxfeD"
into a dictionary. Necessary for backwards compatibility with
XML-based courses.
"""
ret = {'0.75': '', '1.00': '', '1.25': '', '1.50': ''}
if data == '':
return ret
videos = data.split(',')
for video in videos:
pieces = video.split(':')
# HACK
# To elaborate somewhat: in many LMS tests, the keys for
# Youtube IDs are inconsistent. Sometimes a particular
# speed isn't present, and formatting is also inconsistent
# ('1.0' versus '1.00'). So it's necessary to either do
# something like this or update all the tests to work
# properly.
ret['%.2f' % float(pieces[0])] = pieces[1]
return ret
def _parse_video_xml(video, xml_data): @staticmethod
""" def _parse_video_xml(xml_data):
Parse video fields out of xml_data. The fields are set if they are """
present in the XML. Parse video fields out of xml_data. The fields are set if they are
""" present in the XML.
if not xml_data: """
return xml = etree.fromstring(xml_data)
model_data = {}
xml = etree.fromstring(xml_data)
conversions = {
display_name = xml.get('display_name') 'show_captions': json.loads,
if display_name: 'start_time': VideoDescriptor._parse_time,
video.display_name = display_name 'end_time': VideoDescriptor._parse_time
}
youtube = xml.get('youtube')
if youtube: # VideoModule and VideoModule use different names for
speeds = _parse_youtube(youtube) # these attributes -- need to convert between them
if speeds['0.75']: video_compat = {
video.youtube_id_0_75 = speeds['0.75'] 'from': 'start_time',
if speeds['1.00']: 'to': 'end_time'
video.youtube_id_1_0 = speeds['1.00'] }
if speeds['1.25']:
video.youtube_id_1_25 = speeds['1.25'] sources = xml.findall('source')
if speeds['1.50']: if sources:
video.youtube_id_1_5 = speeds['1.50'] model_data['html5_sources'] = [ele.get('src') for ele in sources]
model_data['source'] = model_data['html5_sources'][0]
show_captions = xml.get('show_captions')
if show_captions: track = xml.find('track')
video.show_captions = json.loads(show_captions) if track is not None:
model_data['track'] = track.get('src')
source = _get_first_external(xml, 'source')
if source: for attr, value in xml.items():
video.source = source if attr in video_compat:
attr = video_compat[attr]
track = _get_first_external(xml, 'track') if attr == 'youtube':
if track: speeds = VideoDescriptor._parse_youtube(value)
video.track = track for speed, youtube_id in speeds.items():
# should have made these youtube_id_1_00 for
start_time = _parse_time(xml.get('from')) # cleanliness, but hindsight doesn't need glasses
if start_time: normalized_speed = speed[:-1] if speed.endswith('0') else speed
video.start_time = start_time # If the user has specified html5 sources, make sure we don't use the default video
if youtube_id != '' or 'html5_sources' in model_data:
end_time = _parse_time(xml.get('to')) model_data['youtube_id_{0}'.format(normalized_speed.replace('.', '_'))] = youtube_id
if end_time: else:
video.end_time = end_time # Convert XML attrs into Python values.
if attr in conversions:
value = conversions[attr](value)
def _get_first_external(xmltree, tag): model_data[attr] = value
"""
Returns the src attribute of the nested `tag` in `xmltree`, if it return model_data
exists.
""" @staticmethod
for element in xmltree.findall(tag): def _parse_time(str_time):
src = element.get('src') """Converts s in '12:34:45' format to seconds. If s is
if src: None, returns empty string"""
return src if not str_time:
return None return ''
else:
obj_time = time.strptime(str_time, '%H:%M:%S')
def _parse_youtube(data): return datetime.timedelta(
hours=obj_time.tm_hour,
minutes=obj_time.tm_min,
seconds=obj_time.tm_sec
).total_seconds()
def _create_youtube_string(module):
""" """
Parses a string of Youtube IDs such as "1.0:AXdE34_U,1.5:VO3SxfeD" Create a string of Youtube IDs from `module`'s metadata
into a dictionary. Necessary for backwards compatibility with attributes. Only writes a speed if an ID is present in the
XML-based courses. module. Necessary for backwards compatibility with XML-based
courses.
""" """
ret = {'0.75': '', '1.00': '', '1.25': '', '1.50': ''} youtube_ids = [
if data == '': module.youtube_id_0_75,
return ret module.youtube_id_1_0,
videos = data.split(',') module.youtube_id_1_25,
for video in videos: module.youtube_id_1_5
pieces = video.split(':') ]
# HACK youtube_speeds = ['0.75', '1.00', '1.25', '1.50']
# To elaborate somewhat: in many LMS tests, the keys for return ','.join([':'.join(pair)
# Youtube IDs are inconsistent. Sometimes a particular for pair
# speed isn't present, and formatting is also inconsistent in zip(youtube_speeds, youtube_ids)
# ('1.0' versus '1.00'). So it's necessary to either do if pair[1]])
# something like this or update all the tests to work
# properly.
ret['%.2f' % float(pieces[0])] = pieces[1]
return ret
def _parse_time(str_time):
"""Converts s in '12:34:45' format to seconds. If s is
None, returns empty string"""
if str_time is None or str_time == '':
return ''
else:
obj_time = time.strptime(str_time, '%H:%M:%S')
return datetime.timedelta(
hours=obj_time.tm_hour,
minutes=obj_time.tm_min,
seconds=obj_time.tm_sec
).total_seconds()
# pylint: disable=W0223
"""VideoAlpha is ungraded Xmodule for support video content.
It's new improved video module, which support additional feature:
- Can play non-YouTube video sources via in-browser HTML5 video player.
- YouTube defaults to HTML5 mode from the start.
- Speed changes in both YouTube and non-YouTube videos happen via
in-browser HTML5 video method (when in HTML5 mode).
- Navigational subtitles can be disabled altogether via an attribute
in XML.
"""
import json
import logging
from lxml import etree
from pkg_resources import resource_string
from django.http import Http404
from django.conf import settings
from xmodule.x_module import XModule
from xmodule.editing_module import TabsEditingDescriptor
from xmodule.raw_module import EmptyDataRawDescriptor
from xmodule.modulestore.mongo import MongoModuleStore
from xmodule.modulestore.django import modulestore
from xmodule.contentstore.content import StaticContent
from xblock.core import Scope, String, Boolean, Float, List, Integer
import datetime
import time
log = logging.getLogger(__name__)
class VideoAlphaFields(object):
"""Fields for `VideoAlphaModule` and `VideoAlphaDescriptor`."""
display_name = String(
display_name="Display Name", help="Display name for this module.",
default="Video Alpha",
scope=Scope.settings
)
position = Integer(
help="Current position in the video",
scope=Scope.user_state,
default=0
)
show_captions = Boolean(
help="This controls whether or not captions are shown by default.",
display_name="Show Captions",
scope=Scope.settings,
default=True
)
# TODO: This should be moved to Scope.content, but this will
# require data migration to support the old video module.
youtube_id_1_0 = String(
help="This is the Youtube ID reference for the normal speed video.",
display_name="Youtube ID",
scope=Scope.settings,
default="OEoXaMPEzfM"
)
youtube_id_0_75 = String(
help="The Youtube ID for the .75x speed video.",
display_name="Youtube ID for .75x speed",
scope=Scope.settings,
default=""
)
youtube_id_1_25 = String(
help="The Youtube ID for the 1.25x speed video.",
display_name="Youtube ID for 1.25x speed",
scope=Scope.settings,
default=""
)
youtube_id_1_5 = String(
help="The Youtube ID for the 1.5x speed video.",
display_name="Youtube ID for 1.5x speed",
scope=Scope.settings,
default=""
)
start_time = Float(
help="Start time for the video.",
display_name="Start Time",
scope=Scope.settings,
default=0.0
)
end_time = Float(
help="End time for the video.",
display_name="End Time",
scope=Scope.settings,
default=0.0
)
source = String(
help="The external URL to download the video. This appears as a link beneath the video.",
display_name="Download Video",
scope=Scope.settings,
default=""
)
html5_sources = List(
help="A list of filenames to be used with HTML5 video. The first supported filetype will be displayed.",
display_name="Video Sources",
scope=Scope.settings,
default=[]
)
track = String(
help="The external URL to download the subtitle track. This appears as a link beneath the video.",
display_name="Download Track",
scope=Scope.settings,
default=""
)
sub = String(
help="The name of the subtitle track (for non-Youtube videos).",
display_name="HTML5 Subtitles",
scope=Scope.settings,
default=""
)
class VideoAlphaModule(VideoAlphaFields, XModule):
"""
XML source example:
<videoalpha show_captions="true"
youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg"
url_name="lecture_21_3" display_name="S19V3: Vacancies"
>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.mp4"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.webm"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.ogv"/>
</videoalpha>
"""
video_time = 0
icon_class = 'video'
js = {
'js': [
resource_string(__name__, 'js/src/videoalpha/01_initialize.js'),
resource_string(__name__, 'js/src/videoalpha/02_html5_video.js'),
resource_string(__name__, 'js/src/videoalpha/03_video_player.js'),
resource_string(__name__, 'js/src/videoalpha/04_video_control.js'),
resource_string(__name__, 'js/src/videoalpha/05_video_quality_control.js'),
resource_string(__name__, 'js/src/videoalpha/06_video_progress_slider.js'),
resource_string(__name__, 'js/src/videoalpha/07_video_volume_control.js'),
resource_string(__name__, 'js/src/videoalpha/08_video_speed_control.js'),
resource_string(__name__, 'js/src/videoalpha/09_video_caption.js'),
resource_string(__name__, 'js/src/videoalpha/10_main.js')
]
}
css = {'scss': [resource_string(__name__, 'css/videoalpha/display.scss')]}
js_module_name = "VideoAlpha"
def handle_ajax(self, dispatch, data):
"""This is not being called right now and we raise 404 error."""
log.debug(u"GET {0}".format(data))
log.debug(u"DISPATCH {0}".format(dispatch))
raise Http404()
def get_instance_state(self):
"""Return information about state (position)."""
return json.dumps({'position': self.position})
def get_html(self):
if isinstance(modulestore(), MongoModuleStore):
caption_asset_path = StaticContent.get_base_url_path_for_course_assets(self.location) + '/subs_'
else:
# VS[compat]
# cdodge: filesystem static content support.
caption_asset_path = "/static/subs/"
get_ext = lambda filename: filename.rpartition('.')[-1]
sources = {get_ext(src): src for src in self.html5_sources}
sources['main'] = self.source
return self.system.render_template('videoalpha.html', {
'youtube_streams': _create_youtube_string(self),
'id': self.location.html_id(),
'sub': self.sub,
'sources': sources,
'track': self.track,
'display_name': self.display_name_with_default,
# This won't work when we move to data that
# isn't on the filesystem
'data_dir': getattr(self, 'data_dir', None),
'caption_asset_path': caption_asset_path,
'show_captions': json.dumps(self.show_captions),
'start': self.start_time,
'end': self.end_time,
'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True)
})
class VideoAlphaDescriptor(VideoAlphaFields, TabsEditingDescriptor, EmptyDataRawDescriptor):
"""Descriptor for `VideoAlphaModule`."""
module_class = VideoAlphaModule
tabs = [
# {
# 'name': "Subtitles",
# 'template': "videoalpha/subtitles.html",
# },
{
'name': "Settings",
'template': "tabs/metadata-edit-tab.html",
'current': True
}
]
def __init__(self, *args, **kwargs):
super(VideoAlphaDescriptor, self).__init__(*args, **kwargs)
# For backwards compatibility -- if we've got XML data, parse
# it out and set the metadata fields
if self.data:
model_data = VideoAlphaDescriptor._parse_video_xml(self.data)
self._model_data.update(model_data)
del self.data
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None):
"""
Creates an instance of this descriptor from the supplied xml_data.
This may be overridden by subclasses
xml_data: A string of xml that will be translated into data and children for
this module
system: A DescriptorSystem for interacting with external resources
org and course are optional strings that will be used in the generated modules
url identifiers
"""
# Calling from_xml of XmlDescritor, to get right Location, when importing from XML
video = super(VideoAlphaDescriptor, cls).from_xml(xml_data, system, org, course)
return video
def export_to_xml(self, resource_fs):
"""
Returns an xml string representing this module.
"""
xml = etree.Element('videoalpha')
attrs = {
'display_name': self.display_name,
'show_captions': json.dumps(self.show_captions),
'youtube': _create_youtube_string(self),
'start_time': datetime.timedelta(seconds=self.start_time),
'end_time': datetime.timedelta(seconds=self.end_time),
'sub': self.sub
}
for key, value in attrs.items():
if value:
xml.set(key, str(value))
for source in self.html5_sources:
ele = etree.Element('source')
ele.set('src', source)
xml.append(ele)
if self.track:
ele = etree.Element('track')
ele.set('src', self.track)
xml.append(ele)
return etree.tostring(xml, pretty_print=True)
@staticmethod
def _parse_youtube(data):
"""
Parses a string of Youtube IDs such as "1.0:AXdE34_U,1.5:VO3SxfeD"
into a dictionary. Necessary for backwards compatibility with
XML-based courses.
"""
ret = {'0.75': '', '1.00': '', '1.25': '', '1.50': ''}
if data == '':
return ret
videos = data.split(',')
for video in videos:
pieces = video.split(':')
# HACK
# To elaborate somewhat: in many LMS tests, the keys for
# Youtube IDs are inconsistent. Sometimes a particular
# speed isn't present, and formatting is also inconsistent
# ('1.0' versus '1.00'). So it's necessary to either do
# something like this or update all the tests to work
# properly.
ret['%.2f' % float(pieces[0])] = pieces[1]
return ret
@staticmethod
def _parse_video_xml(xml_data):
"""
Parse video fields out of xml_data. The fields are set if they are
present in the XML.
"""
xml = etree.fromstring(xml_data)
model_data = {}
conversions = {
'show_captions': json.loads,
'start_time': VideoAlphaDescriptor._parse_time,
'end_time': VideoAlphaDescriptor._parse_time
}
# VideoModule and VideoAlphaModule use different names for
# these attributes -- need to convert between them
video_compat = {
'from': 'start_time',
'to': 'end_time'
}
sources = xml.findall('source')
if sources:
model_data['html5_sources'] = [ele.get('src') for ele in sources]
model_data['source'] = model_data['html5_sources'][0]
track = xml.find('track')
if track is not None:
model_data['track'] = track.get('src')
for attr, value in xml.items():
if attr in video_compat:
attr = video_compat[attr]
if attr == 'youtube':
speeds = VideoAlphaDescriptor._parse_youtube(value)
for speed, youtube_id in speeds.items():
# should have made these youtube_id_1_00 for
# cleanliness, but hindsight doesn't need glasses
normalized_speed = speed[:-1] if speed.endswith('0') else speed
# If the user has specified html5 sources, make sure we don't use the default video
if youtube_id != '' or 'html5_sources' in model_data:
model_data['youtube_id_{0}'.format(normalized_speed.replace('.', '_'))] = youtube_id
else:
# Convert XML attrs into Python values.
if attr in conversions:
value = conversions[attr](value)
model_data[attr] = value
return model_data
@staticmethod
def _parse_time(str_time):
"""Converts s in '12:34:45' format to seconds. If s is
None, returns empty string"""
if not str_time:
return ''
else:
obj_time = time.strptime(str_time, '%H:%M:%S')
return datetime.timedelta(
hours=obj_time.tm_hour,
minutes=obj_time.tm_min,
seconds=obj_time.tm_sec
).total_seconds()
def _create_youtube_string(module):
"""
Create a string of Youtube IDs from `module`'s metadata
attributes. Only writes a speed if an ID is present in the
module. Necessary for backwards compatibility with XML-based
courses.
"""
youtube_ids = [
module.youtube_id_0_75,
module.youtube_id_1_0,
module.youtube_id_1_25,
module.youtube_id_1_5
]
youtube_speeds = ['0.75', '1.00', '1.25', '1.50']
return ','.join([':'.join(pair)
for pair
in zip(youtube_speeds, youtube_ids)
if pair[1]])
...@@ -2,22 +2,22 @@ ...@@ -2,22 +2,22 @@
"""Video xmodule tests in mongo.""" """Video xmodule tests in mongo."""
from . import BaseTestXmodule from . import BaseTestXmodule
from .test_videoalpha_xml import SOURCE_XML from .test_video_xml import SOURCE_XML
from django.conf import settings from django.conf import settings
from xmodule.videoalpha_module import _create_youtube_string from xmodule.video_module import _create_youtube_string
class TestVideo(BaseTestXmodule): class TestVideo(BaseTestXmodule):
"""Integration tests: web client + mongo.""" """Integration tests: web client + mongo."""
CATEGORY = "videoalpha" CATEGORY = "video"
DATA = SOURCE_XML DATA = SOURCE_XML
MODEL_DATA = { MODEL_DATA = {
'data': DATA 'data': DATA
} }
def setUp(self): def setUp(self):
# Since the VideoAlphaDescriptor changes `self._model_data`, # Since the VideoDescriptor changes `self._model_data`,
# we need to instantiate `self.item_module` through # we need to instantiate `self.item_module` through
# `self.item_descriptor` rather than directly constructing it # `self.item_descriptor` rather than directly constructing it
super(TestVideo, self).setUp() super(TestVideo, self).setUp()
...@@ -40,7 +40,7 @@ class TestVideo(BaseTestXmodule): ...@@ -40,7 +40,7 @@ class TestVideo(BaseTestXmodule):
]).pop(), ]).pop(),
404) 404)
def test_videoalpha_constructor(self): def test_video_constructor(self):
"""Make sure that all parameters extracted correclty from xml""" """Make sure that all parameters extracted correclty from xml"""
context = self.item_module.get_html() context = self.item_module.get_html()
...@@ -74,7 +74,7 @@ class TestVideoNonYouTube(TestVideo): ...@@ -74,7 +74,7 @@ class TestVideoNonYouTube(TestVideo):
"""Integration tests: web client + mongo.""" """Integration tests: web client + mongo."""
DATA = """ DATA = """
<videoalpha 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"
start_time="01:00:03" end_time="01:00:10" start_time="01:00:03" end_time="01:00:10"
...@@ -82,13 +82,13 @@ class TestVideoNonYouTube(TestVideo): ...@@ -82,13 +82,13 @@ class TestVideoNonYouTube(TestVideo):
<source src="example.mp4"/> <source src="example.mp4"/>
<source src="example.webm"/> <source src="example.webm"/>
<source src="example.ogv"/> <source src="example.ogv"/>
</videoalpha> </video>
""" """
MODEL_DATA = { MODEL_DATA = {
'data': DATA 'data': DATA
} }
def test_videoalpha_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.
""" """
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# pylint: disable=W0212 # pylint: disable=W0212
"""Test for VideoAlpha Xmodule functional logic. """Test for Video Xmodule functional logic.
These test data read from xml, not from mongo. These test data read from xml, not from mongo.
We have a ModuleStoreTestCase class defined in We have a ModuleStoreTestCase class defined in
...@@ -20,14 +20,14 @@ import unittest ...@@ -20,14 +20,14 @@ import unittest
from django.conf import settings from django.conf import settings
from xmodule.videoalpha_module import ( from xmodule.video_module import (
VideoAlphaDescriptor, _create_youtube_string) VideoDescriptor, _create_youtube_string)
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.tests import get_test_system, LogicTest from xmodule.tests import get_test_system, LogicTest
SOURCE_XML = """ SOURCE_XML = """
<videoalpha show_captions="true" <video show_captions="true"
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"
...@@ -36,12 +36,12 @@ SOURCE_XML = """ ...@@ -36,12 +36,12 @@ SOURCE_XML = """
<source src="example.mp4"/> <source src="example.mp4"/>
<source src="example.webm"/> <source src="example.webm"/>
<source src="example.ogv"/> <source src="example.ogv"/>
</videoalpha> </video>
""" """
class VideoAlphaFactory(object): class VideoFactory(object):
"""A helper class to create videoalpha modules with various parameters """A helper class to create video modules with various parameters
for testing. for testing.
""" """
...@@ -50,28 +50,28 @@ class VideoAlphaFactory(object): ...@@ -50,28 +50,28 @@ class VideoAlphaFactory(object):
@staticmethod @staticmethod
def create(): def create():
"""Method return VideoAlpha Xmodule instance.""" """Method return Video Xmodule instance."""
location = Location(["i4x", "edX", "videoalpha", "default", location = Location(["i4x", "edX", "video", "default",
"SampleProblem1"]) "SampleProblem1"])
model_data = {'data': VideoAlphaFactory.sample_problem_xml_youtube, model_data = {'data': VideoFactory.sample_problem_xml_youtube,
'location': location} 'location': location}
system = get_test_system() system = get_test_system()
system.render_template = lambda template, context: context system.render_template = lambda template, context: context
descriptor = VideoAlphaDescriptor(system, model_data) descriptor = VideoDescriptor(system, model_data)
module = descriptor.xmodule(system) module = descriptor.xmodule(system)
return module return module
class VideoAlphaModuleUnitTest(unittest.TestCase): class VideoModuleUnitTest(unittest.TestCase):
"""Unit tests for VideoAlpha Xmodule.""" """Unit tests for Video Xmodule."""
def test_videoalpha_get_html(self): def test_video_get_html(self):
"""Make sure that all parameters extracted correclty from xml""" """Make sure that all parameters extracted correclty from xml"""
module = VideoAlphaFactory.create() module = VideoFactory.create()
module.runtime.render_template = lambda template, context: context module.runtime.render_template = lambda template, context: context
sources = { sources = {
...@@ -98,18 +98,18 @@ class VideoAlphaModuleUnitTest(unittest.TestCase): ...@@ -98,18 +98,18 @@ class VideoAlphaModuleUnitTest(unittest.TestCase):
self.assertEqual(module.get_html(), expected_context) self.assertEqual(module.get_html(), expected_context)
def test_videoalpha_instance_state(self): def test_video_instance_state(self):
module = VideoAlphaFactory.create() module = VideoFactory.create()
self.assertDictEqual( self.assertDictEqual(
json.loads(module.get_instance_state()), json.loads(module.get_instance_state()),
{'position': 0}) {'position': 0})
class VideoAlphaModuleLogicTest(LogicTest): class VideoModuleLogicTest(LogicTest):
"""Tests for logic of VideoAlpha Xmodule.""" """Tests for logic of Video Xmodule."""
descriptor_class = VideoAlphaDescriptor descriptor_class = VideoDescriptor
raw_model_data = { raw_model_data = {
'data': '<video />' 'data': '<video />'
...@@ -117,23 +117,23 @@ class VideoAlphaModuleLogicTest(LogicTest): ...@@ -117,23 +117,23 @@ class VideoAlphaModuleLogicTest(LogicTest):
def test_parse_time(self): def test_parse_time(self):
"""Ensure that times are parsed correctly into seconds.""" """Ensure that times are parsed correctly into seconds."""
output = VideoAlphaDescriptor._parse_time('00:04:07') output = VideoDescriptor._parse_time('00:04:07')
self.assertEqual(output, 247) self.assertEqual(output, 247)
def test_parse_time_none(self): def test_parse_time_none(self):
"""Check parsing of None.""" """Check parsing of None."""
output = VideoAlphaDescriptor._parse_time(None) output = VideoDescriptor._parse_time(None)
self.assertEqual(output, '') self.assertEqual(output, '')
def test_parse_time_empty(self): def test_parse_time_empty(self):
"""Check parsing of the empty string.""" """Check parsing of the empty string."""
output = VideoAlphaDescriptor._parse_time('') output = VideoDescriptor._parse_time('')
self.assertEqual(output, '') self.assertEqual(output, '')
def test_parse_youtube(self): def test_parse_youtube(self):
"""Test parsing old-style Youtube ID strings into a dict.""" """Test parsing old-style Youtube ID strings into a dict."""
youtube_str = '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg' youtube_str = '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg'
output = VideoAlphaDescriptor._parse_youtube(youtube_str) output = VideoDescriptor._parse_youtube(youtube_str)
self.assertEqual(output, {'0.75': 'jNCf2gIqpeE', self.assertEqual(output, {'0.75': 'jNCf2gIqpeE',
'1.00': 'ZwkTiUPN0mg', '1.00': 'ZwkTiUPN0mg',
'1.25': 'rsq9auxASqI', '1.25': 'rsq9auxASqI',
...@@ -145,7 +145,7 @@ class VideoAlphaModuleLogicTest(LogicTest): ...@@ -145,7 +145,7 @@ class VideoAlphaModuleLogicTest(LogicTest):
empty string. empty string.
""" """
youtube_str = '0.75:jNCf2gIqpeE' youtube_str = '0.75:jNCf2gIqpeE'
output = VideoAlphaDescriptor._parse_youtube(youtube_str) output = VideoDescriptor._parse_youtube(youtube_str)
self.assertEqual(output, {'0.75': 'jNCf2gIqpeE', self.assertEqual(output, {'0.75': 'jNCf2gIqpeE',
'1.00': '', '1.00': '',
'1.25': '', '1.25': '',
...@@ -158,8 +158,8 @@ class VideoAlphaModuleLogicTest(LogicTest): ...@@ -158,8 +158,8 @@ class VideoAlphaModuleLogicTest(LogicTest):
youtube_str = '1.00:p2Q6BrNhdh8' youtube_str = '1.00:p2Q6BrNhdh8'
youtube_str_hack = '1.0:p2Q6BrNhdh8' youtube_str_hack = '1.0:p2Q6BrNhdh8'
self.assertEqual( self.assertEqual(
VideoAlphaDescriptor._parse_youtube(youtube_str), VideoDescriptor._parse_youtube(youtube_str),
VideoAlphaDescriptor._parse_youtube(youtube_str_hack) VideoDescriptor._parse_youtube(youtube_str_hack)
) )
def test_parse_youtube_empty(self): def test_parse_youtube_empty(self):
...@@ -167,7 +167,7 @@ class VideoAlphaModuleLogicTest(LogicTest): ...@@ -167,7 +167,7 @@ class VideoAlphaModuleLogicTest(LogicTest):
Some courses have empty youtube attributes, so we should handle Some courses have empty youtube attributes, so we should handle
that well. that well.
""" """
self.assertEqual(VideoAlphaDescriptor._parse_youtube(''), self.assertEqual(VideoDescriptor._parse_youtube(''),
{'0.75': '', {'0.75': '',
'1.00': '', '1.00': '',
'1.25': '', '1.25': '',
......
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