bytecut 0.2.1 → 0.3.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.
Files changed (27) hide show
  1. package/README.md +18 -4
  2. package/dist/{audit-EBKL6L65.js → audit-RKJNHNDZ.js} +11 -11
  3. package/dist/{check-XTTU5SCZ.js → check-MN7R7XOM.js} +10 -10
  4. package/dist/{chunk-IZ4JEL3Q.js → chunk-4AM2CCML.js} +1 -1
  5. package/dist/{chunk-4FIB6VNC.js → chunk-7RHHOWQT.js} +1 -1
  6. package/dist/{chunk-KN5QOB7R.js → chunk-ABHEWNYQ.js} +4 -4
  7. package/dist/{chunk-TCQFQ354.js → chunk-CSDZGNUN.js} +14 -3
  8. package/dist/{chunk-WI4ZO7GP.js → chunk-DZLFVN4G.js} +14 -11
  9. package/dist/{chunk-PQRBLHCC.js → chunk-EM7JMQND.js} +1 -1
  10. package/dist/{chunk-HLBNRB3S.js → chunk-GNW3OK5U.js} +198 -44
  11. package/dist/{chunk-RZ2V4PCS.js → chunk-JMMXC5UA.js} +3 -3
  12. package/dist/{chunk-FY53B4NB.js → chunk-KJK7XGWA.js} +24 -13
  13. package/dist/{chunk-QOG2F4SF.js → chunk-L5HLKZDW.js} +2 -2
  14. package/dist/{chunk-MV6J66AP.js → chunk-MUE3L5XT.js} +1 -1
  15. package/dist/{chunk-SKANYRVT.js → chunk-NOTYXNMY.js} +1 -1
  16. package/dist/{chunk-3PF6BBFK.js → chunk-PE5NKZCH.js} +18 -8
  17. package/dist/{chunk-S3FQYUTI.js → chunk-SMR2764T.js} +1 -1
  18. package/dist/{chunk-7DHE25UE.js → chunk-UZ3Z2T5I.js} +1 -1
  19. package/dist/cli.js +22 -22
  20. package/dist/{compare-MPPGTUC5.js → compare-XRMDMCZL.js} +8 -8
  21. package/dist/{doctor-PWCUEBD5.js → doctor-YYTLIOVG.js} +5 -5
  22. package/dist/{explain-LKOB7FRT.js → explain-GZWGHYRY.js} +12 -7
  23. package/dist/{init-CWDWVJ55.js → init-3IQAWWBW.js} +4 -4
  24. package/dist/{optimize-TLJG7UYF.js → optimize-BDPL4Y2Z.js} +11 -11
  25. package/dist/{snippet-IIPVF6PS.js → snippet-6P7CMROL.js} +7 -7
  26. package/dist/{tui-6WSMCHZS.js → tui-ETSPQFXB.js} +29 -17
  27. package/package.json +1 -1
package/README.md CHANGED
@@ -74,15 +74,26 @@ Everything is optional. Config is only needed to make a choice stick. Put it in
74
74
  "maxSize": "150 KB",
75
75
  "floor": 65
76
76
  },
77
- "loop": { "extends": "background-video", "vmaf": 92 }
77
+ "loop": { "extends": "background-video", "vmaf": 92 },
78
+ "promo": {
79
+ "extends": "content-video",
80
+ "ssimulacra2": 70,
81
+ "floor": 55,
82
+ "maxSize": "5 MB"
83
+ }
84
+ },
85
+ "assets": {
86
+ "public/gallery/**": "gallery",
87
+ "public/loops/*.mp4": "loop",
88
+ "public/promo.mp4": "promo"
78
89
  },
79
- "assets": { "public/gallery/**": "gallery", "public/loops/*.mp4": "loop" },
80
90
  "budgets": { "maxFileSize": "250 KB", "maxTotalSize": "5 MB" }
81
91
  }
82
92
  ```
83
93
 
84
94
  - **Presets**: built-in `photo`, `hero`, `icon`, `background-video`, `content-video` and
85
- `gif-to-video`. Add your own with `extends`; the first matching `assets` glob wins.
95
+ `gif-to-video`. Add your own with `extends`; the first matching `assets` glob wins. In the
96
+ TUI, `p` and `/preset` offer image presets for images and video presets for videos and GIFs.
86
97
  - **Size**: `maxEdge` caps the longest side; `widths` lists widths instead (a responsive set).
87
98
  - **Formats**: `jpg` (default), `png`, `webp`, `avif`. A `<picture>` lists AVIF, then WebP, and
88
99
  the JPEG is the `<img>`.
@@ -98,7 +109,10 @@ that still meets it:
98
109
  confirms on the full-size output.
99
110
  - `maxSize` with a required `floor` lowers quality, then steps the width down, to fit a size
100
111
  cap. A constraint that cannot be met writes nothing and fails the run; it is never silently
101
- exceeded.
112
+ exceeded. On video the cap is per output file (AV1 and H.264 each): the CRF is raised until
113
+ the predicted size fits, never past the `floor`, and then the width steps down by a fifth at a
114
+ time, never below half. The finished file is measured, not just predicted. A narrower file is
115
+ named for its real width, e.g. `promo-1228.h264.mp4`.
102
116
  - `vmaf: 92` (video) uses ffmpeg's `libvmaf`. Without it, the preset's fixed quality is used
103
117
  and a warning says so.
104
118
 
@@ -4,16 +4,16 @@ import {
4
4
  } from "./chunk-LTWUPQDT.js";
5
5
  import {
6
6
  audit
7
- } from "./chunk-WI4ZO7GP.js";
7
+ } from "./chunk-DZLFVN4G.js";
8
8
  import {
9
9
  formatAuditReport
10
- } from "./chunk-HLBNRB3S.js";
10
+ } from "./chunk-GNW3OK5U.js";
11
11
  import "./chunk-3DBJKHOD.js";
12
- import "./chunk-TCQFQ354.js";
12
+ import "./chunk-CSDZGNUN.js";
13
13
  import {
14
14
  sharpImageEngine
15
15
  } from "./chunk-CSFBJ3UH.js";
16
- import "./chunk-RZ2V4PCS.js";
16
+ import "./chunk-JMMXC5UA.js";
17
17
  import {
18
18
  configArg,
19
19
  createVideoEngine,
@@ -21,15 +21,15 @@ import {
21
21
  ffmpegInstallText,
22
22
  ffmpegStatus,
23
23
  noAssetsMessage
24
- } from "./chunk-KN5QOB7R.js";
25
- import "./chunk-S3FQYUTI.js";
26
- import "./chunk-QOG2F4SF.js";
27
- import "./chunk-3PF6BBFK.js";
28
- import "./chunk-IZ4JEL3Q.js";
24
+ } from "./chunk-ABHEWNYQ.js";
25
+ import "./chunk-SMR2764T.js";
26
+ import "./chunk-L5HLKZDW.js";
27
+ import "./chunk-PE5NKZCH.js";
28
+ import "./chunk-4AM2CCML.js";
29
29
  import {
30
30
  guarded
31
- } from "./chunk-MV6J66AP.js";
32
- import "./chunk-FY53B4NB.js";
31
+ } from "./chunk-MUE3L5XT.js";
32
+ import "./chunk-KJK7XGWA.js";
33
33
 
34
34
  // src/cli/commands/audit.ts
35
35
  import { defineCommand } from "citty";
@@ -4,16 +4,16 @@ import {
4
4
  } from "./chunk-LTWUPQDT.js";
5
5
  import {
6
6
  audit
7
- } from "./chunk-WI4ZO7GP.js";
7
+ } from "./chunk-DZLFVN4G.js";
8
8
  import {
9
9
  formatAuditReport
10
- } from "./chunk-HLBNRB3S.js";
10
+ } from "./chunk-GNW3OK5U.js";
11
11
  import "./chunk-3DBJKHOD.js";
12
- import "./chunk-TCQFQ354.js";
12
+ import "./chunk-CSDZGNUN.js";
13
13
  import {
14
14
  sharpImageEngine
15
15
  } from "./chunk-CSFBJ3UH.js";
16
- import "./chunk-RZ2V4PCS.js";
16
+ import "./chunk-JMMXC5UA.js";
17
17
  import {
18
18
  configArg,
19
19
  createVideoEngine,
@@ -21,22 +21,22 @@ import {
21
21
  ffmpegInstallText,
22
22
  ffmpegStatus,
23
23
  noAssetsMessage
24
- } from "./chunk-KN5QOB7R.js";
24
+ } from "./chunk-ABHEWNYQ.js";
25
25
  import {
26
26
  run
27
- } from "./chunk-S3FQYUTI.js";
27
+ } from "./chunk-SMR2764T.js";
28
28
  import {
29
29
  toPosix
30
- } from "./chunk-QOG2F4SF.js";
31
- import "./chunk-3PF6BBFK.js";
32
- import "./chunk-IZ4JEL3Q.js";
30
+ } from "./chunk-L5HLKZDW.js";
31
+ import "./chunk-PE5NKZCH.js";
32
+ import "./chunk-4AM2CCML.js";
33
33
  import {
34
34
  BytecutError,
35
35
  CONFIG_FILES,
36
36
  formatBytes,
37
37
  percentChange,
38
38
  plural
39
- } from "./chunk-FY53B4NB.js";
39
+ } from "./chunk-KJK7XGWA.js";
40
40
 
41
41
  // src/cli/commands/check.ts
42
42
  import { defineCommand } from "citty";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  BytecutError,
4
4
  CONFIG_FILES
5
- } from "./chunk-FY53B4NB.js";
5
+ } from "./chunk-KJK7XGWA.js";
6
6
 
7
7
  // src/core/fs-safe.ts
8
8
  import { randomBytes } from "crypto";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  escapeGlob
4
- } from "./chunk-FY53B4NB.js";
4
+ } from "./chunk-KJK7XGWA.js";
5
5
 
6
6
  // src/core/config/choices.ts
7
7
  import { stringify as stringifyToml } from "smol-toml";
@@ -3,19 +3,19 @@ import {
3
3
  isFile,
4
4
  readOsReleaseId,
5
5
  run
6
- } from "./chunk-S3FQYUTI.js";
6
+ } from "./chunk-SMR2764T.js";
7
7
  import {
8
8
  createFfmpegVideoEngine
9
- } from "./chunk-QOG2F4SF.js";
9
+ } from "./chunk-L5HLKZDW.js";
10
10
  import {
11
11
  installHint,
12
12
  resolveBinary,
13
13
  resolveConfig
14
- } from "./chunk-3PF6BBFK.js";
14
+ } from "./chunk-PE5NKZCH.js";
15
15
  import {
16
16
  BytecutError,
17
17
  loadConfig
18
- } from "./chunk-FY53B4NB.js";
18
+ } from "./chunk-KJK7XGWA.js";
19
19
 
20
20
  // src/cli/shared.ts
21
21
  import { existsSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  BytecutError
4
- } from "./chunk-FY53B4NB.js";
4
+ } from "./chunk-KJK7XGWA.js";
5
5
 
6
6
  // src/core/plan/schema.ts
7
7
  import { z } from "zod";
@@ -49,7 +49,12 @@ var videoOutputSchema = z.discriminatedUnion("role", [
49
49
  maxFps: z.number().positive().nullable(),
50
50
  audio: z.enum(["strip", "keep"]),
51
51
  audioBitrate: z.string().regex(/^\d+k$/),
52
- search: z.object({ metric: z.enum(["ssimulacra2", "vmaf"]), target: z.number().min(1).max(100) }).strict().optional()
52
+ search: z.object({
53
+ metric: z.enum(["ssimulacra2", "vmaf"]),
54
+ target: z.number().min(1).max(100),
55
+ floor: z.number().min(1).max(100).optional(),
56
+ maxBytes: z.number().int().positive().optional()
57
+ }).strict().optional()
53
58
  }).strict(),
54
59
  z.object({
55
60
  role: z.literal("poster"),
@@ -182,7 +187,13 @@ var videoOutputSchema2 = z2.object({
182
187
  codecs: z2.string().optional(),
183
188
  encoder: z2.string(),
184
189
  status: z2.enum(["written", "already-optimal", "stale"]),
185
- search: z2.object({ metric: z2.enum(["ssimulacra2", "vmaf"]), target: z2.number() }).strict().optional(),
190
+ search: z2.object({
191
+ metric: z2.enum(["ssimulacra2", "vmaf"]),
192
+ target: z2.number(),
193
+ floor: z2.number().optional(),
194
+ maxBytes: z2.number().int().optional(),
195
+ plannedPath: z2.string().optional()
196
+ }).strict().optional(),
186
197
  score: z2.number().optional(),
187
198
  scorer: z2.enum(["wasm", "native", "libvmaf"]).optional()
188
199
  }).strict();
@@ -6,38 +6,38 @@ import {
6
6
  sortFindings,
7
7
  walkFiles,
8
8
  withVideos
9
- } from "./chunk-HLBNRB3S.js";
9
+ } from "./chunk-GNW3OK5U.js";
10
10
  import {
11
11
  MANIFEST_FILE,
12
12
  parseManifest
13
- } from "./chunk-TCQFQ354.js";
13
+ } from "./chunk-CSDZGNUN.js";
14
14
  import {
15
15
  ANIMATED_GIF_REASON,
16
16
  buildPlan,
17
17
  classify,
18
18
  classifyVideo,
19
19
  sha256
20
- } from "./chunk-RZ2V4PCS.js";
20
+ } from "./chunk-JMMXC5UA.js";
21
21
  import {
22
22
  isFastStart,
23
23
  toPosix
24
- } from "./chunk-QOG2F4SF.js";
24
+ } from "./chunk-L5HLKZDW.js";
25
25
  import {
26
26
  BUILTIN_POLICY,
27
27
  getImagePreset,
28
28
  pickAv1Encoder,
29
29
  rootPreset
30
- } from "./chunk-3PF6BBFK.js";
30
+ } from "./chunk-PE5NKZCH.js";
31
31
  import {
32
32
  SafeFs
33
- } from "./chunk-IZ4JEL3Q.js";
33
+ } from "./chunk-4AM2CCML.js";
34
34
  import {
35
35
  ALL_RULES,
36
36
  checkCache,
37
37
  checkVideoCache,
38
38
  matchesGlob,
39
39
  savingOf
40
- } from "./chunk-FY53B4NB.js";
40
+ } from "./chunk-KJK7XGWA.js";
41
41
 
42
42
  // src/core/commands/audit.ts
43
43
  import { stat as stat2 } from "fs/promises";
@@ -232,10 +232,10 @@ async function estimateVideo(item, deps, sourcePath) {
232
232
  ].join("|");
233
233
  let streamBytes = deps.cache.getEstimate(key)?.bytes;
234
234
  if (streamBytes === void 0) {
235
- let total = 0;
235
+ let total2 = 0;
236
236
  let seconds = 0;
237
237
  for (const s of starts) {
238
- total += await deps.engine.sampleBytes(
238
+ total2 += await deps.engine.sampleBytes(
239
239
  {
240
240
  source: sourcePath,
241
241
  encoder,
@@ -252,12 +252,15 @@ async function estimateVideo(item, deps, sourcePath) {
252
252
  deps.onStep?.({ done: ++stepsDone, total: stepTotal });
253
253
  seconds += s.durationSec;
254
254
  }
255
- streamBytes = Math.round(seconds > 0 ? total / seconds * item.durationSec : total);
255
+ streamBytes = Math.round(seconds > 0 ? total2 / seconds * item.durationSec : total2);
256
256
  deps.cache.setEstimate(key, { bytes: streamBytes, basis: exact ? "exact" : "extrapolated" });
257
257
  }
258
+ const total = streamBytes + audioBytes(out, item);
258
259
  perCodec.push({
259
260
  codec: out.codec,
260
- bytes: streamBytes + audioBytes(out, item),
261
+ // A size cap is met by raising the CRF or narrowing the width, or the output is not
262
+ // written at all, so what a run will produce never exceeds it.
263
+ bytes: out.search?.maxBytes !== void 0 ? Math.min(total, out.search.maxBytes) : total,
261
264
  basis: exact ? "exact" : "extrapolated"
262
265
  });
263
266
  }
@@ -6,7 +6,7 @@ import {
6
6
  probeFfmpeg,
7
7
  probeFfprobeVersion,
8
8
  resolveBinary
9
- } from "./chunk-3PF6BBFK.js";
9
+ } from "./chunk-PE5NKZCH.js";
10
10
 
11
11
  // src/core/doctor/report.ts
12
12
  var MIN_NODE_MAJOR = 22;