Hide and Seek
This commit is contained in:
parent
68279ff484
commit
0294bdfed8
7 changed files with 492 additions and 868 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
.DS_Store
|
||||
/.vscode
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
272
BMDevice.js
Normal file
272
BMDevice.js
Normal file
|
|
@ -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 */
|
||||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
|
|
@ -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).
|
||||
<br><br>
|
||||
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.
|
||||
<br><br>
|
||||
After instantiation, the constructor does NOT automatically fetch any data from the camera. This can be done using the `getAllInfo()` method, or the individual getters if you only need a few details. These are all asynchronous and so must be waited upon using `await` or `.then()`. Consult your nearest Google search bar for help implementing asynchronous JavaScript, that's how I did it.
|
||||
<br><br>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
142
index.html
142
index.html
|
|
@ -14,7 +14,7 @@
|
|||
</head>
|
||||
<body onload="bodyOnLoad()">
|
||||
<!-- JavaScript Linking -->
|
||||
<script src="BMD-Camera-Control.js"></script>
|
||||
<script src="BMDevice.js"></script>
|
||||
<script src="web-ui.js"></script>
|
||||
|
||||
<!------ Page Content ------>
|
||||
|
|
@ -52,57 +52,57 @@
|
|||
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionContainer">
|
||||
<!-- <div class="flexContainerH" id="cameraControlLGGTabs">
|
||||
<a href="#" class="ccTabLabel selectedTab" onclick="setCCMode(0)">Lift</a>
|
||||
<a href="#" class="ccTabLabel" onclick="setCCMode(1)">Gamma</a>
|
||||
<a href="#" class="ccTabLabel" onclick="setCCMode(2)">Gain</a>
|
||||
<a href="#" class="ccTabLabel selectedTab" onclick="">Lift</a>
|
||||
<a href="#" class="ccTabLabel" onclick="">Gamma</a>
|
||||
<a href="#" class="ccTabLabel" onclick="">Gain</a>
|
||||
</div> -->
|
||||
<span style="margin-top: 0.5em;">Lift</span>
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
|
||||
<button class="CCResetButton circleButton" onclick="resetCC(0)" title="Reset Lift">⟳</button>
|
||||
<button class="CCResetButton circleButton" onclick="" title="Reset Lift">⟳</button>
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(0)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(0)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(0)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(0)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="">0.00</span>
|
||||
</div>
|
||||
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(0)" title="Set Lift">➚</button>
|
||||
<button id="CCHamburgerButton" class="circleButton" onclick="" title="Set Lift">➚</button>
|
||||
</div>
|
||||
|
||||
<span>Gamma</span>
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
|
||||
<button class="CCResetButton circleButton" onclick="resetCC(1)" title="Reset Gamma">⟳</button>
|
||||
<button class="CCResetButton circleButton" onclick="" title="Reset Gamma">⟳</button>
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(1)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(1)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(1)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(1)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="">0.00</span>
|
||||
</div>
|
||||
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(1)" title="Set Gamma">➚</button>
|
||||
<button id="CCHamburgerButton" class="circleButton" onclick="" title="Set Gamma">➚</button>
|
||||
</div>
|
||||
|
||||
<span>Gain</span>
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
|
||||
<button class="CCResetButton circleButton" onclick="resetCC(2)" title="Reset Gain">⟳</button>
|
||||
<button class="CCResetButton circleButton" onclick="" title="Reset Gain">⟳</button>
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(2)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(2)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(2)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(2)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="">0.00</span>
|
||||
</div>
|
||||
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(2)" title="Set Gain">➚</button>
|
||||
<button id="CCHamburgerButton" class="circleButton" onclick="" title="Set Gain">➚</button>
|
||||
</div>
|
||||
|
||||
|
||||
<span>Offset</span>
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionBottomContainer">
|
||||
<button class="CCResetButton circleButton" onclick="resetCC(3)" title="Reset Offset">⟳</button>
|
||||
<button class="CCResetButton circleButton" onclick="" title="Reset Offset">⟳</button>
|
||||
<div class="flexContainerH" id="cameraControlColorCorrectionNumbersContainer">
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(3)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(3)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(3)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="if (event.key === 'Enter') {event.preventDefault(); setCCFromUI(3)}">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #dbdbdb;" class="CClumaLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #e64b3d;" class="CCredLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #00a841;" class="CCgreenLabel" onkeydown="">0.00</span>
|
||||
<span contenteditable="plaintext-only" style="text-decoration-color: #2a78c8;" class="CCblueLabel" onkeydown="">0.00</span>
|
||||
</div>
|
||||
<button id="CCHamburgerButton" class="circleButton" onclick="setCCFromUI(3)" title="Set Offset">➚</button>
|
||||
<button id="CCHamburgerButton" class="circleButton" onclick="" title="Set Offset">➚</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -110,38 +110,38 @@
|
|||
<div class="ccExposureSettingContainer">
|
||||
<span class="exposureControlLabel">FILTER</span>
|
||||
<div class="ccExposureSettingValueContainer">
|
||||
<a class="expAdjArr" href="#" onclick="decreaseND()" id="NDL">◀</a>
|
||||
<span id="ndFilterSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setND(parseInt(this.innerHTML))}">0</span>
|
||||
<a class="expAdjArr" href="#" onclick="increaseND()" id="NDR">▶</a>
|
||||
<a class="expAdjArr" href="#" onclick="" id="NDL">◀</a>
|
||||
<span id="ndFilterSpan" contenteditable="plaintext-only" onkeydown="">0</span>
|
||||
<a class="expAdjArr" href="#" onclick="" id="NDR">▶</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ccExposureSettingContainer">
|
||||
<span class="exposureControlLabel">GAIN</span>
|
||||
<div class="ccExposureSettingValueContainer">
|
||||
<a class="expAdjArr" href="#" onclick="decreaseGain()" id="GAL">◀</a>
|
||||
<span id="gainSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setGain(parseInt(this.innerHTML))}">+0db</span>
|
||||
<a class="expAdjArr" href="#" onclick="increaseGain()" id="GAR">▶</a>
|
||||
<a class="expAdjArr" href="#" onclick="" id="GAL">◀</a>
|
||||
<span id="gainSpan" contenteditable="plaintext-only" onkeydown="">+0db</span>
|
||||
<a class="expAdjArr" href="#" onclick="" id="GAR">▶</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ccExposureSettingContainer">
|
||||
<span class="exposureControlLabel">SHUTTER</span>
|
||||
<div class="ccExposureSettingValueContainer">
|
||||
<a class="expAdjArr" href="#" onclick="decreaseShutter()" id="SHL">◀</a>
|
||||
<span id="shutterSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); handleShutterInput(this.innerHTML)}">1/50</span>
|
||||
<a class="expAdjArr" href="#" onclick="increaseShutter()" id="SHR">▶</a>
|
||||
<a class="expAdjArr" href="#" onclick="" id="SHL">◀</a>
|
||||
<span id="shutterSpan" contenteditable="plaintext-only" onkeydown="">1/50</span>
|
||||
<a class="expAdjArr" href="#" onclick="" id="SHR">▶</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ccExposureSettingContainer">
|
||||
<span class="exposureControlLabel" onclick="swapWBMode()" title="Click here to swap between WB and Tint" id="WBLabel">BALANCE</span>
|
||||
<div class="ccExposureSettingValueContainer" id="WBValueContainer">
|
||||
<a class="expAdjArr" href="#" onclick="decreaseWhiteBalance()" id="WBL">◀</a>
|
||||
<span id="whiteBalanceSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setWhiteBalance(parseInt(this.innerHTML),cameras[ci].whiteBalanceTint)}">5600K</span>
|
||||
<a class="expAdjArr" href="#" onclick="increaseWhiteBalance()" id="WBR">▶</a>
|
||||
<a class="expAdjArr" href="#" onclick="" id="WBL">◀</a>
|
||||
<span id="whiteBalanceSpan" contenteditable="plaintext-only" onkeydown="">5600K</span>
|
||||
<a class="expAdjArr" href="#" onclick="" id="WBR">▶</a>
|
||||
</div>
|
||||
<div class="ccExposureSettingValueContainer dNone" id="WBTintValueContainer">
|
||||
<a class="expAdjArr" href="#" onclick="decreaseWhiteBalanceTint()" id="WBTL">◀</a>
|
||||
<span id="whiteBalanceTintSpan" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setWhiteBalance(cameras[ci].whiteBalance,parseInt(this.innerHTML))}">0</span>
|
||||
<a class="expAdjArr" href="#" onclick="increaseWhiteBalanceTint()" id="WBLR">▶</a>
|
||||
<a class="expAdjArr" href="#" onclick="" id="WBTL">◀</a>
|
||||
<span id="whiteBalanceTintSpan" contenteditable="plaintext-only" onkeydown="">0</span>
|
||||
<a class="expAdjArr" href="#" onclick="" id="WBLR">▶</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -149,17 +149,17 @@
|
|||
<div class="flexContainerH" id="cameraControlLensContainer">
|
||||
<div class="lensSliderContainer">
|
||||
<span>FOCUS</span>
|
||||
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="focusRange" onmouseup="cameras[ci].setFocus(parseFloat(this.value))">
|
||||
<button id="AFButton" class="circleButton" onclick="cameras[ci].doAutoFocus()">AF</button>
|
||||
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="focusRange" onmouseup="">
|
||||
<button id="AFButton" class="circleButton" onclick="">AF</button>
|
||||
</div>
|
||||
<div class="lensSliderContainer">
|
||||
<span>IRIS</span>
|
||||
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="irisRange" onmouseup="cameras[ci].setAperture(parseFloat(this.value))">
|
||||
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="irisRange" onmouseup="">
|
||||
<span id="apertureStopsLabel">X.X</span>
|
||||
</div>
|
||||
<div class="lensSliderContainer">
|
||||
<span>ZOOM</span>
|
||||
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="zoomRange" onmouseup="cameras[ci].setZoom(parseFloat(this.value))">
|
||||
<input type="range" orient="vertical" max="1" min="0" step="0.001" id="zoomRange" onmouseup="">
|
||||
<span id="zoomMMLabel">XXmm</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -176,13 +176,13 @@
|
|||
</div>
|
||||
<div id="transportControls">
|
||||
<!-- These will be sticky buttons for loop, single clip, record -->
|
||||
<!--<button class="circleButton" onclick="" title="Loop">↻</button>
|
||||
<button class="circleButton" onclick="" title="Loop">↻</button>
|
||||
<button class="circleButton" onclick="" title="Single Clip">S</button>
|
||||
<button class="circleButton" onclick="" title="Back">⏴</button>
|
||||
<button class="circleButton" onclick="" title="Forward">⏵</button> -->
|
||||
<button class="circleButton" onclick="cameras[ci].record()" title="Record">⏺</button>
|
||||
<!-- <button class="circleButton" onclick="cameras[ci].play()" title="Play">▶</button> -->
|
||||
<button class="circleButton" onclick="cameras[ci].stopTransport(); cameras[ci].stopRecord();" title="Stop">⏹</button>
|
||||
<button class="circleButton" onclick="" title="Forward">⏵</button>
|
||||
<button class="circleButton" onclick="cameras[ci].toggleRecord()" title="Record">⏺</button>
|
||||
<button class="circleButton" onclick="cameras[ci].play()" title="Play">▶</button>
|
||||
<button class="circleButton" onclick="cameras[ci].stop()" title="Stop">⏹</button>
|
||||
</div>
|
||||
<h2 id="timecodeLabel">TIMECODE</h2>
|
||||
</div>
|
||||
|
|
@ -195,7 +195,7 @@
|
|||
<td>Hostname</td>
|
||||
<td>
|
||||
<input type="text" placeholder="Studio-Camera-6K-Pro.local" id="hostnameInput">
|
||||
<button onclick='initCamera(document.getElementById("hostnameInput").value)'>Connect</button>
|
||||
<button onclick="initCamera()">Connect</button>
|
||||
<span id="connectionErrorSpan"></span>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -227,12 +227,12 @@
|
|||
<tr>
|
||||
<td>Preset Select</td>
|
||||
<td>
|
||||
<select id="presetsDropDown" onchange="cameras[ci].setActivePreset(this.value+'.cset')">
|
||||
<select id="presetsDropDown" onchange="">
|
||||
<!-- Auto-populated by updateUIPresets() -->
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<button onclick="cameras[ci].setActivePreset(document.getElementById('presetsDropDown').value+'.cset')">Restore from Preset</button>
|
||||
<button onclick="">Restore from Preset</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -243,7 +243,7 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>ISO</td>
|
||||
<td><input type="number" id="ISOInput" step="100" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setISO(parseInt(this.value))}"></td>
|
||||
<td><input type="number" id="ISOInput" step="100" onkeydown=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AE Mode</td>
|
||||
|
|
@ -268,7 +268,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button style="margin: 2vh 0 0 3.5vw;" onclick="AEmodeInputHandler()">Set AE Mode</button>
|
||||
<button style="margin: 2vh 0 0 3.5vw;" onclick="">Set AE Mode</button>
|
||||
</div>
|
||||
|
||||
<div class="tableControl">
|
||||
|
|
@ -276,19 +276,19 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>Pivot</td>
|
||||
<td><input type="range" max="5" min="-5" step="0.001" id="CCcontrastPivotRange" onmouseup="cameras[ci].setCCContrast({'pivot': parseFloat(this.value)})"></td>
|
||||
<td><input type="range" max="0" min="1" step="0.001" id="CCcontrastPivotRange" onmouseup=""></td>
|
||||
<td>
|
||||
<span id="CCcontrastPivotLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCContrast({'pivot': parseFloat(this.innerHTML)})}">0</span>
|
||||
<span id="CCcontrastPivotLabel" contenteditable="plaintext-only" onkeydown="">0</span>
|
||||
</td>
|
||||
<td rowspan="2">
|
||||
<button class="CCResetButton circleButton" onclick="resetCC(4)" title="Reset Contrast">⟳</button>
|
||||
<button class="CCResetButton circleButton" onclick="" title="Reset Contrast">⟳</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Adjust</td>
|
||||
<td><input type="range" max="5" min="-5" step="0.001" id="CCcontrastAdjustRange" onmouseup="cameras[ci].setCCContrast({'adjust': parseFloat(this.value)})"></td>
|
||||
<td><input type="range" max="0" min="2" step="0.001" id="CCcontrastAdjustRange" onmouseup=""></td>
|
||||
<td>
|
||||
<span id="CCcontrastAdjustLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCContrast({'adjust': parseFloat(this.innerHTML)})}">0</span>
|
||||
<span id="CCcontrastAdjustLabel" contenteditable="plaintext-only" onkeydown="">0</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -299,26 +299,26 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>Hue</td>
|
||||
<td><input type="range" max="1" min="-1" step="0.001" id="CChueRange" onmouseup="cameras[ci].setCCColor({'hue': parseFloat(this.value)})"></td>
|
||||
<td><input type="range" max="1" min="-1" step="0.001" id="CChueRange" onmouseup=""></td>
|
||||
<td>
|
||||
<span id="CCcolorHueLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCColor({'hue': parseFloat(this.innerHTML)})}">0</span>
|
||||
<span id="CCcolorHueLabel" contenteditable="plaintext-only" onkeydown="">0</span>
|
||||
</td>
|
||||
<td rowspan="3">
|
||||
<button class="CCResetButton circleButton" onclick="resetCC(5)" title="Reset Color">⟳</button>
|
||||
<button class="CCResetButton circleButton" onclick="" title="Reset Color">⟳</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Saturation</td>
|
||||
<td><input type="range" max="2" min="0" step="0.001" id="CCsaturationRange" onmouseup="cameras[ci].setCCColor({'saturation': parseFloat(this.value)})"></td>
|
||||
<td><input type="range" max="2" min="0" step="0.001" id="CCsaturationRange" onmouseup=""></td>
|
||||
<td>
|
||||
<span id="CCcolorSatLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCColor({'saturation': parseFloat(this.innerHTML)})}">0</span>
|
||||
<span id="CCcolorSatLabel" contenteditable="plaintext-only" onkeydown="">0</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Luma Contribution</td>
|
||||
<td><input type="range" max="1" min="0" step="0.001" id="CClumaContributionRange" onmouseup="cameras[ci].setCCLumaContribuion({'lumaContribution': parseFloat(this.value)})"></td>
|
||||
<td><input type="range" max="1" min="0" step="0.001" id="CClumaContributionRange" onmouseup=""></td>
|
||||
<td>
|
||||
<span id="CCcolorLCLabel" contenteditable="plaintext-only" onkeydown="if (event.key === 'Enter') {event.preventDefault(); cameras[ci].setCCLumaContribuion({'lumaContribution': parseFloat(this.innerHTML)})}">0</span>
|
||||
<span id="CCcolorLCLabel" contenteditable="plaintext-only" onkeydown="">0</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -331,14 +331,12 @@
|
|||
<!-- Footer Div -->
|
||||
<div class="flexContainerH" id="footerContainer">
|
||||
<div id="footerLeft">
|
||||
<button onclick="refresh();">Refresh</button>
|
||||
<span id="refreshingText" class="refreshing">Refreshing...</span>
|
||||
<span class="">(v 1.1)</span>
|
||||
</div>
|
||||
<div id="footerLinks">
|
||||
<span><a id="documentationLink" href="#" target="_blank">YAML Documentation</a></span>
|
||||
<span><a id="mediaManagerLink" href="#" target="_blank">Web Media Manager</a></span>
|
||||
<span><a id="githubLink" href="#" target="https://github.com/DylanSpeiser/BM-Camera-Control-WebUI">GitHub</a></span>
|
||||
<span class="">(v 1.0)</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
450
web-ui.js
450
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 ==========
|
||||
|
||||
let resObj = cameras[ci].format.recordResolution;
|
||||
document.getElementById("formatResolution").innerHTML = resObj.width + "x" + resObj.height;
|
||||
document.getElementById("formatFPS").innerHTML = cameras[ci].format.frameRate+" fps";
|
||||
}
|
||||
document.getElementById("formatCodec").innerHTML = cameras[ci].propertyData['/system/format']?.codec.toUpperCase().replace(":"," ").replace("_",":");
|
||||
|
||||
function updateUITransportMode() {
|
||||
//TBI
|
||||
}
|
||||
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 updateUIPlaybackState() {
|
||||
//TBI
|
||||
}
|
||||
// ========== Recording State ==========
|
||||
|
||||
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);
|
||||
|
||||
// ========== Presets Dropdown ==========
|
||||
|
||||
function updateUIPresets() {
|
||||
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;
|
||||
}
|
||||
});
|
||||
let response = sendRequest(requestMethod,requestURL,requestData);
|
||||
|
||||
document.getElementById("manualRequestResponseP").innerHTML = JSON.stringify(response);
|
||||
}
|
||||
|
||||
/* Control Calling Functions */
|
||||
/* Makes the HTML cleaner. */
|
||||
|
||||
function decreaseND() {
|
||||
cameras[ci].setND(cameras[ci].NDStop-2);
|
||||
}
|
||||
|
||||
function increaseND() {
|
||||
cameras[ci].setND(cameras[ci].NDStop+2);
|
||||
}
|
||||
|
||||
function decreaseGain() {
|
||||
cameras[ci].setGain(cameras[ci].gain-2);
|
||||
}
|
||||
|
||||
function increaseGain() {
|
||||
cameras[ci].setGain(cameras[ci].gain+2);
|
||||
}
|
||||
|
||||
function decreaseShutter() {
|
||||
let cam = cameras[ci];
|
||||
|
||||
if ('shutterSpeed' in cam.shutter) {
|
||||
cam.setShutter({"shutterSpeed":cam.shutter.shutterSpeed+10});
|
||||
} else {
|
||||
cam.setShutter({"shutterAngle": cam.shutter.shutterAngle-1000});
|
||||
}
|
||||
}
|
||||
|
||||
function increaseShutter() {
|
||||
let cam = cameras[ci];
|
||||
|
||||
if ('shutterSpeed' in cam.shutter) {
|
||||
cam.setShutter({"shutterSpeed":cam.shutter.shutterSpeed-10});
|
||||
} else {
|
||||
cam.setShutter({"shutterAngle": cam.shutter.shutterAngle+1000});
|
||||
}
|
||||
}
|
||||
|
||||
function handleShutterInput(inputString) {
|
||||
let cam = cameras[ci];
|
||||
|
||||
if ('shutterSpeed' in cam.shutter) {
|
||||
if (inputString.indexOf("1/") >= 0) {
|
||||
cam.setShutter({"shutterSpeed" :parseInt(inputString.substring(2))});
|
||||
} else {
|
||||
cam.setShutter({"shutterSpeed" :parseInt(inputString)});
|
||||
}
|
||||
|
||||
} else {
|
||||
cam.setShutter({"shutterAngle": parseInt(parseFloat(inputString)*100)});
|
||||
}
|
||||
}
|
||||
|
||||
function decreaseWhiteBalance() {
|
||||
cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance-50,cameras[ci].WhiteBalanceTint);
|
||||
}
|
||||
|
||||
function increaseWhiteBalance() {
|
||||
cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance+50,cameras[ci].WhiteBalanceTint);
|
||||
}
|
||||
|
||||
function decreaseWhiteBalanceTint() {
|
||||
cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance,cameras[ci].WhiteBalanceTint-1);
|
||||
}
|
||||
|
||||
function increaseWhiteBalanceTint() {
|
||||
cameras[ci].setWhiteBalance(cameras[ci].WhiteBalance,cameras[ci].WhiteBalanceTint+1);
|
||||
}
|
||||
|
||||
function AEmodeInputHandler() {
|
||||
let AEmode = document.getElementById("AEmodeDropDown").value;
|
||||
let AEtype = document.getElementById("AEtypeDropDown").value;
|
||||
|
||||
cameras[ci].setAutoExposureMode({mode: AEmode, type: AEtype});
|
||||
}
|
||||
|
||||
// 0: lift, 1: gamma, 2: gain, 3: offset
|
||||
function setCCFromUI(which) {
|
||||
let lumaFloat = parseFloat(document.getElementsByClassName("CClumaLabel")[which].innerHTML);
|
||||
let redFloat = parseFloat(document.getElementsByClassName("CCredLabel")[which].innerHTML);
|
||||
let greenFloat = parseFloat(document.getElementsByClassName("CCgreenLabel")[which].innerHTML);
|
||||
let blueFloat = parseFloat(document.getElementsByClassName("CCblueLabel")[which].innerHTML);
|
||||
|
||||
let ccobject = {"red": redFloat, "green": greenFloat, "blue": blueFloat, "luma": lumaFloat};
|
||||
|
||||
if (which == 0) {
|
||||
cameras[ci].setCCLift(ccobject);
|
||||
} else if (which == 1) {
|
||||
cameras[ci].setCCGamma(ccobject);
|
||||
} else if (which == 2) {
|
||||
cameras[ci].setCCGain(ccobject);
|
||||
} else {
|
||||
cameras[ci].setCCOffset(ccobject);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset Color Correction Values
|
||||
// 0: lift, 1: gamma, 2: gain, 3: offset, 4: contrast, 5: color & LC
|
||||
function resetCC(which) {
|
||||
if (which == 0) {
|
||||
cameras[ci].setCCLift({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0});
|
||||
} else if (which == 1) {
|
||||
cameras[ci].setCCGamma({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0});
|
||||
} else if (which == 2) {
|
||||
cameras[ci].setCCGain({"red": 1.0, "green": 1.0, "blue": 1.0, "luma": 1.0});
|
||||
} else if (which == 3) {
|
||||
cameras[ci].setCCOffset({"red": 0.0, "green": 0.0, "blue": 0.0, "luma": 0.0});
|
||||
} else if (which == 4) {
|
||||
cameras[ci].setCCContrast({"pivot": 0.5, "adjust": 1.0});
|
||||
} else if (which == 5) {
|
||||
cameras[ci].setCCColor({"hue": 0.0, "saturation": 1.0});
|
||||
cameras[ci].setCCLumaContribuion({"lumaContribution": 1.0});
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
Loading…
Reference in a new issue