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
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
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)
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
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 (httplib.OK, httplib.CREATED): return resp.json() elif resp.status_code == httplib.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(u'error_code'), resp_dict.get(u'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
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)
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 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)
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) text = data.content.decode('utf8') cleaned_captions_text = remove_escaping(text) return unicode(cleaned_captions_text)
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)
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)
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 = u"WEBVTT\n\n" + unicode(sub) if "WEBVTT" not in sub else unicode( sub) return sub
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
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)
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)
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."))
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)
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=(',', ':')))
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=(',', ':')))
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=(',', ':')))
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)
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 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)
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'}
class BrightcoveApiClientError(ApiClientError): """ Brightcove specific api client errors. """ default_msg = _('Brightcove API error.')
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)
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.message ) 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 == httplib.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
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.'))
class VimeoApiClientError(ApiClientError): """ Vimeo specific api client errors. """ default_msg = _('Vimeo API error.')