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
This commit is contained in:
Thomas Zarebczan 2020-03-24 13:57:17 -04:00 committed by Sean Yesmunt
parent eb54d899fb
commit e35fbdd86a
15 changed files with 279 additions and 50 deletions

View file

@ -1,7 +1,7 @@
declare type WebFile = {
name: string,
title?: string,
path?: string,
path: string,
size: string,
type: string,
}

View file

@ -209,7 +209,7 @@
"yarn": "^1.3"
},
"lbrySettings": {
"lbrynetDaemonVersion": "0.64.0",
"lbrynetDaemonVersion": "0.65.0",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
"lbrynetDaemonDir": "static/daemon",
"lbrynetDaemonFileName": "lbrynet"

View file

@ -1027,6 +1027,12 @@
"For video content, use MP4s in H264/AAC format and a friendly bitrate (720p) for more reliable streaming.": "For video content, use MP4s in H264/AAC format and a friendly bitrate (720p) for more reliable streaming.",
"Your video may not be the best format. Use MP4s in H264/AAC format and a friendly bitrate (720p) for more reliable streaming.": "Your video may not be the best format. Use MP4s in H264/AAC format and a friendly bitrate (720p) for more reliable streaming.",
"Your video has a bitrate over 6 mbps. We suggest transcoding to provide viewers the best experience.": "Your video has a bitrate over 6 mbps. We suggest transcoding to provide viewers the best experience.",
"Transcoding": "Transcoding",
"Optimize and transcode video": "Optimize and transcode video",
"FFmpeg not found": "FFmpeg not found",
"FFmpeg is correctly configured": "FFmpeg is correctly configured",
"ffmpeg not found": "ffmpeg not found",
"Known Tags": "Known Tags",
"Your video has a bitrate over 5 mbps. We suggest transcoding to provide viewers the best experience.": "Your video has a bitrate over 5 mbps. We suggest transcoding to provide viewers the best experience.",
"Almost there": "Almost there",
"More Channels": "More Channels",
@ -1072,4 +1078,4 @@
"You are currently following %followingCount% tags": "You are currently following %followingCount% tags",
"Back": "Back",
"Nice! You are currently following %followingCount% creators": "Nice! You are currently following %followingCount% creators"
}
}

View file

@ -7,14 +7,17 @@ import {
doToast,
doClearPublish,
} from 'lbry-redux';
import { selectFfmpegStatus } from 'redux/selectors/settings';
import PublishPage from './view';
const select = state => ({
name: makeSelectPublishFormValue('name')(state),
filePath: makeSelectPublishFormValue('filePath')(state),
optimize: makeSelectPublishFormValue('optimize')(state),
isStillEditing: selectIsStillEditing(state),
balance: selectBalance(state),
publishing: makeSelectPublishFormValue('publishing')(state),
ffmpegStatus: selectFfmpegStatus(state),
});
const perform = dispatch => ({

View file

@ -5,7 +5,9 @@ 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,
@ -18,6 +20,8 @@ type Props = {
showToast: string => void,
inProgress: boolean,
clearPublish: () => void,
ffmpegStatus: any,
optimize: boolean,
};
function PublishFile(props: Props) {
@ -31,8 +35,11 @@ function PublishFile(props: Props) {
publishing,
inProgress,
clearPublish,
optimize,
ffmpegStatus = {},
} = props;
const { available } = ffmpegStatus;
const [duration, setDuration] = useState(0);
const [size, setSize] = useState(0);
const [oversized, setOversized] = useState(false);
@ -40,6 +47,12 @@ function PublishFile(props: Props) {
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(() => {
@ -70,6 +83,29 @@ function PublishFile(props: Props) {
}
}
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) {
@ -189,7 +225,7 @@ function PublishFile(props: Props) {
}
// @endif
const publishFormParams: { filePath: string | WebFile, name?: string } = {
const publishFormParams: { filePath: string | WebFile, name?: string, optimize?: boolean } = {
filePath: file.path || file,
};
// Strip off extention and replace invalid characters
@ -232,6 +268,40 @@ function PublishFile(props: Props) {
<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>
}
/>

View file

@ -143,9 +143,10 @@ function PublishForm(props: Props) {
return (
<Fragment>
<PublishFile disabled={disabled || publishing} inProgress={isInProgress} />
<div className={classnames({ 'card--disabled': formDisabled })}>
<PublishText disabled={formDisabled} />
<Card actions={<SelectThumbnail />} />
{!publishing && (
<div className={classnames({ 'card--disabled': formDisabled })}>
<PublishText disabled={formDisabled} />
<Card actions={<SelectThumbnail />} />
<TagsSelect
suggestMature
@ -174,40 +175,40 @@ function PublishForm(props: Props) {
tagsChosen={tags}
/>
<Card
actions={
<React.Fragment>
<SelectChannel channel={channel} onChannelChange={channel => updatePublishForm({ channel })} />
<p className="help">
{__('This is a username or handle that your content can be found under.')}{' '}
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
</p>
</React.Fragment>
}
/>
<Card
actions={
<React.Fragment>
<SelectChannel channel={channel} onChannelChange={channel => updatePublishForm({ channel })} />
<p className="help">
{__('This is a username or handle that your content can be found under.')}{' '}
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
</p>
</React.Fragment>
}
/>
<PublishName disabled={formDisabled} />
<PublishPrice disabled={formDisabled} />
<PublishAdditionalOptions disabled={formDisabled} />
<PublishName disabled={formDisabled} />
<PublishPrice disabled={formDisabled} />
<PublishAdditionalOptions disabled={formDisabled} />
</div>
)}
<section>
{!formDisabled && !formValid && <PublishFormErrors />}
<section>
{!formDisabled && !formValid && <PublishFormErrors />}
<div className="card__actions">
<Button
button="primary"
onClick={() => publish(filePath)}
label={submitLabel}
disabled={formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS}
/>
<Button button="link" onClick={clearPublish} label={__('Cancel')} />
</div>
<p className="help">
{__('By continuing, you accept the')}{' '}
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('LBRY Terms of Service')} />.
</p>
</section>
</div>
<div className="card__actions">
<Button
button="primary"
onClick={() => publish(filePath)}
label={submitLabel}
disabled={formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS}
/>
<Button button="link" onClick={clearPublish} label={__('Cancel')} />
</div>
<p className="help">
{__('By continuing, you accept the')}{' '}
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('LBRY Terms of Service')} />.
</p>
</section>
</Fragment>
);
}

View file

@ -116,6 +116,8 @@ export const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED';
export const CLIENT_SETTING_CHANGED = 'CLIENT_SETTING_CHANGED';
export const UPDATE_IS_NIGHT = 'UPDATE_IS_NIGHT';
export const FINDING_FFMPEG_STARTED = 'FINDING_FFMPEG_STARTED';
export const FINDING_FFMPEG_COMPLETED = 'FINDING_FFMPEG_COMPLETED';
// User
export const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED';

View file

@ -7,9 +7,21 @@ import {
doToggle3PAnalytics,
} from 'redux/actions/app';
import { selectAllowAnalytics } from 'redux/selectors/app';
import { doSetDaemonSetting, doSetClientSetting, doSetDarkTime } from 'redux/actions/settings';
import {
doSetDaemonSetting,
doClearDaemonSetting,
doSetClientSetting,
doSetDarkTime,
doFindFFmpeg,
} from 'redux/actions/settings';
import { doSetPlayingUri } from 'redux/actions/content';
import { makeSelectClientSetting, selectDaemonSettings, selectosNotificationsEnabled } from 'redux/selectors/settings';
import {
makeSelectClientSetting,
selectDaemonSettings,
selectFfmpegStatus,
selectosNotificationsEnabled,
selectFindingFFmpeg,
} from 'redux/selectors/settings';
import { doWalletStatus, selectWalletIsEncrypted, selectBlockedChannelsCount, SETTINGS } from 'lbry-redux';
import SettingsPage from './view';
import { selectUserVerifiedEmail } from 'lbryinc';
@ -34,10 +46,13 @@ const select = state => ({
floatingPlayer: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
showReposts: makeSelectClientSetting(SETTINGS.SHOW_REPOSTS)(state),
darkModeTimes: makeSelectClientSetting(SETTINGS.DARK_MODE_TIMES)(state),
ffmpegStatus: selectFfmpegStatus(state),
findingFFmpeg: selectFindingFFmpeg(state),
});
const perform = dispatch => ({
setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
clearDaemonSetting: key => dispatch(doClearDaemonSetting(key)),
toggle3PAnalytics: allow => dispatch(doToggle3PAnalytics(allow)),
clearCache: () => dispatch(doClearCache()),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
@ -47,6 +62,7 @@ const perform = dispatch => ({
confirmForgetPassword: modalProps => dispatch(doNotifyForgetPassword(modalProps)),
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
setDarkTime: (time, options) => dispatch(doSetDarkTime(time, options)),
findFFmpeg: () => dispatch(doFindFFmpeg()),
});
export default connect(

View file

@ -17,6 +17,7 @@ import SyncToggle from 'component/syncToggle';
import { SETTINGS } from 'lbry-redux';
import Card from 'component/common/card';
import { getPasswordFromCookie } from 'util/saved-passwords';
import Spinner from 'component/spinner';
// @if TARGET='app'
export const IS_MAC = process.platform === 'darwin';
@ -46,10 +47,12 @@ type DaemonSettings = {
max_connections_per_download?: number,
save_files: boolean,
save_blobs: boolean,
ffmpeg_path: string,
};
type Props = {
setDaemonSetting: (string, ?SetDaemonSettingArg) => void,
clearDaemonSetting: string => void,
setClientSetting: (string, SetDaemonSettingArg) => void,
toggle3PAnalytics: boolean => void,
clearCache: () => Promise<any>,
@ -78,6 +81,9 @@ type Props = {
clearPlayingUri: () => void,
darkModeTimes: DarkModeTimes,
setDarkTime: (string, {}) => void,
ffmpegStatus: { available: boolean, which: string },
findingFFmpeg: boolean,
findFFmpeg: () => void,
};
type State = {
@ -105,7 +111,17 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
componentDidMount() {
const { isAuthenticated } = this.props;
const { isAuthenticated, ffmpegStatus, daemonSettings, findFFmpeg } = this.props;
// @if TARGET='app'
const { available } = ffmpegStatus;
const { ffmpeg_path: ffmpegPath } = daemonSettings;
if (!available) {
if (ffmpegPath) {
this.clearDaemonSetting('ffmpeg_path');
}
findFFmpeg();
}
// @endif
if (isAuthenticated || !IS_WEB) {
this.props.updateWalletStatus();
getPasswordFromCookie().then(p => {
@ -116,6 +132,11 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
}
onFFmpegFolder(path: string) {
this.setDaemonSetting('ffmpeg_path', path);
this.findFFmpeg();
}
onKeyFeeChange(newValue: Price) {
this.setDaemonSetting('max_key_fee', newValue);
}
@ -187,9 +208,18 @@ class SettingsPage extends React.PureComponent<Props, State> {
this.props.setDaemonSetting(name, value);
}
clearDaemonSetting(name: string): void {
this.props.clearDaemonSetting(name);
}
findFFmpeg(): void {
this.props.findFFmpeg();
}
render() {
const {
daemonSettings,
ffmpegStatus,
allowAnalytics,
showNsfw,
instantPurchaseEnabled,
@ -213,11 +243,13 @@ class SettingsPage extends React.PureComponent<Props, State> {
clearPlayingUri,
darkModeTimes,
clearCache,
findingFFmpeg,
} = this.props;
const { storedPassword } = this.state;
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
// @if TARGET='app'
const { available: ffmpegAvailable, which: ffmpegPath } = ffmpegStatus;
// @endif
const defaultMaxKeyFee = { currency: 'USD', amount: 50 };
const disableMaxKeyFee = !(daemonSettings && daemonSettings.max_key_fee);
@ -622,7 +654,69 @@ class SettingsPage extends React.PureComponent<Props, State> {
}
/>
)}
{/* @if TARGET='app' */}
<Card
title={
<span>
{__('Experimental Transcoding')}
{findingFFmpeg && <Spinner type="small" />}
</span>
}
actions={
<React.Fragment>
<FileSelector
type="openDirectory"
placeholder={__('A Folder containing FFmpeg')}
currentPath={ffmpegPath || daemonSettings.ffmpeg_path}
onFileChosen={(newDirectory: WebFile) => {
this.onFFmpegFolder(newDirectory.path);
}}
disabled={Boolean(ffmpegPath)}
/>
<p className="help">
{ffmpegAvailable ? (
<I18nMessage
tokens={{
learn_more: (
<Button
button="link"
label={__('Learn more')}
href="https://lbry.com/faq/video-publishing-guide#automatic"
/>
),
}}
>
FFmpeg is correctly configured. %learn_more%
</I18nMessage>
) : (
<I18nMessage
tokens={{
check_again: (
<Button
button="link"
label={__('Check again')}
onClick={() => this.findFFmpeg()}
disabled={findingFFmpeg}
/>
),
learn_more: (
<Button
button="link"
label={__('Learn more')}
href="https://lbry.com/faq/video-publishing-guide#automatic"
/>
),
}}
>
FFmpeg could not be found. Navigate to it or Install, Then %check_again% or quit and restart the
app. %learn_more%
</I18nMessage>
)}
</p>
</React.Fragment>
}
/>
{/* @endif */}
{(!IS_WEB || isAuthenticated) && (
<Card
title={__('Experimental Settings')}

View file

@ -27,6 +27,8 @@ import {
doFetchDaemonSettings,
doSetAutoLaunch,
// doSetDaemonSetting
doFindFFmpeg,
doGetDaemonStatus,
} from 'redux/actions/settings';
import {
selectIsUpgradeSkipped,
@ -347,6 +349,8 @@ export function doDaemonReady() {
// @if TARGET='app'
dispatch(doBalanceSubscribe());
dispatch(doSetAutoLaunch());
dispatch(doFindFFmpeg());
dispatch(doGetDaemonStatus());
dispatch(doFetchDaemonSettings());
dispatch(doFetchFileInfosAndPublishedClaims());
if (!selectIsUpgradeSkipped(state)) {

View file

@ -22,6 +22,19 @@ export function doFetchDaemonSettings() {
};
}
export function doFindFFmpeg() {
return dispatch => {
dispatch({
type: LOCAL_ACTIONS.FINDING_FFMPEG_STARTED,
});
return Lbry.ffmpeg_find().then(done => {
dispatch({
type: LOCAL_ACTIONS.FINDING_FFMPEG_COMPLETED,
});
});
};
}
export function doGetDaemonStatus() {
return dispatch => {
return Lbry.status().then(status => {

View file

@ -1 +0,0 @@
// deleted, moved to lbry-redux

View file

@ -14,11 +14,12 @@ settingLanguage.push('en');
const defaultState = {
isNight: false,
findingFFmpeg: false,
loadedLanguages: [...Object.keys(window.i18n_messages), 'en'] || ['en'],
customWalletServers: [],
sharedPreferences: {},
daemonSettings: {},
daemonStatus: {},
daemonStatus: { ffmpeg_status: {} },
clientSettings: {
// UX
[SETTINGS.NEW_USER_ACKNOWLEDGED]: false,
@ -59,6 +60,16 @@ const defaultState = {
},
};
reducers[ACTIONS.FINDING_FFMPEG_STARTED] = state =>
Object.assign({}, state, {
findingFFmpeg: true,
});
reducers[ACTIONS.FINDING_FFMPEG_COMPLETED] = state =>
Object.assign({}, state, {
findingFFmpeg: false,
});
reducers[LBRY_REDUX_ACTIONS.DAEMON_SETTINGS_RECEIVED] = (state, action) =>
Object.assign({}, state, {
daemonSettings: action.data.settings,

View file

@ -14,6 +14,16 @@ export const selectDaemonStatus = createSelector(
state => state.daemonStatus
);
export const selectFfmpegStatus = createSelector(
selectDaemonStatus,
status => status.ffmpeg_status
);
export const selectFindingFFmpeg = createSelector(
selectState,
state => state.findingFFmpeg || false
);
export const selectClientSettings = createSelector(
selectState,
state => state.clientSettings || {}
@ -62,8 +72,7 @@ export const makeSelectSharedPreferencesForKey = key =>
export const selectHasWalletServerPrefs = createSelector(
makeSelectSharedPreferencesForKey(SHARED_PREFERENCES.WALLET_SERVERS),
servers => {
if (servers && servers.length) return true;
return false;
return !!(servers && servers.length);
}
);

View file

@ -204,7 +204,8 @@ img {
display: block;
font-size: var(--font-small);
color: var(--color-text-help);
margin-top: var(--spacing-small);
margin-top: var(--spacing-miniscule);
margin-bottom: var(--spacing-small);
}
.help--warning {