@rin-ts/sticker 0.0.0-stage → 0.2.1
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/LICENSE +674 -0
- package/README.md +187 -2
- package/package.json +40 -4
- package/src/index.ts +20 -0
- package/src/native/sticker_pipeline.cpp +1893 -0
- package/src/native/sticker_pipeline.hpp +66 -0
- package/src/native/sticker_worker.cpp +305 -0
- package/src/native.ts +570 -0
- package/src/types.ts +12 -0
package/README.md
CHANGED
|
@@ -1,3 +1,188 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @rin-ts/sticker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@rin-ts/sticker)
|
|
4
|
+
[](https://github.com/rin-ts/sticker/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Native WhatsApp WebP sticker conversion for Bun and TypeScript. Media decoding,
|
|
8
|
+
pixel processing, and WebP encoding run in a persistent C++ worker; the
|
|
9
|
+
TypeScript API sends bytes and returns validated sticker bytes.
|
|
10
|
+
|
|
11
|
+
## Highlights
|
|
12
|
+
|
|
13
|
+
- PNG, JPEG, static WebP, animated WebP, and FFmpeg-decoded video input
|
|
14
|
+
- 512 × 512 static or animated WebP output validated against conservative
|
|
15
|
+
WhatsApp sticker limits
|
|
16
|
+
- Alpha trimming, proportional sizing, and alpha-aware outlines
|
|
17
|
+
- Fast high-quality lossy output by default, with an opt-in lossless static attempt
|
|
18
|
+
- Persistent workers, per-request deadlines, bounded input, and automatic
|
|
19
|
+
worker replacement after a transport failure or timeout
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add @rin-ts/sticker
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Prebuilt workers are selected automatically on Linux x64 and Linux ARM64.
|
|
28
|
+
Pass `workerPath` only when using a custom native worker executable.
|
|
29
|
+
Keep a custom worker on the same package version as its TypeScript wrapper. A
|
|
30
|
+
newer wrapper can send ordinary requests to an older worker, but that worker
|
|
31
|
+
keeps its older encoding default; `preferLossless: true` is rejected by an
|
|
32
|
+
older worker.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
Create a worker once during application startup and reuse it for requests.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createNativeStickerWorker } from "@rin-ts/sticker";
|
|
40
|
+
|
|
41
|
+
const worker = createNativeStickerWorker({ timeoutMs: 15_000 });
|
|
42
|
+
const source = await Bun.file("./photo.png").arrayBuffer();
|
|
43
|
+
|
|
44
|
+
const sticker = await worker.transcode(source, {
|
|
45
|
+
trim: true,
|
|
46
|
+
alphaThreshold: 4,
|
|
47
|
+
outline: {
|
|
48
|
+
width: 8,
|
|
49
|
+
color: { red: 255, green: 255, blue: 255, alpha: 255 },
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await context.reply({ type: "sticker", media: sticker.data });
|
|
54
|
+
await worker.close();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`transcode()` resolves to:
|
|
58
|
+
|
|
59
|
+
| Field | Description |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `data` | Validated WebP sticker bytes |
|
|
62
|
+
| `width`, `height` | Always `512` on success |
|
|
63
|
+
| `frameCount`, `durationMs` | Animation metadata; static stickers have one frame and zero duration |
|
|
64
|
+
| `isAnimated` | Whether the output contains multiple frames |
|
|
65
|
+
|
|
66
|
+
## Input support
|
|
67
|
+
|
|
68
|
+
| Input | Result |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| PNG, JPEG, static WebP | Static WebP sticker |
|
|
71
|
+
| Animated WebP | Animated WebP sticker |
|
|
72
|
+
| FFmpeg-supported video such as MP4, MOV, WebM, MKV, and AVI | Animated WebP sticker; audio is ignored |
|
|
73
|
+
| GIF, APNG, Lottie/Telegram `.tgs` | Rejected |
|
|
74
|
+
|
|
75
|
+
The worker receives bytes only; it is not a file-path CLI or an HTTP service.
|
|
76
|
+
|
|
77
|
+
## API
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const worker = createNativeStickerWorker({
|
|
81
|
+
workerPath: "/opt/rin/sticker_worker", // optional
|
|
82
|
+
workerArgs: [], // optional
|
|
83
|
+
timeoutMs: 30_000, // optional
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`worker.transcode(source, options)` accepts an `ArrayBuffer` or any typed-array
|
|
88
|
+
view. `options` are:
|
|
89
|
+
|
|
90
|
+
| Option | Default | Description |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `trim` | `true` | Remove fully transparent outer rows and columns |
|
|
93
|
+
| `alphaThreshold` | `0` | Alpha value treated as transparent while trimming |
|
|
94
|
+
| `outline.width` | `0` | Outline radius, from 0 through 64 pixels |
|
|
95
|
+
| `outline.color` | opaque white | `{ red, green, blue, alpha? }`, each channel 0 through 255 |
|
|
96
|
+
| `preferLossless` | `false` | Attempt lossless static WebP before lossy fallback; it still falls back when output exceeds 100 KiB |
|
|
97
|
+
| `timeoutMs` | worker default (30,000 ms) | Deadline for writing, conversion, and reading the response |
|
|
98
|
+
|
|
99
|
+
Calls to one worker are intentionally serialized. Use a finite pool of workers
|
|
100
|
+
when parallel conversion is required. `close()` stops new work, drains already
|
|
101
|
+
accepted work, and terminates the child process.
|
|
102
|
+
|
|
103
|
+
## Limits and production behavior
|
|
104
|
+
|
|
105
|
+
- Source assets are limited to 16 MiB.
|
|
106
|
+
- Output is always 512 × 512 WebP.
|
|
107
|
+
- Static output is limited to 100 KiB.
|
|
108
|
+
- Animated output is limited to 500 KiB, 10 seconds, and requires multiple
|
|
109
|
+
frames with delays of at least 8 ms.
|
|
110
|
+
|
|
111
|
+
The worker is discarded after a timeout, malformed protocol response, or
|
|
112
|
+
transport failure; the next queued call starts a clean process. Still enforce
|
|
113
|
+
download limits before calling this package, set an appropriate worker-pool
|
|
114
|
+
size, and apply OS-level CPU and memory limits in production.
|
|
115
|
+
|
|
116
|
+
## Benchmarking
|
|
117
|
+
|
|
118
|
+
Run the repeatable end-to-end suite:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
bun run benchmark
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
It builds the native worker, generates deterministic full-size media fixtures
|
|
125
|
+
locally, then reports cold latency (worker spawn plus first conversion) and
|
|
126
|
+
warm latency (a reused worker). File I/O is excluded from warm measurements.
|
|
127
|
+
The default fixtures cover a 1280 × 854 image as PNG, JPEG, and static WebP; a
|
|
128
|
+
1024 × 1024 transparent graphic; a three-frame animated WebP; and a
|
|
129
|
+
three-frame 640 × 360 MP4. They are real encoded media files, not 1 × 1 test
|
|
130
|
+
pixels, and no fixture is downloaded.
|
|
131
|
+
|
|
132
|
+
Use your own media to measure the inputs your application handles:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
bun run build:native
|
|
136
|
+
# Compare the fast default and lossless preference using the same source:
|
|
137
|
+
bun run benchmarks/benchmark.ts --iterations 10 ./artwork.webp
|
|
138
|
+
bun run benchmarks/benchmark.ts --iterations 10 --prefer-lossless ./artwork.webp
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The report includes source and output size, cold latency, warm median and p95,
|
|
142
|
+
and warm throughput. Performance varies with codec library versions, CPU,
|
|
143
|
+
source dimensions, frame count, transparency, and how often an output must be
|
|
144
|
+
re-encoded to meet its byte limit; compare results on the deployment hardware.
|
|
145
|
+
Static output uses the high-quality lossy path by default for predictable
|
|
146
|
+
latency. Set `preferLossless: true` when lower latency is not important and a
|
|
147
|
+
lossless attempt is worthwhile; output still falls back to lossy above 100 KiB.
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
### Build from source
|
|
152
|
+
|
|
153
|
+
The native build requires Bun, CMake 3.20+, a C++20 compiler, `pkg-config`,
|
|
154
|
+
and development packages for libpng, libjpeg, libwebp, libwebpmux,
|
|
155
|
+
libwebpdemux, libavformat, libavcodec, libavutil, and libswscale. On Void,
|
|
156
|
+
install FFmpeg headers with:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
sudo xbps-install -S ffmpeg6-devel
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Then build:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
bun install
|
|
166
|
+
bun run build:native
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The worker is written to `build/native/bin/sticker_worker`.
|
|
170
|
+
|
|
171
|
+
### Test
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
bun run test
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Publishing
|
|
178
|
+
|
|
179
|
+
Maintain matching package versions and a `v<version>` tag, then run the
|
|
180
|
+
repository’s **Publish packages** GitHub Actions workflow with the intended npm
|
|
181
|
+
distribution tag. It publishes the platform packages before the main package
|
|
182
|
+
and skips package versions already present on npm. Configure npm trusted
|
|
183
|
+
publishers for the main and Linux platform packages after the initial release;
|
|
184
|
+
the workflow file is [`release.yml`](.github/workflows/release.yml).
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
GPL-3.0-only. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rin-ts/sticker",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Native C++ WhatsApp WebP sticker worker for Rin.",
|
|
5
|
+
"license": "GPL-3.0-only",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/rin-ts/sticker.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": "https://github.com/rin-ts/sticker/issues",
|
|
19
|
+
"homepage": "https://github.com/rin-ts/sticker#readme",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"registry": "https://registry.npmjs.org/",
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build:native": "bun run scripts/build-native.ts",
|
|
26
|
+
"benchmark:fixtures": "bun run build:native && bun run benchmarks/generate-fixtures.ts",
|
|
27
|
+
"benchmark": "bun run benchmark:fixtures && bun run benchmarks/benchmark.ts",
|
|
28
|
+
"test": "bun run typecheck && bun run test:native",
|
|
29
|
+
"test:native": "bun run build:native && ctest --test-dir build/native --output-on-failure && bun test ./tests/native_bridge.test.ts",
|
|
30
|
+
"typecheck": "bun check"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/bun": "latest"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"typescript": "^7"
|
|
37
|
+
},
|
|
38
|
+
"optionalDependencies": {
|
|
39
|
+
"@rin-ts/sticker-linux-arm64-gnu": "0.2.1",
|
|
40
|
+
"@rin-ts/sticker-linux-x64-gnu": "0.2.1"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
NativeStickerWorker,
|
|
3
|
+
NativeStickerWorkerClosedError,
|
|
4
|
+
NativeStickerWorkerError,
|
|
5
|
+
NativeStickerWorkerTimeoutError,
|
|
6
|
+
NativeStickerWorkerTransportError,
|
|
7
|
+
createNativeStickerWorker,
|
|
8
|
+
} from "./native.ts";
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
OutlineOptions,
|
|
12
|
+
RgbaColor,
|
|
13
|
+
} from "./types.ts";
|
|
14
|
+
|
|
15
|
+
export type {
|
|
16
|
+
NativeStickerResult,
|
|
17
|
+
NativeStickerSource,
|
|
18
|
+
NativeStickerTranscodeOptions,
|
|
19
|
+
NativeStickerWorkerOptions,
|
|
20
|
+
} from "./native.ts";
|