diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 1da909a4a784..c72a269960a4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -21,6 +21,7 @@ jobs: runs-on: ${{ matrix.os-version }} strategy: matrix: + fail-fast: false python-version: - "3.11" - "3.12" diff --git a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py index f856ef6a0c19..876e0332c7d3 100644 --- a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py +++ b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py @@ -7,7 +7,7 @@ import tempfile import textwrap import unittest -from unittest.mock import Mock, patch +from unittest.mock import patch from uuid import uuid4 import ddt @@ -30,6 +30,19 @@ TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex +def save_subs_to_store(subs, subs_id, item, language='en'): + """ + Save transcripts into `StaticContent`. + Args: + `subs_id`: str, subtitles id + `item`: video block instance + `language`: two chars str ('uk'), language of translation of transcripts + Returns: location of saved subtitles. + """ + filedata = json.dumps(subs, indent=2).encode('utf-8') + filename = transcripts_utils.subs_filename(subs_id, language) + return transcripts_utils.save_to_store(filedata, filename, 'application/json', item.location) + class TestGenerateSubs(unittest.TestCase): """Tests for `generate_subs` function.""" def setUp(self): @@ -142,18 +155,11 @@ def setUp(self): self.addCleanup(self.clear_subs_content) self.clear_subs_content() - def test_save_unicode_filename(self): - # Mock a video item - item = Mock(location=Mock(course_key=self.course.id)) - transcripts_utils.save_subs_to_store(self.subs, self.subs_id, self.course) - transcripts_utils.copy_or_rename_transcript(self.subs_copied_id, self.subs_id, item) - self.assertTrue(contentstore().find(self.content_copied_location)) - def test_save_subs_to_store(self): with self.assertRaises(NotFoundError): contentstore().find(self.content_location) - result_location = transcripts_utils.save_subs_to_store( + result_location = save_subs_to_store( self.subs, self.subs_id, self.course) @@ -169,7 +175,7 @@ def test_save_unjsonable_subs_to_store(self): contentstore().find(self.content_location_unjsonable) with self.assertRaises(TypeError): - transcripts_utils.save_subs_to_store( + save_subs_to_store( self.unjsonable_subs, self.unjsonable_subs_id, self.course) @@ -295,77 +301,6 @@ def test_success_downloading_chinese_transcripts(self): self.clear_sub_content(good_youtube_sub) -class TestGenerateSubsFromSource(TestDownloadYoutubeSubs): # lint-amnesty, pylint: disable=test-inherits-tests - """Tests for `generate_subs_from_source` function.""" - - def test_success_generating_subs(self): - youtube_subs = { - 0.5: 'JMD_ifUUfsU', - 1.0: 'hI10vDNYz4M', - 2.0: 'AKqURZnYqpk' - } - srt_filedata = textwrap.dedent(""" - 1 - 00:00:10,500 --> 00:00:13,000 - Elephant's Dream - - 2 - 00:00:15,000 --> 00:00:18,000 - At the left we can see... - """) - self.clear_subs_content(youtube_subs) - - # Check TranscriptsGenerationException not thrown. - # Also checks that uppercase file extensions are supported. - transcripts_utils.generate_subs_from_source(youtube_subs, 'SRT', srt_filedata, self.course) - - # Check assets status after importing subtitles. - for subs_id in youtube_subs.values(): - filename = f'subs_{subs_id}.srt.sjson' - content_location = StaticContent.compute_location( - self.course.id, filename - ) - self.assertTrue(contentstore().find(content_location)) - - self.clear_subs_content(youtube_subs) - - def test_fail_bad_subs_type(self): - youtube_subs = { - 0.5: 'JMD_ifUUfsU', - 1.0: 'hI10vDNYz4M', - 2.0: 'AKqURZnYqpk' - } - - srt_filedata = textwrap.dedent(""" - 1 - 00:00:10,500 --> 00:00:13,000 - Elephant's Dream - - 2 - 00:00:15,000 --> 00:00:18,000 - At the left we can see... - """) - - with self.assertRaises(TranscriptsGenerationException) as cm: - transcripts_utils.generate_subs_from_source(youtube_subs, 'BAD_FORMAT', srt_filedata, self.course) - exception_message = str(cm.exception) - self.assertEqual(exception_message, "We support only SubRip (*.srt) transcripts format.") - - def test_fail_bad_subs_filedata(self): - youtube_subs = { - 0.5: 'JMD_ifUUfsU', - 1.0: 'hI10vDNYz4M', - 2.0: 'AKqURZnYqpk' - } - - srt_filedata = """BAD_DATA""" - - with self.assertRaises(TranscriptsGenerationException) as cm: - transcripts_utils.generate_subs_from_source(youtube_subs, 'srt', srt_filedata, self.course) - exception_message = str(cm.exception) - self.assertEqual(exception_message, "Something wrong with SubRip transcripts file during parsing.") - - class TestGenerateSrtFromSjson(TestDownloadYoutubeSubs): # lint-amnesty, pylint: disable=test-inherits-tests """Tests for `generate_srt_from_sjson` function.""" @@ -759,7 +694,7 @@ def create_transcript(self, subs_id, language='en', filename='video.srt', youtub possible_subs = [subs_id, youtube_id_1_0] + transcripts_utils.get_html5_ids(html5_sources) for possible_sub in possible_subs: if possible_sub: - transcripts_utils.save_subs_to_store( + save_subs_to_store( self.subs_sjson, possible_sub, self.video, diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index 1f5924163bd2..bee2cb669799 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -86,23 +86,13 @@ def exam_setting_view_enabled(course_key): return not LEGACY_STUDIO_EXAM_SETTINGS.is_enabled(course_key) -# .. toggle_name: legacy_studio.video_editor -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Temporarily fall back to the old Video component (a.k.a. video block) editor. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2025-03-14 -# .. toggle_target_removal_date: 2025-09-14 -# .. toggle_tickets: https://github.com/openedx/edx-platform/issues/36275 -# .. toggle_warning: In Ulmo, this toggle will be removed. Only the new (React-based) experience will be available. -LEGACY_STUDIO_VIDEO_EDITOR = CourseWaffleFlag('legacy_studio.video_editor', __name__) - - def use_new_video_editor(course_key): """ + TODO: This method and its usage will be removed in the following story: + https://github.com/openedx/public-engineering/issues/464 Returns a boolean = true if new video editor is enabled """ - return not LEGACY_STUDIO_VIDEO_EDITOR.is_enabled(course_key) + return True # .. toggle_name: new_core_editors.use_video_gallery_flow diff --git a/lms/djangoapps/courseware/tests/test_video_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py index f01bfad49432..412879680a66 100644 --- a/lms/djangoapps/courseware/tests/test_video_mongo.py +++ b/lms/djangoapps/courseware/tests/test_video_mongo.py @@ -1526,25 +1526,6 @@ def test_editor_saved_when_html5_sub_not_exist(self): item.editor_saved(self.user, old_metadata, None) assert isinstance(Transcript.get_asset(item.location, 'subs_3_yD_cEKoCk.srt.sjson'), StaticContent) - def test_editor_saved_when_youtube_and_html5_subs_exist(self): - """ - When both youtube_sub and html5_sub already exist then no new - sub will be generated by editor_saved function. - """ - self.initialize_block(metadata=self.metadata) - item = self.store.get_item(self.block.location) - with open(self.file_path, "rb") as myfile: # lint-amnesty, pylint: disable=bad-option-value, open-builtin - save_to_store(myfile.read(), self.file_name, 'text/sjson', item.location) - save_to_store(myfile.read(), 'subs_video.srt.sjson', 'text/sjson', item.location) - item.sub = "3_yD_cEKoCk" - # subs_3_yD_cEKoCk.srt.sjson and subs_video.srt.sjson already exist - assert isinstance(Transcript.get_asset(item.location, self.file_name), StaticContent) - assert isinstance(Transcript.get_asset(item.location, 'subs_video.srt.sjson'), StaticContent) - old_metadata = own_metadata(item) - with patch('xmodule.video_block.video_block.manage_video_subtitles_save') as manage_video_subtitles_save: - item.editor_saved(self.user, old_metadata, None) - assert not manage_video_subtitles_save.called - def test_editor_saved_with_unstripped_video_id(self): """ Verify editor saved when video id contains spaces/tabs. diff --git a/openedx/core/djangoapps/video_config/transcripts_utils.py b/openedx/core/djangoapps/video_config/transcripts_utils.py index be86324cd6e4..1797acc91d77 100644 --- a/openedx/core/djangoapps/video_config/transcripts_utils.py +++ b/openedx/core/djangoapps/video_config/transcripts_utils.py @@ -21,7 +21,6 @@ from opaque_keys.edx.keys import UsageKeyV2 from pysrt import SubRipFile, SubRipItem, SubRipTime from pysrt.srtexc import Error -from opaque_keys.edx.locator import LibraryLocatorV2 from openedx.core.djangoapps.xblock.api import get_component_from_usage_key from xmodule.contentstore.content import StaticContent @@ -116,22 +115,6 @@ def save_to_store(content, name, mime_type, location): return content_location -def save_subs_to_store(subs, subs_id, item, language='en'): - """ - Save transcripts into `StaticContent`. - - Args: - `subs_id`: str, subtitles id - `item`: video block instance - `language`: two chars str ('uk'), language of translation of transcripts - - Returns: location of saved subtitles. - """ - filedata = json.dumps(subs, indent=2).encode('utf-8') - filename = subs_filename(subs_id, language) - return save_to_store(filedata, filename, 'application/json', item.location) - - def get_transcript_link_from_youtube(youtube_id): """ Get the link for YouTube transcript by parsing the source of the YouTube webpage. @@ -291,56 +274,6 @@ def remove_subs_from_store(subs_id, item, lang='en'): Transcript.delete_asset(item.location, filename) -def generate_subs_from_source(speed_subs, subs_type, subs_filedata, block, language='en'): - """Generate transcripts from source files (like SubRip format, etc.) - and save them to assets for `item` module. - We expect, that speed of source subs equal to 1 - - :param speed_subs: dictionary {speed: sub_id, ...} - :param subs_type: type of source subs: "srt", ... - :param subs_filedata:unicode, content of source subs. - :param block: course or block. - :param language: str, language of translation of transcripts - :returns: True, if all subs are generated and saved successfully. - """ - _ = block.runtime.service(block, "i18n").gettext - if subs_type.lower() != 'srt': - raise TranscriptsGenerationException(_("We support only SubRip (*.srt) transcripts format.")) - try: - srt_subs_obj = SubRipFile.from_string(subs_filedata) - except Exception as ex: - msg = _("Something wrong with SubRip transcripts file during parsing. Inner message is {error_message}").format( - error_message=str(ex) - ) - raise TranscriptsGenerationException(msg) # lint-amnesty, pylint: disable=raise-missing-from - if not srt_subs_obj: - raise TranscriptsGenerationException(_("Something wrong with SubRip transcripts file during parsing.")) - - sub_starts = [] - sub_ends = [] - sub_texts = [] - - for sub in srt_subs_obj: - sub_starts.append(sub.start.ordinal) - sub_ends.append(sub.end.ordinal) - sub_texts.append(sub.text.replace('\n', ' ')) - - subs = { - 'start': sub_starts, - 'end': sub_ends, - 'text': sub_texts} - - for speed, subs_id in speed_subs.items(): - save_subs_to_store( - generate_subs(speed, 1, subs), - subs_id, - block, - language - ) - - return subs - - def generate_srt_from_sjson(sjson_subs, speed): """Generate transcripts with speed = 1.0 from sjson to SubRip (*.srt). @@ -395,23 +328,6 @@ def generate_sjson_from_srt(srt_subs): return sjson_subs -def copy_or_rename_transcript(new_name, old_name, item, delete_old=False, user=None): - """ - Renames `old_name` transcript file in storage to `new_name`. - - If `old_name` is not found in storage, raises `NotFoundError`. - If `delete_old` is True, removes `old_name` files from storage. - """ - filename = f'subs_{old_name}.srt.sjson' - content_location = StaticContent.compute_location(item.location.course_key, filename) - transcripts = contentstore().find(content_location).data.decode('utf-8') - save_subs_to_store(json.loads(transcripts), new_name, item) - item.sub = new_name - item.save_with_metadata(user) - if delete_old: - remove_subs_from_store(old_name, item) - - def get_html5_ids(html5_sources): """ Helper method to parse out an HTML5 source into the ideas @@ -421,99 +337,6 @@ def get_html5_ids(html5_sources): return html5_ids -def manage_video_subtitles_save(item, user, old_metadata=None, generate_translation=False): - """ - Does some specific things, that can be done only on save. - - Video player item has some video fields: HTML5 ones and Youtube one. - - If value of `sub` field of `new_item` is cleared, transcripts should be removed. - - `item` is video block instance with updated values of fields, - but actually have not been saved to store yet. - - `old_metadata` contains old values of XFields. - - # 1. - If value of `sub` field of `new_item` is different from values of video fields of `new_item`, - and `new_item.sub` file is present, then code in this function creates copies of - `new_item.sub` file with new names. That names are equal to values of video fields of `new_item` - After that `sub` field of `new_item` is changed to one of values of video fields. - This whole action ensures that after user changes video fields, proper `sub` files, corresponding - to new values of video fields, will be presented in system. - - # 2. convert /static/filename.srt to filename.srt in self.transcripts. - (it is done to allow user to enter both /static/filename.srt and filename.srt) - - # 3. Generate transcripts translation only when user clicks `save` button, not while switching tabs. - a) delete sjson translation for those languages, which were removed from `item.transcripts`. - Note: we are not deleting old SRT files to give user more flexibility. - b) For all SRT files in`item.transcripts` regenerate new SJSON files. - (To avoid confusing situation if you attempt to correct a translation by uploading - a new version of the SRT file with same name). - """ - _ = item.runtime.service(item, "i18n").gettext - - # # 1. - # html5_ids = get_html5_ids(item.html5_sources) - - # # Youtube transcript source should always have a higher priority than html5 sources. Appending - # # `youtube_id_1_0` at the end helps achieve this when we read transcripts list. - # possible_video_id_list = html5_ids + [item.youtube_id_1_0] - # sub_name = item.sub - # for video_id in possible_video_id_list: - # if not video_id: - # continue - # if not sub_name: - # remove_subs_from_store(video_id, item) - # continue - # # copy_or_rename_transcript changes item.sub of module - # try: - # # updates item.sub with `video_id`, if it is successful. - # copy_or_rename_transcript(video_id, sub_name, item, user=user) - # except NotFoundError: - # # subtitles file `sub_name` is not presented in the system. Nothing to copy or rename. - # log.debug( - # "Copying %s file content to %s name is failed, " - # "original file does not exist.", - # sub_name, video_id - # ) - - # 2. - if generate_translation: - for lang, filename in item.transcripts.items(): - item.transcripts[lang] = os.path.split(filename)[-1] - - # 3. - if generate_translation: - old_langs = set(old_metadata.get('transcripts', {})) if old_metadata else set() - new_langs = set(item.transcripts) - - html5_ids = get_html5_ids(item.html5_sources) - possible_video_id_list = html5_ids + [item.youtube_id_1_0] - - for lang in old_langs.difference(new_langs): # 3a - for video_id in possible_video_id_list: - if video_id: - remove_subs_from_store(video_id, item, lang) - - reraised_message = '' - if not isinstance(item.usage_key.context_key, LibraryLocatorV2): - for lang in new_langs: # 3b - try: - generate_sjson_for_all_speeds( - item, - item.transcripts[lang], - {speed: subs_id for subs_id, speed in youtube_speed_dict(item).items()}, - lang, - ) - except TranscriptException: - pass - if reraised_message: - item.save_with_metadata(user) - raise TranscriptException(reraised_message) - - def youtube_speed_dict(item): """ Returns {speed: youtube_ids, ...} dict for existing youtube_ids @@ -534,58 +357,6 @@ def subs_filename(subs_id, lang='en'): return f'{lang}_subs_{subs_id}.srt.sjson' -def generate_sjson_for_all_speeds(block, user_filename, result_subs_dict, lang): - """ - Generates sjson from srt for given lang. - """ - _ = block.runtime.service(block, "i18n").gettext - - try: - srt_transcripts = contentstore().find(Transcript.asset_location(block.location, user_filename)) - except NotFoundError as ex: - raise TranscriptException(_("{exception_message}: Can't find uploaded transcripts: {user_filename}").format( # lint-amnesty, pylint: disable=raise-missing-from - exception_message=str(ex), - user_filename=user_filename - )) - - if not lang: - lang = block.transcript_language - - # Used utf-8-sig encoding type instead of utf-8 to remove BOM(Byte Order Mark), e.g. U+FEFF - generate_subs_from_source( - result_subs_dict, - os.path.splitext(user_filename)[1][1:], - srt_transcripts.data.decode('utf-8-sig'), - block, - lang - ) - - -def get_or_create_sjson(block, transcripts): - """ - Get sjson if already exists, otherwise generate it. - - Generate sjson with subs_id name, from user uploaded srt. - Subs_id is extracted from srt filename, which was set by user. - - Args: - transcipts (dict): dictionary of (language: file) pairs. - - Raises: - TranscriptException: when srt subtitles do not exist, - and exceptions from generate_subs_from_source. - """ - user_filename = transcripts[block.transcript_language] - 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 = Transcript.asset(block.location, source_subs_id, block.transcript_language).data - except NotFoundError: # generating sjson from srt - generate_sjson_for_all_speeds(block, user_filename, result_subs_dict, block.transcript_language) - sjson_transcript = Transcript.asset(block.location, source_subs_id, block.transcript_language).data - return sjson_transcript - - def get_video_ids_info(edx_video_id, youtube_id_1_0, html5_sources): """ Returns list internal or external video ids. @@ -825,6 +596,7 @@ class VideoTranscriptsMixin: This is necessary for VideoBlock. """ + # TODO: We should move this whole method to the video_config_service. def available_translations(self, transcripts, verify_assets=None, is_bumper=False): """ Return a list of language codes for which we have transcripts. diff --git a/xmodule/video_block/video_block.py b/xmodule/video_block/video_block.py index e8a7b7e87ec9..b647dc96c539 100644 --- a/xmodule/video_block/video_block.py +++ b/xmodule/video_block/video_block.py @@ -13,7 +13,6 @@ """ -import copy import json import logging from collections import OrderedDict, defaultdict @@ -38,14 +37,13 @@ from xmodule.contentstore.content import StaticContent from xmodule.editing_block import EditingMixin from xmodule.exceptions import NotFoundError -from xmodule.modulestore.inheritance import InheritanceKeyValueStore, own_metadata +from xmodule.modulestore.inheritance import InheritanceKeyValueStore from xmodule.raw_block import EmptyDataRawMixin from xmodule.util.builtin_assets import add_css_to_fragment, add_webpack_js_to_fragment from xmodule.validation import StudioValidation, StudioValidationMessage -from xmodule.video_block import manage_video_subtitles_save from xmodule.x_module import ( PUBLIC_VIEW, STUDENT_VIEW, - ResourceTemplates, shim_xmodule_js, + ResourceTemplates, XModuleMixin, XModuleToXBlockMixin, ) from xmodule.xml_block import XmlMixin, deserialize_field, is_pointer_tag, name_to_pathname @@ -254,18 +252,6 @@ def author_view(self, context): """ return self.student_view(context) - def studio_view(self, _context): - """ - Return the studio view. - """ - fragment = Fragment( - self.runtime.service(self, 'mako').render_cms_template(self.mako_template, self.get_context()) - ) - add_css_to_fragment(fragment, 'VideoBlockEditor.css') - add_webpack_js_to_fragment(fragment, 'VideoBlockEditor') - shim_xmodule_js(fragment, 'TabsEditingDescriptor') - return fragment - def public_view(self, context): """ Returns a fragment that contains the html for the public view @@ -556,60 +542,6 @@ def validate(self): ) return validation - def editor_saved(self, user, old_metadata, old_content): # lint-amnesty, pylint: disable=unused-argument - """ - Used to update video values during `self`:save method from CMS. - old_metadata: dict, values of fields of `self` with scope=settings which were explicitly set by user. - old_content, same as `old_metadata` but for scope=content. - Due to nature of code flow in block.py::_save_item, before current function is called, - fields of `self` instance have been already updated, but not yet saved. - To obtain values, which were changed by user input, - one should compare own_metadata(self) and old_medatada. - Video player has two tabs, and due to nature of sync between tabs, - metadata from Basic tab is always sent when video player is edited and saved first time, for example: - {'youtube_id_1_0': u'3_yD_cEKoCk', 'display_name': u'Video', 'sub': u'3_yD_cEKoCk', 'html5_sources': []}, - that's why these fields will always present in old_metadata after first save. This should be fixed. - At consequent save requests html5_sources are always sent too, disregard of their change by user. - That means that html5_sources are always in list of fields that were changed (`metadata` param in save_item). - This should be fixed too. - """ - metadata_was_changed_by_user = old_metadata != own_metadata(self) - - # There is an edge case when old_metadata and own_metadata are same and we are importing transcript from youtube - # then there is a syncing issue where html5_subs are not syncing with youtube sub, We can make sync better by - # checking if transcript is present for the video and if any html5_ids transcript is not present then trigger - # the manage_video_subtitles_save to create the missing transcript with particular html5_id. - if not metadata_was_changed_by_user and self.sub and hasattr(self, 'html5_sources'): - html5_ids = get_html5_ids(self.html5_sources) - for subs_id in html5_ids: - try: - Transcript.asset(self.location, subs_id) - except NotFoundError: - # If a transcript does not not exist with particular html5_id then there is no need to check other - # html5_ids because we have to create a new transcript with this missing html5_id by turning on - # metadata_was_changed_by_user flag. - metadata_was_changed_by_user = True - break - - if metadata_was_changed_by_user: - self.edx_video_id = self.edx_video_id and self.edx_video_id.strip() - - # We want to override `youtube_id_1_0` with val youtube profile in the first place when someone adds/edits - # an `edx_video_id` or its underlying YT val profile. Without this, override will only happen when a user - # saves the video second time. This is because of the syncing of basic and advanced video settings which - # also syncs val youtube id from basic tab's `Video Url` to advanced tab's `Youtube ID`. - if self.edx_video_id and edxval_api: - val_youtube_id = edxval_api.get_url_for_profile(self.edx_video_id, 'youtube') - if val_youtube_id and self.youtube_id_1_0 != val_youtube_id: - self.youtube_id_1_0 = val_youtube_id - - manage_video_subtitles_save( - self, - user, - old_metadata if old_metadata else None, - generate_translation=True - ) - def save_with_metadata(self, user): """ Save block with updated metadata to database." @@ -848,87 +780,6 @@ def create_youtube_url(self, youtube_id): else: return '' - def get_context(self): - """ - Extend context by data for transcript basic tab. - """ - _context = { - 'editable_metadata_fields': self.editable_metadata_fields - } - _context.update({ - 'tabs': self.tabs, - 'html_id': self.location.html_id(), # element_id - 'data': self.data, - }) - - metadata_fields = copy.deepcopy(self.editable_metadata_fields) - - display_name = metadata_fields['display_name'] - video_url = metadata_fields['html5_sources'] - video_id = metadata_fields['edx_video_id'] - youtube_id_1_0 = metadata_fields['youtube_id_1_0'] - - def get_youtube_link(video_id): - """ - Returns the fully-qualified YouTube URL for the given video identifier - """ - # First try a lookup in VAL. If we have a YouTube entry there, it overrides the - # one passed in. - if self.edx_video_id and edxval_api: - val_youtube_id = edxval_api.get_url_for_profile(self.edx_video_id, "youtube") - if val_youtube_id: - video_id = val_youtube_id - - return self.create_youtube_url(video_id) - - _ = self.runtime.service(self, "i18n").ugettext - video_url.update({ - 'help': _('The URL for your video. This can be a YouTube URL or a link to an .mp4, .ogg, or ' - '.webm video file hosted elsewhere on the Internet.'), - 'display_name': _('Default Video URL'), - 'field_name': 'video_url', - 'type': 'VideoList', - 'default_value': [get_youtube_link(youtube_id_1_0['default_value'])] - }) - - source_url = self.create_youtube_url(youtube_id_1_0['value']) - # First try a lookup in VAL. If any video encoding is found given the video id then - # override the source_url with it. - if self.edx_video_id and edxval_api: - - val_profiles = ['youtube', 'desktop_webm', 'desktop_mp4'] - if self.is_hls_playback_enabled(self.scope_ids.usage_id.context_key.for_branch(None)): - val_profiles.append('hls') - - # Get video encodings for val profiles. - val_video_encodings = edxval_api.get_urls_for_profiles(self.edx_video_id, val_profiles) - - # VAL's youtube source has greater priority over external youtube source. - if val_video_encodings.get('youtube'): - source_url = self.create_youtube_url(val_video_encodings['youtube']) - - # If no youtube source is provided externally or in VAl, update source_url in order: hls > mp4 and webm - if not source_url: - if val_video_encodings.get('hls'): - source_url = val_video_encodings['hls'] - elif val_video_encodings.get('desktop_mp4'): - source_url = val_video_encodings['desktop_mp4'] - elif val_video_encodings.get('desktop_webm'): - source_url = val_video_encodings['desktop_webm'] - - # Only add if html5 sources do not already contain source_url. - if source_url and source_url not in video_url['value']: - video_url['value'].insert(0, source_url) - - metadata = { - 'display_name': display_name, - 'video_url': video_url, - 'edx_video_id': video_id - } - - _context.update({'transcripts_basic_tab_metadata': metadata}) - return _context - @classmethod def _parse_youtube(cls, data): """ diff --git a/xmodule/video_block/video_handlers.py b/xmodule/video_block/video_handlers.py index ef65f905481e..bec376ae6f8d 100644 --- a/xmodule/video_block/video_handlers.py +++ b/xmodule/video_block/video_handlers.py @@ -24,15 +24,11 @@ from openedx.core.djangoapps.video_config.transcripts_utils import ( Transcript, - TranscriptException, clean_video_id, - generate_sjson_for_all_speeds, get_html5_ids, - get_or_create_sjson, get_transcript_from_contentstore, remove_subs_from_store, subs_filename, - youtube_speed_dict ) from xblocks_contrib.video.exceptions import ( TranscriptsGenerationException, @@ -132,78 +128,6 @@ def handle_ajax(self, dispatch, data): raise NotFoundError('Unexpected dispatch type') - def translation(self, youtube_id, transcripts): - """ - This is called to get transcript file for specific language. - - youtube_id: str: must be one of youtube_ids or None if HTML video - transcripts (dict): A dict with all transcripts and a sub. - - Logic flow: - - If youtube_id doesn't exist, we have a video in HTML5 mode. Otherwise, - video in Youtube or Flash modes. - - if youtube: - If english -> give back youtube_id subtitles: - Return what we have in contentstore for given youtube_id. - If non-english: - a) extract youtube_id from srt file name. - b) try to find sjson by youtube_id and return if successful. - c) generate sjson from srt for all youtube speeds. - if non-youtube: - If english -> give back `sub` subtitles: - Return what we have in contentstore for given subs_if that is stored in self.sub. - If non-english: - a) try to find previously generated sjson. - b) otherwise generate sjson from srt and return it. - - Filenames naming: - en: subs_videoid.srt.sjson - non_en: uk_subs_videoid.srt.sjson - - Raises: - NotFoundError if for 'en' subtitles no asset is uploaded. - NotFoundError if youtube_id does not exist / invalid youtube_id - """ - sub, other_lang = transcripts["sub"], transcripts["transcripts"] - if youtube_id: - # Youtube case: - if self.transcript_language == 'en': - return Transcript.asset(self.location, youtube_id).data - - youtube_ids = youtube_speed_dict(self) - if youtube_id not in youtube_ids: - log.info("Youtube_id %s does not exist", youtube_id) - raise NotFoundError - - try: - 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( - self, - other_lang[self.transcript_language], - {speed: youtube_id for youtube_id, speed in youtube_ids.items()}, - self.transcript_language - ) - sjson_transcript = Transcript.asset(self.location, youtube_id, self.transcript_language).data - - return sjson_transcript - else: - # HTML5 case - if self.transcript_language == 'en': - if '.srt' not in sub: # not bumper case - return Transcript.asset(self.location, sub).data - try: - return get_or_create_sjson(self, {'en': sub}) - except TranscriptException: - pass # to raise NotFoundError and try to get data in get_static_transcript - elif other_lang: - return get_or_create_sjson(self, other_lang) - - raise NotFoundError - def get_static_transcript(self, request, transcripts): """ Courses that are imported with the --nostatic flag do not show @@ -358,6 +282,7 @@ def transcript(self, request, dispatch): try: if is_bumper: + # TODO: We should either move this method to service or merge it with the service get_transcript() content, filename, mimetype = get_transcript_from_contentstore( self, self.transcript_language,