@nitida/asset-client 0.16.2 → 0.16.4
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/AGENTS.md +10 -5
- package/README.md +5 -3
- package/dist/index.cjs +43 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -16
- package/dist/index.d.ts +101 -16
- package/dist/index.js +41 -8
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/index.ts +149 -19
- package/src/palette.ts +20 -19
- package/src/slots.ts +5 -5
- package/src/transform.ts +17 -2
package/dist/index.d.cts
CHANGED
|
@@ -51,14 +51,14 @@ declare function getTextColorForBackground(swatch: PaletteSwatch | string | null
|
|
|
51
51
|
/** Luminancia relativa WCAG de un hex. 0 = negro, 1 = blanco. */
|
|
52
52
|
declare function relativeLuminance(hex: string): number;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* WCAG contrast ratio between two luminances: `(L1 + 0.05) / (L2 + 0.05)`,
|
|
55
|
+
* lighter on top. 1 = identical, 21 = black against white.
|
|
56
56
|
*/
|
|
57
57
|
declare function contrastRatio(l1: number, l2: number): number;
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* Same choice as the recommended text colour, but it also returns **the ratio
|
|
60
|
+
* it achieved** and whether that passes AA — so a caller can decide with the
|
|
61
|
+
* number in front of them instead of assuming it was enough.
|
|
62
62
|
*/
|
|
63
63
|
declare function bestTextContrast(hex: string): {
|
|
64
64
|
color: "#000000" | "#FFFFFF";
|
|
@@ -98,9 +98,9 @@ declare function getPaletteBlurBackground(palette: AssetPalette | null | undefin
|
|
|
98
98
|
* Slots give tenants a way to attach stable, human-readable names
|
|
99
99
|
* ("webapp.wizard.pool-type.icon-1", "storefront.cr.hero-video.landscape_hd_16x9.mp4")
|
|
100
100
|
* to assets they uploaded. Consumers resolve names → AssetDTOs at
|
|
101
|
-
* build / runtime so their source never hardcodes a CDN URL;
|
|
102
|
-
* admin rebinds a slot
|
|
103
|
-
* up the swap on cache refresh.
|
|
101
|
+
* build / runtime so their source never hardcodes a CDN URL; an
|
|
102
|
+
* admin rebinds a slot in the platform console and every consumer
|
|
103
|
+
* picks up the swap on cache refresh.
|
|
104
104
|
*
|
|
105
105
|
* Two layers in this package:
|
|
106
106
|
* - `resolveSlot` / `resolveSlots` — universal (server, edge,
|
|
@@ -137,8 +137,8 @@ type SlotResolution = {
|
|
|
137
137
|
*
|
|
138
138
|
* configureSlotResolver({
|
|
139
139
|
* endpoint: process.env.AQUIENPZ_URL,
|
|
140
|
-
* apiKey: process.env.
|
|
141
|
-
* tenantCode: "
|
|
140
|
+
* apiKey: process.env.AQUIENPZ_API_KEY, // amk_rt_* — server-only
|
|
141
|
+
* tenantCode: "acme-co",
|
|
142
142
|
* });
|
|
143
143
|
*/
|
|
144
144
|
declare function configureSlotResolver(opts: {
|
|
@@ -336,13 +336,28 @@ declare function getVideoTransformUrl(asset: Pick<AssetDTO, "sha">, opts: Transf
|
|
|
336
336
|
* if (prefersNativeHls(video)) {
|
|
337
337
|
* video.src = src;
|
|
338
338
|
* } else {
|
|
339
|
-
*
|
|
340
|
-
* const hls = new Hls({ startLevel: -1, testBandwidth: true, abrEwmaDefaultEstimate: 1_000_000 });
|
|
339
|
+
* const hls = new Hls({ capLevelToPlayerSize: false, abrEwmaDefaultEstimate: 5_000_000 });
|
|
341
340
|
* hls.loadSource(src);
|
|
342
341
|
* hls.attachMedia(video);
|
|
343
342
|
* }
|
|
344
343
|
* ```
|
|
345
344
|
*
|
|
345
|
+
* ⚠️ **Do NOT pass `startLevel: -1` with `testBandwidth: true`.** That pair is
|
|
346
|
+
* documented by hls.js as *"forces the player to download a fragment from the
|
|
347
|
+
* lowest level to establish a bandwidth estimate"* — on a clip short enough to
|
|
348
|
+
* be one segment, the probe IS the whole video, and it plays at the bottom
|
|
349
|
+
* rung from first frame to last. (This doc-comment recommended exactly that
|
|
350
|
+
* until 2026-08-18; a 5.042 s 4K asset was measured being delivered at
|
|
351
|
+
* 426x240 because of it.) Leave `startLevel` unset: hls.js then opens on the
|
|
352
|
+
* FIRST level in the manifest, and the server puts the right one there —
|
|
353
|
+
* a mid rung for long video, the top rung for a clip under 18 s, which is the
|
|
354
|
+
* same rung native HLS opens on per RFC 8216 §6.3.4. The ladder decides; the
|
|
355
|
+
* player should not second-guess it.
|
|
356
|
+
*
|
|
357
|
+
* `capLevelToPlayerSize` is worth disabling explicitly: `@videojs/core`
|
|
358
|
+
* defaults it to `true`, which caps quality to the player's rendered pixel box,
|
|
359
|
+
* so a small inline player is pinned to 240p/360p on any connection.
|
|
360
|
+
*
|
|
346
361
|
* On first request the server returns 202 Accepted while a background
|
|
347
362
|
* job transcodes the ladder (typically 1-3 min for a 90 s source);
|
|
348
363
|
* subsequent requests get 302 to the cached master.m3u8. Keep the
|
|
@@ -416,7 +431,7 @@ declare function getTransformSrcSet(asset: Pick<AssetDTO, "sha">, widths: number
|
|
|
416
431
|
* USED for, removing the implicit landscape-only assumption that bit us
|
|
417
432
|
* with vertical product photos.
|
|
418
433
|
*/
|
|
419
|
-
type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "mp3";
|
|
434
|
+
type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "hls" | "mp3";
|
|
420
435
|
/** 1-char alias used in storage keys / wire `presets` string. */
|
|
421
436
|
declare const PRESET_SHORT: Record<VariantPreset, string>;
|
|
422
437
|
declare const PRESET_LONG: Record<string, VariantPreset>;
|
|
@@ -451,9 +466,79 @@ type AssetVariant = {
|
|
|
451
466
|
*
|
|
452
467
|
* Absent on variants written before the trace field existed.
|
|
453
468
|
*/
|
|
454
|
-
sourceFrom?: VariantPreset | "upload";
|
|
469
|
+
sourceFrom?: VariantPreset | "upload" | "transform-route" | "hls-transcode" | "cdn-probe";
|
|
455
470
|
/** ISO timestamp this variant was written. Absent on pre-trace variants. */
|
|
456
471
|
createdAt?: string;
|
|
472
|
+
/**
|
|
473
|
+
* **`preset: "hls"` only** — the ladder's rungs, in MASTER ORDER.
|
|
474
|
+
*
|
|
475
|
+
* The reason this exists: an entry that only says *there is a ladder* leaves
|
|
476
|
+
* a consumer that plans a composition exactly as blind as no entry at all,
|
|
477
|
+
* because the ceiling of a composition is its weakest ingredient and there is
|
|
478
|
+
* no upscale. Before this field the only way to learn a clip's real rungs was
|
|
479
|
+
* to fetch the master playlist — or worse, download the asset.
|
|
480
|
+
*
|
|
481
|
+
* `rungs[0]` is the rung every client OPENS on (RFC 8216 §6.3.4 for native
|
|
482
|
+
* HLS; hls.js with `startLevel` unset uses "the first level in the
|
|
483
|
+
* manifest"), so the order is a delivery fact — do not sort it in place.
|
|
484
|
+
*
|
|
485
|
+
* Use {@link hlsLadderAlignment} rather than eyeballing `segments`: a ladder
|
|
486
|
+
* can be complete and still unable to adapt.
|
|
487
|
+
*/
|
|
488
|
+
rungs?: HlsRung[];
|
|
489
|
+
};
|
|
490
|
+
/**
|
|
491
|
+
* One rung of an adaptive HLS ladder.
|
|
492
|
+
*
|
|
493
|
+
* `segments` / `durationSec` are what make a ladder JUDGEABLE rather than
|
|
494
|
+
* merely present. Measured on a prod ladder of an 87 s 4K source: 240p cut at
|
|
495
|
+
* 14.35 s in 12 segments, 720p at 5.63 s in 12, 2160p at 5.88 s in 14. Cuts
|
|
496
|
+
* that do not line up cannot be swapped, and a swap is what a rendition switch
|
|
497
|
+
* IS — so that ladder looked complete and adapted badly. One segment means zero
|
|
498
|
+
* switch points: whichever rung the player opens on is the rung it finishes on.
|
|
499
|
+
*
|
|
500
|
+
* Both are optional because a rung whose playlist could not be read is recorded
|
|
501
|
+
* WITHOUT them rather than with a zero — unmeasured and none are different
|
|
502
|
+
* facts, and a `0` there would read as the latter.
|
|
503
|
+
*/
|
|
504
|
+
type HlsRung = {
|
|
505
|
+
/** Rung directory / identity — `"720p"`. */
|
|
506
|
+
name: string;
|
|
507
|
+
/** `RESOLUTION` from the master playlist. */
|
|
508
|
+
width: number;
|
|
509
|
+
height: number;
|
|
510
|
+
/** `BANDWIDTH` in bits per second. */
|
|
511
|
+
bandwidth: number;
|
|
512
|
+
/** Absolute URL of this rung's media playlist. */
|
|
513
|
+
url: string;
|
|
514
|
+
/** `#EXTINF` count. */
|
|
515
|
+
segments?: number;
|
|
516
|
+
/** Sum of the `#EXTINF` values, seconds. */
|
|
517
|
+
durationSec?: number;
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* The ladder of an asset, or `null` when it has none / the DTO does not carry
|
|
521
|
+
* `variants` (the slim list shape never does — use `hasPreset(a, "hls")` there).
|
|
522
|
+
*/
|
|
523
|
+
declare function getHlsLadder(asset: Pick<AssetDTO, "variants">): AssetVariant | null;
|
|
524
|
+
/**
|
|
525
|
+
* What the recorded rungs actually support — computed here, never stored, so
|
|
526
|
+
* one rule serves every consumer and a change to it does not need a backfill.
|
|
527
|
+
*
|
|
528
|
+
* - `switchable`: more than one rung AND more than one segment. False means the
|
|
529
|
+
* player is pinned to whatever rung it opens on for the entire clip.
|
|
530
|
+
* - `aligned`: every measured rung reports the same segment count. `null` means
|
|
531
|
+
* fewer than two rungs were measured — **unknown, not false.** Treating that
|
|
532
|
+
* as `false` rejects ladders nobody looked at.
|
|
533
|
+
* - `ceilingHeight`: the tallest rung. The ceiling of any composition using it.
|
|
534
|
+
*/
|
|
535
|
+
declare function hlsLadderAlignment(rungs: HlsRung[]): {
|
|
536
|
+
rungCount: number;
|
|
537
|
+
ceilingHeight: number;
|
|
538
|
+
floorHeight: number;
|
|
539
|
+
switchable: boolean;
|
|
540
|
+
aligned: boolean | null;
|
|
541
|
+
minSegments: number | null;
|
|
457
542
|
};
|
|
458
543
|
/**
|
|
459
544
|
* Compact wire shape — what the server actually sends. Aliases (`w`, `h`,
|
|
@@ -557,7 +642,7 @@ declare function getAssetUrl(asset: Pick<AssetDTO, "sha"> & OriginalHints, prese
|
|
|
557
642
|
* Reads `dto.presets` — the compact 1-char code string the server sends on EVERY shape, including
|
|
558
643
|
* the slim list/resolver one that carries no `variants` at all. That is why this exists and why it
|
|
559
644
|
* stays the right existence check even now that `GET /assets/:id` really does send `variants`
|
|
560
|
-
* (it did not until 2026-08-17
|
|
645
|
+
* (it did not until the 2026-08-17 deploy).
|
|
561
646
|
*
|
|
562
647
|
* @example
|
|
563
648
|
* ```ts
|
|
@@ -586,4 +671,4 @@ declare function getAssetDimensions(asset: Pick<AssetDTO, "w" | "h">): {
|
|
|
586
671
|
height: number;
|
|
587
672
|
} | null;
|
|
588
673
|
|
|
589
|
-
export { type AssetDTO, type AssetPalette, type AssetVariant, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type ResolveSlotOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl };
|
|
674
|
+
export { type AssetDTO, type AssetPalette, type AssetVariant, type HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type ResolveSlotOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsLadder, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, hlsLadderAlignment, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -51,14 +51,14 @@ declare function getTextColorForBackground(swatch: PaletteSwatch | string | null
|
|
|
51
51
|
/** Luminancia relativa WCAG de un hex. 0 = negro, 1 = blanco. */
|
|
52
52
|
declare function relativeLuminance(hex: string): number;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* WCAG contrast ratio between two luminances: `(L1 + 0.05) / (L2 + 0.05)`,
|
|
55
|
+
* lighter on top. 1 = identical, 21 = black against white.
|
|
56
56
|
*/
|
|
57
57
|
declare function contrastRatio(l1: number, l2: number): number;
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* Same choice as the recommended text colour, but it also returns **the ratio
|
|
60
|
+
* it achieved** and whether that passes AA — so a caller can decide with the
|
|
61
|
+
* number in front of them instead of assuming it was enough.
|
|
62
62
|
*/
|
|
63
63
|
declare function bestTextContrast(hex: string): {
|
|
64
64
|
color: "#000000" | "#FFFFFF";
|
|
@@ -98,9 +98,9 @@ declare function getPaletteBlurBackground(palette: AssetPalette | null | undefin
|
|
|
98
98
|
* Slots give tenants a way to attach stable, human-readable names
|
|
99
99
|
* ("webapp.wizard.pool-type.icon-1", "storefront.cr.hero-video.landscape_hd_16x9.mp4")
|
|
100
100
|
* to assets they uploaded. Consumers resolve names → AssetDTOs at
|
|
101
|
-
* build / runtime so their source never hardcodes a CDN URL;
|
|
102
|
-
* admin rebinds a slot
|
|
103
|
-
* up the swap on cache refresh.
|
|
101
|
+
* build / runtime so their source never hardcodes a CDN URL; an
|
|
102
|
+
* admin rebinds a slot in the platform console and every consumer
|
|
103
|
+
* picks up the swap on cache refresh.
|
|
104
104
|
*
|
|
105
105
|
* Two layers in this package:
|
|
106
106
|
* - `resolveSlot` / `resolveSlots` — universal (server, edge,
|
|
@@ -137,8 +137,8 @@ type SlotResolution = {
|
|
|
137
137
|
*
|
|
138
138
|
* configureSlotResolver({
|
|
139
139
|
* endpoint: process.env.AQUIENPZ_URL,
|
|
140
|
-
* apiKey: process.env.
|
|
141
|
-
* tenantCode: "
|
|
140
|
+
* apiKey: process.env.AQUIENPZ_API_KEY, // amk_rt_* — server-only
|
|
141
|
+
* tenantCode: "acme-co",
|
|
142
142
|
* });
|
|
143
143
|
*/
|
|
144
144
|
declare function configureSlotResolver(opts: {
|
|
@@ -336,13 +336,28 @@ declare function getVideoTransformUrl(asset: Pick<AssetDTO, "sha">, opts: Transf
|
|
|
336
336
|
* if (prefersNativeHls(video)) {
|
|
337
337
|
* video.src = src;
|
|
338
338
|
* } else {
|
|
339
|
-
*
|
|
340
|
-
* const hls = new Hls({ startLevel: -1, testBandwidth: true, abrEwmaDefaultEstimate: 1_000_000 });
|
|
339
|
+
* const hls = new Hls({ capLevelToPlayerSize: false, abrEwmaDefaultEstimate: 5_000_000 });
|
|
341
340
|
* hls.loadSource(src);
|
|
342
341
|
* hls.attachMedia(video);
|
|
343
342
|
* }
|
|
344
343
|
* ```
|
|
345
344
|
*
|
|
345
|
+
* ⚠️ **Do NOT pass `startLevel: -1` with `testBandwidth: true`.** That pair is
|
|
346
|
+
* documented by hls.js as *"forces the player to download a fragment from the
|
|
347
|
+
* lowest level to establish a bandwidth estimate"* — on a clip short enough to
|
|
348
|
+
* be one segment, the probe IS the whole video, and it plays at the bottom
|
|
349
|
+
* rung from first frame to last. (This doc-comment recommended exactly that
|
|
350
|
+
* until 2026-08-18; a 5.042 s 4K asset was measured being delivered at
|
|
351
|
+
* 426x240 because of it.) Leave `startLevel` unset: hls.js then opens on the
|
|
352
|
+
* FIRST level in the manifest, and the server puts the right one there —
|
|
353
|
+
* a mid rung for long video, the top rung for a clip under 18 s, which is the
|
|
354
|
+
* same rung native HLS opens on per RFC 8216 §6.3.4. The ladder decides; the
|
|
355
|
+
* player should not second-guess it.
|
|
356
|
+
*
|
|
357
|
+
* `capLevelToPlayerSize` is worth disabling explicitly: `@videojs/core`
|
|
358
|
+
* defaults it to `true`, which caps quality to the player's rendered pixel box,
|
|
359
|
+
* so a small inline player is pinned to 240p/360p on any connection.
|
|
360
|
+
*
|
|
346
361
|
* On first request the server returns 202 Accepted while a background
|
|
347
362
|
* job transcodes the ladder (typically 1-3 min for a 90 s source);
|
|
348
363
|
* subsequent requests get 302 to the cached master.m3u8. Keep the
|
|
@@ -416,7 +431,7 @@ declare function getTransformSrcSet(asset: Pick<AssetDTO, "sha">, widths: number
|
|
|
416
431
|
* USED for, removing the implicit landscape-only assumption that bit us
|
|
417
432
|
* with vertical product photos.
|
|
418
433
|
*/
|
|
419
|
-
type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "mp3";
|
|
434
|
+
type VariantPreset = "thumb" | "sm" | "md" | "lg" | "xl" | "original" | "poster" | "video" | "aiproxy" | "hls" | "mp3";
|
|
420
435
|
/** 1-char alias used in storage keys / wire `presets` string. */
|
|
421
436
|
declare const PRESET_SHORT: Record<VariantPreset, string>;
|
|
422
437
|
declare const PRESET_LONG: Record<string, VariantPreset>;
|
|
@@ -451,9 +466,79 @@ type AssetVariant = {
|
|
|
451
466
|
*
|
|
452
467
|
* Absent on variants written before the trace field existed.
|
|
453
468
|
*/
|
|
454
|
-
sourceFrom?: VariantPreset | "upload";
|
|
469
|
+
sourceFrom?: VariantPreset | "upload" | "transform-route" | "hls-transcode" | "cdn-probe";
|
|
455
470
|
/** ISO timestamp this variant was written. Absent on pre-trace variants. */
|
|
456
471
|
createdAt?: string;
|
|
472
|
+
/**
|
|
473
|
+
* **`preset: "hls"` only** — the ladder's rungs, in MASTER ORDER.
|
|
474
|
+
*
|
|
475
|
+
* The reason this exists: an entry that only says *there is a ladder* leaves
|
|
476
|
+
* a consumer that plans a composition exactly as blind as no entry at all,
|
|
477
|
+
* because the ceiling of a composition is its weakest ingredient and there is
|
|
478
|
+
* no upscale. Before this field the only way to learn a clip's real rungs was
|
|
479
|
+
* to fetch the master playlist — or worse, download the asset.
|
|
480
|
+
*
|
|
481
|
+
* `rungs[0]` is the rung every client OPENS on (RFC 8216 §6.3.4 for native
|
|
482
|
+
* HLS; hls.js with `startLevel` unset uses "the first level in the
|
|
483
|
+
* manifest"), so the order is a delivery fact — do not sort it in place.
|
|
484
|
+
*
|
|
485
|
+
* Use {@link hlsLadderAlignment} rather than eyeballing `segments`: a ladder
|
|
486
|
+
* can be complete and still unable to adapt.
|
|
487
|
+
*/
|
|
488
|
+
rungs?: HlsRung[];
|
|
489
|
+
};
|
|
490
|
+
/**
|
|
491
|
+
* One rung of an adaptive HLS ladder.
|
|
492
|
+
*
|
|
493
|
+
* `segments` / `durationSec` are what make a ladder JUDGEABLE rather than
|
|
494
|
+
* merely present. Measured on a prod ladder of an 87 s 4K source: 240p cut at
|
|
495
|
+
* 14.35 s in 12 segments, 720p at 5.63 s in 12, 2160p at 5.88 s in 14. Cuts
|
|
496
|
+
* that do not line up cannot be swapped, and a swap is what a rendition switch
|
|
497
|
+
* IS — so that ladder looked complete and adapted badly. One segment means zero
|
|
498
|
+
* switch points: whichever rung the player opens on is the rung it finishes on.
|
|
499
|
+
*
|
|
500
|
+
* Both are optional because a rung whose playlist could not be read is recorded
|
|
501
|
+
* WITHOUT them rather than with a zero — unmeasured and none are different
|
|
502
|
+
* facts, and a `0` there would read as the latter.
|
|
503
|
+
*/
|
|
504
|
+
type HlsRung = {
|
|
505
|
+
/** Rung directory / identity — `"720p"`. */
|
|
506
|
+
name: string;
|
|
507
|
+
/** `RESOLUTION` from the master playlist. */
|
|
508
|
+
width: number;
|
|
509
|
+
height: number;
|
|
510
|
+
/** `BANDWIDTH` in bits per second. */
|
|
511
|
+
bandwidth: number;
|
|
512
|
+
/** Absolute URL of this rung's media playlist. */
|
|
513
|
+
url: string;
|
|
514
|
+
/** `#EXTINF` count. */
|
|
515
|
+
segments?: number;
|
|
516
|
+
/** Sum of the `#EXTINF` values, seconds. */
|
|
517
|
+
durationSec?: number;
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* The ladder of an asset, or `null` when it has none / the DTO does not carry
|
|
521
|
+
* `variants` (the slim list shape never does — use `hasPreset(a, "hls")` there).
|
|
522
|
+
*/
|
|
523
|
+
declare function getHlsLadder(asset: Pick<AssetDTO, "variants">): AssetVariant | null;
|
|
524
|
+
/**
|
|
525
|
+
* What the recorded rungs actually support — computed here, never stored, so
|
|
526
|
+
* one rule serves every consumer and a change to it does not need a backfill.
|
|
527
|
+
*
|
|
528
|
+
* - `switchable`: more than one rung AND more than one segment. False means the
|
|
529
|
+
* player is pinned to whatever rung it opens on for the entire clip.
|
|
530
|
+
* - `aligned`: every measured rung reports the same segment count. `null` means
|
|
531
|
+
* fewer than two rungs were measured — **unknown, not false.** Treating that
|
|
532
|
+
* as `false` rejects ladders nobody looked at.
|
|
533
|
+
* - `ceilingHeight`: the tallest rung. The ceiling of any composition using it.
|
|
534
|
+
*/
|
|
535
|
+
declare function hlsLadderAlignment(rungs: HlsRung[]): {
|
|
536
|
+
rungCount: number;
|
|
537
|
+
ceilingHeight: number;
|
|
538
|
+
floorHeight: number;
|
|
539
|
+
switchable: boolean;
|
|
540
|
+
aligned: boolean | null;
|
|
541
|
+
minSegments: number | null;
|
|
457
542
|
};
|
|
458
543
|
/**
|
|
459
544
|
* Compact wire shape — what the server actually sends. Aliases (`w`, `h`,
|
|
@@ -557,7 +642,7 @@ declare function getAssetUrl(asset: Pick<AssetDTO, "sha"> & OriginalHints, prese
|
|
|
557
642
|
* Reads `dto.presets` — the compact 1-char code string the server sends on EVERY shape, including
|
|
558
643
|
* the slim list/resolver one that carries no `variants` at all. That is why this exists and why it
|
|
559
644
|
* stays the right existence check even now that `GET /assets/:id` really does send `variants`
|
|
560
|
-
* (it did not until 2026-08-17
|
|
645
|
+
* (it did not until the 2026-08-17 deploy).
|
|
561
646
|
*
|
|
562
647
|
* @example
|
|
563
648
|
* ```ts
|
|
@@ -586,4 +671,4 @@ declare function getAssetDimensions(asset: Pick<AssetDTO, "w" | "h">): {
|
|
|
586
671
|
height: number;
|
|
587
672
|
} | null;
|
|
588
673
|
|
|
589
|
-
export { type AssetDTO, type AssetPalette, type AssetVariant, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type ResolveSlotOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl };
|
|
674
|
+
export { type AssetDTO, type AssetPalette, type AssetVariant, type HlsRung, PRESET_EXT, PRESET_LONG, PRESET_MAX_DIM, PRESET_SHORT, type PaletteSwatch, type ResolveSlotOptions, type SignedTransformOptions, type SlotDTO, type SlotResolution, TRANSFORM_WIDTHS, type TransformEffect, type TransformFit, type TransformFormat, type TransformGravity, type TransformOptions, type TransformWidth, type VariantPreset, bestTextContrast, computeVariantDimensions, configureSlotResolver, contrastRatio, extractAssetSha, getAmbientGradient, getAssetDimensions, getAssetSrcSet, getAssetUrl, getCdnBase, getHlsLadder, getHlsStreamingUrl, getPaletteBlurBackground, getPaletteCssVars, getSignedTransformUrl, getTenantId, getTextColorForBackground, getTransformSrcSet, getTransformUrl, getVideoTransformUrl, hasPreset, hlsLadderAlignment, invalidateSlotCache, iteratePaletteSwatches, pickAmbientBackground, relativeLuminance, resolveSlot, resolveSlots, serializeTransform, setCdnBase, setTenantId, signTransformUrl };
|
package/dist/index.js
CHANGED
|
@@ -336,6 +336,11 @@ var PRESET_SHORT = {
|
|
|
336
336
|
poster: "p",
|
|
337
337
|
video: "v",
|
|
338
338
|
aiproxy: "a",
|
|
339
|
+
// `h` — free in both directions: no other preset claims it, and no
|
|
340
|
+
// multi-character token that `stripMultiCharTokens` removes donates one
|
|
341
|
+
// (`transform-<hex>` → t/r/a/n/s/f/o/m + a–f; `pr`; `mp3`). So a server that
|
|
342
|
+
// starts emitting `h` cannot make an OLDER client answer `true` to anything.
|
|
343
|
+
hls: "h",
|
|
339
344
|
// 3 chars, NOT a 1-char alias: the server has no short-form for
|
|
340
345
|
// audio so its `shortPreset("mp3")` falls through to the literal token,
|
|
341
346
|
// and the deployed server already writes the `-mp3.mp3` variant + emits
|
|
@@ -356,6 +361,9 @@ var PRESET_EXT = {
|
|
|
356
361
|
poster: "webp",
|
|
357
362
|
video: "mp4",
|
|
358
363
|
aiproxy: "mp4",
|
|
364
|
+
// The ladder's ENTRY file. Never used to build a key — see `getAssetUrl`,
|
|
365
|
+
// which refuses to derive `<sha>-h.m3u8` because no such object exists.
|
|
366
|
+
hls: "m3u8",
|
|
359
367
|
mp3: "mp3"
|
|
360
368
|
};
|
|
361
369
|
var PRESET_MAX_DIM = {
|
|
@@ -368,10 +376,30 @@ var PRESET_MAX_DIM = {
|
|
|
368
376
|
poster: null,
|
|
369
377
|
video: null,
|
|
370
378
|
aiproxy: null,
|
|
379
|
+
// A ladder has no single max side — it has a rung per size. `null` keeps it
|
|
380
|
+
// out of `getAssetSrcSet`, where offering an `.m3u8` as an `<img>` candidate
|
|
381
|
+
// would be nonsense. Its ceiling is `variants.find(v => v.preset === "hls").height`.
|
|
382
|
+
hls: null,
|
|
371
383
|
// audio has no pixel dimensions; `null` keeps mp3 out of the
|
|
372
384
|
// dimension-based `getAssetSrcSet` / `computeVariantDimensions` logic.
|
|
373
385
|
mp3: null
|
|
374
386
|
};
|
|
387
|
+
function getHlsLadder(asset) {
|
|
388
|
+
return asset.variants?.find((v) => v.preset === "hls") ?? null;
|
|
389
|
+
}
|
|
390
|
+
function hlsLadderAlignment(rungs) {
|
|
391
|
+
const heights = rungs.map((r) => r.height);
|
|
392
|
+
const measured = rungs.map((r) => r.segments).filter((s) => typeof s === "number" && s > 0);
|
|
393
|
+
const minSegments = measured.length > 0 ? Math.min(...measured) : null;
|
|
394
|
+
return {
|
|
395
|
+
rungCount: rungs.length,
|
|
396
|
+
ceilingHeight: heights.length > 0 ? Math.max(...heights) : 0,
|
|
397
|
+
floorHeight: heights.length > 0 ? Math.min(...heights) : 0,
|
|
398
|
+
switchable: rungs.length > 1 && (minSegments ?? 0) > 1,
|
|
399
|
+
aligned: measured.length >= 2 ? new Set(measured).size === 1 : null,
|
|
400
|
+
minSegments
|
|
401
|
+
};
|
|
402
|
+
}
|
|
375
403
|
var cdnBaseUrl = "https://8ok.uk";
|
|
376
404
|
function setCdnBase(url) {
|
|
377
405
|
cdnBaseUrl = url.replace(/\/$/, "");
|
|
@@ -404,15 +432,13 @@ var ORIGINAL_EXT_BY_MIME = {
|
|
|
404
432
|
"video/mp4": "mp4",
|
|
405
433
|
"video/webm": "webm",
|
|
406
434
|
"video/quicktime": "mov",
|
|
407
|
-
// Audio —
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
// 404 y `-o.m4a` da 200.
|
|
435
|
+
// Audio — absent until 2026-08-17. While they were missing this table
|
|
436
|
+
// returned the `bin` sentinel for every voice note, so consumers had to
|
|
437
|
+
// hand-roll the extension themselves. Measured: `-o.bin` 404s, `-o.m4a` 200s.
|
|
411
438
|
//
|
|
412
|
-
// ⚠️
|
|
413
|
-
// `.m4a`
|
|
414
|
-
//
|
|
415
|
-
// corregir por su cuenta.
|
|
439
|
+
// ⚠️ These are keyed by the FULL mime, not the subtype: `audio/mp4` is stored
|
|
440
|
+
// as `.m4a` and `video/mp4` as `.mp4`. A `switch` on the `mp4` subtype cannot
|
|
441
|
+
// tell them apart — a mistake worth not repeating.
|
|
416
442
|
"audio/mpeg": "mpga",
|
|
417
443
|
"audio/mp4": "m4a",
|
|
418
444
|
"audio/x-m4a": "m4a",
|
|
@@ -431,6 +457,11 @@ function getAssetUrl(asset, preset) {
|
|
|
431
457
|
const ext = asset.oext || originalExtForMime(asset.mime);
|
|
432
458
|
return `${cdnBaseUrl}/${variantPrefix()}${asset.sha}-${PRESET_SHORT.original}.${ext}`;
|
|
433
459
|
}
|
|
460
|
+
if (preset === "hls") {
|
|
461
|
+
const stored = asset.variants?.find((v) => v.preset === "hls")?.url;
|
|
462
|
+
if (stored) return stored;
|
|
463
|
+
return `${cdnBaseUrl}/t/format=hls/${asset.sha}.m3u8`;
|
|
464
|
+
}
|
|
434
465
|
return `${cdnBaseUrl}/${variantPrefix()}${asset.sha}-${PRESET_SHORT[preset]}.${PRESET_EXT[preset]}`;
|
|
435
466
|
}
|
|
436
467
|
function hasPreset(asset, preset) {
|
|
@@ -477,6 +508,7 @@ export {
|
|
|
477
508
|
getAssetSrcSet,
|
|
478
509
|
getAssetUrl,
|
|
479
510
|
getCdnBase,
|
|
511
|
+
getHlsLadder,
|
|
480
512
|
getHlsStreamingUrl,
|
|
481
513
|
getPaletteBlurBackground,
|
|
482
514
|
getPaletteCssVars,
|
|
@@ -487,6 +519,7 @@ export {
|
|
|
487
519
|
getTransformUrl,
|
|
488
520
|
getVideoTransformUrl,
|
|
489
521
|
hasPreset,
|
|
522
|
+
hlsLadderAlignment,
|
|
490
523
|
invalidateSlotCache,
|
|
491
524
|
iteratePaletteSwatches,
|
|
492
525
|
pickAmbientBackground,
|