diff --git a/cms/djangoapps/contentstore/views/assets.py b/cms/djangoapps/contentstore/views/assets.py index 94bfa55b58..b35f00f0e2 100644 --- a/cms/djangoapps/contentstore/views/assets.py +++ b/cms/djangoapps/contentstore/views/assets.py @@ -348,6 +348,8 @@ def generate_export_course(request, org, course, name): try: export_to_xml(modulestore('direct'), contentstore(), loc, root_dir, name, modulestore()) except SerializationError, e: + logging.exception('There was an error exporting course {0}. {1}'.format(course_module.location, unicode(e))) + unit = None failed_item = None parent = None @@ -380,6 +382,7 @@ def generate_export_course(request, org, course, name): }) }) except Exception, e: + logging.exception('There was an error exporting course {0}. {1}'.format(course_module.location, unicode(e))) return render_to_response('export.html', { 'context_course': course_module, 'successful_import_redirect_url': '', diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js index e8a30f6e9c..a34f33ba4c 100644 --- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js +++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js @@ -312,15 +312,34 @@ function () { var newIndex; if (this.videoCaption.loaded) { - time = Math.round(Time.convert(time, this.speed, '1.0') * 1000 + 250); + // Current mode === 'flash' can only be for YouTube videos. So, we + // don't have to also check for videoType === 'youtube'. + if (this.currentPlayerMode === 'flash') { + // Total play time changes with speed change. Also there is + // a 250 ms delay we have to take into account. + time = Math.round( + Time.convert(time, this.speed, '1.0') * 1000 + 250 + ); + } else { + // Total play time remains constant when speed changes. + time = Math.round(parseInt(time, 10) * 1000); + } + newIndex = this.videoCaption.search(time); - if (newIndex !== void 0 && this.videoCaption.currentIndex !== newIndex) { + if ( + newIndex !== void 0 && + this.videoCaption.currentIndex !== newIndex + ) { if (this.videoCaption.currentIndex) { - this.videoCaption.subtitlesEl.find('li.current').removeClass('current'); + this.videoCaption.subtitlesEl + .find('li.current') + .removeClass('current'); } - this.videoCaption.subtitlesEl.find("li[data-index='" + newIndex + "']").addClass('current'); + this.videoCaption.subtitlesEl + .find("li[data-index='" + newIndex + "']") + .addClass('current'); this.videoCaption.currentIndex = newIndex; @@ -333,9 +352,29 @@ function () { var time; event.preventDefault(); - time = Math.round(Time.convert($(event.target).data('start'), '1.0', this.speed) / 1000); - this.trigger('videoPlayer.onCaptionSeek', {'type': 'onCaptionSeek', 'time': time}); + // Current mode === 'flash' can only be for YouTube videos. So, we + // don't have to also check for videoType === 'youtube'. + if (this.currentPlayerMode === 'flash') { + // Total play time changes with speed change. Also there is + // a 250 ms delay we have to take into account. + time = Math.round( + Time.convert( + $(event.target).data('start'), '1.0', this.speed + ) / 1000 + ); + } else { + // Total play time remains constant when speed changes. + time = parseInt($(event.target).data('start'), 10)/1000; + } + + this.trigger( + 'videoPlayer.onCaptionSeek', + { + 'type': 'onCaptionSeek', + 'time': time + } + ); } function calculateOffset(element) { diff --git a/common/lib/xmodule/xmodule/tests/test_video.py b/common/lib/xmodule/xmodule/tests/test_video.py index 4a13d565cc..a6a7d86510 100644 --- a/common/lib/xmodule/xmodule/tests/test_video.py +++ b/common/lib/xmodule/xmodule/tests/test_video.py @@ -15,6 +15,7 @@ the course, section, subsection, unit, etc. import unittest from . import LogicTest +from lxml import etree from .import get_test_system from xmodule.modulestore import Location from xmodule.video_module import VideoDescriptor, _create_youtube_string @@ -64,6 +65,32 @@ class VideoModuleTest(LogicTest): '1.25': '', '1.50': ''}) + def test_parse_youtube_invalid(self): + """Ensure that ids that are invalid return an empty dict""" + + # invalid id + youtube_str = 'thisisaninvalidid' + output = VideoDescriptor._parse_youtube(youtube_str) + self.assertEqual(output, {'0.75': '', + '1.00': '', + '1.25': '', + '1.50': ''}) + # another invalid id + youtube_str = ',::,:,,' + output = VideoDescriptor._parse_youtube(youtube_str) + self.assertEqual(output, {'0.75': '', + '1.00': '', + '1.25': '', + '1.50': ''}) + + # and another one, partially invalid + youtube_str = '0.75_BAD!!!,1.0:AXdE34_U,1.25:KLHF9K_Y,1.5:VO3SxfeD,' + output = VideoDescriptor._parse_youtube(youtube_str) + self.assertEqual(output, {'0.75': '', + '1.00': 'AXdE34_U', + '1.25': 'KLHF9K_Y', + '1.50': 'VO3SxfeD'}) + def test_parse_youtube_key_format(self): """ Make sure that inconsistent speed keys are parsed correctly. @@ -263,6 +290,62 @@ class VideoDescriptorImportTestCase(unittest.TestCase): 'data': '' }) + def test_from_xml_double_quotes(self): + """ + Make sure we can handle the double-quoted string format (which was used for exporting for + a few weeks). + """ + module_system = DummySystem(load_error_modules=True) + xml_data =''' +