Commit dd20e84c by Alexander Kryklia

Fix donwload subs for non youtube videos and non-en language.

Check for status in test.
Add test for track url.
Fix test to use cookie.
Use download_track in feature file.
Add new tests for get_transcript.
Add html5 test.
Add content check.
Add tests for available_translations dispatch.
Disable flaky cms test.
Add  w/o english transcript tests.
Add to changelog.
parent ac6b9f8e
......@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
Blades: Fix download subs for non youtube videos and non-en language. BLD-897.
Blades: Fix issues related to videos that have separate YouTube IDs for the
different video speeds. BLD-915, BLD-901.
......
......@@ -369,31 +369,32 @@ Feature: CMS Transcripts
And I click transcript button "choose" number 2
And I see value "test_transcripts|t_not_exist" in the field "Transcript (primary)"
# Flaky test fails occasionally in master. https://edx-wiki.atlassian.net/browse/BLD-927
#21
Scenario: Work with 1 field only: Enter HTML5 source with transcripts - save - > change it to another one HTML5 source w/o transcripts - click on use existing - > change it to another one HTML5 source w/o transcripts - click on use existing
Given I have created a Video component with subtitles "t_not_exist"
And I edit the component
And I enter a "t_not_exist.mp4" source to field number 1
Then I see status message "found"
And I see button "download_to_edit"
And I see button "upload_new_timed_transcripts"
And I see value "t_not_exist" in the field "Transcript (primary)"
And I save changes
And I edit the component
And I enter a "video_name_2.mp4" source to field number 1
Then I see status message "use existing"
And I see button "use_existing"
And I click transcript button "use_existing"
And I see value "video_name_2" in the field "Transcript (primary)"
And I enter a "video_name_3.mp4" source to field number 1
Then I see status message "use existing"
And I see button "use_existing"
And I click transcript button "use_existing"
And I see value "video_name_3" in the field "Transcript (primary)"
#Scenario: Work with 1 field only: Enter HTML5 source with transcripts - save - > change it to another one HTML5 source w/o #transcripts - click on use existing - > change it to another one HTML5 source w/o transcripts - click on use existing
# Given I have created a Video component with subtitles "t_not_exist"
# And I edit the component
#
# And I enter a "t_not_exist.mp4" source to field number 1
# Then I see status message "found"
# And I see button "download_to_edit"
# And I see button "upload_new_timed_transcripts"
# And I see value "t_not_exist" in the field "Transcript (primary)"
#
# And I save changes
# And I edit the component
#
# And I enter a "video_name_2.mp4" source to field number 1
# Then I see status message "use existing"
# And I see button "use_existing"
# And I click transcript button "use_existing"
# And I see value "video_name_2" in the field "Transcript (primary)"
#
# And I enter a "video_name_3.mp4" source to field number 1
# Then I see status message "use existing"
# And I see button "use_existing"
# And I click transcript button "use_existing"
# And I see value "video_name_3" in the field "Transcript (primary)"
#22
Scenario: Work with 1 field only: Enter HTML5 source with transcripts - save -> change it to another one HTML5 source w/o transcripts - click on use existing -> change it to another one HTML5 source w/o transcripts - do not click on use existing -> change it to another one HTML5 source w/o transcripts - click on use existing
......
......@@ -487,3 +487,64 @@ class TestYoutubeTranscripts(unittest.TestCase):
transcripts = transcripts_utils.get_transcripts_from_youtube(youtube_id, settings, translation)
self.assertEqual(transcripts, expected_transcripts)
mock_get.assert_called_with('http://video.google.com/timedtext', params={'lang': 'en', 'v': 'good_youtube_id'})
class TestTranscript(unittest.TestCase):
"""
Tests for Transcript class e.g. different transcript conversions.
"""
def setUp(self):
self.srt_transcript = textwrap.dedent("""\
0
00:00:10,500 --> 00:00:13,000
Elephant's Dream
1
00:00:15,000 --> 00:00:18,000
At the left we can see...
""")
self.sjson_transcript = textwrap.dedent("""\
{
"start": [
10500,
15000
],
"end": [
13000,
18000
],
"text": [
"Elephant's Dream",
"At the left we can see..."
]
}
""")
self.txt_transcript = u"Elephant's Dream\nAt the left we can see..."
def test_convert_srt_to_txt(self):
expected = self.txt_transcript
actual = transcripts_utils.Transcript.convert(self.srt_transcript, 'srt', 'txt')
self.assertEqual(actual, expected)
def test_convert_srt_to_srt(self):
expected = self.srt_transcript
actual = transcripts_utils.Transcript.convert(self.srt_transcript, 'srt', 'srt')
self.assertEqual(actual, expected)
def test_convert_sjson_to_txt(self):
expected = self.txt_transcript
actual = transcripts_utils.Transcript.convert(self.sjson_transcript, 'sjson', 'txt')
self.assertEqual(actual, expected)
def test_convert_sjson_to_srt(self):
expected = self.srt_transcript
actual = transcripts_utils.Transcript.convert(self.sjson_transcript, 'sjson', 'srt')
self.assertEqual(actual, expected)
def test_convert_srt_to_sjson(self):
with self.assertRaises(NotImplementedError):
transcripts_utils.Transcript.convert(self.srt_transcript, 'srt', 'sjson')
......@@ -233,8 +233,8 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
end_time="00:01:00">
<source src="http://www.example.com/source.mp4"/>
<track src="http://www.example.com/track"/>
<transcript language="ua" src="ukrainian_translation.srt" />
<transcript language="ge" src="german_translation.srt" />
<transcript language="uk" src="ukrainian_translation.srt" />
<transcript language="de" src="german_translation.srt" />
</video>
'''
output = VideoDescriptor.from_xml(xml_data, module_system, Mock())
......@@ -251,7 +251,7 @@ class VideoDescriptorImportTestCase(unittest.TestCase):
'download_video': False,
'html5_sources': ['http://www.example.com/source.mp4'],
'data': '',
'transcripts': {'ua': 'ukrainian_translation.srt', 'ge': 'german_translation.srt'},
'transcripts': {'uk': 'ukrainian_translation.srt', 'de': 'german_translation.srt'},
})
def test_from_xml_missing_attributes(self):
......
......@@ -9,6 +9,7 @@ import requests
import logging
from pysrt import SubRipTime, SubRipItem, SubRipFile
from lxml import etree
from HTMLParser import HTMLParser
from xmodule.exceptions import NotFoundError
from xmodule.contentstore.content import StaticContent
......@@ -77,7 +78,7 @@ def save_subs_to_store(subs, subs_id, item, language='en'):
filedata = json.dumps(subs, indent=2)
mime_type = 'application/json'
filename = subs_filename(subs_id, language)
content_location = asset_location(item.location, filename)
content_location = Transcript.asset_location(item.location, filename)
content = StaticContent(content_location, filename, mime_type, filedata)
contentstore().save(content)
return content_location
......@@ -193,7 +194,7 @@ def remove_subs_from_store(subs_id, item, lang='en'):
Remove from store, if transcripts content exists.
"""
try:
content = asset(item.location, subs_id, lang)
content = Transcript.asset(item.location, subs_id, lang)
contentstore().delete(content.get_id())
log.info("Removed subs %s from store", subs_id)
except NotFoundError:
......@@ -412,30 +413,6 @@ def subs_filename(subs_id, lang='en'):
return '{0}_subs_{1}.srt.sjson'.format(lang, subs_id)
def asset_location(location, filename):
"""
Return asset location.
`location` is module location.
"""
return StaticContent.compute_location(
location.org, location.course, filename
)
def asset(location, subs_id, lang='en', filename=None):
"""
Get asset from contentstore, asset location is built from subs_id and lang.
`location` is module location.
"""
return contentstore().find(
asset_location(
location,
subs_filename(subs_id, lang) if not filename else filename
)
)
def generate_sjson_for_all_speeds(item, user_filename, result_subs_dict, lang):
"""
......@@ -444,7 +421,7 @@ def generate_sjson_for_all_speeds(item, user_filename, result_subs_dict, lang):
`item` is module object.
"""
try:
srt_transcripts = contentstore().find(asset_location(item.location, user_filename))
srt_transcript = contentstore().find(Transcript.asset_location(item.location, user_filename))
except NotFoundError as ex:
raise TranscriptException("{}: Can't find uploaded transcripts: {}".format(ex.message, user_filename))
......@@ -454,7 +431,7 @@ def generate_sjson_for_all_speeds(item, user_filename, result_subs_dict, lang):
generate_subs_from_source(
result_subs_dict,
os.path.splitext(user_filename)[1][1:],
srt_transcripts.data.decode('utf8'),
srt_transcript.data.decode('utf8'),
item,
lang
)
......@@ -477,8 +454,72 @@ def get_or_create_sjson(item):
user_subs_id = os.path.splitext(user_filename)[0]
source_subs_id, result_subs_dict = user_subs_id, {1.0: user_subs_id}
try:
sjson_transcript = asset(item.location, source_subs_id, item.transcript_language).data
sjson_transcript = Transcript.asset(item.location, source_subs_id, item.transcript_language).data
except (NotFoundError): # generating sjson from srt
generate_sjson_for_all_speeds(item, user_filename, result_subs_dict, item.transcript_language)
sjson_transcript = asset(item.location, source_subs_id, item.transcript_language).data
sjson_transcript = Transcript.asset(item.location, source_subs_id, item.transcript_language).data
return sjson_transcript
class Transcript(object):
"""
Container for transcript methods.
"""
@staticmethod
def convert(content, input_format, output_format):
"""
Convert transcript `content` from `input_format` to `output_format`.
Accepted input formats: sjson, srt.
Accepted output format: srt, txt.
"""
assert input_format in ('srt', 'sjson')
assert output_format in ('txt', 'srt', 'sjson')
if input_format == output_format:
return content
if input_format == 'srt':
if output_format == 'txt':
text = SubRipFile.from_string(content.decode('utf8')).text
return HTMLParser().unescape(text)
elif output_format == 'sjson':
raise NotImplementedError
if input_format == 'sjson':
if output_format == 'txt':
text = json.loads(content)['text']
return HTMLParser().unescape("\n".join(text))
elif output_format == 'srt':
return generate_srt_from_sjson(json.loads(content), speed=1.0)
@staticmethod
def asset(location, subs_id, lang='en', filename=None):
"""
Get asset from contentstore, asset location is built from subs_id and lang.
`location` is module location.
"""
return contentstore().find(
Transcript.asset_location(
location,
subs_filename(subs_id, lang) if not filename else filename
)
)
@staticmethod
def asset_location(location, filename):
"""
Return asset location.
`location` is module location.
"""
return StaticContent.compute_location(
location.org, location.course, filename
)
......@@ -10,10 +10,10 @@ in-browser HTML5 video method (when in HTML5 mode).
in XML.
"""
import os
import json
import logging
from operator import itemgetter
from HTMLParser import HTMLParser
from lxml import etree
from pkg_resources import resource_string
......@@ -33,12 +33,11 @@ from xblock.core import XBlock
from xblock.fields import Scope, String, Float, Boolean, List, Dict, ScopeIds
from xmodule.fields import RelativeTime
from .transcripts_utils import (
generate_srt_from_sjson,
asset,
get_or_create_sjson,
TranscriptException,
generate_sjson_for_all_speeds,
youtube_speed_dict
youtube_speed_dict,
Transcript,
)
from .video_utils import create_youtube_string
......@@ -271,13 +270,13 @@ class VideoModule(VideoFields, XModule):
track_url = self.runtime.handler_url(self, 'transcript').rstrip('/?') + '/download'
if not self.transcripts:
transcript_language = 'en'
transcript_language = u'en'
languages = {'en': 'English'}
else:
if self.transcript_language in self.transcripts:
transcript_language = self.transcript_language
elif self.sub:
transcript_language = 'en'
transcript_language = u'en'
else:
transcript_language = sorted(self.transcripts.keys())[0]
......@@ -323,30 +322,47 @@ class VideoModule(VideoFields, XModule):
'transcript_available_translations_url': self.runtime.handler_url(self, 'transcript').rstrip('/?') + '/available_translations',
})
def get_transcript(self, format='srt'):
def get_transcript(self, transcript_format='srt'):
"""
Returns transcript in *.srt format.
Returns transcript, filename and MIME type.
Raises:
- NotFoundError if cannot find transcript file in storage.
- ValueError if transcript file is empty or incorrect JSON.
- KeyError if transcript file has incorrect format.
If language is 'en', self.sub should be correct subtitles name.
If language is 'en', but if self.sub is not defined, this means that we
should search for video name in order to get proper transcript (old style courses).
If language is not 'en', give back transcript in proper language and format.
"""
lang = self.transcript_language
subs_id = self.sub if lang == 'en' else self.youtube_id_1_0
data = asset(self.location, subs_id, lang).data
if format == 'txt':
text = json.loads(data)['text']
str_subs = HTMLParser().unescape("\n".join(text))
mime_type = 'text/plain'
if lang == 'en':
if self.sub: # HTML5 case and (Youtube case for new style videos)
transcript_name = self.sub
elif self.youtube_id_1_0: # old courses
transcript_name = self.youtube_id_1_0
else:
log.debug("No subtitles for 'en' language")
raise ValueError
data = Transcript.asset(self.location, transcript_name, lang).data
filename = '{}.{}'.format(transcript_name, transcript_format)
content = Transcript.convert(data, 'sjson', transcript_format)
else:
str_subs = generate_srt_from_sjson(json.loads(data), speed=1.0)
mime_type = 'application/x-subrip'
if not str_subs:
log.debug('generate_srt_from_sjson produces no subtitles')
data = Transcript.asset(self.location, None, None, self.transcripts[lang]).data
filename = '{}.{}'.format(os.path.splitext(self.transcripts[lang])[0], transcript_format)
content = Transcript.convert(data, 'srt', transcript_format)
if not content:
log.debug('no subtitles produced in get_transcript')
raise ValueError
return str_subs, format, mime_type
mime_type = 'text/plain' if transcript_format == 'txt' else 'application/x-subrip'
return content, filename, mime_type
@XBlock.handler
def transcript(self, request, dispatch):
......@@ -384,34 +400,31 @@ class VideoModule(VideoFields, XModule):
elif dispatch == 'download':
try:
subs, format, mime_type = self.get_transcript(format=self.transcript_download_format)
transcript_content, transcript_filename, transcript_mime_type = self.get_transcript(self.transcript_download_format)
except (NotFoundError, ValueError, KeyError):
log.debug("Video@download exception")
response = Response(status=404)
else:
response = Response(
subs,
transcript_content,
headerlist=[
('Content-Disposition', 'attachment; filename="{filename}.{format}"'.format(
filename=self.transcript_language,
format=format,
)),
('Content-Disposition', 'attachment; filename="{}"'.format(transcript_filename)),
]
)
response.content_type = mime_type
response.content_type = transcript_mime_type
elif dispatch == 'available_translations':
available_translations = []
if self.sub: # check if sjson exists for 'en'.
try:
asset(self.location, self.sub, 'en')
Transcript.asset(self.location, self.sub, 'en')
except NotFoundError:
pass
else:
available_translations = ['en']
for lang in self.transcripts:
try:
asset(self.location, None, None, self.transcripts[lang])
Transcript.asset(self.location, None, None, self.transcripts[lang])
except NotFoundError:
continue
available_translations.append(lang)
......@@ -462,13 +475,13 @@ class VideoModule(VideoFields, XModule):
if youtube_id:
# Youtube case:
if self.transcript_language == 'en':
return asset(self.location, youtube_id).data
return Transcript.asset(self.location, youtube_id).data
youtube_ids = youtube_speed_dict(self)
assert youtube_id in youtube_ids
try:
sjson_transcript = asset(self.location, youtube_id, self.transcript_language).data
sjson_transcript = Transcript.asset(self.location, youtube_id, self.transcript_language).data
except (NotFoundError):
log.info("Can't find content in storage for %s transcript: generating.", youtube_id)
generate_sjson_for_all_speeds(
......@@ -477,19 +490,17 @@ class VideoModule(VideoFields, XModule):
{speed: youtube_id for youtube_id, speed in youtube_ids.iteritems()},
self.transcript_language
)
sjson_transcript = asset(self.location, youtube_id, self.transcript_language).data
sjson_transcript = Transcript.asset(self.location, youtube_id, self.transcript_language).data
return sjson_transcript
else:
# HTML5 case
if self.transcript_language == 'en':
return asset(self.location, self.sub).data
return Transcript.asset(self.location, self.sub).data
else:
return get_or_create_sjson(self)
class VideoDescriptor(VideoFields, TabsEditingDescriptor, EmptyDataRawDescriptor):
"""Descriptor for `VideoModule`."""
module_class = VideoModule
......
......@@ -82,9 +82,9 @@ Feature: LMS Video component
# 10
Scenario: Language menu works correctly in Video component
Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Youtube" mode:
And I have a "chinese_transcripts.srt" transcript file in assets
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Youtube" mode:
| transcripts | sub |
| {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM |
And I make sure captions are closed
......@@ -97,8 +97,8 @@ Feature: LMS Video component
# 11
Scenario: CC button works correctly w/o english transcript in HTML5 mode of Video component
Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets
And it has a video in "HTML5" mode:
And I have a "chinese_transcripts.srt" transcript file in assets
And it has a video in "HTML5" mode:
| transcripts |
| {"zh": "chinese_transcripts.srt"} |
And I make sure captions are opened
......@@ -181,11 +181,11 @@ Feature: LMS Video component
| track | download_track |
| http://example.org/ | true |
And I open the section with videos
And I can download transcript in "srt" format
Then I can download transcript in "srt" format and has text "00:00:00,270"
And I select the transcript format "txt"
And I can download transcript in "txt" format
Then I can download transcript in "txt" format and has text "Hi, welcome to Edx."
When I open video "B"
Then I can download transcript in "txt" format
Then I can download transcript in "txt" format and has text "Hi, welcome to Edx."
When I open video "C"
Then menu "download_transcript" doesn't exist
......@@ -203,3 +203,47 @@ Feature: LMS Video component
And I reload the page
Then I see "Hi, welcome to Edx." text in the captions
And I see duration "1:00"
# 21
Scenario: Download button works correctly for non-english transcript in Youtube mode of Video component
Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "Youtube" mode:
| transcripts | sub | download_track |
| {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM | true |
And I select language with code "zh"
And I see "好 各位同学" text in the captions
Then I can download transcript in "srt" format and has text "好 各位同学"
# 22
Scenario: Download button works correctly for non-english transcript in HTML5 mode of Video component
Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets
And I have a "subs_OEoXaMPEzfM.srt.sjson" transcript file in assets
And it has a video in "HTML5" mode:
| transcripts | sub | download_track |
| {"zh": "chinese_transcripts.srt"} | OEoXaMPEzfM | true |
And I select language with code "zh"
And I see "好 各位同学" text in the captions
Then I can download transcript in "srt" format and has text "好 各位同学"
# 23
Scenario: Download button works correctly w/o english transcript in HTML5 mode of Video component
Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets
And it has a video in "HTML5" mode:
| transcripts | download_track |
| {"zh": "chinese_transcripts.srt"} | true |
And I see "好 各位同学" text in the captions
Then I can download transcript in "srt" format and has text "好 各位同学"
# 24
Scenario: Download button works correctly w/o english transcript in Youtube mode of Video component
Given I am registered for the course "test_course"
And I have a "chinese_transcripts.srt" transcript file in assets
And it has a video in "Youtube" mode:
| transcripts | download_track |
| {"zh": "chinese_transcripts.srt"} | true |
And I see "好 各位同学" text in the captions
Then I can download transcript in "srt" format and has text "好 各位同学"
\ No newline at end of file
......@@ -2,16 +2,17 @@
#pylint: disable=C0111
from lettuce import world, step
import json
import os
import requests
import json
import time
import requests
from common import i_am_registered_for_the_course, section_location, visit_scenario_item
from django.utils.translation import ugettext as _
from django.conf import settings
from cache_toolbox.core import del_cached_content
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
LANGUAGES = settings.ALL_LANGUAGES
......@@ -54,7 +55,7 @@ class ReuqestHandlerWithSessionId(object):
"""
kwargs = dict()
session_id = [{i['name']:i['value']} for i in world.browser.cookies.all() if i['name']==u'sessionid']
session_id = [{i['name']:i['value']} for i in world.browser.cookies.all() if i['name'] == u'sessionid']
if session_id:
kwargs.update({
'cookies': session_id[0]
......@@ -118,7 +119,7 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'):
})
if player_mode == 'youtube_html5':
kwargs['metadata'].update({
'html5_sources': HTML5_SOURCES
'html5_sources': HTML5_SOURCES,
})
if player_mode == 'youtube_html5_unsupported_video':
kwargs['metadata'].update({
......@@ -166,7 +167,6 @@ def _change_video_speed(speed):
speed_css = 'li[data-speed="{0}"] a'.format(speed)
world.css_click(speed_css)
def _open_menu(menu):
world.browser.execute_script("$('{selector}').parent().addClass('open')".format(
selector=VIDEO_MENUS[menu]
......@@ -333,11 +333,11 @@ def set_captions_visibility_state(_step, captions_state):
def i_see_menu(_step, menu):
_open_menu(menu)
menu_items = world.css_find(VIDEO_MENUS[menu] + ' li')
Video = world.scenario_dict['VIDEO']
transcripts = dict(Video.transcripts)
if Video.sub:
video = world.scenario_dict['VIDEO']
transcripts = dict(video.transcripts)
if video.sub:
transcripts.update({
'en': Video.sub
'en': video.sub
})
languages = {i[0]: i[1] for i in LANGUAGES}
......@@ -359,9 +359,8 @@ def select_language(_step, code):
selector = VIDEO_MENUS["language"] + ' li[data-lang-code={code}]'.format(
code=code
)
item = world.css_find(selector)
item.click()
world.css_click(selector)
assert world.css_has_class(selector, 'active')
assert len(world.css_find(VIDEO_MENUS["language"] + ' li.active')) == 1
......@@ -406,6 +405,7 @@ def upload_to_assets(_step, filename):
def is_hidden_button(_step, button):
assert not world.css_visible(VIDEO_BUTTONS[button])
@step('menu "([^"]*)" doesn\'t exist$')
def is_hidden_menu(_step, menu):
assert world.is_css_not_present(VIDEO_MENUS[menu])
......@@ -435,27 +435,21 @@ def video_alignment(_step, transcript_visibility):
assert all([width, height])
@step('I can download transcript in "([^"]*)" format$')
def i_can_download_transcript(_step, format):
@step('I can download transcript in "([^"]*)" format and has text "([^"]*)"$')
def i_can_download_transcript(_step, format, text):
button = world.css_find('.video-tracks .a11y-menu-button').first
assert button.text.strip() == '.' + format
formats = {
'srt': {
'content': '0\n00:00:00,270',
'mime_type': 'application/x-subrip'
},
'txt': {
'content': 'Hi, welcome to Edx.',
'mime_type': 'text/plain'
},
'srt': 'application/x-subrip',
'txt': 'text/plain',
}
url = world.css_find(VIDEO_BUTTONS['download_transcript'])[0]['href']
request = ReuqestHandlerWithSessionId()
assert request.get(url).is_success()
assert request.check_header('content-type', formats[format]['mime_type'])
assert request.content.startswith(formats[format]['content'])
assert request.check_header('content-type', formats[format])
assert (text.encode('utf-8') in request.content)
@step('I select the transcript format "([^"]*)"$')
......
......@@ -135,10 +135,69 @@ class TestVideo(BaseTestXmodule):
def tearDown(self):
_clear_assets(self.item_descriptor.location)
class TestTranscriptAvailableTranslationsDispatch(TestVideo):
"""
Test video handler that provide available translations info.
class TestVideoTranscriptTranslation(TestVideo):
Tests for `available_translations` dispatch.
"""
Test video handlers that provide translation transcripts.
non_en_file = _create_srt_file()
DATA = """
<video show_captions="true"
display_name="A Name"
>
<source src="example.mp4"/>
<source src="example.webm"/>
<transcript language="uk" src="{}"/>
</video>
""".format(os.path.split(non_en_file.name)[1])
MODEL_DATA = {
'data': DATA
}
def setUp(self):
super(TestTranscriptAvailableTranslationsDispatch, self).setUp()
self.item_descriptor.render('student_view')
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
self.subs = {"start": [10], "end": [100], "text": ["Hi, welcome to Edx."]}
def test_available_translation_en(self):
good_sjson = _create_file(json.dumps(self.subs))
_upload_sjson_file(good_sjson, self.item_descriptor.location)
self.item.sub = _get_subs_id(good_sjson.name)
request = Request.blank('/translation')
response = self.item.transcript(request=request, dispatch='available_translations')
self.assertEqual(json.loads(response.body), ['en'])
def test_available_translation_non_en(self):
_upload_file(self.non_en_file, self.item_descriptor.location, os.path.split(self.non_en_file.name)[1])
request = Request.blank('/translation')
response = self.item.transcript(request=request, dispatch='available_translations')
self.assertEqual(json.loads(response.body), ['uk'])
def test_multiple_available_translations(self):
good_sjson = _create_file(json.dumps(self.subs))
# Upload english transcript.
_upload_sjson_file(good_sjson, self.item_descriptor.location)
# Upload non-english transcript.
_upload_file(self.non_en_file, self.item_descriptor.location, os.path.split(self.non_en_file.name)[1])
self.item.sub = _get_subs_id(good_sjson.name)
request = Request.blank('/translation')
response = self.item.transcript(request=request, dispatch='available_translations')
self.assertEqual(json.loads(response.body), ['en', 'uk'])
class TestTranscriptDownloadDispatch(TestVideo):
"""
Test video handler that provide translation transcripts.
Tests for `download` dispatch.
"""
non_en_file = _create_srt_file()
......@@ -157,11 +216,10 @@ class TestVideoTranscriptTranslation(TestVideo):
}
def setUp(self):
super(TestVideoTranscriptTranslation, self).setUp()
super(TestTranscriptDownloadDispatch, self).setUp()
self.item_descriptor.render('student_view')
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
# Tests for `download` dispatch:
def test_language_is_not_supported(self):
request = Request.blank('/download?language=ru')
......@@ -173,7 +231,7 @@ class TestVideoTranscriptTranslation(TestVideo):
response = self.item.transcript(request=request, dispatch='download')
self.assertEqual(response.status, '404 Not Found')
@patch('xmodule.video_module.VideoModule.get_transcript', return_value=('Subs!', 'srt', 'application/x-subrip'))
@patch('xmodule.video_module.VideoModule.get_transcript', return_value=('Subs!', 'test_filename.srt', 'application/x-subrip'))
def test_download_srt_exist(self, __):
request = Request.blank('/download?language=en')
response = self.item.transcript(request=request, dispatch='download')
......@@ -195,7 +253,32 @@ class TestVideoTranscriptTranslation(TestVideo):
with self.assertRaises(NotFoundError):
self.item.get_transcript()
# Tests for `translation` dispatch:
class TestTranscriptTranslationDispatch(TestVideo):
"""
Test video handler that provide translation transcripts.
Tests for `translation` dispatch.
"""
non_en_file = _create_srt_file()
DATA = """
<video show_captions="true"
display_name="A Name"
>
<source src="example.mp4"/>
<source src="example.webm"/>
<transcript language="uk" src="{}"/>
</video>
""".format(os.path.split(non_en_file.name)[1])
MODEL_DATA = {
'data': DATA
}
def setUp(self):
super(TestTranscriptTranslationDispatch, self).setUp()
self.item_descriptor.render('student_view')
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
def test_translation_fails(self):
# No language
......@@ -295,30 +378,35 @@ class TestVideoTranscriptTranslation(TestVideo):
self.assertDictEqual(json.loads(response.body), subs)
class TestVideoTranscriptsDownload(TestVideo):
class TestGetTranscript(TestVideo):
"""
Make sure that `get_transcript` method works correctly
"""
non_en_file = _create_srt_file()
DATA = """
<video show_captions="true"
display_name="A Name"
>
<source src="example.mp4"/>
<source src="example.webm"/>
<transcript language="uk" src="{}"/>
</video>
"""
""".format(os.path.split(non_en_file.name)[1])
MODEL_DATA = {
'data': DATA
}
METADATA = {}
def setUp(self):
super(TestVideoTranscriptsDownload, self).setUp()
super(TestGetTranscript, self).setUp()
self.item_descriptor.render('student_view')
self.item = self.item_descriptor.xmodule_runtime.xmodule_instance
def test_good_srt_transcript(self):
def test_good_transcript(self):
"""
Test for download 'en' sub with html5 video and self.sub has correct non-empty value.
"""
good_sjson = _create_file(content=textwrap.dedent("""\
{
"start": [
......@@ -338,7 +426,9 @@ class TestVideoTranscriptsDownload(TestVideo):
_upload_sjson_file(good_sjson, self.item.location)
self.item.sub = _get_subs_id(good_sjson.name)
text, format, download = self.item.get_transcript()
text, filename, mime_type = self.item.get_transcript()
expected_text = textwrap.dedent("""\
0
00:00:00,270 --> 00:00:02,720
......@@ -351,6 +441,8 @@ class TestVideoTranscriptsDownload(TestVideo):
""")
self.assertEqual(text, expected_text)
self.assertEqual(filename[:-4], self.item.sub)
self.assertEqual(mime_type, 'application/x-subrip')
def test_good_txt_transcript(self):
good_sjson = _create_file(content=textwrap.dedent("""\
......@@ -372,17 +464,77 @@ class TestVideoTranscriptsDownload(TestVideo):
_upload_sjson_file(good_sjson, self.item.location)
self.item.sub = _get_subs_id(good_sjson.name)
text, format, mime_type = self.item.get_transcript(format="txt")
text, filename, mime_type = self.item.get_transcript("txt")
expected_text = textwrap.dedent("""\
Hi, welcome to Edx.
Let's start with what is on your screen right now.""")
self.assertEqual(text, expected_text)
self.assertEqual(filename, self.item.sub + '.txt')
self.assertEqual(mime_type, 'text/plain')
def test_en_with_empty_sub(self):
def test_not_found_error(self):
# no self.sub, self.youttube_1_0 exist, but no file in assets
with self.assertRaises(NotFoundError):
self.item.get_transcript()
# no self.sub and no self.youtube_1_0
self.item.youtube_id_1_0 = None
with self.assertRaises(ValueError):
self.item.get_transcript()
# no self.sub but youtube_1_0 exists with file in assets
good_sjson = _create_file(content=textwrap.dedent("""\
{
"start": [
270,
2720
],
"end": [
2720,
5430
],
"text": [
"Hi, welcome to Edx.",
"Let&#39;s start with what is on your screen right now."
]
}
"""))
_upload_sjson_file(good_sjson, self.item.location)
self.item.youtube_id_1_0 = _get_subs_id(good_sjson.name)
text, filename, mime_type = self.item.get_transcript()
expected_text = textwrap.dedent("""\
0
00:00:00,270 --> 00:00:02,720
Hi, welcome to Edx.
1
00:00:02,720 --> 00:00:05,430
Let&#39;s start with what is on your screen right now.
""")
self.assertEqual(text, expected_text)
self.assertEqual(filename, self.item.youtube_id_1_0 + '.srt')
self.assertEqual(mime_type, 'application/x-subrip')
def test_non_en(self):
self.item.transcript_language = 'uk'
self.non_en_file.seek(0)
_upload_file(self.non_en_file, self.item_descriptor.location, os.path.split(self.non_en_file.name)[1])
text, filename, mime_type = self.item.get_transcript()
expected_text = textwrap.dedent("""
0
00:00:00,12 --> 00:00:00,100
Привіт, edX вітає вас.
""")
self.assertEqual(text, expected_text)
self.assertEqual(filename, os.path.split(self.non_en_file.name)[1])
self.assertEqual(mime_type, 'application/x-subrip')
def test_value_error(self):
good_sjson = _create_file(content='bad content')
......
......@@ -42,7 +42,7 @@ class TestVideoYouTube(TestVideo):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/',
'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': 'en',
'transcript_language': u'en',
'transcript_languages': '{"en": "English", "uk": "Ukrainian"}',
'transcript_translation_url': self.item_descriptor.xmodule_runtime.handler_url(
self.item_descriptor, 'transcript'
......@@ -51,6 +51,7 @@ class TestVideoYouTube(TestVideo):
self.item_descriptor, 'transcript'
).rstrip('/?') + '/available_translations',
}
self.assertEqual(
context,
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context),
......@@ -106,7 +107,7 @@ class TestVideoNonYouTube(TestVideo):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/',
'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': 'en',
'transcript_language': u'en',
'transcript_languages': '{"en": "English"}',
'transcript_translation_url': self.item_descriptor.xmodule_runtime.handler_url(
self.item_descriptor, 'transcript'
......@@ -143,6 +144,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
<source src="example.mp4"/>
<source src="example.webm"/>
{track}
{transcripts}
</video>
"""
......@@ -152,24 +154,35 @@ class TestGetHtmlMethod(BaseTestXmodule):
'track': u'<track src="http://www.example.com/track"/>',
'sub': u'a_sub_file.srt.sjson',
'expected_track_url': u'http://www.example.com/track',
'transcripts': '',
},
{
'download_track': u'true',
'track': u'',
'sub': u'a_sub_file.srt.sjson',
'expected_track_url': u'a_sub_file.srt.sjson',
'transcripts': '',
},
{
'download_track': u'true',
'track': u'',
'sub': u'',
'expected_track_url': None
'expected_track_url': None,
'transcripts': '',
},
{
'download_track': u'false',
'track': u'<track src="http://www.example.com/track"/>',
'sub': u'a_sub_file.srt.sjson',
'expected_track_url': None,
'transcripts': '',
},
{
'download_track': u'true',
'track': u'',
'sub': u'',
'expected_track_url': u'a_sub_file.srt.sjson',
'transcripts': '<transcript language="uk" src="ukrainian.srt" />',
},
]
......@@ -201,7 +214,8 @@ class TestGetHtmlMethod(BaseTestXmodule):
DATA = SOURCE_XML.format(
download_track=data['download_track'],
track=data['track'],
sub=data['sub']
sub=data['sub'],
transcripts=data['transcripts'],
)
self.initialize_module(data=DATA)
......@@ -213,8 +227,8 @@ class TestGetHtmlMethod(BaseTestXmodule):
expected_context.update({
'transcript_download_format': None if self.item_descriptor.track and self.item_descriptor.download_track else 'srt',
'transcript_languages': '{"en": "English"}',
'transcript_language': 'en',
'transcript_languages': '{"en": "English"}' if not data['transcripts'] else '{"uk": "Ukrainian"}',
'transcript_language': u'en' if not data['transcripts'] or data.get('sub') else u'uk',
'transcript_translation_url': self.item_descriptor.xmodule_runtime.handler_url(
self.item_descriptor, 'transcript'
).rstrip('/?') + '/translation',
......@@ -312,7 +326,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
'yt_test_url': 'https://gdata.youtube.com/feeds/api/videos/',
'transcript_download_format': 'srt',
'transcript_download_formats_list': [{'display_name': 'SubRip (.srt) file', 'value': 'srt'}, {'display_name': 'Text (.txt) file', 'value': 'txt'}],
'transcript_language': 'en',
'transcript_language': u'en',
'transcript_languages': '{"en": "English"}',
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment