349 lines
No EOL
25 KiB
HTML
349 lines
No EOL
25 KiB
HTML
<!-- (c) 2024 Dylan Speiser -->
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<!-- Page title and metadata -->
|
|
<title>Camera Control WebUI for Blackmagic Cameras</title>
|
|
<meta charset="UTF-8">
|
|
<meta name="description" content="JS-based web interface for controlling Blackmagic Design cameras via the official REST API">
|
|
<meta name="author" content="Dylan Speiser">
|
|
|
|
<!-- Linking the stylesheet -->
|
|
<link rel="stylesheet" href="style.css">
|
|
</head>
|
|
<body onload="bodyOnLoad()">
|
|
<!-- JavaScript Linking -->
|
|
<script src="BMDevice.js"></script>
|
|
<script src="web-ui.js"></script>
|
|
|
|
<!------ Page Content ------>
|
|
|
|
<!-- Header Div -->
|
|
<div class="flexContainerH" id="headerContainer" onclick="document.getElementById('footerContainer').style.display='flex'">
|
|
<h1>Camera Control WebUI for Blackmagic Cameras</h1>
|
|
</div>
|
|
|
|
<!-- Camera Select Bar -->
|
|
<div class="flexContainerH" id="cameraSelectContainer">
|
|
<span class="cameraSwitchLabel selectedCam"><a href="#" onclick="switchCamera(0)">CAM1</a></span>
|
|
<span class="camSelectSeparator">|</span>
|
|
<span class="cameraSwitchLabel"><a href="#" onclick="switchCamera(1)">CAM2</a></span>
|
|
<span class="camSelectSeparator">|</span>
|
|
<span class="cameraSwitchLabel"><a href="#" onclick="switchCamera(2)">CAM3</a></span>
|
|
<span class="camSelectSeparator">|</span>
|
|
<span class="cameraSwitchLabel"><a href="#" onclick="switchCamera(3)">CAM4</a></span>
|
|
<span class="camSelectSeparator">|</span>
|
|
<span class="cameraSwitchLabel"><a href="#" onclick="switchCamera(4)">CAM5</a></span>
|
|
<span class="camSelectSeparator">|</span>
|
|
<span class="cameraSwitchLabel"><a href="#" onclick="switchCamera(5)">CAM6</a></span>
|
|
<span class="camSelectSeparator">|</span>
|
|
<span class="cameraSwitchLabel"><a href="#" onclick="switchCamera(6)">CAM7</a></span>
|
|
<span class="camSelectSeparator">|</span>
|
|
<span class="cameraSwitchLabel"><a href="#" onclick="switchCamera(7)">CAM8</a></span>
|
|
</div>
|
|
|
|
<!-- Camera Controls Box -->
|
|
<div class="flexContainerH" id="allCamerasContainer">
|
|
<div class="flexContainerV" id="cameraControlsContainer">
|
|
<div class="flexContainerH" id="cameraControlHeadContainer">
|
|
<h2 id="cameraNumberLabel">CAM1</h2>
|
|
</div>
|
|
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionContainer">
|
|
<!-- <div class="flexContainerH" id="cameraControlLGGTabs">
|
|
<a href="#" class="ccTabLabel selectedTab" onclick="">Lift</a>
|
|
<a href="#" class="ccTabLabel" onclick="">Gamma</a>
|
|
<a href="#" class="ccTabLabel" onclick="">Gain</a>
|
|
</div> -->
|
|
<span style="margin-top: 0.5em;">Lift</span>
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
|
|
<button class="CCResetButton circleButton" onclick="resetCC(0)" title="Reset Lift">⟳</button>
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onmousedown="CCInputHandler(0)" onkeydown="CCInputHandler(0)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onmousedown="CCInputHandler(0)" onkeydown="CCInputHandler(0)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onmousedown="CCInputHandler(0)" onkeydown="CCInputHandler(0)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onmousedown="CCInputHandler(0)" onkeydown="CCInputHandler(0)">0.00</span>
|
|
</div>
|
|
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(0)" title="Set Lift">➚</button>
|
|
</div>
|
|
|
|
<span>Gamma</span>
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
|
|
<button class="CCResetButton circleButton" onclick="resetCC(1)" title="Reset Gamma">⟳</button>
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onmousedown="CCInputHandler(1)" onkeydown="CCInputHandler(1)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onmousedown="CCInputHandler(1)" onkeydown="CCInputHandler(1)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onmousedown="CCInputHandler(1)" onkeydown="CCInputHandler(1)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onmousedown="CCInputHandler(1)" onkeydown="CCInputHandler(1)">0.00</span>
|
|
</div>
|
|
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(1)" title="Set Gamma">➚</button>
|
|
</div>
|
|
|
|
<span>Gain</span>
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
|
|
<button class="CCResetButton circleButton" onclick="resetCC(2)" title="Reset Gain">⟳</button>
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onmousedown="CCInputHandler(2)" onkeydown="CCInputHandler(2)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onmousedown="CCInputHandler(2)" onkeydown="CCInputHandler(2)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onmousedown="CCInputHandler(2)" onkeydown="CCInputHandler(2)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onmousedown="CCInputHandler(2)" onkeydown="CCInputHandler(2)">0.00</span>
|
|
</div>
|
|
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(2)" title="Set Gain">➚</button>
|
|
</div>
|
|
|
|
|
|
<span>Offset</span>
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
|
|
<button class="CCResetButton circleButton" onclick="resetCC(3)" title="Reset Offset">⟳</button>
|
|
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onmousedown="CCInputHandler(3)" onkeydown="CCInputHandler(3)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onmousedown="CCInputHandler(3)" onkeydown="CCInputHandler(3)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onmousedown="CCInputHandler(3)" onkeydown="CCInputHandler(3)">0.00</span>
|
|
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onmousedown="CCInputHandler(3)" onkeydown="CCInputHandler(3)">0.00</span>
|
|
</div>
|
|
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(4)" title="Set Offset">➚</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flexContainerH" id="cameraControlExposureContainer">
|
|
<div class="ccExposureSettingContainer">
|
|
<span class="exposureControlLabel">FILTER</span>
|
|
<div class="ccExposureSettingValueContainer">
|
|
<a class="expAdjArr" href="#" onclick="decreaseND()" id="NDL">◀</a>
|
|
<span id="ndFilterSpan" contenteditable="plaintext-only" onkeydown="NDFilterInputHandler()" onmousedown="NDFilterInputHandler()">0</span>
|
|
<a class="expAdjArr" href="#" onclick="increaseND()" id="NDR">▶</a>
|
|
</div>
|
|
</div>
|
|
<div class="ccExposureSettingContainer">
|
|
<span class="exposureControlLabel">GAIN</span>
|
|
<div class="ccExposureSettingValueContainer">
|
|
<a class="expAdjArr" href="#" onclick="decreaseGain()" id="GAL">◀</a>
|
|
<span id="gainSpan" contenteditable="plaintext-only" onkeydown="GainInputHandler()" onmousedown="GainInputHandler()">+0db</span>
|
|
<a class="expAdjArr" href="#" onclick="increaseGain()" id="GAR">▶</a>
|
|
</div>
|
|
</div>
|
|
<div class="ccExposureSettingContainer">
|
|
<span class="exposureControlLabel">SHUTTER</span>
|
|
<div class="ccExposureSettingValueContainer">
|
|
<a class="expAdjArr" href="#" onclick="decreaseShutter()" id="SHL">◀</a>
|
|
<span id="shutterSpan" contenteditable="plaintext-only" onkeydown="handleShutterInput()" onmousedown="handleShutterInput()">1/50</span>
|
|
<a class="expAdjArr" href="#" onclick="increaseShutter()" id="SHR">▶</a>
|
|
</div>
|
|
</div>
|
|
<div class="ccExposureSettingContainer">
|
|
<span class="exposureControlLabel" onclick="swapWBMode()" title="Click here to swap between WB and Tint" id="WBLabel">BALANCE</span>
|
|
<div class="ccExposureSettingValueContainer" id="WBValueContainer">
|
|
<a class="expAdjArr" href="#" onclick="decreaseWhiteBalance()" id="WBL">◀</a>
|
|
<span id="whiteBalanceSpan" contenteditable="plaintext-only" onkeydown="WBInputHandler()" onmousedown="WBInputHandler()">5600K</span>
|
|
<a class="expAdjArr" href="#" onclick="increaseWhiteBalance()" id="WBR">▶</a>
|
|
</div>
|
|
<div class="ccExposureSettingValueContainer dNone" id="WBTintValueContainer">
|
|
<a class="expAdjArr" href="#" onclick="decreaseWhiteBalanceTint()" id="WBTL">◀</a>
|
|
<span id="whiteBalanceTintSpan" contenteditable="plaintext-only" onkeydown="WBTInputHandler()" onmousedown="WBTInputHandler()">0</span>
|
|
<a class="expAdjArr" href="#" onclick="increaseWhiteBalanceTint()" id="WBLR">▶</a>
|
|
</div>
|
|
</div>
|
|
<div class="ccExposureSettingContainer">
|
|
<button id="AWBButton" class="circleButton" title="Make the camera do an Auto Whitebalance" onclick="cameras[ci].doAutoWhitebalance()">AW</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flexContainerH" id="cameraControlLensContainer">
|
|
<div class="lensSliderContainer">
|
|
<span>FOCUS</span>
|
|
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="focusRange" oninput="cameras[ci].PUTdata('/lens/focus', {normalised: parseFloat(this.value)})">
|
|
<button id="AFButton" class="circleButton" onclick="cameras[ci].doAutoFocus()">AF</button>
|
|
</div>
|
|
<div class="lensSliderContainer">
|
|
<span>IRIS</span>
|
|
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="irisRange" oninput="cameras[ci].PUTdata('/lens/iris', {normalised: parseFloat(this.value)})">
|
|
<span id="apertureStopsLabel">X.X</span>
|
|
</div>
|
|
<div class="lensSliderContainer">
|
|
<span>ZOOM</span>
|
|
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="zoomRange" oninput="cameras[ci].PUTdata('/lens/zoom', {normalised: parseFloat(this.value)})">
|
|
<span id="zoomMMLabel">XXmm</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flexContainerV" id="cameraControlsContainerExpanded">
|
|
<div class="flexContainerH" id="cameraControlExpandedHeadContainer">
|
|
<h2 id="cameraName">CAMERA NAME</h2>
|
|
<div id="formatDisplay">
|
|
<span id="formatCodec">CODEC</span>
|
|
<span id="formatResolution">RESOLUTION</span>
|
|
<span id="formatFPS">FPS</span>
|
|
</div>
|
|
<div id="transportControls">
|
|
<!-- These will be sticky buttons for loop, single clip, record -->
|
|
<button class="circleButton" onclick="loopHandler('Loop')" title="Loop" id="loopButton">↻</button>
|
|
<button class="circleButton" onclick="loopHandler('Single Clip')" title="Single Clip" id="singleClipButton">S</button>
|
|
<button class="circleButton" onclick="cameras[ci].seek(false)" title="Back">⏴</button>
|
|
<button class="circleButton" onclick="cameras[ci].seek(true)" title="Forward">⏵</button>
|
|
<button class="circleButton" onclick="cameras[ci].toggleRecord()" title="Record" style="color: red;">⏺</button>
|
|
<button class="circleButton" onclick="cameras[ci].play()" title="Play">▶</button>
|
|
<button class="circleButton" onclick="cameras[ci].stop()" title="Stop">⏹</button>
|
|
</div>
|
|
<h2 id="timecodeLabel">TIMECODE</h2>
|
|
</div>
|
|
|
|
<div class="flexContainerV" id="cameraControlExpandedBodyContainer">
|
|
<div class="tableControl">
|
|
<h3>Connection</h3>
|
|
<table>
|
|
<tr>
|
|
<td>Hostname</td>
|
|
<td>
|
|
<input type="text" placeholder=" Camera-Name-Here.local" id="hostnameInput" onclick="hostnameInputHandler()" onkeydown="hostnameInputHandler()" style="text-align: left;">
|
|
<button onclick="initCamera()">Connect</button>
|
|
<input type="checkbox" id="secureCheckbox">
|
|
<span id="secureCheckboxLabel">Use HTTPS</span>
|
|
<span id="connectionErrorSpan"></span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Send API Call</td>
|
|
<td>
|
|
<input type="radio" id="requestTypeGET" value="GET" name="manualRequestType" checked>
|
|
<label for="requestTypeGET">GET</label>
|
|
<input type="radio" id="requestTypePUT" value="PUT" name="manualRequestType">
|
|
<label for="requestTypePUT">PUT</label>
|
|
|
|
<input type="text" id="manualRequestEndpointLabel" placeholder="request endpoint">
|
|
<input type="text" id="manualRequestBodyLabel" placeholder="request body">
|
|
|
|
<button onclick="manualAPICall()">Send API Request</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="2">
|
|
<p id="manualRequestResponseP">Send manual API requests using the above controls. See <a href="https://documents.blackmagicdesign.com/DeveloperManuals/RESTAPIforBlackmagicCameras.pdf?_v=1696143610000" target="_blank" style="color: #6e6e6e;">documentation</a> for details.</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="tableControl">
|
|
<h3>Presets</h3>
|
|
<table>
|
|
<tr>
|
|
<td>Preset Select</td>
|
|
<td>
|
|
<select id="presetsDropDown" onmousedown="unsavedChanges.push('presets')" onchange="presetInputHandler()">
|
|
<!-- Auto-populated by updateUIPresets() -->
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<button onclick="presetInputHandler()">Restore from Preset</button>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="tableControl">
|
|
<h3>Exposure</h3>
|
|
<table>
|
|
<tr>
|
|
<td>ISO</td>
|
|
<td><input type="number" id="ISOInput" step="100" onkeydown="ISOInputHandler()" onmousedown="unsavedChanges.push('ISO')"></td>
|
|
</tr>
|
|
<tr>
|
|
<td>AE Mode</td>
|
|
<td>
|
|
<select id="AEmodeDropDown" onmousedown="unsavedChanges.push('AutoExposure')">
|
|
<option value="Off">Off</option>
|
|
<option value="Continuous">Continuous</option>
|
|
<option value="OneShot">One-Shot</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>AE Type</td>
|
|
<td>
|
|
<select id="AEtypeDropDown" onmousedown="unsavedChanges.push('AutoExposure')">
|
|
<option value="">None</option>
|
|
<option value="Iris">Iris Only</option>
|
|
<option value="Shutter">Shutter Only</option>
|
|
<option value="Shutter,Iris">Shutter + Iris</option>
|
|
<option value="Iris,Shutter">Iris + Shutter</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<button style="margin: 2vh 0 0 3.5vw;" onclick="AEmodeInputHandler()">Set AE Mode</button>
|
|
</div>
|
|
|
|
<div class="tableControl">
|
|
<h3>Contrast</h3>
|
|
<table>
|
|
<tr>
|
|
<td>Pivot</td>
|
|
<td><input type="range" max="1" min="0" step="0.001" id="CCcontrastPivotRange" oninput="cameras[ci].PUTdata('/colorCorrection/contrast', {pivot: parseFloat(this.value)}); unsavedChanges = unsavedChanges.filter((e) => {return !e.includes('CC4')});"></td>
|
|
<td>
|
|
<span id="CCcontrastPivotLabel" contenteditable="plaintext-only" onkeydown="CCInputHandler(4)" onmousedown="CCInputHandler(4)">0</span>
|
|
</td>
|
|
<td rowspan="2">
|
|
<button class="CCResetButton circleButton" onclick="resetCC(4)" title="Reset Contrast">⟳</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Adjust</td>
|
|
<td><input type="range" max="2" min="0" step="0.001" id="CCcontrastAdjustRange" oninput="cameras[ci].PUTdata('/colorCorrection/contrast', {adjust: parseFloat(this.value)}); unsavedChanges = unsavedChanges.filter((e) => {return !e.includes('CC4')});"></td>
|
|
<td>
|
|
<span id="CCcontrastAdjustLabel" contenteditable="plaintext-only" onkeydown="CCInputHandler(4)" onmousedown="CCInputHandler(4)">0</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="tableControl">
|
|
<h3>Color</h3>
|
|
<table>
|
|
<tr>
|
|
<td>Hue</td>
|
|
<td><input type="range" max="1" min="-1" step="0.001" id="CChueRange" oninput="cameras[ci].PUTdata('/colorCorrection/color', {hue: parseFloat(this.value)}); unsavedChanges = unsavedChanges.filter((e) => {return !e.includes('CC5')});"></td>
|
|
<td>
|
|
<span id="CCcolorHueLabel" contenteditable="plaintext-only" onkeydown="CCInputHandler(5)" onmousedown="CCInputHandler(5)">0</span>
|
|
</td>
|
|
<td rowspan="3">
|
|
<button class="CCResetButton circleButton" onclick="resetCC(5)" title="Reset Color">⟳</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Saturation</td>
|
|
<td><input type="range" max="2" min="0" step="0.001" id="CCsaturationRange" oninput="cameras[ci].PUTdata('/colorCorrection/color', {saturation: parseFloat(this.value)}); unsavedChanges = unsavedChanges.filter((e) => {return !e.includes('CC5')});"></td>
|
|
<td>
|
|
<span id="CCcolorSatLabel" contenteditable="plaintext-only" onkeydown="CCInputHandler(5)" onmousedown="CCInputHandler(5)">0</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Luma Contribution</td>
|
|
<td><input type="range" max="1" min="0" step="0.001" id="CClumaContributionRange" oninput="cameras[ci].PUTdata('/colorCorrection/lumaContribution', {lumaContribution: parseFloat(this.value)}); unsavedChanges = unsavedChanges.filter((e) => {return !e.includes('CC5')});"></td>
|
|
<td>
|
|
<span id="CCcolorLCLabel" contenteditable="plaintext-only" onkeydown="CCInputHandler(5)" onmousedown="CCInputHandler(5)">0</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Footer Div -->
|
|
<div class="flexContainerH" id="footerContainer" onclick="this.style.display='none'">
|
|
<div id="footerLeft">
|
|
<span class="">(v 1.4.2)</span>
|
|
<span id="activeElementSpan"></span>
|
|
</div>
|
|
<div id="footerLinks">
|
|
<span><a id="documentationLink" href="#" target="_blank">YAML Documentation</a></span>
|
|
<span><a id="mediaManagerLink" href="#" target="_blank">Web Media Manager</a></span>
|
|
<span><a id="githubLink" href="https://github.com/DylanSpeiser/BM-Camera-Control-WebUI" target="_blank">GitHub</a></span>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html> |