lbry-desktop/ui/component/publishFile/view.jsx
Thomas Zarebczan e35fbdd86a make transcoding work
appstrings

provide optimize checkbox on publish

fix missing status

no crash on web

cleanup

better settings ui

add help and time estimate to publish transcoding

messaging

fix: Special SDK + fix config name

fix: older SDK build

fix app string, style tweak

whoops, and looks better to me this way.

bump SDK
2020-03-25 13:42:40 -04:00

312 lines
9.4 KiB
JavaScript

// @flow
import * as ICONS from 'constants/icons';
import React, { useState, useEffect } from 'react';
import { regexInvalidURI } from 'lbry-redux';
import FileSelector from 'component/common/file-selector';
import Button from 'component/button';
import Card from 'component/common/card';
import { FormField } from 'component/common/form';
import Spinner from 'component/spinner';
import I18nMessage from '../i18nMessage';
type Props = {
name: ?string,
filePath: string | WebFile,
isStillEditing: boolean,
balance: number,
updatePublishForm: ({}) => void,
disabled: boolean,
publishing: boolean,
showToast: string => void,
inProgress: boolean,
clearPublish: () => void,
ffmpegStatus: any,
optimize: boolean,
};
function PublishFile(props: Props) {
const {
name,
balance,
filePath,
isStillEditing,
updatePublishForm,
disabled,
publishing,
inProgress,
clearPublish,
optimize,
ffmpegStatus = {},
} = props;
const { available } = ffmpegStatus;
const [duration, setDuration] = useState(0);
const [size, setSize] = useState(0);
const [oversized, setOversized] = useState(false);
const [isVid, setIsVid] = useState(false);
const RECOMMENDED_BITRATE = 6000000;
const TV_PUBLISH_SIZE_LIMIT: number = 1073741824;
const UPLOAD_SIZE_MESSAGE = 'Lbrytv uploads are limited to 1 GB. Download the app for unrestricted publishing.';
const PROCESSING_MB_PER_SECOND = 0.5;
const MINUTES_THRESHOLD = 30;
const HOURS_THRESHOLD = MINUTES_THRESHOLD * 60;
const sizeInMB = Number(size) / 1000000;
const secondsToProcess = sizeInMB / PROCESSING_MB_PER_SECOND;
// clear warnings
useEffect(() => {
if (!filePath || filePath === '' || filePath.name === '') {
setDuration(0);
setSize(0);
setIsVid(false);
setOversized(false);
}
}, [filePath]);
let currentFile = '';
if (filePath) {
if (typeof filePath === 'string') {
currentFile = filePath;
} else {
currentFile = filePath.name;
}
}
function getBitrate(size, duration) {
const s = Number(size);
const d = Number(duration);
if (s && d) {
return (s * 8) / d;
} else {
return 0;
}
}
function getTimeForMB(s) {
if (s < MINUTES_THRESHOLD) {
return Math.floor(secondsToProcess);
} else if (s >= MINUTES_THRESHOLD && s < HOURS_THRESHOLD) {
return Math.floor(secondsToProcess / 60);
} else {
return Math.floor(secondsToProcess / 60 / 60);
}
}
function getUnitsForMB(s) {
if (s < MINUTES_THRESHOLD) {
if (secondsToProcess > 1) return 'seconds';
return 'second';
} else if (s >= MINUTES_THRESHOLD && s < HOURS_THRESHOLD) {
if (Math.floor(secondsToProcess / 60) > 1) return 'minutes';
return 'minute';
} else {
if (Math.floor(secondsToProcess / 3600) > 1) return 'hours';
return 'hour';
}
}
function getMessage() {
// @if TARGET='web'
if (oversized) {
return (
<p className="help--error">
{__(UPLOAD_SIZE_MESSAGE)}{' '}
<Button button="link" label={__('Publishing Guide')} href="https://lbry.com/faq/video-publishing-guide" />
</p>
);
}
// @endif
if (isVid && duration && getBitrate(size, duration) > RECOMMENDED_BITRATE) {
return (
<p className="help--warning">
{__('Your video has a bitrate over 5 mbps. We suggest transcoding to provide viewers the best experience.')}{' '}
<Button button="link" label={__('Publishing Guide')} href="https://lbry.com/faq/video-publishing-guide" />
</p>
);
}
if (isVid && !duration) {
return (
<p className="help--warning">
{__(
'Your video may not be the best format. Use MP4s in H264/AAC format and a friendly bitrate (720p) for more reliable streaming.'
)}{' '}
<Button button="link" label={__('Publishing Guide')} href="https://lbry.com/faq/video-publishing-guide" />
</p>
);
}
if (!!isStillEditing && name) {
return (
<p className="help">
{__("If you don't choose a file, the file from your existing claim %name% will be used", { name: name })}
</p>
);
}
// @if TARGET='web'
if (!isStillEditing) {
return (
<p className="help">
{__(
'For video content, use MP4s in H264/AAC format and a friendly bitrate (720p) for more reliable streaming. Lbrytv uploads are restricted to 1GB.'
)}{' '}
<Button button="link" label={__('Publishing Guide')} href="https://lbry.com/faq/video-publishing-guide" />
</p>
);
}
// @endif
// @if TARGET='app'
if (!isStillEditing) {
return (
<p className="help">
{__(
'For video content, use MP4s in H264/AAC format and a friendly bitrate (720p) for more reliable streaming.'
)}{' '}
<Button button="link" label={__('Publishing Guide')} href="https://lbry.com/faq/video-publishing-guide" />
</p>
);
}
// @endif
}
function handleFileChange(file: WebFile) {
const { showToast } = props;
window.URL = window.URL || window.webkitURL;
// if electron, we'll set filePath to the path string because SDK is handling publishing.
// if web, we set the filePath (dumb name) to the File() object
// File.path will be undefined from web due to browser security, so it will default to the File Object.
setSize(file ? file.size : 0);
setDuration(0);
setOversized(false);
// select file, start to select a new one, then cancel
if (!file) {
updatePublishForm({ filePath: '', name: '' });
return;
}
// if video, extract duration so we can warn about bitrate
const contentType = file.type.split('/');
const isVideo = contentType[0] === 'video';
const isMp4 = contentType[1] === 'mp4';
if (isVideo) {
if (isMp4) {
const video = document.createElement('video');
video.preload = 'metadata';
video.onloadedmetadata = function() {
setDuration(video.duration);
setSize(file.size);
setIsVid(isVideo);
window.URL.revokeObjectURL(video.src);
};
video.onerror = function() {
setDuration(0);
setSize(file.size);
setIsVid(isVideo);
};
video.src = window.URL.createObjectURL(file);
} else {
setSize(file.size);
setDuration(0);
setIsVid(isVideo);
}
}
// @if TARGET='web'
// we only need to enforce file sizes on 'web'
if (typeof file !== 'string') {
if (file && file.size && Number(file.size) > TV_PUBLISH_SIZE_LIMIT) {
setOversized(true);
showToast(__(UPLOAD_SIZE_MESSAGE));
updatePublishForm({ filePath: '', name: '' });
return;
}
}
// @endif
const publishFormParams: { filePath: string | WebFile, name?: string, optimize?: boolean } = {
filePath: file.path || file,
};
// Strip off extention and replace invalid characters
let fileName = name || file.name.substr(0, file.name.lastIndexOf('.')) || file.name;
let INVALID_URI_CHARS = new RegExp(regexInvalidURI, 'gu');
let parsedFileName = fileName.replace(INVALID_URI_CHARS, '-');
if (!isStillEditing) {
publishFormParams.name = parsedFileName;
}
updatePublishForm(publishFormParams);
}
let title;
if (publishing) {
title = (
<span>
{__('Publishing')}
<Spinner type={'small'} />
</span>
);
} else {
title = isStillEditing ? __('Edit') : __('Publish');
}
return (
<Card
actionIconPadding={false}
icon={ICONS.PUBLISH}
disabled={disabled || balance === 0}
title={
<React.Fragment>
{title}{' '}
{inProgress && <Button button="close" label={__('Cancel')} icon={ICONS.REMOVE} onClick={clearPublish} />}
</React.Fragment>
}
subtitle={
isStillEditing ? __('You are currently editing a claim.') : __('Publish something totally wacky and wild.')
}
actions={
<React.Fragment>
<FileSelector disabled={disabled} currentPath={currentFile} onFileChosen={handleFileChange} />
{getMessage()}
{/* @if TARGET='app' */}
<FormField
type="checkbox"
checked={isVid && available && optimize}
disabled={!isVid || !available}
onChange={e => updatePublishForm({ optimize: e.target.checked })}
label={__('Optimize and transcode video')}
name="optimize"
/>
{!available && (
<p className="help">
<I18nMessage
tokens={{
settings_link: <Button button="link" navigate="/$/settings" label={__('Settings')} />,
}}
>
FFmpeg not configured. More in %settings_link%.
</I18nMessage>
</p>
)}
{Boolean(size) && available && optimize && isVid && (
<p className="help">
<I18nMessage
tokens={{
size: Math.ceil(sizeInMB),
processTime: getTimeForMB(sizeInMB),
units: getUnitsForMB(sizeInMB),
}}
>
Transcoding this %size%MB file should take under %processTime% %units%.
</I18nMessage>
</p>
)}
{/* @endif */}
</React.Fragment>
}
/>
);
}
export default PublishFile;