From 84187361a713f901d1e1a48da35721a1c386c695 Mon Sep 17 00:00:00 2001 From: DylanSpeiser-BMD Date: Fri, 14 Jun 2024 16:40:07 -0700 Subject: [PATCH] v1.0 --- .DS_Store | Bin 6148 -> 0 bytes BMD-Camera-Control.js | 619 ++++++++---------- README.md | 42 +- index.html | 63 +- .../Screenshot 2024-06-12 171720.png | Bin style.css | 8 +- web-ui.js | 340 ++++++++-- 7 files changed, 621 insertions(+), 451 deletions(-) delete mode 100644 .DS_Store rename Screenshot 2024-06-12 171720.png => screenshots/Screenshot 2024-06-12 171720.png (100%) diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 4d25fd6cfb120515f0870931177918627cf555cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKOKt)&47Cf3kx(U;UFHhhAVt#?ut4gn;-j))8U*b=heI5os~)G%v7w9@7Hq1j zvPa2t65Hc>(j+kv@%UpgA{r7=g(k?NbcjrMF5Q`NAIK%flHO=XD@yd<5$G>YN$vyW zJf|%!@%-e!TfdGb^VDq4(!OHZq`IE1(+rl?e){?Rb$@ug9=A!08>~00i!9C(a@84d z2AlzBz!|ui0i4+))j-jEXTTY722Kpf{t(avqhVGoTL-#Q0s!S2T?D$+65|wjfi?p@9S-IEf5I=* zS>(4}eB=x`1OJQx?$?ugjE}Oj_2Tp7tPN-{Xd+^lMS(zXT>>zWedM7m>Ut0zcF`~^ UN)(wd?m)i?6hgdn27ZBoZ>@?nh5!Hn diff --git a/BMD-Camera-Control.js b/BMD-Camera-Control.js index 9d0e43a..d612fef 100644 --- a/BMD-Camera-Control.js +++ b/BMD-Camera-Control.js @@ -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 { // Pretty name and network hostname (strings) name; hostname; APIAddress; - // Camera index, used for muticam support - index; - - // Codec and Video Formats (JSON object) + // Codec and Video Format (JSON object) format; // Current Transport Mode (string) transportMode; - // Playback state (JSON object) + // Current Playback state (JSON object) playbackState; - // Record state (JSON object) + // Current Record state (JSON object) recordState; // Timecode (JSON Object) timecode; - // (pack the source into here also) // Presets (JSON object) presets; @@ -70,28 +83,39 @@ class BMDCamera { CCcolor; 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 = []; + // 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(hostname, index) { + constructor(hostname) { this.hostname = hostname; - this.index = index; this.APIAddress = "http://"+hostname+"/control/api/v1"; 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 @@ -99,6 +123,7 @@ class BMDCamera { // Ask the camera a question let response; + // Only send the request if it's not an Unimplemented Function if (this.UnimplementedFunctionality.indexOf(endpoint) < 0) { response = await sendRequest("GET",this.APIAddress+endpoint,""); } else { @@ -114,447 +139,318 @@ class BMDCamera { 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) { 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 ================== - // 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() { - this.pullData("/system/format").then((value) => {this.format = value; this.updateUIFormat()}); + async getFormat() { + this.format = await this.pullData("/system/format"); + BMDCamera.updateUIFormat(); + return this.format; } - getTransportMode() { - this.pullData("/transports/0").then((value) => {this.transportMode = value; this.updateUITransportMode()}); + async getTransportMode() { + this.transportMode = await this.pullData("/transports/0"); + BMDCamera.updateUITransportMode(); + return this.transportMode; } - getPlaybackState() { - this.pullData("/transports/0/playback").then((value) => {this.playbackState = value; this.updateUIPlaybackState()}); + async getPlaybackState() { + this.playbackState = await this.pullData("/transports/0/playback"); + BMDCamera.updateUIPlaybackState(); + return this.playbackState; } - getRecordState() { - this.pullData("/transports/0/record").then((value) => {this.recordState = value; this.updateUIRecordState()}); + async getRecordState() { + this.recordState = await this.pullData("/transports/0/record"); + BMDCamera.updateUIRecordState(); + return this.recordState; } - getTimecode() { - this.pullData("/transports/0/timecode").then((value) => {this.timecode = value; this.updateUITimecode()}); - this.pullData("/transports/0/timecode/source").then((value) => {this.timecode.source = value.source}); + async getTimecode() { + this.timecode = await this.pullData("/transports/0/timecode"); + BMDCamera.updateUITimecode(); + return this.timecode; } - getPresets() { - this.pullData("/presets").then((value) => {this.presets = value.presets; this.updateUIPresets()}); + async getPresets() { + this.pullData("/presets").then((value) => {this.presets = value.presets; BMDCamera.updateUIPresets();}); + return this.presets; } - getActivePreset() { - this.pullData("/presets/active").then((value) => {this.activePreset = value; this.updateUIActivePreset()}); + async getActivePreset() { + this.activePreset = await this.pullData("/presets/active"); + BMDCamera.updateUIActivePreset(); + return this.activePreset; } - getAperture() { - this.pullData("/lens/iris").then((value) => {this.apertureStop = value.apertureStop; this.apertureNormalised = value.normalised; this.updateUIAperture()}); + async getAperture() { + this.pullData("/lens/iris").then((value) => {this.apertureStop = value.apertureStop; this.apertureNormalised = value.normalised; BMDCamera.updateUIAperture();}); + return this.apertureNormalised; } - getZoom() { - this.pullData("/lens/zoom").then((value) => {this.zoomMM = value.focalLength; this.zoomNormalised = value.normalised; this.updateUIZoom()}); + async getZoom() { + this.pullData("/lens/zoom").then((value) => {this.zoomMM = value.focalLength; this.zoomNormalised = value.normalised; BMDCamera.updateUIZoom();}); + return this.zoomNormalised; } - getFocus() { - this.pullData("/lens/focus").then((value) => {this.focusNormalised = value.normalised; this.updateUIFocus()}); + async getFocus() { + this.pullData("/lens/focus").then((value) => {this.focusNormalised = value.normalised; BMDCamera.updateUIFocus();}); + return this.focusNormalised; } - getISO() { - this.pullData("/video/iso").then((value) => {this.ISO = value.iso; this.updateUIISO()}); + async getISO() { + this.pullData("/video/iso").then((value) => {this.ISO = value.iso; BMDCamera.updateUIISO();}); + return this.ISO; } - getGain() { - this.pullData("/video/gain").then((value) => {this.gain = value.gain; this.updateUIgain()}); + async getGain() { + 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/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() { - this.pullData("/video/ndFilter").then((value) => {this.NDStop = value.stop; this.updateUINDStop()}); - this.pullData("/video/ndFilter/displayMode").then((value) => {this.NDMode = value.displayMode}); + async getND() { + this.pullData("/video/ndFilter").then((value) => {this.NDStop = value.stop}); + this.pullData("/video/ndFilter/displayMode").then((value) => {this.NDMode = value.displayMode; BMDCamera.updateUINDStop();}); + return this.NDStop; } - getShutter() { - this.pullData("/video/shutter").then((value) => {this.shutter = value; this.updateUIshutter()}); + async getShutter() { + this.shutter = await this.pullData("/video/shutter"); + BMDCamera.updateUIshutter(); + return this.shutter; } - getAutoExposureMode() { - this.pullData("/video/autoExposure").then((value) => {this.AutoExposureMode = value; this.updateUIAutoExposureMode()}); + async getAutoExposureMode() { + this.AutoExposureMode = await this.pullData("/video/autoExposure"); + BMDCamera.updateUIAutoExposureMode(); + return this.AutoExposureMode; } - getColorCorrection() { - this.pullData("/colorCorrection/lift").then((value) => {this.CClift = value}); - this.pullData("/colorCorrection/gamma").then((value) => {this.CCgamma = value}); - this.pullData("/colorCorrection/gain").then((value) => {this.CCgain = value}); - this.pullData("/colorCorrection/offset").then((value) => {this.CCoffset = value}); - this.pullData("/colorCorrection/contrast").then((value) => {this.CCcontrast = value}); - this.pullData("/colorCorrection/color").then((value) => {this.CCcolor = value}); - this.pullData("/colorCorrection/lumaContribution").then((value) => {this.CClumacontribution = value; this.updateUIColorCorrection()}); + // This one just fetches the data and stores it in the normal objects. No return value. + async getColorCorrection() { + this.CClift = await this.pullData("/colorCorrection/lift"); + this.CCgamma = await this.pullData("/colorCorrection/gamma"); + this.CCgain = await this.pullData("/colorCorrection/gain"); + this.CCoffset = await this.pullData("/colorCorrection/offset"); + this.CCcontrast = await this.pullData("/colorCorrection/contrast"); + this.CCcolor = await this.pullData("/colorCorrection/color"); + this.CClumacontribution = await this.pullData("/colorCorrection/lumaContribution"); + BMDCamera.updateUIColorCorrection(); } - getAllInfo() { - this.getFormat(); - this.getTransportMode(); - this.getPlaybackState(); - this.getRecordState(); - this.getTimecode(); - this.getPresets(); - this.getActivePreset(); - this.getAperture(); - this.getZoom(); - this.getFocus(); - this.getISO(); - this.getGain(); - this.getWhiteBalance(); - this.getND(); - this.getShutter(); - this.getAutoExposureMode(); - this.getColorCorrection(); + // This method usually takes 200-250 ms + async getAllInfo() { + await this.getFormat(); + await this.getTransportMode(); + await this.getPlaybackState(); + await this.getRecordState(); + await this.getTimecode(); + await this.getPresets(); + await this.getActivePreset(); + await this.getAperture(); + await this.getZoom(); + await this.getFocus(); + await this.getISO(); + await this.getGain(); + await this.getWhiteBalance(); + await this.getND(); + await this.getShutter(); + await this.getAutoExposureMode(); + await this.getColorCorrection(); } // =============== SETTERS ================== - // name, hostname, APIaddress, index should never have to be set + // name, hostname, APIaddress should never have to be set - setCodecFormat(newCodecFormatObject) { - this.pushData("/system/codecFormat",newCodecFormatObject).then(() => sleep(1000).then(() => this.getCodecFormat())); + async setCodecFormat(newCodecFormatObject) { + await this.pushData("/system/codecFormat",newCodecFormatObject); + await sleep(500); + await this.getCodecFormat(); } - setVideoFormat(newVideoFormatObject) { - this.pushData("/system/videoFormat",newVideoFormatObject).then(() => sleep(1000).then(() => this.getCodecFormat())); + async setVideoFormat(newVideoFormatObject) { + await this.pushData("/system/videoFormat",newVideoFormatObject); + await sleep(500); + await this.getCodecFormat(); } - setTransportMode(newTransportModeString) { - this.pushData("/transports/0",{"mode": newTransportModeString}).then(() => sleep(1000).then(() => this.getTransportMode())); + async setTransportMode(newTransportModeString) { + await this.pushData("/transports/0",{"mode": newTransportModeString}); + await sleep(500); + await this.getTransportMode(); } - setPlaybackState(playbackStateObject) { - this.pushData("/transports/0/playback",playbackStateObject).then(() => sleep(1000).then(() => this.getPlaybackState())); + async setPlaybackState(playbackStateObject) { + await this.pushData("/transports/0/playback",playbackStateObject); + await sleep(500); + await this.getPlaybackState(); } - sendPresetFile(file) { - sendRequest("POST",this.APIAddress+"/presets",file) + async sendPresetFile(file) { + await sendRequest("POST",this.APIAddress+"/presets",file); } - setActivePreset(presetString) { - this.pushData("/presets/active",{"preset": presetString}).then(() => sleep(1000).then(() => this.refresh())); + async setActivePreset(presetString) { + await this.pushData("/presets/active",{"preset": presetString}); + await sleep(500); + await this.getAllInfo(); } - updatePreset(presetString) { - this.pushData("/presets/active",{"preset": presetString}).then(() => sleep(1000).then(() => this.getPresets())); + async updatePreset(presetString) { + await this.pushData("/presets/active",{"preset": presetString}); + await sleep(500); + await this.getPresets(); } - setAperture(apertureNormalisedFloat) { - this.pushData("/lens/iris",{"normalised": apertureNormalisedFloat}).then(() => sleep(1000).then(() => this.getAperture())); + async setAperture(apertureNormalisedFloat) { + await this.pushData("/lens/iris",{"normalised": apertureNormalisedFloat}); + await sleep(1500); + await this.getAperture(); } - setZoom(zoomNormalisedFloat) { - this.pushData("/lens/zoom",{"normalised": zoomNormalisedFloat}).then(() => sleep(1000).then(() => this.getZoom())); + async setZoom(zoomNormalisedFloat) { + await this.pushData("/lens/zoom",{"normalised": zoomNormalisedFloat}); + await sleep(1500); + await this.getZoom(); } - setFocus(focusNormalisedFloat) { - this.pushData("/lens/focus",{"normalised": focusNormalisedFloat}).then(() => sleep(1000).then(() => this.getFocus())); + async setFocus(focusNormalisedFloat) { + await this.pushData("/lens/focus",{"normalised": focusNormalisedFloat}); + await sleep(1500); + await this.getFocus(); } - setISO(ISOint) { - this.pushData("/video/iso",{"iso":ISOint}).then(() => sleep(1000).then(() => this.getISO())); + async setISO(ISOint) { + await this.pushData("/video/iso",{"iso":ISOint}); + await sleep(500); + await this.getISO(); + await this.getGain(); } - setGain(gainInt) { - this.pushData("/video/gain",{"gain":gainInt}).then(() => sleep(1000).then(() => this.getGain())); + async setGain(gainInt) { + await this.pushData("/video/gain",{"gain":gainInt}); + await sleep(500); + await this.getGain(); + await this.getISO(); } - setWhiteBalance(whiteBalanceInt, whiteBalanceTintInt) { - this.pushData("/video/whiteBalance",{"whiteBalance": whiteBalanceInt}); - this.pushData("/video/whiteBalanceTint",{"whiteBalanceTint": whiteBalanceTintInt}).then(() => sleep(1000).then(() => this.getWhiteBalance())); + async setWhiteBalance(whiteBalanceInt, whiteBalanceTintInt) { + await this.pushData("/video/whiteBalance",{"whiteBalance": whiteBalanceInt}); + await this.pushData("/video/whiteBalanceTint",{"whiteBalanceTint": whiteBalanceTintInt}); + await sleep(500); + await this.getWhiteBalance(); } - setND(NDstopInt) { - this.pushData("/video/ndFilter",{"stop": NDstopInt}).then(() => sleep(1000).then(() => this.getND())); + async setND(NDstopInt) { + await this.pushData("/video/ndFilter",{"stop": NDstopInt}); + await sleep(500); + await this.getND(); } - setNDDisplayMode(displayModeString) { - this.pushData("/video/ndFilter/displayMode",{"displayMode": displayModeString}).then(() => sleep(1000).then(() => this.getND())); + async setNDDisplayMode(displayModeString) { + await this.pushData("/video/ndFilter/displayMode",{"displayMode": displayModeString}); + await sleep(500); + await this.getND(); } // Accepts JSON obejcts with either shutterSpeed or shutterAngle properties - // Note that shutterAngle is 100x the displayed value - setShutter(shutterObject) { - this.pushData("/video/shutter",shutterObject).then(() => sleep(1000).then(() => this.getShutter())); + // Note that the shutterAngle value returned by the API is 100x the actual value + async setShutter(shutterObject) { + await this.pushData("/video/shutter",shutterObject); + await sleep(500); + await this.getShutter(); } - setAutoExposureMode(AEmodeObject) { - this.pushData("/video/autoExposure",AEmodeObject).then(() => sleep(1000).then(() => this.getAutoExposureMode())); + async setAutoExposureMode(AEmodeObject) { + await this.pushData("/video/autoExposure",AEmodeObject); + await sleep(500); + await this.getAutoExposureMode(); } - setCCLift(CCliftObject) { - this.pushData("/colorCorrection/lift",CCliftObject).then(() => sleep(1000).then(() => this.getColorCorrection())); + async setCCLift(CCliftObject) { + await this.pushData("/colorCorrection/lift",CCliftObject); + await sleep(500); + await this.getColorCorrection(); } - setCCGamma(CCgammaObject) { - this.pushData("/colorCorrection/gamma",CCgammaObject).then(() => sleep(1000).then(() => this.getColorCorrection())); + async setCCGamma(CCgammaObject) { + await this.pushData("/colorCorrection/gamma",CCgammaObject); + await sleep(500); + await this.getColorCorrection(); } - setCCGain(CCgainObject) { - this.pushData("/colorCorrection/gain",CCgainObject).then(() => sleep(1000).then(() => this.getColorCorrection())); + async setCCGain(CCgainObject) { + await this.pushData("/colorCorrection/gain",CCgainObject); + await sleep(500); + await this.getColorCorrection(); } - setCCOffset(CCoffsetObject) { - this.pushData("/colorCorrection/offset",CCoffsetObject).then(() => sleep(1000).then(() => this.getColorCorrection())); + async setCCOffset(CCoffsetObject) { + await this.pushData("/colorCorrection/offset",CCoffsetObject); + await sleep(500); + await this.getColorCorrection(); } - setCCContrast(CCcontrastObject) { - this.pushData("/colorCorrection/contrast",CCcontrastObject).then(() => sleep(1000).then(() => this.getColorCorrection())); + async setCCContrast(CCcontrastObject) { + await this.pushData("/colorCorrection/contrast",CCcontrastObject); + await sleep(500); + await this.getColorCorrection(); } - setCCColor(CCcolorObject) { - this.pushData("/colorCorrection/color",CCcolorObject).then(() => sleep(1000).then(() => this.getColorCorrection())); + async setCCColor(CCcolorObject) { + await this.pushData("/colorCorrection/color",CCcolorObject); + await sleep(500); + await this.getColorCorrection(); } - setCCLumaContribuion(CClumacontributionObject) { - this.pushData("/colorCorrection/lumaContribution",CClumacontributionObject).then(() => sleep(1000).then(() => this.getColorCorrection())); + async setCCLumaContribuion(CClumacontributionObject) { + await this.pushData("/colorCorrection/lumaContribution",CClumacontributionObject); + await sleep(500); + await this.getColorCorrection(); } // =============== Other Commands ======================= - doAutoFocus() { - this.pushData("/lens/focus/doAutoFocus").then(() => sleep(1500).then(() => this.getFocus())); + async doAutoFocus() { + await this.pushData("/lens/focus/doAutoFocus"); + sleep(1500).then(() => this.getFocus()); } - play() { - this.pushData("/transports/0/play").then(() => sleep(1000).then(() => this.getPlaybackState())); + async play() { + await this.pushData("/transports/0/play"); + await sleep(500); + await this.getPlaybackState(); } - record() { - this.pushData("/transports/0/record",{"recording": true}).then(() => { - sleep(2000).then(() => this.getRecordState()); - }); + async record() { + await this.pushData("/transports/0/record",{"recording": true}); + await sleep(1000); + await this.getRecordState(); } - stopTransport() { - this.pushData("/transports/0/stop").then(() => { - sleep(2000).then(() => this.getPlaybackState()); - }); + async stopTransport() { + await this.pushData("/transports/0/stop"); + await sleep(1000); + await this.getPlaybackState(); } - stopRecord() { - this.pushData("/transports/0/record",{"recording": false}).then(() => { - sleep(2000).then(() => this.getRecordState()); - }); + async stopRecord() { + await this.pushData("/transports/0/record",{"recording": false}); + await sleep(1000); + await this.getRecordState(); } } @@ -581,4 +477,7 @@ async function sendRequest(method, url, data) { function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); -} \ No newline at end of file +} + +/* (c) Dylan Speiser 2024 + github.com/DylanSpeiser */ \ No newline at end of file diff --git a/README.md b/README.md index 2f30a79..60974a1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # 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) -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) @@ -34,9 +34,6 @@ The app polls data from the camera every ten seconds (you'll see "Refreshing..." ### 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. -
-(Sometimes this might take a couple of tries). -
### 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. @@ -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 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 | -| Cinema Camera 6K | `Cinema-Camera-6K.local` | | -| URSA Broadcast G2 | `URSA-Broadcast-G2.local`$^1$ | | +| Cinema Camera 6K | `Blackmagic-Cinema-Camera-6K.local` | | +| URSA Broadcast G2 | `URSA-Broadcast-G2.local` | | | Micro Studio Camera 4K G2 | `Micro-Studio-Camera-4K-G2.local`$^1$ | | | Studio Camera 4K Plus | `Studio-Camera-4K-Plus.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
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! +

+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). +

+Cameras are represented as BMDCamera objects, instantiated with the `new` keyword and the constructor, which takes the hostname as a String argument. +

+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. +

+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. +

+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 ## Known Issues - 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 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 codec/format switching settings - Improve responsiveness -- Improve error handling \ No newline at end of file +- 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 +
+Licensed under the GNU General Public License \ No newline at end of file diff --git a/index.html b/index.html index 4da8341..9f67aeb 100644 --- a/index.html +++ b/index.html @@ -1,3 +1,5 @@ + + @@ -58,10 +60,10 @@
- 0.00 - 0.00 - 0.00 - 0.00 + 0.00 + 0.00 + 0.00 + 0.00
@@ -70,10 +72,10 @@
- 0.00 - 0.00 - 0.00 - 0.00 + 0.00 + 0.00 + 0.00 + 0.00
@@ -82,10 +84,10 @@
- 0.00 - 0.00 - 0.00 - 0.00 + 0.00 + 0.00 + 0.00 + 0.00
@@ -95,10 +97,10 @@
- 0.00 - 0.00 - 0.00 - 0.00 + 0.00 + 0.00 + 0.00 + 0.00
@@ -109,7 +111,7 @@ FILTER
- 0 + 0
@@ -117,7 +119,7 @@ GAIN
- +0db + +0db
@@ -125,20 +127,20 @@ SHUTTER
- 1/50 + 1/50
- BALANCE + BALANCE
- 5600K + 5600K
- 0 + 0
@@ -193,7 +195,7 @@ Hostname - + @@ -241,7 +243,7 @@ - + @@ -276,7 +278,7 @@
ISO
AE ModePivot - 0 + 0 @@ -286,7 +288,7 @@ Adjust - 0 + 0
@@ -299,7 +301,7 @@ Hue - 0 + 0 @@ -309,14 +311,14 @@ Saturation - 0 + 0 Luma Contribution - 0 + 0 @@ -329,13 +331,14 @@
- + Refreshing...
diff --git a/Screenshot 2024-06-12 171720.png b/screenshots/Screenshot 2024-06-12 171720.png similarity index 100% rename from Screenshot 2024-06-12 171720.png rename to screenshots/Screenshot 2024-06-12 171720.png diff --git a/style.css b/style.css index 9990cb7..7fa67ec 100644 --- a/style.css +++ b/style.css @@ -1,7 +1,9 @@ +/* Sorry the CSS is such a mess. -DS */ + /* ============= WHOLE PAGE STYLES ================== */ /* Handle vertical screens */ -@media screen and (max-width: 1000px) { +@media screen and (max-width: 1200px) { #cameraControlsContainer { width: 100vw!important; } @@ -9,6 +11,10 @@ #cameraControlsContainerExpanded { display: none; } + + body { + font-size: 90%; + } } /* Load NotoSansDisplay Font from resources */ diff --git a/web-ui.js b/web-ui.js index 5da9d11..41ced98 100644 --- a/web-ui.js +++ b/web-ui.js @@ -1,60 +1,318 @@ +/* Blackmagic Camera Control WebUI + WebUI Script functions + (c) Dylan Speiser 2024 + github.com/DylanSpeiser +*/ + + /* Global variables */ -var cameras = []; -var ci = 0; -var WBMode = 0; // 0: balance, 1: tint +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 +var WBMode = 0; // 0: balance, 1: tint +// Set everything up function bodyOnLoad() { - let intervalIDOne = setInterval(timerCallFunction1, 1000); // Tem second timer for refreshing everything - let intervalIDTen = setInterval(timerCallFunction10, 10000); // Tem second timer for refreshing everything + // Initialize timers + 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; - - if (newCamHostname) { - initCamera(newCamHostname, ci); - } + // Pass along the UI refreshing methods to the BMDCamera class + BMDCamera.updateUIAll = updateUIAll; + BMDCamera.updateUIname = updateUIname; + 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) => { if (response.status < 300) { - cameras[ci] = new BMDCamera(hostname, ind); - document.getElementById("connectionErrorSpan").innerHTML = ""; + // Success, make a new camera, get all relevant info, and populate the UI + cameras[ci] = new BMDCamera(hostname); + cameras[ci].getAllInfo(); + document.getElementById("connectionErrorSpan").innerHTML = "Connected."; + document.getElementById("connectionErrorSpan").setAttribute("style","color: #6e6e6e;"); } else { + // Something has gone wrong, tell the user document.getElementById("connectionErrorSpan").innerHTML = response.statusText; } }).catch(error => { + // Something has gone wrong, tell the user document.getElementById("connectionErrorSpan").title = error; 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 +// Only updates rec/play state and timecode function timerCallFunction1() { if (cameras[ci]) { cameras[ci].getRecordState(); cameras[ci].getPlaybackState(); cameras[ci].getTimecode(); - - cameras[ci].updateUIRecordState(); - cameras[ci].updateUIPlaybackState(); - cameras[ci].updateUITimecode(); } } // Ten Second Timer Call +// Refreshes all elements function timerCallFunction10() { if (cameras[ci]) { - cameras[ci].refresh(); + refresh(); } } +// Called when the user changes tabs to a different camera function switchCamera(index) { ci = index; if (cameras[ci]) { - cameras[ci].refresh(); + refresh(); } for (var i = 0; i < 8; i++) { @@ -69,6 +327,7 @@ function switchCamera(index) { document.getElementById("cameraName").innerHTML = "CAMERA NAME"; } +// For not-yet-implemented Color Correction UI function setCCMode(mode) { if (mode == 0) { // Lift @@ -90,6 +349,7 @@ function setCCMode(mode) { } } +// Allows for changing WB/Tint displayed in the UI function swapWBMode() { if (WBMode == 0) { // 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() { const requestRadioGET = document.getElementById("requestTypeGET"); @@ -130,11 +391,11 @@ function manualAPICall() { } else { document.getElementById("manualRequestResponseP").innerHTML = response.status+": "+response.statusText; } - }); } -/* Control Calling Functions */ +/* Control Calling Functions */ +/* Makes the HTML cleaner. */ function decreaseND() { cameras[ci].setND(cameras[ci].NDStop-2); @@ -176,7 +437,12 @@ function handleShutterInput(inputString) { let cam = cameras[ci]; 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 { 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 function resetCC(which) { if (which == 0) { @@ -241,33 +508,4 @@ function resetCC(which) { cameras[ci].setCCColor({"hue": 0.0, "saturation": 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 ""; } \ No newline at end of file