Пример #1
0
 def test_force_database_error(self, mock_get):
     """
     Tests to see if an database error will be handled
     """
     mock_get.side_effect = DatabaseError("DatabaseError")
     with self.assertRaises(api.ValInternalError):
         api.get_video_info(constants.VIDEO_DICT_FISH.get("edx_video_id"))
Пример #2
0
 def test_force_database_error(self, mock_get):
     """
     Tests to see if an database error will be handled
     """
     mock_get.side_effect = DatabaseError("DatabaseError")
     with self.assertRaises(api.ValInternalError):
         api.get_video_info(constants.VIDEO_DICT_FISH.get("edx_video_id"))
Пример #3
0
    def test_no_such_video(self):
        """
        Tests searching for a video that does not exist
        """

        with self.assertRaises(api.ValVideoNotFoundError):
            api.get_video_info("non_existant-video__")
        with self.assertRaises(api.ValVideoNotFoundError):
            api.get_video_info("")
Пример #4
0
 def test_get_info_queries_for_one_encoded_video(self):
     """
     Tests number of queries for a Video/EncodedVideo(1) pair
     """
     url = reverse("video-list")
     response = self.client.post(url, constants.COMPLETE_SET_STAR, format="json")
     self.assertEqual(response.status_code, status.HTTP_201_CREATED)
     with self.assertNumQueries(5):
         api.get_video_info(constants.COMPLETE_SET_STAR.get("edx_video_id"))
Пример #5
0
    def test_no_such_video(self):
        """
        Tests searching for a video that does not exist
        """

        with self.assertRaises(api.ValVideoNotFoundError):
            api.get_video_info("non_existant-video__")
        with self.assertRaises(api.ValVideoNotFoundError):
            api.get_video_info("")
Пример #6
0
 def test_get_info_queries_for_only_video(self):
     """
     Tests number of queries for a Video with no Encoded Videopair
     """
     url = reverse("video-list")
     response = self.client.post(url, constants.VIDEO_DICT_ZEBRA, format="json")
     self.assertEqual(response.status_code, status.HTTP_201_CREATED)
     with self.assertNumQueries(4):
         api.get_video_info(constants.VIDEO_DICT_ZEBRA.get("edx_video_id"))
Пример #7
0
 def test_force_internal_error(self, mock_init):
     """
     Tests to see if an unknown error will be handled
     """
     mock_init.side_effect = Exception("Mock error")
     with self.assertRaises(api.ValInternalError):
         api.get_video_info(
             constants.VIDEO_DICT_FISH.get("edx_video_id")
         )
Пример #8
0
 def test_get_info_queries_for_only_video(self):
     """
     Tests number of queries for a Video with no Encoded Videopair
     """
     url = reverse('video-list')
     response = self.client.post(url,
                                 constants.VIDEO_DICT_ZEBRA,
                                 format='json')
     self.assertEqual(response.status_code, status.HTTP_201_CREATED)
     with self.assertNumQueries(4):
         api.get_video_info(constants.VIDEO_DICT_ZEBRA.get("edx_video_id"))
Пример #9
0
 def test_get_info_queries_for_one_encoded_video(self):
     """
     Tests number of queries for a Video/EncodedVideo(1) pair
     """
     url = reverse('video-list')
     response = self.client.post(url,
                                 constants.COMPLETE_SET_STAR,
                                 format='json')
     self.assertEqual(response.status_code, status.HTTP_201_CREATED)
     with self.assertNumQueries(5):
         api.get_video_info(constants.COMPLETE_SET_STAR.get("edx_video_id"))
Пример #10
0
 def test_get_video_found(self):
     """
     Tests for successful video request
     """
     self.assertIsNotNone(
         api.get_video_info(
             constants.COMPLETE_SET_FISH.get("edx_video_id")))
Пример #11
0
    def test_import_val_data_invalid(self):
        create_profile('mobile')
        module_system = DummySystem(load_error_modules=True)

        # Negative file_size is invalid
        xml_data = """
            <video edx_video_id="test_edx_video_id">
                <video_asset client_video_id="test_client_video_id" duration="111.0">
                    <encoded_video profile="mobile" url="http://example.com/video" file_size="-222" bitrate="333"/>
                </video_asset>
            </video>
        """
        with self.assertRaises(ValCannotCreateError):
            VideoDescriptor.from_xml(xml_data, module_system, id_generator=Mock())
        with self.assertRaises(ValVideoNotFoundError):
            get_video_info("test_edx_video_id")
Пример #12
0
    def test_import_val_data(self):
        create_profile('mobile')
        module_system = DummySystem(load_error_modules=True)

        xml_data = """
            <video edx_video_id="test_edx_video_id">
                <video_asset client_video_id="test_client_video_id" duration="111.0">
                    <encoded_video profile="mobile" url="http://example.com/video" file_size="222" bitrate="333"/>
                </video_asset>
            </video>
        """
        id_generator = Mock()
        id_generator.target_course_id = "test_course_id"
        video = VideoDescriptor.from_xml(xml_data, module_system, id_generator)
        self.assertEqual(video.edx_video_id, 'test_edx_video_id')
        video_data = get_video_info(video.edx_video_id)
        self.assertEqual(video_data['client_video_id'], 'test_client_video_id')
        self.assertEqual(video_data['duration'], 111)
        self.assertEqual(video_data['status'], 'imported')
        self.assertEqual(video_data['courses'],
                         [id_generator.target_course_id])
        self.assertEqual(video_data['encoded_videos'][0]['profile'], 'mobile')
        self.assertEqual(video_data['encoded_videos'][0]['url'],
                         'http://example.com/video')
        self.assertEqual(video_data['encoded_videos'][0]['file_size'], 222)
        self.assertEqual(video_data['encoded_videos'][0]['bitrate'], 333)
Пример #13
0
    def test_import_val_data_invalid(self):
        create_profile('mobile')
        module_system = DummySystem(load_error_modules=True)

        # Negative file_size is invalid
        xml_data = """
            <video edx_video_id="test_edx_video_id">
                <video_asset client_video_id="test_client_video_id" duration="111.0">
                    <encoded_video profile="mobile" url="http://example.com/video" file_size="-222" bitrate="333"/>
                </video_asset>
            </video>
        """
        with self.assertRaises(ValCannotCreateError):
            VideoDescriptor.from_xml(xml_data, module_system, id_generator=Mock())
        with self.assertRaises(ValVideoNotFoundError):
            get_video_info("test_edx_video_id")
Пример #14
0
 def test_get_video_found(self):
     """
     Tests for successful video request
     """
     self.assertIsNotNone(
         api.get_video_info(
             constants.COMPLETE_SET_FISH.get("edx_video_id")
         )
     )
Пример #15
0
 def setup_val_video(self, associate_course_in_val=False):
     """
     Creates a video entry in VAL.
     Arguments:
         associate_course - If True, associates the test course with the video in VAL.
     """
     create_profile('mobile')
     create_video({
         'edx_video_id': self.TEST_EDX_VIDEO_ID,
         'client_video_id': 'test_client_video_id',
         'duration': self.TEST_DURATION,
         'status': 'dummy',
         'encoded_videos': [self.TEST_ENCODED_VIDEO],
         'courses': [self.video.location.course_key] if associate_course_in_val else [],
     })
     self.val_video = get_video_info(self.TEST_EDX_VIDEO_ID)  # pylint: disable=attribute-defined-outside-init
Пример #16
0
    def test_import_val_data(self):
        create_profile('mobile')
        module_system = DummySystem(load_error_modules=True)

        xml_data = """
            <video edx_video_id="test_edx_video_id">
                <video_asset client_video_id="test_client_video_id" duration="111.0">
                    <encoded_video profile="mobile" url="http://example.com/video" file_size="222" bitrate="333"/>
                </video_asset>
            </video>
        """
        video = VideoDescriptor.from_xml(xml_data, module_system, id_generator=Mock())
        self.assertEqual(video.edx_video_id, 'test_edx_video_id')
        video_data = get_video_info(video.edx_video_id)
        self.assertEqual(video_data['client_video_id'], 'test_client_video_id')
        self.assertEqual(video_data['duration'], 111)
        self.assertEqual(video_data['status'], 'imported')
        self.assertEqual(video_data['courses'], [])
        self.assertEqual(video_data['encoded_videos'][0]['profile'], 'mobile')
        self.assertEqual(video_data['encoded_videos'][0]['url'], 'http://example.com/video')
        self.assertEqual(video_data['encoded_videos'][0]['file_size'], 222)
        self.assertEqual(video_data['encoded_videos'][0]['bitrate'], 333)
Пример #17
0
    def test_import_val_data(self):
        create_profile("mobile")
        module_system = DummySystem(load_error_modules=True)

        xml_data = """
            <video edx_video_id="test_edx_video_id">
                <video_asset client_video_id="test_client_video_id" duration="111.0">
                    <encoded_video profile="mobile" url="http://example.com/video" file_size="222" bitrate="333"/>
                </video_asset>
            </video>
        """
        id_generator = Mock()
        id_generator.target_course_id = "test_course_id"
        video = VideoDescriptor.from_xml(xml_data, module_system, id_generator)
        self.assertEqual(video.edx_video_id, "test_edx_video_id")
        video_data = get_video_info(video.edx_video_id)
        self.assertEqual(video_data["client_video_id"], "test_client_video_id")
        self.assertEqual(video_data["duration"], 111)
        self.assertEqual(video_data["status"], "imported")
        self.assertEqual(video_data["courses"], [id_generator.target_course_id])
        self.assertEqual(video_data["encoded_videos"][0]["profile"], "mobile")
        self.assertEqual(video_data["encoded_videos"][0]["url"], "http://example.com/video")
        self.assertEqual(video_data["encoded_videos"][0]["file_size"], 222)
        self.assertEqual(video_data["encoded_videos"][0]["bitrate"], 333)
Пример #18
0
    def get_html(self):
        track_status = (self.download_track and self.track)
        transcript_download_format = self.transcript_download_format if not track_status else None
        sources = filter(None, self.html5_sources)

        download_video_link = None
        branding_info = None
        youtube_streams = ""
        video_duration = None

        # Determine if there is an alternative source for this video
        # based on user locale.  This exists to support cases where
        # we leverage a geography specific CDN, like China.
        cdn_url = getattr(settings, 'VIDEO_CDN_URL',
                          {}).get(self.system.user_location)

        # If we have an edx_video_id, we prefer its values over what we store
        # internally for download links (source, html5_sources) and the youtube
        # stream.
        if self.edx_video_id and edxval_api:
            try:
                val_profiles = ["youtube", "desktop_webm", "desktop_mp4"]

                if HLSPlaybackEnabledFlag.feature_enabled(self.course_id):
                    val_profiles.append('hls')

                # strip edx_video_id to prevent ValVideoNotFoundError error if unwanted spaces are there. TNL-5769
                val_video_urls = edxval_api.get_urls_for_profiles(
                    self.edx_video_id.strip(), val_profiles)

                # VAL will always give us the keys for the profiles we asked for, but
                # if it doesn't have an encoded video entry for that Video + Profile, the
                # value will map to `None`

                # add the non-youtube urls to the list of alternative sources
                # use the last non-None non-youtube non-hls url as the link to download the video
                for url in [
                        val_video_urls[p] for p in val_profiles
                        if p != "youtube"
                ]:
                    if url:
                        if url not in sources:
                            sources.append(url)
                        # don't include hls urls for download
                        if self.download_video and not url.endswith('.m3u8'):
                            # function returns None when the url cannot be re-written
                            rewritten_link = rewrite_video_url(cdn_url, url)
                            if rewritten_link:
                                download_video_link = rewritten_link
                            else:
                                download_video_link = url

                # set the youtube url
                if val_video_urls["youtube"]:
                    youtube_streams = "1.00:{}".format(
                        val_video_urls["youtube"])

                # get video duration
                video_data = edxval_api.get_video_info(
                    self.edx_video_id.strip())
                video_duration = video_data.get('duration')

            except (edxval_api.ValInternalError,
                    edxval_api.ValVideoNotFoundError):
                # VAL raises this exception if it can't find data for the edx video ID. This can happen if the
                # course data is ported to a machine that does not have the VAL data. So for now, pass on this
                # exception and fallback to whatever we find in the VideoDescriptor.
                log.warning(
                    "Could not retrieve information from VAL for edx Video ID: %s.",
                    self.edx_video_id)

        # If the user comes from China use China CDN for html5 videos.
        # 'CN' is China ISO 3166-1 country code.
        # Video caching is disabled for Studio. User_location is always None in Studio.
        # CountryMiddleware disabled for Studio.
        if getattr(self, 'video_speed_optimizations', True) and cdn_url:
            branding_info = BrandingInfoConfig.get_config().get(
                self.system.user_location)

            for index, source_url in enumerate(sources):
                new_url = rewrite_video_url(cdn_url, source_url)
                if new_url:
                    sources[index] = new_url

        # If there was no edx_video_id, or if there was no download specified
        # for it, we fall back on whatever we find in the VideoDescriptor
        if not download_video_link and self.download_video:
            if self.source:
                download_video_link = self.source
            elif self.html5_sources:
                download_video_link = self.html5_sources[0]

            # don't give the option to download HLS video urls
            if download_video_link and download_video_link.endswith('.m3u8'):
                download_video_link = None

        feature_enabled = is_val_transcript_feature_enabled_for_course(
            self.course_id)
        transcripts = self.get_transcripts_info(
            include_val_transcripts=feature_enabled)
        track_url, transcript_language, sorted_languages = self.get_transcripts_for_student(
            transcripts=transcripts)

        # CDN_VIDEO_URLS is only to be used here and will be deleted
        # TODO([email protected]): Delete this after the CDN experiment has completed.
        html_id = self.location.html_id()
        if self.system.user_location == 'CN' and \
                settings.FEATURES.get('ENABLE_VIDEO_BEACON', False) and \
                html_id in getattr(settings, 'CDN_VIDEO_URLS', {}).keys():
            cdn_urls = getattr(settings, 'CDN_VIDEO_URLS', {})[html_id]
            cdn_exp_group, new_source = random.choice(
                zip(range(len(cdn_urls)), cdn_urls))
            if cdn_exp_group > 0:
                sources[0] = new_source
            cdn_eval = True
        else:
            cdn_eval = False
            cdn_exp_group = None

        self.youtube_streams = youtube_streams or create_youtube_string(self)  # pylint: disable=W0201

        settings_service = self.runtime.service(self, 'settings')

        yt_api_key = None
        if settings_service:
            xblock_settings = settings_service.get_settings_bucket(self)
            if xblock_settings and 'YOUTUBE_API_KEY' in xblock_settings:
                yt_api_key = xblock_settings['YOUTUBE_API_KEY']

        poster = None
        if edxval_api and self.edx_video_id:
            poster = edxval_api.get_course_video_image_url(
                course_id=self.runtime.course_id.for_branch(None),
                edx_video_id=self.edx_video_id.strip())

        metadata = {
            'saveStateUrl':
            self.system.ajax_url + '/save_user_state',
            'autoplay':
            settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
            'streams':
            self.youtube_streams,
            'sub':
            self.sub,
            'sources':
            sources,
            'poster':
            poster,
            'duration':
            video_duration,
            # This won't work when we move to data that
            # isn't on the filesystem
            'captionDataDir':
            getattr(self, 'data_dir', None),
            'showCaptions':
            json.dumps(self.show_captions),
            'generalSpeed':
            self.global_speed,
            'speed':
            self.speed,
            'savedVideoPosition':
            self.saved_video_position.total_seconds(),
            'start':
            self.start_time.total_seconds(),
            'end':
            self.end_time.total_seconds(),
            'transcriptLanguage':
            transcript_language,
            'transcriptLanguages':
            sorted_languages,
            'ytTestTimeout':
            settings.YOUTUBE['TEST_TIMEOUT'],
            'ytApiUrl':
            settings.YOUTUBE['API'],
            'ytMetadataUrl':
            settings.YOUTUBE['METADATA_URL'],
            'ytKey':
            yt_api_key,
            'transcriptTranslationUrl':
            self.runtime.handler_url(self, 'transcript',
                                     'translation/__lang__').rstrip('/?'),
            'transcriptAvailableTranslationsUrl':
            self.runtime.handler_url(self, 'transcript',
                                     'available_translations').rstrip('/?'),

            ## For now, the option "data-autohide-html5" is hard coded. This option
            ## either enables or disables autohiding of controls and captions on mouse
            ## inactivity. If set to true, controls and captions will autohide for
            ## HTML5 sources (non-YouTube) after a period of mouse inactivity over the
            ## whole video. When the mouse moves (or a key is pressed while any part of
            ## the video player is focused), the captions and controls will be shown
            ## once again.
            ##
            ## There is no option in the "Advanced Editor" to set this option. However,
            ## this option will have an effect if changed to "True". The code on
            ## front-end exists.
            'autohideHtml5':
            False,

            # This is the server's guess at whether youtube is available for
            # this user, based on what was recorded the last time we saw the
            # user, and defaulting to True.
            'recordedYoutubeIsAvailable':
            self.youtube_is_available,
        }

        bumperize(self)

        context = {
            'bumper_metadata':
            json.dumps(self.bumper['metadata']),  # pylint: disable=E1101
            'metadata':
            json.dumps(OrderedDict(metadata)),
            'poster':
            json.dumps(get_poster(self)),
            'branding_info':
            branding_info,
            'cdn_eval':
            cdn_eval,
            'cdn_exp_group':
            cdn_exp_group,
            'id':
            self.location.html_id(),
            'display_name':
            self.display_name_with_default,
            'handout':
            self.handout,
            'download_video_link':
            download_video_link,
            'track':
            track_url,
            'transcript_download_format':
            transcript_download_format,
            'transcript_download_formats_list':
            self.descriptor.fields['transcript_download_format'].values,
            'license':
            getattr(self, "license", None),
        }
        return self.system.render_template('video.html', context)
Пример #19
0
    def test_post_success(self, mock_conn, mock_key):
        files = [
            {
                "file_name": "first.mp4",
                "content_type": "video/mp4",
            },
            {
                "file_name": "second.mp4",
                "content_type": "video/mp4",
            },
            {
                "file_name": "third.mov",
                "content_type": "video/quicktime",
            },
            {
                "file_name": "fourth.mp4",
                "content_type": "video/mp4",
            },
        ]

        bucket = Mock()
        mock_conn.return_value = Mock(get_bucket=Mock(return_value=bucket))
        mock_key_instances = [
            Mock(
                generate_url=Mock(
                    return_value="http://example.com/url_{}".format(file_info["file_name"])
                )
            )
            for file_info in files
        ]
        # If extra calls are made, return a dummy
        mock_key.side_effect = mock_key_instances + [Mock()]

        response = self.client.post(
            self.url,
            json.dumps({"files": files}),
            content_type="application/json"
        )
        self.assertEqual(response.status_code, 200)
        response_obj = json.loads(response.content)

        mock_conn.assert_called_once_with(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
        self.assertEqual(len(response_obj["files"]), len(files))
        self.assertEqual(mock_key.call_count, len(files))
        for i, file_info in enumerate(files):
            # Ensure Key was set up correctly and extract id
            key_call_args, __ = mock_key.call_args_list[i]
            self.assertEqual(key_call_args[0], bucket)
            path_match = re.match(
                (
                    settings.VIDEO_UPLOAD_PIPELINE["ROOT_PATH"] +
                    "/([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})$"
                ),
                key_call_args[1]
            )
            self.assertIsNotNone(path_match)
            video_id = path_match.group(1)
            mock_key_instance = mock_key_instances[i]
            mock_key_instance.set_metadata.assert_any_call(
                "course_video_upload_token",
                self.test_token
            )
            mock_key_instance.set_metadata.assert_any_call(
                "client_video_id",
                file_info["file_name"]
            )
            mock_key_instance.set_metadata.assert_any_call("course_key", unicode(self.course.id))
            mock_key_instance.generate_url.assert_called_once_with(
                KEY_EXPIRATION_IN_SECONDS,
                "PUT",
                headers={"Content-Type": file_info["content_type"]}
            )

            # Ensure VAL was updated
            val_info = get_video_info(video_id)
            self.assertEqual(val_info["status"], "upload")
            self.assertEqual(val_info["client_video_id"], file_info["file_name"])
            self.assertEqual(val_info["status"], "upload")
            self.assertEqual(val_info["duration"], 0)
            self.assertEqual(val_info["courses"], [unicode(self.course.id)])

            # Ensure response is correct
            response_file = response_obj["files"][i]
            self.assertEqual(response_file["file_name"], file_info["file_name"])
            self.assertEqual(response_file["upload_url"], mock_key_instance.generate_url())
Пример #20
0
    def test_post_success(self, mock_conn, mock_key):
        files = [
            {
                "file_name": "first.mp4",
                "content_type": "video/mp4",
            },
            {
                "file_name": "second.mp4",
                "content_type": "video/mp4",
            },
            {
                "file_name": "third.mov",
                "content_type": "video/quicktime",
            },
            {
                "file_name": "fourth.mp4",
                "content_type": "video/mp4",
            },
        ]

        bucket = Mock()
        mock_conn.return_value = Mock(get_bucket=Mock(return_value=bucket))
        mock_key_instances = [
            Mock(generate_url=Mock(return_value="http://example.com/url_{}".
                                   format(file_info["file_name"])))
            for file_info in files
        ]
        # If extra calls are made, return a dummy
        mock_key.side_effect = mock_key_instances + [Mock()]

        response = self.client.post(self.url,
                                    json.dumps({"files": files}),
                                    content_type="application/json")
        self.assertEqual(response.status_code, 200)
        response_obj = json.loads(response.content)

        mock_conn.assert_called_once_with(settings.AWS_ACCESS_KEY_ID,
                                          settings.AWS_SECRET_ACCESS_KEY)
        self.assertEqual(len(response_obj["files"]), len(files))
        self.assertEqual(mock_key.call_count, len(files))
        for i, file_info in enumerate(files):
            # Ensure Key was set up correctly and extract id
            key_call_args, __ = mock_key.call_args_list[i]
            self.assertEqual(key_call_args[0], bucket)
            path_match = re.match((
                settings.VIDEO_UPLOAD_PIPELINE["ROOT_PATH"] +
                "/([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})$"
            ), key_call_args[1])
            self.assertIsNotNone(path_match)
            video_id = path_match.group(1)
            mock_key_instance = mock_key_instances[i]
            mock_key_instance.set_metadata.assert_any_call(
                "course_video_upload_token", self.test_token)
            mock_key_instance.set_metadata.assert_any_call(
                "client_video_id", file_info["file_name"])
            mock_key_instance.set_metadata.assert_any_call(
                "course_key", unicode(self.course.id))
            mock_key_instance.generate_url.assert_called_once_with(
                KEY_EXPIRATION_IN_SECONDS,
                "PUT",
                headers={"Content-Type": file_info["content_type"]})

            # Ensure VAL was updated
            val_info = get_video_info(video_id)
            self.assertEqual(val_info["status"], "upload")
            self.assertEqual(val_info["client_video_id"],
                             file_info["file_name"])
            self.assertEqual(val_info["status"], "upload")
            self.assertEqual(val_info["duration"], 0)
            self.assertEqual(val_info["courses"], [unicode(self.course.id)])

            # Ensure response is correct
            response_file = response_obj["files"][i]
            self.assertEqual(response_file["file_name"],
                             file_info["file_name"])
            self.assertEqual(response_file["upload_url"],
                             mock_key_instance.generate_url())
Пример #21
0
    def get_html(self):
        track_url = None
        download_video_link = None
        transcript_download_format = self.transcript_download_format
        sources = filter(None, self.html5_sources)

        # If the user comes from China use China CDN for html5 videos.
        # 'CN' is China ISO 3166-1 country code.
        # Video caching is disabled for Studio. User_location is always None in Studio.
        # CountryMiddleware disabled for Studio.
        cdn_url = getattr(settings, 'VIDEO_CDN_URL',
                          {}).get(self.system.user_location)

        if getattr(self, 'video_speed_optimizations', True) and cdn_url:
            for index, source_url in enumerate(sources):
                new_url = get_video_from_cdn(cdn_url, source_url)
                if new_url:
                    sources[index] = new_url

        if self.download_video:
            if self.edx_video_id and len(video_info.get("encoded_videos")) > 0:
                video_info = api.get_video_info(self.edx_video_id)
                encoded_videos = video_info.get("encoded_videos")
                biggest_file_size = 0
                for item in encoded_videos:
                    if item.get("file_size") > biggest_file_size:
                        biggest_file_size = item.get("file_size")
                        download_video_link = item.get("url")
            elif self.source:
                download_video_link = self.source
            elif self.html5_sources:
                download_video_link = self.html5_sources[0]

        if self.download_track:
            if self.track:
                track_url = self.track
                transcript_download_format = None
            elif self.sub or self.transcripts:
                track_url = self.runtime.handler_url(self, 'transcript',
                                                     'download').rstrip('/?')

        if not self.transcripts:
            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 = u'en'
            else:
                transcript_language = sorted(self.transcripts.keys())[0]

            native_languages = {
                lang: label
                for lang, label in settings.LANGUAGES if len(lang) == 2
            }
            languages = {
                lang: native_languages.get(lang, display)
                for lang, display in settings.ALL_LANGUAGES
                if lang in self.transcripts
            }

            if self.sub:
                languages['en'] = 'English'

        # OrderedDict for easy testing of rendered context in tests
        sorted_languages = sorted(languages.items(), key=itemgetter(1))
        if 'table' in self.transcripts:
            sorted_languages.insert(0, ('table', 'Table of Contents'))

        sorted_languages = OrderedDict(sorted_languages)

        return self.system.render_template(
            'video.html',
            {
                'ajax_url':
                self.system.ajax_url + '/save_user_state',
                'autoplay':
                settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
                # This won't work when we move to data that
                # isn't on the filesystem
                'data_dir':
                getattr(self, 'data_dir', None),
                'display_name':
                self.display_name_with_default,
                'end':
                self.end_time.total_seconds(),
                'handout':
                self.handout,
                'id':
                self.location.html_id(),
                'show_captions':
                json.dumps(self.show_captions),
                'download_video_link':
                download_video_link,
                'sources':
                json.dumps(sources),
                'speed':
                json.dumps(self.speed),
                'general_speed':
                self.global_speed,
                'saved_video_position':
                self.saved_video_position.total_seconds(),
                'start':
                self.start_time.total_seconds(),
                'sub':
                self.sub,
                'track':
                track_url,
                'youtube_streams':
                create_youtube_string(self),
                # TODO: Later on the value 1500 should be taken from some global
                # configuration setting field.
                'yt_test_timeout':
                1500,
                'yt_api_url':
                settings.YOUTUBE['API'],
                'yt_test_url':
                settings.YOUTUBE['TEST_URL'],
                'transcript_download_format':
                transcript_download_format,
                'transcript_download_formats_list':
                self.descriptor.fields['transcript_download_format'].values,
                'transcript_language':
                transcript_language,
                'transcript_languages':
                json.dumps(sorted_languages),
                'transcript_translation_url':
                self.runtime.handler_url(self, 'transcript',
                                         'translation').rstrip('/?'),
                'transcript_available_translations_url':
                self.runtime.handler_url(
                    self, 'transcript', 'available_translations').rstrip('/?'),
            })
Пример #22
0
    def get_html(self):
        track_url = None
        download_video_link = None
        transcript_download_format = self.transcript_download_format
        sources = filter(None, self.html5_sources)

        # If the user comes from China use China CDN for html5 videos.
        # 'CN' is China ISO 3166-1 country code.
        # Video caching is disabled for Studio. User_location is always None in Studio.
        # CountryMiddleware disabled for Studio.
        cdn_url = getattr(settings, 'VIDEO_CDN_URL', {}).get(self.system.user_location)

        if getattr(self, 'video_speed_optimizations', True) and cdn_url:
            for index, source_url in enumerate(sources):
                new_url = get_video_from_cdn(cdn_url, source_url)
                if new_url:
                    sources[index] = new_url

        if self.download_video:
            if self.edx_video_id and len(video_info.get("encoded_videos")) > 0:
                video_info = api.get_video_info(self.edx_video_id)
                encoded_videos = video_info.get("encoded_videos")
                biggest_file_size = 0
                for item in encoded_videos:
                    if item.get("file_size") > biggest_file_size:
                        biggest_file_size = item.get("file_size")
                        download_video_link = item.get("url")
            elif self.source:
                download_video_link = self.source
            elif self.html5_sources:
                download_video_link = self.html5_sources[0]

        if self.download_track:
            if self.track:
                track_url = self.track
                transcript_download_format = None
            elif self.sub or self.transcripts:
                track_url = self.runtime.handler_url(self, 'transcript', 'download').rstrip('/?')

        if not self.transcripts:
            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 = u'en'
            else:
                transcript_language = sorted(self.transcripts.keys())[0]

            native_languages = {lang: label for lang, label in settings.LANGUAGES if len(lang) == 2}
            languages = {
                lang: native_languages.get(lang, display)
                for lang, display in settings.ALL_LANGUAGES
                if lang in self.transcripts
            }

            if self.sub:
                languages['en'] = 'English'

        # OrderedDict for easy testing of rendered context in tests
        sorted_languages = sorted(languages.items(), key=itemgetter(1))
        if 'table' in self.transcripts:
            sorted_languages.insert(0, ('table', 'Table of Contents'))

        sorted_languages = OrderedDict(sorted_languages)

        return self.system.render_template('video.html', {
            'ajax_url': self.system.ajax_url + '/save_user_state',
            'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
            # This won't work when we move to data that
            # isn't on the filesystem
            'data_dir': getattr(self, 'data_dir', None),
            'display_name': self.display_name_with_default,
            'end': self.end_time.total_seconds(),
            'handout': self.handout,
            'id': self.location.html_id(),
            'show_captions': json.dumps(self.show_captions),
            'download_video_link': download_video_link,
            'sources': json.dumps(sources),
            'speed': json.dumps(self.speed),
            'general_speed': self.global_speed,
            'saved_video_position': self.saved_video_position.total_seconds(),
            'start': self.start_time.total_seconds(),
            'sub': self.sub,
            'track': track_url,
            'youtube_streams': create_youtube_string(self),
            # TODO: Later on the value 1500 should be taken from some global
            # configuration setting field.
            'yt_test_timeout': 1500,
            'yt_api_url': settings.YOUTUBE['API'],
            'yt_test_url': settings.YOUTUBE['TEST_URL'],
            'transcript_download_format': transcript_download_format,
            'transcript_download_formats_list': self.descriptor.fields['transcript_download_format'].values,
            'transcript_language': transcript_language,
            'transcript_languages': json.dumps(sorted_languages),
            'transcript_translation_url': self.runtime.handler_url(self, 'transcript', 'translation').rstrip('/?'),
            'transcript_available_translations_url': self.runtime.handler_url(self, 'transcript', 'available_translations').rstrip('/?'),
        })
Пример #23
0
 def test_get_info_queries_for_two_encoded_video(self):
     """
     Tests number of queries for a Video/EncodedVideo(1) pair
     """
     with self.assertNumQueries(6):
         api.get_video_info(constants.COMPLETE_SET_FISH.get("edx_video_id"))
Пример #24
0
    def get_html(self):
        track_status = (self.download_track and self.track)
        transcript_download_format = self.transcript_download_format if not track_status else None
        sources = filter(None, self.html5_sources)

        download_video_link = None
        branding_info = None
        youtube_streams = ""
        video_duration = None

        # Determine if there is an alternative source for this video
        # based on user locale.  This exists to support cases where
        # we leverage a geography specific CDN, like China.
        default_cdn_url = getattr(settings, 'VIDEO_CDN_URL', {}).get('default')
        cdn_url = getattr(settings, 'VIDEO_CDN_URL', {}).get(self.system.user_location, default_cdn_url)

        # If we have an edx_video_id, we prefer its values over what we store
        # internally for download links (source, html5_sources) and the youtube
        # stream.
        if self.edx_video_id and edxval_api:
            try:
                val_profiles = ["youtube", "desktop_webm", "desktop_mp4"]

                if HLSPlaybackEnabledFlag.feature_enabled(self.course_id):
                    val_profiles.append('hls')

                # strip edx_video_id to prevent ValVideoNotFoundError error if unwanted spaces are there. TNL-5769
                val_video_urls = edxval_api.get_urls_for_profiles(self.edx_video_id.strip(), val_profiles)

                # VAL will always give us the keys for the profiles we asked for, but
                # if it doesn't have an encoded video entry for that Video + Profile, the
                # value will map to `None`

                # add the non-youtube urls to the list of alternative sources
                # use the last non-None non-youtube non-hls url as the link to download the video
                for url in [val_video_urls[p] for p in val_profiles if p != "youtube"]:
                    if url:
                        if url not in sources:
                            sources.append(url)
                        # don't include hls urls for download
                        if self.download_video and not url.endswith('.m3u8'):
                            # function returns None when the url cannot be re-written
                            rewritten_link = rewrite_video_url(cdn_url, url)
                            if rewritten_link:
                                download_video_link = rewritten_link
                            else:
                                download_video_link = url

                # set the youtube url
                if val_video_urls["youtube"]:
                    youtube_streams = "1.00:{}".format(val_video_urls["youtube"])

                # get video duration
                video_data = edxval_api.get_video_info(self.edx_video_id.strip())
                video_duration = video_data.get('duration')

            except (edxval_api.ValInternalError, edxval_api.ValVideoNotFoundError):
                # VAL raises this exception if it can't find data for the edx video ID. This can happen if the
                # course data is ported to a machine that does not have the VAL data. So for now, pass on this
                # exception and fallback to whatever we find in the VideoDescriptor.
                log.warning("Could not retrieve information from VAL for edx Video ID: %s.", self.edx_video_id)

        # If the user comes from China use China CDN for html5 videos.
        # 'CN' is China ISO 3166-1 country code.
        # Video caching is disabled for Studio. User_location is always None in Studio.
        # CountryMiddleware disabled for Studio.
        if getattr(self, 'video_speed_optimizations', True) and cdn_url:
            branding_info = BrandingInfoConfig.get_config().get(self.system.user_location)

            for index, source_url in enumerate(sources):
                new_url = rewrite_video_url(cdn_url, source_url)
                if new_url:
                    sources[index] = new_url

        # If there was no edx_video_id, or if there was no download specified
        # for it, we fall back on whatever we find in the VideoDescriptor
        if not download_video_link and self.download_video:
            if self.source:
                download_video_link = self.source
            elif self.html5_sources:
                download_video_link = self.html5_sources[0]

            # don't give the option to download HLS video urls
            if download_video_link and download_video_link.endswith('.m3u8'):
                download_video_link = None

        feature_enabled = is_val_transcript_feature_enabled_for_course(self.course_id)
        transcripts = self.get_transcripts_info(include_val_transcripts=feature_enabled)
        track_url, transcript_language, sorted_languages = self.get_transcripts_for_student(transcripts=transcripts)

        # CDN_VIDEO_URLS is only to be used here and will be deleted
        # TODO([email protected]): Delete this after the CDN experiment has completed.
        html_id = self.location.html_id()
        if self.system.user_location == 'CN' and \
                settings.FEATURES.get('ENABLE_VIDEO_BEACON', False) and \
                html_id in getattr(settings, 'CDN_VIDEO_URLS', {}).keys():
            cdn_urls = getattr(settings, 'CDN_VIDEO_URLS', {})[html_id]
            cdn_exp_group, new_source = random.choice(zip(range(len(cdn_urls)), cdn_urls))
            if cdn_exp_group > 0:
                sources[0] = new_source
            cdn_eval = True
        else:
            cdn_eval = False
            cdn_exp_group = None

        self.youtube_streams = youtube_streams or create_youtube_string(self)  # pylint: disable=W0201

        settings_service = self.runtime.service(self, 'settings')

        yt_api_key = None
        if settings_service:
            xblock_settings = settings_service.get_settings_bucket(self)
            if xblock_settings and 'YOUTUBE_API_KEY' in xblock_settings:
                yt_api_key = xblock_settings['YOUTUBE_API_KEY']

        poster = None
        if edxval_api and self.edx_video_id:
            poster = edxval_api.get_course_video_image_url(
                course_id=self.runtime.course_id.for_branch(None),
                edx_video_id=self.edx_video_id.strip()
            )

        metadata = {
            'saveStateUrl': self.system.ajax_url + '/save_user_state',
            'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', False),
            'streams': self.youtube_streams,
            'sub': self.sub,
            'sources': sources,
            'poster': poster,
            'duration': video_duration,
            # This won't work when we move to data that
            # isn't on the filesystem
            'captionDataDir': getattr(self, 'data_dir', None),

            'showCaptions': json.dumps(self.show_captions),
            'generalSpeed': self.global_speed,
            'speed': self.speed,
            'savedVideoPosition': self.saved_video_position.total_seconds(),
            'start': self.start_time.total_seconds(),
            'end': self.end_time.total_seconds(),
            'transcriptLanguage': transcript_language,
            'transcriptLanguages': sorted_languages,
            'ytTestTimeout': settings.YOUTUBE['TEST_TIMEOUT'],
            'ytApiUrl': settings.YOUTUBE['API'],
            'ytMetadataUrl': settings.YOUTUBE['METADATA_URL'],
            'ytKey': yt_api_key,

            'transcriptTranslationUrl': self.runtime.handler_url(
                self, 'transcript', 'translation/__lang__'
            ).rstrip('/?'),
            'transcriptAvailableTranslationsUrl': self.runtime.handler_url(
                self, 'transcript', 'available_translations'
            ).rstrip('/?'),

            ## For now, the option "data-autohide-html5" is hard coded. This option
            ## either enables or disables autohiding of controls and captions on mouse
            ## inactivity. If set to true, controls and captions will autohide for
            ## HTML5 sources (non-YouTube) after a period of mouse inactivity over the
            ## whole video. When the mouse moves (or a key is pressed while any part of
            ## the video player is focused), the captions and controls will be shown
            ## once again.
            ##
            ## There is no option in the "Advanced Editor" to set this option. However,
            ## this option will have an effect if changed to "True". The code on
            ## front-end exists.
            'autohideHtml5': False,

            # This is the server's guess at whether youtube is available for
            # this user, based on what was recorded the last time we saw the
            # user, and defaulting to True.
            'recordedYoutubeIsAvailable': self.youtube_is_available,
        }

        bumperize(self)

        context = {
            'bumper_metadata': json.dumps(self.bumper['metadata']),  # pylint: disable=E1101
            'metadata': json.dumps(OrderedDict(metadata)),
            'poster': json.dumps(get_poster(self)),
            'branding_info': branding_info,
            'cdn_eval': cdn_eval,
            'cdn_exp_group': cdn_exp_group,
            'id': self.location.html_id(),
            'display_name': self.display_name_with_default,
            'handout': self.handout,
            'download_video_link': download_video_link,
            'track': track_url,
            'transcript_download_format': transcript_download_format,
            'transcript_download_formats_list': self.descriptor.fields['transcript_download_format'].values,
            'license': getattr(self, "license", None),
        }
        return self.system.render_template('video.html', context)
Пример #25
0
 def test_unicode_input(self):
     """
     Tests if unicode inputs are handled correctly
     """
     with self.assertRaises(api.ValVideoNotFoundError):
         api.get_video_info(u"๓ﻉѻฝ๓ٱซ")
Пример #26
0
 def test_unicode_input(self):
     """
     Tests if unicode inputs are handled correctly
     """
     with self.assertRaises(api.ValVideoNotFoundError):
         api.get_video_info(u"๓ﻉѻฝ๓ٱซ")
Пример #27
0
 def test_get_info_queries_for_two_encoded_video(self):
     """
     Tests number of queries for a Video/EncodedVideo(1) pair
     """
     with self.assertNumQueries(6):
         api.get_video_info(constants.COMPLETE_SET_FISH.get("edx_video_id"))
Пример #28
0
    def test_post_success(self, mock_conn, mock_key):
        files = [
            {
                'file_name': 'first.mp4',
                'content_type': 'video/mp4',
            },
            {
                'file_name': 'second.mp4',
                'content_type': 'video/mp4',
            },
            {
                'file_name': 'third.mov',
                'content_type': 'video/quicktime',
            },
            {
                'file_name': 'fourth.mp4',
                'content_type': 'video/mp4',
            },
        ]

        bucket = Mock()
        mock_conn.return_value = Mock(get_bucket=Mock(return_value=bucket))
        mock_key_instances = [
            Mock(
                generate_url=Mock(
                    return_value='http://example.com/url_{}'.format(file_info['file_name'])
                )
            )
            for file_info in files
        ]
        # If extra calls are made, return a dummy
        mock_key.side_effect = mock_key_instances + [Mock()]

        response = self.client.post(
            self.url,
            json.dumps({'files': files}),
            content_type='application/json'
        )
        self.assertEqual(response.status_code, 200)
        response_obj = json.loads(response.content)

        mock_conn.assert_called_once_with(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
        self.assertEqual(len(response_obj['files']), len(files))
        self.assertEqual(mock_key.call_count, len(files))
        for i, file_info in enumerate(files):
            # Ensure Key was set up correctly and extract id
            key_call_args, __ = mock_key.call_args_list[i]
            self.assertEqual(key_call_args[0], bucket)
            path_match = re.match(
                (
                    settings.VIDEO_UPLOAD_PIPELINE['ROOT_PATH'] +
                    '/([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})$'
                ),
                key_call_args[1]
            )
            self.assertIsNotNone(path_match)
            video_id = path_match.group(1)
            mock_key_instance = mock_key_instances[i]
            mock_key_instance.set_metadata.assert_any_call(
                'course_video_upload_token',
                self.test_token
            )
            mock_key_instance.set_metadata.assert_any_call(
                'client_video_id',
                file_info['file_name']
            )
            mock_key_instance.set_metadata.assert_any_call('course_key', unicode(self.course.id))
            mock_key_instance.generate_url.assert_called_once_with(
                KEY_EXPIRATION_IN_SECONDS,
                'PUT',
                headers={'Content-Type': file_info['content_type']}
            )

            # Ensure VAL was updated
            val_info = get_video_info(video_id)
            self.assertEqual(val_info['status'], 'upload')
            self.assertEqual(val_info['client_video_id'], file_info['file_name'])
            self.assertEqual(val_info['status'], 'upload')
            self.assertEqual(val_info['duration'], 0)
            self.assertEqual(val_info['courses'], [{unicode(self.course.id): None}])

            # Ensure response is correct
            response_file = response_obj['files'][i]
            self.assertEqual(response_file['file_name'], file_info['file_name'])
            self.assertEqual(response_file['upload_url'], mock_key_instance.generate_url())
Пример #29
0
    def student_view_data(self, context=None):
        """
        Returns a JSON representation of the student_view of this XModule.
        The contract of the JSON content is between the caller and the particular XModule.
        """
        context = context or {}

        # If the "only_on_web" field is set on this video, do not return the rest of the video's data
        # in this json view, since this video is to be accessed only through its web view."
        if self.only_on_web:
            return {"only_on_web": True}

        encoded_videos = {}
        val_video_data = {}

        # Check in VAL data first if edx_video_id exists
        if self.edx_video_id:
            video_profile_names = context.get("profiles", ["mobile_low"])

            # get and cache bulk VAL data for course
            val_course_data = self.get_cached_val_data_for_course(video_profile_names, self.location.course_key)
            val_video_data = val_course_data.get(self.edx_video_id, {})

            # Get the encoded videos if data from VAL is found
            if val_video_data:
                encoded_videos = val_video_data.get('profiles', {})

            # If information for this edx_video_id is not found in the bulk course data, make a
            # separate request for this individual edx_video_id, unless cache misses are disabled.
            # This is useful/required for videos that don't have a course designated, such as the introductory video
            # that is shared across many courses.  However, this results in a separate database request so watch
            # out for any performance hit if many such videos exist in a course.  Set the 'allow_cache_miss' parameter
            # to False to disable this fall back.
            elif context.get("allow_cache_miss", "True").lower() == "true":
                try:
                    val_video_data = edxval_api.get_video_info(self.edx_video_id)
                    # Unfortunately, the VAL API is inconsistent in how it returns the encodings, so remap here.
                    for enc_vid in val_video_data.pop('encoded_videos'):
                        if enc_vid['profile'] in video_profile_names:
                            encoded_videos[enc_vid['profile']] = {key: enc_vid[key] for key in ["url", "file_size"]}
                except edxval_api.ValVideoNotFoundError:
                    pass

        # Fall back to other video URLs in the video module if not found in VAL
        if not encoded_videos:
            video_url = self.html5_sources[0] if self.html5_sources else self.source
            if video_url:
                encoded_videos["fallback"] = {
                    "url": video_url,
                    "file_size": 0,  # File size is unknown for fallback URLs
                }

            # Include youtube link if there is no encoding for mobile- ie only a fallback URL or no encodings at all
            # We are including a fallback URL for older versions of the mobile app that don't handle Youtube urls
            if self.youtube_id_1_0:
                encoded_videos["youtube"] = {
                    "url": self.create_youtube_url(self.youtube_id_1_0),
                    "file_size": 0,  # File size is not relevant for external link
                }

        transcripts_info = self.get_transcripts_info()
        transcripts = {
            lang: self.runtime.handler_url(self, 'transcript', 'download', query="lang=" + lang, thirdparty=True)
            for lang in self.available_translations(transcripts_info)
        }

        return {
            "only_on_web": self.only_on_web,
            "duration": val_video_data.get('duration', None),
            "transcripts": transcripts,
            "encoded_videos": encoded_videos,
        }
Пример #30
0
    def test_post_success(self, mock_conn, mock_key):
        files = [
            {
                'file_name': 'first.mp4',
                'content_type': 'video/mp4',
            },
            {
                'file_name': 'second.mp4',
                'content_type': 'video/mp4',
            },
            {
                'file_name': 'third.mov',
                'content_type': 'video/quicktime',
            },
            {
                'file_name': 'fourth.mp4',
                'content_type': 'video/mp4',
            },
        ]

        bucket = Mock()
        mock_conn.return_value = Mock(get_bucket=Mock(return_value=bucket))
        mock_key_instances = [
            Mock(generate_url=Mock(return_value='http://example.com/url_{}'.
                                   format(file_info['file_name'])))
            for file_info in files
        ]
        # If extra calls are made, return a dummy
        mock_key.side_effect = mock_key_instances + [Mock()]

        response = self.client.post(self.url,
                                    json.dumps({'files': files}),
                                    content_type='application/json')
        self.assertEqual(response.status_code, 200)
        response_obj = json.loads(response.content)

        mock_conn.assert_called_once_with(settings.AWS_ACCESS_KEY_ID,
                                          settings.AWS_SECRET_ACCESS_KEY)
        self.assertEqual(len(response_obj['files']), len(files))
        self.assertEqual(mock_key.call_count, len(files))
        for i, file_info in enumerate(files):
            # Ensure Key was set up correctly and extract id
            key_call_args, __ = mock_key.call_args_list[i]
            self.assertEqual(key_call_args[0], bucket)
            path_match = re.match((
                settings.VIDEO_UPLOAD_PIPELINE['ROOT_PATH'] +
                '/([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})$'
            ), key_call_args[1])
            self.assertIsNotNone(path_match)
            video_id = path_match.group(1)
            mock_key_instance = mock_key_instances[i]
            mock_key_instance.set_metadata.assert_any_call(
                'course_video_upload_token', self.test_token)
            mock_key_instance.set_metadata.assert_any_call(
                'client_video_id', file_info['file_name'])
            mock_key_instance.set_metadata.assert_any_call(
                'course_key', unicode(self.course.id))
            mock_key_instance.generate_url.assert_called_once_with(
                KEY_EXPIRATION_IN_SECONDS,
                'PUT',
                headers={'Content-Type': file_info['content_type']})

            # Ensure VAL was updated
            val_info = get_video_info(video_id)
            self.assertEqual(val_info['status'], 'upload')
            self.assertEqual(val_info['client_video_id'],
                             file_info['file_name'])
            self.assertEqual(val_info['status'], 'upload')
            self.assertEqual(val_info['duration'], 0)
            self.assertEqual(val_info['courses'],
                             [{
                                 unicode(self.course.id): None
                             }])

            # Ensure response is correct
            response_file = response_obj['files'][i]
            self.assertEqual(response_file['file_name'],
                             file_info['file_name'])
            self.assertEqual(response_file['upload_url'],
                             mock_key_instance.generate_url())
Пример #31
0
    def editor_saved(self, user, old_metadata, old_content):
        """
        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 item.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)

        if edxval_api and int(self.duration) > 0:
            video_url = ""
            if len(self.html5_sources):
                video_url = self.html5_sources[0]
            elif self.youtube_id_1_0:
                video_url = "https://www.youtube.com/watch?v=" + str(self.youtube_id_1_0)

            if video_url:
                
                if not self.edx_video_id:
                    now = datetime.datetime.now()
                    hash_object = hashlib.sha256(str(now))
                    hex_dig = hash_object.hexdigest()
                    self.edx_video_id = hex_dig
                    self.save()

                payload = {
                    "url": video_url,
                    "edx_video_id": self.edx_video_id,
                    "duration": self.duration,
                    "status": "live",
                    "encoded_videos": [{
                        "url": video_url,
                        "file_size": 1,
                        "bitrate": 1,
                        "profile": "mobile_high"
                    }]
                }

                # TODO: Change this try catch
                try:
                    edxval_api.get_video_info(self.edx_video_id)
                except:
                    edxval_api.create_video(payload)
                    
                # with this
                # edxval_api.create_video(payload)
                # when edxval app is updated

        # 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
            )