Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
65adb7b2
Commit
65adb7b2
authored
Mar 03, 2014
by
polesye
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
BLD-896: Fix cc button in HTML5 videos.
parent
78fe797e
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
302 additions
and
229 deletions
+302
-229
common/lib/xmodule/xmodule/js/spec/video/general_spec.js
+2
-26
common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
+51
-18
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
+0
-1
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+0
-6
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
+3
-4
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
+9
-12
common/lib/xmodule/xmodule/video_module/video_module.py
+50
-39
lms/djangoapps/courseware/features/video.feature
+50
-11
lms/djangoapps/courseware/features/video.py
+100
-90
lms/djangoapps/courseware/tests/test_video_handlers.py
+37
-22
No files found.
common/lib/xmodule/xmodule/js/spec/video/general_spec.js
View file @
65adb7b2
...
...
@@ -75,32 +75,8 @@
expect
(
state
.
el
).
toBe
(
'#video_id'
);
});
it
(
'parse the videos if subtitles exist'
,
function
()
{
var
sub
=
'Z5KLxerq05Y'
;
expect
(
state
.
videos
).
toEqual
({
'0.75'
:
sub
,
'1.0'
:
sub
,
'1.25'
:
sub
,
'1.50'
:
sub
});
});
it
(
'parse the videos if subtitles do not exist'
,
function
()
{
var
sub
=
''
;
$
(
'#example'
).
find
(
'.video'
).
data
(
'sub'
,
''
);
state
=
new
window
.
Video
(
'#example'
);
expect
(
state
.
videos
).
toEqual
({
'0.75'
:
sub
,
'1.0'
:
sub
,
'1.25'
:
sub
,
'1.50'
:
sub
});
it
(
'doesn
\'
t have `videos` dictionary'
,
function
()
{
expect
(
state
.
videos
).
toBeUndefined
();
});
it
(
'parse Html5 sources'
,
function
()
{
...
...
common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
View file @
65adb7b2
...
...
@@ -7,8 +7,6 @@
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
.
andReturn
(
null
);
state
=
jasmine
.
initializePlayer
();
videoControl
=
state
.
videoControl
;
$
.
fn
.
scrollTo
.
reset
();
});
...
...
@@ -29,18 +27,20 @@
describe
(
'always'
,
function
()
{
beforeEach
(
function
()
{
spyOn
(
$
,
'ajaxWithPrefix'
).
andCallThrough
();
state
=
jasmine
.
initializePlayer
();
});
it
(
'create the caption element'
,
function
()
{
state
=
jasmine
.
initializePlayer
();
expect
(
$
(
'.video'
)).
toContain
(
'ol.subtitles'
);
});
it
(
'add caption control to video player'
,
function
()
{
state
=
jasmine
.
initializePlayer
();
expect
(
$
(
'.video'
)).
toContain
(
'a.hide-subtitles'
);
});
it
(
'add ARIA attributes to caption control'
,
function
()
{
state
=
jasmine
.
initializePlayer
();
var
captionControl
=
$
(
'a.hide-subtitles'
);
expect
(
captionControl
).
toHaveAttrs
({
'role'
:
'button'
,
...
...
@@ -49,7 +49,11 @@
});
});
it
(
'fetch the caption'
,
function
()
{
it
(
'fetch the caption in HTML5 mode'
,
function
()
{
runs
(
function
()
{
state
=
jasmine
.
initializePlayer
();
});
waitsFor
(
function
()
{
if
(
state
.
videoCaption
.
loaded
===
true
)
{
return
true
;
...
...
@@ -62,29 +66,62 @@
expect
(
$
.
ajaxWithPrefix
).
toHaveBeenCalledWith
({
url
:
'/transcript/translation'
,
notifyOnError
:
false
,
data
:
{
videoId
:
'Z5KLxerq05Y'
,
data
:
jasmine
.
any
(
Object
),
success
:
jasmine
.
any
(
Function
),
error
:
jasmine
.
any
(
Function
)
});
expect
(
$
.
ajaxWithPrefix
.
mostRecentCall
.
args
[
0
].
data
)
.
toEqual
({
language
:
'en'
},
});
});
});
it
(
'fetch the caption in Youtube mode'
,
function
()
{
runs
(
function
()
{
state
=
jasmine
.
initializePlayerYouTube
();
});
waitsFor
(
function
()
{
if
(
state
.
videoCaption
.
loaded
===
true
)
{
return
true
;
}
return
false
;
},
'Expect captions to be loaded.'
,
WAIT_TIMEOUT
);
runs
(
function
()
{
expect
(
$
.
ajaxWithPrefix
).
toHaveBeenCalledWith
({
url
:
'/transcript/translation'
,
notifyOnError
:
false
,
data
:
jasmine
.
any
(
Object
),
success
:
jasmine
.
any
(
Function
),
error
:
jasmine
.
any
(
Function
)
});
expect
(
$
.
ajaxWithPrefix
.
mostRecentCall
.
args
[
0
].
data
)
.
toEqual
({
language
:
'en'
,
videoId
:
'abcdefghijkl'
});
});
});
it
(
'bind window resize event'
,
function
()
{
state
=
jasmine
.
initializePlayer
();
expect
(
$
(
window
)).
toHandleWith
(
'resize'
,
state
.
videoCaption
.
resize
);
});
it
(
'bind the hide caption button'
,
function
()
{
state
=
jasmine
.
initializePlayer
();
expect
(
$
(
'.hide-subtitles'
)).
toHandleWith
(
'click'
,
state
.
videoCaption
.
toggle
);
});
it
(
'bind the mouse movement'
,
function
()
{
state
=
jasmine
.
initializePlayer
();
expect
(
$
(
'.subtitles'
)).
toHandleWith
(
'mouseover'
,
state
.
videoCaption
.
onMouseEnter
);
...
...
@@ -103,8 +140,9 @@
});
it
(
'bind the scroll'
,
function
()
{
expect
(
$
(
'.subtitles'
))
.
toHandleWith
(
'scroll'
,
state
.
videoControl
.
showControls
);
state
=
jasmine
.
initializePlayer
();
expect
(
$
(
'.subtitles'
))
.
toHandleWith
(
'scroll'
,
state
.
videoControl
.
showControls
);
});
});
...
...
@@ -284,7 +322,8 @@
describe
(
'when no captions file was specified'
,
function
()
{
beforeEach
(
function
()
{
state
=
jasmine
.
initializePlayer
(
'video_all.html'
,
{
'sub'
:
''
'sub'
:
''
,
'transcriptLanguages'
:
{},
});
});
...
...
@@ -395,6 +434,8 @@
});
it
(
'reRenderCaption'
,
function
()
{
state
=
jasmine
.
initializePlayer
();
var
Caption
=
state
.
videoCaption
,
li
;
...
...
@@ -426,14 +467,6 @@
spyOn
(
state
,
'youtubeId'
).
andReturn
(
'Z5KLxerq05Y'
);
});
it
(
'do not fetch captions, if 1.0 speed is absent'
,
function
()
{
state
.
youtubeId
.
andReturn
(
void
(
0
));
Caption
.
fetchCaption
();
expect
(
$
.
ajaxWithPrefix
).
not
.
toHaveBeenCalled
();
expect
(
Caption
.
hideCaptions
).
not
.
toHaveBeenCalled
();
});
it
(
'show caption on language change'
,
function
()
{
Caption
.
loaded
=
true
;
Caption
.
fetchCaption
();
...
...
common/lib/xmodule/xmodule/js/spec/video/video_player_spec.js
View file @
65adb7b2
...
...
@@ -45,7 +45,6 @@ function (VideoPlayer) {
it
(
'create video caption'
,
function
()
{
expect
(
state
.
videoCaption
).
toBeDefined
();
expect
(
state
.
youtubeId
(
'1.0'
)).
toEqual
(
'Z5KLxerq05Y'
);
expect
(
state
.
speed
).
toEqual
(
'1.50'
);
expect
(
state
.
config
.
transcriptTranslationUrl
)
.
toEqual
(
'/transcript/translation'
);
...
...
common/lib/xmodule/xmodule/js/src/video/01_initialize.js
View file @
65adb7b2
...
...
@@ -202,12 +202,6 @@ function (VideoPlayer, VideoStorage) {
);
state
.
speeds
=
[
'0.75'
,
'1.0'
,
'1.25'
,
'1.50'
];
state
.
videos
=
{
'0.75'
:
state
.
config
.
sub
,
'1.0'
:
state
.
config
.
sub
,
'1.25'
:
state
.
config
.
sub
,
'1.50'
:
state
.
config
.
sub
};
// We must have at least one non-YouTube video source available.
// Otherwise, return a negative.
...
...
common/lib/xmodule/xmodule/js/src/video/03_video_player.js
View file @
65adb7b2
...
...
@@ -461,7 +461,7 @@ function (HTML5Video, Resizer) {
this
.
videoPlayer
.
log
(
'pause_video'
,
{
'currentTime'
:
this
.
videoPlayer
.
currentTime
currentTime
:
this
.
videoPlayer
.
currentTime
}
);
...
...
@@ -482,7 +482,7 @@ function (HTML5Video, Resizer) {
this
.
videoPlayer
.
log
(
'play_video'
,
{
'currentTime'
:
this
.
videoPlayer
.
currentTime
currentTime
:
this
.
videoPlayer
.
currentTime
}
);
...
...
@@ -863,8 +863,7 @@ function (HTML5Video, Resizer) {
// Default parameters that always get logged.
logInfo
=
{
'id'
:
this
.
id
,
'code'
:
this
.
youtubeId
()
id
:
this
.
id
};
// If extra parameters were passed to the log.
...
...
common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
View file @
65adb7b2
...
...
@@ -226,14 +226,10 @@ function () {
*/
function
fetchCaption
()
{
var
self
=
this
,
Caption
=
self
.
videoCaption
;
// Check whether the captions file was specified. This is the point
// where we either stop with the caption panel (so that a white empty
// panel to the right of the video will not be shown), or carry on
// further.
if
(
!
this
.
youtubeId
(
'1.0'
))
{
return
false
;
}
Caption
=
self
.
videoCaption
,
data
=
{
language
:
this
.
getCurrentLanguage
()
};
if
(
Caption
.
loaded
)
{
Caption
.
hideCaptions
(
false
);
...
...
@@ -245,15 +241,16 @@ function () {
Caption
.
fetchXHR
.
abort
();
}
if
(
this
.
videoType
===
'youtube'
)
{
data
.
videoId
=
this
.
youtubeId
();
}
// Fetch the captions file. If no file was specified, or if an error
// occurred, then we hide the captions panel, and the "CC" button
Caption
.
fetchXHR
=
$
.
ajaxWithPrefix
({
url
:
self
.
config
.
transcriptTranslationUrl
,
notifyOnError
:
false
,
data
:
{
videoId
:
this
.
youtubeId
(),
language
:
this
.
getCurrentLanguage
()
},
data
:
data
,
success
:
function
(
captions
)
{
Caption
.
captions
=
captions
.
text
;
Caption
.
start
=
captions
.
start
;
...
...
common/lib/xmodule/xmodule/video_module/video_module.py
View file @
65adb7b2
...
...
@@ -10,7 +10,6 @@ in-browser HTML5 video method (when in HTML5 mode).
in XML.
"""
import
os
import
json
import
logging
from
operator
import
itemgetter
...
...
@@ -329,7 +328,7 @@ class VideoModule(VideoFields, XModule):
`available_translations`: returns list of languages, for which SRT files exist. For 'en' check if SJSON exists.
"""
if
dispatch
==
'translation'
:
if
'language'
not
in
request
.
GET
or
'videoId'
not
in
request
.
GET
:
if
'language'
not
in
request
.
GET
:
log
.
info
(
"Invalid /transcript GET parameters."
)
return
Response
(
status
=
400
)
...
...
@@ -341,7 +340,7 @@ class VideoModule(VideoFields, XModule):
self
.
transcript_language
=
lang
try
:
transcript
=
self
.
translation
(
request
.
GET
.
get
(
'videoId'
))
transcript
=
self
.
translation
(
request
.
GET
.
get
(
'videoId'
,
None
))
except
(
TranscriptException
,
NotFoundError
)
as
ex
:
log
.
info
(
ex
.
message
)
response
=
Response
(
status
=
404
)
...
...
@@ -390,26 +389,30 @@ class VideoModule(VideoFields, XModule):
return
response
def
translation
(
self
,
subs
_id
):
def
translation
(
self
,
youtube
_id
):
"""
This is called to get transcript file for specific language.
subs_id: str: must be on of: self.sub or one of youtube_ids.
youtube_id: str: must be one of youtube_ids or None if HTML video
Logic flow:
If english -> give back `sub` subtitles:
Return what we have in contentstore for given subs_id,
We should not regenerate needed transcripts, if, for example, they present for youtube 1.0 speed,
and we need for other speeds. Such generation should be done in transcripts workflow.
If non-english:
a) extract subs_id from srt file name
if non-youtube:
b) try to find sjson by subs_id and return if sucessful
c) otherwise generate sjson from srt and return it.
if youtube:
b) try to find sjson by subs_id and return if sucessful
c) generate sjson from srt for all youtube speeds
If youtube_id doesn't exist, we have a video in HTML5 mode. Otherwise,
video video in Youtube or Flash modes.
if youtube:
If english -> give back youtube_id subtitles:
Return what we have in contentstore for given youtube_id.
If non-english:
a) extract youtube_id from srt file name.
b) try to find sjson by youtube_id and return if successful.
c) generate sjson from srt for all youtube speeds.
if non-youtube:
If english -> give back `sub` subtitles:
Return what we have in contentstore for given subs_if that is stored in self.sub.
If non-english:
a) try to find previously generated sjson.
b) otherwise generate sjson from srt and return it.
Filenames naming:
en: subs_videoid.srt.sjson
...
...
@@ -418,28 +421,36 @@ class VideoModule(VideoFields, XModule):
Raises:
NotFoundError if for 'en' subtitles no asset is uploaded.
"""
if
self
.
transcript_language
==
'en'
:
return
asset
(
self
.
location
,
subs_id
)
.
data
if
not
self
.
youtube_id_1_0
:
# Non-youtube (HTML5) case:
return
get_or_create_sjson
(
self
)
# Youtube case:
youtube_ids
=
youtube_speed_dict
(
self
)
assert
subs_id
in
youtube_ids
try
:
sjson_transcript
=
asset
(
self
.
location
,
subs_id
,
self
.
transcript_language
)
.
data
except
(
NotFoundError
):
log
.
info
(
"Can't find content in storage for
%
s transcript: generating."
,
subs_id
)
generate_sjson_for_all_speeds
(
self
,
self
.
transcripts
[
self
.
transcript_language
],
{
speed
:
subs_id
for
subs_id
,
speed
in
youtube_ids
.
iteritems
()},
self
.
transcript_language
)
sjson_transcript
=
asset
(
self
.
location
,
subs_id
,
self
.
transcript_language
)
.
data
return
sjson_transcript
if
youtube_id
:
# Youtube case:
if
self
.
transcript_language
==
'en'
:
return
asset
(
self
.
location
,
youtube_id
)
.
data
youtube_ids
=
youtube_speed_dict
(
self
)
assert
youtube_id
in
youtube_ids
try
:
sjson_transcript
=
asset
(
self
.
location
,
youtube_id
,
self
.
transcript_language
)
.
data
except
(
NotFoundError
):
log
.
info
(
"Can't find content in storage for
%
s transcript: generating."
,
youtube_id
)
generate_sjson_for_all_speeds
(
self
,
self
.
transcripts
[
self
.
transcript_language
],
{
speed
:
youtube_id
for
youtube_id
,
speed
in
youtube_ids
.
iteritems
()},
self
.
transcript_language
)
sjson_transcript
=
asset
(
self
.
location
,
youtube_id
,
self
.
transcript_language
)
.
data
return
sjson_transcript
else
:
# HTML5 case
if
self
.
transcript_language
==
'en'
:
return
asset
(
self
.
location
,
self
.
sub
)
.
data
else
:
return
get_or_create_sjson
(
self
)
class
VideoDescriptor
(
VideoFields
,
TabsEditingDescriptor
,
EmptyDataRawDescriptor
):
...
...
lms/djangoapps/courseware/features/video.feature
View file @
65adb7b2
...
...
@@ -2,7 +2,7 @@
Feature
:
LMS Video component
As a student, I want to view course videos in LMS
#
0
#
1
Scenario
:
Video component stores position correctly when page is reloaded
Given
the course has a Video component in Youtube mode
Then
when I view the video it has rendered in Youtube mode
...
...
@@ -13,51 +13,51 @@ Feature: LMS Video component
And
I click video button
"play"
Then I see video starts playing from "0
:
10"
position
#
1
#
2
Scenario
:
Video component is fully rendered in the LMS in HTML5 mode
Given
the course has a Video component in HTML5 mode
Then
when I view the video it has rendered in HTML5 mode
And
all sources are correct
#
2
#
3
# Firefox doesn't have HTML5 (only mp4 - fix here)
@skip_firefox
Scenario
:
Autoplay is disabled in LMS for a Video component
Given
the course has a Video component in HTML5 mode
Then
when I view the video it does not have autoplay enabled
#
3
#
4
# Youtube testing
Scenario
:
Video component is fully rendered in the LMS in Youtube mode with HTML5 sources
Given
youtube server is up and response time is 0.4 seconds
And
the course has a Video component in Youtube_HTML5 mode
Then
when I view the video it has rendered in Youtube mode
#
4
#
5
Scenario
:
Video component is not rendered in the LMS in Youtube mode with HTML5 sources
Given
youtube server is up and response time is 2 seconds
And
the course has a Video component in Youtube_HTML5 mode
Then
when I view the video it has rendered in HTML5 mode
#
5
#
6
Scenario
:
Video component is rendered in the LMS in Youtube mode without HTML5 sources
Given
youtube server is up and response time is 2 seconds
And
the course has a Video component in Youtube mode
Then
when I view the video it has rendered in Youtube mode
#
6
#
7
Scenario
:
Video component is rendered in the LMS in Youtube mode with HTML5 sources that doesn't supported by browser
Given
youtube server is up and response time is 2 seconds
And
the course has a Video component in Youtube_HTML5_Unsupported_Video mode
Then
when I view the video it has rendered in Youtube mode
#
7
#
8
Scenario
:
Video component is rendered in the LMS in HTML5 mode with HTML5 sources that doesn't supported by browser
Given
the course has a Video component in HTML5_Unsupported_Video mode
Then
error message is shown
And
error message has correct text
#
8
#
9
Scenario
:
Video component stores speed correctly when each video is in separate sequence
Given
I am registered for the course
"test_course"
And
it has a video
"A"
in
"Youtube"
mode in position
"1"
of sequential
...
...
@@ -79,8 +79,8 @@ Feature: LMS Video component
When
I open video
"C"
Then
video
"C"
should start playing at speed
"1.0"
#
9
Scenario
:
Language menu
in Video component works correctly
#
10
Scenario
:
Language menu
works correctly in Video component
Given the course has a Video component in Youtube mode
:
|
transcripts
|
sub
|
|
{"zh":
"OEoXaMPEzfM"}
|
OEoXaMPEzfM
|
...
...
@@ -90,3 +90,42 @@ Feature: LMS Video component
Then
I see
"好 各位同学"
text in the captions
And
I select language with code
"en"
And
I see
"Hi, welcome to Edx."
text in the captions
# 11
Scenario
:
CC button works correctly w/o english transcript in HTML5 mode of Video component
Given the course has a Video component in HTML5 mode
:
|
transcripts
|
|
{"zh":
"OEoXaMPEzfM"}
|
And
I make sure captions are opened
Then
I see
"好 各位同学"
text in the captions
# 12
Scenario
:
CC button works correctly only w/ english transcript in HTML5 mode of Video component
Given
I am registered for the course
"test_course"
And
I have a
"subs_OEoXaMPEzfM.srt.sjson"
transcript file in assets
And it has a video in "HTML5" mode
:
|
sub
|
|
OEoXaMPEzfM
|
And
I make sure captions are opened
Then
I see
"Hi, welcome to Edx."
text in the captions
# 13
Scenario
:
CC button works correctly w/o english transcript in Youtube mode of Video component
Given the course has a Video component in Youtube mode
:
|
transcripts
|
|
{"zh":
"OEoXaMPEzfM"}
|
And
I make sure captions are opened
Then
I see
"好 各位同学"
text in the captions
# 14
Scenario
:
CC button works correctly if transcripts and sub fields are empty, but transcript file exists is assets (Youtube mode of Video component)
Given
I am registered for the course
"test_course"
And
I have a
"subs_OEoXaMPEzfM.srt.sjson"
transcript file in assets
And
it has a video in
"Youtube"
mode
And
I make sure captions are opened
Then
I see
"Hi, welcome to Edx."
text in the captions
# 15
Scenario
:
CC button is hidden if no translations
Given
the course has a Video component in Youtube mode
Then
button
"CC"
is hidden
lms/djangoapps/courseware/features/video.py
View file @
65adb7b2
...
...
@@ -9,7 +9,6 @@ from django.conf import settings
from
cache_toolbox.core
import
del_cached_content
from
xmodule.contentstore.content
import
StaticContent
import
os
from
functools
import
partial
from
xmodule.contentstore.django
import
contentstore
TEST_ROOT
=
settings
.
COMMON_TEST_DATA_ROOT
LANGUAGES
=
settings
.
ALL_LANGUAGES
...
...
@@ -46,48 +45,6 @@ VIDEO_BUTTONS = {
coursenum
=
'test_course'
sequence
=
{}
@step
(
'when I view the (.*) it does not have autoplay enabled$'
)
def
does_not_autoplay
(
_step
,
video_type
):
assert
(
world
.
css_find
(
'.
%
s'
%
video_type
)[
0
][
'data-autoplay'
]
==
'False'
)
@step
(
'the course has a Video component in (.*) mode(?:
\
:)?$'
)
def
view_video
(
_step
,
player_mode
):
i_am_registered_for_the_course
(
_step
,
coursenum
)
# Make sure we have a video
add_video_to_course
(
coursenum
,
player_mode
.
lower
(),
_step
.
hashes
)
visit_scenario_item
(
'SECTION'
)
@step
(
'a video "([^"]*)" in "([^"]*)" mode in position "([^"]*)" of sequential(?:
\
:)?$'
)
def
add_video
(
_step
,
player_id
,
player_mode
,
position
):
sequence
[
player_id
]
=
position
add_video_to_course
(
coursenum
,
player_mode
.
lower
(),
_step
.
hashes
,
display_name
=
player_id
)
@step
(
'I open the section with videos$'
)
def
visit_video_section
(
_step
):
visit_scenario_item
(
'SECTION'
)
@step
(
'I select the "([^"]*)" speed on video "([^"]*)"$'
)
def
change_video_speed
(
_step
,
speed
,
player_id
):
_navigate_to_an_item_in_a_sequence
(
sequence
[
player_id
])
_change_video_speed
(
speed
)
@step
(
'I open video "([^"]*)"$'
)
def
open_video
(
_step
,
player_id
):
_navigate_to_an_item_in_a_sequence
(
sequence
[
player_id
])
@step
(
'video "([^"]*)" should start playing at speed "([^"]*)"$'
)
def
check_video_speed
(
_step
,
player_id
,
speed
):
speed_css
=
'.speeds p.active'
assert
world
.
css_has_text
(
speed_css
,
'{0}x'
.
format
(
speed
))
def
add_video_to_course
(
course
,
player_mode
,
hashes
,
display_name
=
'Video'
):
category
=
'video'
...
...
@@ -129,16 +86,105 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'):
if
'transcripts'
in
kwargs
[
'metadata'
]:
kwargs
[
'metadata'
][
'transcripts'
]
=
json
.
loads
(
kwargs
[
'metadata'
][
'transcripts'
])
course_location
=
world
.
scenario_dict
[
'COURSE'
]
.
location
if
'sub'
in
kwargs
[
'metadata'
]:
_upload_file
(
kwargs
[
'metadata'
][
'sub'
],
'en'
,
world
.
scenario_dict
[
'COURSE'
]
.
location
)
filename
=
_get_transcript_filename
(
kwargs
[
'metadata'
][
'sub'
],
'en'
)
_upload_file
(
filename
,
course_location
)
for
lang
,
videoId
in
kwargs
[
'metadata'
][
'transcripts'
]
.
items
():
_upload_file
(
videoId
,
lang
,
world
.
scenario_dict
[
'COURSE'
]
.
location
)
filename
=
_get_transcript_filename
(
videoId
,
lang
)
_upload_file
(
filename
,
course_location
)
world
.
scenario_dict
[
'VIDEO'
]
=
world
.
ItemFactory
.
create
(
**
kwargs
)
def
_get_transcript_filename
(
videoId
,
lang
):
if
lang
==
'en'
:
return
'subs_{0}.srt.sjson'
.
format
(
videoId
)
else
:
return
'{0}_subs_{1}.srt.sjson'
.
format
(
lang
,
videoId
)
def
_upload_file
(
filename
,
location
):
path
=
os
.
path
.
join
(
TEST_ROOT
,
'uploads/'
,
filename
)
f
=
open
(
os
.
path
.
abspath
(
path
))
mime_type
=
"application/json"
content_location
=
StaticContent
.
compute_location
(
location
.
org
,
location
.
course
,
filename
)
content
=
StaticContent
(
content_location
,
filename
,
mime_type
,
f
.
read
())
contentstore
()
.
save
(
content
)
del_cached_content
(
content
.
location
)
def
_navigate_to_an_item_in_a_sequence
(
number
):
sequence_css
=
'a[data-element="{0}"]'
.
format
(
number
)
world
.
css_click
(
sequence_css
)
def
_change_video_speed
(
speed
):
world
.
browser
.
execute_script
(
"$('.speeds').addClass('open')"
)
speed_css
=
'li[data-speed="{0}"] a'
.
format
(
speed
)
world
.
css_click
(
speed_css
)
def
_open_menu
(
menu
):
world
.
browser
.
execute_script
(
"$('{selector}').parent().addClass('open')"
.
format
(
selector
=
VIDEO_MENUS
[
menu
]
))
@step
(
'when I view the (.*) it does not have autoplay enabled$'
)
def
does_not_autoplay
(
_step
,
video_type
):
assert
(
world
.
css_find
(
'.
%
s'
%
video_type
)[
0
][
'data-autoplay'
]
==
'False'
)
@step
(
'the course has a Video component in (.*) mode(?:
\
:)?$'
)
def
view_video
(
_step
,
player_mode
):
i_am_registered_for_the_course
(
_step
,
coursenum
)
# Make sure we have a video
add_video_to_course
(
coursenum
,
player_mode
.
lower
(),
_step
.
hashes
)
visit_scenario_item
(
'SECTION'
)
@step
(
'a video in "([^"]*)" mode(?:
\
:)?$'
)
def
add_video
(
_step
,
player_mode
):
add_video_to_course
(
coursenum
,
player_mode
.
lower
(),
_step
.
hashes
)
visit_scenario_item
(
'SECTION'
)
@step
(
'a video "([^"]*)" in "([^"]*)" mode in position "([^"]*)" of sequential(?:
\
:)?$'
)
def
add_video_in_position
(
_step
,
player_id
,
player_mode
,
position
):
sequence
[
player_id
]
=
position
add_video_to_course
(
coursenum
,
player_mode
.
lower
(),
_step
.
hashes
,
display_name
=
player_id
)
@step
(
'I open the section with videos$'
)
def
visit_video_section
(
_step
):
visit_scenario_item
(
'SECTION'
)
@step
(
'I select the "([^"]*)" speed on video "([^"]*)"$'
)
def
change_video_speed
(
_step
,
speed
,
player_id
):
_navigate_to_an_item_in_a_sequence
(
sequence
[
player_id
])
_change_video_speed
(
speed
)
@step
(
'I open video "([^"]*)"$'
)
def
open_video
(
_step
,
player_id
):
_navigate_to_an_item_in_a_sequence
(
sequence
[
player_id
])
@step
(
'video "([^"]*)" should start playing at speed "([^"]*)"$'
)
def
check_video_speed
(
_step
,
player_id
,
speed
):
speed_css
=
'.speeds p.active'
assert
world
.
css_has_text
(
speed_css
,
'{0}x'
.
format
(
speed
))
@step
(
'youtube server is up and response time is (.*) seconds$'
)
def
set_youtube_response_timeout
(
_step
,
time
):
world
.
youtube
.
config
[
'time_to_response'
]
=
float
(
time
)
...
...
@@ -232,47 +278,6 @@ def click_button(_step, button):
world
.
css_find
(
VIDEO_BUTTONS
[
button
])
.
click
()
def
_upload_file
(
videoId
,
lang
,
location
):
if
lang
==
'en'
:
filename
=
'subs_{0}.srt.sjson'
.
format
(
videoId
)
else
:
filename
=
'{0}_subs_{1}.srt.sjson'
.
format
(
lang
,
videoId
)
path
=
os
.
path
.
join
(
TEST_ROOT
,
'uploads/'
,
filename
)
f
=
open
(
os
.
path
.
abspath
(
path
))
mime_type
=
"application/json"
content_location
=
StaticContent
.
compute_location
(
location
.
org
,
location
.
course
,
filename
)
sc_partial
=
partial
(
StaticContent
,
content_location
,
filename
,
mime_type
)
content
=
sc_partial
(
f
.
read
())
(
thumbnail_content
,
thumbnail_location
)
=
contentstore
()
.
generate_thumbnail
(
content
,
tempfile_path
=
None
)
del_cached_content
(
thumbnail_location
)
if
thumbnail_content
is
not
None
:
content
.
thumbnail_location
=
thumbnail_location
contentstore
()
.
save
(
content
)
del_cached_content
(
content
.
location
)
def
_navigate_to_an_item_in_a_sequence
(
number
):
sequence_css
=
'a[data-element="{0}"]'
.
format
(
number
)
world
.
css_click
(
sequence_css
)
def
_change_video_speed
(
speed
):
world
.
browser
.
execute_script
(
"$('.speeds').addClass('open')"
)
speed_css
=
'li[data-speed="{0}"] a'
.
format
(
speed
)
world
.
css_click
(
speed_css
)
@step
(
'I click video button "([^"]*)"$'
)
def
click_button_video
(
_step
,
button_type
):
world
.
wait_for_ajax_complete
()
...
...
@@ -295,7 +300,12 @@ def seek_video_to_n_seconds(_step, seconds):
world
.
browser
.
execute_script
(
jsCode
)
def
_open_menu
(
menu
):
world
.
browser
.
execute_script
(
"$('{selector}').parent().addClass('open')"
.
format
(
selector
=
VIDEO_MENUS
[
menu
]
))
@step
(
'I have a "([^"]*)" transcript file in assets$'
)
def
upload_to_assets
(
_step
,
filename
):
_upload_file
(
filename
,
world
.
scenario_dict
[
'COURSE'
]
.
location
)
@step
(
'button "([^"]*)" is hidden$'
)
def
is_hidden_button
(
_step
,
button
):
assert
not
world
.
css_visible
(
VIDEO_BUTTONS
[
button
])
lms/djangoapps/courseware/tests/test_video_handlers.py
View file @
65adb7b2
...
...
@@ -189,17 +189,22 @@ class TestVideoTranscriptTranslation(TestVideo):
# Tests for `translation` dispatch:
def
test_translation_fails
(
self
):
# No
videoId
request
=
Request
.
blank
(
'/translation
?language=ru
'
)
# No
language
request
=
Request
.
blank
(
'/translation'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'translation'
)
self
.
assertEqual
(
response
.
status
,
'400 Bad Request'
)
# No videoId - HTML5 video with language that is not in available languages
request
=
Request
.
blank
(
'/translation?language=ru'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'translation'
)
self
.
assertEqual
(
response
.
status
,
'404 Not Found'
)
# Language is not in available languages
request
=
Request
.
blank
(
'/translation?language=ru&videoId=12345'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'translation'
)
self
.
assertEqual
(
response
.
status
,
'404 Not Found'
)
def
test_translaton_en_success
(
self
):
def
test_translaton_en_
youtube_
success
(
self
):
subs
=
{
"start"
:
[
10
],
"end"
:
[
100
],
"text"
:
[
"Hi, welcome to Edx."
]}
good_sjson
=
_create_file
(
json
.
dumps
(
subs
))
_upload_sjson_file
(
good_sjson
,
self
.
item_descriptor
.
location
)
...
...
@@ -210,25 +215,7 @@ class TestVideoTranscriptTranslation(TestVideo):
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'translation'
)
self
.
assertDictEqual
(
json
.
loads
(
response
.
body
),
subs
)
def
test_translaton_non_en_non_youtube_success
(
self
):
subs
=
{
u'end'
:
[
100
],
u'start'
:
[
12
],
u'text'
:
[
u'
\u041f\u0440\u0438\u0432\u0456\u0442
, edX
\u0432\u0456\u0442\u0430\u0454
\u0432\u0430\u0441
.'
]
}
self
.
non_en_file
.
seek
(
0
)
_upload_file
(
self
.
non_en_file
,
self
.
item_descriptor
.
location
,
os
.
path
.
split
(
self
.
non_en_file
.
name
)[
1
])
subs_id
=
_get_subs_id
(
self
.
non_en_file
.
name
)
# manually clean youtube_id_1_0, as it has default value
self
.
item
.
youtube_id_1_0
=
""
request
=
Request
.
blank
(
'/translation?language=uk&videoId={}'
.
format
(
subs_id
))
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'translation'
)
self
.
assertDictEqual
(
json
.
loads
(
response
.
body
),
subs
)
def
test_translation_non_en_youtube
(
self
):
def
test_translation_non_en_youtube_success
(
self
):
subs
=
{
u'end'
:
[
100
],
u'start'
:
[
12
],
...
...
@@ -270,6 +257,34 @@ class TestVideoTranscriptTranslation(TestVideo):
}
self
.
assertDictEqual
(
json
.
loads
(
response
.
body
),
calculated_1_5
)
def
test_translaton_en_html5_success
(
self
):
subs
=
{
"start"
:
[
10
],
"end"
:
[
100
],
"text"
:
[
"Hi, welcome to Edx."
]}
good_sjson
=
_create_file
(
json
.
dumps
(
subs
))
_upload_sjson_file
(
good_sjson
,
self
.
item_descriptor
.
location
)
subs_id
=
_get_subs_id
(
good_sjson
.
name
)
self
.
item
.
sub
=
subs_id
request
=
Request
.
blank
(
'/translation?language=en'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'translation'
)
self
.
assertDictEqual
(
json
.
loads
(
response
.
body
),
subs
)
def
test_translaton_non_en_html5_success
(
self
):
subs
=
{
u'end'
:
[
100
],
u'start'
:
[
12
],
u'text'
:
[
u'
\u041f\u0440\u0438\u0432\u0456\u0442
, edX
\u0432\u0456\u0442\u0430\u0454
\u0432\u0430\u0441
.'
]
}
self
.
non_en_file
.
seek
(
0
)
_upload_file
(
self
.
non_en_file
,
self
.
item_descriptor
.
location
,
os
.
path
.
split
(
self
.
non_en_file
.
name
)[
1
])
# manually clean youtube_id_1_0, as it has default value
self
.
item
.
youtube_id_1_0
=
""
request
=
Request
.
blank
(
'/translation?language=uk'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'translation'
)
self
.
assertDictEqual
(
json
.
loads
(
response
.
body
),
subs
)
class
TestVideoTranscriptsDownload
(
TestVideo
):
"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment