@boruto_vk7/stickengine 0.0.4 → 0.0.5

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.
Files changed (2) hide show
  1. package/dist/StickEngine.js +64 -60
  2. package/package.json +1 -1
@@ -3,19 +3,14 @@ import path from 'path';
3
3
  import axios from 'axios';
4
4
  import { exec } from 'child_process';
5
5
  import { EventEmitter } from 'events';
6
- import ffmpeg from 'fluent-ffmpeg';
7
- // @ts-ignore
8
6
  import webpMux from 'node-webpmux';
9
- // @ts-ignore
10
7
  import Jimp from 'jimp';
11
- // @ts-ignore
12
8
  import { removeBackgroundFromImageFile } from 'remove.bg';
13
9
  import { fileURLToPath } from 'url';
10
+
14
11
  const __filename = fileURLToPath(import.meta.url);
15
12
  const __dirname = path.dirname(__filename);
16
- /**
17
- * Faz GET em uma URL e retorna o corpo como Buffer.
18
- */
13
+
19
14
  export const getBuffer = async (url, options = {}) => {
20
15
  try {
21
16
  const { data } = await axios({
@@ -40,11 +35,13 @@ export const getBuffer = async (url, options = {}) => {
40
35
  return Buffer.alloc(0);
41
36
  }
42
37
  };
38
+
43
39
  export class StickEngine extends EventEmitter {
44
40
  options;
45
41
  tempDir;
46
42
  queue;
47
43
  createdFiles;
44
+
48
45
  constructor(options = {}) {
49
46
  super();
50
47
  this.options = {
@@ -69,10 +66,12 @@ export class StickEngine extends EventEmitter {
69
66
  this.queue = [];
70
67
  this.createdFiles = [];
71
68
  }
69
+
72
70
  addFile(file) {
73
71
  this.queue.push(file);
74
72
  return this;
75
73
  }
74
+
76
75
  clean() {
77
76
  for (const file of this.createdFiles) {
78
77
  try {
@@ -86,6 +85,7 @@ export class StickEngine extends EventEmitter {
86
85
  }
87
86
  this.createdFiles = [];
88
87
  }
88
+
89
89
  async processFile(input, index) {
90
90
  let filePath = '';
91
91
  let extension = '';
@@ -95,6 +95,7 @@ export class StickEngine extends EventEmitter {
95
95
  this.emit('st.start', { index, input });
96
96
  const ft = await import('file-type');
97
97
  const fileTypeFunc = ft.fromBuffer || ft.fileTypeFromBuffer || (ft.default && (ft.default.fromBuffer || ft.default.fileTypeFromBuffer));
98
+
98
99
  if (Buffer.isBuffer(input)) {
99
100
  const type = await fileTypeFunc(input);
100
101
  extension = type ? type.ext : 'bin';
@@ -116,18 +117,19 @@ export class StickEngine extends EventEmitter {
116
117
  else {
117
118
  throw new Error('Tipo de entrada inválido ou arquivo não encontrado.');
118
119
  }
120
+
119
121
  const mediaInfo = await this.getMediaInfo(filePath);
120
122
  this.emit('st.info', { index, ...mediaInfo });
121
123
  let currentFile = filePath;
122
124
  const isAnimated = mediaInfo.duration > 0 || extension === 'gif' || extension === 'mp4' || extension === 'webp';
123
- // Aplicar Edições Jimp (Apenas para Imagens Estáticas por enquanto)
125
+
124
126
  if (this.options.edit && !isAnimated) {
125
127
  const editedPath = path.join(this.tempDir, `edited_${timestamp}_${index}.png`);
126
128
  await this.applyJimpEdits(currentFile, editedPath, this.options.edit);
127
129
  localCreatedFiles.push(editedPath);
128
130
  currentFile = editedPath;
129
131
  }
130
- // Remoção de fundo (Apenas para Imagens Estáticas)
132
+
131
133
  if (this.options.transparent && !isAnimated) {
132
134
  const apiKey = Array.isArray(this.options.transparent)
133
135
  ? this.options.transparent[Math.floor(Math.random() * this.options.transparent.length)]
@@ -145,15 +147,16 @@ export class StickEngine extends EventEmitter {
145
147
  currentFile = bgRemovedPath;
146
148
  }
147
149
  }
148
- // Conversão para WebP (Suporta Estático e Animado)
150
+
149
151
  const webpPath = path.join(this.tempDir, `sticker_${timestamp}_${index}.webp`);
150
152
  await this.convertToWebp(currentFile, webpPath, isAnimated);
151
153
  localCreatedFiles.push(webpPath);
152
154
  currentFile = webpPath;
153
- // Adição de Metadados (Exif)
155
+
154
156
  if (this.options.metadata) {
155
157
  await this.addExif(currentFile, this.options.metadata);
156
158
  }
159
+
157
160
  this.createdFiles.push(...localCreatedFiles);
158
161
  this.emit('st.data', { index, file: currentFile });
159
162
  return currentFile;
@@ -167,18 +170,14 @@ export class StickEngine extends EventEmitter {
167
170
  throw error;
168
171
  }
169
172
  }
173
+
170
174
  async applyJimpEdits(input, output, options) {
171
175
  const image = await Jimp.read(input);
172
- if (options.greyscale)
173
- image.greyscale();
174
- if (options.invert)
175
- image.invert();
176
- if (options.sepia)
177
- image.sepia();
178
- if (options.blur)
179
- image.blur(options.blur);
180
- if (options.resize)
181
- image.resize(options.resize.width, options.resize.height);
176
+ if (options.greyscale) image.greyscale();
177
+ if (options.invert) image.invert();
178
+ if (options.sepia) image.sepia();
179
+ if (options.blur) image.blur(options.blur);
180
+ if (options.resize) image.resize(options.resize.width, options.resize.height);
182
181
  if (options.text) {
183
182
  const font = await Jimp.loadFont(Jimp.FONT_SANS_32_WHITE);
184
183
  image.print(font, 0, 0, {
@@ -189,11 +188,11 @@ export class StickEngine extends EventEmitter {
189
188
  }
190
189
  await image.writeAsync(output);
191
190
  }
191
+
192
192
  getMediaInfo(file) {
193
193
  return new Promise((resolve) => {
194
- exec(`ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration -of csv=p=0:s=x ${file}`, (err, stdout) => {
195
- if (err)
196
- return resolve({ width: 0, height: 0, duration: 0 });
194
+ exec(`ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration -of csv=p=0:s=x "${file}"`, (err, stdout) => {
195
+ if (err) return resolve({ width: 0, height: 0, duration: 0 });
197
196
  const parts = stdout.trim().split('x');
198
197
  resolve({
199
198
  width: parseInt(parts[0]) || 0,
@@ -203,53 +202,58 @@ export class StickEngine extends EventEmitter {
203
202
  });
204
203
  });
205
204
  }
205
+
206
206
  convertToWebp(input, output, isAnimated) {
207
207
  return new Promise((resolve, reject) => {
208
- const ff = ffmpeg(input)
209
- .on('error', reject)
210
- .on('end', () => resolve());
211
- const videoOptions = [
212
- '-vcodec', 'libwebp',
213
- '-vf', `scale=512:512:force_original_aspect_ratio=decrease,fps=${this.options.fps},pad=512:512:(ow-iw)/2:(oh-ih)/2:color=#00000000,setsar=1`,
214
- '-lossless', '1',
215
- '-loop', '0',
216
- '-preset', 'default',
217
- '-an',
218
- '-vsync', '0'
219
- ];
208
+ const fps = this.options.fps;
209
+ const quality = this.options.quality;
210
+ const scale = `scale=512:512:force_original_aspect_ratio=decrease,pad=512:512:(ow-iw)/2:(oh-ih)/2:color=#00000000,setsar=1`;
211
+
212
+ let cmd;
220
213
  if (isAnimated) {
221
- // Opções específicas para animação para manter o peso baixo (limite do WhatsApp é 1MB)
222
- videoOptions.push('-t', '6'); // Limita a 6 segundos
223
- videoOptions.push('-q:v', String(this.options.quality));
214
+ cmd = `ffmpeg -y -i "${input}" -vcodec libwebp -vf "fps=${fps},${scale}" -lossless 0 -q:v ${quality} -loop 0 -preset default -an -vsync 0 -t 6 "${output}"`;
215
+ } else {
216
+ cmd = `ffmpeg -y -i "${input}" -vcodec libwebp -vf "${scale}" -lossless 1 -loop 0 -preset default -an -vsync 0 "${output}"`;
224
217
  }
225
- ff.addOutputOptions(videoOptions)
226
- .toFormat('webp')
227
- .save(output);
218
+
219
+ exec(cmd, (err, stdout, stderr) => {
220
+ if (err) return reject(new Error(stderr || err.message));
221
+ resolve();
222
+ });
228
223
  });
229
224
  }
225
+
230
226
  async addExif(webpPath, metadata) {
231
- const json = {
232
- 'sticker-pack-id': 'StickEngine-Official',
233
- 'sticker-pack-name': metadata.pack || 'StickEngine',
234
- 'sticker-pack-publisher': metadata.author || 'Borutovk7',
235
- 'emojis': metadata.emojis || ['🥶']
236
- };
237
- const Image = webpMux.Image || (webpMux.default && webpMux.default.Image);
238
- const img = new Image();
239
- 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]);
240
- const jsonBuff = Buffer.from(JSON.stringify(json), 'utf-8');
241
- const exifBuffer = Buffer.concat([exifAttr, jsonBuff]);
242
- exifBuffer.writeUIntLE(jsonBuff.length, 14, 4);
243
- await img.load(webpPath);
244
- img.exif = exifBuffer;
245
- await img.save(webpPath);
227
+ const json = {
228
+ 'sticker-pack-id': 'StickEngine-Official',
229
+ 'sticker-pack-name': metadata.pack || 'StickEngine',
230
+ 'sticker-pack-publisher': metadata.author || 'Borutovk7',
231
+ 'emojis': metadata.emojis || ['🥶']
232
+ };
233
+
234
+ 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]);
235
+ const jsonBuff = Buffer.from(JSON.stringify(json), 'utf-8');
236
+ const exifBuffer = Buffer.concat([exifAttr, jsonBuff]);
237
+ exifBuffer.writeUIntLE(jsonBuff.length, 14, 4);
238
+
239
+ const { Image } = webpMux;
240
+ const img = new Image();
241
+ await img.load(webpPath);
242
+ img.exif = exifBuffer;
243
+
244
+ if (img.hasAnim) {
245
+ await img.muxAnim({ path: webpPath });
246
+ } else {
247
+ await Image.save(webpPath, img);
246
248
  }
249
+ }
250
+
247
251
  async start() {
248
- if (this.queue.length === 0)
249
- throw new Error('Nenhum arquivo na fila.');
252
+ if (this.queue.length === 0) throw new Error('Nenhum arquivo na fila.');
250
253
  const results = await Promise.allSettled(this.queue.map((file, i) => this.processFile(file, i)));
251
254
  this.queue = [];
252
255
  return results;
253
256
  }
254
257
  }
255
- export default StickEngine;
258
+
259
+ export default StickEngine;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boruto_vk7/stickengine",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "StickEngine module for creating WhatsApp stickers",
5
5
  "type": "module",
6
6
  "main": "dist/StickEngine.js",