@hyperframes/studio 0.8.58 → 0.8.60
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/dist/assets/{hyperframes-player-zGyOcwwh.js → hyperframes-player-Di7GyCOP.js} +3 -3
- package/dist/assets/{index-DKJpHYYd.css → index-CBKb_0TI.css} +1 -1
- package/dist/assets/{index-d8ferjRc.js → index-Dpq2lkOm.js} +190 -190
- package/dist/assets/{index-ZJaRCDYV.js → index-Dx_pt9m4.js} +1 -1
- package/dist/assets/{index-BcE2OSVA.js → index-jAjhQTrB.js} +1 -1
- package/dist/index.d.ts +549 -13
- package/dist/index.html +2 -2
- package/dist/index.js +23133 -21941
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -7
- package/src/components/StudioHeader.tsx +2 -13
- package/src/components/TimelineToolbar.test.tsx +42 -0
- package/src/components/TimelineToolbar.tsx +22 -8
- package/src/components/editor/audioInspectorInput.test.ts +55 -0
- package/src/components/editor/audioInspectorInput.ts +55 -0
- package/src/components/editor/propertyPanelFill.tsx +3 -2
- package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +172 -0
- package/src/components/editor/propertyPanelFlatMediaSection.tsx +87 -1
- package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +92 -0
- package/src/components/editor/propertyPanelFlatPrimitives.tsx +13 -9
- package/src/components/editor/propertyPanelFlatSliderReadout.tsx +106 -0
- package/src/components/editor/useColorGradingController.ts +2 -6
- package/src/components/icons/AudioMetersIcon.tsx +16 -0
- package/src/components/icons/InspectorIcon.tsx +18 -0
- package/src/components/icons/ReverseGradientIcon.tsx +23 -0
- package/src/components/icons/RippleEditIcon.tsx +19 -0
- package/src/components/nle/AudioMeterStrip.test.tsx +395 -0
- package/src/components/nle/AudioMeterStrip.tsx +430 -0
- package/src/components/nle/TimelinePane.tsx +29 -23
- package/src/components/ui/Menu.test.tsx +3 -4
- package/src/components/ui/Menu.tsx +18 -5
- package/src/components/ui/Select.tsx +7 -1
- package/src/components/ui/Tooltip.tsx +6 -1
- package/src/contexts/TimelineEditContext.tsx +4 -0
- package/src/hooks/useTimelineAssetDropOps.ts +9 -1
- package/src/index.ts +16 -0
- package/src/player/components/AudioWaveform.tsx +16 -7
- package/src/player/components/ImageThumbnail.tsx +3 -5
- package/src/player/components/LayerDisclosureRow.tsx +9 -4
- package/src/player/components/PlayheadIndicator.tsx +3 -3
- package/src/player/components/Timeline.test.ts +22 -31
- package/src/player/components/Timeline.tsx +34 -7
- package/src/player/components/TimelineAutomationLane.test.tsx +1 -1
- package/src/player/components/TimelineAutomationLane.tsx +5 -5
- package/src/player/components/TimelineCanvas.tsx +28 -71
- package/src/player/components/TimelineClip.test.tsx +41 -4
- package/src/player/components/TimelineClip.tsx +25 -5
- package/src/player/components/TimelineClipDiamonds.test.tsx +3 -1
- package/src/player/components/TimelineClipDiamonds.tsx +2 -2
- package/src/player/components/TimelineClipFades.test.tsx +171 -0
- package/src/player/components/TimelineClipFades.tsx +302 -0
- package/src/player/components/TimelineDiamondConnectors.tsx +2 -2
- package/src/player/components/TimelineEmptyState.tsx +1 -1
- package/src/player/components/TimelineFxButton.tsx +10 -8
- package/src/player/components/TimelineGroupHeader.tsx +18 -9
- package/src/player/components/TimelineGroupLaneLabels.tsx +12 -5
- package/src/player/components/TimelineGroupRow.tsx +1 -1
- package/src/player/components/TimelineLanes.test.tsx +14 -0
- package/src/player/components/TimelineOverlays.tsx +164 -165
- package/src/player/components/TimelineParts.test.tsx +101 -0
- package/src/player/components/TimelineParts.tsx +74 -0
- package/src/player/components/TimelinePartsParity.test.tsx +58 -0
- package/src/player/components/TimelinePropertyLanes.test.tsx +1 -1
- package/src/player/components/TimelineProvider.tsx +25 -0
- package/src/player/components/TimelineRazorInteraction.tsx +2 -2
- package/src/player/components/TimelineRuler.tsx +3 -2
- package/src/player/components/TimelineRulerPart.tsx +22 -0
- package/src/player/components/TimelineShortcutHint.tsx +2 -2
- package/src/player/components/TimelineTrackHeader.test.tsx +1 -1
- package/src/player/components/TimelineTrackHeader.tsx +4 -6
- package/src/player/components/TimelineTrackPlainHeader.tsx +10 -3
- package/src/player/components/VideoThumbnail.tsx +3 -5
- package/src/player/components/timelineMotionStyles.test.ts +96 -30
- package/src/player/components/timelineProviderStateBuilders.ts +20 -0
- package/src/player/components/timelineTheme.test.ts +14 -0
- package/src/player/components/timelineTheme.ts +12 -11
- package/src/player/components/useTimelineProviderState.tsx +19 -12
- package/src/player/lib/timelineElementHelpers.ts +20 -0
- package/src/player/store/timelineElement.ts +5 -0
- package/src/styles/hex-baseline.json +4 -23
- package/src/styles/studio.css +43 -47
- package/src/styles/theme.css +109 -1
- package/src/styles/theme.test.ts +9 -0
- package/src/utils/audioMeterMath.test.ts +44 -0
- package/src/utils/audioMeterMath.ts +79 -0
- package/src/utils/audioMeterVisibility.ts +14 -0
- package/src/utils/studioHelpers.ts +21 -0
- package/src/utils/studioUiPreferences.test.ts +10 -0
- package/src/utils/studioUiPreferences.ts +5 -0
- package/src/utils/timelineAssetDrop.test.ts +32 -0
- package/src/utils/timelineAssetDrop.ts +6 -1
|
@@ -377,6 +377,27 @@ export async function resolveDroppedAssetDuration(
|
|
|
377
377
|
return duration;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
export function mediaMetadataUrl(projectId: string, assetPath: string): string {
|
|
381
|
+
return `/api/projects/${encodeURIComponent(projectId)}/media/metadata?path=${encodeURIComponent(assetPath)}`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Dropped video audio stream from the metadata endpoint. Failure answers false so the drop still lands muted. */
|
|
385
|
+
export async function resolveDroppedAssetHasAudio(
|
|
386
|
+
projectId: string,
|
|
387
|
+
assetPath: string,
|
|
388
|
+
kind: TimelineAssetKind,
|
|
389
|
+
): Promise<boolean> {
|
|
390
|
+
if (kind !== "video") return false;
|
|
391
|
+
try {
|
|
392
|
+
const response = await fetch(mediaMetadataUrl(projectId, assetPath));
|
|
393
|
+
if (!response.ok) return false;
|
|
394
|
+
const data = (await response.json()) as { metadata?: { hasAudio?: boolean } } | null;
|
|
395
|
+
return data?.metadata?.hasAudio === true;
|
|
396
|
+
} catch {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
380
401
|
export async function resolveDroppedAssetDimensions(
|
|
381
402
|
projectId: string,
|
|
382
403
|
assetPath: string,
|
|
@@ -161,3 +161,13 @@ describe("timeline zoom pin persistence", () => {
|
|
|
161
161
|
expect(prefs.timelineManualZoomPercent).toBeUndefined();
|
|
162
162
|
});
|
|
163
163
|
});
|
|
164
|
+
|
|
165
|
+
describe("audioMetersVisible preference", () => {
|
|
166
|
+
it("round-trips and ignores a non-boolean", () => {
|
|
167
|
+
const storage = createStorage();
|
|
168
|
+
writeStudioUiPreferences({ audioMetersVisible: false }, storage);
|
|
169
|
+
expect(readStudioUiPreferences(storage).audioMetersVisible).toBe(false);
|
|
170
|
+
storage.setItem("hf-studio-ui-preferences", JSON.stringify({ audioMetersVisible: "no" }));
|
|
171
|
+
expect(readStudioUiPreferences(storage).audioMetersVisible).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -25,6 +25,8 @@ export interface StudioUiPreferences {
|
|
|
25
25
|
snapToGrid?: boolean;
|
|
26
26
|
/** Timeline magnet: snap clip drags/trims/drops to playhead, clip edges, and beats. */
|
|
27
27
|
timelineSnapEnabled?: boolean;
|
|
28
|
+
/** Audio level meters at the timeline's right edge; hidden unless enabled here. */
|
|
29
|
+
audioMetersVisible?: boolean;
|
|
28
30
|
/** Keeps the main track gapless: deleting a clip closes the gap. Distinct
|
|
29
31
|
* from `timelineSnapEnabled` ("Magnet", drag/trim snapping). */
|
|
30
32
|
rippleEditEnabled?: boolean;
|
|
@@ -140,6 +142,9 @@ function readStorage(storage: Storage | null, key: string): StudioUiPreferences
|
|
|
140
142
|
if (typeof parsed.timelineSnapEnabled === "boolean") {
|
|
141
143
|
preferences.timelineSnapEnabled = parsed.timelineSnapEnabled;
|
|
142
144
|
}
|
|
145
|
+
if (typeof parsed.audioMetersVisible === "boolean") {
|
|
146
|
+
preferences.audioMetersVisible = parsed.audioMetersVisible;
|
|
147
|
+
}
|
|
143
148
|
if (typeof parsed.rippleEditEnabled === "boolean") {
|
|
144
149
|
preferences.rippleEditEnabled = parsed.rippleEditEnabled;
|
|
145
150
|
}
|
|
@@ -198,6 +198,38 @@ describe("insertTimelineAssetIntoSource", () => {
|
|
|
198
198
|
});
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
+
describe("buildTimelineAssetInsertHtml — video audio", () => {
|
|
202
|
+
const base = {
|
|
203
|
+
id: "clip_asset",
|
|
204
|
+
hfId: "hf-vid-1",
|
|
205
|
+
assetPath: "assets/clip.mp4",
|
|
206
|
+
kind: "video" as const,
|
|
207
|
+
start: 0,
|
|
208
|
+
duration: 8,
|
|
209
|
+
track: 1,
|
|
210
|
+
zIndex: 2,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
it("inserts a video muted when nothing says it carries audio", () => {
|
|
214
|
+
const html = buildTimelineAssetInsertHtml(base);
|
|
215
|
+
expect(html).toContain(" muted ");
|
|
216
|
+
expect(html).not.toContain("data-has-audio");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("inserts a video with an audio stream audible: data-has-audio and no muted", () => {
|
|
220
|
+
const html = buildTimelineAssetInsertHtml({ ...base, hasAudio: true });
|
|
221
|
+
expect(html).toContain('data-has-audio="true"');
|
|
222
|
+
expect(html).not.toContain("muted");
|
|
223
|
+
expect(html).toContain("playsinline");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("keeps a video without an audio stream muted", () => {
|
|
227
|
+
const html = buildTimelineAssetInsertHtml({ ...base, hasAudio: false });
|
|
228
|
+
expect(html).toContain(" muted ");
|
|
229
|
+
expect(html).not.toContain("data-has-audio");
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
201
233
|
describe("buildTimelineAssetInsertHtml markup quality", () => {
|
|
202
234
|
const base = {
|
|
203
235
|
id: "clip_1",
|
|
@@ -116,6 +116,8 @@ export function buildTimelineAssetInsertHtml(input: {
|
|
|
116
116
|
track: number;
|
|
117
117
|
zIndex: number;
|
|
118
118
|
geometry?: { left: number; top: number; width: number; height: number };
|
|
119
|
+
/** Video only: true inserts `data-has-audio="true"` with no `muted`. Unknown or false stays muted. */
|
|
120
|
+
hasAudio?: boolean;
|
|
119
121
|
}): string {
|
|
120
122
|
const sharedAttrs = `id="${input.id}" data-hf-id="${input.hfId}" class="clip" src="${input.assetPath}" data-start="${input.start}" data-duration="${input.duration}" data-track-index="${input.track}"`;
|
|
121
123
|
const geometry = input.geometry ?? { left: 0, top: 0, width: 640, height: 360 };
|
|
@@ -126,7 +128,10 @@ export function buildTimelineAssetInsertHtml(input: {
|
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
if (input.kind === "video") {
|
|
129
|
-
|
|
131
|
+
// `muted` and `data-has-audio="true"` are mutually exclusive by the lint
|
|
132
|
+
// contract (video_has_audio_but_muted): an audible drop takes the latter.
|
|
133
|
+
const audio = input.hasAudio ? 'data-has-audio="true"' : "muted";
|
|
134
|
+
return `<video ${sharedAttrs} ${audio} playsinline style="${visualStyles}"></video>`;
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
return `<audio ${sharedAttrs} data-volume="1" style="z-index: ${input.zIndex}"></audio>`;
|