diff --git a/src/Tethr.ts b/src/Tethr.ts index e7ee1e1..c25265c 100644 --- a/src/Tethr.ts +++ b/src/Tethr.ts @@ -8,6 +8,7 @@ import { ConfigNameList, ConfigType, DriveMode, + ExposureComp, ExposureMeteringMode, ExposureMode, FlashMode, @@ -16,8 +17,12 @@ import { FocusMode, FocusPeaking, FunctionalMode, + ImageAspect, + ImageQuality, + ImageSize, ISO, ManualFocusOption, + ShutterSpeed, WhiteBalance, } from './configs' import {TethrObject} from './TethrObject' @@ -412,18 +417,20 @@ export abstract class Tethr } // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setExposureComp(value: string): Promise { + async setExposureComp(value: ExposureComp): Promise { return UnsupportedOperationResult } async getExposureComp() { return (await this.getExposureCompDesc()).value } - async getExposureCompDesc(): Promise> { + async getExposureCompDesc(): Promise> { return UnsupportedConfigDesc } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setExposureMeteringMode(value: string): Promise { + async setExposureMeteringMode( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + value: ExposureMeteringMode + ): Promise { return UnsupportedOperationResult } async getExposureMeteringMode() { @@ -537,18 +544,18 @@ export abstract class Tethr } // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setImageAspect(value: string): Promise { + async setImageAspect(value: ImageAspect): Promise { return UnsupportedOperationResult } async getImageAspect() { return (await this.getImageAspectDesc()).value } - async getImageAspectDesc(): Promise> { + async getImageAspectDesc(): Promise> { return UnsupportedConfigDesc } // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setImageQuality(value: string): Promise { + async setImageQuality(value: ImageQuality): Promise { return UnsupportedOperationResult } async getImageQuality() { @@ -559,13 +566,13 @@ export abstract class Tethr } // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setImageSize(value: string): Promise { + async setImageSize(value: ImageSize): Promise { return UnsupportedOperationResult } async getImageSize() { return (await this.getImageSizeDesc()).value } - async getImageSizeDesc(): Promise> { + async getImageSizeDesc(): Promise> { return UnsupportedConfigDesc } @@ -671,13 +678,13 @@ export abstract class Tethr } // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setShutterSpeed(value: string): Promise { + async setShutterSpeed(value: ShutterSpeed): Promise { return UnsupportedOperationResult } async getShutterSpeed() { return (await this.getShutterSpeedDesc()).value } - async getShutterSpeedDesc(): Promise> { + async getShutterSpeedDesc(): Promise> { return UnsupportedConfigDesc } @@ -726,18 +733,26 @@ export abstract class Tethr } /** - * Runs auto focus. + * Runs auto focus. Use {@link getCanRunAutoFocus} to check if the camera supports this action. * @category Action */ async runAutoFocus(): Promise { return UnsupportedOperationResult } + /** + * Runs manual focus. Use {@link getCanRunAutoFocus} to check if the camera supports this action. + * @category Action + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars async runManualFocus(option: ManualFocusOption): Promise { return UnsupportedOperationResult } + /** + * Takes a photo. Use {@link getCanTakePhoto} to check if the camera supports this action. + * @category Action + */ async takePhoto( // eslint-disable-next-line @typescript-eslint/no-unused-vars option?: TakePhotoOption @@ -745,12 +760,25 @@ export abstract class Tethr return UnsupportedOperationResult } + /** + * Starts liveview. Use {@link getCanStartLiveview} to check if the camera supports this action. + * @category Action + */ async startLiveview(): Promise> { return UnsupportedOperationResult } + + /** + * Stops liveview. + * @category Action + */ async stopLiveview(): Promise { return UnsupportedOperationResult } + + /** + * Gets a liveview image. + */ async getLiveViewImage(): Promise> { return UnsupportedOperationResult } diff --git a/src/TethrPTPUSB/TethrPTPUSB.ts b/src/TethrPTPUSB/TethrPTPUSB.ts index cf92026..82f4951 100644 --- a/src/TethrPTPUSB/TethrPTPUSB.ts +++ b/src/TethrPTPUSB/TethrPTPUSB.ts @@ -8,6 +8,7 @@ import { DriveModeTable, ExposureMode, ExposureModeTable, + ImageSize, ISO, WhiteBalance, WhiteBalanceTable, @@ -290,7 +291,7 @@ export class TethrPTPUSB extends Tethr { }) } - setImageSizeValue(value: string) { + setImageSize(value: ImageSize) { return this.setDevicePropValue({ devicePropCode: DevicePropCode.ImageSize, datatypeCode: DatatypeCode.String, diff --git a/src/TethrPTPUSB/TethrPanasonic.ts b/src/TethrPTPUSB/TethrPanasonic.ts index 68a1cfe..d1e9ba2 100644 --- a/src/TethrPTPUSB/TethrPanasonic.ts +++ b/src/TethrPTPUSB/TethrPanasonic.ts @@ -3,10 +3,14 @@ import {times} from 'lodash' import { Aperture, + ColorMode, ConfigName, + ExposureComp, ExposureMode, + ImageAspect, ISO, ManualFocusOption, + ShutterSpeed, WhiteBalance, } from '../configs' import {ObjectFormatCode, ResCode} from '../PTPDatacode' @@ -166,7 +170,7 @@ export class TethrPanasonic extends TethrPTPUSB { }) } - setColorModeDesc(value: string) { + setColorMode(value: ColorMode) { return this.setDevicePropValuePanasonic({ devicePropCode: DevicePropCodePanasonic.PhotoStyle_Param, @@ -198,7 +202,7 @@ export class TethrPanasonic extends TethrPTPUSB { }) } - setExposureComp(value: string) { + setExposureComp(value: ExposureComp) { return this.setDevicePropValuePanasonic({ devicePropCode: DevicePropCodePanasonic.Exposure_Param, encode: v => { @@ -258,12 +262,12 @@ export class TethrPanasonic extends TethrPTPUSB { } async getManualFocusOptionsDesc() { - return readonlyConfigDesc([ + return readonlyConfigDesc([ 'near:2', 'near:1', 'far:1', 'far:2', - ] as ManualFocusOption[]) + ]) } async getCanTakePhotoDesc() { @@ -299,10 +303,10 @@ export class TethrPanasonic extends TethrPTPUSB { }) } - setImageAspect(value: string) { + setImageAspect(value: ImageAspect) { return this.setDevicePropValuePanasonic({ devicePropCode: DevicePropCodePanasonic.ImageMode_ImageAspect, - encode: (value: string) => { + encode: (value: ImageAspect) => { return this.imageAspectTable.getKey(value) ?? null }, valueSize: 2, @@ -469,7 +473,7 @@ export class TethrPanasonic extends TethrPTPUSB { } } - setShutterSpeed(value: string) { + setShutterSpeed(value: ShutterSpeed) { return this.setDevicePropValuePanasonic({ devicePropCode: DevicePropCodePanasonic.ShutterSpeed_Param, encode: (value: string) => { @@ -506,9 +510,9 @@ export class TethrPanasonic extends TethrPTPUSB { return null } if ((value & 0x80000000) === 0x00000000) { - return '1/' + value / 1000 + return `1/${value / 1000}` as const } else { - return ((value & 0x7fffffff) / 1000).toString() + return `${(value & 0x7fffffff) / 1000}` as const } }, valueSize: 4, @@ -948,7 +952,7 @@ export class TethrPanasonic extends TethrPTPUSB { [22, 'MY PHOTOSTYLE 4'], ]) - protected imageAspectTable = new BiMap([ + protected imageAspectTable = new BiMap([ [1, '4:3'], [2, '3:2'], [3, '16:9'], diff --git a/src/TethrPTPUSB/TethrRicohTheta.ts b/src/TethrPTPUSB/TethrRicohTheta.ts index ebaac15..b458133 100644 --- a/src/TethrPTPUSB/TethrRicohTheta.ts +++ b/src/TethrPTPUSB/TethrRicohTheta.ts @@ -97,10 +97,10 @@ export class TethrRicohTheta extends TethrPTPUSB { const fraction = Number(num & BigInt(0xffffffff)) if (denominator === 1 || denominator === 10) { - return (fraction / denominator).toString() + return `${fraction / denominator}` as const } - return fraction + '/' + denominator + return `${fraction}/${denominator}` as const }, }) } diff --git a/src/TethrPTPUSB/TethrSigma.ts b/src/TethrPTPUSB/TethrSigma.ts index 51494d0..95cc245 100644 --- a/src/TethrPTPUSB/TethrSigma.ts +++ b/src/TethrPTPUSB/TethrSigma.ts @@ -8,12 +8,13 @@ import {FocalLength} from '..' import { Aperture, BatteryLevel, - computeShutterSpeedSeconds, ConfigName, ExposureMode, FocusMeteringMode, FocusPeaking, + ImageAspect, ISO, + ShutterSpeed, WhiteBalance, } from '../configs' import {decodeIFD, encodeIFD, IFDType} from '../IFD' @@ -29,7 +30,7 @@ import { } from '../Tethr' import {TethrObject} from '../TethrObject' import {TethrStorage} from '../TethrStorage' -import {isntNil} from '../util' +import {computeShutterSpeedSeconds, isntNil} from '../util' import {TethrPTPUSB} from '.' enum OpCodeSigma { @@ -782,14 +783,14 @@ export class TethrSigma extends TethrPTPUSB { return {status: 'ok'} }*/ - async setImageAspect(imageAspect: string): Promise { + async setImageAspect(imageAspect: ImageAspect): Promise { const id = this.imageAspectTable.getKey(imageAspect) if (id === undefined) return {status: 'invalid parameter'} return this.setCamData(OpCodeSigma.SetCamDataGroup5, 3, id) } - async getImageAspectDesc(): Promise> { + async getImageAspectDesc(): Promise> { const {imageAspect} = await this.getCamStatus() const value = this.imageAspectTable.get(imageAspect) ?? null @@ -800,7 +801,7 @@ export class TethrSigma extends TethrPTPUSB { value, option: { type: 'enum', - values: values.map(v => this.imageAspectTableIFD.get(v) ?? 'Unknown'), + values: values.map(v => this.imageAspectTableIFD.get(v)!), }, } } @@ -1033,14 +1034,14 @@ export class TethrSigma extends TethrPTPUSB { ) } - async setShutterSpeed(ss: string): Promise { + async setShutterSpeed(ss: ShutterSpeed): Promise { const byte = this.shutterSpeedOneThirdTable.getKey(ss) if (!byte) return {status: 'invalid parameter'} return this.setCamData(OpCodeSigma.SetCamDataGroup1, 0, byte) } - async getShutterSpeedDesc(): Promise> { + async getShutterSpeedDesc(): Promise> { const {shutterSpeed: range, notApexShutterSpeed} = await this.getCamCanSetInfo5() @@ -1805,7 +1806,7 @@ export class TethrSigma extends TethrPTPUSB { [0x11, 'warm gold'], ]) - private imageAspectTable = new BiMap([ + private imageAspectTable = new BiMap([ [1, '21:9'], [2, '16:9'], [3, '3:2'], @@ -1815,7 +1816,7 @@ export class TethrSigma extends TethrPTPUSB { [7, 'a size'], ]) - private imageAspectTableIFD = new BiMap([ + private imageAspectTableIFD = new BiMap([ [1, '21:9'], [2, '16:9'], [3, '3:2'], @@ -1913,7 +1914,7 @@ export class TethrSigma extends TethrPTPUSB { [0b11111101, '-1/3'], ]) - private shutterSpeedOneThirdTable = new BiMap([ + private shutterSpeedOneThirdTable = new BiMap([ [0b00001000, 'bulb'], [0b00010000, '30'], [0b00010011, '25'], @@ -1985,7 +1986,7 @@ export class TethrSigma extends TethrPTPUSB { [0b10110000, '1/32000'], ]) - private shutterSpeedHalfTable = new BiMap([ + private shutterSpeedHalfTable = new BiMap([ [0b00001000, 'bulb'], [0b00010001, '30'], [0b00010100, '20'], diff --git a/src/configs.ts b/src/configs.ts index a68f9a6..a49c056 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -2,25 +2,52 @@ import {BiMap} from 'bim' import {vec2} from 'linearly' /** - * Aperture value. `'auto'` means auto aperture. + * Aperture value. `'auto'` means auto aperture. The corresponding property in PTP spec is `fNumber`. * @example `'auto', 5.6, 9` + * @category Config */ export type Aperture = 'auto' | number /** * Battery level represented as a number between `0` and `100`. `100` means full battery. * . `'ac'` means AC power, `'low'` means low battery. + * @ccategory Config */ export type BatteryLevel = 'ac' | 'low' | number +/** + * Color mode. + * @category Config + */ +export type ColorMode = string + +/** + * Drive mode. The corresponding property in PTP spec is `stillCaptureMode`. + * @category Config + */ export type DriveMode = 'normal' | 'burst' | 'timelapse' +/** + * Exposure bias compensation. + * @example `'-5', '0', '1 1/3'` + * @category Config + */ +export type ExposureComp = string + +/** + * Exposure metering mode. + * @category Config + */ export type ExposureMeteringMode = | 'average' | 'center-weighted-average' | 'multi-spot' | 'center-spot' +/** + * Exposure program mode. The corresponding property in PTP spec is `exposureProgramMode`. + * @category Config + */ export type ExposureMode = | 'P' | 'A' @@ -33,6 +60,10 @@ export type ExposureMode = | `C${1 | 2 | 3}` | `vendor:${string}` +/** + * Color mode. + * @category Config + */ export type FlashMode = | 'auto' | 'off' @@ -41,8 +72,16 @@ export type FlashMode = | 'red eye fill' | 'external sync' +/** + * Focus mode. + * @category Config + */ export type FocusMode = 'af' | 'mf' +/** + * Functional mode. `'standard'` means normal mode, `'sleep'` means sleep mode. + * @category Config + */ export type FunctionalMode = 'standard' | 'sleep' export type FocusMeteringMode = @@ -51,16 +90,65 @@ export type FocusMeteringMode = | `vendor:${string}` /** - * Focus peaking mode. `false` means disabled. + * Focus peaking mode. This usually corresponds to the color of the peaking. `false` means disabled. + * @example `false, 'red', 'white'` + * @category Config */ export type FocusPeaking = false | string +/** + * Focal length in millimeters. Note that it is not 35mm equivalent focal length. + * @category Config + */ export type FocalLength = number | 'spherical' +/** + * Image aspect. + * @example `'3:2', '16:9', 'a size'` + * @category Config + */ +export type ImageAspect = `${number}:${number}` | 'a size' + +/** + * Image quality. + * @category Config + */ +export type ImageQuality = string + +/** + * Image size. + * @example `'L', 'M', 'S'` + * @category Config + */ +export type ImageSize = string + +/** + * ISO sensitivity. + * @category Config + */ export type ISO = 'auto' | number +/** + * Manual focus option. + */ export type ManualFocusOption = `${'near' | 'far'}:${1 | 2 | 3}` +/** + * Shutter speed. + * @example `'auto', 'bulb', 'sync', '1/100', '15'` + * @category Config + */ +export type ShutterSpeed = + | 'auto' + | 'bulb' + | 'sync' + | `${number}/${number}` + | `${number}` + +/** + * White balance. + * @category Config + */ export type WhiteBalance = | 'auto' | 'auto cool' @@ -79,7 +167,7 @@ export type WhiteBalance = | `vendor:${string}` export type ConfigType = { - aperture: Aperture // fNumber + aperture: Aperture autoFocusFrameCenter: vec2 autoFocusFrameSize: string batteryLevel: BatteryLevel @@ -90,16 +178,16 @@ export type ConfigType = { canStartLiveview: boolean canTakePhoto: boolean captureDelay: number - colorMode: string - colorTemperature: number // Added + colorMode: ColorMode + colorTemperature: number contrast: number dateTime: Date destinationToSave: string digitalZoom: number - driveMode: DriveMode // stillCaptureMode - exposureComp: string // exposureBiasCompensation + driveMode: DriveMode + exposureComp: ExposureComp exposureMeteringMode: ExposureMeteringMode - exposureMode: ExposureMode // exposureProgramMode + exposureMode: ExposureMode facingMode: string flashMode: FlashMode focalLength: FocalLength @@ -108,19 +196,19 @@ export type ConfigType = { focusMode: FocusMode focusPeaking: FocusPeaking functionalMode: FunctionalMode - imageAspect: string // Added e.g. 16:9, 3:2... - imageQuality: string // Added e.g. JPEG, JPEG+RAW... - imageSize: string // e.g. L, M, S, 1024x768... - iso: ISO // added - liveviewEnabled: boolean // added - liveviewMagnifyRatio: number // added - liveviewSize: string // ad + imageAspect: ImageAspect + imageQuality: ImageQuality + imageSize: ImageSize + iso: ISO + liveviewEnabled: boolean + liveviewMagnifyRatio: number + liveviewSize: string manualFocusOptions: ManualFocusOption[] manufacturer: string model: string serialNumber: string sharpness: number - shutterSpeed: string + shutterSpeed: ShutterSpeed shutterSound: number timelapseInterval: number timelapseNumber: number @@ -252,17 +340,3 @@ export const DriveModeTable = new BiMap([ [0x2, 'burst'], [0x3, 'timelapse'], ]) - -// Utility functions -export function computeShutterSpeedSeconds(ss: string) { - if (ss === 'bulk' || ss === 'sync') { - return Infinity - } - - if (ss.includes('/')) { - const [fraction, denominator] = ss.split('/') - return parseInt(fraction) / parseInt(denominator) - } - - return parseFloat(ss) -} diff --git a/src/util.ts b/src/util.ts index 6e70611..565ae15 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,3 +1,5 @@ +import {ShutterSpeed} from './configs' + export function toHexString( data: number | ArrayBuffer, bytes: number | 'auto' = 'auto' @@ -55,3 +57,16 @@ export function sliceJpegData(buffer: ArrayBuffer): ArrayBuffer { return buffer.slice(start, end) } + +export function computeShutterSpeedSeconds(ss: ShutterSpeed) { + if (ss === 'bulb' || ss === 'sync') { + return Infinity + } + + if (ss.includes('/')) { + const [fraction, denominator] = ss.split('/') + return parseInt(fraction) / parseInt(denominator) + } + + return parseFloat(ss) +}