Skip to content

Commit 144d9ed

Browse files
Merge pull request #21 from Factory-AI/ain3sh/showcase-aspect-ratio
2 parents 4014446 + 626830e commit 144d9ed

3 files changed

Lines changed: 65 additions & 8 deletions

File tree

plugins/droid-control/remotion/src/compositions/Showcase.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ export const showcaseSchema = z.object({
4646
clipDuration: z.number().optional(),
4747
speed: z.number().positive().optional(),
4848
fidelity: fidelitySchema.optional(),
49+
// How clip video is sized inside its panel.
50+
// - "contain" (default): preserve aspect ratio, letterbox if needed. Safe default.
51+
// - "cover": fill the panel, crop overflow. Use when the clip aspect doesn't match the
52+
// panel aspect (e.g. 16:9 landscape browser capture in a near-square side-by-side panel)
53+
// and you'd rather crop edges than see giant black bars.
54+
// - "fill": stretch to fill the panel (distorts aspect). Rarely what you want.
55+
objectFit: z.enum(['contain', 'cover', 'fill']).optional(),
4956
});
5057

5158
const TITLE_DURATION_S = 4;
@@ -68,7 +75,8 @@ const SingleLayout: React.FC<{
6875
config: ReturnType<typeof getPresetConfig>;
6976
palette: ReturnType<typeof getPalette>;
7077
windowTitle?: string;
71-
}> = ({ clip, config, palette, windowTitle }) => {
78+
objectFit: 'contain' | 'cover' | 'fill';
79+
}> = ({ clip, config, palette, windowTitle, objectFit }) => {
7280
const { width, height } = useVideoConfig();
7381

7482
const frameW = width - 2 * config.margin;
@@ -91,7 +99,7 @@ const SingleLayout: React.FC<{
9199
>
92100
<Video
93101
src={staticFile(clip)}
94-
objectFit="contain"
102+
objectFit={objectFit}
95103
style={{
96104
width: '100%',
97105
height: '100%',
@@ -108,7 +116,8 @@ const SideBySideLayout: React.FC<{
108116
labels: string[];
109117
config: ReturnType<typeof getPresetConfig>;
110118
palette: ReturnType<typeof getPalette>;
111-
}> = ({ clips, labels, config, palette }) => {
119+
objectFit: 'contain' | 'cover' | 'fill';
120+
}> = ({ clips, labels, config, palette, objectFit }) => {
112121
const { width, height } = useVideoConfig();
113122

114123
const totalW = width - 2 * config.margin;
@@ -139,7 +148,7 @@ const SideBySideLayout: React.FC<{
139148
>
140149
<Video
141150
src={staticFile(clip)}
142-
objectFit="contain"
151+
objectFit={objectFit}
143152
style={{
144153
width: '100%',
145154
height: '100%',
@@ -167,6 +176,7 @@ export const ShowcaseComposition: React.FC<z.infer<typeof showcaseSchema>> = (
167176

168177
const titleFrames = TITLE_DURATION_S * fps;
169178
const clipFrames = Math.ceil((props.clipDuration ?? 60) * fps);
179+
const objectFit = props.objectFit ?? 'contain';
170180

171181
const spotlights = useMemo(
172182
() =>
@@ -220,13 +230,15 @@ export const ShowcaseComposition: React.FC<z.infer<typeof showcaseSchema>> = (
220230
labels={props.labels}
221231
config={config}
222232
palette={palette}
233+
objectFit={objectFit}
223234
/>
224235
) : props.clips[0] ? (
225236
<SingleLayout
226237
clip={props.clips[0]}
227238
config={config}
228239
palette={palette}
229240
windowTitle={props.windowTitle}
241+
objectFit={objectFit}
230242
/>
231243
) : null}
232244
</>

plugins/droid-control/skills/capture/SKILL.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,29 @@ The command that invoked you should have provided:
2626
Before recording anything:
2727

2828
- Terminal size is consistent across all sessions (`--cols 120 --rows 36`)
29+
- **Browser viewport size matches the composition layout** (see "Browser viewport sizing" below) — mismatched aspects letterbox in the final video
2930
- Branch/worktree paths and env vars are correct
3031
- Recording format matches the driver: `.cast` for tuistory, `.mp4` for true-input, screenshots for agent-browser
31-
- If comparing branches, both sessions use identical terminal dimensions and launch parameters
32+
- If comparing branches, both sessions use identical terminal / viewport dimensions and launch parameters
3233
- For `droid-dev` captures, `--repo-root` is **mandatory**`tctl` will refuse to launch without it
3334
- **Color env vars are set** (see below)
3435

36+
### Browser viewport sizing
37+
38+
Panel aspect ratio in the final composition is **layout-dependent**. At the default 1920×1080 output with factory preset margins, the window-chrome panels that clips render into come out roughly:
39+
40+
| Layout | Panel aspect | Recommended browser viewport |
41+
|---|---|---|
42+
| `single` | ~1760×920 (≈16:9 landscape) | **1280×720** or **1440×810** |
43+
| `side-by-side` | ~872×920 per panel (≈8:9, near-square / slight portrait) | **960×1000**, **900×1000**, or **1024×1080** |
44+
45+
Feeding a 16:9 landscape recording into a near-square side-by-side panel triggers `objectFit: "contain"` letterboxing — you get a thin strip of content with giant black bars above and below. Two ways to avoid it:
46+
47+
1. **Match aspects at capture time** (preferred) — pick the viewport from the table above based on the committed layout.
48+
2. **Opt into cropping at compose time** — pass `"objectFit": "cover"` in showcase props. Crops the edges of the clip instead of letterboxing. Use when the relevant UI is centered and the clip's edges are expendable.
49+
50+
If you're unsure of the layout when capturing, default to `960×1000` — it is workable in both layouts (slight horizontal letterbox in `single`, no letterbox in `side-by-side`).
51+
3552
```bash
3653
TCTL=${DROID_PLUGIN_ROOT}/bin/tctl
3754
# RUN_ID and RUN_DIR should already be set by the parent (see droid-control ground rule 5)
@@ -62,9 +79,15 @@ $TCTL launch "droid-dev" -s ${RUN_ID}-after --backend tuistory \
6279
--env FORCE_COLOR=3 --env COLORTERM=truecolor
6380
```
6481

65-
**Browser:**
82+
**Browser:** size the viewport to match the composition layout (see table above).
83+
6684
```bash
67-
agent-browser open <url>
85+
# side-by-side layout → near-square panel
86+
agent-browser open <url> --viewport 960x1000
87+
agent-browser record start ${RUN_DIR}/demo.webm
88+
89+
# single layout → 16:9 panel
90+
agent-browser open <url> --viewport 1280x720
6891
agent-browser record start ${RUN_DIR}/demo.webm
6992
```
7093

@@ -126,7 +149,8 @@ Hand these to the **compose** stage:
126149
- screenshots: [/tmp/proof-1.png, /tmp/proof-2.png]
127150
- keys: /tmp/keys.tsv (if keystroke logging was requested)
128151
- driver: tuistory | true-input | agent-browser
129-
- terminal_size: 120x36
152+
- terminal_size: 120x36 # for tuistory / true-input
153+
- viewport: 960x1000 # for agent-browser; report so compose knows the clip aspect
130154
```
131155

132156
## Recovery

plugins/droid-control/skills/compose/SKILL.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,26 @@ Set the `speed` prop to hit the target: if the raw recording is 3 minutes and th
108108

109109
`.mp4`, `.webm`, and `.png` clips are passed through to Remotion unchanged except for staging into `public/`. Re-encode non-`.cast` clips manually only if their pixel format or dimensions are invalid.
110110

111+
### Clip aspect ratio (mandatory check for browser captures)
112+
113+
At the default 1920×1080 output with factory preset margins, panels come out roughly:
114+
115+
| Layout | Panel aspect |
116+
|---|---|
117+
| `single` | ~1760×920 (≈16:9 landscape) |
118+
| `side-by-side` | ~872×920 per panel (≈8:9, near-square / slight portrait) |
119+
120+
`.cast` conversions target panel aspect automatically. **Pre-recorded `.mp4` / `.webm` clips do not** — if the clip aspect doesn't match the panel aspect, the clip will letterbox (with the default `objectFit: "contain"`) or crop (with `"cover"`).
121+
122+
**Common pitfall**: browser captures are typically 16:9 landscape (e.g. 1280×720). Dropped into a `side-by-side` layout they render as a thin band with giant black bars above and below.
123+
124+
Two fixes, in priority order:
125+
126+
1. **Re-capture at a panel-friendly viewport** — go back to the capture stage and set viewport to ~960×1000 for `side-by-side`, ~1280×720 for `single`.
127+
2. **Pass `"objectFit": "cover"` in props** — crops the clip edges to fill the panel. Acceptable when the relevant UI is centered and edges are expendable. Not acceptable if cropped content matters (e.g. sidebar UI cut off).
128+
129+
`.cast` clips rarely need this since their rendered aspect is derived from cols/rows; it's almost always a browser-capture concern.
130+
111131
### Duration checkpoint (mandatory, before proceeding)
112132

113133
Check whether the planned speed factor produces a final duration within the pacing table's target range:
@@ -182,6 +202,7 @@ Use a run-scoped props path like `$PROPS`; do not reuse a global `/tmp/showcase-
182202
| `windowTitle` | `string` | no | Text in the window title bar |
183203
| `width` | `number` | no | Output width (default: 2560 for inspect, else 1920) |
184204
| `height` | `number` | no | Output height (default: 1440 for inspect, else 1080) |
205+
| `objectFit` | `"contain" \| "cover" \| "fill"` | no | How each clip fits its panel. Default `"contain"` (letterbox to preserve aspect). Use `"cover"` when clip aspect doesn't match panel aspect and you'd rather crop than see black bars. See "Clip aspect ratio" below. |
185206

186207
### Preset quick reference
187208

0 commit comments

Comments
 (0)