/* * Copyright 2021 Google LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /** @typedef {import('web-gphoto2').Config} Config */ /** * Depth-first lookup of a config node by its gphoto2 name. * @param {Config | undefined} config * @param {string} name * @returns {Config | undefined} */ export function findConfig(config, name) { if (!config) return undefined; if (config.name === name) return config; if (config.type !== 'window' && config.type !== 'section') return undefined; for (let child of Object.values(config.children)) { let found = findConfig(child, name); if (found) return found; } return undefined; } /** * Read the current value of a config node, if it exists. * @param {Config | undefined} config * @param {string} name */ export function configValue(config, name) { let node = findConfig(config, name); return node && 'value' in node ? node.value : undefined; } /** * The read-only summary shown at the bottom of the home screen. These are the * names the Canon EOS driver (450D included) exposes; anything missing is * simply skipped, so this stays useful on other bodies too. * @param {Config | undefined} config */ export function statusReadout(config) { return [ ['Mode', 'autoexposuremode'], ['Shutter', 'shutterspeed'], ['Aperture', 'aperture'], ['ISO', 'iso'], ['Format', 'imageformat'], ['Battery', 'batterylevel'], ['Shots left', 'availableshots'], ['Target', 'capturetarget'] ] .map(([label, name]) => ({ label, value: configValue(config, name) })) .filter(({ value }) => value !== undefined && value !== ''); } /** * Turn a gphoto2 shutter speed string ("1/250", "30", "0.3", "bulb") into * seconds. Returns undefined when it can't be parsed. * @param {unknown} value */ export function shutterSpeedSeconds(value) { if (typeof value !== 'string') return undefined; let str = value.trim().toLowerCase(); if (str === 'bulb' || str === '') return undefined; let fraction = /^(\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?)$/.exec(str); if (fraction) { let denominator = Number(fraction[2]); return denominator ? Number(fraction[1]) / denominator : undefined; } let seconds = Number(str.replace(/s$/, '')); return Number.isFinite(seconds) ? seconds : undefined; } /** * How the camera can be held open for a long exposure, if at all. * * `bulb` is a plain toggle on many bodies; Canon EOS bodies instead drive the * shutter through `eosremoterelease`. Returns null when neither is available. * @param {Config | undefined} config * @returns {{ kind: 'bulb' } | { kind: 'eosremoterelease', press: string, release: string } | null} */ export function detectBulbSupport(config) { let bulb = findConfig(config, 'bulb'); if (bulb && bulb.type === 'toggle' && !bulb.readonly) { return { kind: 'bulb' }; } let remote = findConfig(config, 'eosremoterelease'); if (remote && remote.type === 'menu' && !remote.readonly) { let press = remote.choices.find(c => /^press full/i.test(c)); let release = remote.choices.find(c => /^release full/i.test(c)); if (press && release) { return { kind: 'eosremoterelease', press, release }; } } return null; }