Commit 4415fb4c by Peter Fogg

Started removing XML from video editor.

TODO: This breaks the 1.5x and .75x speeds. I'm still looking into
why.

TODO: VideoDescriptor inherits from RawDescriptor in order to
use the from_xml and export_to_xml methods. This seems really ugly,
though; I'd rather find a better way to do this.
parent 468dfe34
...@@ -4,7 +4,7 @@ Feature: Video Component Editor ...@@ -4,7 +4,7 @@ Feature: Video Component Editor
Scenario: User can view metadata Scenario: User can view metadata
Given I have created a Video component Given I have created a Video component
And I edit and select Settings And I edit and select Settings
Then I see only the Video display name setting Then I see the correct settings and default values
Scenario: User can modify display name Scenario: User can modify display name
Given I have created a Video component Given I have created a Video component
......
...@@ -4,6 +4,14 @@ ...@@ -4,6 +4,14 @@
from lettuce import world, step from lettuce import world, step
@step('I see only the video display name setting$') @step('I see the correct settings and default values$')
def i_see_only_the_video_display_name(step): def i_see_the_correct_settings_and_values(step):
world.verify_all_setting_entries([['Display Name', "default", True]]) world.verify_all_setting_entries([['.75x', 'JMD_ifUUfsU', False],
['1.25x', 'AKqURZnYqpk', False],
['1.5x', 'DYpADpL7jAY', False],
['Display Name', "default", True],
['External Source', '', False],
['External Track', '', False],
['Normal Speed', 'OEoXaMPEzfM', False],
['Show Captions', 'True', False],
])
...@@ -8,7 +8,7 @@ class @Video ...@@ -8,7 +8,7 @@ class @Video
@show_captions = @el.data('show-captions') == "true" @show_captions = @el.data('show-captions') == "true"
window.player = null window.player = null
@el = $("#video_#{@id}") @el = $("#video_#{@id}")
@parseVideos @el.data('streams') @parseVideos()
@fetchMetadata() @fetchMetadata()
@parseSpeed() @parseSpeed()
$("#video_#{@id}").data('video', this).addClass('video-load-complete') $("#video_#{@id}").data('video', this).addClass('video-load-complete')
...@@ -27,10 +27,11 @@ class @Video ...@@ -27,10 +27,11 @@ class @Video
parseVideos: (videos) -> parseVideos: (videos) ->
@videos = {} @videos = {}
$.each videos.split(/,/), (index, video) => @videos['.75'] = @el.data('youtube-id-0-75')
video = video.split(/:/) @videos['1.0'] = @el.data('normal-speed-video-id')
speed = parseFloat(video[0]).toFixed(2).replace /\.00$/, '.0' @videos['1.25'] = @el.data('youtube-id-1-25')
@videos[speed] = video[1] @videos['1.5'] = @el.data('youtube-id-1-5')
alert @videos['1.5']
parseSpeed: -> parseSpeed: ->
@setSpeed($.cookie('video_speed')) @setSpeed($.cookie('video_speed'))
......
...@@ -8,18 +8,25 @@ from django.http import Http404 ...@@ -8,18 +8,25 @@ from django.http import Http404
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.contentstore.content import StaticContent from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xblock.core import Integer, Scope, String from xblock.core import Integer, Scope, String, Boolean, Float
import datetime
import time
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
YOUTUBE_SPEEDS = ['.75', '1.0', '1.25', '1.5']
class VideoFields(object): class VideoFields(object):
data = String(help="XML data for the problem", scope=Scope.content)
position = Integer(help="Current position in the video", scope=Scope.user_state, default=0) position = Integer(help="Current position in the video", scope=Scope.user_state, default=0)
show_captions = Boolean(help="Whether or not captions are shown", display_name="Show Captions", scope=Scope.settings, default=True)
youtube_id_1_0 = String(help="Youtube ID for normal speed video", display_name="Normal Speed", scope=Scope.settings, default="OEoXaMPEzfM")
youtube_id_0_75 = String(help="Youtube ID for .75x speed video", display_name=".75x", scope=Scope.settings, default="JMD_ifUUfsU")
youtube_id_1_25 = String(help="Youtube ID for 1.25x speed video", display_name="1.25x", scope=Scope.settings, default="AKqURZnYqpk")
youtube_id_1_5 = String(help="Youtube ID for 1.5x speed video", display_name="1.5x", scope=Scope.settings, default="DYpADpL7jAY")
start_time = Float(help="Time the video starts", display_name="Start Time", scope=Scope.settings, default=0.0)
end_time = Float(help="Time the video ends", display_name="End Time", scope=Scope.settings, default=0.0)
source = String(help="External source to download video", display_name="External Source", scope=Scope.settings, default="")
track = String(help="External source to download subtitle strack", display_name="External Track", scope=Scope.settings, default="")
class VideoModule(VideoFields, XModule): class VideoModule(VideoFields, XModule):
...@@ -39,52 +46,6 @@ class VideoModule(VideoFields, XModule): ...@@ -39,52 +46,6 @@ class VideoModule(VideoFields, XModule):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
XModule.__init__(self, *args, **kwargs) XModule.__init__(self, *args, **kwargs)
xmltree = etree.fromstring(self.data)
self.youtube = xmltree.get('youtube')
self.show_captions = xmltree.get('show_captions', 'true')
self.source = self._get_source(xmltree)
self.track = self._get_track(xmltree)
self.start_time, self.end_time = self._get_timeframe(xmltree)
def _get_source(self, xmltree):
# find the first valid source
return self._get_first_external(xmltree, 'source')
def _get_track(self, xmltree):
# find the first valid track
return self._get_first_external(xmltree, 'track')
def _get_first_external(self, xmltree, tag):
"""
Will return the first valid element
of the given tag.
'valid' means has a non-empty 'src' attribute
"""
result = None
for element in xmltree.findall(tag):
src = element.get('src')
if src:
result = src
break
return result
def _get_timeframe(self, xmltree):
""" Converts 'from' and 'to' parameters in video tag to seconds.
If there are no parameters, returns empty string. """
def parse_time(s):
"""Converts s in '12:34:45' format to seconds. If s is
None, returns empty string"""
if s is None:
return ''
else:
x = time.strptime(s, '%H:%M:%S')
return datetime.timedelta(hours=x.tm_hour,
minutes=x.tm_min,
seconds=x.tm_sec).total_seconds()
return parse_time(xmltree.get('from')), parse_time(xmltree.get('to'))
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
''' '''
Handle ajax calls to this video. Handle ajax calls to this video.
...@@ -113,37 +74,92 @@ class VideoModule(VideoFields, XModule): ...@@ -113,37 +74,92 @@ class VideoModule(VideoFields, XModule):
#log.debug(u"STATE POSITION {0}".format(self.position)) #log.debug(u"STATE POSITION {0}".format(self.position))
return json.dumps({'position': self.position}) return json.dumps({'position': self.position})
def video_list(self):
return self.youtube
def get_html(self): def get_html(self):
# We normally let JS parse this, but in the case that we need a hacked
# out <object> player because YouTube has broken their <iframe> API for
# the third time in a year, we need to extract it server side.
normal_speed_video_id = None # The 1.0 speed video
# video_list() example:
# "0.75:nugHYNiD3fI,1.0:7m8pab1MfYY,1.25:3CxdPGXShq8,1.50:F-D7bOFCnXA"
for video_id_str in self.video_list().split(","):
if video_id_str.startswith("1.0:"):
normal_speed_video_id = video_id_str.split(":")[1]
return self.system.render_template('video.html', { return self.system.render_template('video.html', {
'streams': self.video_list(), 'youtube_id_0_75': self.youtube_id_0_75,
'normal_speed_video_id': self.youtube_id_1_0,
'youtube_id_1_25': self.youtube_id_1_25,
'youtube_id_1_5': self.youtube_id_1_5,
'id': self.location.html_id(), 'id': self.location.html_id(),
'position': self.position, 'position': self.position,
'source': self.source, 'source': self.source,
'track': self.track, 'track': self.track,
'display_name': self.display_name_with_default, 'display_name': self.display_name_with_default,
'caption_asset_path': "/static/subs/", 'caption_asset_path': "/static/subs/",
'show_captions': self.show_captions, 'show_captions': 'true' if self.show_captions else 'false',
'start': self.start_time, 'start': self.start_time,
'end': self.end_time, 'end': self.end_time
'normal_speed_video_id': normal_speed_video_id
}) })
# TODO: (pfogg) I am highly uncertain about inheriting from
# RawDescriptor for the xml-related methods. This makes LMS unit tests
# pass, but this really shouldn't be a RawDescriptor if users can't
# see raw xml.
class VideoDescriptor(VideoFields, RawDescriptor):
# also if it's just return super(...)... then we can just remove these methods, ha
class VideoDescriptor(VideoFields,
MetadataOnlyEditingDescriptor,
RawDescriptor):
module_class = VideoModule module_class = VideoModule
stores_state = True stores_state = True
template_dir_name = "video" template_dir_name = "video"
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(MetadataOnlyEditingDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([VideoModule.start_time,
VideoModule.end_time])
return non_editable_fields
@classmethod
def from_xml(cls, xml_data, system, org=None, course=None):
"""
Creates an instance of this descriptor from the supplied xml_data.
This may be overridden by subclasses
xml_data: A string of xml that will be translated into data and children for
this module
system: A DescriptorSystem for interacting with external resources
org and course are optional strings that will be used in the generated modules
url identifiers
"""
return super(RawDescriptor, cls).from_xml(xml_data, system, org, course)
def export_to_xml(self, resource_fs):
"""
Returns an xml string representign this module, and all modules
underneath it. May also write required resources out to resource_fs
Assumes that modules have single parentage (that no module appears twice
in the same course), and that it is thus safe to nest modules as xml
children as appropriate.
The returned XML should be able to be parsed back into an identical
XModuleDescriptor using the from_xml method with the same system, org,
and course
resource_fs is a pyfilesystem object (from the fs package)
"""
return super(RawDescriptor, self).export_to_xml(resource_fs)
# xml = etree.Element('video')
# xml.set('youtube', self.video_list())
# xml.set('show_captions', self.show_captions)
# xml.set('from', self.start_time)
# xml.set('to', self.end_time)
# source_tag = etree.SubElement(xml, 'source')
# source_tag.set('src', self.source)
# track_tag = etree.SubElement(xml, 'track')
# track_tag.set('src', self.track)
# return etree.tostring(xml, pretty_print=True, encoding='utf-8')
# def video_list(self):
# videos = [self.youtube_id_0_75, self.youtube_id_1_0,
# self.youtube_id_1_25, self.youtube_id_1_5]
# streams = [':'.join((video, youtube_id))
# for video, youtube_id
# in zip(YOUTUBE_SPEEDS, videos)]
# return ','.join(streams)
...@@ -32,7 +32,17 @@ ...@@ -32,7 +32,17 @@
width="640" height="390"></embed> width="640" height="390"></embed>
</object> </object>
%else: %else:
<div id="video_${id}" class="video" data-streams="${streams}" data-show-captions="${show_captions}" data-start="${start}" data-end="${end}" data-caption-asset-path="${caption_asset_path}" data-autoplay="${settings.MITX_FEATURES['AUTOPLAY_VIDEOS']}"> <div id="video_${id}"
class="video"
data-youtube-id-0-75="${youtube_id_0_75}"
data-normal-speed-video-id="${normal_speed_video_id}"
data-youtube-id-1-25="${youtube_id_1_25}"
data-youtube-id-1-5="${youtube_id_1_5}"
data-show-captions="${show_captions}"
data-start="${start}"
data-end="${end}"
data-caption-asset-path="${caption_asset_path}"
data-autoplay="${settings.MITX_FEATURES['AUTOPLAY_VIDEOS']}">
<div class="tc-wrapper"> <div class="tc-wrapper">
<article class="video-wrapper"> <article class="video-wrapper">
<section class="video-player"> <section class="video-player">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment