Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to include filter on overlay and widget installs #5126

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 42 additions & 15 deletions app/components-react/pages/BrowseOverlays.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { GuestApiHandler } from 'util/guest-api-handler';
import { IDownloadProgress } from 'util/requests';
import * as remote from '@electron/remote';
import { Services } from 'components-react/service-provider';
import { IOverlayFilter } from 'services/source-filters';

interface IOverlayOptions {
mergePlatform?: boolean;
filter?: IOverlayFilter;
}

export default function BrowseOverlays(p: {
params: { type?: 'overlay' | 'widget-themes' | 'site-themes'; id?: string; install?: string };
Expand All @@ -24,13 +30,18 @@ export default function BrowseOverlays(p: {
NotificationsService,
JsonrpcService,
RestreamService,
SourceFiltersService,
} = Services;
const [downloading, setDownloading] = useState(false);
const [overlaysUrl, setOverlaysUrl] = useState('');

useEffect(() => {
async function getOverlaysUrl() {
const url = await UserService.actions.return.overlaysUrl(p.params?.type, p.params?.id, p.params?.install);
const url = await UserService.actions.return.overlaysUrl(
p.params?.type,
p.params?.id,
p.params?.install,
);
if (!url) return;
setOverlaysUrl(url);
}
Expand Down Expand Up @@ -70,13 +81,14 @@ export default function BrowseOverlays(p: {
url: string,
name: string,
progressCallback?: (progress: IDownloadProgress) => void,
mergePlatform = false,
opts: IOverlayOptions = { mergePlatform: false },
) {
try {
await installOverlayBase(url, name, progressCallback, mergePlatform);
await installOverlayBase(url, name, progressCallback, opts);
NavigationService.actions.navigate('Studio');
} catch(e) {
} catch (e: unknown) {
// If the overlay requires platform merge, navigate to the platform merge page
// @ts-ignore we know this type of error
if (e.message === 'REQUIRES_PLATFORM_MERGE') {
NavigationService.actions.navigate('PlatformMerge', { overlayUrl: url, overlayName: name });
} else {
Expand All @@ -89,7 +101,7 @@ export default function BrowseOverlays(p: {
url: string,
name: string,
progressCallback?: (progress: IDownloadProgress) => void,
mergePlatform = false
opts: IOverlayOptions = { mergePlatform: false },
) {
return new Promise<void>((resolve, reject) => {
const host = new urlLib.URL(url).hostname;
Expand All @@ -106,7 +118,7 @@ export default function BrowseOverlays(p: {
// User should be eligible to enable restream for this behavior to work.
// If restream is already set up, then just install as normal.
if (
mergePlatform &&
opts.mergePlatform &&
UserService.state.auth?.platforms.facebook &&
RestreamService.views.canEnableRestream &&
!RestreamService.shouldGoLiveWithRestream
Expand All @@ -115,12 +127,18 @@ export default function BrowseOverlays(p: {
} else {
setDownloading(true);
const sub = SceneCollectionsService.downloadProgress.subscribe(progressCallback);
SceneCollectionsService.actions.return.installOverlay(url, name)
SceneCollectionsService.actions.return
.installOverlay(url, name)
.then(() => {
sub.unsubscribe();
setDownloading(false);
resolve();
})
.then(() => {
if (opts.filter) {
SourceFiltersService.actions.applyFilterToOverlay(opts.filter);
}
})
.catch((e: unknown) => {
sub.unsubscribe();
setDownloading(false);
Expand All @@ -130,8 +148,8 @@ export default function BrowseOverlays(p: {
});
}

async function installWidgets(urls: string[]) {
await installWidgetsBase(urls);
async function installWidgets(urls: string[], opts?: IOverlayOptions) {
await installWidgetsBase(urls, opts);
NavigationService.actions.navigate('Studio');

NotificationsService.actions.push({
Expand All @@ -146,7 +164,7 @@ export default function BrowseOverlays(p: {
});
}

async function installWidgetsBase(urls: string[]) {
async function installWidgetsBase(urls: string[], opts?: IOverlayOptions) {
for (const url of urls) {
const host = new urlLib.URL(url).hostname;
const trustedHosts = ['cdn.streamlabs.com'];
Expand All @@ -157,16 +175,25 @@ export default function BrowseOverlays(p: {
}

const path = await OverlaysPersistenceService.actions.return.downloadOverlay(url);
await WidgetsService.actions.return.loadWidgetFile(path, ScenesService.views.activeSceneId);
await WidgetsService.actions.return.loadWidgetFile(
path,
ScenesService.views.activeSceneId,
opts?.filter,
);
}
}

async function installOverlayAndWidgets(overlayUrl: string, overlayName: string, widgetUrls: string[]) {
async function installOverlayAndWidgets(
overlayUrl: string,
overlayName: string,
widgetUrls: string[],
opts: IOverlayOptions = { mergePlatform: false },
) {
try {
await installOverlayBase(overlayUrl, overlayName);
await installWidgetsBase(widgetUrls);
await installOverlayBase(overlayUrl, overlayName, () => {}, opts);
await installWidgetsBase(widgetUrls, opts);
NavigationService.actions.navigate('Studio');
} catch (e) {
} catch (e: unknown) {
console.error(e);
}
}
Expand Down
37 changes: 35 additions & 2 deletions app/services/source-filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { metadata } from 'components/shared/inputs';
import path from 'path';
import { Inject } from './core/injector';
import { SourcesService } from './sources';
import { SourcesService, TSourceType } from './sources';
import { WindowsService } from './windows';
import * as obs from '../../obs-api';
import namingHelpers from '../util/NamingHelpers';
Expand Down Expand Up @@ -61,6 +61,11 @@ interface ISourceFilterType {
async: boolean;
}

export interface IOverlayFilter {
type: TSourceFilterType;
settings: Dictionary<TObsValue>;
}

export interface ISourceFilter {
name: string;
type: TSourceFilterType;
Expand Down Expand Up @@ -271,7 +276,6 @@ export class SourceFiltersService extends StatefulService<IFiltersServiceState>

const obsSource = source.getObsInput();
obsSource.addFilter(obsFilter);
// The filter should be created with the settings provided, is this necessary?
if (settings) obsFilter.update(settings);
const filterReference = obsSource.findFilter(filterName);
// There is now 2 references to the filter at that point
Expand Down Expand Up @@ -454,6 +458,35 @@ export class SourceFiltersService extends StatefulService<IFiltersServiceState>
return this.sourcesService.views.getSource(sourceId).getObsInput().findFilter(filterName);
}

sourceCanHaveOverlayFilters(sourceType: TSourceType) {
return [
'image_source',
'color_source',
'ffmpeg_source',
'text_gdiplus',
'text_ft2_source',
'browser_source',
'slideshow',
].includes(sourceType);
}

applyFilterToOverlay(filter: IOverlayFilter) {
const sources = this.sourcesService.views.getSources();
sources.forEach(source => {
if (!this.sourceCanHaveOverlayFilters(source.type)) return;
this.addOverlayFilter(source.sourceId, filter);
});
}

addOverlayFilter(sourceId: string, filter: IOverlayFilter) {
const filterTypes = this.views.getTypesForSource(sourceId);
const applicableFilter = filterTypes.find(filterType => filterType.type === filter.type);
if (applicableFilter) {
const name = this.views.suggestName(sourceId, applicableFilter.description);
this.add(sourceId, filter.type, name, filter.settings);
}
}

@mutation()
SET_FILTERS(sourceId: string, filters: ISourceFilter[]) {
Vue.set(this.state.filters, sourceId, [...filters]);
Expand Down
13 changes: 10 additions & 3 deletions app/services/widgets/widgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { getWidgetsConfig } from './widgets-config';
import { WidgetDisplayData } from '.';
import { DualOutputService } from 'services/dual-output';
import { TDisplayType, VideoSettingsService } from 'services/settings-v2';
import { IOverlayFilter, SourceFiltersService } from 'services/source-filters';

export interface IWidgetSourcesState {
widgetSources: Dictionary<IWidgetSource>;
Expand Down Expand Up @@ -83,6 +84,7 @@ export class WidgetsService
@Inject() editorCommandsService: EditorCommandsService;
@Inject() dualOutputService: DualOutputService;
@Inject() videoSettingsService: VideoSettingsService;
@Inject() sourceFiltersService: SourceFiltersService;

widgetDisplayData = WidgetDisplayData(); // cache widget display data

Expand Down Expand Up @@ -353,8 +355,9 @@ export class WidgetsService
* Load a widget file from the given path
* @param path the path to the widget file to laod
* @param sceneId the id of the scene to load into
* @param filter optional filter attached to a library widget (ie hue adjust)
*/
async loadWidgetFile(path: string, sceneId: string) {
async loadWidgetFile(path: string, sceneId: string, filter?: IOverlayFilter) {
const scene = this.scenesService.views.getScene(sceneId);
const json = await new Promise<string>((resolve, reject) => {
fs.readFile(path, (err, data) => {
Expand All @@ -367,15 +370,15 @@ export class WidgetsService
});

const widget = JSON.parse(json);
this.importWidgetJSON(widget, scene);
this.importWidgetJSON(widget, scene, filter);
}

/**
* Imports a serialized widget into a scene
* @param widget the widget to import
* @param scene the scene to import into
*/
private importWidgetJSON(widget: ISerializableWidget, scene: Scene) {
private importWidgetJSON(widget: ISerializableWidget, scene: Scene, filter?: IOverlayFilter) {
let widgetItem: SceneItem;

// First, look for an existing widget of the same type
Expand All @@ -401,6 +404,10 @@ export class WidgetsService
'horizontal',
);

if (filter) {
this.sourceFiltersService.actions.addOverlayFilter(widgetItem.sourceId, filter);
}

// if this is a dual output scene, also create the vertical scene item
if (this.dualOutputService.views.hasNodeMap()) {
Promise.resolve(
Expand Down
Loading