diff --git a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
index 3bcceb54721c..8660e7942acf 100644
--- a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
+++ b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
@@ -534,27 +534,6 @@ def test_youtube_empty_text(self, mock_get):
with self.assertRaises(transcripts_utils.GetTranscriptsFromYouTubeException):
transcripts_utils.get_transcripts_from_youtube(youtube_id, settings, translation)
- def test_youtube_good_result(self):
- response = textwrap.dedent("""
-
-
- Test text 1.
- Test text 2.
- Test text 3.
-
- """)
- expected_transcripts = {
- 'start': [270, 2720, 5430],
- 'end': [2720, 2720, 7160],
- 'text': ['Test text 1.', 'Test text 2.', 'Test text 3.']
- }
- youtube_id = 'good_youtube_id'
- with patch('xmodule.video_module.transcripts_utils.requests.get') as mock_get:
- mock_get.return_value = Mock(status_code=200, text=response, content=response.encode('utf-8'))
- transcripts = transcripts_utils.get_transcripts_from_youtube(youtube_id, settings, translation)
- self.assertEqual(transcripts, expected_transcripts)
- mock_get.assert_called_with('http://video.google.com/timedtext', params={'lang': 'en', 'v': 'good_youtube_id'})
-
class TestTranscript(unittest.TestCase):
"""
diff --git a/cms/djangoapps/contentstore/views/tests/test_transcripts.py b/cms/djangoapps/contentstore/views/tests/test_transcripts.py
index 535c0a39d69b..63598104a72c 100644
--- a/cms/djangoapps/contentstore/views/tests/test_transcripts.py
+++ b/cms/djangoapps/contentstore/views/tests/test_transcripts.py
@@ -34,13 +34,9 @@
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
-SRT_TRANSCRIPT_CONTENT = u"""0
-00:00:10,500 --> 00:00:13,000
-Elephant's Dream
-
-1
-00:00:15,000 --> 00:00:18,000
-At the left we can see...
+SRT_TRANSCRIPT_CONTENT = """0
+00:00:00,260 --> 00:00:00,260
+Hello, welcome to Open edX.
"""
@@ -166,6 +162,14 @@ def setUp(self):
self.bad_data_srt_file = self.create_transcript_file(content=self.contents['bad'], suffix='.srt')
self.bad_name_srt_file = self.create_transcript_file(content=self.contents['good'], suffix='.bad')
self.bom_srt_file = self.create_transcript_file(content=self.contents['good'], suffix='.srt', include_bom=True)
+ self.good_transcript_data = {
+ 'transcript_srt':
+ '0\n00:00:00,260 --> 00:00:00,260\nHello, welcome to Open edX.'
+ }
+ self.bad_transcript_data = {
+ 'srt':
+ '0\n00:00:00,260 --> 00:00:00,260\nHello, welcome to Open edX.'
+ }
# Setup a VEDA produced video and persist `edx_video_id` in VAL.
create_video({
@@ -206,7 +210,7 @@ def clean_temporary_transcripts(self):
self.bad_name_srt_file.close()
self.bom_srt_file.close()
- def upload_transcript(self, locator, transcript_file, edx_video_id=None):
+ def upload_transcript(self, locator, transcript_data, edx_video_id=None):
"""
Uploads a transcript for a video
"""
@@ -217,8 +221,8 @@ def upload_transcript(self, locator, transcript_file, edx_video_id=None):
if edx_video_id is not None:
payload.update({'edx_video_id': edx_video_id})
- if transcript_file:
- payload.update({'transcript-file': transcript_file})
+ if transcript_data:
+ payload.update({'transcript-file': transcript_data})
upload_url = reverse('upload_transcripts')
response = self.client.post(upload_url, payload)
@@ -246,8 +250,8 @@ def test_transcript_upload_success(self, edx_video_id, include_bom):
modulestore().update_item(self.item, self.user.id)
# Upload a transcript
- transcript_file = self.bom_srt_file if include_bom else self.good_srt_file
- response = self.upload_transcript(self.video_usage_key, transcript_file, '')
+ transcript_data = self.good_transcript_data["transcript_srt"]
+ response = self.upload_transcript(self.video_usage_key, transcript_data, '')
# Verify the response
self.assert_response(response, expected_status_code=200, expected_message='Success')
@@ -272,7 +276,8 @@ def test_transcript_upload_without_locator(self):
"""
Test that transcript upload validation fails if the video locator is missing
"""
- response = self.upload_transcript(locator=None, transcript_file=self.good_srt_file, edx_video_id='')
+ transcript_data = self.good_transcript_data["transcript_srt"]
+ response = self.upload_transcript(locator=None, transcript_data=transcript_data, edx_video_id='')
self.assert_response(
response,
expected_status_code=400,
@@ -283,7 +288,7 @@ def test_transcript_upload_without_file(self):
"""
Test that transcript upload validation fails if transcript file is missing
"""
- response = self.upload_transcript(locator=self.video_usage_key, transcript_file=None, edx_video_id='')
+ response = self.upload_transcript(locator=self.video_usage_key, transcript_data=None, edx_video_id='')
self.assert_response(
response,
expected_status_code=400,
@@ -296,13 +301,13 @@ def test_transcript_upload_bad_format(self):
"""
response = self.upload_transcript(
locator=self.video_usage_key,
- transcript_file=self.bad_name_srt_file,
+ transcript_data=self.bad_transcript_data,
edx_video_id=''
)
self.assert_response(
response,
expected_status_code=400,
- expected_message=u'This transcript file type is not supported.'
+ expected_message=u'There is a problem with this transcript file. Try to upload a different file.'
)
def test_transcript_upload_bad_content(self):
@@ -312,7 +317,7 @@ def test_transcript_upload_bad_content(self):
# Request to upload transcript for the video
response = self.upload_transcript(
locator=self.video_usage_key,
- transcript_file=self.bad_data_srt_file,
+ transcript_data=self.bad_transcript_data,
edx_video_id=''
)
self.assert_response(
@@ -328,7 +333,8 @@ def test_transcript_upload_unknown_category(self):
# non_video module setup - i.e. an item whose category is not 'video'.
usage_key = self.create_non_video_module()
# Request to upload transcript for the item
- response = self.upload_transcript(locator=usage_key, transcript_file=self.good_srt_file, edx_video_id='')
+ transcript_data = self.good_transcript_data["transcript_srt"]
+ response = self.upload_transcript(locator=usage_key, transcript_data=transcript_data, edx_video_id='')
self.assert_response(
response,
expected_status_code=400,
@@ -340,9 +346,10 @@ def test_transcript_upload_non_existent_item(self):
Test that transcript upload validation fails in case of invalid item's locator.
"""
# Request to upload transcript for the item
+ transcript_data = self.good_transcript_data["transcript_srt"]
response = self.upload_transcript(
locator='non_existent_locator',
- transcript_file=self.good_srt_file,
+ transcript_data=transcript_data,
edx_video_id=''
)
self.assert_response(
@@ -351,32 +358,24 @@ def test_transcript_upload_non_existent_item(self):
expected_message=u'Cannot find item by locator.'
)
- def test_transcript_upload_without_edx_video_id(self):
- """
- Test that transcript upload validation fails if the `edx_video_id` is missing
- """
- response = self.upload_transcript(locator=self.video_usage_key, transcript_file=self.good_srt_file)
- self.assert_response(
- response,
- expected_status_code=400,
- expected_message=u'Video ID is required.'
- )
-
def test_transcript_upload_with_non_existant_edx_video_id(self):
"""
Test that transcript upload works as expected if `edx_video_id` set on
video descriptor is different from `edx_video_id` received in POST request.
"""
non_existant_edx_video_id = '1111-2222-3333-4444'
-
+ transcript_data = self.good_transcript_data["transcript_srt"]
# Upload with non-existant `edx_video_id`
response = self.upload_transcript(
locator=self.video_usage_key,
- transcript_file=self.good_srt_file,
+ transcript_data=transcript_data,
edx_video_id=non_existant_edx_video_id
)
# Verify the response
- self.assert_response(response, expected_status_code=400, expected_message='Invalid Video ID')
+ self.assert_response(
+ response, expected_status_code=400,
+ expected_message="edx_video_id doesn't exist."
+ )
# Verify transcript does not exist for non-existant `edx_video_id`
self.assertIsNone(get_video_transcript_content(non_existant_edx_video_id, language_code=u'en'))
diff --git a/cms/djangoapps/contentstore/views/transcripts_ajax.py b/cms/djangoapps/contentstore/views/transcripts_ajax.py
index d31704830ac9..ba39f04f7313 100644
--- a/cms/djangoapps/contentstore/views/transcripts_ajax.py
+++ b/cms/djangoapps/contentstore/views/transcripts_ajax.py
@@ -19,12 +19,16 @@
from django.core.files.base import ContentFile
from django.http import Http404, HttpResponse
from django.utils.translation import ugettext as _
-from edxval.api import create_external_video, create_or_update_video_transcript
+from edxval.api import (
+ create_external_video,
+ create_or_update_video_transcript,
+ _get_video,
+ ValVideoNotFoundError
+)
+from edxval.models import Video
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey
from six import text_type
-
-from cms.djangoapps.contentstore.views.videos import TranscriptProvider
from student.auth import has_course_author_access
from util.json_request import JsonResponse
from xmodule.contentstore.content import StaticContent
@@ -43,9 +47,11 @@
get_transcript_for_video,
get_transcript_from_val,
get_transcripts_from_youtube,
- youtube_video_transcript_name
+ youtube_video_transcript_name,
)
+from cms.djangoapps.contentstore.views.videos import TranscriptProvider
+
__all__ = [
'upload_transcripts',
'download_transcripts',
@@ -82,14 +88,16 @@ def link_video_to_component(video_component, user):
"""
edx_video_id = clean_video_id(video_component.edx_video_id)
if not edx_video_id:
- edx_video_id = create_external_video(display_name=u'external video')
+ edx_video_id = create_external_video(display_name='external video')
video_component.edx_video_id = edx_video_id
video_component.save_with_metadata(user)
return edx_video_id
-def save_video_transcript(edx_video_id, input_format, transcript_content, language_code):
+def save_video_transcript(
+ edx_video_id, input_format, transcript_content, language_code
+):
"""
Saves a video transcript to the VAL and its content to the configured django storage(DS).
@@ -108,7 +116,7 @@ def save_video_transcript(edx_video_id, input_format, transcript_content, langua
sjson_subs = Transcript.convert(
content=transcript_content,
input_format=input_format,
- output_format=Transcript.SJSON
+ output_format=Transcript.SJSON,
).encode()
create_or_update_video_transcript(
video_id=edx_video_id,
@@ -116,7 +124,7 @@ def save_video_transcript(edx_video_id, input_format, transcript_content, langua
metadata={
'provider': TranscriptProvider.CUSTOM,
'file_format': Transcript.SJSON,
- 'language_code': language_code
+ 'language_code': language_code,
},
file_data=ContentFile(sjson_subs),
)
@@ -146,9 +154,9 @@ def validate_video_module(request, locator):
try:
item = _get_item(request, {'locator': locator})
if item.category != 'video':
- error = _(u'Transcripts are supported only for "video" modules.')
+ error = _('Transcripts are supported only for "video" modules.')
except (InvalidKeyError, ItemNotFoundError):
- error = _(u'Cannot find item by locator.')
+ error = _('Cannot find item by locator.')
return error, item
@@ -167,24 +175,27 @@ def validate_transcript_upload_data(request):
error, validated_data = None, {}
data, files = request.POST, request.FILES
video_locator = data.get('locator')
- edx_video_id = data.get('edx_video_id')
if not video_locator:
- error = _(u'Video locator is required.')
- elif 'transcript-file' not in files:
- error = _(u'A transcript file is required.')
- elif os.path.splitext(files['transcript-file'].name)[1][1:] != Transcript.SRT:
- error = _(u'This transcript file type is not supported.')
- elif 'edx_video_id' not in data:
- error = _(u'Video ID is required.')
-
+ error = _('Video locator is required.')
+ elif 'transcript-file' not in files and 'transcript-file' not in data:
+ error = _('A transcript file is required.')
+ if 'edx_video_id' in data and data['edx_video_id']:
+ try:
+ _get_video(data['edx_video_id'])
+ except ValVideoNotFoundError:
+ error = _("edx_video_id doesn't exist.")
if not error:
error, video = validate_video_module(request, video_locator)
+ if 'transcript-file' in files:
+ transcript_file = files['transcript-file']
+ elif 'transcript-file' in data:
+ transcript_file = data['transcript-file']
if not error:
- validated_data.update({
- 'video': video,
- 'edx_video_id': clean_video_id(edx_video_id) or clean_video_id(video.edx_video_id),
- 'transcript_file': files['transcript-file']
- })
+ validated_data.update({'video': video, 'transcript_file': transcript_file})
+ if video.edx_video_id:
+ validated_data.update(
+ {'edx_video_id': clean_video_id(video.edx_video_id)}
+ )
return error, validated_data
@@ -203,33 +214,37 @@ def upload_transcripts(request):
if error:
response = JsonResponse({'status': error}, status=400)
else:
+ edx_video_id = ''
video = validated_data['video']
- edx_video_id = validated_data['edx_video_id']
+ if validated_data.get('edx_video_id', ''):
+ edx_video_id = validated_data['edx_video_id']
transcript_file = validated_data['transcript_file']
# check if we need to create an external VAL video to associate the transcript
# and save its ID on the video component.
if not edx_video_id:
- edx_video_id = create_external_video(display_name=u'external video')
+ edx_video_id = create_external_video(display_name='external video')
video.edx_video_id = edx_video_id
video.save_with_metadata(request.user)
- response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
+ response = JsonResponse(
+ {'edx_video_id': edx_video_id, 'status': 'Success'}, status=200
+ )
try:
# Convert 'srt' transcript into the 'sjson' and upload it to
# configured transcript storage. For example, S3.
sjson_subs = Transcript.convert(
- content=transcript_file.read().decode('utf-8'),
+ content=transcript_file.encode('utf-8'),
input_format=Transcript.SRT,
- output_format=Transcript.SJSON
+ output_format=Transcript.SJSON,
).encode()
transcript_created = create_or_update_video_transcript(
video_id=edx_video_id,
- language_code=u'en',
+ language_code='en',
metadata={
'provider': TranscriptProvider.CUSTOM,
'file_format': Transcript.SJSON,
- 'language_code': u'en'
+ 'language_code': 'en',
},
file_data=ContentFile(sjson_subs),
)
@@ -239,9 +254,14 @@ def upload_transcripts(request):
except (TranscriptsGenerationException, UnicodeDecodeError):
- response = JsonResponse({
- 'status': _(u'There is a problem with this transcript file. Try to upload a different file.')
- }, status=400)
+ response = JsonResponse(
+ {
+ 'status': _(
+ 'There is a problem with this transcript file. Try to upload a different file.'
+ )
+ },
+ status=400,
+ )
return response
@@ -253,18 +273,20 @@ def download_transcripts(request):
Raises Http404 if unsuccessful.
"""
- error, video = validate_video_module(request, locator=request.GET.get('locator'))
+ error, video = validate_video_module(request, locator=request.GET.get("locator"))
if error:
raise Http404
try:
- content, filename, mimetype = get_transcript(video, lang=u'en')
+ content, filename, mimetype = get_transcript(video, lang='en')
except NotFoundError:
raise Http404
# Construct an HTTP response
response = HttpResponse(content, content_type=mimetype)
- response['Content-Disposition'] = u'attachment; filename="{filename}"'.format(filename=filename)
+ response['Content-Disposition'] = 'attachment; filename="{filename}"'.format(
+ filename=filename
+ )
return response
@@ -316,13 +338,17 @@ def check_transcripts(request):
try:
edx_video_id = clean_video_id(videos.get('edx_video_id'))
- get_transcript_from_val(edx_video_id=edx_video_id, lang=u'en')
+ get_transcript_from_val(edx_video_id=edx_video_id, lang='en')
command = 'found'
except NotFoundError:
filename = 'subs_{0}.srt.sjson'.format(item.sub)
- content_location = StaticContent.compute_location(item.location.course_key, filename)
+ content_location = StaticContent.compute_location(
+ item.location.course_key, filename
+ )
try:
- local_transcripts = contentstore().find(content_location).data.decode('utf-8')
+ local_transcripts = (
+ contentstore().find(content_location).data.decode('utf-8')
+ )
transcripts_presence['current_item_subs'] = item.sub
except NotFoundError:
pass
@@ -334,12 +360,18 @@ def check_transcripts(request):
# youtube local
filename = 'subs_{0}.srt.sjson'.format(youtube_id)
- content_location = StaticContent.compute_location(item.location.course_key, filename)
+ content_location = StaticContent.compute_location(
+ item.location.course_key, filename
+ )
try:
- local_transcripts = contentstore().find(content_location).data.decode('utf-8')
+ local_transcripts = (
+ contentstore().find(content_location).data.decode('utf-8')
+ )
transcripts_presence['youtube_local'] = True
except NotFoundError:
- log.debug(u"Can't find transcripts in storage for youtube id: %s", youtube_id)
+ log.debug(
+ "Can't find transcripts in storage for youtube id: %s", youtube_id
+ )
# youtube server
youtube_text_api = copy.deepcopy(settings.YOUTUBE['TEXT_API'])
@@ -347,19 +379,24 @@ def check_transcripts(request):
youtube_transcript_name = youtube_video_transcript_name(youtube_text_api)
if youtube_transcript_name:
youtube_text_api['params']['name'] = youtube_transcript_name
- youtube_response = requests.get('http://' + youtube_text_api['url'], params=youtube_text_api['params'])
+ youtube_response = requests.get(
+ 'http://' + youtube_text_api['url'], params=youtube_text_api['params']
+ )
if youtube_response.status_code == 200 and youtube_response.text:
transcripts_presence['youtube_server'] = True
- #check youtube local and server transcripts for equality
- if transcripts_presence['youtube_server'] and transcripts_presence['youtube_local']:
+ # check youtube local and server transcripts for equality
+ if (
+ transcripts_presence['youtube_server']
+ and transcripts_presence['youtube_local']
+ ):
try:
youtube_server_subs = get_transcripts_from_youtube(
- youtube_id,
- settings,
- item.runtime.service(item, "i18n")
+ youtube_id, settings, item.runtime.service(item, 'i18n')
)
- if json.loads(local_transcripts) == youtube_server_subs: # check transcripts for equality
+ if (
+ json.loads(local_transcripts) == youtube_server_subs
+ ): # check transcripts for equality
transcripts_presence['youtube_diff'] = False
except GetTranscriptsFromYouTubeException:
pass
@@ -368,16 +405,21 @@ def check_transcripts(request):
html5_subs = []
for html5_id in videos['html5']:
filename = 'subs_{0}.srt.sjson'.format(html5_id)
- content_location = StaticContent.compute_location(item.location.course_key, filename)
+ content_location = StaticContent.compute_location(
+ item.location.course_key, filename
+ )
try:
html5_subs.append(contentstore().find(content_location).data)
transcripts_presence['html5_local'].append(html5_id)
except NotFoundError:
- log.debug(u"Can't find transcripts in storage for non-youtube video_id: %s", html5_id)
- if len(html5_subs) == 2: # check html5 transcripts for equality
- transcripts_presence['html5_equal'] = (
- json.loads(html5_subs[0].decode('utf-8')) == json.loads(html5_subs[1].decode('utf-8'))
+ log.debug(
+ "Can't find transcripts in storage for non-youtube video_id: %s",
+ html5_id,
)
+ if len(html5_subs) == 2: # check html5 transcripts for equality
+ transcripts_presence['html5_equal'] = json.loads(
+ html5_subs[0].decode('utf-8')
+ ) == json.loads(html5_subs[1].decode('utf-8'))
command, __ = _transcripts_logic(transcripts_presence, videos)
@@ -405,13 +447,14 @@ def _transcripts_logic(transcripts_presence, videos):
command = None
# new value of item.sub field, that should be set in module.
- subs = ''
+ subs = ""
# youtube transcripts are of high priority than html5 by design
if (
- transcripts_presence['youtube_diff'] and
- transcripts_presence['youtube_local'] and
- transcripts_presence['youtube_server']): # youtube server and local exist
+ transcripts_presence['youtube_diff']
+ and transcripts_presence['youtube_local']
+ and transcripts_presence['youtube_server']
+ ): # youtube server and local exist
command = 'replace'
subs = videos['youtube']
elif transcripts_presence['youtube_local']: # only youtube local exist
@@ -421,7 +464,10 @@ def _transcripts_logic(transcripts_presence, videos):
command = 'import'
else: # html5 part
if transcripts_presence['html5_local']: # can be 1 or 2 html5 videos
- if len(transcripts_presence['html5_local']) == 1 or transcripts_presence['html5_equal']:
+ if (
+ len(transcripts_presence['html5_local']) == 1
+ or transcripts_presence['html5_equal']
+ ):
command = 'found'
subs = transcripts_presence['html5_local'][0]
else:
@@ -429,16 +475,22 @@ def _transcripts_logic(transcripts_presence, videos):
subs = transcripts_presence['html5_local'][0]
else: # html5 source have no subtitles
# check if item sub has subtitles
- if transcripts_presence['current_item_subs'] and not transcripts_presence['is_youtube_mode']:
- log.debug(u"Command is use existing %s subs", transcripts_presence['current_item_subs'])
+ if (
+ transcripts_presence['current_item_subs']
+ and not transcripts_presence['is_youtube_mode']
+ ):
+ log.debug(
+ 'Command is use existing %s subs',
+ transcripts_presence['current_item_subs'],
+ )
command = 'use_existing'
else:
command = 'not_found'
log.debug(
- u"Resulted command: %s, current transcripts: %s, youtube mode: %s",
+ 'Resulted command: %s, current transcripts: %s, youtube mode: %s',
command,
transcripts_presence['current_item_subs'],
- transcripts_presence['is_youtube_mode']
+ transcripts_presence['is_youtube_mode'],
)
return command, subs
@@ -466,7 +518,9 @@ def _validate_transcripts_data(request):
raise TranscriptsRequestValidationException(_("Can't find item by locator."))
if item.category != 'video':
- raise TranscriptsRequestValidationException(_('Transcripts are supported only for "video" modules.'))
+ raise TranscriptsRequestValidationException(
+ _('Transcripts are supported only for "video" modules.')
+ )
# parse data form request.GET.['data']['video'] to useful format
videos = {'youtube': '', 'html5': {}}
@@ -500,7 +554,7 @@ def validate_transcripts_request(request, include_yt=False, include_html5=False)
# Loads the request data
data = json.loads(request.GET.get('data', '{}'))
if not data:
- error = _(u'Incoming video data is empty.')
+ error = _('Incoming video data is empty.')
else:
error, video = validate_video_module(request, locator=data.get('locator'))
if not error:
@@ -508,11 +562,13 @@ def validate_transcripts_request(request, include_yt=False, include_html5=False)
videos = data.get('videos', [])
if include_yt:
- validated_data.update({
- video['type']: video['video']
- for video in videos
- if video['type'] == 'youtube'
- })
+ validated_data.update(
+ {
+ video['type']: video['video']
+ for video in videos
+ if video['type'] == 'youtube'
+ }
+ )
if include_html5:
validated_data['chosen_html5_id'] = data.get('html5_id')
@@ -546,7 +602,7 @@ def choose_transcripts(request):
video.location,
subs_id=chosen_html5_id,
file_name=chosen_html5_id,
- language=u'en'
+ language='en',
)
except NotFoundError:
return error_response({}, _('No such transcript.'))
@@ -555,11 +611,17 @@ def choose_transcripts(request):
edx_video_id = link_video_to_component(video, request.user)
# 3. Upload the retrieved transcript to DS for the linked video ID.
- success = save_video_transcript(edx_video_id, input_format, transcript_content, language_code=u'en')
+ success = save_video_transcript(
+ edx_video_id, input_format, transcript_content, language_code='en'
+ )
if success:
- response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
+ response = JsonResponse(
+ {'edx_video_id': edx_video_id, 'status': 'Success'}, status=200
+ )
else:
- response = error_response({}, _('There is a problem with the chosen transcript file.'))
+ response = error_response(
+ {}, _('There is a problem with the chosen transcript file.')
+ )
return response
@@ -582,10 +644,7 @@ def rename_transcripts(request):
try:
video = validated_data['video']
input_format, __, transcript_content = get_transcript_for_video(
- video.location,
- subs_id=video.sub,
- file_name=video.sub,
- language=u'en'
+ video.location, subs_id=video.sub, file_name=video.sub, language='en'
)
except NotFoundError:
return error_response({}, _('No such transcript.'))
@@ -594,12 +653,19 @@ def rename_transcripts(request):
edx_video_id = link_video_to_component(video, request.user)
# 3. Upload the retrieved transcript to DS for the linked video ID.
- success = save_video_transcript(edx_video_id, input_format, transcript_content, language_code=u'en')
+ success = save_video_transcript(
+ edx_video_id, input_format, transcript_content, language_code='en'
+ )
if success:
- response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
+ response = JsonResponse(
+ {'edx_video_id': edx_video_id, 'status': 'Success'}, status=200
+ )
else:
response = error_response(
- {}, _('There is a problem with the existing transcript file. Please upload a different file.')
+ {},
+ _(
+ 'There is a problem with the existing transcript file. Please upload a different file.'
+ ),
)
return response
@@ -619,7 +685,7 @@ def replace_transcripts(request):
if error:
response = error_response({}, error)
elif not youtube_id:
- response = error_response({}, _(u'YouTube ID is required.'))
+ response = error_response({}, _('YouTube ID is required.'))
else:
# 1. Download transcript from YouTube.
try:
@@ -632,11 +698,17 @@ def replace_transcripts(request):
edx_video_id = link_video_to_component(video, request.user)
# 3. Upload YT transcript to DS for the linked video ID.
- success = save_video_transcript(edx_video_id, Transcript.SJSON, transcript_content, language_code=u'en')
+ success = save_video_transcript(
+ edx_video_id, Transcript.SJSON, transcript_content, language_code='en'
+ )
if success:
- response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
+ response = JsonResponse(
+ {'edx_video_id': edx_video_id, 'status': 'Success'}, status=200
+ )
else:
- response = error_response({}, _('There is a problem with the YouTube transcript file.'))
+ response = error_response(
+ {}, _('There is a problem with the YouTube transcript file.')
+ )
return response
diff --git a/cms/static/js/views/video/transcripts/message_manager.js b/cms/static/js/views/video/transcripts/message_manager.js
index 8c645e3a387e..1e8c56cf6686 100644
--- a/cms/static/js/views/video/transcripts/message_manager.js
+++ b/cms/static/js/views/video/transcripts/message_manager.js
@@ -1,231 +1,287 @@
-define(
- [
- 'jquery', 'backbone', 'underscore',
- 'js/views/video/transcripts/utils', 'js/views/video/transcripts/file_uploader',
- 'gettext'
- ],
-function($, Backbone, _, Utils, FileUploader, gettext) {
- var MessageManager = Backbone.View.extend({
- tagName: 'div',
- elClass: '.wrapper-transcripts-message',
- invisibleClass: 'is-invisible',
-
- events: {
- 'click .setting-import': 'importHandler',
- 'click .setting-replace': 'replaceHandler',
- 'click .setting-choose': 'chooseHandler',
- 'click .setting-use-existing': 'useExistingHandler'
- },
-
- // Pre-defined dict with anchors to status templates.
- templates: {
- not_found: '#transcripts-not-found',
- found: '#transcripts-found',
- import: '#transcripts-import',
- replace: '#transcripts-replace',
- uploaded: '#transcripts-uploaded',
- use_existing: '#transcripts-use-existing',
- choose: '#transcripts-choose'
- },
-
- initialize: function(options) {
- _.bindAll(this,
- 'importHandler', 'replaceHandler', 'chooseHandler', 'useExistingHandler', 'showError', 'hideError'
- );
-
- this.options = _.extend({}, options);
-
- this.component_locator = this.$el.closest('[data-locator]').data('locator');
-
- this.fileUploader = new FileUploader({
- el: this.$el,
- messenger: this,
- component_locator: this.component_locator
- });
- },
-
- render: function(template_id, params) {
- var tplHtml = $(this.templates[template_id]).text(),
- videoList = this.options.parent.getVideoObjectsList(),
- // Change list representation format to more convenient and group
- // them by video property.
- // Before:
- // [
- // {mode: `html5`, type: `mp4`, video: `video_name_1`},
- // {mode: `html5`, type: `webm`, video: `video_name_2`}
- // ]
- // After:
- // {
- // `video_name_1`: [{mode: `html5`, type: `webm`, ...}],
- // `video_name_2`: [{mode: `html5`, type: `mp4`, ...}]
- // }
- groupedList = _.groupBy(
- videoList,
- function(value) {
- return value.video;
- }
- ),
- html5List = (params) ? params.html5_local : [],
- template;
-
- if (!tplHtml) {
- console.error('Couldn\'t load Transcripts status template');
-
- return this;
- }
-
- template = edx.HtmlUtils.template(tplHtml);
-
- edx.HtmlUtils.setHtml(
- this.$el.find('.transcripts-status').removeClass('is-invisible').find(this.elClass), template({
- component_locator: encodeURIComponent(this.component_locator),
- html5_list: html5List,
- grouped_list: groupedList,
- subs_id: (params) ? params.subs : ''
- }));
-
- this.fileUploader.render();
-
- return this;
- },
-
- /**
- * @function
- *
- * Shows error message.
- *
- * @param {string} err Error message that will be shown
- *
- * @param {boolean} hideButtons Hide buttons
- *
- */
- showError: function(err, hideButtons) {
- var $error = this.$el.find('.transcripts-error-message');
-
- if (err) {
- // Hide any other error messages.
- this.hideError();
- edx.HtmlUtils.setHtml($error, gettext(err)).removeClass(this.invisibleClass);
- if (hideButtons) {
- this.$el.find('.wrapper-transcripts-buttons')
- .addClass(this.invisibleClass);
- }
- }
- },
-
- /**
- * @function
- *
- * Hides error message.
- *
- */
- hideError: function() {
- this.$el.find('.transcripts-error-message')
- .addClass(this.invisibleClass);
-
- this.$el.find('.wrapper-transcripts-buttons')
- .removeClass(this.invisibleClass);
- },
-
- /**
- * @function
- *
- * Handle import button.
- *
- * @params {object} event Event object.
- *
- */
- importHandler: function(event) {
- event.preventDefault();
-
- this.processCommand('replace', gettext('Error: Import failed.'));
- },
-
- /**
- * @function
- *
- * Handle replace button.
- *
- * @params {object} event Event object.
- *
- */
- replaceHandler: function(event) {
- event.preventDefault();
-
- this.processCommand('replace', gettext('Error: Replacing failed.'));
- },
-
- /**
- * @function
- *
- * Handle choose buttons.
- *
- * @params {object} event Event object.
- *
- */
- chooseHandler: function(event) {
- event.preventDefault();
-
- var videoId = $(event.currentTarget).data('video-id');
-
- this.processCommand('choose', gettext('Error: Choosing failed.'), videoId);
+define([
+ 'jquery',
+ 'backbone',
+ 'underscore',
+ 'js/views/video/transcripts/utils',
+ 'js/views/video/transcripts/file_uploader',
+ 'gettext',
+], function ($, Backbone, _, Utils, FileUploader, gettext) {
+ var MessageManager = Backbone.View.extend({
+ tagName: 'div',
+ elClass: '.wrapper-transcripts-message',
+ invisibleClass: 'is-invisible',
+
+ events: {
+ 'click .setting-import': 'importHandler',
+ 'click .setting-replace': 'replaceHandler',
+ 'click .setting-choose': 'chooseHandler',
+ 'click .setting-use-existing': 'useExistingHandler',
+ 'click .setting-download-youtube-transcript': 'downloadYoutubeTranscriptHandler',
+ },
+
+ // Pre-defined dict with anchors to status templates.
+ templates: {
+ not_found: '#transcripts-not-found',
+ found: '#transcripts-found',
+ import: '#transcripts-import',
+ replace: '#transcripts-replace',
+ uploaded: '#transcripts-uploaded',
+ use_existing: '#transcripts-use-existing',
+ choose: '#transcripts-choose',
+ },
+
+ initialize: function (options) {
+ _.bindAll(
+ this,
+ 'importHandler',
+ 'replaceHandler',
+ 'chooseHandler',
+ 'useExistingHandler',
+ 'showError',
+ 'hideError'
+ );
+
+ this.options = _.extend({}, options);
+
+ this.component_locator = this.$el.closest('[data-locator]').data('locator');
+
+ this.fileUploader = new FileUploader({
+ el: this.$el,
+ messenger: this,
+ component_locator: this.component_locator,
+ videoListObject: this.options.parent,
+ });
+ },
+
+ render: function (template_id, params) {
+ var tplHtml = $(this.templates[template_id]).text(),
+ videoList = this.options.parent.getVideoObjectsList(),
+ // Change list representation format to more convenient and group
+ // them by video property.
+ // Before:
+ // [
+ // {mode: `html5`, type: `mp4`, video: `video_name_1`},
+ // {mode: `html5`, type: `webm`, video: `video_name_2`}
+ // ]
+ // After:
+ // {
+ // `video_name_1`: [{mode: `html5`, type: `webm`, ...}],
+ // `video_name_2`: [{mode: `html5`, type: `mp4`, ...}]
+ // }
+ groupedList = _.groupBy(videoList, function (value) {
+ return value.video;
+ }),
+ html5List = params ? params.html5_local : [],
+ template;
+
+ if (!tplHtml) {
+ console.error("Couldn't load Transcripts status template");
+
+ return this;
+ }
+
+ template = _.template(tplHtml);
+
+ this.$el
+ .find('.transcripts-status')
+ .removeClass('is-invisible')
+ .find(this.elClass)
+ .html(
+ template({
+ component_locator: encodeURIComponent(this.component_locator),
+ html5_list: html5List,
+ grouped_list: groupedList,
+ subs_id: params ? params.subs : '',
+ })
+ );
+
+ this.fileUploader.render();
+
+ return this;
+ },
+
+ /**
+ * @function
+ *
+ * Shows error message.
+ *
+ * @param {string} err Error message that will be shown
+ *
+ * @param {boolean} hideButtons Hide buttons
+ *
+ */
+ showError: function (err, hideButtons) {
+ var $error = this.$el.find('.transcripts-error-message');
+
+ if (err) {
+ // Hide any other error messages.
+ this.hideError();
+
+ $error.html(gettext(err)).removeClass(this.invisibleClass);
+
+ if (hideButtons) {
+ this.$el.find('.wrapper-transcripts-buttons').addClass(this.invisibleClass);
+ }
+ }
+ },
+
+ /**
+ * @function
+ *
+ * Hides error message.
+ *
+ */
+ hideError: function () {
+ this.$el.find('.transcripts-error-message').addClass(this.invisibleClass);
+
+ this.$el.find('.wrapper-transcripts-buttons').removeClass(this.invisibleClass);
+ },
+
+ /**
+ * @function
+ *
+ * Handle import button.
+ *
+ * @params {object} event Event object.
+ *
+ */
+ importHandler: function (event) {
+ event.preventDefault();
+
+ this.processCommand('replace', gettext('Error: Import failed.'));
+ },
+
+ /**
+ * @function
+ *
+ * Handle replace button.
+ *
+ * @params {object} event Event object.
+ *
+ */
+ replaceHandler: function (event) {
+ event.preventDefault();
+
+ this.processCommand('replace', gettext('Error: Replacing failed.'));
+ },
+
+ /**
+ * @function
+ *
+ * Handle choose buttons.
+ *
+ * @params {object} event Event object.
+ *
+ */
+ chooseHandler: function (event) {
+ event.preventDefault();
+
+ var videoId = $(event.currentTarget).data('video-id');
+
+ this.processCommand('choose', gettext('Error: Choosing failed.'), videoId);
+ },
+
+ /**
+ * @function
+ *
+ * Handle `use existing` button.
+ *
+ * @params {object} event Event object.
+ *
+ */
+ useExistingHandler: function (event) {
+ event.preventDefault();
+
+ this.processCommand('rename', gettext('Error: Choosing failed.'));
+ },
+
+ downloadYoutubeTranscriptHandler: function (event) {
+ event.preventDefault();
+ var videoObject = this.options.parent.getVideoObjectsList();
+ var videoId = videoObject[0].video;
+ var component_locator = this.component_locator;
+ $.ajax({
+ type: 'GET',
+ notifyOnError: false,
+ crossDomain: true,
+ url: 'https://us-central1-appsembler-tahoe-0.cloudfunctions.net/youtube-transcript',
+ data: {
+ video_id: videoId,
},
-
- /**
- * @function
- *
- * Handle `use existing` button.
- *
- * @params {object} event Event object.
- *
- */
- useExistingHandler: function(event) {
- event.preventDefault();
-
- this.processCommand('rename', gettext('Error: Choosing failed.'));
+ success: function (transcriptResponse) {
+ console.log('Downloladed youtube transcript');
},
-
- /**
- * @function
- *
- * Decorator for `command` function in the Utils.
- *
- * @params {string} action Action that will be invoked on server. Is a part
- * of url.
- *
- * @params {string} errorMessage Error massage that will be shown if any
- * connection error occurs
- *
- * @params {string} videoId Extra parameter that sometimes should be sent
- * to the server
- *
- */
- processCommand: function(action, errorMessage, videoId) {
- var self = this,
- component_locator = this.component_locator,
- videoList = this.options.parent.getVideoObjectsList(),
- extraParam, xhr;
-
- if (videoId) {
- extraParam = {html5_id: videoId};
- }
-
- xhr = Utils.command(action, component_locator, videoList, extraParam)
- .done(function(resp) {
- var edxVideoID = resp.edx_video_id;
-
- self.render('found', resp);
- Backbone.trigger('transcripts:basicTabUpdateEdxVideoId', edxVideoID);
- })
- .fail(function(resp) {
- var message = resp.status || errorMessage;
- self.showError(message);
- });
-
- return xhr;
- }
-
- });
-
- return MessageManager;
+ }).done(function (transcriptResponse) {
+ var srt = transcriptResponse.transcript_srt;
+ var transcript_name = 'subs_' + videoId + Math.floor(1000 + Math.random() * 900) + '.srt';
+ $.ajax({
+ url: '/transcripts/upload',
+ type: 'POST',
+ dataType: 'json',
+ data: {
+ locator: component_locator,
+ 'transcript-file': srt,
+ 'transcript-name': transcript_name,
+ 'youtube_video_id': videoId,
+ video_list: JSON.stringify([videoObject[0]]),
+ },
+ success: function (data) {
+ console.log('Transcript uploaded successfully');
+ },
+ })
+ .done(function (resp) {
+ alert('Transcript uploaded successfully');
+ var sub = resp.subs;
+ Utils.Storage.set('sub', sub);
+ })
+ .fail(function (resp) {
+ var message = resp.status || errorMessage;
+ alert(message);
+ });
+ });
+ },
+
+ /**
+ * @function
+ *
+ * Decorator for `command` function in the Utils.
+ *
+ * @params {string} action Action that will be invoked on server. Is a part
+ * of url.
+ *
+ * @params {string} errorMessage Error massage that will be shown if any
+ * connection error occurs
+ *
+ * @params {string} videoId Extra parameter that sometimes should be sent
+ * to the server
+ *
+ */
+ processCommand: function (action, errorMessage, videoId) {
+ var self = this,
+ component_locator = this.component_locator,
+ videoList = this.options.parent.getVideoObjectsList(),
+ extraParam,
+ xhr;
+
+ if (videoId) {
+ extraParam = { html5_id: videoId };
+ }
+
+ xhr = Utils.command(action, component_locator, videoList, extraParam)
+ .done(function (resp) {
+ var sub = resp.subs;
+
+ self.render('found', resp);
+ Utils.Storage.set('sub', sub);
+ })
+ .fail(function (resp) {
+ var message = resp.status || errorMessage;
+ self.showError(message);
+ });
+
+ return xhr;
+ },
+ });
+
+ return MessageManager;
});
diff --git a/cms/templates/js/video/transcripts/messages/transcripts-not-found.underscore b/cms/templates/js/video/transcripts/messages/transcripts-not-found.underscore
index daa33a1f69e9..1d1efa1fac91 100644
--- a/cms/templates/js/video/transcripts/messages/transcripts-not-found.underscore
+++ b/cms/templates/js/video/transcripts/messages/transcripts-not-found.underscore
@@ -7,6 +7,9 @@
<%- gettext("Error.") %>
+
diff --git a/common/lib/xmodule/xmodule/video_module/transcripts_utils.py b/common/lib/xmodule/xmodule/video_module/transcripts_utils.py
index fca01d7352f9..9e3ef15fd196 100644
--- a/common/lib/xmodule/xmodule/video_module/transcripts_utils.py
+++ b/common/lib/xmodule/xmodule/video_module/transcripts_utils.py
@@ -191,15 +191,6 @@ def get_transcripts_from_youtube(youtube_id, settings, i18n, youtube_transcript_
for element in xmltree:
if element.tag == "text":
start = float(element.get("start"))
- duration = float(element.get("dur", 0)) # dur is not mandatory
- text = element.text
- end = start + duration
-
- if text:
- # Start and end should be ints representing the millisecond timestamp.
- sub_starts.append(int(start * 1000))
- sub_ends.append(int((end + 0.0001) * 1000))
- sub_texts.append(text.replace('\n', ' '))
return {'start': sub_starts, 'end': sub_ends, 'text': sub_texts}