1169 lines
47 KiB
Python
1169 lines
47 KiB
Python
# pylint: disable=protected-access
|
|
"""Test for Video XBlock functional logic.
|
|
These test data read from xml, not from mongo.
|
|
|
|
We have a ModuleStoreTestCase class defined in
|
|
xmodule/modulestore/tests/django_utils.py. You can
|
|
search for usages of this in the cms and lms tests for examples. You use
|
|
this so that it will do things like point the modulestore setting to mongo,
|
|
flush the contentstore before and after, load the templates, etc.
|
|
You can then use the CourseFactory and BlockFactory as defined
|
|
in xmodule/modulestore/tests/factories.py to create
|
|
the course, section, subsection, unit, etc.
|
|
"""
|
|
|
|
|
|
import datetime
|
|
import json
|
|
import shutil
|
|
import unittest
|
|
from tempfile import mkdtemp
|
|
from uuid import uuid4
|
|
from unittest.mock import ANY, MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
import ddt
|
|
from django.conf import settings
|
|
from django.test import TestCase
|
|
from django.test.utils import override_settings
|
|
from fs.osfs import OSFS
|
|
from lxml import etree
|
|
from opaque_keys.edx.keys import CourseKey
|
|
from opaque_keys.edx.locator import CourseLocator
|
|
from xblock.field_data import DictFieldData
|
|
from xblock.fields import ScopeIds
|
|
|
|
from xmodule.tests import get_test_descriptor_system
|
|
from xmodule.validation import StudioValidationMessage
|
|
from xmodule.video_block import EXPORT_IMPORT_STATIC_DIR, VideoBlock, create_youtube_string
|
|
from openedx.core.djangoapps.video_config.transcripts_utils import save_to_store
|
|
from xblock.core import XBlockAside
|
|
from xmodule.modulestore.tests.test_asides import AsideTestType
|
|
|
|
from .test_import import DummyModuleStoreRuntime
|
|
|
|
SRT_FILEDATA = '''
|
|
0
|
|
00:00:00,270 --> 00:00:02,720
|
|
sprechen sie deutsch?
|
|
|
|
1
|
|
00:00:02,720 --> 00:00:05,430
|
|
Ja, ich spreche Deutsch
|
|
'''
|
|
|
|
CRO_SRT_FILEDATA = '''
|
|
0
|
|
00:00:00,270 --> 00:00:02,720
|
|
Dobar dan!
|
|
|
|
1
|
|
00:00:02,720 --> 00:00:05,430
|
|
Kako ste danas?
|
|
'''
|
|
|
|
YOUTUBE_SUBTITLES = (
|
|
"Sample trascript line 1. "
|
|
"Sample trascript line 2. "
|
|
"Sample trascript line 3."
|
|
)
|
|
|
|
MOCKED_YOUTUBE_TRANSCRIPT_API_RESPONSE = '''
|
|
<transcript>
|
|
<text start="27.88" dur="3.68">Sample trascript line 1.</text>
|
|
<text start="31.76" dur="9.54">Sample trascript line 2.</text>
|
|
<text start="44.04" dur="3.1">Sample trascript line 3.</text>
|
|
</transcript>
|
|
'''
|
|
|
|
ALL_LANGUAGES = (
|
|
["en", "English"],
|
|
["eo", "Esperanto"],
|
|
["ur", "Urdu"]
|
|
)
|
|
|
|
|
|
def instantiate_block(**field_data):
|
|
"""
|
|
Instantiate block with most properties.
|
|
"""
|
|
if field_data.get('data', None):
|
|
field_data = VideoBlock.parse_video_xml(field_data['data'])
|
|
system = get_test_descriptor_system()
|
|
course_key = CourseLocator('org', 'course', 'run')
|
|
usage_key = course_key.make_usage_key('video', 'SampleProblem')
|
|
return system.construct_xblock_from_class(
|
|
VideoBlock,
|
|
scope_ids=ScopeIds(None, None, usage_key, usage_key),
|
|
field_data=DictFieldData(field_data),
|
|
)
|
|
|
|
|
|
# Because of the way xmodule.video_block.video_block imports edxval.api, we
|
|
# must mock the entire module, which requires making mock exception classes.
|
|
|
|
class _MockValVideoNotFoundError(Exception):
|
|
"""Mock ValVideoNotFoundError exception"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
|
|
class _MockValCannotCreateError(Exception):
|
|
"""Mock ValCannotCreateError exception"""
|
|
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
|
|
|
|
|
class VideoBlockTest(unittest.TestCase):
|
|
"""Logic tests for Video XBlock."""
|
|
|
|
raw_field_data = {
|
|
'data': '<video />'
|
|
}
|
|
|
|
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 = VideoBlock._parse_youtube(youtube_str)
|
|
assert output == {'0.75': 'jNCf2gIqpeE', '1.00': 'ZwkTiUPN0mg', '1.25': 'rsq9auxASqI', '1.50': 'kMyNdzVHHgg'}
|
|
|
|
def test_parse_youtube_one_video(self):
|
|
"""
|
|
Ensure that all keys are present and missing speeds map to the
|
|
empty string.
|
|
"""
|
|
youtube_str = '0.75:jNCf2gIqpeE'
|
|
output = VideoBlock._parse_youtube(youtube_str)
|
|
assert output == {'0.75': 'jNCf2gIqpeE', '1.00': '', '1.25': '', '1.50': ''}
|
|
|
|
def test_parse_youtube_invalid(self):
|
|
"""Ensure that ids that are invalid return an empty dict"""
|
|
# invalid id
|
|
youtube_str = 'thisisaninvalidid'
|
|
output = VideoBlock._parse_youtube(youtube_str)
|
|
assert output == {'0.75': '', '1.00': '', '1.25': '', '1.50': ''}
|
|
# another invalid id
|
|
youtube_str = ',::,:,,'
|
|
output = VideoBlock._parse_youtube(youtube_str)
|
|
assert output == {'0.75': '', '1.00': '', '1.25': '', '1.50': ''}
|
|
|
|
# and another one, partially invalid
|
|
youtube_str = '0.75_BAD!!!,1.0:AXdE34_U,1.25:KLHF9K_Y,1.5:VO3SxfeD,'
|
|
output = VideoBlock._parse_youtube(youtube_str)
|
|
assert output == {'0.75': '', '1.00': 'AXdE34_U', '1.25': 'KLHF9K_Y', '1.50': 'VO3SxfeD'}
|
|
|
|
def test_parse_youtube_key_format(self):
|
|
"""
|
|
Make sure that inconsistent speed keys are parsed correctly.
|
|
"""
|
|
youtube_str = '1.00:p2Q6BrNhdh8'
|
|
youtube_str_hack = '1.0:p2Q6BrNhdh8'
|
|
assert VideoBlock._parse_youtube(youtube_str) == VideoBlock._parse_youtube(youtube_str_hack)
|
|
|
|
def test_parse_youtube_empty(self):
|
|
"""
|
|
Some courses have empty youtube attributes, so we should handle
|
|
that well.
|
|
"""
|
|
assert VideoBlock._parse_youtube('') == {'0.75': '', '1.00': '', '1.25': '', '1.50': ''}
|
|
|
|
|
|
class VideoBlockTestBase(unittest.TestCase):
|
|
"""
|
|
Base class for tests for VideoBlock
|
|
"""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.block = instantiate_block()
|
|
|
|
def assertXmlEqual(self, expected, xml):
|
|
"""
|
|
Assert that the given XML fragments have the same attributes, text, and
|
|
(recursively) children
|
|
"""
|
|
def get_child_tags(elem):
|
|
"""Extract the list of tag names for children of elem"""
|
|
return [child.tag for child in elem]
|
|
|
|
for attr in ['tag', 'attrib', 'text', 'tail']:
|
|
expected_attr = getattr(expected, attr)
|
|
actual_attr = getattr(xml, attr)
|
|
assert expected_attr == actual_attr
|
|
|
|
assert get_child_tags(expected) == get_child_tags(xml)
|
|
for left, right in zip(expected, xml):
|
|
self.assertXmlEqual(left, right)
|
|
|
|
|
|
class TestCreateYoutubeString(VideoBlockTestBase):
|
|
"""
|
|
Checks that create_youtube_string correcty extracts information from Video block.
|
|
"""
|
|
|
|
def test_create_youtube_string(self):
|
|
"""
|
|
Test that Youtube ID strings are correctly created when writing back out to XML.
|
|
"""
|
|
self.block.youtube_id_0_75 = 'izygArpw-Qo'
|
|
self.block.youtube_id_1_0 = 'p2Q6BrNhdh8'
|
|
self.block.youtube_id_1_25 = '1EeWXzPdhSA'
|
|
self.block.youtube_id_1_5 = 'rABDYkeK0x8'
|
|
expected = "0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"
|
|
assert create_youtube_string(self.block) == expected
|
|
|
|
def test_create_youtube_string_missing(self):
|
|
"""
|
|
Test that Youtube IDs which aren't explicitly set aren't included in the output string.
|
|
"""
|
|
self.block.youtube_id_0_75 = 'izygArpw-Qo'
|
|
self.block.youtube_id_1_0 = 'p2Q6BrNhdh8'
|
|
self.block.youtube_id_1_25 = '1EeWXzPdhSA'
|
|
expected = "0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA"
|
|
assert create_youtube_string(self.block) == expected
|
|
|
|
|
|
class TestCreateYouTubeUrl(VideoBlockTestBase):
|
|
"""
|
|
Tests for helper method `create_youtube_url`.
|
|
"""
|
|
|
|
def test_create_youtube_url_unicode(self):
|
|
"""
|
|
Test that passing unicode to `create_youtube_url` doesn't throw
|
|
an error.
|
|
"""
|
|
self.block.create_youtube_url("üñîçø∂é")
|
|
|
|
|
|
@ddt.ddt
|
|
class VideoBlockImportTestCase(TestCase):
|
|
"""
|
|
Make sure that VideoBlock can import an old XML-based video correctly.
|
|
"""
|
|
|
|
def assert_attributes_equal(self, video, attrs):
|
|
"""
|
|
Assert that `video` has the correct attributes. `attrs` is a map of {metadata_field: value}.
|
|
"""
|
|
for key, value in attrs.items():
|
|
assert getattr(video, key) == value
|
|
|
|
def test_constructor(self):
|
|
sample_xml = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
download_track="true"
|
|
download_video="true"
|
|
start_time="00:00:01"
|
|
end_time="00: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"/>
|
|
<handout src="http://www.example.com/handout"/>
|
|
<transcript language="ua" src="ukrainian_translation.srt" />
|
|
<transcript language="ge" src="german_translation.srt" />
|
|
</video>
|
|
'''
|
|
block = instantiate_block(data=sample_xml)
|
|
self.assert_attributes_equal(block, {
|
|
'youtube_id_0_75': 'izygArpw-Qo',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': 'rABDYkeK0x8',
|
|
'download_video': True,
|
|
'show_captions': False,
|
|
'start_time': datetime.timedelta(seconds=1),
|
|
'end_time': datetime.timedelta(seconds=60),
|
|
'track': 'http://www.example.com/track',
|
|
'handout': 'http://www.example.com/handout',
|
|
'download_track': True,
|
|
'html5_sources': ['http://www.example.com/source.mp4', 'http://www.example.com/source.ogg'],
|
|
'data': '',
|
|
'transcripts': {'ua': 'ukrainian_translation.srt', 'ge': 'german_translation.srt'}
|
|
})
|
|
|
|
def test_parse_xml(self):
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
download_track="false"
|
|
start_time="00:00:01"
|
|
download_video="false"
|
|
end_time="00:01:00">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<track src="http://www.example.com/track"/>
|
|
<handout src="http://www.example.com/handout"/>
|
|
<transcript language="uk" src="ukrainian_translation.srt" />
|
|
<transcript language="de" src="german_translation.srt" />
|
|
</video>
|
|
'''
|
|
xml_object = etree.fromstring(xml_data)
|
|
output = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(output, {
|
|
'youtube_id_0_75': 'izygArpw-Qo',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': 'rABDYkeK0x8',
|
|
'show_captions': False,
|
|
'start_time': datetime.timedelta(seconds=1),
|
|
'end_time': datetime.timedelta(seconds=60),
|
|
'track': 'http://www.example.com/track',
|
|
'handout': 'http://www.example.com/handout',
|
|
'download_track': False,
|
|
'download_video': False,
|
|
'html5_sources': ['http://www.example.com/source.mp4'],
|
|
'data': '',
|
|
'transcripts': {'uk': 'ukrainian_translation.srt', 'de': 'german_translation.srt'},
|
|
})
|
|
|
|
@XBlockAside.register_temp_plugin(AsideTestType, "test_aside")
|
|
@patch('xmodule.video_block.video_block.VideoBlock.load_file')
|
|
@patch('xmodule.video_block.video_block.is_pointer_tag')
|
|
@ddt.data(True, False)
|
|
def test_parse_xml_with_asides(self, video_xml_has_aside, mock_is_pointer_tag, mock_load_file):
|
|
"""Test that `parse_xml` parses asides from the video xml"""
|
|
runtime = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
if video_xml_has_aside:
|
|
xml_data = '''
|
|
<video url_name="a16643fa63234fef8f6ebbc1902e2253">
|
|
<test_aside xblock-family="xblock_asides.v1" data_field="aside parsed"/>
|
|
</video>
|
|
'''
|
|
else:
|
|
xml_data = '''
|
|
<video url_name="a16643fa63234fef8f6ebbc1902e2253">
|
|
</video>
|
|
'''
|
|
mock_is_pointer_tag.return_value = True
|
|
xml_object = etree.fromstring(xml_data)
|
|
mock_load_file.return_value = xml_object
|
|
output = VideoBlock.parse_xml(xml_object, runtime, None)
|
|
aside = runtime.get_aside_of_type(output, "test_aside")
|
|
if video_xml_has_aside:
|
|
assert aside.content == "default_content"
|
|
assert aside.data_field == "aside parsed"
|
|
else:
|
|
assert aside.content == "default_content"
|
|
assert aside.data_field == "default_data"
|
|
|
|
@ddt.data(
|
|
('course-v1:test_org+test_course+test_run',
|
|
'/asset-v1:test_org+test_course+test_run+type@asset+block@test.png'),
|
|
('test_org/test_course/test_run', '/c4x/test_org/test_course/asset/test.png')
|
|
)
|
|
@ddt.unpack
|
|
def test_parse_xml_when_handout_is_course_asset(self, course_id_string, expected_handout_link):
|
|
"""
|
|
Test that if handout link is course_asset then it will contain targeted course_id in handout link.
|
|
"""
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
course_id = CourseKey.from_string(course_id_string)
|
|
xml_data = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
download_track="false"
|
|
start_time="00:00:01"
|
|
download_video="false"
|
|
end_time="00:01:00">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<track src="http://www.example.com/track"/>
|
|
<handout src="/asset-v1:test_org_1+test_course_1+test_run_1+type@asset+block@test.png"/>
|
|
<transcript language="uk" src="ukrainian_translation.srt" />
|
|
<transcript language="de" src="german_translation.srt" />
|
|
</video>
|
|
'''
|
|
xml_object = etree.fromstring(xml_data)
|
|
module_system.id_generator.target_course_id = course_id
|
|
|
|
output = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(output, {
|
|
'youtube_id_0_75': 'izygArpw-Qo',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': 'rABDYkeK0x8',
|
|
'show_captions': False,
|
|
'start_time': datetime.timedelta(seconds=1),
|
|
'end_time': datetime.timedelta(seconds=60),
|
|
'track': 'http://www.example.com/track',
|
|
'handout': expected_handout_link,
|
|
'download_track': False,
|
|
'download_video': False,
|
|
'html5_sources': ['http://www.example.com/source.mp4'],
|
|
'data': '',
|
|
'transcripts': {'uk': 'ukrainian_translation.srt', 'de': 'german_translation.srt'},
|
|
})
|
|
|
|
def test_parse_xml_missing_attributes(self):
|
|
"""
|
|
Ensure that attributes have the right values if they aren't
|
|
explicitly set in XML.
|
|
"""
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA"
|
|
show_captions="true">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
</video>
|
|
'''
|
|
xml_object = etree.fromstring(xml_data)
|
|
output = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(output, {
|
|
'youtube_id_0_75': '',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': '',
|
|
'show_captions': True,
|
|
'start_time': datetime.timedelta(seconds=0.0),
|
|
'end_time': datetime.timedelta(seconds=0.0),
|
|
'track': '',
|
|
'handout': None,
|
|
'download_track': False,
|
|
'download_video': False,
|
|
'html5_sources': ['http://www.example.com/source.mp4'],
|
|
'data': ''
|
|
})
|
|
|
|
def test_parse_xml_missing_download_track(self):
|
|
"""
|
|
Ensure that attributes have the right values if they aren't
|
|
explicitly set in XML.
|
|
"""
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = '''
|
|
<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"/>
|
|
</video>
|
|
'''
|
|
xml_object = etree.fromstring(xml_data)
|
|
output = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(output, {
|
|
'youtube_id_0_75': '',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': '',
|
|
'show_captions': True,
|
|
'start_time': datetime.timedelta(seconds=0.0),
|
|
'end_time': datetime.timedelta(seconds=0.0),
|
|
'track': 'http://www.example.com/track',
|
|
'download_track': True,
|
|
'download_video': False,
|
|
'html5_sources': ['http://www.example.com/source.mp4'],
|
|
'data': '',
|
|
'transcripts': {},
|
|
})
|
|
|
|
def test_parse_xml_no_attributes(self):
|
|
"""
|
|
Make sure settings are correct if none are explicitly set in XML.
|
|
"""
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = '<video></video>'
|
|
xml_object = etree.fromstring(xml_data)
|
|
output = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(output, {
|
|
'youtube_id_0_75': '',
|
|
'youtube_id_1_0': '3_yD_cEKoCk',
|
|
'youtube_id_1_25': '',
|
|
'youtube_id_1_5': '',
|
|
'show_captions': True,
|
|
'start_time': datetime.timedelta(seconds=0.0),
|
|
'end_time': datetime.timedelta(seconds=0.0),
|
|
'track': '',
|
|
'handout': None,
|
|
'download_track': False,
|
|
'download_video': False,
|
|
'html5_sources': [],
|
|
'data': '',
|
|
'transcripts': {},
|
|
})
|
|
|
|
def test_parse_xml_double_quotes(self):
|
|
"""
|
|
Make sure we can handle the double-quoted string format (which was used for exporting for
|
|
a few weeks).
|
|
"""
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = '''
|
|
<video display_name=""display_name""
|
|
html5_sources="["source_1", "source_2"]"
|
|
show_captions="false"
|
|
download_video="true"
|
|
sub=""html5_subtitles""
|
|
track=""http://www.example.com/track""
|
|
handout=""http://www.example.com/handout""
|
|
download_track="true"
|
|
youtube_id_0_75=""OEoXaMPEzf65""
|
|
youtube_id_1_25=""OEoXaMPEzf125""
|
|
youtube_id_1_5=""OEoXaMPEzf15""
|
|
youtube_id_1_0=""OEoXaMPEzf10""
|
|
/>
|
|
'''
|
|
xml_object = etree.fromstring(xml_data)
|
|
output = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(output, {
|
|
'youtube_id_0_75': 'OEoXaMPEzf65',
|
|
'youtube_id_1_0': 'OEoXaMPEzf10',
|
|
'youtube_id_1_25': 'OEoXaMPEzf125',
|
|
'youtube_id_1_5': 'OEoXaMPEzf15',
|
|
'show_captions': False,
|
|
'start_time': datetime.timedelta(seconds=0.0),
|
|
'end_time': datetime.timedelta(seconds=0.0),
|
|
'track': 'http://www.example.com/track',
|
|
'handout': 'http://www.example.com/handout',
|
|
'download_track': True,
|
|
'download_video': True,
|
|
'html5_sources': ["source_1", "source_2"],
|
|
'data': ''
|
|
})
|
|
|
|
def test_parse_xml_double_quote_concatenated_youtube(self):
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:"p2Q6BrNhdh8",1.25:"1EeWXzPdhSA"">
|
|
</video>
|
|
'''
|
|
xml_object = etree.fromstring(xml_data)
|
|
output = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(output, {
|
|
'youtube_id_0_75': '',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': '',
|
|
'show_captions': True,
|
|
'start_time': datetime.timedelta(seconds=0.0),
|
|
'end_time': datetime.timedelta(seconds=0.0),
|
|
'track': '',
|
|
'handout': None,
|
|
'download_track': False,
|
|
'download_video': False,
|
|
'html5_sources': [],
|
|
'data': ''
|
|
})
|
|
|
|
def test_old_video_format(self):
|
|
"""
|
|
Test backwards compatibility with VideoBlock's XML format.
|
|
"""
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = """
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
source="http://www.example.com/source.mp4"
|
|
from="00:00:01"
|
|
to="00:01:00">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<track src="http://www.example.com/track"/>
|
|
</video>
|
|
"""
|
|
xml_object = etree.fromstring(xml_data)
|
|
output = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(output, {
|
|
'youtube_id_0_75': 'izygArpw-Qo',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': 'rABDYkeK0x8',
|
|
'show_captions': False,
|
|
'start_time': datetime.timedelta(seconds=1),
|
|
'end_time': datetime.timedelta(seconds=60),
|
|
'track': 'http://www.example.com/track',
|
|
# 'download_track': True,
|
|
'html5_sources': ['http://www.example.com/source.mp4'],
|
|
'data': '',
|
|
})
|
|
|
|
def test_old_video_data(self):
|
|
"""
|
|
Ensure that Video is able to read VideoBlock's model data.
|
|
"""
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = """
|
|
<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"/>
|
|
</video>
|
|
"""
|
|
xml_object = etree.fromstring(xml_data)
|
|
video = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(video, {
|
|
'youtube_id_0_75': 'izygArpw-Qo',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': 'rABDYkeK0x8',
|
|
'show_captions': False,
|
|
'start_time': datetime.timedelta(seconds=1),
|
|
'end_time': datetime.timedelta(seconds=60),
|
|
'track': 'http://www.example.com/track',
|
|
# 'download_track': True,
|
|
'html5_sources': ['http://www.example.com/source.mp4'],
|
|
'data': ''
|
|
})
|
|
|
|
def test_import_with_float_times(self):
|
|
"""
|
|
Ensure that Video is able to read VideoBlock's model data.
|
|
"""
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
xml_data = """
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
from="1.0"
|
|
to="60.0">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<track src="http://www.example.com/track"/>
|
|
</video>
|
|
"""
|
|
xml_object = etree.fromstring(xml_data)
|
|
video = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
self.assert_attributes_equal(video, {
|
|
'youtube_id_0_75': 'izygArpw-Qo',
|
|
'youtube_id_1_0': 'p2Q6BrNhdh8',
|
|
'youtube_id_1_25': '1EeWXzPdhSA',
|
|
'youtube_id_1_5': 'rABDYkeK0x8',
|
|
'show_captions': False,
|
|
'start_time': datetime.timedelta(seconds=1),
|
|
'end_time': datetime.timedelta(seconds=60),
|
|
'track': 'http://www.example.com/track',
|
|
# 'download_track': True,
|
|
'html5_sources': ['http://www.example.com/source.mp4'],
|
|
'data': ''
|
|
})
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api')
|
|
def test_import_val_data(self, mock_val_api):
|
|
"""
|
|
Test that `parse_xml` works method works as expected.
|
|
"""
|
|
def mock_val_import(xml, edx_video_id, resource_fs, static_dir, external_transcripts, course_id):
|
|
"""Mock edxval.api.import_parse_xml"""
|
|
assert xml.tag == 'video_asset'
|
|
assert dict(list(xml.items())) == {'mock_attr': ''}
|
|
assert edx_video_id == 'test_edx_video_id'
|
|
assert static_dir == EXPORT_IMPORT_STATIC_DIR
|
|
assert resource_fs is not None
|
|
assert external_transcripts == {'en': ['subs_3_yD_cEKoCk.srt.sjson']}
|
|
assert course_id == 'test_course_id'
|
|
return edx_video_id
|
|
|
|
edx_video_id = 'test_edx_video_id'
|
|
mock_val_api.import_from_xml = Mock(wraps=mock_val_import)
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
|
|
# Create static directory in import file system and place transcript files inside it.
|
|
module_system.resources_fs.makedirs(EXPORT_IMPORT_STATIC_DIR, recreate=True)
|
|
|
|
# import new edx_video_id
|
|
xml_data = """
|
|
<video edx_video_id="{edx_video_id}">
|
|
<video_asset mock_attr=""/>
|
|
</video>
|
|
""".format(
|
|
edx_video_id=edx_video_id
|
|
)
|
|
xml_object = etree.fromstring(xml_data)
|
|
module_system.id_generator.target_course_id = 'test_course_id'
|
|
video = VideoBlock.parse_xml(xml_object, module_system, None)
|
|
|
|
self.assert_attributes_equal(video, {'edx_video_id': edx_video_id})
|
|
mock_val_api.import_from_xml.assert_called_once_with(
|
|
ANY,
|
|
edx_video_id,
|
|
module_system.resources_fs,
|
|
EXPORT_IMPORT_STATIC_DIR,
|
|
{'en': ['subs_3_yD_cEKoCk.srt.sjson']},
|
|
course_id='test_course_id'
|
|
)
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api')
|
|
def test_import_val_data_invalid(self, mock_val_api):
|
|
mock_val_api.ValCannotCreateError = _MockValCannotCreateError
|
|
mock_val_api.import_from_xml = Mock(side_effect=mock_val_api.ValCannotCreateError)
|
|
module_system = DummyModuleStoreRuntime(load_error_blocks=True)
|
|
|
|
# Negative duration is invalid
|
|
xml_data = """
|
|
<video edx_video_id="test_edx_video_id">
|
|
<video_asset client_video_id="test_client_video_id" duration="-1"/>
|
|
</video>
|
|
"""
|
|
xml_object = etree.fromstring(xml_data)
|
|
with pytest.raises(mock_val_api.ValCannotCreateError):
|
|
VideoBlock.parse_xml(xml_object, module_system, None)
|
|
|
|
|
|
class VideoExportTestCase(VideoBlockTestBase):
|
|
"""
|
|
Make sure that VideoBlock can export itself to XML correctly.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.temp_dir = mkdtemp()
|
|
self.file_system = OSFS(self.temp_dir)
|
|
self.addCleanup(shutil.rmtree, self.temp_dir)
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api')
|
|
def test_export_to_xml(self, mock_val_api):
|
|
"""
|
|
Test that we write the correct XML on export.
|
|
"""
|
|
edx_video_id = 'test_edx_video_id'
|
|
mock_val_api.export_to_xml = Mock(
|
|
return_value=dict(
|
|
xml=etree.Element('video_asset'),
|
|
transcripts={}
|
|
)
|
|
)
|
|
self.block.youtube_id_0_75 = 'izygArpw-Qo'
|
|
self.block.youtube_id_1_0 = 'p2Q6BrNhdh8'
|
|
self.block.youtube_id_1_25 = '1EeWXzPdhSA'
|
|
self.block.youtube_id_1_5 = 'rABDYkeK0x8'
|
|
self.block.show_captions = False
|
|
self.block.start_time = datetime.timedelta(seconds=1.0)
|
|
self.block.end_time = datetime.timedelta(seconds=60)
|
|
self.block.track = 'http://www.example.com/track'
|
|
self.block.handout = 'http://www.example.com/handout'
|
|
self.block.download_track = True
|
|
self.block.html5_sources = ['http://www.example.com/source.mp4', 'http://www.example.com/source1.ogg']
|
|
self.block.download_video = True
|
|
self.block.transcripts = {'ua': 'ukrainian_translation.srt', 'ge': 'german_translation.srt'}
|
|
self.block.edx_video_id = edx_video_id
|
|
|
|
xml = self.block.definition_to_xml(self.file_system)
|
|
parser = etree.XMLParser(remove_blank_text=True)
|
|
xml_string = '''\
|
|
<video
|
|
url_name="SampleProblem"
|
|
start_time="0:00:01"
|
|
show_captions="false"
|
|
end_time="0:01:00"
|
|
download_video="true"
|
|
download_track="true"
|
|
youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"
|
|
transcripts='{"ge": "german_translation.srt", "ua": "ukrainian_translation.srt"}'
|
|
>
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<source src="http://www.example.com/source1.ogg"/>
|
|
<track src="http://www.example.com/track"/>
|
|
<handout src="http://www.example.com/handout"/>
|
|
<video_asset />
|
|
<transcript language="ge" src="german_translation.srt" />
|
|
<transcript language="ua" src="ukrainian_translation.srt" />
|
|
</video>
|
|
'''
|
|
expected = etree.XML(xml_string, parser=parser)
|
|
self.assertXmlEqual(expected, xml)
|
|
mock_val_api.export_to_xml.assert_called_once_with(
|
|
video_id=edx_video_id,
|
|
static_dir=EXPORT_IMPORT_STATIC_DIR,
|
|
resource_fs=self.file_system,
|
|
course_id=self.block.scope_ids.usage_id.context_key,
|
|
)
|
|
|
|
def test_export_to_xml_without_video_id(self):
|
|
"""
|
|
Test that we write the correct XML on export of a video without edx_video_id.
|
|
"""
|
|
self.block.youtube_id_0_75 = 'izygArpw-Qo'
|
|
self.block.youtube_id_1_0 = 'p2Q6BrNhdh8'
|
|
self.block.youtube_id_1_25 = '1EeWXzPdhSA'
|
|
self.block.youtube_id_1_5 = 'rABDYkeK0x8'
|
|
self.block.show_captions = False
|
|
self.block.start_time = datetime.timedelta(seconds=1.0)
|
|
self.block.end_time = datetime.timedelta(seconds=60)
|
|
self.block.track = 'http://www.example.com/track'
|
|
self.block.handout = 'http://www.example.com/handout'
|
|
self.block.download_track = True
|
|
self.block.html5_sources = ['http://www.example.com/source.mp4', 'http://www.example.com/source1.ogg']
|
|
self.block.download_video = True
|
|
self.block.transcripts = {'ua': 'ukrainian_translation.srt', 'ge': 'german_translation.srt'}
|
|
|
|
xml = self.block.definition_to_xml(self.file_system)
|
|
parser = etree.XMLParser(remove_blank_text=True)
|
|
xml_string = '''\
|
|
<video
|
|
url_name="SampleProblem"
|
|
start_time="0:00:01"
|
|
show_captions="false"
|
|
end_time="0:01:00"
|
|
download_video="true"
|
|
download_track="true"
|
|
youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"
|
|
transcripts='{"ge": "german_translation.srt", "ua": "ukrainian_translation.srt"}'
|
|
>
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<source src="http://www.example.com/source1.ogg"/>
|
|
<track src="http://www.example.com/track"/>
|
|
<handout src="http://www.example.com/handout"/>
|
|
<transcript language="ge" src="german_translation.srt" />
|
|
<transcript language="ua" src="ukrainian_translation.srt" />
|
|
</video>
|
|
'''
|
|
expected = etree.XML(xml_string, parser=parser)
|
|
self.assertXmlEqual(expected, xml)
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api')
|
|
def test_export_to_xml_val_error(self, mock_val_api):
|
|
# Export should succeed without VAL data if video does not exist
|
|
mock_val_api.ValVideoNotFoundError = _MockValVideoNotFoundError
|
|
mock_val_api.export_to_xml = Mock(side_effect=mock_val_api.ValVideoNotFoundError)
|
|
self.block.edx_video_id = 'test_edx_video_id'
|
|
|
|
xml = self.block.definition_to_xml(self.file_system)
|
|
parser = etree.XMLParser(remove_blank_text=True)
|
|
xml_string = '<video youtube="1.00:3_yD_cEKoCk" url_name="SampleProblem"/>'
|
|
expected = etree.XML(xml_string, parser=parser)
|
|
self.assertXmlEqual(expected, xml)
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api', None)
|
|
def test_export_to_xml_empty_end_time(self):
|
|
"""
|
|
Test that we write the correct XML on export.
|
|
"""
|
|
self.block.youtube_id_0_75 = 'izygArpw-Qo'
|
|
self.block.youtube_id_1_0 = 'p2Q6BrNhdh8'
|
|
self.block.youtube_id_1_25 = '1EeWXzPdhSA'
|
|
self.block.youtube_id_1_5 = 'rABDYkeK0x8'
|
|
self.block.show_captions = False
|
|
self.block.start_time = datetime.timedelta(seconds=5.0)
|
|
self.block.end_time = datetime.timedelta(seconds=0.0)
|
|
self.block.track = 'http://www.example.com/track'
|
|
self.block.download_track = True
|
|
self.block.html5_sources = ['http://www.example.com/source.mp4', 'http://www.example.com/source.ogg']
|
|
self.block.download_video = True
|
|
|
|
xml = self.block.definition_to_xml(self.file_system)
|
|
parser = etree.XMLParser(remove_blank_text=True)
|
|
xml_string = '''\
|
|
<video url_name="SampleProblem" start_time="0:00:05" youtube="0.75:izygArpw-Qo,1.00:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8" show_captions="false" download_video="true" download_track="true">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<source src="http://www.example.com/source.ogg"/>
|
|
<track src="http://www.example.com/track"/>
|
|
</video>
|
|
'''
|
|
expected = etree.XML(xml_string, parser=parser)
|
|
self.assertXmlEqual(expected, xml)
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api', None)
|
|
def test_export_to_xml_empty_parameters(self):
|
|
"""
|
|
Test XML export with defaults.
|
|
"""
|
|
xml = self.block.definition_to_xml(self.file_system)
|
|
# Check that download_video field is also set to default (False) in xml for backward compatibility
|
|
expected = '<video youtube="1.00:3_yD_cEKoCk" url_name="SampleProblem"/>\n'
|
|
assert expected == etree.tostring(xml, pretty_print=True).decode('utf-8')
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api', None)
|
|
def test_export_to_xml_with_transcripts_as_none(self):
|
|
"""
|
|
Test XML export with transcripts being overridden to None.
|
|
"""
|
|
self.block.transcripts = None
|
|
xml = self.block.definition_to_xml(self.file_system)
|
|
expected = b'<video youtube="1.00:3_yD_cEKoCk" url_name="SampleProblem"/>\n'
|
|
assert expected == etree.tostring(xml, pretty_print=True)
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api', None)
|
|
def test_export_to_xml_invalid_characters_in_attributes(self):
|
|
"""
|
|
Test XML export will *not* raise TypeError by lxml library if contains illegal characters.
|
|
The illegal characters in a String field are removed from the string instead.
|
|
"""
|
|
self.block.display_name = 'Display\x1eName'
|
|
xml = self.block.definition_to_xml(self.file_system)
|
|
assert xml.get('display_name') == 'DisplayName'
|
|
|
|
@patch('xmodule.video_block.video_block.edxval_api', None)
|
|
def test_export_to_xml_unicode_characters(self):
|
|
"""
|
|
Test XML export handles the unicode characters.
|
|
"""
|
|
self.block.display_name = '这是文'
|
|
xml = self.block.definition_to_xml(self.file_system)
|
|
assert xml.get('display_name') == '这是文'
|
|
|
|
|
|
@ddt.ddt
|
|
@patch.object(settings, 'FEATURES', create=True, new={
|
|
'FALLBACK_TO_ENGLISH_TRANSCRIPTS': False,
|
|
})
|
|
class VideoBlockStudentViewDataTestCase(unittest.TestCase):
|
|
"""
|
|
Make sure that VideoBlock returns the expected student_view_data.
|
|
"""
|
|
|
|
VIDEO_URL_1 = 'http://www.example.com/source_low.mp4'
|
|
VIDEO_URL_2 = 'http://www.example.com/source_med.mp4'
|
|
VIDEO_URL_3 = 'http://www.example.com/source_high.mp4'
|
|
|
|
@ddt.data(
|
|
# Ensure no extra data is returned if video block configured only for web display.
|
|
(
|
|
{'only_on_web': True},
|
|
{'only_on_web': True},
|
|
),
|
|
# Ensure that YouTube URLs are included in `encoded_videos`, but not `all_sources`.
|
|
(
|
|
{
|
|
'only_on_web': False,
|
|
'youtube_id_1_0': 'abc',
|
|
'html5_sources': [VIDEO_URL_2, VIDEO_URL_3],
|
|
},
|
|
{
|
|
'only_on_web': False,
|
|
'duration': None,
|
|
'transcripts': {},
|
|
'encoded_videos': {
|
|
'fallback': {'url': VIDEO_URL_2, 'file_size': 0},
|
|
'youtube': {'url': 'https://www.youtube.com/watch?v=abc', 'file_size': 0},
|
|
},
|
|
'all_sources': [VIDEO_URL_2, VIDEO_URL_3],
|
|
},
|
|
),
|
|
)
|
|
@ddt.unpack
|
|
def test_student_view_data(self, field_data, expected_student_view_data):
|
|
"""
|
|
Ensure that student_view_data returns the expected results for video blocks.
|
|
"""
|
|
block = instantiate_block(**field_data)
|
|
student_view_data = block.student_view_data()
|
|
assert student_view_data == expected_student_view_data
|
|
|
|
@patch(
|
|
'openedx.core.djangoapps.video_config.services.VideoConfigService.is_hls_playback_enabled',
|
|
Mock(return_value=True)
|
|
)
|
|
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_available_transcript_languages',
|
|
Mock(return_value=['es']))
|
|
@patch('edxval.api.get_video_info_for_course_and_profiles', Mock(return_value={}))
|
|
@patch('openedx.core.djangoapps.video_config.transcripts_utils.get_video_transcript_content')
|
|
@patch('edxval.api.get_video_info')
|
|
def test_student_view_data_with_hls_flag(self, mock_get_video_info, mock_get_video_transcript_content):
|
|
mock_get_video_info.return_value = {
|
|
'url': '/edxval/video/example',
|
|
'edx_video_id': 'example_id',
|
|
'duration': 111.0,
|
|
'client_video_id': 'The example video',
|
|
'encoded_videos': [
|
|
{
|
|
'url': 'http://www.meowmix.com',
|
|
'file_size': 25556,
|
|
'bitrate': 9600,
|
|
'profile': 'hls'
|
|
}
|
|
]
|
|
}
|
|
|
|
mock_get_video_transcript_content.return_value = {
|
|
'content': json.dumps({
|
|
"start": [10],
|
|
"end": [100],
|
|
"text": ["Hi, welcome to Edx."],
|
|
}),
|
|
'file_name': 'edx.sjson'
|
|
}
|
|
|
|
block = instantiate_block(edx_video_id='example_id', only_on_web=False)
|
|
block.runtime.handler_url = MagicMock()
|
|
student_view_data = block.student_view_data()
|
|
expected_video_data = {'hls': {'url': 'http://www.meowmix.com', 'file_size': 25556}}
|
|
self.assertDictEqual(student_view_data.get('encoded_videos'), expected_video_data)
|
|
|
|
|
|
@ddt.ddt
|
|
@patch.object(settings, 'YOUTUBE', create=True, new={
|
|
# YouTube JavaScript API
|
|
'API': 'www.youtube.com/iframe_api',
|
|
|
|
# URL to get YouTube metadata
|
|
'METADATA_URL': 'www.googleapis.com/youtube/v3/videos/',
|
|
|
|
# Current youtube api for requesting transcripts.
|
|
# For example: http://video.google.com/timedtext?lang=en&v=j_jEn79vS3g.
|
|
'TEXT_API': {
|
|
'url': 'video.google.com/timedtext',
|
|
'params': {
|
|
'lang': 'en',
|
|
'v': 'set_youtube_id_of_11_symbols_here',
|
|
},
|
|
},
|
|
|
|
# Current web page mechanism for scraping transcript information from youtube video pages
|
|
'TRANSCRIPTS': {
|
|
'CAPTION_TRACKS_REGEX': r"captionTracks\"\:\[(?P<caption_tracks>[^\]]+)",
|
|
'YOUTUBE_URL_BASE': 'https://www.youtube.com/watch?v=',
|
|
}
|
|
})
|
|
@patch.object(settings, 'CONTENTSTORE', create=True, new={
|
|
'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',
|
|
'DOC_STORE_CONFIG': {
|
|
'host': 'localhost',
|
|
'db': 'test_xcontent_%s' % uuid4().hex,
|
|
},
|
|
# allow for additional options that can be keyed on a name, e.g. 'trashcan'
|
|
'ADDITIONAL_OPTIONS': {
|
|
'trashcan': {
|
|
'bucket': 'trash_fs'
|
|
}
|
|
}
|
|
})
|
|
@patch.object(settings, 'FEATURES', create=True, new={
|
|
# The default value in {lms,cms}/envs/common.py and xmodule/tests/test_video.py should be consistent.
|
|
'FALLBACK_TO_ENGLISH_TRANSCRIPTS': True,
|
|
})
|
|
class VideoBlockIndexingTestCase(unittest.TestCase):
|
|
"""
|
|
Make sure that VideoBlock can format data for indexing as expected.
|
|
"""
|
|
|
|
def test_video_with_no_subs_index_dictionary(self):
|
|
"""
|
|
Test index dictionary of a video block without subtitles.
|
|
"""
|
|
xml_data = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
download_track="false"
|
|
start_time="00:00:01"
|
|
download_video="false"
|
|
end_time="00:01:00">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<track src="http://www.example.com/track"/>
|
|
<handout src="http://www.example.com/handout"/>
|
|
</video>
|
|
'''
|
|
block = instantiate_block(data=xml_data)
|
|
assert block.index_dictionary() == {'content': {'display_name': 'Test Video'}, 'content_type': 'Video'}
|
|
|
|
def test_video_with_multiple_transcripts_index_dictionary(self):
|
|
"""
|
|
Test index dictionary of a video block with
|
|
two transcripts uploaded by a user.
|
|
"""
|
|
xml_data_transcripts = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
download_track="false"
|
|
start_time="00:00:01"
|
|
download_video="false"
|
|
end_time="00:01:00">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<track src="http://www.example.com/track"/>
|
|
<handout src="http://www.example.com/handout"/>
|
|
<transcript language="ge" src="subs_grmtran1.srt" />
|
|
<transcript language="hr" src="subs_croatian1.srt" />
|
|
</video>
|
|
'''
|
|
|
|
block = instantiate_block(data=xml_data_transcripts)
|
|
save_to_store(SRT_FILEDATA, "subs_grmtran1.srt", 'text/srt', block.location)
|
|
save_to_store(CRO_SRT_FILEDATA, "subs_croatian1.srt", 'text/srt', block.location)
|
|
assert block.index_dictionary() ==\
|
|
{'content': {'display_name': 'Test Video',
|
|
'transcript_ge': 'sprechen sie deutsch? Ja, ich spreche Deutsch',
|
|
'transcript_hr': 'Dobar dan! Kako ste danas?'}, 'content_type': 'Video'}
|
|
|
|
@override_settings(ALL_LANGUAGES=ALL_LANGUAGES)
|
|
def test_video_with_language_do_not_have_transcripts_translation(self):
|
|
"""
|
|
Test translation retrieval of a video block with
|
|
a language having no transcripts uploaded by a user.
|
|
"""
|
|
xml_data_transcripts = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
download_track="false"
|
|
start_time="00:00:01"
|
|
download_video="false"
|
|
end_time="00:01:00">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<track src="http://www.example.com/track"/>
|
|
<handout src="http://www.example.com/handout"/>
|
|
<transcript language="ur" src="" />
|
|
</video>
|
|
'''
|
|
block = instantiate_block(data=xml_data_transcripts)
|
|
video_config_service = block.runtime.service(block, 'video_config')
|
|
translations = video_config_service.available_translations(
|
|
block,
|
|
block.get_transcripts_info(),
|
|
verify_assets=False
|
|
)
|
|
assert translations != ['ur']
|
|
|
|
def assert_validation_message(self, validation, expected_msg):
|
|
"""
|
|
Asserts that the validation message has all expected content.
|
|
|
|
Args:
|
|
validation (StudioValidation): A validation object.
|
|
expected_msg (string): An expected validation message.
|
|
"""
|
|
assert not validation.empty
|
|
# Validation contains some warning/message
|
|
assert validation.summary
|
|
assert StudioValidationMessage.WARNING == validation.summary.type
|
|
assert expected_msg in validation.summary.text.replace('Urdu, Esperanto', 'Esperanto, Urdu')
|
|
|
|
@ddt.data(
|
|
(
|
|
'<transcript language="ur" src="" />',
|
|
'There is no transcript file associated with the Urdu language.'
|
|
),
|
|
(
|
|
'<transcript language="eo" src="" /><transcript language="ur" src="" />',
|
|
'There are no transcript files associated with the Esperanto, Urdu languages.'
|
|
),
|
|
)
|
|
@ddt.unpack
|
|
@override_settings(ALL_LANGUAGES=ALL_LANGUAGES)
|
|
def test_no_transcript_validation_message(self, xml_transcripts, expected_validation_msg):
|
|
"""
|
|
Test the validation message when no associated transcript file uploaded.
|
|
"""
|
|
xml_data_transcripts = '''
|
|
<video display_name="Test Video"
|
|
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
|
show_captions="false"
|
|
download_track="false"
|
|
start_time="00:00:01"
|
|
download_video="false"
|
|
end_time="00:01:00">
|
|
<source src="http://www.example.com/source.mp4"/>
|
|
<track src="http://www.example.com/track"/>
|
|
<handout src="http://www.example.com/handout"/>
|
|
{xml_transcripts}
|
|
</video>
|
|
'''.format(xml_transcripts=xml_transcripts)
|
|
block = instantiate_block(data=xml_data_transcripts)
|
|
validation = block.validate()
|
|
self.assert_validation_message(validation, expected_validation_msg)
|
|
|
|
def test_video_transcript_none(self):
|
|
"""
|
|
Test video when transcripts is None.
|
|
"""
|
|
block = instantiate_block(data=None)
|
|
block.transcripts = None
|
|
response = block.get_transcripts_info()
|
|
expected = {'transcripts': {}, 'sub': ''}
|
|
assert expected == response
|