def _get_index_videos(course): """ Returns the information about each video upload required for the video list """ course_id = unicode(course.id) attrs = ['edx_video_id', 'client_video_id', 'created', 'duration', 'status', 'courses'] if VideoTranscriptEnabledFlag.feature_enabled(course.id): attrs += ['transcripts'] def _get_values(video): """ Get data for predefined video attributes. """ values = {} for attr in attrs: if attr == 'courses': course = filter(lambda c: course_id in c, video['courses']) (__, values['course_video_image_url']), = course[0].items() else: values[attr] = video[attr] return values return [ _get_values(video) for video in _get_videos(course) ]
def videos_index_html(course): """ Returns an HTML page to display previous video uploads and allow new ones """ is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled( course.id) context = { 'context_course': course, 'image_upload_url': reverse_course_url('video_images_handler', unicode(course.id)), 'video_handler_url': reverse_course_url('videos_handler', unicode(course.id)), 'encodings_download_url': reverse_course_url('video_encodings_download', unicode(course.id)), 'default_video_image_url': _get_default_video_image_url(), 'previous_uploads': _get_index_videos(course), 'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0), 'video_supported_file_formats': VIDEO_SUPPORTED_FILE_FORMATS.keys(), 'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB, 'video_image_settings': { 'video_image_upload_enabled': WAFFLE_SWITCHES.is_enabled(VIDEO_IMAGE_UPLOAD_ENABLED), 'max_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'], 'min_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'], 'max_width': settings.VIDEO_IMAGE_MAX_WIDTH, 'max_height': settings.VIDEO_IMAGE_MAX_HEIGHT, 'supported_file_formats': settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS }, 'is_video_transcript_enabled': is_video_transcript_enabled, 'video_transcript_settings': None, 'active_transcript_preferences': None } if is_video_transcript_enabled: context['video_transcript_settings'] = { 'transcript_preferences_handler_url': reverse_course_url('transcript_preferences_handler', unicode(course.id)), 'transcription_plans': get_3rd_party_transcription_plans(), } context['active_transcript_preferences'] = get_transcript_preferences( unicode(course.id)) return render_to_response('videos_index.html', context)
def is_val_transcript_feature_enabled_for_course(course_id): """ Get edx-val transcript feature flag Arguments: course_id(CourseKey): Course key identifying a course whose feature flag is being inspected. """ return VideoTranscriptEnabledFlag.feature_enabled(course_id=course_id)
def transcript_credentials_handler(request, course_key_string): """ JSON view handler to update the transcript organization credentials. Arguments: request: WSGI request object course_key_string: A course identifier to extract the org. Returns: - A 200 response if credentials are valid and successfully updated in edx-video-pipeline. - A 404 response if transcript feature is not enabled for this course. - A 400 if credentials do not pass validations, hence not updated in edx-video-pipeline. """ course_key = CourseKey.from_string(course_key_string) if not VideoTranscriptEnabledFlag.feature_enabled(course_key): return HttpResponseNotFound() provider = request.json.pop('provider') error_message, validated_credentials = validate_transcript_credentials( provider=provider, **request.json) if error_message: response = JsonResponse({'error': error_message}, status=400) else: # Send the validated credentials to edx-video-pipeline. credentials_payload = dict(validated_credentials, org=course_key.org, provider=provider) if waffle_flags()[SAVE_CREDENTIALS_IN_VAL].is_enabled(course_key): from edxval.api import create_or_update_transcript_credentials response = create_or_update_transcript_credentials( **credentials_payload) error_response, is_updated = response, not response.get( 'error_type') else: error_response, is_updated = update_3rd_party_transcription_service_credentials( **credentials_payload) # Send appropriate response based on whether credentials were updated or not. if is_updated: # Cache credentials state in edx-val. update_transcript_credentials_state_for_org(org=course_key.org, provider=provider, exists=is_updated) response = JsonResponse(status=200) else: # Error response would contain error types and the following # error type is received from edx-video-pipeline whenever we've # got invalid credentials for a provider. Its kept this way because # edx-video-pipeline doesn't support i18n translations yet. error_type = error_response.get('error_type') if error_type == TranscriptionProviderErrorType.INVALID_CREDENTIALS: error_message = _('The information you entered is incorrect.') response = JsonResponse({'error': error_message}, status=400) return response
def transcript_upload_handler(request, course_key_string): """ View to upload a transcript file. Arguments: request: A WSGI request object course_key_string: Course key identifying a course Transcript file, edx video id and transcript language are required. Transcript file should be in SRT(SubRip) format. Returns - A 400 if any of the validation fails - A 404 if the corresponding feature flag is disabled - A 200 if transcript has been uploaded successfully """ # Check whether the feature is available for this course. course_key = CourseKey.from_string(course_key_string) if not VideoTranscriptEnabledFlag.feature_enabled(course_key): return HttpResponseNotFound() error = validate_transcript_upload_data(data=request.POST, files=request.FILES) if error: response = JsonResponse({'error': error}, status=400) else: edx_video_id = request.POST['edx_video_id'] language_code = request.POST['language_code'] new_language_code = request.POST['new_language_code'] transcript_file = request.FILES['file'] try: # Convert SRT transcript into an SJSON format # and upload it to S3. sjson_subs = Transcript.convert( content=transcript_file.read(), input_format=Transcript.SRT, output_format=Transcript.SJSON ) create_or_update_video_transcript( video_id=edx_video_id, language_code=language_code, metadata={ 'provider': TranscriptProvider.CUSTOM, 'file_format': Transcript.SJSON, 'language_code': new_language_code }, file_data=ContentFile(sjson_subs), ) response = JsonResponse(status=201) except (TranscriptsGenerationException, UnicodeDecodeError): response = JsonResponse( {'error': _(u'There is a problem with this transcript file. Try to upload a different file.')}, status=400 ) return response
def videos_index_html(course, pagination_conf=None): """ Returns an HTML page to display previous video uploads and allow new ones """ is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id) previous_uploads, pagination_context = _get_index_videos(course, pagination_conf) context = { 'context_course': course, 'image_upload_url': reverse_course_url('video_images_handler', six.text_type(course.id)), 'video_handler_url': reverse_course_url('videos_handler', six.text_type(course.id)), 'encodings_download_url': reverse_course_url('video_encodings_download', six.text_type(course.id)), 'default_video_image_url': _get_default_video_image_url(), 'previous_uploads': previous_uploads, 'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0), 'video_supported_file_formats': list(VIDEO_SUPPORTED_FILE_FORMATS.keys()), 'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB, 'video_image_settings': { 'video_image_upload_enabled': WAFFLE_SWITCHES.is_enabled(VIDEO_IMAGE_UPLOAD_ENABLED), 'max_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'], 'min_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'], 'max_width': settings.VIDEO_IMAGE_MAX_WIDTH, 'max_height': settings.VIDEO_IMAGE_MAX_HEIGHT, 'supported_file_formats': settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS }, 'is_video_transcript_enabled': is_video_transcript_enabled, 'active_transcript_preferences': None, 'transcript_credentials': None, 'transcript_available_languages': get_all_transcript_languages(), 'video_transcript_settings': { 'transcript_download_handler_url': reverse('transcript_download_handler'), 'transcript_upload_handler_url': reverse('transcript_upload_handler'), 'transcript_delete_handler_url': reverse_course_url('transcript_delete_handler', six.text_type(course.id)), 'trancript_download_file_format': Transcript.SRT }, 'pagination_context': pagination_context } if is_video_transcript_enabled: context['video_transcript_settings'].update({ 'transcript_preferences_handler_url': reverse_course_url( 'transcript_preferences_handler', six.text_type(course.id) ), 'transcript_credentials_handler_url': reverse_course_url( 'transcript_credentials_handler', six.text_type(course.id) ), 'transcription_plans': get_3rd_party_transcription_plans(), }) context['active_transcript_preferences'] = get_transcript_preferences(six.text_type(course.id)) # Cached state for transcript providers' credentials (org-specific) context['transcript_credentials'] = get_transcript_credentials_state_for_org(course.id.org) return render_to_response('videos_index.html', context)
def videos_index_html(course): """ Returns an HTML page to display previous video uploads and allow new ones """ is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id) context = { 'context_course': course, 'image_upload_url': reverse_course_url('video_images_handler', unicode(course.id)), 'video_handler_url': reverse_course_url('videos_handler', unicode(course.id)), 'encodings_download_url': reverse_course_url('video_encodings_download', unicode(course.id)), 'default_video_image_url': _get_default_video_image_url(), 'previous_uploads': _get_index_videos(course), 'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0), 'video_supported_file_formats': VIDEO_SUPPORTED_FILE_FORMATS.keys(), 'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB, 'video_image_settings': { 'video_image_upload_enabled': WAFFLE_SWITCHES.is_enabled(VIDEO_IMAGE_UPLOAD_ENABLED), 'max_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'], 'min_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'], 'max_width': settings.VIDEO_IMAGE_MAX_WIDTH, 'max_height': settings.VIDEO_IMAGE_MAX_HEIGHT, 'supported_file_formats': settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS }, 'is_video_transcript_enabled': is_video_transcript_enabled, 'active_transcript_preferences': None, 'transcript_credentials': None, 'transcript_available_languages': get_all_transcript_languages(), 'video_transcript_settings': { 'transcript_download_handler_url': reverse('transcript_download_handler'), 'transcript_upload_handler_url': reverse('transcript_upload_handler'), 'transcript_delete_handler_url': reverse_course_url('transcript_delete_handler', unicode(course.id)), 'trancript_download_file_format': Transcript.SRT } } if is_video_transcript_enabled: context['video_transcript_settings'].update({ 'transcript_preferences_handler_url': reverse_course_url( 'transcript_preferences_handler', unicode(course.id) ), 'transcript_credentials_handler_url': reverse_course_url( 'transcript_credentials_handler', unicode(course.id) ), 'transcription_plans': get_3rd_party_transcription_plans(), }) context['active_transcript_preferences'] = get_transcript_preferences(unicode(course.id)) # Cached state for transcript providers' credentials (org-specific) context['transcript_credentials'] = get_transcript_credentials_state_for_org(course.id.org) return render_to_response('videos_index.html', context)
def _get_videos(course): """ Retrieves the list of videos from VAL corresponding to this course. """ is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id) videos = list(get_videos_for_course(unicode(course.id), VideoSortField.created, SortDirection.desc)) # convert VAL's status to studio's Video Upload feature status. for video in videos: video["status"] = convert_video_status(video) if is_video_transcript_enabled: video['transcripts'] = get_available_transcript_languages(video_id=video['edx_video_id']) return videos
def transcript_download_handler(request, course_key_string): """ JSON view handler to download a transcript. Arguments: request: WSGI request object course_key_string: course key Returns: - A 200 response with SRT transcript file attached. - A 400 if there is a validation error. - A 404 if there is no such transcript or feature flag is disabled. """ course_key = CourseKey.from_string(course_key_string) if not VideoTranscriptEnabledFlag.feature_enabled(course_key): return HttpResponseNotFound() missing = [attr for attr in ['edx_video_id', 'language_code'] if attr not in request.GET] if missing: return JsonResponse( {'error': _(u'The following parameters are required: {missing}.').format(missing=', '.join(missing))}, status=400 ) edx_video_id = request.GET['edx_video_id'] language_code = request.GET['language_code'] transcript = get_video_transcript_data(video_id=edx_video_id, language_code=language_code) if transcript: name_and_extension = os.path.splitext(transcript['file_name']) basename, file_format = name_and_extension[0], name_and_extension[1][1:] transcript_filename = '{base_name}.{ext}'.format(base_name=basename.encode('utf8'), ext=Transcript.SRT) transcript_content = Transcript.convert( content=transcript['content'], input_format=file_format, output_format=Transcript.SRT ) # Construct an HTTP response response = HttpResponse(transcript_content, content_type=Transcript.mime_types[Transcript.SRT]) response['Content-Disposition'] = 'attachment; filename="{filename}"'.format(filename=transcript_filename) else: response = HttpResponseNotFound() return response
def transcript_credentials_handler(request, course_key_string): """ JSON view handler to update the transcript organization credentials. Arguments: request: WSGI request object course_key_string: A course identifier to extract the org. Returns: - A 200 response if credentials are valid and successfully updated in edx-video-pipeline. - A 404 response if transcript feature is not enabled for this course. - A 400 if credentials do not pass validations, hence not updated in edx-video-pipeline. """ course_key = CourseKey.from_string(course_key_string) if not VideoTranscriptEnabledFlag.feature_enabled(course_key): return HttpResponseNotFound() provider = request.json.pop('provider') error_message, validated_credentials = validate_transcript_credentials(provider=provider, **request.json) if error_message: response = JsonResponse({'error': error_message}, status=400) else: # Send the validated credentials to edx-video-pipeline. credentials_payload = dict(validated_credentials, org=course_key.org, provider=provider) error_response, is_updated = update_3rd_party_transcription_service_credentials(**credentials_payload) # Send appropriate response based on whether credentials were updated or not. if is_updated: # Cache credentials state in edx-val. update_transcript_credentials_state_for_org(org=course_key.org, provider=provider, exists=is_updated) response = JsonResponse(status=200) else: # Error response would contain error types and the following # error type is received from edx-video-pipeline whenever we've # got invalid credentials for a provider. Its kept this way because # edx-video-pipeline doesn't support i18n translations yet. error_type = error_response.get('error_type') if error_type == TranscriptionProviderErrorType.INVALID_CREDENTIALS: error_message = _('The information you entered is incorrect.') response = JsonResponse({'error': error_message}, status=400) return response
def transcript_preferences_handler(request, course_key_string): """ JSON view handler to post the transcript preferences. Arguments: request: WSGI request object course_key_string: string for course key Returns: valid json response or 400 with error message """ course_key = CourseKey.from_string(course_key_string) is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled( course_key) if not is_video_transcript_enabled: return HttpResponseNotFound() if request.method == 'POST': data = request.json provider = data.get('provider') error, preferences = validate_transcript_preferences( provider=provider, cielo24_fidelity=data.get('cielo24_fidelity', ''), cielo24_turnaround=data.get('cielo24_turnaround', ''), three_play_turnaround=data.get('three_play_turnaround', ''), video_source_language=data.get('video_source_language'), preferred_languages=list( map(str, data.get('preferred_languages', [])))) if error: response = JsonResponse({'error': error}, status=400) else: preferences.update({'provider': provider}) transcript_preferences = create_or_update_transcript_preferences( course_key_string, **preferences) response = JsonResponse( {'transcript_preferences': transcript_preferences}, status=200) return response elif request.method == 'DELETE': remove_transcript_preferences(course_key_string) return JsonResponse()
def transcript_preferences_handler(request, course_key_string): """ JSON view handler to post the transcript preferences. Arguments: request: WSGI request object course_key_string: string for course key Returns: valid json response or 400 with error message """ course_key = CourseKey.from_string(course_key_string) is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course_key) if not is_video_transcript_enabled: return HttpResponseNotFound() if request.method == 'POST': data = request.json provider = data.get('provider') error, preferences = validate_transcript_preferences( provider=provider, cielo24_fidelity=data.get('cielo24_fidelity', ''), cielo24_turnaround=data.get('cielo24_turnaround', ''), three_play_turnaround=data.get('three_play_turnaround', ''), video_source_language=data.get('video_source_language'), preferred_languages=data.get('preferred_languages', []) ) if error: response = JsonResponse({'error': error}, status=400) else: preferences.update({'provider': provider}) transcript_preferences = create_or_update_transcript_preferences(course_key_string, **preferences) response = JsonResponse({'transcript_preferences': transcript_preferences}, status=200) return response elif request.method == 'DELETE': remove_transcript_preferences(course_key_string) return JsonResponse()
def transcript_delete_handler(request, course_key_string, edx_video_id, language_code): """ View to delete a transcript file. Arguments: request: A WSGI request object course_key_string: Course key identifying a course. edx_video_id: edX video identifier whose transcript need to be deleted. language_code: transcript's language code. Returns - A 404 if the corresponding feature flag is disabled or user does not have required permisions - A 200 if transcript is deleted without any error(s) """ # Check whether the feature is available for this course. course_key = CourseKey.from_string(course_key_string) video_transcripts_enabled = VideoTranscriptEnabledFlag.feature_enabled(course_key) # User needs to have studio write access for this course. if not video_transcripts_enabled or not has_studio_write_access(request.user, course_key): return HttpResponseNotFound() delete_video_transcript(video_id=edx_video_id, language_code=language_code) return JsonResponse(status=200)
def videos_post(course, request): """ Input (JSON): { "files": [{ "file_name": "video.mp4", "content_type": "video/mp4" }] } Returns (JSON): { "files": [{ "file_name": "video.mp4", "upload_url": "http://example.com/put_video" }] } The returned array corresponds exactly to the input array. """ error = None data = request.json if 'files' not in data: error = "Request object is not JSON or does not contain 'files'" elif any( 'file_name' not in file or 'content_type' not in file for file in data['files'] ): error = "Request 'files' entry does not contain 'file_name' and 'content_type'" elif any( file['content_type'] not in VIDEO_SUPPORTED_FILE_FORMATS.values() for file in data['files'] ): error = "Request 'files' entry contain unsupported content_type" if error: return JsonResponse({'error': error}, status=400) req_files = data['files'] resp_files = [] for req_file in req_files: file_name = req_file['file_name'] try: file_name.encode('ascii') except UnicodeEncodeError: error_msg = 'The file name for %s must contain only ASCII characters.' % file_name return JsonResponse({'error': error_msg}, status=400) edx_video_id = unicode(uuid4()) metadata_list = [ ('client_video_id', file_name), ('course_key', unicode(course.id)), ] # Only include `course_video_upload_token` if its set, as it won't be required if video uploads # are enabled by default. course_video_upload_token = course.video_upload_pipeline.get('course_video_upload_token') if course_video_upload_token: metadata_list.append(('course_video_upload_token', course_video_upload_token)) is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id) if is_video_transcript_enabled: transcript_preferences = get_transcript_preferences(unicode(course.id)) if transcript_preferences is not None: metadata_list.append(('transcript_preferences', json.dumps(transcript_preferences))) upload_url = settings.VIDEO_UPLOAD_PREAUTH_URL+edx_video_id # persist edx_video_id in VAL create_video({ 'edx_video_id': edx_video_id, 'status': 'upload', 'client_video_id': file_name, 'duration': 0, 'encoded_videos': [], 'courses': [unicode(course.id)] }) resp_files.append({'file_name': file_name, 'upload_url': upload_url, 'edx_video_id': edx_video_id}) return JsonResponse({'files': resp_files}, status=200)
def videos_post(course, request): """ Input (JSON): { "files": [{ "file_name": "video.mp4", "content_type": "video/mp4" }] } Returns (JSON): { "files": [{ "file_name": "video.mp4", "upload_url": "http://example.com/put_video" }] } The returned array corresponds exactly to the input array. """ error = None data = request.json if 'files' not in data: error = "Request object is not JSON or does not contain 'files'" elif any( 'file_name' not in file or 'content_type' not in file for file in data['files'] ): error = "Request 'files' entry does not contain 'file_name' and 'content_type'" elif any( file['content_type'] not in VIDEO_SUPPORTED_FILE_FORMATS.values() for file in data['files'] ): error = "Request 'files' entry contain unsupported content_type" if error: return JsonResponse({'error': error}, status=400) bucket = storage_service_bucket() req_files = data['files'] resp_files = [] for req_file in req_files: file_name = req_file['file_name'] try: file_name.encode('ascii') except UnicodeEncodeError: error_msg = u'The file name for %s must contain only ASCII characters.' % file_name return JsonResponse({'error': error_msg}, status=400) edx_video_id = unicode(uuid4()) key = storage_service_key(bucket, file_name=edx_video_id) metadata_list = [ ('client_video_id', file_name), ('course_key', unicode(course.id)), ] deprecate_youtube = waffle_flags()[DEPRECATE_YOUTUBE] course_video_upload_token = course.video_upload_pipeline.get('course_video_upload_token') # Only include `course_video_upload_token` if youtube has not been deprecated # for this course. if not deprecate_youtube.is_enabled(course.id) and course_video_upload_token: metadata_list.append(('course_video_upload_token', course_video_upload_token)) is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id) if is_video_transcript_enabled: transcript_preferences = get_transcript_preferences(unicode(course.id)) if transcript_preferences is not None: metadata_list.append(('transcript_preferences', json.dumps(transcript_preferences))) for metadata_name, value in metadata_list: key.set_metadata(metadata_name, value) upload_url = key.generate_url( KEY_EXPIRATION_IN_SECONDS, 'PUT', headers={'Content-Type': req_file['content_type']} ) # persist edx_video_id in VAL create_video({ 'edx_video_id': edx_video_id, 'status': 'upload', 'client_video_id': file_name, 'duration': 0, 'encoded_videos': [], 'courses': [unicode(course.id)] }) resp_files.append({'file_name': file_name, 'upload_url': upload_url, 'edx_video_id': edx_video_id}) return JsonResponse({'files': resp_files}, status=200)
def videos_post(course, request): """ Input (JSON): { "files": [{ "file_name": "video.mp4", "content_type": "video/mp4" }] } Returns (JSON): { "files": [{ "file_name": "video.mp4", "upload_url": "http://example.com/put_video" }] } The returned array corresponds exactly to the input array. """ error = None data = request.json if 'files' not in data: error = "Request object is not JSON or does not contain 'files'" elif any( 'file_name' not in file or 'content_type' not in file for file in data['files'] ): error = "Request 'files' entry does not contain 'file_name' and 'content_type'" elif any( file['content_type'] not in VIDEO_SUPPORTED_FILE_FORMATS.values() for file in data['files'] ): error = "Request 'files' entry contain unsupported content_type" if error: return JsonResponse({'error': error}, status=400) bucket = storage_service_bucket() req_files = data['files'] resp_files = [] for req_file in req_files: file_name = req_file['file_name'] try: file_name.encode('ascii') except UnicodeEncodeError: error_msg = 'The file name for %s must contain only ASCII characters.' % file_name return JsonResponse({'error': error_msg}, status=400) edx_video_id = unicode(uuid4()) key = storage_service_key(bucket, file_name=edx_video_id) metadata_list = [ ('client_video_id', file_name), ('course_key', unicode(course.id)), ] # Only include `course_video_upload_token` if its set, as it won't be required if video uploads # are enabled by default. course_video_upload_token = course.video_upload_pipeline.get('course_video_upload_token') if course_video_upload_token: metadata_list.append(('course_video_upload_token', course_video_upload_token)) is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id) if is_video_transcript_enabled: transcript_preferences = get_transcript_preferences(unicode(course.id)) if transcript_preferences is not None: metadata_list.append(('transcript_preferences', json.dumps(transcript_preferences))) for metadata_name, value in metadata_list: key.set_metadata(metadata_name, value) upload_url = key.generate_url( KEY_EXPIRATION_IN_SECONDS, 'PUT', headers={'Content-Type': req_file['content_type']} ) # persist edx_video_id in VAL create_video({ 'edx_video_id': edx_video_id, 'status': 'upload', 'client_video_id': file_name, 'duration': 0, 'encoded_videos': [], 'courses': [unicode(course.id)] }) resp_files.append({'file_name': file_name, 'upload_url': upload_url, 'edx_video_id': edx_video_id}) return JsonResponse({'files': resp_files}, status=200)