def test_no_video_thumbnail_downloaded(
        self,
        image_content,
        image_content_type,
        error_message,
        mock_download_youtube_thumbnail,
        mock_logger
    ):
        """
        Test that when no thumbnail is downloaded, video image is not updated.
        """
        mock_download_youtube_thumbnail.return_value = image_content, image_content_type
        course_id = unicode(self.course.id)
        video1_edx_video_id = 'test-youtube-video-1'

        # Verify that video1 has no image attached.
        video1_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video1_edx_video_id)
        self.assertIsNone(video1_image_url)

        # Scrape video thumbnail.
        scrape_youtube_thumbnail(course_id, video1_edx_video_id, 'test-yt-id')

        mock_logger.info.assert_called_with(
            u'VIDEOS: Scraping youtube video thumbnail failed for edx_video_id [%s] in course [%s] with error: %s',
            video1_edx_video_id,
            course_id,
            error_message
        )

        # Verify that no image is attached to video1.
        video1_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video1_edx_video_id)
        self.assertIsNone(video1_image_url)
    def test_scrape_youtube_thumbnail(self, mocked_request):
        """
        Test that youtube thumbnails are correctly scrapped.
        """
        course_id = unicode(self.course.id)
        video1_edx_video_id = 'test-youtube-video-1'
        video2_edx_video_id = 'test-youtube-video-2'

        # Mock get youtube thumbnail responses.
        mocked_request.side_effect = [self.mocked_youtube_thumbnail_response()]

        # Verify that video1 has no image attached.
        video1_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video1_edx_video_id)
        self.assertIsNone(video1_image_url)

        # Verify that video2 has already image attached.
        video2_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video2_edx_video_id)
        self.assertIsNotNone(video2_image_url)

        # Scrape video thumbnails.
        scrape_youtube_thumbnail(course_id, video1_edx_video_id, 'test-yt-id')
        scrape_youtube_thumbnail(course_id, video2_edx_video_id, 'test-yt-id2')

        # Verify that now video1 image is attached.
        video1_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video1_edx_video_id)
        self.assertIsNotNone(video1_image_url)

        # Also verify that video2's image is not updated.
        video2_image_url_latest = get_course_video_image_url(course_id=course_id, edx_video_id=video2_edx_video_id)
        self.assertEqual(video2_image_url, video2_image_url_latest)
Beispiel #3
0
    def test_no_video_thumbnail_downloaded(self, image_content,
                                           image_content_type, error_message,
                                           mock_download_youtube_thumbnail,
                                           mock_logger):
        """
        Test that when no thumbnail is downloaded, video image is not updated.
        """
        mock_download_youtube_thumbnail.return_value = image_content, image_content_type
        course_id = six.text_type(self.course.id)
        video1_edx_video_id = 'test-youtube-video-1'

        # Verify that video1 has no image attached.
        video1_image_url = get_course_video_image_url(
            course_id=course_id, edx_video_id=video1_edx_video_id)
        self.assertIsNone(video1_image_url)

        # Scrape video thumbnail.
        scrape_youtube_thumbnail(course_id, video1_edx_video_id, 'test-yt-id')

        mock_logger.info.assert_called_with(
            u'VIDEOS: Scraping youtube video thumbnail failed for edx_video_id [%s] in course [%s] with error: %s',
            video1_edx_video_id, course_id, error_message)

        # Verify that no image is attached to video1.
        video1_image_url = get_course_video_image_url(
            course_id=course_id, edx_video_id=video1_edx_video_id)
        self.assertIsNone(video1_image_url)
Beispiel #4
0
    def test_scrape_youtube_thumbnail(self, mocked_request):
        """
        Test that youtube thumbnails are correctly scrapped.
        """
        course_id = six.text_type(self.course.id)
        video1_edx_video_id = 'test-youtube-video-1'
        video2_edx_video_id = 'test-youtube-video-2'

        # Mock get youtube thumbnail responses.
        mocked_request.side_effect = [self.mocked_youtube_thumbnail_response()]

        # Verify that video1 has no image attached.
        video1_image_url = get_course_video_image_url(
            course_id=course_id, edx_video_id=video1_edx_video_id)
        self.assertIsNone(video1_image_url)

        # Verify that video2 has already image attached.
        video2_image_url = get_course_video_image_url(
            course_id=course_id, edx_video_id=video2_edx_video_id)
        self.assertIsNotNone(video2_image_url)

        # Scrape video thumbnails.
        scrape_youtube_thumbnail(course_id, video1_edx_video_id, 'test-yt-id')
        scrape_youtube_thumbnail(course_id, video2_edx_video_id, 'test-yt-id2')

        # Verify that now video1 image is attached.
        video1_image_url = get_course_video_image_url(
            course_id=course_id, edx_video_id=video1_edx_video_id)
        self.assertIsNotNone(video1_image_url)

        # Also verify that video2's image is not updated.
        video2_image_url_latest = get_course_video_image_url(
            course_id=course_id, edx_video_id=video2_edx_video_id)
        self.assertEqual(video2_image_url, video2_image_url_latest)
Beispiel #5
0
    def test_no_video_image(self):
        """
        Test image url is set to None if no video image.
        """
        edx_video_id = 'test1'
        get_videos_url = reverse_course_url('videos_handler', self.course.id)
        video_image_upload_url = self.get_url_for_course_key(
            self.course.id, {'edx_video_id': edx_video_id})
        with make_image_file(
                dimensions=(settings.VIDEO_IMAGE_MIN_WIDTH,
                            settings.VIDEO_IMAGE_MIN_HEIGHT), ) as image_file:
            self.client.post(video_image_upload_url, {'file': image_file},
                             format='multipart')

        val_image_url = get_course_video_image_url(course_id=self.course.id,
                                                   edx_video_id=edx_video_id)

        response = self.client.get_json(get_videos_url)
        self.assertEqual(response.status_code, 200)
        response_videos = json.loads(response.content)["videos"]
        for response_video in response_videos:
            if response_video['edx_video_id'] == edx_video_id:
                self.assertEqual(response_video['course_video_image_url'],
                                 val_image_url)
            else:
                self.assertEqual(response_video['course_video_image_url'],
                                 None)
def scrape_youtube_thumbnail(course_id, edx_video_id, youtube_id):
    """
    Scrapes youtube thumbnail for a given video.
    """
    # Scrape when course video image does not exist for edx_video_id.
    if not get_course_video_image_url(course_id, edx_video_id):
        thumbnail_content, thumbnail_content_type = download_youtube_video_thumbnail(youtube_id)
        supported_content_types = {v: k for k, v in settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.iteritems()}
        image_filename = '{youtube_id}{image_extention}'.format(
            youtube_id=youtube_id,
            image_extention=supported_content_types.get(
                thumbnail_content_type, supported_content_types['image/jpeg']
            )
        )
        image_file = SimpleUploadedFile(image_filename, thumbnail_content, thumbnail_content_type)
        validate_and_update_video_image(course_id, edx_video_id, image_file, image_filename)
Beispiel #7
0
def scrape_youtube_thumbnail(course_id, edx_video_id, youtube_id):
    """
    Scrapes youtube thumbnail for a given video.
    """
    # Scrape when course video image does not exist for edx_video_id.
    if not get_course_video_image_url(course_id, edx_video_id):
        thumbnail_content, thumbnail_content_type = download_youtube_video_thumbnail(youtube_id)
        supported_content_types = {v: k for k, v in settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.items()}
        image_filename = '{youtube_id}{image_extention}'.format(
            youtube_id=youtube_id,
            image_extention=supported_content_types.get(
                thumbnail_content_type, supported_content_types['image/jpeg']
            )
        )
        image_file = SimpleUploadedFile(image_filename, thumbnail_content, thumbnail_content_type)
        validate_and_update_video_image(course_id, edx_video_id, image_file, image_filename)
Beispiel #8
0
    def verify_image_upload_reponse(self, course_id, edx_video_id, upload_response):
        """
        Verify that image is uploaded successfully.

        Arguments:
            course_id: ID of course
            edx_video_id: ID of video
            upload_response: Upload response object

        Returns:
            uploaded image url
        """
        self.assertEqual(upload_response.status_code, 200)
        response = json.loads(upload_response.content)
        val_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=edx_video_id)
        self.assertEqual(response['image_url'], val_image_url)

        return val_image_url
    def verify_image_upload_reponse(self, course_id, edx_video_id, upload_response):
        """
        Verify that image is uploaded successfully.

        Arguments:
            course_id: ID of course
            edx_video_id: ID of video
            upload_response: Upload response object

        Returns:
            uploaded image url
        """
        self.assertEqual(upload_response.status_code, 200)
        response = json.loads(upload_response.content)
        val_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=edx_video_id)
        self.assertEqual(response['image_url'], val_image_url)

        return val_image_url
Beispiel #10
0
    def test_no_video_image(self):
        """
        Test image url is set to None if no video image.
        """
        edx_video_id = 'test1'
        get_videos_url = reverse_course_url('videos_handler', self.course.id)
        video_image_upload_url = self.get_url_for_course_key(self.course.id, {'edx_video_id': edx_video_id})
        with make_image_file(
            dimensions=(settings.VIDEO_IMAGE_MIN_WIDTH, settings.VIDEO_IMAGE_MIN_HEIGHT),
        ) as image_file:
            self.client.post(video_image_upload_url, {'file': image_file}, format='multipart')

        val_image_url = get_course_video_image_url(course_id=self.course.id, edx_video_id=edx_video_id)

        response = self.client.get_json(get_videos_url)
        self.assertEqual(response.status_code, 200)
        response_videos = json.loads(response.content)["videos"]
        for response_video in response_videos:
            if response_video['edx_video_id'] == edx_video_id:
                self.assertEqual(response_video['course_video_image_url'], val_image_url)
            else:
                self.assertEqual(response_video['course_video_image_url'], None)
Beispiel #11
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 = ""

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

            except edxval_api.ValInternalError:
                # 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

        track_url, transcript_language, sorted_languages = self.get_transcripts_for_student(self.get_transcripts_info())

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