Skip to content

Commit

Permalink
Merge pull request #64 from nebulabroadcast/feature/set_poster_frame
Browse files Browse the repository at this point in the history
Add poster frame actions to video preview
  • Loading branch information
martastain authored May 2, 2024
2 parents 1026246 + cf748c6 commit 899e65a
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 40 deletions.
12 changes: 2 additions & 10 deletions frontend/src/components/Dropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,10 @@ const DropdownContainer = styled.div`
}
`

const DropdownOption = ({
currentValue,
separator,
disabled,
...props
}) => (
const DropdownOption = ({ currentValue, separator, disabled, ...props }) => (
<span>
{separator && <hr />}
<Button
{...props}
disabled={disabled || currentValue === props.value}
/>
<Button {...props} disabled={disabled || currentValue === props.value} />
</span>
)

Expand Down
97 changes: 76 additions & 21 deletions frontend/src/components/Video.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,33 @@ const Trackbar = styled.input`
flex-grow: 1;
appearance: none;
height: var(--input-height);
outline: none;
padding: 0;
margin: 0;
outline: 1px solid var(--color-surface-04);
border-radius: 4px;
&::-webkit-slider-thumb {
padding: 0;
margin: 0;
appearance: none;
width: 5px;
width: 4px;
height: 25px;
background-color: #d4d4d4;
cursor: pointer;
}
&:before {
content: '';
position: absolute;
display: ${(props) => (props.posterFrame ? 'block' : 'none')};
height: 4px;
bottom: 0;
width: 2px;
border: 0;
left: ${(props) => props.posterFrame}%;
background: var(--color-yellow);
}
/* highlight the specified range using the highlightStart and highlightEnd props */
background: linear-gradient(
to right,
Expand Down Expand Up @@ -60,7 +75,15 @@ const PlayoutControls = styled.div`
justify-content: center;
`

const Video = ({ src, style, showMarks, marks = {}, setMarks = () => {} }) => {
const Video = ({
src,
style,
showMarks,
position,
setPosition,
marks = {},
setMarks = () => {},
}) => {
const videoRef = useRef(null)
const sliderRef = useRef(null)
const playButtonRef = useRef(null)
Expand All @@ -77,6 +100,17 @@ const Video = ({ src, style, showMarks, marks = {}, setMarks = () => {} }) => {
setVideoDuration(0)
}, [src])

useEffect(() => {
if (position !== undefined) {
videoRef.current.currentTime = position
}
}, [position])

useEffect(() => {
if (!setPosition) return
setPosition(videoPosition)
}, [videoPosition])

const handleFinishSlider = useCallback(() => {
if (playButtonRef.current) {
playButtonRef.current.focus()
Expand Down Expand Up @@ -261,17 +295,28 @@ const Video = ({ src, style, showMarks, marks = {}, setMarks = () => {} }) => {
videoRef.current.currentTime = value
}}
/>
<Trackbar
value={trackbarPosition}
onInput={onSeek}
highlightStart={((marks.mark_in || 0) / videoDuration) * 100}
highlightEnd={
((marks.mark_out || videoDuration) / videoDuration) * 100
}
ref={sliderRef}
onMouseLeave={handleFinishSlider}
onMouseUp={handleFinishSlider}
/>
<div
style={{
flexGrow: 1,
position: 'relative',
display: 'flex',
padding: 0,
margin: 0,
}}
>
<Trackbar
value={trackbarPosition}
onInput={onSeek}
highlightStart={((marks.mark_in || 0) / videoDuration) * 100}
highlightEnd={
((marks.mark_out || videoDuration) / videoDuration) * 100
}
posterFrame={((marks.poster_frame || 0) / videoDuration) * 100}
ref={sliderRef}
onMouseLeave={handleFinishSlider}
onMouseUp={handleFinishSlider}
/>
</div>
<InputTimecode value={videoDuration} readOnly={true} title="Duration" />
</PlayoutControls>

Expand All @@ -280,42 +325,52 @@ const Video = ({ src, style, showMarks, marks = {}, setMarks = () => {} }) => {
<>
<Button
icon="line_start_diamond"
title="Mark in"
tooltip="Mark in"
onClick={() => onMarkIn(videoRef.current.currentTime)}
/>
<Button
icon="first_page"
title="Go to mark in"
tooltip="Go to mark in"
onClick={onGoToIn}
/>
</>
)}
<Button
icon="keyboard_double_arrow_left"
tooltip="Back 5 frames"
onClick={() => frameStep(-5)}
/>
<Button icon="chevron_left" onClick={() => frameStep(-1)} />
<Button
icon="chevron_left"
tooltip="Back 1 frame"
onClick={() => frameStep(-1)}
/>
<Button
icon={playButtonIcon}
title="Play/Pause"
tooltip="Play/Pause"
onClick={onPlay}
ref={playButtonRef}
/>
<Button icon="chevron_right" onClick={() => frameStep(1)} />
<Button
icon="chevron_right"
tooltip="Forward 1 frame"
onClick={() => frameStep(1)}
/>
<Button
icon="keyboard_double_arrow_right"
tooltip="Forward 5 frames"
onClick={() => frameStep(5)}
/>
{showMarks && (
<>
<Button
icon="last_page"
title="Go to mark out"
tooltip="Go to mark out"
onClick={onGoToOut}
/>
<Button
icon="line_end_diamond"
title="Mark out"
tooltip="Mark out"
onClick={() => onMarkOut(videoRef.current.currentTime)}
/>
</>
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/containers/Browser/Formatting.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ const getColumnWidth = (key) => {

// Field formatters


const getDefaultFormatter = (key) => {
const getDefaultFormatter = (key) => {
const metaType = nebula.metaType(key)
switch (metaType.type) {
case 'boolean':
Expand Down Expand Up @@ -91,7 +90,6 @@ const getDefaultFormatter = (key) => {
} // switch metaType
}


const getFormatter = (key) => {
if (['title', 'subtitle', 'description'].includes(key))
// eslint-disable-next-line
Expand Down
42 changes: 36 additions & 6 deletions frontend/src/pages/AssetEditor/Preview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styled from 'styled-components'
import { toast } from 'react-toastify'
import { useState, useEffect } from 'react'
import {
Dropdown,
Video,
Spacer,
InputText,
Expand Down Expand Up @@ -98,6 +99,7 @@ const Preview = ({ assetData, setAssetData }) => {
const accessToken = nebula.getAccessToken()
const [selection, setSelection] = useState({})
const [subclips, setSubclips] = useState([])
const [position, setPosition] = useState(0)

useEffect(() => {
setSelection({})
Expand Down Expand Up @@ -127,37 +129,64 @@ const Preview = ({ assetData, setAssetData }) => {
const videoSrc =
assetData.id && accessToken && `/proxy/${assetData.id}?token=${accessToken}`

const posterOptions = [
{
label: 'Set poster frame',
onClick: () => {
setAssetData((o) => {
return { ...o, poster_frame: position }
})
},
},
{
label: 'Go to poster frame',
onClick: () => {
setPosition(assetData.poster_frame)
},
},
{
label: 'Clear poster frame',
onClick: () => {
setAssetData((o) => {
return { ...o, poster_frame: null }
})
},
},
]

return (
<div className="grow column" style={{ minWidth: 300, maxWidth: 600 }}>
<section className="column">
<Video
src={videoSrc}
style={{ width: '100%' }}
showMarks={true}
marks={selection}
marks={{ ...selection, poster_frame: assetData.poster_frame }}
setMarks={setSelection}
position={position}
setPosition={setPosition}
/>
</section>
<section className="column">
<RegionRow>
<InputTimecode
value={assetData.mark_in}
readOnly={true}
title="Content start"
tooltip="Content start"
/>
<InputTimecode
value={assetData.mark_out}
readOnly={true}
title="Content end"
tooltip="Content end"
/>
<Button
icon="download"
title="Marks from selection"
tooltip="Marks from selection"
onClick={onSetMarks}
/>
<Button
icon="upload"
title="Marks to selection"
tooltip="Marks to selection"
onClick={() =>
setSelection({
mark_in: assetData.mark_in || null,
Expand All @@ -168,7 +197,7 @@ const Preview = ({ assetData, setAssetData }) => {
<Spacer />
<Button
icon="add"
label="New subclip"
tooltip="New subclip"
onClick={() => {
if (!(selection.mark_in && selection.mark_out)) {
toast.error('Please select a region first')
Expand All @@ -180,6 +209,7 @@ const Preview = ({ assetData, setAssetData }) => {
])
}}
/>
<Dropdown icon="image" align="right" options={posterOptions} />
</RegionRow>
</section>

Expand Down

0 comments on commit 899e65a

Please sign in to comment.