@xyo-network/image-thumbnail-plugin 2.75.0 → 2.75.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs.json +50260 -0
- package/dist/node/Diviner/Config.d.cts +11 -0
- package/dist/node/Diviner/Config.d.cts.map +1 -0
- package/dist/node/Diviner/Config.js +3 -1
- package/dist/node/Diviner/Config.js.map +1 -1
- package/dist/node/Diviner/Config.mjs +2 -1
- package/dist/node/Diviner/Config.mjs.map +1 -1
- package/dist/node/Diviner/Diviner.d.cts +30 -0
- package/dist/node/Diviner/Diviner.d.cts.map +1 -0
- package/dist/node/Diviner/Diviner.js +19 -11
- package/dist/node/Diviner/Diviner.js.map +1 -1
- package/dist/node/Diviner/Diviner.mjs +17 -10
- package/dist/node/Diviner/Diviner.mjs.map +1 -1
- package/dist/node/Diviner/Params.d.cts +5 -0
- package/dist/node/Diviner/Params.d.cts.map +1 -0
- package/dist/node/Diviner/Params.js +2 -0
- package/dist/node/Diviner/Params.js.map +1 -1
- package/dist/node/Diviner/index.d.cts +4 -0
- package/dist/node/Diviner/index.d.cts.map +1 -0
- package/dist/node/Diviner/index.js +189 -7
- package/dist/node/Diviner/index.js.map +1 -1
- package/dist/node/Diviner/index.mjs +180 -3
- package/dist/node/Diviner/index.mjs.map +1 -1
- package/dist/node/Plugin.d.cts +116 -0
- package/dist/node/Plugin.d.cts.map +1 -0
- package/dist/node/Plugin.js +517 -7
- package/dist/node/Plugin.js.map +1 -1
- package/dist/node/Plugin.mjs +504 -5
- package/dist/node/Plugin.mjs.map +1 -1
- package/dist/node/Witness/Config.d.cts +16 -0
- package/dist/node/Witness/Config.d.cts.map +1 -0
- package/dist/node/Witness/Config.js +3 -1
- package/dist/node/Witness/Config.js.map +1 -1
- package/dist/node/Witness/Config.mjs +2 -1
- package/dist/node/Witness/Config.mjs.map +1 -1
- package/dist/node/Witness/Params.d.cts +7 -0
- package/dist/node/Witness/Params.d.cts.map +1 -0
- package/dist/node/Witness/Params.js +2 -0
- package/dist/node/Witness/Params.js.map +1 -1
- package/dist/node/Witness/Witness.d.cts +66 -0
- package/dist/node/Witness/Witness.d.cts.map +1 -0
- package/dist/node/Witness/Witness.js +78 -29
- package/dist/node/Witness/Witness.js.map +1 -1
- package/dist/node/Witness/Witness.mjs +70 -22
- package/dist/node/Witness/Witness.mjs.map +1 -1
- package/dist/node/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.d.cts +8 -0
- package/dist/node/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.d.cts.map +1 -0
- package/dist/node/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.js +5 -3
- package/dist/node/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.js.map +1 -1
- package/dist/node/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.mjs +4 -3
- package/dist/node/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.mjs.map +1 -1
- package/dist/node/Witness/ffmpeg/fluent/index.d.cts +2 -0
- package/dist/node/Witness/ffmpeg/fluent/index.d.cts.map +1 -0
- package/dist/node/Witness/ffmpeg/fluent/index.js +59 -3
- package/dist/node/Witness/ffmpeg/fluent/index.js.map +1 -1
- package/dist/node/Witness/ffmpeg/fluent/index.mjs +41 -1
- package/dist/node/Witness/ffmpeg/fluent/index.mjs.map +1 -1
- package/dist/node/Witness/ffmpeg/index.d.cts +3 -0
- package/dist/node/Witness/ffmpeg/index.d.cts.map +1 -0
- package/dist/node/Witness/ffmpeg/index.js +89 -5
- package/dist/node/Witness/ffmpeg/index.js.map +1 -1
- package/dist/node/Witness/ffmpeg/index.mjs +69 -2
- package/dist/node/Witness/ffmpeg/index.mjs.map +1 -1
- package/dist/node/Witness/ffmpeg/spawn/executeFfmpeg.d.cts +9 -0
- package/dist/node/Witness/ffmpeg/spawn/executeFfmpeg.d.cts.map +1 -0
- package/dist/node/Witness/ffmpeg/spawn/executeFfmpeg.js +3 -1
- package/dist/node/Witness/ffmpeg/spawn/executeFfmpeg.js.map +1 -1
- package/dist/node/Witness/ffmpeg/spawn/executeFfmpeg.mjs +2 -1
- package/dist/node/Witness/ffmpeg/spawn/executeFfmpeg.mjs.map +1 -1
- package/dist/node/Witness/ffmpeg/spawn/getVideoFrameAsImage.d.cts +8 -0
- package/dist/node/Witness/ffmpeg/spawn/getVideoFrameAsImage.d.cts.map +1 -0
- package/dist/node/Witness/ffmpeg/spawn/getVideoFrameAsImage.js +26 -3
- package/dist/node/Witness/ffmpeg/spawn/getVideoFrameAsImage.js.map +1 -1
- package/dist/node/Witness/ffmpeg/spawn/getVideoFrameAsImage.mjs +22 -2
- package/dist/node/Witness/ffmpeg/spawn/getVideoFrameAsImage.mjs.map +1 -1
- package/dist/node/Witness/ffmpeg/spawn/index.d.cts +3 -0
- package/dist/node/Witness/ffmpeg/spawn/index.d.cts.map +1 -0
- package/dist/node/Witness/ffmpeg/spawn/index.js +38 -5
- package/dist/node/Witness/ffmpeg/spawn/index.js.map +1 -1
- package/dist/node/Witness/ffmpeg/spawn/index.mjs +29 -2
- package/dist/node/Witness/ffmpeg/spawn/index.mjs.map +1 -1
- package/dist/node/Witness/index.d.cts +4 -0
- package/dist/node/Witness/index.d.cts.map +1 -0
- package/dist/node/Witness/index.js +343 -7
- package/dist/node/Witness/index.js.map +1 -1
- package/dist/node/Witness/index.mjs +324 -3
- package/dist/node/Witness/index.mjs.map +1 -1
- package/dist/node/index.d.cts +6 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.js +542 -8
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +523 -3
- package/dist/node/index.mjs.map +1 -1
- package/package.json +19 -19
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Params.d.ts","sourceRoot":"","sources":["../../../src/Witness/Params.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,OAAO,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAA;AAEtD,MAAM,MAAM,2BAA2B,GAAG,aAAa,CACrD,eAAe,CAAC,2BAA2B,CAAC,EAC5C;IACE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB,CACF,CAAA"}
|
|
@@ -12,6 +12,8 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
12
12
|
return to;
|
|
13
13
|
};
|
|
14
14
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
|
|
16
|
+
// src/Witness/Params.ts
|
|
15
17
|
var Params_exports = {};
|
|
16
18
|
module.exports = __toCommonJS(Params_exports);
|
|
17
19
|
//# sourceMappingURL=Params.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/Witness/Params.ts"],"sourcesContent":["import { AnyConfigSchema } from '@xyo-network/module'\nimport { WitnessParams } from '@xyo-network/witness'\n\nimport { ImageThumbnailWitnessConfig } from './Config'\n\nexport type ImageThumbnailWitnessParams = WitnessParams<\n AnyConfigSchema<ImageThumbnailWitnessConfig>,\n {\n loaders?: string[]\n }\n>\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/Witness/Params.ts"],"sourcesContent":["import { AnyConfigSchema } from '@xyo-network/module'\nimport { WitnessParams } from '@xyo-network/witness'\n\nimport { ImageThumbnailWitnessConfig } from './Config'\n\nexport type ImageThumbnailWitnessParams = WitnessParams<\n AnyConfigSchema<ImageThumbnailWitnessConfig>,\n {\n loaders?: string[]\n }\n>\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ImageThumbnail } from '@xyo-network/image-thumbnail-payload-plugin';
|
|
2
|
+
import { UrlPayload } from '@xyo-network/url-payload-plugin';
|
|
3
|
+
import { AbstractWitness } from '@xyo-network/witness';
|
|
4
|
+
import { LRUCache } from 'lru-cache';
|
|
5
|
+
import { ImageThumbnailEncoding } from './Config';
|
|
6
|
+
import { ImageThumbnailWitnessParams } from './Params';
|
|
7
|
+
export interface ImageThumbnailWitnessError extends Error {
|
|
8
|
+
name: 'ImageThumbnailWitnessError';
|
|
9
|
+
url: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DnsError extends Error {
|
|
12
|
+
code: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams = ImageThumbnailWitnessParams> extends AbstractWitness<TParams> {
|
|
15
|
+
static configSchemas: "network.xyo.image.thumbnail.witness.config"[];
|
|
16
|
+
private _cache?;
|
|
17
|
+
private _semaphore;
|
|
18
|
+
get cache(): LRUCache<string, import("@xyo-network/payload-model").SchemaFields & import("@xyo-network/payload-model").PayloadFields & {
|
|
19
|
+
http?: {
|
|
20
|
+
dnsError?: string | undefined;
|
|
21
|
+
ipAddress?: string | undefined;
|
|
22
|
+
status?: number | undefined;
|
|
23
|
+
} | undefined;
|
|
24
|
+
mime?: {
|
|
25
|
+
detected?: {
|
|
26
|
+
ext?: string | undefined;
|
|
27
|
+
mime?: string | undefined;
|
|
28
|
+
} | undefined;
|
|
29
|
+
invalid?: boolean | undefined;
|
|
30
|
+
returned?: string | undefined;
|
|
31
|
+
type?: string | undefined;
|
|
32
|
+
} | undefined;
|
|
33
|
+
sourceHash?: string | undefined;
|
|
34
|
+
sourceUrl: string;
|
|
35
|
+
url?: string | undefined;
|
|
36
|
+
} & {
|
|
37
|
+
schema: "network.xyo.image.thumbnail";
|
|
38
|
+
}, unknown>;
|
|
39
|
+
get encoding(): ImageThumbnailEncoding;
|
|
40
|
+
get height(): number;
|
|
41
|
+
get ipfGateway(): string;
|
|
42
|
+
get maxAsyncProcesses(): number;
|
|
43
|
+
get maxCacheBytes(): number;
|
|
44
|
+
get maxCacheEntries(): number;
|
|
45
|
+
get quality(): number;
|
|
46
|
+
get width(): number;
|
|
47
|
+
private static binaryToSha256;
|
|
48
|
+
private static bufferFromDataUrl;
|
|
49
|
+
/**
|
|
50
|
+
* Returns the equivalent IPFS gateway URL for the supplied URL.
|
|
51
|
+
* @param urlToCheck The URL to check
|
|
52
|
+
* @returns If the supplied URL is an IPFS URL, it converts the URL to the
|
|
53
|
+
* equivalent IPFS gateway URL. Otherwise, returns the original URL.
|
|
54
|
+
*/
|
|
55
|
+
checkIpfsUrl(urlToCheck: string): string;
|
|
56
|
+
protected observeHandler(payloads?: UrlPayload[]): Promise<ImageThumbnail[]>;
|
|
57
|
+
private createThumbnailDataUrl;
|
|
58
|
+
/**
|
|
59
|
+
* Creates an image thumbnail from a video.
|
|
60
|
+
* @param videoBuffer The input video buffer.
|
|
61
|
+
* @returns An buffer containing an image thumbnail for the video.
|
|
62
|
+
*/
|
|
63
|
+
private createThumbnailFromVideo;
|
|
64
|
+
private fromHttp;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=Witness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Witness.d.ts","sourceRoot":"","sources":["../../../src/Witness/Witness.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,cAAc,EAAwB,MAAM,6CAA6C,CAAA;AAClG,OAAO,EAAE,UAAU,EAAa,MAAM,iCAAiC,CAAA;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAMtD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAIpC,OAAO,EAAE,sBAAsB,EAAqC,MAAM,UAAU,CAAA;AAEpF,OAAO,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAA;AAStD,MAAM,WAAW,0BAA2B,SAAQ,KAAK;IACvD,IAAI,EAAE,4BAA4B,CAAA;IAClC,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,QAAS,SAAQ,KAAK;IACrC,IAAI,EAAE,MAAM,CAAA;CACb;AAED,qBAAa,qBAAqB,CAAC,OAAO,SAAS,2BAA2B,GAAG,2BAA2B,CAAE,SAAQ,eAAe,CAAC,OAAO,CAAC;IAC5I,OAAgB,aAAa,iDAAsC;IAEnE,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,UAAU,CAAwC;IAE1D,IAAI,KAAK;;;;;;;;;;;;;;;;;;;;gBAUR;IAED,IAAI,QAAQ,2BAEX;IAED,IAAI,MAAM,WAET;IAED,IAAI,UAAU,WAEb;IAED,IAAI,iBAAiB,WAEpB;IAED,IAAI,aAAa,WAEhB;IAED,IAAI,eAAe,WAElB;IAED,IAAI,OAAO,WAEV;IAED,IAAI,KAAK,WAER;mBAEoB,cAAc;IAanC,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAgBhC;;;;;OAKG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM;cAiBN,cAAc,CAAC,QAAQ,GAAE,UAAU,EAAO,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;YAuCjF,sBAAsB;IAiBpC;;;;OAIG;YACW,wBAAwB;YAKxB,QAAQ;CA6HvB"}
|
|
@@ -26,17 +26,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
mod
|
|
27
27
|
));
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/Witness/Witness.ts
|
|
29
31
|
var Witness_exports = {};
|
|
30
32
|
__export(Witness_exports, {
|
|
31
33
|
ImageThumbnailWitness: () => ImageThumbnailWitness
|
|
32
34
|
});
|
|
33
35
|
module.exports = __toCommonJS(Witness_exports);
|
|
34
|
-
var import_node_dns = require("
|
|
36
|
+
var import_node_dns = require("dns");
|
|
35
37
|
var import_lodash = require("@xylabs/lodash");
|
|
36
38
|
var import_url = require("@xylabs/url");
|
|
37
39
|
var import_axios = require("@xyo-network/axios");
|
|
38
|
-
var
|
|
39
|
-
var
|
|
40
|
+
var import_core2 = require("@xyo-network/core");
|
|
41
|
+
var import_image_thumbnail_payload_plugin2 = require("@xyo-network/image-thumbnail-payload-plugin");
|
|
40
42
|
var import_url_payload_plugin = require("@xyo-network/url-payload-plugin");
|
|
41
43
|
var import_witness = require("@xyo-network/witness");
|
|
42
44
|
var import_async_mutex = require("async-mutex");
|
|
@@ -47,11 +49,54 @@ var import_hash_wasm = require("hash-wasm");
|
|
|
47
49
|
var import_lru_cache = require("lru-cache");
|
|
48
50
|
var import_sha = __toESM(require("sha.js"));
|
|
49
51
|
var import_url_parse = __toESM(require("url-parse"));
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
|
|
53
|
+
// src/Witness/Config.ts
|
|
54
|
+
var import_image_thumbnail_payload_plugin = require("@xyo-network/image-thumbnail-payload-plugin");
|
|
55
|
+
var ImageThumbnailWitnessConfigSchema = `${import_image_thumbnail_payload_plugin.ImageThumbnailSchema}.witness.config`;
|
|
56
|
+
|
|
57
|
+
// src/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.ts
|
|
58
|
+
var import_core = require("@xyo-network/core");
|
|
59
|
+
var import_fluent_ffmpeg = __toESM(require("fluent-ffmpeg"));
|
|
60
|
+
var import_promises = require("fs/promises");
|
|
61
|
+
var import_os = require("os");
|
|
62
|
+
var import_stream = require("stream");
|
|
63
|
+
var FfmpegOutputStream = class extends import_stream.Writable {
|
|
64
|
+
chunks = [];
|
|
65
|
+
constructor(options) {
|
|
66
|
+
super(options);
|
|
67
|
+
}
|
|
68
|
+
_write(chunk, _encoding, callback) {
|
|
69
|
+
this.chunks.push(chunk);
|
|
70
|
+
callback();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Collects the output from ffmpeg into a buffer.
|
|
74
|
+
* @returns A buffer containing the concatenated
|
|
75
|
+
* output from ffmpeg.
|
|
76
|
+
*/
|
|
77
|
+
toBuffer = () => Buffer.concat(this.chunks);
|
|
78
|
+
};
|
|
79
|
+
var getVideoFrameAsImageFluent = async (videoBuffer) => {
|
|
80
|
+
const tmpFile = `/${(0, import_os.tmpdir)()}/${(0, import_core.uuid)()}`;
|
|
81
|
+
try {
|
|
82
|
+
await (0, import_promises.writeFile)(tmpFile, videoBuffer, { encoding: "binary" });
|
|
83
|
+
const imageBuffer = await new Promise((resolve, reject) => {
|
|
84
|
+
const ffmpegOutput = new FfmpegOutputStream();
|
|
85
|
+
(0, import_fluent_ffmpeg.default)().on("error", (err) => reject(err.message)).on("end", () => resolve(ffmpegOutput.toBuffer())).input(tmpFile).takeFrames(1).withNoAudio().outputOptions("-f image2pipe").videoCodec("png").pipe(ffmpegOutput);
|
|
86
|
+
});
|
|
87
|
+
return imageBuffer;
|
|
88
|
+
} finally {
|
|
89
|
+
try {
|
|
90
|
+
await (0, import_promises.unlink)(tmpFile);
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/Witness/Witness.ts
|
|
97
|
+
var gm = import_gm.default.subClass({ imageMagick: "7+" });
|
|
98
|
+
var ImageThumbnailWitness = class _ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
99
|
+
static configSchemas = [ImageThumbnailWitnessConfigSchema];
|
|
55
100
|
_cache;
|
|
56
101
|
_semaphore = new import_async_mutex.Semaphore(this.maxAsyncProcesses);
|
|
57
102
|
get cache() {
|
|
@@ -59,7 +104,10 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
59
104
|
max: this.maxCacheEntries,
|
|
60
105
|
maxSize: this.maxCacheBytes,
|
|
61
106
|
//just returning the size of the data
|
|
62
|
-
sizeCalculation: (value) =>
|
|
107
|
+
sizeCalculation: (value) => {
|
|
108
|
+
var _a;
|
|
109
|
+
return ((_a = value.url) == null ? void 0 : _a.length) ?? 1;
|
|
110
|
+
}
|
|
63
111
|
});
|
|
64
112
|
return this._cache;
|
|
65
113
|
}
|
|
@@ -88,12 +136,12 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
88
136
|
return this.config.width ?? 128;
|
|
89
137
|
}
|
|
90
138
|
static async binaryToSha256(data) {
|
|
91
|
-
await
|
|
92
|
-
if (
|
|
139
|
+
await import_core2.PayloadHasher.wasmInitialized;
|
|
140
|
+
if (import_core2.PayloadHasher.wasmSupport.canUseWasm) {
|
|
93
141
|
try {
|
|
94
142
|
return await (0, import_hash_wasm.sha256)(data);
|
|
95
143
|
} catch (ex) {
|
|
96
|
-
|
|
144
|
+
import_core2.PayloadHasher.wasmSupport.allowWasm = false;
|
|
97
145
|
}
|
|
98
146
|
}
|
|
99
147
|
return (0, import_sha.default)("sha256").update(data).digest().toString();
|
|
@@ -130,7 +178,7 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
130
178
|
host = this.ipfGateway;
|
|
131
179
|
path = url.host === "ipfs" ? `ipfs${path}` : `ipfs/${url.host}${path}`;
|
|
132
180
|
const root = `${protocol}//${host}/${path}`;
|
|
133
|
-
return query
|
|
181
|
+
return (query == null ? void 0 : query.length) > 0 ? `${root}?${query}` : root;
|
|
134
182
|
} else {
|
|
135
183
|
return urlToCheck;
|
|
136
184
|
}
|
|
@@ -149,11 +197,11 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
149
197
|
return cachedResult;
|
|
150
198
|
}
|
|
151
199
|
let result;
|
|
152
|
-
const dataBuffer =
|
|
200
|
+
const dataBuffer = _ImageThumbnailWitness.bufferFromDataUrl(url);
|
|
153
201
|
if (dataBuffer) {
|
|
154
202
|
result = {
|
|
155
|
-
schema:
|
|
156
|
-
sourceHash: await
|
|
203
|
+
schema: import_image_thumbnail_payload_plugin2.ImageThumbnailSchema,
|
|
204
|
+
sourceHash: await _ImageThumbnailWitness.binaryToSha256(dataBuffer),
|
|
157
205
|
sourceUrl: url,
|
|
158
206
|
url
|
|
159
207
|
};
|
|
@@ -186,10 +234,11 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
186
234
|
* @returns An buffer containing an image thumbnail for the video.
|
|
187
235
|
*/
|
|
188
236
|
async createThumbnailFromVideo(videoBuffer) {
|
|
189
|
-
const imageBuffer = await
|
|
237
|
+
const imageBuffer = await getVideoFrameAsImageFluent(videoBuffer);
|
|
190
238
|
return this.createThumbnailDataUrl(imageBuffer);
|
|
191
239
|
}
|
|
192
240
|
async fromHttp(url, sourceUrl) {
|
|
241
|
+
var _a, _b, _c, _d, _e, _f;
|
|
193
242
|
let response;
|
|
194
243
|
let dnsResult;
|
|
195
244
|
try {
|
|
@@ -201,7 +250,7 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
201
250
|
http: {
|
|
202
251
|
dnsError: error.code
|
|
203
252
|
},
|
|
204
|
-
schema:
|
|
253
|
+
schema: import_image_thumbnail_payload_plugin2.ImageThumbnailSchema,
|
|
205
254
|
sourceUrl: sourceUrl ?? url
|
|
206
255
|
};
|
|
207
256
|
return result2;
|
|
@@ -216,9 +265,9 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
216
265
|
const result2 = {
|
|
217
266
|
http: {
|
|
218
267
|
ipAddress: dnsResult[0],
|
|
219
|
-
status: axiosError
|
|
268
|
+
status: (_a = axiosError == null ? void 0 : axiosError.response) == null ? void 0 : _a.status
|
|
220
269
|
},
|
|
221
|
-
schema:
|
|
270
|
+
schema: import_image_thumbnail_payload_plugin2.ImageThumbnailSchema,
|
|
222
271
|
sourceUrl: sourceUrl ?? url
|
|
223
272
|
};
|
|
224
273
|
return result2;
|
|
@@ -230,11 +279,11 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
230
279
|
http: {
|
|
231
280
|
status: response.status
|
|
232
281
|
},
|
|
233
|
-
schema:
|
|
282
|
+
schema: import_image_thumbnail_payload_plugin2.ImageThumbnailSchema,
|
|
234
283
|
sourceUrl: sourceUrl ?? url
|
|
235
284
|
};
|
|
236
285
|
if (response.status >= 200 && response.status < 300) {
|
|
237
|
-
const contentType = response.headers["content-type"]
|
|
286
|
+
const contentType = (_b = response.headers["content-type"]) == null ? void 0 : _b.toString();
|
|
238
287
|
const [mediaType, fileType] = contentType.split("/");
|
|
239
288
|
result.mime = result.mime ?? {};
|
|
240
289
|
result.mime.returned = mediaType;
|
|
@@ -243,15 +292,15 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
243
292
|
result.mime.detected = await import_file_type.default.fromBuffer(sourceBuffer);
|
|
244
293
|
} catch (ex) {
|
|
245
294
|
const error = ex;
|
|
246
|
-
this.logger
|
|
295
|
+
(_c = this.logger) == null ? void 0 : _c.error(`FileType error: ${error.message}`);
|
|
247
296
|
}
|
|
248
297
|
const processImage = async (encoding2) => {
|
|
249
|
-
result.sourceHash = await
|
|
298
|
+
result.sourceHash = await _ImageThumbnailWitness.binaryToSha256(sourceBuffer);
|
|
250
299
|
result.url = await this.createThumbnailDataUrl(sourceBuffer, encoding2);
|
|
251
300
|
};
|
|
252
301
|
const processVideo = async () => {
|
|
253
302
|
if (import_hasbin.default.sync("ffmpeg")) {
|
|
254
|
-
result.sourceHash = await
|
|
303
|
+
result.sourceHash = await _ImageThumbnailWitness.binaryToSha256(sourceBuffer);
|
|
255
304
|
result.url = await this.createThumbnailFromVideo(sourceBuffer);
|
|
256
305
|
} else {
|
|
257
306
|
result.mime = result.mime ?? {};
|
|
@@ -280,15 +329,15 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
280
329
|
break;
|
|
281
330
|
}
|
|
282
331
|
default: {
|
|
283
|
-
switch (result.mime.detected
|
|
332
|
+
switch ((_d = result.mime.detected) == null ? void 0 : _d.mime) {
|
|
284
333
|
case "image": {
|
|
285
334
|
await processImage();
|
|
286
|
-
result.mime.type = result.mime.detected
|
|
335
|
+
result.mime.type = (_e = result.mime.detected) == null ? void 0 : _e.mime;
|
|
287
336
|
break;
|
|
288
337
|
}
|
|
289
338
|
case "video": {
|
|
290
339
|
await processVideo();
|
|
291
|
-
result.mime.type = result.mime.detected
|
|
340
|
+
result.mime.type = (_f = result.mime.detected) == null ? void 0 : _f.mime;
|
|
292
341
|
break;
|
|
293
342
|
}
|
|
294
343
|
default: {
|
|
@@ -302,7 +351,7 @@ class ImageThumbnailWitness extends import_witness.AbstractWitness {
|
|
|
302
351
|
}
|
|
303
352
|
return result;
|
|
304
353
|
}
|
|
305
|
-
}
|
|
354
|
+
};
|
|
306
355
|
// Annotate the CommonJS export names for ESM import in node:
|
|
307
356
|
0 && (module.exports = {
|
|
308
357
|
ImageThumbnailWitness
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/Witness/Witness.ts"],"sourcesContent":["/* eslint-disable max-statements */\nimport { promises as dnsPromises } from 'node:dns'\n\nimport { compact } from '@xylabs/lodash'\nimport { URL } from '@xylabs/url'\nimport { axios, AxiosError, AxiosResponse } from '@xyo-network/axios'\nimport { PayloadHasher } from '@xyo-network/core'\nimport { ImageThumbnail, ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin'\nimport { UrlPayload, UrlSchema } from '@xyo-network/url-payload-plugin'\nimport { AbstractWitness } from '@xyo-network/witness'\nimport { Semaphore } from 'async-mutex'\nimport FileType from 'file-type'\nimport graphicsMagick from 'gm'\nimport hasbin from 'hasbin'\nimport { sha256 } from 'hash-wasm'\nimport { LRUCache } from 'lru-cache'\nimport shajs from 'sha.js'\nimport Url from 'url-parse'\n\nimport { ImageThumbnailEncoding, ImageThumbnailWitnessConfigSchema } from './Config'\nimport { getVideoFrameAsImageFluent } from './ffmpeg'\nimport { ImageThumbnailWitnessParams } from './Params'\n\n//TODO: Break this into two Witnesses?\n\n// setFfmpegPath(ffmpegPath)\n\n// eslint-disable-next-line import/no-named-as-default-member\nconst gm = graphicsMagick.subClass({ imageMagick: '7+' })\n\nexport interface ImageThumbnailWitnessError extends Error {\n name: 'ImageThumbnailWitnessError'\n url: string\n}\n\nexport interface DnsError extends Error {\n code: string\n}\n\nexport class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams = ImageThumbnailWitnessParams> extends AbstractWitness<TParams> {\n static override configSchemas = [ImageThumbnailWitnessConfigSchema]\n\n private _cache?: LRUCache<string, ImageThumbnail>\n private _semaphore = new Semaphore(this.maxAsyncProcesses)\n\n get cache() {\n this._cache =\n this._cache ??\n new LRUCache<string, ImageThumbnail>({\n max: this.maxCacheEntries,\n maxSize: this.maxCacheBytes,\n //just returning the size of the data\n sizeCalculation: (value) => value.url?.length ?? 1,\n })\n return this._cache\n }\n\n get encoding() {\n return this.config.encoding ?? 'PNG'\n }\n\n get height() {\n return this.config.height ?? 128\n }\n\n get ipfGateway() {\n return this.config.ipfsGateway ?? '5d7b6582.beta.decentralnetworkservices.com'\n }\n\n get maxAsyncProcesses() {\n return this.config.maxAsyncProcesses ?? 2\n }\n\n get maxCacheBytes() {\n return this.config.maxCacheBytes ?? 1024 * 1024 * 16 //64MB max size\n }\n\n get maxCacheEntries() {\n return this.config.maxCacheEntries ?? 500\n }\n\n get quality() {\n return this.config.quality ?? 50\n }\n\n get width() {\n return this.config.width ?? 128\n }\n\n private static async binaryToSha256(data: Uint8Array) {\n await PayloadHasher.wasmInitialized\n if (PayloadHasher.wasmSupport.canUseWasm) {\n try {\n return await sha256(data)\n } catch (ex) {\n PayloadHasher.wasmSupport.allowWasm = false\n }\n }\n\n return shajs('sha256').update(data).digest().toString()\n }\n\n private static bufferFromDataUrl(url: string): Buffer | undefined {\n if (url.startsWith('data:image')) {\n const data = url.split(',')[1]\n if (data) {\n return Buffer.from(Uint8Array.from(atob(data), (c) => c.charCodeAt(0)))\n } else {\n const error: ImageThumbnailWitnessError = {\n message: 'Invalid data Url',\n name: 'ImageThumbnailWitnessError',\n url,\n }\n throw error\n }\n }\n }\n\n /**\n * Returns the equivalent IPFS gateway URL for the supplied URL.\n * @param urlToCheck The URL to check\n * @returns If the supplied URL is an IPFS URL, it converts the URL to the\n * equivalent IPFS gateway URL. Otherwise, returns the original URL.\n */\n checkIpfsUrl(urlToCheck: string) {\n const url = new URL(urlToCheck)\n let protocol = url.protocol\n let host = url.host\n let path = url.pathname\n const query = url.search\n if (protocol === 'ipfs:') {\n protocol = 'https:'\n host = this.ipfGateway\n path = url.host === 'ipfs' ? `ipfs${path}` : `ipfs/${url.host}${path}`\n const root = `${protocol}//${host}/${path}`\n return query?.length > 0 ? `${root}?${query}` : root\n } else {\n return urlToCheck\n }\n }\n\n protected override async observeHandler(payloads: UrlPayload[] = []): Promise<ImageThumbnail[]> {\n // eslint-disable-next-line import/no-named-as-default-member\n if (!hasbin.sync('magick')) {\n throw Error('ImageMagick is required for this witness')\n }\n const urlPayloads = payloads.filter((payload) => payload.schema === UrlSchema)\n return await this._semaphore.runExclusive(async () =>\n compact(\n await Promise.all(\n urlPayloads.map<Promise<ImageThumbnail>>(async ({ url }) => {\n const cachedResult = this.cache.get(url)\n if (cachedResult) {\n return cachedResult\n }\n let result: ImageThumbnail\n\n //if it is a data URL, return a Buffer\n const dataBuffer = ImageThumbnailWitness.bufferFromDataUrl(url)\n\n if (dataBuffer) {\n result = {\n schema: ImageThumbnailSchema,\n sourceHash: await ImageThumbnailWitness.binaryToSha256(dataBuffer),\n sourceUrl: url,\n url,\n }\n } else {\n //if it is ipfs, go through cloud flair\n const mutatedUrl = this.checkIpfsUrl(url)\n result = await this.fromHttp(mutatedUrl, url)\n }\n this.cache.set(url, result)\n return result\n }),\n ),\n ),\n )\n }\n\n private async createThumbnailDataUrl(sourceBuffer: Buffer, encoding?: ImageThumbnailEncoding) {\n const thumb = await new Promise<Buffer>((resolve, reject) => {\n gm(sourceBuffer)\n .quality(this.quality)\n .resize(this.width, this.height)\n .flatten()\n .toBuffer(encoding ?? this.encoding, (error, buffer) => {\n if (error) {\n reject(error)\n } else {\n resolve(buffer)\n }\n })\n })\n return `data:image/png;base64,${thumb.toString('base64')}`\n }\n\n /**\n * Creates an image thumbnail from a video.\n * @param videoBuffer The input video buffer.\n * @returns An buffer containing an image thumbnail for the video.\n */\n private async createThumbnailFromVideo(videoBuffer: Buffer) {\n const imageBuffer = await getVideoFrameAsImageFluent(videoBuffer)\n return this.createThumbnailDataUrl(imageBuffer)\n }\n\n private async fromHttp(url: string, sourceUrl?: string): Promise<ImageThumbnail> {\n let response: AxiosResponse\n let dnsResult: string[]\n try {\n const urlObj = new Url(url)\n dnsResult = await dnsPromises.resolve(urlObj.host)\n // console.log(`dnsResult: ${JSON.stringify(dnsResult, null, 2)}`)\n } catch (ex) {\n const error = ex as DnsError\n const result: ImageThumbnail = {\n http: {\n dnsError: error.code,\n },\n schema: ImageThumbnailSchema,\n sourceUrl: sourceUrl ?? url,\n }\n return result\n }\n try {\n response = await axios.get(url, {\n responseType: 'arraybuffer',\n })\n } catch (ex) {\n const axiosError = ex as AxiosError\n if (axiosError.isAxiosError) {\n //selectively pick fields from AxiosError\n const result: ImageThumbnail = {\n http: {\n ipAddress: dnsResult[0],\n status: axiosError?.response?.status,\n },\n schema: ImageThumbnailSchema,\n sourceUrl: sourceUrl ?? url,\n }\n return result\n } else {\n throw ex\n }\n }\n\n const result: ImageThumbnail = {\n http: {\n status: response.status,\n },\n schema: ImageThumbnailSchema,\n sourceUrl: sourceUrl ?? url,\n }\n\n if (response.status >= 200 && response.status < 300) {\n const contentType: string = response.headers['content-type']?.toString()\n const [mediaType, fileType] = contentType.split('/')\n result.mime = result.mime ?? {}\n result.mime.returned = mediaType\n const sourceBuffer = Buffer.from(response.data, 'binary')\n\n try {\n result.mime.detected = await FileType.fromBuffer(sourceBuffer)\n } catch (ex) {\n const error = ex as Error\n this.logger?.error(`FileType error: ${error.message}`)\n }\n\n const processImage = async (encoding?: ImageThumbnailEncoding) => {\n result.sourceHash = await ImageThumbnailWitness.binaryToSha256(sourceBuffer)\n result.url = await this.createThumbnailDataUrl(sourceBuffer, encoding)\n }\n\n const processVideo = async () => {\n // Gracefully handle the case where ffmpeg is not installed.\n // eslint-disable-next-line import/no-named-as-default-member\n if (hasbin.sync('ffmpeg')) {\n result.sourceHash = await ImageThumbnailWitness.binaryToSha256(sourceBuffer)\n result.url = await this.createThumbnailFromVideo(sourceBuffer)\n } else {\n result.mime = result.mime ?? {}\n result.mime.invalid = true\n }\n }\n\n let encoding: ImageThumbnailEncoding = 'PNG'\n\n switch (fileType.toUpperCase()) {\n case 'GIF':\n encoding = 'GIF'\n break\n case 'JPG':\n case 'JPEG':\n encoding = 'JPG'\n break\n }\n\n switch (mediaType) {\n case 'image': {\n await processImage(encoding)\n result.mime.type = mediaType\n break\n }\n case 'video': {\n await processVideo()\n result.mime.type = mediaType\n break\n }\n default: {\n switch (result.mime.detected?.mime) {\n case 'image': {\n await processImage()\n result.mime.type = result.mime.detected?.mime\n break\n }\n case 'video': {\n await processVideo()\n result.mime.type = result.mime.detected?.mime\n break\n }\n default: {\n result.mime.invalid = true\n break\n }\n }\n break\n }\n }\n }\n return result\n }\n}\n"],"mappings":"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","names":["graphicsMagick","shajs","hasbin","Url","dnsPromises","result","FileType","encoding"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/Witness/Witness.ts","../../../src/Witness/Config.ts","../../../src/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.ts"],"sourcesContent":["/* eslint-disable max-statements */\nimport { promises as dnsPromises } from 'node:dns'\n\nimport { compact } from '@xylabs/lodash'\nimport { URL } from '@xylabs/url'\nimport { axios, AxiosError, AxiosResponse } from '@xyo-network/axios'\nimport { PayloadHasher } from '@xyo-network/core'\nimport { ImageThumbnail, ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin'\nimport { UrlPayload, UrlSchema } from '@xyo-network/url-payload-plugin'\nimport { AbstractWitness } from '@xyo-network/witness'\nimport { Semaphore } from 'async-mutex'\nimport FileType from 'file-type'\nimport graphicsMagick from 'gm'\nimport hasbin from 'hasbin'\nimport { sha256 } from 'hash-wasm'\nimport { LRUCache } from 'lru-cache'\nimport shajs from 'sha.js'\nimport Url from 'url-parse'\n\nimport { ImageThumbnailEncoding, ImageThumbnailWitnessConfigSchema } from './Config'\nimport { getVideoFrameAsImageFluent } from './ffmpeg'\nimport { ImageThumbnailWitnessParams } from './Params'\n\n//TODO: Break this into two Witnesses?\n\n// setFfmpegPath(ffmpegPath)\n\n// eslint-disable-next-line import/no-named-as-default-member\nconst gm = graphicsMagick.subClass({ imageMagick: '7+' })\n\nexport interface ImageThumbnailWitnessError extends Error {\n name: 'ImageThumbnailWitnessError'\n url: string\n}\n\nexport interface DnsError extends Error {\n code: string\n}\n\nexport class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams = ImageThumbnailWitnessParams> extends AbstractWitness<TParams> {\n static override configSchemas = [ImageThumbnailWitnessConfigSchema]\n\n private _cache?: LRUCache<string, ImageThumbnail>\n private _semaphore = new Semaphore(this.maxAsyncProcesses)\n\n get cache() {\n this._cache =\n this._cache ??\n new LRUCache<string, ImageThumbnail>({\n max: this.maxCacheEntries,\n maxSize: this.maxCacheBytes,\n //just returning the size of the data\n sizeCalculation: (value) => value.url?.length ?? 1,\n })\n return this._cache\n }\n\n get encoding() {\n return this.config.encoding ?? 'PNG'\n }\n\n get height() {\n return this.config.height ?? 128\n }\n\n get ipfGateway() {\n return this.config.ipfsGateway ?? '5d7b6582.beta.decentralnetworkservices.com'\n }\n\n get maxAsyncProcesses() {\n return this.config.maxAsyncProcesses ?? 2\n }\n\n get maxCacheBytes() {\n return this.config.maxCacheBytes ?? 1024 * 1024 * 16 //64MB max size\n }\n\n get maxCacheEntries() {\n return this.config.maxCacheEntries ?? 500\n }\n\n get quality() {\n return this.config.quality ?? 50\n }\n\n get width() {\n return this.config.width ?? 128\n }\n\n private static async binaryToSha256(data: Uint8Array) {\n await PayloadHasher.wasmInitialized\n if (PayloadHasher.wasmSupport.canUseWasm) {\n try {\n return await sha256(data)\n } catch (ex) {\n PayloadHasher.wasmSupport.allowWasm = false\n }\n }\n\n return shajs('sha256').update(data).digest().toString()\n }\n\n private static bufferFromDataUrl(url: string): Buffer | undefined {\n if (url.startsWith('data:image')) {\n const data = url.split(',')[1]\n if (data) {\n return Buffer.from(Uint8Array.from(atob(data), (c) => c.charCodeAt(0)))\n } else {\n const error: ImageThumbnailWitnessError = {\n message: 'Invalid data Url',\n name: 'ImageThumbnailWitnessError',\n url,\n }\n throw error\n }\n }\n }\n\n /**\n * Returns the equivalent IPFS gateway URL for the supplied URL.\n * @param urlToCheck The URL to check\n * @returns If the supplied URL is an IPFS URL, it converts the URL to the\n * equivalent IPFS gateway URL. Otherwise, returns the original URL.\n */\n checkIpfsUrl(urlToCheck: string) {\n const url = new URL(urlToCheck)\n let protocol = url.protocol\n let host = url.host\n let path = url.pathname\n const query = url.search\n if (protocol === 'ipfs:') {\n protocol = 'https:'\n host = this.ipfGateway\n path = url.host === 'ipfs' ? `ipfs${path}` : `ipfs/${url.host}${path}`\n const root = `${protocol}//${host}/${path}`\n return query?.length > 0 ? `${root}?${query}` : root\n } else {\n return urlToCheck\n }\n }\n\n protected override async observeHandler(payloads: UrlPayload[] = []): Promise<ImageThumbnail[]> {\n // eslint-disable-next-line import/no-named-as-default-member\n if (!hasbin.sync('magick')) {\n throw Error('ImageMagick is required for this witness')\n }\n const urlPayloads = payloads.filter((payload) => payload.schema === UrlSchema)\n return await this._semaphore.runExclusive(async () =>\n compact(\n await Promise.all(\n urlPayloads.map<Promise<ImageThumbnail>>(async ({ url }) => {\n const cachedResult = this.cache.get(url)\n if (cachedResult) {\n return cachedResult\n }\n let result: ImageThumbnail\n\n //if it is a data URL, return a Buffer\n const dataBuffer = ImageThumbnailWitness.bufferFromDataUrl(url)\n\n if (dataBuffer) {\n result = {\n schema: ImageThumbnailSchema,\n sourceHash: await ImageThumbnailWitness.binaryToSha256(dataBuffer),\n sourceUrl: url,\n url,\n }\n } else {\n //if it is ipfs, go through cloud flair\n const mutatedUrl = this.checkIpfsUrl(url)\n result = await this.fromHttp(mutatedUrl, url)\n }\n this.cache.set(url, result)\n return result\n }),\n ),\n ),\n )\n }\n\n private async createThumbnailDataUrl(sourceBuffer: Buffer, encoding?: ImageThumbnailEncoding) {\n const thumb = await new Promise<Buffer>((resolve, reject) => {\n gm(sourceBuffer)\n .quality(this.quality)\n .resize(this.width, this.height)\n .flatten()\n .toBuffer(encoding ?? this.encoding, (error, buffer) => {\n if (error) {\n reject(error)\n } else {\n resolve(buffer)\n }\n })\n })\n return `data:image/png;base64,${thumb.toString('base64')}`\n }\n\n /**\n * Creates an image thumbnail from a video.\n * @param videoBuffer The input video buffer.\n * @returns An buffer containing an image thumbnail for the video.\n */\n private async createThumbnailFromVideo(videoBuffer: Buffer) {\n const imageBuffer = await getVideoFrameAsImageFluent(videoBuffer)\n return this.createThumbnailDataUrl(imageBuffer)\n }\n\n private async fromHttp(url: string, sourceUrl?: string): Promise<ImageThumbnail> {\n let response: AxiosResponse\n let dnsResult: string[]\n try {\n const urlObj = new Url(url)\n dnsResult = await dnsPromises.resolve(urlObj.host)\n // console.log(`dnsResult: ${JSON.stringify(dnsResult, null, 2)}`)\n } catch (ex) {\n const error = ex as DnsError\n const result: ImageThumbnail = {\n http: {\n dnsError: error.code,\n },\n schema: ImageThumbnailSchema,\n sourceUrl: sourceUrl ?? url,\n }\n return result\n }\n try {\n response = await axios.get(url, {\n responseType: 'arraybuffer',\n })\n } catch (ex) {\n const axiosError = ex as AxiosError\n if (axiosError.isAxiosError) {\n //selectively pick fields from AxiosError\n const result: ImageThumbnail = {\n http: {\n ipAddress: dnsResult[0],\n status: axiosError?.response?.status,\n },\n schema: ImageThumbnailSchema,\n sourceUrl: sourceUrl ?? url,\n }\n return result\n } else {\n throw ex\n }\n }\n\n const result: ImageThumbnail = {\n http: {\n status: response.status,\n },\n schema: ImageThumbnailSchema,\n sourceUrl: sourceUrl ?? url,\n }\n\n if (response.status >= 200 && response.status < 300) {\n const contentType: string = response.headers['content-type']?.toString()\n const [mediaType, fileType] = contentType.split('/')\n result.mime = result.mime ?? {}\n result.mime.returned = mediaType\n const sourceBuffer = Buffer.from(response.data, 'binary')\n\n try {\n result.mime.detected = await FileType.fromBuffer(sourceBuffer)\n } catch (ex) {\n const error = ex as Error\n this.logger?.error(`FileType error: ${error.message}`)\n }\n\n const processImage = async (encoding?: ImageThumbnailEncoding) => {\n result.sourceHash = await ImageThumbnailWitness.binaryToSha256(sourceBuffer)\n result.url = await this.createThumbnailDataUrl(sourceBuffer, encoding)\n }\n\n const processVideo = async () => {\n // Gracefully handle the case where ffmpeg is not installed.\n // eslint-disable-next-line import/no-named-as-default-member\n if (hasbin.sync('ffmpeg')) {\n result.sourceHash = await ImageThumbnailWitness.binaryToSha256(sourceBuffer)\n result.url = await this.createThumbnailFromVideo(sourceBuffer)\n } else {\n result.mime = result.mime ?? {}\n result.mime.invalid = true\n }\n }\n\n let encoding: ImageThumbnailEncoding = 'PNG'\n\n switch (fileType.toUpperCase()) {\n case 'GIF':\n encoding = 'GIF'\n break\n case 'JPG':\n case 'JPEG':\n encoding = 'JPG'\n break\n }\n\n switch (mediaType) {\n case 'image': {\n await processImage(encoding)\n result.mime.type = mediaType\n break\n }\n case 'video': {\n await processVideo()\n result.mime.type = mediaType\n break\n }\n default: {\n switch (result.mime.detected?.mime) {\n case 'image': {\n await processImage()\n result.mime.type = result.mime.detected?.mime\n break\n }\n case 'video': {\n await processVideo()\n result.mime.type = result.mime.detected?.mime\n break\n }\n default: {\n result.mime.invalid = true\n break\n }\n }\n break\n }\n }\n }\n return result\n }\n}\n","import { ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin'\nimport { WitnessConfig } from '@xyo-network/witness'\n\nexport const ImageThumbnailWitnessConfigSchema = `${ImageThumbnailSchema}.witness.config` as const\nexport type ImageThumbnailWitnessConfigSchema = typeof ImageThumbnailWitnessConfigSchema\n\nexport type ImageThumbnailEncoding = 'PNG' | 'JPG' | 'GIF'\n\nexport type ImageThumbnailWitnessConfig = WitnessConfig<{\n encoding?: ImageThumbnailEncoding\n height?: number\n ipfsGateway?: string\n maxAsyncProcesses?: number\n maxCacheBytes?: number\n maxCacheEntries?: number\n quality?: number\n schema: ImageThumbnailWitnessConfigSchema\n width?: number\n}>\n","import { uuid } from '@xyo-network/core'\nimport ffmpeg from 'fluent-ffmpeg'\nimport { unlink, writeFile } from 'fs/promises'\nimport { tmpdir } from 'os'\nimport { Writable, WritableOptions } from 'stream'\n\n/**\n * A Writable stream that collects output from ffmpeg.\n */\nclass FfmpegOutputStream extends Writable {\n private readonly chunks: Uint8Array[] = []\n\n constructor(options?: WritableOptions) {\n super(options)\n }\n\n override _write(chunk: never, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void {\n this.chunks.push(chunk)\n callback()\n }\n\n /**\n * Collects the output from ffmpeg into a buffer.\n * @returns A buffer containing the concatenated\n * output from ffmpeg.\n */\n toBuffer = () => Buffer.concat(this.chunks)\n}\n\n/**\n * Execute FFmpeg using fluent API with provided input buffer and video thumbnail image.\n * @param videoBuffer Input video buffer.\n * @returns Output buffer containing the video thumbnail image.\n */\nexport const getVideoFrameAsImageFluent = async (videoBuffer: Buffer) => {\n // Get a temp file name\n const tmpFile = `/${tmpdir()}/${uuid()}`\n try {\n // Write videoBuffer to temp file for use as input to ffmpeg to\n // avoid issues with ffmpeg inferring premature EOF from buffer\n // passed via stdin (happens when ffmpeg is trying to infer\n // input video format)\n await writeFile(tmpFile, videoBuffer, { encoding: 'binary' })\n const imageBuffer = await new Promise<Buffer>((resolve, reject) => {\n // Create a Writable stream to collect PNG output from ffmpeg\n const ffmpegOutput = new FfmpegOutputStream()\n // Execute ffmpeg using fluent API\n ffmpeg()\n // NOTE: Uncomment to debug CLI args to ffmpeg\n // .on('start', (commandLine) => console.log('Spawned Ffmpeg with command: ' + commandLine))\n .on('error', (err) => reject(err.message))\n // Listen for the 'end' event to combine the output into a buffer holding the PNG image\n .on('end', () => resolve(ffmpegOutput.toBuffer()))\n .input(tmpFile) // Use temp file as input\n .takeFrames(1) // Only take 1st video frame\n .withNoAudio() // Don't include audio\n .outputOptions('-f image2pipe') // Write output to stdout\n .videoCodec('png') // Force PNG output\n // Start processing and direct ffmpeg stdout to writable stream\n .pipe(ffmpegOutput)\n })\n return imageBuffer\n } finally {\n // Cleanup temp file\n try {\n await unlink(tmpFile)\n } catch {\n // No error here since file doesn't exist\n }\n }\n}\n"],"mappings":"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","names":["import_core","import_image_thumbnail_payload_plugin","ffmpeg","graphicsMagick","shajs","hasbin","Url","dnsPromises","result","FileType","encoding"]}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
// src/Witness/Witness.ts
|
|
1
2
|
import { promises as dnsPromises } from "node:dns";
|
|
2
3
|
import { compact } from "@xylabs/lodash";
|
|
3
4
|
import { URL } from "@xylabs/url";
|
|
4
5
|
import { axios } from "@xyo-network/axios";
|
|
5
6
|
import { PayloadHasher } from "@xyo-network/core";
|
|
6
|
-
import { ImageThumbnailSchema } from "@xyo-network/image-thumbnail-payload-plugin";
|
|
7
|
+
import { ImageThumbnailSchema as ImageThumbnailSchema2 } from "@xyo-network/image-thumbnail-payload-plugin";
|
|
7
8
|
import { UrlSchema } from "@xyo-network/url-payload-plugin";
|
|
8
9
|
import { AbstractWitness } from "@xyo-network/witness";
|
|
9
10
|
import { Semaphore } from "async-mutex";
|
|
@@ -14,10 +15,53 @@ import { sha256 } from "hash-wasm";
|
|
|
14
15
|
import { LRUCache } from "lru-cache";
|
|
15
16
|
import shajs from "sha.js";
|
|
16
17
|
import Url from "url-parse";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
|
|
19
|
+
// src/Witness/Config.ts
|
|
20
|
+
import { ImageThumbnailSchema } from "@xyo-network/image-thumbnail-payload-plugin";
|
|
21
|
+
var ImageThumbnailWitnessConfigSchema = `${ImageThumbnailSchema}.witness.config`;
|
|
22
|
+
|
|
23
|
+
// src/Witness/ffmpeg/fluent/getVideoFrameAsImageFluent.ts
|
|
24
|
+
import { uuid } from "@xyo-network/core";
|
|
25
|
+
import ffmpeg from "fluent-ffmpeg";
|
|
26
|
+
import { unlink, writeFile } from "fs/promises";
|
|
27
|
+
import { tmpdir } from "os";
|
|
28
|
+
import { Writable } from "stream";
|
|
29
|
+
var FfmpegOutputStream = class extends Writable {
|
|
30
|
+
chunks = [];
|
|
31
|
+
constructor(options) {
|
|
32
|
+
super(options);
|
|
33
|
+
}
|
|
34
|
+
_write(chunk, _encoding, callback) {
|
|
35
|
+
this.chunks.push(chunk);
|
|
36
|
+
callback();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Collects the output from ffmpeg into a buffer.
|
|
40
|
+
* @returns A buffer containing the concatenated
|
|
41
|
+
* output from ffmpeg.
|
|
42
|
+
*/
|
|
43
|
+
toBuffer = () => Buffer.concat(this.chunks);
|
|
44
|
+
};
|
|
45
|
+
var getVideoFrameAsImageFluent = async (videoBuffer) => {
|
|
46
|
+
const tmpFile = `/${tmpdir()}/${uuid()}`;
|
|
47
|
+
try {
|
|
48
|
+
await writeFile(tmpFile, videoBuffer, { encoding: "binary" });
|
|
49
|
+
const imageBuffer = await new Promise((resolve, reject) => {
|
|
50
|
+
const ffmpegOutput = new FfmpegOutputStream();
|
|
51
|
+
ffmpeg().on("error", (err) => reject(err.message)).on("end", () => resolve(ffmpegOutput.toBuffer())).input(tmpFile).takeFrames(1).withNoAudio().outputOptions("-f image2pipe").videoCodec("png").pipe(ffmpegOutput);
|
|
52
|
+
});
|
|
53
|
+
return imageBuffer;
|
|
54
|
+
} finally {
|
|
55
|
+
try {
|
|
56
|
+
await unlink(tmpFile);
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/Witness/Witness.ts
|
|
63
|
+
var gm = graphicsMagick.subClass({ imageMagick: "7+" });
|
|
64
|
+
var ImageThumbnailWitness = class _ImageThumbnailWitness extends AbstractWitness {
|
|
21
65
|
static configSchemas = [ImageThumbnailWitnessConfigSchema];
|
|
22
66
|
_cache;
|
|
23
67
|
_semaphore = new Semaphore(this.maxAsyncProcesses);
|
|
@@ -26,7 +70,10 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
26
70
|
max: this.maxCacheEntries,
|
|
27
71
|
maxSize: this.maxCacheBytes,
|
|
28
72
|
//just returning the size of the data
|
|
29
|
-
sizeCalculation: (value) =>
|
|
73
|
+
sizeCalculation: (value) => {
|
|
74
|
+
var _a;
|
|
75
|
+
return ((_a = value.url) == null ? void 0 : _a.length) ?? 1;
|
|
76
|
+
}
|
|
30
77
|
});
|
|
31
78
|
return this._cache;
|
|
32
79
|
}
|
|
@@ -97,7 +144,7 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
97
144
|
host = this.ipfGateway;
|
|
98
145
|
path = url.host === "ipfs" ? `ipfs${path}` : `ipfs/${url.host}${path}`;
|
|
99
146
|
const root = `${protocol}//${host}/${path}`;
|
|
100
|
-
return query
|
|
147
|
+
return (query == null ? void 0 : query.length) > 0 ? `${root}?${query}` : root;
|
|
101
148
|
} else {
|
|
102
149
|
return urlToCheck;
|
|
103
150
|
}
|
|
@@ -116,11 +163,11 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
116
163
|
return cachedResult;
|
|
117
164
|
}
|
|
118
165
|
let result;
|
|
119
|
-
const dataBuffer =
|
|
166
|
+
const dataBuffer = _ImageThumbnailWitness.bufferFromDataUrl(url);
|
|
120
167
|
if (dataBuffer) {
|
|
121
168
|
result = {
|
|
122
|
-
schema:
|
|
123
|
-
sourceHash: await
|
|
169
|
+
schema: ImageThumbnailSchema2,
|
|
170
|
+
sourceHash: await _ImageThumbnailWitness.binaryToSha256(dataBuffer),
|
|
124
171
|
sourceUrl: url,
|
|
125
172
|
url
|
|
126
173
|
};
|
|
@@ -157,6 +204,7 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
157
204
|
return this.createThumbnailDataUrl(imageBuffer);
|
|
158
205
|
}
|
|
159
206
|
async fromHttp(url, sourceUrl) {
|
|
207
|
+
var _a, _b, _c, _d, _e, _f;
|
|
160
208
|
let response;
|
|
161
209
|
let dnsResult;
|
|
162
210
|
try {
|
|
@@ -168,7 +216,7 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
168
216
|
http: {
|
|
169
217
|
dnsError: error.code
|
|
170
218
|
},
|
|
171
|
-
schema:
|
|
219
|
+
schema: ImageThumbnailSchema2,
|
|
172
220
|
sourceUrl: sourceUrl ?? url
|
|
173
221
|
};
|
|
174
222
|
return result2;
|
|
@@ -183,9 +231,9 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
183
231
|
const result2 = {
|
|
184
232
|
http: {
|
|
185
233
|
ipAddress: dnsResult[0],
|
|
186
|
-
status: axiosError
|
|
234
|
+
status: (_a = axiosError == null ? void 0 : axiosError.response) == null ? void 0 : _a.status
|
|
187
235
|
},
|
|
188
|
-
schema:
|
|
236
|
+
schema: ImageThumbnailSchema2,
|
|
189
237
|
sourceUrl: sourceUrl ?? url
|
|
190
238
|
};
|
|
191
239
|
return result2;
|
|
@@ -197,11 +245,11 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
197
245
|
http: {
|
|
198
246
|
status: response.status
|
|
199
247
|
},
|
|
200
|
-
schema:
|
|
248
|
+
schema: ImageThumbnailSchema2,
|
|
201
249
|
sourceUrl: sourceUrl ?? url
|
|
202
250
|
};
|
|
203
251
|
if (response.status >= 200 && response.status < 300) {
|
|
204
|
-
const contentType = response.headers["content-type"]
|
|
252
|
+
const contentType = (_b = response.headers["content-type"]) == null ? void 0 : _b.toString();
|
|
205
253
|
const [mediaType, fileType] = contentType.split("/");
|
|
206
254
|
result.mime = result.mime ?? {};
|
|
207
255
|
result.mime.returned = mediaType;
|
|
@@ -210,15 +258,15 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
210
258
|
result.mime.detected = await FileType.fromBuffer(sourceBuffer);
|
|
211
259
|
} catch (ex) {
|
|
212
260
|
const error = ex;
|
|
213
|
-
this.logger
|
|
261
|
+
(_c = this.logger) == null ? void 0 : _c.error(`FileType error: ${error.message}`);
|
|
214
262
|
}
|
|
215
263
|
const processImage = async (encoding2) => {
|
|
216
|
-
result.sourceHash = await
|
|
264
|
+
result.sourceHash = await _ImageThumbnailWitness.binaryToSha256(sourceBuffer);
|
|
217
265
|
result.url = await this.createThumbnailDataUrl(sourceBuffer, encoding2);
|
|
218
266
|
};
|
|
219
267
|
const processVideo = async () => {
|
|
220
268
|
if (hasbin.sync("ffmpeg")) {
|
|
221
|
-
result.sourceHash = await
|
|
269
|
+
result.sourceHash = await _ImageThumbnailWitness.binaryToSha256(sourceBuffer);
|
|
222
270
|
result.url = await this.createThumbnailFromVideo(sourceBuffer);
|
|
223
271
|
} else {
|
|
224
272
|
result.mime = result.mime ?? {};
|
|
@@ -247,15 +295,15 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
247
295
|
break;
|
|
248
296
|
}
|
|
249
297
|
default: {
|
|
250
|
-
switch (result.mime.detected
|
|
298
|
+
switch ((_d = result.mime.detected) == null ? void 0 : _d.mime) {
|
|
251
299
|
case "image": {
|
|
252
300
|
await processImage();
|
|
253
|
-
result.mime.type = result.mime.detected
|
|
301
|
+
result.mime.type = (_e = result.mime.detected) == null ? void 0 : _e.mime;
|
|
254
302
|
break;
|
|
255
303
|
}
|
|
256
304
|
case "video": {
|
|
257
305
|
await processVideo();
|
|
258
|
-
result.mime.type = result.mime.detected
|
|
306
|
+
result.mime.type = (_f = result.mime.detected) == null ? void 0 : _f.mime;
|
|
259
307
|
break;
|
|
260
308
|
}
|
|
261
309
|
default: {
|
|
@@ -269,7 +317,7 @@ class ImageThumbnailWitness extends AbstractWitness {
|
|
|
269
317
|
}
|
|
270
318
|
return result;
|
|
271
319
|
}
|
|
272
|
-
}
|
|
320
|
+
};
|
|
273
321
|
export {
|
|
274
322
|
ImageThumbnailWitness
|
|
275
323
|
};
|