halfsize 0.1.2 → 0.1.3
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/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/package.json +1 -1
- package/src/cli.mjs +10 -6
- package/src/encode.mjs +3 -3
- package/web/index.html +4 -1
package/README.md
CHANGED
|
@@ -164,6 +164,7 @@ halfsize <file> [options]
|
|
|
164
164
|
--json Machine-readable result
|
|
165
165
|
--dry-run Print the plan, do not encode
|
|
166
166
|
--web Start the local web app
|
|
167
|
+
--port <n> Port for the web app (default 4174)
|
|
167
168
|
--doctor Show which FFmpeg and encoders were found
|
|
168
169
|
-h, --help Help
|
|
169
170
|
-v, --version Version
|
package/README.zh-CN.md
CHANGED
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { encode } from "./encode.mjs";
|
|
|
7
7
|
import { measureQuality, verdict } from "./quality.mjs";
|
|
8
8
|
import { bytes, duration, bitrate, ratio, parseSize, clock } from "./human.mjs";
|
|
9
9
|
|
|
10
|
-
const VERSION = "0.1.
|
|
10
|
+
const VERSION = "0.1.3";
|
|
11
11
|
|
|
12
12
|
const HELP = `
|
|
13
13
|
halfsize ${VERSION} - compress a video to an exact size, and prove it still looks the same.
|
|
@@ -17,6 +17,7 @@ const HELP = `
|
|
|
17
17
|
halfsize <file> --to 25mb --codec h265
|
|
18
18
|
halfsize <file> --crf 23 Quality-first mode
|
|
19
19
|
halfsize --web Open the local app
|
|
20
|
+
halfsize --web --port 8080 Use a different port
|
|
20
21
|
halfsize --doctor Check FFmpeg and hardware encoders
|
|
21
22
|
|
|
22
23
|
Options
|
|
@@ -29,6 +30,7 @@ const HELP = `
|
|
|
29
30
|
--no-downscale Never reduce resolution
|
|
30
31
|
--json Machine-readable result
|
|
31
32
|
--dry-run Print the plan, do not encode
|
|
33
|
+
--port <n> Port for --web (default 4174)
|
|
32
34
|
-h, --help Show this help
|
|
33
35
|
-v, --version Show version
|
|
34
36
|
|
|
@@ -52,6 +54,7 @@ function parseArgs(argv) {
|
|
|
52
54
|
else if (a === "--no-downscale") args.flags.downscale = false;
|
|
53
55
|
else if (a === "--web") args.flags.web = true;
|
|
54
56
|
else if (a === "--doctor") args.flags.doctor = true;
|
|
57
|
+
else if (a === "--port") args.flags.port = Number(argv[++i]);
|
|
55
58
|
else if (a === "--to") args.flags.to = argv[++i];
|
|
56
59
|
else if (a === "--crf") args.flags.crf = Number(argv[++i]);
|
|
57
60
|
else if (a === "--codec") args.flags.codec = argv[++i];
|
|
@@ -76,7 +79,7 @@ export async function main(argv) {
|
|
|
76
79
|
return;
|
|
77
80
|
}
|
|
78
81
|
if (flags.doctor) return doctor();
|
|
79
|
-
if (flags.web) return startWeb();
|
|
82
|
+
if (flags.web) return startWeb(flags.port);
|
|
80
83
|
|
|
81
84
|
const input = path.resolve(args._[0]);
|
|
82
85
|
if (!fs.existsSync(input)) throw new Error(`File not found: ${input}`);
|
|
@@ -192,7 +195,7 @@ class Progress {
|
|
|
192
195
|
this.start = Date.now();
|
|
193
196
|
}
|
|
194
197
|
|
|
195
|
-
update({ ratio, speed }) {
|
|
198
|
+
update({ ratio, speed, pass, passes }) {
|
|
196
199
|
if (!this.enabled) return;
|
|
197
200
|
const now = Date.now();
|
|
198
201
|
if (now - this.lastPrint < 120 && ratio < 1) return;
|
|
@@ -203,8 +206,9 @@ class Progress {
|
|
|
203
206
|
const elapsed = (now - this.start) / 1000;
|
|
204
207
|
const eta = ratio > 0.02 ? (elapsed / ratio) * (1 - ratio) : 0;
|
|
205
208
|
const speedText = String(speed || "").replace("x", "");
|
|
209
|
+
const passText = passes > 1 ? `pass ${pass}/2 ` : "";
|
|
206
210
|
process.stdout.write(
|
|
207
|
-
`\r [${bar}] ${String(Math.round(ratio * 100)).padStart(3)}% ${speedText ? `${speedText}x` : ""} eta ${clock(eta)} `,
|
|
211
|
+
`\r [${bar}] ${String(Math.round(ratio * 100)).padStart(3)}% ${passText}${speedText ? `${speedText}x` : ""} eta ${clock(eta)} `,
|
|
208
212
|
);
|
|
209
213
|
}
|
|
210
214
|
|
|
@@ -271,9 +275,9 @@ async function doctor() {
|
|
|
271
275
|
console.log("");
|
|
272
276
|
}
|
|
273
277
|
|
|
274
|
-
async function startWeb() {
|
|
278
|
+
async function startWeb(port) {
|
|
275
279
|
const { startServer } = await import("./server.mjs");
|
|
276
|
-
const url = await startServer();
|
|
280
|
+
const url = await startServer(port ? { port } : {});
|
|
277
281
|
console.log("");
|
|
278
282
|
console.log(` halfsize is running at ${url}`);
|
|
279
283
|
console.log(" Press Ctrl+C to stop.");
|
package/src/encode.mjs
CHANGED
|
@@ -46,7 +46,7 @@ export async function encode(input, output, plan, info, onProgress = () => {}) {
|
|
|
46
46
|
"-f", "null",
|
|
47
47
|
NULL_DEVICE,
|
|
48
48
|
];
|
|
49
|
-
await runFfmpeg(ffmpeg, pass1, info.duration, () => {});
|
|
49
|
+
await runFfmpeg(ffmpeg, pass1, info.duration, (p) => onProgress({ ...p, ratio: p.ratio * 0.5, pass: 1, passes: 2 }));
|
|
50
50
|
const pass2 = [
|
|
51
51
|
...baseArgs(input, plan, passlog),
|
|
52
52
|
"-b:v", `${plan.videoKbps}k`,
|
|
@@ -55,7 +55,7 @@ export async function encode(input, output, plan, info, onProgress = () => {}) {
|
|
|
55
55
|
"-movflags", "+faststart",
|
|
56
56
|
output,
|
|
57
57
|
];
|
|
58
|
-
await runFfmpeg(ffmpeg, pass2, info.duration, onProgress);
|
|
58
|
+
await runFfmpeg(ffmpeg, pass2, info.duration, (p) => onProgress({ ...p, ratio: 0.5 + p.ratio * 0.5, pass: 2, passes: 2 }));
|
|
59
59
|
} else {
|
|
60
60
|
const args = [...baseArgs(input, plan, null)];
|
|
61
61
|
if (plan.mode === "size") {
|
|
@@ -64,7 +64,7 @@ export async function encode(input, output, plan, info, onProgress = () => {}) {
|
|
|
64
64
|
args.push("-crf", String(plan.crf));
|
|
65
65
|
}
|
|
66
66
|
args.push(...audioArgs(plan), "-movflags", "+faststart", output);
|
|
67
|
-
await runFfmpeg(ffmpeg, args, info.duration, onProgress);
|
|
67
|
+
await runFfmpeg(ffmpeg, args, info.duration, (p) => onProgress({ ...p, pass: 1, passes: 1 }));
|
|
68
68
|
}
|
|
69
69
|
} finally {
|
|
70
70
|
cleanup(passlog);
|
package/web/index.html
CHANGED
|
@@ -248,6 +248,7 @@ const I18N = {
|
|
|
248
248
|
smallerLabel: "更小",
|
|
249
249
|
accuracyUnder: (p) => `比目标小 ${p}%`,
|
|
250
250
|
accuracyOver: (p) => `超出目标 ${p}%`,
|
|
251
|
+
passLabel: (n) => `第 ${n}/2 遍`,
|
|
251
252
|
alreadySmall: (a, b) => `这个文件已经只有 ${a},小于 ${b} 的目标。换一个更小的目标。`,
|
|
252
253
|
badSize: "看不懂这个大小。试试 25mb、700k 或 1.5gb。",
|
|
253
254
|
failed: "压缩失败",
|
|
@@ -287,6 +288,7 @@ const I18N = {
|
|
|
287
288
|
smallerLabel: "smaller",
|
|
288
289
|
accuracyUnder: (p) => `${p}% under target`,
|
|
289
290
|
accuracyOver: (p) => `${p}% over target`,
|
|
291
|
+
passLabel: (n) => `pass ${n}/2`,
|
|
290
292
|
alreadySmall: (a, b) => `This file is already ${a}, smaller than the ${b} target. Pick a smaller target.`,
|
|
291
293
|
badSize: "Can't read that size. Try 25mb, 700k or 1.5gb.",
|
|
292
294
|
failed: "compression failed",
|
|
@@ -461,7 +463,8 @@ go.addEventListener("click", async () => {
|
|
|
461
463
|
const percent = Math.round((p.ratio || 0) * 100);
|
|
462
464
|
fill.style.width = `${percent}%`;
|
|
463
465
|
pct.textContent = `${percent}%`;
|
|
464
|
-
|
|
466
|
+
const pass = p.passes > 1 ? `${strings.passLabel(p.pass)} ` : "";
|
|
467
|
+
speed.textContent = `${pass}${p.speed || ""} eta ${fmtDur(p.eta)}`;
|
|
465
468
|
});
|
|
466
469
|
es.addEventListener("phase", () => {
|
|
467
470
|
fill.style.width = "100%";
|