@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/storage/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DataStore } from '@tus/server';
|
|
2
|
+
import type { WebReadyResult } from '../lib/web-ready.js';
|
|
2
3
|
/**
|
|
3
4
|
* How the GET route should serve a resolved artifact. Stream means
|
|
4
5
|
* `@fastify/send` reads directly from a local path; Redirect issues an HTTP
|
|
@@ -82,6 +83,112 @@ export type ReserveUploadParams = {
|
|
|
82
83
|
* an operator can tell which app build produced a file. Display metadata only, like `name`.
|
|
83
84
|
*/
|
|
84
85
|
appVersion?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Opaque host data the capability token carried (`issueCapabilityToken`'s `context`: an owner,
|
|
88
|
+
* a destination), as the `authorize` hook returned it for this create. Persisted verbatim so
|
|
89
|
+
* every later hook, status read and completion replay gets it back without a host lookup.
|
|
90
|
+
* JSON only; never interpreted by PulseVault.
|
|
91
|
+
*/
|
|
92
|
+
context?: unknown;
|
|
93
|
+
};
|
|
94
|
+
/** Everything an adapter knows about one artifact, from `describeArtifact`. */
|
|
95
|
+
export type PulseVaultArtifactMeta = {
|
|
96
|
+
artifactId: string;
|
|
97
|
+
kind: UploadKind;
|
|
98
|
+
/**
|
|
99
|
+
* Lowercase extension of the stored file, including the leading dot: the uploaded file's,
|
|
100
|
+
* until a web-ready conversion turned another container into an MP4 (then `.mp4`).
|
|
101
|
+
*/
|
|
102
|
+
ext: string;
|
|
103
|
+
/**
|
|
104
|
+
* The uploaded file's extension — `filename`'s, and the one tus's record of the upload is kept
|
|
105
|
+
* under — when a conversion stored the artifact under a different `ext`; absent otherwise. Use
|
|
106
|
+
* `uploadIdOf` to name the tus upload.
|
|
107
|
+
*/
|
|
108
|
+
sourceExt?: string;
|
|
109
|
+
/** Original filename from `Upload-Metadata.filename`. */
|
|
110
|
+
filename: string;
|
|
111
|
+
relatedTo?: string;
|
|
112
|
+
checksum?: string;
|
|
113
|
+
name?: string;
|
|
114
|
+
appVersion?: string;
|
|
115
|
+
/** See `ReserveUploadParams.context`. */
|
|
116
|
+
context?: unknown;
|
|
117
|
+
/** `true` once the upload finished and is served. */
|
|
118
|
+
ready: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Whether the host's `onUploadComplete` has returned for this artifact: `false` from reserve,
|
|
121
|
+
* `true` once the core recorded the hook's return. A completion the host never recorded (a
|
|
122
|
+
* throw, a restart) is replayed from this flag. A sidecar written before the flag existed reads
|
|
123
|
+
* as `true` when finished (an upgrade replays nothing) and `false` while still uploading (its
|
|
124
|
+
* completion is still to come).
|
|
125
|
+
*/
|
|
126
|
+
acknowledged: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Whether the web-ready conversion has finished for this artifact: `false` from reserve, `true`
|
|
129
|
+
* once the core recorded it. Only meaningful for a video on a server with `webReady`; the core
|
|
130
|
+
* derives the `processing` status from it. Legacy sidecars read as `true` when finished.
|
|
131
|
+
*/
|
|
132
|
+
converted: boolean;
|
|
133
|
+
/** What the web-ready conversion did (`none`, `remuxed`, …, or `skipped` and why), once it ran. */
|
|
134
|
+
webReady?: WebReadyResult;
|
|
135
|
+
/** Whatever the host recorded with `recordOutcome` (where the upload went, why it was kept). */
|
|
136
|
+
outcome?: unknown;
|
|
137
|
+
/**
|
|
138
|
+
* A token for this reservation of the artifactId, new at every `reserveUpload`, so work that
|
|
139
|
+
* began on one upload can tell the id was removed and reserved again meanwhile (the
|
|
140
|
+
* `generation` patch condition). Required for `webReady` conversions: an adapter that offers
|
|
141
|
+
* `getLocalPath` but keeps no generation has its videos recorded `skipped`, never converted,
|
|
142
|
+
* since a queued conversion couldn't tell its upload from a later one of the same id. (The
|
|
143
|
+
* local adapter keeps one, and gives one at `markReady` to an upload reserved before it did.)
|
|
144
|
+
*/
|
|
145
|
+
generation?: string;
|
|
146
|
+
/** Same meaning as `PulseVaultArtifactRecord.updatedAt`. */
|
|
147
|
+
updatedAt: number;
|
|
148
|
+
/**
|
|
149
|
+
* When the upload finished (ms since the epoch), recorded by `markReady` and never changed
|
|
150
|
+
* after — a stable order for "the newest related file", which `updatedAt` isn't (a later
|
|
151
|
+
* acknowledgement or outcome moves it). Absent on sidecars written before it was recorded.
|
|
152
|
+
*/
|
|
153
|
+
readyAt?: number;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* tus's id for an artifact's upload (`<kind>/<artifactId><ext>`), under the extension it was
|
|
157
|
+
* uploaded with — which a conversion to another container doesn't change.
|
|
158
|
+
*/
|
|
159
|
+
export declare function uploadIdOf(meta: Pick<PulseVaultArtifactMeta, 'kind' | 'artifactId' | 'ext' | 'sourceExt'>): string;
|
|
160
|
+
/** The fields `patchArtifact` may change. Each is optional; absent means unchanged. */
|
|
161
|
+
export type PulseVaultArtifactPatch = {
|
|
162
|
+
acknowledged?: boolean;
|
|
163
|
+
converted?: boolean;
|
|
164
|
+
/** What the web-ready conversion did. */
|
|
165
|
+
webReady?: WebReadyResult;
|
|
166
|
+
/**
|
|
167
|
+
* The stored file's new extension, after a web-ready conversion wrote the artifact's bytes
|
|
168
|
+
* under it (`<kind>/<artifactId><ext>`): from then on the artifact is served from that file.
|
|
169
|
+
* Only adapters that store files on local disk support it; others throw.
|
|
170
|
+
*/
|
|
171
|
+
ext?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Apply the patch only while `converted` is still false, so a conversion's record never
|
|
174
|
+
* replaces one another pass already made (two instances converting the same artifact).
|
|
175
|
+
*/
|
|
176
|
+
unlessConverted?: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Apply the patch only to this reservation of the artifactId (`PulseVaultArtifactMeta
|
|
179
|
+
* .generation`; `null` for one without a generation), not to an upload that reserved the same
|
|
180
|
+
* id after it was removed.
|
|
181
|
+
*/
|
|
182
|
+
generation?: string | null;
|
|
183
|
+
/**
|
|
184
|
+
* A file to install as the artifact's stored bytes, under the same lock as the rest of the
|
|
185
|
+
* patch: renamed onto `<kind>/<artifactId><ext>` (with `ext` the new one when it changes),
|
|
186
|
+
* after which a previous stored file under another extension is deleted. Must be in the
|
|
187
|
+
* artifact's kind directory. Only adapters that store files on local disk support it.
|
|
188
|
+
*/
|
|
189
|
+
file?: string;
|
|
190
|
+
/** `null` clears a recorded outcome. */
|
|
191
|
+
outcome?: unknown;
|
|
85
192
|
};
|
|
86
193
|
/** One artifact as `listArtifacts` reports it. */
|
|
87
194
|
export type PulseVaultArtifactRecord = {
|
|
@@ -176,5 +283,24 @@ export interface PulseVaultStorage {
|
|
|
176
283
|
listArtifacts?(opts?: {
|
|
177
284
|
changedBefore?: number;
|
|
178
285
|
}): AsyncIterable<PulseVaultArtifactRecord>;
|
|
286
|
+
/**
|
|
287
|
+
* Everything stored about one artifact, or `null` if the artifactId is unknown. Optional —
|
|
288
|
+
* the richer hook context, `getStatus`, `getPulse`, reclaiming idle uploads and replaying
|
|
289
|
+
* completions all need it (both built-in adapters have it).
|
|
290
|
+
*/
|
|
291
|
+
describeArtifact?(artifactId: string): Promise<PulseVaultArtifactMeta | null>;
|
|
292
|
+
/**
|
|
293
|
+
* Change the bookkeeping of one artifact (`acknowledged`, `converted`, `webReady`, `ext`,
|
|
294
|
+
* `outcome`, or its stored `file`). Resolves `false`, changing nothing, when the artifactId is
|
|
295
|
+
* unknown or a condition (`unlessConverted`, `generation`) doesn't hold. Optional —
|
|
296
|
+
* completion replay, background web-ready and `recordOutcome` need it.
|
|
297
|
+
*/
|
|
298
|
+
patchArtifact?(artifactId: string, patch: PulseVaultArtifactPatch): Promise<boolean>;
|
|
299
|
+
/**
|
|
300
|
+
* The artifacts that declared `relatedTo` this one (a pulse's captions, beat manifest and
|
|
301
|
+
* thumbnail), in flight or finished. Optional — `getPulse` and the poster route need it. Built
|
|
302
|
+
* from an index the adapter keeps at reserve time, so it never scans every artifact.
|
|
303
|
+
*/
|
|
304
|
+
listRelated?(artifactId: string): AsyncIterable<PulseVaultArtifactRecord>;
|
|
179
305
|
}
|
|
180
306
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"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"}
|
package/dist/storage/types.js
CHANGED
|
@@ -11,4 +11,11 @@ export function parseUploadKind(raw) {
|
|
|
11
11
|
const lower = (raw ?? '').trim().toLowerCase();
|
|
12
12
|
return UPLOAD_KINDS.includes(lower) ? lower : 'video';
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* tus's id for an artifact's upload (`<kind>/<artifactId><ext>`), under the extension it was
|
|
16
|
+
* uploaded with — which a conversion to another container doesn't change.
|
|
17
|
+
*/
|
|
18
|
+
export function uploadIdOf(meta) {
|
|
19
|
+
return `${meta.kind}/${meta.artifactId}${meta.sourceExt ?? meta.ext}`;
|
|
20
|
+
}
|
|
14
21
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AA6BA;;uCAEuC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAU,CAAC;AAInF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAA8B;IAC5D,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/C,OAAQ,YAAkC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAoB,CAAC,CAAC,CAAC,OAAO,CAAC;AAC/F,CAAC;AA0HD;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAA+E;IACxG,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;AACxE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mieweb/pulsevault",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Resumable video uploads via TUS for Fastify (plugin) or any Node backend (framework-agnostic core), with a pluggable storage adapter and a filesystem-first local-storage default.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/app.js",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"@tus/s3-store": "^2.0.6"
|
|
106
106
|
},
|
|
107
107
|
"pulseProtocol": {
|
|
108
|
-
"version": "2.
|
|
108
|
+
"version": "2.5",
|
|
109
109
|
"min": 2,
|
|
110
110
|
"max": 2
|
|
111
111
|
}
|
package/protocol/openapi.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Pulse upload protocol",
|
|
5
5
|
"description": "The HTTP surface of the Pulse upload protocol, as implemented by @mieweb/pulsevault. Generated from the plugin's route schemas — see PROTOCOL.md for the full contract.",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.5"
|
|
7
7
|
},
|
|
8
8
|
"components": {
|
|
9
9
|
"schemas": {}
|
|
@@ -1047,6 +1047,275 @@
|
|
|
1047
1047
|
}
|
|
1048
1048
|
}
|
|
1049
1049
|
}
|
|
1050
|
+
},
|
|
1051
|
+
"/pulsevault/artifacts/{artifactId}/status": {
|
|
1052
|
+
"get": {
|
|
1053
|
+
"summary": "Where an upload is: uploading, processing or ready",
|
|
1054
|
+
"tags": [
|
|
1055
|
+
"pulsevault"
|
|
1056
|
+
],
|
|
1057
|
+
"description": "Reports the artifact's state (`unknown`, `uploading`, `processing` while a web-ready conversion rewrites it, `ready`), its kind and `relatedTo`, the bytes received against its declared length, whether the host's `onUploadComplete` has finished (`acknowledged`), and what the host recorded with `recordOutcome` (`outcome`). Runs the `authorize` hook with `phase: \"status\"`; `createCapabilityAuthorize` grants it to the pairing token and to a view token. Never cached.",
|
|
1058
|
+
"parameters": [
|
|
1059
|
+
{
|
|
1060
|
+
"schema": {
|
|
1061
|
+
"type": "string"
|
|
1062
|
+
},
|
|
1063
|
+
"in": "query",
|
|
1064
|
+
"name": "token",
|
|
1065
|
+
"required": false,
|
|
1066
|
+
"description": "Optional bearer token (the pairing token or a view token), forwarded to the `authorize` hook as `ctx.token` for browsers that can’t set a header."
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
"schema": {
|
|
1070
|
+
"type": "string",
|
|
1071
|
+
"format": "uuid"
|
|
1072
|
+
},
|
|
1073
|
+
"in": "path",
|
|
1074
|
+
"name": "artifactId",
|
|
1075
|
+
"required": true,
|
|
1076
|
+
"description": "UUID of the upload to report on."
|
|
1077
|
+
}
|
|
1078
|
+
],
|
|
1079
|
+
"responses": {
|
|
1080
|
+
"200": {
|
|
1081
|
+
"description": "Where an upload is (PROTOCOL.md §6.5, protocol 2.3; `webReady` 2.5): for a page waiting on it, so the host needs no table of its own. Authorized like opening the artifact. Never cached.",
|
|
1082
|
+
"content": {
|
|
1083
|
+
"application/json": {
|
|
1084
|
+
"schema": {
|
|
1085
|
+
"title": "GET /artifacts/<id>/status response",
|
|
1086
|
+
"description": "Where an upload is (PROTOCOL.md §6.5, protocol 2.3; `webReady` 2.5): for a page waiting on it, so the host needs no table of its own. Authorized like opening the artifact. Never cached.",
|
|
1087
|
+
"type": "object",
|
|
1088
|
+
"required": [
|
|
1089
|
+
"artifactId",
|
|
1090
|
+
"state"
|
|
1091
|
+
],
|
|
1092
|
+
"properties": {
|
|
1093
|
+
"artifactId": {
|
|
1094
|
+
"type": "string",
|
|
1095
|
+
"format": "uuid",
|
|
1096
|
+
"description": "The artifact asked about."
|
|
1097
|
+
},
|
|
1098
|
+
"state": {
|
|
1099
|
+
"type": "string",
|
|
1100
|
+
"enum": [
|
|
1101
|
+
"unknown",
|
|
1102
|
+
"uploading",
|
|
1103
|
+
"processing",
|
|
1104
|
+
"ready"
|
|
1105
|
+
],
|
|
1106
|
+
"description": "`unknown` for an id the server has never seen; `uploading` until the final byte; `processing` while the server converts the bytes for the web; `ready` once the artifact is served."
|
|
1107
|
+
},
|
|
1108
|
+
"kind": {
|
|
1109
|
+
"type": "string",
|
|
1110
|
+
"enum": [
|
|
1111
|
+
"video",
|
|
1112
|
+
"project",
|
|
1113
|
+
"captions",
|
|
1114
|
+
"thumbnail"
|
|
1115
|
+
],
|
|
1116
|
+
"description": "Artifact kind. Absent when `state` is `unknown`."
|
|
1117
|
+
},
|
|
1118
|
+
"relatedTo": {
|
|
1119
|
+
"type": "string",
|
|
1120
|
+
"format": "uuid",
|
|
1121
|
+
"description": "The video this artifact belongs to, if it declared one."
|
|
1122
|
+
},
|
|
1123
|
+
"name": {
|
|
1124
|
+
"type": "string",
|
|
1125
|
+
"description": "The display name the client sent (`Upload-Metadata.name`), if any."
|
|
1126
|
+
},
|
|
1127
|
+
"bytesReceived": {
|
|
1128
|
+
"type": "number",
|
|
1129
|
+
"description": "Bytes received so far while `uploading`, or stored once finished, when the server knows."
|
|
1130
|
+
},
|
|
1131
|
+
"size": {
|
|
1132
|
+
"type": "number",
|
|
1133
|
+
"description": "The upload's declared length, when the server knows."
|
|
1134
|
+
},
|
|
1135
|
+
"acknowledged": {
|
|
1136
|
+
"type": "boolean",
|
|
1137
|
+
"description": "Whether the host has finished handling the completed upload (its `onUploadComplete` returned)."
|
|
1138
|
+
},
|
|
1139
|
+
"webReady": {
|
|
1140
|
+
"type": "object",
|
|
1141
|
+
"required": [
|
|
1142
|
+
"action",
|
|
1143
|
+
"reason"
|
|
1144
|
+
],
|
|
1145
|
+
"description": "What the server's web-ready conversion did, once it ran (protocol 2.5): `none` (already in the served format), `remuxed`, `transcoded`, `conformed` (another container became an MP4), or `skipped` (the original bytes serve) — with a reason a person can read. Absent until it has run, and on servers that don't convert.",
|
|
1146
|
+
"properties": {
|
|
1147
|
+
"action": {
|
|
1148
|
+
"type": "string",
|
|
1149
|
+
"enum": [
|
|
1150
|
+
"none",
|
|
1151
|
+
"remuxed",
|
|
1152
|
+
"transcoded",
|
|
1153
|
+
"conformed",
|
|
1154
|
+
"skipped"
|
|
1155
|
+
]
|
|
1156
|
+
},
|
|
1157
|
+
"reason": {
|
|
1158
|
+
"type": "string"
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
},
|
|
1162
|
+
"outcome": {
|
|
1163
|
+
"description": "What the host recorded about where the upload went, or why it didn't. Any JSON; absent until the host records one."
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
"400": {
|
|
1171
|
+
"description": "`artifactId` is not a valid UUID.",
|
|
1172
|
+
"content": {
|
|
1173
|
+
"application/json": {
|
|
1174
|
+
"schema": {
|
|
1175
|
+
"description": "`artifactId` is not a valid UUID.",
|
|
1176
|
+
"type": "object",
|
|
1177
|
+
"properties": {
|
|
1178
|
+
"ok": {
|
|
1179
|
+
"type": "boolean"
|
|
1180
|
+
},
|
|
1181
|
+
"error": {
|
|
1182
|
+
"type": "string"
|
|
1183
|
+
}
|
|
1184
|
+
},
|
|
1185
|
+
"required": [
|
|
1186
|
+
"ok",
|
|
1187
|
+
"error"
|
|
1188
|
+
]
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
"403": {
|
|
1194
|
+
"description": "Authorize hook rejected the request.",
|
|
1195
|
+
"content": {
|
|
1196
|
+
"application/json": {
|
|
1197
|
+
"schema": {
|
|
1198
|
+
"description": "Authorize hook rejected the request.",
|
|
1199
|
+
"type": "object",
|
|
1200
|
+
"properties": {
|
|
1201
|
+
"ok": {
|
|
1202
|
+
"type": "boolean"
|
|
1203
|
+
},
|
|
1204
|
+
"error": {
|
|
1205
|
+
"type": "string"
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
"required": [
|
|
1209
|
+
"ok",
|
|
1210
|
+
"error"
|
|
1211
|
+
]
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
},
|
|
1219
|
+
"/pulsevault/artifacts/{artifactId}/poster": {
|
|
1220
|
+
"get": {
|
|
1221
|
+
"summary": "Serve a video's poster frame",
|
|
1222
|
+
"tags": [
|
|
1223
|
+
"pulsevault"
|
|
1224
|
+
],
|
|
1225
|
+
"description": "Streams or redirects to the finished thumbnail `relatedTo` the video, exactly as `GET /artifacts/:artifactId` would serve it. Runs the `authorize` hook with `phase: \"resolve\"` on the video, so whoever may watch it may see its poster. 404 until the poster has landed.",
|
|
1226
|
+
"parameters": [
|
|
1227
|
+
{
|
|
1228
|
+
"schema": {
|
|
1229
|
+
"type": "string"
|
|
1230
|
+
},
|
|
1231
|
+
"in": "query",
|
|
1232
|
+
"name": "token",
|
|
1233
|
+
"required": false,
|
|
1234
|
+
"description": "Optional bearer token for pre-authenticated watch links, forwarded to the `authorize` hook as `ctx.token`."
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
"schema": {
|
|
1238
|
+
"type": "string",
|
|
1239
|
+
"format": "uuid"
|
|
1240
|
+
},
|
|
1241
|
+
"in": "path",
|
|
1242
|
+
"name": "artifactId",
|
|
1243
|
+
"required": true,
|
|
1244
|
+
"description": "UUID of the video (the pulse's anchor artifact)."
|
|
1245
|
+
}
|
|
1246
|
+
],
|
|
1247
|
+
"responses": {
|
|
1248
|
+
"400": {
|
|
1249
|
+
"description": "`artifactId` is not a valid UUID.",
|
|
1250
|
+
"content": {
|
|
1251
|
+
"application/json": {
|
|
1252
|
+
"schema": {
|
|
1253
|
+
"description": "`artifactId` is not a valid UUID.",
|
|
1254
|
+
"type": "object",
|
|
1255
|
+
"properties": {
|
|
1256
|
+
"ok": {
|
|
1257
|
+
"type": "boolean"
|
|
1258
|
+
},
|
|
1259
|
+
"error": {
|
|
1260
|
+
"type": "string"
|
|
1261
|
+
}
|
|
1262
|
+
},
|
|
1263
|
+
"required": [
|
|
1264
|
+
"ok",
|
|
1265
|
+
"error"
|
|
1266
|
+
]
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1271
|
+
"403": {
|
|
1272
|
+
"description": "Authorize hook rejected the request.",
|
|
1273
|
+
"content": {
|
|
1274
|
+
"application/json": {
|
|
1275
|
+
"schema": {
|
|
1276
|
+
"description": "Authorize hook rejected the request.",
|
|
1277
|
+
"type": "object",
|
|
1278
|
+
"properties": {
|
|
1279
|
+
"ok": {
|
|
1280
|
+
"type": "boolean"
|
|
1281
|
+
},
|
|
1282
|
+
"error": {
|
|
1283
|
+
"type": "string"
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
"required": [
|
|
1287
|
+
"ok",
|
|
1288
|
+
"error"
|
|
1289
|
+
]
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
"404": {
|
|
1295
|
+
"description": "The video has no finished poster frame.",
|
|
1296
|
+
"content": {
|
|
1297
|
+
"application/json": {
|
|
1298
|
+
"schema": {
|
|
1299
|
+
"description": "The video has no finished poster frame.",
|
|
1300
|
+
"type": "object",
|
|
1301
|
+
"properties": {
|
|
1302
|
+
"ok": {
|
|
1303
|
+
"type": "boolean"
|
|
1304
|
+
},
|
|
1305
|
+
"error": {
|
|
1306
|
+
"type": "string"
|
|
1307
|
+
}
|
|
1308
|
+
},
|
|
1309
|
+
"required": [
|
|
1310
|
+
"ok",
|
|
1311
|
+
"error"
|
|
1312
|
+
]
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1050
1319
|
}
|
|
1051
1320
|
},
|
|
1052
1321
|
"tags": [
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/mieweb/pulsevault/blob/main/protocol/schemas/artifact-status.schema.json",
|
|
4
|
+
"title": "GET /artifacts/<id>/status response",
|
|
5
|
+
"description": "Where an upload is (PROTOCOL.md §6.5, protocol 2.3; `webReady` 2.5): for a page waiting on it, so the host needs no table of its own. Authorized like opening the artifact. Never cached.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["artifactId", "state"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"artifactId": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"format": "uuid",
|
|
12
|
+
"description": "The artifact asked about."
|
|
13
|
+
},
|
|
14
|
+
"state": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["unknown", "uploading", "processing", "ready"],
|
|
17
|
+
"description": "`unknown` for an id the server has never seen; `uploading` until the final byte; `processing` while the server converts the bytes for the web; `ready` once the artifact is served."
|
|
18
|
+
},
|
|
19
|
+
"kind": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"enum": ["video", "project", "captions", "thumbnail"],
|
|
22
|
+
"description": "Artifact kind. Absent when `state` is `unknown`."
|
|
23
|
+
},
|
|
24
|
+
"relatedTo": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"format": "uuid",
|
|
27
|
+
"description": "The video this artifact belongs to, if it declared one."
|
|
28
|
+
},
|
|
29
|
+
"name": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "The display name the client sent (`Upload-Metadata.name`), if any."
|
|
32
|
+
},
|
|
33
|
+
"bytesReceived": {
|
|
34
|
+
"type": "number",
|
|
35
|
+
"description": "Bytes received so far while `uploading`, or stored once finished, when the server knows."
|
|
36
|
+
},
|
|
37
|
+
"size": {
|
|
38
|
+
"type": "number",
|
|
39
|
+
"description": "The upload's declared length, when the server knows."
|
|
40
|
+
},
|
|
41
|
+
"acknowledged": {
|
|
42
|
+
"type": "boolean",
|
|
43
|
+
"description": "Whether the host has finished handling the completed upload (its `onUploadComplete` returned)."
|
|
44
|
+
},
|
|
45
|
+
"webReady": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"required": ["action", "reason"],
|
|
48
|
+
"description": "What the server's web-ready conversion did, once it ran (protocol 2.5): `none` (already in the served format), `remuxed`, `transcoded`, `conformed` (another container became an MP4), or `skipped` (the original bytes serve) — with a reason a person can read. Absent until it has run, and on servers that don't convert.",
|
|
49
|
+
"properties": {
|
|
50
|
+
"action": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"enum": ["none", "remuxed", "transcoded", "conformed", "skipped"]
|
|
53
|
+
},
|
|
54
|
+
"reason": { "type": "string" }
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"outcome": {
|
|
58
|
+
"description": "What the host recorded about where the upload went, or why it didn't. Any JSON; absent until the host records one."
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
"type": "string",
|
|
32
32
|
"enum": ["view"],
|
|
33
33
|
"description": "`view` on a read-only view token (§5.5), which only opens artifacts and is signed with a key derived from the secret. Absent on the capability token an upload uses."
|
|
34
|
+
},
|
|
35
|
+
"ctx": {
|
|
36
|
+
"description": "Opaque host data signed into the token (protocol 2.3): who the upload is for, where it goes. The server stores it with the artifact the token creates and hands it back to its own hooks; a client never reads it. Any JSON, at most 1 KiB encoded."
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://github.com/mieweb/pulsevault/blob/main/protocol/schemas/deep-link.schema.json",
|
|
4
|
-
"title": "Pairing link
|
|
5
|
-
"description": "The
|
|
4
|
+
"title": "Pairing link parameters",
|
|
5
|
+
"description": "The parameters of a pairing link (PROTOCOL.md §3), as built by `buildUploadLink`: the query string of `pulsecam://?…`, or the fragment of `https://mieweb.github.io/pulse/open#…`. Clients must ignore parameters they don't recognize.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["v", "artifactId", "server"],
|
|
8
8
|
"properties": {
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// Conform every already-uploaded video artifact in a local-storage workspace
|
|
3
|
+
// to the format PulseVault serves (`CONFORM_TARGET`). New uploads are handled
|
|
4
|
+
// by the `webReady` option; this script is the one-time backfill for
|
|
5
|
+
// artifacts that landed before it was on. Stop the server first: it caches
|
|
6
|
+
// each artifact's extension, which a container change rewrites.
|
|
6
7
|
//
|
|
7
8
|
// For each ready `kind: "video"` sidecar in `<workspaceDir>/.pulsevault/`:
|
|
8
|
-
// -
|
|
9
|
-
// -
|
|
10
|
-
// -
|
|
9
|
+
// - already in the target format → untouched
|
|
10
|
+
// - moov atom at the end of an MP4 → lossless faststart remux
|
|
11
|
+
// - off-target video or audio → one ffmpeg run (H.264/AAC, scaled, tone-mapped)
|
|
12
|
+
// - another container (WebM, MKV, MOV, …) → a new `<id>.mp4`; the sidecar
|
|
13
|
+
// switches to it and the original is deleted
|
|
11
14
|
//
|
|
12
15
|
// Rewrites are atomic (tmp file + rename), so interrupting the script never
|
|
13
16
|
// corrupts an artifact — rerun it and it picks up where it left off (already
|
|
14
|
-
// fixed files report `none`
|
|
17
|
+
// fixed files report `none`).
|
|
15
18
|
//
|
|
16
19
|
// Usage:
|
|
17
20
|
// node scripts/web-ready-migrate.mjs <workspaceDir> [--dry-run] [--no-transcode]
|
|
@@ -46,7 +49,7 @@ try {
|
|
|
46
49
|
process.exit(1);
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
const counts = { none: 0, remuxed: 0, transcoded: 0, skipped: 0, missing: 0 };
|
|
52
|
+
const counts = { none: 0, remuxed: 0, transcoded: 0, conformed: 0, skipped: 0, missing: 0 };
|
|
50
53
|
let processed = 0;
|
|
51
54
|
|
|
52
55
|
for (const sidecarFile of sidecarFiles) {
|
|
@@ -84,8 +87,29 @@ for (const sidecarFile of sidecarFiles) {
|
|
|
84
87
|
continue;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
const result = await ensureWebReady(filePath, { transcode, logger: console });
|
|
90
|
+
const { outputPath, ...result } = await ensureWebReady(filePath, { transcode, logger: console });
|
|
88
91
|
counts[result.action] += 1;
|
|
92
|
+
// Record what was done on the sidecar, as the server does; a new container also switches the
|
|
93
|
+
// artifact to its `.mp4` (keeping the uploaded extension as `sourceExt`), then the original
|
|
94
|
+
// goes. Atomic tmp + rename, like the adapter's own sidecar writes.
|
|
95
|
+
// A rerun finds a converted file already in the format: keep what the conversion recorded,
|
|
96
|
+
// and finish what an interrupted run left: the original, once the sidecar names the `.mp4`.
|
|
97
|
+
if (result.action === "none" && sidecar.webReady) {
|
|
98
|
+
if (sidecar.sourceExt && sidecar.sourceExt !== sidecar.ext) {
|
|
99
|
+
await fs.rm(path.join(root, kind, `${artifactId}${sidecar.sourceExt}`), { force: true });
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const next = { ...sidecar, converted: true, webReady: result };
|
|
104
|
+
if (outputPath) {
|
|
105
|
+
next.sourceExt = sidecar.sourceExt ?? sidecar.ext;
|
|
106
|
+
next.ext = path.extname(outputPath);
|
|
107
|
+
}
|
|
108
|
+
const sidecarPath = path.join(sidecarDir, sidecarFile);
|
|
109
|
+
const tmpPath = `${sidecarPath}.migrate.tmp`;
|
|
110
|
+
await fs.writeFile(tmpPath, JSON.stringify(next), "utf8");
|
|
111
|
+
await fs.rename(tmpPath, sidecarPath);
|
|
112
|
+
if (outputPath) await fs.rm(filePath, { force: true });
|
|
89
113
|
if (result.action !== "none") {
|
|
90
114
|
console.log(`${result.action.padEnd(11)} ${artifactId} (${result.reason})`);
|
|
91
115
|
}
|
|
@@ -96,7 +120,7 @@ if (dryRun) {
|
|
|
96
120
|
} else {
|
|
97
121
|
console.log(
|
|
98
122
|
`\nDone: ${processed} ready video artifact(s) — ` +
|
|
99
|
-
`${counts.remuxed} remuxed, ${counts.transcoded} transcoded, ` +
|
|
123
|
+
`${counts.remuxed} remuxed, ${counts.transcoded} transcoded, ${counts.conformed} conformed, ` +
|
|
100
124
|
`${counts.none} already web-ready, ${counts.skipped} skipped, ${counts.missing} missing.`,
|
|
101
125
|
);
|
|
102
126
|
}
|