@antiphony/shared 0.4.0 → 0.5.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.
@@ -46,21 +46,85 @@ var FirestoreTimestampSchema = zod.z.union([
46
46
  return date;
47
47
  });
48
48
  var ProcessingStageStatusSchema = zod.z.enum(["pending", "ready", "failed", "skipped"]);
49
+ var PROCESSING_STAGES = ["denoise", "trim", "transcribe", "waveform"];
50
+ zod.z.enum(PROCESSING_STAGES);
49
51
  var ProcessingRequestSchema = zod.z.object({
50
52
  transcribe: zod.z.boolean().optional(),
51
- denoise: zod.z.boolean().optional()
53
+ denoise: zod.z.boolean().optional(),
54
+ trim: zod.z.boolean().optional(),
55
+ waveform: zod.z.boolean().optional(),
56
+ /**
57
+ * Whether a completed byte-mutating stage should invalidate and recompute
58
+ * the derived artifacts that describe the old audio. Defaults to **true**
59
+ * — a transcript of superseded audio is wrong, not merely stale.
60
+ *
61
+ * `false` opts out, for an app that would rather keep the existing
62
+ * transcript than pay to regenerate it. It does NOT name a stage, so a
63
+ * request carrying only `reprocess` requests no work.
64
+ */
65
+ reprocess: zod.z.boolean().optional()
52
66
  });
53
- var ProcessingStateSchema = zod.z.object({
67
+ var ProcessingStageMapSchema = zod.z.object({
54
68
  transcribe: ProcessingStageStatusSchema.optional(),
55
69
  denoise: ProcessingStageStatusSchema.optional(),
56
- /** Content CID of the denoised audio variant, once `denoise === 'ready'`. */
57
- denoisedBlobCid: zod.z.string().optional(),
58
- updatedAt: FirestoreTimestampSchema
70
+ trim: ProcessingStageStatusSchema.optional(),
71
+ waveform: ProcessingStageStatusSchema.optional()
59
72
  });
60
- var ProcessingViewSchema = zod.z.object({
61
- transcribe: ProcessingStageStatusSchema.optional(),
62
- denoise: ProcessingStageStatusSchema.optional()
73
+ var ResolvedProcessingSchema = ProcessingStageMapSchema.extend({
74
+ reprocess: zod.z.boolean().optional()
75
+ });
76
+ var ProcessingStateSchema = ResolvedProcessingSchema.extend({
77
+ /**
78
+ * Content CID of the processed audio variant — the composed output of every
79
+ * byte-mutating stage that has completed. The record's own
80
+ * `embed.audio.ref.$link` stays the ORIGINAL CID (immutable content
81
+ * address); only the read-time view swaps playback to this variant.
82
+ */
83
+ processedBlobCid: zod.z.string().optional(),
84
+ /**
85
+ * MIME type of the processed variant. Present because providers may
86
+ * TRANSCODE — the ElevenLabs Voice Isolator returns MP3 regardless of what
87
+ * it is given — so the variant's type cannot be assumed to match
88
+ * `embed.audio.mimeType`. Anything reading the variant's bytes must use
89
+ * this, not the embed's.
90
+ */
91
+ processedMimeType: zod.z.string().optional(),
92
+ /**
93
+ * Duration of the processed variant, when a byte-mutating stage changed it
94
+ * (i.e. trim). Absent when the variant's duration matches the original.
95
+ */
96
+ processedDurationMs: zod.z.number().int().min(0).optional(),
97
+ /**
98
+ * Peaks for the processed variant, once the `waveform` stage completes.
99
+ * Same normalization and bounds as `embed.waveform` (0–100, max 1000), so
100
+ * a view can never carry a larger payload than the record allows.
101
+ */
102
+ waveformPeaks: zod.z.array(zod.z.number().int().min(0).max(100)).max(1e3).optional(),
103
+ /**
104
+ * When the current runner's exclusive claim on this post expires.
105
+ *
106
+ * Queue delivery is at-least-once, so the same job can arrive twice and
107
+ * run CONCURRENTLY. `process()` is idempotent under sequential retry — it
108
+ * acts on `pending` and re-does nothing already settled — but two passes
109
+ * interleaved is a different failure: both read the same `pending` state,
110
+ * both bill the provider for the same stage, and both write
111
+ * `processedBlobCid`, so the surviving variant is whichever finished last
112
+ * and the other's blob is orphaned.
113
+ *
114
+ * A runner claims this field transactionally before doing any work and
115
+ * clears it when finished; a second runner finding it unexpired declines
116
+ * and returns. It is an EXPIRY, not a boolean lock, because the holder can
117
+ * die mid-run (instance recycled, process killed) with no chance to
118
+ * release — a plain flag would strand the post permanently, where a lapsed
119
+ * lease lets the next delivery pick it up.
120
+ *
121
+ * Internal, like the variant fields above: `toProcessingView` projects
122
+ * stages only, so this never reaches a client.
123
+ */
124
+ leaseUntil: FirestoreTimestampSchema.optional(),
125
+ updatedAt: FirestoreTimestampSchema
63
126
  });
127
+ var ProcessingViewSchema = ProcessingStageMapSchema;
64
128
 
65
129
  // types/audio.ts
66
130
  var StrongRefSchema = zod.z.object({
@@ -96,20 +160,34 @@ var TimedTranscriptSchema = zod.z.object({
96
160
  });
97
161
  var AudioEmbedViewSchema = zod.z.object({
98
162
  $type: zod.z.literal("dev.antiphony.embed.audio#view"),
163
+ /**
164
+ * `url`, `durationMs` and `waveform` are RESOLVED, not copied: once
165
+ * processing has produced an audio variant they describe that variant
166
+ * rather than the bytes the client uploaded. They always agree with one
167
+ * another — a duration and a set of peaks are only meaningful against the
168
+ * audio `url` actually points at.
169
+ *
170
+ * A client that stored `durationMs` at upload time should therefore expect
171
+ * it to change (trim removes leading/trailing silence), and should render
172
+ * these three as a set rather than caching them independently. The record's
173
+ * originals are immutable and unaffected; this is a read-time resolution.
174
+ */
99
175
  url: zod.z.string().url(),
100
176
  durationMs: zod.z.number().int().min(0).optional(),
101
- // `alt`/`waveform` are copied from the stored embed; keep the same bounds
102
- // so a view can never carry a larger payload than the record allows.
177
+ // `alt` is copied from the stored embed; keep the same bounds so a view can
178
+ // never carry a larger payload than the record allows.
103
179
  alt: zod.z.string().max(1e4).optional(),
104
180
  waveform: zod.z.array(zod.z.number().int().min(0).max(100)).max(1e3).optional(),
105
181
  /** Lifted from the transcript enrichment record; absent until transcription completes. */
106
182
  transcript: TimedTranscriptSchema.optional(),
107
183
  /**
108
- * Per-stage audio-processing status (transcribe / denoise), when the app
109
- * opted into processing on create. Absent otherwise. A `pending` stage
110
- * means the client should poll (or re-render) for the result. When
111
- * `denoise === 'ready'`, `url` above already resolves to the cleaned
112
- * audio variant. See `types/processing.ts`.
184
+ * Per-stage audio-processing status (denoise / trim / transcribe /
185
+ * waveform), when the app opted into processing. Absent otherwise. A
186
+ * `pending` stage means the client should poll (or re-render) for the
187
+ * result including a stage that returns to `pending` after having been
188
+ * `ready`, which is how a recompute surfaces. Once a byte-mutating stage
189
+ * completes, `url`/`durationMs`/`waveform` above already resolve to the
190
+ * processed audio variant. See `types/processing.ts`.
113
191
  */
114
192
  processing: ProcessingViewSchema.optional()
115
193
  });
@@ -251,9 +329,11 @@ var CreateAudioPostRequestSchema = zod.z.object({
251
329
  /** Author self-label values (content warnings). */
252
330
  selfLabels: zod.z.array(zod.z.string()).optional(),
253
331
  /**
254
- * Opt-in audio processing for this post's audio (transcribe / denoise).
255
- * Both default off. Stages the deployment can't provide come back marked
256
- * `skipped` on the view rather than failing the create. See
332
+ * Opt-in audio processing for this post's audio (denoise / trim /
333
+ * transcribe / waveform). All default off. Stages the deployment can't
334
+ * provide come back marked `skipped` on the view rather than failing the
335
+ * create. A multi-stage request runs denoise → trim → (transcribe,
336
+ * waveform); request stages individually to override that order. See
257
337
  * `types/processing.ts`.
258
338
  */
259
339
  processing: ProcessingRequestSchema.optional()
@@ -53,9 +53,9 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
53
53
  mimeType: string;
54
54
  size: number;
55
55
  };
56
+ waveform?: number[] | undefined;
56
57
  durationMs?: number | undefined;
57
58
  alt?: string | undefined;
58
- waveform?: number[] | undefined;
59
59
  }, {
60
60
  $type: "dev.antiphony.embed.audio";
61
61
  audio: {
@@ -66,9 +66,9 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
66
66
  mimeType: string;
67
67
  size: number;
68
68
  };
69
+ waveform?: number[] | undefined;
69
70
  durationMs?: number | undefined;
70
71
  alt?: string | undefined;
71
- waveform?: number[] | undefined;
72
72
  }>>;
73
73
  /** Present ⇒ this is a reply (StrongRef root + parent). */
74
74
  reply: z.ZodOptional<z.ZodObject<{
@@ -116,26 +116,40 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
116
116
  /** Author self-label values (content warnings). */
117
117
  selfLabels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
118
118
  /**
119
- * Opt-in audio processing for this post's audio (transcribe / denoise).
120
- * Both default off. Stages the deployment can't provide come back marked
121
- * `skipped` on the view rather than failing the create. See
119
+ * Opt-in audio processing for this post's audio (denoise / trim /
120
+ * transcribe / waveform). All default off. Stages the deployment can't
121
+ * provide come back marked `skipped` on the view rather than failing the
122
+ * create. A multi-stage request runs denoise → trim → (transcribe,
123
+ * waveform); request stages individually to override that order. See
122
124
  * `types/processing.ts`.
123
125
  */
124
126
  processing: z.ZodOptional<z.ZodObject<{
125
127
  transcribe: z.ZodOptional<z.ZodBoolean>;
126
128
  denoise: z.ZodOptional<z.ZodBoolean>;
129
+ trim: z.ZodOptional<z.ZodBoolean>;
130
+ waveform: z.ZodOptional<z.ZodBoolean>;
131
+ reprocess: z.ZodOptional<z.ZodBoolean>;
127
132
  }, "strip", z.ZodTypeAny, {
128
- transcribe?: boolean | undefined;
129
133
  denoise?: boolean | undefined;
130
- }, {
134
+ trim?: boolean | undefined;
131
135
  transcribe?: boolean | undefined;
136
+ waveform?: boolean | undefined;
137
+ reprocess?: boolean | undefined;
138
+ }, {
132
139
  denoise?: boolean | undefined;
140
+ trim?: boolean | undefined;
141
+ transcribe?: boolean | undefined;
142
+ waveform?: boolean | undefined;
143
+ reprocess?: boolean | undefined;
133
144
  }>>;
134
145
  }, "strip", z.ZodTypeAny, {
135
146
  text: string;
136
147
  processing?: {
137
- transcribe?: boolean | undefined;
138
148
  denoise?: boolean | undefined;
149
+ trim?: boolean | undefined;
150
+ transcribe?: boolean | undefined;
151
+ waveform?: boolean | undefined;
152
+ reprocess?: boolean | undefined;
139
153
  } | undefined;
140
154
  reply?: {
141
155
  root: {
@@ -158,17 +172,20 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
158
172
  mimeType: string;
159
173
  size: number;
160
174
  };
175
+ waveform?: number[] | undefined;
161
176
  durationMs?: number | undefined;
162
177
  alt?: string | undefined;
163
- waveform?: number[] | undefined;
164
178
  } | undefined;
165
179
  langs?: string[] | undefined;
166
180
  selfLabels?: string[] | undefined;
167
181
  }, {
168
182
  text?: string | undefined;
169
183
  processing?: {
170
- transcribe?: boolean | undefined;
171
184
  denoise?: boolean | undefined;
185
+ trim?: boolean | undefined;
186
+ transcribe?: boolean | undefined;
187
+ waveform?: boolean | undefined;
188
+ reprocess?: boolean | undefined;
172
189
  } | undefined;
173
190
  reply?: {
174
191
  root: {
@@ -191,17 +208,20 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
191
208
  mimeType: string;
192
209
  size: number;
193
210
  };
211
+ waveform?: number[] | undefined;
194
212
  durationMs?: number | undefined;
195
213
  alt?: string | undefined;
196
- waveform?: number[] | undefined;
197
214
  } | undefined;
198
215
  langs?: string[] | undefined;
199
216
  selfLabels?: string[] | undefined;
200
217
  }>, {
201
218
  text: string;
202
219
  processing?: {
203
- transcribe?: boolean | undefined;
204
220
  denoise?: boolean | undefined;
221
+ trim?: boolean | undefined;
222
+ transcribe?: boolean | undefined;
223
+ waveform?: boolean | undefined;
224
+ reprocess?: boolean | undefined;
205
225
  } | undefined;
206
226
  reply?: {
207
227
  root: {
@@ -224,17 +244,20 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
224
244
  mimeType: string;
225
245
  size: number;
226
246
  };
247
+ waveform?: number[] | undefined;
227
248
  durationMs?: number | undefined;
228
249
  alt?: string | undefined;
229
- waveform?: number[] | undefined;
230
250
  } | undefined;
231
251
  langs?: string[] | undefined;
232
252
  selfLabels?: string[] | undefined;
233
253
  }, {
234
254
  text?: string | undefined;
235
255
  processing?: {
236
- transcribe?: boolean | undefined;
237
256
  denoise?: boolean | undefined;
257
+ trim?: boolean | undefined;
258
+ transcribe?: boolean | undefined;
259
+ waveform?: boolean | undefined;
260
+ reprocess?: boolean | undefined;
238
261
  } | undefined;
239
262
  reply?: {
240
263
  root: {
@@ -257,17 +280,20 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
257
280
  mimeType: string;
258
281
  size: number;
259
282
  };
283
+ waveform?: number[] | undefined;
260
284
  durationMs?: number | undefined;
261
285
  alt?: string | undefined;
262
- waveform?: number[] | undefined;
263
286
  } | undefined;
264
287
  langs?: string[] | undefined;
265
288
  selfLabels?: string[] | undefined;
266
289
  }>, {
267
290
  text: string;
268
291
  processing?: {
269
- transcribe?: boolean | undefined;
270
292
  denoise?: boolean | undefined;
293
+ trim?: boolean | undefined;
294
+ transcribe?: boolean | undefined;
295
+ waveform?: boolean | undefined;
296
+ reprocess?: boolean | undefined;
271
297
  } | undefined;
272
298
  reply?: {
273
299
  root: {
@@ -290,17 +316,20 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
290
316
  mimeType: string;
291
317
  size: number;
292
318
  };
319
+ waveform?: number[] | undefined;
293
320
  durationMs?: number | undefined;
294
321
  alt?: string | undefined;
295
- waveform?: number[] | undefined;
296
322
  } | undefined;
297
323
  langs?: string[] | undefined;
298
324
  selfLabels?: string[] | undefined;
299
325
  }, {
300
326
  text?: string | undefined;
301
327
  processing?: {
302
- transcribe?: boolean | undefined;
303
328
  denoise?: boolean | undefined;
329
+ trim?: boolean | undefined;
330
+ transcribe?: boolean | undefined;
331
+ waveform?: boolean | undefined;
332
+ reprocess?: boolean | undefined;
304
333
  } | undefined;
305
334
  reply?: {
306
335
  root: {
@@ -323,9 +352,9 @@ declare const CreateAudioPostRequestSchema: z.ZodEffects<z.ZodEffects<z.ZodObjec
323
352
  mimeType: string;
324
353
  size: number;
325
354
  };
355
+ waveform?: number[] | undefined;
326
356
  durationMs?: number | undefined;
327
357
  alt?: string | undefined;
328
- waveform?: number[] | undefined;
329
358
  } | undefined;
330
359
  langs?: string[] | undefined;
331
360
  selfLabels?: string[] | undefined;
@@ -346,22 +375,37 @@ declare const PatchAudioPostRequestSchema: z.ZodObject<{
346
375
  processing: z.ZodObject<{
347
376
  transcribe: z.ZodOptional<z.ZodBoolean>;
348
377
  denoise: z.ZodOptional<z.ZodBoolean>;
378
+ trim: z.ZodOptional<z.ZodBoolean>;
379
+ waveform: z.ZodOptional<z.ZodBoolean>;
380
+ reprocess: z.ZodOptional<z.ZodBoolean>;
349
381
  }, "strip", z.ZodTypeAny, {
350
- transcribe?: boolean | undefined;
351
382
  denoise?: boolean | undefined;
352
- }, {
383
+ trim?: boolean | undefined;
353
384
  transcribe?: boolean | undefined;
385
+ waveform?: boolean | undefined;
386
+ reprocess?: boolean | undefined;
387
+ }, {
354
388
  denoise?: boolean | undefined;
389
+ trim?: boolean | undefined;
390
+ transcribe?: boolean | undefined;
391
+ waveform?: boolean | undefined;
392
+ reprocess?: boolean | undefined;
355
393
  }>;
356
394
  }, "strip", z.ZodTypeAny, {
357
395
  processing: {
358
- transcribe?: boolean | undefined;
359
396
  denoise?: boolean | undefined;
397
+ trim?: boolean | undefined;
398
+ transcribe?: boolean | undefined;
399
+ waveform?: boolean | undefined;
400
+ reprocess?: boolean | undefined;
360
401
  };
361
402
  }, {
362
403
  processing: {
363
- transcribe?: boolean | undefined;
364
404
  denoise?: boolean | undefined;
405
+ trim?: boolean | undefined;
406
+ transcribe?: boolean | undefined;
407
+ waveform?: boolean | undefined;
408
+ reprocess?: boolean | undefined;
365
409
  };
366
410
  }>;
367
411
  type PatchAudioPostRequest = z.infer<typeof PatchAudioPostRequestSchema>;
@@ -61,21 +61,105 @@ var FirestoreTimestampSchema = zod.z.union([
61
61
  return date;
62
62
  });
63
63
  var ProcessingStageStatusSchema = zod.z.enum(["pending", "ready", "failed", "skipped"]);
64
+ var PROCESSING_STAGES = ["denoise", "trim", "transcribe", "waveform"];
65
+ var ProcessingStageSchema = zod.z.enum(PROCESSING_STAGES);
66
+ var BYTE_MUTATING_STAGES = ["denoise", "trim"];
67
+ var DERIVED_STAGES = ["transcribe", "waveform"];
64
68
  var ProcessingRequestSchema = zod.z.object({
65
69
  transcribe: zod.z.boolean().optional(),
66
- denoise: zod.z.boolean().optional()
70
+ denoise: zod.z.boolean().optional(),
71
+ trim: zod.z.boolean().optional(),
72
+ waveform: zod.z.boolean().optional(),
73
+ /**
74
+ * Whether a completed byte-mutating stage should invalidate and recompute
75
+ * the derived artifacts that describe the old audio. Defaults to **true**
76
+ * — a transcript of superseded audio is wrong, not merely stale.
77
+ *
78
+ * `false` opts out, for an app that would rather keep the existing
79
+ * transcript than pay to regenerate it. It does NOT name a stage, so a
80
+ * request carrying only `reprocess` requests no work.
81
+ */
82
+ reprocess: zod.z.boolean().optional()
67
83
  });
68
- var ProcessingStateSchema = zod.z.object({
84
+ var ProcessingStageMapSchema = zod.z.object({
69
85
  transcribe: ProcessingStageStatusSchema.optional(),
70
86
  denoise: ProcessingStageStatusSchema.optional(),
71
- /** Content CID of the denoised audio variant, once `denoise === 'ready'`. */
72
- denoisedBlobCid: zod.z.string().optional(),
73
- updatedAt: FirestoreTimestampSchema
87
+ trim: ProcessingStageStatusSchema.optional(),
88
+ waveform: ProcessingStageStatusSchema.optional()
74
89
  });
75
- var ProcessingViewSchema = zod.z.object({
76
- transcribe: ProcessingStageStatusSchema.optional(),
77
- denoise: ProcessingStageStatusSchema.optional()
90
+ var ResolvedProcessingSchema = ProcessingStageMapSchema.extend({
91
+ reprocess: zod.z.boolean().optional()
92
+ });
93
+ var ProcessingStateSchema = ResolvedProcessingSchema.extend({
94
+ /**
95
+ * Content CID of the processed audio variant — the composed output of every
96
+ * byte-mutating stage that has completed. The record's own
97
+ * `embed.audio.ref.$link` stays the ORIGINAL CID (immutable content
98
+ * address); only the read-time view swaps playback to this variant.
99
+ */
100
+ processedBlobCid: zod.z.string().optional(),
101
+ /**
102
+ * MIME type of the processed variant. Present because providers may
103
+ * TRANSCODE — the ElevenLabs Voice Isolator returns MP3 regardless of what
104
+ * it is given — so the variant's type cannot be assumed to match
105
+ * `embed.audio.mimeType`. Anything reading the variant's bytes must use
106
+ * this, not the embed's.
107
+ */
108
+ processedMimeType: zod.z.string().optional(),
109
+ /**
110
+ * Duration of the processed variant, when a byte-mutating stage changed it
111
+ * (i.e. trim). Absent when the variant's duration matches the original.
112
+ */
113
+ processedDurationMs: zod.z.number().int().min(0).optional(),
114
+ /**
115
+ * Peaks for the processed variant, once the `waveform` stage completes.
116
+ * Same normalization and bounds as `embed.waveform` (0–100, max 1000), so
117
+ * a view can never carry a larger payload than the record allows.
118
+ */
119
+ waveformPeaks: zod.z.array(zod.z.number().int().min(0).max(100)).max(1e3).optional(),
120
+ /**
121
+ * When the current runner's exclusive claim on this post expires.
122
+ *
123
+ * Queue delivery is at-least-once, so the same job can arrive twice and
124
+ * run CONCURRENTLY. `process()` is idempotent under sequential retry — it
125
+ * acts on `pending` and re-does nothing already settled — but two passes
126
+ * interleaved is a different failure: both read the same `pending` state,
127
+ * both bill the provider for the same stage, and both write
128
+ * `processedBlobCid`, so the surviving variant is whichever finished last
129
+ * and the other's blob is orphaned.
130
+ *
131
+ * A runner claims this field transactionally before doing any work and
132
+ * clears it when finished; a second runner finding it unexpired declines
133
+ * and returns. It is an EXPIRY, not a boolean lock, because the holder can
134
+ * die mid-run (instance recycled, process killed) with no chance to
135
+ * release — a plain flag would strand the post permanently, where a lapsed
136
+ * lease lets the next delivery pick it up.
137
+ *
138
+ * Internal, like the variant fields above: `toProcessingView` projects
139
+ * stages only, so this never reaches a client.
140
+ */
141
+ leaseUntil: FirestoreTimestampSchema.optional(),
142
+ updatedAt: FirestoreTimestampSchema
78
143
  });
144
+ var ProcessingViewSchema = ProcessingStageMapSchema;
145
+ function toProcessingView(state) {
146
+ const view = {};
147
+ for (const stage of PROCESSING_STAGES) {
148
+ if (state[stage] !== void 0) view[stage] = state[stage];
149
+ }
150
+ return view;
151
+ }
152
+ function resolveAudioVariant(canonical, state) {
153
+ var _a;
154
+ if (!state) return canonical;
155
+ const hasVariant = state.processedBlobCid !== void 0;
156
+ const peaksAreCurrent = state.waveform === "ready" && state.waveformPeaks !== void 0;
157
+ return {
158
+ blobCid: hasVariant ? state.processedBlobCid : canonical.blobCid,
159
+ durationMs: hasVariant ? (_a = state.processedDurationMs) != null ? _a : canonical.durationMs : canonical.durationMs,
160
+ waveform: peaksAreCurrent ? state.waveformPeaks : canonical.waveform
161
+ };
162
+ }
79
163
 
80
164
  // types/audio.ts
81
165
  var StrongRefSchema = zod.z.object({
@@ -111,20 +195,34 @@ var TimedTranscriptSchema = zod.z.object({
111
195
  });
112
196
  var AudioEmbedViewSchema = zod.z.object({
113
197
  $type: zod.z.literal("dev.antiphony.embed.audio#view"),
198
+ /**
199
+ * `url`, `durationMs` and `waveform` are RESOLVED, not copied: once
200
+ * processing has produced an audio variant they describe that variant
201
+ * rather than the bytes the client uploaded. They always agree with one
202
+ * another — a duration and a set of peaks are only meaningful against the
203
+ * audio `url` actually points at.
204
+ *
205
+ * A client that stored `durationMs` at upload time should therefore expect
206
+ * it to change (trim removes leading/trailing silence), and should render
207
+ * these three as a set rather than caching them independently. The record's
208
+ * originals are immutable and unaffected; this is a read-time resolution.
209
+ */
114
210
  url: zod.z.string().url(),
115
211
  durationMs: zod.z.number().int().min(0).optional(),
116
- // `alt`/`waveform` are copied from the stored embed; keep the same bounds
117
- // so a view can never carry a larger payload than the record allows.
212
+ // `alt` is copied from the stored embed; keep the same bounds so a view can
213
+ // never carry a larger payload than the record allows.
118
214
  alt: zod.z.string().max(1e4).optional(),
119
215
  waveform: zod.z.array(zod.z.number().int().min(0).max(100)).max(1e3).optional(),
120
216
  /** Lifted from the transcript enrichment record; absent until transcription completes. */
121
217
  transcript: TimedTranscriptSchema.optional(),
122
218
  /**
123
- * Per-stage audio-processing status (transcribe / denoise), when the app
124
- * opted into processing on create. Absent otherwise. A `pending` stage
125
- * means the client should poll (or re-render) for the result. When
126
- * `denoise === 'ready'`, `url` above already resolves to the cleaned
127
- * audio variant. See `types/processing.ts`.
219
+ * Per-stage audio-processing status (denoise / trim / transcribe /
220
+ * waveform), when the app opted into processing. Absent otherwise. A
221
+ * `pending` stage means the client should poll (or re-render) for the
222
+ * result including a stage that returns to `pending` after having been
223
+ * `ready`, which is how a recompute surfaces. Once a byte-mutating stage
224
+ * completes, `url`/`durationMs`/`waveform` above already resolve to the
225
+ * processed audio variant. See `types/processing.ts`.
128
226
  */
129
227
  processing: ProcessingViewSchema.optional()
130
228
  });
@@ -266,9 +364,11 @@ var CreateAudioPostRequestSchema = zod.z.object({
266
364
  /** Author self-label values (content warnings). */
267
365
  selfLabels: zod.z.array(zod.z.string()).optional(),
268
366
  /**
269
- * Opt-in audio processing for this post's audio (transcribe / denoise).
270
- * Both default off. Stages the deployment can't provide come back marked
271
- * `skipped` on the view rather than failing the create. See
367
+ * Opt-in audio processing for this post's audio (denoise / trim /
368
+ * transcribe / waveform). All default off. Stages the deployment can't
369
+ * provide come back marked `skipped` on the view rather than failing the
370
+ * create. A multi-stage request runs denoise → trim → (transcribe,
371
+ * waveform); request stages individually to override that order. See
272
372
  * `types/processing.ts`.
273
373
  */
274
374
  processing: ProcessingRequestSchema.optional()
@@ -401,23 +501,29 @@ exports.AudioEmbedSchema = AudioEmbedSchema;
401
501
  exports.AudioEmbedViewSchema = AudioEmbedViewSchema;
402
502
  exports.AudioPostRecordSchema = AudioPostRecordSchema;
403
503
  exports.AudioPostViewSchema = AudioPostViewSchema;
504
+ exports.BYTE_MUTATING_STAGES = BYTE_MUTATING_STAGES;
404
505
  exports.BlobRefSchema = BlobRefSchema;
405
506
  exports.COLLECTIONS = COLLECTIONS;
406
507
  exports.ConflictError = ConflictError;
407
508
  exports.CreateAudioPostRequestSchema = CreateAudioPostRequestSchema;
509
+ exports.DERIVED_STAGES = DERIVED_STAGES;
408
510
  exports.EMBED_NSID = EMBED_NSID;
409
511
  exports.FirestoreTimestampSchema = FirestoreTimestampSchema;
410
512
  exports.ForbiddenError = ForbiddenError;
411
513
  exports.NSID = NSID;
412
514
  exports.NotFoundError = NotFoundError;
515
+ exports.PROCESSING_STAGES = PROCESSING_STAGES;
413
516
  exports.PatchAudioPostRequestSchema = PatchAudioPostRequestSchema;
414
517
  exports.PostRecordPublicSchema = PostRecordPublicSchema;
415
518
  exports.ProcessingRequestSchema = ProcessingRequestSchema;
519
+ exports.ProcessingStageMapSchema = ProcessingStageMapSchema;
520
+ exports.ProcessingStageSchema = ProcessingStageSchema;
416
521
  exports.ProcessingStageStatusSchema = ProcessingStageStatusSchema;
417
522
  exports.ProcessingStateSchema = ProcessingStateSchema;
418
523
  exports.ProcessingViewSchema = ProcessingViewSchema;
419
524
  exports.RateLimitError = RateLimitError;
420
525
  exports.ReplyRefSchema = ReplyRefSchema;
526
+ exports.ResolvedProcessingSchema = ResolvedProcessingSchema;
421
527
  exports.ServiceError = ServiceError;
422
528
  exports.StrongRefSchema = StrongRefSchema;
423
529
  exports.TimedTranscriptSchema = TimedTranscriptSchema;
@@ -429,3 +535,5 @@ exports.ViewerStateSchema = ViewerStateSchema;
429
535
  exports.buildReportedErrorEvent = buildReportedErrorEvent;
430
536
  exports.isFirestoreTimestamp = isFirestoreTimestamp;
431
537
  exports.reportError = reportError;
538
+ exports.resolveAudioVariant = resolveAudioVariant;
539
+ exports.toProcessingView = toProcessingView;