@boruto_vk7/stickengine 1.1.1 → 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,
@@ -126,45 +219,45 @@ class StickEngine extends node_events_1.EventEmitter {
126
219
  fps: 15,
127
220
  quality: 75,
128
221
  maxDuration: 6,
129
- fit: 'cover',
130
- metadata: {
131
- pack: 'StickEngine',
132
- author: 'Borutovk7',
133
- emojis: ['✨'],
134
- },
222
+ fit: 'natural',
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,90 +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 for a clean 512 sticker canvas.
455
- * Critical: `format=rgba` BEFORE pad, and pad color must be fully transparent
456
- * (`black@0`). Using `color=0x00000000` after an opaque format yields black bars.
457
- */
458
504
  buildScaleFilter(isAnimated, fps) {
459
- const fit = this.options.fit || 'cover';
505
+ const fit = this.options.fit || 'natural';
460
506
  const parts = ['format=rgba'];
461
- if (isAnimated) {
507
+ if (isAnimated)
462
508
  parts.push(`fps=${Math.max(1, fps)}`);
463
- }
464
509
  switch (fit) {
510
+ case 'cover':
511
+ parts.push('scale=512:512:force_original_aspect_ratio=increase:flags=lanczos', 'crop=512:512:(iw-ow)/2:(ih-oh)/2');
512
+ break;
465
513
  case 'contain':
466
- // Full image visible + true transparent margins (no black bars)
467
- parts.push('scale=512:512:force_original_aspect_ratio=decrease:flags=lanczos',
468
- // black@0 = transparent; must come after format=rgba
469
- 'pad=512:512:(ow-iw)/2:(oh-ih)/2:color=black@0');
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');
470
515
  break;
471
516
  case 'fill':
472
- // Stretch (rarely wanted)
473
517
  parts.push('scale=512:512:flags=lanczos');
474
518
  break;
475
519
  case 'none':
476
- // Only shrink if larger than 512 on any side; keep original ratio; no pad
477
520
  parts.push("scale='min(512,iw)':'min(512,ih)':force_original_aspect_ratio=decrease:flags=lanczos");
478
521
  break;
479
- case 'cover':
480
522
  default:
481
- // DEFAULT — fill the sticker square like normal WhatsApp stickers
482
- // scale up to cover 512×512, then center-crop (no empty margins)
483
- parts.push('scale=512:512:force_original_aspect_ratio=increase:flags=lanczos', 'crop=512:512:(iw-ow)/2:(ih-oh)/2');
523
+ parts.push("scale='if(gt(iw,ih),512,-2)':'if(gt(ih,iw),512,-2)':flags=lanczos");
484
524
  break;
485
525
  }
486
- parts.push('setsar=1', 'format=rgba');
526
+ parts.push("scale=trunc(iw/2)*2:trunc(ih/2)*2:flags=lanczos", 'setsar=1', 'format=rgba');
487
527
  return parts.join(',');
488
528
  }
489
529
  async convertToWebp(input, output, isAnimated) {
490
530
  const run = (quality, fps) => new Promise((resolve, reject) => {
491
- const ff = (0, fluent_ffmpeg_1.default)(input).on('error', reject).on('end', () => resolve());
492
531
  const vf = this.buildScaleFilter(isAnimated, fps);
493
- const opts = [
494
- '-vcodec',
495
- 'libwebp',
496
- '-vf',
497
- vf,
498
- '-pix_fmt',
499
- 'yuva420p',
500
- '-lossless',
501
- isAnimated ? '0' : '0',
502
- '-compression_level',
503
- '6',
504
- '-q:v',
505
- String(Math.max(1, Math.min(100, quality))),
506
- '-loop',
507
- '0',
508
- '-preset',
509
- 'default',
510
- '-an',
511
- '-vsync',
512
- '0',
513
- ];
514
- // Prefer alpha-capable webp
515
- if (!isAnimated) {
516
- // slight quality bump for static; still lossy keeps alpha reliable across ffmpeg builds
517
- opts.push('-quality', String(Math.max(1, Math.min(100, quality))));
518
- }
519
- if (isAnimated) {
520
- opts.push('-t', String(this.options.maxDuration || 6));
521
- }
522
- ff.addOutputOptions(opts).toFormat('webp').save(output);
532
+ const q = Math.max(1, Math.min(100, quality));
533
+ const opts = ['-vcodec', 'libwebp', '-vf', vf, '-loop', '0', '-an', '-vsync', '0', '-preset', 'default'];
534
+ if (isAnimated)
535
+ opts.push('-lossless', '0', '-compression_level', '6', '-q:v', String(q), '-quality', String(q), '-t', String(this.options.maxDuration || 6));
536
+ else
537
+ opts.push('-lossless', q >= 90 ? '1' : '0', '-compression_level', '6', '-q:v', String(q), '-quality', String(q));
538
+ (0, fluent_ffmpeg_1.default)(input).on('error', reject).on('end', () => resolve()).addOutputOptions(opts).toFormat('webp').save(output);
523
539
  });
524
- let quality = this.options.quality ?? 80;
540
+ let quality = this.options.quality ?? (isAnimated ? 80 : 95);
525
541
  let fps = this.options.fps ?? 15;
526
542
  await run(quality, fps);
527
- // WhatsApp animated stickers should stay under ~1MB
528
543
  if (isAnimated && node_fs_1.default.existsSync(output)) {
529
544
  let attempts = 0;
530
545
  while (node_fs_1.default.statSync(output).size > 1000000 && attempts < 4) {
@@ -534,9 +549,8 @@ class StickEngine extends node_events_1.EventEmitter {
534
549
  attempts++;
535
550
  }
536
551
  }
537
- if (!node_fs_1.default.existsSync(output) || node_fs_1.default.statSync(output).size < 20) {
538
- throw new Error('FFmpeg failed to produce a valid WebP sticker.');
539
- }
552
+ if (!node_fs_1.default.existsSync(output) || node_fs_1.default.statSync(output).size < 20)
553
+ throw new Error('FFmpeg failed');
540
554
  }
541
555
  async applyJimpEdits(input, output, options) {
542
556
  const image = await jimp_1.default.read(input);
@@ -559,99 +573,268 @@ class StickEngine extends node_events_1.EventEmitter {
559
573
  if (options.text?.content) {
560
574
  const fontPath = options.text.color === 'BLACK' ? jimp_1.default.FONT_SANS_32_BLACK : jimp_1.default.FONT_SANS_32_WHITE;
561
575
  const font = await jimp_1.default.loadFont(fontPath);
562
- const printOptions = {
563
- text: options.text.content,
564
- alignmentX: options.text.alignmentX || jimp_1.default.HORIZONTAL_ALIGN_CENTER,
565
- alignmentY: options.text.alignmentY || jimp_1.default.VERTICAL_ALIGN_BOTTOM,
566
- };
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 };
567
577
  if (options.text.stroke) {
568
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);
569
- for (let x = -2; x <= 2; x++) {
570
- for (let y = -2; y <= 2; y++) {
571
- 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)
572
582
  image.print(strokeFont, x, y, printOptions, image.bitmap.width, image.bitmap.height);
573
- }
574
- }
575
- }
576
583
  }
577
584
  image.print(font, 0, 0, printOptions, image.bitmap.width, image.bitmap.height);
578
585
  }
579
586
  await image.writeAsync(output);
580
587
  }
581
- getMediaInfo(file) {
582
- return new Promise((resolve) => {
583
- const args = [
584
- '-v',
585
- 'error',
586
- '-select_streams',
587
- 'v:0',
588
- '-show_entries',
589
- 'stream=width,height,duration',
590
- '-of',
591
- 'csv=p=0:s=x',
592
- 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')
593
599
  ];
594
- (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) => {
595
740
  if (err)
596
741
  return resolve({ width: 0, height: 0, duration: 0 });
597
742
  const parts = String(stdout).trim().split('x');
598
- resolve({
599
- width: parseInt(parts[0], 10) || 0,
600
- height: parseInt(parts[1], 10) || 0,
601
- duration: parseFloat(parts[2]) || 0,
602
- });
743
+ resolve({ width: parseInt(parts[0], 10) || 0, height: parseInt(parts[1], 10) || 0, duration: parseFloat(parts[2]) || 0 });
603
744
  });
604
745
  });
605
746
  }
606
- // ── Extras ───────────────────────────────────────────────────────────────
607
747
  static fileToBase64(filePath) {
608
748
  return node_fs_1.default.readFileSync(filePath).toString('base64');
609
749
  }
610
- /** 96×96 tray icon WebP for sticker packs. */
611
- async createTrayIcon(input) {
612
- const ts = Date.now();
613
- const pngPath = node_path_1.default.join(this.tempDir, `tray_${ts}.png`);
614
- const webpPath = node_path_1.default.join(this.tempDir, `tray_${ts}.webp`);
615
- let buffer;
616
- if (Buffer.isBuffer(input))
617
- buffer = input;
618
- else if (typeof input === 'string' && isHttp(input))
619
- buffer = await getBuffer(input);
620
- else if (typeof input === 'string' && node_fs_1.default.existsSync(input))
621
- buffer = node_fs_1.default.readFileSync(input);
622
- else
623
- throw new Error('Invalid tray icon input.');
624
- const image = await jimp_1.default.read(buffer);
625
- await image.cover(96, 96).writeAsync(pngPath);
626
- await new Promise((resolve, reject) => {
627
- (0, fluent_ffmpeg_1.default)(pngPath)
628
- .on('error', reject)
629
- .on('end', () => resolve())
630
- .addOutputOptions(['-vcodec', 'libwebp', '-lossless', '1', '-q:v', '80'])
631
- .toFormat('webp')
632
- .save(webpPath);
633
- });
634
- this.track(pngPath);
635
- this.track(webpPath);
636
- return webpPath;
637
- }
638
- /** Text-only sticker (transparent 512×512). */
639
- async createTextSticker(text, options = {}) {
640
- const ts = Date.now();
641
- const pngPath = node_path_1.default.join(this.tempDir, `text_${ts}.png`);
642
- const image = new jimp_1.default(512, 512, options.background ?? 0x00000000);
643
- const fontPath = options.color === 'BLACK' ? jimp_1.default.FONT_SANS_64_BLACK : jimp_1.default.FONT_SANS_64_WHITE;
644
- const font = await jimp_1.default.loadFont(fontPath);
645
- image.print(font, 0, 0, {
646
- text,
647
- alignmentX: jimp_1.default.HORIZONTAL_ALIGN_CENTER,
648
- alignmentY: jimp_1.default.VERTICAL_ALIGN_MIDDLE,
649
- }, 512, 512);
650
- await image.writeAsync(pngPath);
651
- this.track(pngPath);
652
- return this.build(pngPath);
653
- }
654
750
  }
655
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;
656
839
  exports.default = StickEngine;
657
840
  //# sourceMappingURL=StickEngine.js.map