From 718369ec31e1dbe25c7f733c0782c5addcf817a8 Mon Sep 17 00:00:00 2001 From: "romain.lenzotti" Date: Wed, 8 Aug 2018 10:24:29 +0200 Subject: [PATCH 01/10] feat: Add LiveView - Add class LiveView --- examples/camera-liveview.js | 22 +++++++++++++++++ src/components/Camera.ts | 9 +++++++ src/components/Liveview.ts | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 examples/camera-liveview.js create mode 100644 src/components/Liveview.ts diff --git a/examples/camera-liveview.js b/examples/camera-liveview.js new file mode 100644 index 0000000..e5d78a2 --- /dev/null +++ b/examples/camera-liveview.js @@ -0,0 +1,22 @@ +const { Camera, closeQuietly } = require('../src'); +const path = require('path'); +const camera = new Camera(); + +camera.initialize(); + +if (!camera.isClosed()) { + const liveview = camera.liveview(); + + liveview.on('data', (chunk) => { + process.stdout.write(chunk); + }); + + liveview.start(); + + setTimeout(() => { + + liveview.stop(); + + closeQuietly(camera); + }, 10000); +} diff --git a/src/components/Camera.ts b/src/components/Camera.ts index f08d492..b938382 100644 --- a/src/components/Camera.ts +++ b/src/components/Camera.ts @@ -6,6 +6,7 @@ import {CameraFile} from "./CameraFile"; import {CameraFilePath} from "./CameraFilePath"; import {CameraWidgets} from "./CameraWidgets"; import {Context} from "./Context"; +import {Liveview} from "./Liveview"; import {PointerWrapper} from "./PointerWrapper"; import {PortInfo} from "./PortInfo"; @@ -94,6 +95,14 @@ export class Camera extends PointerWrapper { } } + public liveview(): Liveview { + this.checkNotClosed(); + + // this.ref(); + + return new Liveview(this.pointer); + } + /** * Captures a quick preview image on the camera. * @return camera file, never null. Must be closed afterwards. diff --git a/src/components/Liveview.ts b/src/components/Liveview.ts new file mode 100644 index 0000000..42c1dbb --- /dev/null +++ b/src/components/Liveview.ts @@ -0,0 +1,48 @@ +import * as EventEmitter from "events"; +import {alloc} from "ref"; +import {checkCode, GPhoto2Driver, GPPointerString, PointerToString} from "../driver"; +import {PointerCamera, PointerCameraFile, RefCameraFile} from "../driver/modules"; +import {PointerOf} from "../driver/types"; +import {Context} from "./Context"; + +export class Liveview extends EventEmitter { + private timer: any; + private buffer: PointerOf; + + constructor(private camera: PointerCamera) { + super(); + + this.buffer = alloc(RefCameraFile) as any; + + checkCode(GPhoto2Driver.gp_file_new_from_fd(this.buffer, (process.stdout as any).fd), "gp_file_new_from_fd"); + } + + /** + * + */ + start() { + this.timer = setInterval(() => this.onTick(), 100); + } + + /** + * + */ + stop() { + if (this.timer) { + clearInterval(this.timer); + + checkCode(GPhoto2Driver.gp_file_unref(this.buffer.deref())); + } + } + + private async onTick() { + const mime = GPPointerString(); + const result = GPhoto2Driver.gp_camera_capture_preview(this.camera, this.buffer.deref(), Context.get().pointer); + + checkCode(result); + + GPhoto2Driver.gp_file_get_mime_type(this.buffer.deref(), mime); + + this.emit("data", PointerToString(this.buffer.deref() as any)); + } +} From 925791a4e05e5a84f3599219ceb566b0a29ed37d Mon Sep 17 00:00:00 2001 From: "romain.lenzotti" Date: Fri, 17 Aug 2018 22:16:10 +0200 Subject: [PATCH 02/10] feat: Add options on Liveview method - Options accept fps configuration - Fix PointerWrapper class --- CONTRIBUTING.md | 8 ++- examples/camera-liveview.js | 2 +- package.json | 4 +- src/components/AbilitiesList.ts | 2 +- src/components/Camera.ts | 83 +++++++++++++---------------- src/components/CameraFile.ts | 62 ++++++++++++++++++---- src/components/CameraFileFromFd.ts | 11 ++++ src/components/Garbarge.ts | 5 +- src/components/List.ts | 2 +- src/components/Liveview.ts | 66 +++++++++++++++-------- src/components/PointerWrapper.ts | 84 ++++++++++++++++++++++++++---- src/components/PortInfo.ts | 2 +- src/components/PortInfoList.ts | 2 +- src/components/index.ts | 1 + src/interfaces/ILiveviewOptions.ts | 4 ++ src/interfaces/index.ts | 1 + 16 files changed, 241 insertions(+), 98 deletions(-) create mode 100644 src/components/CameraFileFromFd.ts create mode 100644 src/interfaces/ILiveviewOptions.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0a1fc6..c46e068 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,9 +36,15 @@ If you have any questions, create an [issue](https://github.com/Romakita/tsed/bl Clone your fork of the repository ```bash -$ git clone https://github.com/YOUR_USERNAME/ts-express-decorators.git +$ git clone https://github.com/YOUR_USERNAME/ts-gphoto2-driver.git ``` +Or if you have access to this repository: + +```bash + $ git checkout https://github.com/TypedProject/ts-gphoto2-driver.git + ``` + Install npm dependencies ```bash diff --git a/examples/camera-liveview.js b/examples/camera-liveview.js index e5d78a2..d58bd51 100644 --- a/examples/camera-liveview.js +++ b/examples/camera-liveview.js @@ -8,7 +8,7 @@ if (!camera.isClosed()) { const liveview = camera.liveview(); liveview.on('data', (chunk) => { - process.stdout.write(chunk); + console.log(chunk); }); liveview.start(); diff --git a/package.json b/package.json index e92f420..e8ba73b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "prettier": "prettier '{src,test}/**/*.ts' --write" }, "dependencies": { - "ffi-napi": "2.4.3", + "ffi-napi": "^2.4.3", "ref": "^1.3.3", "ref-array": "^1.2.0", "ref-struct": "^1.1.0", @@ -74,4 +74,4 @@ "release": { "branch": "production" } -} \ No newline at end of file +} diff --git a/src/components/AbilitiesList.ts b/src/components/AbilitiesList.ts index 7c11f22..b2ba584 100644 --- a/src/components/AbilitiesList.ts +++ b/src/components/AbilitiesList.ts @@ -7,7 +7,7 @@ import {PortInfoList} from "./PortInfoList"; export class AbilitiesList extends PointerWrapper { constructor() { - super("gp_abilities_list", RefAbilitiesList); + super({method: "gp_abilities_list", refType: RefAbilitiesList}); } get size(): number { diff --git a/src/components/Camera.ts b/src/components/Camera.ts index b938382..23d9bda 100644 --- a/src/components/Camera.ts +++ b/src/components/Camera.ts @@ -1,6 +1,6 @@ -import {checkCode, GPCameraCaptureType, GPhoto2Driver} from "../driver"; +import {GPCameraCaptureType, GPCodes} from "../driver"; import {PointerCamera, RefCamera, StructCameraText} from "../driver/modules"; -import {GPCodes} from "../driver/types"; +import {ILiveviewOptions} from "../interfaces"; import {CameraAbilities} from "./CameraAbilities"; import {CameraFile} from "./CameraFile"; import {CameraFilePath} from "./CameraFilePath"; @@ -16,7 +16,7 @@ export class Camera extends PointerWrapper { private _widgets: CameraWidgets; constructor() { - super("gp_camera", RefCamera); + super({method: "gp_camera", refType: RefCamera}); } get widgets(): CameraWidgets { @@ -39,7 +39,8 @@ export class Camera extends PointerWrapper { this.setPortInfo(portInfo); } - checkCode(GPhoto2Driver.gp_camera_init(this.pointer, Context.get().pointer)); + this.call("init", Context.get().pointer); + this.initialized = true; } } @@ -68,7 +69,7 @@ export class Camera extends PointerWrapper { this.checkNotClosed(); if (this.isInitialized()) { this.initialized = false; - checkCode(GPhoto2Driver.gp_camera_exit(this.pointer, Context.get().pointer)); + this.call("exit", Context.get().pointer); } } @@ -95,39 +96,35 @@ export class Camera extends PointerWrapper { } } - public liveview(): Liveview { + public liveview(options: ILiveviewOptions): Liveview { this.checkNotClosed(); - // this.ref(); - - return new Liveview(this.pointer); + return new Liveview(this, options); } /** * Captures a quick preview image on the camera. * @return camera file, never null. Must be closed afterwards. */ - public capturePreview(path?: string): CameraFile | undefined { + public capturePreview(path?: string, file: CameraFile = new CameraFile()): CameraFile | undefined { this.checkNotClosed(); - const cFile = new CameraFile(); try { - const result = GPhoto2Driver.gp_camera_capture_preview(this.pointer, cFile.pointer, Context.get().pointer); - checkCode(result, "gp_camera_capture_preview"); + this.call("capture_preview", file.pointer, Context.get().pointer); if (path) { try { - cFile.save(path); + file.save(path); } finally { this.deinitialize(); this.initialize(); - cFile.closeQuietly(); + file.closeQuietly(); } } - return cFile; + return file; } catch (er) { - cFile.closeQuietly(); + file.closeQuietly(); throw er; } } @@ -136,27 +133,25 @@ export class Camera extends PointerWrapper { * Captures a quick preview image on the camera. * @return camera file, never null. Must be closed afterwards. */ - public async capturePreviewAsync(path?: string): Promise { + public async capturePreviewAsync(path?: string, file: CameraFile = new CameraFile()): Promise { this.checkNotClosed(); - const cFile = new CameraFile(); try { - const result = await GPhoto2Driver.gp_camera_capture_preview_async(this.pointer, cFile.pointer, Context.get().pointer); - checkCode(result, "gp_camera_capture_preview_async"); + await this.callAsync("capture_preview", file.pointer, Context.get().pointer); if (path) { try { - await cFile.saveAsync(path); + await file.saveAsync(path); } finally { this.deinitialize(); this.initialize(); - cFile.closeQuietly(); + file.closeQuietly(); } } - return cFile; + return file; } catch (er) { - cFile.closeQuietly(); + file.closeQuietly(); throw er; } } @@ -222,9 +217,8 @@ export class Camera extends PointerWrapper { protected capture(type: GPCameraCaptureType, path?: string): CameraFile | undefined { this.checkNotClosed(); const cFilePath = new CameraFilePath(); - const result = GPhoto2Driver.gp_camera_capture(this.pointer, type, cFilePath.buffer, Context.get().pointer); - checkCode(result, "gp_camera_capture"); + this.call("capture", type, cFilePath.buffer, Context.get().pointer); const cFile = cFilePath.newFile(this.pointer); @@ -252,9 +246,7 @@ export class Camera extends PointerWrapper { this.checkNotClosed(); const cFilePath = new CameraFilePath(); - const result = await GPhoto2Driver.gp_camera_capture_async(this.pointer, type, cFilePath.buffer, Context.get().pointer); - - checkCode(result, "gp_camera_capture_async"); + await this.callAsync("capture", type, cFilePath.buffer, Context.get().pointer); const cFile = await cFilePath.newFileAsync(this.pointer); @@ -278,9 +270,7 @@ export class Camera extends PointerWrapper { public triggerCapture(): GPCodes { this.checkNotClosed(); - const result = GPhoto2Driver.gp_camera_trigger_capture(this.pointer, Context.get().pointer); - - checkCode(result, "gp_camera_trigger_capture"); + const result = this.call("trigger_capture", Context.get().pointer); this.deinitialize(); this.initialize(); @@ -294,9 +284,7 @@ export class Camera extends PointerWrapper { public async triggerCaptureAsync(): Promise { this.checkNotClosed(); - const result = await GPhoto2Driver.gp_camera_trigger_capture_async(this.pointer, Context.get().pointer); - - checkCode(result, "gp_camera_trigger_capture_async"); + const result = await this.callAsync("trigger_capture", Context.get().pointer); this.deinitialize(); this.initialize(); @@ -310,7 +298,7 @@ export class Camera extends PointerWrapper { public getAbilities() { const abilities = new CameraAbilities(); - checkCode(GPhoto2Driver.gp_camera_get_abilities(this.pointer, abilities.buffer), "gp_camera_get_abilities"); + this.call("get_abilities", abilities.buffer); return abilities; } @@ -323,7 +311,7 @@ export class Camera extends PointerWrapper { const struct = new StructCameraText(); const buffer = struct.ref(); - checkCode(GPhoto2Driver.gp_camera_get_summary(this.pointer, buffer, Context.get().pointer), "gp_camera_get_summary"); + this.call("get_summary", buffer, Context.get().pointer); return struct.text.buffer.readCString(0); } @@ -336,7 +324,7 @@ export class Camera extends PointerWrapper { const struct = new StructCameraText(); const buffer = struct.ref(); - checkCode(GPhoto2Driver.gp_camera_get_about(this.pointer, buffer, Context.get().pointer), "gp_camera_get_about"); + this.call("get_about", buffer, Context.get().pointer); return struct.text.buffer.readCString(0); } @@ -349,7 +337,7 @@ export class Camera extends PointerWrapper { const struct = new StructCameraText(); const buffer = struct.ref(); - checkCode(GPhoto2Driver.gp_camera_get_manual(this.pointer, buffer, Context.get().pointer), "gp_camera_get_manual"); + this.call("get_manual", buffer, Context.get().pointer); return struct.text.buffer.readCString(0); } @@ -357,26 +345,29 @@ export class Camera extends PointerWrapper { /** * */ - public ref(): void { + public ref(): GPCodes { this.checkNotClosed(); - checkCode(GPhoto2Driver.gp_camera_ref(this.pointer)); + + return this.call("ref"); } /** * */ - public unref(): void { + public unref(): GPCodes { this.checkNotClosed(); - checkCode(GPhoto2Driver.gp_camera_unref(this.pointer)); + + return this.call("unref"); } /** * * @param {PointerPortInfo} portInfo */ - public setPortInfo(portInfo: PortInfo): void { + public setPortInfo(portInfo: PortInfo): GPCodes { this.checkNotClosed(); - checkCode(GPhoto2Driver.gp_camera_set_port_info(this.pointer, portInfo.pointer)); + + return this.call("set_port_info", portInfo.pointer); } toString(): string { diff --git a/src/components/CameraFile.ts b/src/components/CameraFile.ts index ceef5dc..057e2f9 100644 --- a/src/components/CameraFile.ts +++ b/src/components/CameraFile.ts @@ -1,15 +1,25 @@ -import {checkCode, GPhoto2Driver} from "../driver"; import {PointerCameraFile, RefCameraFile} from "../driver/modules"; -import {GPCodes} from "../driver/types"; -import {PointerWrapper} from "./PointerWrapper"; +import {GPCodes, PointerOf} from "../driver/types"; +import {IPointerWrapperOptions, PointerWrapper} from "./PointerWrapper"; export class CameraFile extends PointerWrapper { - constructor() { - super("gp_file", RefCameraFile); + constructor(options: Partial = {}, ...args: any[]) { + super( + { + ...options, + method: "gp_file", + refType: RefCameraFile + }, + ...args + ); } public clean(): void { - checkCode(GPhoto2Driver.gp_file_clean(this.pointer)); + return this.call("clean"); + } + + public free(): void { + return this.call("free"); } /** @@ -17,7 +27,7 @@ export class CameraFile extends PointerWrapper { * @param filename OS-dependent path on the local file system. */ public save(filename: string): GPCodes { - return checkCode(GPhoto2Driver.gp_file_save(this.pointer, filename), "gp_file_save"); + return this.call("save", filename); } /** @@ -26,20 +36,50 @@ export class CameraFile extends PointerWrapper { * @returns {Promise} */ public async saveAsync(filename: string): Promise { - return checkCode(GPhoto2Driver.gp_file_save_async(this.pointer, filename), "gp_file_save_async"); + return this.callAsync("save", filename); } /** * */ public ref() { - checkCode(GPhoto2Driver.gp_file_ref(this.pointer)); + return this.call("ref"); + } + + /** + * + */ + public unref(): GPCodes { + return this.call("unref"); } /** * + * @param {PointerOf} mime + * @returns {GPCodes} */ - public unref() { - checkCode(GPhoto2Driver.gp_file_unref(this.pointer)); + public getMimeType(mime: PointerOf): GPCodes { + return this.call("get_mime_type", mime); + } + + /** + * Get a pointer to the data and the file's size. + * + * @param data + * @param size + * @return a gphoto2 error code. + * + * Both data and size can be NULL and will then be ignored. + * + * For regular CameraFiles, the pointer to data that is returned is + * still owned by libgphoto2 and its lifetime is the same as the #file. + * + * For filedescriptor or handler based CameraFile types, the returned + * data pointer is owned by the caller and needs to be free()d to avoid + * memory leaks. + * + **/ + public getDataAndSize(data: PointerOf, size: PointerOf): GPCodes { + return this.call("get_data_and_size", data, size); } } diff --git a/src/components/CameraFileFromFd.ts b/src/components/CameraFileFromFd.ts new file mode 100644 index 0000000..49c7204 --- /dev/null +++ b/src/components/CameraFileFromFd.ts @@ -0,0 +1,11 @@ +import {CameraFile} from "./CameraFile"; + +export class CameraFileFromFd extends CameraFile { + constructor(fd: number) { + super({openMethod: "new_from_fd"}, fd); + } + + close() { + return this; + } +} diff --git a/src/components/Garbarge.ts b/src/components/Garbarge.ts index f4669b3..41e0591 100644 --- a/src/components/Garbarge.ts +++ b/src/components/Garbarge.ts @@ -18,7 +18,9 @@ export function closeAll() { console.debug("Close " + garbage.size + " references unclosed"); } garbage.forEach((item: ICloseable) => { - item.close(); + try { + item.close(); + } catch (er) {} }); } } @@ -32,3 +34,4 @@ process.on("SIGINT", closeAll); // catches "kill pid" (for example: nodemon restart) process.on("SIGUSR1", closeAll); process.on("SIGUSR2", closeAll); +process.on("SIGSEGV", closeAll); diff --git a/src/components/List.ts b/src/components/List.ts index 77b3074..aa73c06 100644 --- a/src/components/List.ts +++ b/src/components/List.ts @@ -4,7 +4,7 @@ import {PointerWrapper} from "./PointerWrapper"; export class List extends PointerWrapper { constructor() { - super("gp_list", RefList); + super({method: "gp_list", refType: RefList}); } /** diff --git a/src/components/Liveview.ts b/src/components/Liveview.ts index 42c1dbb..318021b 100644 --- a/src/components/Liveview.ts +++ b/src/components/Liveview.ts @@ -1,48 +1,70 @@ import * as EventEmitter from "events"; -import {alloc} from "ref"; -import {checkCode, GPhoto2Driver, GPPointerString, PointerToString} from "../driver"; -import {PointerCamera, PointerCameraFile, RefCameraFile} from "../driver/modules"; -import {PointerOf} from "../driver/types"; +import * as fs from "fs"; +import * as path from "path"; +import {PointerCamera} from "../driver/modules"; +import {ICloseable, ILiveviewOptions} from "../interfaces"; +import {CameraFileFromFd} from "./CameraFileFromFd"; import {Context} from "./Context"; +import {addInstance} from "./Garbarge"; +import {PointerWrapper} from "./PointerWrapper"; -export class Liveview extends EventEmitter { +const TMP_PATH = path.join(__dirname, "../../.tmp/liveview.jpg"); + +export class Liveview extends EventEmitter implements ICloseable { + /** + * + */ private timer: any; - private buffer: PointerOf; + /** + * + */ + private file: CameraFileFromFd; + /** + * + */ + private fd: number; - constructor(private camera: PointerCamera) { + constructor(private camera: PointerWrapper, private options: Partial = {fps: 100}) { super(); - - this.buffer = alloc(RefCameraFile) as any; - - checkCode(GPhoto2Driver.gp_file_new_from_fd(this.buffer, (process.stdout as any).fd), "gp_file_new_from_fd"); + addInstance(this); } /** * */ - start() { - this.timer = setInterval(() => this.onTick(), 100); + public start() { + if (this.options.stdout) { + this.fd = (process.stdout as any).fd; + } else { + this.fd = fs.openSync(TMP_PATH, "w+", 0o666); + } + + this.file = new CameraFileFromFd(this.fd); + + this.timer = setInterval(() => this.onTick(), this.options.fps); } /** * */ - stop() { + public stop() { if (this.timer) { clearInterval(this.timer); - - checkCode(GPhoto2Driver.gp_file_unref(this.buffer.deref())); + this.file.unref(); } } - private async onTick() { - const mime = GPPointerString(); - const result = GPhoto2Driver.gp_camera_capture_preview(this.camera, this.buffer.deref(), Context.get().pointer); + public close() { + this.stop(); - checkCode(result); + return this; + } - GPhoto2Driver.gp_file_get_mime_type(this.buffer.deref(), mime); + private async onTick() { + await this.camera.callAsync("capture_preview", this.file.pointer, Context.get().pointer); - this.emit("data", PointerToString(this.buffer.deref() as any)); + fs.readFile(TMP_PATH, {encoding: "binary"}, (err, data) => { + this.emit("data", data); + }); } } diff --git a/src/components/PointerWrapper.ts b/src/components/PointerWrapper.ts index a77df8e..06420d1 100644 --- a/src/components/PointerWrapper.ts +++ b/src/components/PointerWrapper.ts @@ -1,26 +1,59 @@ import {alloc, Type} from "ref"; -import {checkCode, closeQuietly, GPhoto2Driver} from "../driver"; +import {checkCode, closeQuietly, GPCodes, GPhoto2Driver} from "../driver"; import {PointerOf} from "../driver/types"; import {ICloseable} from "../interfaces"; import {addInstance, removeInstance} from "./Garbarge"; +export interface IPointerWrapperOptions { + method: string; + refType: Type; + openMethod?: string; + closeMethod?: string; +} + export class PointerWrapper

> implements ICloseable { - readonly buffer: PointerOf

; + private _buffer: PointerOf

; - constructor(private gpMethodType: string, refType: Type) { - this.buffer = alloc(refType) as any; + constructor(private options: IPointerWrapperOptions, ...args: any[]) { + this.new(...args); + } - checkCode((GPhoto2Driver as any)[`${gpMethodType}_new`](this.buffer), `${gpMethodType}_new`); + get buffer(): PointerOf

{ + return this._buffer; + } - addInstance(this); + get byRef(): PointerOf

{ + return this._buffer; } get pointer(): P { - return this.buffer.deref(); + return this._buffer.deref(); + } + + private getOptions() { + const {method, refType, openMethod = "new", closeMethod = "free"} = this.options; + + return { + method, + refType, + openMethod, + closeMethod + }; + } + + protected new(...args: any[]): GPCodes { + const {openMethod, refType} = this.getOptions(); + + addInstance(this); + this._buffer = alloc(refType) as any; + + return this.callByRef(openMethod!, ...args); } close(): this { - this.call("free"); + const {closeMethod} = this.getOptions(); + + this.call(closeMethod); removeInstance(this); return this; @@ -30,13 +63,44 @@ export class PointerWrapper

> implements ICloseable { closeQuietly(this); } + callByRef(key: string, ...args: any[]) { + let {method} = this.getOptions(); + method = `${method}_${key}`; + + if ((GPhoto2Driver as any)[method]) { + return checkCode((GPhoto2Driver as any)[method](this.byRef, ...args), method); + } + + if (process.env.NODE_ENV !== "production") { + console.warn(`${method} on GPhoto2Driver doesn't exists`); + } + } + call(key: string, ...args: any[]) { - const method = `${this.gpMethodType}_${key}`; + let {method} = this.getOptions(); + method = `${method}_${key}`; if ((GPhoto2Driver as any)[method]) { return checkCode((GPhoto2Driver as any)[method](this.pointer, ...args), method); } - throw new Error(method + " on GPhoto2Driver doesn't exists"); + if (process.env.NODE_ENV !== "production") { + console.warn(`${method} on GPhoto2Driver doesn't exists`); + } + } + + async callAsync(key: string, ...args: any[]) { + let {method} = this.getOptions(); + method = `${method}_${key}`; + + if ((GPhoto2Driver as any)[method]) { + const result = await (GPhoto2Driver as any)[method](this.pointer, ...args); + + return checkCode(result, method); + } + + if (process.env.NODE_ENV !== "production") { + console.warn(`${method} on GPhoto2Driver doesn't exists`); + } } } diff --git a/src/components/PortInfo.ts b/src/components/PortInfo.ts index c1f5540..6a7d8a2 100644 --- a/src/components/PortInfo.ts +++ b/src/components/PortInfo.ts @@ -5,7 +5,7 @@ import {PointerWrapper} from "./PointerWrapper"; export class PortInfo extends PointerWrapper { constructor() { - super("gp_port_info", RefPortInfo); + super({method: "gp_port_info", refType: RefPortInfo}); } get name() { diff --git a/src/components/PortInfoList.ts b/src/components/PortInfoList.ts index cd448e9..87e64cb 100644 --- a/src/components/PortInfoList.ts +++ b/src/components/PortInfoList.ts @@ -7,7 +7,7 @@ export class PortInfoList extends PointerWrapper { private map: Map = new Map(); constructor() { - super("gp_port_info_list", RefPortInfoList); + super({method: "gp_port_info_list", refType: RefPortInfoList}); } get size(): number { diff --git a/src/components/index.ts b/src/components/index.ts index 9d0cb10..79dd6ee 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,6 +1,7 @@ export * from "./AbilitiesList"; export * from "./Camera"; export * from "./CameraFile"; +export * from "./CameraFileFromFd"; export * from "./CameraFilePath"; export * from "./CameraList"; export * from "./CameraWidgets"; diff --git a/src/interfaces/ILiveviewOptions.ts b/src/interfaces/ILiveviewOptions.ts new file mode 100644 index 0000000..2fbdf83 --- /dev/null +++ b/src/interfaces/ILiveviewOptions.ts @@ -0,0 +1,4 @@ +export interface ILiveviewOptions { + fps: number; + stdout: boolean; +} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 5ec8331..96451c2 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,3 +1,4 @@ export * from "./ICamera"; export * from "./ICloseable"; +export * from "./ILiveviewOptions"; export * from "./IWidget"; From 82dbc51c39e21d181a33380f438cffbabd2c3018 Mon Sep 17 00:00:00 2001 From: goooseman Date: Tue, 21 Aug 2018 11:07:51 +0200 Subject: [PATCH 03/10] feat: Add getDataAndSize and update getMimeType - gp_file_get_data_and_size driver module - getDataAndSize CameraFile method - getMimeType in CameraFile should return the string --- src/components/CameraFile.ts | 40 +++++++++++++++++++++++++++--- src/driver/modules/GPFileModule.ts | 3 ++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/components/CameraFile.ts b/src/components/CameraFile.ts index 057e2f9..467c18e 100644 --- a/src/components/CameraFile.ts +++ b/src/components/CameraFile.ts @@ -1,5 +1,6 @@ import {PointerCameraFile, RefCameraFile} from "../driver/modules"; import {GPCodes, PointerOf} from "../driver/types"; +import {GPPointer, GPPointerString, checkCode} from "../driver"; import {IPointerWrapperOptions, PointerWrapper} from "./PointerWrapper"; export class CameraFile extends PointerWrapper { @@ -58,8 +59,12 @@ export class CameraFile extends PointerWrapper { * @param {PointerOf} mime * @returns {GPCodes} */ - public getMimeType(mime: PointerOf): GPCodes { - return this.call("get_mime_type", mime); + public async getMimeType(mime: PointerOf): Promise { + const mimePointer = GPPointerString(); + const code = await this.call("get_mime_type", mime); + checkCode(code); + + return mimePointer.deref(); } /** @@ -79,7 +84,34 @@ export class CameraFile extends PointerWrapper { * memory leaks. * **/ - public getDataAndSize(data: PointerOf, size: PointerOf): GPCodes { - return this.call("get_data_and_size", data, size); + + public async getDataAndSize( + encoding: "binary" | "base64" = "binary" + ): Promise<{ + data: Buffer | string; + size: Number; + }> { + const dataPointer = GPPointerString(); + const sizePointer: PointerOf = GPPointer("int"); + const code = await this.callAsync("get_data_and_size", dataPointer, sizePointer); + const size = sizePointer.deref(); + const binary = Buffer.from(dataPointer.deref(), "binary"); + checkCode(code); + let data: Buffer | string = ""; + switch (encoding) { + case "binary": + data = binary; + + break; + case "base64": + data = binary.toString("base64"); + + break; + } + + return { + data, + size + }; } } diff --git a/src/driver/modules/GPFileModule.ts b/src/driver/modules/GPFileModule.ts index cf6877a..0876130 100644 --- a/src/driver/modules/GPFileModule.ts +++ b/src/driver/modules/GPFileModule.ts @@ -13,7 +13,8 @@ export const GPFileModuleDescription = { gp_file_clean: ["int", [RefCameraFile]], gp_file_save: ["int", [RefCameraFile, types.CString]], gp_file_unref: ["int", [RefCameraFile]], - gp_file_free: ["int", [RefCameraFile]] + gp_file_free: ["int", [RefCameraFile]], + gp_file_get_data_and_size: ["int", [RefCameraFile, refType(types.CString), refType(types.int)]] }; export interface IGPFileModule { From 53ed35cf82bf94c90c6135d4c528c11a27cdd187 Mon Sep 17 00:00:00 2001 From: goooseman Date: Tue, 21 Aug 2018 15:06:02 +0200 Subject: [PATCH 04/10] feat: Liveview support binary, base64 and file output options - added examples for liveview, - added comments to new examples, - added .tmp to .gitignore; - change timeout in liveview examples --- .gitignore | 3 +- examples/camera-liveview-base64.js | 32 +++++++++++++++++++++ examples/camera-liveview-binary.js | 31 ++++++++++++++++++++ examples/camera-liveview-file.js | 30 ++++++++++++++++++++ examples/camera-liveview.js | 22 --------------- src/components/CameraFile.ts | 3 +- src/components/Liveview.ts | 45 ++++++++++++++++-------------- src/interfaces/ILiveviewOptions.ts | 3 +- 8 files changed, 123 insertions(+), 46 deletions(-) create mode 100644 examples/camera-liveview-base64.js create mode 100644 examples/camera-liveview-binary.js create mode 100644 examples/camera-liveview-file.js delete mode 100644 examples/camera-liveview.js diff --git a/.gitignore b/.gitignore index 0eab9be..4f3215c 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,5 @@ test/spec/*.js.map /.nyc_output/ /test/integration/app/spec/swagger.json -/examples/*.jpeg \ No newline at end of file +/examples/*.jpeg +.tmp \ No newline at end of file diff --git a/examples/camera-liveview-base64.js b/examples/camera-liveview-base64.js new file mode 100644 index 0000000..0b5771a --- /dev/null +++ b/examples/camera-liveview-base64.js @@ -0,0 +1,32 @@ +// This example illustrates the usecase, where you need to capture liveview from the camera +// and get live output of each frame in base64 + +const { Camera } = require('../src'); +// If you launch this example not from library folder, change the previous two lines to: +// const { Camera } = require('@typedproject/gphoto2-driver'); +const camera = new Camera(); + +const NUMBER_OF_SECONDS_TO_LISTEN = 10; + +camera.initialize(); + +if (!camera.isClosed()) { + const liveview = camera.liveview({ + output: "base64", + fps: 24, // Number of frames per second. Default is 24. + }); + + liveview.start(); + + liveview.on("data", (data, size) => { + console.log("base64 of the frame", data); + console.log("Size of the frame", size); + }); + + setTimeout(() => { + + liveview.stop(); + + camera.closeQuietly(); + }, NUMBER_OF_SECONDS_TO_LISTEN * 1000); +} diff --git a/examples/camera-liveview-binary.js b/examples/camera-liveview-binary.js new file mode 100644 index 0000000..c9a472b --- /dev/null +++ b/examples/camera-liveview-binary.js @@ -0,0 +1,31 @@ +// This example illustrates the usecase, where you need to capture liveview from the camera +// and get live output of each frame as Buffer object (binary data) + +const { Camera } = require('../src'); +// If you launch this example not from library folder, change the previous two lines to: +// const { Camera } = require('@typedproject/gphoto2-driver'); +const camera = new Camera(); + +const NUMBER_OF_SECONDS_TO_LISTEN = 10; + +camera.initialize(); + +if (!camera.isClosed()) { + const liveview = camera.liveview({ + output: "binary", + fps: 24, // Number of frames per second. Default is 24. + }); + + liveview.start(); + + liveview.on("data", (data, size) => { + process.stdout.write(data); // We can not use console.log for binary, becayse it puts \n after each line + }); + + setTimeout(() => { + + liveview.stop(); + + camera.closeQuietly(); + }, NUMBER_OF_SECONDS_TO_LISTEN * 1000); +} diff --git a/examples/camera-liveview-file.js b/examples/camera-liveview-file.js new file mode 100644 index 0000000..5403ea8 --- /dev/null +++ b/examples/camera-liveview-file.js @@ -0,0 +1,30 @@ +// This example illustrates the usecase, where you need to capture liveview from the camera +// and then save it to .mjpg file + +const { Camera } = require('../src'); +// If you launch this example not from library folder, change the previous two lines to: +// const { Camera } = require('@typedproject/gphoto2-driver'); +const path = require('path'); +const camera = new Camera(); + +const NUMBER_OF_SECONDS_TO_WRITE = 10; +const PATH_TO_SAVE = path.join(__dirname, '../.tmp/live.mjpg'); // Make sure that this folder exists + +camera.initialize(); + +if (!camera.isClosed()) { + const liveview = camera.liveview({ + output: "file", + fps: 24, // Number of frames per second. Default is 24. + filePath: PATH_TO_SAVE, + }); + + liveview.start(); + + setTimeout(() => { + + liveview.stop(); + + camera.closeQuietly(); + }, NUMBER_OF_SECONDS_TO_WRITE * 1000); +} diff --git a/examples/camera-liveview.js b/examples/camera-liveview.js deleted file mode 100644 index d58bd51..0000000 --- a/examples/camera-liveview.js +++ /dev/null @@ -1,22 +0,0 @@ -const { Camera, closeQuietly } = require('../src'); -const path = require('path'); -const camera = new Camera(); - -camera.initialize(); - -if (!camera.isClosed()) { - const liveview = camera.liveview(); - - liveview.on('data', (chunk) => { - console.log(chunk); - }); - - liveview.start(); - - setTimeout(() => { - - liveview.stop(); - - closeQuietly(camera); - }, 10000); -} diff --git a/src/components/CameraFile.ts b/src/components/CameraFile.ts index 467c18e..36034b1 100644 --- a/src/components/CameraFile.ts +++ b/src/components/CameraFile.ts @@ -89,7 +89,7 @@ export class CameraFile extends PointerWrapper { encoding: "binary" | "base64" = "binary" ): Promise<{ data: Buffer | string; - size: Number; + size: number; }> { const dataPointer = GPPointerString(); const sizePointer: PointerOf = GPPointer("int"); @@ -108,6 +108,7 @@ export class CameraFile extends PointerWrapper { break; } + // TODO now it does not work. I just see �� in the console for binary or /f39/Q== for Base64 return { data, diff --git a/src/components/Liveview.ts b/src/components/Liveview.ts index 318021b..af36f65 100644 --- a/src/components/Liveview.ts +++ b/src/components/Liveview.ts @@ -1,15 +1,11 @@ import * as EventEmitter from "events"; -import * as fs from "fs"; -import * as path from "path"; import {PointerCamera} from "../driver/modules"; import {ICloseable, ILiveviewOptions} from "../interfaces"; -import {CameraFileFromFd} from "./CameraFileFromFd"; +import {CameraFile} from "./CameraFile"; import {Context} from "./Context"; import {addInstance} from "./Garbarge"; import {PointerWrapper} from "./PointerWrapper"; -const TMP_PATH = path.join(__dirname, "../../.tmp/liveview.jpg"); - export class Liveview extends EventEmitter implements ICloseable { /** * @@ -18,14 +14,15 @@ export class Liveview extends EventEmitter implements ICloseable { /** * */ - private file: CameraFileFromFd; + private file: CameraFile; /** * */ - private fd: number; + private options: ILiveviewOptions; - constructor(private camera: PointerWrapper, private options: Partial = {fps: 100}) { + constructor(private camera: PointerWrapper, options: Partial) { super(); + this.options = {fps: 24, output: "binary", ...options}; addInstance(this); } @@ -33,15 +30,9 @@ export class Liveview extends EventEmitter implements ICloseable { * */ public start() { - if (this.options.stdout) { - this.fd = (process.stdout as any).fd; - } else { - this.fd = fs.openSync(TMP_PATH, "w+", 0o666); - } - - this.file = new CameraFileFromFd(this.fd); + this.file = new CameraFile(); - this.timer = setInterval(() => this.onTick(), this.options.fps); + this.timer = setInterval(() => this.onTick(), 1000 / this.options.fps); } /** @@ -50,8 +41,20 @@ export class Liveview extends EventEmitter implements ICloseable { public stop() { if (this.timer) { clearInterval(this.timer); - this.file.unref(); } + if (this.options.output === "file") { + if (!this.options.filePath) { + throw new Error("You should specify filePath option, if you choose output: file"); + } + // TODO if output === "file", it will save only the lase frame, because CamerFile overwrites each time + // we fire capture_preview. + // Maybe it is a good idea, to just use CameraFileFromFd to save it directly from C library + this.file.save(this.options.filePath); + } + this.file.closeQuietly(); + // TODO after stop() I always see an error in the console like + // malloc: *** error for object 0x102609d00: pointer being freed was not allocated + // *** set a breakpoint in malloc_error_break to debug } public close() { @@ -62,9 +65,9 @@ export class Liveview extends EventEmitter implements ICloseable { private async onTick() { await this.camera.callAsync("capture_preview", this.file.pointer, Context.get().pointer); - - fs.readFile(TMP_PATH, {encoding: "binary"}, (err, data) => { - this.emit("data", data); - }); + if (this.options.output === "binary" || this.options.output === "base64") { + const {data, size} = await this.file.getDataAndSize(this.options.output); + this.emit("data", data, size); + } } } diff --git a/src/interfaces/ILiveviewOptions.ts b/src/interfaces/ILiveviewOptions.ts index 2fbdf83..4cc2377 100644 --- a/src/interfaces/ILiveviewOptions.ts +++ b/src/interfaces/ILiveviewOptions.ts @@ -1,4 +1,5 @@ export interface ILiveviewOptions { fps: number; - stdout: boolean; + output: "binary" | "base64" | "file"; + filePath?: string; } From 78bd4d58c226299c2a233a6b68eec553fa76722d Mon Sep 17 00:00:00 2001 From: "romain.lenzotti" Date: Wed, 22 Aug 2018 09:38:33 +0200 Subject: [PATCH 05/10] feat: Add gp_file_set_mime_type --- src/components/CameraFile.ts | 13 ++++++++----- src/driver/modules/GPFileModule.ts | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/CameraFile.ts b/src/components/CameraFile.ts index 36034b1..d3dd121 100644 --- a/src/components/CameraFile.ts +++ b/src/components/CameraFile.ts @@ -1,6 +1,6 @@ +import {checkCode, GPPointer, GPPointerString} from "../driver"; import {PointerCameraFile, RefCameraFile} from "../driver/modules"; import {GPCodes, PointerOf} from "../driver/types"; -import {GPPointer, GPPointerString, checkCode} from "../driver"; import {IPointerWrapperOptions, PointerWrapper} from "./PointerWrapper"; export class CameraFile extends PointerWrapper { @@ -70,8 +70,6 @@ export class CameraFile extends PointerWrapper { /** * Get a pointer to the data and the file's size. * - * @param data - * @param size * @return a gphoto2 error code. * * Both data and size can be NULL and will then be ignored. @@ -83,6 +81,7 @@ export class CameraFile extends PointerWrapper { * data pointer is owned by the caller and needs to be free()d to avoid * memory leaks. * + * @param encoding **/ public async getDataAndSize( @@ -93,11 +92,15 @@ export class CameraFile extends PointerWrapper { }> { const dataPointer = GPPointerString(); const sizePointer: PointerOf = GPPointer("int"); - const code = await this.callAsync("get_data_and_size", dataPointer, sizePointer); + + await this.callAsync("set_mime_type", "image/jpeg"); + await this.callAsync("get_data_and_size", dataPointer, sizePointer); + const size = sizePointer.deref(); const binary = Buffer.from(dataPointer.deref(), "binary"); - checkCode(code); + let data: Buffer | string = ""; + switch (encoding) { case "binary": data = binary; diff --git a/src/driver/modules/GPFileModule.ts b/src/driver/modules/GPFileModule.ts index 0876130..cf7a75c 100644 --- a/src/driver/modules/GPFileModule.ts +++ b/src/driver/modules/GPFileModule.ts @@ -9,6 +9,7 @@ export const GPFileModuleDescription = { gp_file_new: ["int", [refType(RefCameraFile)]], gp_file_new_from_fd: ["int", [refType(RefCameraFile), "int"]], gp_file_get_mime_type: ["int", [RefCameraFile, refType(types.CString)]], + gp_file_set_mime_type: ["int", [RefCameraFile, types.CString]], gp_file_ref: ["int", [RefCameraFile]], gp_file_clean: ["int", [RefCameraFile]], gp_file_save: ["int", [RefCameraFile, types.CString]], @@ -41,6 +42,7 @@ export interface IGPFileModule { */ gp_file_get_mime_type(file: PointerCameraFile, mime: PointerOf): GPCodes; + gp_file_set_mime_type(file: PointerCameraFile, mime: string): GPCodes; /** * * @param {PointerCameraFile} cameraFile From c111e7a6164eb79f79b201f78828a8a3988b769f Mon Sep 17 00:00:00 2001 From: goooseman Date: Mon, 27 Aug 2018 12:25:23 +0300 Subject: [PATCH 06/10] feat: Rewrite Liveview support on the driver level --- src/components/CameraFile.ts | 4 ++-- src/driver/modules/GPFileModule.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/CameraFile.ts b/src/components/CameraFile.ts index d3dd121..be7fe94 100644 --- a/src/components/CameraFile.ts +++ b/src/components/CameraFile.ts @@ -90,14 +90,14 @@ export class CameraFile extends PointerWrapper { data: Buffer | string; size: number; }> { - const dataPointer = GPPointerString(); + const dataPointer: PointerOf> = GPPointer(GPPointer("char")); const sizePointer: PointerOf = GPPointer("int"); await this.callAsync("set_mime_type", "image/jpeg"); await this.callAsync("get_data_and_size", dataPointer, sizePointer); const size = sizePointer.deref(); - const binary = Buffer.from(dataPointer.deref(), "binary"); + const binary = Buffer.from(dataPointer.deref().deref(), "binary"); let data: Buffer | string = ""; diff --git a/src/driver/modules/GPFileModule.ts b/src/driver/modules/GPFileModule.ts index cf7a75c..c2c02da 100644 --- a/src/driver/modules/GPFileModule.ts +++ b/src/driver/modules/GPFileModule.ts @@ -15,7 +15,7 @@ export const GPFileModuleDescription = { gp_file_save: ["int", [RefCameraFile, types.CString]], gp_file_unref: ["int", [RefCameraFile]], gp_file_free: ["int", [RefCameraFile]], - gp_file_get_data_and_size: ["int", [RefCameraFile, refType(types.CString), refType(types.int)]] + gp_file_get_data_and_size: ["int", [RefCameraFile, refType(refType(types.CString)), refType(types.int)]] }; export interface IGPFileModule { @@ -89,6 +89,8 @@ export interface IGPFileModule { /** * Get a pointer to the data and the file's size. + * http://www.gphoto.org/doc/api/gphoto2-file_8c.html#afe4a2c1685df174f61f20b2986edd93b + * int gp_file_get_data_and_size ( CameraFile * file, const char ** data, unsigned long int * size) * * @param file a #CameraFile * @param data @@ -105,5 +107,5 @@ export interface IGPFileModule { * memory leaks. * **/ - gp_file_get_data_and_size(file: PointerCameraFile, data: PointerOf, size: PointerOf): GPCodes; + gp_file_get_data_and_size(file: PointerCameraFile, data: PointerOf>, size: PointerOf): GPCodes; } From 245172dc691f5a1456e4681353dbebdeb87adaa4 Mon Sep 17 00:00:00 2001 From: goooseman Date: Mon, 27 Aug 2018 22:34:58 +0300 Subject: [PATCH 07/10] fix: Implement getDataAndSize with right string pointer --- src/components/CameraFile.ts | 10 +++------- src/driver/modules/GPFileModule.ts | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/CameraFile.ts b/src/components/CameraFile.ts index be7fe94..01c7d2a 100644 --- a/src/components/CameraFile.ts +++ b/src/components/CameraFile.ts @@ -2,7 +2,7 @@ import {checkCode, GPPointer, GPPointerString} from "../driver"; import {PointerCameraFile, RefCameraFile} from "../driver/modules"; import {GPCodes, PointerOf} from "../driver/types"; import {IPointerWrapperOptions, PointerWrapper} from "./PointerWrapper"; - +import {reinterpret} from "ref"; export class CameraFile extends PointerWrapper { constructor(options: Partial = {}, ...args: any[]) { super( @@ -90,14 +90,11 @@ export class CameraFile extends PointerWrapper { data: Buffer | string; size: number; }> { - const dataPointer: PointerOf> = GPPointer(GPPointer("char")); + const dataPointer: PointerOf = GPPointer("char *"); const sizePointer: PointerOf = GPPointer("int"); - - await this.callAsync("set_mime_type", "image/jpeg"); await this.callAsync("get_data_and_size", dataPointer, sizePointer); - const size = sizePointer.deref(); - const binary = Buffer.from(dataPointer.deref().deref(), "binary"); + const binary = reinterpret(dataPointer.deref(), sizePointer.deref()); let data: Buffer | string = ""; @@ -111,7 +108,6 @@ export class CameraFile extends PointerWrapper { break; } - // TODO now it does not work. I just see �� in the console for binary or /f39/Q== for Base64 return { data, diff --git a/src/driver/modules/GPFileModule.ts b/src/driver/modules/GPFileModule.ts index c2c02da..d68435e 100644 --- a/src/driver/modules/GPFileModule.ts +++ b/src/driver/modules/GPFileModule.ts @@ -15,7 +15,7 @@ export const GPFileModuleDescription = { gp_file_save: ["int", [RefCameraFile, types.CString]], gp_file_unref: ["int", [RefCameraFile]], gp_file_free: ["int", [RefCameraFile]], - gp_file_get_data_and_size: ["int", [RefCameraFile, refType(refType(types.CString)), refType(types.int)]] + gp_file_get_data_and_size: ["int", [RefCameraFile, refType(refType(types.char)), refType(types.int)]] }; export interface IGPFileModule { @@ -107,5 +107,5 @@ export interface IGPFileModule { * memory leaks. * **/ - gp_file_get_data_and_size(file: PointerCameraFile, data: PointerOf>, size: PointerOf): GPCodes; + gp_file_get_data_and_size(file: PointerCameraFile, data: PointerOf>, size: PointerOf): GPCodes; } From b55c02583b7c00f24bae68f2619af393865aa659 Mon Sep 17 00:00:00 2001 From: goooseman Date: Mon, 27 Aug 2018 23:00:51 +0300 Subject: [PATCH 08/10] fix: Add close file process in Liveview --- examples/camera-liveview-base64.js | 2 +- examples/camera-liveview-binary.js | 2 +- examples/camera-liveview-file.js | 2 +- src/components/Liveview.ts | 24 +++++++++--------------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/examples/camera-liveview-base64.js b/examples/camera-liveview-base64.js index 0b5771a..1b70e45 100644 --- a/examples/camera-liveview-base64.js +++ b/examples/camera-liveview-base64.js @@ -25,7 +25,7 @@ if (!camera.isClosed()) { setTimeout(() => { - liveview.stop(); + liveview.close(); camera.closeQuietly(); }, NUMBER_OF_SECONDS_TO_LISTEN * 1000); diff --git a/examples/camera-liveview-binary.js b/examples/camera-liveview-binary.js index c9a472b..e18b04c 100644 --- a/examples/camera-liveview-binary.js +++ b/examples/camera-liveview-binary.js @@ -24,7 +24,7 @@ if (!camera.isClosed()) { setTimeout(() => { - liveview.stop(); + liveview.close(); camera.closeQuietly(); }, NUMBER_OF_SECONDS_TO_LISTEN * 1000); diff --git a/examples/camera-liveview-file.js b/examples/camera-liveview-file.js index 5403ea8..0806ea8 100644 --- a/examples/camera-liveview-file.js +++ b/examples/camera-liveview-file.js @@ -23,7 +23,7 @@ if (!camera.isClosed()) { setTimeout(() => { - liveview.stop(); + liveview.close(); camera.closeQuietly(); }, NUMBER_OF_SECONDS_TO_WRITE * 1000); diff --git a/src/components/Liveview.ts b/src/components/Liveview.ts index af36f65..172b033 100644 --- a/src/components/Liveview.ts +++ b/src/components/Liveview.ts @@ -14,7 +14,7 @@ export class Liveview extends EventEmitter implements ICloseable { /** * */ - private file: CameraFile; + private file?: CameraFile; /** * */ @@ -35,35 +35,29 @@ export class Liveview extends EventEmitter implements ICloseable { this.timer = setInterval(() => this.onTick(), 1000 / this.options.fps); } - /** - * - */ - public stop() { + public close() { if (this.timer) { clearInterval(this.timer); } - if (this.options.output === "file") { + if (this.file && this.options.output === "file") { if (!this.options.filePath) { throw new Error("You should specify filePath option, if you choose output: file"); } - // TODO if output === "file", it will save only the lase frame, because CamerFile overwrites each time + // TODO if output === "file", it will save only the lase frame, because CameraFile overwrites each time // we fire capture_preview. // Maybe it is a good idea, to just use CameraFileFromFd to save it directly from C library this.file.save(this.options.filePath); } - this.file.closeQuietly(); - // TODO after stop() I always see an error in the console like - // malloc: *** error for object 0x102609d00: pointer being freed was not allocated - // *** set a breakpoint in malloc_error_break to debug - } - - public close() { - this.stop(); + this.file && this.file.closeQuietly(); + this.file = undefined; return this; } private async onTick() { + if (!this.file) { + throw new Error("LiveView was closed"); + } await this.camera.callAsync("capture_preview", this.file.pointer, Context.get().pointer); if (this.options.output === "binary" || this.options.output === "base64") { const {data, size} = await this.file.getDataAndSize(this.options.output); From bc004891abb19b244749c1df6c271d7add4f481c Mon Sep 17 00:00:00 2001 From: goooseman Date: Tue, 28 Aug 2018 09:33:46 +0300 Subject: [PATCH 09/10] feat: Liveview produce output --- README.md | 3 +- examples/camera-liveview-base64.js | 32 ------------------- examples/camera-liveview-binary.js | 31 ------------------ examples/camera-liveview-file.js | 24 +++++++------- examples/camera-liveview.js | 50 ++++++++++++++++++++++++++++++ src/components/Camera.ts | 10 ++++++ src/components/CameraFile.ts | 1 + src/components/Liveview.ts | 49 +++++++++++++++++------------ 8 files changed, 103 insertions(+), 97 deletions(-) delete mode 100644 examples/camera-liveview-base64.js delete mode 100644 examples/camera-liveview-binary.js create mode 100644 examples/camera-liveview.js diff --git a/README.md b/README.md index 2dcbec6..ce01230 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,10 @@ A Node.js wrapper for libgphoto2 written in TypeScript. Useful for remote contro - Camera autodetection, - Take a picture/movie capture, - - Take a preview (not a live preview mode), + - Take a preview, - Retrieve camera list, - Select camera, + - Take a liveview from camera and get binary or base64 of each frame, or write it to file, - Display info about your camera (summary, about, manual). ## Prerequisite diff --git a/examples/camera-liveview-base64.js b/examples/camera-liveview-base64.js deleted file mode 100644 index 1b70e45..0000000 --- a/examples/camera-liveview-base64.js +++ /dev/null @@ -1,32 +0,0 @@ -// This example illustrates the usecase, where you need to capture liveview from the camera -// and get live output of each frame in base64 - -const { Camera } = require('../src'); -// If you launch this example not from library folder, change the previous two lines to: -// const { Camera } = require('@typedproject/gphoto2-driver'); -const camera = new Camera(); - -const NUMBER_OF_SECONDS_TO_LISTEN = 10; - -camera.initialize(); - -if (!camera.isClosed()) { - const liveview = camera.liveview({ - output: "base64", - fps: 24, // Number of frames per second. Default is 24. - }); - - liveview.start(); - - liveview.on("data", (data, size) => { - console.log("base64 of the frame", data); - console.log("Size of the frame", size); - }); - - setTimeout(() => { - - liveview.close(); - - camera.closeQuietly(); - }, NUMBER_OF_SECONDS_TO_LISTEN * 1000); -} diff --git a/examples/camera-liveview-binary.js b/examples/camera-liveview-binary.js deleted file mode 100644 index e18b04c..0000000 --- a/examples/camera-liveview-binary.js +++ /dev/null @@ -1,31 +0,0 @@ -// This example illustrates the usecase, where you need to capture liveview from the camera -// and get live output of each frame as Buffer object (binary data) - -const { Camera } = require('../src'); -// If you launch this example not from library folder, change the previous two lines to: -// const { Camera } = require('@typedproject/gphoto2-driver'); -const camera = new Camera(); - -const NUMBER_OF_SECONDS_TO_LISTEN = 10; - -camera.initialize(); - -if (!camera.isClosed()) { - const liveview = camera.liveview({ - output: "binary", - fps: 24, // Number of frames per second. Default is 24. - }); - - liveview.start(); - - liveview.on("data", (data, size) => { - process.stdout.write(data); // We can not use console.log for binary, becayse it puts \n after each line - }); - - setTimeout(() => { - - liveview.close(); - - camera.closeQuietly(); - }, NUMBER_OF_SECONDS_TO_LISTEN * 1000); -} diff --git a/examples/camera-liveview-file.js b/examples/camera-liveview-file.js index 0806ea8..67565b8 100644 --- a/examples/camera-liveview-file.js +++ b/examples/camera-liveview-file.js @@ -1,5 +1,5 @@ // This example illustrates the usecase, where you need to capture liveview from the camera -// and then save it to .mjpg file +// and then save it to .mjpg file. The file will be updated in live mode. const { Camera } = require('../src'); // If you launch this example not from library folder, change the previous two lines to: @@ -12,19 +12,17 @@ const PATH_TO_SAVE = path.join(__dirname, '../.tmp/live.mjpg'); // Make sure tha camera.initialize(); -if (!camera.isClosed()) { - const liveview = camera.liveview({ - output: "file", - fps: 24, // Number of frames per second. Default is 24. - filePath: PATH_TO_SAVE, - }); +const liveview = camera.liveview({ + output: "file", + fps: 24, // Number of frames per second. Default is 24. + filePath: PATH_TO_SAVE, +}); - liveview.start(); +liveview.start(); - setTimeout(() => { +setTimeout(() => { - liveview.close(); + liveview.stop(); - camera.closeQuietly(); - }, NUMBER_OF_SECONDS_TO_WRITE * 1000); -} + camera.closeQuietly(); +}, NUMBER_OF_SECONDS_TO_WRITE * 1000); diff --git a/examples/camera-liveview.js b/examples/camera-liveview.js new file mode 100644 index 0000000..006cb02 --- /dev/null +++ b/examples/camera-liveview.js @@ -0,0 +1,50 @@ +// This example illustrates the usecase, where you need to capture liveview from the camera +// and get live output of each frame as binary (Buffer) or base64 + +// To check if liveview works with your camera, you can just use +// node examples/camera-liveview.js | xxd | grep ffd9 +// if you see a lot of ffd9 (not in the first column) - then it works + +const { Camera } = require('../src'); +// If you launch this example not from library folder, change the previous two lines to: +// const { Camera } = require('@typedproject/gphoto2-driver'); +const camera = new Camera(); + +const NUMBER_OF_SECONDS_TO_LISTEN = 10; +const NUMBER_OF_SECONDS_TO_WAIT = 10; +const OUTPUT = "binary"; +// change these lines to see base64 output +// const OUTPUT = "base64"; + +camera.initialize(); + +const liveview = camera.liveview({ + output: OUTPUT, + fps: 24, // Number of frames per second. Default is 24. +}); + +liveview.on("data", (data, size) => { + process.stdout.write(data); // We can not use console.log for binary, becayse it puts \n after each line +}); + +liveview.start(); + +setTimeout(() => { + + liveview.stop(); + +}, NUMBER_OF_SECONDS_TO_LISTEN * 1000); + +setTimeout(() => { + // Here we wait for NUMBER_OF_SECONDS_TO_WAIT + NUMBER_OF_SECONDS_TO_LISTEN and then + // launch liveview one more time + // The output should be caught by original liveview.on("data") event handler + liveview.start(); + + setTimeout(() => { + + liveview.stop(); + + camera.closeQuietly(); // Do not forget to close camera + }, NUMBER_OF_SECONDS_TO_LISTEN * 1000); +}, (NUMBER_OF_SECONDS_TO_WAIT + NUMBER_OF_SECONDS_TO_LISTEN) * 1000) \ No newline at end of file diff --git a/src/components/Camera.ts b/src/components/Camera.ts index 23d9bda..06b6da0 100644 --- a/src/components/Camera.ts +++ b/src/components/Camera.ts @@ -96,8 +96,18 @@ export class Camera extends PointerWrapper { } } + /** + * + */ + private checkIsInitialized() { + if (!this.initialized) { + throw new Error("Invalid state: not initialized"); + } + } + public liveview(options: ILiveviewOptions): Liveview { this.checkNotClosed(); + this.checkIsInitialized(); return new Liveview(this, options); } diff --git a/src/components/CameraFile.ts b/src/components/CameraFile.ts index 01c7d2a..f3ffea5 100644 --- a/src/components/CameraFile.ts +++ b/src/components/CameraFile.ts @@ -3,6 +3,7 @@ import {PointerCameraFile, RefCameraFile} from "../driver/modules"; import {GPCodes, PointerOf} from "../driver/types"; import {IPointerWrapperOptions, PointerWrapper} from "./PointerWrapper"; import {reinterpret} from "ref"; + export class CameraFile extends PointerWrapper { constructor(options: Partial = {}, ...args: any[]) { super( diff --git a/src/components/Liveview.ts b/src/components/Liveview.ts index 172b033..6eac9d0 100644 --- a/src/components/Liveview.ts +++ b/src/components/Liveview.ts @@ -1,16 +1,17 @@ import * as EventEmitter from "events"; -import {PointerCamera} from "../driver/modules"; +import * as fs from "fs"; +import {Camera} from "./Camera"; import {ICloseable, ILiveviewOptions} from "../interfaces"; import {CameraFile} from "./CameraFile"; +import {CameraFileFromFd} from "./CameraFileFromFd"; import {Context} from "./Context"; import {addInstance} from "./Garbarge"; -import {PointerWrapper} from "./PointerWrapper"; export class Liveview extends EventEmitter implements ICloseable { /** * */ - private timer: any; + private timer?: NodeJS.Timer; /** * */ @@ -19,8 +20,12 @@ export class Liveview extends EventEmitter implements ICloseable { * */ private options: ILiveviewOptions; + /** + * + */ + private fd?: number; // File descriptor of the the file to write to - constructor(private camera: PointerWrapper, options: Partial) { + constructor(private camera: Camera, options: Partial) { super(); this.options = {fps: 24, output: "binary", ...options}; addInstance(this); @@ -30,26 +35,29 @@ export class Liveview extends EventEmitter implements ICloseable { * */ public start() { - this.file = new CameraFile(); - + switch (this.options.output) { + case "file": + if (!this.options.filePath) { + throw new Error("You should specify filePath option, if you choose output: file"); + } + this.fd = fs.openSync(this.options.filePath, "w", 0o777); + this.file = new CameraFileFromFd(this.fd); + break; + default: + this.file = new CameraFile(); + } this.timer = setInterval(() => this.onTick(), 1000 / this.options.fps); } - public close() { - if (this.timer) { - clearInterval(this.timer); - } - if (this.file && this.options.output === "file") { - if (!this.options.filePath) { - throw new Error("You should specify filePath option, if you choose output: file"); - } - // TODO if output === "file", it will save only the lase frame, because CameraFile overwrites each time - // we fire capture_preview. - // Maybe it is a good idea, to just use CameraFileFromFd to save it directly from C library - this.file.save(this.options.filePath); - } + public stop() { + this.timer && clearInterval(this.timer); + this.fd && fs.closeSync(this.fd); this.file && this.file.closeQuietly(); - this.file = undefined; + this.file, this.timer, (this.fd = undefined); + } + + public close() { + this.stop(); return this; } @@ -59,6 +67,7 @@ export class Liveview extends EventEmitter implements ICloseable { throw new Error("LiveView was closed"); } await this.camera.callAsync("capture_preview", this.file.pointer, Context.get().pointer); + if (this.options.output === "binary" || this.options.output === "base64") { const {data, size} = await this.file.getDataAndSize(this.options.output); this.emit("data", data, size); From 2efa1cbd5e48af4e6e71d715d8469351da79723e Mon Sep 17 00:00:00 2001 From: goooseman Date: Wed, 29 Aug 2018 08:35:48 +0300 Subject: [PATCH 10/10] fix: setting file to undefined in liveview to prevent double closing --- README.md | 18 ++++++++++++++++++ src/components/CameraFile.ts | 4 ++-- src/components/Liveview.ts | 6 ++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ce01230..92b5985 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,24 @@ if (cameraList.size) { cameraList.close(); ``` +## CameraFile + +A lot of different API's of this library returns a CameraFile object. + +This object does not contain the image, it is just a pointer to the file in camera's RAM. + +You have several options to get your image: + +1) Use `.save(filename)` of `.saveAsync(filename)` methods, that will save the image to your filesystem. +2) Use `.getDataAndSizeAsync('binary' | 'base64')` method, which returns following object: + +```typescript +{ + data: data, // Buffer for binary format and string for base64. + size: size +} +``` + ## Examples Some example are available in the examples directory, when you have cloned or downloaded the complete project from github. diff --git a/src/components/CameraFile.ts b/src/components/CameraFile.ts index f3ffea5..cbec7af 100644 --- a/src/components/CameraFile.ts +++ b/src/components/CameraFile.ts @@ -60,7 +60,7 @@ export class CameraFile extends PointerWrapper { * @param {PointerOf} mime * @returns {GPCodes} */ - public async getMimeType(mime: PointerOf): Promise { + public async getMimeTypeAsync(mime: PointerOf): Promise { const mimePointer = GPPointerString(); const code = await this.call("get_mime_type", mime); checkCode(code); @@ -85,7 +85,7 @@ export class CameraFile extends PointerWrapper { * @param encoding **/ - public async getDataAndSize( + public async getDataAndSizeAsync( encoding: "binary" | "base64" = "binary" ): Promise<{ data: Buffer | string; diff --git a/src/components/Liveview.ts b/src/components/Liveview.ts index 6eac9d0..6310974 100644 --- a/src/components/Liveview.ts +++ b/src/components/Liveview.ts @@ -53,7 +53,9 @@ export class Liveview extends EventEmitter implements ICloseable { this.timer && clearInterval(this.timer); this.fd && fs.closeSync(this.fd); this.file && this.file.closeQuietly(); - this.file, this.timer, (this.fd = undefined); + this.file = undefined; + this.timer = undefined; + this.fd = undefined; } public close() { @@ -69,7 +71,7 @@ export class Liveview extends EventEmitter implements ICloseable { await this.camera.callAsync("capture_preview", this.file.pointer, Context.get().pointer); if (this.options.output === "binary" || this.options.output === "base64") { - const {data, size} = await this.file.getDataAndSize(this.options.output); + const {data, size} = await this.file.getDataAndSizeAsync(this.options.output); this.emit("data", data, size); } }