@mieweb/pulsevault 0.4.2 → 0.6.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/CHANGELOG.md +216 -1
- package/OPERATIONS.md +135 -44
- package/PROTOCOL.md +63 -4
- package/README.md +186 -34
- package/dist/app.d.ts +30 -5
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +24 -5
- package/dist/app.js.map +1 -1
- package/dist/augment.d.ts +3 -0
- package/dist/augment.d.ts.map +1 -1
- package/dist/augment.js.map +1 -1
- package/dist/core.d.ts +107 -8
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +381 -47
- package/dist/core.js.map +1 -1
- package/dist/lib/authorize.d.ts +28 -6
- package/dist/lib/authorize.d.ts.map +1 -1
- package/dist/lib/capability-token.d.ts +26 -0
- package/dist/lib/capability-token.d.ts.map +1 -1
- package/dist/lib/capability-token.js +73 -5
- package/dist/lib/capability-token.js.map +1 -1
- package/dist/lib/completion.d.ts +138 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +375 -0
- package/dist/lib/completion.js.map +1 -0
- package/dist/lib/deeplinks.d.ts +18 -2
- package/dist/lib/deeplinks.d.ts.map +1 -1
- package/dist/lib/deeplinks.js +13 -2
- package/dist/lib/deeplinks.js.map +1 -1
- package/dist/lib/magic.d.ts +41 -1
- package/dist/lib/magic.d.ts.map +1 -1
- package/dist/lib/magic.js +104 -0
- package/dist/lib/magic.js.map +1 -1
- package/dist/lib/options.d.ts +2 -0
- package/dist/lib/options.d.ts.map +1 -1
- package/dist/lib/options.js +8 -2
- package/dist/lib/options.js.map +1 -1
- package/dist/lib/protocol-version.gen.js +1 -1
- package/dist/lib/pulsevaultTus.d.ts +28 -26
- package/dist/lib/pulsevaultTus.d.ts.map +1 -1
- package/dist/lib/pulsevaultTus.js +149 -32
- package/dist/lib/pulsevaultTus.js.map +1 -1
- package/dist/lib/web-ready.d.ts +138 -18
- package/dist/lib/web-ready.d.ts.map +1 -1
- package/dist/lib/web-ready.js +453 -91
- package/dist/lib/web-ready.js.map +1 -1
- package/dist/routes/pulsevault.d.ts +12 -1
- package/dist/routes/pulsevault.d.ts.map +1 -1
- package/dist/routes/pulsevault.js +92 -1
- package/dist/routes/pulsevault.js.map +1 -1
- package/dist/storage/local.d.ts +7 -1
- package/dist/storage/local.d.ts.map +1 -1
- package/dist/storage/local.js +408 -52
- package/dist/storage/local.js.map +1 -1
- package/dist/storage/s3.d.ts +7 -1
- package/dist/storage/s3.d.ts.map +1 -1
- package/dist/storage/s3.js +256 -14
- package/dist/storage/s3.js.map +1 -1
- package/dist/storage/types.d.ts +126 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +7 -0
- package/dist/storage/types.js.map +1 -1
- package/package.json +2 -2
- package/protocol/openapi.json +270 -1
- package/protocol/schemas/artifact-status.schema.json +61 -0
- package/protocol/schemas/capability-token.schema.json +3 -0
- package/protocol/schemas/deep-link.schema.json +2 -2
- package/scripts/web-ready-migrate.mjs +35 -11
package/dist/lib/web-ready.d.ts
CHANGED
|
@@ -1,8 +1,52 @@
|
|
|
1
1
|
import type { PulseVaultLogger } from './request.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Conform uploaded videos to one web-playable format: the one the Pulse app records.
|
|
4
|
+
*
|
|
5
|
+
* A video reaches PulseVault from the Pulse app (already in the target format) or from a host's
|
|
6
|
+
* own file picker: a screen recording, an iPhone HEVC `.mov` (often 10-bit HDR), a WebM from a
|
|
7
|
+
* browser recorder, a 4K clip. Browsers streaming over progressive HTTP need the `moov` atom at
|
|
8
|
+
* the front, a codec they all decode, and 8-bit SDR colour; a phone needs a sane size.
|
|
9
|
+
*
|
|
10
|
+
* `ensureWebReady` does the least work that gets a file there: nothing when it already
|
|
11
|
+
* conforms (a Pulse upload keeps its bytes), a lossless remux when only the container or the
|
|
12
|
+
* `moov` position is off, and otherwise one ffmpeg run that re-encodes only the streams that
|
|
13
|
+
* are off-target. Fail-open by design: without `ffmpeg`/`ffprobe`, or when a run fails or
|
|
14
|
+
* times out, the original bytes keep serving and the result says why.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The format every video is served in. "Already conforms" checks exactly these properties —
|
|
18
|
+
* never the orientation: a landscape video stays landscape and a portrait one portrait, with no
|
|
19
|
+
* crop, pad or stretch.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CONFORM_TARGET: {
|
|
22
|
+
/** MP4 with the `moov` atom at the front (faststart). */
|
|
23
|
+
readonly container: "mp4";
|
|
24
|
+
readonly extension: ".mp4";
|
|
25
|
+
/**
|
|
26
|
+
* H.264, 8-bit 4:2:0 in limited range, even width and height, SDR, no rotation tag. HDR (PQ,
|
|
27
|
+
* HLG) is tone-mapped to BT.709; an SDR source keeps its own colour tags (BT.709 or BT.601),
|
|
28
|
+
* which browsers honour — so a Pulse upload from any phone keeps its bytes.
|
|
29
|
+
*/
|
|
30
|
+
readonly videoCodec: "h264";
|
|
31
|
+
readonly pixelFormat: "yuv420p";
|
|
32
|
+
/** Default cap on the longest edge, in pixels (`webReady.maxEdge`). */
|
|
33
|
+
readonly maxEdge: 1920;
|
|
34
|
+
/** AAC; a video without audio stays silent. */
|
|
35
|
+
readonly audioCodec: "aac";
|
|
36
|
+
};
|
|
37
|
+
/** The containers the conform step turns into the target: the default video `allowedExtensions`. */
|
|
38
|
+
export declare const CONFORM_VIDEO_EXTENSIONS: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* - `none`: already in the target format; the bytes are untouched.
|
|
41
|
+
* - `remuxed`: only the `moov` position was off; rewritten losslessly, same file.
|
|
42
|
+
* - `transcoded`: an MP4 whose video or audio was re-encoded, same file.
|
|
43
|
+
* - `conformed`: a different container (WebM, MKV, MOV, …) rewritten as a new `.mp4`.
|
|
44
|
+
* - `skipped`: nothing was done (no ffmpeg, a failed or timed-out run); the original serves.
|
|
45
|
+
*/
|
|
46
|
+
export type WebReadyAction = 'none' | 'remuxed' | 'transcoded' | 'conformed' | 'skipped';
|
|
3
47
|
export type WebReadyResult = {
|
|
4
48
|
action: WebReadyAction;
|
|
5
|
-
/** Human-readable explanation (what was detected, or why nothing was done). */
|
|
49
|
+
/** Human-readable explanation (what was detected and done, or why nothing was done). */
|
|
6
50
|
reason: string;
|
|
7
51
|
};
|
|
8
52
|
export type WebReadyOptions = {
|
|
@@ -11,15 +55,34 @@ export type WebReadyOptions = {
|
|
|
11
55
|
/** Path to the ffprobe binary. Default `"ffprobe"` (resolved via PATH). */
|
|
12
56
|
ffprobePath?: string;
|
|
13
57
|
/**
|
|
14
|
-
* Whether
|
|
15
|
-
*
|
|
16
|
-
* useful when transcode cost on the serving
|
|
58
|
+
* Whether an off-target stream is re-encoded. Default `true`. Set `false` to never re-encode:
|
|
59
|
+
* only lossless remuxes still run (a `moov` moved to the front, or another container whose
|
|
60
|
+
* streams already conform copied into an `.mp4`) — useful when transcode cost on the serving
|
|
61
|
+
* host is a concern.
|
|
17
62
|
*/
|
|
18
63
|
transcode?: boolean;
|
|
64
|
+
/** Longest edge of the served video, in pixels; larger videos are scaled down. Default `1920`. */
|
|
65
|
+
maxEdge?: number;
|
|
19
66
|
/** x264 CRF for the transcode path (lower = better/larger). Default `23`. */
|
|
20
67
|
crf?: number;
|
|
21
68
|
/** x264 preset for the transcode path. Default `"veryfast"`. */
|
|
22
69
|
preset?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Wall-clock limit for one ffmpeg run, in seconds; the run is killed and the original kept.
|
|
72
|
+
* A guard against a run that hangs, not a speed limit. Default `60 + 10 ×` the video's
|
|
73
|
+
* duration counted in 1080p30 seconds (a second of 4K at 120 fps counts 16), at most an hour,
|
|
74
|
+
* and an hour when the duration is unknown. The default comes from the upload's own metadata,
|
|
75
|
+
* so it's capped: a file can't claim its way to a days-long run. Set this for longer runs.
|
|
76
|
+
*/
|
|
77
|
+
timeoutSeconds?: number;
|
|
78
|
+
/**
|
|
79
|
+
* `false`: leave every rewrite at a temporary `.mp4` path beside the input (the result's
|
|
80
|
+
* `outputPath`) instead of renaming it into place, so the caller can install it under its own
|
|
81
|
+
* lock and conditions. Default `true`.
|
|
82
|
+
*/
|
|
83
|
+
install?: boolean;
|
|
84
|
+
/** Wall-clock limit for each ffprobe run over the upload, in seconds. Default `60`. */
|
|
85
|
+
probeTimeoutSeconds?: number;
|
|
23
86
|
/** Optional logger; `error` fires when ffmpeg/ffprobe are missing or a rewrite fails. */
|
|
24
87
|
logger?: PulseVaultLogger;
|
|
25
88
|
};
|
|
@@ -32,23 +95,78 @@ export type MoovPosition = 'front' | 'end' | 'unknown';
|
|
|
32
95
|
* costs microseconds regardless of file size.
|
|
33
96
|
*
|
|
34
97
|
* Returns `"unknown"` for non-MP4 bytes, truncated headers, or files missing
|
|
35
|
-
* either box
|
|
98
|
+
* either box.
|
|
36
99
|
*/
|
|
37
100
|
export declare function scanMoovPosition(filePath: string): Promise<MoovPosition>;
|
|
101
|
+
/** What ffprobe says about a video: the streams the conform step maps, and its length. */
|
|
102
|
+
export type VideoProbe = {
|
|
103
|
+
video: {
|
|
104
|
+
index: number;
|
|
105
|
+
codec: string;
|
|
106
|
+
pixelFormat: string;
|
|
107
|
+
width: number;
|
|
108
|
+
height: number;
|
|
109
|
+
/** The colour transfer (`bt709`, `arib-std-b67`, …), when tagged. */
|
|
110
|
+
transfer?: string;
|
|
111
|
+
/** `pc` for full range, `tv` for limited, when tagged. */
|
|
112
|
+
range?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Display rotation in degrees from the stream's matrix: `0`, `90`, `-90` or `180`, or another
|
|
115
|
+
* angle in (-180, 180] when the matrix isn't within a degree of a quarter turn (not converted).
|
|
116
|
+
*/
|
|
117
|
+
rotation: number;
|
|
118
|
+
/** Average frames per second, when ffprobe can tell. */
|
|
119
|
+
frameRate?: number;
|
|
120
|
+
};
|
|
121
|
+
/** The first audio stream, or `null` for a silent video. */
|
|
122
|
+
audio: {
|
|
123
|
+
index: number;
|
|
124
|
+
codec: string;
|
|
125
|
+
} | null;
|
|
126
|
+
/** Seconds, or `null` when neither the header nor the packets say. */
|
|
127
|
+
durationSeconds: number | null;
|
|
128
|
+
};
|
|
129
|
+
export type ProbeOptions = {
|
|
130
|
+
/** Path to the ffprobe binary. Default `"ffprobe"` (resolved via PATH). */
|
|
131
|
+
ffprobePath?: string;
|
|
132
|
+
/** Wall-clock limit for each ffprobe run, in seconds; past it the probe fails. Default `60`. */
|
|
133
|
+
timeoutSeconds?: number;
|
|
134
|
+
};
|
|
135
|
+
/** An ffprobe run past its `timeoutSeconds`: the file couldn't be read in time. */
|
|
136
|
+
export declare class ProbeTimeoutError extends Error {
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* ffprobe a file: its first real video stream (not cover art), its first audio stream and its
|
|
140
|
+
* duration. `null` when it isn't a video: ffprobe can't read it, it has no video stream, or it
|
|
141
|
+
* is a picture (an image file, or a stream of one frame). Each ffprobe run is bounded by
|
|
142
|
+
* `timeoutSeconds`; past it, rejects with `ProbeTimeoutError`.
|
|
143
|
+
*/
|
|
144
|
+
export declare function probeVideo(filePath: string, options?: ProbeOptions): Promise<VideoProbe | null>;
|
|
145
|
+
export declare function isBinaryAvailable(binPath: string): Promise<boolean>;
|
|
146
|
+
/** Whether this host can conform videos: both `ffmpeg` and `ffprobe` run. */
|
|
147
|
+
export declare function webReadyAvailable(options?: Pick<WebReadyOptions, 'ffmpegPath' | 'ffprobePath'>): Promise<boolean>;
|
|
148
|
+
/** Fail at once on an option that can't work, for `ensureWebReady` and the core's `webReady`. */
|
|
149
|
+
export declare function validateWebReadyOptions(options: WebReadyOptions): void;
|
|
38
150
|
/**
|
|
39
|
-
*
|
|
151
|
+
* Bring the video at `filePath` to `CONFORM_TARGET`:
|
|
40
152
|
*
|
|
41
|
-
* -
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
153
|
+
* - already conforming → `none`, bytes untouched;
|
|
154
|
+
* - an MP4 with conforming streams but the `moov` at the end → lossless faststart remux in
|
|
155
|
+
* place (`remuxed`);
|
|
156
|
+
* - any other container with conforming streams → lossless remux into a new `.mp4` beside it
|
|
157
|
+
* (`conformed`);
|
|
158
|
+
* - otherwise one ffmpeg run: the video re-encoded to H.264 when its codec, pixel format, bit
|
|
159
|
+
* depth, size, rotation or dynamic range is off (scaled by the longest edge, rotation
|
|
160
|
+
* applied, HDR tone-mapped), the audio re-encoded to AAC unless it already is — in place for
|
|
161
|
+
* an MP4 (`transcoded`), into a new `.mp4` otherwise (`conformed`);
|
|
162
|
+
* - no ffmpeg/ffprobe, a file ffprobe can't read, a failed or timed-out run → `skipped` with
|
|
163
|
+
* the reason; the original is untouched.
|
|
47
164
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
165
|
+
* When the result carries `outputPath`, the conformed file was written there (`<name>.mp4`)
|
|
166
|
+
* and the original is left in place: the caller switches the artifact over, then removes it.
|
|
167
|
+
* With `install: false` nothing is renamed: every rewrite is left at a temporary path beside
|
|
168
|
+
* the input, returned as `outputPath`, for the caller to install (or remove).
|
|
169
|
+
* Never throws for pipeline reasons.
|
|
52
170
|
*
|
|
53
171
|
* NOTE: a rewrite changes the artifact's bytes, so any upload-time checksum
|
|
54
172
|
* recorded for it (e.g. the local adapter's sidecar `checksum` from
|
|
@@ -56,5 +174,7 @@ export declare function scanMoovPosition(filePath: string): Promise<MoovPosition
|
|
|
56
174
|
* Consumers verifying bytes against `getChecksum` must treat it as
|
|
57
175
|
* upload-time provenance, not current-file integrity.
|
|
58
176
|
*/
|
|
59
|
-
export declare function ensureWebReady(filePath: string, options?: WebReadyOptions): Promise<WebReadyResult
|
|
177
|
+
export declare function ensureWebReady(filePath: string, options?: WebReadyOptions): Promise<WebReadyResult & {
|
|
178
|
+
outputPath?: string;
|
|
179
|
+
}>;
|
|
60
180
|
//# sourceMappingURL=web-ready.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-ready.d.ts","sourceRoot":"","sources":["../../src/lib/web-ready.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-ready.d.ts","sourceRoot":"","sources":["../../src/lib/web-ready.ts"],"names":[],"mappings":"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"}
|