219 lines
7.1 KiB
JavaScript
219 lines
7.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* OBS Virtual Camera with Noise Source
|
|
*
|
|
* This script connects to OBS via websocket and:
|
|
* 1. Creates a scene with a noise/random pattern source
|
|
* 2. Starts the virtual camera
|
|
*
|
|
* Prerequisites:
|
|
* - OBS Studio 28+ (has obs-websocket built-in)
|
|
* - Enable WebSocket Server in OBS: Tools -> WebSocket Server Settings
|
|
* - Set a password or disable authentication for local use
|
|
*
|
|
* Usage: node obs-noise-camera.mjs [start|stop] [--password=YOUR_PASSWORD]
|
|
*/
|
|
|
|
import OBSWebSocket from 'obs-websocket-js';
|
|
|
|
const obs = new OBSWebSocket();
|
|
|
|
const SCENE_NAME = 'NoiseCamera';
|
|
const SOURCE_NAME = 'RandomNoise';
|
|
|
|
async function connect(password) {
|
|
const url = 'ws://127.0.0.1:4455';
|
|
try {
|
|
const config = password ? { rpcVersion: 1, password } : { rpcVersion: 1 };
|
|
await obs.connect(url, password);
|
|
console.log('Connected to OBS WebSocket');
|
|
return true;
|
|
} catch (err) {
|
|
if (err.message?.includes('Authentication')) {
|
|
console.error('Authentication required. Use --password=YOUR_PASSWORD');
|
|
console.error('Or disable authentication in OBS: Tools -> WebSocket Server Settings');
|
|
} else {
|
|
console.error('Failed to connect to OBS:', err.message);
|
|
console.error('Make sure OBS is running and WebSocket Server is enabled');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function createNoiseScene() {
|
|
// Check if scene exists
|
|
const { scenes } = await obs.call('GetSceneList');
|
|
const sceneExists = scenes.some(s => s.sceneName === SCENE_NAME);
|
|
|
|
if (!sceneExists) {
|
|
console.log(`Creating scene: ${SCENE_NAME}`);
|
|
await obs.call('CreateScene', { sceneName: SCENE_NAME });
|
|
}
|
|
|
|
// Switch to the scene
|
|
await obs.call('SetCurrentProgramScene', { sceneName: SCENE_NAME });
|
|
|
|
// Check if source exists in scene
|
|
const { sceneItems } = await obs.call('GetSceneItemList', { sceneName: SCENE_NAME });
|
|
const sourceExists = sceneItems.some(item => item.sourceName === SOURCE_NAME);
|
|
|
|
if (!sourceExists) {
|
|
console.log(`Creating noise source: ${SOURCE_NAME}`);
|
|
|
|
// Create a color source with shader/noise effect
|
|
// We'll use a color source and apply noise filter, or use browser source with noise
|
|
await obs.call('CreateInput', {
|
|
sceneName: SCENE_NAME,
|
|
inputName: SOURCE_NAME,
|
|
inputKind: 'color_source_v3',
|
|
inputSettings: {
|
|
color: 0xFF808080, // Gray base
|
|
width: 1920,
|
|
height: 1080
|
|
}
|
|
});
|
|
|
|
// Add a noise/static filter using the Shader filter if available
|
|
// Or we can use a different approach - let's try adding the "Noise Displacement" filter
|
|
try {
|
|
await obs.call('CreateSourceFilter', {
|
|
sourceName: SOURCE_NAME,
|
|
filterName: 'ShaderNoise',
|
|
filterKind: 'streamfx-filter-shader',
|
|
filterSettings: {
|
|
'Shader.Type': 0, // File
|
|
}
|
|
});
|
|
} catch (e) {
|
|
// StreamFX might not be installed, try built-in approach
|
|
console.log('Note: For best results, install StreamFX plugin for shader-based noise');
|
|
}
|
|
}
|
|
|
|
console.log(`Scene "${SCENE_NAME}" ready`);
|
|
}
|
|
|
|
async function createBrowserNoiseSource() {
|
|
// Alternative: Create a browser source with animated noise
|
|
const { scenes } = await obs.call('GetSceneList');
|
|
const sceneExists = scenes.some(s => s.sceneName === SCENE_NAME);
|
|
|
|
if (!sceneExists) {
|
|
console.log(`Creating scene: ${SCENE_NAME}`);
|
|
await obs.call('CreateScene', { sceneName: SCENE_NAME });
|
|
}
|
|
|
|
await obs.call('SetCurrentProgramScene', { sceneName: SCENE_NAME });
|
|
|
|
const { sceneItems } = await obs.call('GetSceneItemList', { sceneName: SCENE_NAME });
|
|
const sourceExists = sceneItems.some(item => item.sourceName === SOURCE_NAME);
|
|
|
|
if (!sourceExists) {
|
|
console.log(`Creating browser noise source: ${SOURCE_NAME}`);
|
|
|
|
// HTML/JS that generates animated noise
|
|
const noiseHtml = `data:text/html,
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><style>body{margin:0;overflow:hidden}canvas{display:block}</style></head>
|
|
<body>
|
|
<canvas id="c"></canvas>
|
|
<script>
|
|
const c=document.getElementById('c'),x=c.getContext('2d');
|
|
c.width=1920;c.height=1080;
|
|
function draw(){
|
|
const d=x.createImageData(c.width,c.height);
|
|
for(let i=0;i<d.data.length;i+=4){
|
|
const v=Math.random()*255|0;
|
|
d.data[i]=d.data[i+1]=d.data[i+2]=v;
|
|
d.data[i+3]=255;
|
|
}
|
|
x.putImageData(d,0,0);
|
|
requestAnimationFrame(draw);
|
|
}
|
|
draw();
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
|
|
await obs.call('CreateInput', {
|
|
sceneName: SCENE_NAME,
|
|
inputName: SOURCE_NAME,
|
|
inputKind: 'browser_source',
|
|
inputSettings: {
|
|
url: noiseHtml,
|
|
width: 1920,
|
|
height: 1080,
|
|
fps: 30,
|
|
reroute_audio: false
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log(`Browser noise source "${SOURCE_NAME}" ready`);
|
|
}
|
|
|
|
async function startVirtualCamera() {
|
|
const { outputActive } = await obs.call('GetVirtualCamStatus');
|
|
if (!outputActive) {
|
|
console.log('Starting virtual camera...');
|
|
await obs.call('StartVirtualCam');
|
|
console.log('Virtual camera started');
|
|
} else {
|
|
console.log('Virtual camera already running');
|
|
}
|
|
}
|
|
|
|
async function stopVirtualCamera() {
|
|
const { outputActive } = await obs.call('GetVirtualCamStatus');
|
|
if (outputActive) {
|
|
console.log('Stopping virtual camera...');
|
|
await obs.call('StopVirtualCam');
|
|
console.log('Virtual camera stopped');
|
|
} else {
|
|
console.log('Virtual camera not running');
|
|
}
|
|
}
|
|
|
|
async function getStatus() {
|
|
const { outputActive } = await obs.call('GetVirtualCamStatus');
|
|
const { currentProgramSceneName } = await obs.call('GetCurrentProgramScene');
|
|
console.log(`Virtual Camera: ${outputActive ? 'RUNNING' : 'STOPPED'}`);
|
|
console.log(`Current Scene: ${currentProgramSceneName}`);
|
|
}
|
|
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
const command = args.find(a => !a.startsWith('--')) || 'start';
|
|
const passwordArg = args.find(a => a.startsWith('--password='));
|
|
const password = passwordArg ? passwordArg.split('=')[1] : undefined;
|
|
|
|
if (!await connect(password)) {
|
|
process.exit(1);
|
|
}
|
|
|
|
try {
|
|
switch (command) {
|
|
case 'start':
|
|
await createBrowserNoiseSource();
|
|
await startVirtualCamera();
|
|
await getStatus();
|
|
break;
|
|
case 'stop':
|
|
await stopVirtualCamera();
|
|
break;
|
|
case 'status':
|
|
await getStatus();
|
|
break;
|
|
default:
|
|
console.log('Usage: node obs-noise-camera.mjs [start|stop|status] [--password=PASS]');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error:', err.message);
|
|
} finally {
|
|
obs.disconnect();
|
|
}
|
|
}
|
|
|
|
main();
|