@boruto_vk7/stickengine 1.2.0 → 2.0.0

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.
@@ -36,88 +36,181 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.StickEngine = void 0;
39
+ exports.writeExifVid2 = exports.writeExifImg2 = exports.videoToWebp2 = exports.imageToWebp2 = exports.sendVideoAsSticker2 = exports.sendImageAsSticker2 = exports.StickEngine = void 0;
40
40
  exports.getBuffer = getBuffer;
41
+ exports.sendImageAsSticker = sendImageAsSticker;
42
+ exports.sendVideoAsSticker = sendVideoAsSticker;
43
+ exports.imageToWebp = imageToWebp;
44
+ exports.videoToWebp = videoToWebp;
45
+ exports.writeExifImg = writeExifImg;
46
+ exports.writeExifVid = writeExifVid;
41
47
  const node_fs_1 = __importDefault(require("node:fs"));
42
48
  const promises_1 = __importDefault(require("node:fs/promises"));
43
49
  const node_path_1 = __importDefault(require("node:path"));
50
+ const node_os_1 = __importDefault(require("node:os"));
44
51
  const node_child_process_1 = require("node:child_process");
45
52
  const node_events_1 = require("node:events");
53
+ const node_crypto_1 = __importDefault(require("node:crypto"));
46
54
  const axios_1 = __importDefault(require("axios"));
55
+ // @ts-ignore
47
56
  const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
48
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
49
57
  // @ts-ignore
50
58
  const node_webpmux_1 = __importDefault(require("node-webpmux"));
51
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
52
59
  // @ts-ignore
53
60
  const jimp_1 = __importDefault(require("jimp"));
54
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
55
- // @ts-ignore
56
- const remove_bg_1 = require("remove.bg");
57
- const moduleDir = typeof __dirname !== 'undefined' ? __dirname : process.cwd();
58
- // ── Helpers ────────────────────────────────────────────────────────────────
61
+ const node_url_1 = require("node:url");
62
+ const node_module_1 = require("node:module");
63
+ const moduleDir = (() => {
64
+ try {
65
+ const getMetaUrl = new Function('try { return import.meta.url } catch { return null }');
66
+ const metaUrl = getMetaUrl();
67
+ if (metaUrl)
68
+ return node_path_1.default.dirname((0, node_url_1.fileURLToPath)(metaUrl));
69
+ }
70
+ catch { }
71
+ return typeof __dirname !== 'undefined' ? __dirname : process.cwd();
72
+ })();
73
+ let ffmpegPath = null;
74
+ let ffprobePath = null;
75
+ const getCjsRequire = () => {
76
+ try {
77
+ return eval("require");
78
+ }
79
+ catch { }
80
+ try {
81
+ const getUrl = new Function('try { return import.meta.url } catch { return null }');
82
+ const url = getUrl();
83
+ if (url)
84
+ return (0, node_module_1.createRequire)(url);
85
+ }
86
+ catch { }
87
+ try {
88
+ return (0, node_module_1.createRequire)((0, node_url_1.fileURLToPath)(new Function('return import.meta.url')));
89
+ }
90
+ catch { }
91
+ return null;
92
+ };
93
+ const cjsRequire = getCjsRequire();
94
+ if (cjsRequire) {
95
+ try {
96
+ const installer = cjsRequire('@ffmpeg-installer/ffmpeg');
97
+ if (installer?.path) {
98
+ ffmpegPath = installer.path;
99
+ // @ts-ignore
100
+ fluent_ffmpeg_1.default.setFfmpegPath(ffmpegPath);
101
+ }
102
+ }
103
+ catch { }
104
+ try {
105
+ const installer = cjsRequire('@ffprobe-installer/ffprobe');
106
+ if (installer?.path) {
107
+ ffprobePath = installer.path;
108
+ // @ts-ignore
109
+ fluent_ffmpeg_1.default.setFfprobePath(ffprobePath);
110
+ }
111
+ }
112
+ catch { }
113
+ }
59
114
  async function getBuffer(url, options = {}) {
60
115
  try {
61
116
  const { data } = await (0, axios_1.default)({
62
117
  method: 'get',
63
118
  url,
64
119
  headers: {
65
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
66
- DNT: '1',
120
+ 'user-agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36',
121
+ 'DNT': 1,
122
+ 'Upgrade-Insecure-Request': 1,
123
+ ...(options.headers || {})
67
124
  },
68
125
  ...options,
69
126
  responseType: 'arraybuffer',
70
- timeout: 45000,
127
+ timeout: options.timeout || 45000
71
128
  });
72
- return Buffer.from(data);
129
+ return Buffer.isBuffer(data) ? data : Buffer.from(data);
73
130
  }
74
131
  catch (err) {
75
- const fallback = node_path_1.default.join(moduleDir, 'emror.jpg');
76
- if (node_fs_1.default.existsSync(fallback))
77
- return node_fs_1.default.readFileSync(fallback);
78
- throw new Error(`Failed to download: ${err instanceof Error ? err.message : String(err)}`);
132
+ const candidates = [
133
+ node_path_1.default.join(moduleDir, 'emror.jpg'),
134
+ node_path_1.default.join(moduleDir, '../src/emror.jpg'),
135
+ node_path_1.default.join(moduleDir, 'logo.png'),
136
+ node_path_1.default.join(process.cwd(), 'src/emror.jpg'),
137
+ node_path_1.default.join(process.cwd(), 'dist/cjs/emror.jpg'),
138
+ node_path_1.default.join(process.cwd(), 'dist/esm/emror.jpg'),
139
+ node_path_1.default.join(node_os_1.default.tmpdir(), 'emror.jpg'),
140
+ node_path_1.default.resolve('./src/emror.jpg')
141
+ ];
142
+ for (const p of candidates) {
143
+ try {
144
+ if (node_fs_1.default.existsSync(p))
145
+ return node_fs_1.default.readFileSync(p);
146
+ }
147
+ catch { }
148
+ }
149
+ return Buffer.alloc(0);
79
150
  }
80
151
  }
81
152
  function resolveWebpImage() {
82
- const Image = node_webpmux_1.default.Image || node_webpmux_1.default.default?.Image;
153
+ const Image = node_webpmux_1.default.Image || node_webpmux_1.default.default?.Image || node_webpmux_1.default.default;
83
154
  if (!Image)
84
155
  throw new Error('node-webpmux Image class not found');
85
156
  return Image;
86
157
  }
87
158
  async function fileTypeFromBuf(buf) {
88
- const ft = await Promise.resolve().then(() => __importStar(require('file-type')));
89
- const mod = ft;
90
- const fn = mod.fileTypeFromBuffer ||
91
- mod.fromBuffer ||
92
- mod.default?.fileTypeFromBuffer ||
93
- mod.default?.fromBuffer;
94
- if (!fn)
95
- return undefined;
96
- return fn(buf);
159
+ try {
160
+ const ft = await Promise.resolve().then(() => __importStar(require('file-type')));
161
+ const fn = ft.fileTypeFromBuffer || ft.fromBuffer || ft.default?.fileTypeFromBuffer || ft.default?.fromBuffer;
162
+ if (!fn)
163
+ return undefined;
164
+ return await fn(buf);
165
+ }
166
+ catch {
167
+ try {
168
+ const ft = eval("require")('file-type');
169
+ const fn = ft.fileTypeFromBuffer || ft.fromBuffer || ft.default?.fileTypeFromBuffer || ft.default?.fromBuffer;
170
+ if (!fn)
171
+ return undefined;
172
+ return await fn(buf);
173
+ }
174
+ catch {
175
+ return undefined;
176
+ }
177
+ }
97
178
  }
98
179
  function isHttp(s) {
99
180
  return /^https?:\/\//i.test(s);
100
181
  }
101
- // ── Engine ─────────────────────────────────────────────────────────────────
102
- /**
103
- * WhatsApp sticker engine (static + animated WebP + EXIF pack metadata).
104
- *
105
- * @example
106
- * ```ts
107
- * import StickEngine from '@boruto_vk7/stickengine';
108
- *
109
- * const sticker = await StickEngine.create('./cat.gif', {
110
- * metadata: { pack: 'My Pack', author: 'Me' },
111
- * });
112
- * // sticker.buffer → send to WhatsApp
113
- * // sticker.path → temp file path
114
- * ```
115
- */
182
+ function isDataURI(s) {
183
+ return /^data:.*?\/.*?;base64,/i.test(s);
184
+ }
185
+ function parseDataURItoBuffer(dataURI) {
186
+ const base64 = dataURI.split(',')[1];
187
+ return Buffer.from(base64, 'base64');
188
+ }
189
+ function normalizeMetadata(options = {}) {
190
+ const meta = options.metadata || options;
191
+ const pack = meta.packname || meta.pack || options.packname || options.pack || 'Lagos Solutions';
192
+ const author = meta.author || options.author || 'StickEngine';
193
+ const emojis = meta.categories || meta.emojis || options.categories || options.emojis || ['✨'];
194
+ const id = meta.id || options.id;
195
+ return { pack, author, emojis, id };
196
+ }
197
+ function resolveTempDir(options) {
198
+ const dir = options.tempDir || options.dirTemp || options.Dirtemp || options.DirTemp || options.dirtemp || options.TempDir || node_path_1.default.join(node_os_1.default.tmpdir(), 'stickengine');
199
+ return node_path_1.default.resolve(dir);
200
+ }
201
+ function resolveWatermark(options) {
202
+ const wm = options.watermark ?? options.marcaDagua ?? options.marca_agua ?? options.MarcaDaAgua ?? options.marcaDaAgua ?? false;
203
+ return wm;
204
+ }
116
205
  class StickEngine extends node_events_1.EventEmitter {
117
206
  constructor(options = {}) {
118
207
  super();
119
208
  this.queue = [];
120
209
  this.createdFiles = [];
210
+ const normalizedMeta = normalizeMetadata(options);
211
+ this.tempDir = resolveTempDir(options);
212
+ node_fs_1.default.mkdirSync(this.tempDir, { recursive: true });
213
+ const watermarkValue = resolveWatermark(options);
121
214
  this.options = {
122
215
  webp: true,
123
216
  edit: false,
@@ -127,44 +220,44 @@ class StickEngine extends node_events_1.EventEmitter {
127
220
  quality: 75,
128
221
  maxDuration: 6,
129
222
  fit: 'natural',
130
- metadata: {
131
- pack: 'StickEngine',
132
- author: 'Borutovk7',
133
- emojis: ['✨'],
134
- },
223
+ watermark: watermarkValue,
135
224
  ...options,
225
+ tempDir: this.tempDir,
226
+ dirTemp: this.tempDir,
227
+ metadata: {
228
+ pack: normalizedMeta.pack,
229
+ author: normalizedMeta.author,
230
+ emojis: normalizedMeta.emojis,
231
+ id: normalizedMeta.id,
232
+ ...(options.metadata || {})
233
+ }
136
234
  };
137
- this.tempDir = node_path_1.default.resolve(options.tempDir || node_path_1.default.join(process.cwd(), 'tmp_stickengine'));
138
- node_fs_1.default.mkdirSync(this.tempDir, { recursive: true });
235
+ if (options.packname)
236
+ this.options.metadata.pack = options.packname;
237
+ if (options.author)
238
+ this.options.metadata.author = options.author;
239
+ if (options.categories)
240
+ this.options.metadata.emojis = options.categories;
241
+ const wm = resolveWatermark(options);
242
+ if (wm !== undefined)
243
+ this.options.watermark = wm;
139
244
  }
140
- // ── One-shot API (simplest) ──────────────────────────────────────────────
141
- /**
142
- * Create a sticker in one call. Returns path + buffer + base64.
143
- */
144
245
  static async create(input, options = {}) {
145
246
  const engine = new StickEngine({ ...options, autoClean: false });
146
- try {
147
- return await engine.build(input);
148
- }
149
- finally {
150
- // keep result file; drop other temps if any
151
- }
247
+ return await engine.build(input);
152
248
  }
153
- /**
154
- * Create sticker and return only the Buffer (handy for bots).
155
- */
156
249
  static async toBuffer(input, options = {}) {
157
250
  const r = await StickEngine.create(input, options);
158
251
  return r.buffer;
159
252
  }
160
- /**
161
- * Create sticker and return base64 string.
162
- */
163
253
  static async toBase64String(input, options = {}) {
164
254
  const r = await StickEngine.create(input, options);
165
255
  return r.base64;
166
256
  }
167
- /** Instance one-shot (uses constructor options). */
257
+ static async toFile(input, options = {}) {
258
+ const r = await StickEngine.create(input, options);
259
+ return r.path;
260
+ }
168
261
  async build(input) {
169
262
  const filePath = await this.processFile(input, 0);
170
263
  const buffer = await promises_1.default.readFile(filePath);
@@ -177,12 +270,11 @@ class StickEngine extends node_events_1.EventEmitter {
177
270
  animated,
178
271
  width: info.width || 512,
179
272
  height: info.height || 512,
180
- size: buffer.length,
273
+ size: buffer.length
181
274
  };
182
275
  this.emit('st.done', result);
183
276
  return result;
184
277
  }
185
- // ── Queue API (batch) ────────────────────────────────────────────────────
186
278
  addFile(file) {
187
279
  this.queue.push(file);
188
280
  return this;
@@ -191,81 +283,59 @@ class StickEngine extends node_events_1.EventEmitter {
191
283
  this.queue.push(...files);
192
284
  return this;
193
285
  }
194
- /**
195
- * Process the queue. Returns settled results with `value` = absolute path.
196
- * Prefer `build()` / `StickEngine.create()` for a simpler API.
197
- */
198
286
  async start() {
199
287
  if (!this.queue.length)
200
- throw new Error('Queue is empty. Use addFile() or StickEngine.create().');
201
- const jobs = this.queue.map((file, i) => this.processFile(file, i));
288
+ throw new Error('Queue is empty');
289
+ const jobs = this.queue.map((f, i) => this.processFile(f, i));
202
290
  this.queue = [];
203
291
  return Promise.allSettled(jobs);
204
292
  }
205
- /** Delete tracked temp files. */
206
293
  clean() {
207
294
  for (const file of this.createdFiles) {
208
295
  try {
209
296
  if (node_fs_1.default.existsSync(file))
210
297
  node_fs_1.default.unlinkSync(file);
211
298
  }
212
- catch {
213
- /* ignore */
214
- }
299
+ catch { }
215
300
  }
216
301
  this.createdFiles = [];
217
302
  }
218
- // ── Core pipeline ────────────────────────────────────────────────────────
219
303
  track(file) {
220
304
  this.createdFiles.push(file);
221
305
  return file;
222
306
  }
223
307
  async processFile(input, index) {
224
- const ts = `${Date.now()}_${index}_${Math.random().toString(36).slice(2, 7)}`;
308
+ const ts = `${Date.now()}_${index}_${node_crypto_1.default.randomBytes(3).toString('hex')}`;
225
309
  const local = [];
226
310
  try {
227
- this.emit('st.start', { index, input: typeof input === 'string' ? input : '<buffer>' });
311
+ this.emit('st.start', { index, input: typeof input === 'string' ? (input.length > 100 ? input.slice(0, 100) + '...' : input) : '<buffer>' });
228
312
  const { filePath, extension } = await this.materializeInput(input, ts);
229
- local.push(filePath);
313
+ if (filePath.includes(this.tempDir))
314
+ local.push(filePath);
230
315
  const mediaInfo = await this.getMediaInfo(filePath);
231
316
  this.emit('st.info', { index, ...mediaInfo, extension });
232
317
  let current = filePath;
233
- let animated = mediaInfo.duration > 0.05 ||
234
- ['gif', 'mp4', 'webm', 'mov', 'mkv', 'avi'].includes(extension) ||
235
- (extension === 'webp' && (await this.detectAnimatedWebp(filePath)));
236
- // Static edits only
318
+ let animated = mediaInfo.duration > 0.05 || ['gif', 'mp4', 'webm', 'mov', 'mkv', 'avi'].includes(extension) || (extension === 'webp' && await this.detectAnimatedWebp(filePath));
237
319
  if (this.options.edit && !animated) {
238
320
  const edited = node_path_1.default.join(this.tempDir, `edited_${ts}.png`);
239
321
  await this.applyJimpEdits(current, edited, this.options.edit);
240
322
  local.push(edited);
241
323
  current = edited;
242
324
  }
243
- // remove.bg static only
244
- if (this.options.transparent && !animated) {
245
- const key = Array.isArray(this.options.transparent)
246
- ? this.options.transparent[Math.floor(Math.random() * this.options.transparent.length)]
247
- : this.options.transparent;
248
- if (typeof key === 'string' && key.length > 5) {
249
- const out = node_path_1.default.join(this.tempDir, `nobg_${ts}.png`);
250
- const result = await (0, remove_bg_1.removeBackgroundFromImageFile)({
251
- path: current,
252
- apiKey: key,
253
- size: 'auto',
254
- type: 'auto',
255
- });
256
- node_fs_1.default.writeFileSync(out, Buffer.from(result.base64img, 'base64'));
257
- local.push(out);
258
- current = out;
259
- }
325
+ const watermark = this.options.watermark;
326
+ if (watermark && !animated) {
327
+ const watermarked = node_path_1.default.join(this.tempDir, `wm_${ts}.png`);
328
+ await this.applyWatermark(current, watermarked, watermark);
329
+ local.push(watermarked);
330
+ current = watermarked;
260
331
  }
261
332
  const webpPath = node_path_1.default.join(this.tempDir, `sticker_${ts}.webp`);
262
333
  await this.convertToWebp(current, webpPath, animated);
263
334
  local.push(webpPath);
264
335
  current = webpPath;
265
- // Re-check after ffmpeg (source of truth)
266
336
  animated = await this.detectAnimatedWebp(current);
267
- if (this.options.metadata !== undefined && this.options.metadata !== null) {
268
- await this.addExif(current, this.options.metadata || {});
337
+ if (this.options.metadata) {
338
+ await this.addExif(current, this.options.metadata);
269
339
  }
270
340
  for (const f of local)
271
341
  this.track(f);
@@ -279,89 +349,93 @@ class StickEngine extends node_events_1.EventEmitter {
279
349
  if (node_fs_1.default.existsSync(f))
280
350
  node_fs_1.default.unlinkSync(f);
281
351
  }
282
- catch {
283
- /* ignore */
284
- }
352
+ catch { }
285
353
  }
286
354
  throw error;
287
355
  }
288
356
  }
289
357
  async materializeInput(input, ts) {
290
358
  if (Buffer.isBuffer(input)) {
359
+ if (input.length === 0)
360
+ throw new Error('Buffer vazio');
291
361
  const type = await fileTypeFromBuf(input);
292
- const extension = type?.ext || 'bin';
362
+ const extension = type?.ext || 'png';
293
363
  const filePath = node_path_1.default.join(this.tempDir, `input_${ts}.${extension}`);
294
364
  node_fs_1.default.writeFileSync(filePath, input);
295
365
  return { filePath, extension };
296
366
  }
297
- if (typeof input === 'string' && isHttp(input)) {
298
- const buffer = await getBuffer(input);
299
- const type = await fileTypeFromBuf(buffer);
300
- const extension = type?.ext || 'bin';
301
- const filePath = node_path_1.default.join(this.tempDir, `input_${ts}.${extension}`);
302
- node_fs_1.default.writeFileSync(filePath, buffer);
303
- return { filePath, extension };
304
- }
305
- if (typeof input === 'string' && node_fs_1.default.existsSync(input)) {
306
- const filePath = node_path_1.default.resolve(input);
307
- const extension = node_path_1.default.extname(filePath).replace('.', '').toLowerCase() || 'bin';
308
- return { filePath, extension };
367
+ if (typeof input === 'string') {
368
+ if (isDataURI(input)) {
369
+ const buffer = parseDataURItoBuffer(input);
370
+ if (buffer.length === 0)
371
+ throw new Error('Base64 inválido');
372
+ const type = await fileTypeFromBuf(buffer);
373
+ const extension = type?.ext || 'png';
374
+ const filePath = node_path_1.default.join(this.tempDir, `input_${ts}.${extension}`);
375
+ node_fs_1.default.writeFileSync(filePath, buffer);
376
+ return { filePath, extension };
377
+ }
378
+ if (isHttp(input)) {
379
+ const buffer = await getBuffer(input);
380
+ if (buffer.length === 0)
381
+ throw new Error('Falha ao baixar');
382
+ const type = await fileTypeFromBuf(buffer);
383
+ const extension = type?.ext || node_path_1.default.extname(new URL(input).pathname).replace('.', '') || 'bin';
384
+ const filePath = node_path_1.default.join(this.tempDir, `input_${ts}.${extension}`);
385
+ node_fs_1.default.writeFileSync(filePath, buffer);
386
+ return { filePath, extension: extension.toLowerCase() };
387
+ }
388
+ if (node_fs_1.default.existsSync(input)) {
389
+ const filePath = node_path_1.default.resolve(input);
390
+ const extension = node_path_1.default.extname(filePath).replace('.', '').toLowerCase() || 'bin';
391
+ return { filePath, extension };
392
+ }
393
+ if (/^[A-Za-z0-9+/=]+$/.test(input.slice(0, 100)) && input.length > 100) {
394
+ try {
395
+ const buffer = Buffer.from(input, 'base64');
396
+ if (buffer.length > 50) {
397
+ const type = await fileTypeFromBuf(buffer);
398
+ if (type) {
399
+ const filePath = node_path_1.default.join(this.tempDir, `input_${ts}.${type.ext}`);
400
+ node_fs_1.default.writeFileSync(filePath, buffer);
401
+ return { filePath, extension: type.ext };
402
+ }
403
+ }
404
+ }
405
+ catch { }
406
+ }
407
+ throw new Error('Invalid input: ' + input);
309
408
  }
310
- throw new Error('Invalid input. Pass a file path, http(s) URL, or Buffer.');
409
+ throw new Error('Invalid input type');
311
410
  }
312
- // ── EXIF (fixes animated save error) ─────────────────────────────────────
313
- /**
314
- * Write WhatsApp sticker pack EXIF.
315
- * Animated WebP MUST use muxAnim — save() throws on animations.
316
- */
317
411
  async addExif(webpPath, metadata) {
318
412
  const Image = resolveWebpImage();
319
413
  const img = new Image();
320
414
  const json = {
321
415
  'sticker-pack-id': metadata.id || `stickengine-${Date.now()}`,
322
- 'sticker-pack-name': metadata.pack || 'StickEngine',
323
- 'sticker-pack-publisher': metadata.author || 'Borutovk7',
324
- emojis: metadata.emojis?.length ? metadata.emojis : ['✨'],
416
+ 'sticker-pack-name': metadata.pack || 'Lagos Solutions',
417
+ 'sticker-pack-publisher': metadata.author || 'StickEngine',
418
+ 'emojis': metadata.emojis?.length ? metadata.emojis : metadata.categories?.length ? metadata.categories : ['✨']
325
419
  };
326
- const exifAttr = Buffer.from([
327
- 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00,
328
- 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
329
- ]);
420
+ const exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00]);
330
421
  const jsonBuff = Buffer.from(JSON.stringify(json), 'utf-8');
331
422
  const exif = Buffer.concat([exifAttr, jsonBuff]);
332
423
  exif.writeUIntLE(jsonBuff.length, 14, 4);
333
424
  await img.load(webpPath);
334
425
  img.exif = exif;
335
- const frameCount = typeof img.frameCount === 'number'
336
- ? img.frameCount
337
- : Array.isArray(img.frames)
338
- ? img.frames.length
339
- : 0;
426
+ const frameCount = typeof img.frameCount === 'number' ? img.frameCount : Array.isArray(img.frames) ? img.frames.length : 0;
340
427
  const animated = !!img.hasAnim || frameCount > 1;
341
- /**
342
- * node-webpmux compatibility layer
343
- *
344
- * v3.x (current):
345
- * await img.save(path) // works for static AND animated (passes frames)
346
- *
347
- * v1.x / early forks:
348
- * static → img.save(path)
349
- * anim → img.muxAnim({ path, exif: true }) OR Image.muxAnim({ frames, exif, path })
350
- * calling save() on anim throws:
351
- * "Using `save` for animations is not currently supported. Use `muxAnim` instead"
352
- */
353
428
  const saveV3 = async () => {
354
- // v3 instance save supports both; pass exif explicitly when supported
355
429
  return img.save(webpPath, {
356
430
  exif: true,
357
431
  frames: animated ? img.frames : undefined,
358
432
  width: img.width,
359
433
  height: img.height,
360
434
  loops: img.anim?.loops ?? img.anim?.loopCount ?? 0,
361
- bgColor: img.anim?.bgColor ?? img.anim?.backgroundColor ?? [0, 0, 0, 0],
435
+ bgColor: img.anim?.bgColor ?? img.anim?.backgroundColor ?? [0, 0, 0, 0]
362
436
  });
363
437
  };
364
- const saveLegacyMuxAnim = async () => {
438
+ const saveLegacy = async () => {
365
439
  const opts = {
366
440
  path: webpPath,
367
441
  frames: img.frames || [],
@@ -369,22 +443,12 @@ class StickEngine extends node_events_1.EventEmitter {
369
443
  height: img.height || 512,
370
444
  loops: img.anim?.loopCount ?? img.anim?.loops ?? 0,
371
445
  bgColor: img.anim?.backgroundColor ?? img.anim?.bgColor ?? [0, 0, 0, 0],
372
- exif,
446
+ exif
373
447
  };
374
- if (typeof img.muxAnim === 'function') {
375
- return img.muxAnim({
376
- path: webpPath,
377
- exif: true,
378
- width: opts.width,
379
- height: opts.height,
380
- loops: opts.loops,
381
- bgColor: opts.bgColor,
382
- });
383
- }
384
- if (typeof Image.muxAnim === 'function') {
448
+ if (typeof img.muxAnim === 'function')
449
+ return img.muxAnim({ path: webpPath, exif: true, width: opts.width, height: opts.height, loops: opts.loops, bgColor: opts.bgColor });
450
+ if (typeof Image.muxAnim === 'function')
385
451
  return Image.muxAnim(opts);
386
- }
387
- // absolute last resort: try plain save (v3)
388
452
  return img.save(webpPath);
389
453
  };
390
454
  try {
@@ -394,41 +458,31 @@ class StickEngine extends node_events_1.EventEmitter {
394
458
  }
395
459
  catch (err) {
396
460
  const msg = String(err?.message || err);
397
- if (/muxAnim|animations is not currently supported|Using `save`/i.test(msg)) {
398
- await saveLegacyMuxAnim();
399
- }
461
+ if (/muxAnim|not currently supported/i.test(msg))
462
+ await saveLegacy();
400
463
  else if (animated) {
401
- // some builds want save without second arg
402
464
  try {
403
465
  await img.save(webpPath);
404
466
  }
405
- catch (err2) {
406
- const m2 = String(err2?.message || err2);
407
- if (/muxAnim|animations is not currently supported|Using `save`/i.test(m2)) {
408
- await saveLegacyMuxAnim();
409
- }
410
- else {
411
- throw err2;
412
- }
467
+ catch (e2) {
468
+ if (/muxAnim|not currently supported/i.test(String(e2?.message)))
469
+ await saveLegacy();
470
+ else
471
+ throw e2;
413
472
  }
414
473
  }
415
- else {
474
+ else
416
475
  throw err;
417
- }
418
476
  }
419
477
  }
420
- else if (typeof Image.save === 'function') {
421
- // very old static save(path, image)
478
+ else if (typeof Image.save === 'function')
422
479
  await Image.save(webpPath, img);
423
- }
424
- else {
425
- await saveLegacyMuxAnim();
426
- }
480
+ else
481
+ await saveLegacy();
427
482
  }
428
483
  catch (err) {
429
- const msg = String(err?.message || err);
430
- if (/muxAnim|animations is not currently supported|Using `save`/i.test(msg)) {
431
- await saveLegacyMuxAnim();
484
+ if (/muxAnim|not currently supported/i.test(String(err?.message))) {
485
+ await saveLegacy();
432
486
  return;
433
487
  }
434
488
  throw err;
@@ -441,91 +495,51 @@ class StickEngine extends node_events_1.EventEmitter {
441
495
  const Image = resolveWebpImage();
442
496
  const img = new Image();
443
497
  await img.load(filePath);
444
- return (!!img.hasAnim ||
445
- (typeof img.frameCount === 'number' && img.frameCount > 1) ||
446
- (Array.isArray(img.frames) && img.frames.length > 1));
498
+ return !!img.hasAnim || (typeof img.frameCount === 'number' && img.frameCount > 1) || (Array.isArray(img.frames) && img.frames.length > 1);
447
499
  }
448
500
  catch {
449
501
  return false;
450
502
  }
451
503
  }
452
- // ── FFmpeg / Jimp ────────────────────────────────────────────────────────
453
- /**
454
- * Build FFmpeg -vf chain.
455
- *
456
- * Normal WhatsApp stickers are NOT forced square:
457
- * the longest side becomes 512 and the other side stays proportional
458
- * (e.g. 512×320 or 280×512) with real alpha — same look as hand-made stickers.
459
- */
460
504
  buildScaleFilter(isAnimated, fps) {
461
505
  const fit = this.options.fit || 'natural';
462
506
  const parts = ['format=rgba'];
463
- if (isAnimated) {
507
+ if (isAnimated)
464
508
  parts.push(`fps=${Math.max(1, fps)}`);
465
- }
466
509
  switch (fit) {
467
510
  case 'cover':
468
- // Force square sticker: fill 512×512, center-crop overflow
469
511
  parts.push('scale=512:512:force_original_aspect_ratio=increase:flags=lanczos', 'crop=512:512:(iw-ow)/2:(ih-oh)/2');
470
512
  break;
471
513
  case 'contain':
472
- // Full image inside a square 512×512 with transparent pad (no black bars)
473
514
  parts.push('scale=512:512:force_original_aspect_ratio=decrease:flags=lanczos', 'pad=512:512:(ow-iw)/2:(oh-ih)/2:color=black@0');
474
515
  break;
475
516
  case 'fill':
476
517
  parts.push('scale=512:512:flags=lanczos');
477
518
  break;
478
519
  case 'none':
479
- // Shrink only if larger than 512; never upscale; keep ratio
480
520
  parts.push("scale='min(512,iw)':'min(512,ih)':force_original_aspect_ratio=decrease:flags=lanczos");
481
521
  break;
482
- case 'natural':
483
522
  default:
484
- // DEFAULT — real WhatsApp sticker geometry:
485
- // longest side = 512, other side proportional, NO pad, NO crop
486
- // Example: 1080×1920 → 288×512 | 1920×1080 → 512×288
487
- parts.push(
488
- // -2 keeps aspect and forces even dims (friendlier for some encoders)
489
- "scale='if(gt(iw,ih),512,-2)':'if(gt(ih,iw),512,-2)':flags=lanczos");
523
+ parts.push("scale='if(gt(iw,ih),512,-2)':'if(gt(ih,iw),512,-2)':flags=lanczos");
490
524
  break;
491
525
  }
492
- // Ensure even width/height (webp/yuv safety) without changing aspect much
493
526
  parts.push("scale=trunc(iw/2)*2:trunc(ih/2)*2:flags=lanczos", 'setsar=1', 'format=rgba');
494
527
  return parts.join(',');
495
528
  }
496
529
  async convertToWebp(input, output, isAnimated) {
497
530
  const run = (quality, fps) => new Promise((resolve, reject) => {
498
- const ff = (0, fluent_ffmpeg_1.default)(input).on('error', reject).on('end', () => resolve());
499
531
  const vf = this.buildScaleFilter(isAnimated, fps);
500
532
  const q = Math.max(1, Math.min(100, quality));
501
- const opts = [
502
- '-vcodec',
503
- 'libwebp',
504
- '-vf',
505
- vf,
506
- '-loop',
507
- '0',
508
- '-an',
509
- '-vsync',
510
- '0',
511
- '-preset',
512
- 'default',
513
- ];
514
- if (isAnimated) {
515
- // Animated: lossy + size budget
533
+ const opts = ['-vcodec', 'libwebp', '-vf', vf, '-loop', '0', '-an', '-vsync', '0', '-preset', 'default'];
534
+ if (isAnimated)
516
535
  opts.push('-lossless', '0', '-compression_level', '6', '-q:v', String(q), '-quality', String(q), '-t', String(this.options.maxDuration || 6));
517
- }
518
- else {
519
- // Static anime/art stickers look much better near-lossless
536
+ else
520
537
  opts.push('-lossless', q >= 90 ? '1' : '0', '-compression_level', '6', '-q:v', String(q), '-quality', String(q));
521
- }
522
- ff.addOutputOptions(opts).toFormat('webp').save(output);
538
+ (0, fluent_ffmpeg_1.default)(input).on('error', reject).on('end', () => resolve()).addOutputOptions(opts).toFormat('webp').save(output);
523
539
  });
524
- // Static defaults higher quality; animated stays flexible for 1MB cap
525
540
  let quality = this.options.quality ?? (isAnimated ? 80 : 95);
526
541
  let fps = this.options.fps ?? 15;
527
542
  await run(quality, fps);
528
- // WhatsApp animated stickers should stay under ~1MB
529
543
  if (isAnimated && node_fs_1.default.existsSync(output)) {
530
544
  let attempts = 0;
531
545
  while (node_fs_1.default.statSync(output).size > 1000000 && attempts < 4) {
@@ -535,9 +549,8 @@ class StickEngine extends node_events_1.EventEmitter {
535
549
  attempts++;
536
550
  }
537
551
  }
538
- if (!node_fs_1.default.existsSync(output) || node_fs_1.default.statSync(output).size < 20) {
539
- throw new Error('FFmpeg failed to produce a valid WebP sticker.');
540
- }
552
+ if (!node_fs_1.default.existsSync(output) || node_fs_1.default.statSync(output).size < 20)
553
+ throw new Error('FFmpeg failed');
541
554
  }
542
555
  async applyJimpEdits(input, output, options) {
543
556
  const image = await jimp_1.default.read(input);
@@ -560,99 +573,268 @@ class StickEngine extends node_events_1.EventEmitter {
560
573
  if (options.text?.content) {
561
574
  const fontPath = options.text.color === 'BLACK' ? jimp_1.default.FONT_SANS_32_BLACK : jimp_1.default.FONT_SANS_32_WHITE;
562
575
  const font = await jimp_1.default.loadFont(fontPath);
563
- const printOptions = {
564
- text: options.text.content,
565
- alignmentX: options.text.alignmentX || jimp_1.default.HORIZONTAL_ALIGN_CENTER,
566
- alignmentY: options.text.alignmentY || jimp_1.default.VERTICAL_ALIGN_BOTTOM,
567
- };
576
+ const printOptions = { text: options.text.content, alignmentX: options.text.alignmentX || jimp_1.default.HORIZONTAL_ALIGN_CENTER, alignmentY: options.text.alignmentY || jimp_1.default.VERTICAL_ALIGN_BOTTOM };
568
577
  if (options.text.stroke) {
569
578
  const strokeFont = await jimp_1.default.loadFont(options.text.color === 'BLACK' ? jimp_1.default.FONT_SANS_32_WHITE : jimp_1.default.FONT_SANS_32_BLACK);
570
- for (let x = -2; x <= 2; x++) {
571
- for (let y = -2; y <= 2; y++) {
572
- if (x !== 0 || y !== 0) {
579
+ for (let x = -2; x <= 2; x++)
580
+ for (let y = -2; y <= 2; y++)
581
+ if (x !== 0 || y !== 0)
573
582
  image.print(strokeFont, x, y, printOptions, image.bitmap.width, image.bitmap.height);
574
- }
575
- }
576
- }
577
583
  }
578
584
  image.print(font, 0, 0, printOptions, image.bitmap.width, image.bitmap.height);
579
585
  }
580
586
  await image.writeAsync(output);
581
587
  }
582
- getMediaInfo(file) {
583
- return new Promise((resolve) => {
584
- const args = [
585
- '-v',
586
- 'error',
587
- '-select_streams',
588
- 'v:0',
589
- '-show_entries',
590
- 'stream=width,height,duration',
591
- '-of',
592
- 'csv=p=0:s=x',
593
- file,
588
+ async applyWatermark(input, output, watermark) {
589
+ const findLogo = () => {
590
+ const candidates = [
591
+ node_path_1.default.join(moduleDir, 'logo.png'),
592
+ node_path_1.default.join(moduleDir, '../src/logo.png'),
593
+ node_path_1.default.join(moduleDir, '../logo.png'),
594
+ node_path_1.default.join(process.cwd(), 'logo.png'),
595
+ node_path_1.default.join(process.cwd(), 'src/logo.png'),
596
+ node_path_1.default.join(process.cwd(), 'dist/cjs/logo.png'),
597
+ node_path_1.default.join(process.cwd(), 'dist/esm/logo.png'),
598
+ node_path_1.default.resolve('./logo.png')
594
599
  ];
595
- (0, node_child_process_1.execFile)('ffprobe', args, { timeout: 20000 }, (err, stdout) => {
600
+ for (const p of candidates) {
601
+ try {
602
+ if (node_fs_1.default.existsSync(p) && node_fs_1.default.statSync(p).size > 100)
603
+ return p;
604
+ }
605
+ catch { }
606
+ }
607
+ return null;
608
+ };
609
+ let isImageWatermark = false;
610
+ let logoPath = null;
611
+ let text = 'Lagos Solutions';
612
+ let position = 'bottom-right';
613
+ let margin = 10;
614
+ let sizePercent = 0.25;
615
+ if (watermark === true) {
616
+ isImageWatermark = true;
617
+ logoPath = findLogo();
618
+ }
619
+ else if (typeof watermark === 'string') {
620
+ const possiblePath = node_path_1.default.resolve(watermark);
621
+ if ((watermark.endsWith('.png') || watermark.endsWith('.jpg') || watermark.endsWith('.jpeg') || watermark.endsWith('.webp')) && node_fs_1.default.existsSync(possiblePath)) {
622
+ isImageWatermark = true;
623
+ logoPath = possiblePath;
624
+ }
625
+ else if (node_fs_1.default.existsSync(watermark)) {
626
+ isImageWatermark = true;
627
+ logoPath = watermark;
628
+ }
629
+ else {
630
+ text = watermark;
631
+ isImageWatermark = false;
632
+ }
633
+ }
634
+ else if (typeof watermark === 'object') {
635
+ text = watermark.text || text;
636
+ position = watermark.position || position;
637
+ margin = watermark.margin ?? margin;
638
+ sizePercent = watermark.size ?? watermark.sizePercent ?? sizePercent;
639
+ if (watermark.image || watermark.logo || watermark.path) {
640
+ isImageWatermark = true;
641
+ logoPath = watermark.image || watermark.logo || watermark.path;
642
+ if (logoPath && !node_fs_1.default.existsSync(logoPath)) {
643
+ const resolved = node_path_1.default.resolve(logoPath);
644
+ if (node_fs_1.default.existsSync(resolved))
645
+ logoPath = resolved;
646
+ else
647
+ logoPath = findLogo();
648
+ }
649
+ }
650
+ else if (watermark.text) {
651
+ isImageWatermark = false;
652
+ }
653
+ else {
654
+ isImageWatermark = true;
655
+ logoPath = findLogo();
656
+ }
657
+ }
658
+ if (isImageWatermark) {
659
+ if (!logoPath)
660
+ logoPath = findLogo();
661
+ if (!logoPath || !node_fs_1.default.existsSync(logoPath)) {
662
+ node_fs_1.default.copyFileSync(input, output);
663
+ return;
664
+ }
665
+ try {
666
+ const main = await jimp_1.default.read(input);
667
+ const logo = await jimp_1.default.read(logoPath);
668
+ const mainWidth = main.bitmap.width;
669
+ const mainHeight = main.bitmap.height;
670
+ const logoTargetWidth = Math.floor(mainWidth * sizePercent);
671
+ logo.resize(logoTargetWidth, jimp_1.default.AUTO);
672
+ if (typeof watermark === 'object' && watermark.opacity !== undefined) {
673
+ logo.opacity(watermark.opacity);
674
+ }
675
+ else {
676
+ logo.opacity(0.85);
677
+ }
678
+ let x = mainWidth - logo.bitmap.width - margin;
679
+ let y = mainHeight - logo.bitmap.height - margin;
680
+ if (position === 'bottom-left') {
681
+ x = margin;
682
+ y = mainHeight - logo.bitmap.height - margin;
683
+ }
684
+ if (position === 'top-right') {
685
+ x = mainWidth - logo.bitmap.width - margin;
686
+ y = margin;
687
+ }
688
+ if (position === 'top-left') {
689
+ x = margin;
690
+ y = margin;
691
+ }
692
+ if (position === 'center') {
693
+ x = (mainWidth - logo.bitmap.width) / 2;
694
+ y = (mainHeight - logo.bitmap.height) / 2;
695
+ }
696
+ main.composite(logo, x, y, { mode: jimp_1.default.BLEND_SOURCE_OVER, opacitySource: 1, opacityDest: 1 });
697
+ await main.writeAsync(output);
698
+ return;
699
+ }
700
+ catch {
701
+ node_fs_1.default.copyFileSync(input, output);
702
+ return;
703
+ }
704
+ }
705
+ if (!text) {
706
+ node_fs_1.default.copyFileSync(input, output);
707
+ return;
708
+ }
709
+ const image = await jimp_1.default.read(input);
710
+ const font = await jimp_1.default.loadFont(jimp_1.default.FONT_SANS_16_WHITE);
711
+ const textWidth = jimp_1.default.measureText(font, text);
712
+ const textHeight = jimp_1.default.measureTextHeight(font, text, image.bitmap.width);
713
+ let x = image.bitmap.width - textWidth - margin;
714
+ let y = image.bitmap.height - textHeight - margin;
715
+ if (position === 'bottom-left') {
716
+ x = margin;
717
+ y = image.bitmap.height - textHeight - margin;
718
+ }
719
+ if (position === 'top-right') {
720
+ x = image.bitmap.width - textWidth - margin;
721
+ y = margin;
722
+ }
723
+ if (position === 'top-left') {
724
+ x = margin;
725
+ y = margin;
726
+ }
727
+ if (position === 'center') {
728
+ x = (image.bitmap.width - textWidth) / 2;
729
+ y = (image.bitmap.height - textHeight) / 2;
730
+ }
731
+ const bg = new jimp_1.default(textWidth + 10, textHeight + 6, 0x00000088);
732
+ image.composite(bg, x - 5, y - 3);
733
+ image.print(font, x, y, text);
734
+ await image.writeAsync(output);
735
+ }
736
+ getMediaInfo(file) {
737
+ const probePath = ffprobePath || 'ffprobe';
738
+ return new Promise(resolve => {
739
+ (0, node_child_process_1.execFile)(probePath, ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=width,height,duration', '-of', 'csv=p=0:s=x', file], { timeout: 20000 }, (err, stdout) => {
596
740
  if (err)
597
741
  return resolve({ width: 0, height: 0, duration: 0 });
598
742
  const parts = String(stdout).trim().split('x');
599
- resolve({
600
- width: parseInt(parts[0], 10) || 0,
601
- height: parseInt(parts[1], 10) || 0,
602
- duration: parseFloat(parts[2]) || 0,
603
- });
743
+ resolve({ width: parseInt(parts[0], 10) || 0, height: parseInt(parts[1], 10) || 0, duration: parseFloat(parts[2]) || 0 });
604
744
  });
605
745
  });
606
746
  }
607
- // ── Extras ───────────────────────────────────────────────────────────────
608
747
  static fileToBase64(filePath) {
609
748
  return node_fs_1.default.readFileSync(filePath).toString('base64');
610
749
  }
611
- /** 96×96 tray icon WebP for sticker packs. */
612
- async createTrayIcon(input) {
613
- const ts = Date.now();
614
- const pngPath = node_path_1.default.join(this.tempDir, `tray_${ts}.png`);
615
- const webpPath = node_path_1.default.join(this.tempDir, `tray_${ts}.webp`);
616
- let buffer;
617
- if (Buffer.isBuffer(input))
618
- buffer = input;
619
- else if (typeof input === 'string' && isHttp(input))
620
- buffer = await getBuffer(input);
621
- else if (typeof input === 'string' && node_fs_1.default.existsSync(input))
622
- buffer = node_fs_1.default.readFileSync(input);
623
- else
624
- throw new Error('Invalid tray icon input.');
625
- const image = await jimp_1.default.read(buffer);
626
- await image.cover(96, 96).writeAsync(pngPath);
627
- await new Promise((resolve, reject) => {
628
- (0, fluent_ffmpeg_1.default)(pngPath)
629
- .on('error', reject)
630
- .on('end', () => resolve())
631
- .addOutputOptions(['-vcodec', 'libwebp', '-lossless', '1', '-q:v', '80'])
632
- .toFormat('webp')
633
- .save(webpPath);
634
- });
635
- this.track(pngPath);
636
- this.track(webpPath);
637
- return webpPath;
638
- }
639
- /** Text-only sticker (transparent 512×512). */
640
- async createTextSticker(text, options = {}) {
641
- const ts = Date.now();
642
- const pngPath = node_path_1.default.join(this.tempDir, `text_${ts}.png`);
643
- const image = new jimp_1.default(512, 512, options.background ?? 0x00000000);
644
- const fontPath = options.color === 'BLACK' ? jimp_1.default.FONT_SANS_64_BLACK : jimp_1.default.FONT_SANS_64_WHITE;
645
- const font = await jimp_1.default.loadFont(fontPath);
646
- image.print(font, 0, 0, {
647
- text,
648
- alignmentX: jimp_1.default.HORIZONTAL_ALIGN_CENTER,
649
- alignmentY: jimp_1.default.VERTICAL_ALIGN_MIDDLE,
650
- }, 512, 512);
651
- await image.writeAsync(pngPath);
652
- this.track(pngPath);
653
- return this.build(pngPath);
654
- }
655
750
  }
656
751
  exports.StickEngine = StickEngine;
752
+ async function sendImageAsSticker(conn, jid, input, quoted, options = {}) {
753
+ const normalized = normalizeMetadata(options);
754
+ const engine = new StickEngine({
755
+ metadata: { pack: normalized.pack, author: normalized.author, emojis: normalized.emojis, id: normalized.id },
756
+ fit: options.fit || 'natural',
757
+ quality: options.quality ?? 95,
758
+ tempDir: resolveTempDir(options),
759
+ watermark: resolveWatermark(options),
760
+ ...options
761
+ });
762
+ let finalInput = input;
763
+ if (typeof input === 'string') {
764
+ if (isDataURI(input))
765
+ finalInput = parseDataURItoBuffer(input);
766
+ else if (isHttp(input))
767
+ finalInput = await getBuffer(input);
768
+ }
769
+ const result = await engine.build(finalInput);
770
+ await conn.sendMessage(jid, { sticker: { url: result.buffer }, ...options }, { quoted });
771
+ return result.buffer;
772
+ }
773
+ async function sendVideoAsSticker(conn, jid, input, quoted, options = {}) {
774
+ const normalized = normalizeMetadata(options);
775
+ const engine = new StickEngine({
776
+ metadata: { pack: normalized.pack, author: normalized.author, emojis: normalized.emojis, id: normalized.id },
777
+ fit: options.fit || 'natural',
778
+ fps: options.fps || 15,
779
+ maxDuration: options.maxDuration || 6,
780
+ quality: options.quality ?? 80,
781
+ tempDir: resolveTempDir(options),
782
+ watermark: resolveWatermark(options),
783
+ ...options
784
+ });
785
+ let finalInput = input;
786
+ if (typeof input === 'string') {
787
+ if (isDataURI(input))
788
+ finalInput = parseDataURItoBuffer(input);
789
+ else if (isHttp(input))
790
+ finalInput = await getBuffer(input);
791
+ }
792
+ const result = await engine.build(finalInput);
793
+ await conn.sendMessage(jid, { sticker: { url: result.buffer }, ...options }, { quoted });
794
+ return result.buffer;
795
+ }
796
+ exports.sendImageAsSticker2 = sendImageAsSticker;
797
+ exports.sendVideoAsSticker2 = sendVideoAsSticker;
798
+ async function imageToWebp(media) {
799
+ const engine = new StickEngine({ fit: 'natural', quality: 95 });
800
+ const ts = `${Date.now()}_${node_crypto_1.default.randomBytes(3).toString('hex')}`;
801
+ const filePath = node_path_1.default.join(engine.tempDir, `img_${ts}.png`);
802
+ node_fs_1.default.writeFileSync(filePath, media);
803
+ const out = node_path_1.default.join(engine.tempDir, `out_${ts}.webp`);
804
+ await engine.convertToWebp(filePath, out, false);
805
+ const buf = node_fs_1.default.readFileSync(out);
806
+ try {
807
+ node_fs_1.default.unlinkSync(filePath);
808
+ node_fs_1.default.unlinkSync(out);
809
+ }
810
+ catch { }
811
+ return buf;
812
+ }
813
+ async function videoToWebp(media) {
814
+ const engine = new StickEngine({ fit: 'natural', fps: 15, maxDuration: 5 });
815
+ const ts = `${Date.now()}_${node_crypto_1.default.randomBytes(3).toString('hex')}`;
816
+ const filePath = node_path_1.default.join(engine.tempDir, `vid_${ts}.mp4`);
817
+ node_fs_1.default.writeFileSync(filePath, media);
818
+ const out = node_path_1.default.join(engine.tempDir, `out_${ts}.webp`);
819
+ await engine.convertToWebp(filePath, out, true);
820
+ const buf = node_fs_1.default.readFileSync(out);
821
+ try {
822
+ node_fs_1.default.unlinkSync(filePath);
823
+ node_fs_1.default.unlinkSync(out);
824
+ }
825
+ catch { }
826
+ return buf;
827
+ }
828
+ async function writeExifImg(media, metadata) {
829
+ const result = await StickEngine.create(media, { metadata: { pack: metadata.packname || metadata.pack, author: metadata.author, emojis: metadata.categories || metadata.emojis } });
830
+ return result.path;
831
+ }
832
+ async function writeExifVid(media, metadata) {
833
+ return writeExifImg(media, metadata);
834
+ }
835
+ exports.imageToWebp2 = imageToWebp;
836
+ exports.videoToWebp2 = videoToWebp;
837
+ exports.writeExifImg2 = writeExifImg;
838
+ exports.writeExifVid2 = writeExifVid;
657
839
  exports.default = StickEngine;
658
840
  //# sourceMappingURL=StickEngine.js.map