diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-11-09 16:27:49 +0100 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-11-09 16:27:49 +0100 |
| commit | 0cffae759496a086eabe7459a8cfd066760f474e (patch) | |
| tree | 6b2d9a89664670b9b57feff55791618c1b865acc /animism-align/frontend/app | |
| parent | 5c669a020e6db1c8095161941d3a62d5564eacd1 (diff) | |
video subtitles in SRT format
Diffstat (limited to 'animism-align/frontend/app')
8 files changed, 103 insertions, 7 deletions
diff --git a/animism-align/frontend/app/views/media/components/media.formVideo.js b/animism-align/frontend/app/views/media/components/media.formVideo.js index d85fc4d..35cee2d 100644 --- a/animism-align/frontend/app/views/media/components/media.formVideo.js +++ b/animism-align/frontend/app/views/media/components/media.formVideo.js @@ -106,8 +106,8 @@ export default class MediaVideoForm extends Component { title="Subtitles" name="subtitles" required - data={data} - onChange={this.handleChange} + data={data.settings} + onChange={this.handleSettingsChange} autoComplete="off" /> </div> diff --git a/animism-align/frontend/app/views/viewer/player/components.fullscreen/fullscreen.video.js b/animism-align/frontend/app/views/viewer/player/components.fullscreen/fullscreen.video.js index 84cf59e..252a278 100644 --- a/animism-align/frontend/app/views/viewer/player/components.fullscreen/fullscreen.video.js +++ b/animism-align/frontend/app/views/viewer/player/components.fullscreen/fullscreen.video.js @@ -4,7 +4,7 @@ import VimeoPlayer from 'app/utils/vendor/vimeo' import actions from 'app/actions' import { timestampToSeconds } from 'app/utils' -import { VideoScrubber } from '../components.media' +import { VideoScrubber, VideoSubtitles } from '../components.media' class FullscreenVideo extends Component { state = { @@ -127,6 +127,10 @@ class FullscreenVideo extends Component { timing={this.state} onScrub={this.handleScrub} /> + <VideoSubtitles + play_ts={seconds} + mediaItem={item} + /> </div> ) } diff --git a/animism-align/frontend/app/views/viewer/player/components.media/index.js b/animism-align/frontend/app/views/viewer/player/components.media/index.js index 04b7f03..742d719 100644 --- a/animism-align/frontend/app/views/viewer/player/components.media/index.js +++ b/animism-align/frontend/app/views/viewer/player/components.media/index.js @@ -14,6 +14,7 @@ import { import Carousel from './media.carousel' import VideoScrubber from './video.scrubber' +import VideoSubtitles from './video.subtitles' import { Grid @@ -26,4 +27,5 @@ export { Carousel, Grid, VideoScrubber, + VideoSubtitles, }
\ No newline at end of file diff --git a/animism-align/frontend/app/views/viewer/player/components.media/media.css b/animism-align/frontend/app/views/viewer/player/components.media/media.css index fea25ec..72eec07 100644 --- a/animism-align/frontend/app/views/viewer/player/components.media/media.css +++ b/animism-align/frontend/app/views/viewer/player/components.media/media.css @@ -424,3 +424,32 @@ opacity: 1.0; } +/* subtitles */ + +.video-subtitles { + position: absolute; + bottom: 3rem; + height: 10rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + transform: translateZ(0) translateY(0); + transition: transform 0.2s; +} +.video-scrubber.show ~ .video-subtitles { + transform: translateZ(0) translateY(-3rem); +} +.video-subtitles span { + display: inline-block; + box-shadow: -3px -2px 0 #000, + 3px -2px 0 #000, + -3px 2px 0 #000, + 3px 2px 0 #000; + box-decoration-break: clone; + background: black; + color: white; + font-size: 1.25rem; + margin-bottom: 0.125rem; + padding: 0 0.125rem; +} diff --git a/animism-align/frontend/app/views/viewer/player/components.media/video.scrubber.js b/animism-align/frontend/app/views/viewer/player/components.media/video.scrubber.js index e281559..f866628 100644 --- a/animism-align/frontend/app/views/viewer/player/components.media/video.scrubber.js +++ b/animism-align/frontend/app/views/viewer/player/components.media/video.scrubber.js @@ -60,7 +60,7 @@ class VideoScrubber extends Component { // in case the video loops, modulo the length of the original video const video_seek = ((seconds + video_start_ts) % timing.duration) // - console.log(start_ts, seconds) + // console.log(start_ts, seconds) onScrub({ seek: video_seek, seconds: seconds, @@ -73,7 +73,7 @@ class VideoScrubber extends Component { } handleMouseMove(e) { e.stopPropagation() - console.log('move', this.defer) + // console.log('move', this.defer) if (this.defer) { this.defer = false this.show() diff --git a/animism-align/frontend/app/views/viewer/player/components.media/video.subtitles.js b/animism-align/frontend/app/views/viewer/player/components.media/video.subtitles.js new file mode 100644 index 0000000..dfa7a85 --- /dev/null +++ b/animism-align/frontend/app/views/viewer/player/components.media/video.subtitles.js @@ -0,0 +1,61 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' + +import actions from 'app/actions' +import { timestampToSeconds, floatInRange } from 'app/utils' + +const REGEXP_ALL_COMMAS = new RegExp(',', 'g') + +export default class VideoSubtitles extends Component { + state = { + subtitles: [], + current: null, + } + componentDidMount() { + this.loadSubtitles() + } + componentDidUpdate(prevProps) { + if (this.props.play_ts !== prevProps.play_ts) { + this.updateCurrentSubtitle() + } + } + loadSubtitles() { + if (!this.props.mediaItem || !this.props.mediaItem.settings.subtitles) return; + const groups = this.props.mediaItem.settings.subtitles.split("\n\n") + const subtitles = groups.map((group) => { + if (!group) return + const lines = group.trim().split("\n") + if (!lines.length || !parseInt(lines[0])) { + return null + } + let ts_parts = lines[1].replace(REGEXP_ALL_COMMAS, '.').split(" --> ").map(timestampToSeconds) + return { + id: parseInt(lines[0]), + start_ts: ts_parts[0], + end_ts: ts_parts[1], + lines: lines.slice(2), + } + }).filter(a => !!a) + this.setState({ subtitles, current: null }) + } + updateCurrentSubtitle() { + const { play_ts } = this.props + const current = this.state.subtitles.filter(({ start_ts, end_ts }) => ( + floatInRange(start_ts, play_ts, end_ts ) + )).slice(-1) + if (!current.length) { + this.setState({ current: null }) + } else { + this.setState({ current: current[0] }) + } + } + render() { + const { current } = this.state + if (!current) return <div className="video-subtitles hidden" /> + return ( + <div className="video-subtitles"> + {current.lines.map(line => <span key={line}>{line}</span>)} + </div> + ) + } +} diff --git a/animism-align/frontend/app/views/viewer/transcript/components/elementTypes.gallery.js b/animism-align/frontend/app/views/viewer/transcript/components/elementTypes.gallery.js index 30a8fa5..effd8a7 100644 --- a/animism-align/frontend/app/views/viewer/transcript/components/elementTypes.gallery.js +++ b/animism-align/frontend/app/views/viewer/transcript/components/elementTypes.gallery.js @@ -10,7 +10,7 @@ export const MediaGallery = ({ paragraph, media, currentParagraph, currentAnnota return <div /> } const item = media.lookup[annotation.settings.media_id] - console.log(item) + // console.log(item) if (!item) return <div>Media not found: {annotation.settings.media_id}</div> return ( <div diff --git a/animism-align/frontend/app/views/viewer/transcript/components/elementTypes.image.js b/animism-align/frontend/app/views/viewer/transcript/components/elementTypes.image.js index b1b846f..6e24c9a 100644 --- a/animism-align/frontend/app/views/viewer/transcript/components/elementTypes.image.js +++ b/animism-align/frontend/app/views/viewer/transcript/components/elementTypes.image.js @@ -36,7 +36,7 @@ export const MediaImage = ({ paragraph, media, currentParagraph, currentAnnotati if (!media.lookup) return <div /> const className = currentParagraph ? 'media image current' : 'media image' const annotation = paragraph.annotations[0] - console.log(annotation.settings.hide_in_transcript) + // console.log(annotation.settings.hide_in_transcript) if (annotation.settings.hide_in_transcript) { return <div /> } |
