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

633 lines
26 KiB
JavaScript
Raw Permalink Normal View History

/* Blackmagic Camera Control WebUI
(c) Dylan Speiser 2024 UI redesign 2025
2024-06-14 19:40:07 -04:00
*/
2024-06-11 19:46:25 -04:00
var cameras = [];
var ci = 0;
var WBMode = 0; // 0: balance, 1: tint
var unsavedChanges = [];
2024-06-14 19:40:07 -04:00
function bodyOnLoad() {
document.getElementById('hostnameInput').value = localStorage.getItem('camerahostname_' + ci) || '';
if (localStorage.getItem('camerasecurity_' + ci) === 'true') {
document.getElementById('secureCheckbox').checked = true;
}
2024-06-11 19:46:25 -04:00
}
// =====================================================================
// Tab switching
// =====================================================================
function switchTab(name) {
document.querySelectorAll('.tabPanel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-' + name).classList.add('active');
document.getElementById('tab-btn-' + name).classList.add('active');
}
// =====================================================================
// Camera init
// =====================================================================
2024-07-02 17:53:25 -04:00
function initCamera() {
const hostname = document.getElementById('hostnameInput').value;
const security = document.getElementById('secureCheckbox').checked;
const errorSpan = document.getElementById('connectionErrorSpan');
2024-07-02 17:53:25 -04:00
try {
const response = sendRequest('GET', (security ? 'https://' : 'http://') + hostname + '/control/api/v1/system', '');
2024-06-13 17:46:46 -04:00
if (response.status < 300) {
2024-07-09 12:46:16 -04:00
cameras[ci] = new BMCamera(hostname, security);
localStorage.setItem('camerahostname_' + ci, hostname);
localStorage.setItem('camerasecurity_' + ci, security);
2024-07-02 17:53:25 -04:00
cameras[ci].updateUI = updateUIAll;
cameras[ci].active = true;
errorSpan.textContent = 'Connected.';
errorSpan.style.color = 'var(--accent)';
2024-06-13 17:46:46 -04:00
} else {
errorSpan.textContent = response.statusText;
errorSpan.style.color = 'var(--rec)';
2024-06-13 17:46:46 -04:00
}
2024-07-02 17:53:25 -04:00
} catch (error) {
errorSpan.title = error;
errorSpan.textContent = 'Error ' + error.code + ': ' + error.name;
errorSpan.style.color = 'var(--rec)';
2024-07-02 17:53:25 -04:00
}
unsavedChanges = unsavedChanges.filter(e => e !== 'Hostname');
2024-06-13 17:46:46 -04:00
}
// =====================================================================
// Main UI updater (called by WebSocket)
// =====================================================================
2024-06-14 19:40:07 -04:00
function updateUIAll() {
const cam = cameras[ci];
if (!cam) return;
// Camera name
document.getElementById('cameraName').textContent = cam.name;
// Hostname
if (!unsavedChanges.includes('Hostname')) {
document.getElementById('hostnameInput').value = cam.hostname;
}
// Format display
const fmt = cam.propertyData['/system/format'];
document.getElementById('formatCodec').textContent =
fmt?.codec?.toUpperCase().replace(':', ' ').replace('_', ':') || '—';
const res = fmt?.recordResolution;
document.getElementById('formatResolution').textContent =
res ? res.width + 'x' + res.height : '—';
document.getElementById('formatFPS').textContent =
fmt?.frameRate ? fmt.frameRate + ' fps' : '—';
// Recording state
const isRecording = cam.propertyData['/transports/0/record']?.recording;
document.getElementById('topBar').classList.toggle('recording', !!isRecording);
document.getElementById('recordButton').classList.toggle('recording', !!isRecording);
document.getElementById('recLabel').textContent = isRecording ? 'RECORDING' : 'REC';
// Loop / single clip buttons
const loopState = cam.propertyData['/transports/0/playback']?.loop;
const singleClipState = cam.propertyData['/transports/0/playback']?.singleClip;
document.getElementById('loopButton').classList.toggle('activated', !!loopState);
document.getElementById('singleClipButton').classList.toggle('activated', !!singleClipState);
// Timecode
document.getElementById('timecodeLabel').textContent =
parseTimecode(cam.propertyData['/transports/0/timecode']?.timecode);
// Presets dropdown
if (!unsavedChanges.includes('presets')) {
const dd = document.getElementById('presetsDropDown');
dd.innerHTML = '';
cam.propertyData['/presets']?.presets?.forEach(item => {
const name = item.split('.', 1)[0];
const opt = document.createElement('option');
opt.textContent = name;
dd.appendChild(opt);
});
dd.childNodes.forEach(child => {
if (child.nodeName === 'OPTION') {
child.selected = (child.value + '.cset') === cam.propertyData['/presets/active']?.preset;
}
});
}
2024-06-14 19:40:07 -04:00
// Lens
document.getElementById('irisRange').value = cam.propertyData['/lens/iris']?.normalised ?? 0;
document.getElementById('apertureStopsLabel').textContent =
cam.propertyData['/lens/iris']?.apertureStop != null
? 'f/' + cam.propertyData['/lens/iris'].apertureStop.toFixed(1)
: '—';
document.getElementById('zoomRange').value = cam.propertyData['/lens/zoom']?.normalised ?? 0;
document.getElementById('zoomMMLabel').textContent =
cam.propertyData['/lens/zoom']?.focalLength != null
? cam.propertyData['/lens/zoom'].focalLength + 'mm'
: '—mm';
document.getElementById('focusRange').value = cam.propertyData['/lens/focus']?.normalised ?? 0;
// ISO
if (!unsavedChanges.includes('ISO')) {
const iso = cam.propertyData['/video/iso']?.iso;
if (iso != null) {
document.getElementById('ISODisplay').textContent = iso;
document.getElementById('ISOInput').value = iso;
}
}
2024-06-14 19:40:07 -04:00
// Gain
if (!unsavedChanges.includes('Gain')) {
const gain = cam.propertyData['/video/gain']?.gain;
if (gain != null) {
document.getElementById('gainDisplay').textContent = (gain >= 0 ? '+' : '') + gain + 'dB';
}
}
2024-06-14 19:40:07 -04:00
// White balance
if (!unsavedChanges.includes('WB')) {
const wb = cam.propertyData['/video/whiteBalance']?.whiteBalance;
if (wb != null) document.getElementById('whiteBalanceDisplay').textContent = wb + 'K';
}
if (!unsavedChanges.includes('WBT')) {
const wbt = cam.propertyData['/video/whiteBalanceTint']?.whiteBalanceTint;
if (wbt != null) document.getElementById('whiteBalanceTintDisplay').textContent = wbt;
}
2024-07-02 17:53:25 -04:00
// ND filter
if (!unsavedChanges.includes('ND')) {
const nd = cam.propertyData['/video/ndFilter'];
document.getElementById('ndFilterDisplay').textContent = nd ? nd.stop : '0';
2024-06-14 19:40:07 -04:00
}
// Shutter
if (!unsavedChanges.includes('Shutter')) {
const shutter = cam.propertyData['/video/shutter'];
let shutterStr = '—';
if (shutter?.shutterSpeed) {
shutterStr = '1/' + shutter.shutterSpeed;
} else if (shutter?.shutterAngle) {
const angle = (shutter.shutterAngle / 100).toFixed(1);
shutterStr = (angle.endsWith('.0') ? parseFloat(angle).toFixed(0) : angle) + '°';
2024-06-14 19:40:07 -04:00
}
document.getElementById('shutterDisplay').textContent = shutterStr;
}
2024-06-14 19:40:07 -04:00
// AE mode/type
if (!unsavedChanges.includes('AutoExposure')) {
document.getElementById('AEmodeDropDown').value = cam.propertyData['/video/autoExposure']?.mode || 'Off';
document.getElementById('AEtypeDropDown').value = cam.propertyData['/video/autoExposure']?.type || '';
}
2024-07-02 17:53:25 -04:00
// Color correction — Lift
if (!unsavedChanges.includes('CC0')) {
const lift = cam.propertyData['/colorCorrection/lift'];
if (lift) {
document.getElementsByClassName('CClumaLabel')[0].textContent = lift.luma?.toFixed(2);
document.getElementsByClassName('CCredLabel')[0].textContent = lift.red?.toFixed(2);
document.getElementsByClassName('CCgreenLabel')[0].textContent = lift.green?.toFixed(2);
document.getElementsByClassName('CCblueLabel')[0].textContent = lift.blue?.toFixed(2);
}
}
2024-06-14 19:40:07 -04:00
// Gamma
if (!unsavedChanges.includes('CC1')) {
const gamma = cam.propertyData['/colorCorrection/gamma'];
if (gamma) {
document.getElementsByClassName('CClumaLabel')[1].textContent = gamma.luma?.toFixed(2);
document.getElementsByClassName('CCredLabel')[1].textContent = gamma.red?.toFixed(2);
document.getElementsByClassName('CCgreenLabel')[1].textContent = gamma.green?.toFixed(2);
document.getElementsByClassName('CCblueLabel')[1].textContent = gamma.blue?.toFixed(2);
}
}
2024-06-14 19:40:07 -04:00
// Gain CC
if (!unsavedChanges.includes('CC2')) {
const gainCC = cam.propertyData['/colorCorrection/gain'];
if (gainCC) {
document.getElementsByClassName('CClumaLabel')[2].textContent = gainCC.luma?.toFixed(2);
document.getElementsByClassName('CCredLabel')[2].textContent = gainCC.red?.toFixed(2);
document.getElementsByClassName('CCgreenLabel')[2].textContent = gainCC.green?.toFixed(2);
document.getElementsByClassName('CCblueLabel')[2].textContent = gainCC.blue?.toFixed(2);
}
}
2024-06-14 19:40:07 -04:00
// Offset
if (!unsavedChanges.includes('CC3')) {
const offset = cam.propertyData['/colorCorrection/offset'];
if (offset) {
document.getElementsByClassName('CClumaLabel')[3].textContent = offset.luma?.toFixed(2);
document.getElementsByClassName('CCredLabel')[3].textContent = offset.red?.toFixed(2);
document.getElementsByClassName('CCgreenLabel')[3].textContent = offset.green?.toFixed(2);
document.getElementsByClassName('CCblueLabel')[3].textContent = offset.blue?.toFixed(2);
}
}
2024-06-14 19:40:07 -04:00
// Contrast
if (!unsavedChanges.includes('CC4')) {
const contrast = cam.propertyData['/colorCorrection/contrast'];
if (contrast) {
document.getElementById('CCcontrastPivotRange').value = contrast.pivot;
document.getElementById('CCcontrastPivotLabel').textContent = contrast.pivot?.toFixed(2);
document.getElementById('CCcontrastAdjustRange').value = contrast.adjust;
document.getElementById('CCcontrastAdjustLabel').textContent = parseInt(contrast.adjust * 50) + '%';
}
}
2024-06-14 19:40:07 -04:00
// Color / hue / sat
if (!unsavedChanges.includes('CC5')) {
const color = cam.propertyData['/colorCorrection/color'];
if (color) {
document.getElementById('CChueRange').value = color.hue;
document.getElementById('CCcolorHueLabel').textContent = parseInt((color.hue + 1) * 180) + '°';
document.getElementById('CCsaturationRange').value = color.saturation;
document.getElementById('CCcolorSatLabel').textContent = parseInt(color.saturation * 50) + '%';
}
const lc = cam.propertyData['/colorCorrection/lumaContribution'];
if (lc) {
document.getElementById('CClumaContributionRange').value = lc.lumaContribution;
document.getElementById('CCcolorLCLabel').textContent = parseInt(lc.lumaContribution * 100) + '%';
}
}
2024-06-14 19:40:07 -04:00
// Color science (new)
updateColorScienceUI();
// Footer links
document.getElementById('documentationLink').href =
(cam.useHTTPS ? 'https://' : 'http://') + cam.hostname + '/control/documentation.html';
document.getElementById('mediaManagerLink').href =
(cam.useHTTPS ? 'https://' : 'http://') + cam.hostname;
2024-06-14 19:40:07 -04:00
}
// =====================================================================
// Color Science (new)
// =====================================================================
2024-06-14 19:40:07 -04:00
function updateColorScienceUI() {
const cam = cameras[ci];
if (!cam) return;
const cs = cam.propertyData['/video/colorScience'];
if (!cs) return;
2024-06-14 19:40:07 -04:00
const gamma = cs.gamma || '';
const gamut = cs.gamut || '';
2024-06-11 19:46:25 -04:00
document.getElementById('currentGamma').textContent = gamma || '—';
document.getElementById('currentGamut').textContent = gamut || '—';
2024-06-12 20:18:19 -04:00
// Highlight selected gamma button
document.querySelectorAll('#gammaOptions .csOptionBtn').forEach(btn => {
btn.classList.toggle('selected', btn.dataset.gamma === gamma);
});
2024-07-02 17:53:25 -04:00
// Highlight selected gamut button
document.querySelectorAll('#gamutOptions .csOptionBtn').forEach(btn => {
btn.classList.toggle('selected', btn.dataset.gamut === gamut);
});
}
2024-06-12 20:18:19 -04:00
function setGamma(btn) {
if (!cameras[ci]) return;
const gamma = btn.dataset.gamma;
cameras[ci].setColorScience(null, gamma);
// Optimistic UI update
if (!cameras[ci].propertyData['/video/colorScience']) {
cameras[ci].propertyData['/video/colorScience'] = {};
2024-06-12 20:18:19 -04:00
}
cameras[ci].propertyData['/video/colorScience'].gamma = gamma;
updateColorScienceUI();
}
2024-06-12 20:18:19 -04:00
function setGamut(btn) {
if (!cameras[ci]) return;
const gamut = btn.dataset.gamut;
cameras[ci].setColorScience(gamut, null);
if (!cameras[ci].propertyData['/video/colorScience']) {
cameras[ci].propertyData['/video/colorScience'] = {};
2024-07-02 17:53:25 -04:00
}
cameras[ci].propertyData['/video/colorScience'].gamut = gamut;
updateColorScienceUI();
2024-06-12 20:18:19 -04:00
}
const COLOR_SCIENCE_PRESETS = {
'braw-film': { gamut: 'Blackmagic Wide Gamut', gamma: 'Blackmagic Design Film' },
'braw-video': { gamut: 'Blackmagic Design', gamma: 'Blackmagic Design Video' },
'braw-ext': { gamut: 'Blackmagic Design', gamma: 'Blackmagic Design Extended Video' },
'rec709': { gamut: 'Rec.709', gamma: 'Rec709' },
};
function applyColorSciencePreset(presetKey) {
if (!cameras[ci]) return;
const preset = COLOR_SCIENCE_PRESETS[presetKey];
if (!preset) return;
cameras[ci].setColorScience(preset.gamut, preset.gamma);
if (!cameras[ci].propertyData['/video/colorScience']) {
cameras[ci].propertyData['/video/colorScience'] = {};
}
Object.assign(cameras[ci].propertyData['/video/colorScience'], preset);
updateColorScienceUI();
}
2024-06-12 20:18:19 -04:00
// =====================================================================
// Video format (new)
// =====================================================================
2024-06-12 20:18:19 -04:00
function applyVideoFormat() {
if (!cameras[ci]) return;
const codec = document.getElementById('codecSelect').value;
const frameRate = document.getElementById('frameRateSelect').value;
const statusEl = document.getElementById('formatSetStatus');
2024-06-12 20:18:19 -04:00
const data = {};
if (codec) data.codec = codec;
if (frameRate) data.frameRate = frameRate;
2024-06-12 20:18:19 -04:00
if (!Object.keys(data).length) {
statusEl.textContent = 'Select a codec or frame rate first.';
return;
2024-06-12 20:18:19 -04:00
}
cameras[ci].PUTdata('/system/format', data);
statusEl.textContent = 'Sent.';
setTimeout(() => { statusEl.textContent = ''; }, 2000);
2024-06-13 17:46:46 -04:00
}
// =====================================================================
// Exposure controls
// =====================================================================
2024-06-13 17:46:46 -04:00
function adjustISO(delta) {
if (!cameras[ci]) return;
const current = cameras[ci].propertyData['/video/iso']?.iso ?? 800;
cameras[ci].PUTdata('/video/iso', { iso: current + delta });
}
2024-06-13 17:46:46 -04:00
function ISOKeyHandler(event) {
if (event.key === 'Enter') {
event.preventDefault();
const val = parseInt(document.getElementById('ISODisplay').textContent);
if (!isNaN(val)) cameras[ci].PUTdata('/video/iso', { iso: val });
unsavedChanges = unsavedChanges.filter(e => e !== 'ISO');
} else {
unsavedChanges.push('ISO');
2024-06-13 17:46:46 -04:00
}
}
2024-06-13 17:46:46 -04:00
function ISOBlurHandler() {
unsavedChanges = unsavedChanges.filter(e => e !== 'ISO');
2024-06-12 20:18:19 -04:00
}
function ISOInputHandler() {
if (event.key === 'Enter') {
event.preventDefault();
cameras[ci].PUTdata('/video/iso', { iso: parseInt(document.getElementById('ISOInput').value) });
unsavedChanges = unsavedChanges.filter(e => e !== 'ISO');
} else {
unsavedChanges.push('ISO');
}
}
2024-07-02 18:30:59 -04:00
function decreaseND() {
if (!cameras[ci]) return;
cameras[ci].PUTdata('/video/ndFilter', { stop: (cameras[ci].propertyData['/video/ndFilter']?.stop ?? 0) - 2 });
2024-07-02 18:30:59 -04:00
}
function increaseND() {
if (!cameras[ci]) return;
cameras[ci].PUTdata('/video/ndFilter', { stop: (cameras[ci].propertyData['/video/ndFilter']?.stop ?? 0) + 2 });
}
function NDFilterInputHandler() {
if (event.key === 'Enter') {
event.preventDefault();
cameras[ci].PUTdata('/video/ndFilter', { stop: parseInt(document.getElementById('ndFilterDisplay').textContent) });
unsavedChanges = unsavedChanges.filter(e => e !== 'ND');
} else {
unsavedChanges.push('ND');
}
2024-07-02 18:30:59 -04:00
}
function decreaseGain() {
if (!cameras[ci]) return;
cameras[ci].PUTdata('/video/gain', { gain: (cameras[ci].propertyData['/video/gain']?.gain ?? 0) - 2 });
2024-07-02 18:30:59 -04:00
}
function increaseGain() {
if (!cameras[ci]) return;
cameras[ci].PUTdata('/video/gain', { gain: (cameras[ci].propertyData['/video/gain']?.gain ?? 0) + 2 });
2024-07-02 18:30:59 -04:00
}
function GainInputHandler() {
if (event.key === 'Enter') {
event.preventDefault();
cameras[ci].PUTdata('/video/gain', { gain: parseInt(document.getElementById('gainDisplay').textContent) });
unsavedChanges = unsavedChanges.filter(e => e !== 'Gain');
} else {
unsavedChanges.push('Gain');
}
}
2024-07-02 18:30:59 -04:00
function decreaseShutter() {
if (!cameras[ci]) return;
const cam = cameras[ci];
if ('shutterSpeed' in (cam.propertyData['/video/shutter'] ?? {})) {
cam.PUTdata('/video/shutter', { shutterSpeed: cam.propertyData['/video/shutter'].shutterSpeed + 10 });
2024-07-02 18:30:59 -04:00
} else {
cam.PUTdata('/video/shutter', { shutterAngle: cam.propertyData['/video/shutter'].shutterAngle - 1000 });
2024-07-02 18:30:59 -04:00
}
}
function increaseShutter() {
if (!cameras[ci]) return;
const cam = cameras[ci];
if ('shutterSpeed' in (cam.propertyData['/video/shutter'] ?? {})) {
cam.PUTdata('/video/shutter', { shutterSpeed: cam.propertyData['/video/shutter'].shutterSpeed - 10 });
2024-07-02 18:30:59 -04:00
} else {
cam.PUTdata('/video/shutter', { shutterAngle: cam.propertyData['/video/shutter'].shutterAngle + 1000 });
2024-07-02 18:30:59 -04:00
}
}
function handleShutterInput() {
const input = document.getElementById('shutterDisplay').textContent;
if (event.key === 'Enter') {
event.preventDefault();
const cam = cameras[ci];
if ('shutterSpeed' in (cam.propertyData['/video/shutter'] ?? {})) {
const val = input.includes('1/') ? parseInt(input.substring(2)) : parseInt(input);
cam.PUTdata('/video/shutter', { shutterSpeed: val });
2024-07-02 18:30:59 -04:00
} else {
cam.PUTdata('/video/shutter', { shutterAngle: parseInt(parseFloat(input) * 100) });
2024-07-02 18:30:59 -04:00
}
unsavedChanges = unsavedChanges.filter(e => e !== 'Shutter');
2024-07-02 18:30:59 -04:00
} else {
unsavedChanges.push('Shutter');
2024-07-02 18:30:59 -04:00
}
}
function swapWBMode() {
if (WBMode === 0) {
document.getElementById('WBLabel').textContent = 'TINT ↕';
document.getElementById('WBValueContainer').classList.add('hidden');
document.getElementById('WBTintValueContainer').classList.remove('hidden');
WBMode = 1;
} else {
document.getElementById('WBLabel').textContent = 'WB ↕';
document.getElementById('WBValueContainer').classList.remove('hidden');
document.getElementById('WBTintValueContainer').classList.add('hidden');
WBMode = 0;
}
2024-07-02 18:30:59 -04:00
}
function decreaseWhiteBalance() {
if (!cameras[ci]) return;
cameras[ci].PUTdata('/video/whiteBalance', { whiteBalance: (cameras[ci].propertyData['/video/whiteBalance']?.whiteBalance ?? 5600) - 50 });
}
function increaseWhiteBalance() {
if (!cameras[ci]) return;
cameras[ci].PUTdata('/video/whiteBalance', { whiteBalance: (cameras[ci].propertyData['/video/whiteBalance']?.whiteBalance ?? 5600) + 50 });
2024-07-02 18:30:59 -04:00
}
function WBInputHandler() {
2024-07-09 13:02:58 -04:00
if (event.key === 'Enter') {
event.preventDefault();
cameras[ci].PUTdata('/video/whiteBalance', { whiteBalance: parseInt(document.getElementById('whiteBalanceDisplay').textContent) });
unsavedChanges = unsavedChanges.filter(e => e !== 'WB');
2024-07-09 13:02:58 -04:00
} else {
unsavedChanges.push('WB');
2024-07-09 13:02:58 -04:00
}
}
function decreaseWhiteBalanceTint() {
if (!cameras[ci]) return;
cameras[ci].PUTdata('/video/whiteBalanceTint', { whiteBalanceTint: (cameras[ci].propertyData['/video/whiteBalanceTint']?.whiteBalanceTint ?? 0) - 1 });
}
function increaseWhiteBalanceTint() {
if (!cameras[ci]) return;
cameras[ci].PUTdata('/video/whiteBalanceTint', { whiteBalanceTint: (cameras[ci].propertyData['/video/whiteBalanceTint']?.whiteBalanceTint ?? 0) + 1 });
}
function WBTInputHandler() {
if (event.key === 'Enter') {
event.preventDefault();
cameras[ci].PUTdata('/video/whiteBalanceTint', { whiteBalanceTint: parseInt(document.getElementById('whiteBalanceTintDisplay').textContent) });
unsavedChanges = unsavedChanges.filter(e => e !== 'WBT');
} else {
unsavedChanges.push('WBT');
}
}
// =====================================================================
// Color correction
// =====================================================================
function CCInputHandler(which) {
if (event.key === 'Enter') {
event.preventDefault();
setCCFromUI(which);
} else {
unsavedChanges.push('CC' + which);
}
}
function setCCFromUI(which) {
if (which < 4) {
const luma = parseFloat(document.getElementsByClassName('CClumaLabel')[which].textContent);
const red = parseFloat(document.getElementsByClassName('CCredLabel')[which].textContent);
const green = parseFloat(document.getElementsByClassName('CCgreenLabel')[which].textContent);
const blue = parseFloat(document.getElementsByClassName('CCblueLabel')[which].textContent);
const obj = { red, green, blue, luma };
const endpoints = ['/colorCorrection/lift', '/colorCorrection/gamma', '/colorCorrection/gain', '/colorCorrection/offset'];
cameras[ci].PUTdata(endpoints[which], obj);
} else if (which === 4) {
const pivot = parseFloat(document.getElementById('CCcontrastPivotLabel').textContent);
const adjust = parseInt(document.getElementById('CCcontrastAdjustLabel').textContent) / 50.0;
cameras[ci].PUTdata('/colorCorrection/contrast', { pivot, adjust });
} else {
const hue = (parseInt(document.getElementById('CCcolorHueLabel').textContent) / 180.0) - 1.0;
const sat = parseInt(document.getElementById('CCcolorSatLabel').textContent) / 50.0;
const lc = parseInt(document.getElementById('CCcolorLCLabel').textContent) / 100.0;
cameras[ci].PUTdata('/colorCorrection/color', { hue, saturation: sat });
cameras[ci].PUTdata('/colorCorrection/lumaContribution', { lumaContribution: lc });
}
unsavedChanges = unsavedChanges.filter(e => !e.includes('CC' + which));
}
function resetCC(which) {
const resets = [
['/colorCorrection/lift', { red: 0.0, green: 0.0, blue: 0.0, luma: 0.0 }],
['/colorCorrection/gamma', { red: 0.0, green: 0.0, blue: 0.0, luma: 0.0 }],
['/colorCorrection/gain', { red: 1.0, green: 1.0, blue: 1.0, luma: 1.0 }],
['/colorCorrection/offset', { red: 0.0, green: 0.0, blue: 0.0, luma: 0.0 }],
['/colorCorrection/contrast', { pivot: 0.5, adjust: 1.0 }],
];
if (which < 5) {
cameras[ci].PUTdata(resets[which][0], resets[which][1]);
} else {
cameras[ci].PUTdata('/colorCorrection/color', { hue: 0.0, saturation: 1.0 });
cameras[ci].PUTdata('/colorCorrection/lumaContribution', { lumaContribution: 1.0 });
}
unsavedChanges = unsavedChanges.filter(e => !e.includes('CC' + which));
}
// =====================================================================
// Presets / AE / Manual API
// =====================================================================
function presetInputHandler() {
cameras[ci].PUTdata('/presets/active', { preset: document.getElementById('presetsDropDown').value + '.cset' });
unsavedChanges = unsavedChanges.filter(e => e !== 'presets');
2024-07-02 18:30:59 -04:00
}
function AEmodeInputHandler() {
cameras[ci].PUTdata('/video/autoExposure', {
mode: document.getElementById('AEmodeDropDown').value,
type: document.getElementById('AEtypeDropDown').value,
});
unsavedChanges = unsavedChanges.filter(e => e !== 'AutoExposure');
}
function manualAPICall() {
const isGET = document.getElementById('requestTypeGET').checked;
const endpoint = document.getElementById('manualRequestEndpointLabel').value;
let body = '';
try { body = JSON.parse(document.getElementById('manualRequestBodyLabel').value); } catch (_) {}
const response = sendRequest(isGET ? 'GET' : 'PUT', cameras[ci].APIAddress + endpoint, body);
document.getElementById('manualRequestResponseP').textContent = JSON.stringify(response, null, 2);
2024-07-02 18:30:59 -04:00
}
function hostnameInputHandler() {
if (event.key === 'Enter') {
event.preventDefault();
unsavedChanges = unsavedChanges.filter(e => e !== 'Hostname');
initCamera();
} else {
unsavedChanges.push('Hostname');
2024-07-02 18:30:59 -04:00
}
}
function loopHandler(callerString) {
const playbackState = cameras[ci].propertyData['/transports/0/playback'];
if (callerString === 'Loop') {
playbackState.loop = !playbackState.loop;
} else if (callerString === 'Single Clip') {
playbackState.singleClip = !playbackState.singleClip;
}
cameras[ci].PUTdata('/transports/0/playback', playbackState);
}
// =====================================================================
// Helpers
// =====================================================================
2024-07-02 17:53:25 -04:00
function parseTimecode(timecodeBCD) {
if (timecodeBCD == null) return '--:--:--:--';
const noDF = timecodeBCD & 0x7fffffff;
const str = parseInt(noDF.toString(16), 10).toString().padStart(8, '0');
return str.match(/.{1,2}/g).join(':');
}