diff --git a/CHANGELOG.md b/CHANGELOG.md index 97cdb5e..6746981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v1.8.0 > vx.x.x - Mod uses placeholders for ingress setups ([#560](https://github.com/datarhei/restreamer-ui/issues/560)) +- Add frame interpolation (framerate) filter ## v1.7.0 > v1.8.0 diff --git a/src/misc/filters/index.js b/src/misc/filters/index.js index 728982d..fe4b284 100644 --- a/src/misc/filters/index.js +++ b/src/misc/filters/index.js @@ -6,6 +6,7 @@ import * as Loudnorm from './audio/Loudnorm'; // Video Filter import * as Bwdif from './video/Bwdif'; +import * as Framerate from './video/Framerate'; import * as Scale from './video/Scale'; import * as Transpose from './video/Transpose'; import * as HFlip from './video/HFlip'; @@ -54,6 +55,7 @@ audioRegistry.Register(Loudnorm); // Video Filters const videoRegistry = new Registry('video'); videoRegistry.Register(Bwdif); +videoRegistry.Register(Framerate); videoRegistry.Register(Scale); videoRegistry.Register(Transpose); videoRegistry.Register(HFlip); diff --git a/src/misc/filters/video/Framerate.js b/src/misc/filters/video/Framerate.js new file mode 100644 index 0000000..0243e19 --- /dev/null +++ b/src/misc/filters/video/Framerate.js @@ -0,0 +1,143 @@ +import React from 'react'; + +import { useLingui } from '@lingui/react'; +import { Trans, t } from '@lingui/macro'; +import Grid from '@mui/material/Grid'; + +import Checkbox from '../../Checkbox'; +import SelectCustom from '../../../misc/SelectCustom'; + +// Framerate Filter +// http://ffmpeg.org/ffmpeg-all.html#framerate + +function init(initialState) { + const state = { + enabled: false, + fps: '30', + ...initialState, + }; + + return state; +} + +function createGraph(settings) { + settings = init(settings); + + const mapping = []; + + if (settings.enabled) { + mapping.push(`framerate=fps=${settings.fps}`); + } + + return mapping.join(','); +} + +function Framerate(props) { + const { i18n } = useLingui(); + const sizes = [ + { value: '60', label: '60' }, + { value: '59.94', label: '59.94' }, + { value: '50', label: '50' }, + { value: '30', label: '30' }, + { value: '29.97', label: '29.97 (NTSC)' }, + { value: '25', label: '25 (PAL)' }, + { value: '24', label: '24 (Film)' }, + { value: '23.97', label: '23.97 (NTSC Film)' }, + { value: '15', label: '15' }, + { value: '10', label: '10' }, + { value: 'custom', label: i18n._(t`Custom ...`) }, + ]; + + return ( + + ); +} + +Framerate.defaultProps = { + label: Framerate, + customLabel: Custom framerate, + value: '', + variant: 'outlined', + allowCustom: true, + onChange: function (event) {}, +}; + +function Filter(props) { + const settings = init(props.settings); + + const handleChange = (newSettings) => { + let automatic = false; + if (!newSettings) { + newSettings = settings; + automatic = true; + } + + props.onChange(newSettings, createGraph(newSettings), automatic); + }; + + const update = (what) => (event) => { + const newSettings = { + ...settings, + }; + if (['enabled'].includes(what)) { + newSettings[what] = !settings.enabled; + } else { + newSettings[what] = event.target.value; + } + + handleChange(newSettings); + }; + + React.useEffect(() => { + handleChange(null); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + Framerate conversion (frame interpolation)} checked={settings.enabled} onChange={update('enabled')} /> + + {settings.enabled && ( + + + + + + )} + + ); +} + +Filter.defaultProps = { + settings: {}, + onChange: function (settings, mapping) {}, +}; + +const filter = 'fps'; +const name = 'Frame Interpolation'; +const type = 'video'; +const hwaccel = false; + +function summarize(settings) { + return `${name} (${settings.fps}fps)`; +} + +function defaults() { + const settings = init({}); + + return { + settings: settings, + graph: createGraph(settings), + }; +} + +export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };