BM-Camera-Control-WebUI/web-ui.js

459 lines
18 KiB
JavaScript
Raw Normal View History

2024-06-14 19:40:07 -04:00
/* Blackmagic Camera Control WebUI
WebUI Script functions
(c) Dylan Speiser 2024
github.com/DylanSpeiser
*/
2024-06-11 19:46:25 -04:00
2024-06-13 19:49:36 -04:00
2024-06-14 19:40:07 -04:00
/* Global variables */
var cameras = []; // Array to store all of the camera objects
var ci = 0; // Index into this array for the currently selected camera.
// cameras[ci] is used to reference the currently selected camera object
2024-06-11 19:46:25 -04:00
2024-06-14 19:40:07 -04:00
var WBMode = 0; // 0: balance, 1: tint
2024-06-11 19:46:25 -04:00
2024-07-02 17:53:25 -04:00
var defaultControlsHTML;
2024-06-14 19:40:07 -04:00
// Set everything up
function bodyOnLoad() {
2024-07-02 17:53:25 -04:00
defaultControlsHTML = document.getElementById("allCamerasContainer").innerHTML;
2024-06-11 19:46:25 -04:00
}
2024-07-02 17:53:25 -04:00
// Checks the hostname, if it replies successfully then a new BMCamera object
2024-06-14 19:40:07 -04:00
// is made and gets put in the array at ind
2024-07-02 17:53:25 -04:00
function initCamera() {
// Get hostname from Hostname text field
let hostname = document.getElementById("hostnameInput").value;
try {
// Check if the hostname is valid
let response = sendRequest("GET", "http://"+hostname+"/control/api/v1/system","");
2024-06-13 17:46:46 -04:00
if (response.status < 300) {
2024-06-14 19:40:07 -04:00
// Success, make a new camera, get all relevant info, and populate the UI
2024-07-02 17:53:25 -04:00
cameras[ci] = new BMCamera(hostname);
cameras[ci].updateUI = updateUIAll;
cameras[ci].active = true;
2024-06-14 19:40:07 -04:00
document.getElementById("connectionErrorSpan").innerHTML = "Connected.";
document.getElementById("connectionErrorSpan").setAttribute("style","color: #6e6e6e;");
2024-06-13 17:46:46 -04:00
} else {
2024-06-14 19:40:07 -04:00
// Something has gone wrong, tell the user
2024-06-13 17:46:46 -04:00
document.getElementById("connectionErrorSpan").innerHTML = response.statusText;
}
2024-07-02 17:53:25 -04:00
} catch (error) {
2024-06-14 19:40:07 -04:00
// Something has gone wrong, tell the user
2024-06-13 17:46:46 -04:00
document.getElementById("connectionErrorSpan").title = error;
document.getElementById("connectionErrorSpan").innerHTML = "Error "+error.code+": "+error.name+" (Your hostname is probably incorrect, hover for more details)";
2024-07-02 17:53:25 -04:00
}
2024-06-13 17:46:46 -04:00
}
2024-07-02 17:53:25 -04:00
// =============================== UI Updater ==================================
// =============================================================================
2024-06-14 19:40:07 -04:00
function updateUIAll() {
2024-07-02 17:53:25 -04:00
// ========== Camera Name ==========
2024-06-14 19:40:07 -04:00
document.getElementById("cameraName").innerHTML = cameras[ci].name;
2024-07-02 17:53:25 -04:00
// ========== Hostname ==========
2024-06-14 19:40:07 -04:00
document.getElementById("hostnameInput").value = cameras[ci].hostname;
2024-07-02 17:53:25 -04:00
// ========== Format ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document.getElementById("formatCodec").innerHTML = cameras[ci].propertyData['/system/format']?.codec.toUpperCase().replace(":"," ").replace("_",":");
let resObj = cameras[ci].propertyData['/system/format']?.recordResolution;
document.getElementById("formatResolution").innerHTML = resObj?.width + "x" + resObj?.height;
document.getElementById("formatFPS").innerHTML = cameras[ci].propertyData['/system/format']?.frameRate+" fps";
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ========== Recording State ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
if (cameras[ci].propertyData['/transports/0/record']?.recording) {
2024-06-14 19:40:07 -04:00
document.getElementById("cameraControlHeadContainer").classList.add("liveCam");
document.getElementById("cameraControlExpandedHeadContainer").classList.add("liveCam");
} else {
document.getElementById("cameraControlHeadContainer").classList.remove("liveCam");
document.getElementById("cameraControlExpandedHeadContainer").classList.remove("liveCam");
}
2024-07-02 17:53:25 -04:00
// ========== Timecode ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document.getElementById("timecodeLabel").innerHTML = parseTimecode(cameras[ci].propertyData['/transports/0/timecode']?.timecode);
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ========== Presets Dropdown ==========
2024-06-14 19:40:07 -04:00
var presetsList = document.getElementById("presetsDropDown");
presetsList.innerHTML = "";
2024-07-02 17:53:25 -04:00
cameras[ci].propertyData['/presets']?.presets.forEach((presetItem) => {
2024-06-14 19:40:07 -04:00
let presetName = presetItem.split('.', 1);
let textNode = document.createTextNode(presetName);
let optionNode = document.createElement("option");
optionNode.setAttribute("name", "presetOption"+presetName);
optionNode.appendChild(textNode);
document.getElementById("presetsDropDown").appendChild(optionNode);
});
2024-07-02 17:53:25 -04:00
// ========== Active Preset ==========
2024-06-14 19:40:07 -04:00
var presetsList = document.getElementById("presetsDropDown");
presetsList.childNodes.forEach((child) => {
2024-07-02 17:53:25 -04:00
if (child.nodeName == 'OPTION' && child.value == cameras[ci].propertyData['/presets/active']?.preset) {
2024-06-14 19:40:07 -04:00
child.selected=true
} else {
child.selected=false
}
})
2024-07-02 17:53:25 -04:00
// ========== Iris ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document.getElementById("irisRange").value = cameras[ci].propertyData['/lens/iris']?.normalised;
document.getElementById("apertureStopsLabel").innerHTML = cameras[ci].propertyData['/lens/iris']?.apertureStop.toFixed(1);
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ========== Zoom ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document.getElementById("zoomRange").value = cameras[ci].propertyData['/lens/zoom']?.normalised;
document.getElementById("zoomMMLabel").innerHTML = cameras[ci].propertyData['/lens/zoom']?.focalLength +"mm";
// ========== Focus ==========
document.getElementById("focusRange").value = cameras[ci].propertyData['/lens/focus']?.normalised;
// ========== ISO ==========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
if (cameras[ci].propertyData['/video/iso'])
document.getElementById("ISOInput").value = cameras[ci].propertyData['/video/iso']?.iso;
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ========== GAIN ==========
let gainString = "";
let gainInt = cameras[ci].propertyData['/video/gain']?.gain
if (gainInt >= 0) {
gainString = "+"+gainInt+"db"
2024-06-14 19:40:07 -04:00
} else {
2024-07-02 17:53:25 -04:00
gainString = gainInt+"db"
2024-06-14 19:40:07 -04:00
}
document.getElementById("gainSpan").innerHTML = gainString;
2024-07-02 17:53:25 -04:00
// ========== WHITE BALANCE ===========
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document.getElementById("whiteBalanceSpan").innerHTML = cameras[ci].propertyData['/video/whiteBalance']?.whiteBalance+"K";
document.getElementById("whiteBalanceTintSpan").innerHTML = cameras[ci].propertyData['/video/whiteBalanceTint']?.whiteBalanceTint;
// =========== ND =============
if (cameras[ci].propertyData['/video/ndFilter']) {
document.getElementById("ndFilterSpan").innerHTML = cameras[ci].propertyData['/video/ndFilter']?.stop;
} else {
2024-06-14 19:40:07 -04:00
document.getElementById("ndFilterSpan").innerHTML = 0;
document.getElementById("ndFilterSpan").disabled = true;
}
2024-07-02 17:53:25 -04:00
// ============ Shutter =====================
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
let shutterString = "SS"
let shutterObj = cameras[ci].propertyData['/video/shutter'];
if (shutterObj?.shutterSpeed) {
shutterString = "1/"+shutterObj.shutterSpeed
} else if (shutterObj?.shutterAngle) {
var shangleString = (shutterObj.shutterAngle / 100).toFixed(1).toString()
2024-06-14 19:40:07 -04:00
if (shangleString.indexOf(".0") > 0) {
shutterString = parseFloat(shangleString).toFixed(0)+"°";
} else {
shutterString = shangleString+"°";
}
}
document.getElementById("shutterSpan").innerHTML = shutterString;
2024-07-02 17:53:25 -04:00
// =========== Auto Exposure Mode ===========
2024-06-14 19:40:07 -04:00
let AEmodeSelect = document.getElementById("AEmodeDropDown");
let AEtypeSelect = document.getElementById("AEtypeDropDown");
2024-07-02 17:53:25 -04:00
AEmodeSelect.value = cameras[ci].propertyData['/video/autoExposure']?.mode;
AEtypeSelect.value = cameras[ci].propertyData['/video/autoExposure']?.type;
// =========== COLOR CORRECTION =============
2024-06-14 19:40:07 -04:00
// Lift
2024-07-02 17:53:25 -04:00
let liftProps = cameras[ci].propertyData['/colorCorrection/lift'];
document.getElementsByClassName("CClumaLabel")[0].innerHTML = liftProps?.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[0].innerHTML = liftProps?.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[0].innerHTML = liftProps?.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[0].innerHTML = liftProps?.blue.toFixed(2);
2024-06-14 19:40:07 -04:00
// Gamma
2024-07-02 17:53:25 -04:00
let gammaProps = cameras[ci].propertyData['/colorCorrection/gamma'];
document.getElementsByClassName("CClumaLabel")[1].innerHTML = gammaProps?.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[1].innerHTML = gammaProps?.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[1].innerHTML = gammaProps?.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[1].innerHTML = gammaProps?.blue.toFixed(2);
2024-06-14 19:40:07 -04:00
// Gain
2024-07-02 17:53:25 -04:00
let gainProps = cameras[ci].propertyData['/colorCorrection/gain'];
document.getElementsByClassName("CClumaLabel")[2].innerHTML = gainProps?.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[2].innerHTML = gainProps?.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[2].innerHTML = gainProps?.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[2].innerHTML = gainProps?.blue.toFixed(2);
2024-06-14 19:40:07 -04:00
// Offset
2024-07-02 17:53:25 -04:00
let offsetProps = cameras[ci].propertyData['/colorCorrection/offset'];
document.getElementsByClassName("CClumaLabel")[3].innerHTML = offsetProps?.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[3].innerHTML = offsetProps?.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[3].innerHTML = offsetProps?.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[3].innerHTML = offsetProps?.blue.toFixed(2);
2024-06-14 19:40:07 -04:00
// Contrast
2024-07-02 17:53:25 -04:00
let constrastProps = cameras[ci].propertyData['/colorCorrection/contrast'];
document.getElementById("CCcontrastPivotRange").value = constrastProps?.pivot;
document.getElementById("CCcontrastPivotLabel").innerHTML = constrastProps?.pivot.toFixed(2);
document.getElementById("CCcontrastAdjustRange").value = constrastProps?.adjust;
document.getElementById("CCcontrastAdjustLabel").innerHTML = parseInt(constrastProps?.adjust * 50)+"%";
2024-06-14 19:40:07 -04:00
// Color
2024-07-02 17:53:25 -04:00
let colorProps = cameras[ci].propertyData['/colorCorrection/color'];
document.getElementById("CChueRange").value = colorProps?.hue;
document.getElementById("CCcolorHueLabel").innerHTML = parseInt((colorProps?.hue + 1) * 180)+"°";
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
document.getElementById("CCsaturationRange").value = colorProps?.saturation;
document.getElementById("CCcolorSatLabel").innerHTML = parseInt(colorProps?.saturation * 50)+"%";
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
let lumaContributionProps = cameras[ci].propertyData['/colorCorrection/lumaContribution'];
document.getElementById("CClumaContributionRange").value = lumaContributionProps?.lumaContribution;
document.getElementById("CCcolorLCLabel").innerHTML = parseInt(lumaContributionProps?.lumaContribution * 100)+"%";
2024-06-14 19:40:07 -04:00
2024-07-02 17:53:25 -04:00
// ============ Footer Links ===============
2024-06-14 19:40:07 -04:00
document.getElementById("documentationLink").href = "http://"+cameras[ci].hostname+"/control/documentation.html";
document.getElementById("mediaManagerLink").href = "http://"+cameras[ci].hostname;
}
// ==============================================================================
2024-07-02 17:53:25 -04:00
// Called when the user changes tabs to a different camera
function switchCamera(index) {
2024-06-12 20:18:19 -04:00
if (cameras[ci]) {
2024-07-02 17:53:25 -04:00
cameras[ci].active = false;
2024-06-12 20:18:19 -04:00
}
2024-06-11 19:46:25 -04:00
2024-06-12 20:18:19 -04:00
ci = index;
2024-07-02 17:53:25 -04:00
// Reset the Controls
document.getElementById("allCamerasContainer").innerHTML = defaultControlsHTML;
// Update the UI
2024-06-12 20:18:19 -04:00
for (var i = 0; i < 8; i++) {
if (i == ci) {
document.getElementsByClassName("cameraSwitchLabel")[i].classList.add("selectedCam");
} else {
document.getElementsByClassName("cameraSwitchLabel")[i].classList.remove("selectedCam");
}
}
document.getElementById("cameraNumberLabel").innerHTML = "CAM"+(ci+1);
2024-06-13 19:49:36 -04:00
document.getElementById("cameraName").innerHTML = "CAMERA NAME";
2024-07-02 17:53:25 -04:00
if (cameras[ci]) {
cameras[ci].active = true;
}
2024-06-12 20:18:19 -04:00
}
2024-06-14 19:40:07 -04:00
// For not-yet-implemented Color Correction UI
2024-06-12 20:18:19 -04:00
function setCCMode(mode) {
if (mode == 0) {
// Lift
} else if (mode == 1) {
// Gamma
} else {
// Gain
}
for (var i = 0; i < 3; i++) {
if (i == mode) {
document.getElementsByClassName("ccTabLabel")[i].classList.add("selectedTab");
} else {
document.getElementsByClassName("ccTabLabel")[i].classList.remove("selectedTab");
}
}
}
2024-06-14 19:40:07 -04:00
// Allows for changing WB/Tint displayed in the UI
2024-06-13 17:46:46 -04:00
function swapWBMode() {
if (WBMode == 0) {
// Balance
document.getElementById("WBLabel").innerHTML = "TINT";
document.getElementById("WBValueContainer").classList.add("dNone");
document.getElementById("WBTintValueContainer").classList.remove("dNone");
WBMode = 1;
} else {
//Tint
document.getElementById("WBLabel").innerHTML = "BALANCE";
document.getElementById("WBValueContainer").classList.remove("dNone");
document.getElementById("WBTintValueContainer").classList.add("dNone");
WBMode = 0;
}
}
2024-06-14 19:40:07 -04:00
// Triggered by the button by those text boxes. Reads the info from the inputs and sends it to the camera.
2024-06-13 17:46:46 -04:00
function manualAPICall() {
const requestRadioGET = document.getElementById("requestTypeGET");
const requestEndpointText = document.getElementById("manualRequestEndpointLabel").value;
let requestData = "";
try {
requestData = JSON.parse(document.getElementById("manualRequestBodyLabel").value);
} catch (err) {
document.getElementById("manualRequestResponseP").innerHTML = err;
}
const requestMethod = (requestRadioGET.checked ? "GET" : "PUT");
const requestURL = cameras[ci].APIAddress+requestEndpointText;
2024-07-02 17:53:25 -04:00
let response = sendRequest(requestMethod,requestURL,requestData);
2024-06-12 20:18:19 -04:00
2024-07-02 17:53:25 -04:00
document.getElementById("manualRequestResponseP").innerHTML = JSON.stringify(response);
2024-06-12 20:18:19 -04:00
}
2024-07-02 18:30:59 -04:00
/* Control Calling Functions */
/* Makes the HTML cleaner. */
function decreaseND() {
cameras[ci].setND(cameras[ci].NDStop-2);
}
function increaseND() {
cameras[ci].setND(cameras[ci].NDStop+2);
}
function decreaseGain() {
cameras[ci].setGain(cameras[ci].gain-2);
}
function increaseGain() {
cameras[ci].setGain(cameras[ci].gain+2);
}
function decreaseShutter() {
let cam = cameras[ci];
if ('shutterSpeed' in cam.shutter) {
cam.setShutter({"shutterSpeed":cam.shutter.shutterSpeed+10});
} else {
cam.setShutter({"shutterAngle": cam.shutter.shutterAngle-1000});
}
}
function increaseShutter() {
let cam = cameras[ci];
if ('shutterSpeed' in cam.shutter) {
cam.setShutter({"shutterSpeed":cam.shutter.shutterSpeed-10});
} else {
cam.setShutter({"shutterAngle": cam.shutter.shutterAngle+1000});
}
}
function handleShutterInput(inputString) {
let cam = cameras[ci];
if ('shutterSpeed' in cam.shutter) {
if (inputString.indexOf("1/") >= 0) {
cam.setShutter({"shutterSpeed" :parseInt(inputString.substring(2))});
} else {
cam.setShutter({"shutterSpeed" :parseInt(inputString)});
}
} else {
cam.setShutter({"shutterAngle": parseInt(parseFloat(inputString)*100)});
}
}
function decreaseWhiteBalance() {
cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance-50,cameras[ci].WhiteBalanceTint);
}
function increaseWhiteBalance() {
cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance+50,cameras[ci].WhiteBalanceTint);
}
function decreaseWhiteBalanceTint() {
cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance,cameras[ci].WhiteBalanceTint-1);
}
function increaseWhiteBalanceTint() {
cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance,cameras[ci].WhiteBalanceTint+1);
}
function AEmodeInputHandler() {
let AEmode = document.getElementById("AEmodeDropDown").value;
let AEtype = document.getElementById("AEtypeDropDown").value;
cameras[ci].setAutoExposureMode({mode: AEmode, type: AEtype});
}
// 0: lift, 1: gamma, 2: gain, 3: offset
function setCCFromUI(which) {
let lumaFloat = parseFloat(document.getElementsByClassName("CClumaLabel")[which].innerHTML);
let redFloat = parseFloat(document.getElementsByClassName("CCredLabel")[which].innerHTML);
let greenFloat = parseFloat(document.getElementsByClassName("CCgreenLabel")[which].innerHTML);
let blueFloat = parseFloat(document.getElementsByClassName("CCblueLabel")[which].innerHTML);
let ccobject = {"red": redFloat, "green": greenFloat, "blue": blueFloat, "luma": lumaFloat};
if (which == 0) {
cameras[ci].setCCLift(ccobject);
} else if (which == 1) {
cameras[ci].setCCGamma(ccobject);
} else if (which == 2) {
cameras[ci].setCCGain(ccobject);
} else {
cameras[ci].setCCOffset(ccobject);
}
}
// Reset Color Correction Values
// 0: lift, 1: gamma, 2: gain, 3: offset, 4: contrast, 5: color & LC
function resetCC(which) {
if (which == 0) {
cameras[ci].setCCLift({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0});
} else if (which == 1) {
cameras[ci].setCCGamma({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0});
} else if (which == 2) {
cameras[ci].setCCGain({"red": 1.0, "green": 1.0, "blue": 1.0, "luma": 1.0});
} else if (which == 3) {
cameras[ci].setCCOffset({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0});
} else if (which == 4) {
cameras[ci].setCCContrast({"pivot": 0.5, "adjust": 1.0});
} else if (which == 5) {
cameras[ci].setCCColor({"hue": 0.0, "saturation": 1.0});
cameras[ci].setCCLumaContribuion({"lumaContribution": 1.0});
}
}
2024-07-02 17:53:25 -04:00
/* Helper Functions */
function parseTimecode(timecodeBCD) {
let noDropFrame = timecodeBCD & 0b01111111111111111111111111111111; // The first bit of the timecode is 1 if "Drop Frame Timecode" is on. We don't want to include that in the display.
let decimalTCInt = parseInt(noDropFrame.toString(16), 10); // Convert the BCD number into base ten
let decimalTCString = decimalTCInt.toString().padStart(8, '0'); // Convert the base ten number to a string eight characters long
let finalTCString = decimalTCString.match(/.{1,2}/g).join(':'); // Put colons between every two characters
return finalTCString;
2024-06-11 19:46:25 -04:00
}