@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/README.md CHANGED
@@ -1,3 +1,188 @@
1
- # Temporary Holding Version
1
+ # @rin-ts/sticker
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/%40rin-ts%2Fsticker?logo=npm)](https://www.npmjs.com/package/@rin-ts/sticker)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/rin-ts/sticker/ci.yml?branch=main&label=CI)](https://github.com/rin-ts/sticker/actions/workflows/ci.yml)
5
+ [![License](https://img.shields.io/npm/l/%40rin-ts%2Fsticker)](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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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";