@hypit/caption-fine 0.0.0-stage → 0.1.1
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/LICENSE +26 -0
- package/README.md +154 -2
- package/dist/activation.d.ts +8 -0
- package/dist/activation.js +30 -0
- package/dist/component.d.ts +37 -0
- package/dist/component.js +34 -0
- package/dist/fragment.d.ts +4 -0
- package/dist/fragment.js +84 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/joined-box.d.ts +6 -0
- package/dist/joined-box.js +114 -0
- package/dist/manifest.d.ts +854 -0
- package/dist/manifest.js +387 -0
- package/dist/recipe.d.ts +18 -0
- package/dist/recipe.js +116 -0
- package/dist/render.d.ts +8 -0
- package/dist/render.js +1043 -0
- package/dist/schedule.d.ts +12 -0
- package/dist/schedule.js +135 -0
- package/dist/spacing.d.ts +7 -0
- package/dist/spacing.js +13 -0
- package/dist/studio.d.ts +7 -0
- package/dist/studio.js +175 -0
- package/dist/style.d.ts +8 -0
- package/dist/style.js +340 -0
- package/dist/surface.d.ts +3 -0
- package/dist/surface.js +171 -0
- package/dist/types.d.ts +163 -0
- package/dist/types.js +1 -0
- package/package.json +26 -4
- package/preview/Track.png +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CaptionProgram, CaptionTiming } from "@hypit/hypit/caption";
|
|
2
|
+
import type { CaptionDocument } from "@hypit/hypit/caption";
|
|
3
|
+
import type { FineCaptionSchedule } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve visible Cue envelopes before the renderer runs.
|
|
6
|
+
*
|
|
7
|
+
* Resolved unit timing stays untouched. `leadFrames` and `tailFrames` only
|
|
8
|
+
* widen the visible envelope, and `cut` prevents adjacent Cues from competing
|
|
9
|
+
* for the same handoff interval without shortening either Cue's spoken time.
|
|
10
|
+
*/
|
|
11
|
+
export declare function scheduleFineCaption(timing: CaptionTiming, program: CaptionProgram, document: CaptionDocument): FineCaptionSchedule;
|
|
12
|
+
export declare function assertFineCaptionSchedule(value: FineCaptionSchedule): void;
|
package/dist/schedule.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { assertCaptionProgramForDocument, assertCaptionTimingForDocument, captionUseVisibility } from "@hypit/hypit/caption";
|
|
2
|
+
import { assertFineCaptionParameters, FINE_CAPTION_FAMILY } from "./style.js";
|
|
3
|
+
function assertIntegerFrame(value, label) {
|
|
4
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
5
|
+
throw new Error(`${label} must be a non-negative integer Frame`);
|
|
6
|
+
}
|
|
7
|
+
/** Join authored Cues to their complete absolute Unit timing. */
|
|
8
|
+
function fineCaptionContentCues(timing, document) {
|
|
9
|
+
const timingByUnit = new Map(timing.units.map((unit) => [unit.unitId, unit]));
|
|
10
|
+
return document.cues.map((cue) => {
|
|
11
|
+
const units = cue.unitIds.map((unitId) => timingByUnit.get(unitId));
|
|
12
|
+
return {
|
|
13
|
+
id: cue.id,
|
|
14
|
+
...(cue.role === undefined ? {} : { role: cue.role }),
|
|
15
|
+
startFrame: Math.min(...units.map((unit) => unit.startFrame)),
|
|
16
|
+
endFrameExclusive: Math.max(...units.map((unit) => unit.endFrameExclusive)),
|
|
17
|
+
units,
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve visible Cue envelopes before the renderer runs.
|
|
23
|
+
*
|
|
24
|
+
* Resolved unit timing stays untouched. `leadFrames` and `tailFrames` only
|
|
25
|
+
* widen the visible envelope, and `cut` prevents adjacent Cues from competing
|
|
26
|
+
* for the same handoff interval without shortening either Cue's spoken time.
|
|
27
|
+
*/
|
|
28
|
+
export function scheduleFineCaption(timing, program, document) {
|
|
29
|
+
assertCaptionProgramForDocument(program, document);
|
|
30
|
+
assertCaptionTimingForDocument(timing, document);
|
|
31
|
+
const parameters = new Map();
|
|
32
|
+
const activeStyles = new Set(program.uses.map(use => use.styleId));
|
|
33
|
+
for (const style of program.styles) {
|
|
34
|
+
if (!activeStyles.has(style.id))
|
|
35
|
+
continue;
|
|
36
|
+
if (style.rendering === null)
|
|
37
|
+
continue;
|
|
38
|
+
if (style.rendering.family !== FINE_CAPTION_FAMILY) {
|
|
39
|
+
throw new Error(`Fine Caption cannot schedule Style family ${style.rendering.family}`);
|
|
40
|
+
}
|
|
41
|
+
const value = style.rendering.parameters;
|
|
42
|
+
assertFineCaptionParameters(value);
|
|
43
|
+
parameters.set(style.id, value);
|
|
44
|
+
}
|
|
45
|
+
const contentCues = fineCaptionContentCues(timing, document);
|
|
46
|
+
const roleByCue = new Map(contentCues.map((cue) => [cue.id, cue.role]));
|
|
47
|
+
const cues = [];
|
|
48
|
+
for (const [index, use] of program.uses.entries()) {
|
|
49
|
+
if (use.window !== undefined && use.window.start.timelineId !== timing.timelineId)
|
|
50
|
+
throw new Error("Caption Use belongs to another Timeline");
|
|
51
|
+
const style = parameters.get(use.styleId);
|
|
52
|
+
if (style === undefined)
|
|
53
|
+
continue; // Hidden still participates in coverage below.
|
|
54
|
+
const desired = contentCues.filter(cue => use.role === undefined || cue.role === use.role).map((cue) => ({
|
|
55
|
+
id: `${use.id}:${cue.id}`, cueId: cue.id, styleId: use.styleId,
|
|
56
|
+
timedStartFrame: cue.startFrame, timedEndFrameExclusive: cue.endFrameExclusive,
|
|
57
|
+
visibleStartFrame: Math.max(0, cue.startFrame - style.timing.leadFrames),
|
|
58
|
+
visibleEndFrameExclusive: cue.endFrameExclusive + style.timing.tailFrames,
|
|
59
|
+
units: cue.units, visibility: [],
|
|
60
|
+
}));
|
|
61
|
+
// Resolve each speaker's ordinary Cue handoff before masking by Use windows.
|
|
62
|
+
desired.sort((a, b) => a.timedStartFrame - b.timedStartFrame);
|
|
63
|
+
const previousByRole = new Map();
|
|
64
|
+
const envelopes = [];
|
|
65
|
+
for (const wanted of desired) {
|
|
66
|
+
let cue = wanted;
|
|
67
|
+
const role = roleByCue.get(cue.cueId);
|
|
68
|
+
const previousIndex = previousByRole.get(role);
|
|
69
|
+
const previous = previousIndex === undefined ? undefined : envelopes[previousIndex];
|
|
70
|
+
if (previous !== undefined && style.timing.handoff === "cut" && previous.timedEndFrameExclusive <= cue.timedStartFrame) {
|
|
71
|
+
const previousEnd = Math.max(previous.timedEndFrameExclusive, Math.min(previous.visibleEndFrameExclusive, cue.timedStartFrame));
|
|
72
|
+
envelopes[previousIndex] = { ...previous, visibleEndFrameExclusive: previousEnd };
|
|
73
|
+
cue = { ...cue, visibleStartFrame: Math.min(cue.timedStartFrame, Math.max(cue.visibleStartFrame, previousEnd)) };
|
|
74
|
+
}
|
|
75
|
+
previousByRole.set(role, envelopes.length);
|
|
76
|
+
envelopes.push(cue);
|
|
77
|
+
}
|
|
78
|
+
for (const cue of envelopes) {
|
|
79
|
+
const role = roleByCue.get(cue.cueId);
|
|
80
|
+
const visibility = captionUseVisibility(program, index, role, { startFrame: cue.visibleStartFrame, endFrameExclusive: cue.visibleEndFrameExclusive });
|
|
81
|
+
if (visibility.length)
|
|
82
|
+
cues.push({ ...cue, visibility });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const ids = new Set();
|
|
86
|
+
for (const cue of cues) {
|
|
87
|
+
if (ids.has(cue.id))
|
|
88
|
+
throw new Error(`Fine Caption Schedule repeats Cue ${cue.id}`);
|
|
89
|
+
ids.add(cue.id);
|
|
90
|
+
assertIntegerFrame(cue.timedStartFrame, `Fine Caption Cue ${cue.id} timed start`);
|
|
91
|
+
assertIntegerFrame(cue.timedEndFrameExclusive, `Fine Caption Cue ${cue.id} timed end`);
|
|
92
|
+
assertIntegerFrame(cue.visibleStartFrame, `Fine Caption Cue ${cue.id} visible start`);
|
|
93
|
+
assertIntegerFrame(cue.visibleEndFrameExclusive, `Fine Caption Cue ${cue.id} visible end`);
|
|
94
|
+
if (cue.timedEndFrameExclusive <= cue.timedStartFrame
|
|
95
|
+
|| cue.visibleStartFrame > cue.timedStartFrame
|
|
96
|
+
|| cue.visibleEndFrameExclusive < cue.timedEndFrameExclusive
|
|
97
|
+
|| cue.visibleEndFrameExclusive <= cue.visibleStartFrame) {
|
|
98
|
+
throw new Error(`Fine Caption Cue ${cue.id} has an invalid visible envelope`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
timelineId: timing.timelineId,
|
|
103
|
+
documentId: document.id,
|
|
104
|
+
cues,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function assertFineCaptionSchedule(value) {
|
|
108
|
+
if (!value.timelineId || !value.documentId) {
|
|
109
|
+
throw new Error("Fine Caption Schedule provenance is empty");
|
|
110
|
+
}
|
|
111
|
+
const ids = new Set();
|
|
112
|
+
for (const cue of value.cues) {
|
|
113
|
+
if (!cue.id || !cue.styleId || ids.has(cue.id) || cue.units.length === 0) {
|
|
114
|
+
throw new Error("Fine Caption Schedule contains an invalid Cue");
|
|
115
|
+
}
|
|
116
|
+
ids.add(cue.id);
|
|
117
|
+
if (!cue.cueId || cue.visibility.length === 0 || cue.visibility.some(span => !Number.isSafeInteger(span.startFrame) || !Number.isSafeInteger(span.endFrameExclusive)
|
|
118
|
+
|| span.startFrame < cue.visibleStartFrame || span.endFrameExclusive > cue.visibleEndFrameExclusive
|
|
119
|
+
|| span.endFrameExclusive <= span.startFrame))
|
|
120
|
+
throw new Error("Fine Caption visibility is invalid");
|
|
121
|
+
for (const [label, frame] of [
|
|
122
|
+
["timed start", cue.timedStartFrame],
|
|
123
|
+
["timed end", cue.timedEndFrameExclusive],
|
|
124
|
+
["visible start", cue.visibleStartFrame],
|
|
125
|
+
["visible end", cue.visibleEndFrameExclusive],
|
|
126
|
+
])
|
|
127
|
+
assertIntegerFrame(frame, `Fine Caption Cue ${cue.id} ${label}`);
|
|
128
|
+
if (cue.timedEndFrameExclusive <= cue.timedStartFrame
|
|
129
|
+
|| cue.visibleStartFrame > cue.timedStartFrame
|
|
130
|
+
|| cue.visibleEndFrameExclusive < cue.timedEndFrameExclusive
|
|
131
|
+
|| cue.visibleEndFrameExclusive <= cue.visibleStartFrame) {
|
|
132
|
+
throw new Error(`Fine Caption Cue ${cue.id} has an invalid visible envelope`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CaptionDisplayWord } from "@hypit/hypit/caption";
|
|
2
|
+
type Surface = Pick<CaptionDisplayWord, "text" | "separatorBefore">;
|
|
3
|
+
/** The first displayed word starts a line; all other gaps come from authored content. */
|
|
4
|
+
export declare function wordGaps(words: readonly Surface[]): readonly boolean[];
|
|
5
|
+
/** A uniform run can use column-gap; a mixed run requires individual separators. */
|
|
6
|
+
export declare function uniformGap(gaps: readonly boolean[]): boolean | undefined;
|
|
7
|
+
export {};
|
package/dist/spacing.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The first displayed word starts a line; all other gaps come from authored content. */
|
|
2
|
+
export function wordGaps(words) {
|
|
3
|
+
return words.map((word, index) => index > 0 && word.separatorBefore === " ");
|
|
4
|
+
}
|
|
5
|
+
/** A uniform run can use column-gap; a mixed run requires individual separators. */
|
|
6
|
+
export function uniformGap(gaps) {
|
|
7
|
+
const boundaries = gaps.slice(1);
|
|
8
|
+
if (boundaries.length === 0)
|
|
9
|
+
return false;
|
|
10
|
+
if (boundaries.every(gap => gap))
|
|
11
|
+
return true;
|
|
12
|
+
return boundaries.some(gap => gap) ? undefined : false;
|
|
13
|
+
}
|
package/dist/studio.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StudioTrackCompanion, StudioTrackCompanionContext, StudioItemDraft, StudioInspectorObjectDraft, StudioInspectorFieldDeclaration } from "@hypit/studio-companion";
|
|
2
|
+
export declare const captionFineInspectorFields: readonly StudioInspectorFieldDeclaration[];
|
|
3
|
+
export declare function projectCaptionContents(context: StudioTrackCompanionContext): readonly StudioItemDraft[];
|
|
4
|
+
export declare function projectCaption(context: StudioTrackCompanionContext): readonly StudioItemDraft[];
|
|
5
|
+
export declare function projectCaptionUses(context: StudioTrackCompanionContext): readonly StudioInspectorObjectDraft[];
|
|
6
|
+
export declare const captionFineStudioTrackCompanions: readonly StudioTrackCompanion[];
|
|
7
|
+
export declare const captionFineStudioParameterCompanions: readonly import("@hypit/studio-companion").StudioParameterCompanion[];
|
package/dist/studio.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { captionTypes } from "@hypit/hypit/caption";
|
|
2
|
+
import { compositionTypes } from "@hypit/hypit/composition";
|
|
3
|
+
import { fineCaptionEditableDefaults } from "./recipe.js";
|
|
4
|
+
import { captionFineMarkupSurfaces, captionFineModuleRef } from "./manifest.js";
|
|
5
|
+
import { authoredChildFor, requiredReferencedValue, requiredSurfaceValue } from "@hypit/studio-companion";
|
|
6
|
+
const styleSurface = captionFineMarkupSurfaces.find((surface) => surface.name === "style");
|
|
7
|
+
const recipeVocabulary = styleSurface?.vocabulary.attributes
|
|
8
|
+
.find((attribute) => attribute.name === "recipe")?.recipe ?? [];
|
|
9
|
+
function title(name) {
|
|
10
|
+
return name.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
11
|
+
}
|
|
12
|
+
function valuesFor(property) {
|
|
13
|
+
return "values" in property ? property.values : undefined;
|
|
14
|
+
}
|
|
15
|
+
function captionPlace(domain, page, section) {
|
|
16
|
+
return {
|
|
17
|
+
domain,
|
|
18
|
+
page: { id: page, label: title(page) },
|
|
19
|
+
section: { id: section, label: title(section) },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const captionPlacement = new Map();
|
|
23
|
+
function placeCaption(names, domain, page, section) {
|
|
24
|
+
for (const name of names)
|
|
25
|
+
captionPlacement.set(name, captionPlace(domain, page, section));
|
|
26
|
+
}
|
|
27
|
+
placeCaption(["stack-order", "x", "y", "width", "height", "anchor-x", "anchor-y"], "where", "placement", "region");
|
|
28
|
+
placeCaption([
|
|
29
|
+
"align", "block-align", "inline-size", "wrap", "max-lines", "max-words-per-line", "direction",
|
|
30
|
+
"line-height", "letter-spacing", "word-gap",
|
|
31
|
+
], "where", "flow", "flow");
|
|
32
|
+
placeCaption(["size", "kerning", "caps", "text-transform"], "how", "text", "typography");
|
|
33
|
+
placeCaption([
|
|
34
|
+
"fill", "opacity", "gradient-from", "gradient-to", "gradient-angle", "stroke-color", "stroke-width", "shadow-color",
|
|
35
|
+
"shadow-opacity", "shadow-x", "shadow-y", "shadow-blur", "shadow-spread", "long-shadow-color", "long-shadow-opacity",
|
|
36
|
+
"long-shadow-distance", "long-shadow-angle", "glow-color", "glow-opacity", "glow-blur", "glow-spread",
|
|
37
|
+
], "how", "paint", "base-paint");
|
|
38
|
+
placeCaption([
|
|
39
|
+
"active-fill", "active-opacity", "active-gradient-from", "active-gradient-to", "active-gradient-angle",
|
|
40
|
+
"active-stroke-color", "active-stroke-width", "active-shadow-color", "active-shadow-opacity", "active-shadow-x",
|
|
41
|
+
"active-shadow-y", "active-shadow-blur", "active-shadow-spread", "active-long-shadow-color",
|
|
42
|
+
"active-long-shadow-opacity", "active-long-shadow-distance", "active-long-shadow-angle", "active-glow-color",
|
|
43
|
+
"active-glow-opacity", "active-glow-blur", "active-glow-spread",
|
|
44
|
+
], "how", "paint", "active-paint");
|
|
45
|
+
placeCaption([
|
|
46
|
+
"background", "border-color", "border-width", "padding", "radius", "cue-shadow-color", "cue-shadow-opacity",
|
|
47
|
+
"cue-shadow-x", "cue-shadow-y", "cue-shadow-blur", "cue-shadow-spread",
|
|
48
|
+
], "how", "cue-box", "cue-box");
|
|
49
|
+
placeCaption([
|
|
50
|
+
"underline", "underline-color", "underline-thickness", "underline-offset", "active-underline-color",
|
|
51
|
+
"active-underline-thickness", "active-underline-offset", "active-box-background", "active-box-border-color",
|
|
52
|
+
"active-box-border-width", "active-box-padding", "active-box-radius",
|
|
53
|
+
], "how", "decoration", "decoration");
|
|
54
|
+
placeCaption(["lead-frames", "tail-frames", "handoff"], "when", "cue", "envelope");
|
|
55
|
+
placeCaption([
|
|
56
|
+
"cue-enter", "cue-enter-frames", "cue-enter-start-scale", "cue-exit", "cue-exit-frames",
|
|
57
|
+
], "when", "cue", "cue");
|
|
58
|
+
placeCaption([
|
|
59
|
+
"karaoke", "karaoke-transition", "active-underline", "active-box", "active-box-continuity", "active-box-enter",
|
|
60
|
+
"active-box-exit", "active-box-transition-frames", "atom-enter", "atom-enter-frames", "atom-exit", "atom-exit-frames",
|
|
61
|
+
"atom-reveal", "active-response", "active-response-frames", "active-scale", "slide-distance",
|
|
62
|
+
], "when", "token", "token");
|
|
63
|
+
placeCaption(["loop", "loop-target", "loop-period-frames", "loop-intensity"], "when", "loop", "loop");
|
|
64
|
+
const captionColorProperties = new Set([
|
|
65
|
+
"fill", "gradient-from", "gradient-to", "stroke-color", "shadow-color", "long-shadow-color", "glow-color",
|
|
66
|
+
"active-fill", "active-gradient-from", "active-gradient-to", "active-stroke-color", "active-shadow-color",
|
|
67
|
+
"active-long-shadow-color", "active-glow-color", "background", "border-color", "cue-shadow-color", "underline-color",
|
|
68
|
+
"active-underline-color", "active-box-background", "active-box-border-color",
|
|
69
|
+
]);
|
|
70
|
+
const captionTextProperties = new Set(["loop"]);
|
|
71
|
+
const captionPercentProperties = new Set(["x", "y", "width", "height", "opacity", "active-opacity", "active-scale"]);
|
|
72
|
+
const captionPixelProperties = new Set(["size", "letter-spacing", "word-gap", "radius", "stroke-width", "border-width", "shadow-blur", "glow-blur"]);
|
|
73
|
+
export const captionFineInspectorFields = recipeVocabulary.map((property) => {
|
|
74
|
+
const placement = captionPlacement.get(property.name);
|
|
75
|
+
if (placement === undefined)
|
|
76
|
+
throw new Error(`Caption Studio has no explicit Inspector declaration for ${property.name}.`);
|
|
77
|
+
const options = valuesFor(property);
|
|
78
|
+
return {
|
|
79
|
+
binding: `style.${property.name}`,
|
|
80
|
+
label: title(property.name),
|
|
81
|
+
...placement,
|
|
82
|
+
summary: property.summary,
|
|
83
|
+
control: options !== undefined ? "select" : captionColorProperties.has(property.name) ? "color"
|
|
84
|
+
: captionTextProperties.has(property.name) ? "text" : "number",
|
|
85
|
+
...(options === undefined ? {} : { options }),
|
|
86
|
+
...(captionPercentProperties.has(property.name) ? { unit: "%", number: { scale: 100, step: 1 } } : {}),
|
|
87
|
+
...(captionPixelProperties.has(property.name) ? { unit: "px", number: { step: 1 } } : {}),
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
function cueText(document, unitIds) {
|
|
91
|
+
if (document === undefined)
|
|
92
|
+
return "";
|
|
93
|
+
const selected = new Set(unitIds);
|
|
94
|
+
return document.words
|
|
95
|
+
.filter((word) => selected.has(word.unitId))
|
|
96
|
+
.map((word, index) => (index === 0 ? "" : word.separatorBefore) + word.text)
|
|
97
|
+
.join("");
|
|
98
|
+
}
|
|
99
|
+
export function projectCaptionContents(context) {
|
|
100
|
+
const timing = requiredReferencedValue(context, "timing", captionTypes.timing);
|
|
101
|
+
const schedule = requiredSurfaceValue(context, "schedule");
|
|
102
|
+
const document = requiredReferencedValue(context, "document", captionTypes.document);
|
|
103
|
+
if (document.id !== timing.documentId)
|
|
104
|
+
throw new Error("Caption timing belongs to another CaptionDocument.");
|
|
105
|
+
const units = new Map(timing.units.map((unit) => [unit.unitId, unit]));
|
|
106
|
+
return document.cues.map((cue, index) => {
|
|
107
|
+
const timed = cue.unitIds.map((unitId) => {
|
|
108
|
+
const unit = units.get(unitId);
|
|
109
|
+
if (unit === undefined)
|
|
110
|
+
throw new Error(`Caption Cue ${cue.id} has no timing for Unit ${unitId}.`);
|
|
111
|
+
return unit;
|
|
112
|
+
});
|
|
113
|
+
if (timed.length === 0)
|
|
114
|
+
throw new Error(`Caption Cue ${cue.id} contains no Units.`);
|
|
115
|
+
const startFrame = Math.min(...timed.map((unit) => unit.startFrame));
|
|
116
|
+
const endFrameExclusive = Math.max(...timed.map((unit) => unit.endFrameExclusive));
|
|
117
|
+
return {
|
|
118
|
+
id: `${context.track.outputRef}:cue:${cue.id}`, authoredId: cue.id,
|
|
119
|
+
display: { title: `#${index + 1}`, layers: [{ kind: "text", role: "content", text: cueText(document, cue.unitIds) }] },
|
|
120
|
+
startFrame, endFrameExclusive, stackOrder: 0,
|
|
121
|
+
renderIds: schedule.cues.filter(item => item.cueId === cue.id).map(item => item.id),
|
|
122
|
+
presentation: { kind: "caption-cue", chrome: "standard" },
|
|
123
|
+
inspector: [
|
|
124
|
+
{ id: "range", label: "Range", domain: "when", section: { id: "cue", label: "Cue" }, value: `${startFrame}–${endFrameExclusive}`, unit: "f" },
|
|
125
|
+
...(cue.role === undefined ? [] : [{ id: "role", label: "Role", domain: "how", section: { id: "cue", label: "Cue" }, value: cue.role }]),
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export function projectCaption(context) {
|
|
131
|
+
return projectCaptionContents(context);
|
|
132
|
+
}
|
|
133
|
+
export function projectCaptionUses(context) {
|
|
134
|
+
const program = requiredSurfaceValue(context, "program");
|
|
135
|
+
return program.uses.map((use, index) => {
|
|
136
|
+
const child = authoredChildFor(context, use.id, []);
|
|
137
|
+
return {
|
|
138
|
+
id: `use:${use.id}`,
|
|
139
|
+
authoredId: use.id,
|
|
140
|
+
title: use.styleId,
|
|
141
|
+
...(child === undefined ? {} : { elementRange: child.range }),
|
|
142
|
+
parameterReferences: { style: use.styleId },
|
|
143
|
+
inspector: [
|
|
144
|
+
{ id: "order", label: "Order", domain: "how", section: { id: "rule", label: "Rule" }, value: index + 1 },
|
|
145
|
+
{ id: "scope", label: "Scope", domain: "when", section: { id: "rule", label: "Rule" },
|
|
146
|
+
value: use.window === undefined ? "All matching cues" : use.window.subjectId },
|
|
147
|
+
{ id: "role", label: "Role", domain: "how", section: { id: "rule", label: "Rule" }, value: use.role ?? "Any" },
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
export const captionFineStudioTrackCompanions = [
|
|
153
|
+
{
|
|
154
|
+
id: "track", role: "track",
|
|
155
|
+
output: { type: compositionTypes.visualTrack, surface: "caption", modules: [captionFineModuleRef] },
|
|
156
|
+
family: "caption", tone: "magenta", icon: "captions",
|
|
157
|
+
lane: { heightPx: 36 },
|
|
158
|
+
inspectorObjects: [{
|
|
159
|
+
id: "uses", label: "Presentation Rules",
|
|
160
|
+
bindings: [{ name: "style", companion: true }],
|
|
161
|
+
inspector: [{ binding: "style", label: "Style", domain: "how", section: { id: "style", label: "Style" }, control: "text" }],
|
|
162
|
+
project: projectCaptionUses,
|
|
163
|
+
}],
|
|
164
|
+
requiredValues: ["schedule", "program"], project: projectCaption,
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
export const captionFineStudioParameterCompanions = [{
|
|
168
|
+
id: "style", match: { module: captionFineModuleRef, surface: "style" },
|
|
169
|
+
bindings: [{ name: "font", companion: true }, { name: "recipe", recipe: { bindings: recipeVocabulary.map(({ name }) => ({ name,
|
|
170
|
+
...(Object.hasOwn(fineCaptionEditableDefaults, name) ? { fallback: fineCaptionEditableDefaults[name] } : {}),
|
|
171
|
+
})) } }],
|
|
172
|
+
inspector: [
|
|
173
|
+
...captionFineInspectorFields.map(field => ({ ...field, binding: field.binding.replace(/^style\./u, "recipe.") })),
|
|
174
|
+
],
|
|
175
|
+
}];
|
package/dist/style.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CaptionStyleIntent } from "@hypit/hypit/caption";
|
|
2
|
+
import type { FontArtifactRef } from "@hypit/hypit/media";
|
|
3
|
+
import type { Recipe } from "@hypit/hypit/recipe";
|
|
4
|
+
import type { FineCaptionParameters } from "./types.js";
|
|
5
|
+
export declare const FINE_CAPTION_FAMILY = "@hypit/caption-fine@1";
|
|
6
|
+
export declare function fineCaptionParameters(recipe: Recipe, exactFonts: readonly FontArtifactRef[]): FineCaptionParameters;
|
|
7
|
+
export declare function assertFineCaptionParameters(value: FineCaptionParameters): void;
|
|
8
|
+
export declare function fineCaptionStyle(id: string, recipe: Recipe, exactFonts: readonly FontArtifactRef[]): CaptionStyleIntent;
|