@bendyline/squisq 2.13.14 → 2.13.16
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/dist/{Doc-BhvotZx_.d.ts → Doc-DLfTam0R.d.ts} +82 -6
- package/dist/{ImageEditDoc-D8hYio6b.d.ts → ImageEditDoc-DdSHb6sj.d.ts} +1 -1
- package/dist/{chunk-YQQWE4HG.js → chunk-33CAD575.js} +200 -262
- package/dist/{chunk-L7QZORLB.js → chunk-37RLFDIT.js} +11 -4
- package/dist/chunk-4R3YZTPX.js +42 -0
- package/dist/{chunk-DBVCSVNV.js → chunk-A4ZFYGU6.js} +13 -38
- package/dist/{chunk-CTT4XAGO.js → chunk-BG2OYBGC.js} +2534 -737
- package/dist/{chunk-2SRF5III.js → chunk-BVL3F64S.js} +1 -1
- package/dist/chunk-C7DKMO4H.js +88 -0
- package/dist/{chunk-SGNWO7OF.js → chunk-MA7XUCSQ.js} +517 -30
- package/dist/{chunk-JSGIWW35.js → chunk-MTIR5PWF.js} +1 -1
- package/dist/{chunk-C4SB3NN4.js → chunk-NBDF6SQ5.js} +22 -2
- package/dist/chunk-TOPEP5B7.js +515 -0
- package/dist/{chunk-OIQ2GO2O.js → chunk-TXN23SG2.js} +1 -1
- package/dist/{chunk-7MS26OXD.js → chunk-UDPPWYYM.js} +6 -0
- package/dist/{chunk-J2G6EOEJ.js → chunk-XY5Z23C4.js} +75 -16
- package/dist/{chunk-5DFFVW75.js → chunk-Z73DG6IL.js} +1 -1
- package/dist/doc/index.d.ts +412 -26
- package/dist/doc/index.js +66 -11
- package/dist/fence/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +9 -9
- package/dist/index.js +147 -29
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +5 -4
- package/dist/markdown/index.js +3 -3
- package/dist/{materializePageSection-CWxou4mX.d.ts → materializePageSection-HIoRYiZu.d.ts} +121 -8
- package/dist/mediaEdit/index.d.ts +154 -3
- package/dist/mediaEdit/index.js +241 -2
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +7 -5
- package/dist/recommend/index.d.ts +129 -2
- package/dist/recommend/index.js +13 -4
- package/dist/schemas/index.d.ts +26 -7
- package/dist/schemas/index.js +14 -8
- package/dist/{themeLibrary-BIYE6aLw.d.ts → themeLibrary-B2K-aAOy.d.ts} +1 -1
- package/dist/timing/index.d.ts +66 -2
- package/dist/timing/index.js +12 -0
- package/dist/transform/index.d.ts +173 -16
- package/dist/transform/index.js +58 -17
- package/package.json +1 -1
- package/dist/chunk-FCB6JLWB.js +0 -125
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { k as Block,
|
|
1
|
+
import { k as Block, b0 as PageSectionKind, a_ as PageEmphasis, bP as ThemeColorScheme, aY as PageBackground, bN as Theme, cj as ViewportConfig, D as CustomTemplateDefinition, b3 as PageVariant, bw as StartBlockConfig, U as Doc, bQ as ThemePageStyle } from './Doc-DLfTam0R.js';
|
|
2
2
|
import { M as MarkdownBlockNode } from './types-Zk8qYuaT.js';
|
|
3
3
|
|
|
4
4
|
/** A prominent number found inside a short stat-like string. */
|
|
@@ -15,7 +15,7 @@ interface NumberHighlightMatch {
|
|
|
15
15
|
|
|
16
16
|
declare const PRESENTATION_KEY = "squisq-presentation";
|
|
17
17
|
/** The layouts a summary (a headline and up to four points) can fill. */
|
|
18
|
-
declare const PRESENTATION_LAYOUTS: readonly ["title", "divider", "statement", "emphasis", "quote", "stat", "definition", "list", "comparison", "steps", "image", "cards", "process", "cycle", "layers"];
|
|
18
|
+
declare const PRESENTATION_LAYOUTS: readonly ["title", "divider", "statement", "emphasis", "quote", "stat", "definition", "list", "comparison", "steps", "image", "cards", "process", "cycle", "layers", "stats", "pyramid", "funnel", "hub", "venn", "matrix"];
|
|
19
19
|
type PresentationLayout = (typeof PRESENTATION_LAYOUTS)[number];
|
|
20
20
|
/** Shows a section's own block, with the template its author chose, instead of
|
|
21
21
|
* a summary. A section shown this way is one slide for its whole narration. */
|
|
@@ -27,12 +27,26 @@ declare function presentationStat(headline: string, points: readonly string[]):
|
|
|
27
27
|
line: string;
|
|
28
28
|
index: number;
|
|
29
29
|
} | null;
|
|
30
|
-
/** Whether a summary can fill a layout:
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
/** Whether a summary can fill a layout: the items a drawn layout takes
|
|
31
|
+
* (`conceptItemsProblem`: three stages for a cycle, a figure in every key
|
|
32
|
+
* number, four ideas for a matrix…); enough points for a list, steps or a
|
|
33
|
+
* comparison; a prominent number for a key number; a short headline for a big
|
|
34
|
+
* statement; and, for a quote, a headline found word for word in `passage`,
|
|
35
|
+
* the source text the slide summarizes. An image slide also needs a picture,
|
|
36
|
+
* which callers check. */
|
|
35
37
|
declare function presentationLayoutFits(layout: PresentationLayout, headline: string, points: readonly string[], passage: string): boolean;
|
|
38
|
+
/** Why a summary cannot fill a layout, as a sentence its author can act on
|
|
39
|
+
* (a person reading a designer's help, a model reading a correction), or
|
|
40
|
+
* `undefined` when it can. */
|
|
41
|
+
declare function presentationLayoutProblem(layout: PresentationLayout, headline: string, points: readonly string[], passage: string): string | undefined;
|
|
42
|
+
/** What a slide must have for a layout to apply, as a designer's help says it
|
|
43
|
+
* ("at least 2 points", "exactly 4 points"), or `undefined` when any slide
|
|
44
|
+
* will do. It states what `presentationLayoutProblem` checks, so a picker's
|
|
45
|
+
* help and the plan agree. `points` names a slide's points in the host's own
|
|
46
|
+
* word ("highlights"). */
|
|
47
|
+
declare function presentationLayoutNeeds(layout: PresentationLayout, wording?: {
|
|
48
|
+
points?: string;
|
|
49
|
+
}): string | undefined;
|
|
36
50
|
interface PresentationBeat {
|
|
37
51
|
id: string;
|
|
38
52
|
sourceStart: number;
|
|
@@ -45,7 +59,19 @@ interface PresentationBeat {
|
|
|
45
59
|
media?: string[];
|
|
46
60
|
/** Left out of the deck; the slide shown before it stays on screen instead. */
|
|
47
61
|
hidden?: true;
|
|
62
|
+
/** A hub only: the one to three words its ideas share, written inside it. */
|
|
63
|
+
center?: string;
|
|
64
|
+
/** A matrix only: what grows from left to right, named along the foot. */
|
|
65
|
+
xAxis?: string;
|
|
66
|
+
/** A matrix only: what grows from bottom to top, named up the side. */
|
|
67
|
+
yAxis?: string;
|
|
48
68
|
}
|
|
69
|
+
/** What a layout draws beyond its headline and points. */
|
|
70
|
+
type PresentationBeatExtras = Pick<PresentationBeat, 'center' | 'xAxis' | 'yAxis'>;
|
|
71
|
+
/** The extras of `source` that `layout` draws, trimmed; ones it does not draw,
|
|
72
|
+
* empty ones and ones too long to set are left out. Read loosely, so a model's
|
|
73
|
+
* reply and a saved beat pass through the same door. */
|
|
74
|
+
declare function presentationBeatExtras(layout: string, source: unknown): PresentationBeatExtras;
|
|
49
75
|
interface PresentationPlan {
|
|
50
76
|
version: 1;
|
|
51
77
|
sourceText: string;
|
|
@@ -57,10 +83,97 @@ declare const PRESENTATION_LIMITS: {
|
|
|
57
83
|
readonly beats: 80;
|
|
58
84
|
readonly serializedCharacters: 160000;
|
|
59
85
|
readonly mediaPerBeat: 4;
|
|
86
|
+
/** The most points a summary slide holds. */
|
|
87
|
+
readonly points: 4;
|
|
88
|
+
/** The longest a hub's centre or a matrix's axis name may be. */
|
|
89
|
+
readonly extraCharacters: 40;
|
|
60
90
|
};
|
|
61
91
|
declare function parsePresentationPlan(input: unknown): PresentationPlan | null;
|
|
62
92
|
declare function serializePresentationPlan(plan: PresentationPlan): string;
|
|
63
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Concept specs — what each concept template takes, as data.
|
|
96
|
+
*
|
|
97
|
+
* The ten concept templates draw a handful of ideas, and every product that
|
|
98
|
+
* lets an author choose one has to say the same things about it: how many
|
|
99
|
+
* items it takes, how an item is written, what order they come in, and what
|
|
100
|
+
* the template draws when it is given something else. Those facts used to be
|
|
101
|
+
* restated wherever they were needed (a prompt's "3–5", a validator's table,
|
|
102
|
+
* a picker's help text, each template's own fallback) and the copies drifted.
|
|
103
|
+
* This module is the one statement of them:
|
|
104
|
+
*
|
|
105
|
+
* - {@link CONCEPT_SPECS}: per template, the items an author should write and
|
|
106
|
+
* the inputs it takes beyond them. The authoring guide renders its counts
|
|
107
|
+
* from here ({@link conceptItemCount}), so a prompt and a check agree.
|
|
108
|
+
* - {@link conceptItemsProblem}: whether a set of items meets that contract,
|
|
109
|
+
* as a sentence an author or a repair prompt can act on.
|
|
110
|
+
* - {@link conceptDowngrade}: what a template draws instead when it cannot
|
|
111
|
+
* draw its own picture. The templates call it, so it cannot disagree with
|
|
112
|
+
* them, and `markdownToDoc` reports it (`template-shape-unmet`).
|
|
113
|
+
*
|
|
114
|
+
* Pure text in, plain data out.
|
|
115
|
+
*
|
|
116
|
+
* Related: `conceptItems.ts` (how one item is read), `conceptBlocks.ts` and
|
|
117
|
+
* `conceptShapes.ts` (the drawings), `authoringGuide.ts` (what authors are told).
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
/** Every template that draws a handful of ideas. */
|
|
121
|
+
declare const CONCEPT_TEMPLATES: readonly ["cards", "process", "cycle", "layers", "stats", "pyramid", "funnel", "hub", "venn", "matrix"];
|
|
122
|
+
type ConceptTemplateName = (typeof CONCEPT_TEMPLATES)[number];
|
|
123
|
+
/** The most ideas a concept template draws; more are shown as the list they are. */
|
|
124
|
+
declare const CONCEPT_MAX_DRAWN = 6;
|
|
125
|
+
/** An input a concept template takes beyond `title` and `items`. */
|
|
126
|
+
interface ConceptSpecInput {
|
|
127
|
+
key: string;
|
|
128
|
+
/** What an author writes there, as a guide lists it. */
|
|
129
|
+
guide: string;
|
|
130
|
+
}
|
|
131
|
+
interface ConceptSpec {
|
|
132
|
+
/** The fewest and the most items an author should write. */
|
|
133
|
+
items: readonly [min: number, max: number];
|
|
134
|
+
/** How an item is written: a label and its detail, or a figure and what it measures. */
|
|
135
|
+
itemForm: 'label-detail' | 'figure-caption';
|
|
136
|
+
/** The order the items are written in, when the drawing depends on it. */
|
|
137
|
+
order?: string;
|
|
138
|
+
/** Inputs beyond `title` and `items`. */
|
|
139
|
+
inputs?: readonly ConceptSpecInput[];
|
|
140
|
+
}
|
|
141
|
+
declare const CONCEPT_SPECS: Readonly<Record<ConceptTemplateName, ConceptSpec>>;
|
|
142
|
+
/** The concept template a name denotes, or `undefined`. */
|
|
143
|
+
declare function conceptTemplate(template: string): ConceptTemplateName | undefined;
|
|
144
|
+
interface ConceptItemsOptions {
|
|
145
|
+
/**
|
|
146
|
+
* The most items the author's surface holds, when that is fewer than the
|
|
147
|
+
* template takes (a summary slide holds four points).
|
|
148
|
+
*/
|
|
149
|
+
max?: number;
|
|
150
|
+
}
|
|
151
|
+
/** How many items a template takes, as a guide says it: "2–4", or "exactly 4". */
|
|
152
|
+
declare function conceptItemCount(template: ConceptTemplateName, options?: ConceptItemsOptions): string;
|
|
153
|
+
/**
|
|
154
|
+
* Why `items` do not meet a template's contract, or `undefined` when they do:
|
|
155
|
+
* too few or too many, a key number without a figure, a Venn diagram without
|
|
156
|
+
* the item that names what its sets share. The sentence is written for the
|
|
157
|
+
* author, so a validator can log it and a repair prompt can quote it.
|
|
158
|
+
*/
|
|
159
|
+
declare function conceptItemsProblem(template: ConceptTemplateName, items: readonly unknown[] | undefined, options?: ConceptItemsOptions): string | undefined;
|
|
160
|
+
/** What a concept template draws when it cannot draw its own picture, and why. */
|
|
161
|
+
interface ConceptDowngrade {
|
|
162
|
+
drawsAs: ConceptTemplateName | 'list';
|
|
163
|
+
/** Completes "…, so it is drawn as <drawsAs>": "a matrix places exactly four items and this has three". */
|
|
164
|
+
reason: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* What a template draws instead of its own picture for these items, or
|
|
168
|
+
* `undefined` when it draws itself. A template never draws a broken diagram:
|
|
169
|
+
* a matrix without four ideas, a Venn diagram without two sets and key
|
|
170
|
+
* numbers without a figure draw as cards; a cycle too short or too wordy for
|
|
171
|
+
* a ring draws as a process; more than six ideas are shown as a list.
|
|
172
|
+
*/
|
|
173
|
+
declare function conceptDowngrade(template: ConceptTemplateName, items: readonly unknown[] | undefined): ConceptDowngrade | undefined;
|
|
174
|
+
/** The template that draws these items: `template` itself unless it is downgraded. */
|
|
175
|
+
declare function conceptDrawsAs(template: ConceptTemplateName, items: readonly unknown[] | undefined): ConceptTemplateName | 'list';
|
|
176
|
+
|
|
64
177
|
/**
|
|
65
178
|
* PageSection — the framework-agnostic block model for Page (linear) mode.
|
|
66
179
|
*
|
|
@@ -300,4 +413,4 @@ declare function materializePageSection(block: Block, options?: MaterializePageS
|
|
|
300
413
|
*/
|
|
301
414
|
declare function materializePageSections(doc: Doc, options?: MaterializePageSectionsOptions): PageSectionMaterialization[];
|
|
302
415
|
|
|
303
|
-
export { AUTHORED_PRESENTATION_LAYOUT as A, type MaterializePageSectionOptions as M, PRESENTATION_KEY as P,
|
|
416
|
+
export { AUTHORED_PRESENTATION_LAYOUT as A, conceptItemCount as B, CONCEPT_MAX_DRAWN as C, conceptItemsProblem as D, conceptTemplate as E, materializePageSection as F, materializePageSections as G, parsePresentationPlan as H, presentationBeatExtras as I, presentationLayoutFits as J, presentationLayoutNeeds as K, presentationLayoutProblem as L, type MaterializePageSectionOptions as M, presentationStat as N, resolvePageStyle as O, PRESENTATION_KEY as P, serializePresentationPlan as Q, CONCEPT_SPECS as a, CONCEPT_TEMPLATES as b, type ConceptDowngrade as c, type ConceptItemsOptions as d, type ConceptSpec as e, type ConceptSpecInput as f, type ConceptTemplateName as g, type MaterializePageSectionsOptions as h, PRESENTATION_LAYOUTS as i, PRESENTATION_LIMITS as j, type PageMedia as k, type PageRichText as l, type PageSection as m, type PageSectionDiagnostic as n, type PageSectionItem as o, type PageSectionMaterialization as p, type PageSectionSource as q, type PageSpatialKind as r, type PageTransformHints as s, type PresentationBeat as t, type PresentationBeatExtras as u, type PresentationBeatLayout as v, type PresentationLayout as w, type PresentationPlan as x, conceptDowngrade as y, conceptDrawsAs as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { at as MediaCut, U as Doc, ar as MediaClip, cQ as MediaFxOpId, cR as MediaFxChain } from '../Doc-DLfTam0R.js';
|
|
2
|
+
export { cS as MEDIA_EDIT_PARAMS, cT as MEDIA_FX_ORDER, cU as MEDIA_FX_SPECS, as as MediaCrop, cV as MediaEditParamKey, cW as MediaEditParamValues, au as MediaEdits, cX as MediaFxOp, cY as canonicalMediaFx, cZ as formatMediaNumber, c_ as hasRenderableFx, c$ as mediaEditHtmlAttribute, d0 as mediaEditValuesFromAttributes, d1 as mediaEditValuesFromParams, d2 as mediaRecipeHash, d3 as mediaRenderKey, d4 as normalizeMediaCrop, d5 as normalizeMediaCuts, d6 as parseMediaCrop, d7 as parseMediaCuts, d8 as parseMediaEdits, d9 as parseMediaFx, da as serializeMediaCrop, db as serializeMediaCuts, dc as serializeMediaEdits, dd as serializeMediaFx } from '../Doc-DLfTam0R.js';
|
|
3
3
|
import '../types-Zk8qYuaT.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -175,6 +175,157 @@ declare function mediaRenderKeyFor(src: string, fx: string | undefined): string
|
|
|
175
175
|
*/
|
|
176
176
|
declare function selectMediaRendersForGc(index: ReadonlyMap<string, MediaRenderEntry>, referencedKeys: ReadonlySet<string>, manifests: ReadonlyMap<string, MediaRenderManifest>, now?: Date, olderThanDays?: number): string[];
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Baked clips — a clip with its whole recipe applied, as one ordinary media
|
|
180
|
+
* file.
|
|
181
|
+
*
|
|
182
|
+
* The player, video export and the CLI apply a recipe as they play: they skip
|
|
183
|
+
* cuts, set the gain, swap in the processed audio. A script-free HTML page or
|
|
184
|
+
* another app cannot. For those the recipe is BAKED: trim, cuts, cleaned-up
|
|
185
|
+
* sound, gain, fades and crop are rendered into a single file that any player
|
|
186
|
+
* plays as is.
|
|
187
|
+
*
|
|
188
|
+
* A baked file is a cache, like an `fx` render. The recipe in the markdown
|
|
189
|
+
* stays the source of truth, the source file is never touched, and a bake is
|
|
190
|
+
* made only on demand (an export that needs it, "Save edited copy") — never
|
|
191
|
+
* merely because a recipe changed. It lives in the same `.mediaEdits/` folder:
|
|
192
|
+
*
|
|
193
|
+
* .mediaEdits/<stem>.<key>.baked.<ext> the baked clip
|
|
194
|
+
* .mediaEdits/<stem>.<key>.baked.json its manifest
|
|
195
|
+
*
|
|
196
|
+
* `<key>` is {@link mediaBakeKey}: 12 hex digits over the source path and
|
|
197
|
+
* everything in the recipe that changes the result. The extra `.baked`
|
|
198
|
+
* segment keeps these apart from `fx` renders, so neither index sees the
|
|
199
|
+
* other's files.
|
|
200
|
+
*
|
|
201
|
+
* Pure and dependency-free.
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
/** Engine identifier. It is part of the key: a new engine version re-bakes. */
|
|
205
|
+
declare const MEDIA_BAKE_ENGINE = "squisq-media-bake/1";
|
|
206
|
+
/** Manifest schema version. */
|
|
207
|
+
declare const MEDIA_BAKE_MANIFEST_VERSION = 1;
|
|
208
|
+
/**
|
|
209
|
+
* Fade applied on each side of a cut so the join does not click. The player
|
|
210
|
+
* and the export mix use the same length.
|
|
211
|
+
*/
|
|
212
|
+
declare const MEDIA_CUT_EDGE_FADE_SEC = 0.01;
|
|
213
|
+
/** The parts of a clip that decide its baked file. */
|
|
214
|
+
type MediaBakeClip = Pick<MediaClip, 'src' | 'kind' | 'clipStart' | 'clipEnd' | 'edits'>;
|
|
215
|
+
/** The clip's cuts that fall inside its trim window — the only ones that matter. */
|
|
216
|
+
declare function mediaBakeCuts(clip: MediaBakeClip): MediaCut[];
|
|
217
|
+
/**
|
|
218
|
+
* True when a player that cannot apply a recipe would play this clip wrongly:
|
|
219
|
+
* it has cuts, cleaned-up audio, a gain, fades, or (video) a crop. A trim
|
|
220
|
+
* alone does not count — a plain player honors it as a media fragment.
|
|
221
|
+
*/
|
|
222
|
+
declare function needsMediaBake(clip: MediaBakeClip): boolean;
|
|
223
|
+
/** True when baking would produce anything other than a copy of the source. */
|
|
224
|
+
declare function canBakeMedia(clip: MediaBakeClip): boolean;
|
|
225
|
+
/**
|
|
226
|
+
* Canonical text of everything that decides a clip's baked file. Recipe
|
|
227
|
+
* fields that do not change the result (`group`, unknown `fx` tokens, a crop
|
|
228
|
+
* on audio, cuts outside the trim) are left out, so they never force a
|
|
229
|
+
* re-bake.
|
|
230
|
+
*/
|
|
231
|
+
declare function mediaBakeSpec(clip: MediaBakeClip): string;
|
|
232
|
+
/** Stable key naming the baked file of a clip. 12 hex digits. */
|
|
233
|
+
declare function mediaBakeKey(clip: MediaBakeClip): string;
|
|
234
|
+
declare function mediaBakeFilePath(src: string, key: string, extension: string): string;
|
|
235
|
+
declare function mediaBakeManifestPath(src: string, key: string): string;
|
|
236
|
+
interface ParsedMediaBakePath {
|
|
237
|
+
stem: string;
|
|
238
|
+
key: string;
|
|
239
|
+
extension: string;
|
|
240
|
+
isManifest: boolean;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Recognize a baked clip or its manifest, with or without a leading media
|
|
244
|
+
* folder (`notes_files/.mediaEdits/take.1a2b3c4d5e6f.baked.m4a`).
|
|
245
|
+
*/
|
|
246
|
+
declare function parseMediaBakePath(path: string): ParsedMediaBakePath | null;
|
|
247
|
+
declare function isMediaBakePath(path: string): boolean;
|
|
248
|
+
/** A baked clip found in a listing. */
|
|
249
|
+
type MediaBakeEntry = MediaRenderEntry;
|
|
250
|
+
/** Index a media listing by bake key. A baked file without a manifest is still indexed. */
|
|
251
|
+
declare function buildMediaBakeIndex(entries: ReadonlyArray<{
|
|
252
|
+
name: string;
|
|
253
|
+
size: number;
|
|
254
|
+
}>): Map<string, MediaBakeEntry>;
|
|
255
|
+
interface MediaBakeManifest {
|
|
256
|
+
version: typeof MEDIA_BAKE_MANIFEST_VERSION;
|
|
257
|
+
/** The source reference exactly as the document writes it. */
|
|
258
|
+
src: string;
|
|
259
|
+
key: string;
|
|
260
|
+
/** The canonical recipe text the key was made from ({@link mediaBakeSpec}). */
|
|
261
|
+
spec: string;
|
|
262
|
+
/** Baked file path, relative to the media root. */
|
|
263
|
+
file: string;
|
|
264
|
+
mimeType: string;
|
|
265
|
+
/** Source byte size at bake time (staleness check), when known. */
|
|
266
|
+
sourceSize: number | null;
|
|
267
|
+
/** Length of the baked clip in seconds. */
|
|
268
|
+
duration: number | null;
|
|
269
|
+
engine: string;
|
|
270
|
+
/** ISO-8601 creation time. */
|
|
271
|
+
createdAt: string;
|
|
272
|
+
}
|
|
273
|
+
/** Validate manifest JSON; null when malformed or from an unknown version. */
|
|
274
|
+
declare function parseMediaBakeManifest(text: string): MediaBakeManifest | null;
|
|
275
|
+
declare function serializeMediaBakeManifest(manifest: MediaBakeManifest): string;
|
|
276
|
+
/**
|
|
277
|
+
* True when a baked clip no longer matches its source: the source file's size
|
|
278
|
+
* differs from bake time (a re-recorded take under the same name).
|
|
279
|
+
*/
|
|
280
|
+
declare function isMediaBakeStale(manifest: MediaBakeManifest, current: {
|
|
281
|
+
sourceSize?: number | null;
|
|
282
|
+
}): boolean;
|
|
283
|
+
/**
|
|
284
|
+
* Baked clips (and their manifests) of the given sources that no current
|
|
285
|
+
* recipe asks for. Unlike `fx` renders these are removed at once rather than
|
|
286
|
+
* aged out: a baked video is large, is made again on demand, and playback
|
|
287
|
+
* never depends on it. A file without a manifest cannot be attributed to a
|
|
288
|
+
* source, so it is left alone.
|
|
289
|
+
*/
|
|
290
|
+
declare function selectSupersededMediaBakes(index: ReadonlyMap<string, MediaBakeEntry>, manifests: ReadonlyMap<string, MediaBakeManifest>, sources: ReadonlySet<string>, referencedKeys: ReadonlySet<string>): string[];
|
|
291
|
+
/** `take.webm` + `m4a` → `take-edited.m4a`: the file name of an edited copy. */
|
|
292
|
+
declare function mediaBakeCopyName(src: string, extension: string): string;
|
|
293
|
+
/** The edited copy's path beside its source (`notes_files/audio/take-edited.m4a`). */
|
|
294
|
+
declare function mediaBakeCopyPath(src: string, extension: string): string;
|
|
295
|
+
/** Kept source ranges as whole-frame offsets from `windowStart`, for slicing decoded audio. */
|
|
296
|
+
declare function mediaBakeFrameRanges(kept: readonly MediaCut[], windowStart: number, sampleRate: number): Array<[number, number]>;
|
|
297
|
+
interface MediaBakeAudioStageOptions {
|
|
298
|
+
/**
|
|
299
|
+
* The frames to keep, as offsets from the first frame fed: ascending and
|
|
300
|
+
* non-overlapping (see {@link mediaBakeFrameRanges}).
|
|
301
|
+
*/
|
|
302
|
+
ranges: ReadonlyArray<readonly [number, number]>;
|
|
303
|
+
sampleRate: number;
|
|
304
|
+
/** Clip gain in dB. */
|
|
305
|
+
gainDb?: number;
|
|
306
|
+
/** Fade-in seconds at the start of the baked clip. */
|
|
307
|
+
fadeInSec?: number;
|
|
308
|
+
/** Fade-out seconds at the end of the baked clip. */
|
|
309
|
+
fadeOutSec?: number;
|
|
310
|
+
}
|
|
311
|
+
interface MediaBakeAudioStage {
|
|
312
|
+
/** Frames the baked clip will hold. */
|
|
313
|
+
readonly totalFrames: number;
|
|
314
|
+
/**
|
|
315
|
+
* Feed the next decoded block (planar, consecutive from the window start).
|
|
316
|
+
* Returns the part of it the baked clip keeps, with gain, fades and the
|
|
317
|
+
* cut-edge fades applied — or null when the whole block is cut.
|
|
318
|
+
*/
|
|
319
|
+
process(block: readonly Float32Array[]): Float32Array[] | null;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* What a recipe's timeline ops do to sound: drop the cut ranges, close the
|
|
323
|
+
* gaps, apply the gain and the two fades, and fade each join a few
|
|
324
|
+
* milliseconds either side so it does not click — the same result the player
|
|
325
|
+
* and the export mix produce live.
|
|
326
|
+
*/
|
|
327
|
+
declare function createMediaBakeAudioStage(options: MediaBakeAudioStageOptions): MediaBakeAudioStage;
|
|
328
|
+
|
|
178
329
|
/**
|
|
179
330
|
* Breath detection for spoken-word recordings.
|
|
180
331
|
*
|
|
@@ -464,4 +615,4 @@ declare const PAUSE_TIGHTEN_DEFAULTS: Readonly<{
|
|
|
464
615
|
/** Cuts that shorten every long pause in the take. */
|
|
465
616
|
declare function detectPauseCuts(frames: readonly BreathFrame[], options?: PauseTightenOptions): MediaCut[];
|
|
466
617
|
|
|
467
|
-
export { type BreathAnalyzer, type BreathDetectOptions, type BreathFrame, type BreathRegion, type DenoiseStage, type Denoiser, type DenoiserFactory, type Limiter, type LimiterOptions, type LoudnessMeter, MEDIA_FX_ENGINE, MEDIA_FX_LOUDNESS_TOLERANCE, MEDIA_FX_SAMPLE_RATE, MEDIA_FX_TRUE_PEAK_CEILING_DB, MEDIA_RENDER_DIR, MEDIA_RENDER_GC_AGE_DAYS, MEDIA_RENDER_MANIFEST_VERSION, MediaCut, type MediaFxAnalysis, MediaFxChain, MediaFxOpId, type MediaFxOptions, type MediaFxRenderOptions, type MediaFxRenderResult, type MediaFxSource, type MediaRenderAnalysis, type MediaRenderEntry, type MediaRenderManifest, type MediaTimeMap, type MediaTimeMapInput, PAUSE_TIGHTEN_DEFAULTS, type ParsedMediaRenderPath, type PauseTightenOptions, type Resampler, analyzeMediaFx, applyBreathEnvelope, breathGainAt, buildMediaRenderIndex, collectDocMediaClips, correctedLoudnessAnalysis, createBreathAnalyzer, createDenoiseStage, createLimiter, createLoudnessMeter, createMediaTimeMap, createResampler, detectBreaths, detectPauseCuts, integratedLoudnessFromSteps, isMediaRenderPath, mediaClipRenderKey, mediaRenderFilePath, mediaRenderKeyFor, mediaRenderManifestPath, mediaRenderStaleness, mediaRenderStem, parseMediaRenderManifest, parseMediaRenderPath, referencedMediaRenderKeys, renderMediaFx, selectMediaRendersForGc, serializeMediaRenderManifest };
|
|
618
|
+
export { type BreathAnalyzer, type BreathDetectOptions, type BreathFrame, type BreathRegion, type DenoiseStage, type Denoiser, type DenoiserFactory, type Limiter, type LimiterOptions, type LoudnessMeter, MEDIA_BAKE_ENGINE, MEDIA_BAKE_MANIFEST_VERSION, MEDIA_CUT_EDGE_FADE_SEC, MEDIA_FX_ENGINE, MEDIA_FX_LOUDNESS_TOLERANCE, MEDIA_FX_SAMPLE_RATE, MEDIA_FX_TRUE_PEAK_CEILING_DB, MEDIA_RENDER_DIR, MEDIA_RENDER_GC_AGE_DAYS, MEDIA_RENDER_MANIFEST_VERSION, type MediaBakeAudioStage, type MediaBakeAudioStageOptions, type MediaBakeClip, type MediaBakeEntry, type MediaBakeManifest, MediaCut, type MediaFxAnalysis, MediaFxChain, MediaFxOpId, type MediaFxOptions, type MediaFxRenderOptions, type MediaFxRenderResult, type MediaFxSource, type MediaRenderAnalysis, type MediaRenderEntry, type MediaRenderManifest, type MediaTimeMap, type MediaTimeMapInput, PAUSE_TIGHTEN_DEFAULTS, type ParsedMediaBakePath, type ParsedMediaRenderPath, type PauseTightenOptions, type Resampler, analyzeMediaFx, applyBreathEnvelope, breathGainAt, buildMediaBakeIndex, buildMediaRenderIndex, canBakeMedia, collectDocMediaClips, correctedLoudnessAnalysis, createBreathAnalyzer, createDenoiseStage, createLimiter, createLoudnessMeter, createMediaBakeAudioStage, createMediaTimeMap, createResampler, detectBreaths, detectPauseCuts, integratedLoudnessFromSteps, isMediaBakePath, isMediaBakeStale, isMediaRenderPath, mediaBakeCopyName, mediaBakeCopyPath, mediaBakeCuts, mediaBakeFilePath, mediaBakeFrameRanges, mediaBakeKey, mediaBakeManifestPath, mediaBakeSpec, mediaClipRenderKey, mediaRenderFilePath, mediaRenderKeyFor, mediaRenderManifestPath, mediaRenderStaleness, mediaRenderStem, needsMediaBake, parseMediaBakeManifest, parseMediaBakePath, parseMediaRenderManifest, parseMediaRenderPath, referencedMediaRenderKeys, renderMediaFx, selectMediaRendersForGc, selectSupersededMediaBakes, serializeMediaBakeManifest, serializeMediaRenderManifest };
|
package/dist/mediaEdit/index.js
CHANGED
|
@@ -5,16 +5,18 @@ import {
|
|
|
5
5
|
} from "../chunk-NEWNNLWN.js";
|
|
6
6
|
import {
|
|
7
7
|
createMediaTimeMap
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-MTIR5PWF.js";
|
|
9
9
|
import {
|
|
10
10
|
MEDIA_EDIT_PARAMS,
|
|
11
11
|
MEDIA_FX_ORDER,
|
|
12
12
|
MEDIA_FX_SPECS,
|
|
13
|
+
canonicalMediaFx,
|
|
13
14
|
formatMediaNumber,
|
|
14
15
|
hasRenderableFx,
|
|
15
16
|
mediaEditHtmlAttribute,
|
|
16
17
|
mediaEditValuesFromAttributes,
|
|
17
18
|
mediaEditValuesFromParams,
|
|
19
|
+
mediaRecipeHash,
|
|
18
20
|
mediaRenderKey,
|
|
19
21
|
normalizeMediaCrop,
|
|
20
22
|
normalizeMediaCuts,
|
|
@@ -26,7 +28,7 @@ import {
|
|
|
26
28
|
serializeMediaCuts,
|
|
27
29
|
serializeMediaEdits,
|
|
28
30
|
serializeMediaFx
|
|
29
|
-
} from "../chunk-
|
|
31
|
+
} from "../chunk-37RLFDIT.js";
|
|
30
32
|
|
|
31
33
|
// src/mediaEdit/renderStore.ts
|
|
32
34
|
var MEDIA_RENDER_DIR = ".mediaEdits";
|
|
@@ -173,6 +175,220 @@ function selectMediaRendersForGc(index, referencedKeys, manifests, now = /* @__P
|
|
|
173
175
|
return paths;
|
|
174
176
|
}
|
|
175
177
|
|
|
178
|
+
// src/mediaEdit/bake.ts
|
|
179
|
+
var MEDIA_BAKE_ENGINE = "squisq-media-bake/1";
|
|
180
|
+
var MEDIA_BAKE_MANIFEST_VERSION = 1;
|
|
181
|
+
var MEDIA_CUT_EDGE_FADE_SEC = 0.01;
|
|
182
|
+
var BAKE_EXTENSIONS = /* @__PURE__ */ new Set(["mp4", "webm", "m4a", "wav"]);
|
|
183
|
+
function mediaBakeCuts(clip) {
|
|
184
|
+
return normalizeMediaCuts(
|
|
185
|
+
clip.edits?.cuts ?? [],
|
|
186
|
+
Math.max(0, clip.clipStart ?? 0),
|
|
187
|
+
clip.clipEnd ?? Number.POSITIVE_INFINITY
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
function needsMediaBake(clip) {
|
|
191
|
+
const edits = clip.edits;
|
|
192
|
+
if (!edits) return false;
|
|
193
|
+
return mediaBakeCuts(clip).length > 0 || hasRenderableFx(edits.fx) || Boolean(edits.gain) || Boolean(edits.fadeIn) || Boolean(edits.fadeOut) || clip.kind === "video" && edits.crop != null;
|
|
194
|
+
}
|
|
195
|
+
function canBakeMedia(clip) {
|
|
196
|
+
return needsMediaBake(clip) || (clip.clipStart ?? 0) > 0 || clip.clipEnd != null;
|
|
197
|
+
}
|
|
198
|
+
function mediaBakeSpec(clip) {
|
|
199
|
+
const edits = clip.edits;
|
|
200
|
+
const seconds = (value) => value ? formatMediaNumber(value) : "";
|
|
201
|
+
return [
|
|
202
|
+
MEDIA_BAKE_ENGINE,
|
|
203
|
+
clip.src,
|
|
204
|
+
clip.kind,
|
|
205
|
+
`trim=${seconds(clip.clipStart)}-${clip.clipEnd != null ? formatMediaNumber(clip.clipEnd) : ""}`,
|
|
206
|
+
`cuts=${serializeMediaCuts(mediaBakeCuts(clip))}`,
|
|
207
|
+
`fx=${hasRenderableFx(edits?.fx) ? canonicalMediaFx(edits.fx) : ""}`,
|
|
208
|
+
`gain=${edits?.gain ? formatMediaNumber(edits.gain, 2) : ""}`,
|
|
209
|
+
`fadeIn=${seconds(edits?.fadeIn)}`,
|
|
210
|
+
`fadeOut=${seconds(edits?.fadeOut)}`,
|
|
211
|
+
`crop=${clip.kind === "video" && edits?.crop ? serializeMediaCrop(edits.crop) : ""}`
|
|
212
|
+
].join("\n");
|
|
213
|
+
}
|
|
214
|
+
function mediaBakeKey(clip) {
|
|
215
|
+
return mediaRecipeHash(mediaBakeSpec(clip));
|
|
216
|
+
}
|
|
217
|
+
function mediaBakeFilePath(src, key, extension) {
|
|
218
|
+
return `${MEDIA_RENDER_DIR}/${mediaRenderStem(src)}.${key}.baked.${extension}`;
|
|
219
|
+
}
|
|
220
|
+
function mediaBakeManifestPath(src, key) {
|
|
221
|
+
return `${MEDIA_RENDER_DIR}/${mediaRenderStem(src)}.${key}.baked.json`;
|
|
222
|
+
}
|
|
223
|
+
function parseMediaBakePath(path) {
|
|
224
|
+
const segments = path.split("/");
|
|
225
|
+
const dirIndex = segments.length - 2;
|
|
226
|
+
if (dirIndex < 0 || segments[dirIndex] !== MEDIA_RENDER_DIR) return null;
|
|
227
|
+
const match = segments[segments.length - 1].match(/^(.+)\.([0-9a-f]{12})\.baked\.([a-z0-9]+)$/);
|
|
228
|
+
if (!match) return null;
|
|
229
|
+
const extension = match[3];
|
|
230
|
+
const isManifest = extension === "json";
|
|
231
|
+
if (!isManifest && !BAKE_EXTENSIONS.has(extension)) return null;
|
|
232
|
+
return { stem: match[1], key: match[2], extension, isManifest };
|
|
233
|
+
}
|
|
234
|
+
function isMediaBakePath(path) {
|
|
235
|
+
return parseMediaBakePath(path) != null;
|
|
236
|
+
}
|
|
237
|
+
function buildMediaBakeIndex(entries) {
|
|
238
|
+
const manifests = /* @__PURE__ */ new Map();
|
|
239
|
+
const index = /* @__PURE__ */ new Map();
|
|
240
|
+
for (const entry of entries) {
|
|
241
|
+
const parsed = parseMediaBakePath(entry.name);
|
|
242
|
+
if (!parsed) continue;
|
|
243
|
+
if (parsed.isManifest) manifests.set(parsed.key, entry.name);
|
|
244
|
+
else {
|
|
245
|
+
index.set(parsed.key, {
|
|
246
|
+
key: parsed.key,
|
|
247
|
+
path: entry.name,
|
|
248
|
+
manifestPath: null,
|
|
249
|
+
size: entry.size
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
for (const [key, entry] of index) entry.manifestPath = manifests.get(key) ?? null;
|
|
254
|
+
return index;
|
|
255
|
+
}
|
|
256
|
+
function parseMediaBakeManifest(text) {
|
|
257
|
+
let raw;
|
|
258
|
+
try {
|
|
259
|
+
raw = JSON.parse(text);
|
|
260
|
+
} catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
if (!raw || typeof raw !== "object") return null;
|
|
264
|
+
const m = raw;
|
|
265
|
+
const str = (v) => typeof v === "string";
|
|
266
|
+
const numOrNull = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
267
|
+
if (m.version !== MEDIA_BAKE_MANIFEST_VERSION) return null;
|
|
268
|
+
if (!str(m.src) || !str(m.key) || !str(m.spec) || !str(m.file) || !str(m.mimeType)) return null;
|
|
269
|
+
if (!str(m.engine) || !str(m.createdAt)) return null;
|
|
270
|
+
return {
|
|
271
|
+
version: MEDIA_BAKE_MANIFEST_VERSION,
|
|
272
|
+
src: m.src,
|
|
273
|
+
key: m.key,
|
|
274
|
+
spec: m.spec,
|
|
275
|
+
file: m.file,
|
|
276
|
+
mimeType: m.mimeType,
|
|
277
|
+
sourceSize: numOrNull(m.sourceSize),
|
|
278
|
+
duration: numOrNull(m.duration),
|
|
279
|
+
engine: m.engine,
|
|
280
|
+
createdAt: m.createdAt
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function serializeMediaBakeManifest(manifest) {
|
|
284
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
function isMediaBakeStale(manifest, current) {
|
|
288
|
+
return current.sourceSize != null && manifest.sourceSize != null && current.sourceSize !== manifest.sourceSize;
|
|
289
|
+
}
|
|
290
|
+
function selectSupersededMediaBakes(index, manifests, sources, referencedKeys) {
|
|
291
|
+
const paths = [];
|
|
292
|
+
for (const [key, entry] of index) {
|
|
293
|
+
if (referencedKeys.has(key)) continue;
|
|
294
|
+
const manifest = manifests.get(key);
|
|
295
|
+
if (!manifest || !sources.has(manifest.src)) continue;
|
|
296
|
+
paths.push(entry.path);
|
|
297
|
+
if (entry.manifestPath) paths.push(entry.manifestPath);
|
|
298
|
+
}
|
|
299
|
+
return paths;
|
|
300
|
+
}
|
|
301
|
+
function mediaBakeCopyName(src, extension) {
|
|
302
|
+
const base = src.split(/[?#]/, 1)[0].split("/").pop() || "media";
|
|
303
|
+
const stem = base.replace(/\.[^.]+$/, "") || "media";
|
|
304
|
+
return `${stem}-edited.${extension}`;
|
|
305
|
+
}
|
|
306
|
+
function mediaBakeCopyPath(src, extension) {
|
|
307
|
+
const path = src.split(/[?#]/, 1)[0];
|
|
308
|
+
const slash = path.lastIndexOf("/");
|
|
309
|
+
const name = mediaBakeCopyName(src, extension);
|
|
310
|
+
return slash < 0 ? name : `${path.slice(0, slash + 1)}${name}`;
|
|
311
|
+
}
|
|
312
|
+
function mediaBakeFrameRanges(kept, windowStart, sampleRate) {
|
|
313
|
+
const ranges = [];
|
|
314
|
+
for (const range of kept) {
|
|
315
|
+
const start = Math.max(0, Math.round((range.start - windowStart) * sampleRate));
|
|
316
|
+
const end = Math.round((range.end - windowStart) * sampleRate);
|
|
317
|
+
if (end > start) ranges.push([start, end]);
|
|
318
|
+
}
|
|
319
|
+
return ranges;
|
|
320
|
+
}
|
|
321
|
+
function createMediaBakeAudioStage(options) {
|
|
322
|
+
const { ranges, sampleRate } = options;
|
|
323
|
+
const gain = options.gainDb ? Math.pow(10, options.gainDb / 20) : 1;
|
|
324
|
+
const totalFrames = ranges.reduce((sum, [start, end]) => sum + (end - start), 0);
|
|
325
|
+
const fadeIn = Math.min(totalFrames, Math.round((options.fadeInSec ?? 0) * sampleRate));
|
|
326
|
+
const fadeOut = Math.min(totalFrames, Math.round((options.fadeOutSec ?? 0) * sampleRate));
|
|
327
|
+
const edge = Math.max(1, Math.round(MEDIA_CUT_EDGE_FADE_SEC * sampleRate));
|
|
328
|
+
const joins = [];
|
|
329
|
+
let cumulative = 0;
|
|
330
|
+
for (let i = 0; i < ranges.length - 1; i++) {
|
|
331
|
+
cumulative += ranges[i][1] - ranges[i][0];
|
|
332
|
+
joins.push(cumulative);
|
|
333
|
+
}
|
|
334
|
+
const levelAt = (frame, joinIndex2) => {
|
|
335
|
+
let level = gain;
|
|
336
|
+
if (frame < fadeIn) level *= frame / fadeIn;
|
|
337
|
+
if (frame >= totalFrames - fadeOut) level *= (totalFrames - frame) / fadeOut;
|
|
338
|
+
const join = joins[joinIndex2];
|
|
339
|
+
if (join !== void 0) {
|
|
340
|
+
const distance = frame < join ? join - 1 - frame : frame - join;
|
|
341
|
+
if (distance < edge) level *= distance / edge;
|
|
342
|
+
}
|
|
343
|
+
return level;
|
|
344
|
+
};
|
|
345
|
+
let fed = 0;
|
|
346
|
+
let played = 0;
|
|
347
|
+
let rangeIndex = 0;
|
|
348
|
+
let joinIndex = 0;
|
|
349
|
+
return {
|
|
350
|
+
totalFrames,
|
|
351
|
+
process(block) {
|
|
352
|
+
const length = block[0]?.length ?? 0;
|
|
353
|
+
const blockStart = fed;
|
|
354
|
+
const blockEnd = fed + length;
|
|
355
|
+
fed = blockEnd;
|
|
356
|
+
const slices = [];
|
|
357
|
+
let kept = 0;
|
|
358
|
+
while (rangeIndex < ranges.length) {
|
|
359
|
+
const [rangeStart, rangeEnd] = ranges[rangeIndex];
|
|
360
|
+
if (rangeStart >= blockEnd) break;
|
|
361
|
+
const from = Math.max(rangeStart, blockStart);
|
|
362
|
+
const to = Math.min(rangeEnd, blockEnd);
|
|
363
|
+
if (to > from) {
|
|
364
|
+
slices.push([from - blockStart, to - blockStart]);
|
|
365
|
+
kept += to - from;
|
|
366
|
+
}
|
|
367
|
+
if (rangeEnd > blockEnd) break;
|
|
368
|
+
rangeIndex++;
|
|
369
|
+
}
|
|
370
|
+
if (kept === 0) return null;
|
|
371
|
+
const out = block.map(() => new Float32Array(kept));
|
|
372
|
+
let at = 0;
|
|
373
|
+
for (const [from, to] of slices) {
|
|
374
|
+
for (let c = 0; c < block.length; c++) out[c].set(block[c].subarray(from, to), at);
|
|
375
|
+
at += to - from;
|
|
376
|
+
}
|
|
377
|
+
const first = played;
|
|
378
|
+
const last = played + kept;
|
|
379
|
+
played = last;
|
|
380
|
+
for (let i = 0; i < kept; i++) {
|
|
381
|
+
const frame = first + i;
|
|
382
|
+
while (joinIndex < joins.length && joins[joinIndex] + edge <= frame) joinIndex++;
|
|
383
|
+
const level = levelAt(frame, joinIndex);
|
|
384
|
+
if (level === 1) continue;
|
|
385
|
+
for (const channel of out) channel[i] *= level;
|
|
386
|
+
}
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
176
392
|
// src/mediaEdit/dsp/breath.ts
|
|
177
393
|
var HOP_SEC = 0.01;
|
|
178
394
|
var FRAME_SEC = 0.02;
|
|
@@ -932,6 +1148,9 @@ function detectPauseCuts(frames, options = {}) {
|
|
|
932
1148
|
return cuts;
|
|
933
1149
|
}
|
|
934
1150
|
export {
|
|
1151
|
+
MEDIA_BAKE_ENGINE,
|
|
1152
|
+
MEDIA_BAKE_MANIFEST_VERSION,
|
|
1153
|
+
MEDIA_CUT_EDGE_FADE_SEC,
|
|
935
1154
|
MEDIA_EDIT_PARAMS,
|
|
936
1155
|
MEDIA_FX_ENGINE,
|
|
937
1156
|
MEDIA_FX_LOUDNESS_TOLERANCE,
|
|
@@ -946,13 +1165,17 @@ export {
|
|
|
946
1165
|
analyzeMediaFx,
|
|
947
1166
|
applyBreathEnvelope,
|
|
948
1167
|
breathGainAt,
|
|
1168
|
+
buildMediaBakeIndex,
|
|
949
1169
|
buildMediaRenderIndex,
|
|
1170
|
+
canBakeMedia,
|
|
1171
|
+
canonicalMediaFx,
|
|
950
1172
|
collectDocMediaClips,
|
|
951
1173
|
correctedLoudnessAnalysis,
|
|
952
1174
|
createBreathAnalyzer,
|
|
953
1175
|
createDenoiseStage,
|
|
954
1176
|
createLimiter,
|
|
955
1177
|
createLoudnessMeter,
|
|
1178
|
+
createMediaBakeAudioStage,
|
|
956
1179
|
createMediaTimeMap,
|
|
957
1180
|
createResampler,
|
|
958
1181
|
detectBreaths,
|
|
@@ -960,19 +1183,33 @@ export {
|
|
|
960
1183
|
formatMediaNumber,
|
|
961
1184
|
hasRenderableFx,
|
|
962
1185
|
integratedLoudnessFromSteps,
|
|
1186
|
+
isMediaBakePath,
|
|
1187
|
+
isMediaBakeStale,
|
|
963
1188
|
isMediaRenderPath,
|
|
1189
|
+
mediaBakeCopyName,
|
|
1190
|
+
mediaBakeCopyPath,
|
|
1191
|
+
mediaBakeCuts,
|
|
1192
|
+
mediaBakeFilePath,
|
|
1193
|
+
mediaBakeFrameRanges,
|
|
1194
|
+
mediaBakeKey,
|
|
1195
|
+
mediaBakeManifestPath,
|
|
1196
|
+
mediaBakeSpec,
|
|
964
1197
|
mediaClipRenderKey,
|
|
965
1198
|
mediaEditHtmlAttribute,
|
|
966
1199
|
mediaEditValuesFromAttributes,
|
|
967
1200
|
mediaEditValuesFromParams,
|
|
1201
|
+
mediaRecipeHash,
|
|
968
1202
|
mediaRenderFilePath,
|
|
969
1203
|
mediaRenderKey,
|
|
970
1204
|
mediaRenderKeyFor,
|
|
971
1205
|
mediaRenderManifestPath,
|
|
972
1206
|
mediaRenderStaleness,
|
|
973
1207
|
mediaRenderStem,
|
|
1208
|
+
needsMediaBake,
|
|
974
1209
|
normalizeMediaCrop,
|
|
975
1210
|
normalizeMediaCuts,
|
|
1211
|
+
parseMediaBakeManifest,
|
|
1212
|
+
parseMediaBakePath,
|
|
976
1213
|
parseMediaCrop,
|
|
977
1214
|
parseMediaCuts,
|
|
978
1215
|
parseMediaEdits,
|
|
@@ -982,6 +1219,8 @@ export {
|
|
|
982
1219
|
referencedMediaRenderKeys,
|
|
983
1220
|
renderMediaFx,
|
|
984
1221
|
selectMediaRendersForGc,
|
|
1222
|
+
selectSupersededMediaBakes,
|
|
1223
|
+
serializeMediaBakeManifest,
|
|
985
1224
|
serializeMediaCrop,
|
|
986
1225
|
serializeMediaCuts,
|
|
987
1226
|
serializeMediaEdits,
|