From 0294bdfed8579a91720dad9a15481e8db503e22b Mon Sep 17 00:00:00 2001 From: DylanSpeiser Date: Tue, 2 Jul 2024 14:53:25 -0700 Subject: [PATCH] Hide and Seek --- .gitignore | 1 + BMD-Camera-Control.js | 483 ------------------------------------------ BMDevice.js | 272 ++++++++++++++++++++++++ README.md | 8 +- index.html | 142 ++++++------- style.css | 4 + web-ui.js | 450 ++++++++++++--------------------------- 7 files changed, 492 insertions(+), 868 deletions(-) delete mode 100644 BMD-Camera-Control.js create mode 100644 BMDevice.js diff --git a/.gitignore b/.gitignore index e43b0f9..dd5c568 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +/.vscode diff --git a/BMD-Camera-Control.js b/BMD-Camera-Control.js deleted file mode 100644 index d612fef..0000000 --- a/BMD-Camera-Control.js +++ /dev/null @@ -1,483 +0,0 @@ -/* 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; - - // Codec and Video Format (JSON object) - format; - - // Current Transport Mode (string) - transportMode; - - // Current Playback state (JSON object) - playbackState; - - // Current Record state (JSON object) - recordState; - - // Timecode (JSON Object) - timecode; - - // Presets (JSON object) - presets; - activePreset; - - // Iris (floats) - apertureStop; - apertureNormalised; - - // Zoom (floats) - zoomMM; - zoomNormalised; - - // Focus (float) - focusNormalised; - - // ISO (int) - ISO; - - // Gain (int) - gain; - - // White Balance (ints) - WhiteBalance; - WhiteBalanceTint; - - // ND Filter (int, string) - NDStop; - NDMode; - - // Shutter (JSON object) - shutter; - // has to be an object because it either returns with shutterSpeed or shutterAngle - - // AE Mode (JSON Object) - AutoExposureMode; - - // Basic Color Correction (JSON objects w/ RGBL) - CClift; - CCgamma; - CCgain; - CCoffset; - - // Other Color Correction (JSON objects w/ 2 numbers) - CCcontrast; - CCcolor; - CClumacontribution; - - // 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) { - this.hostname = hostname; - this.APIAddress = "http://"+hostname+"/control/api/v1"; - this.name = this.hostname.replace(".local","").replaceAll("-"," "); - } - - // Wrapper for API call, returns the JSON object from the camera - async pullData(endpoint) { - // 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 { - response = "Unimplemented"; - } - - // Check for unimplemented features - if (response.status == 501) { - this.UnimplementedFunctionality.push(endpoint); - response = "Unimplemented"; - } - - return response - } - - // Wrapper for API call, sends data to the camera. - async pushData(endpoint, data) { - return await sendRequest("PUT",this.APIAddress+endpoint,data); - } - - // =============== GETTERS ================== - - // name, hostname, APIaddress handled by constructor - // since these are all asynchronous, they will return promises to use with await or .then() - - async getFormat() { - this.format = await this.pullData("/system/format"); - BMDCamera.updateUIFormat(); - return this.format; - } - - async getTransportMode() { - this.transportMode = await this.pullData("/transports/0"); - BMDCamera.updateUITransportMode(); - return this.transportMode; - } - - async getPlaybackState() { - this.playbackState = await this.pullData("/transports/0/playback"); - BMDCamera.updateUIPlaybackState(); - return this.playbackState; - } - - async getRecordState() { - this.recordState = await this.pullData("/transports/0/record"); - BMDCamera.updateUIRecordState(); - return this.recordState; - } - - async getTimecode() { - this.timecode = await this.pullData("/transports/0/timecode"); - BMDCamera.updateUITimecode(); - return this.timecode; - } - - async getPresets() { - this.pullData("/presets").then((value) => {this.presets = value.presets; BMDCamera.updateUIPresets();}); - return this.presets; - } - - async getActivePreset() { - this.activePreset = await this.pullData("/presets/active"); - BMDCamera.updateUIActivePreset(); - return this.activePreset; - } - - async getAperture() { - this.pullData("/lens/iris").then((value) => {this.apertureStop = value.apertureStop; this.apertureNormalised = value.normalised; BMDCamera.updateUIAperture();}); - return this.apertureNormalised; - } - - async getZoom() { - this.pullData("/lens/zoom").then((value) => {this.zoomMM = value.focalLength; this.zoomNormalised = value.normalised; BMDCamera.updateUIZoom();}); - return this.zoomNormalised; - } - - async getFocus() { - this.pullData("/lens/focus").then((value) => {this.focusNormalised = value.normalised; BMDCamera.updateUIFocus();}); - return this.focusNormalised; - } - - async getISO() { - this.pullData("/video/iso").then((value) => {this.ISO = value.iso; BMDCamera.updateUIISO();}); - return this.ISO; - } - - async getGain() { - this.pullData("/video/gain").then((value) => {this.gain = value.gain; BMDCamera.updateUIgain();}); - return this.gain; - } - - async getWhiteBalance() { - this.pullData("/video/whiteBalance").then((value) => {this.WhiteBalance = value.whiteBalance}); - this.pullData("/video/whiteBalanceTint").then((value) => {this.WhiteBalanceTint = value.whiteBalanceTint; BMDCamera.updateUIWhiteBalance();}); - return this.WhiteBalance; - } - - 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; - } - - async getShutter() { - this.shutter = await this.pullData("/video/shutter"); - BMDCamera.updateUIshutter(); - return this.shutter; - } - - async getAutoExposureMode() { - this.AutoExposureMode = await this.pullData("/video/autoExposure"); - BMDCamera.updateUIAutoExposureMode(); - return this.AutoExposureMode; - } - - // 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(); - } - - // 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 should never have to be set - - async setCodecFormat(newCodecFormatObject) { - await this.pushData("/system/codecFormat",newCodecFormatObject); - await sleep(500); - await this.getCodecFormat(); - } - - async setVideoFormat(newVideoFormatObject) { - await this.pushData("/system/videoFormat",newVideoFormatObject); - await sleep(500); - await this.getCodecFormat(); - } - - async setTransportMode(newTransportModeString) { - await this.pushData("/transports/0",{"mode": newTransportModeString}); - await sleep(500); - await this.getTransportMode(); - } - - async setPlaybackState(playbackStateObject) { - await this.pushData("/transports/0/playback",playbackStateObject); - await sleep(500); - await this.getPlaybackState(); - } - - async sendPresetFile(file) { - await sendRequest("POST",this.APIAddress+"/presets",file); - } - - async setActivePreset(presetString) { - await this.pushData("/presets/active",{"preset": presetString}); - await sleep(500); - await this.getAllInfo(); - } - - async updatePreset(presetString) { - await this.pushData("/presets/active",{"preset": presetString}); - await sleep(500); - await this.getPresets(); - } - - async setAperture(apertureNormalisedFloat) { - await this.pushData("/lens/iris",{"normalised": apertureNormalisedFloat}); - await sleep(1500); - await this.getAperture(); - } - - async setZoom(zoomNormalisedFloat) { - await this.pushData("/lens/zoom",{"normalised": zoomNormalisedFloat}); - await sleep(1500); - await this.getZoom(); - } - - async setFocus(focusNormalisedFloat) { - await this.pushData("/lens/focus",{"normalised": focusNormalisedFloat}); - await sleep(1500); - await this.getFocus(); - } - - async setISO(ISOint) { - await this.pushData("/video/iso",{"iso":ISOint}); - await sleep(500); - await this.getISO(); - await this.getGain(); - } - - async setGain(gainInt) { - await this.pushData("/video/gain",{"gain":gainInt}); - await sleep(500); - await this.getGain(); - await this.getISO(); - } - - async setWhiteBalance(whiteBalanceInt, whiteBalanceTintInt) { - await this.pushData("/video/whiteBalance",{"whiteBalance": whiteBalanceInt}); - await this.pushData("/video/whiteBalanceTint",{"whiteBalanceTint": whiteBalanceTintInt}); - await sleep(500); - await this.getWhiteBalance(); - } - - async setND(NDstopInt) { - await this.pushData("/video/ndFilter",{"stop": NDstopInt}); - await sleep(500); - await 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 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(); - } - - async setAutoExposureMode(AEmodeObject) { - await this.pushData("/video/autoExposure",AEmodeObject); - await sleep(500); - await this.getAutoExposureMode(); - } - - async setCCLift(CCliftObject) { - await this.pushData("/colorCorrection/lift",CCliftObject); - await sleep(500); - await this.getColorCorrection(); - } - - async setCCGamma(CCgammaObject) { - await this.pushData("/colorCorrection/gamma",CCgammaObject); - await sleep(500); - await this.getColorCorrection(); - } - - async setCCGain(CCgainObject) { - await this.pushData("/colorCorrection/gain",CCgainObject); - await sleep(500); - await this.getColorCorrection(); - } - - async setCCOffset(CCoffsetObject) { - await this.pushData("/colorCorrection/offset",CCoffsetObject); - await sleep(500); - await this.getColorCorrection(); - } - - async setCCContrast(CCcontrastObject) { - await this.pushData("/colorCorrection/contrast",CCcontrastObject); - await sleep(500); - await this.getColorCorrection(); - } - - async setCCColor(CCcolorObject) { - await this.pushData("/colorCorrection/color",CCcolorObject); - await sleep(500); - await this.getColorCorrection(); - } - - async setCCLumaContribuion(CClumacontributionObject) { - await this.pushData("/colorCorrection/lumaContribution",CClumacontributionObject); - await sleep(500); - await this.getColorCorrection(); - } - - // =============== Other Commands ======================= - async doAutoFocus() { - await this.pushData("/lens/focus/doAutoFocus"); - sleep(1500).then(() => this.getFocus()); - } - - async play() { - await this.pushData("/transports/0/play"); - await sleep(500); - await this.getPlaybackState(); - } - - async record() { - await this.pushData("/transports/0/record",{"recording": true}); - await sleep(1000); - await this.getRecordState(); - } - - async stopTransport() { - await this.pushData("/transports/0/stop"); - await sleep(1000); - await this.getPlaybackState(); - } - - async stopRecord() { - await this.pushData("/transports/0/record",{"recording": false}); - await sleep(1000); - await this.getRecordState(); - } -} - -/* Helper Functions */ -async function sendRequest(method, url, data) { - const xhttp = new XMLHttpRequest(); - var responseObject; - - xhttp.onload = function() { - if (this.status == 200) { - // Success w/ Data - responseObject = JSON.parse(this.responseText); - } else { - // Pass along response data and stuff - responseObject = this; - } - } - - xhttp.open(method, url, false); - xhttp.send(JSON.stringify(data)); - - return responseObject; -} - -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -/* (c) Dylan Speiser 2024 - github.com/DylanSpeiser */ \ No newline at end of file diff --git a/BMDevice.js b/BMDevice.js new file mode 100644 index 0000000..69324fe --- /dev/null +++ b/BMDevice.js @@ -0,0 +1,272 @@ +/* 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. +*/ + +// Generic Blackmagic Device class +class BMDevice { + // Pretty name and network hostname (strings) + name; + hostname; + APIAddress; + + // WebSocket items + ws; + availableProperties; + + // Active Flag + // Won't call updateUI if this is false + active = false; + + // JSON Object to store all data + propertyData = {}; + + // Reference to UI Updating callback function + // 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. + updateUI() {}; + + // ============= CONSTRUCTOR ================ + constructor(hostname) { + // Set name properties + this.hostname = hostname; + this.APIAddress = "http://"+hostname+"/control/api/v1"; + this.name = this.hostname.replace(".local","").replaceAll("-"," "); + + // Initialize WebSocket + this.ws = new WebSocket("ws://"+hostname+"/control/api/v1/event/websocket"); + + // Get a self object for accessing within callback fns + var self = this; + + // Set the onmessage behavior + this.ws.onmessage = (event) => { + // Parse the event's data as JSON + let eventData = JSON.parse(event.data); + + // Extract data we really care about + let messageData = eventData.data; + + // If it's a listProperties message, update the available properties array + if (messageData.action == "listProperties") { + self.availableProperties = messageData.properties; + } + + // If we get a response from the camera with property information, save it. + if (eventData.type == "response") { + Object.assign(this.propertyData, messageData.values); + } + + // If it's a propertyValueChanged event, update the camera object accordingly and show it on the web page. + if (messageData.action == "propertyValueChanged") { + this.propertyData[messageData.property] = messageData.value; + } + + if (this.active) { + // Update the UI + this.updateUI(); + } + + // Output info to console. + // console.log("WebSocket message received: ", eventData); + } + + // Wait for the WebSocket to open + this.ws.onopen = (event) => { + // Once the WebSocket is open, + + // Ask it for all the properties + self.ws.send(JSON.stringify({type: "request", data: {action: "listProperties"}})); + + sleep(100).then(() => { + // Subscribe to all available events + this.availableProperties.forEach((str) => { + self.ws.send(JSON.stringify({type: "request", data: {action: "subscribe", properties: [str]}})); + }); + }); + } + } + + // Returns a JSON Object of data we got from the device + GETdata(endpoint) { + // Just call sendRequest + return sendRequest("GET", this.APIAddress+endpoint); + } + + // Send JSON Object data to the device + PUTdata(endpoint, data) { + // Just call sendRequest + return sendRequest("PUT", this.APIAddress+endpoint, data); + } + + // ================= SETTERS ================= + // Basically just wrappers for PUT requests to specific endpoints + + // If the optional parameter is set to false, it will stop recording + record(state = true) { + this.PUTdata("/transports/0/record",{recording: state}); + } + + toggleRecord() { + let recordState = this.propertyData['/transports/0/record'].recording; + + this.PUTdata("/transports/0/record",{recording: !recordState}); + } + + play() { + this.PUTdata("/transports/0/play"); + } + + stop() { + this.PUTdata("/transports/0/stop"); + } + + // Boolean parameter, true = forward, false = backwards + seek(direction) { + let clips = this.GETdata("/timelines/0")?.clips; + let playbackData = this.GETdata("/transports/playback"); + + let runningSum = 0; + let currentClipIndex = 0; + let clipStartingTimecodes = []; + let i = 0; + + clips.forEach((clip) => { + if (runningSum+clip.frameCount > playbackData.position) { + currentClipIndex = i; + } + clipStartingTimecodes[i] = runningSum; + runningSum += clip.frameCount; + i++; + }); + + let newClipIndex = min(max(0,(direction ? currentClipIndex+1 : currentClipIndex-1)), clips.length); + + playbackData.position = clipStartingTimecodes[newClipIndex]; + + this.PUTdata("/transports/playback", playbackData); + } + + // Sets Timeline / Clip Looping + // Argument can be either "None", "Loop", or "Loop Clip" + setLoopMode(modeString) { + let newStateObj = this.propertyData['/transports/0/playback']; + + if (modeString === "None") { + newStateObj.loop = false; + newStateObj.singleClip = false; + } else if (modeString === "Loop") { + newStateObj.loop = true; + newStateObj.singleClip = false; + } else if (modeString === "Loop Clip") { + newStateObj.loop = true; + newStateObj.singleClip = true; + } + + this.PUTdata("/transports/0/playback", newStateObj); + } +} + +// Child Class Specifically for Cameras +class BMCamera extends BMDevice { + // Child class constructor + // Just passing the hostname to the superclass's constructor + constructor(hostname) { + super(hostname); + } + + // Sets the white balance and tint based on the following preset: + // 0: Sunlight, 1: Tungsten, 2: Fluorescent, 3: Shade, 4: Cloudy + // Any other value will not affect the WB setting + setWhiteBalancePreset(presetIndex) { + let newWhiteBalance; + let newWhiteBalanceTint; + + switch (presetIndex) { + case 0: + // Sunlight + newWhiteBalance = 5600; + newWhiteBalanceTint = 10; + break; + case 1: + // Tungsten + newWhiteBalance = 3200; + newWhiteBalanceTint = 0; + break; + case 2: + // Fluorescent + newWhiteBalance = 4000; + newWhiteBalanceTint = 15; + break; + case 3: + // Shade + newWhiteBalance = 4500; + newWhiteBalanceTint = 15; + break; + case 4: + // Cloudy + newWhiteBalance = 6500; + newWhiteBalanceTint = 10; + break; + default: + // If any other value is set, don't change anything + newWhiteBalance = this.GETdata("/video/whiteBalance").whiteBalance; + newWhiteBalanceTint = this.GETdata("/video/whiteBalanceTint").whiteBalanceTint; + } + + this.PUTdata("/video/whiteBalance",{whiteBalance: newWhiteBalance}); + this.PUTdata("/video/whiteBalanceTint",{whiteBalanceTint: newWhiteBalanceTint}); + } +} + +/* Helper Functions */ + +// Send request with other method type +function sendRequest(method, url, data) { + // Instantiate the XMLHttpRequest object + let xhr = new XMLHttpRequest(); + + // Create an object to store and return the response + let responseObject = {}; + + // Define the onload function + xhr.onload = function() { + if (this.status < 300) { // If the operation is successful + if (this.responseText) + responseObject = JSON.parse(this.responseText); // Give the data to the responseObject + responseObject.status = this.status; // Also pass along the status code for error handling + } else { // If there has been an error + responseObject = this; // Give the XMLHttpRequest data to the responseObject + console.error("Error ", this.status, ": ", this.statusText); // Log the error in the console + } + }; + + // Open the connection + // The "false" here specifies that we want to wait for the response to come back before returning from xhr.send() + xhr.open(method, url, false); + + // Send the request with data + xhr.send(JSON.stringify(data)); + + // Return response data + return responseObject; +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/* (c) Dylan Speiser 2024 + github.com/DylanSpeiser */ \ No newline at end of file diff --git a/README.md b/README.md index 132e7aa..58c6034 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This web app is utilizes the [Blackmagic](https://blackmagicdesign.com) Camera C >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 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. +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 `BMCamera.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) @@ -74,10 +74,10 @@ If you like this project and want it to improve, consider making a Pull Request ## Tutorials For more information about using the BMD REST API, check out [my tutorial series](https://github.com/DylanSpeiser/BM-API-Tutorial/) that explains the basics of how to interact with the camera in JavaScript and Python. -## Using `BMD-Camera-Control.js` +## Using `BMCamera.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. +Cameras are represented as BMCamera 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.

@@ -86,7 +86,7 @@ Many of setter functions take Objects as arguments, rather than Strings or ints. 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`. +If you want to use `BMCamera.js` in your own UI, there are static references to functions in the BMCamera 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 `BMCamera.js`. # Issues and To-Dos ## Known Issues diff --git a/index.html b/index.html index 9f67aeb..7853cba 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ - + @@ -52,57 +52,57 @@
Lift
- +
- 0.00 - 0.00 - 0.00 - 0.00 + 0.00 + 0.00 + 0.00 + 0.00
- +
Gamma
- +
- 0.00 - 0.00 - 0.00 - 0.00 + 0.00 + 0.00 + 0.00 + 0.00
- +
Gain
- +
- 0.00 - 0.00 - 0.00 - 0.00 + 0.00 + 0.00 + 0.00 + 0.00
- +
Offset
- +
- 0.00 - 0.00 - 0.00 - 0.00 + 0.00 + 0.00 + 0.00 + 0.00
- +
@@ -110,38 +110,38 @@
FILTER
- - 0 - + + 0 +
GAIN
- - +0db - + + +0db +
SHUTTER
- - 1/50 - + + 1/50 +
BALANCE
- - 5600K - + + 5600K +
- - 0 - + + 0 +
@@ -149,17 +149,17 @@
FOCUS - - + +
IRIS - + X.X
ZOOM - + XXmm
@@ -176,13 +176,13 @@
- - - - + + + +

TIMECODE

@@ -195,7 +195,7 @@ Hostname - + @@ -227,12 +227,12 @@ Preset Select - - + @@ -243,7 +243,7 @@ - + @@ -268,7 +268,7 @@
ISO
AE Mode
- +
@@ -276,19 +276,19 @@ - + - +
Pivot - 0 + 0 - +
Adjust - 0 + 0
@@ -299,26 +299,26 @@ - + - + - +
Hue - 0 + 0 - +
Saturation - 0 + 0
Luma Contribution - 0 + 0
@@ -331,14 +331,12 @@
- - Refreshing... + (v 1.1)
diff --git a/style.css b/style.css index 7fa67ec..faf04ca 100644 --- a/style.css +++ b/style.css @@ -219,6 +219,10 @@ input[type=file]:focus { margin-right: 1.25vw; } +#footerLeft span { + margin-left: 1.25vw; +} + #footerLinks a { text-decoration: none; color: #e66c01; diff --git a/web-ui.js b/web-ui.js index 41ced98..a3ef79a 100644 --- a/web-ui.js +++ b/web-ui.js @@ -12,142 +12,86 @@ var ci = 0; // Index into this array for the currently selected came var WBMode = 0; // 0: balance, 1: tint +var defaultControlsHTML; + // Set everything up function bodyOnLoad() { - // 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 - - // 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; + defaultControlsHTML = document.getElementById("allCamerasContainer").innerHTML; } -// Basically a wrapper for BMDCamera() constructor -// Checks the hostname, if it replies successfully then a new BMDCamera object +// Checks the hostname, if it replies successfully then a new BMCamera 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) => { +function initCamera() { + // Get hostname from Hostname text field + let hostname = document.getElementById("hostnameInput").value; + + try { + // Check if the hostname is valid + let response = sendRequest("GET", "http://"+hostname+"/control/api/v1/system",""); + if (response.status < 300) { // Success, make a new camera, get all relevant info, and populate the UI - cameras[ci] = new BMDCamera(hostname); - cameras[ci].getAllInfo(); + cameras[ci] = new BMCamera(hostname); + + cameras[ci].updateUI = updateUIAll; + + cameras[ci].active = true; + 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 => { + } 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 ================================== -// ============================================================================== +// =============================== UI Updater ================================== +// ============================================================================= function updateUIAll() { - updateUIname(); - updateUIhostname(); - updateUIFormat(); - updateUITransportMode(); - updateUIPlaybackState(); - updateUIRecordState(); - updateUITimecode(); - updateUIPresets(); - updateUIActivePreset(); - updateUIAperture(); - updateUIZoom(); - updateUIFocus(); - updateUIISO(); - updateUIgain(); - updateUIWhiteBalance(); - updateUINDStop(); - updateUIshutter(); - updateUIAutoExposureMode(); - updateUIColorCorrection(); - updateUILinks(); -} + // ========== Camera Name ========== -function updateUIname() { document.getElementById("cameraName").innerHTML = cameras[ci].name; -} -function updateUIhostname() { + // ========== Hostname ========== + document.getElementById("hostnameInput").value = cameras[ci].hostname; -} -function updateUIFormat() { - document.getElementById("formatCodec").innerHTML = cameras[ci].format.codec.toUpperCase().replace(":"," ").replace("_",":"); + // ========== Format ========== + + document.getElementById("formatCodec").innerHTML = cameras[ci].propertyData['/system/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"; -} + let resObj = cameras[ci].propertyData['/system/format']?.recordResolution; + document.getElementById("formatResolution").innerHTML = resObj?.width + "x" + resObj?.height; + document.getElementById("formatFPS").innerHTML = cameras[ci].propertyData['/system/format']?.frameRate+" fps"; -function updateUITransportMode() { - //TBI -} + // ========== Recording State ========== -function updateUIPlaybackState() { - //TBI -} - -function updateUIRecordState() { - if (cameras[ci].recordState.recording) { + if (cameras[ci].propertyData['/transports/0/record']?.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(':'); + // ========== Timecode ========== - document.getElementById("timecodeLabel").innerHTML = tcString; -} + document.getElementById("timecodeLabel").innerHTML = parseTimecode(cameras[ci].propertyData['/transports/0/timecode']?.timecode); -function updateUIPresets() { + // ========== Presets Dropdown ========== + var presetsList = document.getElementById("presetsDropDown"); presetsList.innerHTML = ""; - cameras[ci].presets.forEach((presetItem) => { + cameras[ci].propertyData['/presets']?.presets.forEach((presetItem) => { let presetName = presetItem.split('.', 1); let textNode = document.createTextNode(presetName); @@ -156,70 +100,74 @@ function updateUIPresets() { optionNode.appendChild(textNode); document.getElementById("presetsDropDown").appendChild(optionNode); }); -} -function updateUIActivePreset() { + // ========== Active Preset ========== + var presetsList = document.getElementById("presetsDropDown"); presetsList.childNodes.forEach((child) => { - if (child.nodeName == 'OPTION' && child.value == cameras[ci].activePreset) { + if (child.nodeName == 'OPTION' && child.value == cameras[ci].propertyData['/presets/active']?.preset) { 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); -} + // ========== Iris ========== -function updateUIZoom() { - document.getElementById("zoomRange").value = cameras[ci].zoomNormalised; - document.getElementById("zoomMMLabel").innerHTML = cameras[ci].zoomMM +"mm"; -} + document.getElementById("irisRange").value = cameras[ci].propertyData['/lens/iris']?.normalised; + document.getElementById("apertureStopsLabel").innerHTML = cameras[ci].propertyData['/lens/iris']?.apertureStop.toFixed(1); -function updateUIFocus() { - document.getElementById("focusRange").value = cameras[ci].focusNormalised; -} + // ========== Zoom ========== -function updateUIISO() { - document.getElementById("ISOInput").value = cameras[ci].ISO; -} + document.getElementById("zoomRange").value = cameras[ci].propertyData['/lens/zoom']?.normalised; + document.getElementById("zoomMMLabel").innerHTML = cameras[ci].propertyData['/lens/zoom']?.focalLength +"mm"; -function updateUIgain() { - var gainString = ""; + // ========== Focus ========== - if (cameras[ci].gain >= 0) { - gainString = "+"+cameras[ci].gain+"db" + document.getElementById("focusRange").value = cameras[ci].propertyData['/lens/focus']?.normalised; + + // ========== ISO ========== + + if (cameras[ci].propertyData['/video/iso']) + document.getElementById("ISOInput").value = cameras[ci].propertyData['/video/iso']?.iso; + + // ========== GAIN ========== + + let gainString = ""; + let gainInt = cameras[ci].propertyData['/video/gain']?.gain + + if (gainInt >= 0) { + gainString = "+"+gainInt+"db" } else { - gainString = cameras[ci].gain+"db" + gainString = gainInt+"db" } document.getElementById("gainSpan").innerHTML = gainString; -} -function updateUIWhiteBalance() { - document.getElementById("whiteBalanceSpan").innerHTML = cameras[ci].WhiteBalance+"K"; - document.getElementById("whiteBalanceTintSpan").innerHTML = cameras[ci].WhiteBalanceTint; -} + // ========== WHITE BALANCE =========== -function updateUINDStop() { - document.getElementById("ndFilterSpan").innerHTML = cameras[ci].NDStop; - if (cameras[ci].UnimplementedFunctionality.includes("/video/ndFilter")) { + document.getElementById("whiteBalanceSpan").innerHTML = cameras[ci].propertyData['/video/whiteBalance']?.whiteBalance+"K"; + document.getElementById("whiteBalanceTintSpan").innerHTML = cameras[ci].propertyData['/video/whiteBalanceTint']?.whiteBalanceTint; + + // =========== ND ============= + + if (cameras[ci].propertyData['/video/ndFilter']) { + document.getElementById("ndFilterSpan").innerHTML = cameras[ci].propertyData['/video/ndFilter']?.stop; + } else { document.getElementById("ndFilterSpan").innerHTML = 0; document.getElementById("ndFilterSpan").disabled = true; } -} -function updateUIshutter() { - var shutterString = "" + // ============ Shutter ===================== - if ('shutterSpeed' in cameras[ci].shutter) { - shutterString = "1/"+cameras[ci].shutter.shutterSpeed - } else { - var shangleString = (cameras[ci].shutter.shutterAngle / 100).toFixed(1).toString() + let shutterString = "SS" + let shutterObj = cameras[ci].propertyData['/video/shutter']; + + if (shutterObj?.shutterSpeed) { + shutterString = "1/"+shutterObj.shutterSpeed + } else if (shutterObj?.shutterAngle) { + var shangleString = (shutterObj.shutterAngle / 100).toFixed(1).toString() if (shangleString.indexOf(".0") > 0) { shutterString = parseFloat(shangleString).toFixed(0)+"°"; } else { @@ -228,59 +176,65 @@ function updateUIshutter() { } document.getElementById("shutterSpan").innerHTML = shutterString; -} -function updateUIAutoExposureMode() { + // =========== Auto Exposure Mode =========== + let AEmodeSelect = document.getElementById("AEmodeDropDown"); let AEtypeSelect = document.getElementById("AEtypeDropDown"); - AEmodeSelect.value = cameras[ci].AutoExposureMode.mode; - AEtypeSelect.value = cameras[ci].AutoExposureMode.type; -} + AEmodeSelect.value = cameras[ci].propertyData['/video/autoExposure']?.mode; + AEtypeSelect.value = cameras[ci].propertyData['/video/autoExposure']?.type; + + // =========== COLOR CORRECTION ============= -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); + let liftProps = cameras[ci].propertyData['/colorCorrection/lift']; + document.getElementsByClassName("CClumaLabel")[0].innerHTML = liftProps?.luma.toFixed(2); + document.getElementsByClassName("CCredLabel")[0].innerHTML = liftProps?.red.toFixed(2); + document.getElementsByClassName("CCgreenLabel")[0].innerHTML = liftProps?.green.toFixed(2); + document.getElementsByClassName("CCblueLabel")[0].innerHTML = liftProps?.blue.toFixed(2); // 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); + let gammaProps = cameras[ci].propertyData['/colorCorrection/gamma']; + document.getElementsByClassName("CClumaLabel")[1].innerHTML = gammaProps?.luma.toFixed(2); + document.getElementsByClassName("CCredLabel")[1].innerHTML = gammaProps?.red.toFixed(2); + document.getElementsByClassName("CCgreenLabel")[1].innerHTML = gammaProps?.green.toFixed(2); + document.getElementsByClassName("CCblueLabel")[1].innerHTML = gammaProps?.blue.toFixed(2); // 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); + let gainProps = cameras[ci].propertyData['/colorCorrection/gain']; + document.getElementsByClassName("CClumaLabel")[2].innerHTML = gainProps?.luma.toFixed(2); + document.getElementsByClassName("CCredLabel")[2].innerHTML = gainProps?.red.toFixed(2); + document.getElementsByClassName("CCgreenLabel")[2].innerHTML = gainProps?.green.toFixed(2); + document.getElementsByClassName("CCblueLabel")[2].innerHTML = gainProps?.blue.toFixed(2); // 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); + let offsetProps = cameras[ci].propertyData['/colorCorrection/offset']; + document.getElementsByClassName("CClumaLabel")[3].innerHTML = offsetProps?.luma.toFixed(2); + document.getElementsByClassName("CCredLabel")[3].innerHTML = offsetProps?.red.toFixed(2); + document.getElementsByClassName("CCgreenLabel")[3].innerHTML = offsetProps?.green.toFixed(2); + document.getElementsByClassName("CCblueLabel")[3].innerHTML = offsetProps?.blue.toFixed(2); // 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); + let constrastProps = cameras[ci].propertyData['/colorCorrection/contrast']; + document.getElementById("CCcontrastPivotRange").value = constrastProps?.pivot; + document.getElementById("CCcontrastPivotLabel").innerHTML = constrastProps?.pivot.toFixed(2); + document.getElementById("CCcontrastAdjustRange").value = constrastProps?.adjust; + document.getElementById("CCcontrastAdjustLabel").innerHTML = parseInt(constrastProps?.adjust * 50)+"%"; // Color - document.getElementById("CChueRange").value = cameras[ci].CCcolor.hue; - document.getElementById("CCcolorHueLabel").innerHTML = cameras[ci].CCcolor.hue.toFixed(2); + let colorProps = cameras[ci].propertyData['/colorCorrection/color']; + document.getElementById("CChueRange").value = colorProps?.hue; + document.getElementById("CCcolorHueLabel").innerHTML = parseInt((colorProps?.hue + 1) * 180)+"°"; - document.getElementById("CCsaturationRange").value = cameras[ci].CCcolor.saturation; - document.getElementById("CCcolorSatLabel").innerHTML = cameras[ci].CCcolor.saturation.toFixed(2); + document.getElementById("CCsaturationRange").value = colorProps?.saturation; + document.getElementById("CCcolorSatLabel").innerHTML = parseInt(colorProps?.saturation * 50)+"%"; - document.getElementById("CClumaContributionRange").value = cameras[ci].CClumacontribution.lumaContribution; - document.getElementById("CCcolorLCLabel").innerHTML = cameras[ci].CClumacontribution.lumaContribution.toFixed(2); -} + let lumaContributionProps = cameras[ci].propertyData['/colorCorrection/lumaContribution']; + document.getElementById("CClumaContributionRange").value = lumaContributionProps?.lumaContribution; + document.getElementById("CCcolorLCLabel").innerHTML = parseInt(lumaContributionProps?.lumaContribution * 100)+"%"; -function updateUILinks() { + // ============ Footer Links =============== document.getElementById("documentationLink").href = "http://"+cameras[ci].hostname+"/control/documentation.html"; document.getElementById("mediaManagerLink").href = "http://"+cameras[ci].hostname; } @@ -288,32 +242,18 @@ function updateUILinks() { // ============================================================================== - -// 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(); - } -} - -// Ten Second Timer Call -// Refreshes all elements -function timerCallFunction10() { - if (cameras[ci]) { - refresh(); - } -} - // Called when the user changes tabs to a different camera function switchCamera(index) { + if (cameras[ci]) { + cameras[ci].active = false; + } + ci = index; - if (cameras[ci]) { - refresh(); - } + // Reset the Controls + document.getElementById("allCamerasContainer").innerHTML = defaultControlsHTML; + + // Update the UI for (var i = 0; i < 8; i++) { if (i == ci) { @@ -325,6 +265,10 @@ function switchCamera(index) { document.getElementById("cameraNumberLabel").innerHTML = "CAM"+(ci+1); document.getElementById("cameraName").innerHTML = "CAMERA NAME"; + + if (cameras[ci]) { + cameras[ci].active = true; + } } // For not-yet-implemented Color Correction UI @@ -384,128 +328,16 @@ function manualAPICall() { const requestMethod = (requestRadioGET.checked ? "GET" : "PUT"); const requestURL = cameras[ci].APIAddress+requestEndpointText; - sendRequest(requestMethod,requestURL,requestData).then((response) => { - // console.log("Manual API Call Response: ", response); - if (!response.status) { - document.getElementById("manualRequestResponseP").innerHTML = JSON.stringify(response); - } else { - document.getElementById("manualRequestResponseP").innerHTML = response.status+": "+response.statusText; - } - }); -} - -/* Control Calling Functions */ -/* Makes the HTML cleaner. */ - -function decreaseND() { - cameras[ci].setND(cameras[ci].NDStop-2); -} - -function increaseND() { - cameras[ci].setND(cameras[ci].NDStop+2); -} - -function decreaseGain() { - cameras[ci].setGain(cameras[ci].gain-2); -} - -function increaseGain() { - cameras[ci].setGain(cameras[ci].gain+2); -} - -function decreaseShutter() { - let cam = cameras[ci]; - - if ('shutterSpeed' in cam.shutter) { - cam.setShutter({"shutterSpeed":cam.shutter.shutterSpeed+10}); - } else { - cam.setShutter({"shutterAngle": cam.shutter.shutterAngle-1000}); - } -} - -function increaseShutter() { - let cam = cameras[ci]; - - if ('shutterSpeed' in cam.shutter) { - cam.setShutter({"shutterSpeed":cam.shutter.shutterSpeed-10}); - } else { - cam.setShutter({"shutterAngle": cam.shutter.shutterAngle+1000}); - } -} - -function handleShutterInput(inputString) { - let cam = cameras[ci]; - - if ('shutterSpeed' in cam.shutter) { - if (inputString.indexOf("1/") >= 0) { - cam.setShutter({"shutterSpeed" :parseInt(inputString.substring(2))}); - } else { - cam.setShutter({"shutterSpeed" :parseInt(inputString)}); - } - - } else { - cam.setShutter({"shutterAngle": parseInt(parseFloat(inputString)*100)}); - } -} - -function decreaseWhiteBalance() { - cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance-50,cameras[ci].WhiteBalanceTint); -} - -function increaseWhiteBalance() { - cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance+50,cameras[ci].WhiteBalanceTint); -} - -function decreaseWhiteBalanceTint() { - cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance,cameras[ci].WhiteBalanceTint-1); -} - -function increaseWhiteBalanceTint() { - cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance,cameras[ci].WhiteBalanceTint+1); -} - -function AEmodeInputHandler() { - let AEmode = document.getElementById("AEmodeDropDown").value; - let AEtype = document.getElementById("AEtypeDropDown").value; - - cameras[ci].setAutoExposureMode({mode: AEmode, type: AEtype}); -} - -// 0: lift, 1: gamma, 2: gain, 3: offset -function setCCFromUI(which) { - let lumaFloat = parseFloat(document.getElementsByClassName("CClumaLabel")[which].innerHTML); - let redFloat = parseFloat(document.getElementsByClassName("CCredLabel")[which].innerHTML); - let greenFloat = parseFloat(document.getElementsByClassName("CCgreenLabel")[which].innerHTML); - let blueFloat = parseFloat(document.getElementsByClassName("CCblueLabel")[which].innerHTML); + let response = sendRequest(requestMethod,requestURL,requestData); - let ccobject = {"red": redFloat, "green": greenFloat, "blue": blueFloat, "luma": lumaFloat}; - - if (which == 0) { - cameras[ci].setCCLift(ccobject); - } else if (which == 1) { - cameras[ci].setCCGamma(ccobject); - } else if (which == 2) { - cameras[ci].setCCGain(ccobject); - } else { - cameras[ci].setCCOffset(ccobject); - } + document.getElementById("manualRequestResponseP").innerHTML = JSON.stringify(response); } -// Reset Color Correction Values -// 0: lift, 1: gamma, 2: gain, 3: offset, 4: contrast, 5: color & LC -function resetCC(which) { - if (which == 0) { - cameras[ci].setCCLift({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0}); - } else if (which == 1) { - cameras[ci].setCCGamma({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0}); - } else if (which == 2) { - cameras[ci].setCCGain({"red": 1.0, "green": 1.0, "blue": 1.0, "luma": 1.0}); - } else if (which == 3) { - cameras[ci].setCCOffset({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0}); - } else if (which == 4) { - cameras[ci].setCCContrast({"pivot": 0.5, "adjust": 1.0}); - } else if (which == 5) { - cameras[ci].setCCColor({"hue": 0.0, "saturation": 1.0}); - cameras[ci].setCCLumaContribuion({"lumaContribution": 1.0}); - } +/* Helper Functions */ +function parseTimecode(timecodeBCD) { + let noDropFrame = timecodeBCD & 0b01111111111111111111111111111111; // The first bit of the timecode is 1 if "Drop Frame Timecode" is on. We don't want to include that in the display. + let decimalTCInt = parseInt(noDropFrame.toString(16), 10); // Convert the BCD number into base ten + let decimalTCString = decimalTCInt.toString().padStart(8, '0'); // Convert the base ten number to a string eight characters long + let finalTCString = decimalTCString.match(/.{1,2}/g).join(':'); // Put colons between every two characters + return finalTCString; } \ No newline at end of file