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
3a740c04
Commit
3a740c04
authored
Feb 25, 2014
by
jmclaus
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
BLD-844: Add possibility to download transcripts in different formats.
parent
2c9585ea
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
422 additions
and
31 deletions
+422
-31
CHANGELOG.rst
+2
-0
cms/static/sass/elements/_xmodules.scss
+9
-0
common/lib/xmodule/xmodule/css/video/accessible_menu.scss
+133
-0
common/lib/xmodule/xmodule/css/video/display.scss
+13
-4
common/lib/xmodule/xmodule/js/fixtures/video_all.html
+17
-0
common/lib/xmodule/xmodule/js/spec/video/video_accessible_menu_spec.js
+0
-0
common/lib/xmodule/xmodule/js/src/video/035_video_accessible_menu.js
+0
-0
common/lib/xmodule/xmodule/js/src/video/10_main.js
+3
-0
common/lib/xmodule/xmodule/video_module/video_module.py
+51
-14
common/test/data/uploads/subs_OEoXaMPEzfM.srt.sjson
+1
-1
lms/djangoapps/courseware/features/video.feature
+21
-0
lms/djangoapps/courseware/features/video.py
+99
-4
lms/djangoapps/courseware/tests/test_video_handlers.py
+42
-6
lms/djangoapps/courseware/tests/test_video_mongo.py
+8
-1
lms/templates/video.html
+23
-1
No files found.
CHANGELOG.rst
View file @
3a740c04
...
@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
...
@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
the top. Include a label indicating the component affected.
Blades: Add .txt and .srt options to the "download transcript" button. BLD-844.
Blades: Fix bug when transcript cutting off view in full view mode. BLD-852.
Blades: Fix bug when transcript cutting off view in full view mode. BLD-852.
Blades: Show start time or starting position on slider and VCR. BLD-823.
Blades: Show start time or starting position on slider and VCR. BLD-823.
...
...
cms/static/sass/elements/_xmodules.scss
View file @
3a740c04
...
@@ -22,6 +22,15 @@
...
@@ -22,6 +22,15 @@
.video-controls
.add-fullscreen
{
.video-controls
.add-fullscreen
{
display
:
none
!
important
;
// nasty, but needed to override the bad specificity of the xmodule css selectors
display
:
none
!
important
;
// nasty, but needed to override the bad specificity of the xmodule css selectors
}
}
.video-tracks
{
.a11y-menu-container
{
.a11y-menu-list
{
bottom
:
100%
;
top
:
auto
;
}
}
}
}
}
}
}
...
...
common/lib/xmodule/xmodule/css/video/accessible_menu.scss
0 → 100644
View file @
3a740c04
$gray
:
rgb
(
127
,
127
,
127
);
$blue
:
rgb
(
0
,
159
,
230
);
$gray-d1
:
shade
(
$gray
,
20%
);
$gray-l2
:
tint
(
$gray
,
40%
);
$gray-l3
:
tint
(
$gray
,
60%
);
$blue-s1
:
saturate
(
$blue
,
15%
);
%use-font-awesome
{
font-family
:
FontAwesome
;
-webkit-font-smoothing
:
antialiased
;
display
:
inline-block
;
speak
:
none
;
}
.a11y-menu-container
{
position
:
relative
;
&
.open
{
.a11y-menu-list
{
display
:
block
;
}
}
.a11y-menu-list
{
top
:
100%
;
margin
:
0
;
padding
:
0
;
display
:
none
;
position
:
absolute
;
z-index
:
10
;
list-style
:
none
;
background-color
:
$white
;
border
:
1px
solid
#eee
;
li
{
margin
:
0
;
padding
:
0
;
border-bottom
:
1px
solid
#eee
;
color
:
$white
;
cursor
:
pointer
;
a
{
display
:
block
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
color
:
$gray-l2
;
font-size
:
14px
;
line-height
:
23px
;
&
:hover
{
color
:
$gray-d1
;
}
}
&
.active
{
a
{
color
:
$blue
;
}
}
&
:last-child
{
box-shadow
:
none
;
border-bottom
:
0
;
margin-top
:
0
;
}
}
}
}
// Video track button specific styles
.video-tracks
{
.a11y-menu-container
{
display
:
inline-block
;
vertical-align
:
top
;
border-left
:
1px
solid
#eee
;
&
.open
{
>
a
{
background-color
:
$action-primary-active-bg
;
color
:
$very-light-text
;
&
:after
{
color
:
$very-light-text
;
}
}
}
>
a
{
@include
transition
(
all
0
.25s
ease-in-out
0s
);
@include
font-size
(
12
);
display
:
block
;
border-radius
:
0
3px
3px
0
;
background-color
:
$very-light-text
;
padding
:
(
$baseline
*.
75
$baseline
*
1
.25
$baseline
*.
75
$baseline
*.
75
);
color
:
$gray-l2
;
min-width
:
1
.5em
;
line-height
:
14px
;
text-align
:
center
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
&
:after
{
@extend
%use-font-awesome
;
content
:
"\f0d7"
;
position
:
absolute
;
right
:
(
$baseline
*.
5
);
top
:
33%
;
color
:
$lighter-base-font-color
;
}
}
.a11y-menu-list
{
right
:
0
;
li
{
font-size
:
em
(
14
);
a
{
border
:
0
;
display
:
block
;
padding
:
lh
(
.5
);
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
}
}
}
}
common/lib/xmodule/xmodule/css/video/display.scss
View file @
3a740c04
...
@@ -46,13 +46,15 @@ div.video {
...
@@ -46,13 +46,15 @@ div.video {
.video-sources
,
.video-sources
,
.video-tracks
{
.video-tracks
{
display
:
inline-block
;
display
:
inline-block
;
vertical-align
:
top
;
margin
:
(
$baseline
*.
75
)
(
$baseline
/
2
)
0
0
;
margin
:
(
$baseline
*.
75
)
(
$baseline
/
2
)
0
0
;
a
{
>
a
{
@include
transition
(
all
0
.25s
ease-in-out
0s
);
@include
transition
(
all
0
.25s
ease-in-out
0s
);
@include
font-size
(
14
);
@include
font-size
(
14
);
display
:
inline-block
;
line-height
:
14px
;
border-radius
:
3px
3px
3px
3px
;
float
:
left
;
border-radius
:
3px
;
background-color
:
$very-light-text
;
background-color
:
$very-light-text
;
padding
:
(
$baseline
*.
75
);
padding
:
(
$baseline
*.
75
);
color
:
$lighter-base-font-color
;
color
:
$lighter-base-font-color
;
...
@@ -62,7 +64,14 @@ div.video {
...
@@ -62,7 +64,14 @@ div.video {
color
:
$very-light-text
;
color
:
$very-light-text
;
}
}
}
}
}
.video-tracks
{
>
a
{
border-radius
:
3px
0
0
3px
;
}
>
a
.external-track
{
border-radius
:
3px
;
}
}
}
}
}
...
...
common/lib/xmodule/xmodule/js/fixtures/video_all.html
View file @
3a740c04
...
@@ -69,6 +69,23 @@
...
@@ -69,6 +69,23 @@
</div>
</div>
<div
class=
"focus_grabber last"
></div>
<div
class=
"focus_grabber last"
></div>
<ul
class=
"wrapper-downloads"
>
<li
class=
"video-tracks"
>
<div
class=
"a11y-menu-container"
>
<a
class=
"a11y-menu-button"
href=
"#"
title=
".srt"
>
.srt
</a>
<ol
class=
"a11y-menu-list"
>
<li
class=
"a11y-menu-item"
>
<a
class=
"a11y-menu-item-link"
href=
"#txt"
title=
"Text (.txt) file"
data-value=
"txt"
>
Text (.txt) file
</a>
</li>
<li
class=
"a11y-menu-item active"
>
<a
class=
"a11y-menu-item-link"
href=
"#srt"
title=
"SubRip (.srt) file"
data-value=
"srt"
>
SubRip (.srt) file
</a>
</li>
</ol>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
common/lib/xmodule/xmodule/js/spec/video/video_accessible_menu_spec.js
0 → 100644
View file @
3a740c04
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/js/src/video/035_video_accessible_menu.js
0 → 100644
View file @
3a740c04
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/js/src/video/10_main.js
View file @
3a740c04
...
@@ -42,6 +42,7 @@ require(
...
@@ -42,6 +42,7 @@ require(
[
[
'video/01_initialize.js'
,
'video/01_initialize.js'
,
'video/025_focus_grabber.js'
,
'video/025_focus_grabber.js'
,
'video/035_video_accessible_menu.js'
,
'video/04_video_control.js'
,
'video/04_video_control.js'
,
'video/05_video_quality_control.js'
,
'video/05_video_quality_control.js'
,
'video/06_video_progress_slider.js'
,
'video/06_video_progress_slider.js'
,
...
@@ -52,6 +53,7 @@ require(
...
@@ -52,6 +53,7 @@ require(
function
(
function
(
Initialize
,
Initialize
,
FocusGrabber
,
FocusGrabber
,
VideoAccessibleMenu
,
VideoControl
,
VideoControl
,
VideoQualityControl
,
VideoQualityControl
,
VideoProgressSlider
,
VideoProgressSlider
,
...
@@ -87,6 +89,7 @@ function (
...
@@ -87,6 +89,7 @@ function (
state
.
modules
=
[
state
.
modules
=
[
FocusGrabber
,
FocusGrabber
,
VideoAccessibleMenu
,
VideoControl
,
VideoControl
,
VideoQualityControl
,
VideoQualityControl
,
VideoProgressSlider
,
VideoProgressSlider
,
...
...
common/lib/xmodule/xmodule/video_module/video_module.py
View file @
3a740c04
...
@@ -13,6 +13,7 @@ in XML.
...
@@ -13,6 +13,7 @@ in XML.
import
json
import
json
import
logging
import
logging
from
operator
import
itemgetter
from
operator
import
itemgetter
from
HTMLParser
import
HTMLParser
from
lxml
import
etree
from
lxml
import
etree
from
pkg_resources
import
resource_string
from
pkg_resources
import
resource_string
...
@@ -155,6 +156,15 @@ class VideoFields(object):
...
@@ -155,6 +156,15 @@ class VideoFields(object):
scope
=
Scope
.
preferences
,
scope
=
Scope
.
preferences
,
default
=
"en"
default
=
"en"
)
)
transcript_download_format
=
String
(
help
=
"Transcript file format to download by user."
,
scope
=
Scope
.
preferences
,
values
=
[
{
"display_name"
:
"SubRip (.srt) file"
,
"value"
:
"srt"
},
{
"display_name"
:
"Text (.txt) file"
,
"value"
:
"txt"
}
],
default
=
'srt'
,
)
speed
=
Float
(
speed
=
Float
(
help
=
"The last speed that was explicitly set by user for the video."
,
help
=
"The last speed that was explicitly set by user for the video."
,
scope
=
Scope
.
user_state
,
scope
=
Scope
.
user_state
,
...
@@ -193,6 +203,7 @@ class VideoModule(VideoFields, XModule):
...
@@ -193,6 +203,7 @@ class VideoModule(VideoFields, XModule):
resource_string
(
module
,
'js/src/video/025_focus_grabber.js'
),
resource_string
(
module
,
'js/src/video/025_focus_grabber.js'
),
resource_string
(
module
,
'js/src/video/02_html5_video.js'
),
resource_string
(
module
,
'js/src/video/02_html5_video.js'
),
resource_string
(
module
,
'js/src/video/03_video_player.js'
),
resource_string
(
module
,
'js/src/video/03_video_player.js'
),
resource_string
(
module
,
'js/src/video/035_video_accessible_menu.js'
),
resource_string
(
module
,
'js/src/video/04_video_control.js'
),
resource_string
(
module
,
'js/src/video/04_video_control.js'
),
resource_string
(
module
,
'js/src/video/05_video_quality_control.js'
),
resource_string
(
module
,
'js/src/video/05_video_quality_control.js'
),
resource_string
(
module
,
'js/src/video/06_video_progress_slider.js'
),
resource_string
(
module
,
'js/src/video/06_video_progress_slider.js'
),
...
@@ -202,20 +213,33 @@ class VideoModule(VideoFields, XModule):
...
@@ -202,20 +213,33 @@ class VideoModule(VideoFields, XModule):
resource_string
(
module
,
'js/src/video/10_main.js'
)
resource_string
(
module
,
'js/src/video/10_main.js'
)
]
]
}
}
css
=
{
'scss'
:
[
resource_string
(
module
,
'css/video/display.scss'
)]}
css
=
{
'scss'
:
[
resource_string
(
module
,
'css/video/display.scss'
),
resource_string
(
module
,
'css/video/accessible_menu.scss'
),
]}
js_module_name
=
"Video"
js_module_name
=
"Video"
def
handle_ajax
(
self
,
dispatch
,
data
):
def
handle_ajax
(
self
,
dispatch
,
data
):
accepted_keys
=
[
'speed'
,
'saved_video_position'
,
'transcript_language'
]
accepted_keys
=
[
if
dispatch
==
'save_user_state'
:
'speed'
,
'saved_video_position'
,
'transcript_language'
,
'transcript_download_format'
,
]
conversions
=
{
'speed'
:
json
.
loads
,
'saved_video_position'
:
lambda
v
:
RelativeTime
.
isotime_to_timedelta
(
v
),
}
if
dispatch
==
'save_user_state'
:
for
key
in
data
:
for
key
in
data
:
if
hasattr
(
self
,
key
)
and
key
in
accepted_keys
:
if
hasattr
(
self
,
key
)
and
key
in
accepted_keys
:
if
key
==
'saved_video_position'
:
if
key
in
conversions
:
relative_position
=
RelativeTime
.
isotime_to_timedelta
(
data
[
key
])
value
=
conversions
[
key
](
data
[
key
])
self
.
saved_video_position
=
relative_position
else
:
else
:
setattr
(
self
,
key
,
json
.
loads
(
data
[
key
]))
value
=
data
[
key
]
setattr
(
self
,
key
,
value
)
if
key
==
'speed'
:
if
key
==
'speed'
:
self
.
global_speed
=
self
.
speed
self
.
global_speed
=
self
.
speed
...
@@ -228,6 +252,7 @@ class VideoModule(VideoFields, XModule):
...
@@ -228,6 +252,7 @@ class VideoModule(VideoFields, XModule):
def
get_html
(
self
):
def
get_html
(
self
):
track_url
=
None
track_url
=
None
transcript_download_format
=
self
.
transcript_download_format
get_ext
=
lambda
filename
:
filename
.
rpartition
(
'.'
)[
-
1
]
get_ext
=
lambda
filename
:
filename
.
rpartition
(
'.'
)[
-
1
]
sources
=
{
get_ext
(
src
):
src
for
src
in
self
.
html5_sources
}
sources
=
{
get_ext
(
src
):
src
for
src
in
self
.
html5_sources
}
...
@@ -241,7 +266,8 @@ class VideoModule(VideoFields, XModule):
...
@@ -241,7 +266,8 @@ class VideoModule(VideoFields, XModule):
if
self
.
download_track
:
if
self
.
download_track
:
if
self
.
track
:
if
self
.
track
:
track_url
=
self
.
track
track_url
=
self
.
track
elif
self
.
sub
:
transcript_download_format
=
None
elif
self
.
sub
or
self
.
transcripts
:
track_url
=
self
.
runtime
.
handler_url
(
self
,
'transcript'
)
.
rstrip
(
'/?'
)
+
'/download'
track_url
=
self
.
runtime
.
handler_url
(
self
,
'transcript'
)
.
rstrip
(
'/?'
)
+
'/download'
if
not
self
.
transcripts
:
if
not
self
.
transcripts
:
...
@@ -289,13 +315,15 @@ class VideoModule(VideoFields, XModule):
...
@@ -289,13 +315,15 @@ class VideoModule(VideoFields, XModule):
# configuration setting field.
# configuration setting field.
'yt_test_timeout'
:
1500
,
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
settings
.
YOUTUBE_TEST_URL
,
'yt_test_url'
:
settings
.
YOUTUBE_TEST_URL
,
'transcript_download_format'
:
transcript_download_format
,
'transcript_download_formats_list'
:
self
.
descriptor
.
fields
[
'transcript_download_format'
]
.
values
,
'transcript_language'
:
transcript_language
,
'transcript_language'
:
transcript_language
,
'transcript_languages'
:
json
.
dumps
(
sorted_languages
),
'transcript_languages'
:
json
.
dumps
(
sorted_languages
),
'transcript_translation_url'
:
self
.
runtime
.
handler_url
(
self
,
'transcript'
)
.
rstrip
(
'/?'
)
+
'/translation'
,
'transcript_translation_url'
:
self
.
runtime
.
handler_url
(
self
,
'transcript'
)
.
rstrip
(
'/?'
)
+
'/translation'
,
'transcript_available_translations_url'
:
self
.
runtime
.
handler_url
(
self
,
'transcript'
)
.
rstrip
(
'/?'
)
+
'/available_translations'
,
'transcript_available_translations_url'
:
self
.
runtime
.
handler_url
(
self
,
'transcript'
)
.
rstrip
(
'/?'
)
+
'/available_translations'
,
})
})
def
get_transcript
(
self
):
def
get_transcript
(
self
,
format
=
'srt'
):
"""
"""
Returns transcript in *.srt format.
Returns transcript in *.srt format.
...
@@ -307,12 +335,18 @@ class VideoModule(VideoFields, XModule):
...
@@ -307,12 +335,18 @@ class VideoModule(VideoFields, XModule):
lang
=
self
.
transcript_language
lang
=
self
.
transcript_language
subs_id
=
self
.
sub
if
lang
==
'en'
else
self
.
youtube_id_1_0
subs_id
=
self
.
sub
if
lang
==
'en'
else
self
.
youtube_id_1_0
data
=
asset
(
self
.
location
,
subs_id
,
lang
)
.
data
data
=
asset
(
self
.
location
,
subs_id
,
lang
)
.
data
str_subs
=
generate_srt_from_sjson
(
json
.
loads
(
data
),
speed
=
1.0
)
if
format
==
'txt'
:
text
=
json
.
loads
(
data
)[
'text'
]
str_subs
=
HTMLParser
()
.
unescape
(
"
\n
"
.
join
(
text
))
mime_type
=
'text/plain'
else
:
str_subs
=
generate_srt_from_sjson
(
json
.
loads
(
data
),
speed
=
1.0
)
mime_type
=
'application/x-subrip'
if
not
str_subs
:
if
not
str_subs
:
log
.
debug
(
'generate_srt_from_sjson produces no subtitles'
)
log
.
debug
(
'generate_srt_from_sjson produces no subtitles'
)
raise
ValueError
raise
ValueError
return
str_subs
return
str_subs
,
format
,
mime_type
@XBlock.handler
@XBlock.handler
def
transcript
(
self
,
request
,
dispatch
):
def
transcript
(
self
,
request
,
dispatch
):
...
@@ -350,7 +384,7 @@ class VideoModule(VideoFields, XModule):
...
@@ -350,7 +384,7 @@ class VideoModule(VideoFields, XModule):
elif
dispatch
==
'download'
:
elif
dispatch
==
'download'
:
try
:
try
:
subs
=
self
.
get_transcript
(
)
subs
,
format
,
mime_type
=
self
.
get_transcript
(
format
=
self
.
transcript_download_format
)
except
(
NotFoundError
,
ValueError
,
KeyError
):
except
(
NotFoundError
,
ValueError
,
KeyError
):
log
.
debug
(
"Video@download exception"
)
log
.
debug
(
"Video@download exception"
)
response
=
Response
(
status
=
404
)
response
=
Response
(
status
=
404
)
...
@@ -358,10 +392,13 @@ class VideoModule(VideoFields, XModule):
...
@@ -358,10 +392,13 @@ class VideoModule(VideoFields, XModule):
response
=
Response
(
response
=
Response
(
subs
,
subs
,
headerlist
=
[
headerlist
=
[
(
'Content-Disposition'
,
'attachment; filename="{0}.srt"'
.
format
(
self
.
transcript_language
)),
(
'Content-Disposition'
,
'attachment; filename="{filename}.{format}"'
.
format
(
filename
=
self
.
transcript_language
,
format
=
format
,
)),
]
]
)
)
response
.
content_type
=
"application/x-subrip"
response
.
content_type
=
mime_type
elif
dispatch
==
'available_translations'
:
elif
dispatch
==
'available_translations'
:
available_translations
=
[]
available_translations
=
[]
...
...
common/test/data/uploads/subs_OEoXaMPEzfM.srt.sjson
View file @
3a740c04
...
@@ -94,7 +94,7 @@
...
@@ -94,7 +94,7 @@
114220
114220
],
],
"text": [
"text": [
"
LILA FISHER:
Hi, welcome to Edx.",
"Hi, welcome to Edx.",
"I'm Lila Fisher, an Edx fellow helping to put",
"I'm Lila Fisher, an Edx fellow helping to put",
"together these courses.",
"together these courses.",
"As you know, our courses are entirely online.",
"As you know, our courses are entirely online.",
...
...
lms/djangoapps/courseware/features/video.feature
View file @
3a740c04
...
@@ -155,3 +155,24 @@ Feature: LMS Video component
...
@@ -155,3 +155,24 @@ Feature: LMS Video component
Then
I see video aligned correctly with enabled transcript
Then
I see video aligned correctly with enabled transcript
And
I click video button
"CC"
And
I click video button
"CC"
Then
I see video aligned correctly without enabled transcript
Then
I see video aligned correctly without enabled transcript
# 19
Scenario
:
Download Transcript button works correctly in Video component
Given
I am registered for the course
"test_course"
And it has a video "A" in "Youtube" mode in position "1" of sequential
:
|
sub
|
download_track
|
|
OEoXaMPEzfM
|
true
|
And a video "B" in "Youtube" mode in position "2" of sequential
:
|
sub
|
download_track
|
|
OEoXaMPEzfM
|
true
|
And a video "C" in "Youtube" mode in position "3" of sequential
:
|
track
|
download_track
|
|
http://example.org/
|
true
|
And
I open the section with videos
And
I can download transcript in
"srt"
format
And
I select the transcript format
"txt"
And
I can download transcript in
"txt"
format
When
I open video
"B"
Then
I can download transcript in
"txt"
format
When
I open video
"C"
Then
menu
"download_transcript"
doesn't exist
lms/djangoapps/courseware/features/video.py
View file @
3a740c04
...
@@ -3,12 +3,13 @@
...
@@ -3,12 +3,13 @@
from
lettuce
import
world
,
step
from
lettuce
import
world
,
step
import
json
import
json
import
os
import
requests
from
common
import
i_am_registered_for_the_course
,
section_location
,
visit_scenario_item
from
common
import
i_am_registered_for_the_course
,
section_location
,
visit_scenario_item
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
django.conf
import
settings
from
django.conf
import
settings
from
cache_toolbox.core
import
del_cached_content
from
cache_toolbox.core
import
del_cached_content
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.contentstore.content
import
StaticContent
import
os
from
xmodule.contentstore.django
import
contentstore
from
xmodule.contentstore.django
import
contentstore
TEST_ROOT
=
settings
.
COMMON_TEST_DATA_ROOT
TEST_ROOT
=
settings
.
COMMON_TEST_DATA_ROOT
LANGUAGES
=
settings
.
ALL_LANGUAGES
LANGUAGES
=
settings
.
ALL_LANGUAGES
...
@@ -32,17 +33,57 @@ VIDEO_BUTTONS = {
...
@@ -32,17 +33,57 @@ VIDEO_BUTTONS = {
'play'
:
'.video_control.play'
,
'play'
:
'.video_control.play'
,
'pause'
:
'.video_control.pause'
,
'pause'
:
'.video_control.pause'
,
'fullscreen'
:
'.add-fullscreen'
,
'fullscreen'
:
'.add-fullscreen'
,
'download_transcript'
:
'.video-tracks > a'
,
}
}
VIDEO_MENUS
=
{
VIDEO_MENUS
=
{
'language'
:
'.lang .menu'
,
'language'
:
'.lang .menu'
,
'speed'
:
'.speed .menu'
,
'speed'
:
'.speed .menu'
,
'download_transcript'
:
'.video-tracks .a11y-menu-list'
,
}
}
coursenum
=
'test_course'
coursenum
=
'test_course'
sequence
=
{}
sequence
=
{}
class
ReuqestHandlerWithSessionId
(
object
):
def
get
(
self
,
url
):
"""
Sends a request.
"""
kwargs
=
dict
()
session_id
=
[{
i
[
'name'
]:
i
[
'value'
]}
for
i
in
world
.
browser
.
cookies
.
all
()
if
i
[
'name'
]
==
u'sessionid'
]
if
session_id
:
kwargs
.
update
({
'cookies'
:
session_id
[
0
]
})
response
=
requests
.
get
(
url
,
**
kwargs
)
self
.
response
=
response
self
.
status_code
=
response
.
status_code
self
.
headers
=
response
.
headers
self
.
content
=
response
.
content
return
self
def
is_success
(
self
):
"""
Returns `True` if the response was succeed, otherwise, returns `False`.
"""
if
self
.
status_code
<
400
:
return
True
return
False
def
check_header
(
self
,
name
,
value
):
"""
Returns `True` if the response header exist and has appropriate value,
otherwise, returns `False`.
"""
if
value
in
self
.
headers
.
get
(
name
,
''
):
return
True
return
False
def
add_video_to_course
(
course
,
player_mode
,
hashes
,
display_name
=
'Video'
):
def
add_video_to_course
(
course
,
player_mode
,
hashes
,
display_name
=
'Video'
):
category
=
'video'
category
=
'video'
...
@@ -80,15 +121,23 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'):
...
@@ -80,15 +121,23 @@ def add_video_to_course(course, player_mode, hashes, display_name='Video'):
if
hashes
:
if
hashes
:
kwargs
[
'metadata'
]
.
update
(
hashes
[
0
])
kwargs
[
'metadata'
]
.
update
(
hashes
[
0
])
course_location
=
world
.
scenario_dict
[
'COURSE'
]
.
location
course_location
=
world
.
scenario_dict
[
'COURSE'
]
.
location
conversions
=
{
'transcripts'
:
json
.
loads
,
'download_track'
:
json
.
loads
,
'download_video'
:
json
.
loads
,
}
for
key
in
kwargs
[
'metadata'
]:
if
key
in
conversions
:
kwargs
[
'metadata'
][
key
]
=
conversions
[
key
](
kwargs
[
'metadata'
][
key
])
if
'sub'
in
kwargs
[
'metadata'
]:
if
'sub'
in
kwargs
[
'metadata'
]:
filename
=
_get_sjson_filename
(
kwargs
[
'metadata'
][
'sub'
],
'en'
)
filename
=
_get_sjson_filename
(
kwargs
[
'metadata'
][
'sub'
],
'en'
)
_upload_file
(
filename
,
course_location
)
_upload_file
(
filename
,
course_location
)
if
'transcripts'
in
kwargs
[
'metadata'
]:
if
'transcripts'
in
kwargs
[
'metadata'
]:
kwargs
[
'metadata'
][
'transcripts'
]
=
json
.
loads
(
kwargs
[
'metadata'
][
'transcripts'
])
for
lang
,
filename
in
kwargs
[
'metadata'
][
'transcripts'
]
.
items
():
for
lang
,
filename
in
kwargs
[
'metadata'
][
'transcripts'
]
.
items
():
_upload_file
(
filename
,
course_location
)
_upload_file
(
filename
,
course_location
)
...
@@ -322,6 +371,10 @@ def upload_to_assets(_step, filename):
...
@@ -322,6 +371,10 @@ def upload_to_assets(_step, filename):
def
is_hidden_button
(
_step
,
button
):
def
is_hidden_button
(
_step
,
button
):
assert
not
world
.
css_visible
(
VIDEO_BUTTONS
[
button
])
assert
not
world
.
css_visible
(
VIDEO_BUTTONS
[
button
])
@step
(
'menu "([^"]*)" doesn
\'
t exist$'
)
def
is_hidden_menu
(
_step
,
menu
):
assert
world
.
is_css_not_present
(
VIDEO_MENUS
[
menu
])
@step
(
'I see video aligned correctly (with(?:out)?) enabled transcript$'
)
@step
(
'I see video aligned correctly (with(?:out)?) enabled transcript$'
)
def
video_alignment
(
_step
,
transcript_visibility
):
def
video_alignment
(
_step
,
transcript_visibility
):
...
@@ -345,3 +398,45 @@ def video_alignment(_step, transcript_visibility):
...
@@ -345,3 +398,45 @@ def video_alignment(_step, transcript_visibility):
)
)
assert
all
([
width
,
height
])
assert
all
([
width
,
height
])
@step
(
'I can download transcript in "([^"]*)" format$'
)
def
i_can_download_transcript
(
_step
,
format
):
button
=
world
.
css_find
(
'.video-tracks .a11y-menu-button'
)
.
first
assert
button
.
text
.
strip
()
==
'.'
+
format
formats
=
{
'srt'
:
{
'content'
:
'0
\n
00:00:00,270'
,
'mime_type'
:
'application/x-subrip'
},
'txt'
:
{
'content'
:
'Hi, welcome to Edx.'
,
'mime_type'
:
'text/plain'
},
}
url
=
world
.
css_find
(
VIDEO_BUTTONS
[
'download_transcript'
])[
0
][
'href'
]
request
=
ReuqestHandlerWithSessionId
()
assert
request
.
get
(
url
)
.
is_success
()
assert
request
.
check_header
(
'content-type'
,
formats
[
format
][
'mime_type'
])
assert
request
.
content
.
startswith
(
formats
[
format
][
'content'
])
@step
(
'I select the transcript format "([^"]*)"$'
)
def
select_transcript_format
(
_step
,
format
):
button
=
world
.
css_find
(
'.video-tracks .a11y-menu-button'
)
.
first
button
.
mouse_over
()
assert
button
.
text
.
strip
()
==
'...'
menu_selector
=
VIDEO_MENUS
[
'download_transcript'
]
menu_items
=
world
.
css_find
(
menu_selector
+
' a'
)
for
item
in
menu_items
:
if
item
[
'data-value'
]
==
format
:
item
.
click
()
world
.
wait_for_ajax_complete
()
break
assert
world
.
css_find
(
menu_selector
+
' .active a'
)[
0
][
'data-value'
]
==
format
assert
button
.
text
.
strip
()
==
'.'
+
format
lms/djangoapps/courseware/tests/test_video_handlers.py
View file @
3a740c04
...
@@ -110,7 +110,7 @@ class TestVideo(BaseTestXmodule):
...
@@ -110,7 +110,7 @@ class TestVideo(BaseTestXmodule):
data
=
[
data
=
[
{
'speed'
:
2.0
},
{
'speed'
:
2.0
},
{
'saved_video_position'
:
"00:00:10"
},
{
'saved_video_position'
:
"00:00:10"
},
{
'transcript_language'
:
json
.
dumps
(
'uk'
)
},
{
'transcript_language'
:
'uk'
},
]
]
for
sample
in
data
:
for
sample
in
data
:
response
=
self
.
clients
[
self
.
users
[
0
]
.
username
]
.
post
(
response
=
self
.
clients
[
self
.
users
[
0
]
.
username
]
.
post
(
...
@@ -129,7 +129,7 @@ class TestVideo(BaseTestXmodule):
...
@@ -129,7 +129,7 @@ class TestVideo(BaseTestXmodule):
self
.
assertEqual
(
self
.
item_descriptor
.
saved_video_position
,
timedelta
(
0
,
10
))
self
.
assertEqual
(
self
.
item_descriptor
.
saved_video_position
,
timedelta
(
0
,
10
))
self
.
assertEqual
(
self
.
item_descriptor
.
transcript_language
,
'en'
)
self
.
assertEqual
(
self
.
item_descriptor
.
transcript_language
,
'en'
)
self
.
item_descriptor
.
handle_ajax
(
'save_user_state'
,
{
'transcript_language'
:
json
.
dumps
(
"uk"
)
})
self
.
item_descriptor
.
handle_ajax
(
'save_user_state'
,
{
'transcript_language'
:
"uk"
})
self
.
assertEqual
(
self
.
item_descriptor
.
transcript_language
,
'uk'
)
self
.
assertEqual
(
self
.
item_descriptor
.
transcript_language
,
'uk'
)
def
tearDown
(
self
):
def
tearDown
(
self
):
...
@@ -173,11 +173,20 @@ class TestVideoTranscriptTranslation(TestVideo):
...
@@ -173,11 +173,20 @@ class TestVideoTranscriptTranslation(TestVideo):
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'download'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'download'
)
self
.
assertEqual
(
response
.
status
,
'404 Not Found'
)
self
.
assertEqual
(
response
.
status
,
'404 Not Found'
)
@patch
(
'xmodule.video_module.VideoModule.get_transcript'
,
return_value
=
'Subs!'
)
@patch
(
'xmodule.video_module.VideoModule.get_transcript'
,
return_value
=
(
'Subs!'
,
'srt'
,
'application/x-subrip'
)
)
def
test_download_exist
(
self
,
__
):
def
test_download_
srt_
exist
(
self
,
__
):
request
=
Request
.
blank
(
'/download?language=en'
)
request
=
Request
.
blank
(
'/download?language=en'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'download'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'download'
)
self
.
assertEqual
(
response
.
body
,
'Subs!'
)
self
.
assertEqual
(
response
.
body
,
'Subs!'
)
self
.
assertEqual
(
response
.
headers
[
'Content-Type'
],
'application/x-subrip'
)
@patch
(
'xmodule.video_module.VideoModule.get_transcript'
,
return_value
=
(
'Subs!'
,
'txt'
,
'text/plain'
))
def
test_download_txt_exist
(
self
,
__
):
self
.
item
.
transcript_format
=
'txt'
request
=
Request
.
blank
(
'/download?language=en'
)
response
=
self
.
item
.
transcript
(
request
=
request
,
dispatch
=
'download'
)
self
.
assertEqual
(
response
.
body
,
'Subs!'
)
self
.
assertEqual
(
response
.
headers
[
'Content-Type'
],
'text/plain'
)
def
test_download_en_no_sub
(
self
):
def
test_download_en_no_sub
(
self
):
request
=
Request
.
blank
(
'/download?language=en'
)
request
=
Request
.
blank
(
'/download?language=en'
)
...
@@ -309,7 +318,7 @@ class TestVideoTranscriptsDownload(TestVideo):
...
@@ -309,7 +318,7 @@ class TestVideoTranscriptsDownload(TestVideo):
self
.
item_descriptor
.
render
(
'student_view'
)
self
.
item_descriptor
.
render
(
'student_view'
)
self
.
item
=
self
.
item_descriptor
.
xmodule_runtime
.
xmodule_instance
self
.
item
=
self
.
item_descriptor
.
xmodule_runtime
.
xmodule_instance
def
test_good_transcript
(
self
):
def
test_good_
srt_
transcript
(
self
):
good_sjson
=
_create_file
(
content
=
textwrap
.
dedent
(
"""
\
good_sjson
=
_create_file
(
content
=
textwrap
.
dedent
(
"""
\
{
{
"start": [
"start": [
...
@@ -329,7 +338,7 @@ class TestVideoTranscriptsDownload(TestVideo):
...
@@ -329,7 +338,7 @@ class TestVideoTranscriptsDownload(TestVideo):
_upload_sjson_file
(
good_sjson
,
self
.
item
.
location
)
_upload_sjson_file
(
good_sjson
,
self
.
item
.
location
)
self
.
item
.
sub
=
_get_subs_id
(
good_sjson
.
name
)
self
.
item
.
sub
=
_get_subs_id
(
good_sjson
.
name
)
text
=
self
.
item
.
get_transcript
()
text
,
format
,
download
=
self
.
item
.
get_transcript
()
expected_text
=
textwrap
.
dedent
(
"""
\
expected_text
=
textwrap
.
dedent
(
"""
\
0
0
00:00:00,270 --> 00:00:02,720
00:00:00,270 --> 00:00:02,720
...
@@ -343,6 +352,33 @@ class TestVideoTranscriptsDownload(TestVideo):
...
@@ -343,6 +352,33 @@ class TestVideoTranscriptsDownload(TestVideo):
self
.
assertEqual
(
text
,
expected_text
)
self
.
assertEqual
(
text
,
expected_text
)
def
test_good_txt_transcript
(
self
):
good_sjson
=
_create_file
(
content
=
textwrap
.
dedent
(
"""
\
{
"start": [
270,
2720
],
"end": [
2720,
5430
],
"text": [
"Hi, welcome to Edx.",
"Let's start with what is on your screen right now."
]
}
"""
))
_upload_sjson_file
(
good_sjson
,
self
.
item
.
location
)
self
.
item
.
sub
=
_get_subs_id
(
good_sjson
.
name
)
text
,
format
,
mime_type
=
self
.
item
.
get_transcript
(
format
=
"txt"
)
expected_text
=
textwrap
.
dedent
(
"""
\
Hi, welcome to Edx.
Let's start with what is on your screen right now."""
)
self
.
assertEqual
(
text
,
expected_text
)
def
test_not_found_error
(
self
):
def
test_not_found_error
(
self
):
with
self
.
assertRaises
(
NotFoundError
):
with
self
.
assertRaises
(
NotFoundError
):
self
.
item
.
get_transcript
()
self
.
item
.
get_transcript
()
...
...
lms/djangoapps/courseware/tests/test_video_mongo.py
View file @
3a740c04
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
"""Video xmodule tests in mongo."""
"""Video xmodule tests in mongo."""
from
mock
import
patch
,
PropertyMock
from
mock
import
patch
,
PropertyMock
import
json
from
.
import
BaseTestXmodule
from
.
import
BaseTestXmodule
from
.test_video_xml
import
SOURCE_XML
from
.test_video_xml
import
SOURCE_XML
...
@@ -41,6 +40,8 @@ class TestVideoYouTube(TestVideo):
...
@@ -41,6 +40,8 @@ class TestVideoYouTube(TestVideo):
'youtube_streams'
:
create_youtube_string
(
self
.
item_descriptor
),
'youtube_streams'
:
create_youtube_string
(
self
.
item_descriptor
),
'yt_test_timeout'
:
1500
,
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
,
'transcript_download_format'
:
'srt'
,
'transcript_download_formats_list'
:
[{
'display_name'
:
'SubRip (.srt) file'
,
'value'
:
'srt'
},
{
'display_name'
:
'Text (.txt) file'
,
'value'
:
'txt'
}],
'transcript_language'
:
'en'
,
'transcript_language'
:
'en'
,
'transcript_languages'
:
'{"en": "English", "uk": "Ukrainian"}'
,
'transcript_languages'
:
'{"en": "English", "uk": "Ukrainian"}'
,
'transcript_translation_url'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcript_translation_url'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
...
@@ -103,6 +104,8 @@ class TestVideoNonYouTube(TestVideo):
...
@@ -103,6 +104,8 @@ class TestVideoNonYouTube(TestVideo):
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'yt_test_timeout'
:
1500
,
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
,
'transcript_download_format'
:
'srt'
,
'transcript_download_formats_list'
:
[{
'display_name'
:
'SubRip (.srt) file'
,
'value'
:
'srt'
},
{
'display_name'
:
'Text (.txt) file'
,
'value'
:
'txt'
}],
'transcript_language'
:
'en'
,
'transcript_language'
:
'en'
,
'transcript_languages'
:
'{"en": "English"}'
,
'transcript_languages'
:
'{"en": "English"}'
,
'transcript_translation_url'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcript_translation_url'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
...
@@ -191,6 +194,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -191,6 +194,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'yt_test_timeout'
:
1500
,
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
,
'transcript_download_formats_list'
:
[{
'display_name'
:
'SubRip (.srt) file'
,
'value'
:
'srt'
},
{
'display_name'
:
'Text (.txt) file'
,
'value'
:
'txt'
}],
}
}
for
data
in
cases
:
for
data
in
cases
:
...
@@ -208,6 +212,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -208,6 +212,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
context
=
self
.
item_descriptor
.
render
(
'student_view'
)
.
content
context
=
self
.
item_descriptor
.
render
(
'student_view'
)
.
content
expected_context
.
update
({
expected_context
.
update
({
'transcript_download_format'
:
None
if
self
.
item_descriptor
.
track
and
self
.
item_descriptor
.
download_track
else
'srt'
,
'transcript_languages'
:
'{"en": "English"}'
,
'transcript_languages'
:
'{"en": "English"}'
,
'transcript_language'
:
'en'
,
'transcript_language'
:
'en'
,
'transcript_translation_url'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcript_translation_url'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
...
@@ -305,6 +310,8 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -305,6 +310,8 @@ class TestGetHtmlMethod(BaseTestXmodule):
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
True
),
'yt_test_timeout'
:
1500
,
'yt_test_timeout'
:
1500
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
,
'yt_test_url'
:
'https://gdata.youtube.com/feeds/api/videos/'
,
'transcript_download_format'
:
'srt'
,
'transcript_download_formats_list'
:
[{
'display_name'
:
'SubRip (.srt) file'
,
'value'
:
'srt'
},
{
'display_name'
:
'Text (.txt) file'
,
'value'
:
'txt'
}],
'transcript_language'
:
'en'
,
'transcript_language'
:
'en'
,
'transcript_languages'
:
'{"en": "English"}'
,
'transcript_languages'
:
'{"en": "English"}'
,
}
}
...
...
lms/templates/video.html
View file @
3a740c04
...
@@ -112,7 +112,29 @@
...
@@ -112,7 +112,29 @@
% endif
% endif
% if track:
% if track:
<li
class=
"video-tracks"
>
<li
class=
"video-tracks"
>
${('
<a
href=
"%s"
>
' + _('Download timed transcript') + '
</a>
') % track}
% if transcript_download_format:
${('
<a
href=
"%s"
>
' + _('Download transcript') + '
</a>
') % track
}
<div
class=
"a11y-menu-container"
>
<a
class=
"a11y-menu-button"
href=
"#"
title=
"${'.' + transcript_download_format}"
>
${'.' + transcript_download_format}
</a>
<ol
class=
"a11y-menu-list"
>
% for item in transcript_download_formats_list:
% if item['value'] == transcript_download_format:
<li
class=
"a11y-menu-item active"
>
% else:
<li
class=
"a11y-menu-item"
>
% endif
<a
class=
"a11y-menu-item-link"
href=
"#${item['value']}"
title=
"${_('{file_format}'.format(file_format=item['display_name']))}"
data-value=
"${item['value']}"
>
${_('{file_format}'.format(file_format=item['display_name']))}
</a>
</li>
% endfor
</ol>
</div>
% else:
${('
<a
href=
"%s"
class=
"external-track"
>
' + _('Download transcript') + '
</a>
') % track
}
% endif
</li>
</li>
% endif
% endif
</ul>
</ul>
...
...
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