diff --git a/src/interfaces.ts b/src/interfaces.ts index 87698bc..75a7495 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -44,6 +44,7 @@ export interface IFaceXRequestBuilder { setSegment(segment: string): this; setPhotoData(s: string): this; setPhoto(s: Buffer | string | NodeJS.ReadableStream): Promise; + setVideo(s: Buffer | string | NodeJS.ReadableStream): Promise; setComment(comment: string): this; setResultNumber(resultNumber: number): this; setParams(par1: number, par2: number): this; diff --git a/src/request/builder.ts b/src/request/builder.ts index 10e0e35..c419e1f 100644 --- a/src/request/builder.ts +++ b/src/request/builder.ts @@ -1,5 +1,5 @@ import { createReadStream } from 'fs'; -import { BufferStream } from '@myrotvorets/buffer-stream'; +import { BufferStream, streamToBuffer } from '@myrotvorets/buffer-stream'; import { FaceXRequest, IFaceXRequestBuilder, IGuidGenerator, IImageProcessor } from '../interfaces'; export class FaceXRequestBuilder implements IFaceXRequestBuilder { @@ -91,6 +91,20 @@ export class FaceXRequestBuilder implements IFaceXRequestBuilder { return this; } + public async setVideo(s: Buffer | string | NodeJS.ReadableStream): Promise { + let b: Buffer; + if (Buffer.isBuffer(s)) { + b = s; + } else if (typeof s === 'string') { + b = Buffer.from(s); + } else { + b = await streamToBuffer(s); + } + + this._request.data.foto = b.toString('base64'); + return this; + } + public setComment(comment: string): this { this._request.data.comment = comment; return this; diff --git a/src/request/commands.ts b/src/request/commands.ts index 5d44eaf..11c5630 100644 --- a/src/request/commands.ts +++ b/src/request/commands.ts @@ -24,3 +24,9 @@ export const enum AdminCommands { DELETE_INIT = 208, DELETE_STATUS = 209, } + +export const enum VideoCommands { + VIDEO_UPLOAD = 240, + VIDEO_STATUS = 241, + VIDEO_RESULT = 242, +} diff --git a/src/responsefactory.ts b/src/responsefactory.ts index 263a6be..0ca093c 100644 --- a/src/responsefactory.ts +++ b/src/responsefactory.ts @@ -42,6 +42,10 @@ const lookupTable: Record = { 208: Response.DeleteAck, 209: Response.DeleteStatus, + + 241: Response.VideoUploadAck, + 243: Response.VideoStatus, + 245: Response.VideoResult, }; export function responseFactory(r: Response.RawResponse): Response.Response { diff --git a/src/responses/index.ts b/src/responses/index.ts index f00c583..4087881 100644 --- a/src/responses/index.ts +++ b/src/responses/index.ts @@ -27,3 +27,6 @@ export * from './preparedfaces'; // ans_type = 206 export * from './addpreparedfacesack'; // ans_type = 207 export * from './deleteack'; // ans_type = 208 export * from './deletestatus'; // ans_type = 209 +export * from './videouploadack'; // ans_type = 241 +export * from './videostatus'; // ans_type = 243 +export * from './videoresult'; // ans_type = 245 diff --git a/src/responses/videoresult.ts b/src/responses/videoresult.ts new file mode 100644 index 0000000..1945a2e --- /dev/null +++ b/src/responses/videoresult.ts @@ -0,0 +1,43 @@ +import { PhotoEntry, Response } from './response'; + +export class VideoMatch { + private readonly _e: PhotoEntry; + + public constructor(x: PhotoEntry) { + this._e = x; + } + + public get archive(): string { + return this._e.foto as string; + } + + public get archiveAsBuffer(): Buffer { + return Buffer.from(this._e.foto as string, 'base64'); + } +} + +// ans_type = 245 +export class VideoResult extends Response { + private _idx = 0; + + // eslint-disable-next-line class-methods-use-this + public isCacheable(): boolean { + return true; + } + + public [Symbol.iterator](): Iterator { + return { + next: (): IteratorResult => { + if (this._idx < this._raw.data.fotos.length) { + return { + value: new VideoMatch(this._raw.data.fotos[this._idx++]), + done: false, + }; + } + + this._idx = 0; + return { value: undefined, done: true }; + }, + }; + } +} diff --git a/src/responses/videostatus.ts b/src/responses/videostatus.ts new file mode 100644 index 0000000..beb2075 --- /dev/null +++ b/src/responses/videostatus.ts @@ -0,0 +1,24 @@ +import { Response } from './response'; + +// ans_type = 243 +export class VideoStatus extends Response { + public isError(): boolean { + return this.resultCode < 0; + } + + public isAccepted(): boolean { + return this.resultCode === 1; + } + + public isInProgress(): boolean { + return this.resultCode === 2; + } + + public isCompleted(): boolean { + return this.resultCode === 3; + } + + public description(): string { + return this.comment; + } +} diff --git a/src/responses/videouploadack.ts b/src/responses/videouploadack.ts new file mode 100644 index 0000000..62dfcf1 --- /dev/null +++ b/src/responses/videouploadack.ts @@ -0,0 +1,8 @@ +import { Response } from './response'; + +// ans_type = 241 +export class VideoUploadAck extends Response { + public isError(): boolean { + return this.resultCode < 0; + } +} diff --git a/src/videoclient.ts b/src/videoclient.ts new file mode 100644 index 0000000..7a84ee3 --- /dev/null +++ b/src/videoclient.ts @@ -0,0 +1,29 @@ +import { ClientBase } from './clientbase'; +import { IFaceXRequestBuilder, IRemoteTransport } from './interfaces'; +import { SvcClientRequestEncoder } from './encoders/svcclient'; +import { VideoCommands } from './request/commands'; +import * as R from './responses'; + +type VideoType = Buffer | string | NodeJS.ReadableStream; + +export class VideoClient extends ClientBase { + public constructor(url: string, transport: IRemoteTransport, requestBuilder: IFaceXRequestBuilder) { + super(url, transport, new SvcClientRequestEncoder(), requestBuilder); + } + + public async uploadVideo(video: VideoType): Promise { + const builder = await this._requestBuilder.reset(VideoCommands.VIDEO_UPLOAD).setVideo(video); + return this._sendRequest(await builder.get()); + } + + public async getVideoStatus(guid: string): Promise { + const builder = this._requestBuilder.reset(VideoCommands.VIDEO_STATUS, guid); + return this._sendRequest(await builder.get()); + } + + public async getVideoResult(guid: string, type: 'detect' | 'match', archiveNumber = 1): Promise { + const builder = this._requestBuilder.reset(VideoCommands.VIDEO_RESULT, guid); + builder.setParams(type === 'detect' ? 1 : 2, archiveNumber); + return this._sendRequest(await builder.get()); + } +}