Feature media: record at 1024x640

The UI has less empty space and is easier to read in posts. The script now checks that the screen is the size of the window, since clicks are at fixed positions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QJfcnstoUXVtcR4FuTuTs
Co-authored-by: Mikael Finstad <finstaden@gmail.com>
This commit is contained in:
Claude
2026-10-21 15:20:33 +00:00
committed by Mikael Finstad
co-authored by Mikael Finstad
parent 4bb0fa1ec4
commit 261a897251
4 changed files with 16 additions and 13 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ Never submit to a store, publish a release or post an announcement without the u
```bash
yarn build && yarn download-ffmpeg-linux-x64
# If missing: apt-get install xvfb xdotool
xvfb-run -a -s "-screen 0 1280x800x24" node script/featureMedia.ts cut-segments
xvfb-run -a -s "-screen 0 1024x640x24" node script/featureMedia.ts cut-segments
```
The clips come from `dist/feature-media-clips`, or are downloaded from https://static.mifi.no/losslesscut (or `FEATURE_MEDIA_CLIPS_URL`; behind a proxy, e.g. in a cloud session, set `NODE_USE_ENV_PROXY=1`). Scenarios can use only the clips there; ask the user to upload new ones. `tears-of-steel.mkv` is a 45 s excerpt (00:20–01:05) of [Tears of Steel](https://mango.blender.org/) with its stereo and 5.1 mixes and 6 subtitle tracks. It's CC BY 3.0, so posts that show it must credit "Tears of Steel (CC) Blender Foundation | mango.blender.org". The results go to `dist/feature-media`. Failed actions don't fail the script, so look at the screenshots (and `<scenario>-app.log`) to check that the demo did what it should. Never commit clips or results.
+6 -3
View File
@@ -1,6 +1,6 @@
// Records screenshots and a video of a feature demo (see script/featureMedia/scenarios.ts and AGENTS.md "Feature media").
// Linux only (captures the X display), e.g. in a cloud session. Run `yarn build` and `yarn download-ffmpeg-linux-x64` first, then:
// xvfb-run -a -s "-screen 0 1280x800x24" node script/featureMedia.ts <scenario> [--clips dir]
// xvfb-run -a -s "-screen 0 1024x640x24" node script/featureMedia.ts <scenario> [--clips dir]
import assert from 'node:assert';
import { createWriteStream } from 'node:fs';
import { access, mkdir, mkdtemp, rename, rm, writeFile } from 'node:fs/promises';
@@ -19,9 +19,12 @@ assert(scenario != null, `Usage: node script/featureMedia.ts <scenario> [--clips
const display = process.env['DISPLAY'];
assert(process.platform === 'linux' && display != null, 'Run under Xvfb on Linux, see the top of this file');
const width = 1280;
const height = 800;
const width = 1024;
const height = 640;
const port = 8099;
// Scenarios click at fixed positions, so the screen must be the size of the window
const { stdout: screenSize } = await execa('xdotool', ['getdisplaygeometry']);
assert(screenSize === `${width} ${height}`, `The screen is ${screenSize}, run Xvfb with -screen 0 ${width}x${height}x24`);
const ffmpeg = 'ffmpeg/linux-x64/lib/ffmpeg';
const outDir = 'dist/feature-media';
await mkdir(outDir, { recursive: true });
+8 -8
View File
@@ -1,6 +1,6 @@
// Feature demo scenarios for script/featureMedia.ts. Each one drives the app through the HTTP API (see docs/api.md):
// `action` runs a keyboard action by name, `shot` saves a screenshot and `wait` pauses so the video shows each step.
// `click`, `key` and `type` are for what actions can't do (dialogs, the tracks panel). Click positions are for the 1280×800 window: find them in a screenshot.
// `click`, `key` and `type` are for what actions can't do (dialogs, the tracks panel). Click positions are for the 1024×640 window: find them in a screenshot.
// Keep each scenario short (10-30 s of video) and about one feature.
import type { Scenario } from './types.ts';
@@ -79,8 +79,8 @@ const scenarios: Record<string, Scenario> = {
await wait(2000);
await shot('tracks');
// The Keep buttons of the 5.1 track and the last 4 subtitles
for (const y of [391, 469, 495, 521, 547]) {
await click(110, y);
for (const y of [325, 417, 443, 469, 495]) {
await click(97, y);
await wait(600);
}
await wait(1000);
@@ -105,13 +105,13 @@ const scenarios: Record<string, Scenario> = {
await action('toggleStreamsSelector');
await wait(1500);
// The menu button of track 2, then "Edit track metadata"
await click(1167, 365);
await click(909, 300);
await wait(1000);
await click(1162, 441);
await click(905, 376);
await wait(1500);
await shot('dialog');
// Edit the title
await click(871, 338);
await click(743, 258);
await wait(800);
await key('ctrl+a');
await type('English stereo mix');
@@ -119,10 +119,10 @@ const scenarios: Record<string, Scenario> = {
await key('Return');
await wait(1000);
// Delete the ENCODER tag
await click(919, 376);
await click(791, 296);
await wait(1500);
await shot('edited');
await click(889, 627);
await click(761, 547);
await wait(1500);
await shot('tracks');
},
+1 -1
View File
@@ -9,7 +9,7 @@ export interface ScenarioContext {
key: (keys: string) => Promise<void>,
/** Types text with xdotool */
type: (text: string) => Promise<void>,
/** Clicks at a position in the 1280×800 window (find it in a screenshot) */
/** Clicks at a position in the 1024×640 window (find it in a screenshot) */
click: (x: number, y: number) => Promise<void>,
/** Saves a screenshot as <scenario>-<name>.png */
shot: (name: string) => Promise<void>,