Skip to content

Commit 0f7fb27

Browse files
committed
docs(skills): an agent adds media with the duration Studio gives a dropped file
1 parent 351eb48 commit 0f7fb27

3 files changed

Lines changed: 69 additions & 1 deletion

File tree

‎scripts/creator-editing-recipes.test.mjs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,20 @@ test("the Studio skill's safe boxes equal the preview's", async () => {
7373
assert.match(skill, new RegExp(`Action-safe\\s*\\|\\s*${action}%`));
7474
assert.match(skill, new RegExp(`Title-safe\\s*\\|\\s*${title}%`));
7575
});
76+
77+
test("the add-media recipe matches Studio's own drop defaults", async () => {
78+
const helpers = await read("packages/studio/src/utils/studioHelpers.ts");
79+
const defaults = helpers.match(/DEFAULT_TIMELINE_ASSET_DURATION[^=]*=\s*\{([^}]*)\}/)?.[1] ?? "";
80+
const secs = (kind) => defaults.match(new RegExp(`${kind}:\\s*(\\d+)`))?.[1];
81+
const section = (await read(OWNER)).split("## Add media")[1]?.split("\n## ")[0] ?? "";
82+
assert.ok(secs("image"), "could not read Studio's image default");
83+
assert.match(section, new RegExp(`Image: \`data-duration="${secs("image")}"\``));
84+
assert.match(section, new RegExp(`\`${secs("video")}\` only when the length cannot be read`));
85+
const image = section.match(/<img[\s\S]*?\/>/)?.[0] ?? "";
86+
assert.match(image, new RegExp(`data-duration="${secs("image")}"`));
87+
const drop = await read("packages/studio/src/utils/timelineAssetDrop.ts");
88+
for (const attr of ["class=\"clip\"", "data-start", "data-duration", "data-track-index"]) {
89+
assert.ok(drop.includes(attr), `Studio no longer writes ${attr}`);
90+
assert.ok(image.includes(attr.split("=")[0]), `doc example lacks ${attr}`);
91+
}
92+
});

‎skills-manifest.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"files": 11
3535
},
3636
"hyperframes-core": {
37-
"hash": "054fe5577b064927",
37+
"hash": "5944015f0f48d820",
3838
"files": 11
3939
},
4040
"hyperframes-creative": {

‎skills/hyperframes-core/references/creator-editing-recipes.md‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,57 @@ Timeline math: an audio element in the root composition has `data-start` in abso
417417

418418
Timeline math: pick the clips first and say which ones you picked (by id) if the request does not match the file exactly; then add one `delta` to every member's `data-start`, so relative spacing is preserved (here `delta = 40`). Give each copy a new unique `id` and the next unused `data-track-index`; keep `src`, `data-duration`, `data-media-start`, `data-volume` and any `data-automation` as they are. Leave the originals untouched. Check the copies still end inside the composition's duration. Owner: `/hyperframes-core`. Limit: copies of a `<video>` or a sub-composition host follow the same rule, and a copied sub-composition needs its own host `id`.
419419

420+
## Add media (image, video, audio)
421+
422+
Write exactly what Studio writes when a person drops a file on the timeline, so an agent-added clip behaves the same as a dropped one. Studio's source of truth is `DEFAULT_TIMELINE_ASSET_DURATION` in `packages/studio/src/utils/studioHelpers.ts` and `buildTimelineAssetInsertHtml` in `packages/studio/src/utils/timelineAssetDrop.ts`; a test keeps this section equal to them.
423+
424+
- **Image: `data-duration="3"`.** A still has no length of its own, so it gets 3 seconds. Without a `data-duration` it never ends and stays on screen for the rest of the composition.
425+
- **Video and audio: the file's own length** in seconds (`ffprobe -v error -show_entries format=duration -of csv=p=0 file`); `5` only when the length cannot be read. Studio rounds to hundredths.
426+
- **Start: the playhead or the requested time, never a silent `0`.** Studio's asset-panel Add uses the playhead time on track `0`; a drop uses the drop point. Several files dropped together run end to end from the first start.
427+
- Give every clip `id`, `class="clip"`, `data-start`, `data-duration` and `data-track-index`. Video is `muted playsinline`; audio carries `data-volume="1"`.
428+
- Then make sure the root composition's `data-duration` is at least `data-start + data-duration`; Studio extends it, an agent must too, or the clip lies past the end and never plays.
429+
- Images and video sit absolutely positioned, `object-fit: contain`, centred at their natural size (scaled down to fit): `left = (frameWidth - width) / 2`, `top = (frameHeight - height) / 2`. Below, an 800x600 still in a 1920x1080 frame.
430+
431+
```html
432+
<img
433+
id="photo"
434+
class="clip"
435+
src="assets/photo.png"
436+
data-start="4"
437+
data-duration="3"
438+
data-track-index="1"
439+
style="position: absolute; left: 560px; top: 240px; width: 800px; height: 600px; object-fit: contain; z-index: 2"
440+
/>
441+
```
442+
443+
```html
444+
<video
445+
id="broll"
446+
class="clip"
447+
src="assets/broll.mp4"
448+
data-start="4"
449+
data-duration="6.5"
450+
data-track-index="2"
451+
muted
452+
playsinline
453+
style="position: absolute; left: 0px; top: 0px; width: 1920px; height: 1080px; object-fit: contain; z-index: 3"
454+
></video>
455+
```
456+
457+
```html
458+
<audio
459+
id="whoosh"
460+
class="clip"
461+
src="assets/whoosh.mp3"
462+
data-start="4"
463+
data-duration="1.2"
464+
data-track-index="3"
465+
data-volume="1"
466+
></audio>
467+
```
468+
469+
Inside a sub-composition file, `data-start` is scene-local (see `## Align a sound to an on-screen event`). Owner: `/hyperframes-core`.
470+
420471
## Swap a media file
421472

422473
```html

0 commit comments

Comments
 (0)