diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 09413be7b77fd7496a6805eb41907b0cdc02dc81..2ffd5a3684c1d69ad70b2cfc76884421276ade5a 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -107,8 +107,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): expected_types is the list of elements that should appear on the page. expected_types and component_types should be similar, but not - exactly the same -- for example, 'videoalpha' in - component_types should cause 'Video Alpha' to be present. + exactly the same -- for example, 'video' in + component_types should cause 'Video' to be present. """ store = modulestore('direct') import_from_xml(store, 'common/test/data/', ['simple']) @@ -143,7 +143,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): 'Peer Grading Interface']) 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): store = modulestore('direct') @@ -1624,7 +1624,7 @@ class MetadataSaveTestCase(ModuleStoreTestCase): constructor are correctly persisted. """ # 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 = { 'show_captions', 'youtube_id_1_0', @@ -1634,6 +1634,7 @@ class MetadataSaveTestCase(ModuleStoreTestCase): 'start_time', 'end_time', 'source', + 'html5_sources', 'track' } # We strip out all metadata fields to reproduce a bug where @@ -1646,11 +1647,11 @@ class MetadataSaveTestCase(ModuleStoreTestCase): field.delete_from(self.descriptor) # 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( self.descriptor.location, own_metadata(self.descriptor) ) module = get_modulestore(self.descriptor.location).get_item(self.descriptor.location) # Assert that get_item correctly sets the metadata - self.assertIn('source', own_metadata(module)) + self.assertIn('html5_sources', own_metadata(module)) diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 7cb503db1ebcd735f44825a2f9f8dba9c27fa83f..d7b41acb24fddd7112b1cd2a1035f737d904935a 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -49,7 +49,6 @@ NOTE_COMPONENT_TYPES = ['notes'] ADVANCED_COMPONENT_TYPES = [ 'annotatable', 'word_cloud', - 'videoalpha', 'graphical_slider_tool' ] + OPEN_ENDED_COMPONENT_TYPES + NOTE_COMPONENT_TYPES ADVANCED_COMPONENT_CATEGORY = 'advanced' diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 6b106dd94db8ed499966c93f83e7f1ebaee52159..704de15ea77760e1538f88f789f4b7db5514951d 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -40,7 +40,7 @@ setup( "timelimit = xmodule.timelimit_module:TimeLimitDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor", "video = xmodule.video_module:VideoDescriptor", - "videoalpha = xmodule.videoalpha_module:VideoAlphaDescriptor", + "videoalpha = xmodule.video_module:VideoDescriptor", "videodev = xmodule.backcompat_module:TranslateCustomTagDescriptor", "videosequence = xmodule.seq_module:SequenceDescriptor", "discussion = xmodule.discussion_module:DiscussionDescriptor", diff --git a/common/lib/xmodule/xmodule/tests/test_editing_module.py b/common/lib/xmodule/xmodule/tests/test_editing_module.py index 03e257940f9ba754056866fc767d69698aeb7e47..838a4f9adaaf006635a526cf0ca5987ebe0b9d4b 100644 --- a/common/lib/xmodule/xmodule/tests/test_editing_module.py +++ b/common/lib/xmodule/xmodule/tests/test_editing_module.py @@ -33,7 +33,7 @@ class TabsEditingDescriptorTestCase(unittest.TestCase): }, { 'name': "Subtitles", - 'template': "videoalpha/subtitles.html", + 'template': "video/subtitles.html", }, { 'name': "Settings", diff --git a/common/lib/xmodule/xmodule/tests/test_videoalpha.py b/common/lib/xmodule/xmodule/tests/test_video.py similarity index 78% rename from common/lib/xmodule/xmodule/tests/test_videoalpha.py rename to common/lib/xmodule/xmodule/tests/test_video.py index 76f86d6d4aa4571195af2f2b0504a924b58d8806..baafc05d450169cfd3f67d46498699420ac48bee 100644 --- a/common/lib/xmodule/xmodule/tests/test_videoalpha.py +++ b/common/lib/xmodule/xmodule/tests/test_video.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- #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. We have a ModuleStoreTestCase class defined in @@ -17,37 +17,36 @@ import unittest from . import LogicTest from .import get_test_system from xmodule.modulestore import Location -from xmodule.videoalpha_module import VideoAlphaDescriptor, _create_youtube_string -from xmodule.video_module import VideoDescriptor +from xmodule.video_module import VideoDescriptor, _create_youtube_string from .test_import import DummySystem from textwrap import dedent -class VideoAlphaModuleTest(LogicTest): - """Logic tests for VideoAlpha Xmodule.""" - descriptor_class = VideoAlphaDescriptor +class VideoModuleTest(LogicTest): + """Logic tests for Video Xmodule.""" + descriptor_class = VideoDescriptor raw_model_data = { - 'data': '<videoalpha />' + 'data': '<video />' } def test_parse_time_empty(self): """Ensure parse_time returns correctly with None or empty string.""" expected = '' - self.assertEqual(VideoAlphaDescriptor._parse_time(None), expected) - self.assertEqual(VideoAlphaDescriptor._parse_time(''), expected) + self.assertEqual(VideoDescriptor._parse_time(None), expected) + self.assertEqual(VideoDescriptor._parse_time(''), expected) def test_parse_time(self): """Ensure that times are parsed correctly into seconds.""" expected = 247 - output = VideoAlphaDescriptor._parse_time('00:04:07') + output = VideoDescriptor._parse_time('00:04:07') self.assertEqual(output, expected) def test_parse_youtube(self): """Test parsing old-style Youtube ID strings into a dict.""" 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', '1.00': 'ZwkTiUPN0mg', '1.25': 'rsq9auxASqI', @@ -59,7 +58,7 @@ class VideoAlphaModuleTest(LogicTest): empty string. """ youtube_str = '0.75:jNCf2gIqpeE' - output = VideoAlphaDescriptor._parse_youtube(youtube_str) + output = VideoDescriptor._parse_youtube(youtube_str) self.assertEqual(output, {'0.75': 'jNCf2gIqpeE', '1.00': '', '1.25': '', @@ -72,8 +71,8 @@ class VideoAlphaModuleTest(LogicTest): youtube_str = '1.00:p2Q6BrNhdh8' youtube_str_hack = '1.0:p2Q6BrNhdh8' self.assertEqual( - VideoAlphaDescriptor._parse_youtube(youtube_str), - VideoAlphaDescriptor._parse_youtube(youtube_str_hack) + VideoDescriptor._parse_youtube(youtube_str), + VideoDescriptor._parse_youtube(youtube_str_hack) ) def test_parse_youtube_empty(self): @@ -82,7 +81,7 @@ class VideoAlphaModuleTest(LogicTest): that well. """ self.assertEqual( - VideoAlphaDescriptor._parse_youtube(''), + VideoDescriptor._parse_youtube(''), {'0.75': '', '1.00': '', '1.25': '', @@ -90,12 +89,12 @@ class VideoAlphaModuleTest(LogicTest): ) -class VideoAlphaDescriptorTest(unittest.TestCase): - """Test for VideoAlphaDescriptor""" +class VideoDescriptorTest(unittest.TestCase): + """Test for VideoDescriptor""" def setUp(self): system = get_test_system() - self.descriptor = VideoAlphaDescriptor( + self.descriptor = VideoDescriptor( runtime=system, model_data={}) @@ -117,9 +116,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase): back out to XML. """ system = DummySystem(load_error_modules=True) - location = Location(["i4x", "edX", "videoalpha", "default", "SampleProblem1"]) + location = Location(["i4x", "edX", "video", "default", "SampleProblem1"]) model_data = {'location': location} - descriptor = VideoAlphaDescriptor(system, model_data) + descriptor = VideoDescriptor(system, model_data) descriptor.youtube_id_0_75 = 'izygArpw-Qo' descriptor.youtube_id_1_0 = 'p2Q6BrNhdh8' descriptor.youtube_id_1_25 = '1EeWXzPdhSA' @@ -133,9 +132,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase): in the output string. """ system = DummySystem(load_error_modules=True) - location = Location(["i4x", "edX", "videoalpha", "default", "SampleProblem1"]) + location = Location(["i4x", "edX", "video", "default", "SampleProblem1"]) model_data = {'location': location} - descriptor = VideoAlphaDescriptor(system, model_data) + descriptor = VideoDescriptor(system, model_data) descriptor.youtube_id_0_75 = 'izygArpw-Qo' descriptor.youtube_id_1_0 = 'p2Q6BrNhdh8' descriptor.youtube_id_1_25 = '1EeWXzPdhSA' @@ -143,9 +142,9 @@ class VideoAlphaDescriptorTest(unittest.TestCase): 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): @@ -158,7 +157,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): def test_constructor(self): 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" show_captions="false" start_time="00:00:01" @@ -166,14 +165,14 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): <source src="http://www.example.com/source.mp4"/> <source src="http://www.example.com/source.ogg"/> <track src="http://www.example.com/track"/> - </videoalpha> + </video> ''' - location = Location(["i4x", "edX", "videoalpha", "default", + location = Location(["i4x", "edX", "video", "default", "SampleProblem1"]) model_data = {'data': sample_xml, 'location': location} system = DummySystem(load_error_modules=True) - descriptor = VideoAlphaDescriptor(system, model_data) + descriptor = VideoDescriptor(system, model_data) self.assert_attributes_equal(descriptor, { 'youtube_id_0_75': 'izygArpw-Qo', 'youtube_id_1_0': 'p2Q6BrNhdh8', @@ -190,16 +189,16 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): def test_from_xml(self): module_system = DummySystem(load_error_modules=True) 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" show_captions="false" start_time="00:00:01" end_time="00:01:00"> <source src="http://www.example.com/source.mp4"/> <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, { 'youtube_id_0_75': 'izygArpw-Qo', 'youtube_id_1_0': 'p2Q6BrNhdh8', @@ -221,14 +220,14 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): """ module_system = DummySystem(load_error_modules=True) xml_data = ''' - <videoalpha display_name="Test Video" + <video display_name="Test Video" youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA" show_captions="true"> <source src="http://www.example.com/source.mp4"/> <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, { 'youtube_id_0_75': '', 'youtube_id_1_0': 'p2Q6BrNhdh8', @@ -248,8 +247,8 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): Make sure settings are correct if none are explicitly set in XML. """ module_system = DummySystem(load_error_modules=True) - xml_data = '<videoalpha></videoalpha>' - output = VideoAlphaDescriptor.from_xml(xml_data, module_system) + xml_data = '<video></video>' + output = VideoDescriptor.from_xml(xml_data, module_system) self.assert_attributes_equal(output, { 'youtube_id_0_75': '', 'youtube_id_1_0': 'OEoXaMPEzfM', @@ -270,16 +269,16 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): """ module_system = DummySystem(load_error_modules=True) 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" show_captions="false" from="00:00:01" to="00:01:00"> <source src="http://www.example.com/source.mp4"/> <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, { 'youtube_id_0_75': 'izygArpw-Qo', 'youtube_id_1_0': 'p2Q6BrNhdh8', @@ -295,7 +294,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): 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) xml_data = """ @@ -309,8 +308,7 @@ class VideoAlphaDescriptorImportTestCase(unittest.TestCase): </video> """ video = VideoDescriptor.from_xml(xml_data, module_system) - video_alpha = VideoAlphaDescriptor(module_system, video._model_data) - self.assert_attributes_equal(video_alpha, { + self.assert_attributes_equal(video, { 'youtube_id_0_75': 'izygArpw-Qo', 'youtube_id_1_0': 'p2Q6BrNhdh8', 'youtube_id_1_25': '1EeWXzPdhSA', @@ -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. """ def test_export_to_xml(self): """Test that we write the correct XML on export.""" module_system = DummySystem(load_error_modules=True) - location = Location(["i4x", "edX", "videoalpha", "default", "SampleProblem1"]) - desc = VideoAlphaDescriptor(module_system, {'location': location}) + location = Location(["i4x", "edX", "video", "default", "SampleProblem1"]) + desc = VideoDescriptor(module_system, {'location': location}) desc.youtube_id_0_75 = 'izygArpw-Qo' desc.youtube_id_1_0 = 'p2Q6BrNhdh8' @@ -348,11 +346,11 @@ class VideoAlphaExportTestCase(unittest.TestCase): xml = desc.export_to_xml(None) # We don't use the `resource_fs` parameter 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.ogg"/> <track src="http://www.example.com/track"/> - </videoalpha> + </video> ''') self.assertEquals(expected, xml) @@ -360,10 +358,10 @@ class VideoAlphaExportTestCase(unittest.TestCase): def test_export_to_xml_empty_parameters(self): """Test XML export with defaults.""" module_system = DummySystem(load_error_modules=True) - location = Location(["i4x", "edX", "videoalpha", "default", "SampleProblem1"]) - desc = VideoAlphaDescriptor(module_system, {'location': location}) + location = Location(["i4x", "edX", "video", "default", "SampleProblem1"]) + desc = VideoDescriptor(module_system, {'location': location}) 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) diff --git a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py index 90ff209c7d3ded1cc0566cee852b895cee5ceee6..c98f980c6220b7d81c8447af87073a050dac7586 100644 --- a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py +++ b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py @@ -16,10 +16,9 @@ from xmodule.gst_module import GraphicalSliderToolDescriptor from xmodule.html_module import HtmlDescriptor from xmodule.peer_grading_module import PeerGradingDescriptor from xmodule.poll_module import PollDescriptor -from xmodule.video_module import VideoDescriptor from xmodule.word_cloud_module import WordCloudDescriptor 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.conditional_module import ConditionalDescriptor from xmodule.randomize_module import RandomizeDescriptor @@ -35,9 +34,8 @@ LEAF_XMODULES = ( HtmlDescriptor, PeerGradingDescriptor, PollDescriptor, - VideoDescriptor, # This is being excluded because it has dependencies on django - #VideoAlphaDescriptor, + #VideoDescriptor, WordCloudDescriptor, ) diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py index 763975fc3bc95e315394b73933c416bbd1b923c2..c18d6d066b1cea476aa09e49619fa4798c422452 100644 --- a/common/lib/xmodule/xmodule/video_module.py +++ b/common/lib/xmodule/xmodule/video_module.py @@ -1,20 +1,34 @@ # 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 logging from lxml import etree -from pkg_resources import resource_string, resource_listdir -import datetime -import time +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.editing_module import MetadataOnlyEditingDescriptor -from xblock.core import Integer, Scope, String, Float, Boolean +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__) @@ -22,51 +36,118 @@ log = logging.getLogger(__name__) class VideoFields(object): """Fields for `VideoModule` and `VideoDescriptor`.""" display_name = String( - display_name="Display Name", - help="This name appears in the horizontal navigation at the top of the page.", + display_name="Display Name", help="Display name for this module.", + 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, - # it'd be nice to have a useful default but it screws up other things; so, - # use display_name_with_default for those - default="Video" + default=True ) - data = String( - help="XML data for the problem", - default='', - scope=Scope.content + # 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="" ) - 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): - """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 icon_class = 'video' js = { - 'coffee': [ - resource_string(__name__, 'js/src/time.coffee'), - resource_string(__name__, 'js/src/video/display.coffee') - ] + - [resource_string(__name__, 'js/src/video/display/' + filename) - for filename - in sorted(resource_listdir(__name__, 'js/src/video/display')) - if filename.endswith('.coffee')] + 'js': [ + resource_string(__name__, 'js/src/video/01_initialize.js'), + 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/04_video_control.js'), + resource_string(__name__, 'js/src/video/05_video_quality_control.js'), + resource_string(__name__, 'js/src/video/06_video_progress_slider.js'), + 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')]} js_module_name = "Video" - def __init__(self, *args, **kwargs): - XModule.__init__(self, *args, **kwargs) - 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)) @@ -78,41 +159,59 @@ class VideoModule(VideoFields, XModule): 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('video.html', { - 'youtube_id_0_75': self.youtube_id_0_75, - '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, + 'youtube_streams': _create_youtube_string(self), 'id': self.location.html_id(), - 'position': self.position, - 'source': self.source, + 'sub': self.sub, + 'sources': sources, 'track': self.track, 'display_name': self.display_name_with_default, - 'caption_asset_path': "/static/subs/", - 'show_captions': 'true' if self.show_captions else 'false', + # 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 + 'end': self.end_time, + 'autoplay': settings.MITX_FEATURES.get('AUTOPLAY_VIDEOS', True) }) -class VideoDescriptor(VideoFields, - MetadataOnlyEditingDescriptor, - EmptyDataRawDescriptor): +class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor): + """Descriptor for `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): super(VideoDescriptor, self).__init__(*args, **kwargs) - # If we don't have a `youtube_id_1_0`, this is an XML course - # and we parse out the fields. - if self.data and 'youtube_id_1_0' not in self._model_data: - _parse_video_xml(self, self.data) - - @property - 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 + # For backwards compatibility -- if we've got XML data, parse + # it out and set the metadata fields + if self.data: + model_data = VideoDescriptor._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): @@ -126,102 +225,143 @@ class VideoDescriptor(VideoFields, 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(VideoDescriptor, cls).from_xml(xml_data, system, org, course) - _parse_video_xml(video, video.data) 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): - """ - 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) - - display_name = xml.get('display_name') - if display_name: - video.display_name = display_name - - youtube = xml.get('youtube') - if youtube: - speeds = _parse_youtube(youtube) - if speeds['0.75']: - video.youtube_id_0_75 = speeds['0.75'] - if speeds['1.00']: - video.youtube_id_1_0 = speeds['1.00'] - if speeds['1.25']: - video.youtube_id_1_25 = speeds['1.25'] - if speeds['1.50']: - video.youtube_id_1_5 = speeds['1.50'] - - show_captions = xml.get('show_captions') - if show_captions: - video.show_captions = json.loads(show_captions) - - source = _get_first_external(xml, 'source') - if source: - video.source = source - - track = _get_first_external(xml, 'track') - if track: - video.track = track - - start_time = _parse_time(xml.get('from')) - if start_time: - video.start_time = start_time - - end_time = _parse_time(xml.get('to')) - if end_time: - video.end_time = end_time - - -def _get_first_external(xmltree, tag): - """ - Returns the src attribute of the nested `tag` in `xmltree`, if it - exists. - """ - for element in xmltree.findall(tag): - src = element.get('src') - if src: - return src - return None - - -def _parse_youtube(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. + """ + xml = etree.fromstring(xml_data) + model_data = {} + + conversions = { + 'show_captions': json.loads, + 'start_time': VideoDescriptor._parse_time, + 'end_time': VideoDescriptor._parse_time + } + + # VideoModule and VideoModule 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 = VideoDescriptor._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): """ - 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. + 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. """ - 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_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() + 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]]) diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py deleted file mode 100644 index 176b19237792e28c638a2d8501e56907ef5c3882..0000000000000000000000000000000000000000 --- a/common/lib/xmodule/xmodule/videoalpha_module.py +++ /dev/null @@ -1,367 +0,0 @@ -# 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]]) diff --git a/lms/djangoapps/courseware/tests/test_videoalpha_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py similarity index 90% rename from lms/djangoapps/courseware/tests/test_videoalpha_mongo.py rename to lms/djangoapps/courseware/tests/test_video_mongo.py index 38b2b6fb8dc7d7aa6395c29a0b5bb0fb4911643c..2927bfc37a2773eb9f3f737d40a510519ab0b161 100644 --- a/lms/djangoapps/courseware/tests/test_videoalpha_mongo.py +++ b/lms/djangoapps/courseware/tests/test_video_mongo.py @@ -2,22 +2,22 @@ """Video xmodule tests in mongo.""" from . import BaseTestXmodule -from .test_videoalpha_xml import SOURCE_XML +from .test_video_xml import SOURCE_XML from django.conf import settings -from xmodule.videoalpha_module import _create_youtube_string +from xmodule.video_module import _create_youtube_string class TestVideo(BaseTestXmodule): """Integration tests: web client + mongo.""" - CATEGORY = "videoalpha" + CATEGORY = "video" DATA = SOURCE_XML MODEL_DATA = { 'data': DATA } 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 # `self.item_descriptor` rather than directly constructing it super(TestVideo, self).setUp() @@ -40,7 +40,7 @@ class TestVideo(BaseTestXmodule): ]).pop(), 404) - def test_videoalpha_constructor(self): + def test_video_constructor(self): """Make sure that all parameters extracted correclty from xml""" context = self.item_module.get_html() @@ -74,7 +74,7 @@ class TestVideoNonYouTube(TestVideo): """Integration tests: web client + mongo.""" DATA = """ - <videoalpha show_captions="true" + <video show_captions="true" display_name="A Name" sub="a_sub_file.srt.sjson" start_time="01:00:03" end_time="01:00:10" @@ -82,13 +82,13 @@ class TestVideoNonYouTube(TestVideo): <source src="example.mp4"/> <source src="example.webm"/> <source src="example.ogv"/> - </videoalpha> + </video> """ MODEL_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 the template generates an empty string for the YouTube streams. """ diff --git a/lms/djangoapps/courseware/tests/test_videoalpha_xml.py b/lms/djangoapps/courseware/tests/test_video_xml.py similarity index 74% rename from lms/djangoapps/courseware/tests/test_videoalpha_xml.py rename to lms/djangoapps/courseware/tests/test_video_xml.py index e83582e1316c23283333c37fd75b9c6c97f029a2..64dbe4057bd3ca5718a512ebba374704b2b8a261 100644 --- a/lms/djangoapps/courseware/tests/test_videoalpha_xml.py +++ b/lms/djangoapps/courseware/tests/test_video_xml.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # pylint: disable=W0212 -"""Test for VideoAlpha Xmodule functional logic. +"""Test for Video Xmodule functional logic. These test data read from xml, not from mongo. We have a ModuleStoreTestCase class defined in @@ -20,14 +20,14 @@ import unittest from django.conf import settings -from xmodule.videoalpha_module import ( - VideoAlphaDescriptor, _create_youtube_string) +from xmodule.video_module import ( + VideoDescriptor, _create_youtube_string) from xmodule.modulestore import Location from xmodule.tests import get_test_system, LogicTest SOURCE_XML = """ - <videoalpha show_captions="true" + <video show_captions="true" display_name="A Name" youtube="0.75:jNCf2gIqpeE,1.0:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg" sub="a_sub_file.srt.sjson" @@ -36,12 +36,12 @@ SOURCE_XML = """ <source src="example.mp4"/> <source src="example.webm"/> <source src="example.ogv"/> - </videoalpha> + </video> """ -class VideoAlphaFactory(object): - """A helper class to create videoalpha modules with various parameters +class VideoFactory(object): + """A helper class to create video modules with various parameters for testing. """ @@ -50,28 +50,28 @@ class VideoAlphaFactory(object): @staticmethod def create(): - """Method return VideoAlpha Xmodule instance.""" - location = Location(["i4x", "edX", "videoalpha", "default", + """Method return Video Xmodule instance.""" + location = Location(["i4x", "edX", "video", "default", "SampleProblem1"]) - model_data = {'data': VideoAlphaFactory.sample_problem_xml_youtube, + model_data = {'data': VideoFactory.sample_problem_xml_youtube, 'location': location} system = get_test_system() system.render_template = lambda template, context: context - descriptor = VideoAlphaDescriptor(system, model_data) + descriptor = VideoDescriptor(system, model_data) module = descriptor.xmodule(system) return module -class VideoAlphaModuleUnitTest(unittest.TestCase): - """Unit tests for VideoAlpha Xmodule.""" +class VideoModuleUnitTest(unittest.TestCase): + """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""" - module = VideoAlphaFactory.create() + module = VideoFactory.create() module.runtime.render_template = lambda template, context: context sources = { @@ -98,18 +98,18 @@ class VideoAlphaModuleUnitTest(unittest.TestCase): self.assertEqual(module.get_html(), expected_context) - def test_videoalpha_instance_state(self): - module = VideoAlphaFactory.create() + def test_video_instance_state(self): + module = VideoFactory.create() self.assertDictEqual( json.loads(module.get_instance_state()), {'position': 0}) -class VideoAlphaModuleLogicTest(LogicTest): - """Tests for logic of VideoAlpha Xmodule.""" +class VideoModuleLogicTest(LogicTest): + """Tests for logic of Video Xmodule.""" - descriptor_class = VideoAlphaDescriptor + descriptor_class = VideoDescriptor raw_model_data = { 'data': '<video />' @@ -117,23 +117,23 @@ class VideoAlphaModuleLogicTest(LogicTest): def test_parse_time(self): """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) def test_parse_time_none(self): """Check parsing of None.""" - output = VideoAlphaDescriptor._parse_time(None) + output = VideoDescriptor._parse_time(None) self.assertEqual(output, '') def test_parse_time_empty(self): """Check parsing of the empty string.""" - output = VideoAlphaDescriptor._parse_time('') + output = VideoDescriptor._parse_time('') self.assertEqual(output, '') def test_parse_youtube(self): """Test parsing old-style Youtube ID strings into a dict.""" 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', '1.00': 'ZwkTiUPN0mg', '1.25': 'rsq9auxASqI', @@ -145,7 +145,7 @@ class VideoAlphaModuleLogicTest(LogicTest): empty string. """ youtube_str = '0.75:jNCf2gIqpeE' - output = VideoAlphaDescriptor._parse_youtube(youtube_str) + output = VideoDescriptor._parse_youtube(youtube_str) self.assertEqual(output, {'0.75': 'jNCf2gIqpeE', '1.00': '', '1.25': '', @@ -158,8 +158,8 @@ class VideoAlphaModuleLogicTest(LogicTest): youtube_str = '1.00:p2Q6BrNhdh8' youtube_str_hack = '1.0:p2Q6BrNhdh8' self.assertEqual( - VideoAlphaDescriptor._parse_youtube(youtube_str), - VideoAlphaDescriptor._parse_youtube(youtube_str_hack) + VideoDescriptor._parse_youtube(youtube_str), + VideoDescriptor._parse_youtube(youtube_str_hack) ) def test_parse_youtube_empty(self): @@ -167,7 +167,7 @@ class VideoAlphaModuleLogicTest(LogicTest): Some courses have empty youtube attributes, so we should handle that well. """ - self.assertEqual(VideoAlphaDescriptor._parse_youtube(''), + self.assertEqual(VideoDescriptor._parse_youtube(''), {'0.75': '', '1.00': '', '1.25': '',