@maravilla-labs/platform 0.12.0 → 0.14.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.
@@ -32,6 +32,15 @@ interface TranscodeOpts {
32
32
  max_height?: number;
33
33
  audio_codec?: string;
34
34
  bitrate_kbps?: number;
35
+ /**
36
+ * Force a constant output frame rate (fps). Pass this for variable-frame-rate
37
+ * sources (e.g. MediaRecorder WebM) so the MP4 stays frame-accurately
38
+ * seekable for the frames pipeline — without it, a near-static VFR segment
39
+ * can collapse to a handful of frames that a fixed-rate renderer stalls on.
40
+ * When omitted, the worker auto-applies CFR only if it probes the source as
41
+ * VFR; a determinate-rate source is left untouched.
42
+ */
43
+ fps?: number;
35
44
  }
36
45
  /** Options for `transforms.thumbnail` — extract a single video frame. */
37
46
  interface ThumbnailOpts {
@@ -43,6 +52,80 @@ interface ThumbnailOpts {
43
52
  format?: ImageFormat;
44
53
  quality?: number;
45
54
  }
55
+ /**
56
+ * Options for `transforms.extractFrames` — decode a video into a sequence of
57
+ * still images at a fixed fps. General capability (any MP4 → frames); the
58
+ * frames renderer consumes the result to swap `<video>`→`<img>` per frame, and
59
+ * it's reusable for thumbnails/scrubbing.
60
+ */
61
+ interface ExtractFramesOpts {
62
+ /** Frames per second to sample the source at. */
63
+ fps: number;
64
+ /** Optional max width (height auto, aspect preserved, no upscaling). */
65
+ width?: number;
66
+ /** Output image format. Defaults to `"jpg"` server-side when omitted. */
67
+ format?: ImageFormat;
68
+ /** JPEG/WebP quality 1..=100 (higher = better). Ignored for PNG. */
69
+ quality?: number;
70
+ /** Start offset into the clip, ms (default 0). */
71
+ seek_ms?: number;
72
+ /** How much of the clip to extract from `seek_ms`, ms (default: to end). */
73
+ duration_ms?: number;
74
+ /**
75
+ * Also extract the source's audio track to `detach_audio.target` in the SAME
76
+ * job — one call yields both the frame set and a detachable audio asset
77
+ * (folds the upload-time "detach audio" step into frame extraction). The
78
+ * frame-set manifest is still the job's primary output; the audio is written
79
+ * to the caller-chosen `target` key.
80
+ */
81
+ detach_audio?: DetachAudioOpts;
82
+ }
83
+ /**
84
+ * Bundle a detached-audio side-output into an {@link ExtractFramesOpts} job.
85
+ * The worker runs one extra audio-extraction pass on the same source and writes
86
+ * the AAC/m4a result to `target` (a caller-chosen, tenant-scoped storage key).
87
+ */
88
+ interface DetachAudioOpts {
89
+ /** Storage key to write the extracted audio (AAC/m4a) to. */
90
+ target: string;
91
+ /** Output bitrate, kbps. Server picks a sensible AAC bitrate when omitted. */
92
+ bitrate_kbps?: number;
93
+ /** Start offset into the source, ms (default 0). */
94
+ seek_ms?: number;
95
+ /** How much of the source to extract from `seek_ms`, ms (default: to end). */
96
+ duration_ms?: number;
97
+ }
98
+ /**
99
+ * Options for `transforms.extractAudio` — split a video's audio track off into
100
+ * a standalone AAC/m4a asset. Used to detach the mic/system audio recorded
101
+ * alongside a camera/screen clip so it can be placed + ducked independently on
102
+ * the frames timeline.
103
+ */
104
+ interface ExtractAudioOpts {
105
+ /** Output bitrate, kbps. Server picks a sensible AAC bitrate when omitted. */
106
+ bitrate_kbps?: number;
107
+ /** Start offset into the source, ms (default 0). */
108
+ seek_ms?: number;
109
+ /** How much of the source to extract from `seek_ms`, ms (default: to end). */
110
+ duration_ms?: number;
111
+ }
112
+ /**
113
+ * Result of an `extractFrames` job: a JSON manifest stored at the job's
114
+ * `output_key` describing the frame set. Frame `i` (0-based) lives at
115
+ * `${prefix}/frame_${i padded to 6}.${ext}`.
116
+ */
117
+ interface FramesetManifest {
118
+ /** Storage key prefix the frame images live under (no trailing slash). */
119
+ prefix: string;
120
+ /** Number of frames written. */
121
+ count: number;
122
+ /** Sampling fps the frames were extracted at. */
123
+ fps: number;
124
+ /** Frame image format. */
125
+ format: ImageFormat;
126
+ width?: number;
127
+ height?: number;
128
+ }
46
129
  /** Options for `transforms.resize`. */
47
130
  interface ResizeOpts {
48
131
  width?: number;
@@ -174,6 +257,10 @@ type TransformSpec = ({
174
257
  } & TranscodeOpts) | ({
175
258
  kind: 'thumbnail';
176
259
  } & ThumbnailOpts) | ({
260
+ kind: 'extract_frames';
261
+ } & ExtractFramesOpts) | ({
262
+ kind: 'extract_audio';
263
+ } & ExtractAudioOpts) | ({
177
264
  kind: 'resize';
178
265
  } & ResizeOpts) | ({
179
266
  kind: 'ocr';
@@ -219,6 +306,17 @@ interface JobStatusResponse {
219
306
  interface TransformsService {
220
307
  transcode(srcKey: string, opts: TranscodeOpts): Promise<JobHandle>;
221
308
  thumbnail(srcKey: string, opts: ThumbnailOpts): Promise<JobHandle>;
309
+ /**
310
+ * Decode a video into a sequence of frame images at a fixed fps. Resolves to
311
+ * a `JobHandle` whose `output_key` holds a `FramesetManifest` (JSON) once
312
+ * complete; frames live under `${manifest.prefix}/frame_NNNNNN.<ext>`.
313
+ */
314
+ extractFrames(srcKey: string, opts: ExtractFramesOpts): Promise<JobHandle>;
315
+ /**
316
+ * Extract a video's audio track to a standalone AAC/m4a asset. Resolves to a
317
+ * `JobHandle` whose `output_key` holds the audio file once complete.
318
+ */
319
+ extractAudio(srcKey: string, opts?: ExtractAudioOpts | null): Promise<JobHandle>;
222
320
  resize(srcKey: string, opts: ResizeOpts): Promise<JobHandle>;
223
321
  probe(srcKey: string): Promise<MediaInfo>;
224
322
  ocr(srcKey: string, opts?: OcrOpts | null): Promise<JobHandle>;
@@ -298,4 +396,4 @@ interface TransformsPatternSpec {
298
396
  */
299
397
  type TransformsConfig = Record<string, TransformsPatternSpec>;
300
398
 
301
- export { type DocFormat as D, type ImageFormat as I, type JobStatus as J, type MediaInfo as M, type OcrOpts as O, type QrCodeSpec as Q, type ResizeOpts as R, type TransformsConfig as T, type VideoFormat as V, type TransformsPatternSpec as a, type TranscodeOpts as b, type ThumbnailOpts as c, type DocToPdfOpts as d, type DocThumbnailOpts as e, type DocConvertOpts as f, type DocToMarkdownOpts as g, type DocToHtmlOpts as h, type ImageRef as i, type DocReplaceImagesOpts as j, type DocInsertQrCodeOpts as k, type QrPayload as l, type DocTemplateMergeOpts as m, type TransformSpec as n, type JobHandle as o, type JobStatusResponse as p, type TransformsService as q, keyFor as r, transforms as t };
399
+ export { type DetachAudioOpts as D, type ExtractFramesOpts as E, type FramesetManifest as F, type ImageFormat as I, type JobStatus as J, type MediaInfo as M, type OcrOpts as O, type QrCodeSpec as Q, type ResizeOpts as R, type TransformsConfig as T, type VideoFormat as V, type TransformsPatternSpec as a, type TranscodeOpts as b, type ThumbnailOpts as c, type ExtractAudioOpts as d, type DocFormat as e, type DocToPdfOpts as f, type DocThumbnailOpts as g, type DocConvertOpts as h, type DocToMarkdownOpts as i, type DocToHtmlOpts as j, type ImageRef as k, type DocReplaceImagesOpts as l, type DocInsertQrCodeOpts as m, type QrPayload as n, type DocTemplateMergeOpts as o, type TransformSpec as p, type JobHandle as q, type JobStatusResponse as r, type TransformsService as s, keyFor as t, transforms as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maravilla-labs/platform",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Universal platform client for Maravilla runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,5 +1,5 @@
1
1
  import type { KvNamespace, KvListResult, Database, DbDocument, DbFindOptions, Storage, RealtimeService, PresenceService, AuthService, AuthCaller, AuthUser, AuthSession, AuthField, RegisterOptions, LoginOptions, CreateManagedUserOptions, UserListFilter, UserListResponse, UpdateUserOptions, Relation, AddRelationOptions, ListRelationsOptions, PolicyExplain, CanCheck, ConnectedClient, ApiKeyInfo, LoginSession, PolicyService, VectorIndexSpec, VectorIndexDescriptor, VectorQueryWithFilter, VectorSearchHit, IndexSpec, IndexDescriptor, Workflows, WorkflowHandle, WorkflowRun, WorkflowStepRecord, BrowserClient, FramesClient, BrowserJobHandle, ScreenshotRequest, PdfRequest, FramesRenderRequest } from './types.js';
2
- import type { TransformsService, TranscodeOpts, ThumbnailOpts, ResizeOpts, OcrOpts, DocToPdfOpts, DocThumbnailOpts, DocConvertOpts, DocToMarkdownOpts, DocToHtmlOpts, DocReplaceImagesOpts, DocInsertQrCodeOpts, DocTemplateMergeOpts, JobHandle, JobStatusResponse, MediaInfo } from './transforms.js';
2
+ import type { TransformsService, TranscodeOpts, ExtractFramesOpts, ExtractAudioOpts, ThumbnailOpts, ResizeOpts, OcrOpts, DocToPdfOpts, DocThumbnailOpts, DocConvertOpts, DocToMarkdownOpts, DocToHtmlOpts, DocReplaceImagesOpts, DocInsertQrCodeOpts, DocTemplateMergeOpts, JobHandle, JobStatusResponse, MediaInfo } from './transforms.js';
3
3
  import { RemoteMediaService } from './media.js';
4
4
  import { getRequestAuthHeader } from './request-scope.js';
5
5
 
@@ -1280,6 +1280,14 @@ class RemoteTransformsService implements TransformsService {
1280
1280
  return this.post<JobHandle>('/transcode', { srcKey, opts });
1281
1281
  }
1282
1282
 
1283
+ extractFrames(srcKey: string, opts: ExtractFramesOpts): Promise<JobHandle> {
1284
+ return this.post<JobHandle>('/extract_frames', { srcKey, opts });
1285
+ }
1286
+
1287
+ extractAudio(srcKey: string, opts?: ExtractAudioOpts | null): Promise<JobHandle> {
1288
+ return this.post<JobHandle>('/extract_audio', { srcKey, opts: opts ?? {} });
1289
+ }
1290
+
1283
1291
  thumbnail(srcKey: string, opts: ThumbnailOpts): Promise<JobHandle> {
1284
1292
  return this.post<JobHandle>('/thumbnail', { srcKey, opts });
1285
1293
  }
package/src/transforms.ts CHANGED
@@ -80,6 +80,15 @@ export interface TranscodeOpts {
80
80
  max_height?: number;
81
81
  audio_codec?: string;
82
82
  bitrate_kbps?: number;
83
+ /**
84
+ * Force a constant output frame rate (fps). Pass this for variable-frame-rate
85
+ * sources (e.g. MediaRecorder WebM) so the MP4 stays frame-accurately
86
+ * seekable for the frames pipeline — without it, a near-static VFR segment
87
+ * can collapse to a handful of frames that a fixed-rate renderer stalls on.
88
+ * When omitted, the worker auto-applies CFR only if it probes the source as
89
+ * VFR; a determinate-rate source is left untouched.
90
+ */
91
+ fps?: number;
83
92
  }
84
93
 
85
94
  /** Options for `transforms.thumbnail` — extract a single video frame. */
@@ -93,6 +102,84 @@ export interface ThumbnailOpts {
93
102
  quality?: number;
94
103
  }
95
104
 
105
+ /**
106
+ * Options for `transforms.extractFrames` — decode a video into a sequence of
107
+ * still images at a fixed fps. General capability (any MP4 → frames); the
108
+ * frames renderer consumes the result to swap `<video>`→`<img>` per frame, and
109
+ * it's reusable for thumbnails/scrubbing.
110
+ */
111
+ export interface ExtractFramesOpts {
112
+ /** Frames per second to sample the source at. */
113
+ fps: number;
114
+ /** Optional max width (height auto, aspect preserved, no upscaling). */
115
+ width?: number;
116
+ /** Output image format. Defaults to `"jpg"` server-side when omitted. */
117
+ format?: ImageFormat;
118
+ /** JPEG/WebP quality 1..=100 (higher = better). Ignored for PNG. */
119
+ quality?: number;
120
+ /** Start offset into the clip, ms (default 0). */
121
+ seek_ms?: number;
122
+ /** How much of the clip to extract from `seek_ms`, ms (default: to end). */
123
+ duration_ms?: number;
124
+ /**
125
+ * Also extract the source's audio track to `detach_audio.target` in the SAME
126
+ * job — one call yields both the frame set and a detachable audio asset
127
+ * (folds the upload-time "detach audio" step into frame extraction). The
128
+ * frame-set manifest is still the job's primary output; the audio is written
129
+ * to the caller-chosen `target` key.
130
+ */
131
+ detach_audio?: DetachAudioOpts;
132
+ }
133
+
134
+ /**
135
+ * Bundle a detached-audio side-output into an {@link ExtractFramesOpts} job.
136
+ * The worker runs one extra audio-extraction pass on the same source and writes
137
+ * the AAC/m4a result to `target` (a caller-chosen, tenant-scoped storage key).
138
+ */
139
+ export interface DetachAudioOpts {
140
+ /** Storage key to write the extracted audio (AAC/m4a) to. */
141
+ target: string;
142
+ /** Output bitrate, kbps. Server picks a sensible AAC bitrate when omitted. */
143
+ bitrate_kbps?: number;
144
+ /** Start offset into the source, ms (default 0). */
145
+ seek_ms?: number;
146
+ /** How much of the source to extract from `seek_ms`, ms (default: to end). */
147
+ duration_ms?: number;
148
+ }
149
+
150
+ /**
151
+ * Options for `transforms.extractAudio` — split a video's audio track off into
152
+ * a standalone AAC/m4a asset. Used to detach the mic/system audio recorded
153
+ * alongside a camera/screen clip so it can be placed + ducked independently on
154
+ * the frames timeline.
155
+ */
156
+ export interface ExtractAudioOpts {
157
+ /** Output bitrate, kbps. Server picks a sensible AAC bitrate when omitted. */
158
+ bitrate_kbps?: number;
159
+ /** Start offset into the source, ms (default 0). */
160
+ seek_ms?: number;
161
+ /** How much of the source to extract from `seek_ms`, ms (default: to end). */
162
+ duration_ms?: number;
163
+ }
164
+
165
+ /**
166
+ * Result of an `extractFrames` job: a JSON manifest stored at the job's
167
+ * `output_key` describing the frame set. Frame `i` (0-based) lives at
168
+ * `${prefix}/frame_${i padded to 6}.${ext}`.
169
+ */
170
+ export interface FramesetManifest {
171
+ /** Storage key prefix the frame images live under (no trailing slash). */
172
+ prefix: string;
173
+ /** Number of frames written. */
174
+ count: number;
175
+ /** Sampling fps the frames were extracted at. */
176
+ fps: number;
177
+ /** Frame image format. */
178
+ format: ImageFormat;
179
+ width?: number;
180
+ height?: number;
181
+ }
182
+
96
183
  /** Options for `transforms.resize`. */
97
184
  export interface ResizeOpts {
98
185
  width?: number;
@@ -236,6 +323,8 @@ export interface DocTemplateMergeOpts {
236
323
  export type TransformSpec =
237
324
  | ({ kind: 'transcode' } & TranscodeOpts)
238
325
  | ({ kind: 'thumbnail' } & ThumbnailOpts)
326
+ | ({ kind: 'extract_frames' } & ExtractFramesOpts)
327
+ | ({ kind: 'extract_audio' } & ExtractAudioOpts)
239
328
  | ({ kind: 'resize' } & ResizeOpts)
240
329
  | ({ kind: 'ocr' } & OcrOpts)
241
330
  | ({ kind: 'doc_to_pdf' } & DocToPdfOpts)
@@ -277,6 +366,17 @@ export interface JobStatusResponse {
277
366
  export interface TransformsService {
278
367
  transcode(srcKey: string, opts: TranscodeOpts): Promise<JobHandle>;
279
368
  thumbnail(srcKey: string, opts: ThumbnailOpts): Promise<JobHandle>;
369
+ /**
370
+ * Decode a video into a sequence of frame images at a fixed fps. Resolves to
371
+ * a `JobHandle` whose `output_key` holds a `FramesetManifest` (JSON) once
372
+ * complete; frames live under `${manifest.prefix}/frame_NNNNNN.<ext>`.
373
+ */
374
+ extractFrames(srcKey: string, opts: ExtractFramesOpts): Promise<JobHandle>;
375
+ /**
376
+ * Extract a video's audio track to a standalone AAC/m4a asset. Resolves to a
377
+ * `JobHandle` whose `output_key` holds the audio file once complete.
378
+ */
379
+ extractAudio(srcKey: string, opts?: ExtractAudioOpts | null): Promise<JobHandle>;
280
380
  resize(srcKey: string, opts: ResizeOpts): Promise<JobHandle>;
281
381
  probe(srcKey: string): Promise<MediaInfo>;
282
382
  ocr(srcKey: string, opts?: OcrOpts | null): Promise<JobHandle>;
@@ -340,6 +440,11 @@ function outputExtension(spec: TransformSpec): string {
340
440
  switch (spec.kind) {
341
441
  case 'transcode':
342
442
  return spec.format; // 'mp4' | 'webm'
443
+ case 'extract_frames':
444
+ // Output key is the JSON frameset manifest; frames live under its base.
445
+ return 'json';
446
+ case 'extract_audio':
447
+ return 'm4a';
343
448
  case 'thumbnail':
344
449
  case 'resize': {
345
450
  // thumbnail's `format` is optional in TS (defaults to jpg server-side);