@jtdigital/renderbox-sdk 0.2.0 → 0.4.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/README.md +133 -53
- package/dist/graph.d.ts +127 -15
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +178 -40
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +3 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -10
- package/dist/index.js.map +1 -1
- package/dist/recipes/depthBlurPipeline.d.ts +2 -3
- package/dist/recipes/depthBlurPipeline.d.ts.map +1 -1
- package/dist/recipes/depthBlurPipeline.js +8 -11
- package/dist/recipes/depthBlurPipeline.js.map +1 -1
- package/dist/recipes/faceRedact.d.ts +3 -5
- package/dist/recipes/faceRedact.d.ts.map +1 -1
- package/dist/recipes/faceRedact.js +15 -19
- package/dist/recipes/faceRedact.js.map +1 -1
- package/dist/recipes/index.d.ts +1 -1
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js.map +1 -1
- package/dist/recipes/letterbox.d.ts +3 -2
- package/dist/recipes/letterbox.d.ts.map +1 -1
- package/dist/recipes/letterbox.js +5 -4
- package/dist/recipes/letterbox.js.map +1 -1
- package/dist/recipes/makeSlideshow.d.ts +80 -3
- package/dist/recipes/makeSlideshow.d.ts.map +1 -1
- package/dist/recipes/makeSlideshow.js +164 -46
- package/dist/recipes/makeSlideshow.js.map +1 -1
- package/dist/recipes/snip.d.ts +3 -2
- package/dist/recipes/snip.d.ts.map +1 -1
- package/dist/recipes/snip.js +2 -3
- package/dist/recipes/snip.js.map +1 -1
- package/dist/streams.d.ts +1481 -0
- package/dist/streams.d.ts.map +1 -0
- package/dist/streams.js +241 -0
- package/dist/streams.js.map +1 -0
- package/dist/types.d.ts +0 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/input.d.ts +0 -30
- package/dist/input.d.ts.map +0 -1
- package/dist/input.js +0 -167
- package/dist/input.js.map +0 -1
- package/dist/job.d.ts +0 -18
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -15
- package/dist/job.js.map +0 -1
- package/dist/ops/ai.d.ts +0 -156
- package/dist/ops/ai.d.ts.map +0 -1
- package/dist/ops/ai.js +0 -134
- package/dist/ops/ai.js.map +0 -1
- package/dist/ops/audio.d.ts +0 -359
- package/dist/ops/audio.d.ts.map +0 -1
- package/dist/ops/audio.js +0 -296
- package/dist/ops/audio.js.map +0 -1
- package/dist/ops/combine.d.ts +0 -246
- package/dist/ops/combine.d.ts.map +0 -1
- package/dist/ops/combine.js +0 -230
- package/dist/ops/combine.js.map +0 -1
- package/dist/ops/source.d.ts +0 -41
- package/dist/ops/source.d.ts.map +0 -1
- package/dist/ops/source.js +0 -34
- package/dist/ops/source.js.map +0 -1
- package/dist/ops/video.d.ts +0 -695
- package/dist/ops/video.d.ts.map +0 -1
- package/dist/ops/video.js +0 -620
- package/dist/ops/video.js.map +0 -1
- package/dist/output.d.ts +0 -26
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -144
- package/dist/output.js.map +0 -1
- package/dist/recipes/subtitleBurn.d.ts +0 -11
- package/dist/recipes/subtitleBurn.d.ts.map +0 -1
- package/dist/recipes/subtitleBurn.js +0 -16
- package/dist/recipes/subtitleBurn.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,81 +1,161 @@
|
|
|
1
1
|
# @jtdigital/renderbox-sdk
|
|
2
2
|
|
|
3
3
|
Typed TypeScript SDK for [renderbox](https://renderbox.dev) video processing.
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
You describe a pipeline as a graph of sort-typed streams; renderbox's GPU/CPU
|
|
5
|
+
workers run it. The SDK builds the graph and the job message — it does **not**
|
|
6
|
+
process video itself.
|
|
6
7
|
|
|
7
8
|
```bash
|
|
8
9
|
npm install @jtdigital/renderbox-sdk
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
##
|
|
12
|
+
## Building a pipeline
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Open inputs, chain operations on the returned streams, attach outputs:
|
|
14
15
|
|
|
15
16
|
```ts
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
graph,
|
|
26
|
-
inputFiles: [
|
|
27
|
-
{ s3_key: 'uploads/clip.mp4', filename: 'input.mp4', content_type: 'video/mp4' },
|
|
28
|
-
],
|
|
29
|
-
output: { bucket: 'my-bucket', key: 'out/job-123.mp4', format: 'mp4' },
|
|
30
|
-
});
|
|
31
|
-
// `job` is a plain JobMessage — publish it to your renderbox engine over AMQP,
|
|
32
|
-
// then consume ProgressMessage / CompletionMessage from your result queue.
|
|
17
|
+
import { graph } from '@jtdigital/renderbox-sdk';
|
|
18
|
+
|
|
19
|
+
const g = graph();
|
|
20
|
+
const [v, a] = g.open('in.mp4'); // [VideoStream, AudioStream]
|
|
21
|
+
|
|
22
|
+
const out = v.scale({ w: 1280, h: 720 }) // ops are methods → autocomplete
|
|
23
|
+
.fade({ type: 'in', d: 1 });
|
|
24
|
+
|
|
25
|
+
g.write('out.mp4', out, a);
|
|
33
26
|
```
|
|
34
27
|
|
|
35
|
-
|
|
28
|
+
Streams are sort-typed: `VideoStream` exposes video ops, `AudioStream` audio
|
|
29
|
+
ops, `DetectionStream` tracking and filtering. A sort mismatch (piping audio
|
|
30
|
+
into a video op) is a **compile error**, not a runtime one.
|
|
31
|
+
|
|
32
|
+
### Detection → tracking → redaction
|
|
36
33
|
|
|
37
34
|
```ts
|
|
38
|
-
|
|
35
|
+
const g = graph();
|
|
36
|
+
const [v, a] = g.open('street.mp4');
|
|
37
|
+
|
|
38
|
+
const faces = v
|
|
39
|
+
.detect({ model: 'scrfd_10g', threshold: 0.12 }) // VideoStream → DetectionStream
|
|
40
|
+
.track({ algorithm: 'bytetrack' }); // stable ids across frames
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
const dets = ai.detect({ model: 'scrfd_10g' })(v); // Video → Detection
|
|
42
|
-
const redacted = combine.redact(dets, { mode: 'black' })(v);
|
|
43
|
-
const graph = sinkAv('out.mp4', video.scale({ w: 1280, h: 720 })(redacted), a);
|
|
42
|
+
g.write('redacted.mp4', v.redact(faces, { mode: 'black' }), a);
|
|
44
43
|
```
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
mismatch is a compile error rather than a runtime one.
|
|
45
|
+
### Multiple inputs
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
Combine ops are methods on the first stream; open more inputs on the same graph:
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`sinkMqtt`, `sinkHttp`, `outputSlot`
|
|
56
|
-
- **Ops** — the `video` / `audio` / `ai` / `combine` / `source` namespaces
|
|
57
|
-
(220+ typed operations)
|
|
58
|
-
- **Recipes** — `letterbox`, `snip`, `faceRedact`, `depthBlurPipeline`,
|
|
59
|
-
`makeSlideshow`
|
|
60
|
-
- **Job / wire** — `buildJob`, `JobMessage`, `ProgressMessage`,
|
|
61
|
-
`CompletionMessage`, `FailureMessage`
|
|
49
|
+
```ts
|
|
50
|
+
const g = graph();
|
|
51
|
+
const [v, a] = g.open('clip.mp4');
|
|
52
|
+
const [logo] = g.open('logo.png');
|
|
62
53
|
|
|
63
|
-
|
|
54
|
+
g.write('branded.mp4', v.overlay(logo, { x: 20, y: 20 }), a);
|
|
55
|
+
```
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
by hand. Regenerate from the renderbox-sdk repo root:
|
|
57
|
+
### Detections to a data sink
|
|
67
58
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
Not every output is a video — write structured results too:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const g = graph();
|
|
63
|
+
const [v] = g.open('traffic.mp4');
|
|
64
|
+
const cars = v.detect({ model: 'yolo11x', classes: 'car' }).track({ algorithm: 'bytetrack' });
|
|
65
|
+
g.writeDetections('cars.jsonl', cars);
|
|
71
66
|
```
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
### Recipes
|
|
69
|
+
|
|
70
|
+
One-liners for common pipelines. `faceRedact`, `depthBlurPipeline`, and
|
|
71
|
+
`makeSlideshow` return a ready `Graph`; `letterbox` and `snip` are stream
|
|
72
|
+
transforms:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { faceRedact, snip } from '@jtdigital/renderbox-sdk';
|
|
76
|
+
|
|
77
|
+
const g = faceRedact('in.mp4', 'out.mp4', { redactMode: 'blur' });
|
|
78
|
+
|
|
79
|
+
// or as a step in your own graph:
|
|
80
|
+
const [v] = graph().open('long.mp4');
|
|
81
|
+
const clip = snip(v, 30, 45); // seconds 30–45
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Running your pipeline on renderbox
|
|
85
|
+
|
|
86
|
+
A `Graph` is a *description*. To actually render it, submit it as a job to
|
|
87
|
+
renderbox infrastructure — the orchestrator compiles it and dispatches it to a
|
|
88
|
+
GPU or CPU worker, which streams back progress and a completion result.
|
|
89
|
+
|
|
90
|
+
You reference inputs and outputs **by S3 key**. Two calls give you everything the
|
|
91
|
+
worker needs: `g.json()` is the graph_ir, and `g.io()` is the S3 ⇄ storage
|
|
92
|
+
binding (which object to download for each input, where to upload each output).
|
|
93
|
+
The SDK assigns **no identity** — the job id, tenant, and user come from whoever
|
|
94
|
+
submits (your app/auth, or the managed client).
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { graph, type ProgressMessage, type CompletionMessage } from '@jtdigital/renderbox-sdk';
|
|
98
|
+
|
|
99
|
+
const g = graph();
|
|
100
|
+
const [v, a] = g.open('uploads/raw.mp4'); // reference inputs by S3 key
|
|
101
|
+
g.write('renders/out.mp4', v.scale({ w: 1920, h: 1080 }), a);
|
|
102
|
+
|
|
103
|
+
const io = g.io();
|
|
104
|
+
// io.inputs → [{ s3_key: 'uploads/raw.mp4', filename: 'raw.mp4', content_type: 'video/mp4' }]
|
|
105
|
+
// io.outputs → [{ s3_key: 'renders/out.mp4', filename: 'out.mp4' }]
|
|
106
|
+
|
|
107
|
+
// Assemble the job with identity from YOUR context, then publish over AMQP.
|
|
108
|
+
// (The message omits pipeline_id so the orchestrator routes on graph_ir.)
|
|
109
|
+
const job = {
|
|
110
|
+
job_id: myApp.newJobId(), // assigned by your app/DB — not the SDK
|
|
111
|
+
tenant_id: ctx.tenantId, // from auth
|
|
112
|
+
graph_ir: g.json(),
|
|
113
|
+
input_files: io.inputs,
|
|
114
|
+
};
|
|
115
|
+
await channel.publish(EXCHANGE, ROUTING_KEY, Buffer.from(JSON.stringify(job)));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
You never write filenames by hand: `open('uploads/raw.mp4')` gives the graph a
|
|
119
|
+
sandbox-safe basename (`raw.mp4`) and records the S3 key for you — the worker
|
|
120
|
+
rejects slashes in the sandbox, which is why the graph and the binding differ.
|
|
121
|
+
|
|
122
|
+
Then consume results off your result queue. Progress arrives as the render
|
|
123
|
+
proceeds; completion carries the output location(s):
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
function onProgress(msg: ProgressMessage) {
|
|
127
|
+
// percent is null until it can be computed (null ≠ 0%)
|
|
128
|
+
console.log(`${msg.job_id}: ${msg.percent ?? '—'}% @ ${msg.fps} fps`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function onCompleted(msg: CompletionMessage) {
|
|
132
|
+
if (msg.status === 'success') {
|
|
133
|
+
for (const out of msg.outputs) console.log(out.kind, out.s3_key, out.bytes);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Prefer a managed client?
|
|
139
|
+
|
|
140
|
+
[`@jtdigital/renderbox-client`](https://renderbox.dev/sdk/typescript) wraps the
|
|
141
|
+
renderbox SaaS HTTP API — API-key auth, asset upload, job submission, SSE
|
|
142
|
+
progress streaming, and result download — so you can hand it a `Graph` and get a
|
|
143
|
+
finished render back without running your own transport.
|
|
144
|
+
|
|
145
|
+
## Surface
|
|
146
|
+
|
|
147
|
+
- **`graph()` → `Graph`** — inputs: `open` / `openVideo` / `openAudio` / `rtsp` /
|
|
148
|
+
`hls` / `mqtt` / `camera` / `screenCapture` / `slot`, plus synthetic sources.
|
|
149
|
+
Outputs: `write` / `writeVideo` / `writeAudio` / `writeDetections` /
|
|
150
|
+
`writeTranscript` / `writeMqtt` / `writeHttp` / `outputSlot`. Terminals:
|
|
151
|
+
`json()` (graph_ir) and `io()` (S3 ⇄ sandbox bindings).
|
|
152
|
+
- **Stream methods** — 220+ typed operations, one per engine op, on the sort it
|
|
153
|
+
applies to (`scale`, `crop`, `fade`, `detect`, `track`, `transcribe`,
|
|
154
|
+
`overlay`, `redact`, `depthBlur`, …).
|
|
155
|
+
- **Recipes** — `letterbox`, `snip`, `faceRedact`, `depthBlurPipeline`,
|
|
156
|
+
`makeSlideshow`.
|
|
157
|
+
- **Wire types** — `GraphJSON`, `OpNode`, `JobMessage`, `ProgressMessage`,
|
|
158
|
+
`CompletionMessage`, `FailureMessage`.
|
|
79
159
|
|
|
80
160
|
## License
|
|
81
161
|
|
package/dist/graph.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ParamValue } from './types.js';
|
|
2
|
+
import type { VideoStream, AudioStream, DetectionStream, TranscriptStream } from './streams.js';
|
|
3
|
+
import type { InputFile } from './protocol.js';
|
|
2
4
|
export type SortJSON = string | {
|
|
3
5
|
Product: [SortJSON, SortJSON];
|
|
4
6
|
};
|
|
@@ -170,25 +172,135 @@ export interface GraphJSON {
|
|
|
170
172
|
roots: number[];
|
|
171
173
|
io_manifest?: IoManifest;
|
|
172
174
|
}
|
|
173
|
-
|
|
175
|
+
/** One file output the graph writes. `s3_key` is where you asked it stored;
|
|
176
|
+
* `filename` is the sandbox-local name the worker writes before uploading. */
|
|
177
|
+
export interface OutputBinding {
|
|
178
|
+
s3_key: string;
|
|
179
|
+
filename: string;
|
|
180
|
+
}
|
|
181
|
+
/** The S3 ⇄ sandbox mapping the submission layer needs: `inputs` drop straight
|
|
182
|
+
* into a job's `input_files`; each `output` names where a sink should land. */
|
|
183
|
+
export interface GraphIo {
|
|
184
|
+
inputs: InputFile[];
|
|
185
|
+
outputs: OutputBinding[];
|
|
186
|
+
}
|
|
187
|
+
export interface SinkAvOpts {
|
|
188
|
+
codec?: string;
|
|
189
|
+
crf?: number;
|
|
190
|
+
bitrate?: string;
|
|
191
|
+
preset?: string;
|
|
192
|
+
[key: string]: ParamValue | undefined;
|
|
193
|
+
}
|
|
194
|
+
export interface FileOpts {
|
|
195
|
+
loop?: number;
|
|
196
|
+
t?: number;
|
|
197
|
+
[key: string]: ParamValue | undefined;
|
|
198
|
+
}
|
|
199
|
+
export interface AmovieOpts {
|
|
200
|
+
filename: string;
|
|
201
|
+
seek_point?: number;
|
|
202
|
+
streams?: string;
|
|
203
|
+
loop?: number;
|
|
204
|
+
}
|
|
205
|
+
export interface AnullsrcOpts {
|
|
206
|
+
d?: number;
|
|
207
|
+
}
|
|
208
|
+
export interface BlankOpts {
|
|
209
|
+
width?: number;
|
|
210
|
+
height?: number;
|
|
211
|
+
duration?: number;
|
|
212
|
+
color?: string;
|
|
213
|
+
}
|
|
214
|
+
export interface MovieOpts {
|
|
215
|
+
filename: string;
|
|
216
|
+
seek_point?: number;
|
|
217
|
+
streams?: string;
|
|
218
|
+
loop?: number;
|
|
219
|
+
}
|
|
220
|
+
export interface SineOpts {
|
|
221
|
+
frequency?: number;
|
|
222
|
+
duration?: number;
|
|
223
|
+
}
|
|
224
|
+
export interface TestPatternOpts {
|
|
225
|
+
width?: number;
|
|
226
|
+
height?: number;
|
|
227
|
+
duration?: number;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* A pipeline under construction. Open inputs by S3 key, chain ops on the
|
|
231
|
+
* returned streams, attach outputs, then read json() (graph_ir) and io()
|
|
232
|
+
* (the S3 ⇄ sandbox binding) and hand both to your submission layer.
|
|
233
|
+
*/
|
|
234
|
+
export declare class Graph {
|
|
174
235
|
/** @internal */ readonly _nodes: OpNode[];
|
|
175
236
|
/** @internal */ readonly _roots: number[];
|
|
176
|
-
/** @internal */ readonly
|
|
237
|
+
/** @internal */ readonly _slots: IoSlotDecl[];
|
|
238
|
+
/** @internal */ readonly _inputs: InputFile[];
|
|
239
|
+
/** @internal */ readonly _outputs: OutputBinding[];
|
|
240
|
+
/** @internal */ readonly _names: Set<string>;
|
|
241
|
+
/** @internal — S3 key → a unique sandbox basename. The worker joins this onto
|
|
242
|
+
* the work dir and rejects slashes / `..`, so keys are reduced to a basename;
|
|
243
|
+
* collisions get a numeric prefix. */
|
|
244
|
+
_basename(s3Key: string): string;
|
|
177
245
|
/** @internal */
|
|
178
|
-
|
|
246
|
+
_contentType(filename: string): string;
|
|
179
247
|
/** @internal */
|
|
180
|
-
|
|
248
|
+
_addNode(node: OpNode): number;
|
|
181
249
|
/** @internal */
|
|
182
250
|
_addSlot(slot: IoSlotDecl): void;
|
|
183
|
-
|
|
251
|
+
/** @internal */
|
|
252
|
+
_source(origin: Origin, sort: SortJSON, decoding: Decoding): number;
|
|
253
|
+
/**
|
|
254
|
+
* Open an S3 object as [video, audio]. Reference inputs by S3 key — the SDK
|
|
255
|
+
* records the input binding and gives the graph a sandbox-safe name, so
|
|
256
|
+
* `io().inputs` drops straight into a job's `input_files`.
|
|
257
|
+
*/
|
|
258
|
+
open(s3Key: string, opts?: FileOpts): [VideoStream, AudioStream];
|
|
259
|
+
openVideo(s3Key: string): VideoStream;
|
|
260
|
+
openAudio(s3Key: string): AudioStream;
|
|
261
|
+
rtsp(url: string): [VideoStream, AudioStream];
|
|
262
|
+
hls(url: string): [VideoStream, AudioStream];
|
|
263
|
+
camera(device: number): [VideoStream, AudioStream];
|
|
264
|
+
screenCapture(): [VideoStream, AudioStream];
|
|
265
|
+
/** MQTT detection source (JSONL). */
|
|
266
|
+
mqtt(broker: string, topic: string): DetectionStream;
|
|
267
|
+
slot(label: string): [VideoStream, AudioStream];
|
|
268
|
+
slotVideo(label: string): VideoStream;
|
|
269
|
+
slotAudio(label: string): AudioStream;
|
|
270
|
+
aevalsrc(): AudioStream;
|
|
271
|
+
amovie(opts: AmovieOpts): AudioStream;
|
|
272
|
+
anoisesrc(): AudioStream;
|
|
273
|
+
anullsrc(opts?: AnullsrcOpts): AudioStream;
|
|
274
|
+
blank(opts?: BlankOpts): VideoStream;
|
|
275
|
+
movie(opts: MovieOpts): VideoStream;
|
|
276
|
+
sine(opts?: SineOpts): AudioStream;
|
|
277
|
+
testPattern(opts?: TestPatternOpts): VideoStream;
|
|
278
|
+
/** @internal */
|
|
279
|
+
_sink(inputs: number[], sorts: SortJSON[], target: SinkTarget, encoding: SinkEncoding, opts: Record<string, ParamValue>): void;
|
|
280
|
+
/** @internal — a file sink written to an S3 key (records the output binding). */
|
|
281
|
+
_fileSink(s3Key: string, inputs: number[], sorts: SortJSON[], encoding: SinkEncoding, opts: Record<string, ParamValue>): void;
|
|
282
|
+
/** Muxed video+audio, written to an S3 key. */
|
|
283
|
+
write(s3Key: string, video: VideoStream, audio: AudioStream, opts?: SinkAvOpts): this;
|
|
284
|
+
writeVideo(s3Key: string, video: VideoStream, opts?: SinkAvOpts): this;
|
|
285
|
+
writeAudio(s3Key: string, audio: AudioStream): this;
|
|
286
|
+
writeDetections(s3Key: string, detections: DetectionStream): this;
|
|
287
|
+
writeTranscript(s3Key: string, transcript: TranscriptStream, format?: 'srt' | 'vtt'): this;
|
|
288
|
+
writeMqtt(broker: string, topic: string, detections: DetectionStream, opts?: {
|
|
289
|
+
qos?: number;
|
|
290
|
+
}): this;
|
|
291
|
+
writeHttp(url: string, detections: DetectionStream, opts?: {
|
|
292
|
+
method?: string;
|
|
293
|
+
}): this;
|
|
294
|
+
outputSlot(label: string, video: VideoStream, audio: AudioStream, opts?: SinkAvOpts): this;
|
|
295
|
+
outputSlotVideo(label: string, video: VideoStream): this;
|
|
296
|
+
/** The graph_ir (renderbox-ir Graph serde shape) — submit this as a job's
|
|
297
|
+
* `graph_ir`. */
|
|
298
|
+
json(): GraphJSON;
|
|
299
|
+
/** The S3 ⇄ sandbox mapping the submission layer needs: `inputs` go into the
|
|
300
|
+
* job's `input_files`; pair each `output` with a destination. Identity
|
|
301
|
+
* (job id, tenant, user) is assigned by whoever submits — not here. */
|
|
302
|
+
io(): GraphIo;
|
|
184
303
|
}
|
|
185
|
-
/**
|
|
186
|
-
export declare function
|
|
187
|
-
/** @internal */
|
|
188
|
-
export declare function _id(s: Stream<SortTag>): number;
|
|
189
|
-
/** @internal */
|
|
190
|
-
export declare function _g(s: Stream<SortTag>): GraphBuilder;
|
|
191
|
-
/** @internal */
|
|
192
|
-
export declare function _p(opts: object): Record<string, ParamValue>;
|
|
193
|
-
export declare function pipe<S extends SortTag>(...fns: Array<(s: Stream<S>) => Stream<S>>): (input: Stream<S>) => Stream<S>;
|
|
304
|
+
/** Start a new pipeline. */
|
|
305
|
+
export declare function graph(): Graph;
|
|
194
306
|
//# sourceMappingURL=graph.d.ts.map
|
package/dist/graph.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
|
package/dist/graph.js
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { mk, _p } from './streams.js';
|
|
2
|
+
const AV = { Product: ['Video', 'Audio'] };
|
|
3
|
+
/**
|
|
4
|
+
* A pipeline under construction. Open inputs by S3 key, chain ops on the
|
|
5
|
+
* returned streams, attach outputs, then read json() (graph_ir) and io()
|
|
6
|
+
* (the S3 ⇄ sandbox binding) and hand both to your submission layer.
|
|
7
|
+
*/
|
|
8
|
+
export class Graph {
|
|
3
9
|
/** @internal */ _nodes = [];
|
|
4
10
|
/** @internal */ _roots = [];
|
|
5
|
-
/** @internal */
|
|
11
|
+
/** @internal */ _slots = [];
|
|
12
|
+
/** @internal */ _inputs = [];
|
|
13
|
+
/** @internal */ _outputs = [];
|
|
14
|
+
/** @internal */ _names = new Set();
|
|
15
|
+
/** @internal — S3 key → a unique sandbox basename. The worker joins this onto
|
|
16
|
+
* the work dir and rejects slashes / `..`, so keys are reduced to a basename;
|
|
17
|
+
* collisions get a numeric prefix. */
|
|
18
|
+
_basename(s3Key) {
|
|
19
|
+
const base = (s3Key.split('/').pop() || s3Key).replace(/[/\\]/g, '_') || 'file';
|
|
20
|
+
let name = base;
|
|
21
|
+
for (let i = 1; this._names.has(name); i++)
|
|
22
|
+
name = `${i}_${base}`;
|
|
23
|
+
this._names.add(name);
|
|
24
|
+
return name;
|
|
25
|
+
}
|
|
26
|
+
/** @internal */
|
|
27
|
+
_contentType(filename) {
|
|
28
|
+
const ext = filename.includes('.') ? filename.split('.').pop().toLowerCase() : '';
|
|
29
|
+
const map = {
|
|
30
|
+
mp4: 'video/mp4', mov: 'video/quicktime', webm: 'video/webm', mkv: 'video/x-matroska',
|
|
31
|
+
mp3: 'audio/mpeg', wav: 'audio/wav', aac: 'audio/aac', m4a: 'audio/mp4', flac: 'audio/flac',
|
|
32
|
+
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', webp: 'image/webp', gif: 'image/gif',
|
|
33
|
+
srt: 'application/x-subrip', vtt: 'text/vtt',
|
|
34
|
+
jsonl: 'application/x-ndjson', json: 'application/json',
|
|
35
|
+
};
|
|
36
|
+
return map[ext] ?? 'application/octet-stream';
|
|
37
|
+
}
|
|
6
38
|
/** @internal */
|
|
7
39
|
_addNode(node) {
|
|
8
40
|
const id = this._nodes.length;
|
|
@@ -10,53 +42,159 @@ export class GraphBuilder {
|
|
|
10
42
|
return id;
|
|
11
43
|
}
|
|
12
44
|
/** @internal */
|
|
13
|
-
_addRoot(id) {
|
|
14
|
-
this._roots.push(id);
|
|
15
|
-
}
|
|
16
|
-
/** @internal */
|
|
17
45
|
_addSlot(slot) {
|
|
18
|
-
const dup = this.
|
|
46
|
+
const dup = this._slots.some((s) => s.label === slot.label && s.direction === slot.direction);
|
|
19
47
|
if (dup) {
|
|
20
|
-
// Duplicate input labels share one binding (bind() rewrites every
|
|
21
|
-
// matching source); duplicate output labels are ambiguous.
|
|
22
48
|
if (slot.direction === 'Output') {
|
|
23
49
|
throw new Error(`duplicate output slot label: ${slot.label}`);
|
|
24
50
|
}
|
|
25
|
-
return;
|
|
51
|
+
return; // duplicate input labels share one binding
|
|
26
52
|
}
|
|
27
|
-
this.
|
|
53
|
+
this._slots.push(slot);
|
|
54
|
+
}
|
|
55
|
+
/** @internal */
|
|
56
|
+
_source(origin, sort, decoding) {
|
|
57
|
+
return this._addNode({ kind: 'Source', origin, sort, decoding, opts: {} });
|
|
58
|
+
}
|
|
59
|
+
// --- Inputs ---------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Open an S3 object as [video, audio]. Reference inputs by S3 key — the SDK
|
|
62
|
+
* records the input binding and gives the graph a sandbox-safe name, so
|
|
63
|
+
* `io().inputs` drops straight into a job's `input_files`.
|
|
64
|
+
*/
|
|
65
|
+
open(s3Key, opts) {
|
|
66
|
+
const filename = this._basename(s3Key);
|
|
67
|
+
this._inputs.push({ s3_key: s3Key, filename, content_type: this._contentType(filename) });
|
|
68
|
+
const id = this._addNode({
|
|
69
|
+
kind: 'Source',
|
|
70
|
+
origin: { File: { path: filename } },
|
|
71
|
+
sort: AV,
|
|
72
|
+
decoding: 'Passthrough',
|
|
73
|
+
opts: opts ? _p(opts) : {},
|
|
74
|
+
});
|
|
75
|
+
return [mk('Video', id, this), mk('Audio', id, this)];
|
|
76
|
+
}
|
|
77
|
+
openVideo(s3Key) {
|
|
78
|
+
return this.open(s3Key)[0];
|
|
28
79
|
}
|
|
29
|
-
|
|
80
|
+
openAudio(s3Key) {
|
|
81
|
+
return this.open(s3Key)[1];
|
|
82
|
+
}
|
|
83
|
+
rtsp(url) {
|
|
84
|
+
const id = this._source({ Rtsp: { url, transport: null } }, AV, 'Passthrough');
|
|
85
|
+
return [mk('Video', id, this), mk('Audio', id, this)];
|
|
86
|
+
}
|
|
87
|
+
hls(url) {
|
|
88
|
+
const id = this._source({ Hls: { url } }, AV, 'Passthrough');
|
|
89
|
+
return [mk('Video', id, this), mk('Audio', id, this)];
|
|
90
|
+
}
|
|
91
|
+
camera(device) {
|
|
92
|
+
const id = this._source({ Camera: { device: String(device) } }, AV, 'Passthrough');
|
|
93
|
+
return [mk('Video', id, this), mk('Audio', id, this)];
|
|
94
|
+
}
|
|
95
|
+
screenCapture() {
|
|
96
|
+
const id = this._source({ ScreenCapture: { display: null } }, AV, 'Passthrough');
|
|
97
|
+
return [mk('Video', id, this), mk('Audio', id, this)];
|
|
98
|
+
}
|
|
99
|
+
/** MQTT detection source (JSONL). */
|
|
100
|
+
mqtt(broker, topic) {
|
|
101
|
+
const id = this._source({ Mqtt: { broker, topic, qos: null } }, 'Detection', 'JsonlDetection');
|
|
102
|
+
return mk('Detection', id, this);
|
|
103
|
+
}
|
|
104
|
+
slot(label) {
|
|
105
|
+
const id = this._source({ Slot: { label } }, AV, 'Passthrough');
|
|
106
|
+
this._addSlot({ label, node_ids: [[id, 'Video'], [id, 'Audio']], direction: 'Input', optional: false });
|
|
107
|
+
return [mk('Video', id, this), mk('Audio', id, this)];
|
|
108
|
+
}
|
|
109
|
+
slotVideo(label) {
|
|
110
|
+
const id = this._source({ Slot: { label } }, 'Video', 'Passthrough');
|
|
111
|
+
this._addSlot({ label, node_ids: [[id, 'Video']], direction: 'Input', optional: false });
|
|
112
|
+
return mk('Video', id, this);
|
|
113
|
+
}
|
|
114
|
+
slotAudio(label) {
|
|
115
|
+
const id = this._source({ Slot: { label } }, 'Audio', 'Passthrough');
|
|
116
|
+
this._addSlot({ label, node_ids: [[id, 'Audio']], direction: 'Input', optional: false });
|
|
117
|
+
return mk('Audio', id, this);
|
|
118
|
+
}
|
|
119
|
+
aevalsrc() { const id = this._addNode({ kind: 'Source', origin: { Synthetic: { name: 'aevalsrc', params: {} } }, sort: 'Audio', decoding: 'Passthrough', opts: {} }); return mk('Audio', id, this); }
|
|
120
|
+
amovie(opts) { const id = this._addNode({ kind: 'Source', origin: { Synthetic: { name: 'amovie', params: _p(opts) } }, sort: 'Audio', decoding: 'Passthrough', opts: {} }); return mk('Audio', id, this); }
|
|
121
|
+
anoisesrc() { const id = this._addNode({ kind: 'Source', origin: { Synthetic: { name: 'anoisesrc', params: {} } }, sort: 'Audio', decoding: 'Passthrough', opts: {} }); return mk('Audio', id, this); }
|
|
122
|
+
anullsrc(opts) { const id = this._addNode({ kind: 'Source', origin: { Synthetic: { name: 'anullsrc', params: opts ? _p(opts) : {} } }, sort: 'Audio', decoding: 'Passthrough', opts: {} }); return mk('Audio', id, this); }
|
|
123
|
+
blank(opts) { const id = this._addNode({ kind: 'Source', origin: { Synthetic: { name: 'blank', params: opts ? _p(opts) : {} } }, sort: 'Video', decoding: 'Passthrough', opts: {} }); return mk('Video', id, this); }
|
|
124
|
+
movie(opts) { const id = this._addNode({ kind: 'Source', origin: { Synthetic: { name: 'movie', params: _p(opts) } }, sort: 'Video', decoding: 'Passthrough', opts: {} }); return mk('Video', id, this); }
|
|
125
|
+
sine(opts) { const id = this._addNode({ kind: 'Source', origin: { Synthetic: { name: 'sine', params: opts ? _p(opts) : {} } }, sort: 'Audio', decoding: 'Passthrough', opts: {} }); return mk('Audio', id, this); }
|
|
126
|
+
testPattern(opts) { const id = this._addNode({ kind: 'Source', origin: { Synthetic: { name: 'test_pattern', params: opts ? _p(opts) : {} } }, sort: 'Video', decoding: 'Passthrough', opts: {} }); return mk('Video', id, this); }
|
|
127
|
+
// --- Outputs --------------------------------------------------------------
|
|
128
|
+
/** @internal */
|
|
129
|
+
_sink(inputs, sorts, target, encoding, opts) {
|
|
130
|
+
const id = this._addNode({ kind: 'Sink', inputs, input_sorts: sorts, target, encoding, opts });
|
|
131
|
+
this._roots.push(id);
|
|
132
|
+
}
|
|
133
|
+
/** @internal — a file sink written to an S3 key (records the output binding). */
|
|
134
|
+
_fileSink(s3Key, inputs, sorts, encoding, opts) {
|
|
135
|
+
const filename = this._basename(s3Key);
|
|
136
|
+
this._outputs.push({ s3_key: s3Key, filename });
|
|
137
|
+
this._sink(inputs, sorts, { File: { path: filename } }, encoding, opts);
|
|
138
|
+
}
|
|
139
|
+
/** Muxed video+audio, written to an S3 key. */
|
|
140
|
+
write(s3Key, video, audio, opts) {
|
|
141
|
+
this._fileSink(s3Key, [video._id, audio._id], ['Video', 'Audio'], 'Container', opts ? _p(opts) : {});
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
writeVideo(s3Key, video, opts) {
|
|
145
|
+
this._fileSink(s3Key, [video._id], ['Video'], 'Container', opts ? _p(opts) : {});
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
writeAudio(s3Key, audio) {
|
|
149
|
+
this._fileSink(s3Key, [audio._id], ['Audio'], 'Container', {});
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
writeDetections(s3Key, detections) {
|
|
153
|
+
this._fileSink(s3Key, [detections._id], ['Detection'], 'Jsonl', {});
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
writeTranscript(s3Key, transcript, format = 'srt') {
|
|
157
|
+
this._fileSink(s3Key, [transcript._id], ['Transcript'], format === 'vtt' ? 'Vtt' : 'Srt', {});
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
writeMqtt(broker, topic, detections, opts) {
|
|
161
|
+
this._sink([detections._id], ['Detection'], { Mqtt: { broker, topic, qos: opts?.qos ?? null } }, 'Jsonl', {});
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
writeHttp(url, detections, opts) {
|
|
165
|
+
this._sink([detections._id], ['Detection'], { Http: { url, method: opts?.method ?? null } }, 'Jsonl', {});
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
outputSlot(label, video, audio, opts) {
|
|
169
|
+
const id = this._addNode({ kind: 'Sink', inputs: [video._id, audio._id], input_sorts: ['Video', 'Audio'], target: { Slot: { label } }, encoding: 'Container', opts: opts ? _p(opts) : {} });
|
|
170
|
+
this._roots.push(id);
|
|
171
|
+
this._addSlot({ label, node_ids: [[id, 'Video'], [id, 'Audio']], direction: 'Output', optional: false });
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
outputSlotVideo(label, video) {
|
|
175
|
+
const id = this._addNode({ kind: 'Sink', inputs: [video._id], input_sorts: ['Video'], target: { Slot: { label } }, encoding: 'Container', opts: {} });
|
|
176
|
+
this._roots.push(id);
|
|
177
|
+
this._addSlot({ label, node_ids: [[id, 'Video']], direction: 'Output', optional: false });
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
// --- Terminals ------------------------------------------------------------
|
|
181
|
+
/** The graph_ir (renderbox-ir Graph serde shape) — submit this as a job's
|
|
182
|
+
* `graph_ir`. */
|
|
183
|
+
json() {
|
|
30
184
|
const g = { arena: this._nodes, roots: this._roots };
|
|
31
|
-
if (this.
|
|
32
|
-
g.io_manifest = { slots: this.
|
|
185
|
+
if (this._slots.length > 0)
|
|
186
|
+
g.io_manifest = { slots: this._slots };
|
|
33
187
|
return g;
|
|
34
188
|
}
|
|
189
|
+
/** The S3 ⇄ sandbox mapping the submission layer needs: `inputs` go into the
|
|
190
|
+
* job's `input_files`; pair each `output` with a destination. Identity
|
|
191
|
+
* (job id, tenant, user) is assigned by whoever submits — not here. */
|
|
192
|
+
io() {
|
|
193
|
+
return { inputs: [...this._inputs], outputs: [...this._outputs] };
|
|
194
|
+
}
|
|
35
195
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return { _id: id, _graph: g, _sort: sort };
|
|
40
|
-
}
|
|
41
|
-
/** @internal */
|
|
42
|
-
export function _id(s) {
|
|
43
|
-
return s._id;
|
|
44
|
-
}
|
|
45
|
-
/** @internal */
|
|
46
|
-
export function _g(s) {
|
|
47
|
-
return s._graph;
|
|
48
|
-
}
|
|
49
|
-
/** @internal */
|
|
50
|
-
export function _p(opts) {
|
|
51
|
-
const out = {};
|
|
52
|
-
for (const [k, v] of Object.entries(opts)) {
|
|
53
|
-
if (v !== undefined)
|
|
54
|
-
out[k] = v;
|
|
55
|
-
}
|
|
56
|
-
return out;
|
|
57
|
-
}
|
|
58
|
-
// --- Composition ---
|
|
59
|
-
export function pipe(...fns) {
|
|
60
|
-
return (input) => fns.reduce((s, fn) => fn(s), input);
|
|
196
|
+
/** Start a new pipeline. */
|
|
197
|
+
export function graph() {
|
|
198
|
+
return new Graph();
|
|
61
199
|
}
|
|
62
200
|
//# sourceMappingURL=graph.js.map
|