@boruto_vk7/stickengine 0.0.3-alpha → 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.
- package/README.md +78 -92
- package/assets/preview_animated.png +0 -0
- package/assets/preview_sepia.png +0 -0
- package/dist/StickEngine.d.ts +23 -2
- package/dist/StickEngine.js +127 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,135 +1,121 @@
|
|
|
1
|
-
# StickEngine
|
|
1
|
+
# 🚀 StickEngine
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/Borutovk7/StickEngine/blob/main/LICENSE)
|
|
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.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@boruto_vk7/stickengine)
|
|
6
|
+
[](https://github.com/Borutovk7/StickEngine)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
---
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## ✨ Funcionalidades
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
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`.
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
---
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
- [ffmpeg](https://ffmpeg.org/) instalado no sistema
|
|
22
|
+
## 🖼️ Exemplos Visuais
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
| Filtro Séia + Texto | Figurinha Animada (Mockup) |
|
|
25
|
+
| :---: | :---: |
|
|
26
|
+
|  |  |
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
sudo apt install ffmpeg
|
|
29
|
-
```
|
|
28
|
+
---
|
|
30
29
|
|
|
31
|
-
## Instalação
|
|
30
|
+
## 📦 Instalação
|
|
32
31
|
|
|
33
32
|
```bash
|
|
34
33
|
npm install @boruto_vk7/stickengine
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
> **Requisito:** Certifique-se de ter o [FFmpeg](https://ffmpeg.org/) instalado no seu sistema. No Termux: `pkg install ffmpeg`.
|
|
37
|
+
|
|
38
|
+
---
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
## 🚀 Como Usar (Exemplos Técnicos)
|
|
40
41
|
|
|
42
|
+
### 1. Criando uma Figurinha Estática Simples
|
|
41
43
|
```javascript
|
|
42
44
|
import StickEngine from '@boruto_vk7/stickengine';
|
|
43
45
|
|
|
44
|
-
const engine = new StickEngine(
|
|
45
|
-
|
|
46
|
-
pack: 'Meu Pack de Stickers',
|
|
47
|
-
author: 'Borutovk7',
|
|
48
|
-
emojis: ['✨', '🚀']
|
|
49
|
-
},
|
|
50
|
-
// Opcional: chaves da API remove.bg para remoção de fundo
|
|
51
|
-
// transparent: ['SUA_API_KEY']
|
|
52
|
-
});
|
|
46
|
+
const engine = new StickEngine();
|
|
47
|
+
engine.addFile('https://exemplo.com/foto.jpg');
|
|
53
48
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
engine.
|
|
49
|
+
const results = await engine.start();
|
|
50
|
+
console.log('Caminho do WebP:', results[0].value);
|
|
51
|
+
engine.clean();
|
|
52
|
+
```
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
### 2. Figurinha Animada (GIF ou Vídeo)
|
|
55
|
+
O motor detecta automaticamente a duração e converte para WebP animado.
|
|
56
|
+
```javascript
|
|
57
|
+
const engine = new StickEngine({
|
|
58
|
+
fps: 20, // Aumenta a fluidez
|
|
59
|
+
quality: 70 // Otimiza o tamanho para o WhatsApp
|
|
60
|
+
});
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
engine.addFile('./video_engracado.mp4');
|
|
63
|
+
await engine.start();
|
|
64
|
+
engine.clean();
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
###
|
|
68
|
-
|
|
67
|
+
### 3. Aplicando Filtros e Texto
|
|
69
68
|
```javascript
|
|
70
|
-
const StickEngine = require('@boruto_vk7/stickengine');
|
|
71
|
-
|
|
72
69
|
const engine = new StickEngine({
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
edit: {
|
|
71
|
+
sepia: true,
|
|
72
|
+
blur: 5,
|
|
73
|
+
text: {
|
|
74
|
+
content: 'MUITO BOM!',
|
|
75
|
+
alignmentY: 2 // 0: Topo, 1: Meio, 2: Baixo
|
|
76
|
+
}
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
engine.
|
|
81
|
-
engine.
|
|
82
|
-
engine.
|
|
83
|
-
|
|
84
|
-
engine.addFile('https://picsum.photos/200')
|
|
85
|
-
.start()
|
|
86
|
-
.then(results => console.log(results))
|
|
87
|
-
.catch(err => console.error(err));
|
|
80
|
+
engine.addFile(bufferDeImagem);
|
|
81
|
+
await engine.start();
|
|
82
|
+
engine.clean();
|
|
88
83
|
```
|
|
89
84
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
| `st.info` | Metadados do arquivo sendo processado |
|
|
96
|
-
| `st.data` | Sticker gerado com sucesso |
|
|
97
|
-
| `st.error` | Erro ao processar um arquivo |
|
|
98
|
-
|
|
99
|
-
## API
|
|
100
|
-
|
|
101
|
-
### `new StickEngine(options)`
|
|
102
|
-
|
|
103
|
-
| Opção | Tipo | Descrição |
|
|
104
|
-
|-------|------|-----------|
|
|
105
|
-
| `metadata.pack` | `string` | Nome do pacote de stickers |
|
|
106
|
-
| `metadata.author` | `string` | Nome do autor |
|
|
107
|
-
| `metadata.emojis` | `string[]` | Emojis associados ao sticker |
|
|
108
|
-
| `transparent` | `string[]` | Chaves da API remove.bg para remoção de fundo |
|
|
109
|
-
|
|
110
|
-
### `.addFile(input)`
|
|
111
|
-
|
|
112
|
-
Adiciona um arquivo à fila. Aceita URL, Buffer ou caminho local. Retorna a instância para encadeamento.
|
|
85
|
+
### 4. Remoção de Fundo (Remove.bg)
|
|
86
|
+
```javascript
|
|
87
|
+
const engine = new StickEngine({
|
|
88
|
+
transparent: 'SUA_API_KEY_AQUI'
|
|
89
|
+
});
|
|
113
90
|
|
|
114
|
-
|
|
91
|
+
engine.addFile('foto_com_fundo.png');
|
|
92
|
+
await engine.start();
|
|
93
|
+
engine.clean();
|
|
94
|
+
```
|
|
115
95
|
|
|
116
|
-
|
|
96
|
+
---
|
|
117
97
|
|
|
118
|
-
|
|
98
|
+
## ⚙️ Opções do Construtor
|
|
119
99
|
|
|
120
|
-
|
|
100
|
+
| Opção | Tipo | Padrão | Descrição |
|
|
101
|
+
| :--- | :--- | :--- | :--- |
|
|
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
|
+
| `quality` | `Number` | `80` | Qualidade do WebP (1-100). |
|
|
107
|
+
| `fps` | `Number` | `15` | Frames por segundo para animações. |
|
|
121
108
|
|
|
122
|
-
|
|
123
|
-
import { getBuffer } from '@boruto_vk7/stickengine';
|
|
109
|
+
---
|
|
124
110
|
|
|
125
|
-
|
|
126
|
-
```
|
|
111
|
+
## 🤝 Contribuições
|
|
127
112
|
|
|
128
|
-
|
|
113
|
+
Contribuições são sempre bem-vindas! Sinta-se à vontade para abrir uma **Issue** ou enviar um **Pull Request**.
|
|
129
114
|
|
|
130
|
-
|
|
115
|
+
Desenvolvido por [Borutovk7](https://github.com/Borutovk7) e [Eduh Dev](https://github.com/EduhDev).
|
|
131
116
|
|
|
117
|
+
---
|
|
132
118
|
|
|
133
|
-
## Licença
|
|
119
|
+
## 📄 Licença
|
|
134
120
|
|
|
135
|
-
ISC
|
|
121
|
+
Este projeto está sob a licença [ISC](LICENSE).
|
|
Binary file
|
|
Binary file
|
package/dist/StickEngine.d.ts
CHANGED
|
@@ -5,25 +5,46 @@ export interface StickerMetadata {
|
|
|
5
5
|
author?: string;
|
|
6
6
|
emojis?: string[];
|
|
7
7
|
}
|
|
8
|
+
export interface JimpOptions {
|
|
9
|
+
greyscale?: boolean;
|
|
10
|
+
invert?: boolean;
|
|
11
|
+
sepia?: boolean;
|
|
12
|
+
blur?: number;
|
|
13
|
+
resize?: {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
text?: {
|
|
18
|
+
content: string;
|
|
19
|
+
alignmentX?: number;
|
|
20
|
+
alignmentY?: number;
|
|
21
|
+
color?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
8
24
|
export interface StickEngineOptions {
|
|
9
25
|
webp?: boolean;
|
|
10
|
-
edit?:
|
|
26
|
+
edit?: JimpOptions | boolean;
|
|
11
27
|
convert?: boolean;
|
|
12
28
|
transparent?: string | string[] | false;
|
|
13
29
|
metadata?: StickerMetadata;
|
|
30
|
+
autoClean?: boolean;
|
|
31
|
+
fps?: number;
|
|
32
|
+
quality?: number;
|
|
14
33
|
}
|
|
15
34
|
/**
|
|
16
35
|
* Faz GET em uma URL e retorna o corpo como Buffer.
|
|
17
|
-
* Fornecido pelo usuário para integração.
|
|
18
36
|
*/
|
|
19
37
|
export declare const getBuffer: (url: string, options?: AxiosRequestConfig) => Promise<Buffer>;
|
|
20
38
|
export declare class StickEngine extends EventEmitter {
|
|
21
39
|
options: StickEngineOptions;
|
|
22
40
|
private tempDir;
|
|
23
41
|
private queue;
|
|
42
|
+
private createdFiles;
|
|
24
43
|
constructor(options?: StickEngineOptions);
|
|
25
44
|
addFile(file: string | Buffer): this;
|
|
45
|
+
clean(): void;
|
|
26
46
|
private processFile;
|
|
47
|
+
private applyJimpEdits;
|
|
27
48
|
private getMediaInfo;
|
|
28
49
|
private convertToWebp;
|
|
29
50
|
private addExif;
|
package/dist/StickEngine.js
CHANGED
|
@@ -3,18 +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
|
-
|
|
7
|
+
import Jimp from 'jimp';
|
|
10
8
|
import { removeBackgroundFromImageFile } from 'remove.bg';
|
|
11
9
|
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
12
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
12
|
const __dirname = path.dirname(__filename);
|
|
14
|
-
|
|
15
|
-
* Faz GET em uma URL e retorna o corpo como Buffer.
|
|
16
|
-
* Fornecido pelo usuário para integração.
|
|
17
|
-
*/
|
|
13
|
+
|
|
18
14
|
export const getBuffer = async (url, options = {}) => {
|
|
19
15
|
try {
|
|
20
16
|
const { data } = await axios({
|
|
@@ -32,17 +28,20 @@ export const getBuffer = async (url, options = {}) => {
|
|
|
32
28
|
}
|
|
33
29
|
catch (err) {
|
|
34
30
|
console.error(`Erro em getBuffer: ${err}`);
|
|
35
|
-
const errorImagePath = path.join(__dirname, '
|
|
31
|
+
const errorImagePath = path.join(__dirname, 'emror.jpg');
|
|
36
32
|
if (fs.existsSync(errorImagePath)) {
|
|
37
33
|
return fs.readFileSync(errorImagePath);
|
|
38
34
|
}
|
|
39
35
|
return Buffer.alloc(0);
|
|
40
36
|
}
|
|
41
37
|
};
|
|
38
|
+
|
|
42
39
|
export class StickEngine extends EventEmitter {
|
|
43
40
|
options;
|
|
44
41
|
tempDir;
|
|
45
42
|
queue;
|
|
43
|
+
createdFiles;
|
|
44
|
+
|
|
46
45
|
constructor(options = {}) {
|
|
47
46
|
super();
|
|
48
47
|
this.options = {
|
|
@@ -50,6 +49,9 @@ export class StickEngine extends EventEmitter {
|
|
|
50
49
|
edit: false,
|
|
51
50
|
convert: false,
|
|
52
51
|
transparent: false,
|
|
52
|
+
autoClean: true,
|
|
53
|
+
fps: 15,
|
|
54
|
+
quality: 80,
|
|
53
55
|
metadata: {
|
|
54
56
|
pack: 'StickEngine',
|
|
55
57
|
author: 'Borutovk7 & Eduh Dev',
|
|
@@ -62,24 +64,44 @@ export class StickEngine extends EventEmitter {
|
|
|
62
64
|
fs.mkdirSync(this.tempDir, { recursive: true });
|
|
63
65
|
}
|
|
64
66
|
this.queue = [];
|
|
67
|
+
this.createdFiles = [];
|
|
65
68
|
}
|
|
69
|
+
|
|
66
70
|
addFile(file) {
|
|
67
71
|
this.queue.push(file);
|
|
68
72
|
return this;
|
|
69
73
|
}
|
|
74
|
+
|
|
75
|
+
clean() {
|
|
76
|
+
for (const file of this.createdFiles) {
|
|
77
|
+
try {
|
|
78
|
+
if (fs.existsSync(file)) {
|
|
79
|
+
fs.unlinkSync(file);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error(`Erro ao deletar arquivo temporário ${file}:`, err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.createdFiles = [];
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
async processFile(input, index) {
|
|
71
|
-
let filePath;
|
|
72
|
-
let extension;
|
|
90
|
+
let filePath = '';
|
|
91
|
+
let extension = '';
|
|
73
92
|
const timestamp = Date.now();
|
|
93
|
+
const localCreatedFiles = [];
|
|
74
94
|
try {
|
|
75
95
|
this.emit('st.start', { index, input });
|
|
76
96
|
const ft = await import('file-type');
|
|
77
97
|
const fileTypeFunc = ft.fromBuffer || ft.fileTypeFromBuffer || (ft.default && (ft.default.fromBuffer || ft.default.fileTypeFromBuffer));
|
|
98
|
+
|
|
78
99
|
if (Buffer.isBuffer(input)) {
|
|
79
100
|
const type = await fileTypeFunc(input);
|
|
80
101
|
extension = type ? type.ext : 'bin';
|
|
81
102
|
filePath = path.join(this.tempDir, `input_${timestamp}_${index}.${extension}`);
|
|
82
103
|
fs.writeFileSync(filePath, input);
|
|
104
|
+
localCreatedFiles.push(filePath);
|
|
83
105
|
}
|
|
84
106
|
else if (typeof input === 'string' && input.startsWith('http')) {
|
|
85
107
|
const buffer = await getBuffer(input);
|
|
@@ -87,18 +109,28 @@ export class StickEngine extends EventEmitter {
|
|
|
87
109
|
extension = type ? type.ext : 'bin';
|
|
88
110
|
filePath = path.join(this.tempDir, `input_${timestamp}_${index}.${extension}`);
|
|
89
111
|
fs.writeFileSync(filePath, buffer);
|
|
112
|
+
localCreatedFiles.push(filePath);
|
|
90
113
|
}
|
|
91
114
|
else if (typeof input === 'string' && fs.existsSync(input)) {
|
|
92
|
-
extension = path.extname(input).slice(1);
|
|
93
115
|
filePath = input;
|
|
94
116
|
}
|
|
95
117
|
else {
|
|
96
118
|
throw new Error('Tipo de entrada inválido ou arquivo não encontrado.');
|
|
97
119
|
}
|
|
98
|
-
|
|
99
|
-
|
|
120
|
+
|
|
121
|
+
const mediaInfo = await this.getMediaInfo(filePath);
|
|
122
|
+
this.emit('st.info', { index, ...mediaInfo });
|
|
100
123
|
let currentFile = filePath;
|
|
101
|
-
|
|
124
|
+
const isAnimated = mediaInfo.duration > 0 || extension === 'gif' || extension === 'mp4' || extension === 'webp';
|
|
125
|
+
|
|
126
|
+
if (this.options.edit && !isAnimated) {
|
|
127
|
+
const editedPath = path.join(this.tempDir, `edited_${timestamp}_${index}.png`);
|
|
128
|
+
await this.applyJimpEdits(currentFile, editedPath, this.options.edit);
|
|
129
|
+
localCreatedFiles.push(editedPath);
|
|
130
|
+
currentFile = editedPath;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (this.options.transparent && !isAnimated) {
|
|
102
134
|
const apiKey = Array.isArray(this.options.transparent)
|
|
103
135
|
? this.options.transparent[Math.floor(Math.random() * this.options.transparent.length)]
|
|
104
136
|
: this.options.transparent;
|
|
@@ -111,78 +143,117 @@ export class StickEngine extends EventEmitter {
|
|
|
111
143
|
type: 'auto'
|
|
112
144
|
});
|
|
113
145
|
fs.writeFileSync(bgRemovedPath, Buffer.from(result.base64img, 'base64'));
|
|
146
|
+
localCreatedFiles.push(bgRemovedPath);
|
|
114
147
|
currentFile = bgRemovedPath;
|
|
115
148
|
}
|
|
116
149
|
}
|
|
150
|
+
|
|
117
151
|
const webpPath = path.join(this.tempDir, `sticker_${timestamp}_${index}.webp`);
|
|
118
|
-
await this.convertToWebp(currentFile, webpPath);
|
|
152
|
+
await this.convertToWebp(currentFile, webpPath, isAnimated);
|
|
153
|
+
localCreatedFiles.push(webpPath);
|
|
119
154
|
currentFile = webpPath;
|
|
155
|
+
|
|
120
156
|
if (this.options.metadata) {
|
|
121
157
|
await this.addExif(currentFile, this.options.metadata);
|
|
122
158
|
}
|
|
159
|
+
|
|
160
|
+
this.createdFiles.push(...localCreatedFiles);
|
|
123
161
|
this.emit('st.data', { index, file: currentFile });
|
|
124
162
|
return currentFile;
|
|
125
163
|
}
|
|
126
164
|
catch (error) {
|
|
127
165
|
this.emit('st.error', { index, error: error.message });
|
|
166
|
+
for (const f of localCreatedFiles) {
|
|
167
|
+
if (fs.existsSync(f))
|
|
168
|
+
fs.unlinkSync(f);
|
|
169
|
+
}
|
|
128
170
|
throw error;
|
|
129
171
|
}
|
|
130
172
|
}
|
|
173
|
+
|
|
174
|
+
async applyJimpEdits(input, output, options) {
|
|
175
|
+
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);
|
|
181
|
+
if (options.text) {
|
|
182
|
+
const font = await Jimp.loadFont(Jimp.FONT_SANS_32_WHITE);
|
|
183
|
+
image.print(font, 0, 0, {
|
|
184
|
+
text: options.text.content,
|
|
185
|
+
alignmentX: options.text.alignmentX || Jimp.HORIZONTAL_ALIGN_CENTER,
|
|
186
|
+
alignmentY: options.text.alignmentY || Jimp.VERTICAL_ALIGN_BOTTOM
|
|
187
|
+
}, image.bitmap.width, image.bitmap.height);
|
|
188
|
+
}
|
|
189
|
+
await image.writeAsync(output);
|
|
190
|
+
}
|
|
191
|
+
|
|
131
192
|
getMediaInfo(file) {
|
|
132
|
-
return new Promise((resolve
|
|
133
|
-
exec(`ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration -of csv=p=0:s=x ${file}`, (err, stdout) => {
|
|
134
|
-
if (err)
|
|
135
|
-
|
|
136
|
-
const [width, height, duration] = stdout.split('x');
|
|
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) return resolve({ width: 0, height: 0, duration: 0 });
|
|
196
|
+
const parts = stdout.trim().split('x');
|
|
137
197
|
resolve({
|
|
138
|
-
width: parseInt(
|
|
139
|
-
height: parseInt(
|
|
140
|
-
duration: parseFloat(
|
|
198
|
+
width: parseInt(parts[0]) || 0,
|
|
199
|
+
height: parseInt(parts[1]) || 0,
|
|
200
|
+
duration: parseFloat(parts[2]) || 0
|
|
141
201
|
});
|
|
142
202
|
});
|
|
143
203
|
});
|
|
144
204
|
}
|
|
145
|
-
|
|
205
|
+
|
|
206
|
+
convertToWebp(input, output, isAnimated) {
|
|
146
207
|
return new Promise((resolve, reject) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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();
|
|
222
|
+
});
|
|
161
223
|
});
|
|
162
224
|
}
|
|
225
|
+
|
|
163
226
|
async addExif(webpPath, metadata) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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);
|
|
179
248
|
}
|
|
249
|
+
}
|
|
250
|
+
|
|
180
251
|
async start() {
|
|
181
|
-
if (this.queue.length === 0)
|
|
182
|
-
throw new Error('Nenhum arquivo na fila.');
|
|
252
|
+
if (this.queue.length === 0) throw new Error('Nenhum arquivo na fila.');
|
|
183
253
|
const results = await Promise.allSettled(this.queue.map((file, i) => this.processFile(file, i)));
|
|
184
254
|
this.queue = [];
|
|
185
255
|
return results;
|
|
186
256
|
}
|
|
187
257
|
}
|
|
188
|
-
|
|
258
|
+
|
|
259
|
+
export default StickEngine;
|