@boruto_vk7/stickengine 0.0.5 → 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;
@@ -3,14 +3,20 @@ 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
6
8
  import webpMux from 'node-webpmux';
9
+ // @ts-ignore
7
10
  import Jimp from 'jimp';
11
+ // @ts-ignore
8
12
  import { removeBackgroundFromImageFile } from 'remove.bg';
9
13
  import { fileURLToPath } from 'url';
10
-
14
+ // Correção para ESM
11
15
  const __filename = fileURLToPath(import.meta.url);
12
16
  const __dirname = path.dirname(__filename);
13
-
17
+ /**
18
+ * Faz GET em uma URL e retorna o corpo como Buffer.
19
+ */
14
20
  export const getBuffer = async (url, options = {}) => {
15
21
  try {
16
22
  const { data } = await axios({
@@ -35,13 +41,11 @@ export const getBuffer = async (url, options = {}) => {
35
41
  return Buffer.alloc(0);
36
42
  }
37
43
  };
38
-
39
44
  export class StickEngine extends EventEmitter {
40
45
  options;
41
46
  tempDir;
42
47
  queue;
43
48
  createdFiles;
44
-
45
49
  constructor(options = {}) {
46
50
  super();
47
51
  this.options = {
@@ -66,12 +70,10 @@ export class StickEngine extends EventEmitter {
66
70
  this.queue = [];
67
71
  this.createdFiles = [];
68
72
  }
69
-
70
73
  addFile(file) {
71
74
  this.queue.push(file);
72
75
  return this;
73
76
  }
74
-
75
77
  clean() {
76
78
  for (const file of this.createdFiles) {
77
79
  try {
@@ -85,7 +87,6 @@ export class StickEngine extends EventEmitter {
85
87
  }
86
88
  this.createdFiles = [];
87
89
  }
88
-
89
90
  async processFile(input, index) {
90
91
  let filePath = '';
91
92
  let extension = '';
@@ -95,7 +96,6 @@ export class StickEngine extends EventEmitter {
95
96
  this.emit('st.start', { index, input });
96
97
  const ft = await import('file-type');
97
98
  const fileTypeFunc = ft.fromBuffer || ft.fileTypeFromBuffer || (ft.default && (ft.default.fromBuffer || ft.default.fileTypeFromBuffer));
98
-
99
99
  if (Buffer.isBuffer(input)) {
100
100
  const type = await fileTypeFunc(input);
101
101
  extension = type ? type.ext : 'bin';
@@ -117,19 +117,18 @@ export class StickEngine extends EventEmitter {
117
117
  else {
118
118
  throw new Error('Tipo de entrada inválido ou arquivo não encontrado.');
119
119
  }
120
-
121
120
  const mediaInfo = await this.getMediaInfo(filePath);
122
121
  this.emit('st.info', { index, ...mediaInfo });
123
122
  let currentFile = filePath;
124
123
  const isAnimated = mediaInfo.duration > 0 || extension === 'gif' || extension === 'mp4' || extension === 'webp';
125
-
124
+ // Aplicar Edições Jimp (Apenas para Imagens Estáticas por enquanto)
126
125
  if (this.options.edit && !isAnimated) {
127
126
  const editedPath = path.join(this.tempDir, `edited_${timestamp}_${index}.png`);
128
127
  await this.applyJimpEdits(currentFile, editedPath, this.options.edit);
129
128
  localCreatedFiles.push(editedPath);
130
129
  currentFile = editedPath;
131
130
  }
132
-
131
+ // Remoção de fundo (Apenas para Imagens Estáticas)
133
132
  if (this.options.transparent && !isAnimated) {
134
133
  const apiKey = Array.isArray(this.options.transparent)
135
134
  ? this.options.transparent[Math.floor(Math.random() * this.options.transparent.length)]
@@ -147,16 +146,15 @@ export class StickEngine extends EventEmitter {
147
146
  currentFile = bgRemovedPath;
148
147
  }
149
148
  }
150
-
149
+ // Conversão para WebP (Suporta Estático e Animado)
151
150
  const webpPath = path.join(this.tempDir, `sticker_${timestamp}_${index}.webp`);
152
151
  await this.convertToWebp(currentFile, webpPath, isAnimated);
153
152
  localCreatedFiles.push(webpPath);
154
153
  currentFile = webpPath;
155
-
154
+ // Adição de Metadados (Exif)
156
155
  if (this.options.metadata) {
157
156
  await this.addExif(currentFile, this.options.metadata);
158
157
  }
159
-
160
158
  this.createdFiles.push(...localCreatedFiles);
161
159
  this.emit('st.data', { index, file: currentFile });
162
160
  return currentFile;
@@ -170,29 +168,53 @@ export class StickEngine extends EventEmitter {
170
168
  throw error;
171
169
  }
172
170
  }
173
-
174
171
  async applyJimpEdits(input, output, options) {
175
172
  const image = await Jimp.read(input);
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);
173
+ if (options.greyscale)
174
+ image.greyscale();
175
+ if (options.invert)
176
+ image.invert();
177
+ if (options.sepia)
178
+ image.sepia();
179
+ if (options.blur)
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();
187
+ if (options.resize)
188
+ image.resize(options.resize.width, options.resize.height);
181
189
  if (options.text) {
182
- const font = await Jimp.loadFont(Jimp.FONT_SANS_32_WHITE);
183
- 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 = {
184
193
  text: options.text.content,
185
194
  alignmentX: options.text.alignmentX || Jimp.HORIZONTAL_ALIGN_CENTER,
186
195
  alignmentY: options.text.alignmentY || Jimp.VERTICAL_ALIGN_BOTTOM
187
- }, 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);
188
210
  }
189
211
  await image.writeAsync(output);
190
212
  }
191
-
192
213
  getMediaInfo(file) {
193
214
  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) return resolve({ width: 0, height: 0, duration: 0 });
215
+ exec(`ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration -of csv=p=0:s=x ${file}`, (err, stdout) => {
216
+ if (err)
217
+ return resolve({ width: 0, height: 0, duration: 0 });
196
218
  const parts = stdout.trim().split('x');
197
219
  resolve({
198
220
  width: parseInt(parts[0]) || 0,
@@ -202,58 +224,146 @@ export class StickEngine extends EventEmitter {
202
224
  });
203
225
  });
204
226
  }
205
-
206
- convertToWebp(input, output, isAnimated) {
207
- return new Promise((resolve, reject) => {
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;
213
- if (isAnimated) {
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}"`;
217
- }
218
-
219
- exec(cmd, (err, stdout, stderr) => {
220
- if (err) return reject(new Error(stderr || err.message));
221
- resolve();
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);
222
249
  });
223
- });
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++;
267
+ }
268
+ }
224
269
  }
225
-
226
270
  async addExif(webpPath, metadata) {
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);
271
+ const json = {
272
+ 'sticker-pack-id': 'StickEngine-Official',
273
+ 'sticker-pack-name': metadata.pack || 'StickEngine',
274
+ 'sticker-pack-publisher': metadata.author || 'Borutovk7',
275
+ 'emojis': metadata.emojis || ['🥶']
276
+ };
277
+ const Image = webpMux.Image || (webpMux.default && webpMux.default.Image);
278
+ const img = new Image();
279
+ 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]);
280
+ const jsonBuff = Buffer.from(JSON.stringify(json), 'utf-8');
281
+ const exifBuffer = Buffer.concat([exifAttr, jsonBuff]);
282
+ exifBuffer.writeUIntLE(jsonBuff.length, 14, 4);
283
+ await img.load(webpPath);
284
+ img.exif = exifBuffer;
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
+ }
248
292
  }
249
- }
250
-
251
293
  async start() {
252
- if (this.queue.length === 0) throw new Error('Nenhum arquivo na fila.');
294
+ if (this.queue.length === 0)
295
+ throw new Error('Nenhum arquivo na fila.');
253
296
  const results = await Promise.allSettled(this.queue.map((file, i) => this.processFile(file, i)));
254
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
+ }
255
302
  return results;
256
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
+ }
257
368
  }
258
-
259
- export default StickEngine;
369
+ export default StickEngine;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boruto_vk7/stickengine",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "StickEngine module for creating WhatsApp stickers",
5
5
  "type": "module",
6
6
  "main": "dist/StickEngine.js",