mirror of
https://github.com/koush/scrypted.git
synced 2026-06-01 17:20:26 +01:00
snapshot: refactor
This commit is contained in:
@@ -11,6 +11,7 @@ import MimeType from 'whatwg-mimetype';
|
||||
import { ffmpegFilterImage, ffmpegFilterImageBuffer } from './ffmpeg-image-filter';
|
||||
import { ImageReader, ImageReaderNativeId, loadVipsImage, loadSharp } from './image-reader';
|
||||
import { ImageWriter, ImageWriterNativeId } from './image-writer';
|
||||
import { parseDims, parseImageOp, processImageOp } from './parse-dims';
|
||||
|
||||
const { mediaManager, systemManager } = sdk;
|
||||
|
||||
@@ -532,31 +533,6 @@ class SnapshotMixin extends SettingsMixinDeviceBase<Camera> implements Camera {
|
||||
}
|
||||
}
|
||||
|
||||
type DimDict<T extends string> = {
|
||||
[key in T]: string;
|
||||
};
|
||||
|
||||
export function parseDims<T extends string>(dict: DimDict<T>) {
|
||||
const ret: {
|
||||
[key in T]?: number;
|
||||
} & {
|
||||
fractional?: boolean;
|
||||
} = {
|
||||
};
|
||||
|
||||
for (const t of Object.keys(dict)) {
|
||||
const val = dict[t as T];
|
||||
if (val?.endsWith('%')) {
|
||||
ret.fractional = true;
|
||||
ret[t] = parseFloat(val?.substring(0, val?.length - 1)) / 100;
|
||||
}
|
||||
else {
|
||||
ret[t] = val ? parseFloat(val) : undefined;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
class SnapshotPlugin extends AutoenableMixinProvider implements MixinProvider, BufferConverter, Settings, DeviceProvider {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
debugLogging: {
|
||||
@@ -629,100 +605,10 @@ class SnapshotPlugin extends AutoenableMixinProvider implements MixinProvider, B
|
||||
async convert(data: any, fromMimeType: string, toMimeType: string, options?: MediaObjectOptions): Promise<any> {
|
||||
const mime = new MimeType(toMimeType);
|
||||
|
||||
const op = parseImageOp(mime.parameters);
|
||||
const ffmpegInput = JSON.parse(data.toString()) as FFmpegInput;
|
||||
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
fractional
|
||||
} = parseDims({
|
||||
width: mime.parameters.get('width'),
|
||||
height: mime.parameters.get('height'),
|
||||
});
|
||||
|
||||
const {
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
fractional: cropFractional,
|
||||
} = parseDims({
|
||||
left: mime.parameters.get('left'),
|
||||
top: mime.parameters.get('top'),
|
||||
right: mime.parameters.get('right'),
|
||||
bottom: mime.parameters.get('bottom'),
|
||||
});
|
||||
|
||||
const filename = ffmpegInput.url?.startsWith('file:') && new URL(ffmpegInput.url).pathname;
|
||||
if (filename && loadSharp()) {
|
||||
const vips = await loadVipsImage(filename, options?.sourceId);
|
||||
|
||||
const resize = width && {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
|
||||
if (fractional) {
|
||||
if (resize.width)
|
||||
resize.width *= vips.width;
|
||||
if (resize.height)
|
||||
resize.height *= vips.height;
|
||||
}
|
||||
|
||||
const crop = left && {
|
||||
left,
|
||||
top,
|
||||
width: right - left,
|
||||
height: bottom - top,
|
||||
};
|
||||
|
||||
if (cropFractional) {
|
||||
crop.left *= vips.width;
|
||||
crop.top *= vips.height;
|
||||
crop.width *= vips.width;
|
||||
crop.height *= vips.height;
|
||||
}
|
||||
|
||||
try {
|
||||
const ret = await vips.toBuffer({
|
||||
resize,
|
||||
crop,
|
||||
format: 'jpg',
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
finally {
|
||||
vips.close();
|
||||
}
|
||||
}
|
||||
|
||||
const args = [
|
||||
...ffmpegInput.inputArguments,
|
||||
...(ffmpegInput.h264EncoderArguments || []),
|
||||
];
|
||||
|
||||
return ffmpegFilterImage(args, {
|
||||
console: this.debugConsole,
|
||||
ffmpegPath: await mediaManager.getFFmpegPath(),
|
||||
resize: width === undefined && height === undefined
|
||||
? undefined
|
||||
: {
|
||||
width,
|
||||
height,
|
||||
fractional,
|
||||
},
|
||||
crop: left === undefined || right === undefined || top === undefined || bottom === undefined
|
||||
? undefined
|
||||
: {
|
||||
left,
|
||||
top,
|
||||
width: right - left,
|
||||
height: bottom - top,
|
||||
fractional: cropFractional,
|
||||
},
|
||||
timeout: 10000,
|
||||
time: parseFloat(mime.parameters.get('time')),
|
||||
});
|
||||
return processImageOp(ffmpegInput, op, parseFloat(mime.parameters.get('time')), options?.sourceId, this.debugConsole);
|
||||
}
|
||||
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
|
||||
135
plugins/snapshot/src/parse-dims.ts
Normal file
135
plugins/snapshot/src/parse-dims.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import sdk, { FFmpegInput } from '@scrypted/sdk';
|
||||
import type { MIMETypeParameters } from 'whatwg-mimetype';
|
||||
import { loadSharp, loadVipsImage } from './image-reader';
|
||||
import { ffmpegFilterImage } from './ffmpeg-image-filter';
|
||||
|
||||
export type DimDict<T extends string> = {
|
||||
[key in T]: string;
|
||||
};
|
||||
|
||||
export function parseDims<T extends string>(dict: DimDict<T>) {
|
||||
const ret: {
|
||||
[key in T]?: number;
|
||||
} & {
|
||||
fractional?: boolean;
|
||||
} = {
|
||||
};
|
||||
|
||||
for (const t of Object.keys(dict)) {
|
||||
const val = dict[t as T];
|
||||
if (val?.endsWith('%')) {
|
||||
ret.fractional = true;
|
||||
ret[t] = parseFloat(val?.substring(0, val?.length - 1)) / 100;
|
||||
}
|
||||
else {
|
||||
ret[t] = val ? parseFloat(val) : undefined;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export interface ImageOp {
|
||||
resize?: ReturnType<typeof parseDims<'width' | 'height'>>;
|
||||
crop?: ReturnType<typeof parseDims<'left' | 'top' | 'right' | 'bottom'>>;
|
||||
}
|
||||
|
||||
export function parseImageOp(parameters: MIMETypeParameters | URLSearchParams): ImageOp {
|
||||
return {
|
||||
resize: parseDims({
|
||||
width: parameters.get('width'),
|
||||
height: parameters.get('height'),
|
||||
}),
|
||||
crop: parseDims({
|
||||
left: parameters.get('left'),
|
||||
top: parameters.get('top'),
|
||||
right: parameters.get('right'),
|
||||
bottom: parameters.get('bottom'),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function processImageOp(input: string | FFmpegInput, op: ImageOp, time: number, sourceId: string, debugConsole: Console): Promise<Buffer> {
|
||||
const { crop, resize } = op;
|
||||
const { width, height, fractional } = resize;
|
||||
const { left, top, right, bottom, fractional: cropFractional } = crop;
|
||||
|
||||
const filename = typeof input === 'string' ? input : input.url?.startsWith('file:') && new URL(input.url).pathname;
|
||||
|
||||
if (filename && loadSharp()) {
|
||||
const vips = await loadVipsImage(filename, sourceId);
|
||||
|
||||
const resize = width && {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
|
||||
if (fractional) {
|
||||
if (resize.width)
|
||||
resize.width *= vips.width;
|
||||
if (resize.height)
|
||||
resize.height *= vips.height;
|
||||
}
|
||||
|
||||
const crop = left && {
|
||||
left,
|
||||
top,
|
||||
width: right - left,
|
||||
height: bottom - top,
|
||||
};
|
||||
|
||||
if (cropFractional) {
|
||||
crop.left *= vips.width;
|
||||
crop.top *= vips.height;
|
||||
crop.width *= vips.width;
|
||||
crop.height *= vips.height;
|
||||
}
|
||||
|
||||
try {
|
||||
const ret = await vips.toBuffer({
|
||||
resize,
|
||||
crop,
|
||||
format: 'jpg',
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
finally {
|
||||
vips.close();
|
||||
}
|
||||
}
|
||||
|
||||
const ffmpegInput: FFmpegInput = typeof input !== 'string'
|
||||
? input
|
||||
: {
|
||||
inputArguments: [
|
||||
'-i', input,
|
||||
]
|
||||
};
|
||||
|
||||
const args = [
|
||||
...ffmpegInput.inputArguments,
|
||||
...(ffmpegInput.h264EncoderArguments || []),
|
||||
];
|
||||
|
||||
return ffmpegFilterImage(args, {
|
||||
console: debugConsole,
|
||||
ffmpegPath: await sdk.mediaManager.getFFmpegPath(),
|
||||
resize: width === undefined && height === undefined
|
||||
? undefined
|
||||
: {
|
||||
width,
|
||||
height,
|
||||
fractional,
|
||||
},
|
||||
crop: left === undefined || right === undefined || top === undefined || bottom === undefined
|
||||
? undefined
|
||||
: {
|
||||
left,
|
||||
top,
|
||||
width: right - left,
|
||||
height: bottom - top,
|
||||
fractional: cropFractional,
|
||||
},
|
||||
timeout: 10000,
|
||||
time,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user