Add filter support
This commit is contained in:
parent
7849256593
commit
fd209165e5
@ -2,6 +2,10 @@
|
||||
|
||||
#### v1.1.0 > v1.2.0
|
||||
|
||||
- Add video rotation filter ([#347](https://github.com/datarhei/restreamer/discussions/347))
|
||||
- Add video h/v flip filter
|
||||
- Add audio volume filter ([#313](https://github.com/datarhei/restreamer/issues/313))
|
||||
- Add audio loudness normalization filter
|
||||
- Add HLS Master playlist (requires FFmpeg hlsbitrate.patch) (thx Dwaynarang, Electra Player compatibility)
|
||||
- Add linkedIn & Azure Media Services to publication services (thx kalashnikov)
|
||||
- Add AirPlay support with silvermine videojs plugin
|
||||
@ -14,6 +18,7 @@
|
||||
- Add Polish translations (thx Robert Rykała)
|
||||
- Mod extends the datarhei Core publication service with srt streaming
|
||||
- Mod Allow decoders and encoders to set global options
|
||||
- Fix player problem with different stream formats (9:16)
|
||||
- Mod Allow trailing slash on Core address
|
||||
- Fix process report naming
|
||||
- Fix publication service icon styles
|
||||
|
||||
@ -379,7 +379,7 @@
|
||||
<div class="col-xs-12 player-l2">
|
||||
<div class="player-l3">
|
||||
{{#ifEquals player "videojs"}}
|
||||
<video id="player" class="vjs-public video-js player-l4" playsinline></video>
|
||||
<video id="player" class="vjs-public video-js vjs-16-9 player-l4" playsinline></video>
|
||||
{{else}}
|
||||
<div id="player" class="player-l4"></div>
|
||||
{{/ifEquals}}
|
||||
|
||||
@ -239,5 +239,5 @@ EncodingSelect.defaultProps = {
|
||||
codecs: [],
|
||||
availableEncoders: [],
|
||||
availableDecoders: [],
|
||||
onChange: function (encoder, decoder) {},
|
||||
onChange: function (encoder, decoder, automatic) {},
|
||||
};
|
||||
|
||||
134
src/misc/FilterSelect.js
Normal file
134
src/misc/FilterSelect.js
Normal file
@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
// Import all filters (audio/video)
|
||||
import * as Filters from './filters';
|
||||
|
||||
// Import all encoders (audio/video)
|
||||
import * as Encoders from './coders/Encoders';
|
||||
|
||||
export default function FilterSelect(props) {
|
||||
const profile = props.profile;
|
||||
|
||||
// handleFilterChange
|
||||
// what: Filter name
|
||||
// settings (component settings): {Key: Value}
|
||||
// mapping (FFmpeg -af/-vf args): ['String', ...]
|
||||
const handleFilterSettingsChange = (what) => (settings, graph, automatic) => {
|
||||
const filter = profile.filter;
|
||||
|
||||
// Store mapping/settings per component
|
||||
filter.settings[what] = {
|
||||
settings: settings,
|
||||
graph: graph,
|
||||
};
|
||||
|
||||
// Get the order of the filters
|
||||
let filterOrder = [];
|
||||
if (props.type === 'video') {
|
||||
filterOrder = Filters.Video.Filters();
|
||||
} else {
|
||||
filterOrder = Filters.Audio.Filters();
|
||||
}
|
||||
|
||||
// Create the filter graph in the order as the filters are registered
|
||||
const graphs = [];
|
||||
for (let f of filterOrder) {
|
||||
if (!(f in filter.settings)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filter.settings[f].graph.length !== 0) {
|
||||
graphs.push(filter.settings[f].graph);
|
||||
}
|
||||
}
|
||||
|
||||
filter.graph = graphs.join(',');
|
||||
|
||||
props.onChange(filter, automatic);
|
||||
};
|
||||
|
||||
// Set filterRegistry by type
|
||||
let filterRegistry = null;
|
||||
if (props.type === 'video') {
|
||||
filterRegistry = Filters.Video;
|
||||
} else if (props.type === 'audio') {
|
||||
filterRegistry = Filters.Audio;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Checks the state of hwaccel (gpu encoding)
|
||||
let encoderRegistry = null;
|
||||
let hwaccel = false;
|
||||
if (props.type === 'video') {
|
||||
encoderRegistry = Encoders.Video;
|
||||
for (let encoder of encoderRegistry.List()) {
|
||||
if (encoder.codec === props.profile.encoder.coder && encoder.hwaccel) {
|
||||
hwaccel = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates filter components
|
||||
let filterSettings = [];
|
||||
if (!hwaccel) {
|
||||
for (let c of filterRegistry.List()) {
|
||||
// Checks FFmpeg skills (filter is available)
|
||||
if (props.availableFilters.includes(c.filter)) {
|
||||
const Settings = c.component;
|
||||
|
||||
if (!(c.filter in profile.filter.settings)) {
|
||||
profile.filter.settings[c.filter] = c.defaults();
|
||||
} else {
|
||||
profile.filter.settings[c.filter] = {
|
||||
...c.defaults(),
|
||||
...profile.filter.settings[c.filter],
|
||||
};
|
||||
}
|
||||
|
||||
filterSettings.push(
|
||||
<Settings key={c.filter} settings={profile.filter.settings[c.filter].settings} onChange={handleFilterSettingsChange(c.filter)} />
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No suitable filter found
|
||||
if (filterSettings === null && !hwaccel) {
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>No suitable filter found.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
// hwaccel requires further settings
|
||||
} else if (hwaccel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Select your filter settings (optional):</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{filterSettings}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
FilterSelect.defaultProps = {
|
||||
type: '',
|
||||
profile: {},
|
||||
availableFilters: [],
|
||||
onChange: function (filter, automatic) {},
|
||||
};
|
||||
@ -31,6 +31,7 @@ export default function VideoJS(props) {
|
||||
player.addClass('vjs-internal');
|
||||
}
|
||||
player.addClass('video-js');
|
||||
player.addClass('vjs-16-9');
|
||||
} else {
|
||||
// you can update player here [update player through props]
|
||||
// const player = playerRef.current;
|
||||
|
||||
@ -7,9 +7,6 @@ import Audio from '../../settings/Audio';
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
bitrate: '64',
|
||||
channels: '2',
|
||||
layout: 'stereo',
|
||||
sampling: '44100',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
@ -17,18 +14,7 @@ function init(initialState) {
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
let sampling = settings.sampling;
|
||||
let layout = settings.layout;
|
||||
|
||||
if (sampling === 'inherit') {
|
||||
sampling = stream.sampling_hz;
|
||||
}
|
||||
|
||||
if (layout === 'inherit') {
|
||||
layout = stream.layout;
|
||||
}
|
||||
|
||||
const local = ['-codec:a', 'aac', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`];
|
||||
const local = ['-codec:a', 'aac', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
if (stream.codec === 'aac') {
|
||||
local.push('-bsf:a', 'aac_adtstoasc');
|
||||
@ -64,23 +50,6 @@ function Coder(props) {
|
||||
[what]: value,
|
||||
};
|
||||
|
||||
if (what === 'layout') {
|
||||
let channels = stream.channels;
|
||||
|
||||
switch (value) {
|
||||
case 'mono':
|
||||
channels = 1;
|
||||
break;
|
||||
case 'stereo':
|
||||
channels = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newSettings.channels = channels;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
@ -94,12 +63,6 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Audio.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Sampling value={settings.sampling} onChange={update('sampling')} allowInherit allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Layout value={settings.layout} onChange={update('layout')} allowInherit />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -117,7 +80,7 @@ const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.layout}, ${settings.sampling}Hz`;
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
|
||||
@ -7,9 +7,6 @@ import Audio from '../../settings/Audio';
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
bitrate: '64',
|
||||
channels: '2',
|
||||
layout: 'stereo',
|
||||
sampling: '44100',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
@ -17,18 +14,7 @@ function init(initialState) {
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
let sampling = settings.sampling;
|
||||
let layout = settings.layout;
|
||||
|
||||
if (sampling === 'inherit') {
|
||||
sampling = stream.sampling_hz;
|
||||
}
|
||||
|
||||
if (layout === 'inherit') {
|
||||
layout = stream.layout;
|
||||
}
|
||||
|
||||
const local = ['-codec:a', 'aac_at', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`];
|
||||
const local = ['-codec:a', 'aac_at', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
if (stream.codec === 'aac') {
|
||||
local.push('-bsf:a', 'aac_adtstoasc');
|
||||
@ -64,23 +50,6 @@ function Coder(props) {
|
||||
[what]: value,
|
||||
};
|
||||
|
||||
if (what === 'layout') {
|
||||
let channels = stream.channels;
|
||||
|
||||
switch (value) {
|
||||
case 'mono':
|
||||
channels = 1;
|
||||
break;
|
||||
case 'stereo':
|
||||
channels = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newSettings.channels = channels;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
@ -94,12 +63,6 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Audio.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Sampling value={settings.sampling} onChange={update('sampling')} allowCustom allowInherit />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Layout value={settings.layout} onChange={update('layout')} allowInherit />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -117,7 +80,7 @@ const type = 'audio';
|
||||
const hwaccel = true;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.layout}, ${settings.sampling}Hz`;
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
|
||||
@ -7,9 +7,6 @@ import Audio from '../../settings/Audio';
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
bitrate: '64',
|
||||
channels: '2',
|
||||
layout: 'stereo',
|
||||
sampling: '44100',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
@ -17,18 +14,7 @@ function init(initialState) {
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
let sampling = settings.sampling;
|
||||
let layout = settings.layout;
|
||||
|
||||
if (sampling === 'inherit') {
|
||||
sampling = stream.sampling_hz;
|
||||
}
|
||||
|
||||
if (layout === 'inherit') {
|
||||
layout = stream.layout;
|
||||
}
|
||||
|
||||
const local = ['-codec:a', 'libopus', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`];
|
||||
const local = ['-codec:a', 'libopus', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
@ -60,23 +46,6 @@ function Coder(props) {
|
||||
[what]: value,
|
||||
};
|
||||
|
||||
if (what === 'layout') {
|
||||
let channels = stream.channels;
|
||||
|
||||
switch (value) {
|
||||
case 'mono':
|
||||
channels = 1;
|
||||
break;
|
||||
case 'stereo':
|
||||
channels = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newSettings.channels = channels;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
@ -90,12 +59,6 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Audio.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Sampling value={settings.sampling} onChange={update('sampling')} allowInherit allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Layout value={settings.layout} onChange={update('layout')} allowInherit />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -113,7 +76,7 @@ const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.layout}, ${settings.sampling}Hz`;
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
|
||||
@ -7,9 +7,6 @@ import Audio from '../../settings/Audio';
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
bitrate: '64',
|
||||
channels: '2',
|
||||
layout: 'stereo',
|
||||
sampling: '44100',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
@ -17,18 +14,7 @@ function init(initialState) {
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
let sampling = settings.sampling;
|
||||
let layout = settings.layout;
|
||||
|
||||
if (sampling === 'inherit') {
|
||||
sampling = stream.sampling_hz;
|
||||
}
|
||||
|
||||
if (layout === 'inherit') {
|
||||
layout = stream.layout;
|
||||
}
|
||||
|
||||
const local = ['-codec:a', 'libvorbis', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`];
|
||||
const local = ['-codec:a', 'libvorbis', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
@ -60,23 +46,6 @@ function Coder(props) {
|
||||
[what]: value,
|
||||
};
|
||||
|
||||
if (what === 'layout') {
|
||||
let channels = stream.channels;
|
||||
|
||||
switch (value) {
|
||||
case 'mono':
|
||||
channels = 1;
|
||||
break;
|
||||
case 'stereo':
|
||||
channels = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newSettings.channels = channels;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
@ -90,12 +59,6 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Audio.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Sampling value={settings.sampling} onChange={update('sampling')} allowInherit allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Layout value={settings.layout} onChange={update('layout')} allowInherit />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -113,7 +76,7 @@ const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.layout}, ${settings.sampling}Hz`;
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
|
||||
@ -7,9 +7,6 @@ import Audio from '../../settings/Audio';
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
bitrate: '64',
|
||||
channels: '2',
|
||||
layout: 'stereo',
|
||||
sampling: '44100',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
@ -17,19 +14,8 @@ function init(initialState) {
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
let sampling = settings.sampling;
|
||||
let layout = settings.layout;
|
||||
|
||||
if (sampling === 'inherit') {
|
||||
sampling = stream.sampling_hz;
|
||||
}
|
||||
|
||||
if (layout === 'inherit') {
|
||||
layout = stream.layout;
|
||||
}
|
||||
|
||||
// '-qscale:a', '6'
|
||||
const local = ['-codec:a', 'libmp3lame', '-b:a', `${settings.bitrate}k`, '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`];
|
||||
const local = ['-codec:a', 'libmp3lame', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
@ -61,23 +47,6 @@ function Coder(props) {
|
||||
[what]: value,
|
||||
};
|
||||
|
||||
if (what === 'layout') {
|
||||
let channels = stream.channels;
|
||||
|
||||
switch (value) {
|
||||
case 'mono':
|
||||
channels = 1;
|
||||
break;
|
||||
case 'stereo':
|
||||
channels = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newSettings.channels = channels;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
@ -91,12 +60,6 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Audio.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Sampling value={settings.sampling} onChange={update('sampling')} allowInherit allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Layout value={settings.layout} onChange={update('layout')} allowInherit />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -114,7 +77,7 @@ const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.layout}, ${settings.sampling}Hz`;
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
|
||||
@ -13,9 +13,6 @@ function init(initialState) {
|
||||
const state = {
|
||||
bitrate: '64',
|
||||
delay: 'auto',
|
||||
channels: '2',
|
||||
layout: 'stereo',
|
||||
sampling: '44100',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
@ -34,7 +31,7 @@ function createMapping(settings, stream) {
|
||||
layout = stream.layout;
|
||||
}
|
||||
|
||||
const local = ['-codec:a', 'opus', '-b:a', `${settings.bitrate}k`, '-vbr', 'on', '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`];
|
||||
const local = ['-codec:a', 'opus', '-b:a', `${settings.bitrate}k`, '-vbr', 'on', '-shortest'];
|
||||
|
||||
if (settings.delay !== 'auto') {
|
||||
local.push('opus_delay', settings.delay);
|
||||
@ -113,23 +110,6 @@ function Coder(props) {
|
||||
[what]: value,
|
||||
};
|
||||
|
||||
if (what === 'layout') {
|
||||
let channels = stream.channels;
|
||||
|
||||
switch (value) {
|
||||
case 'mono':
|
||||
channels = 1;
|
||||
break;
|
||||
case 'stereo':
|
||||
channels = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newSettings.channels = channels;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
@ -146,12 +126,6 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Delay value={settings.delay} onChange={update('delay')} allowAuto allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Sampling value={settings.sampling} onChange={update('sampling')} allowInherit allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Layout value={settings.layout} onChange={update('layout')} allowInherit />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -169,7 +143,7 @@ const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.layout}, ${settings.sampling}Hz`;
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
|
||||
@ -7,9 +7,6 @@ import Audio from '../../settings/Audio';
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
bitrate: '64',
|
||||
channels: '2',
|
||||
layout: 'stereo',
|
||||
sampling: '44100',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
@ -17,18 +14,7 @@ function init(initialState) {
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
let sampling = settings.sampling;
|
||||
let layout = settings.layout;
|
||||
|
||||
if (sampling === 'inherit') {
|
||||
sampling = stream.sampling_hz;
|
||||
}
|
||||
|
||||
if (layout === 'inherit') {
|
||||
layout = stream.layout;
|
||||
}
|
||||
|
||||
const local = ['-codec:a', 'vorbis', '-b:a', `${settings.bitrate}k`, '-qscale:a', '3', '-shortest', '-af', `aresample=osr=${sampling}:ocl=${layout}`];
|
||||
const local = ['-codec:a', 'vorbis', '-b:a', `${settings.bitrate}k`, '-qscale:a', '3', '-shortest'];
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
@ -60,23 +46,6 @@ function Coder(props) {
|
||||
[what]: value,
|
||||
};
|
||||
|
||||
if (what === 'layout') {
|
||||
let channels = stream.channels;
|
||||
|
||||
switch (value) {
|
||||
case 'mono':
|
||||
channels = 1;
|
||||
break;
|
||||
case 'stereo':
|
||||
channels = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newSettings.channels = channels;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
@ -90,12 +59,6 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Audio.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Sampling value={settings.sampling} onChange={update('sampling')} allowInherit allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Audio.Layout value={settings.layout} onChange={update('layout')} allowInherit />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -113,7 +76,7 @@ const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.layout}, ${settings.sampling}Hz`;
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
|
||||
95
src/misc/filters/audio/Loudnorm.js
Normal file
95
src/misc/filters/audio/Loudnorm.js
Normal file
@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Checkbox from '../../Checkbox';
|
||||
|
||||
// Loudnorm Filter
|
||||
// http://ffmpeg.org/ffmpeg-all.html#loudnorm
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
enabled: false,
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createGraph(settings) {
|
||||
settings = init(settings);
|
||||
|
||||
const mapping = [];
|
||||
|
||||
if (settings.enabled) {
|
||||
mapping.push('loudnorm');
|
||||
}
|
||||
|
||||
return mapping.join(',');
|
||||
}
|
||||
|
||||
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 (
|
||||
<React.Fragment>
|
||||
<Grid item>
|
||||
<Checkbox label={<Trans>Loudness Normalization</Trans>} checked={settings.enabled} onChange={update('enabled')} />
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Filter.defaultProps = {
|
||||
settings: {},
|
||||
onChange: function (settings, graph, automatic) {},
|
||||
};
|
||||
|
||||
const filter = 'loudnorm';
|
||||
const name = 'Loudness Normalization';
|
||||
const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
graph: createGraph(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };
|
||||
226
src/misc/filters/audio/Resample.js
Normal file
226
src/misc/filters/audio/Resample.js
Normal file
@ -0,0 +1,226 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import SelectCustom from '../../../misc/SelectCustom';
|
||||
|
||||
// Resample Filter
|
||||
// https://ffmpeg.org/ffmpeg-filters.html#toc-aresample-1
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
channels: '2',
|
||||
layout: 'stereo',
|
||||
sampling: '44100',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createGraph(settings) {
|
||||
settings = init(settings);
|
||||
|
||||
const mapping = [];
|
||||
|
||||
const sampling = settings.sampling;
|
||||
const layout = settings.layout;
|
||||
|
||||
if (sampling !== 'inherit') {
|
||||
mapping.push(`osr=${sampling}`);
|
||||
}
|
||||
|
||||
if (layout !== 'inherit') {
|
||||
mapping.push(`ocl=${layout}`);
|
||||
}
|
||||
|
||||
if (mapping.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'aresample=' + mapping.join(':');
|
||||
}
|
||||
|
||||
function Layout(props) {
|
||||
const { i18n } = useLingui();
|
||||
const options = [
|
||||
{ value: 'mono', label: 'mono' },
|
||||
{ value: 'stereo', label: 'stereo' },
|
||||
];
|
||||
|
||||
if (props.allowAuto === true) {
|
||||
options.unshift({ value: 'auto', label: 'auto' });
|
||||
}
|
||||
|
||||
if (props.allowInherit === true) {
|
||||
options.unshift({ value: 'inherit', label: i18n._(t`Inherit`) });
|
||||
}
|
||||
|
||||
if (props.allowCustom === true) {
|
||||
options.push({ value: 'custom', label: i18n._(t`Custom ...`) });
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SelectCustom
|
||||
options={options}
|
||||
label={props.label}
|
||||
customLabel={props.customLabel}
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
variant={props.variant}
|
||||
allowCustom={props.allowCustom}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<Trans>The layout of the audio stream.</Trans>
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Layout.defaultProps = {
|
||||
variant: 'outlined',
|
||||
allowAuto: false,
|
||||
allowInherit: false,
|
||||
allowCustom: false,
|
||||
label: <Trans>Layout</Trans>,
|
||||
customLabel: <Trans>Custom layout</Trans>,
|
||||
onChange: function () {},
|
||||
};
|
||||
|
||||
function Sampling(props) {
|
||||
const { i18n } = useLingui();
|
||||
const options = [
|
||||
{ value: '96000', label: '96000 Hz' },
|
||||
{ value: '88200', label: '88200 Hz' },
|
||||
{ value: '48000', label: '48000 Hz' },
|
||||
{ value: '44100', label: '44100 Hz' },
|
||||
{ value: '22050', label: '22050 Hz' },
|
||||
{ value: '8000', label: '8000 Hz' },
|
||||
];
|
||||
|
||||
if (props.allowAuto === true) {
|
||||
options.unshift({ value: 'auto', label: 'auto' });
|
||||
}
|
||||
|
||||
if (props.allowInherit === true) {
|
||||
options.unshift({ value: 'inherit', label: i18n._(t`Inherit`) });
|
||||
}
|
||||
|
||||
if (props.allowCustom === true) {
|
||||
options.push({ value: 'custom', label: i18n._(t`Custom ...`) });
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SelectCustom
|
||||
options={options}
|
||||
label={props.label}
|
||||
customLabel={props.customLabel}
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
variant={props.variant}
|
||||
allowCustom={props.allowCustom}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<Trans>The sample rate of the audio stream.</Trans>
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Sampling.defaultProps = {
|
||||
variant: 'outlined',
|
||||
allowAuto: false,
|
||||
allowInherit: false,
|
||||
allowCustom: false,
|
||||
label: <Trans>Sampling</Trans>,
|
||||
customLabel: <Trans>Custom sampling (Hz)</Trans>,
|
||||
onChange: function () {},
|
||||
};
|
||||
|
||||
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 value = event.target.value;
|
||||
|
||||
const newSettings = {
|
||||
...settings,
|
||||
[what]: value,
|
||||
};
|
||||
|
||||
if (what === 'layout') {
|
||||
let channels = 2;
|
||||
|
||||
switch (value) {
|
||||
case 'mono':
|
||||
channels = 1;
|
||||
break;
|
||||
case 'stereo':
|
||||
channels = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newSettings.channels = channels;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
handleChange(null);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Sampling value={settings.sampling} onChange={update('sampling')} allowInherit allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Layout value={settings.layout} onChange={update('layout')} allowInherit />
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Filter.defaultProps = {
|
||||
settings: {},
|
||||
onChange: function (settings, graph, automatic) {},
|
||||
};
|
||||
|
||||
const filter = 'aresample';
|
||||
const name = 'Resample';
|
||||
const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name} (${settings.layout}, ${settings.sampling}Hz)`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
graph: createGraph(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };
|
||||
162
src/misc/filters/audio/Volume.js
Normal file
162
src/misc/filters/audio/Volume.js
Normal file
@ -0,0 +1,162 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import TextField from '@mui/material/TextField';
|
||||
|
||||
import Select from '../../Select';
|
||||
|
||||
// Volume Filter
|
||||
// http://ffmpeg.org/ffmpeg-all.html#volume
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
level: 'none',
|
||||
db: 0,
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createGraph(settings) {
|
||||
settings = init(settings);
|
||||
|
||||
const mapping = [];
|
||||
|
||||
switch (settings.level) {
|
||||
case 'none':
|
||||
break;
|
||||
case 'custom':
|
||||
mapping.push(`volume=volume=${settings.db}dB`);
|
||||
break;
|
||||
default:
|
||||
mapping.push(`volume=volume=${parseInt(settings.level) / 100}`);
|
||||
break;
|
||||
}
|
||||
|
||||
return mapping.join(',');
|
||||
}
|
||||
|
||||
function VolumeLevel(props) {
|
||||
return (
|
||||
<Select label={<Trans>Volume</Trans>} value={props.value} onChange={props.onChange}>
|
||||
<MenuItem value="none">
|
||||
<Trans>None</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="10">10%</MenuItem>
|
||||
<MenuItem value="20">20%</MenuItem>
|
||||
<MenuItem value="30">30%</MenuItem>
|
||||
<MenuItem value="40">40%</MenuItem>
|
||||
<MenuItem value="50">50%</MenuItem>
|
||||
<MenuItem value="60">60%</MenuItem>
|
||||
<MenuItem value="70">70%</MenuItem>
|
||||
<MenuItem value="80">80%</MenuItem>
|
||||
<MenuItem value="90">90%</MenuItem>
|
||||
<MenuItem value="100">100%</MenuItem>
|
||||
<MenuItem value="custom">
|
||||
<Trans>Custom ...</Trans>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
VolumeLevel.defaultProps = {
|
||||
value: '',
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
function VolumeDB(props) {
|
||||
return (
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label={<Trans>Decibels (dB)</Trans>}
|
||||
type="number"
|
||||
value={props.value}
|
||||
disabled={props.disabled}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
VolumeDB.defaultProps = {
|
||||
value: '',
|
||||
disabled: false,
|
||||
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,
|
||||
[what]: event.target.value,
|
||||
};
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
handleChange(null);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={6}>
|
||||
<VolumeLevel value={settings.level} onChange={update('level')} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<VolumeDB value={settings.db} onChange={update('db')} disabled={settings.level !== 'custom'} />
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Filter.defaultProps = {
|
||||
settings: {},
|
||||
onChange: function (settings, graph, automatic) {},
|
||||
};
|
||||
|
||||
const filter = 'volume';
|
||||
const name = 'Volume';
|
||||
const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
let summary = `${name} (`;
|
||||
|
||||
if (settings.level === 'custom') {
|
||||
summary += `${settings.db}dB`;
|
||||
} else {
|
||||
summary += `${settings.level}%`;
|
||||
}
|
||||
|
||||
summary += ')';
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
graph: createGraph(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };
|
||||
57
src/misc/filters/index.js
Normal file
57
src/misc/filters/index.js
Normal file
@ -0,0 +1,57 @@
|
||||
// Audio Filter
|
||||
import * as AResample from './audio/Resample';
|
||||
import * as Volume from './audio/Volume';
|
||||
import * as Loudnorm from './audio/Loudnorm';
|
||||
|
||||
// Video Filter
|
||||
import * as Transpose from './video/Transpose';
|
||||
import * as HFlip from './video/HFlip';
|
||||
import * as VFlip from './video/VFlip';
|
||||
|
||||
// Register filters type: audio/video
|
||||
class Registry {
|
||||
constructor(type) {
|
||||
this.type = type;
|
||||
this.services = new Map();
|
||||
}
|
||||
|
||||
Register(service) {
|
||||
if (service.type !== this.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.services.set(service.filter, service);
|
||||
}
|
||||
|
||||
Get(filter) {
|
||||
const service = this.services.get(filter);
|
||||
if (service) {
|
||||
return service;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Filters() {
|
||||
return Array.from(this.services.keys());
|
||||
}
|
||||
|
||||
List() {
|
||||
return Array.from(this.services.values());
|
||||
}
|
||||
}
|
||||
|
||||
// Audio Filters
|
||||
const audioRegistry = new Registry('audio');
|
||||
audioRegistry.Register(AResample);
|
||||
audioRegistry.Register(Volume);
|
||||
audioRegistry.Register(Loudnorm);
|
||||
|
||||
// Video Filters
|
||||
const videoRegistry = new Registry('video');
|
||||
videoRegistry.Register(Transpose);
|
||||
videoRegistry.Register(HFlip);
|
||||
videoRegistry.Register(VFlip);
|
||||
|
||||
// Export registrys for ../SelectFilters.js
|
||||
export { audioRegistry as Audio, videoRegistry as Video };
|
||||
93
src/misc/filters/video/HFlip.js
Normal file
93
src/misc/filters/video/HFlip.js
Normal file
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Checkbox from '../../Checkbox';
|
||||
|
||||
// HFlip Filter
|
||||
// http://ffmpeg.org/ffmpeg-all.html#hflip
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
enabled: false,
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createGraph(settings) {
|
||||
settings = init(settings);
|
||||
|
||||
const mapping = [];
|
||||
|
||||
if (settings.enabled) {
|
||||
mapping.push('hflip');
|
||||
}
|
||||
|
||||
return mapping.join(',');
|
||||
}
|
||||
|
||||
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 (
|
||||
<Grid item>
|
||||
<Checkbox label={<Trans>Horizontal Flip</Trans>} checked={settings.enabled} onChange={update('enabled')} />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
Filter.defaultProps = {
|
||||
settings: {},
|
||||
onChange: function (settings, graph, automatic) {},
|
||||
};
|
||||
|
||||
const filter = 'hflip';
|
||||
const name = 'Horizonal Flip';
|
||||
const type = 'video';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
graph: createGraph(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };
|
||||
117
src/misc/filters/video/Transpose.js
Normal file
117
src/misc/filters/video/Transpose.js
Normal file
@ -0,0 +1,117 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
|
||||
import Select from '../../Select';
|
||||
|
||||
// Transpose Filter
|
||||
// http://ffmpeg.org/ffmpeg-all.html#transpose-1
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
value: 'none',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createGraph(settings) {
|
||||
settings = init(settings);
|
||||
|
||||
const mapping = [];
|
||||
|
||||
switch (settings.value) {
|
||||
case '90':
|
||||
mapping.push('transpose=dir=clock:passthrough=none');
|
||||
break;
|
||||
case '180':
|
||||
mapping.push('transpose=dir=clock:passthrough=none', 'transpose=dir=clock:passthrough=none');
|
||||
break;
|
||||
case '270':
|
||||
mapping.push('transpose=dir=cclock:passthrough=none');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return mapping.join(',');
|
||||
}
|
||||
|
||||
// filter
|
||||
function Rotate(props) {
|
||||
return (
|
||||
<Select label={<Trans>Rotate</Trans>} value={props.value} onChange={props.onChange}>
|
||||
<MenuItem value="none">None</MenuItem>
|
||||
<MenuItem value="90">90°</MenuItem>
|
||||
<MenuItem value="180">180°</MenuItem>
|
||||
<MenuItem value="270">270°</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
Rotate.defaultProps = {
|
||||
value: '',
|
||||
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,
|
||||
[what]: event.target.value,
|
||||
};
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
handleChange(null);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Rotate value={settings.value} onChange={update('value')} allowCustom />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
Filter.defaultProps = {
|
||||
settings: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
const filter = 'transpose';
|
||||
const name = 'Transpose';
|
||||
const type = 'video';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name} (${settings.value}° clockwise)`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
graph: createGraph(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };
|
||||
93
src/misc/filters/video/VFlip.js
Normal file
93
src/misc/filters/video/VFlip.js
Normal file
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Checkbox from '../../Checkbox';
|
||||
|
||||
// VFlip Filter
|
||||
// http://ffmpeg.org/ffmpeg-all.html#vflip
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
enabled: false,
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createGraph(settings) {
|
||||
settings = init(settings);
|
||||
|
||||
const mapping = [];
|
||||
|
||||
if (settings.enabled) {
|
||||
mapping.push('vflip');
|
||||
}
|
||||
|
||||
return mapping.join(',');
|
||||
}
|
||||
|
||||
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 (
|
||||
<Grid item>
|
||||
<Checkbox label={<Trans>Vertical Flip</Trans>} checked={settings.enabled} onChange={update('enabled')} />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
Filter.defaultProps = {
|
||||
settings: {},
|
||||
onChange: function (settings, graph, automatic) {},
|
||||
};
|
||||
|
||||
const filter = 'vflip';
|
||||
const name = 'Vertical Flip';
|
||||
const type = 'video';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
graph: createGraph(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };
|
||||
@ -3,7 +3,7 @@
|
||||
Ingest Metadata Layout:
|
||||
|
||||
data = {
|
||||
version: 1,
|
||||
version: "1.2.0",
|
||||
meta: {
|
||||
name: 'Livestream 1',
|
||||
description: 'Live from earth. Powered by datarhei/restreamer.',
|
||||
@ -99,12 +99,24 @@ data = {
|
||||
'-codec:a', 'aac',
|
||||
'-b:a', '64k',
|
||||
'-bsf:a', 'aac_adtstoasc',
|
||||
'-shortest',
|
||||
'-af', 'aresample=osr=44100:ocl=2'
|
||||
'-shortest'
|
||||
]
|
||||
}
|
||||
},
|
||||
decoder: null,
|
||||
filter: {
|
||||
graph: 'aresample=osr=44100:ocl=stereo',
|
||||
settings: {
|
||||
aresample: {
|
||||
graph: 'aresample=osr=44100:ocl=stereo',
|
||||
settings: {
|
||||
channels: 2,
|
||||
layout: 'stereo',
|
||||
sampling: 44100
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
video: {
|
||||
source: 0,
|
||||
@ -124,6 +136,7 @@ data = {
|
||||
}
|
||||
},
|
||||
decoder: null,
|
||||
filter: null,
|
||||
},
|
||||
"or": {},
|
||||
"video": {
|
||||
@ -210,7 +223,7 @@ data = {
|
||||
Egress Metadata Layout:
|
||||
|
||||
data = {
|
||||
version: 1,
|
||||
version: "1.2.0",
|
||||
name: "foobar",
|
||||
control: {
|
||||
process: {
|
||||
@ -231,15 +244,20 @@ data = {
|
||||
|
||||
*/
|
||||
|
||||
import SemverGt from 'semver/functions/gt';
|
||||
import SemverCompare from 'semver/functions/compare';
|
||||
|
||||
import * as Coders from '../misc/coders/Encoders';
|
||||
import * as Filters from '../misc/filters';
|
||||
import * as version from '../version';
|
||||
|
||||
const defaultMetadata = {
|
||||
version: 1,
|
||||
version: version.Version,
|
||||
playersite: {},
|
||||
};
|
||||
|
||||
const defaultIngestMetadata = {
|
||||
version: 1,
|
||||
version: version.Version,
|
||||
sources: [],
|
||||
profiles: [{}],
|
||||
streams: [],
|
||||
@ -283,7 +301,7 @@ const defaultIngestMetadata = {
|
||||
};
|
||||
|
||||
const defaultEgressMetadata = {
|
||||
version: 1,
|
||||
version: version.Version,
|
||||
name: '',
|
||||
control: {
|
||||
process: {
|
||||
@ -317,6 +335,12 @@ const getDefaultEgressMetadata = () => {
|
||||
return JSON.parse(JSON.stringify(defaultEgressMetadata));
|
||||
};
|
||||
|
||||
const initMetadata = (initialMetadata) => {
|
||||
return mergeMetadata(initialMetadata);
|
||||
};
|
||||
|
||||
const transformers = {};
|
||||
|
||||
const mergeMetadata = (metadata, base) => {
|
||||
if (!metadata) {
|
||||
metadata = {};
|
||||
@ -333,28 +357,57 @@ const mergeMetadata = (metadata, base) => {
|
||||
...metadata,
|
||||
};
|
||||
|
||||
if (metadata.version !== defaultMetadata.version) {
|
||||
metadata = {
|
||||
...defaultMetadata,
|
||||
};
|
||||
}
|
||||
|
||||
metadata.playersite = {
|
||||
...base.playersite,
|
||||
...metadata.playersite,
|
||||
};
|
||||
|
||||
return metadata;
|
||||
};
|
||||
metadata = transformMetadata(metadata, defaultMetadata.version, transformers);
|
||||
|
||||
const initMetadata = (initialMetadata) => {
|
||||
return mergeMetadata(initialMetadata);
|
||||
return metadata;
|
||||
};
|
||||
|
||||
const initIngestMetadata = (initialMetadata) => {
|
||||
return mergeIngestMetadata(initialMetadata);
|
||||
};
|
||||
|
||||
const ingestTransformers = {
|
||||
'1.2.0': (metadata) => {
|
||||
for (let p = 0; p < metadata.profiles.length; p++) {
|
||||
const profile = metadata.profiles[p];
|
||||
|
||||
if (profile.audio.encoder.coder === 'copy' || profile.audio.encoder.coder === 'none') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const settings = profile.audio.encoder.settings;
|
||||
|
||||
profile.audio.filter = {
|
||||
settings: {
|
||||
aresample: {
|
||||
settings: {
|
||||
channels: settings.channels,
|
||||
layout: settings.layout,
|
||||
sampling: settings.sampling,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
delete profile.audio.encoder.settings.channels;
|
||||
delete profile.audio.encoder.settings.layout;
|
||||
delete profile.audio.encoder.settings.sampling;
|
||||
|
||||
profile.audio.filter.settings.aresample.graph = Filters.Audio.Get('aresample').createGraph(profile.audio.filter.settings.aresample.settings);
|
||||
profile.audio.filter.graph = profile.audio.filter.settings.aresample.graph;
|
||||
}
|
||||
|
||||
metadata.version = '1.2.0';
|
||||
|
||||
return metadata;
|
||||
},
|
||||
};
|
||||
|
||||
const mergeIngestMetadata = (metadata, base) => {
|
||||
if (!metadata) {
|
||||
metadata = {};
|
||||
@ -371,12 +424,6 @@ const mergeIngestMetadata = (metadata, base) => {
|
||||
...metadata,
|
||||
};
|
||||
|
||||
if (metadata.version !== defaultMetadata.version) {
|
||||
metadata = {
|
||||
...defaultMetadata,
|
||||
};
|
||||
}
|
||||
|
||||
metadata.meta = {
|
||||
...base.meta,
|
||||
...metadata.meta,
|
||||
@ -436,6 +483,8 @@ const mergeIngestMetadata = (metadata, base) => {
|
||||
}
|
||||
}
|
||||
|
||||
metadata = transformMetadata(metadata, defaultMetadata.version, ingestTransformers);
|
||||
|
||||
return metadata;
|
||||
};
|
||||
|
||||
@ -443,6 +492,8 @@ const initEgressMetadata = (initialMetadata) => {
|
||||
return mergeEgressMetadata(initialMetadata);
|
||||
};
|
||||
|
||||
const egressTransformers = {};
|
||||
|
||||
const mergeEgressMetadata = (metadata, base) => {
|
||||
if (!metadata) {
|
||||
metadata = {};
|
||||
@ -459,12 +510,6 @@ const mergeEgressMetadata = (metadata, base) => {
|
||||
...metadata,
|
||||
};
|
||||
|
||||
if (metadata.version !== defaultMetadata.version) {
|
||||
metadata = {
|
||||
...defaultMetadata,
|
||||
};
|
||||
}
|
||||
|
||||
metadata.control = {
|
||||
...base.control,
|
||||
...metadata.control,
|
||||
@ -504,6 +549,8 @@ const mergeEgressMetadata = (metadata, base) => {
|
||||
}
|
||||
}
|
||||
|
||||
metadata = transformMetadata(metadata, defaultMetadata.version, egressTransformers);
|
||||
|
||||
return metadata;
|
||||
};
|
||||
|
||||
@ -598,7 +645,19 @@ const createInputsOutputs = (sources, profiles) => {
|
||||
|
||||
global = [...global, ...profile.video.encoder.mapping.global];
|
||||
|
||||
const options = ['-map', index + ':' + stream.stream, ...profile.video.encoder.mapping.local];
|
||||
const local = profile.video.encoder.mapping.local.slice();
|
||||
|
||||
if (profile.video.filter.graph.length !== 0) {
|
||||
// Check if there's already a video filter in the local mapping
|
||||
let filterIndex = local.indexOf('-filter:v');
|
||||
if (filterIndex !== -1) {
|
||||
local[filterIndex + 1] += ',' + profile.video.filter.graph;
|
||||
} else {
|
||||
local.unshift('-filter:v', profile.video.filter.graph);
|
||||
}
|
||||
}
|
||||
|
||||
const options = ['-map', index + ':' + stream.stream, ...local];
|
||||
|
||||
if (profile.audio.encoder.coder !== 'none' && profile.audio.source !== -1 && profile.audio.stream !== -1) {
|
||||
global = [...global, ...profile.audio.decoder.mapping.global];
|
||||
@ -620,7 +679,19 @@ const createInputsOutputs = (sources, profiles) => {
|
||||
|
||||
global = [...global, ...profile.audio.encoder.mapping.global];
|
||||
|
||||
options.push('-map', index + ':' + stream.stream, ...profile.audio.encoder.mapping.local);
|
||||
const local = profile.audio.encoder.mapping.local.slice();
|
||||
|
||||
if (profile.audio.filter.graph.length !== 0) {
|
||||
// Check if there's already a audio filter in the local mapping
|
||||
let filterIndex = local.indexOf('-filter:a');
|
||||
if (filterIndex !== -1) {
|
||||
local[filterIndex + 1] += ',' + profile.audio.filter.graph;
|
||||
} else {
|
||||
local.unshift('-filter:a', profile.audio.filter.graph);
|
||||
}
|
||||
}
|
||||
|
||||
options.push('-map', index + ':' + stream.stream, ...local);
|
||||
} else {
|
||||
options.push('-an');
|
||||
}
|
||||
@ -746,6 +817,7 @@ const initProfile = (initialProfile) => {
|
||||
stream: -1,
|
||||
encoder: {},
|
||||
decoder: {},
|
||||
filter: {},
|
||||
...profile.video,
|
||||
};
|
||||
|
||||
@ -790,11 +862,18 @@ const initProfile = (initialProfile) => {
|
||||
};
|
||||
}
|
||||
|
||||
profile.video.filter = {
|
||||
graph: '',
|
||||
settings: {},
|
||||
...profile.video.filter,
|
||||
};
|
||||
|
||||
profile.audio = {
|
||||
source: -1,
|
||||
stream: -1,
|
||||
encoder: {},
|
||||
decoder: {},
|
||||
filter: {},
|
||||
...profile.audio,
|
||||
};
|
||||
|
||||
@ -838,6 +917,12 @@ const initProfile = (initialProfile) => {
|
||||
};
|
||||
}
|
||||
|
||||
profile.audio.filter = {
|
||||
graph: '',
|
||||
settings: {},
|
||||
...profile.audio.filter,
|
||||
};
|
||||
|
||||
profile.custom = {
|
||||
selected: profile.audio.source === 1,
|
||||
stream: profile.audio.source === 1 ? -2 : profile.audio.stream,
|
||||
@ -1113,6 +1198,39 @@ const cleanupProfile = (profile) => {
|
||||
};
|
||||
};
|
||||
|
||||
const transformMetadata = (metadata, targetVersion, transformers) => {
|
||||
if (metadata.version === 1) {
|
||||
metadata.version = '1.0.0';
|
||||
}
|
||||
|
||||
if (targetVersion === 1) {
|
||||
targetVersion = '1.0.0';
|
||||
}
|
||||
|
||||
if (metadata.version === targetVersion) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
// Create a list of all transformers that are greater than the current version
|
||||
// and sort them in ascending order.
|
||||
const tlist = [];
|
||||
|
||||
for (let v in transformers) {
|
||||
if (SemverGt(v, metadata.version)) {
|
||||
tlist.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
tlist.sort(SemverCompare);
|
||||
|
||||
// Apply all found transformers
|
||||
for (let t of tlist) {
|
||||
metadata = transformers[t](metadata);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
};
|
||||
|
||||
export {
|
||||
getDefaultMetadata,
|
||||
getDefaultIngestMetadata,
|
||||
|
||||
@ -502,6 +502,7 @@ class Restreamer {
|
||||
input: [],
|
||||
output: [],
|
||||
},
|
||||
filter: [],
|
||||
sources: {
|
||||
network: [],
|
||||
virtualaudio: [],
|
||||
@ -523,6 +524,7 @@ class Restreamer {
|
||||
formats: {},
|
||||
protocols: {},
|
||||
devices: {},
|
||||
filter: [],
|
||||
...val,
|
||||
};
|
||||
|
||||
@ -565,6 +567,10 @@ class Restreamer {
|
||||
skills.decoders.video.push(hwaccel.id);
|
||||
}
|
||||
|
||||
for (let filter of val.filter) {
|
||||
skills.filter.push(filter.id);
|
||||
}
|
||||
|
||||
val.formats = {
|
||||
demuxers: [],
|
||||
muxers: [],
|
||||
|
||||
@ -3,5 +3,6 @@ import { name, version, bundle } from '../package.json';
|
||||
const Core = '^16.9.0';
|
||||
const FFmpeg = '^4.1.0 || ^5.0.0';
|
||||
const UI = bundle ? bundle : name + ' v' + version;
|
||||
const Version = version;
|
||||
|
||||
export { Core, FFmpeg, UI };
|
||||
export { Core, FFmpeg, UI, Version };
|
||||
|
||||
@ -18,6 +18,8 @@ import ProbeModal from '../../misc/modals/Probe';
|
||||
import SourceSelect from './SourceSelect';
|
||||
import StreamSelect from './StreamSelect';
|
||||
|
||||
import FilterSelect from '../../misc/FilterSelect';
|
||||
|
||||
export default function Source(props) {
|
||||
const [$sources, setSources] = React.useState({
|
||||
video: M.initSource('video', props.sources[0]),
|
||||
@ -212,6 +214,17 @@ export default function Source(props) {
|
||||
});
|
||||
};
|
||||
|
||||
const handleFilter = (type) => (filter) => {
|
||||
const profile = $profile[type];
|
||||
|
||||
profile.filter = filter;
|
||||
|
||||
setProfile({
|
||||
...$profile,
|
||||
[type]: profile,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDone = () => {
|
||||
const sources = M.cleanupSources($sources);
|
||||
const profile = M.cleanupProfile($profile);
|
||||
@ -395,6 +408,16 @@ export default function Source(props) {
|
||||
onChange={handleEncoding('video')}
|
||||
/>
|
||||
</Grid>
|
||||
{$profile.video.encoder.coder !== 'none' && $profile.video.encoder.coder !== 'copy' && (
|
||||
<Grid item xs={12}>
|
||||
<FilterSelect
|
||||
type="video"
|
||||
profile={$profile.video}
|
||||
availableFilters={props.skills.filter}
|
||||
onChange={handleFilter('video')}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
@ -457,6 +480,16 @@ export default function Source(props) {
|
||||
onChange={handleEncoding('audio')}
|
||||
/>
|
||||
</Grid>
|
||||
{$profile.audio.encoder.coder !== 'none' && $profile.audio.encoder.coder !== 'copy' && (
|
||||
<Grid item xs={12}>
|
||||
<FilterSelect
|
||||
type="audio"
|
||||
profile={$profile.audio}
|
||||
availableFilters={props.skills.filter}
|
||||
onChange={handleFilter('audio')}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{$profile.custom.selected === true && (
|
||||
@ -524,6 +557,16 @@ export default function Source(props) {
|
||||
onChange={handleEncoding('audio')}
|
||||
/>
|
||||
</Grid>
|
||||
{$profile.audio.encoder.coder !== 'none' && $profile.audio.encoder.coder !== 'copy' && (
|
||||
<Grid item xs={12}>
|
||||
<FilterSelect
|
||||
type="audio"
|
||||
profile={$profile.audio}
|
||||
availableFilters={props.skills.filter}
|
||||
onChange={handleFilter('audio')}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
||||
@ -6,6 +6,7 @@ import Typography from '@mui/material/Typography';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import * as Coders from '../../misc/coders/Encoders';
|
||||
import * as Filters from '../../misc/filters';
|
||||
import BoxText from '../../misc/BoxText';
|
||||
import Sources from './Sources';
|
||||
|
||||
@ -28,6 +29,7 @@ export default function Summary(props) {
|
||||
let name = i18n._(t`No source selected`);
|
||||
let address = '';
|
||||
let encodingSummary = i18n._(t`None`);
|
||||
let filterSummary = [];
|
||||
|
||||
let showEncoding = false;
|
||||
|
||||
@ -51,6 +53,30 @@ export default function Summary(props) {
|
||||
if (coder !== null) {
|
||||
encodingSummary = coder.summarize(profile.encoder.settings);
|
||||
}
|
||||
|
||||
if (profile.filter.graph.length !== 0) {
|
||||
let filters = null;
|
||||
|
||||
if (props.type === 'video') {
|
||||
filters = Filters.Video;
|
||||
} else if (props.type === 'audio') {
|
||||
filters = Filters.Audio;
|
||||
}
|
||||
|
||||
for (let filter of filters.List()) {
|
||||
const name = filter.filter;
|
||||
|
||||
if (!(name in profile.filter.settings)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.filter.settings[name].graph.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filterSummary.push(filter.summarize(profile.filter.settings[name].settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@ -61,12 +87,26 @@ export default function Summary(props) {
|
||||
<Typography variant="body1">{address}</Typography>
|
||||
</Grid>
|
||||
{showEncoding === true && (
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="subtitle2">
|
||||
<Trans>Encoding</Trans>
|
||||
</Typography>
|
||||
<Typography variant="body1">{encodingSummary}</Typography>
|
||||
</Grid>
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="subtitle2">
|
||||
<Trans>Encoding</Trans>
|
||||
</Typography>
|
||||
<Typography variant="body1">{encodingSummary}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="subtitle2">
|
||||
<Trans>Filter</Trans>
|
||||
</Typography>
|
||||
{filterSummary.length ? (
|
||||
<Typography variant="body1">{filterSummary.join(', ')}</Typography>
|
||||
) : (
|
||||
<Typography variant="body1">
|
||||
<Trans>None</Trans>
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</BoxText>
|
||||
|
||||
@ -97,12 +97,12 @@ export default function Edit(props) {
|
||||
setProcess(proc);
|
||||
|
||||
let metadata = await props.restreamer.GetIngestMetadata(_channelid);
|
||||
if (metadata.version && metadata.version === 1) {
|
||||
setData({
|
||||
...$data,
|
||||
...metadata,
|
||||
});
|
||||
}
|
||||
setData({
|
||||
...$data,
|
||||
...metadata,
|
||||
});
|
||||
|
||||
console.log(metadata);
|
||||
|
||||
const skills = await props.restreamer.Skills();
|
||||
setSkills(skills);
|
||||
|
||||
@ -101,12 +101,10 @@ export default function Main(props) {
|
||||
setConfig(config);
|
||||
|
||||
const metadata = await props.restreamer.GetIngestMetadata(_channelid);
|
||||
if (metadata.version && metadata.version === 1) {
|
||||
setMetadata({
|
||||
...$metadata,
|
||||
...metadata,
|
||||
});
|
||||
}
|
||||
setMetadata({
|
||||
...$metadata,
|
||||
...metadata,
|
||||
});
|
||||
|
||||
await update();
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user