frameyard 0.8.0 → 0.9.0
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/CHANGELOG.md +110 -14
- package/GLOSSARY.md +112 -2
- package/README.md +302 -265
- package/dist/assets/integrations.d.ts +8 -4
- package/dist/assets/integrations.d.ts.map +1 -1
- package/dist/assets/integrations.js +8 -4
- package/dist/assets/integrations.js.map +1 -1
- package/dist/assets/provenance.d.ts +2 -2
- package/dist/assets/provenance.d.ts.map +1 -1
- package/dist/assets/provenance.js +5 -3
- package/dist/assets/provenance.js.map +1 -1
- package/dist/assets/tts-supertonic.d.ts +28 -0
- package/dist/assets/tts-supertonic.d.ts.map +1 -0
- package/dist/assets/tts-supertonic.js +156 -0
- package/dist/assets/tts-supertonic.js.map +1 -0
- package/dist/assets/tts.d.ts +18 -6
- package/dist/assets/tts.d.ts.map +1 -1
- package/dist/assets/tts.js +219 -97
- package/dist/assets/tts.js.map +1 -1
- package/dist/cli.js +1685 -93
- package/dist/cli.js.map +1 -1
- package/dist/composition/format.d.ts +1 -1
- package/dist/composition/format.d.ts.map +1 -1
- package/dist/composition/format.js +1 -1
- package/dist/composition/format.js.map +1 -1
- package/dist/context.d.ts +82 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +277 -0
- package/dist/context.js.map +1 -0
- package/dist/contracts.d.ts +25 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/doctor.d.ts +14 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +166 -7
- package/dist/doctor.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fonts/add.d.ts +41 -0
- package/dist/fonts/add.d.ts.map +1 -0
- package/dist/fonts/add.js +259 -0
- package/dist/fonts/add.js.map +1 -0
- package/dist/fonts/binary.d.ts +4 -0
- package/dist/fonts/binary.d.ts.map +1 -0
- package/dist/fonts/binary.js +21 -0
- package/dist/fonts/binary.js.map +1 -0
- package/dist/fonts/css.d.ts +4 -0
- package/dist/fonts/css.d.ts.map +1 -0
- package/dist/fonts/css.js +50 -0
- package/dist/fonts/css.js.map +1 -0
- package/dist/fonts/fontsource.d.ts +107 -0
- package/dist/fonts/fontsource.d.ts.map +1 -0
- package/dist/fonts/fontsource.js +565 -0
- package/dist/fonts/fontsource.js.map +1 -0
- package/dist/fonts/index.d.ts +11 -0
- package/dist/fonts/index.d.ts.map +1 -0
- package/dist/fonts/index.js +11 -0
- package/dist/fonts/index.js.map +1 -0
- package/dist/fonts/install.d.ts +24 -0
- package/dist/fonts/install.d.ts.map +1 -0
- package/dist/fonts/install.js +145 -0
- package/dist/fonts/install.js.map +1 -0
- package/dist/fonts/manifest.d.ts +21 -0
- package/dist/fonts/manifest.d.ts.map +1 -0
- package/dist/fonts/manifest.js +96 -0
- package/dist/fonts/manifest.js.map +1 -0
- package/dist/fonts/options.d.ts +9 -0
- package/dist/fonts/options.d.ts.map +1 -0
- package/dist/fonts/options.js +66 -0
- package/dist/fonts/options.js.map +1 -0
- package/dist/fonts/schema.d.ts +4 -0
- package/dist/fonts/schema.d.ts.map +1 -0
- package/dist/fonts/schema.js +316 -0
- package/dist/fonts/schema.js.map +1 -0
- package/dist/fonts/summary.d.ts +16 -0
- package/dist/fonts/summary.d.ts.map +1 -0
- package/dist/fonts/summary.js +22 -0
- package/dist/fonts/summary.js.map +1 -0
- package/dist/fonts/types.d.ts +56 -0
- package/dist/fonts/types.d.ts.map +1 -0
- package/dist/fonts/types.js +8 -0
- package/dist/fonts/types.js.map +1 -0
- package/dist/fonts/verify.d.ts +33 -0
- package/dist/fonts/verify.d.ts.map +1 -0
- package/dist/fonts/verify.js +200 -0
- package/dist/fonts/verify.js.map +1 -0
- package/dist/html/validate.d.ts.map +1 -1
- package/dist/html/validate.js +59 -0
- package/dist/html/validate.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +61 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +325 -0
- package/dist/inspect.js.map +1 -0
- package/dist/lint.d.ts +63 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +265 -0
- package/dist/lint.js.map +1 -0
- package/dist/policy.d.ts +69 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +361 -0
- package/dist/policy.js.map +1 -0
- package/dist/preview/timeline-data.d.ts +17 -69
- package/dist/preview/timeline-data.d.ts.map +1 -1
- package/dist/preview/timeline-host.d.ts.map +1 -1
- package/dist/preview/timeline-host.js +70 -45
- package/dist/preview/timeline-host.js.map +1 -1
- package/dist/preview/timeline-main.d.ts.map +1 -1
- package/dist/preview/timeline-main.js +47 -4
- package/dist/preview/timeline-main.js.map +1 -1
- package/dist/project.d.ts +10 -3
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +20 -17
- package/dist/project.js.map +1 -1
- package/dist/renderer/ffmpeg.d.ts +42 -2
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +254 -22
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/index.js +1 -1
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/keyframe-expressions.d.ts +39 -0
- package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
- package/dist/renderer/keyframe-expressions.js +180 -0
- package/dist/renderer/keyframe-expressions.js.map +1 -0
- package/dist/renderer/timeline.d.ts +2 -0
- package/dist/renderer/timeline.d.ts.map +1 -1
- package/dist/renderer/timeline.js +89 -116
- package/dist/renderer/timeline.js.map +1 -1
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/host.js +4 -0
- package/dist/runtime/host.js.map +1 -1
- package/dist/scaffold.d.ts +3 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +48 -320
- package/dist/scaffold.js.map +1 -1
- package/dist/server/composition-html.d.ts +2 -0
- package/dist/server/composition-html.d.ts.map +1 -1
- package/dist/server/composition-html.js +7 -0
- package/dist/server/composition-html.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +31 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/timeline-preview-data.d.ts.map +1 -1
- package/dist/server/timeline-preview-data.js +12 -34
- package/dist/server/timeline-preview-data.js.map +1 -1
- package/dist/server/timeline-preview-html.d.ts.map +1 -1
- package/dist/server/timeline-preview-html.js +18 -2
- package/dist/server/timeline-preview-html.js.map +1 -1
- package/dist/templates.d.ts +16 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +186 -0
- package/dist/templates.js.map +1 -0
- package/dist/timeline/easing.d.ts +36 -0
- package/dist/timeline/easing.d.ts.map +1 -0
- package/dist/timeline/easing.js +225 -0
- package/dist/timeline/easing.js.map +1 -0
- package/dist/timeline/keyframes.d.ts +23 -0
- package/dist/timeline/keyframes.d.ts.map +1 -0
- package/dist/timeline/keyframes.js +95 -0
- package/dist/timeline/keyframes.js.map +1 -0
- package/dist/timeline/plan.d.ts +6 -12
- package/dist/timeline/plan.d.ts.map +1 -1
- package/dist/timeline/plan.js +17 -30
- package/dist/timeline/plan.js.map +1 -1
- package/dist/timeline/resolve.d.ts +12 -0
- package/dist/timeline/resolve.d.ts.map +1 -1
- package/dist/timeline/resolve.js +105 -9
- package/dist/timeline/resolve.js.map +1 -1
- package/dist/timeline/scene.d.ts +126 -0
- package/dist/timeline/scene.d.ts.map +1 -0
- package/dist/timeline/scene.js +109 -0
- package/dist/timeline/scene.js.map +1 -0
- package/dist/timeline/schema.d.ts +31 -0
- package/dist/timeline/schema.d.ts.map +1 -1
- package/dist/timeline/schema.js +240 -9
- package/dist/timeline/schema.js.map +1 -1
- package/dist/timeline/transitions.d.ts +21 -0
- package/dist/timeline/transitions.d.ts.map +1 -0
- package/dist/timeline/transitions.js +23 -0
- package/dist/timeline/transitions.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
- package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
- package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +2 -1
- package/docs/adr/0015-inherit-composition-format-from-the-project.md +2 -2
- package/docs/adr/0016-author-compositions-as-html-fragments.md +1 -1
- package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
- package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
- package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
- package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
- package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
- package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
- package/docs/migration-0.4.md +2 -2
- package/docs/preview-testing.md +19 -6
- package/docs/{v0.1-design.md → v0.x-design.md} +55 -30
- package/examples/blend/README.md +3 -3
- package/examples/demo/README.md +2 -2
- package/examples/intro/README.md +71 -0
- package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
- package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
- package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
- package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
- package/examples/intro/assets/grain.png +0 -0
- package/examples/intro/assets/score.ogg +0 -0
- package/examples/intro/compositions/brand.html +412 -0
- package/examples/intro/compositions/chrome.controller.js +63 -0
- package/examples/intro/compositions/chrome.html +256 -0
- package/examples/intro/compositions/lattice.controller.js +148 -0
- package/examples/intro/compositions/lattice.html +308 -0
- package/examples/intro/compositions/opening.html +504 -0
- package/examples/intro/compositions/studio.html +930 -0
- package/examples/intro/compositions/theme.css +218 -0
- package/examples/intro/fy.config.js +13 -0
- package/examples/intro/fy.fonts.json +100 -0
- package/examples/intro/scripts/make-grain.mjs +79 -0
- package/examples/intro/scripts/make-score.mjs +702 -0
- package/examples/intro/timelines/intro.timeline.json +60 -0
- package/examples/keyframes/README.md +76 -0
- package/examples/keyframes/compositions/backdrop.html +60 -0
- package/examples/keyframes/compositions/card.html +99 -0
- package/examples/keyframes/fy.config.js +10 -0
- package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
- package/examples/nested/README.md +112 -0
- package/examples/nested/assets/pulse.wav +0 -0
- package/examples/nested/compositions/backdrop.html +84 -0
- package/examples/nested/compositions/card.html +275 -0
- package/examples/nested/compositions/overlay.html +307 -0
- package/examples/nested/fy.config.js +13 -0
- package/examples/nested/scripts/make-pulse.sh +20 -0
- package/examples/nested/timelines/chapter-a.timeline.json +39 -0
- package/examples/nested/timelines/chapter-b.timeline.json +39 -0
- package/examples/nested/timelines/nested-reel.timeline.json +52 -0
- package/examples/static/README.md +2 -2
- package/examples/transitions/README.md +128 -0
- package/examples/transitions/assets/alpha.wav +0 -0
- package/examples/transitions/assets/beta.wav +0 -0
- package/examples/transitions/assets/gamma.wav +0 -0
- package/examples/transitions/compositions/alpha.html +56 -0
- package/examples/transitions/compositions/beta.html +56 -0
- package/examples/transitions/compositions/gamma.html +56 -0
- package/examples/transitions/fy.config.js +12 -0
- package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
- package/examples/transitions/timelines/transitions.timeline.json +31 -0
- package/examples/video/README.md +4 -4
- package/examples/webgl/README.md +1 -1
- package/package.json +12 -5
- package/scaffolds/init/AGENTS.md +131 -0
- package/scaffolds/init/README.md +56 -0
- package/scaffolds/init/compositions/hello.html +79 -0
- package/scaffolds/init/fy.config.js +9 -0
- package/scaffolds/init/gitignore.template +1 -0
- package/scaffolds/init/package.json +11 -0
- package/scaffolds/init/props/hello.json +4 -0
- package/scaffolds/init/props/main.json +6 -0
- package/scaffolds/init/timelines/main.timeline.json +20 -0
- package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
- package/scaffolds/local-models/fy.policy.json +5 -0
- package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
- package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
- package/scaffolds/local-models/pi/sandbox.json +15 -0
- package/scaffolds/local-models/pi/settings.json +15 -0
- package/skills/frameyard/README.md +1 -0
- package/skills/frameyard/SKILL.md +59 -61
- package/skills/frameyard/references/audio-assets.md +32 -3
- package/skills/frameyard/references/common-mistakes.md +53 -2
- package/skills/frameyard/references/deterministic-authoring.md +10 -3
- package/skills/frameyard/references/diagnostics.md +31 -1
- package/skills/frameyard/references/failure-recovery.md +32 -6
- package/skills/frameyard/references/fonts.md +132 -0
- package/skills/frameyard/references/json-results.md +119 -6
- package/skills/frameyard/references/quick-reference.md +222 -0
- package/skills/frameyard/references/timelines.md +97 -7
- /package/examples/blend/{video.config.js → fy.config.js} +0 -0
- /package/examples/demo/{video.config.js → fy.config.js} +0 -0
- /package/examples/static/{video.config.js → fy.config.js} +0 -0
- /package/examples/video/{video.config.js → fy.config.js} +0 -0
- /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
package/dist/project.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAA0B,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AudioVolumeEnvelope } from "../contracts.js";
|
|
2
|
+
import type { BlendMode, TimelineKeyframes } from "../timeline/schema.js";
|
|
3
|
+
import type { TimelineSceneLayer, TimelineSceneSource } from "../timeline/scene.js";
|
|
2
4
|
/** Fails early when the installed FFmpeg cannot produce the required output. */
|
|
3
5
|
export declare function assertFfmpegCapabilities(signal?: AbortSignal, codec?: VideoCodec): Promise<void>;
|
|
4
6
|
/** Output video codec. */
|
|
@@ -72,6 +74,8 @@ export interface MixClip {
|
|
|
72
74
|
trimStart: number;
|
|
73
75
|
trimDuration?: number;
|
|
74
76
|
volume: number;
|
|
77
|
+
/** Multiplicative equal-power ramps evaluated against absolute timeline frames. */
|
|
78
|
+
volumeEnvelopes?: AudioVolumeEnvelope[];
|
|
75
79
|
}
|
|
76
80
|
/** Normalizes any decodable source to the 16 kHz mono PCM WAV that whisper.cpp expects. */
|
|
77
81
|
export declare function normalizeAudioForTranscription(options: {
|
|
@@ -79,6 +83,12 @@ export declare function normalizeAudioForTranscription(options: {
|
|
|
79
83
|
outputPath: string;
|
|
80
84
|
signal?: AbortSignal;
|
|
81
85
|
}): Promise<void>;
|
|
86
|
+
/** Normalizes generated speech to the 24 kHz mono PCM WAV that Frameyard publishes. */
|
|
87
|
+
export declare function normalizeSpeechAudio(options: {
|
|
88
|
+
inputPath: string;
|
|
89
|
+
outputPath: string;
|
|
90
|
+
signal?: AbortSignal;
|
|
91
|
+
}): Promise<void>;
|
|
82
92
|
export declare function mixAudioTimeline(options: {
|
|
83
93
|
clips: MixClip[];
|
|
84
94
|
fps: number;
|
|
@@ -107,7 +117,8 @@ export declare function startVideoEncoder(options: {
|
|
|
107
117
|
signal?: AbortSignal;
|
|
108
118
|
}): Promise<VideoEncoder>;
|
|
109
119
|
export interface TimelineCompositeLayer {
|
|
110
|
-
|
|
120
|
+
/** How FFmpeg reads the captured artifact. */
|
|
121
|
+
input: "sequence" | "image" | "video";
|
|
111
122
|
path: string;
|
|
112
123
|
sourceWidth: number;
|
|
113
124
|
sourceHeight: number;
|
|
@@ -124,7 +135,32 @@ export interface TimelineCompositeLayer {
|
|
|
124
135
|
blend: BlendMode;
|
|
125
136
|
/** Absolute source frame at the segment start, for video inputs. */
|
|
126
137
|
sourceStartFrame?: number;
|
|
138
|
+
/** Per-channel presentation animation, evaluated in clip-local frames. */
|
|
139
|
+
keyframes?: TimelineKeyframes;
|
|
140
|
+
/** Timeline frame where the clip starts. Defaults to 0. */
|
|
141
|
+
clipStart?: number;
|
|
142
|
+
/** Transition role while the composite range intersects the overlap window. */
|
|
143
|
+
transition?: {
|
|
144
|
+
role: "in" | "out";
|
|
145
|
+
/** Inclusive first timeline frame. */
|
|
146
|
+
start: number;
|
|
147
|
+
/** Exclusive last timeline frame. */
|
|
148
|
+
end: number;
|
|
149
|
+
easing?: string;
|
|
150
|
+
};
|
|
127
151
|
}
|
|
152
|
+
export interface TimelineCompositeArtifact {
|
|
153
|
+
path: string;
|
|
154
|
+
input: TimelineCompositeLayer["input"];
|
|
155
|
+
/** Absolute source frame at the segment start, for video inputs. */
|
|
156
|
+
sourceStartFrame?: number;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Builds FFmpeg's narrow layer input from a projected scene layer. The mapper
|
|
160
|
+
* owns the scene-to-filter boundary; capture chooses the artifact and its input
|
|
161
|
+
* mode, while the scene owns presentation, keyframes, and transition roles.
|
|
162
|
+
*/
|
|
163
|
+
export declare function sceneLayerToCompositeLayer<S extends TimelineSceneSource>(layer: TimelineSceneLayer<S>, artifact: TimelineCompositeArtifact): TimelineCompositeLayer;
|
|
128
164
|
/** Encode a numbered PNG sequence into a lossless alpha-preserving FFV1 file. */
|
|
129
165
|
export declare function encodePngSequenceToFfv1(options: {
|
|
130
166
|
pattern: string;
|
|
@@ -142,6 +178,8 @@ export declare function compositeTimelineSegment(options: {
|
|
|
142
178
|
/** CSS color, "black", or "transparent" for nested timelines. */
|
|
143
179
|
background: string;
|
|
144
180
|
layers: readonly TimelineCompositeLayer[];
|
|
181
|
+
/** Absolute timeline frame of the first output frame. Defaults to 0. */
|
|
182
|
+
frameOffset?: number;
|
|
145
183
|
/** Segment payload: lossless FFV1 intermediates or a delivery codec. */
|
|
146
184
|
codec: VideoCodec | "ffv1";
|
|
147
185
|
/** Encoder for delivery codecs; defaults to `libx264`. */
|
|
@@ -180,6 +218,8 @@ export declare function compositeTimelineFrame(options: {
|
|
|
180
218
|
fps: number;
|
|
181
219
|
background: string;
|
|
182
220
|
layers: readonly TimelineCompositeLayer[];
|
|
221
|
+
/** Absolute timeline frame being captured. Defaults to 0. */
|
|
222
|
+
frameOffset?: number;
|
|
183
223
|
signal?: AbortSignal;
|
|
184
224
|
}): Promise<void>;
|
|
185
225
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/renderer/ffmpeg.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/renderer/ffmpeg.ts"],"names":[],"mappings":"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"}
|
package/dist/renderer/ffmpeg.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { writeFile } from "node:fs/promises";
|
|
3
3
|
import { FrameyardError } from "../errors.js";
|
|
4
|
+
import { evaluatePresentation } from "../timeline/keyframes.js";
|
|
5
|
+
import { transitionProgress } from "../timeline/transitions.js";
|
|
6
|
+
import { keyframeChannelExpression, transitionGainExpression, transitionProgressExpression, } from "./keyframe-expressions.js";
|
|
4
7
|
const STDERR_LIMIT = 16 * 1024;
|
|
5
8
|
function captureTail(child) {
|
|
6
9
|
let tail = "";
|
|
@@ -302,8 +305,7 @@ export async function probeMedia(filePath, options = {}) {
|
|
|
302
305
|
throw new FrameyardError("FFPROBE_FAILED", `Could not parse ffprobe output for ${filePath}.`, { cause: error });
|
|
303
306
|
}
|
|
304
307
|
}
|
|
305
|
-
|
|
306
|
-
export async function normalizeAudioForTranscription(options) {
|
|
308
|
+
async function normalizeAudio(options) {
|
|
307
309
|
await runCaptured("ffmpeg", [
|
|
308
310
|
"-hide_banner",
|
|
309
311
|
"-loglevel",
|
|
@@ -318,12 +320,20 @@ export async function normalizeAudioForTranscription(options) {
|
|
|
318
320
|
"-ac",
|
|
319
321
|
"1",
|
|
320
322
|
"-ar",
|
|
321
|
-
|
|
323
|
+
String(options.sampleRate),
|
|
322
324
|
"-c:a",
|
|
323
325
|
"pcm_s16le",
|
|
324
326
|
options.outputPath,
|
|
325
327
|
], { signal: options.signal });
|
|
326
328
|
}
|
|
329
|
+
/** Normalizes any decodable source to the 16 kHz mono PCM WAV that whisper.cpp expects. */
|
|
330
|
+
export async function normalizeAudioForTranscription(options) {
|
|
331
|
+
await normalizeAudio({ ...options, sampleRate: 16_000 });
|
|
332
|
+
}
|
|
333
|
+
/** Normalizes generated speech to the 24 kHz mono PCM WAV that Frameyard publishes. */
|
|
334
|
+
export async function normalizeSpeechAudio(options) {
|
|
335
|
+
await normalizeAudio({ ...options, sampleRate: 24_000 });
|
|
336
|
+
}
|
|
327
337
|
export async function mixAudioTimeline(options) {
|
|
328
338
|
if (options.clips.length === 0) {
|
|
329
339
|
throw new FrameyardError("AUDIO_INVALID", "Cannot mix an empty audio timeline.");
|
|
@@ -340,10 +350,22 @@ export async function mixAudioTimeline(options) {
|
|
|
340
350
|
trim.push(`duration=${formatNumber(clip.trimDuration)}`);
|
|
341
351
|
}
|
|
342
352
|
const delayMs = Math.round((clip.startFrame * 1000) / options.fps);
|
|
353
|
+
const gain = clip.volumeEnvelopes?.length
|
|
354
|
+
? clip.volumeEnvelopes
|
|
355
|
+
.map((envelope) => transitionGainExpression({
|
|
356
|
+
progressExpression: `((${formatNumber(clip.startFrame)}+t*${formatNumber(options.fps)}-${formatNumber(envelope.startFrame)})/${formatNumber(envelope.endFrame - envelope.startFrame)})`,
|
|
357
|
+
...(envelope.easing !== undefined ? { easing: envelope.easing } : {}),
|
|
358
|
+
role: envelope.role,
|
|
359
|
+
}))
|
|
360
|
+
.join("*")
|
|
361
|
+
: undefined;
|
|
362
|
+
const volume = gain === undefined
|
|
363
|
+
? `volume=${formatNumber(clip.volume)}`
|
|
364
|
+
: `volume='${formatNumber(clip.volume)}*(${gain})':eval=frame`;
|
|
343
365
|
const chain = [
|
|
344
366
|
`atrim=${trim.join(":")}`,
|
|
345
367
|
"asetpts=PTS-STARTPTS",
|
|
346
|
-
|
|
368
|
+
volume,
|
|
347
369
|
"aresample=48000",
|
|
348
370
|
"aformat=sample_fmts=fltp:channel_layouts=stereo",
|
|
349
371
|
"apad",
|
|
@@ -492,6 +514,44 @@ export async function startVideoEncoder(options) {
|
|
|
492
514
|
abort,
|
|
493
515
|
};
|
|
494
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Builds FFmpeg's narrow layer input from a projected scene layer. The mapper
|
|
519
|
+
* owns the scene-to-filter boundary; capture chooses the artifact and its input
|
|
520
|
+
* mode, while the scene owns presentation, keyframes, and transition roles.
|
|
521
|
+
*/
|
|
522
|
+
export function sceneLayerToCompositeLayer(layer, artifact) {
|
|
523
|
+
const transform = layer.transform;
|
|
524
|
+
const transition = layer.transition;
|
|
525
|
+
return {
|
|
526
|
+
input: artifact.input,
|
|
527
|
+
path: artifact.path,
|
|
528
|
+
sourceWidth: layer.sourceWidth,
|
|
529
|
+
sourceHeight: layer.sourceHeight,
|
|
530
|
+
...(transform.width !== undefined && transform.height !== undefined
|
|
531
|
+
? { width: transform.width, height: transform.height }
|
|
532
|
+
: {}),
|
|
533
|
+
fit: transform.fit,
|
|
534
|
+
x: transform.x,
|
|
535
|
+
y: transform.y,
|
|
536
|
+
scale: transform.scale,
|
|
537
|
+
rotation: transform.rotation,
|
|
538
|
+
opacity: transform.opacity,
|
|
539
|
+
blend: layer.blend,
|
|
540
|
+
...(artifact.sourceStartFrame !== undefined ? { sourceStartFrame: artifact.sourceStartFrame } : {}),
|
|
541
|
+
...(layer.keyframes !== undefined ? { keyframes: layer.keyframes } : {}),
|
|
542
|
+
clipStart: layer.clip.start,
|
|
543
|
+
...(transition !== undefined
|
|
544
|
+
? {
|
|
545
|
+
transition: {
|
|
546
|
+
role: transition.role,
|
|
547
|
+
start: transition.window.start,
|
|
548
|
+
end: transition.window.end,
|
|
549
|
+
...(transition.window.easing !== undefined ? { easing: transition.window.easing } : {}),
|
|
550
|
+
},
|
|
551
|
+
}
|
|
552
|
+
: {}),
|
|
553
|
+
};
|
|
554
|
+
}
|
|
495
555
|
function ffmpegBackgroundColor(value) {
|
|
496
556
|
if (value === "transparent") {
|
|
497
557
|
return "black@0.0";
|
|
@@ -533,25 +593,160 @@ function fitFilter(layer, stageWidth, stageHeight) {
|
|
|
533
593
|
const fittedHeight = Math.max(1, Math.round(sourceHeight * scale));
|
|
534
594
|
return `scale=${fittedWidth}:${fittedHeight},pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:color=black@0`;
|
|
535
595
|
}
|
|
536
|
-
function
|
|
537
|
-
|
|
596
|
+
function layerTransform(layer) {
|
|
597
|
+
return {
|
|
598
|
+
fit: layer.fit,
|
|
599
|
+
x: layer.x,
|
|
600
|
+
y: layer.y,
|
|
601
|
+
...(layer.width !== undefined && layer.height !== undefined ? { width: layer.width, height: layer.height } : {}),
|
|
602
|
+
scale: layer.scale,
|
|
603
|
+
rotation: layer.rotation,
|
|
604
|
+
opacity: layer.opacity,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function hasKeyframe(layer, channel) {
|
|
608
|
+
return layer.keyframes?.[channel] !== undefined;
|
|
609
|
+
}
|
|
610
|
+
/** The incoming layer's animated alpha at an absolute timeline frame. */
|
|
611
|
+
function transitionAlphaAt(layer, frame) {
|
|
612
|
+
if (layer.transition?.role !== "in") {
|
|
613
|
+
return 1;
|
|
614
|
+
}
|
|
615
|
+
return transitionProgress(layer.transition, frame);
|
|
616
|
+
}
|
|
617
|
+
/** False when a keyed or transitioning layer is invisible for every frame of the range. */
|
|
618
|
+
function isLayerVisibleInRange(layer, frameOffset, frames) {
|
|
619
|
+
const animated = hasKeyframe(layer, "opacity") || hasKeyframe(layer, "scale");
|
|
620
|
+
if (!animated && layer.transition?.role !== "in") {
|
|
621
|
+
return layer.opacity > 0 && layer.scale > 0;
|
|
622
|
+
}
|
|
623
|
+
const transform = layerTransform(layer);
|
|
624
|
+
const clipStart = layer.clipStart ?? 0;
|
|
625
|
+
for (let index = 0; index < frames; index += 1) {
|
|
626
|
+
const frame = frameOffset + index;
|
|
627
|
+
const values = evaluatePresentation(transform, layer.keyframes, frame - clipStart);
|
|
628
|
+
if (values.opacity * transitionAlphaAt(layer, frame) > 0 && values.scale > 0) {
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
function baseLayerFilters(layer, stageWidth, stageHeight, fps) {
|
|
635
|
+
return [
|
|
538
636
|
// Matroska and WebM timestamps can be quantized to milliseconds. Normalize
|
|
539
637
|
// every timed layer to the project frame rate before overlay framesync so
|
|
540
638
|
// it does not duplicate or drop frames. Still images are already static.
|
|
541
|
-
...(layer.
|
|
639
|
+
...(layer.input === "image" ? [] : [`fps=${formatNumber(fps)}`]),
|
|
542
640
|
fitFilter(layer, stageWidth, stageHeight),
|
|
543
641
|
"format=rgba",
|
|
544
642
|
];
|
|
545
|
-
|
|
546
|
-
|
|
643
|
+
}
|
|
644
|
+
/** Dimensions entering the rotate filter for this layer. */
|
|
645
|
+
function rotationInputSize(layer, stageWidth, stageHeight) {
|
|
646
|
+
const width = layer.width ?? stageWidth;
|
|
647
|
+
const height = layer.height ?? stageHeight;
|
|
648
|
+
if (layer.keyframes?.scale !== undefined) {
|
|
649
|
+
// Animated scale is applied after rotation, keeping the rotate input fixed.
|
|
650
|
+
return { width, height };
|
|
547
651
|
}
|
|
548
|
-
|
|
549
|
-
|
|
652
|
+
return {
|
|
653
|
+
width: Math.max(1, Math.ceil(width * layer.scale)),
|
|
654
|
+
height: Math.max(1, Math.ceil(height * layer.scale)),
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function rotatedBounds(width, height, degrees) {
|
|
658
|
+
const radians = (degrees * Math.PI) / 180;
|
|
659
|
+
const cosine = Math.abs(Math.cos(radians));
|
|
660
|
+
const sine = Math.abs(Math.sin(radians));
|
|
661
|
+
// Two extra pixels absorb filter rounding and anti-aliased edge pixels.
|
|
662
|
+
return {
|
|
663
|
+
width: Math.max(1, Math.ceil(width * cosine + height * sine) + 2),
|
|
664
|
+
height: Math.max(1, Math.ceil(width * sine + height * cosine) + 2),
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* FFmpeg's rotate filter clips to a fixed output size. A keyed rotation uses a
|
|
669
|
+
* square diagonal bound so every segment uses the same size and corners stay
|
|
670
|
+
* visible at any angle. Static rotation uses its exact bounding box.
|
|
671
|
+
*/
|
|
672
|
+
function rotationOutputSize(layer, stageWidth, stageHeight) {
|
|
673
|
+
const input = rotationInputSize(layer, stageWidth, stageHeight);
|
|
674
|
+
if (layer.keyframes?.rotation) {
|
|
675
|
+
const diagonal = Math.max(1, Math.ceil(Math.hypot(input.width, input.height)) + 2);
|
|
676
|
+
return { width: diagonal, height: diagonal };
|
|
550
677
|
}
|
|
551
|
-
|
|
678
|
+
return rotatedBounds(input.width, input.height, layer.rotation);
|
|
679
|
+
}
|
|
680
|
+
function needsOpacityMask(layer) {
|
|
681
|
+
return hasKeyframe(layer, "opacity") || hasKeyframe(layer, "scale") || layer.transition?.role === "in";
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Presentation filters after the optional opacity mask. Animated scale is
|
|
685
|
+
* applied after rotation because uniform scale and rotation commute; rotation
|
|
686
|
+
* then keeps a fixed input size for the rotate filter.
|
|
687
|
+
*/
|
|
688
|
+
function presentationLayerFilters(layer, stageWidth, stageHeight, frameOffset) {
|
|
689
|
+
const filters = [];
|
|
690
|
+
const clipStart = layer.clipStart ?? 0;
|
|
691
|
+
const frameOptions = { frameVariable: "n", frameOffset, clipStart };
|
|
692
|
+
const scaleKeys = layer.keyframes?.scale;
|
|
693
|
+
const rotationKeys = layer.keyframes?.rotation;
|
|
694
|
+
const opacityKeys = layer.keyframes?.opacity;
|
|
695
|
+
const addRotation = () => {
|
|
696
|
+
if (rotationKeys || layer.rotation !== 0) {
|
|
697
|
+
const output = rotationOutputSize(layer, stageWidth, stageHeight);
|
|
698
|
+
const outputSize = `ow=${output.width}:oh=${output.height}`;
|
|
699
|
+
if (rotationKeys) {
|
|
700
|
+
const angle = keyframeChannelExpression("rotation", rotationKeys, frameOptions);
|
|
701
|
+
filters.push(`rotate=angle='(${angle})*PI/180':fillcolor=black@0:${outputSize}`);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
filters.push(`rotate=angle=${formatNumber(layer.rotation)}*PI/180:fillcolor=black@0:${outputSize}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
if (scaleKeys) {
|
|
709
|
+
addRotation();
|
|
710
|
+
const scale = keyframeChannelExpression("scale", scaleKeys, frameOptions);
|
|
711
|
+
filters.push(`scale=w='max(1,iw*(${scale}))':h='max(1,ih*(${scale}))':eval=frame`);
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
if (layer.scale !== 1) {
|
|
715
|
+
filters.push(`scale=iw*${formatNumber(layer.scale)}:ih*${formatNumber(layer.scale)}`);
|
|
716
|
+
}
|
|
717
|
+
addRotation();
|
|
718
|
+
}
|
|
719
|
+
// A transitioning layer carries its opacity in the animated mask instead.
|
|
720
|
+
if (!opacityKeys && !scaleKeys && layer.transition?.role !== "in" && layer.opacity < 1) {
|
|
552
721
|
filters.push(`colorchannelmixer=aa=${formatNumber(layer.opacity)}`);
|
|
553
722
|
}
|
|
554
|
-
return filters
|
|
723
|
+
return filters;
|
|
724
|
+
}
|
|
725
|
+
/** Builds the animated alpha mask at the fitted box size. */
|
|
726
|
+
function opacityMaskFilter(options) {
|
|
727
|
+
const { layer, stageWidth, stageHeight, frameOffset, inputIndex, outputLabel } = options;
|
|
728
|
+
const clipStart = layer.clipStart ?? 0;
|
|
729
|
+
const frameOptions = { frameVariable: "N", frameOffset, clipStart };
|
|
730
|
+
const opacityKeys = layer.keyframes?.opacity;
|
|
731
|
+
const scaleKeys = layer.keyframes?.scale;
|
|
732
|
+
const opacity = opacityKeys
|
|
733
|
+
? keyframeChannelExpression("opacity", opacityKeys, frameOptions)
|
|
734
|
+
: formatNumber(layer.opacity);
|
|
735
|
+
const scale = scaleKeys ? keyframeChannelExpression("scale", scaleKeys, frameOptions) : undefined;
|
|
736
|
+
const visible = scale !== undefined ? `if(gt(${scale},0),1,0)` : "1";
|
|
737
|
+
const transition = layer.transition?.role === "in"
|
|
738
|
+
? transitionProgressExpression({
|
|
739
|
+
frameVariable: "N",
|
|
740
|
+
frameOffset,
|
|
741
|
+
start: layer.transition.start,
|
|
742
|
+
end: layer.transition.end,
|
|
743
|
+
...(layer.transition.easing !== undefined ? { easing: layer.transition.easing } : {}),
|
|
744
|
+
})
|
|
745
|
+
: undefined;
|
|
746
|
+
const alpha = `min(1,max(0,(${opacity})*(${visible})${transition !== undefined ? `*(${transition})` : ""}))`;
|
|
747
|
+
const width = layer.width ?? stageWidth;
|
|
748
|
+
const height = layer.height ?? stageHeight;
|
|
749
|
+
return `[${inputIndex}:v]format=rgba,geq=r=0:g=0:b=0:a='255*${alpha}',scale=${width}:${height}:flags=neighbor,format=rgba[${outputLabel}]`;
|
|
555
750
|
}
|
|
556
751
|
/** W3C soft-light in 0-255 space; FFmpeg's native mode uses a different formula. */
|
|
557
752
|
const SOFT_LIGHT_EXPRESSION = "if(lt(B,128), A-(1-2*B/255)*A*(1-A/255), A+(2*B/255-1)*(if(lt(A,64),((16*A/255-12)*A/255+4)*A/255,sqrt(A/255))*255-A))";
|
|
@@ -580,8 +775,17 @@ function blendSpec(mode) {
|
|
|
580
775
|
sourceFirst: mode === "color-dodge" || mode === "color-burn",
|
|
581
776
|
};
|
|
582
777
|
}
|
|
583
|
-
function placementFilter(layer) {
|
|
584
|
-
|
|
778
|
+
function placementFilter(layer, frameOffset) {
|
|
779
|
+
const clipStart = layer.clipStart ?? 0;
|
|
780
|
+
const frameOptions = { frameVariable: "n", frameOffset, clipStart };
|
|
781
|
+
const xKeys = layer.keyframes?.x;
|
|
782
|
+
const yKeys = layer.keyframes?.y;
|
|
783
|
+
const x = xKeys ? keyframeChannelExpression("x", xKeys, frameOptions) : formatNumber(layer.x);
|
|
784
|
+
const y = yKeys ? keyframeChannelExpression("y", yKeys, frameOptions) : formatNumber(layer.y);
|
|
785
|
+
if (!xKeys && !yKeys) {
|
|
786
|
+
return `x=(main_w-overlay_w)/2+${x}:y=(main_h-overlay_h)/2+${y}`;
|
|
787
|
+
}
|
|
788
|
+
return `x='(main_w-overlay_w)/2+(${x})':y='(main_h-overlay_h)/2+(${y})':eval=frame`;
|
|
585
789
|
}
|
|
586
790
|
/**
|
|
587
791
|
* Builds the FFmpeg filter graph for one timeline composite. Blend layers are
|
|
@@ -605,7 +809,7 @@ function buildCompositeGraph(options) {
|
|
|
605
809
|
let current = "base0";
|
|
606
810
|
let inputIndex = 0;
|
|
607
811
|
let includedIndex = 0;
|
|
608
|
-
const blendedLayers = options.layers.filter((layer) => layer.blend !== "normal" && layer.
|
|
812
|
+
const blendedLayers = options.layers.filter((layer) => layer.blend !== "normal" && isLayerVisibleInRange(layer, options.frameOffset, options.frames));
|
|
609
813
|
const canvasLabels = [];
|
|
610
814
|
if (blendedLayers.length > 0) {
|
|
611
815
|
args.push("-f", "lavfi", "-i", `color=c=black@0.0:s=${options.width}x${options.height}:r=${formatNumber(options.fps)}:d=${formatNumber(durationSeconds)}`);
|
|
@@ -620,10 +824,19 @@ function buildCompositeGraph(options) {
|
|
|
620
824
|
}
|
|
621
825
|
let canvasIndex = 0;
|
|
622
826
|
for (const layer of options.layers) {
|
|
623
|
-
if (layer.
|
|
827
|
+
if (!isLayerVisibleInRange(layer, options.frameOffset, options.frames)) {
|
|
624
828
|
continue;
|
|
625
829
|
}
|
|
626
|
-
|
|
830
|
+
const masked = needsOpacityMask(layer);
|
|
831
|
+
let maskInput = 0;
|
|
832
|
+
if (masked) {
|
|
833
|
+
args.push("-f", "lavfi", "-i",
|
|
834
|
+
// FFmpeg 6 rejects a 1x1 yuv420p source (chroma planes round to zero).
|
|
835
|
+
`color=c=black@0.0:s=2x2:r=${formatNumber(options.fps)}:d=${formatNumber(durationSeconds)}`);
|
|
836
|
+
inputIndex += 1;
|
|
837
|
+
maskInput = inputIndex;
|
|
838
|
+
}
|
|
839
|
+
if (layer.input === "video") {
|
|
627
840
|
const startSeconds = (layer.sourceStartFrame ?? 0) / options.fps;
|
|
628
841
|
if (options.segment) {
|
|
629
842
|
args.push("-ss", formatNumber(startSeconds), "-t", formatNumber(durationSeconds), "-i", layer.path, "-an");
|
|
@@ -632,7 +845,7 @@ function buildCompositeGraph(options) {
|
|
|
632
845
|
args.push("-ss", formatNumber(startSeconds), "-i", layer.path, "-an");
|
|
633
846
|
}
|
|
634
847
|
}
|
|
635
|
-
else if (layer.
|
|
848
|
+
else if (layer.input === "image") {
|
|
636
849
|
if (options.segment) {
|
|
637
850
|
args.push("-loop", "1", "-framerate", formatNumber(options.fps), "-t", formatNumber(durationSeconds), "-i", layer.path, "-an");
|
|
638
851
|
}
|
|
@@ -645,15 +858,32 @@ function buildCompositeGraph(options) {
|
|
|
645
858
|
}
|
|
646
859
|
inputIndex += 1;
|
|
647
860
|
includedIndex += 1;
|
|
648
|
-
|
|
861
|
+
const layerInput = inputIndex;
|
|
862
|
+
const base = baseLayerFilters(layer, options.width, options.height, options.fps);
|
|
863
|
+
const presentation = presentationLayerFilters(layer, options.width, options.height, options.frameOffset);
|
|
864
|
+
if (masked) {
|
|
865
|
+
filters.push(`[${layerInput}:v]${base.join(",")}[v${includedIndex}]`);
|
|
866
|
+
filters.push(opacityMaskFilter({
|
|
867
|
+
layer,
|
|
868
|
+
stageWidth: options.width,
|
|
869
|
+
stageHeight: options.height,
|
|
870
|
+
frameOffset: options.frameOffset,
|
|
871
|
+
inputIndex: maskInput,
|
|
872
|
+
outputLabel: `km${includedIndex}`,
|
|
873
|
+
}));
|
|
874
|
+
filters.push(`[v${includedIndex}][km${includedIndex}]blend=c0_mode=normal:c1_mode=normal:c2_mode=normal:c3_mode=multiply${presentation.length > 0 ? `,${presentation.join(",")}` : ""}[l${includedIndex}]`);
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
filters.push(`[${layerInput}:v]${[...base, ...presentation].join(",")}[l${includedIndex}]`);
|
|
878
|
+
}
|
|
649
879
|
if (layer.blend === "normal") {
|
|
650
|
-
filters.push(`[${current}][l${includedIndex}]overlay=${placementFilter(layer)}:format=auto[b${includedIndex}]`);
|
|
880
|
+
filters.push(`[${current}][l${includedIndex}]overlay=${placementFilter(layer, options.frameOffset)}:format=auto[b${includedIndex}]`);
|
|
651
881
|
current = `b${includedIndex}`;
|
|
652
882
|
continue;
|
|
653
883
|
}
|
|
654
884
|
const canvas = canvasLabels[canvasIndex];
|
|
655
885
|
canvasIndex += 1;
|
|
656
|
-
filters.push(`[${canvas}][l${includedIndex}]overlay=${placementFilter(layer)}:format=auto,format=rgba,split=2[pb${includedIndex}][pm${includedIndex}]`);
|
|
886
|
+
filters.push(`[${canvas}][l${includedIndex}]overlay=${placementFilter(layer, options.frameOffset)}:format=auto,format=rgba,split=2[pb${includedIndex}][pm${includedIndex}]`);
|
|
657
887
|
filters.push(`[${current}]format=rgba,split=3[bc${includedIndex}][bm${includedIndex}][bo${includedIndex}]`);
|
|
658
888
|
filters.push(`[bm${includedIndex}]alphaextract,format=rgba[mask${includedIndex}]`);
|
|
659
889
|
const spec = blendSpec(layer.blend);
|
|
@@ -692,6 +922,7 @@ export async function compositeTimelineSegment(options) {
|
|
|
692
922
|
height: options.height,
|
|
693
923
|
fps: options.fps,
|
|
694
924
|
frames: options.frames,
|
|
925
|
+
frameOffset: options.frameOffset ?? 0,
|
|
695
926
|
background: options.background,
|
|
696
927
|
layers: options.layers,
|
|
697
928
|
segment: true,
|
|
@@ -766,6 +997,7 @@ export async function compositeTimelineFrame(options) {
|
|
|
766
997
|
height: options.height,
|
|
767
998
|
fps: options.fps,
|
|
768
999
|
frames: 1,
|
|
1000
|
+
frameOffset: options.frameOffset ?? 0,
|
|
769
1001
|
background: options.background,
|
|
770
1002
|
layers: options.layers,
|
|
771
1003
|
segment: false,
|