From 64b078557f24389e6de562aa8b0be8cff4a79669 Mon Sep 17 00:00:00 2001 From: Decobus Date: Tue, 22 Jul 2025 13:20:54 -0400 Subject: [PATCH] Fix text alignment to center correctly using alignment value 0 --- lib/obsClient.js | 170 +++++++++++++++++++++++++++++++---------------- 1 file changed, 114 insertions(+), 56 deletions(-) diff --git a/lib/obsClient.js b/lib/obsClient.js index 9193964..4390568 100644 --- a/lib/obsClient.js +++ b/lib/obsClient.js @@ -243,6 +243,21 @@ async function getAvailableTextInputKind() { } } +async function inspectTextSourceProperties(inputKind) { + try { + const obsClient = await getOBSClient(); + + // Get the default properties for this input kind + const { inputProperties } = await obsClient.call('GetInputDefaultSettings', { inputKind }); + console.log(`Default properties for ${inputKind}:`, JSON.stringify(inputProperties, null, 2)); + + return inputProperties; + } catch (error) { + console.error('Error inspecting text source properties:', error.message); + return null; + } +} + async function createTextSource(sceneName, textSourceName, text) { try { const obsClient = await getOBSClient(); @@ -250,13 +265,59 @@ async function createTextSource(sceneName, textSourceName, text) { // Check if text source already exists globally in OBS const { inputs } = await obsClient.call('GetInputList'); const existingInput = inputs.find(input => input.inputName === textSourceName); + const colorSourceName = `${textSourceName}_bg`; if (!existingInput) { - console.log(`Creating text source "${textSourceName}" in scene "${sceneName}"`); + console.log(`Creating text source "${textSourceName}" with color background in scene "${sceneName}"`); + + // First, create a color source for the background + const colorSourceExists = inputs.some(input => input.inputName === colorSourceName); + + if (!colorSourceExists) { + console.log(`Creating color source background "${colorSourceName}"`); + await obsClient.call('CreateInput', { + sceneName, + inputName: colorSourceName, + inputKind: 'color_source_v3', // Use v3 if available, fallback handled below + inputSettings: { + color: 0xFF002B4B, // Background color #002b4b + width: 800, // Width to accommodate text + height: 100 // Height for text background + } + }).catch(async (error) => { + // If v3 doesn't exist, try v2 + console.log('color_source_v3 failed, trying color_source_v2:', error.message); + await obsClient.call('CreateInput', { + sceneName, + inputName: colorSourceName, + inputKind: 'color_source_v2', + inputSettings: { + color: 0xFF002B4B, + width: 800, + height: 100 + } + }).catch(async (fallbackError) => { + // Final fallback to basic color_source + console.log('color_source_v2 failed, trying color_source:', fallbackError.message); + await obsClient.call('CreateInput', { + sceneName, + inputName: colorSourceName, + inputKind: 'color_source', + inputSettings: { + color: 0xFF002B4B, + width: 800, + height: 100 + } + }); + }); + }); + console.log(`Created color source background "${colorSourceName}"`); + } // Get the correct text input kind for this OBS installation const inputKind = await getAvailableTextInputKind(); + // Create text source with simple settings (no background needed) const inputSettings = { text, font: { @@ -267,9 +328,12 @@ async function createTextSource(sceneName, textSourceName, text) { color: 0xFFFFFFFF, // White text outline: true, outline_color: 0xFF000000, // Black outline - outline_size: 4 + outline_size: 2 }; + console.log(`Creating text source with inputKind: ${inputKind}`); + console.log('Input settings:', JSON.stringify(inputSettings, null, 2)); + await obsClient.call('CreateInput', { sceneName, inputName: textSourceName, @@ -293,7 +357,9 @@ async function createTextSource(sceneName, textSourceName, text) { color: 0xFFFFFFFF, // White text outline: true, outline_color: 0xFF000000, // Black outline - outline_size: 4 + outline_size: 4, + bk_color: 0xFF002B4B, // Background color #002b4b + bk_opacity: 255 // Full opacity background }; await obsClient.call('SetInputSettings', { @@ -381,12 +447,25 @@ async function createStreamGroup(groupName, streamName, teamName, url) { } } - // Add text source to nested scene + // Add text source and its background to nested scene + const colorSourceName = `${textSourceName}_bg`; + + try { + await obsClient.call('CreateSceneItem', { + sceneName: streamGroupName, + sourceName: colorSourceName + }); + console.log(`Added color source background "${colorSourceName}" to nested scene`); + } catch (e) { + console.log('Color source background might already be in nested scene'); + } + try { await obsClient.call('CreateSceneItem', { sceneName: streamGroupName, sourceName: textSourceName }); + console.log(`Added text source "${textSourceName}" to nested scene`); } catch (e) { console.log('Text source might already be in nested scene'); } @@ -394,80 +473,59 @@ async function createStreamGroup(groupName, streamName, teamName, url) { // Get the scene items in the nested scene const { sceneItems: nestedSceneItems } = await obsClient.call('GetSceneItemList', { sceneName: streamGroupName }); - // Find the browser source and text source items in nested scene + // Find the browser source, text source, and color background items in nested scene const browserSourceItem = nestedSceneItems.find(item => item.sourceName === sourceName); const textSourceItem = nestedSceneItems.find(item => item.sourceName === textSourceName); + const colorSourceItem = nestedSceneItems.find(item => item.sourceName === colorSourceName); // Position the sources properly in the nested scene - if (browserSourceItem && textSourceItem) { + if (browserSourceItem && textSourceItem && colorSourceItem) { try { - // Position text overlay at top, then center horizontally + // Position text overlay centered horizontally using center alignment await obsClient.call('SetSceneItemTransform', { - sceneName: streamGroupName, // In the nested scene + sceneName: streamGroupName, sceneItemId: textSourceItem.sceneItemId, sceneItemTransform: { - positionX: 0, // Start at left - positionY: 10, // Keep at top + positionX: 960, // Center of 1920px canvas + positionY: 50, // Move down from top scaleX: 1.0, scaleY: 1.0, - alignment: 5 // Center alignment + alignment: 0 // Center alignment (0 = center, 1 = left, 2 = right) } }); - // Apply center horizontally transform (like clicking "Center Horizontally" in OBS UI) - const { sceneItemTransform: currentTransform } = await obsClient.call('GetSceneItemTransform', { + // Get the actual text width after positioning + const { sceneItemTransform: textTransform } = await obsClient.call('GetSceneItemTransform', { sceneName: streamGroupName, sceneItemId: textSourceItem.sceneItemId }); - console.log('Current text transform before centering:', JSON.stringify(currentTransform, null, 2)); + const actualTextWidth = textTransform.width || textTransform.sourceWidth || (teamName.length * 40); + console.log('Actual text width:', actualTextWidth); - // Get the actual scene dimensions - let sceneWidth = 1920; // Default assumption - let sceneHeight = 1080; + // Calculate color source width with padding + const colorSourceWidth = Math.max(actualTextWidth + 40, 200); // Add 40px padding, minimum 200px + console.log('Color source width:', colorSourceWidth); - try { - const sceneInfo = await obsClient.call('GetSceneItemList', { sceneName: streamGroupName }); - console.log(`Scene dimensions check for "${streamGroupName}":`, sceneInfo); - } catch (e) { - console.log('Could not get scene info:', e.message); - } - - // Manual positioning: Calculate where to place text so its center is at canvas center - const canvasWidth = sceneWidth; - const canvasCenter = canvasWidth / 2; - const textWidth = currentTransform.width || currentTransform.sourceWidth || 0; - - // Since we know the scene is bounded to 1600x900 from earlier logs, try that - const boundedWidth = 1600; - const boundedCenter = boundedWidth / 2; // 800 - const alternatePosition = boundedCenter - (textWidth / 2); - - console.log(`Manual centering calculation:`); - console.log(`- Scene/Canvas width: ${canvasWidth}`); - console.log(`- Canvas center: ${canvasCenter}`); - console.log(`- Text width: ${textWidth}`); - console.log(`- Position for 1920px canvas: ${canvasCenter - (textWidth / 2)}`); - console.log(`- Bounded scene width: ${boundedWidth}`); - console.log(`- Bounded center: ${boundedCenter}`); - console.log(`- Position for 1600px bounded scene: ${alternatePosition}`); - - // Set the position with left alignment (0) for predictable positioning + // Adjust the color source settings to match the text's actual width and height + await obsClient.call('SetInputSettings', { + inputName: colorSourceName, + inputSettings: { + width: Math.floor(colorSourceWidth), // Ensure it's a whole number + height: 90 // Slightly shorter height to better match text + } + }); + + // Position color source background centered, same position as text await obsClient.call('SetSceneItemTransform', { sceneName: streamGroupName, - sceneItemId: textSourceItem.sceneItemId, + sceneItemId: colorSourceItem.sceneItemId, sceneItemTransform: { - positionX: alternatePosition, // Use 1600px scene width calculation - positionY: 10, // Keep at top - alignment: 0, // Left alignment for predictable positioning - rotation: currentTransform.rotation || 0, - scaleX: currentTransform.scaleX || 1, - scaleY: currentTransform.scaleY || 1, - cropBottom: currentTransform.cropBottom || 0, - cropLeft: currentTransform.cropLeft || 0, - cropRight: currentTransform.cropRight || 0, - cropTop: currentTransform.cropTop || 0, - cropToBounds: currentTransform.cropToBounds || false + positionX: 960, // Same center position as text + positionY: 50, // Same Y position as text for perfect alignment + scaleX: 1.0, + scaleY: 1.0, + alignment: 0 // Same center alignment as text } });