rendering video thumbnail

This commit is contained in:
Roland Osborne 2022-05-04 15:13:25 -07:00
parent 5202a19b52
commit ce5416210d
12 changed files with 115 additions and 11 deletions

View File

@ -0,0 +1,4 @@
#!/bin/bash
#ffmpeg -i $1 -y -f mp4 -map_metadata -1 -c:v copy -c:a copy $2
#ffmpeg -f mp4 -i color=c=black:s=1280x720:r=5 -i $1 -crf 0 -c:a copy -shortest $2
ffmpeg -i $1 -y -f mp3 -map_metadata -1 -c:a copy $2

View File

@ -0,0 +1,3 @@
#!/bin/bash
convert -strip $1 -auto-orient $2

View File

@ -0,0 +1,3 @@
#!/bin/bash
convert -strip $1 -auto-orient -resize '640x640>' $2

View File

@ -0,0 +1,3 @@
#!/bin/bash
ffmpeg -i $1 -y -f mp4 -map_metadata -1 -c:v copy -c:a copy $2

View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
TMPFILE=$(mktemp /tmp/databag-XXXXX)
ffmpeg -ss 0 -i $1 -y -vframes 1 -q:v 2 $TMPFILE.jpg
convert -strip $TMPFILE.jpg -auto-orient -resize '640x640>' $2

View File

@ -130,8 +130,8 @@ export function Carousel({ ready, items, itemRenderer, itemRemove }) {
<Skeleton.Image style={{ height: 128 }} /> <Skeleton.Image style={{ height: 128 }} />
</div> </div>
<div class="arrows"> <div class="arrows">
<div class="arrow" onClick={onRight}><RightOutlined style={{ visibility: scrollRight }} /></div> <div class="arrow" onClick={onRight}><LeftOutlined style={{ visibility: 'hidden' }} /></div>
<div class="arrow" onClick={onLeft}><LeftOutlined style={{ visibility: scrollLeft }} /></div> <div class="arrow" onClick={onLeft}><RightOutlined style={{ visibility: 'hidden' }} /></div>
</div> </div>
</CarouselWrapper> </CarouselWrapper>
) )
@ -144,8 +144,8 @@ export function Carousel({ ready, items, itemRenderer, itemRemove }) {
{slots} {slots}
</div> </div>
<div class="arrows"> <div class="arrows">
<div class="arrow" onClick={onRight}><RightOutlined style={{ visibility: scrollRight }} /></div> <div class="arrow" onClick={onRight}><LeftOutlined style={{ visibility: scrollRight }} /></div>
<div class="arrow" onClick={onLeft}><LeftOutlined style={{ visibility: scrollLeft }} /></div> <div class="arrow" onClick={onLeft}><RightOutlined style={{ visibility: scrollLeft }} /></div>
</div> </div>
</CarouselWrapper> </CarouselWrapper>
); );

View File

@ -0,0 +1,8 @@
import React, { useEffect, useState } from 'react';
import ReactPlayer from 'react-player'
export function AudioAsset({ label, audioUrl }) {
return <ReactPlayer height="100%" width="auto" controls="true" url={audioUrl} />
}

View File

@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import ReactPlayer from 'react-player'
import { TopicItemWrapper } from './TopicItem.styled'; import { TopicItemWrapper } from './TopicItem.styled';
import ReactResizeDetector from 'react-resize-detector';
import { useTopicItem } from './useTopicItem.hook'; import { useTopicItem } from './useTopicItem.hook';
import { VideoAsset } from './VideoAsset/VideoAsset';
import { AudioAsset } from './AudioAsset/AudioAsset';
import { Avatar } from 'avatar/Avatar'; import { Avatar } from 'avatar/Avatar';
import { CommentOutlined } from '@ant-design/icons'; import { CommentOutlined } from '@ant-design/icons';
import { Carousel } from 'Carousel/Carousel'; import { Carousel } from 'Carousel/Carousel';
@ -18,13 +18,16 @@ export function TopicItem({ topic }) {
const renderAsset = (asset) => { const renderAsset = (asset) => {
if (asset.image) { if (asset.image) {
return <img style={{ height: '100%', objectFit: 'container' }} src={actions.getAssetUrl(asset.image.full)} alt="" /> if (asset.image.thumb) {
return <img style={{ height: '100%', objectFit: 'contain' }} src={actions.getAssetUrl(asset.image.thumb)} alt="" />
}
return <img style={{ height: '100%', objectFit: 'contain' }} src={actions.getAssetUrl(asset.image.full)} alt="" />
} }
if (asset.video) { if (asset.video) {
return <ReactPlayer height="100%" width="auto" controls="true" url={actions.getAssetUrl(asset.video.full)} /> return <VideoAsset thumbUrl={actions.getAssetUrl(asset.video.thumb)} videoUrl={actions.getAssetUrl(asset.video.full)} />
} }
if (asset.audio) { if (asset.audio) {
return <ReactPlayer height="100%" width="auto" controls="true" url={actions.getAssetUrl(asset.audio.full)} /> return <AudioAsset label={asset.audio.label} audioUrl={actions.getAssetUrl(asset.audio.full)} />
} }
return <></> return <></>
} }

View File

@ -0,0 +1,57 @@
import React, { useEffect, useState } from 'react';
import { Button } from 'antd';
import ReactPlayer from 'react-player'
import ReactResizeDetector from 'react-resize-detector';
import { PlayCircleOutlined } from '@ant-design/icons';
import { VideoAssetWrapper } from './VideoAsset.styled';
export function VideoAsset({ thumbUrl, videoUrl }) {
const [active, setActive] = useState(false);
const [dimension, setDimension] = useState({});
const [visibility, setVisibility] = useState('hidden');
const [playing, setPlaying] = useState(false);
useEffect(() => {
setActive(false);
setVisibility('hidden');
setPlaying(false);
}, [thumbUrl, videoUrl]);
const onReady = () => {
setVisibility('visible');
setPlaying(true);
}
if (!thumbUrl) {
return <ReactPlayer height="100%" width="auto" controls="true" url={videoUrl} />
}
const Player = () => {
if (!active) {
return (
<div onClick={() => setActive(true)}>
<PlayCircleOutlined style={{ fontSize: 48, color: '#eeeeee', cursor: 'pointer' }} />
</div>
)
}
return <ReactPlayer style={{ visibility }} playing={playing} height="100%" width="100%" controls="true" url={videoUrl} onReady={onReady} />
}
return (
<VideoAssetWrapper>
<ReactResizeDetector handleWidth={true} handleHeight={true}>
{({ width, height }) => {
if (width != dimension.width || height != dimension.height) {
setDimension({ width, height });
}
return <img style={{ height: '100%', objectFit: 'contain' }} src={thumbUrl} alt="" />
}}
</ReactResizeDetector>
<div class="player" style={{ width: dimension.width, height: dimension.height }}>
<Player />
</div>
</VideoAssetWrapper>
)
}

View File

@ -0,0 +1,16 @@
import styled from 'styled-components';
export const VideoAssetWrapper = styled.div`
position: relative;
height: 100%;
.player {
top: 0;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
`;

View File

@ -38,12 +38,13 @@ export async function addChannelTopic(token, channelId, message, assets ) {
else if (asset.video) { else if (asset.video) {
const formData = new FormData(); const formData = new FormData();
formData.append('asset', asset.video); formData.append('asset', asset.video);
let transform = encodeURIComponent(JSON.stringify(["vcopy;video"])); let transform = encodeURIComponent(JSON.stringify(["vcopy;video", 'vthumb;video']));
let topicAsset = await fetch(`/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&agent=${token}`, { method: 'POST', body: formData }); let topicAsset = await fetch(`/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&agent=${token}`, { method: 'POST', body: formData });
checkResponse(topicAsset); checkResponse(topicAsset);
let assetEntry = await topicAsset.json(); let assetEntry = await topicAsset.json();
message.assets.push({ message.assets.push({
video: { video: {
thumb: assetEntry.find(item => item.transform === 'vthumb;video').assetId,
full: assetEntry.find(item => item.transform === 'vcopy;video').assetId, full: assetEntry.find(item => item.transform === 'vcopy;video').assetId,
} }
}); });

View File

@ -39,12 +39,13 @@ export async function addContactChannelTopic(server, token, channelId, message,
else if (asset.video) { else if (asset.video) {
const formData = new FormData(); const formData = new FormData();
formData.append('asset', asset.video); formData.append('asset', asset.video);
let transform = encodeURIComponent(JSON.stringify(["vcopy;video"])); let transform = encodeURIComponent(JSON.stringify(["vcopy;video", "vthumb;video"]));
let topicAsset = await fetch(`https://${server}/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&contact=${token}`, { method: 'POST', body: formData }); let topicAsset = await fetch(`https://${server}/content/channels/${channelId}/topics/${slot.id}/assets?transforms=${transform}&contact=${token}`, { method: 'POST', body: formData });
checkResponse(topicAsset); checkResponse(topicAsset);
let assetEntry = await topicAsset.json(); let assetEntry = await topicAsset.json();
message.assets.push({ message.assets.push({
video: { video: {
thumb: assetEntry.find(item => item.transform === 'vthumb;video').assetId,
full: assetEntry.find(item => item.transform === 'vcopy;video').assetId, full: assetEntry.find(item => item.transform === 'vcopy;video').assetId,
} }
}); });