2019-06-28 09:27:55 +02:00
// @flow
2019-09-27 20:56:15 +02:00
import * as ICONS from 'constants/icons' ;
2020-03-07 00:11:16 +01:00
import React , { useState , useEffect } from 'react' ;
2019-06-28 09:27:55 +02:00
import { regexInvalidURI } from 'lbry-redux' ;
import FileSelector from 'component/common/file-selector' ;
2019-07-17 05:23:45 +02:00
import Button from 'component/button' ;
2019-09-27 20:56:15 +02:00
import Card from 'component/common/card' ;
2020-03-24 18:57:17 +01:00
import { FormField } from 'component/common/form' ;
2019-10-11 02:37:18 +02:00
import Spinner from 'component/spinner' ;
2020-03-24 18:57:17 +01:00
import I18nMessage from '../i18nMessage' ;
2019-06-28 09:27:55 +02:00
type Props = {
name : ? string ,
2019-10-07 22:02:32 +02:00
filePath : string | WebFile ,
2019-06-28 09:27:55 +02:00
isStillEditing : boolean ,
balance : number ,
updatePublishForm : ( { } ) => void ,
2019-09-27 20:56:15 +02:00
disabled : boolean ,
2019-10-11 02:37:18 +02:00
publishing : boolean ,
2019-12-09 19:51:00 +01:00
showToast : string => void ,
2019-12-26 16:37:26 +01:00
inProgress : boolean ,
clearPublish : ( ) => void ,
2020-03-24 18:57:17 +01:00
ffmpegStatus : any ,
optimize : boolean ,
2020-03-30 20:19:32 +02:00
size : number ,
duration : number ,
isVid : boolean ,
2019-06-28 09:27:55 +02:00
} ;
function PublishFile ( props : Props ) {
2019-12-26 16:37:26 +01:00
const {
name ,
balance ,
filePath ,
isStillEditing ,
updatePublishForm ,
disabled ,
publishing ,
inProgress ,
clearPublish ,
2020-03-24 18:57:17 +01:00
optimize ,
ffmpegStatus = { } ,
2020-03-30 20:19:32 +02:00
size ,
duration ,
isVid ,
2019-12-26 16:37:26 +01:00
} = props ;
2019-10-11 02:37:18 +02:00
2020-03-24 18:57:17 +01:00
const { available } = ffmpegStatus ;
2020-03-07 00:11:16 +01:00
const [ oversized , setOversized ] = 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.' ;
2020-03-24 18:57:17 +01:00
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 ;
2020-03-07 00:11:16 +01:00
// clear warnings
useEffect ( ( ) => {
2020-03-30 20:19:32 +02:00
if ( ! filePath || filePath === '' ) {
updateOptimizeState ( 0 , 0 , false ) ;
2020-03-07 00:11:16 +01:00
setOversized ( false ) ;
}
} , [ filePath ] ) ;
2019-10-07 22:02:32 +02:00
let currentFile = '' ;
if ( filePath ) {
if ( typeof filePath === 'string' ) {
currentFile = filePath ;
} else {
currentFile = filePath . name ;
2019-06-28 09:27:55 +02:00
}
2019-10-07 22:02:32 +02:00
}
2020-03-30 20:19:32 +02:00
function updateOptimizeState ( duration , size , isvid ) {
updatePublishForm ( { fileDur : duration , fileSize : size , fileVid : isvid } ) ;
}
2020-03-07 00:11:16 +01:00
function getBitrate ( size , duration ) {
const s = Number ( size ) ;
const d = Number ( duration ) ;
if ( s && d ) {
return ( s * 8 ) / d ;
} else {
return 0 ;
}
}
2020-03-24 18:57:17 +01:00
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' ;
}
}
2020-03-07 00:11:16 +01:00
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" >
{ _ _ (
2020-04-26 22:47:39 +02:00
'Your video may not be the best format. Use MP4s in H264/AAC format and a friendly bitrate (under 5 mbps) and resolution (720p) for more reliable streaming.'
2020-03-07 00:11:16 +01:00
) } { ' ' }
< 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" >
{ _ _ (
2020-04-26 22:47:39 +02:00
'For video content, use MP4s in H264/AAC format and a friendly bitrate (under 5 mbps) and resolution (720p) for more reliable streaming. Lbrytv uploads are restricted to 1GB.'
2020-03-07 00:11:16 +01:00
) } { ' ' }
< 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" >
{ _ _ (
2020-04-26 22:47:39 +02:00
'For video content, use MP4s in H264/AAC format and a friendly bitrate (under 5 mbps) and resolution (720p) for more reliable streaming.'
2020-03-07 00:11:16 +01:00
) } { ' ' }
< Button button = "link" label = { _ _ ( 'Publishing Guide' ) } href = "https://lbry.com/faq/video-publishing-guide" / >
< / p >
) ;
}
// @endif
}
2019-10-07 22:02:32 +02:00
function handleFileChange ( file : WebFile ) {
2019-12-09 19:51:00 +01:00
const { showToast } = props ;
2020-03-07 00:11:16 +01:00
window . URL = window . URL || window . webkitURL ;
2019-10-07 22:02:32 +02:00
// 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
2019-10-11 02:37:18 +02:00
// File.path will be undefined from web due to browser security, so it will default to the File Object.
2020-03-07 00:11:16 +01:00
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 ( ) {
2020-03-30 20:19:32 +02:00
updateOptimizeState ( video . duration , file . size , isVideo ) ;
2020-03-07 00:11:16 +01:00
window . URL . revokeObjectURL ( video . src ) ;
} ;
video . onerror = function ( ) {
2020-03-30 20:19:32 +02:00
updateOptimizeState ( 0 , file . size , isVideo ) ;
2020-03-07 00:11:16 +01:00
} ;
video . src = window . URL . createObjectURL ( file ) ;
} else {
2020-03-30 20:19:32 +02:00
updateOptimizeState ( 0 , file . size , isVideo ) ;
2020-03-07 00:11:16 +01:00
}
}
2019-10-11 02:37:18 +02:00
// @if TARGET='web'
// we only need to enforce file sizes on 'web'
if ( typeof file !== 'string' ) {
2020-03-07 00:11:16 +01:00
if ( file && file . size && Number ( file . size ) > TV _PUBLISH _SIZE _LIMIT ) {
setOversized ( true ) ;
showToast ( _ _ ( UPLOAD _SIZE _MESSAGE ) ) ;
2019-10-11 02:37:18 +02:00
updatePublishForm ( { filePath : '' , name : '' } ) ;
return ;
}
}
// @endif
2019-12-09 19:51:00 +01:00
2020-03-24 18:57:17 +01:00
const publishFormParams : { filePath : string | WebFile , name ? : string , optimize ? : boolean } = {
2019-10-07 22:02:32 +02:00
filePath : file . path || file ,
} ;
2019-11-01 18:27:01 +01:00
// Strip off extention and replace invalid characters
2019-12-20 17:44:52 +01:00
let fileName = name || file . name . substr ( 0 , file . name . lastIndexOf ( '.' ) ) || file . name ;
2019-12-16 22:33:17 +01:00
let INVALID _URI _CHARS = new RegExp ( regexInvalidURI , 'gu' ) ;
let parsedFileName = fileName . replace ( INVALID _URI _CHARS , '-' ) ;
2019-12-14 20:35:55 +01:00
if ( ! isStillEditing ) {
publishFormParams . name = parsedFileName ;
}
2019-06-28 09:27:55 +02:00
updatePublishForm ( publishFormParams ) ;
}
2019-10-11 02:37:18 +02:00
let title ;
if ( publishing ) {
title = (
< span >
{ _ _ ( 'Publishing' ) }
< Spinner type = { 'small' } / >
< / span >
) ;
} else {
title = isStillEditing ? _ _ ( 'Edit' ) : _ _ ( 'Publish' ) ;
}
2019-12-09 19:51:00 +01:00
2019-06-28 09:27:55 +02:00
return (
2019-09-27 20:56:15 +02:00
< Card
icon = { ICONS . PUBLISH }
2019-10-01 06:53:33 +02:00
disabled = { disabled || balance === 0 }
2019-12-26 16:37:26 +01:00
title = {
< React.Fragment >
{ title } { ' ' }
{ inProgress && < Button button = "close" label = { _ _ ( 'Cancel' ) } icon = { ICONS . REMOVE } onClick = { clearPublish } / > }
< / React.Fragment >
}
2019-10-03 23:40:54 +02:00
subtitle = {
isStillEditing ? _ _ ( 'You are currently editing a claim.' ) : _ _ ( 'Publish something totally wacky and wild.' )
}
2019-09-27 20:56:15 +02:00
actions = {
< React.Fragment >
2019-12-09 19:51:00 +01:00
< FileSelector disabled = { disabled } currentPath = { currentFile } onFileChosen = { handleFileChange } / >
2020-03-07 00:11:16 +01:00
{ getMessage ( ) }
2020-03-24 18:57:17 +01:00
{ /* @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 */ }
2019-09-27 20:56:15 +02:00
< / React.Fragment >
}
/ >
2019-06-28 09:27:55 +02:00
) ;
}
export default PublishFile ;