Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

Overview → minute-precision time range → hourly drill-down → calendar → projects and task trees → search and Fast filters → JSON / CSV export → model pricing → themes and languages.

[Try the interactive demo](https://zjay26.github.io/codex-usage/?lang=en) · [High-resolution video](docs/media/codex-usage-demo-en.mp4) · [30-second promo](docs/media/codex-usage-promo-en.mp4) ([vertical](docs/media/codex-usage-promo-en-vertical.mp4))
[Try the interactive demo](https://zjay26.github.io/codex-usage/?lang=en) · [High-resolution video](docs/media/codex-usage-demo-en.mp4) · [Promo film](docs/media/codex-usage-promo-en.mp4) ([vertical](docs/media/codex-usage-promo-en-vertical.mp4))

> Recorded directly from the current Dashboard with healthy, fully priced synthetic data. The online demo does not read your files, set cookies, run analytics, or make external requests.

Expand Down Expand Up @@ -309,7 +309,7 @@ npm test

By default, `npm test` builds and launches a real Go binary in a temporary directory. Set `CODEX_USAGE_BIN` to reuse an existing build.

Regenerate README animations, videos, and screenshots with `npm run capture:media`, and the 30-second promo with `npm run capture:promo`; see [media notes](docs/media/README.md) for dependencies, scenarios, and recording checks.
Regenerate README animations, videos, and screenshots with `npm run capture:media`, and the promo film with `npm run capture:promo`; see [media notes](docs/media/README.md) for dependencies, scenarios, and recording checks.

Current [CI](https://github.com/zJay26/codex-usage/actions/workflows/ci.yml) covers Go tests and vet on Windows, Linux, and macOS Apple Silicon, Linux concurrency checks, Dashboard tests, and one native macOS install/uninstall/reinstall cycle; merges to main also run six-target cross-builds. Release publication additionally repeats the native install checks three times on both macOS Apple Silicon and Intel.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

概览 → 分钟级时间查询 → 小时下钻 → 每日月历 → 项目与任务树 → 搜索与 Fast 筛选 → JSON / CSV 导出 → 模型定价 → 明暗主题与中英切换。

[亲手试用在线 Demo](https://zjay26.github.io/codex-usage/?lang=zh-CN) · [高清演示视频](docs/media/codex-usage-demo-zh.mp4) · [30 秒宣传片](docs/media/codex-usage-promo-zh.mp4)([竖屏版](docs/media/codex-usage-promo-zh-vertical.mp4))
[亲手试用在线 Demo](https://zjay26.github.io/codex-usage/?lang=zh-CN) · [高清演示视频](docs/media/codex-usage-demo-zh.mp4) · [宣传片](docs/media/codex-usage-promo-zh.mp4)([竖屏版](docs/media/codex-usage-promo-zh-vertical.mp4))

> 演示使用完整定价、无异常记录的合成数据,直接录制当前 Dashboard。在线 Demo 不读取你的文件、不设 Cookie、无埋点或外部请求。

Expand Down Expand Up @@ -311,7 +311,7 @@ npm test

`npm test` 默认在临时目录构建并启动真实 Go 二进制;设置 `CODEX_USAGE_BIN` 可以复用已有构建产物。

README 动图、视频和截图可通过 `npm run capture:media` 重新生成,30 秒宣传片可通过 `npm run capture:promo` 重新生成;依赖、演示场景与录制检查见 [媒体说明](docs/media/README.md)。
README 动图、视频和截图可通过 `npm run capture:media` 重新生成,宣传片可通过 `npm run capture:promo` 重新生成;依赖、演示场景与录制检查见 [媒体说明](docs/media/README.md)。

当前 [CI](https://github.com/zJay26/codex-usage/actions/workflows/ci.yml) 覆盖 Windows、Linux、macOS Apple Silicon 的 Go 测试与 vet、Linux 并发检查、Dashboard 测试,以及 macOS 上的一轮安装、卸载和重装;合并到 main 后还会做六目标交叉构建。Release 发布前还会在 macOS Apple Silicon / Intel 上各重复三轮原生安装检查。

Expand Down
42 changes: 37 additions & 5 deletions docs/media/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,52 @@ The export dialog is demonstrated without downloading files. Software installati

## Promo film

`npm run capture:promo` renders a 30-second promotional film from the same synthetic demo, in Chinese and English, landscape and vertical:
`npm run capture:promo` renders a 59-second promotional film in Chinese and English, in landscape and vertical cuts:

- `codex-usage-promo-zh.mp4` and `codex-usage-promo-en.mp4`: 1920 × 1080, 30 fps, H.264 + AAC.
- `codex-usage-promo-zh-vertical.mp4` and `codex-usage-promo-en-vertical.mp4`: 1080 × 1920 for short-video platforms.
- `codex-usage-promo-poster.jpg` and `codex-usage-promo-poster-en.jpg`: landscape posters with a play badge.

[![Codex Usage promo poster](codex-usage-promo-poster-en.jpg)](codex-usage-promo-en.mp4)

The film opens with a question and the brand, then shows ten features, one idea per shot:

1. Totals and cost
2. The last 30 days of daily usage
3. Hourly usage
4. A minute-precision range since the last reset
5. The calendar with its Standard / Fast split
6. Breakdowns by model and project
7. Session search
8. The task tree
9. Export, live usage updates, and software updates
10. Theme, language, and mobile layout

It closes with the privacy promise and install instructions.

Each shot uses the same devices. The camera settles on one area, the rest of the page dims, a label names what matters, and a cursor performs the click that leads to the next still.

The film uses its own synthetic ledger, `?scenario=promo` in `scripts/demo-api.js`, with heavier, more realistic use than the default demo:

- About 2.38B tokens and $3,100 of API-equivalent cost over 30 days.
- A weekday rhythm, a release-day spike, and idle Sundays.
- Seven projects and task-like Session titles in the viewer's language.
- Subagent trees and a mock "update available" state.

The default online demo and its tests are unchanged.

The pipeline has three steps, all offline:

1. `scripts/promo/assets.mjs` captures 2× Dashboard stills of each feature and the rectangles the camera focuses on. The portrait cut uses a 1000 × 1150 Dashboard viewport so the panel fills the frame. The demo notice banner is left out of the stills; the film carries its own "synthetic demo data" label instead.
2. `scripts/promo/promo.html`, `promo.css`, and `promo.js` compose the scenes. Every frame is a pure function of time, so `scripts/capture-promo.mjs` seeks frame by frame and pipes lossless screenshots into FFmpeg. There is no real-time recording, dropped frames, or timing drift.
3. `scripts/promo/music.mjs` synthesizes the soundtrack (pads, bass, drums, plucks, bells, and whooshes) in Node, then normalizes it to −16 LUFS with FFmpeg's two-pass `loudnorm`. The soundtrack is generated, so it carries no licensing obligations.
1. `scripts/promo/assets.mjs` captures 2× Dashboard stills for every beat, together with the rectangles that the camera, spotlight, labels, and cursor are anchored to. The portrait cut uses a 1000 × 1150 Dashboard viewport so the panel fills the frame. The demo notice banner is left out of the stills; the film carries its own "synthetic demo data" label instead.
2. `scripts/promo/promo.html`, `promo.css`, and `promo.js` compose the scenes from a beat sheet. Every frame is a pure function of time, so `scripts/capture-promo.mjs` seeks frame by frame and pipes lossless screenshots into FFmpeg. There is no real-time recording, dropped frames, or timing drift.
3. `scripts/promo/music.mjs` synthesizes the soundtrack in Node (pads, bass, drums, plucks, bells, and whooshes), then normalizes it to −16 LUFS with FFmpeg's two-pass `loudnorm`. The soundtrack is generated, so it carries no licensing obligations.

`scripts/promo/timeline.json` is the single source of scene timing for both picture and sound. Several environment variables help when rendering or iterating:

- `MEDIA_LOCALE=zh-CN|en` and `PROMO_FORMAT=landscape|vertical` render a single version.
- `PROMO_PREVIEW=1` writes only one review still per second, plus the posters.
- `PROMO_ASSETS=<dir>` caches the captured stills between runs.

`scripts/promo/timeline.json` is the single source of scene timing for both picture and sound. `MEDIA_LOCALE=zh-CN|en` and `PROMO_FORMAT=landscape|vertical` render a single version. `PROMO_PREVIEW=1` writes only the review keyframes and posters. Rendering checks each MP4's resolution, frame count, duration, audio stream, and size. Review stills and `render.json` are written to `../../dist/promo-review/`.
Rendering checks each MP4's resolution, frame count, duration, audio stream, and size. Review stills and `render.json` are written to `../../dist/promo-review/`.

The copy uses system fonts, the same as the Dashboard. Segoe UI Variable and Microsoft YaHei were used for the committed renders, so other systems may show slightly different typography.
Binary file modified docs/media/codex-usage-promo-en-vertical.mp4
Binary file not shown.
Binary file modified docs/media/codex-usage-promo-en.mp4
Binary file not shown.
Binary file modified docs/media/codex-usage-promo-poster-en.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/codex-usage-promo-poster.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/codex-usage-promo-zh-vertical.mp4
Binary file not shown.
Binary file modified docs/media/codex-usage-promo-zh.mp4
Binary file not shown.
13 changes: 8 additions & 5 deletions scripts/capture-promo.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ const sizes = { landscape: { width: 1920, height: 1080 }, vertical: { width: 108
const stillViewports = { landscape: { width: 1440, height: 900 }, vertical: { width: 1000, height: 1150 } };
const frameCount = Math.round(timeline.duration * timeline.fps);
const tempDir = await mkdtemp(path.join(tmpdir(), "codex-usage-promo-"));
const assetsDir = path.join(tempDir, "assets");
// PROMO_ASSETS keeps captured Dashboard stills between runs while iterating on the composition.
const assetsDir = process.env.PROMO_ASSETS ? path.resolve(process.env.PROMO_ASSETS) : path.join(tempDir, "assets");

function outputName(locale, format) {
return `codex-usage-promo-${locale === "en" ? "en" : "zh"}${format === "vertical" ? "-vertical" : ""}.mp4`;
}

// One review still per scene (its settled middle) plus the poster time.
// One review still per second, named after its scene, plus the poster time.
function reviewTimes() {
const times = timeline.scenes.map(s => ({ name: s.id, t: s.id === "cta" ? s.end - .2 : s.start + (s.end - s.start) * .62 }));
const times = [];
for (let t = .5; t < timeline.duration; t += 1) times.push({ name: timeline.scenes.findLast(s => s.start <= t).id, t });
return [...times, { name: "poster", t: timeline.poster }];
}

Expand Down Expand Up @@ -112,7 +114,7 @@ async function verify(file, format) {
frames: Number(video.nb_read_frames) === frameCount,
duration: Math.abs(Number(probe.format.duration) - timeline.duration) < .1,
audio: audio?.codec_name === "aac",
size: size > 200_000 && size < 16_000_000
size: size > 200_000 && size < 30_000_000
};
const failed = Object.entries(checks).filter(([, ok]) => !ok).map(([name]) => name);
if (failed.length) throw new Error(`${file} failed checks: ${failed.join(", ")} ${JSON.stringify(probe)}`);
Expand All @@ -130,7 +132,8 @@ try {
// Both languages are captured so the interface scene can show the language switch.
for (const locale of ["zh-CN", "en"]) for (const format of formats) {
const dir = path.join(assetsDir, `${locale}-${format}`);
const manifest = await captureAssets(browser, server.baseURL, locale, dir, stillViewports[format]);
const cached = process.env.PROMO_ASSETS ? await readFile(path.join(dir, "manifest.json"), "utf8").then(JSON.parse, () => null) : null;
const manifest = cached || await captureAssets(browser, server.baseURL, locale, dir, stillViewports[format]);
manifests[`${locale}-${format}`] = { ...manifest, base: pathToFileURL(dir).href + "/" };
console.log(`${locale} ${format}: captured ${Object.keys(manifest.shots).length} Dashboard stills`);
}
Expand Down
Loading
Loading