@getvitops/utils 2.0.0 → 2.1.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.
@@ -0,0 +1,277 @@
1
+ //#region src/media/types.d.ts
2
+ /**
3
+ * The media module's own option surface.
4
+ *
5
+ * Everything here describes *how to encode*, never *what the site is* — which is
6
+ * why there is no `SiteConfig` block behind it. `legal`, `icons` and `indexing`
7
+ * are anchored to a site config because what they emit describes the site; an
8
+ * encoder setting describes a build step.
9
+ */
10
+ /** What a source video is turned into. */
11
+ type OutputKind = 'webm' | 'mp4' | 'poster';
12
+ /** The default set: a modern codec, a universal fallback, and a poster frame. */
13
+ declare const DEFAULT_OUTPUTS: OutputKind[];
14
+ /** File extension per output kind. */
15
+ declare const OUTPUT_EXT: Record<OutputKind, string>;
16
+ /**
17
+ * Bumped when the ffmpeg arguments in `ffmpeg.ts` change in a way that alters the
18
+ * output bytes. It is part of the settings hash, so bumping it re-encodes every
19
+ * source on the next run.
20
+ *
21
+ * Bump it deliberately and rarely. Consumers commit their processed outputs (see
22
+ * the README), so every bump is a large binary diff in someone's repository —
23
+ * that cost is worth paying for a real encoder fix and not for a tidy-up.
24
+ */
25
+ declare const ENCODER_VERSION = 1;
26
+ /** How to encode. Every field here feeds the settings hash. */
27
+ interface MediaConfig {
28
+ /**
29
+ * Cap the output width in pixels, preserving aspect ratio. Default 1920.
30
+ *
31
+ * A source larger than its display size is the usual reason a hero video is
32
+ * enormous, so this is on by default rather than opt-in. `0` disables scaling.
33
+ */
34
+ maxWidth?: number | undefined;
35
+ /**
36
+ * Constant-quality level, on **VP9's CRF scale (0–63, lower is better)**.
37
+ * Default 32, which is the range VP9's own guidance recommends for 1080p.
38
+ *
39
+ * H.264 uses a different scale (0–51), so the MP4 fallback is encoded at
40
+ * `round(crf * 0.72)` — 23 at the default, x264's own default. One knob, because
41
+ * two would drift apart and nobody would know which one mattered.
42
+ */
43
+ crf?: number | undefined;
44
+ /**
45
+ * Optional ceiling, e.g. `'2M'`. Absent by default: a bitrate cap makes the
46
+ * encode constrained-quality rather than constant-quality, which is right when
47
+ * you are protecting a data budget and wrong as a default, because the correct
48
+ * cap depends on the resolution you are capping.
49
+ */
50
+ maxBitrate?: string | undefined;
51
+ /**
52
+ * Keep the audio track. Default false — the common case is a muted autoplay
53
+ * loop, where the audio stream is bytes nobody will ever hear.
54
+ */
55
+ audio?: boolean | undefined;
56
+ /**
57
+ * Timestamp (seconds) the poster frame is taken from. Default 0.
58
+ *
59
+ * Frame 0 is often black on a clip that fades in, which is worth knowing before
60
+ * concluding the poster is broken.
61
+ */
62
+ posterTime?: number | undefined;
63
+ /** Which outputs to produce. Default `DEFAULT_OUTPUTS`. */
64
+ outputs?: OutputKind[] | undefined;
65
+ }
66
+ /** A discovered source video. */
67
+ interface MediaSource {
68
+ /** Absolute path on disk. */
69
+ path: string;
70
+ /**
71
+ * Path relative to the raw directory, POSIX-separated — the manifest key.
72
+ * Relative so a manifest stays valid when the project moves or is cloned
73
+ * somewhere else, which is the whole point of committing it.
74
+ */
75
+ id: string;
76
+ /** sha256 of the file contents, truncated. */
77
+ hash: string;
78
+ }
79
+ /** One file an encode will write. */
80
+ interface MediaOutput {
81
+ kind: OutputKind;
82
+ /** Path relative to the output directory, POSIX-separated. */
83
+ file: string;
84
+ }
85
+ /** One source and everything decided about it. */
86
+ interface MediaJob {
87
+ source: MediaSource;
88
+ outputs: MediaOutput[];
89
+ /** Hash of the settings these outputs were produced under. */
90
+ settings: string;
91
+ /** Why this job needs no work. Present iff it is being skipped. */
92
+ skip?: string | undefined;
93
+ }
94
+ /** The complete account of a run, decided before any of it happens. */
95
+ interface MediaPlan {
96
+ jobs: MediaJob[];
97
+ /** Things the operator needs to read. Printed by `--dry` and by a real run. */
98
+ notes: string[];
99
+ }
100
+ /** One manifest record: what these outputs were made from, and under what settings. */
101
+ interface MediaManifestEntry {
102
+ /** `MediaSource.hash` at the time of encoding. */
103
+ source: string;
104
+ /** Settings hash at the time of encoding. */
105
+ settings: string;
106
+ /** Output paths relative to the output directory. */
107
+ outputs: string[];
108
+ }
109
+ /**
110
+ * The cache. A committed artifact, so it carries no timestamp and its keys are
111
+ * sorted — a rebuild of unchanged sources must produce a byte-identical file, or
112
+ * it shows up as noise in every diff.
113
+ */
114
+ interface MediaManifest {
115
+ version: 1;
116
+ entries: Record<string, MediaManifestEntry>;
117
+ }
118
+ //#endregion
119
+ //#region src/media/discover.d.ts
120
+ /** Container formats worth accepting as input. */
121
+ declare const VIDEO_EXT: ReadonlySet<string>;
122
+ /** Directories that never hold hand-managed source assets. */
123
+ declare const DISCOVER_SKIP: ReadonlySet<string>;
124
+ /** sha256 of the file contents, streamed. Truncated — 16 hex chars is 64 bits. */
125
+ declare function hashFile(path: string): Promise<string>;
126
+ /**
127
+ * Every video under `dir`, with its content hash.
128
+ *
129
+ * Sorted by `id` so a plan — and therefore the manifest and the log — comes out in
130
+ * the same order on every machine, regardless of what the filesystem hands back.
131
+ */
132
+ declare function discoverVideos(dir: string): Promise<MediaSource[]>;
133
+ //#endregion
134
+ //#region src/media/plan.d.ts
135
+ /** Defaults, resolved once so the settings hash is computed over real values. */
136
+ interface ResolvedMediaConfig {
137
+ maxWidth: number;
138
+ crf: number;
139
+ maxBitrate: string | undefined;
140
+ audio: boolean;
141
+ posterTime: number;
142
+ outputs: OutputKind[];
143
+ }
144
+ /**
145
+ * Injected existence probe — `plan.ts` touches no filesystem itself.
146
+ *
147
+ * Takes a path relative to the output directory. The same injection shape as
148
+ * `collectEntries(source, read)` in `indexing/sitemap.ts`.
149
+ */
150
+ type OutputProbe = (relativePath: string) => boolean;
151
+ interface MediaPlanInput {
152
+ sources: MediaSource[];
153
+ config?: MediaConfig | undefined;
154
+ /** The previous run's manifest, if there is a usable one. */
155
+ previous?: MediaManifest | undefined;
156
+ /** Whether an output file is already on disk. Defaults to "nothing exists". */
157
+ probe?: OutputProbe | undefined;
158
+ /** `--force`: re-encode regardless of the cache. */
159
+ force?: boolean | undefined;
160
+ }
161
+ /** Fill in defaults. Exported because the encoder and the hash must agree on them. */
162
+ declare function resolveConfig(config?: MediaConfig): ResolvedMediaConfig;
163
+ /**
164
+ * Hash the settings that decide the output bytes.
165
+ *
166
+ * Order-independent in the one place it could bite (`outputs` is sorted), so
167
+ * re-ordering the list in a config doesn't re-encode a whole library.
168
+ */
169
+ declare function settingsHash(config: ResolvedMediaConfig): string;
170
+ /**
171
+ * Decide what to encode.
172
+ *
173
+ * A job is skipped only when all three hold: the source contents hash the same as
174
+ * last time, the settings hash the same, and every declared output is still on
175
+ * disk. Anything else re-encodes — including an absent or unreadable manifest,
176
+ * which reads as "encode everything" and never as "everything is done".
177
+ *
178
+ * Throws on an output collision (`hero.mp4` and `hero.mov` in one directory both
179
+ * want `hero.webm`). That is a layout mistake rather than a runtime condition, and
180
+ * the failure it would otherwise cause — two sources overwriting each other every
181
+ * run, each invalidating the other's cache entry — is invisible from the outside.
182
+ */
183
+ declare function planMedia(input: MediaPlanInput): MediaPlan;
184
+ /** The jobs that will actually run. */
185
+ declare const pendingJobs: (plan: MediaPlan) => MediaJob[];
186
+ /** Human-readable account of a plan, for `--dry` and for the run summary. */
187
+ declare function formatPlan(plan: MediaPlan): string;
188
+ //#endregion
189
+ //#region src/media/ffmpeg.d.ts
190
+ /**
191
+ * Fail early and clearly when ffmpeg isn't installed.
192
+ *
193
+ * Deliberately not the `optimizePng` shape in `favicon.ts`, which swallows a
194
+ * missing `oxipng` and warns once. That is right for a best-effort lossless crush,
195
+ * where skipping costs a few kilobytes. It is wrong here: skipping produces no
196
+ * output at all, and a build that carries on would ship the page referencing a
197
+ * video that was never encoded.
198
+ */
199
+ declare function requireFfmpeg(): Promise<void>;
200
+ /**
201
+ * VP9/WebM, two passes.
202
+ *
203
+ * The pass log goes into a fresh temp directory rather than the working directory.
204
+ * ffmpeg's default (`ffmpeg2pass-0.log`, in cwd) makes cleanup cwd-dependent and
205
+ * makes two concurrent encodes silently share one log — the second encode's rate
206
+ * control then reads the first clip's statistics.
207
+ */
208
+ declare function encodeWebm(input: string, output: string, config: ResolvedMediaConfig): Promise<void>;
209
+ /**
210
+ * H.264/MP4 — the fallback that makes `<video>` work on older iOS and in the
211
+ * in-app webviews that still don't decode VP9.
212
+ *
213
+ * `+faststart` moves the moov atom to the front. Without it the browser has to
214
+ * download the whole file before it can show frame one, which on a hero video
215
+ * looks exactly like the optimisation didn't work.
216
+ */
217
+ declare function encodeMp4(input: string, output: string, config: ResolvedMediaConfig): Promise<void>;
218
+ /** The poster frame. `-ss` before `-i` so the seek is a fast one. */
219
+ declare function extractPoster(input: string, output: string, config: ResolvedMediaConfig): Promise<void>;
220
+ //#endregion
221
+ //#region src/media/manifest.d.ts
222
+ /** Default location, relative to the consumer's project root. */
223
+ declare const MEDIA_MANIFEST_PATH = ".vitops/media-manifest.json";
224
+ /**
225
+ * Read the manifest, or `undefined` when there isn't a usable one.
226
+ *
227
+ * A corrupt or wrong-version file reads as absent, which re-encodes everything.
228
+ * The opposite reading — treating it as an empty but valid manifest — is the same
229
+ * shape of bug `readSnapshot` avoids, except here it would be silent: nothing
230
+ * would encode, and the build would reference outputs that were never written.
231
+ */
232
+ declare function readManifest(path: string): MediaManifest | undefined;
233
+ /** Build a manifest with its keys and output lists in a stable order. */
234
+ declare function toManifest(entries: Record<string, MediaManifestEntry>): MediaManifest;
235
+ /**
236
+ * Write the manifest, creating its directory.
237
+ *
238
+ * Record a job only once its outputs are on disk. Writing eagerly would mark a
239
+ * source as encoded that isn't, and because the next run trusts this file, the
240
+ * result is a missing video that no rebuild ever fixes.
241
+ */
242
+ declare function writeManifest(path: string, manifest: MediaManifest): void;
243
+ //#endregion
244
+ //#region src/media/index.d.ts
245
+ interface ProcessMediaOptions {
246
+ /** Directory of unprocessed sources. Walked recursively. */
247
+ raw: string;
248
+ /** Where the encoded outputs go. Structure under `raw` is preserved. */
249
+ out: string;
250
+ config?: MediaConfig | undefined;
251
+ /** Cache location. Default `.vitops/media-manifest.json`. */
252
+ manifest?: string | undefined;
253
+ /** Re-encode regardless of the cache. */
254
+ force?: boolean | undefined;
255
+ /** Decide everything, run nothing. */
256
+ dry?: boolean | undefined;
257
+ /** Called before each source is encoded — these take minutes, so say so. */
258
+ onProgress?: ((message: string) => void) | undefined;
259
+ }
260
+ interface ProcessMediaResult {
261
+ plan: MediaPlan;
262
+ /** Outputs written this run, relative to `out`. */
263
+ written: string[];
264
+ /** Sources that needed no work. */
265
+ skipped: number;
266
+ }
267
+ /**
268
+ * Encode everything under `raw` that has changed since the last run.
269
+ *
270
+ * Callers get the cache for free because it lives here rather than in either of
271
+ * them: `vitops media` and the Astro integration are equally incremental, since
272
+ * they call this same function.
273
+ */
274
+ declare function processMedia(options: ProcessMediaOptions): Promise<ProcessMediaResult>;
275
+ //#endregion
276
+ export { DEFAULT_OUTPUTS, DISCOVER_SKIP, ENCODER_VERSION, MEDIA_MANIFEST_PATH, type MediaConfig, type MediaJob, type MediaManifest, type MediaManifestEntry, type MediaOutput, type MediaPlan, type MediaPlanInput, type MediaSource, OUTPUT_EXT, type OutputKind, type OutputProbe, ProcessMediaOptions, ProcessMediaResult, type ResolvedMediaConfig, VIDEO_EXT, discoverVideos, encodeMp4, encodeWebm, extractPoster, formatPlan, hashFile, pendingJobs, planMedia, processMedia, readManifest, requireFfmpeg, resolveConfig, settingsHash, toManifest, writeManifest };
277
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/media/types.ts","../../src/media/discover.ts","../../src/media/plan.ts","../../src/media/ffmpeg.ts","../../src/media/manifest.ts","../../src/media/index.ts"],"mappings":"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"}