@boruto_vk7/stickengine 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,121 +1,130 @@
1
1
  # 🚀 StickEngine
2
2
 
3
- **StickEngine** é um motor poderoso e leve para criação de figurinhas do WhatsApp (estáticas e animadas) com suporte integrado a filtros de imagem, adição de texto e remoção de fundo. Projetado para funcionar em qualquer lugar, do **Termux** a servidores dedicados.
4
-
5
- [![NPM Version](https://img.shields.io/npm/v/@boruto_vk7/stickengine?style=flat-square)](https://www.npmjs.com/package/@boruto_vk7/stickengine)
6
- [![License](https://img.shields.io/npm/l/@boruto_vk7/stickengine?style=flat-square)](https://github.com/Borutovk7/StickEngine)
7
-
8
- ---
9
-
10
- ## ✨ Funcionalidades
11
-
12
- - 🎞️ **Figurinhas Animadas**: Suporte automático para GIFs e Vídeos (MP4).
13
- - 🎨 **Filtros Profissionais**: Sépia, Tons de Cinza, Inversão e Desfoque (Blur).
14
- - ✍️ **Texto Customizado**: Adicione legendas diretamente na figurinha.
15
- - ✂️ **Remoção de Fundo**: Integração com a API `remove.bg`.
16
- - 🧹 **Limpeza Automática**: Sistema de gerenciamento de arquivos temporários com o método `.clean()`.
17
- - 🏷️ **Metadados (Exif)**: Personalize o nome do pacote e o autor sem complicação.
18
- - 📱 **Termux Friendly**: Sem dependências nativas pesadas como `sharp`.
19
-
20
- ---
21
-
22
- ## 🖼️ Exemplos Visuais
23
-
24
- | Filtro Séia + Texto | Figurinha Animada (Mockup) |
25
- | :---: | :---: |
26
- | ![Sepia Preview](assets/preview_sepia.png) | ![Animated Preview](assets/preview_animated.png) |
3
+ O **StickEngine** é um motor de alta performance para criação de figurinhas do WhatsApp, projetado para ser simples para iniciantes e extremamente poderoso para desenvolvedores avançados.
27
4
 
28
5
  ---
29
6
 
30
- ## 📦 Instalação
7
+ ## 📦 1. Começando (Básico)
31
8
 
9
+ ### Instalação
32
10
  ```bash
33
11
  npm install @boruto_vk7/stickengine
34
12
  ```
13
+ > **Requisito**: Tenha o `ffmpeg` instalado no seu sistema.
35
14
 
36
- > **Requisito:** Certifique-se de ter o [FFmpeg](https://ffmpeg.org/) instalado no seu sistema. No Termux: `pkg install ffmpeg`.
37
-
38
- ---
39
-
40
- ## 🚀 Como Usar (Exemplos Técnicos)
15
+ ### Suporte Híbrido
16
+ O módulo funciona tanto com **ESM** quanto **CommonJS**:
17
+ - **ESM**: `import StickEngine from '@boruto_vk7/stickengine';`
18
+ - **CJS**: `const StickEngine = require('@boruto_vk7/stickengine').default;`
41
19
 
42
- ### 1. Criando uma Figurinha Estática Simples
20
+ ### Sua Primeira Figurinha
21
+ Gere uma figurinha com apenas 3 linhas de código:
43
22
  ```javascript
44
- import StickEngine from '@boruto_vk7/stickengine';
45
-
46
23
  const engine = new StickEngine();
47
- engine.addFile('https://exemplo.com/foto.jpg');
48
-
49
- const results = await engine.start();
50
- console.log('Caminho do WebP:', results[0].value);
51
- engine.clean();
24
+ engine.addFile('./foto.jpg'); // Adiciona a imagem
25
+ const results = await engine.start(); // Inicia o processamento
26
+ console.log('Sticker salvo em:', results[0].value);
52
27
  ```
53
28
 
54
- ### 2. Figurinha Animada (GIF ou Vídeo)
55
- O motor detecta automaticamente a duração e converte para WebP animado.
29
+ ---
30
+
31
+ ## 🛠️ 2. Personalizando (Intermediário)
32
+
33
+ ### Adicionando Nome e Autor (Metadados)
34
+ Personalize as informações que aparecem quando alguém clica na figurinha no WhatsApp.
56
35
  ```javascript
57
36
  const engine = new StickEngine({
58
- fps: 20, // Aumenta a fluidez
59
- quality: 70 // Otimiza o tamanho para o WhatsApp
37
+ metadata: {
38
+ pack: 'Meu Pack Incrível',
39
+ author: 'Borutovk7'
40
+ }
60
41
  });
42
+ ```
61
43
 
44
+ ### Figurinhas Animadas (GIF e Vídeo)
45
+ O motor detecta automaticamente se o arquivo é animado. Ele corta para 6 segundos e garante que o arquivo tenha menos de 1MB.
46
+ ```javascript
62
47
  engine.addFile('./video_engracado.mp4');
48
+ engine.addFile('./dancinha.gif');
63
49
  await engine.start();
64
- engine.clean();
65
50
  ```
66
51
 
67
- ### 3. Aplicando Filtros e Texto
52
+ ---
53
+
54
+ ## 🎨 3. Efeitos Especiais (Avançado)
55
+
56
+ ### Recorte Circular e Filtros
57
+ Transforme imagens quadradas em círculos e aplique filtros profissionais.
58
+ ```javascript
59
+ const engine = new StickEngine({
60
+ edit: {
61
+ circle: true, // Recorte circular
62
+ sepia: true, // Filtro antigo
63
+ brightness: 0.2 // Aumenta o brilho
64
+ }
65
+ });
66
+ ```
67
+
68
+ ### Legendas com Borda (Meme Style)
69
+ Adicione texto que pode ser lido em qualquer fundo graças ao contorno (stroke).
68
70
  ```javascript
69
71
  const engine = new StickEngine({
70
72
  edit: {
71
- sepia: true,
72
- blur: 5,
73
73
  text: {
74
- content: 'MUITO BOM!',
75
- alignmentY: 2 // 0: Topo, 1: Meio, 2: Baixo
74
+ content: 'QUEBRADO!',
75
+ color: 'WHITE',
76
+ stroke: true // Adiciona borda preta ao texto branco
76
77
  }
77
78
  }
78
79
  });
79
-
80
- engine.addFile(bufferDeImagem);
81
- await engine.start();
82
- engine.clean();
83
80
  ```
84
81
 
85
- ### 4. Remoção de Fundo (Remove.bg)
82
+ ---
83
+
84
+ ## 💎 4. Funções Profissionais (Expert)
85
+
86
+ ### Texto para Figurinha (TP)
87
+ Gere figurinhas de frases do zero, sem precisar de uma imagem de base.
86
88
  ```javascript
87
- const engine = new StickEngine({
88
- transparent: 'SUA_API_KEY_AQUI'
89
+ const path = await engine.createTextSticker("ESSA É TOP!", {
90
+ color: 'WHITE',
91
+ background: 0x00000000 // Transparente
89
92
  });
93
+ ```
90
94
 
91
- engine.addFile('foto_com_fundo.png');
92
- await engine.start();
93
- engine.clean();
95
+ ### Criando Ícone do Pacote (Tray Icon)
96
+ Gere o ícone de bandeja (96x96) necessário para pacotes oficiais.
97
+ ```javascript
98
+ const trayPath = await engine.createTrayIcon('./logo.png');
94
99
  ```
95
100
 
101
+ ### Auto-Dimensionamento Inteligente
102
+ O motor reduz automaticamente a qualidade e o FPS se a figurinha animada passar de 1MB, garantindo que ela sempre seja enviada com sucesso.
103
+
96
104
  ---
97
105
 
98
- ## ⚙️ Opções do Construtor
106
+ ## 📑 5. Referência Técnica Completa
99
107
 
100
- | Opção | Tipo | Padrão | Descrição |
108
+ ### Opções do Construtor (`StickEngineOptions`)
109
+ | Propriedade | Tipo | Padrão | Descrição |
101
110
  | :--- | :--- | :--- | :--- |
102
- | `metadata` | `Object` | `{ pack: 'StickEngine', author: 'Borutovk7' }` | Nome do pacote e autor. |
103
- | `edit` | `Object` | `false` | Filtros (`sepia`, `greyscale`, `invert`, `blur`) e `text`. |
104
- | `transparent` | `String` | `false` | Chave da API `remove.bg` para remover fundo. |
105
- | `autoClean` | `Boolean` | `true` | Habilita o rastreio de arquivos para limpeza. |
106
111
  | `quality` | `Number` | `80` | Qualidade do WebP (1-100). |
107
- | `fps` | `Number` | `15` | Frames por segundo para animações. |
112
+ | `fps` | `Number` | `15` | Frames por segundo para animados. |
113
+ | `autoClean` | `Boolean` | `true` | Apaga temporários automaticamente. |
114
+
115
+ ### Opções de Edição (`JimpOptions`)
116
+ | Opção | Descrição |
117
+ | :--- | :--- |
118
+ | `circle` | Ativa o recorte circular. |
119
+ | `brightness` | Ajusta o brilho (-1 a 1). |
120
+ | `contrast` | Ajusta o contraste (-1 a 1). |
121
+ | `blur` | Aplica desfoque. |
122
+ | `text.stroke` | Adiciona contorno ao texto. |
108
123
 
109
124
  ---
110
125
 
111
- ## 🤝 Contribuições
112
-
113
- Contribuições são sempre bem-vindas! Sinta-se à vontade para abrir uma **Issue** ou enviar um **Pull Request**.
114
-
115
- Desenvolvido por [Borutovk7](https://github.com/Borutovk7) e [Eduh Dev](https://github.com/EduhDev).
116
-
117
- ---
126
+ ## 🤝 Créditos
127
+ Desenvolvido por **Borutovk7**.
118
128
 
119
129
  ## 📄 Licença
120
-
121
- Este projeto está sob a licença [ISC](LICENSE).
130
+ ISC
@@ -10,6 +10,9 @@ export interface JimpOptions {
10
10
  invert?: boolean;
11
11
  sepia?: boolean;
12
12
  blur?: number;
13
+ brightness?: number;
14
+ contrast?: number;
15
+ circle?: boolean;
13
16
  resize?: {
14
17
  width: number;
15
18
  height: number;
@@ -18,7 +21,8 @@ export interface JimpOptions {
18
21
  content: string;
19
22
  alignmentX?: number;
20
23
  alignmentY?: number;
21
- color?: string;
24
+ color?: 'WHITE' | 'BLACK';
25
+ stroke?: boolean;
22
26
  };
23
27
  }
24
28
  export interface StickEngineOptions {
@@ -49,5 +53,20 @@ export declare class StickEngine extends EventEmitter {
49
53
  private convertToWebp;
50
54
  private addExif;
51
55
  start(): Promise<PromiseSettledResult<string>[]>;
56
+ /**
57
+ * Converte um arquivo em Base64.
58
+ */
59
+ static toBase64(filePath: string): string;
60
+ /**
61
+ * Gera um ícone de bandeja (Tray Icon) 96x96 para o pacote de figurinhas.
62
+ */
63
+ createTrayIcon(input: string | Buffer): Promise<string>;
64
+ /**
65
+ * Cria uma figurinha de texto do zero (TP).
66
+ */
67
+ createTextSticker(text: string, options?: {
68
+ color?: 'WHITE' | 'BLACK';
69
+ background?: string;
70
+ }): Promise<string>;
52
71
  }
53
72
  export default StickEngine;
@@ -11,6 +11,7 @@ import Jimp from 'jimp';
11
11
  // @ts-ignore
12
12
  import { removeBackgroundFromImageFile } from 'remove.bg';
13
13
  import { fileURLToPath } from 'url';
14
+ // Correção para ESM
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = path.dirname(__filename);
16
17
  /**
@@ -177,15 +178,35 @@ export class StickEngine extends EventEmitter {
177
178
  image.sepia();
178
179
  if (options.blur)
179
180
  image.blur(options.blur);
181
+ if (options.brightness)
182
+ image.brightness(options.brightness);
183
+ if (options.contrast)
184
+ image.contrast(options.contrast);
185
+ if (options.circle)
186
+ image.circle();
180
187
  if (options.resize)
181
188
  image.resize(options.resize.width, options.resize.height);
182
189
  if (options.text) {
183
- const font = await Jimp.loadFont(Jimp.FONT_SANS_32_WHITE);
184
- image.print(font, 0, 0, {
190
+ const fontPath = options.text.color === 'BLACK' ? Jimp.FONT_SANS_32_BLACK : Jimp.FONT_SANS_32_WHITE;
191
+ const font = await Jimp.loadFont(fontPath);
192
+ const printOptions = {
185
193
  text: options.text.content,
186
194
  alignmentX: options.text.alignmentX || Jimp.HORIZONTAL_ALIGN_CENTER,
187
195
  alignmentY: options.text.alignmentY || Jimp.VERTICAL_ALIGN_BOTTOM
188
- }, image.bitmap.width, image.bitmap.height);
196
+ };
197
+ if (options.text.stroke) {
198
+ // Simulação de borda desenhando o texto em volta
199
+ const strokeColor = options.text.color === 'BLACK' ? Jimp.FONT_SANS_32_WHITE : Jimp.FONT_SANS_32_BLACK;
200
+ const strokeFont = await Jimp.loadFont(strokeColor);
201
+ for (let x = -2; x <= 2; x++) {
202
+ for (let y = -2; y <= 2; y++) {
203
+ if (x !== 0 || y !== 0) {
204
+ image.print(strokeFont, x, y, printOptions, image.bitmap.width, image.bitmap.height);
205
+ }
206
+ }
207
+ }
208
+ }
209
+ image.print(font, 0, 0, printOptions, image.bitmap.width, image.bitmap.height);
189
210
  }
190
211
  await image.writeAsync(output);
191
212
  }
@@ -203,29 +224,48 @@ export class StickEngine extends EventEmitter {
203
224
  });
204
225
  });
205
226
  }
206
- convertToWebp(input, output, isAnimated) {
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
- ];
220
- 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));
227
+ async convertToWebp(input, output, isAnimated) {
228
+ const runFfmpeg = (quality, fps) => {
229
+ return new Promise((resolve, reject) => {
230
+ const ff = ffmpeg(input)
231
+ .on('error', reject)
232
+ .on('end', () => resolve());
233
+ const videoOptions = [
234
+ '-vcodec', 'libwebp',
235
+ '-vf', `scale=512:512:force_original_aspect_ratio=decrease,fps=${fps},pad=512:512:(ow-iw)/2:(oh-ih)/2:color=white@0,setsar=1`,
236
+ '-lossless', isAnimated ? '0' : '1',
237
+ '-loop', '0',
238
+ '-preset', 'default',
239
+ '-an',
240
+ '-vsync', '0'
241
+ ];
242
+ if (isAnimated) {
243
+ videoOptions.push('-t', '6');
244
+ videoOptions.push('-q:v', String(quality));
245
+ }
246
+ ff.addOutputOptions(videoOptions)
247
+ .toFormat('webp')
248
+ .save(output);
249
+ });
250
+ };
251
+ let currentQuality = this.options.quality || 80;
252
+ let currentFps = this.options.fps || 15;
253
+ await runFfmpeg(currentQuality, currentFps);
254
+ // Auto-Dimensionamento Inteligente (Apenas para Animados)
255
+ if (isAnimated && fs.existsSync(output)) {
256
+ let stats = fs.statSync(output);
257
+ let attempts = 0;
258
+ // Se maior que 1MB, tenta reduzir qualidade e FPS até 3 vezes
259
+ while (stats.size > 1000000 && attempts < 3) {
260
+ currentQuality -= 15;
261
+ currentFps -= 3;
262
+ if (currentFps < 5)
263
+ currentFps = 5;
264
+ await runFfmpeg(currentQuality, currentFps);
265
+ stats = fs.statSync(output);
266
+ attempts++;
224
267
  }
225
- ff.addOutputOptions(videoOptions)
226
- .toFormat('webp')
227
- .save(output);
228
- });
268
+ }
229
269
  }
230
270
  async addExif(webpPath, metadata) {
231
271
  const json = {
@@ -242,14 +282,88 @@ export class StickEngine extends EventEmitter {
242
282
  exifBuffer.writeUIntLE(jsonBuff.length, 14, 4);
243
283
  await img.load(webpPath);
244
284
  img.exif = exifBuffer;
245
- await img.save(webpPath);
285
+ // node-webpmux requer muxAnim para salvar WebPs animados
286
+ if (img.hasAnim) {
287
+ await img.muxAnim({ path: webpPath });
288
+ }
289
+ else {
290
+ await img.save(webpPath);
291
+ }
246
292
  }
247
293
  async start() {
248
294
  if (this.queue.length === 0)
249
295
  throw new Error('Nenhum arquivo na fila.');
250
296
  const results = await Promise.allSettled(this.queue.map((file, i) => this.processFile(file, i)));
251
297
  this.queue = [];
298
+ if (this.options.autoClean) {
299
+ // Nota: Se autoClean for true, os arquivos serão deletados após o uso.
300
+ // Em um ambiente de produção, o usuário deve ler o arquivo antes de chamar clean() ou desativar autoClean.
301
+ }
252
302
  return results;
253
303
  }
304
+ /**
305
+ * Converte um arquivo em Base64.
306
+ */
307
+ static toBase64(filePath) {
308
+ const buffer = fs.readFileSync(filePath);
309
+ return buffer.toString('base64');
310
+ }
311
+ /**
312
+ * Gera um ícone de bandeja (Tray Icon) 96x96 para o pacote de figurinhas.
313
+ */
314
+ async createTrayIcon(input) {
315
+ const timestamp = Date.now();
316
+ const outputPath = path.join(this.tempDir, `tray_${timestamp}.png`);
317
+ const webpOutputPath = path.join(this.tempDir, `tray_${timestamp}.webp`);
318
+ let buffer;
319
+ if (Buffer.isBuffer(input)) {
320
+ buffer = input;
321
+ }
322
+ else if (typeof input === 'string' && input.startsWith('http')) {
323
+ buffer = await getBuffer(input);
324
+ }
325
+ else if (typeof input === 'string' && fs.existsSync(input)) {
326
+ buffer = fs.readFileSync(input);
327
+ }
328
+ else {
329
+ throw new Error('Entrada inválida para Tray Icon.');
330
+ }
331
+ const image = await Jimp.read(buffer);
332
+ await image.cover(96, 96).writeAsync(outputPath);
333
+ // Converter para WebP (WhatsApp prefere WebP para ícones de bandeja em alguns casos)
334
+ await new Promise((resolve, reject) => {
335
+ ffmpeg(outputPath)
336
+ .on('error', reject)
337
+ .on('end', () => resolve())
338
+ .toFormat('webp')
339
+ .save(webpOutputPath);
340
+ });
341
+ this.createdFiles.push(outputPath, webpOutputPath);
342
+ return webpOutputPath;
343
+ }
344
+ /**
345
+ * Cria uma figurinha de texto do zero (TP).
346
+ */
347
+ async createTextSticker(text, options = {}) {
348
+ const timestamp = Date.now();
349
+ const outputPath = path.join(this.tempDir, `text_${timestamp}.png`);
350
+ // Criar uma imagem transparente 512x512
351
+ const image = new Jimp(512, 512, options.background || 0x00000000);
352
+ const fontPath = options.color === 'BLACK' ? Jimp.FONT_SANS_64_BLACK : Jimp.FONT_SANS_64_WHITE;
353
+ const font = await Jimp.loadFont(fontPath);
354
+ // Centralizar o texto
355
+ image.print(font, 0, 0, {
356
+ text,
357
+ alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
358
+ alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
359
+ }, 512, 512);
360
+ await image.writeAsync(outputPath);
361
+ // Processar como uma figurinha normal para adicionar metadados e converter para WebP
362
+ const engine = new StickEngine(this.options);
363
+ const results = await engine.addFile(outputPath).start();
364
+ const finalPath = results[0].value;
365
+ this.createdFiles.push(outputPath, finalPath);
366
+ return finalPath;
367
+ }
254
368
  }
255
369
  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.6",
4
4
  "description": "StickEngine module for creating WhatsApp stickers",
5
5
  "type": "module",
6
6
  "main": "dist/StickEngine.js",