Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions common/lib/xmodule/xmodule/tests/test_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
import ddt

from django.conf import settings
from django.test.utils import override_settings

from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.keys import CourseKey
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_module import VideoDescriptor, create_youtube_string
from xmodule.video_module.transcripts_utils import download_youtube_subs, save_to_store
from . import LogicTest
Expand Down Expand Up @@ -77,6 +79,12 @@
" that now. The tutorial will continue in the next video."
)

ALL_LANGUAGES = (
[u"en", u"English"],
[u"eo", u"Esperanto"],
[u"ur", u"Urdu"]
)


def instantiate_descriptor(**field_data):
"""
Expand Down Expand Up @@ -780,6 +788,7 @@ def test_export_to_xml_unicode_characters(self):
self.assertEqual(xml.get('display_name'), u'\u8fd9\u662f\u6587')


@ddt.ddt
class VideoDescriptorIndexingTestCase(unittest.TestCase):
"""
Make sure that VideoDescriptor can format data for indexing as expected.
Expand Down Expand Up @@ -993,3 +1002,74 @@ def test_video_with_no_transcripts_translation_retrieval(self):
descriptor = instantiate_descriptor(data=None)
translations = descriptor.available_translations(descriptor.get_transcripts_info(), verify_assets=False)
self.assertEqual(translations, ['en'])

@override_settings(ALL_LANGUAGES=ALL_LANGUAGES)
def test_video_with_language_do_not_have_transcripts_translation(self):
"""
Test translation retrieval of a video module 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>
'''
descriptor = instantiate_descriptor(data=xml_data_transcripts)
translations = descriptor.available_translations(descriptor.get_transcripts_info(), verify_assets=False)
self.assertNotEqual(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.
"""
self.assertFalse(validation.empty) # Validation contains some warning/message
self.assertTrue(validation.summary)
self.assertEqual(StudioValidationMessage.WARNING, validation.summary.type)
self.assertIn(expected_msg, validation.summary.text)

@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)
descriptor = instantiate_descriptor(data=xml_data_transcripts)
validation = descriptor.validate()
self.assert_validation_message(validation, expected_validation_msg)
28 changes: 17 additions & 11 deletions common/lib/xmodule/xmodule/video_module/transcripts_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,7 @@ def manage_video_subtitles_save(item, user, old_metadata=None, generate_translat
lang,
)
except TranscriptException as ex:
# remove key from transcripts because proper srt file does not exist in assets.
item.transcripts.pop(lang)
reraised_message += ' ' + ex.message
pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we don't need this anymore ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because now we are saving language and not raising exception.

if reraised_message:
item.save_with_metadata(user)
raise TranscriptException(reraised_message)
Expand Down Expand Up @@ -531,6 +529,9 @@ def asset_location(location, filename):
"""
Return asset location. `location` is module location.
"""
# If user transcript filename is empty, raise `TranscriptException` to avoid `InvalidKeyError`.
if not filename:
raise TranscriptException("Transcript not uploaded yet")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does raising this exception results into error on studio/lms ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After @marcotuts 's suggestion to save the language even if the associated transcript file is not uploaded, this does not result into the error on studio/lms.

However, as a precaution, I have added this so that a correct exception would be generated which may/may-not be handled (depends upon the caller of asset_location method). This is just a way for us to be safe from InvalidKeyError when no asset is present.

return StaticContent.compute_location(location.course_key, filename)

@staticmethod
Expand Down Expand Up @@ -670,12 +671,17 @@ def get_transcripts_info(self, is_bumper=False):
"""
if is_bumper:
transcripts = copy.deepcopy(get_bumper_settings(self).get('transcripts', {}))
return {
"sub": transcripts.pop("en", ""),
"transcripts": transcripts,
}
sub = transcripts.pop("en", "")
else:
return {
"sub": self.sub,
"transcripts": self.transcripts,
}
transcripts = self.transcripts
sub = self.sub

# Only attach transcripts that are not empty.
transcripts = {
language_code: transcript_file
for language_code, transcript_file in transcripts.items() if transcript_file != ''
}
return {
"sub": sub,
"transcripts": transcripts,
}
36 changes: 36 additions & 0 deletions common/lib/xmodule/xmodule/video_module/video_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from xmodule.xml_module import is_pointer_tag, name_to_pathname, deserialize_field
from xmodule.exceptions import NotFoundError
from xmodule.contentstore.content import StaticContent
from xmodule.validation import StudioValidationMessage, StudioValidation

from .transcripts_utils import VideoTranscriptsMixin, Transcript, get_html5_ids
from .video_utils import create_youtube_string, get_poster, rewrite_video_url, format_xml_exception_message
Expand Down Expand Up @@ -152,6 +153,12 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
]}
js_module_name = "Video"

def validate(self):
"""
Validates the state of this Video Module Instance.
"""
return self.descriptor.validate()

def get_transcripts_for_student(self, transcripts):
"""Return transcript information necessary for rendering the XModule student view.
This is more or less a direct extraction from `get_html`.
Expand Down Expand Up @@ -427,6 +434,35 @@ def __init__(self, *args, **kwargs):
if not self.fields['download_track'].is_set_on(self) and self.track:
self.download_track = True

def validate(self):
"""
Validates the state of this video Module Instance. This
is the override of the general XBlock method, and it will also ask
its superclass to validate.
"""
validation = super(VideoDescriptor, self).validate()
if not isinstance(validation, StudioValidation):
validation = StudioValidation.copy(validation)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mushtaqak Why we need to make copy here?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's the way to have validation copy object when we don't have one. Already being used in couple of places.


no_transcript_lang = []
for lang_code, transcript in self.transcripts.items():
if not transcript:
no_transcript_lang.append([label for code, label in settings.ALL_LANGUAGES if code == lang_code][0])

if no_transcript_lang:
ungettext = self.runtime.service(self, "i18n").ungettext
validation.set_summary(
StudioValidationMessage(
StudioValidationMessage.WARNING,
ungettext(
'There is no transcript file associated with the {lang} language.',
'There are no transcript files associated with the {lang} languages.',
len(no_transcript_lang)
).format(lang=', '.join(no_transcript_lang))
)
)
return validation

def editor_saved(self, user, old_metadata, old_content):
"""
Used to update video values during `self`:save method from CMS.
Expand Down
21 changes: 21 additions & 0 deletions common/test/acceptance/tests/video/test_studio_video_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,27 @@ def test_translations_uploading(self):
self.assertIn(unicode_text, self.video.captions_text)
self.assertEqual(self.video.caption_languages.keys(), ['zh', 'uk'])

def test_save_language_upload_no_transcript(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mushtaqak This tests check studio side flow. Do we also need a test for lms?
@muzaffaryousaf Any thoughts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@muhammad-ammar Actually this test checks if a transcript language is shown in language menu if no transcript file is uploaded on video which would be same on LMS side as well.

"""
Scenario: Transcript language is not shown in language menu if no transcript file is uploaded
Given I have created a Video component
And I edit the component
And I open tab "Advanced"
And I add a language "uk" but do not upload an .srt file
And I save changes
When I view the video language menu
Then I am not able to see the language "uk" translation language
"""
self._create_video_component()
self.edit_component()
self.open_advanced_tab()
language_code = 'uk'
self.video.click_button('translation_add')
translations_count = self.video.translations_count()
self.video.select_translation_language(language_code, translations_count - 1)
self.save_unit_settings()
self.assertNotIn(language_code, self.video.caption_languages.keys())

def test_upload_large_transcript(self):
"""
Scenario: User can upload transcript file with > 1mb size
Expand Down