Adjust guide video annotation timing
CI / Lint (push) Waiting to run
CI / Type Check (push) Waiting to run
CI / Test (push) Waiting to run
CI / Build (push) Waiting to run
Bump Nix package on release / bump (release) Waiting to run
Update Homebrew Cask / update-cask (release) Waiting to run
CI / Lint (push) Waiting to run
CI / Type Check (push) Waiting to run
CI / Test (push) Waiting to run
CI / Build (push) Waiting to run
Bump Nix package on release / bump (release) Waiting to run
Update Homebrew Cask / update-cask (release) Waiting to run
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openscreen",
|
||||
"version": "1.4.11",
|
||||
"version": "1.4.12",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openscreen",
|
||||
"version": "1.4.11",
|
||||
"version": "1.4.12",
|
||||
"dependencies": {
|
||||
"@fix-webm-duration/fix": "^1.0.1",
|
||||
"@pixi/filter-drop-shadow": "^5.2.0",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "openscreen",
|
||||
"private": true,
|
||||
"version": "1.4.11",
|
||||
"version": "1.4.12",
|
||||
"type": "module",
|
||||
"packageManager": "npm@10.9.4",
|
||||
"engines": {
|
||||
|
||||
@@ -65,7 +65,11 @@ describe("buildGuideVideoAnnotations", () => {
|
||||
startMs: 1200,
|
||||
content: "1. Click Settings.",
|
||||
});
|
||||
expect(annotations[0]?.endMs).toBe(3200);
|
||||
expect(annotations[0]?.position.x).toBeGreaterThan(20);
|
||||
expect(annotations[1]?.endMs).toBe(3200);
|
||||
expect(annotations[1]?.position.x).toBeGreaterThan((annotations[0]?.position.x ?? 0) + 34);
|
||||
expect(annotations[1]?.position.y).toBeCloseTo(30.5);
|
||||
expect(annotations[1]).toMatchObject({
|
||||
id: "guide-video-2",
|
||||
type: "magnifier",
|
||||
@@ -79,10 +83,13 @@ describe("buildGuideVideoAnnotations", () => {
|
||||
expect(annotations[2]).toMatchObject({
|
||||
id: "guide-video-3",
|
||||
type: "figure",
|
||||
endMs: 3200,
|
||||
figureData: {
|
||||
arrowDirection: "left",
|
||||
color: "#34B27B",
|
||||
},
|
||||
});
|
||||
expect(annotations[2]?.position.x).toBeGreaterThan(20);
|
||||
});
|
||||
|
||||
it("returns an empty list when no draft exists", () => {
|
||||
@@ -97,7 +104,7 @@ describe("buildGuideVideoAnnotations", () => {
|
||||
expect(annotations).toEqual([]);
|
||||
});
|
||||
|
||||
it("creates 0.3x speed regions for one second at each guide point", () => {
|
||||
it("creates 0.3x speed regions for two seconds at each guide point", () => {
|
||||
let id = 1;
|
||||
const speedRegions = buildGuideVideoSpeedRegions(createSession(), {
|
||||
nextId: () => `guide-speed-${id++}`,
|
||||
@@ -107,7 +114,7 @@ describe("buildGuideVideoAnnotations", () => {
|
||||
{
|
||||
id: "guide-speed-1",
|
||||
startMs: 1200,
|
||||
endMs: 2200,
|
||||
endMs: 3200,
|
||||
speed: 0.3,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -14,13 +14,14 @@ export interface BuildGuideVideoAnnotationsOptions {
|
||||
defaultDurationMs?: number;
|
||||
}
|
||||
|
||||
const DEFAULT_STEP_DURATION_MS = 3200;
|
||||
const DEFAULT_STEP_SLOW_MOTION_DURATION_MS = 1000;
|
||||
const DEFAULT_STEP_DURATION_MS = 2000;
|
||||
const DEFAULT_STEP_SLOW_MOTION_DURATION_MS = 2000;
|
||||
const DEFAULT_STEP_SLOW_MOTION_SPEED = 0.3;
|
||||
const CAPTION_WIDTH = 34;
|
||||
const CAPTION_HEIGHT = 13;
|
||||
const MAGNIFIER_SIZE = 18;
|
||||
const ARROW_SIZE = 10;
|
||||
const ANNOTATION_GAP = 2;
|
||||
|
||||
function clamp(value: number, min: number, max: number) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
@@ -58,15 +59,19 @@ function getCaptionPosition(candidate: GuideStepCandidate | undefined) {
|
||||
|
||||
function getArrowDirection(
|
||||
candidate: GuideStepCandidate | undefined,
|
||||
captionPosition: { x: number; y: number },
|
||||
originPosition: { x: number; y: number },
|
||||
originSize: { width: number; height: number } = {
|
||||
width: CAPTION_WIDTH,
|
||||
height: CAPTION_HEIGHT,
|
||||
},
|
||||
): ArrowDirection {
|
||||
const target = candidate?.position;
|
||||
if (!target) return "right";
|
||||
|
||||
const captionCenterX = captionPosition.x + CAPTION_WIDTH / 2;
|
||||
const captionCenterY = captionPosition.y + CAPTION_HEIGHT / 2;
|
||||
const dx = target.normalizedX * 100 - captionCenterX;
|
||||
const dy = target.normalizedY * 100 - captionCenterY;
|
||||
const originCenterX = originPosition.x + originSize.width / 2;
|
||||
const originCenterY = originPosition.y + originSize.height / 2;
|
||||
const dx = target.normalizedX * 100 - originCenterX;
|
||||
const dy = target.normalizedY * 100 - originCenterY;
|
||||
const horizontal = dx > 8 ? "right" : dx < -8 ? "left" : "";
|
||||
const vertical = dy > 8 ? "down" : dy < -8 ? "up" : "";
|
||||
|
||||
@@ -74,6 +79,40 @@ function getArrowDirection(
|
||||
return (horizontal || vertical || "right") as ArrowDirection;
|
||||
}
|
||||
|
||||
function getMagnifierPosition(captionPosition: { x: number; y: number }) {
|
||||
const canPlaceRight = captionPosition.x + CAPTION_WIDTH + ANNOTATION_GAP + MAGNIFIER_SIZE <= 98;
|
||||
const x = canPlaceRight
|
||||
? captionPosition.x + CAPTION_WIDTH + ANNOTATION_GAP
|
||||
: captionPosition.x - MAGNIFIER_SIZE - ANNOTATION_GAP;
|
||||
const y = captionPosition.y + (CAPTION_HEIGHT - MAGNIFIER_SIZE) / 2;
|
||||
|
||||
return {
|
||||
x: clamp(x, 2, 100 - MAGNIFIER_SIZE - 2),
|
||||
y: clamp(y, 2, 100 - MAGNIFIER_SIZE - 2),
|
||||
};
|
||||
}
|
||||
|
||||
function getArrowPosition(
|
||||
position: NonNullable<GuideStepCandidate["position"]>,
|
||||
originPosition: { x: number; y: number },
|
||||
originSize: { width: number; height: number },
|
||||
) {
|
||||
const targetX = position.normalizedX * 100;
|
||||
const targetY = position.normalizedY * 100;
|
||||
const originCenterX = originPosition.x + originSize.width / 2;
|
||||
const originCenterY = originPosition.y + originSize.height / 2;
|
||||
const distance = Math.hypot(targetX - originCenterX, targetY - originCenterY);
|
||||
const targetOffset = Math.min(18, Math.max(10, distance * 0.35));
|
||||
const ratio = distance > 0 ? Math.max(0, (distance - targetOffset) / distance) : 0;
|
||||
const arrowCenterX = originCenterX + (targetX - originCenterX) * ratio;
|
||||
const arrowCenterY = originCenterY + (targetY - originCenterY) * ratio;
|
||||
|
||||
return {
|
||||
x: clamp(arrowCenterX - ARROW_SIZE / 2, 0, 100 - ARROW_SIZE),
|
||||
y: clamp(arrowCenterY - ARROW_SIZE / 2, 0, 100 - ARROW_SIZE),
|
||||
};
|
||||
}
|
||||
|
||||
function buildCaption(step: GeneratedGuideStep) {
|
||||
const instruction = step.instruction.trim();
|
||||
const title = step.title.trim();
|
||||
@@ -101,7 +140,6 @@ export function buildGuideVideoAnnotations(
|
||||
const startMs = Math.max(0, Math.round(candidate?.timeMs ?? index * durationMs));
|
||||
const endMs = Math.max(startMs + 750, startMs + durationMs);
|
||||
const captionPosition = getCaptionPosition(candidate);
|
||||
const arrowDirection = getArrowDirection(candidate, captionPosition);
|
||||
|
||||
annotations.push({
|
||||
id: options.nextId(),
|
||||
@@ -124,24 +162,23 @@ export function buildGuideVideoAnnotations(
|
||||
});
|
||||
|
||||
if (candidate?.position) {
|
||||
const magnifierPosition = getMagnifierPosition(captionPosition);
|
||||
const arrowPosition = getArrowPosition(candidate.position, magnifierPosition, {
|
||||
width: MAGNIFIER_SIZE,
|
||||
height: MAGNIFIER_SIZE,
|
||||
});
|
||||
const arrowDirection = getArrowDirection(candidate, arrowPosition, {
|
||||
width: ARROW_SIZE,
|
||||
height: ARROW_SIZE,
|
||||
});
|
||||
|
||||
annotations.push({
|
||||
id: options.nextId(),
|
||||
startMs,
|
||||
endMs,
|
||||
type: "magnifier",
|
||||
content: buildCaption(step),
|
||||
position: {
|
||||
x: clamp(
|
||||
candidate.position.normalizedX * 100 - MAGNIFIER_SIZE / 2,
|
||||
0,
|
||||
100 - MAGNIFIER_SIZE,
|
||||
),
|
||||
y: clamp(
|
||||
candidate.position.normalizedY * 100 - MAGNIFIER_SIZE / 2,
|
||||
0,
|
||||
100 - MAGNIFIER_SIZE,
|
||||
),
|
||||
},
|
||||
position: magnifierPosition,
|
||||
size: { width: MAGNIFIER_SIZE, height: MAGNIFIER_SIZE },
|
||||
style: { ...DEFAULT_ANNOTATION_STYLE },
|
||||
zIndex: options.nextZIndex(),
|
||||
@@ -160,10 +197,7 @@ export function buildGuideVideoAnnotations(
|
||||
endMs,
|
||||
type: "figure",
|
||||
content: "",
|
||||
position: {
|
||||
x: clamp(candidate.position.normalizedX * 100 - ARROW_SIZE / 2, 0, 100 - ARROW_SIZE),
|
||||
y: clamp(candidate.position.normalizedY * 100 - ARROW_SIZE / 2, 0, 100 - ARROW_SIZE),
|
||||
},
|
||||
position: arrowPosition,
|
||||
size: { width: ARROW_SIZE, height: ARROW_SIZE },
|
||||
style: { ...DEFAULT_ANNOTATION_STYLE },
|
||||
zIndex: options.nextZIndex(),
|
||||
|
||||
Reference in New Issue
Block a user