示例#1
0
    def get_default_transcripts(self, **kwargs):
        """
        Fetch transcripts list from a video platform.

        Arguments:
            kwargs (dict): Key-value pairs with video_id, fetched from video xblock,
                           and access_token for Vimeo API.
        Returns:
            default_transcripts (list): list of dicts of transcripts. Example:
                [
                    {
                        'lang': 'en',
                        'label': 'English',
                        'url': 'captions.cloud.vimeo.com/captions/{transcript_id}.vtt?expires=1497970668&sig=
                                {signature_hash}&download={file_name.vtt}"'
                    },
                    # ...
                ]
            message (str): Message for a user with details on default transcripts fetching outcomes.
        """
        if not self.api_client.access_token:
            raise VimeoApiClientError(_('No API credentials provided.'))

        video_id = kwargs.get('video_id')
        url = self.captions_api['url'].format(media_id=video_id)
        message = _(
            'Default transcripts successfully fetched from a video platform.')
        default_transcripts = []
        # Fetch available transcripts' languages and urls.
        try:
            json_data = self.api_client.get(url)
        except VimeoApiClientError:
            message = _(
                'No timed transcript may be fetched from a video platform.<br>'
            )
            return default_transcripts, message

        if not json_data:
            message = _(
                'There are no default transcripts for the video on the video platform.'
            )
            return default_transcripts, message

        # Populate default_transcripts
        transcripts_data = json_data.get('data')
        try:
            default_transcripts = self.parse_vimeo_texttracks(transcripts_data)
            return default_transcripts, message
        except VimeoApiClientError as client_exc:
            message = client_exc.message
            return default_transcripts, message
示例#2
0
    def test_vimeo_get_default_transcripts(self):
        """
        Test Vimeo's default transcripts fetching (positive scenario).
        """
        # Arrange
        test_json_data = {"data": [{"test_key": "test_value"}]}
        success_message = _(
            'Default transcripts successfully fetched from a video platform.')

        with patch.object(self.vimeo_player, 'api_client') as api_client_mock, \
                patch.object(self.vimeo_player, 'parse_vimeo_texttracks') as parse_texttracks_mock:
            type(api_client_mock).access_token = PropertyMock(
                return_value="test_token")
            api_client_mock.get.return_value = test_json_data
            parse_texttracks_mock.return_value = test_json_data["data"]

            # Act
            transcripts, message = self.vimeo_player.get_default_transcripts(
                video_id="test_video_id")

            # Assert
            api_client_mock.get.assert_called_with(
                'https://api.vimeo.com/videos/test_video_id/texttracks')
            parse_texttracks_mock.assert_called_with(test_json_data["data"])

            self.assertIsInstance(transcripts, list)
            self.assertEqual(message, success_message)
示例#3
0
    def get_transcript_language_parameters(lang_code):
        """
        Get parameters of a transcript's language, having checked on consistency with settings.

        Arguments:
            lang_code (str): Raw language code of a transcript, fetched from the video platform.
        Returns:
            lang_code (str): Pre-configured language code, e.g. 'br'
            lang_label (str): Pre-configured language label, e.g. 'Breton'
        """
        # Delete region subtags
        # Reference: https://github.com/edx/edx-platform/blob/release-2017-02-16-12.24/lms/envs/common.py#L861
        lang_code = lang_code[0:2]
        # Check on consistency with the pre-configured ALL_LANGUAGES
        if lang_code not in [
                language[0] for language in settings.ALL_LANGUAGES
        ]:
            raise VideoXBlockException(
                _('Not all the languages of transcripts fetched from video platform are consistent '
                  'with the pre-configured ALL_LANGUAGES'))
        lang_label = [
            language[1] for language in settings.ALL_LANGUAGES
            if language[0] == lang_code
        ][0]
        return lang_code, lang_label
示例#4
0
    def test_validate_three_play_media_config_with_3pm_streaming(
            self, get_3pm_transcripts_list_mock):
        """
        Test 3PlayMedia configuration validation (streaming enabled case).
        """
        # Arrange:
        success_message = _('Success')
        test_feedback = {'status': Status.success, 'message': success_message}
        test_transcripts_list = [{"test_transcript"}]
        get_3pm_transcripts_list_mock.return_value = test_feedback, test_transcripts_list
        request_mock = arrange_request_mock(
            '{"api_key": "test_apikey", "file_id": "test_fileid", "streaming_enabled": "1"}'  # JSON string
        )
        # Act:
        result_response = self.xblock.validate_three_play_media_config(
            request_mock)
        result = result_response.body  # pylint: disable=no-member

        # Assert:
        self.assertEqual(
            result,
            json.dumps({
                'isValid': True,
                'message': success_message
            },
                       separators=(',', ':')))
        get_3pm_transcripts_list_mock.assert_called_once_with(
            "test_fileid", "test_apikey")  # Python string
示例#5
0
    def get(self, url, headers=None, can_retry=False):
        """
        Issue REST GET request to a given URL. Can throw ApiClientError or its subclass.

        Arguments:
            url (str): API url to fetch a resource from.
            headers (dict): Headers necessary as per API, e.g. authorization bearer to perform
            authorised requests.
        Returns:
            Response in python native data format.
        """
        headers_ = {
            'Authorization':
            'Bearer {}'.format(self.access_token.encode(encoding='utf-8')),
            'Accept':
            'application/json'
        }
        if headers is not None:
            headers_.update(headers)
        resp = requests.get(url, headers=headers_)
        if resp.status_code == httplib.OK:
            return resp.json()
        else:
            raise VimeoApiClientError(
                _("Can't fetch requested data from API."))
示例#6
0
    def test_wistia_get_default_transcripts_baberlfish(self, requests_get_mock, babel_mock):
        """
        Test Wistia's default transcripts fetching (babelfish fallback).
        """
        # Arrange
        # ref: https://wistia.com/doc/data-api#captions_index
        test_api_data = [{'language': 'eng', 'english_name': 'English', }]
        requests_get_mock.return_value = ResponseStub(status_code=200, body=test_api_data)
        babel_mock.return_value = lang_code_mock = Mock()
        type(lang_code_mock).alpha2 = PropertyMock(side_effect=ValueError())

        kwargs = {
            'video_id': 'test_video_id',
            'token': 'test_token'
        }
        test_url = 'https://api.wistia.com/v1/medias/test_video_id/captions.json?api_password=test_token'
        test_download_url = 'http://api.wistia.com/v1/medias/test_video_id/captions/eng.json?api_password=test_token'
        test_message = _('Success.')
        test_transcripts = [{
            'lang': babel_mock.fromalpha3b().alpha2,
            'label': 'English',
            'url': test_download_url,
            'source': TranscriptSource.DEFAULT
        }]

        with patch.object(self.wistia_player, 'get_transcript_language_parameters') as get_params_mock:
            get_params_mock.return_value = ('en', 'English')

            # Act
            transcripts, message = self.wistia_player.get_default_transcripts(**kwargs)

            # Assert
            requests_get_mock.assert_called_once_with(test_url)
            self.assertEqual(transcripts, test_transcripts)
            self.assertEqual(message, test_message)
示例#7
0
    def test_xblock_fields_default_values(self):
        """
        Test xblock fields consistency with their default values.
        """

        self.assertEqual(self.xblock.account_id, 'account_id')
        self.assertEqual(self.xblock.captions_enabled, False)
        self.assertEqual(self.xblock.captions_language, '')
        self.assertEqual(self.xblock.current_time, 0)
        self.assertEqual(self.xblock.default_transcripts, '')
        self.assertEqual(self.xblock.display_name, _('Video'))
        self.assertEqual(self.xblock.download_transcript_allowed, False)
        self.assertEqual(self.xblock.download_video_allowed, False)
        self.assertEqual(self.xblock.download_video_url, '')
        self.assertEqual(self.xblock.end_time, datetime.timedelta(seconds=0))
        self.assertEqual(self.xblock.handout, '')
        self.assertEqual(self.xblock.href, '')
        self.assertEqual(self.xblock.muted, False)
        self.assertEqual(self.xblock.playback_rate, 1)
        self.assertEqual(self.xblock.player_id, 'default')
        self.assertEqual(self.xblock.player_name, PlayerName.DUMMY)
        self.assertEqual(self.xblock.start_time, datetime.timedelta(seconds=0))
        self.assertEqual(self.xblock.threeplaymedia_apikey, '')
        self.assertEqual(self.xblock.threeplaymedia_file_id, '')
        self.assertEqual(self.xblock.token, '')
        self.assertEqual(self.xblock.transcripts, '')
        self.assertEqual(self.xblock.transcripts_enabled, False)
        self.assertEqual(self.xblock.volume, 1)
示例#8
0
    def test_get_3pm_transcripts_list_success(self, requests_get_mock):
        """
        Test fetching of the list of available 3PlayMedia transcripts (success case).
        """
        # Arrange:
        test_json = [{"test": "json_string"}]
        test_message = _("3PlayMedia transcripts fetched successfully.")
        test_feedback = {'status': Status.success, 'message': test_message}
        requests_get_mock.return_value = ResponseStub(
            body=test_json,
            ok=True
        )
        file_id = 'test_file_id'
        api_key = 'test_api_key'
        test_api_url = 'https://static.3playmedia.com/files/test_file_id/transcripts?apikey=test_api_key'

        # Act:
        feedback, transcripts_list = self.xblock.get_3pm_transcripts_list(file_id, api_key)

        # Assert:
        self.assertTrue(requests_get_mock.ok)
        self.assertTrue(requests_get_mock.json.assert_called)
        self.assertEqual(transcripts_list, test_json)
        self.assertEqual(feedback, test_feedback)
        requests_get_mock.assert_called_once_with(test_api_url)
示例#9
0
    def parse_vimeo_texttracks(self, transcripts_data):
        """
        Pull from texttracks' Vimeo API response json_data language and url information.

        Arguments:
            transcripts_data (list of dicts): Transcripts data.
        Returns:
            transcript (dict): {language code, language label, download url}
        """
        default_transcripts = []
        for t_data in transcripts_data:
            try:
                lang_code = t_data["language"]
                lang_label = self.get_transcript_language_parameters(
                    lang_code)[1]
                default_transcripts.append({
                    'lang': lang_code,
                    'label': lang_label,
                    'url': t_data["link"],
                })
            except KeyError:
                raise VimeoApiClientError(
                    _('Transcripts API has been changed.'))
        log.debug("Parsed Vimeo transcripts: " + str(default_transcripts))
        return default_transcripts
示例#10
0
 def test_get_transcript_language_parameters(self, lng_abbr, lng_name):
     """
     Check parameters of the transcript's language.
     """
     for backend in self.backends:
         player = self.player[backend](self.xblock)
         try:
             res = player.get_transcript_language_parameters(lng_abbr)
             self.assertEqual(res, (lng_abbr, lng_name))
         except VideoXBlockException as ex:
             self.assertIn(_('Not all the languages of transcripts fetched from video platform'), ex.message)
示例#11
0
    def download_default_transcript(self, url=None, language_code=None):  # pylint: disable=unused-argument
        """
        Download default transcript from a video platform API in WebVVT format.

        Arguments:
            url (str): Transcript download url.
        Returns:
            sub (unicode): Transcripts formatted per WebVTT format https://w3c.github.io/webvtt/
        """
        log.debug("BC: downloading default transcript from url:{}".format(url))
        if url is None:
            raise VideoXBlockException(_('`url` parameter is required.'))
        data = requests.get(url)

        return remove_escaping(data.content)
示例#12
0
    def test_vimeo_get_default_transcripts_no_token(self):
        """
        Test Vimeo's default transcripts fetching without provided API token.
        """
        # Arrange
        failure_message = _('No API credentials provided.')

        with patch.object(self.vimeo_player, 'api_client') as api_client_mock:
            type(api_client_mock).access_token = PropertyMock(return_value=None)

            # Act
            with self.assertRaises(vimeo.VimeoApiClientError) as raised:
                self.vimeo_player.get_default_transcripts()

                # Assert
                self.assertEqual(str(raised.exception), failure_message)
示例#13
0
    def test_vimeo_get_default_transcripts_get_failed(self):
        """
        Test Vimeo's default transcripts fetching with GET request failure.
        """
        # Arrange
        failure_message = _('No timed transcript may be fetched from a video platform.<br>')

        with patch.object(self.vimeo_player, 'api_client') as api_client_mock:
            type(api_client_mock).access_token = PropertyMock(return_value="test_token")
            api_client_mock.get.side_effect = vimeo.VimeoApiClientError()

            # Act
            default_transcripts, message = self.vimeo_player.get_default_transcripts(video_id="test_video_id")

            # Assert
            self.assertEqual(default_transcripts, [])
            self.assertEqual(message, failure_message)
示例#14
0
    def test_validate_three_play_media_config_initial_case(self):
        """
        Test 3PlayMedia configuration validation (initial case).
        """
        # Arrange:
        success_message = _("Initialization")
        request_mock = arrange_request_mock(
            '{"streaming_enabled": "0"}'  # JSON string
        )
        # Act:
        result_response = self.xblock.validate_three_play_media_config(request_mock)
        result = result_response.body  # pylint: disable=no-member

        # Assert:
        self.assertEqual(
            result,
            json.dumps({'isValid': True, 'message': success_message}, separators=(',', ':'))
        )
示例#15
0
    def test_validate_three_play_media_config_without_streaming(self):
        """
        Test 3PlayMedia configuration validation (streaming disabled case).
        """
        # Arrange:
        success_message = _('Success')
        request_mock = arrange_request_mock(
            '{"api_key": "test_apikey", "file_id": "test_fileid", "streaming_enabled": "0"}'  # JSON string
        )
        # Act:
        result_response = self.xblock.validate_three_play_media_config(request_mock)
        result = result_response.body  # pylint: disable=no-member

        # Assert:
        self.assertEqual(
            result,
            json.dumps({'isValid': True, 'message': success_message}, separators=(',', ':'))
        )
示例#16
0
    def test_validate_three_play_media_config_partially_configured(self):
        """
        Test 3PlayMedia configuration validation (improperly configured case).
        """
        # Arrange:
        invalid_message = _('Check provided 3PlayMedia configuration')
        request_mock = arrange_request_mock(
            '{"file_id": "test_fileid", "streaming_enabled": "1"}'  # "api_key" not provided
        )
        # Act:
        result_response = self.xblock.validate_three_play_media_config(request_mock)
        result = result_response.body  # pylint: disable=no-member

        # Assert:
        self.assertEqual(
            result,
            json.dumps({'isValid': False, 'message': invalid_message}, separators=(',', ':'))
        )
示例#17
0
    def test_vimeo_get_default_transcripts_no_data(self):
        """
        Test Vimeo's default transcripts fetching with no data returned.
        """
        # Arrange
        test_json_data = []
        success_message = _('There are no default transcripts for the video on the video platform.')

        with patch.object(self.vimeo_player, 'api_client') as api_client_mock:
            type(api_client_mock).access_token = PropertyMock(return_value="test_token")
            api_client_mock.get.return_value = test_json_data

            # Act
            transcripts, message = self.vimeo_player.get_default_transcripts(video_id="test_video_id")

            # Assert
            self.assertEqual(transcripts, [])
            self.assertEqual(message, success_message)
示例#18
0
    def download_default_transcript(self, url=None, language_code=None):  # pylint: disable=unused-argument
        """
        Download default transcript from Youtube API and format it to WebVTT-like unicode.

        Reference to `get_transcripts_from_youtube()`:
            https://github.com/edx/edx-platform/blob/ecc3473d36b3c7a360e260f8962e21cb01eb1c39/common/lib/xmodule/xmodule/video_module/transcripts_utils.py#L122
        """
        if url is None:
            raise VideoXBlockException(_('`url` parameter is required.'))
        utf8_parser = etree.XMLParser(encoding='utf-8')
        data = requests.get(url)
        xmltree = etree.fromstring(data.content, parser=utf8_parser)
        sub = [
            self.format_transcript_element(element, i)
            for i, element in enumerate(xmltree, 1)
        ]
        sub = "".join(sub)
        sub = "WEBVTT\n\n" + str(sub) if "WEBVTT" not in sub else str(sub)
        return sub
示例#19
0
    def dispatch(self, _request, suffix):
        """
        Brightcove dispatch method exposes different utility entry points.

        Entry point can either return info about video or Brightcove account
        or perform some action via Brightcove API.
        """
        if not self.api_key and self.api_secret:
            raise BrightcoveApiClientError(_('No API credentials provided'))

        routes = {
            'create_credentials':
            lambda: self.create_credentials(self.xblock.token, self.xblock.
                                            account_id),
            'ensure_ingest_profiles':
            lambda: self.ensure_ingest_profiles(self.xblock.account_id),
            'get_video_renditions':
            lambda: self.get_video_renditions(self.xblock.account_id,
                                              self.media_id(self.xblock.href)),
            'get_video_tech_info':
            lambda: self.get_video_tech_info(self.xblock.account_id,
                                             self.media_id(self.xblock.href)),
            'get_ingest_profiles':
            lambda: self.get_ingest_profiles(self.xblock.account_id),
            'retranscode-status':
            lambda: self.xblock.metadata.get('retranscode-status'),
            'submit_retranscode_default':
            lambda: self.submit_retranscode_job(
                self.xblock.account_id, self.media_id(self.xblock.href),
                'default'),
            'submit_retranscode_autoquality':
            lambda: self.submit_retranscode_job(
                self.xblock.account_id, self.media_id(self.xblock.href),
                'autoquality'),
            'submit_retranscode_encryption':
            lambda: self.submit_retranscode_job(
                self.xblock.account_id, self.media_id(self.xblock.href),
                'encryption'),
        }

        if suffix in routes:
            return routes[suffix]()
        return {'success': False, 'message': 'Unknown method'}
示例#20
0
    def test_get_3pm_transcripts_list_api_failure(self, requests_get_mock):
        """
        Test fetching of the list of available 3PlayMedia transcripts (api failure case).
        """
        # Arrange:
        test_message = _("3PlayMedia transcripts fetching API request has failed!")
        test_feedback = {'status': Status.error, 'message': test_message}
        requests_get_mock.side_effect = requests.RequestException()
        file_id = 'test_file_id'
        api_key = 'test_api_key'
        test_api_url = 'https://static.3playmedia.com/files/test_file_id/transcripts?apikey=test_api_key'

        # Act:
        feedback, transcripts_list = self.xblock.get_3pm_transcripts_list(file_id, api_key)

        # Assert:
        self.assertEqual(transcripts_list, [])
        self.assertEqual(feedback, test_feedback)
        requests_get_mock.assert_called_once_with(test_api_url)
示例#21
0
    def test_wistia_get_default_transcripts_api_failure(self, requests_get_mock):
        """
        Test Wistia's default transcripts fetching (request failure).
        """
        # Arrange
        kwargs = {
            'video_id': 'test_video_id',
            'token': 'test_token'
        }
        test_message = _('No timed transcript may be fetched from a video platform.\nError details: test_exc_message')
        test_url = 'https://api.wistia.com/v1/medias/test_video_id/captions.json?api_password=test_token'
        requests_get_mock.side_effect = requests.RequestException("test_exc_message")

        # Act
        transcripts, message = self.wistia_player.get_default_transcripts(**kwargs)

        # Assert
        requests_get_mock.assert_called_once_with(test_url)
        self.assertEqual(transcripts, [])
        self.assertEqual(message, test_message)
示例#22
0
 def _refresh_access_token(self):
     """
     Request new access token to send with requests to Brightcove. Access Token expires every 5 minutes.
     """
     url = "https://oauth.brightcove.com/v3/access_token"
     params = {"grant_type": "client_credentials"}
     auth_string = base64.encodestring('{}:{}'.format(
         self.api_key, self.api_secret)).replace('\n', '')
     headers = {
         "Content-Type": "application/x-www-form-urlencoded",
         "Authorization": "Basic " + auth_string
     }
     try:
         resp = requests.post(url, headers=headers, data=params)
         if resp.status_code == httplib.OK:
             result = resp.json()
             return result['access_token']
     except IOError:
         log.exception(
             _("Connection issue. Couldn't refresh API access token."))
         return None
示例#23
0
    def post(self, url, payload, headers=None, can_retry=True):
        """
        Issue REST POST request to a given URL. Can throw ApiClientError or its subclass.

        Arguments:
            url (str): API url to fetch a resource from.
            payload (dict): POST data.
            headers (dict): Headers necessary as per API, e.g. authorization bearer to perform authorised requests.
            can_retry (bool): True if in a case of authentication error it can refresh access token and retry a call.
        Returns:
            Response in Python native data format.
        """
        headers_ = {
            'Authorization': 'Bearer ' + self.access_token,
            'Content-type': 'application/json'
        }
        if headers is not None:
            headers_.update(headers)

        resp = requests.post(url, data=payload, headers=headers_)
        log.debug("BC response status: {}".format(resp.status_code))
        if resp.status_code in (http.client.OK, http.client.CREATED):
            return resp.json()
        elif resp.status_code == http.client.UNAUTHORIZED and can_retry:
            self.access_token = self._refresh_access_token()
            return self.post(url, payload, headers, can_retry=False)

        try:
            resp_dict = resp.json()[0]
            log.warn("API error code: %s - %s", resp_dict.get('error_code'),
                     resp_dict.get('message'))
        except (ValueError, IndexError):
            message = _(
                "Can't parse unexpected response during POST request to Brightcove API!"
            )
            log.exception(message)
            resp_dict = {"message": message}
        return resp_dict
示例#24
0
    def _refresh_access_token(self):
        """
        Request new access token to send with requests to Brightcove. Access Token expires every 5 minutes.
        """
        url = "https://oauth.brightcove.com/v3/access_token"
        params = {"grant_type": "client_credentials"}
        headers = {
            "Content-Type": "application/x-www-form-urlencoded",
        }
        basicauth = requests.auth.HTTPBasicAuth(self.api_key, self.api_secret)

        try:
            resp = requests.post(url,
                                 auth=basicauth,
                                 headers=headers,
                                 data=params)
            if resp.status_code == http.client.OK:
                result = resp.json()
                return result['access_token']
        except IOError:
            log.exception(
                _("Connection issue. Couldn't refresh API access token."))
            return None
示例#25
0
class VimeoApiClientError(ApiClientError):
    """
    Vimeo specific api client errors.
    """

    default_msg = _('Vimeo API error.')
示例#26
0
    def get_default_transcripts(self, **kwargs):
        """
        Fetch transcripts list from Wistia API.

        Urls of transcripts are to be fetched later on with separate API calls.
        References:
            https://wistia.com/doc/data-api#captions_index
            https://wistia.com/doc/data-api#captions_show

        Arguments:
            kwargs (dict): Key-value pairs with video_id, fetched from video xblock, and token, fetched from Wistia API.
        Returns:
            list: List of dicts of transcripts. Example:
            [
                {
                    'lang': 'en',
                    'label': 'English',
                    'url': 'default_url_to_be_replaced',
                    'source': 'default'
                },
                # ...
            ]
        """
        video_id = kwargs.get('video_id')
        token = kwargs.get('token')
        url = self.captions_api['url'].format(token=token, media_id=video_id)

        message = _('Success.')
        self.default_transcripts = []
        # Fetch available transcripts' languages (codes and English labels), and assign its' urls.
        try:
            # get all languages caps data:
            response = requests.get('https://{}'.format(url))
        except IOError as exc:
            # Probably, current API has changed
            message = _(
                'No timed transcript may be fetched from a video platform.\nError details: {}'
            ).format(exc)
            log.exception("Transcripts INDEX request failure.")
            return self.default_transcripts, message

        # If a video does not exist, the response will be an empty HTTP 404 Not Found.
        # Reference: https://wistia.com/doc/data-api#captions_index
        if response.status_code == http_client.NOT_FOUND:
            message = _("Wistia video {} doesn't exist.").format(video_id)
            return self.default_transcripts, message

        # Fetch other failure cases:
        if not response.ok:
            message = _("Invalid request.")
            return self.default_transcripts, message

        try:
            wistia_data = response.json()
        except ValueError:
            wistia_data = ''

        # No transcripts case, see: wistia.com/doc/data-api#captions_index
        if not wistia_data:
            message = _(
                "For now, video platform doesn't have any timed transcript for this video."
            )
            return self.default_transcripts, message

        transcripts_data = [[el.get('language'),
                             el.get('english_name')] for el in wistia_data]
        # Populate default_transcripts
        for lang_code, lang_label in transcripts_data:
            download_url = self.captions_api['download_url'].format(
                media_id=video_id, lang_code=lang_code, token=token)
            # Wistia's API uses ISO-639-2, so "lang_code" is a 3-character code, e.g. "eng".
            # Reference: https://wistia.com/doc/data-api#captions_show
            # Convert from ISO-639-2 to ISO-639-1; reference: https://pythonhosted.org/babelfish/
            try:
                lang_code = babelfish.Language(lang_code).alpha2
            except ValueError:
                # In case of B or T codes, e.g. 'fre'.
                # Reference: https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
                lang_code = babelfish.Language.fromalpha3b(lang_code).alpha2  # pylint: disable=no-member

            lang_label = self.get_transcript_language_parameters(lang_code)[1]

            self.default_transcripts.append({
                'lang': lang_code,
                'label': lang_label,
                'url': download_url,
                'source': TranscriptSource.DEFAULT,
            })

        return self.default_transcripts, message
示例#27
0
class BrightcoveApiClientError(ApiClientError):
    """
    Brightcove specific api client errors.
    """

    default_msg = _('Brightcove API error.')
示例#28
0
    def get_default_transcripts(self, **kwargs):
        """
        Fetch transcripts list from a video platform.

        Arguments:
            kwargs (dict): Key-value pairs with account_id and video_id, fetched from video xblock,
                           and access_token, fetched from Brightcove API.
        Returns:
            default_transcripts (list): list of dicts of transcripts. Example:
                [
                    {
                        'lang': 'en',
                        'label': 'English',
                        'url': 'learning-services-media.brightcove.com/captions/bc_smart_ja.vtt'
                    },
                    # ...
                ]
            message (str): Message for a user with details on default transcripts fetching outcomes.
        """
        log.debug("BC: getting default transcripts...")
        if not self.api_key and not self.api_secret:
            raise BrightcoveApiClientError(_('No API credentials provided'))

        video_id = kwargs.get('video_id')
        account_id = kwargs.get('account_id')
        url = self.captions_api['url'].format(account_id=account_id,
                                              media_id=video_id)
        message = ''
        default_transcripts = []
        # Fetch available transcripts' languages and urls if authentication succeeded.
        try:
            text = self.api_client.get(url)
        except BrightcoveApiClientError:
            message = _(
                'No timed transcript may be fetched from a video platform.')
            return default_transcripts, message

        if not text:
            message = _(
                'No timed transcript may be fetched from a video platform.')
            return default_transcripts, message

        # Handle empty response (no subs uploaded on a platform)
        captions_data = text.get('text_tracks')

        if not captions_data:
            message = _(
                "For now, video platform doesn't have any timed transcript for this video."
            )
            return default_transcripts, message

        # Populate default_transcripts
        transcripts_data = ([cap_data.get('src'),
                             cap_data.get('srclang')]
                            for cap_data in captions_data)

        for transcript_url, lang_code in transcripts_data:
            lang_label = self.get_transcript_language_parameters(lang_code)[1]
            default_transcripts.append({
                'lang': lang_code,
                'label': lang_label,
                'url': transcript_url,
                'source': TranscriptSource.DEFAULT,
            })
        log.debug("BC: default transcripts: {}".format(default_transcripts))
        return default_transcripts, message
示例#29
0
class VimeoPlayer(BaseVideoPlayer):
    """
    VimeoPlayer is used for videos hosted on vimeo.com.
    """

    # Regex is taken from http://regexr.com/3a2p0
    # Reference: https://vimeo.com/153979733
    url_re = re.compile(r'https?:\/\/(.+)?(vimeo.com)\/(?P<media_id>.*)')

    metadata_fields = ['access_token']
    default_transcripts_in_vtt = True

    # Current Vimeo api for requesting transcripts.

    # Note: Vimeo will automatically delete tokens that have not been used for an extended period of time.
    #       If your API calls are months apart you might need to create a new token.

    # For example: GET https://api.vimeo.com/videos/204151304/texttracks
    # Docs on captions: https://developer.vimeo.com/api/endpoints/videos#/%7Bvideo_id%7D/texttracks
    # Docs on auth: https://developer.vimeo.com/api/authentication
    captions_api = {
        'url': 'https://api.vimeo.com/videos/{media_id}/texttracks',
        'authorised_request_header': {
            'Authorization': 'Bearer {access_token}'
        },
        'response': {
            'total':
            '{transcripts_count}',
            'data': [{
                'uri': '/texttracks/{transcript_id}',
                'active': 'true',
                'type': 'subtitles',
                'language':
                'en',  # no language_label translated in English may be fetched from API
                'link': 'https://{link_to_vtt_file}',
                'link_expires_time': 1497954324,
                'hls_link': 'https://{link_to_vtt_file_with_hls}',
                'hls_link_expires_time': 1497954324,
                'name': '{captions_file_name.vtt}'
            }]
        }
    }

    def __init__(self, xblock):
        """
        Initialize Vimeo player class object.
        """
        super(VimeoPlayer, self).__init__(xblock)
        self.api_client = VimeoApiClient(token=xblock.token)

    @property
    def advanced_fields(self):
        """
        Tuple of VideoXBlock fields to display in Advanced tab of edit modal window.

        Vimeo videos require Access token to be set.
        """
        return super(VimeoPlayer, self).advanced_fields

    @property
    def trans_fields(self):
        """
        List of VideoXBlock fields to display on `Manual & default transcripts` panel.
        """
        fields_list = super(VimeoPlayer, self).trans_fields
        # Add `token` after `default_transcripts`
        fields_list.append('token')
        return fields_list

    fields_help = {
        'href':
        _('URL of the video page. E.g. https://vimeo.com/987654321'),
        'token':
        _('You can generate a Vimeo access token via <b>Application console\'s Authentication section</b> by '
          '<a href="https://developer.vimeo.com/apps/new" '
          'target="_blank">creating new app</a>. Please ensure appropriate operations '
          'scope ("private") has been set for access token.')
    }

    def media_id(self, href):
        """
        Extract Platform's media id from the video url.

        E.g. https://example.wistia.com/medias/12345abcde -> 12345abcde
        """
        return self.url_re.search(href).group('media_id')

    def get_frag(self, **context):
        """
        Return a Fragment required to render video player on the client side.
        """
        context['data_setup'] = json.dumps(
            VimeoPlayer.player_data_setup(context))

        frag = super(VimeoPlayer, self).get_frag(**context)
        frag.add_content(
            self.render_resource('static/html/vimeo.html', **context))
        js_files = [
            'static/vendor/js/Vimeo.js',
            'static/vendor/js/videojs-offset.min.js'
        ]

        for js_file in js_files:
            frag.add_javascript(self.resource_string(js_file))

        return frag

    @staticmethod
    def player_data_setup(context):
        """
        Vimeo Player data setup.
        """
        result = BaseVideoPlayer.player_data_setup(context)
        del result["playbackRates"]
        del result["plugins"]["videoJSSpeedHandler"]
        result.update({
            "techOrder": ["vimeo"],
            "sources": [{
                "type": "video/vimeo",
                "src": context['url']
            }],
            "vimeo": {
                "iv_load_policy": 1
            },
        })
        return result

    def get_default_transcripts(self, **kwargs):
        """
        Fetch transcripts list from a video platform.

        Arguments:
            kwargs (dict): Key-value pairs with video_id, fetched from video xblock,
                           and access_token for Vimeo API.
        Returns:
            default_transcripts (list): list of dicts of transcripts. Example:
                [
                    {
                        'lang': 'en',
                        'label': 'English',
                        'url': 'captions.cloud.vimeo.com/captions/{transcript_id}.vtt?expires=1497970668&sig=
                                {signature_hash}&download={file_name.vtt}"'
                    },
                    # ...
                ]
            message (str): Message for a user with details on default transcripts fetching outcomes.
        """
        if not self.api_client.access_token:
            raise VimeoApiClientError(_('No API credentials provided.'))

        video_id = kwargs.get('video_id')
        url = self.captions_api['url'].format(media_id=video_id)
        message = _(
            'Default transcripts successfully fetched from a video platform.')
        default_transcripts = []
        # Fetch available transcripts' languages and urls.
        try:
            json_data = self.api_client.get(url)
        except VimeoApiClientError:
            message = _(
                'No timed transcript may be fetched from a video platform.<br>'
            )
            return default_transcripts, message

        if not json_data:
            message = _(
                'There are no default transcripts for the video on the video platform.'
            )
            return default_transcripts, message

        # Populate default_transcripts
        transcripts_data = json_data.get('data')
        try:
            default_transcripts = self.parse_vimeo_texttracks(transcripts_data)
            return default_transcripts, message
        except VimeoApiClientError as client_exc:
            message = client_exc.message
            return default_transcripts, message

    def parse_vimeo_texttracks(self, transcripts_data):
        """
        Pull from texttracks' Vimeo API response json_data language and url information.

        Arguments:
            transcripts_data (list of dicts): Transcripts data.
        Returns:
            transcript (dict): {language code, language label, download url}
        """
        default_transcripts = []
        for t_data in transcripts_data:
            try:
                lang_code = t_data["language"]
                lang_label = self.get_transcript_language_parameters(
                    lang_code)[1]
                default_transcripts.append({
                    'lang': lang_code,
                    'label': lang_label,
                    'url': t_data["link"],
                })
            except KeyError:
                raise VimeoApiClientError(
                    _('Transcripts API has been changed.'))
        log.debug("Parsed Vimeo transcripts: " + str(default_transcripts))
        return default_transcripts

    def download_default_transcript(self, url, language_code=None):  # pylint: disable=unused-argument
        """
        Download default transcript from Vimeo video platform API in WebVVT format.

        Arguments:
            url (str): Transcript download url.
        Returns:
            sub (unicode): Transcripts formatted per WebVTT format https://w3c.github.io/webvtt/
        """
        data = requests.get(url)
        text = data.content.decode('utf8')
        cleaned_captions_text = remove_escaping(text)
        return unicode(cleaned_captions_text)
示例#30
0
 def post(self, url, payload, headers=None, can_retry=False):
     """
     Issue REST POST request to a given URL. Can throw ApiClientError or its subclass.
     """
     raise VimeoApiClientError(
         _('Advanced API operations not allowed for now.'))