This commit is contained in:
DylanSpeiser-BMD 2024-06-14 16:40:07 -07:00
parent 6562ce0712
commit 84187361a7
7 changed files with 621 additions and 451 deletions

BIN
.DS_Store vendored

Binary file not shown.

View file

@ -1,27 +1,40 @@
/* Blackmagic Camera Control JS Class
Written based on the Camera Control
API Documentation from Blackmagic's
Developer info website.
(c) Dylan Speiser 2024
github.com/DylanSpeiser
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published.
This program is distributed in the hope that it will be useful but without
any warranty; without even the implied warranty of merchantability or fitness
for a particular purpose as specified by the License, which you should consult
for more details at LICENSE.txt in this repository.
*/
class BMDCamera { class BMDCamera {
// Pretty name and network hostname (strings) // Pretty name and network hostname (strings)
name; name;
hostname; hostname;
APIAddress; APIAddress;
// Camera index, used for muticam support // Codec and Video Format (JSON object)
index;
// Codec and Video Formats (JSON object)
format; format;
// Current Transport Mode (string) // Current Transport Mode (string)
transportMode; transportMode;
// Playback state (JSON object) // Current Playback state (JSON object)
playbackState; playbackState;
// Record state (JSON object) // Current Record state (JSON object)
recordState; recordState;
// Timecode (JSON Object) // Timecode (JSON Object)
timecode; timecode;
// (pack the source into here also)
// Presets (JSON object) // Presets (JSON object)
presets; presets;
@ -70,28 +83,39 @@ class BMDCamera {
CCcolor; CCcolor;
CClumacontribution; CClumacontribution;
// Keep track of unimplemented functions on the camera (array of strings) // Keep track of unimplemented functions on the camera (array of endpoint strings)
UnimplementedFunctionality = []; UnimplementedFunctionality = [];
// UI refreshing functions, will get called after every get method to keep the UI updated,
// For BYOUI purposes (Bring-Your-Own-UI). If you're using this class for your own UI,
// set this function to point to your UI updater.
static updateUIAll() {};
static updateUIname() {};
static updateUIhostname() {};
static updateUIFormat() {};
static updateUITransportMode() {};
static updateUIPlaybackState() {};
static updateUIRecordState() {};
static updateUITimecode() {};
static updateUIPresets() {};
static updateUIActivePreset() {};
static updateUIAperture() {};
static updateUIZoom() {};
static updateUIFocus() {};
static updateUIISO() {};
static updateUIgain() {};
static updateUIWhiteBalance() {};
static updateUINDStop() {};
static updateUIshutter() {};
static updateUIAutoExposureMode() {};
static updateUIColorCorrection() {};
static updateUILinks() {};
// ============= CONSTRUCTOR ================ // ============= CONSTRUCTOR ================
constructor(hostname, index) { constructor(hostname) {
this.hostname = hostname; this.hostname = hostname;
this.index = index;
this.APIAddress = "http://"+hostname+"/control/api/v1"; this.APIAddress = "http://"+hostname+"/control/api/v1";
this.name = this.hostname.replace(".local","").replaceAll("-"," "); this.name = this.hostname.replace(".local","").replaceAll("-"," ");
this.refresh();
}
// Important refreshing function
refresh() {
document.getElementById("refreshingText").classList.add("refreshing");
this.getAllInfo();
sleep(200).then(() => {
this.updateUIAll();
document.getElementById("refreshingText").classList.remove("refreshing");
}
);
} }
// Wrapper for API call, returns the JSON object from the camera // Wrapper for API call, returns the JSON object from the camera
@ -99,6 +123,7 @@ class BMDCamera {
// Ask the camera a question // Ask the camera a question
let response; let response;
// Only send the request if it's not an Unimplemented Function
if (this.UnimplementedFunctionality.indexOf(endpoint) < 0) { if (this.UnimplementedFunctionality.indexOf(endpoint) < 0) {
response = await sendRequest("GET",this.APIAddress+endpoint,""); response = await sendRequest("GET",this.APIAddress+endpoint,"");
} else { } else {
@ -114,447 +139,318 @@ class BMDCamera {
return response return response
} }
// Wrapper for API call, returns whatever the camera sent back in response // Wrapper for API call, sends data to the camera.
async pushData(endpoint, data) { async pushData(endpoint, data) {
return await sendRequest("PUT",this.APIAddress+endpoint,data); return await sendRequest("PUT",this.APIAddress+endpoint,data);
} }
// ======= UI Updaters ==========
updateUIAll() {
this.updateUIname();
this.updateUIhostname();
this.updateUIFormat();
this.updateUITransportMode();
this.updateUIPlaybackState();
this.updateUIRecordState();
this.updateUITimecode();
this.updateUIPresets();
this.updateUIActivePreset();
this.updateUIAperture();
this.updateUIZoom();
this.updateUIFocus();
this.updateUIISO();
this.updateUIgain();
this.updateUIWhiteBalance();
this.updateUINDStop();
this.updateUIshutter();
this.updateUIAutoExposureMode();
this.updateUIColorCorrection();
this.updateUILinks();
}
updateUIname() {
document.getElementById("cameraName").innerHTML = this.name;
}
updateUIhostname() {
document.getElementById("hostnameInput").value = this.hostname;
}
updateUIFormat() {
document.getElementById("formatCodec").innerHTML = this.format.codec.toUpperCase().replace(":"," ").replace("_",":");
let resObj = this.format.recordResolution;
document.getElementById("formatResolution").innerHTML = resObj.width + "x" + resObj.height;
document.getElementById("formatFPS").innerHTML = this.format.frameRate+" fps";
}
updateUITransportMode() {
//TBD
}
updateUIPlaybackState() {
//TBD
}
updateUIRecordState() {
if (this.recordState.recording) {
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");
}
}
updateUITimecode() {
var tcString = parseInt(this.timecode.timecode.toString(16),10).toString().padStart(8,'0').match(/.{1,2}/g).join(':');
document.getElementById("timecodeLabel").innerHTML = tcString;
}
updateUIPresets() {
var presetsList = document.getElementById("presetsDropDown");
presetsList.innerHTML = "";
this.presets.forEach((presetItem) => {
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);
});
}
updateUIActivePreset() {
var presetsList = document.getElementById("presetsDropDown");
presetsList.childNodes.forEach((child) => {
if (child.nodeName == 'OPTION' && child.value == cameras[ci].activePreset) {
child.selected=true
} else {
child.selected=false
}
})
}
updateUIAperture() {
document.getElementById("irisRange").value = this.apertureNormalised;
document.getElementById("apertureStopsLabel").innerHTML = this.apertureStop.toFixed(1);
}
updateUIZoom() {
document.getElementById("zoomRange").value = this.zoomNormalised;
document.getElementById("zoomMMLabel").innerHTML = this.zoomMM;
}
updateUIFocus() {
document.getElementById("focusRange").value = this.focusNormalised;
}
updateUIISO() {
document.getElementById("ISOInput").value = this.ISO;
}
updateUIgain() {
var gainString = "";
if (this.gain >= 0) {
gainString = "+"+this.gain+"db"
} else {
gainString = this.gain+"db"
}
document.getElementById("gainSpan").innerHTML = gainString;
}
updateUIWhiteBalance() {
document.getElementById("whiteBalanceSpan").innerHTML = this.WhiteBalance+"K";
document.getElementById("whiteBalanceTintSpan").innerHTML = this.WhiteBalanceTint;
}
updateUINDStop() {
document.getElementById("ndFilterSpan").innerHTML = this.NDStop;
if (this.UnimplementedFunctionality.includes("/video/ndFilter")) {
document.getElementById("ndFilterSpan").innerHTML = 0;
document.getElementById("ndFilterSpan").disabled = true;
}
}
updateUIshutter() {
var shutterString = ""
if ('shutterSpeed' in this.shutter) {
shutterString = "1/"+this.shutter.shutterSpeed
} else {
var shangleString = (this.shutter.shutterAngle / 100).toFixed(1).toString()
if (shangleString.indexOf(".0") > 0) {
shutterString = parseFloat(shangleString).toFixed(0)+"°";
} else {
shutterString = shangleString+"°";
}
}
document.getElementById("shutterSpan").innerHTML = shutterString;
}
updateUIAutoExposureMode() {
let AEmodeSelect = document.getElementById("AEmodeDropDown");
let AEtypeSelect = document.getElementById("AEtypeDropDown");
AEmodeSelect.value = cameras[ci].AutoExposureMode.mode;
AEtypeSelect.value = cameras[ci].AutoExposureMode.type;
}
updateUIColorCorrection() {
// Lift
document.getElementsByClassName("CClumaLabel")[0].innerHTML = this.CClift.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[0].innerHTML = this.CClift.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[0].innerHTML = this.CClift.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[0].innerHTML = this.CClift.blue.toFixed(2);
// Gamma
document.getElementsByClassName("CClumaLabel")[1].innerHTML = this.CCgamma.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[1].innerHTML = this.CCgamma.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[1].innerHTML = this.CCgamma.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[1].innerHTML = this.CCgamma.blue.toFixed(2);
// Gain
document.getElementsByClassName("CClumaLabel")[2].innerHTML = this.CCgain.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[2].innerHTML = this.CCgain.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[2].innerHTML = this.CCgain.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[2].innerHTML = this.CCgain.blue.toFixed(2);
// Offset
document.getElementsByClassName("CClumaLabel")[3].innerHTML = this.CCoffset.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[3].innerHTML = this.CCoffset.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[3].innerHTML = this.CCoffset.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[3].innerHTML = this.CCoffset.blue.toFixed(2);
// Contrast
document.getElementById("CCcontrastPivotRange").value = this.CCcontrast.pivot;
document.getElementById("CCcontrastPivotLabel").innerHTML = this.CCcontrast.pivot.toFixed(2);
document.getElementById("CCcontrastAdjustRange").value = this.CCcontrast.adjust;
document.getElementById("CCcontrastAdjustLabel").innerHTML = this.CCcontrast.adjust.toFixed(2);
// Color
document.getElementById("CChueRange").value = this.CCcolor.hue;
document.getElementById("CCcolorHueLabel").innerHTML = this.CCcolor.hue.toFixed(2);
document.getElementById("CCsaturationRange").value = this.CCcolor.saturation;
document.getElementById("CCcolorSatLabel").innerHTML = this.CCcolor.saturation.toFixed(2);
document.getElementById("CClumaContributionRange").value = this.CClumacontribution.lumaContribution;
document.getElementById("CCcolorLCLabel").innerHTML = this.CClumacontribution.lumaContribution.toFixed(2);
}
updateUILinks() {
document.getElementById("documentationLink").href = "http://"+this.hostname+"/control/documentation.html";
document.getElementById("mediaManagerLink").href = "http://"+this.hostname;
}
// =============== GETTERS ================== // =============== GETTERS ==================
// name, hostname, APIaddress, index handled by constructor // name, hostname, APIaddress handled by constructor
// since these are all asynchronous, they will return promises to use with await or .then()
getFormat() { async getFormat() {
this.pullData("/system/format").then((value) => {this.format = value; this.updateUIFormat()}); this.format = await this.pullData("/system/format");
BMDCamera.updateUIFormat();
return this.format;
} }
getTransportMode() { async getTransportMode() {
this.pullData("/transports/0").then((value) => {this.transportMode = value; this.updateUITransportMode()}); this.transportMode = await this.pullData("/transports/0");
BMDCamera.updateUITransportMode();
return this.transportMode;
} }
getPlaybackState() { async getPlaybackState() {
this.pullData("/transports/0/playback").then((value) => {this.playbackState = value; this.updateUIPlaybackState()}); this.playbackState = await this.pullData("/transports/0/playback");
BMDCamera.updateUIPlaybackState();
return this.playbackState;
} }
getRecordState() { async getRecordState() {
this.pullData("/transports/0/record").then((value) => {this.recordState = value; this.updateUIRecordState()}); this.recordState = await this.pullData("/transports/0/record");
BMDCamera.updateUIRecordState();
return this.recordState;
} }
getTimecode() { async getTimecode() {
this.pullData("/transports/0/timecode").then((value) => {this.timecode = value; this.updateUITimecode()}); this.timecode = await this.pullData("/transports/0/timecode");
this.pullData("/transports/0/timecode/source").then((value) => {this.timecode.source = value.source}); BMDCamera.updateUITimecode();
return this.timecode;
} }
getPresets() { async getPresets() {
this.pullData("/presets").then((value) => {this.presets = value.presets; this.updateUIPresets()}); this.pullData("/presets").then((value) => {this.presets = value.presets; BMDCamera.updateUIPresets();});
return this.presets;
} }
getActivePreset() { async getActivePreset() {
this.pullData("/presets/active").then((value) => {this.activePreset = value; this.updateUIActivePreset()}); this.activePreset = await this.pullData("/presets/active");
BMDCamera.updateUIActivePreset();
return this.activePreset;
} }
getAperture() { async getAperture() {
this.pullData("/lens/iris").then((value) => {this.apertureStop = value.apertureStop; this.apertureNormalised = value.normalised; this.updateUIAperture()}); this.pullData("/lens/iris").then((value) => {this.apertureStop = value.apertureStop; this.apertureNormalised = value.normalised; BMDCamera.updateUIAperture();});
return this.apertureNormalised;
} }
getZoom() { async getZoom() {
this.pullData("/lens/zoom").then((value) => {this.zoomMM = value.focalLength; this.zoomNormalised = value.normalised; this.updateUIZoom()}); this.pullData("/lens/zoom").then((value) => {this.zoomMM = value.focalLength; this.zoomNormalised = value.normalised; BMDCamera.updateUIZoom();});
return this.zoomNormalised;
} }
getFocus() { async getFocus() {
this.pullData("/lens/focus").then((value) => {this.focusNormalised = value.normalised; this.updateUIFocus()}); this.pullData("/lens/focus").then((value) => {this.focusNormalised = value.normalised; BMDCamera.updateUIFocus();});
return this.focusNormalised;
} }
getISO() { async getISO() {
this.pullData("/video/iso").then((value) => {this.ISO = value.iso; this.updateUIISO()}); this.pullData("/video/iso").then((value) => {this.ISO = value.iso; BMDCamera.updateUIISO();});
return this.ISO;
} }
getGain() { async getGain() {
this.pullData("/video/gain").then((value) => {this.gain = value.gain; this.updateUIgain()}); this.pullData("/video/gain").then((value) => {this.gain = value.gain; BMDCamera.updateUIgain();});
return this.gain;
} }
getWhiteBalance() { async getWhiteBalance() {
this.pullData("/video/whiteBalance").then((value) => {this.WhiteBalance = value.whiteBalance}); this.pullData("/video/whiteBalance").then((value) => {this.WhiteBalance = value.whiteBalance});
this.pullData("/video/whiteBalanceTint").then((value) => {this.WhiteBalanceTint = value.whiteBalanceTint; this.updateUIWhiteBalance()}); this.pullData("/video/whiteBalanceTint").then((value) => {this.WhiteBalanceTint = value.whiteBalanceTint; BMDCamera.updateUIWhiteBalance();});
return this.WhiteBalance;
} }
getND() { async getND() {
this.pullData("/video/ndFilter").then((value) => {this.NDStop = value.stop; this.updateUINDStop()}); this.pullData("/video/ndFilter").then((value) => {this.NDStop = value.stop});
this.pullData("/video/ndFilter/displayMode").then((value) => {this.NDMode = value.displayMode}); this.pullData("/video/ndFilter/displayMode").then((value) => {this.NDMode = value.displayMode; BMDCamera.updateUINDStop();});
return this.NDStop;
} }
getShutter() { async getShutter() {
this.pullData("/video/shutter").then((value) => {this.shutter = value; this.updateUIshutter()}); this.shutter = await this.pullData("/video/shutter");
BMDCamera.updateUIshutter();
return this.shutter;
} }
getAutoExposureMode() { async getAutoExposureMode() {
this.pullData("/video/autoExposure").then((value) => {this.AutoExposureMode = value; this.updateUIAutoExposureMode()}); this.AutoExposureMode = await this.pullData("/video/autoExposure");
BMDCamera.updateUIAutoExposureMode();
return this.AutoExposureMode;
} }
getColorCorrection() { // This one just fetches the data and stores it in the normal objects. No return value.
this.pullData("/colorCorrection/lift").then((value) => {this.CClift = value}); async getColorCorrection() {
this.pullData("/colorCorrection/gamma").then((value) => {this.CCgamma = value}); this.CClift = await this.pullData("/colorCorrection/lift");
this.pullData("/colorCorrection/gain").then((value) => {this.CCgain = value}); this.CCgamma = await this.pullData("/colorCorrection/gamma");
this.pullData("/colorCorrection/offset").then((value) => {this.CCoffset = value}); this.CCgain = await this.pullData("/colorCorrection/gain");
this.pullData("/colorCorrection/contrast").then((value) => {this.CCcontrast = value}); this.CCoffset = await this.pullData("/colorCorrection/offset");
this.pullData("/colorCorrection/color").then((value) => {this.CCcolor = value}); this.CCcontrast = await this.pullData("/colorCorrection/contrast");
this.pullData("/colorCorrection/lumaContribution").then((value) => {this.CClumacontribution = value; this.updateUIColorCorrection()}); this.CCcolor = await this.pullData("/colorCorrection/color");
this.CClumacontribution = await this.pullData("/colorCorrection/lumaContribution");
BMDCamera.updateUIColorCorrection();
} }
getAllInfo() { // This method usually takes 200-250 ms
this.getFormat(); async getAllInfo() {
this.getTransportMode(); await this.getFormat();
this.getPlaybackState(); await this.getTransportMode();
this.getRecordState(); await this.getPlaybackState();
this.getTimecode(); await this.getRecordState();
this.getPresets(); await this.getTimecode();
this.getActivePreset(); await this.getPresets();
this.getAperture(); await this.getActivePreset();
this.getZoom(); await this.getAperture();
this.getFocus(); await this.getZoom();
this.getISO(); await this.getFocus();
this.getGain(); await this.getISO();
this.getWhiteBalance(); await this.getGain();
this.getND(); await this.getWhiteBalance();
this.getShutter(); await this.getND();
this.getAutoExposureMode(); await this.getShutter();
this.getColorCorrection(); await this.getAutoExposureMode();
await this.getColorCorrection();
} }
// =============== SETTERS ================== // =============== SETTERS ==================
// name, hostname, APIaddress, index should never have to be set // name, hostname, APIaddress should never have to be set
setCodecFormat(newCodecFormatObject) { async setCodecFormat(newCodecFormatObject) {
this.pushData("/system/codecFormat",newCodecFormatObject).then(() => sleep(1000).then(() => this.getCodecFormat())); await this.pushData("/system/codecFormat",newCodecFormatObject);
await sleep(500);
await this.getCodecFormat();
} }
setVideoFormat(newVideoFormatObject) { async setVideoFormat(newVideoFormatObject) {
this.pushData("/system/videoFormat",newVideoFormatObject).then(() => sleep(1000).then(() => this.getCodecFormat())); await this.pushData("/system/videoFormat",newVideoFormatObject);
await sleep(500);
await this.getCodecFormat();
} }
setTransportMode(newTransportModeString) { async setTransportMode(newTransportModeString) {
this.pushData("/transports/0",{"mode": newTransportModeString}).then(() => sleep(1000).then(() => this.getTransportMode())); await this.pushData("/transports/0",{"mode": newTransportModeString});
await sleep(500);
await this.getTransportMode();
} }
setPlaybackState(playbackStateObject) { async setPlaybackState(playbackStateObject) {
this.pushData("/transports/0/playback",playbackStateObject).then(() => sleep(1000).then(() => this.getPlaybackState())); await this.pushData("/transports/0/playback",playbackStateObject);
await sleep(500);
await this.getPlaybackState();
} }
sendPresetFile(file) { async sendPresetFile(file) {
sendRequest("POST",this.APIAddress+"/presets",file) await sendRequest("POST",this.APIAddress+"/presets",file);
} }
setActivePreset(presetString) { async setActivePreset(presetString) {
this.pushData("/presets/active",{"preset": presetString}).then(() => sleep(1000).then(() => this.refresh())); await this.pushData("/presets/active",{"preset": presetString});
await sleep(500);
await this.getAllInfo();
} }
updatePreset(presetString) { async updatePreset(presetString) {
this.pushData("/presets/active",{"preset": presetString}).then(() => sleep(1000).then(() => this.getPresets())); await this.pushData("/presets/active",{"preset": presetString});
await sleep(500);
await this.getPresets();
} }
setAperture(apertureNormalisedFloat) { async setAperture(apertureNormalisedFloat) {
this.pushData("/lens/iris",{"normalised": apertureNormalisedFloat}).then(() => sleep(1000).then(() => this.getAperture())); await this.pushData("/lens/iris",{"normalised": apertureNormalisedFloat});
await sleep(1500);
await this.getAperture();
} }
setZoom(zoomNormalisedFloat) { async setZoom(zoomNormalisedFloat) {
this.pushData("/lens/zoom",{"normalised": zoomNormalisedFloat}).then(() => sleep(1000).then(() => this.getZoom())); await this.pushData("/lens/zoom",{"normalised": zoomNormalisedFloat});
await sleep(1500);
await this.getZoom();
} }
setFocus(focusNormalisedFloat) { async setFocus(focusNormalisedFloat) {
this.pushData("/lens/focus",{"normalised": focusNormalisedFloat}).then(() => sleep(1000).then(() => this.getFocus())); await this.pushData("/lens/focus",{"normalised": focusNormalisedFloat});
await sleep(1500);
await this.getFocus();
} }
setISO(ISOint) { async setISO(ISOint) {
this.pushData("/video/iso",{"iso":ISOint}).then(() => sleep(1000).then(() => this.getISO())); await this.pushData("/video/iso",{"iso":ISOint});
await sleep(500);
await this.getISO();
await this.getGain();
} }
setGain(gainInt) { async setGain(gainInt) {
this.pushData("/video/gain",{"gain":gainInt}).then(() => sleep(1000).then(() => this.getGain())); await this.pushData("/video/gain",{"gain":gainInt});
await sleep(500);
await this.getGain();
await this.getISO();
} }
setWhiteBalance(whiteBalanceInt, whiteBalanceTintInt) { async setWhiteBalance(whiteBalanceInt, whiteBalanceTintInt) {
this.pushData("/video/whiteBalance",{"whiteBalance": whiteBalanceInt}); await this.pushData("/video/whiteBalance",{"whiteBalance": whiteBalanceInt});
this.pushData("/video/whiteBalanceTint",{"whiteBalanceTint": whiteBalanceTintInt}).then(() => sleep(1000).then(() => this.getWhiteBalance())); await this.pushData("/video/whiteBalanceTint",{"whiteBalanceTint": whiteBalanceTintInt});
await sleep(500);
await this.getWhiteBalance();
} }
setND(NDstopInt) { async setND(NDstopInt) {
this.pushData("/video/ndFilter",{"stop": NDstopInt}).then(() => sleep(1000).then(() => this.getND())); await this.pushData("/video/ndFilter",{"stop": NDstopInt});
await sleep(500);
await this.getND();
} }
setNDDisplayMode(displayModeString) { async setNDDisplayMode(displayModeString) {
this.pushData("/video/ndFilter/displayMode",{"displayMode": displayModeString}).then(() => sleep(1000).then(() => this.getND())); await this.pushData("/video/ndFilter/displayMode",{"displayMode": displayModeString});
await sleep(500);
await this.getND();
} }
// Accepts JSON obejcts with either shutterSpeed or shutterAngle properties // Accepts JSON obejcts with either shutterSpeed or shutterAngle properties
// Note that shutterAngle is 100x the displayed value // Note that the shutterAngle value returned by the API is 100x the actual value
setShutter(shutterObject) { async setShutter(shutterObject) {
this.pushData("/video/shutter",shutterObject).then(() => sleep(1000).then(() => this.getShutter())); await this.pushData("/video/shutter",shutterObject);
await sleep(500);
await this.getShutter();
} }
setAutoExposureMode(AEmodeObject) { async setAutoExposureMode(AEmodeObject) {
this.pushData("/video/autoExposure",AEmodeObject).then(() => sleep(1000).then(() => this.getAutoExposureMode())); await this.pushData("/video/autoExposure",AEmodeObject);
await sleep(500);
await this.getAutoExposureMode();
} }
setCCLift(CCliftObject) { async setCCLift(CCliftObject) {
this.pushData("/colorCorrection/lift",CCliftObject).then(() => sleep(1000).then(() => this.getColorCorrection())); await this.pushData("/colorCorrection/lift",CCliftObject);
await sleep(500);
await this.getColorCorrection();
} }
setCCGamma(CCgammaObject) { async setCCGamma(CCgammaObject) {
this.pushData("/colorCorrection/gamma",CCgammaObject).then(() => sleep(1000).then(() => this.getColorCorrection())); await this.pushData("/colorCorrection/gamma",CCgammaObject);
await sleep(500);
await this.getColorCorrection();
} }
setCCGain(CCgainObject) { async setCCGain(CCgainObject) {
this.pushData("/colorCorrection/gain",CCgainObject).then(() => sleep(1000).then(() => this.getColorCorrection())); await this.pushData("/colorCorrection/gain",CCgainObject);
await sleep(500);
await this.getColorCorrection();
} }
setCCOffset(CCoffsetObject) { async setCCOffset(CCoffsetObject) {
this.pushData("/colorCorrection/offset",CCoffsetObject).then(() => sleep(1000).then(() => this.getColorCorrection())); await this.pushData("/colorCorrection/offset",CCoffsetObject);
await sleep(500);
await this.getColorCorrection();
} }
setCCContrast(CCcontrastObject) { async setCCContrast(CCcontrastObject) {
this.pushData("/colorCorrection/contrast",CCcontrastObject).then(() => sleep(1000).then(() => this.getColorCorrection())); await this.pushData("/colorCorrection/contrast",CCcontrastObject);
await sleep(500);
await this.getColorCorrection();
} }
setCCColor(CCcolorObject) { async setCCColor(CCcolorObject) {
this.pushData("/colorCorrection/color",CCcolorObject).then(() => sleep(1000).then(() => this.getColorCorrection())); await this.pushData("/colorCorrection/color",CCcolorObject);
await sleep(500);
await this.getColorCorrection();
} }
setCCLumaContribuion(CClumacontributionObject) { async setCCLumaContribuion(CClumacontributionObject) {
this.pushData("/colorCorrection/lumaContribution",CClumacontributionObject).then(() => sleep(1000).then(() => this.getColorCorrection())); await this.pushData("/colorCorrection/lumaContribution",CClumacontributionObject);
await sleep(500);
await this.getColorCorrection();
} }
// =============== Other Commands ======================= // =============== Other Commands =======================
doAutoFocus() { async doAutoFocus() {
this.pushData("/lens/focus/doAutoFocus").then(() => sleep(1500).then(() => this.getFocus())); await this.pushData("/lens/focus/doAutoFocus");
sleep(1500).then(() => this.getFocus());
} }
play() { async play() {
this.pushData("/transports/0/play").then(() => sleep(1000).then(() => this.getPlaybackState())); await this.pushData("/transports/0/play");
await sleep(500);
await this.getPlaybackState();
} }
record() { async record() {
this.pushData("/transports/0/record",{"recording": true}).then(() => { await this.pushData("/transports/0/record",{"recording": true});
sleep(2000).then(() => this.getRecordState()); await sleep(1000);
}); await this.getRecordState();
} }
stopTransport() { async stopTransport() {
this.pushData("/transports/0/stop").then(() => { await this.pushData("/transports/0/stop");
sleep(2000).then(() => this.getPlaybackState()); await sleep(1000);
}); await this.getPlaybackState();
} }
stopRecord() { async stopRecord() {
this.pushData("/transports/0/record",{"recording": false}).then(() => { await this.pushData("/transports/0/record",{"recording": false});
sleep(2000).then(() => this.getRecordState()); await sleep(1000);
}); await this.getRecordState();
} }
} }
@ -581,4 +477,7 @@ async function sendRequest(method, url, data) {
function sleep(ms) { function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
/* (c) Dylan Speiser 2024
github.com/DylanSpeiser */

View file

@ -1,9 +1,9 @@
# BM Camera Control WebUI # BM Camera Control WebUI
This web app is a demonstration of the [Blackmagic Design](https://blackmagicdesign.com) Camera Control REST API via an extensible web interface. Modeled after the interface of ATEM Software Control, most of the major functions of the camera that can be controlled by the API are available here. This web app is utilizes the [Blackmagic](https://blackmagicdesign.com) Camera Control REST API to control cameras via an extensible web interface. Modeled after the interface of ATEM Software Control, most of the camera's functions that can be controlled by the API are available here.
>This program was written based on the official REST API documentation from Blackmagic, which can be found [here](https://documents.blackmagicdesign.com/DeveloperManuals/RESTAPIforBlackmagicCameras.pdf) >This program was written based on the official REST API documentation from Blackmagic, which can be found [here](https://documents.blackmagicdesign.com/DeveloperManuals/RESTAPIforBlackmagicCameras.pdf)
Using this tool, you can control many features of your Blackmagic studio and cinema cameras *without any extra hardware!* Use it for remote monitoring, color correction, focus pulling, or keeping tabs on your eqiupment. The `BMD-Camera-Control.js` file is also useful if you want to write your own web app using the REST API. Using this tool, you can control your Blackmagic studio and cinema cameras *without any extra hardware!* Use it for remote monitoring, color correction, focus pulling, or keeping tabs on your eqiupment. The `BMD-Camera-Control.js` file is also useful if you want to write your own web app using the REST API. More details on how to interface with it can be found below.
![Screenshot 1](screenshots/WebUI1.png) ![Screenshot 1](screenshots/WebUI1.png)
@ -34,9 +34,6 @@ The app polls data from the camera every ten seconds (you'll see "Refreshing..."
### Arrows, Buttons, and Text Boxes ### Arrows, Buttons, and Text Boxes
Many controls work both with sliders/buttons, but if you want to enter a specific value then click on the number and enter the value manually. Many controls work both with sliders/buttons, but if you want to enter a specific value then click on the number and enter the value manually.
<br>
(Sometimes this might take a couple of tries).
<br>
### Media Management ### Media Management
To view files on the drives of your camera, follow the link in the bottom-right corder for the **Web Media Manager**. This will take you to *your camera's* internal web server where you can view, download, and upload video files over the network. To view files on the drives of your camera, follow the link in the bottom-right corder for the **Web Media Manager**. This will take you to *your camera's* internal web server where you can view, download, and upload video files over the network.
@ -57,9 +54,10 @@ This app (as of June 2024), should be compatible with the following Blackmagic c
|-|-|-| |-|-|-|
| Pocket Cinema Camera 4K | `Pocket-Cinema-Camera-4K.local` | FW 8.6+ Required | | Pocket Cinema Camera 4K | `Pocket-Cinema-Camera-4K.local` | FW 8.6+ Required |
| Pocket Cinema Camera 6K | `Pocket-Cinema-Camera-6K.local` | FW 8.6+ Required | | Pocket Cinema Camera 6K | `Pocket-Cinema-Camera-6K.local` | FW 8.6+ Required |
| Pocket Cinema Camera 6K G2 | `Pocket-Cinema-Camera-6K-G2.local` | FW 8.6+ Required |
| Pocket Cinema Camera 6K Pro | `Pocket-Cinema-Camera-6K-Pro.local` | FW 8.6+ Required | | Pocket Cinema Camera 6K Pro | `Pocket-Cinema-Camera-6K-Pro.local` | FW 8.6+ Required |
| Cinema Camera 6K | `Cinema-Camera-6K.local` | | | Cinema Camera 6K | `Blackmagic-Cinema-Camera-6K.local` | |
| URSA Broadcast G2 | `URSA-Broadcast-G2.local`$^1$ | | | URSA Broadcast G2 | `URSA-Broadcast-G2.local` | |
| Micro Studio Camera 4K G2 | `Micro-Studio-Camera-4K-G2.local`$^1$ | | | Micro Studio Camera 4K G2 | `Micro-Studio-Camera-4K-G2.local`$^1$ | |
| Studio Camera 4K Plus | `Studio-Camera-4K-Plus.local` | | | Studio Camera 4K Plus | `Studio-Camera-4K-Plus.local` | |
| Studio Camera 4K Pro | `Studio-Camera-4K-Pro.local` | | | Studio Camera 4K Pro | `Studio-Camera-4K-Pro.local` | |
@ -70,10 +68,29 @@ This app (as of June 2024), should be compatible with the following Blackmagic c
$^1:$ Unverified best guess <br> $^1:$ Unverified best guess <br>
If any of this information is incorrect, please let me know in the Issues section of this repository. If any of this information is incorrect, please let me know in the Issues section of this repository.
# The Code
It's open source, so feel free to modify the code to add new features or suit it to your setup. (Just don't sell it, okay?) It's all vanilla JavaScript and HTML so it's super easy to work with and modify. Fork it and make something cool!
<br><br>
If you like this project and want it to improve, consider making a Pull Request and I'll give it a look. Or, if coding isn't your thing, open an Issue in the repo's issue tracker.
## Using `BMD-Camera-Control.js`
You are more than welcome to use this JavaScript class in your own projects. Just include the file (with its attributions).
<br><br>
Cameras are represented as BMDCamera objects, instantiated with the `new` keyword and the constructor, which takes the hostname as a String argument.
<br><br>
After instantiation, the constructor does NOT automatically fetch any data from the camera. This can be done using the `getAllInfo()` method, or the individual getters if you only need a few details. These are all asynchronous and so must be waited upon using `await` or `.then()`. Consult your nearest Google search bar for help implementing asynchronous JavaScript, that's how I did it.
<br><br>
Many of setter functions take Objects as arguments, rather than Strings or ints. Consult the comments and the REST API documentation for details. Many also have integrated waiting periods before fetching the result of the operation and updating the UI. This is to give the camera time to physically respond to the command.
<br><br>
This file is heavily commented so everything _should_ be pretty clear, but let me know in the Issue tracker if you're having trouble.
## BYOUI
If you want to use `BMD-Camera-Control.js` in your own UI, there are static references to functions in the BMDCamera class that get called after changing a value. They all look like `updateUIxxxxxx()`. Set these references to point to your UI updating routines in _your_ source file. No need to write them in `BMD-Camera-Control.js`.
# Issues and To-Dos # Issues and To-Dos
## Known Issues ## Known Issues
- Page responsiveness - Page responsiveness
- Editable spans are hard to use - Sometimes the refresh happens while you're typing in a text field and replaces what you've typed
## Unknown Issues ## Unknown Issues
Please report issues to the repo's issue tracker so I can fix them! Please report issues to the repo's issue tracker so I can fix them!
@ -86,4 +103,11 @@ If you're having trouble and don't know why, check the browser console.
- Add audio settings - Add audio settings
- Add codec/format switching settings - Add codec/format switching settings
- Improve responsiveness - Improve responsiveness
- Improve error handling - Improve error handling
- Save / download preset files to/from the camera
- Code cleanup (once I learn better web design lol)
### For License and Copyright details, See `LICENSE.txt`
(c) 2024 Dylan Speiser
<br>
Licensed under the GNU General Public License

View file

@ -1,3 +1,5 @@
<!-- (c) 2024 Dylan Speiser -->
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -58,10 +60,10 @@
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer"> <div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
<button class="CCResetButton circleButton" onclick="resetCC(0)" title="Reset Lift">&#10227</button> <button class="CCResetButton circleButton" onclick="resetCC(0)" title="Reset Lift">&#10227</button>
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer"> <div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" oninput="setCCFromUI(0)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(0)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" oninput="setCCFromUI(0)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(0)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" oninput="setCCFromUI(0)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(0)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" oninput="setCCFromUI(0)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(0)}">0.00</span>
</div> </div>
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(0)" title="Set Lift">&#10138</button> <button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(0)" title="Set Lift">&#10138</button>
</div> </div>
@ -70,10 +72,10 @@
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer"> <div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
<button class="CCResetButton circleButton" onclick="resetCC(1)" title="Reset Gamma">&#10227</button> <button class="CCResetButton circleButton" onclick="resetCC(1)" title="Reset Gamma">&#10227</button>
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer"> <div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" oninput="setCCFromUI(1)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(1)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" oninput="setCCFromUI(1)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(1)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" oninput="setCCFromUI(1)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(1)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" oninput="setCCFromUI(1)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(1)}">0.00</span>
</div> </div>
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(1)" title="Set Gamma">&#10138</button> <button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(1)" title="Set Gamma">&#10138</button>
</div> </div>
@ -82,10 +84,10 @@
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer"> <div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
<button class="CCResetButton circleButton" onclick="resetCC(2)" title="Reset Gain">&#10227</button> <button class="CCResetButton circleButton" onclick="resetCC(2)" title="Reset Gain">&#10227</button>
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer"> <div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" oninput="setCCFromUI(2)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(2)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" oninput="setCCFromUI(2)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(2)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" oninput="setCCFromUI(2)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(2)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" oninput="setCCFromUI(2)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(2)}">0.00</span>
</div> </div>
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(2)" title="Set Gain">&#10138</button> <button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(2)" title="Set Gain">&#10138</button>
</div> </div>
@ -95,10 +97,10 @@
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer"> <div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
<button class="CCResetButton circleButton" onclick="resetCC(3)" title="Reset Offset">&#10227</button> <button class="CCResetButton circleButton" onclick="resetCC(3)" title="Reset Offset">&#10227</button>
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer"> <div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" oninput="setCCFromUI(3)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(3)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" oninput="setCCFromUI(3)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(3)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" oninput="setCCFromUI(3)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(3)}">0.00</span>
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" oninput="setCCFromUI(3)">0.00</span> <span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(3)}">0.00</span>
</div> </div>
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(3)" title="Set Offset">&#10138</button> <button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(3)" title="Set Offset">&#10138</button>
</div> </div>
@ -109,7 +111,7 @@
<span class="exposureControlLabel">FILTER</span> <span class="exposureControlLabel">FILTER</span>
<div class="ccExposureSettingValueContainer"> <div class="ccExposureSettingValueContainer">
<a class="expAdjArr" href="#" onclick="decreaseND()" id="NDL">&#9664</a> <a class="expAdjArr" href="#" onclick="decreaseND()" id="NDL">&#9664</a>
<span id="ndFilterSpan" contenteditable="plaintext-only" oninput="cameras[ci].setND(parseInt(this.innerHTML))">0</span> <span id="ndFilterSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setND(parseInt(this.innerHTML))}">0</span>
<a class="expAdjArr" href="#" onclick="increaseND()" id="NDR">&#9654</a> <a class="expAdjArr" href="#" onclick="increaseND()" id="NDR">&#9654</a>
</div> </div>
</div> </div>
@ -117,7 +119,7 @@
<span class="exposureControlLabel">GAIN</span> <span class="exposureControlLabel">GAIN</span>
<div class="ccExposureSettingValueContainer"> <div class="ccExposureSettingValueContainer">
<a class="expAdjArr" href="#" onclick="decreaseGain()" id="GAL">&#9664</a> <a class="expAdjArr" href="#" onclick="decreaseGain()" id="GAL">&#9664</a>
<span id="gainSpan" contenteditable="plaintext-only" oninput="cameras[ci].setGain(parseInt(this.innerHTML))">+0db</span> <span id="gainSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setGain(parseInt(this.innerHTML))}">+0db</span>
<a class="expAdjArr" href="#" onclick="increaseGain()" id="GAR">&#9654</a> <a class="expAdjArr" href="#" onclick="increaseGain()" id="GAR">&#9654</a>
</div> </div>
</div> </div>
@ -125,20 +127,20 @@
<span class="exposureControlLabel">SHUTTER</span> <span class="exposureControlLabel">SHUTTER</span>
<div class="ccExposureSettingValueContainer"> <div class="ccExposureSettingValueContainer">
<a class="expAdjArr" href="#" onclick="decreaseShutter()" id="SHL">&#9664</a> <a class="expAdjArr" href="#" onclick="decreaseShutter()" id="SHL">&#9664</a>
<span id="shutterSpan" contenteditable="plaintext-only" oninput="handleShutterInput(this.innerHTML)">1/50</span> <span id="shutterSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); handleShutterInput(this.innerHTML)}">1/50</span>
<a class="expAdjArr" href="#" onclick="increaseShutter()" id="SHR">&#9654</a> <a class="expAdjArr" href="#" onclick="increaseShutter()" id="SHR">&#9654</a>
</div> </div>
</div> </div>
<div class="ccExposureSettingContainer"> <div class="ccExposureSettingContainer">
<span class="exposureControlLabel" onclick="swapWBMode()" id="WBLabel">BALANCE</span> <span class="exposureControlLabel" onclick="swapWBMode()" title="Click here to swap between WB and Tint" id="WBLabel">BALANCE</span>
<div class="ccExposureSettingValueContainer" id="WBValueContainer"> <div class="ccExposureSettingValueContainer" id="WBValueContainer">
<a class="expAdjArr" href="#" onclick="decreaseWhiteBalance()" id="WBL">&#9664</a> <a class="expAdjArr" href="#" onclick="decreaseWhiteBalance()" id="WBL">&#9664</a>
<span id="whiteBalanceSpan" contenteditable="plaintext-only" oninput="cameras[ci].setWhiteBalance(parseInt(this.innerHTML),cameras[ci].whiteBalanceTint)">5600K</span> <span id="whiteBalanceSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setWhiteBalance(parseInt(this.innerHTML),cameras[ci].whiteBalanceTint)}">5600K</span>
<a class="expAdjArr" href="#" onclick="increaseWhiteBalance()" id="WBR">&#9654</a> <a class="expAdjArr" href="#" onclick="increaseWhiteBalance()" id="WBR">&#9654</a>
</div> </div>
<div class="ccExposureSettingValueContainer dNone" id="WBTintValueContainer"> <div class="ccExposureSettingValueContainer dNone" id="WBTintValueContainer">
<a class="expAdjArr" href="#" onclick="decreaseWhiteBalanceTint()" id="WBTL">&#9664</a> <a class="expAdjArr" href="#" onclick="decreaseWhiteBalanceTint()" id="WBTL">&#9664</a>
<span id="whiteBalanceTintSpan" contenteditable="plaintext-only" oninput="cameras[ci].setWhiteBalance(cameras[ci].whiteBalance,parseInt(this.innerHTML))">0</span> <span id="whiteBalanceTintSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setWhiteBalance(cameras[ci].whiteBalance,parseInt(this.innerHTML))}">0</span>
<a class="expAdjArr" href="#" onclick="increaseWhiteBalanceTint()" id="WBLR">&#9654</a> <a class="expAdjArr" href="#" onclick="increaseWhiteBalanceTint()" id="WBLR">&#9654</a>
</div> </div>
</div> </div>
@ -193,7 +195,7 @@
<td>Hostname</td> <td>Hostname</td>
<td> <td>
<input type="text" placeholder="Studio-Camera-6K-Pro.local" id="hostnameInput"> <input type="text" placeholder="Studio-Camera-6K-Pro.local" id="hostnameInput">
<button onclick='initCamera(document.getElementById("hostnameInput").value, ci)'>Connect</button> <button onclick='initCamera(document.getElementById("hostnameInput").value)'>Connect</button>
<span id="connectionErrorSpan"></span> <span id="connectionErrorSpan"></span>
</td> </td>
</tr> </tr>
@ -241,7 +243,7 @@
<table> <table>
<tr> <tr>
<td>ISO</td> <td>ISO</td>
<td><input type="number" id="ISOInput" step="100" oninput="cameras[ci].setISO(parseInt(this.value))"></td> <td><input type="number" id="ISOInput" step="100" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setISO(parseInt(this.value))}"></td>
</tr> </tr>
<tr> <tr>
<td>AE Mode</td> <td>AE Mode</td>
@ -276,7 +278,7 @@
<td>Pivot</td> <td>Pivot</td>
<td><input type="range" max="5" min="-5" step="0.001" id="CCcontrastPivotRange" onmouseup="cameras[ci].setCCContrast({'pivot': parseFloat(this.value)})"></td> <td><input type="range" max="5" min="-5" step="0.001" id="CCcontrastPivotRange" onmouseup="cameras[ci].setCCContrast({'pivot': parseFloat(this.value)})"></td>
<td> <td>
<span id="CCcontrastPivotLabel" contenteditable="plaintext-only" oninput="cameras[ci].setCCContrast({'pivot': parseFloat(this.innerHTML)})">0</span> <span id="CCcontrastPivotLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCContrast({'pivot': parseFloat(this.innerHTML)})}">0</span>
</td> </td>
<td rowspan="2"> <td rowspan="2">
<button class="CCResetButton circleButton" onclick="resetCC(4)" title="Reset Contrast">&#10227</button> <button class="CCResetButton circleButton" onclick="resetCC(4)" title="Reset Contrast">&#10227</button>
@ -286,7 +288,7 @@
<td>Adjust</td> <td>Adjust</td>
<td><input type="range" max="5" min="-5" step="0.001" id="CCcontrastAdjustRange" onmouseup="cameras[ci].setCCContrast({'adjust': parseFloat(this.value)})"></td> <td><input type="range" max="5" min="-5" step="0.001" id="CCcontrastAdjustRange" onmouseup="cameras[ci].setCCContrast({'adjust': parseFloat(this.value)})"></td>
<td> <td>
<span id="CCcontrastAdjustLabel" contenteditable="plaintext-only" oninput="cameras[ci].setCCContrast({'adjust': parseFloat(this.innerHTML)})">0</span> <span id="CCcontrastAdjustLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCContrast({'adjust': parseFloat(this.innerHTML)})}">0</span>
</td> </td>
</tr> </tr>
</table> </table>
@ -299,7 +301,7 @@
<td>Hue</td> <td>Hue</td>
<td><input type="range" max="1" min="-1" step="0.001" id="CChueRange" onmouseup="cameras[ci].setCCColor({'hue': parseFloat(this.value)})"></td> <td><input type="range" max="1" min="-1" step="0.001" id="CChueRange" onmouseup="cameras[ci].setCCColor({'hue': parseFloat(this.value)})"></td>
<td> <td>
<span id="CCcolorHueLabel" contenteditable="plaintext-only" oninput="cameras[ci].setCCColor({'hue': parseFloat(this.innerHTML)})">0</span> <span id="CCcolorHueLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCColor({'hue': parseFloat(this.innerHTML)})}">0</span>
</td> </td>
<td rowspan="3"> <td rowspan="3">
<button class="CCResetButton circleButton" onclick="resetCC(5)" title="Reset Color">&#10227</button> <button class="CCResetButton circleButton" onclick="resetCC(5)" title="Reset Color">&#10227</button>
@ -309,14 +311,14 @@
<td>Saturation</td> <td>Saturation</td>
<td><input type="range" max="2" min="0" step="0.001" id="CCsaturationRange" onmouseup="cameras[ci].setCCColor({'saturation': parseFloat(this.value)})"></td> <td><input type="range" max="2" min="0" step="0.001" id="CCsaturationRange" onmouseup="cameras[ci].setCCColor({'saturation': parseFloat(this.value)})"></td>
<td> <td>
<span id="CCcolorSatLabel" contenteditable="plaintext-only" oninput="cameras[ci].setCCColor({'saturation': parseFloat(this.innerHTML)})">0</span> <span id="CCcolorSatLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCColor({'saturation': parseFloat(this.innerHTML)})}">0</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td>Luma Contribution</td> <td>Luma Contribution</td>
<td><input type="range" max="1" min="0" step="0.001" id="CClumaContributionRange" onmouseup="cameras[ci].setCCLumaContribuion({'lumaContribution': parseFloat(this.value)})"></td> <td><input type="range" max="1" min="0" step="0.001" id="CClumaContributionRange" onmouseup="cameras[ci].setCCLumaContribuion({'lumaContribution': parseFloat(this.value)})"></td>
<td> <td>
<span id="CCcolorLCLabel" contenteditable="plaintext-only" oninput="cameras[ci].setCCLumaContribuion({'lumaContribution': parseFloat(this.innerHTML)})">0</span> <span id="CCcolorLCLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCLumaContribuion({'lumaContribution': parseFloat(this.innerHTML)})}">0</span>
</td> </td>
</tr> </tr>
</table> </table>
@ -329,13 +331,14 @@
<!-- Footer Div --> <!-- Footer Div -->
<div class="flexContainerH" id="footerContainer"> <div class="flexContainerH" id="footerContainer">
<div id="footerLeft"> <div id="footerLeft">
<button onclick="cameras.forEach((element) => {element.refresh()});">Refresh</button> <button onclick="refresh();">Refresh</button>
<span id="refreshingText" class="refreshing">Refreshing...</span> <span id="refreshingText" class="refreshing">Refreshing...</span>
</div> </div>
<div id="footerLinks"> <div id="footerLinks">
<span><a id="documentationLink" href="#" target="_blank">YAML Documentation</a></span> <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="mediaManagerLink" href="#" target="_blank">Web Media Manager</a></span>
<span><a id="githubLink" href="#" target="https://github.com/DylanSpeiser/BM-Camera-Control-WebUI">GitHub</a></span> <span><a id="githubLink" href="#" target="https://github.com/DylanSpeiser/BM-Camera-Control-WebUI">GitHub</a></span>
<span class="">(v 1.0)</span>
</div> </div>
</div> </div>
</body> </body>

View file

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View file

@ -1,7 +1,9 @@
/* Sorry the CSS is such a mess. -DS */
/* ============= WHOLE PAGE STYLES ================== */ /* ============= WHOLE PAGE STYLES ================== */
/* Handle vertical screens */ /* Handle vertical screens */
@media screen and (max-width: 1000px) { @media screen and (max-width: 1200px) {
#cameraControlsContainer { #cameraControlsContainer {
width: 100vw!important; width: 100vw!important;
} }
@ -9,6 +11,10 @@
#cameraControlsContainerExpanded { #cameraControlsContainerExpanded {
display: none; display: none;
} }
body {
font-size: 90%;
}
} }
/* Load NotoSansDisplay Font from resources */ /* Load NotoSansDisplay Font from resources */

340
web-ui.js
View file

@ -1,60 +1,318 @@
/* Blackmagic Camera Control WebUI
WebUI Script functions
(c) Dylan Speiser 2024
github.com/DylanSpeiser
*/
/* Global variables */ /* Global variables */
var cameras = []; var cameras = []; // Array to store all of the camera objects
var ci = 0; var ci = 0; // Index into this array for the currently selected camera.
var WBMode = 0; // 0: balance, 1: tint // cameras[ci] is used to reference the currently selected camera object
var WBMode = 0; // 0: balance, 1: tint
// Set everything up
function bodyOnLoad() { function bodyOnLoad() {
let intervalIDOne = setInterval(timerCallFunction1, 1000); // Tem second timer for refreshing everything // Initialize timers
let intervalIDTen = setInterval(timerCallFunction10, 10000); // Tem second timer for refreshing everything let intervalIDOne = setInterval(timerCallFunction1, 1000); // One second timer for refreshing record / timecode
let intervalIDTen = setInterval(timerCallFunction10, 10000); // Ten second timer for refreshing everything
let newCamHostname = document.getElementById("hostnameInput").value; // Pass along the UI refreshing methods to the BMDCamera class
BMDCamera.updateUIAll = updateUIAll;
if (newCamHostname) { BMDCamera.updateUIname = updateUIname;
initCamera(newCamHostname, ci); BMDCamera.updateUIhostname = updateUIhostname;
} BMDCamera.updateUIFormat = updateUIFormat;
BMDCamera.updateUITransportMode = updateUITransportMode;
BMDCamera.updateUIPlaybackState = updateUIPlaybackState;
BMDCamera.updateUIRecordState = updateUIRecordState;
BMDCamera.updateUITimecode = updateUITimecode;
BMDCamera.updateUIPresets = updateUIPresets;
BMDCamera.updateUIActivePreset = updateUIActivePreset;
BMDCamera.updateUIAperture = updateUIAperture;
BMDCamera.updateUIZoom = updateUIZoom;
BMDCamera.updateUIFocus = updateUIFocus;
BMDCamera.updateUIISO = updateUIISO;
BMDCamera.updateUIgain = updateUIgain;
BMDCamera.updateUIWhiteBalance = updateUIWhiteBalance;
BMDCamera.updateUINDStop = updateUINDStop;
BMDCamera.updateUIshutter = updateUIshutter;
BMDCamera.updateUIAutoExposureMode = updateUIAutoExposureMode;
BMDCamera.updateUIColorCorrection = updateUIColorCorrection;
BMDCamera.updateUILinks = updateUILinks;
} }
function initCamera(hostname, ind) { // Basically a wrapper for BMDCamera() constructor
// Checks the hostname, if it replies successfully then a new BMDCamera object
// is made and gets put in the array at ind
function initCamera(hostname) {
// Check if the hostname is valid
sendRequest("GET", "http://"+hostname+"/control/api/v1/system","").then((response) => { sendRequest("GET", "http://"+hostname+"/control/api/v1/system","").then((response) => {
if (response.status < 300) { if (response.status < 300) {
cameras[ci] = new BMDCamera(hostname, ind); // Success, make a new camera, get all relevant info, and populate the UI
document.getElementById("connectionErrorSpan").innerHTML = ""; cameras[ci] = new BMDCamera(hostname);
cameras[ci].getAllInfo();
document.getElementById("connectionErrorSpan").innerHTML = "Connected.";
document.getElementById("connectionErrorSpan").setAttribute("style","color: #6e6e6e;");
} else { } else {
// Something has gone wrong, tell the user
document.getElementById("connectionErrorSpan").innerHTML = response.statusText; document.getElementById("connectionErrorSpan").innerHTML = response.statusText;
} }
}).catch(error => { }).catch(error => {
// Something has gone wrong, tell the user
document.getElementById("connectionErrorSpan").title = error; document.getElementById("connectionErrorSpan").title = error;
document.getElementById("connectionErrorSpan").innerHTML = "Error "+error.code+": "+error.name+" (Your hostname is probably incorrect, hover for more details)"; document.getElementById("connectionErrorSpan").innerHTML = "Error "+error.code+": "+error.name+" (Your hostname is probably incorrect, hover for more details)";
}); });
} }
// Important refreshing function. Gets all of the camera's info and populates the UI with it.
// This function controls the visibility of the "Refreshing..." text in the bottom right corner.
function refresh() {
document.getElementById("refreshingText").classList.add("refreshing");
cameras[ci].getAllInfo().then(() => {
document.getElementById("refreshingText").classList.remove("refreshing");
}
);
}
// =============================== UI Updaters ==================================
// ==============================================================================
function updateUIAll() {
updateUIname();
updateUIhostname();
updateUIFormat();
updateUITransportMode();
updateUIPlaybackState();
updateUIRecordState();
updateUITimecode();
updateUIPresets();
updateUIActivePreset();
updateUIAperture();
updateUIZoom();
updateUIFocus();
updateUIISO();
updateUIgain();
updateUIWhiteBalance();
updateUINDStop();
updateUIshutter();
updateUIAutoExposureMode();
updateUIColorCorrection();
updateUILinks();
}
function updateUIname() {
document.getElementById("cameraName").innerHTML = cameras[ci].name;
}
function updateUIhostname() {
document.getElementById("hostnameInput").value = cameras[ci].hostname;
}
function updateUIFormat() {
document.getElementById("formatCodec").innerHTML = cameras[ci].format.codec.toUpperCase().replace(":"," ").replace("_",":");
let resObj = cameras[ci].format.recordResolution;
document.getElementById("formatResolution").innerHTML = resObj.width + "x" + resObj.height;
document.getElementById("formatFPS").innerHTML = cameras[ci].format.frameRate+" fps";
}
function updateUITransportMode() {
//TBI
}
function updateUIPlaybackState() {
//TBI
}
function updateUIRecordState() {
if (cameras[ci].recordState.recording) {
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");
}
}
function updateUITimecode() {
var tcString = parseInt(cameras[ci].timecode.timecode.toString(16),10).toString().padStart(8,'0').match(/.{1,2}/g).join(':');
document.getElementById("timecodeLabel").innerHTML = tcString;
}
function updateUIPresets() {
var presetsList = document.getElementById("presetsDropDown");
presetsList.innerHTML = "";
cameras[ci].presets.forEach((presetItem) => {
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);
});
}
function updateUIActivePreset() {
var presetsList = document.getElementById("presetsDropDown");
presetsList.childNodes.forEach((child) => {
if (child.nodeName == 'OPTION' && child.value == cameras[ci].activePreset) {
child.selected=true
} else {
child.selected=false
}
})
}
function updateUIAperture() {
document.getElementById("irisRange").value = cameras[ci].apertureNormalised;
document.getElementById("apertureStopsLabel").innerHTML = cameras[ci].apertureStop.toFixed(1);
}
function updateUIZoom() {
document.getElementById("zoomRange").value = cameras[ci].zoomNormalised;
document.getElementById("zoomMMLabel").innerHTML = cameras[ci].zoomMM +"mm";
}
function updateUIFocus() {
document.getElementById("focusRange").value = cameras[ci].focusNormalised;
}
function updateUIISO() {
document.getElementById("ISOInput").value = cameras[ci].ISO;
}
function updateUIgain() {
var gainString = "";
if (cameras[ci].gain >= 0) {
gainString = "+"+cameras[ci].gain+"db"
} else {
gainString = cameras[ci].gain+"db"
}
document.getElementById("gainSpan").innerHTML = gainString;
}
function updateUIWhiteBalance() {
document.getElementById("whiteBalanceSpan").innerHTML = cameras[ci].WhiteBalance+"K";
document.getElementById("whiteBalanceTintSpan").innerHTML = cameras[ci].WhiteBalanceTint;
}
function updateUINDStop() {
document.getElementById("ndFilterSpan").innerHTML = cameras[ci].NDStop;
if (cameras[ci].UnimplementedFunctionality.includes("/video/ndFilter")) {
document.getElementById("ndFilterSpan").innerHTML = 0;
document.getElementById("ndFilterSpan").disabled = true;
}
}
function updateUIshutter() {
var shutterString = ""
if ('shutterSpeed' in cameras[ci].shutter) {
shutterString = "1/"+cameras[ci].shutter.shutterSpeed
} else {
var shangleString = (cameras[ci].shutter.shutterAngle / 100).toFixed(1).toString()
if (shangleString.indexOf(".0") > 0) {
shutterString = parseFloat(shangleString).toFixed(0)+"°";
} else {
shutterString = shangleString+"°";
}
}
document.getElementById("shutterSpan").innerHTML = shutterString;
}
function updateUIAutoExposureMode() {
let AEmodeSelect = document.getElementById("AEmodeDropDown");
let AEtypeSelect = document.getElementById("AEtypeDropDown");
AEmodeSelect.value = cameras[ci].AutoExposureMode.mode;
AEtypeSelect.value = cameras[ci].AutoExposureMode.type;
}
function updateUIColorCorrection() {
// Lift
document.getElementsByClassName("CClumaLabel")[0].innerHTML = cameras[ci].CClift.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[0].innerHTML = cameras[ci].CClift.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[0].innerHTML = cameras[ci].CClift.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[0].innerHTML = cameras[ci].CClift.blue.toFixed(2);
// Gamma
document.getElementsByClassName("CClumaLabel")[1].innerHTML = cameras[ci].CCgamma.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[1].innerHTML = cameras[ci].CCgamma.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[1].innerHTML = cameras[ci].CCgamma.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[1].innerHTML = cameras[ci].CCgamma.blue.toFixed(2);
// Gain
document.getElementsByClassName("CClumaLabel")[2].innerHTML = cameras[ci].CCgain.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[2].innerHTML = cameras[ci].CCgain.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[2].innerHTML = cameras[ci].CCgain.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[2].innerHTML = cameras[ci].CCgain.blue.toFixed(2);
// Offset
document.getElementsByClassName("CClumaLabel")[3].innerHTML = cameras[ci].CCoffset.luma.toFixed(2);
document.getElementsByClassName("CCredLabel")[3].innerHTML = cameras[ci].CCoffset.red.toFixed(2);
document.getElementsByClassName("CCgreenLabel")[3].innerHTML = cameras[ci].CCoffset.green.toFixed(2);
document.getElementsByClassName("CCblueLabel")[3].innerHTML = cameras[ci].CCoffset.blue.toFixed(2);
// Contrast
document.getElementById("CCcontrastPivotRange").value = cameras[ci].CCcontrast.pivot;
document.getElementById("CCcontrastPivotLabel").innerHTML = cameras[ci].CCcontrast.pivot.toFixed(2);
document.getElementById("CCcontrastAdjustRange").value = cameras[ci].CCcontrast.adjust;
document.getElementById("CCcontrastAdjustLabel").innerHTML = cameras[ci].CCcontrast.adjust.toFixed(2);
// Color
document.getElementById("CChueRange").value = cameras[ci].CCcolor.hue;
document.getElementById("CCcolorHueLabel").innerHTML = cameras[ci].CCcolor.hue.toFixed(2);
document.getElementById("CCsaturationRange").value = cameras[ci].CCcolor.saturation;
document.getElementById("CCcolorSatLabel").innerHTML = cameras[ci].CCcolor.saturation.toFixed(2);
document.getElementById("CClumaContributionRange").value = cameras[ci].CClumacontribution.lumaContribution;
document.getElementById("CCcolorLCLabel").innerHTML = cameras[ci].CClumacontribution.lumaContribution.toFixed(2);
}
function updateUILinks() {
document.getElementById("documentationLink").href = "http://"+cameras[ci].hostname+"/control/documentation.html";
document.getElementById("mediaManagerLink").href = "http://"+cameras[ci].hostname;
}
// ==============================================================================
// One Second Timer Call // One Second Timer Call
// Only updates rec/play state and timecode
function timerCallFunction1() { function timerCallFunction1() {
if (cameras[ci]) { if (cameras[ci]) {
cameras[ci].getRecordState(); cameras[ci].getRecordState();
cameras[ci].getPlaybackState(); cameras[ci].getPlaybackState();
cameras[ci].getTimecode(); cameras[ci].getTimecode();
cameras[ci].updateUIRecordState();
cameras[ci].updateUIPlaybackState();
cameras[ci].updateUITimecode();
} }
} }
// Ten Second Timer Call // Ten Second Timer Call
// Refreshes all elements
function timerCallFunction10() { function timerCallFunction10() {
if (cameras[ci]) { if (cameras[ci]) {
cameras[ci].refresh(); refresh();
} }
} }
// Called when the user changes tabs to a different camera
function switchCamera(index) { function switchCamera(index) {
ci = index; ci = index;
if (cameras[ci]) { if (cameras[ci]) {
cameras[ci].refresh(); refresh();
} }
for (var i = 0; i < 8; i++) { for (var i = 0; i < 8; i++) {
@ -69,6 +327,7 @@ function switchCamera(index) {
document.getElementById("cameraName").innerHTML = "CAMERA NAME"; document.getElementById("cameraName").innerHTML = "CAMERA NAME";
} }
// For not-yet-implemented Color Correction UI
function setCCMode(mode) { function setCCMode(mode) {
if (mode == 0) { if (mode == 0) {
// Lift // Lift
@ -90,6 +349,7 @@ function setCCMode(mode) {
} }
} }
// Allows for changing WB/Tint displayed in the UI
function swapWBMode() { function swapWBMode() {
if (WBMode == 0) { if (WBMode == 0) {
// Balance // Balance
@ -108,6 +368,7 @@ function swapWBMode() {
} }
} }
// Triggered by the button by those text boxes. Reads the info from the inputs and sends it to the camera.
function manualAPICall() { function manualAPICall() {
const requestRadioGET = document.getElementById("requestTypeGET"); const requestRadioGET = document.getElementById("requestTypeGET");
@ -130,11 +391,11 @@ function manualAPICall() {
} else { } else {
document.getElementById("manualRequestResponseP").innerHTML = response.status+": "+response.statusText; document.getElementById("manualRequestResponseP").innerHTML = response.status+": "+response.statusText;
} }
}); });
} }
/* Control Calling Functions */ /* Control Calling Functions */
/* Makes the HTML cleaner. */
function decreaseND() { function decreaseND() {
cameras[ci].setND(cameras[ci].NDStop-2); cameras[ci].setND(cameras[ci].NDStop-2);
@ -176,7 +437,12 @@ function handleShutterInput(inputString) {
let cam = cameras[ci]; let cam = cameras[ci];
if ('shutterSpeed' in cam.shutter) { if ('shutterSpeed' in cam.shutter) {
cam.setShutter({"shutterSpeed" :parseInt(inputString)}); if (inputString.indexOf("1/") >= 0) {
cam.setShutter({"shutterSpeed" :parseInt(inputString.substring(2))});
} else {
cam.setShutter({"shutterSpeed" :parseInt(inputString)});
}
} else { } else {
cam.setShutter({"shutterAngle": parseInt(parseFloat(inputString)*100)}); cam.setShutter({"shutterAngle": parseInt(parseFloat(inputString)*100)});
} }
@ -225,6 +491,7 @@ function setCCFromUI(which) {
} }
} }
// Reset Color Correction Values
// 0: lift, 1: gamma, 2: gain, 3: offset, 4: contrast, 5: color & LC // 0: lift, 1: gamma, 2: gain, 3: offset, 4: contrast, 5: color & LC
function resetCC(which) { function resetCC(which) {
if (which == 0) { if (which == 0) {
@ -241,33 +508,4 @@ function resetCC(which) {
cameras[ci].setCCColor({"hue": 0.0, "saturation": 1.0}); cameras[ci].setCCColor({"hue": 0.0, "saturation": 1.0});
cameras[ci].setCCLumaContribuion({"lumaContribution": 1.0}); cameras[ci].setCCLumaContribuion({"lumaContribution": 1.0});
} }
}
/* Cookie Setting functions from StackOverflow :P */
function createCookie(name, value, days) {
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
} else {
expires="";
}
document.cookie = name + "=" + value + expires + "; path=/";
}
function getCookie(c_name) {
if (document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + "=");
if (c_start != -1) {
c_start = c_start + c_name.length + 1;
c_end = document.cookie.indexOf(";", c_start);
if (c_end == -1) {
c_end = document.cookie.length;
}
return unescape(document.cookie.substring(c_start, c_end));
}
}
return "";
} }