From 0cffae759496a086eabe7459a8cfd066760f474e Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 9 Nov 2020 16:27:49 +0100 Subject: video subtitles in SRT format --- .../components.fullscreen/fullscreen.video.js | 6 ++- .../views/viewer/player/components.media/index.js | 2 + .../views/viewer/player/components.media/media.css | 29 ++++++++++ .../player/components.media/video.scrubber.js | 4 +- .../player/components.media/video.subtitles.js | 61 ++++++++++++++++++++++ .../transcript/components/elementTypes.gallery.js | 2 +- .../transcript/components/elementTypes.image.js | 2 +- 7 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 animism-align/frontend/app/views/viewer/player/components.media/video.subtitles.js (limited to 'animism-align/frontend/app/views/viewer') 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} /> + ) } 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
+ return ( +
+ {current.lines.map(line => {line})} +
+ ) + } +} 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
} const item = media.lookup[annotation.settings.media_id] - console.log(item) + // console.log(item) if (!item) return
Media not found: {annotation.settings.media_id}
return (
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
} -- cgit v1.2.3-70-g09d2