halfsize 0.1.4 → 0.1.5
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/CHANGELOG.md +7 -0
- package/README.md +53 -13
- package/README.zh-CN.md +55 -10
- package/package.json +59 -49
- package/src/cli.mjs +7 -6
- package/web/index.html +11 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
- `--for discord` now targets 20 MB (Discord raised its free upload cap from 10 MB in August 2026)
|
|
6
|
+
- `--for wechat` now targets 20 MB (WeChat's chat-video cap, per its help center)
|
|
7
|
+
- The web app's presets and copy match the current caps
|
|
8
|
+
- `npx` installs behind a restrictive network can use the npmmirror FFmpeg mirror (see Install notes)
|
|
9
|
+
|
|
3
10
|
## 0.1.4
|
|
4
11
|
|
|
5
12
|
- Batch mode: `halfsize a.mp4 b.mp4 --to 10mb` compresses several files in a row
|
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
<p align="center">
|
|
21
21
|
<a href="README.zh-CN.md">中文说明</a> ·
|
|
22
|
+
<a href="https://YKLovePython.github.io/halfsize/">Website & guides</a> ·
|
|
22
23
|
<a href="#quick-start">Quick start</a> ·
|
|
23
24
|
<a href="#how-it-works">How it works</a> ·
|
|
24
25
|
<a href="#faq">FAQ</a>
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
29
|
-
WeChat
|
|
30
|
+
WeChat and Discord both stop at **20 MB**. Email chokes around the same.
|
|
30
31
|
So you open a video editor, guess a bitrate, export, check the size, export again, and still
|
|
31
32
|
can't tell whether the picture got worse.
|
|
32
33
|
|
|
@@ -40,7 +41,7 @@ It computes the bitrate, picks the resolution that actually fits, encodes twice,
|
|
|
40
41
|
the result with **SSIM** and **VMAF** so you know whether it still looks the same.
|
|
41
42
|
|
|
42
43
|
```
|
|
43
|
-
halfsize 0.1.
|
|
44
|
+
halfsize 0.1.5
|
|
44
45
|
|
|
45
46
|
source.mp4
|
|
46
47
|
23.2 MB 8s 1920x1080 30.00 fps h264
|
|
@@ -51,9 +52,9 @@ the result with **SSIM** and **VMAF** so you know whether it still looks the sam
|
|
|
51
52
|
estimate 1.94 MB (12x smaller)
|
|
52
53
|
output source.2mb.mp4
|
|
53
54
|
|
|
54
|
-
measuring quality done in
|
|
55
|
+
measuring quality done in 1s
|
|
55
56
|
|
|
56
|
-
23.2 MB -> 1.99 MB 12x smaller 0.
|
|
57
|
+
23.2 MB -> 1.99 MB 12x smaller 0.4% under target
|
|
57
58
|
VMAF 84.4 SSIM 0.968 slight softening
|
|
58
59
|
|
|
59
60
|
-> C:\Videos\source.2mb.mp4
|
|
@@ -61,7 +62,7 @@ the result with **SSIM** and **VMAF** so you know whether it still looks the sam
|
|
|
61
62
|
|
|
62
63
|
## Why this exists
|
|
63
64
|
|
|
64
|
-
"Make it smaller" is a solved problem. **"Make it fit in exactly
|
|
65
|
+
"Make it smaller" is a solved problem. **"Make it fit in exactly 20 MB, and tell me if it
|
|
65
66
|
still looks right"** is the part every tool makes you guess at.
|
|
66
67
|
|
|
67
68
|
- HandBrake is excellent, but it asks you to think in codecs, RF values, and presets.
|
|
@@ -118,7 +119,7 @@ npm install -g halfsize
|
|
|
118
119
|
npx halfsize --web
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
Opens `http://127.0.0.1:4174` — drag a video in, pick
|
|
122
|
+
Opens `http://127.0.0.1:4174` — drag a video in, pick 20 MB / 16 MB / 5 MB or type
|
|
122
123
|
your own limit, and watch the encode. The file goes to a temporary folder **on your own
|
|
123
124
|
machine**; nothing is sent anywhere.
|
|
124
125
|
|
|
@@ -129,6 +130,12 @@ machine**; nothing is sent anywhere.
|
|
|
129
130
|
After each run you can download a **share card** (the image at the top of this README) with the
|
|
130
131
|
real numbers on it.
|
|
131
132
|
|
|
133
|
+
Step-by-step guides with measured quality numbers:
|
|
134
|
+
[Discord 20 MB](https://YKLovePython.github.io/halfsize/discord/) ·
|
|
135
|
+
[WhatsApp 16 MB](https://YKLovePython.github.io/halfsize/whatsapp/) ·
|
|
136
|
+
[WeChat 20 MB](https://YKLovePython.github.io/halfsize/wechat/) ·
|
|
137
|
+
[any 20 MB limit](https://YKLovePython.github.io/halfsize/20mb/).
|
|
138
|
+
|
|
132
139
|
### Use it from an agent
|
|
133
140
|
|
|
134
141
|
The repo ships a skill in [`skills/halfsize/SKILL.md`](skills/halfsize/SKILL.md). Drop that
|
|
@@ -162,6 +169,25 @@ below 70 clearly degraded
|
|
|
162
169
|
VMAF is Netflix's perceptual metric; it correlates with human judgement far better than PSNR
|
|
163
170
|
or bitrate. It is not magic, but it is a much better answer than "trust me".
|
|
164
171
|
|
|
172
|
+
## What each size costs
|
|
173
|
+
|
|
174
|
+
Same 60-second 1080p clip, default settings, measured on a normal laptop:
|
|
175
|
+
|
|
176
|
+
| Target | Output | Resolution | Video bitrate | VMAF | Verdict |
|
|
177
|
+
|---|---|---|---|---|---|
|
|
178
|
+
| 20 MB | 19.45 MB | 1280×720 | 2.46 Mbps | 88.3 | slight softening |
|
|
179
|
+
| 16 MB | 15.59 MB | 960×540 | 1.94 Mbps | 84.8 | slight softening |
|
|
180
|
+
| 10 MB | 9.77 MB | 960×540 | 1.17 Mbps | 82.9 | slight softening |
|
|
181
|
+
| 5 MB | 4.93 MB | 640×360 | 0.55 Mbps | 72.0 | visible loss |
|
|
182
|
+
|
|
183
|
+
Source: 60 s, 1920×1080, 30 fps, 18 Mbps H.264 (136 MB). VMAF is measured against the
|
|
184
|
+
source at the midpoint, SSIM at three points. Real numbers move with content: a talking head
|
|
185
|
+
does much better than confetti.
|
|
186
|
+
|
|
187
|
+
At 20 MB the tool picked 720p on purpose. Sixty seconds of 1080p at 2.4 Mbps looks worse than
|
|
188
|
+
720p at the same bits. `--no-downscale` overrides that, and `--codec h265` buys roughly 30%
|
|
189
|
+
more quality at the same size if your player supports it.
|
|
190
|
+
|
|
165
191
|
## CLI reference
|
|
166
192
|
|
|
167
193
|
```
|
|
@@ -187,19 +213,24 @@ halfsize <file> [options]
|
|
|
187
213
|
|
|
188
214
|
Sizes accept `b`, `k`/`kb`, `m`/`mb`, `g`/`gb`, with or without a space. No unit means MB.
|
|
189
215
|
|
|
190
|
-
`--for` uses the current free-tier caps
|
|
191
|
-
`email` 20 MB, `twitter` 512 MB. Caps change, so `--to`
|
|
216
|
+
`--for` uses the current free-tier caps (checked November 2026): `discord` 20 MB,
|
|
217
|
+
`whatsapp` 16 MB, `wechat` 20 MB, `email` 20 MB, `twitter` 512 MB. Caps change, so `--to`
|
|
218
|
+
always overrides.
|
|
192
219
|
|
|
193
220
|
Exit codes: `0` success, `1` error. `--json` returns the plan, the measured quality, and the
|
|
194
221
|
target accuracy, which makes it easy to wrap in a script.
|
|
195
222
|
|
|
196
223
|
## Install notes
|
|
197
224
|
|
|
198
|
-
`ffmpeg-static`
|
|
199
|
-
|
|
200
|
-
|
|
225
|
+
`ffmpeg-static` downloads a prebuilt FFmpeg during `npm install`; FFprobe ships inside
|
|
226
|
+
`ffprobe-static`, so it needs no download. If you are behind a proxy or a TLS-inspecting
|
|
227
|
+
network, that download can fail. Point it at a mirror, or use a build you already have:
|
|
201
228
|
|
|
202
229
|
```bash
|
|
230
|
+
# use the npmmirror binary mirror (fast in mainland China)
|
|
231
|
+
FFMPEG_BINARIES_URL=https://registry.npmmirror.com/-/binary/ffmpeg-static npx halfsize video.mp4 --to 25mb
|
|
232
|
+
|
|
233
|
+
# or an FFmpeg already on disk
|
|
203
234
|
HALFSIZE_FFMPEG=/path/to/ffmpeg HALFSIZE_FFPROBE=/path/to/ffprobe npx halfsize video.mp4 --to 25mb
|
|
204
235
|
```
|
|
205
236
|
|
|
@@ -212,14 +243,23 @@ Debian/Ubuntu, `sudo apt install ffmpeg`. Run `npx halfsize --doctor` to see wha
|
|
|
212
243
|
No. The CLI never touches the network. The web app talks only to `127.0.0.1`, and uploaded
|
|
213
244
|
files go to a temp directory that is deleted when the process exits.
|
|
214
245
|
|
|
246
|
+
**How do I get a video under Discord's 20 MB limit?**
|
|
247
|
+
`npx halfsize clip.mp4 --for discord` targets the free-tier cap (20 MB as of late 2026) and
|
|
248
|
+
writes a normal MP4 that plays in Discord's preview. Long 1080p clips usually come out at 720p;
|
|
249
|
+
that resolution drop is what buys the size.
|
|
250
|
+
|
|
251
|
+
**WeChat says the video is too large. What now?**
|
|
252
|
+
`npx halfsize clip.mp4 --for wechat` targets the 20 MB chat-video cap. If you need it smaller
|
|
253
|
+
still, pass `--to 10mb` (or any size) instead.
|
|
254
|
+
|
|
215
255
|
**Why did it lower my resolution?**
|
|
216
|
-
Because
|
|
256
|
+
Because 20 MB of 4K is not enough bits to look good. Lowering resolution and keeping the
|
|
217
257
|
bitrate per pixel sane almost always looks better than keeping 4K and starving it. Use
|
|
218
258
|
`--no-downscale` if you want to override that.
|
|
219
259
|
|
|
220
260
|
**Why is the output slightly under the target, not exactly on it?**
|
|
221
261
|
Container overhead and two-pass ABR both vary a little. `halfsize` aims ~3% under so it never
|
|
222
|
-
overshoots a hard platform cap. In the example above it landed 0.
|
|
262
|
+
overshoots a hard platform cap. In the example above it landed 0.4% under.
|
|
223
263
|
|
|
224
264
|
**H.264 or H.265?**
|
|
225
265
|
H.264 plays everywhere, including old Android phones and browsers. H.265 is roughly 30% smaller
|
package/README.zh-CN.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
<p align="center">
|
|
21
21
|
<a href="README.md">English</a> ·
|
|
22
|
+
<a href="https://YKLovePython.github.io/halfsize/zh/">官网与教程</a> ·
|
|
22
23
|
<a href="#快速开始">快速开始</a> ·
|
|
23
24
|
<a href="#它怎么工作">它怎么工作</a> ·
|
|
24
25
|
<a href="#常见问题">常见问题</a>
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
微信和 Discord 都只让发 **20 MB**,邮件也大多卡在 **20 MB**。
|
|
30
31
|
于是你打开剪辑软件,猜一个码率,导出,看一眼大小,再导一次——最后还是不知道画质到底掉了多少。
|
|
31
32
|
|
|
32
33
|
`halfsize` 把这件事变成一行命令:
|
|
@@ -39,7 +40,7 @@ npx halfsize 婚礼.mp4 --to 25mb
|
|
|
39
40
|
画质到底还在不在。
|
|
40
41
|
|
|
41
42
|
```
|
|
42
|
-
halfsize 0.1.
|
|
43
|
+
halfsize 0.1.5
|
|
43
44
|
|
|
44
45
|
source.mp4
|
|
45
46
|
23.2 MB 8s 1920x1080 30.00 fps h264
|
|
@@ -50,9 +51,9 @@ npx halfsize 婚礼.mp4 --to 25mb
|
|
|
50
51
|
estimate 1.94 MB (12x smaller)
|
|
51
52
|
output source.2mb.mp4
|
|
52
53
|
|
|
53
|
-
measuring quality done in
|
|
54
|
+
measuring quality done in 1s
|
|
54
55
|
|
|
55
|
-
23.2 MB -> 1.99 MB 12x smaller 0.
|
|
56
|
+
23.2 MB -> 1.99 MB 12x smaller 0.4% under target
|
|
56
57
|
VMAF 84.4 SSIM 0.968 slight softening
|
|
57
58
|
|
|
58
59
|
-> C:\Videos\source.2mb.mp4
|
|
@@ -60,7 +61,7 @@ npx halfsize 婚礼.mp4 --to 25mb
|
|
|
60
61
|
|
|
61
62
|
## 为什么做这个
|
|
62
63
|
|
|
63
|
-
「把视频变小」早就有人解决了。真正没人做好的是:**「刚好塞进
|
|
64
|
+
「把视频变小」早就有人解决了。真正没人做好的是:**「刚好塞进 20 MB,然后告诉我画质还行不行」**。
|
|
64
65
|
|
|
65
66
|
- HandBrake 很好用,但它要你想清楚编码器、RF 值、preset 这些词。
|
|
66
67
|
- 在线压缩网站也能用,直到你想起自己刚把私人视频传给了陌生人。
|
|
@@ -115,7 +116,7 @@ npm install -g halfsize
|
|
|
115
116
|
npx halfsize --web
|
|
116
117
|
```
|
|
117
118
|
|
|
118
|
-
打开 `http://127.0.0.1:4174`,把视频拖进去,选
|
|
119
|
+
打开 `http://127.0.0.1:4174`,把视频拖进去,选 20 MB / 16 MB / 5 MB,或者自己输入一个数,
|
|
119
120
|
然后看着它压。文件只会写进**你自己电脑**的临时目录,不会发到任何地方。
|
|
120
121
|
|
|
121
122
|
<p align="center">
|
|
@@ -124,6 +125,12 @@ npx halfsize --web
|
|
|
124
125
|
|
|
125
126
|
每次压完可以生成一张**分享图**(就是本文最上面那张),上面是这次真实的数字。
|
|
126
127
|
|
|
128
|
+
附实测画质的分平台教程:
|
|
129
|
+
[微信 20 MB](https://YKLovePython.github.io/halfsize/zh/wechat/) ·
|
|
130
|
+
[任意 20 MB 限制](https://YKLovePython.github.io/halfsize/zh/20mb/) ·
|
|
131
|
+
[Discord 20 MB](https://YKLovePython.github.io/halfsize/discord/) ·
|
|
132
|
+
[WhatsApp 16 MB](https://YKLovePython.github.io/halfsize/whatsapp/)。
|
|
133
|
+
|
|
127
134
|
### 给 agent 用
|
|
128
135
|
|
|
129
136
|
仓库里带了 [`skills/halfsize/SKILL.md`](skills/halfsize/SKILL.md)。把整个文件夹放进你的
|
|
@@ -152,6 +159,23 @@ VMAF ≥ 70 能看出损失
|
|
|
152
159
|
VMAF 是 Netflix 提出的感知质量指标,比 PSNR 或「码率多少」更接近人眼判断。
|
|
153
160
|
它不是魔法,但比「相信我」靠谱得多。
|
|
154
161
|
|
|
162
|
+
## 各档大小到底损失多少画质
|
|
163
|
+
|
|
164
|
+
同一个 60 秒 1080p 视频,默认设置,在普通笔记本上实测:
|
|
165
|
+
|
|
166
|
+
| 目标 | 输出 | 分辨率 | 视频码率 | VMAF | 结论 |
|
|
167
|
+
|---|---|---|---|---|---|
|
|
168
|
+
| 20 MB | 19.45 MB | 1280×720 | 2.46 Mbps | 88.3 | 轻微变软 |
|
|
169
|
+
| 16 MB | 15.59 MB | 960×540 | 1.94 Mbps | 84.8 | 轻微变软 |
|
|
170
|
+
| 10 MB | 9.77 MB | 960×540 | 1.17 Mbps | 82.9 | 轻微变软 |
|
|
171
|
+
| 5 MB | 4.93 MB | 640×360 | 0.55 Mbps | 72.0 | 能看出损失 |
|
|
172
|
+
|
|
173
|
+
源片:60 秒、1920×1080、30fps、18 Mbps H.264(136 MB)。VMAF 在中间点对着源片测量,
|
|
174
|
+
SSIM 取三个采样点。实际数字会随内容变化:新闻口播会好很多,满屏彩纸会差很多。
|
|
175
|
+
|
|
176
|
+
20 MB 这一档是故意降到 720p 的:60 秒 1080p 给 2.4 Mbps,还不如把同样的比特给 720p。
|
|
177
|
+
想强制保分辨率就加 `--no-downscale`;播放器支持的话,`--codec h265` 能在同尺寸下多换约 30% 画质。
|
|
178
|
+
|
|
155
179
|
## 命令行参数
|
|
156
180
|
|
|
157
181
|
```
|
|
@@ -177,8 +201,8 @@ halfsize <file> [options]
|
|
|
177
201
|
|
|
178
202
|
大小支持 `b`、`k`/`kb`、`m`/`mb`、`g`/`gb`,中间可以有空格。不写单位按 MB 算。
|
|
179
203
|
|
|
180
|
-
`--for`
|
|
181
|
-
`email` 20 MB、`twitter` 512 MB。平台上限会变,写死不如随时用 `--to` 覆盖。
|
|
204
|
+
`--for` 用的是当前免费档上限(2026 年 11 月核对):`discord` 20 MB、`whatsapp` 16 MB、
|
|
205
|
+
`wechat` 20 MB、`email` 20 MB、`twitter` 512 MB。平台上限会变,写死不如随时用 `--to` 覆盖。
|
|
182
206
|
|
|
183
207
|
退出码:`0` 成功,`1` 出错。`--json` 会返回方案、画质测量和目标误差,方便包进脚本。
|
|
184
208
|
|
|
@@ -194,18 +218,39 @@ HALFSIZE_FFMPEG=/path/to/ffmpeg HALFSIZE_FFPROBE=/path/to/ffprobe npx halfsize v
|
|
|
194
218
|
Windows 上 `winget install Gyan.FFmpeg` 就够了;macOS 用 `brew install ffmpeg`;
|
|
195
219
|
Debian/Ubuntu 用 `sudo apt install ffmpeg`。跑一下 `npx halfsize --doctor` 能看到实际用的是哪个。
|
|
196
220
|
|
|
221
|
+
如果 GitHub 下载慢或被阻断(大陆网络很常见),可以让 `ffmpeg-static` 改从 npmmirror 取二进制:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
# macOS / Linux
|
|
225
|
+
FFMPEG_BINARIES_URL=https://registry.npmmirror.com/-/binary/ffmpeg-static npx halfsize 视频.mp4 --to 25mb
|
|
226
|
+
|
|
227
|
+
# Windows PowerShell
|
|
228
|
+
$env:FFMPEG_BINARIES_URL="https://registry.npmmirror.com/-/binary/ffmpeg-static"; npx halfsize 视频.mp4 --to 25mb
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`ffprobe` 是随包附带的,不需要单独下载。已经装过系统 FFmpeg 的话,`halfsize` 会优先用它,
|
|
232
|
+
连这一步都可以跳过。
|
|
233
|
+
|
|
197
234
|
## 常见问题
|
|
198
235
|
|
|
199
236
|
**视频会被上传吗?**
|
|
200
237
|
不会。命令行完全不联网。网页版只和 `127.0.0.1` 通信,上传的文件放在临时目录,进程退出就删掉。
|
|
201
238
|
|
|
239
|
+
**Discord 发不出去,怎么压到 20 MB?**
|
|
240
|
+
`npx halfsize 视频.mp4 --for discord` 会按免费档上限(2026 年底是 20 MB)压,输出就是普通 MP4,
|
|
241
|
+
Discord 预览能直接播。长一点的 1080p 视频通常会降到 720p,尺寸就是靠这一步省出来的。
|
|
242
|
+
|
|
243
|
+
**微信提示视频太大怎么办?**
|
|
244
|
+
`npx halfsize 视频.mp4 --for wechat` 会按微信聊天视频的 20 MB 上限压。还想更小就换成
|
|
245
|
+
`--to 10mb`(或者任意大小)。
|
|
246
|
+
|
|
202
247
|
**为什么给我降分辨率了?**
|
|
203
|
-
因为
|
|
248
|
+
因为 20 MB 的 4K 根本不够比特,硬撑 4K 只会满屏马赛克。降分辨率、把每像素比特数保住,
|
|
204
249
|
通常比「分辨率不变但饿死编码器」好看得多。不想降就加 `--no-downscale`。
|
|
205
250
|
|
|
206
251
|
**为什么输出比目标小一点,而不是刚好等于?**
|
|
207
252
|
容器开销和两遍 ABR 都会有波动。`halfsize` 会刻意瞄准低 3%,保证不会超过平台的硬上限。
|
|
208
|
-
上面那个例子里,最终比目标低 0.
|
|
253
|
+
上面那个例子里,最终比目标低 0.4%。
|
|
209
254
|
|
|
210
255
|
**H.264 还是 H.265?**
|
|
211
256
|
H.264 到处都能播,包括老安卓机和浏览器。H.265 同画质大约小 30%,但部分剪辑软件和老硬件
|
package/package.json
CHANGED
|
@@ -1,49 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "halfsize",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Compress any video to an exact
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"halfsize": "bin/halfsize.mjs"
|
|
8
|
-
},
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/YKLovePython/halfsize.git"
|
|
12
|
-
},
|
|
13
|
-
"homepage": "https://github.
|
|
14
|
-
"bugs": {
|
|
15
|
-
"url": "https://github.com/YKLovePython/halfsize/issues"
|
|
16
|
-
},
|
|
17
|
-
"engines": {
|
|
18
|
-
"node": ">=18"
|
|
19
|
-
},
|
|
20
|
-
"files": [
|
|
21
|
-
"bin",
|
|
22
|
-
"src",
|
|
23
|
-
"web",
|
|
24
|
-
"README.md",
|
|
25
|
-
"README.zh-CN.md",
|
|
26
|
-
"CHANGELOG.md",
|
|
27
|
-
"LICENSE"
|
|
28
|
-
],
|
|
29
|
-
"scripts": {
|
|
30
|
-
"start": "node bin/halfsize.mjs",
|
|
31
|
-
"web": "node bin/halfsize.mjs --web",
|
|
32
|
-
"test": "node --test test/human.test.mjs test/plan.test.mjs test/quality.test.mjs test/cli.test.mjs test/e2e.test.mjs"
|
|
33
|
-
},
|
|
34
|
-
"keywords": [
|
|
35
|
-
"video",
|
|
36
|
-
"compress",
|
|
37
|
-
"video
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "halfsize",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Compress any video to an exact size (Discord or WeChat 20 MB, WhatsApp 16 MB) locally with FFmpeg and verify the quality with VMAF/SSIM.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"halfsize": "bin/halfsize.mjs"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/YKLovePython/halfsize.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://YKLovePython.github.io/halfsize/",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/YKLovePython/halfsize/issues"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin",
|
|
22
|
+
"src",
|
|
23
|
+
"web",
|
|
24
|
+
"README.md",
|
|
25
|
+
"README.zh-CN.md",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"start": "node bin/halfsize.mjs",
|
|
31
|
+
"web": "node bin/halfsize.mjs --web",
|
|
32
|
+
"test": "node --test test/human.test.mjs test/plan.test.mjs test/quality.test.mjs test/cli.test.mjs test/e2e.test.mjs"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"video",
|
|
36
|
+
"compress",
|
|
37
|
+
"compress-video",
|
|
38
|
+
"video-compression",
|
|
39
|
+
"video-compressor",
|
|
40
|
+
"reduce-video-size",
|
|
41
|
+
"ffmpeg",
|
|
42
|
+
"target-size",
|
|
43
|
+
"vmaf",
|
|
44
|
+
"ssim",
|
|
45
|
+
"discord",
|
|
46
|
+
"wechat",
|
|
47
|
+
"whatsapp",
|
|
48
|
+
"cli",
|
|
49
|
+
"privacy",
|
|
50
|
+
"windows",
|
|
51
|
+
"macos",
|
|
52
|
+
"linux"
|
|
53
|
+
],
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"ffmpeg-static": "^5.3.0",
|
|
57
|
+
"ffprobe-static": "^3.1.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
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.5";
|
|
11
11
|
|
|
12
12
|
const HELP = `
|
|
13
13
|
halfsize ${VERSION} - compress a video to an exact size, and prove it still looks the same.
|
|
@@ -46,12 +46,13 @@ const HELP = `
|
|
|
46
46
|
|
|
47
47
|
const DEFAULTS = { codec: "h264", preset: "medium", verify: true, downscale: true, audio: true };
|
|
48
48
|
|
|
49
|
-
// Current upload caps as people hit them
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
// Current upload caps as people hit them (checked November 2026). Discord raised
|
|
50
|
+
// its free cap to 20 MB in August 2026 and WeChat's chat-video cap is 20 MB.
|
|
51
|
+
// Caps change, so --to always wins if a preset is wrong for your account tier.
|
|
52
|
+
export const PLATFORMS = {
|
|
53
|
+
discord: { bytes: 20 * 1_000_000, label: "Discord free (20 MB)" },
|
|
53
54
|
whatsapp: { bytes: 16 * 1_000_000, label: "WhatsApp media (16 MB)" },
|
|
54
|
-
wechat: { bytes:
|
|
55
|
+
wechat: { bytes: 20 * 1_000_000, label: "WeChat video (20 MB)" },
|
|
55
56
|
email: { bytes: 20 * 1_000_000, label: "Email attachment (20 MB)" },
|
|
56
57
|
twitter: { bytes: 512 * 1_000_000, label: "X / Twitter (512 MB)" },
|
|
57
58
|
};
|
package/web/index.html
CHANGED
|
@@ -138,6 +138,8 @@
|
|
|
138
138
|
.error.on { display: block; }
|
|
139
139
|
|
|
140
140
|
footer { margin-top: 46px; color: var(--dim); font-size: 12px; display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
|
141
|
+
footer a { color: var(--muted); }
|
|
142
|
+
footer a:hover { color: var(--accent); }
|
|
141
143
|
.kbd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--muted); background: var(--panel-2); border: 1px solid var(--line); padding: 3px 7px; border-radius: 6px; }
|
|
142
144
|
.hidden { display: none !important; }
|
|
143
145
|
</style>
|
|
@@ -150,7 +152,7 @@
|
|
|
150
152
|
</header>
|
|
151
153
|
|
|
152
154
|
<h1 data-i18n-html="title">把任何视频,塞进任何限制。<br><em>并且证明画质没变。</em></h1>
|
|
153
|
-
<p class="sub" data-i18n-html="sub"
|
|
155
|
+
<p class="sub" data-i18n-html="sub">微信和 Discord 都只让发 20 MB,邮件也差不多。选一个数字,剩下的交给它:算码率、降分辨率、两遍编码,最后用 <b>VMAF</b> 告诉你画质掉没掉。</p>
|
|
154
156
|
|
|
155
157
|
<div class="card" id="card">
|
|
156
158
|
<div id="drop" class="drop" role="button" tabindex="0">
|
|
@@ -172,9 +174,8 @@
|
|
|
172
174
|
<div>
|
|
173
175
|
<div class="label" data-i18n="target">目标大小</div>
|
|
174
176
|
<div class="chips" id="presets">
|
|
175
|
-
<button class="chip on" data-size="
|
|
176
|
-
<button class="chip" data-size="
|
|
177
|
-
<button class="chip" data-size="20mb">20 MB<small data-i18n="email">邮件</small></button>
|
|
177
|
+
<button class="chip on" data-size="20mb">20 MB<small data-i18n="wechat">微信 / Discord</small></button>
|
|
178
|
+
<button class="chip" data-size="16mb">16 MB<small>WhatsApp</small></button>
|
|
178
179
|
<button class="chip" data-size="5mb">5 MB<small data-i18n="web">网页</small></button>
|
|
179
180
|
</div>
|
|
180
181
|
<div class="row">
|
|
@@ -217,7 +218,7 @@
|
|
|
217
218
|
|
|
218
219
|
<footer>
|
|
219
220
|
<div><span class="kbd">npx halfsize video.mp4 --to 25mb</span></div>
|
|
220
|
-
<div data-i18n="footer">本地 ffmpeg · 零上传 · MIT</div>
|
|
221
|
+
<div><span data-i18n="footer">本地 ffmpeg · 零上传 · MIT</span> · <a href="https://github.com/YKLovePython/halfsize" target="_blank" rel="noopener">GitHub ★</a> · <a href="https://YKLovePython.github.io/halfsize/" target="_blank" rel="noopener">Guides</a></div>
|
|
221
222
|
</footer>
|
|
222
223
|
</div>
|
|
223
224
|
|
|
@@ -226,7 +227,7 @@ const I18N = {
|
|
|
226
227
|
zh: {
|
|
227
228
|
pill: "本地运行 · 不上传 · 无账号",
|
|
228
229
|
title: "把任何视频,塞进任何限制。<br><em>并且证明画质没变。</em>",
|
|
229
|
-
sub: "
|
|
230
|
+
sub: "微信和 Discord 都只让发 20 MB,邮件也差不多。选一个数字,剩下的交给它:算码率、降分辨率、两遍编码,最后用 <b>VMAF</b> 告诉你画质掉没掉。",
|
|
230
231
|
dropBig: "把视频拖进来,或点击选择",
|
|
231
232
|
dropSmall: "MP4 · MOV · MKV · AVI · WebM · 不超过 8 GB",
|
|
232
233
|
target: "目标大小",
|
|
@@ -242,8 +243,7 @@ const I18N = {
|
|
|
242
243
|
verifying: "正在测画质…",
|
|
243
244
|
done: "完成",
|
|
244
245
|
uploading: "上传中",
|
|
245
|
-
wechat: "微信",
|
|
246
|
-
email: "邮件",
|
|
246
|
+
wechat: "微信 / Discord",
|
|
247
247
|
web: "网页",
|
|
248
248
|
smallerLabel: "更小",
|
|
249
249
|
accuracyUnder: (p) => `比目标小 ${p}%`,
|
|
@@ -266,7 +266,7 @@ const I18N = {
|
|
|
266
266
|
en: {
|
|
267
267
|
pill: "runs locally · no upload · no account",
|
|
268
268
|
title: "Fit any video into any limit.<br><em>And prove nothing changed.</em>",
|
|
269
|
-
sub: "WeChat
|
|
269
|
+
sub: "WeChat and Discord both cap videos at 20 MB. Email is about the same. Pick the number. It does the rest: bitrate math, resolution, two-pass encoding, then measures the result with <b>VMAF</b>.",
|
|
270
270
|
dropBig: "Drop a video, or click to choose",
|
|
271
271
|
dropSmall: "MP4 · MOV · MKV · AVI · WebM · up to 8 GB",
|
|
272
272
|
target: "Target size",
|
|
@@ -282,8 +282,7 @@ const I18N = {
|
|
|
282
282
|
verifying: "measuring quality…",
|
|
283
283
|
done: "done",
|
|
284
284
|
uploading: "uploading",
|
|
285
|
-
wechat: "WeChat",
|
|
286
|
-
email: "email",
|
|
285
|
+
wechat: "WeChat / Discord",
|
|
287
286
|
web: "web",
|
|
288
287
|
smallerLabel: "smaller",
|
|
289
288
|
accuracyUnder: (p) => `${p}% under target`,
|
|
@@ -320,7 +319,7 @@ const drop = $("drop"), fileInput = $("file"), editor = $("editor"), preview = $
|
|
|
320
319
|
const meta = $("meta"), fname = $("fname"), go = $("go"), progress = $("progress");
|
|
321
320
|
const fill = $("fill"), pct = $("pct"), speed = $("speed"), result = $("result"), error = $("error");
|
|
322
321
|
|
|
323
|
-
let state = { id: null, target: "
|
|
322
|
+
let state = { id: null, target: "20mb", codec: "h264", data: null };
|
|
324
323
|
|
|
325
324
|
function fmtBytes(n) {
|
|
326
325
|
if (!n && n !== 0) return "—";
|