divoom-timesgate-sdk 0.1.0
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 +21 -0
- package/README.md +371 -0
- package/dist/common-D8oHDNi6.d.cts +54 -0
- package/dist/common-D8oHDNi6.d.ts +54 -0
- package/dist/image/index.cjs +515 -0
- package/dist/image/index.cjs.map +1 -0
- package/dist/image/index.d.cts +279 -0
- package/dist/image/index.d.ts +279 -0
- package/dist/image/index.js +498 -0
- package/dist/image/index.js.map +1 -0
- package/dist/index.cjs +1021 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +921 -0
- package/dist/index.d.ts +921 -0
- package/dist/index.js +991 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { Sharp } from 'sharp';
|
|
2
|
+
import { F as FetchLike, P as PanelIndex } from '../common-D8oHDNi6.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Types for the image pipeline (`divoom-timesgate-sdk/image`).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Anything the image helpers can turn into a panel frame:
|
|
12
|
+
* - a file path (`"./cover.png"`),
|
|
13
|
+
* - an `http(s)` URL (downloaded automatically),
|
|
14
|
+
* - raw encoded bytes (`Buffer` / `Uint8Array`), or
|
|
15
|
+
* - an existing `sharp` pipeline.
|
|
16
|
+
*/
|
|
17
|
+
type ImageSource = string | Buffer | Uint8Array | Sharp;
|
|
18
|
+
/** Output encoding for a panel frame. */
|
|
19
|
+
type EncodedFormat = 'jpeg' | 'png';
|
|
20
|
+
/** Resize strategies, mirroring sharp's `fit` options. */
|
|
21
|
+
type ResizeFit = 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
22
|
+
/** A fully-encoded frame, ready to hand to the device's draw commands. */
|
|
23
|
+
interface EncodedFrame {
|
|
24
|
+
/**
|
|
25
|
+
* Base64-encoded image data — drop this straight into the `picData`
|
|
26
|
+
* argument of {@link DrawCommands.sendImage} (the device's `PicData` field).
|
|
27
|
+
*/
|
|
28
|
+
data: string;
|
|
29
|
+
/** The raw encoded bytes. */
|
|
30
|
+
buffer: Buffer;
|
|
31
|
+
/** The container format the bytes are encoded in. */
|
|
32
|
+
format: EncodedFormat;
|
|
33
|
+
/** Frame width in pixels (square frames: equal to {@link EncodedFrame.height}). */
|
|
34
|
+
width: number;
|
|
35
|
+
/** Frame height in pixels. */
|
|
36
|
+
height: number;
|
|
37
|
+
}
|
|
38
|
+
/** Common encoding options shared by most image helpers. */
|
|
39
|
+
interface EncodeOptions {
|
|
40
|
+
/** Output edge length in pixels. Defaults to `128` (one panel). */
|
|
41
|
+
size?: number;
|
|
42
|
+
/** Output format. Defaults to `"jpeg"` (smaller, what the device expects). */
|
|
43
|
+
format?: EncodedFormat;
|
|
44
|
+
/** JPEG quality, `1`–`100`. Defaults to `95`. */
|
|
45
|
+
quality?: number;
|
|
46
|
+
/** JPEG chroma subsampling. Defaults to `"4:4:4"` for crisp edges. */
|
|
47
|
+
chromaSubsampling?: string;
|
|
48
|
+
/** Background used when flattening transparency for JPEG. Defaults to black. */
|
|
49
|
+
background?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Options for {@link encodePanel}. */
|
|
52
|
+
interface EncodePanelOptions extends EncodeOptions {
|
|
53
|
+
/** How the source is fitted into the square frame. Defaults to `"cover"`. */
|
|
54
|
+
fit?: ResizeFit;
|
|
55
|
+
/** Resampling kernel. Defaults to `"lanczos3"` for the sharpest downscale. */
|
|
56
|
+
kernel?: 'nearest' | 'cubic' | 'mitchell' | 'lanczos2' | 'lanczos3';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Turns any {@link ImageSource} into a `sharp` pipeline.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* **Trust model.** A `string` source is treated as an `http(s)` URL when it
|
|
64
|
+
* starts with that scheme, and otherwise as a **local file path**. Do not pass
|
|
65
|
+
* untrusted/attacker-influenced strings here: a URL can trigger a server-side
|
|
66
|
+
* request (SSRF) and a path can read arbitrary local files. URL downloads are
|
|
67
|
+
* bounded by a timeout and a maximum size, but callers handling untrusted input
|
|
68
|
+
* should validate/allowlist the source themselves (and prefer passing a
|
|
69
|
+
* `Buffer` you fetched under your own controls).
|
|
70
|
+
*
|
|
71
|
+
* @packageDocumentation
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/** Options controlling how URL image sources are downloaded. */
|
|
75
|
+
interface LoadImageOptions {
|
|
76
|
+
/** Per-download timeout in milliseconds (URL sources only). Defaults to `15000`. */
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
/** Maximum bytes to download before aborting (URL sources only). Defaults to 64 MiB. */
|
|
79
|
+
maxBytes?: number;
|
|
80
|
+
/** A custom `fetch` for URL sources. Defaults to the global `fetch`. */
|
|
81
|
+
fetch?: FetchLike;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolves an {@link ImageSource} to a `sharp` pipeline. Existing pipelines are
|
|
85
|
+
* cloned so callers can reuse the original safely.
|
|
86
|
+
*
|
|
87
|
+
* @param source - A file path, an `http(s)` URL, raw bytes, or a `sharp` instance.
|
|
88
|
+
* See the module-level note on the URL/path trust model before passing
|
|
89
|
+
* untrusted input.
|
|
90
|
+
* @param options - Download controls for URL sources (timeout, size cap, fetch).
|
|
91
|
+
* @throws {@link DivoomValidationError} for unsupported sources or oversized downloads.
|
|
92
|
+
* @throws {@link DivoomConnectionError} when a URL cannot be downloaded.
|
|
93
|
+
* @throws {@link DivoomTimeoutError} when a download exceeds `timeoutMs`.
|
|
94
|
+
*/
|
|
95
|
+
declare function loadImage(source: ImageSource, options?: LoadImageOptions): Promise<Sharp>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Frame encoding: resize-and-encode any source to a panel-ready
|
|
99
|
+
* {@link EncodedFrame}, plus a helper for solid-color frames.
|
|
100
|
+
*
|
|
101
|
+
* @packageDocumentation
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Encodes an already-sized `sharp` pipeline into an {@link EncodedFrame},
|
|
106
|
+
* applying the SDK's default high-quality JPEG settings (q95, 4:4:4 chroma).
|
|
107
|
+
*
|
|
108
|
+
* @internal Shared by the higher-level helpers; exported for advanced use.
|
|
109
|
+
*/
|
|
110
|
+
declare function finalizeFrame(pipeline: Sharp, options: EncodeOptions & {
|
|
111
|
+
size: number;
|
|
112
|
+
}): Promise<EncodedFrame>;
|
|
113
|
+
/**
|
|
114
|
+
* Resizes and encodes any {@link ImageSource} into a single square panel frame.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* const frame = await encodePanel('./album.jpg'); // 128×128 JPEG, q95
|
|
119
|
+
* await client.draw.sendImage(0, frame.data);
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
declare function encodePanel(source: ImageSource, options?: EncodePanelOptions): Promise<EncodedFrame>;
|
|
123
|
+
/**
|
|
124
|
+
* Produces a solid-color panel frame — handy for clearing a panel or showing a
|
|
125
|
+
* status color.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* await client.draw.sendImage(1, (await solidFrame('#000000')).data); // blank
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function solidFrame(color: string, options?: EncodeOptions): Promise<EncodedFrame>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Color helpers: hex⇄RGB conversion, mixing, and accent extraction from
|
|
136
|
+
* artwork (used to tint the now-playing panels).
|
|
137
|
+
*
|
|
138
|
+
* @packageDocumentation
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
/** An `[r, g, b]` triple with each channel in `0`–`255`. */
|
|
142
|
+
type Rgb = [number, number, number];
|
|
143
|
+
/** Parses a `#RGB` or `#RRGGBB` hex string into an {@link Rgb} triple. */
|
|
144
|
+
declare function hexToRgb(hex: string): Rgb;
|
|
145
|
+
/** Formats an {@link Rgb} triple as an uppercase `#RRGGBB` string. */
|
|
146
|
+
declare function rgbToHex([r, g, b]: Rgb): string;
|
|
147
|
+
/** Linearly interpolates between two colors (`t` in `0`–`1`). */
|
|
148
|
+
declare function mixRgb(a: Rgb, b: Rgb, t: number): Rgb;
|
|
149
|
+
/** Scales every channel of a color by `factor`. */
|
|
150
|
+
declare function scaleRgb([r, g, b]: Rgb, factor: number): Rgb;
|
|
151
|
+
/**
|
|
152
|
+
* Extracts a vivid accent color from artwork — the most saturated reasonably
|
|
153
|
+
* bright pixel — for tinting backgrounds and progress bars. Mirrors the proven
|
|
154
|
+
* heuristic from the reference now-playing app.
|
|
155
|
+
*
|
|
156
|
+
* @param source - The artwork to sample.
|
|
157
|
+
* @param fallback - Returned when no saturated pixel is found. Defaults to
|
|
158
|
+
* Spotify green (`#1DB954`).
|
|
159
|
+
*/
|
|
160
|
+
declare function getAccentColor(source: ImageSource, fallback?: string): Promise<string>;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Album-art styling — the two looks from the reference now-playing app:
|
|
164
|
+
* a crisp "smooth" treatment and a chunky "pixel" treatment.
|
|
165
|
+
*
|
|
166
|
+
* @packageDocumentation
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
/** Visual treatment applied by {@link prepareAlbumArt}. */
|
|
170
|
+
type AlbumArtStyle = 'smooth' | 'pixel';
|
|
171
|
+
/** Options for {@link prepareAlbumArt}. */
|
|
172
|
+
interface PrepareAlbumArtOptions extends EncodeOptions {
|
|
173
|
+
/**
|
|
174
|
+
* `"smooth"` (default) gently boosts saturation/contrast and unsharp-masks for
|
|
175
|
+
* a clean look; `"pixel"` downsamples to 32×32 nearest-neighbour for a retro,
|
|
176
|
+
* blocky aesthetic.
|
|
177
|
+
*/
|
|
178
|
+
style?: AlbumArtStyle;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Prepares album/cover artwork for a panel, matching the look of the reference
|
|
182
|
+
* Spotify app.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* const art = await prepareAlbumArt(coverUrl, { style: 'smooth' });
|
|
187
|
+
* await client.draw.sendImage(0, art.data);
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
declare function prepareAlbumArt(source: ImageSource, options?: PrepareAlbumArtOptions): Promise<EncodedFrame>;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Split one image across several panels to create a single wide (or tall)
|
|
194
|
+
* canvas spanning the Times Gate.
|
|
195
|
+
*
|
|
196
|
+
* @packageDocumentation
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
/** A frame paired with the panel it belongs on. */
|
|
200
|
+
interface PanelFrame {
|
|
201
|
+
/** The target panel index. */
|
|
202
|
+
panel: PanelIndex;
|
|
203
|
+
/** The encoded frame for that panel. */
|
|
204
|
+
frame: EncodedFrame;
|
|
205
|
+
}
|
|
206
|
+
/** Options for {@link splitImageAcrossPanels}. */
|
|
207
|
+
interface SplitOptions extends EncodeOptions {
|
|
208
|
+
/** Panels to span, in visual order. Defaults to all five (`[0,1,2,3,4]`). */
|
|
209
|
+
panels?: PanelIndex[];
|
|
210
|
+
/**
|
|
211
|
+
* `"horizontal"` (default) lays the image out left-to-right across panels;
|
|
212
|
+
* `"vertical"` stacks top-to-bottom.
|
|
213
|
+
*/
|
|
214
|
+
layout?: 'horizontal' | 'vertical';
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Slices a single image into one square frame per panel, so a panorama spans
|
|
218
|
+
* the whole device.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* const tiles = await splitImageAcrossPanels('./wide-banner.png');
|
|
223
|
+
* for (const { panel, frame } of tiles) {
|
|
224
|
+
* await client.draw.sendImage(panel, frame.data);
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
declare function splitImageAcrossPanels(source: ImageSource, options?: SplitOptions): Promise<PanelFrame[]>;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Renders a designed "now-playing"-style text panel: a tinted gradient
|
|
232
|
+
* background, an optional eyebrow with a play glyph, an auto-fitted title, a
|
|
233
|
+
* subtitle, and an optional progress bar. Text is rendered with Pango (via
|
|
234
|
+
* sharp) for crisp, properly-wrapped output that doesn't depend on the host's
|
|
235
|
+
* system fonts the way the original Python reference did.
|
|
236
|
+
*
|
|
237
|
+
* @packageDocumentation
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
/** Options for {@link renderTextPanel}. */
|
|
241
|
+
interface RenderTextPanelOptions extends EncodeOptions {
|
|
242
|
+
/** The headline text (e.g. a track title). Auto-sized to fit. */
|
|
243
|
+
title: string;
|
|
244
|
+
/** Secondary text below the title (e.g. an artist). Optional. */
|
|
245
|
+
subtitle?: string;
|
|
246
|
+
/** Small uppercase label above the title (e.g. `"NOW PLAYING"`). Optional. */
|
|
247
|
+
eyebrow?: string;
|
|
248
|
+
/** Accent color (hex) used for the tint, eyebrow, subtitle and bar. Defaults to `#1DB954`. */
|
|
249
|
+
accent?: string;
|
|
250
|
+
/** Progress fraction `0`–`1`. When set, a progress bar is drawn. */
|
|
251
|
+
progress?: number;
|
|
252
|
+
/**
|
|
253
|
+
* Background style: `"gradient"` (default, an accent-tinted vertical fade),
|
|
254
|
+
* `"solid"` (a flat accent), or any hex string for a custom flat color.
|
|
255
|
+
*/
|
|
256
|
+
background?: 'gradient' | 'solid' | string;
|
|
257
|
+
/** Pango font family. Defaults to `"DejaVu Sans"`. */
|
|
258
|
+
font?: string;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Renders a designed now-playing-style panel and returns an
|
|
262
|
+
* {@link EncodedFrame} ready for a panel. Overly long titles/subtitles are
|
|
263
|
+
* shrunk to fit rather than overflowing the panel.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* const panel = await renderTextPanel({
|
|
268
|
+
* eyebrow: 'Now Playing',
|
|
269
|
+
* title: 'Bohemian Rhapsody',
|
|
270
|
+
* subtitle: 'Queen',
|
|
271
|
+
* accent: '#E94F37',
|
|
272
|
+
* progress: 0.42,
|
|
273
|
+
* });
|
|
274
|
+
* await client.draw.sendImage(1, panel.data);
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
declare function renderTextPanel(options: RenderTextPanelOptions): Promise<EncodedFrame>;
|
|
278
|
+
|
|
279
|
+
export { type AlbumArtStyle, type EncodeOptions, type EncodePanelOptions, type EncodedFormat, type EncodedFrame, type ImageSource, type LoadImageOptions, type PanelFrame, type PrepareAlbumArtOptions, type RenderTextPanelOptions, type ResizeFit, type Rgb, type SplitOptions, encodePanel, finalizeFrame, getAccentColor, hexToRgb, loadImage, mixRgb, prepareAlbumArt, renderTextPanel, rgbToHex, scaleRgb, solidFrame, splitImageAcrossPanels };
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { Sharp } from 'sharp';
|
|
2
|
+
import { F as FetchLike, P as PanelIndex } from '../common-D8oHDNi6.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Types for the image pipeline (`divoom-timesgate-sdk/image`).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Anything the image helpers can turn into a panel frame:
|
|
12
|
+
* - a file path (`"./cover.png"`),
|
|
13
|
+
* - an `http(s)` URL (downloaded automatically),
|
|
14
|
+
* - raw encoded bytes (`Buffer` / `Uint8Array`), or
|
|
15
|
+
* - an existing `sharp` pipeline.
|
|
16
|
+
*/
|
|
17
|
+
type ImageSource = string | Buffer | Uint8Array | Sharp;
|
|
18
|
+
/** Output encoding for a panel frame. */
|
|
19
|
+
type EncodedFormat = 'jpeg' | 'png';
|
|
20
|
+
/** Resize strategies, mirroring sharp's `fit` options. */
|
|
21
|
+
type ResizeFit = 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
22
|
+
/** A fully-encoded frame, ready to hand to the device's draw commands. */
|
|
23
|
+
interface EncodedFrame {
|
|
24
|
+
/**
|
|
25
|
+
* Base64-encoded image data — drop this straight into the `picData`
|
|
26
|
+
* argument of {@link DrawCommands.sendImage} (the device's `PicData` field).
|
|
27
|
+
*/
|
|
28
|
+
data: string;
|
|
29
|
+
/** The raw encoded bytes. */
|
|
30
|
+
buffer: Buffer;
|
|
31
|
+
/** The container format the bytes are encoded in. */
|
|
32
|
+
format: EncodedFormat;
|
|
33
|
+
/** Frame width in pixels (square frames: equal to {@link EncodedFrame.height}). */
|
|
34
|
+
width: number;
|
|
35
|
+
/** Frame height in pixels. */
|
|
36
|
+
height: number;
|
|
37
|
+
}
|
|
38
|
+
/** Common encoding options shared by most image helpers. */
|
|
39
|
+
interface EncodeOptions {
|
|
40
|
+
/** Output edge length in pixels. Defaults to `128` (one panel). */
|
|
41
|
+
size?: number;
|
|
42
|
+
/** Output format. Defaults to `"jpeg"` (smaller, what the device expects). */
|
|
43
|
+
format?: EncodedFormat;
|
|
44
|
+
/** JPEG quality, `1`–`100`. Defaults to `95`. */
|
|
45
|
+
quality?: number;
|
|
46
|
+
/** JPEG chroma subsampling. Defaults to `"4:4:4"` for crisp edges. */
|
|
47
|
+
chromaSubsampling?: string;
|
|
48
|
+
/** Background used when flattening transparency for JPEG. Defaults to black. */
|
|
49
|
+
background?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Options for {@link encodePanel}. */
|
|
52
|
+
interface EncodePanelOptions extends EncodeOptions {
|
|
53
|
+
/** How the source is fitted into the square frame. Defaults to `"cover"`. */
|
|
54
|
+
fit?: ResizeFit;
|
|
55
|
+
/** Resampling kernel. Defaults to `"lanczos3"` for the sharpest downscale. */
|
|
56
|
+
kernel?: 'nearest' | 'cubic' | 'mitchell' | 'lanczos2' | 'lanczos3';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Turns any {@link ImageSource} into a `sharp` pipeline.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* **Trust model.** A `string` source is treated as an `http(s)` URL when it
|
|
64
|
+
* starts with that scheme, and otherwise as a **local file path**. Do not pass
|
|
65
|
+
* untrusted/attacker-influenced strings here: a URL can trigger a server-side
|
|
66
|
+
* request (SSRF) and a path can read arbitrary local files. URL downloads are
|
|
67
|
+
* bounded by a timeout and a maximum size, but callers handling untrusted input
|
|
68
|
+
* should validate/allowlist the source themselves (and prefer passing a
|
|
69
|
+
* `Buffer` you fetched under your own controls).
|
|
70
|
+
*
|
|
71
|
+
* @packageDocumentation
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/** Options controlling how URL image sources are downloaded. */
|
|
75
|
+
interface LoadImageOptions {
|
|
76
|
+
/** Per-download timeout in milliseconds (URL sources only). Defaults to `15000`. */
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
/** Maximum bytes to download before aborting (URL sources only). Defaults to 64 MiB. */
|
|
79
|
+
maxBytes?: number;
|
|
80
|
+
/** A custom `fetch` for URL sources. Defaults to the global `fetch`. */
|
|
81
|
+
fetch?: FetchLike;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolves an {@link ImageSource} to a `sharp` pipeline. Existing pipelines are
|
|
85
|
+
* cloned so callers can reuse the original safely.
|
|
86
|
+
*
|
|
87
|
+
* @param source - A file path, an `http(s)` URL, raw bytes, or a `sharp` instance.
|
|
88
|
+
* See the module-level note on the URL/path trust model before passing
|
|
89
|
+
* untrusted input.
|
|
90
|
+
* @param options - Download controls for URL sources (timeout, size cap, fetch).
|
|
91
|
+
* @throws {@link DivoomValidationError} for unsupported sources or oversized downloads.
|
|
92
|
+
* @throws {@link DivoomConnectionError} when a URL cannot be downloaded.
|
|
93
|
+
* @throws {@link DivoomTimeoutError} when a download exceeds `timeoutMs`.
|
|
94
|
+
*/
|
|
95
|
+
declare function loadImage(source: ImageSource, options?: LoadImageOptions): Promise<Sharp>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Frame encoding: resize-and-encode any source to a panel-ready
|
|
99
|
+
* {@link EncodedFrame}, plus a helper for solid-color frames.
|
|
100
|
+
*
|
|
101
|
+
* @packageDocumentation
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Encodes an already-sized `sharp` pipeline into an {@link EncodedFrame},
|
|
106
|
+
* applying the SDK's default high-quality JPEG settings (q95, 4:4:4 chroma).
|
|
107
|
+
*
|
|
108
|
+
* @internal Shared by the higher-level helpers; exported for advanced use.
|
|
109
|
+
*/
|
|
110
|
+
declare function finalizeFrame(pipeline: Sharp, options: EncodeOptions & {
|
|
111
|
+
size: number;
|
|
112
|
+
}): Promise<EncodedFrame>;
|
|
113
|
+
/**
|
|
114
|
+
* Resizes and encodes any {@link ImageSource} into a single square panel frame.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* const frame = await encodePanel('./album.jpg'); // 128×128 JPEG, q95
|
|
119
|
+
* await client.draw.sendImage(0, frame.data);
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
declare function encodePanel(source: ImageSource, options?: EncodePanelOptions): Promise<EncodedFrame>;
|
|
123
|
+
/**
|
|
124
|
+
* Produces a solid-color panel frame — handy for clearing a panel or showing a
|
|
125
|
+
* status color.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* await client.draw.sendImage(1, (await solidFrame('#000000')).data); // blank
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function solidFrame(color: string, options?: EncodeOptions): Promise<EncodedFrame>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Color helpers: hex⇄RGB conversion, mixing, and accent extraction from
|
|
136
|
+
* artwork (used to tint the now-playing panels).
|
|
137
|
+
*
|
|
138
|
+
* @packageDocumentation
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
/** An `[r, g, b]` triple with each channel in `0`–`255`. */
|
|
142
|
+
type Rgb = [number, number, number];
|
|
143
|
+
/** Parses a `#RGB` or `#RRGGBB` hex string into an {@link Rgb} triple. */
|
|
144
|
+
declare function hexToRgb(hex: string): Rgb;
|
|
145
|
+
/** Formats an {@link Rgb} triple as an uppercase `#RRGGBB` string. */
|
|
146
|
+
declare function rgbToHex([r, g, b]: Rgb): string;
|
|
147
|
+
/** Linearly interpolates between two colors (`t` in `0`–`1`). */
|
|
148
|
+
declare function mixRgb(a: Rgb, b: Rgb, t: number): Rgb;
|
|
149
|
+
/** Scales every channel of a color by `factor`. */
|
|
150
|
+
declare function scaleRgb([r, g, b]: Rgb, factor: number): Rgb;
|
|
151
|
+
/**
|
|
152
|
+
* Extracts a vivid accent color from artwork — the most saturated reasonably
|
|
153
|
+
* bright pixel — for tinting backgrounds and progress bars. Mirrors the proven
|
|
154
|
+
* heuristic from the reference now-playing app.
|
|
155
|
+
*
|
|
156
|
+
* @param source - The artwork to sample.
|
|
157
|
+
* @param fallback - Returned when no saturated pixel is found. Defaults to
|
|
158
|
+
* Spotify green (`#1DB954`).
|
|
159
|
+
*/
|
|
160
|
+
declare function getAccentColor(source: ImageSource, fallback?: string): Promise<string>;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Album-art styling — the two looks from the reference now-playing app:
|
|
164
|
+
* a crisp "smooth" treatment and a chunky "pixel" treatment.
|
|
165
|
+
*
|
|
166
|
+
* @packageDocumentation
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
/** Visual treatment applied by {@link prepareAlbumArt}. */
|
|
170
|
+
type AlbumArtStyle = 'smooth' | 'pixel';
|
|
171
|
+
/** Options for {@link prepareAlbumArt}. */
|
|
172
|
+
interface PrepareAlbumArtOptions extends EncodeOptions {
|
|
173
|
+
/**
|
|
174
|
+
* `"smooth"` (default) gently boosts saturation/contrast and unsharp-masks for
|
|
175
|
+
* a clean look; `"pixel"` downsamples to 32×32 nearest-neighbour for a retro,
|
|
176
|
+
* blocky aesthetic.
|
|
177
|
+
*/
|
|
178
|
+
style?: AlbumArtStyle;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Prepares album/cover artwork for a panel, matching the look of the reference
|
|
182
|
+
* Spotify app.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* const art = await prepareAlbumArt(coverUrl, { style: 'smooth' });
|
|
187
|
+
* await client.draw.sendImage(0, art.data);
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
declare function prepareAlbumArt(source: ImageSource, options?: PrepareAlbumArtOptions): Promise<EncodedFrame>;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Split one image across several panels to create a single wide (or tall)
|
|
194
|
+
* canvas spanning the Times Gate.
|
|
195
|
+
*
|
|
196
|
+
* @packageDocumentation
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
/** A frame paired with the panel it belongs on. */
|
|
200
|
+
interface PanelFrame {
|
|
201
|
+
/** The target panel index. */
|
|
202
|
+
panel: PanelIndex;
|
|
203
|
+
/** The encoded frame for that panel. */
|
|
204
|
+
frame: EncodedFrame;
|
|
205
|
+
}
|
|
206
|
+
/** Options for {@link splitImageAcrossPanels}. */
|
|
207
|
+
interface SplitOptions extends EncodeOptions {
|
|
208
|
+
/** Panels to span, in visual order. Defaults to all five (`[0,1,2,3,4]`). */
|
|
209
|
+
panels?: PanelIndex[];
|
|
210
|
+
/**
|
|
211
|
+
* `"horizontal"` (default) lays the image out left-to-right across panels;
|
|
212
|
+
* `"vertical"` stacks top-to-bottom.
|
|
213
|
+
*/
|
|
214
|
+
layout?: 'horizontal' | 'vertical';
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Slices a single image into one square frame per panel, so a panorama spans
|
|
218
|
+
* the whole device.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* const tiles = await splitImageAcrossPanels('./wide-banner.png');
|
|
223
|
+
* for (const { panel, frame } of tiles) {
|
|
224
|
+
* await client.draw.sendImage(panel, frame.data);
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
declare function splitImageAcrossPanels(source: ImageSource, options?: SplitOptions): Promise<PanelFrame[]>;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Renders a designed "now-playing"-style text panel: a tinted gradient
|
|
232
|
+
* background, an optional eyebrow with a play glyph, an auto-fitted title, a
|
|
233
|
+
* subtitle, and an optional progress bar. Text is rendered with Pango (via
|
|
234
|
+
* sharp) for crisp, properly-wrapped output that doesn't depend on the host's
|
|
235
|
+
* system fonts the way the original Python reference did.
|
|
236
|
+
*
|
|
237
|
+
* @packageDocumentation
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
/** Options for {@link renderTextPanel}. */
|
|
241
|
+
interface RenderTextPanelOptions extends EncodeOptions {
|
|
242
|
+
/** The headline text (e.g. a track title). Auto-sized to fit. */
|
|
243
|
+
title: string;
|
|
244
|
+
/** Secondary text below the title (e.g. an artist). Optional. */
|
|
245
|
+
subtitle?: string;
|
|
246
|
+
/** Small uppercase label above the title (e.g. `"NOW PLAYING"`). Optional. */
|
|
247
|
+
eyebrow?: string;
|
|
248
|
+
/** Accent color (hex) used for the tint, eyebrow, subtitle and bar. Defaults to `#1DB954`. */
|
|
249
|
+
accent?: string;
|
|
250
|
+
/** Progress fraction `0`–`1`. When set, a progress bar is drawn. */
|
|
251
|
+
progress?: number;
|
|
252
|
+
/**
|
|
253
|
+
* Background style: `"gradient"` (default, an accent-tinted vertical fade),
|
|
254
|
+
* `"solid"` (a flat accent), or any hex string for a custom flat color.
|
|
255
|
+
*/
|
|
256
|
+
background?: 'gradient' | 'solid' | string;
|
|
257
|
+
/** Pango font family. Defaults to `"DejaVu Sans"`. */
|
|
258
|
+
font?: string;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Renders a designed now-playing-style panel and returns an
|
|
262
|
+
* {@link EncodedFrame} ready for a panel. Overly long titles/subtitles are
|
|
263
|
+
* shrunk to fit rather than overflowing the panel.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* const panel = await renderTextPanel({
|
|
268
|
+
* eyebrow: 'Now Playing',
|
|
269
|
+
* title: 'Bohemian Rhapsody',
|
|
270
|
+
* subtitle: 'Queen',
|
|
271
|
+
* accent: '#E94F37',
|
|
272
|
+
* progress: 0.42,
|
|
273
|
+
* });
|
|
274
|
+
* await client.draw.sendImage(1, panel.data);
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
declare function renderTextPanel(options: RenderTextPanelOptions): Promise<EncodedFrame>;
|
|
278
|
+
|
|
279
|
+
export { type AlbumArtStyle, type EncodeOptions, type EncodePanelOptions, type EncodedFormat, type EncodedFrame, type ImageSource, type LoadImageOptions, type PanelFrame, type PrepareAlbumArtOptions, type RenderTextPanelOptions, type ResizeFit, type Rgb, type SplitOptions, encodePanel, finalizeFrame, getAccentColor, hexToRgb, loadImage, mixRgb, prepareAlbumArt, renderTextPanel, rgbToHex, scaleRgb, solidFrame, splitImageAcrossPanels };
|