@pixerate/flair 0.0.0-stage → 0.1.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 +7 -0
- package/LICENSE +21 -0
- package/README.md +254 -2
- package/dist/catalog/index.d.ts +74 -0
- package/dist/catalog/index.js +47 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/chunk-36DTNSHU.js +160 -0
- package/dist/chunk-36DTNSHU.js.map +1 -0
- package/dist/chunk-3FAUZOGL.js +331 -0
- package/dist/chunk-3FAUZOGL.js.map +1 -0
- package/dist/chunk-CFCLT2XL.js +1 -0
- package/dist/chunk-CFCLT2XL.js.map +1 -0
- package/dist/chunk-EHDJ3CPJ.js +327 -0
- package/dist/chunk-EHDJ3CPJ.js.map +1 -0
- package/dist/chunk-G5FNZDTA.js +536 -0
- package/dist/chunk-G5FNZDTA.js.map +1 -0
- package/dist/chunk-HKXOHRCD.js +763 -0
- package/dist/chunk-HKXOHRCD.js.map +1 -0
- package/dist/chunk-J55UBNAT.js +164 -0
- package/dist/chunk-J55UBNAT.js.map +1 -0
- package/dist/chunk-JWZCPMFG.js +2074 -0
- package/dist/chunk-JWZCPMFG.js.map +1 -0
- package/dist/chunk-MWVTQFV5.js +1894 -0
- package/dist/chunk-MWVTQFV5.js.map +1 -0
- package/dist/chunk-UQQNZSYL.js +594 -0
- package/dist/chunk-UQQNZSYL.js.map +1 -0
- package/dist/chunk-WMZQCUFJ.js +73 -0
- package/dist/chunk-WMZQCUFJ.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +331 -0
- package/dist/cli.js.map +1 -0
- package/dist/clock-BhdXWH-n.d.ts +47 -0
- package/dist/controls-DdXMbovi.d.ts +97 -0
- package/dist/editor/index.d.ts +413 -0
- package/dist/editor/index.js +1009 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/export-FEBPEEO7.js +22 -0
- package/dist/export-FEBPEEO7.js.map +1 -0
- package/dist/index.d.ts +1431 -0
- package/dist/index.js +1263 -0
- package/dist/index.js.map +1 -0
- package/dist/motion-D0TVv2CF.d.ts +148 -0
- package/dist/node.d.ts +24 -0
- package/dist/node.js +10 -0
- package/dist/node.js.map +1 -0
- package/dist/recorder-WJTWMEQN.js +10 -0
- package/dist/recorder-WJTWMEQN.js.map +1 -0
- package/dist/renderer-Oyynz-fs.d.ts +293 -0
- package/dist/types-CkKkavvB.d.ts +71 -0
- package/dist/vector/index.d.ts +83 -0
- package/dist/vector/index.js +39 -0
- package/dist/vector/index.js.map +1 -0
- package/package.json +96 -4
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CAMERA_MS,
|
|
3
|
+
DEFAULT_STAGE,
|
|
4
|
+
isElementCommand,
|
|
5
|
+
isUpdateCommand
|
|
6
|
+
} from "./chunk-36DTNSHU.js";
|
|
7
|
+
|
|
8
|
+
// src/export/motn.ts
|
|
9
|
+
function escapeXml(unsafe) {
|
|
10
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11
|
+
}
|
|
12
|
+
function exportMotn(scene, options = {}) {
|
|
13
|
+
const frameRate = options.frameRate ?? 30;
|
|
14
|
+
const width = scene.stage?.width ?? DEFAULT_STAGE.width;
|
|
15
|
+
const height = scene.stage?.height ?? DEFAULT_STAGE.height;
|
|
16
|
+
let currentMs = 0;
|
|
17
|
+
const shotTimings = scene.shots.map((shot, idx) => {
|
|
18
|
+
const camMs = idx > 0 && shot.camera ? CAMERA_MS : 0;
|
|
19
|
+
const holdMs = shot.hold ?? 800;
|
|
20
|
+
const durationMs = camMs + holdMs;
|
|
21
|
+
const startMs = currentMs;
|
|
22
|
+
currentMs += durationMs;
|
|
23
|
+
const startFrame = Math.round(startMs / 1e3 * frameRate);
|
|
24
|
+
const durationFrames = Math.max(1, Math.round(durationMs / 1e3 * frameRate));
|
|
25
|
+
const endFrame = startFrame + durationFrames - 1;
|
|
26
|
+
return {
|
|
27
|
+
shot,
|
|
28
|
+
startFrame,
|
|
29
|
+
durationFrames,
|
|
30
|
+
endFrame
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
const totalFrames = Math.max(1, Math.round(currentMs / 1e3 * frameRate));
|
|
34
|
+
let nodeId = 1e4;
|
|
35
|
+
const lines = [];
|
|
36
|
+
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
37
|
+
lines.push("<!DOCTYPE ozxmlscene>");
|
|
38
|
+
lines.push('<ozml version="4.0">');
|
|
39
|
+
if (scene.name) {
|
|
40
|
+
lines.push(` <!-- Scene: ${escapeXml(scene.name)} -->`);
|
|
41
|
+
}
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push(" <displayversion>5.0</displayversion>");
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push(' <factory id="1" uuid="044beba5ad3211d7ac9b000393833f6a">');
|
|
46
|
+
lines.push(" <description>Style</description>");
|
|
47
|
+
lines.push(" <manufacturer>Apple</manufacturer>");
|
|
48
|
+
lines.push(" <version>1</version>");
|
|
49
|
+
lines.push(" </factory>");
|
|
50
|
+
lines.push(' <factory id="2" uuid="babfc7777f4711d7aaa7000393833f6a">');
|
|
51
|
+
lines.push(" <description>Text</description>");
|
|
52
|
+
lines.push(" <manufacturer>Apple</manufacturer>");
|
|
53
|
+
lines.push(" <version>1</version>");
|
|
54
|
+
lines.push(" </factory>");
|
|
55
|
+
lines.push(' <factory id="3" uuid="712462a4323911d78f8400039389b702">');
|
|
56
|
+
lines.push(" <description>Shape</description>");
|
|
57
|
+
lines.push(" <manufacturer>Apple</manufacturer>");
|
|
58
|
+
lines.push(" <version>1</version>");
|
|
59
|
+
lines.push(" </factory>");
|
|
60
|
+
lines.push(' <factory id="4" uuid="65cb4dc9d4504fa281921f5f751fba06">');
|
|
61
|
+
lines.push(" <description>Widget</description>");
|
|
62
|
+
lines.push(" <manufacturer>Apple</manufacturer>");
|
|
63
|
+
lines.push(" <version>1</version>");
|
|
64
|
+
lines.push(" </factory>");
|
|
65
|
+
lines.push("");
|
|
66
|
+
lines.push(" <scene>");
|
|
67
|
+
lines.push(" <sceneSettings>");
|
|
68
|
+
lines.push(` <width>${width}</width>`);
|
|
69
|
+
lines.push(` <height>${height}</height>`);
|
|
70
|
+
lines.push(` <duration>${totalFrames}</duration>`);
|
|
71
|
+
lines.push(` <frameRate>${frameRate}</frameRate>`);
|
|
72
|
+
lines.push(" <NTSC>0</NTSC>");
|
|
73
|
+
lines.push(" <channels>4</channels>");
|
|
74
|
+
lines.push(" <channelDepth>8</channelDepth>");
|
|
75
|
+
lines.push(" <pixelAspectRatio>1</pixelAspectRatio>");
|
|
76
|
+
lines.push(' <backgroundColor red="0" green="0" blue="0" alpha="1"/>');
|
|
77
|
+
lines.push(" </sceneSettings>");
|
|
78
|
+
lines.push(" <currentFrame>0</currentFrame>");
|
|
79
|
+
lines.push(` <timeRange offset="0" duration="${totalFrames}"/>`);
|
|
80
|
+
lines.push(` <playRange offset="0" duration="${totalFrames}"/>`);
|
|
81
|
+
lines.push(" <flags>1</flags>");
|
|
82
|
+
if (scene.rig && Object.keys(scene.rig.widgets).length > 0) {
|
|
83
|
+
lines.push(` <scenenode name="Rigs" id="${nodeId++}" factoryID="4" version="1">`);
|
|
84
|
+
for (const [widgetName, widget] of Object.entries(scene.rig.widgets)) {
|
|
85
|
+
lines.push(` <scenenode name="${escapeXml(widgetName)}" id="${nodeId++}" factoryID="4" version="1">`);
|
|
86
|
+
lines.push(' <parameter name="Object" id="2">');
|
|
87
|
+
if (widget.type === "slider") {
|
|
88
|
+
lines.push(` <parameter name="Widget" id="100" default="0" value="${widget.value}"/>`);
|
|
89
|
+
} else if (widget.type === "toggle") {
|
|
90
|
+
lines.push(` <parameter name="Widget" id="100" default="0" value="${widget.enabled ? 1 : 0}"/>`);
|
|
91
|
+
} else if (widget.type === "popup") {
|
|
92
|
+
lines.push(` <parameter name="Widget" id="100" default="0" value="${escapeXml(widget.selected)}"/>`);
|
|
93
|
+
}
|
|
94
|
+
lines.push(" </parameter>");
|
|
95
|
+
lines.push(" </scenenode>");
|
|
96
|
+
}
|
|
97
|
+
lines.push(" </scenenode>");
|
|
98
|
+
}
|
|
99
|
+
for (const [idx, item] of shotTimings.entries()) {
|
|
100
|
+
const shotLabel = item.shot.title ? `Shot ${idx + 1}: ${escapeXml(item.shot.title)}` : `Shot ${idx + 1}`;
|
|
101
|
+
lines.push(` <group name="${shotLabel}" id="${nodeId++}">`);
|
|
102
|
+
lines.push(` <timing in="${item.startFrame}" out="${item.endFrame}" offset="${item.startFrame}"/>`);
|
|
103
|
+
if (item.shot.run) {
|
|
104
|
+
for (const cmd of item.shot.run) {
|
|
105
|
+
if (isElementCommand(cmd) && cmd.element) {
|
|
106
|
+
const el = cmd.element;
|
|
107
|
+
const isText = el.type === "text";
|
|
108
|
+
const factoryId = isText ? 2 : 3;
|
|
109
|
+
lines.push(` <scenenode name="${escapeXml(el.id)}" id="${nodeId++}" factoryID="${factoryId}" version="1">`);
|
|
110
|
+
lines.push(` <timing in="${item.startFrame}" out="${item.endFrame}" offset="${item.startFrame}"/>`);
|
|
111
|
+
lines.push(' <parameter name="Properties" id="1">');
|
|
112
|
+
lines.push(' <parameter name="Transform" id="100">');
|
|
113
|
+
lines.push(' <parameter name="Position" id="101">');
|
|
114
|
+
lines.push(` <parameter name="X" id="1" value="${el.x}"/>`);
|
|
115
|
+
lines.push(` <parameter name="Y" id="2" value="${-el.y}"/>`);
|
|
116
|
+
lines.push(" </parameter>");
|
|
117
|
+
if (!isText) {
|
|
118
|
+
lines.push(' <parameter name="Scale" id="102">');
|
|
119
|
+
lines.push(` <parameter name="X" id="1" value="${el.width}"/>`);
|
|
120
|
+
lines.push(` <parameter name="Y" id="2" value="${el.height}"/>`);
|
|
121
|
+
lines.push(" </parameter>");
|
|
122
|
+
}
|
|
123
|
+
lines.push(" </parameter>");
|
|
124
|
+
lines.push(" </parameter>");
|
|
125
|
+
if (isText && el.text) {
|
|
126
|
+
lines.push(` <text>${escapeXml(el.text)}</text>`);
|
|
127
|
+
}
|
|
128
|
+
lines.push(" </scenenode>");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
lines.push(" </group>");
|
|
133
|
+
}
|
|
134
|
+
lines.push(" </scene>");
|
|
135
|
+
lines.push("</ozml>");
|
|
136
|
+
lines.push("");
|
|
137
|
+
return lines.join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/export/lottie.ts
|
|
141
|
+
function createLottieEase(durationFrames, deltaVal, outEase, inEase) {
|
|
142
|
+
const infOut = Math.max(1e-3, Math.min(1, (outEase?.influence ?? 33.333) / 100));
|
|
143
|
+
const infIn = Math.max(1e-3, Math.min(1, (inEase?.influence ?? 33.333) / 100));
|
|
144
|
+
const safeDelta = Math.abs(deltaVal) > 1e-6 ? deltaVal : 1;
|
|
145
|
+
const speedOut = outEase?.speed ?? 0;
|
|
146
|
+
const speedIn = inEase?.speed ?? 0;
|
|
147
|
+
const ox = infOut;
|
|
148
|
+
const oy = Math.max(0, Math.min(1, speedOut * infOut * durationFrames / safeDelta));
|
|
149
|
+
const ix = 1 - infIn;
|
|
150
|
+
const iy = Math.max(0, Math.min(1, 1 - speedIn * infIn * durationFrames / safeDelta));
|
|
151
|
+
return {
|
|
152
|
+
o: { x: Number(ox.toFixed(4)), y: Number(oy.toFixed(4)) },
|
|
153
|
+
i: { x: Number(ix.toFixed(4)), y: Number(iy.toFixed(4)) }
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function exportLottie(scene, options = {}) {
|
|
157
|
+
const fr = options.frameRate ?? 30;
|
|
158
|
+
const width = scene.stage?.width ?? DEFAULT_STAGE.width;
|
|
159
|
+
const height = scene.stage?.height ?? DEFAULT_STAGE.height;
|
|
160
|
+
let currentMs = 0;
|
|
161
|
+
const shotTimings = scene.shots.map((shot, idx) => {
|
|
162
|
+
const camMs = idx > 0 && shot.camera ? CAMERA_MS : 0;
|
|
163
|
+
const holdMs = shot.hold ?? 800;
|
|
164
|
+
const durationMs = camMs + holdMs;
|
|
165
|
+
const startMs = currentMs;
|
|
166
|
+
currentMs += durationMs;
|
|
167
|
+
const startFrame = Math.round(startMs / 1e3 * fr);
|
|
168
|
+
const durationFrames = Math.max(1, Math.round(durationMs / 1e3 * fr));
|
|
169
|
+
const endFrame = startFrame + durationFrames;
|
|
170
|
+
return {
|
|
171
|
+
shot,
|
|
172
|
+
startFrame,
|
|
173
|
+
durationFrames,
|
|
174
|
+
endFrame
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
const totalFrames = Math.max(1, Math.round(currentMs / 1e3 * fr));
|
|
178
|
+
const tracks = /* @__PURE__ */ new Map();
|
|
179
|
+
for (const timing of shotTimings) {
|
|
180
|
+
const shot = timing.shot;
|
|
181
|
+
if (!shot.run) continue;
|
|
182
|
+
for (const cmd of shot.run) {
|
|
183
|
+
if (isElementCommand(cmd)) {
|
|
184
|
+
const el = cmd.element;
|
|
185
|
+
if (!tracks.has(el.id)) {
|
|
186
|
+
tracks.set(el.id, {
|
|
187
|
+
id: el.id,
|
|
188
|
+
type: el.type,
|
|
189
|
+
initialX: el.x,
|
|
190
|
+
initialY: el.y,
|
|
191
|
+
width: el.width,
|
|
192
|
+
height: el.height,
|
|
193
|
+
text: el.text,
|
|
194
|
+
opacity: el.opacity ?? 1,
|
|
195
|
+
inFrame: timing.startFrame,
|
|
196
|
+
outFrame: totalFrames,
|
|
197
|
+
posKeyframes: [
|
|
198
|
+
{
|
|
199
|
+
t: timing.startFrame,
|
|
200
|
+
s: [el.x + el.width * 0.5, el.y + el.height * 0.5, 0]
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
opacityKeyframes: [
|
|
204
|
+
{
|
|
205
|
+
t: timing.startFrame,
|
|
206
|
+
s: [(el.opacity ?? 1) * 100]
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
} else if (isUpdateCommand(cmd)) {
|
|
212
|
+
const track = tracks.get(cmd.id);
|
|
213
|
+
if (track) {
|
|
214
|
+
const props = cmd.props;
|
|
215
|
+
const prevPos = track.posKeyframes[track.posKeyframes.length - 1];
|
|
216
|
+
const newX = props.x !== void 0 ? props.x : prevPos.s[0] - track.width * 0.5;
|
|
217
|
+
const newY = props.y !== void 0 ? props.y : prevPos.s[1] - track.height * 0.5;
|
|
218
|
+
const ease = createLottieEase(timing.durationFrames, 1);
|
|
219
|
+
prevPos.o = ease.o;
|
|
220
|
+
prevPos.i = ease.i;
|
|
221
|
+
track.posKeyframes.push({
|
|
222
|
+
t: timing.endFrame,
|
|
223
|
+
s: [newX + track.width * 0.5, newY + track.height * 0.5, 0]
|
|
224
|
+
});
|
|
225
|
+
if (props.opacity !== void 0) {
|
|
226
|
+
const prevOp = track.opacityKeyframes[track.opacityKeyframes.length - 1];
|
|
227
|
+
prevOp.o = ease.o;
|
|
228
|
+
prevOp.i = ease.i;
|
|
229
|
+
track.opacityKeyframes.push({
|
|
230
|
+
t: timing.endFrame,
|
|
231
|
+
s: [props.opacity * 100]
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const layers = [];
|
|
239
|
+
let layerIndex = 1;
|
|
240
|
+
for (const track of tracks.values()) {
|
|
241
|
+
const isPosAnimated = track.posKeyframes.length > 1;
|
|
242
|
+
const isOpAnimated = track.opacityKeyframes.length > 1;
|
|
243
|
+
const p = isPosAnimated ? { a: 1, k: track.posKeyframes } : { a: 0, k: [track.initialX + track.width * 0.5, track.initialY + track.height * 0.5, 0] };
|
|
244
|
+
const o = isOpAnimated ? { a: 1, k: track.opacityKeyframes } : { a: 0, k: track.opacity * 100 };
|
|
245
|
+
const ks = {
|
|
246
|
+
p,
|
|
247
|
+
a: { a: 0, k: [0, 0, 0] },
|
|
248
|
+
s: { a: 0, k: [100, 100, 100] },
|
|
249
|
+
r: { a: 0, k: 0 },
|
|
250
|
+
o
|
|
251
|
+
};
|
|
252
|
+
const shapes = [
|
|
253
|
+
{
|
|
254
|
+
ty: "gr",
|
|
255
|
+
nm: track.id,
|
|
256
|
+
it: [
|
|
257
|
+
{
|
|
258
|
+
ty: "rc",
|
|
259
|
+
nm: "Rectangle Path",
|
|
260
|
+
d: 1,
|
|
261
|
+
s: { a: 0, k: [track.width, track.height] },
|
|
262
|
+
p: { a: 0, k: [0, 0] },
|
|
263
|
+
r: { a: 0, k: 8 }
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
ty: "fl",
|
|
267
|
+
nm: "Fill",
|
|
268
|
+
c: { a: 0, k: [0.2, 0.4, 0.9, 1] },
|
|
269
|
+
o: { a: 0, k: 100 },
|
|
270
|
+
r: 1
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
ty: "tr",
|
|
274
|
+
nm: "Transform",
|
|
275
|
+
p: { a: 0, k: [0, 0] },
|
|
276
|
+
a: { a: 0, k: [0, 0] },
|
|
277
|
+
s: { a: 0, k: [100, 100] },
|
|
278
|
+
r: { a: 0, k: 0 },
|
|
279
|
+
o: { a: 0, k: 100 }
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
];
|
|
284
|
+
layers.push({
|
|
285
|
+
ddd: 0,
|
|
286
|
+
ind: layerIndex++,
|
|
287
|
+
ty: 4,
|
|
288
|
+
// Shape layer
|
|
289
|
+
nm: track.id,
|
|
290
|
+
sr: 1,
|
|
291
|
+
ks,
|
|
292
|
+
ao: 0,
|
|
293
|
+
ip: track.inFrame,
|
|
294
|
+
op: track.outFrame,
|
|
295
|
+
st: track.inFrame,
|
|
296
|
+
bm: 0,
|
|
297
|
+
// Normal blend mode
|
|
298
|
+
shapes
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
v: "5.5.2",
|
|
303
|
+
fr,
|
|
304
|
+
ip: 0,
|
|
305
|
+
op: totalFrames,
|
|
306
|
+
w: width,
|
|
307
|
+
h: height,
|
|
308
|
+
nm: scene.name || "Flair Animation",
|
|
309
|
+
ddd: 0,
|
|
310
|
+
assets: [],
|
|
311
|
+
layers
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/export/interchange.ts
|
|
316
|
+
function escapeXml2(unsafe) {
|
|
317
|
+
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
318
|
+
}
|
|
319
|
+
function calculateShotTimings(scene, frameRate = 30) {
|
|
320
|
+
let currentMs = 0;
|
|
321
|
+
return scene.shots.map((shot, index) => {
|
|
322
|
+
const camMs = index > 0 && shot.camera ? CAMERA_MS : 0;
|
|
323
|
+
const holdMs = shot.hold ?? 800;
|
|
324
|
+
const durationMs = camMs + holdMs;
|
|
325
|
+
const startMs = currentMs;
|
|
326
|
+
currentMs += durationMs;
|
|
327
|
+
const startFrame = Math.round(startMs / 1e3 * frameRate);
|
|
328
|
+
const durationFrames = Math.max(1, Math.round(durationMs / 1e3 * frameRate));
|
|
329
|
+
const endFrame = startFrame + durationFrames;
|
|
330
|
+
return {
|
|
331
|
+
shot,
|
|
332
|
+
index,
|
|
333
|
+
startFrame,
|
|
334
|
+
endFrame,
|
|
335
|
+
durationFrames,
|
|
336
|
+
startMs,
|
|
337
|
+
durationMs
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
function framesToTimecode(frame, timebase = 30, dropFrame = false) {
|
|
342
|
+
const roundedFps = Math.round(timebase);
|
|
343
|
+
let totalFrames = Math.max(0, Math.floor(frame));
|
|
344
|
+
if (dropFrame && (roundedFps === 30 || roundedFps === 29)) {
|
|
345
|
+
const dropFramesPer10Mins = 18;
|
|
346
|
+
const framesPer10Mins = 17982;
|
|
347
|
+
const framesPerMinute = 1798;
|
|
348
|
+
const d = Math.floor(totalFrames / framesPer10Mins);
|
|
349
|
+
const m = totalFrames % framesPer10Mins;
|
|
350
|
+
if (m >= 2) {
|
|
351
|
+
totalFrames += dropFramesPer10Mins * d + 2 * Math.floor((m - 2) / framesPerMinute);
|
|
352
|
+
} else {
|
|
353
|
+
totalFrames += dropFramesPer10Mins * d;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const ff = totalFrames % roundedFps;
|
|
357
|
+
const totalSeconds = Math.floor(totalFrames / roundedFps);
|
|
358
|
+
const ss = totalSeconds % 60;
|
|
359
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
360
|
+
const mm = totalMinutes % 60;
|
|
361
|
+
const hh = Math.floor(totalMinutes / 60);
|
|
362
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
363
|
+
const separator = dropFrame ? ";" : ":";
|
|
364
|
+
return `${pad(hh)}:${pad(mm)}:${pad(ss)}${separator}${pad(ff)}`;
|
|
365
|
+
}
|
|
366
|
+
function timecodeToFrames(timecode, timebase = 30, dropFrame = false) {
|
|
367
|
+
const parts = timecode.split(/[:;]/).map(Number);
|
|
368
|
+
if (parts.length !== 4 || parts.some(isNaN)) {
|
|
369
|
+
return 0;
|
|
370
|
+
}
|
|
371
|
+
const [hh, mm, ss, ff] = parts;
|
|
372
|
+
const roundedFps = Math.round(timebase);
|
|
373
|
+
if (dropFrame && (roundedFps === 30 || roundedFps === 29)) {
|
|
374
|
+
const totalMinutes = hh * 60 + mm;
|
|
375
|
+
const dropCount = (totalMinutes - Math.floor(totalMinutes / 10)) * 2;
|
|
376
|
+
return (hh * 3600 + mm * 60 + ss) * roundedFps + ff - dropCount;
|
|
377
|
+
}
|
|
378
|
+
return (hh * 3600 + mm * 60 + ss) * roundedFps + ff;
|
|
379
|
+
}
|
|
380
|
+
function exportFcp7Xml(scene, options = {}) {
|
|
381
|
+
const frameRate = options.frameRate ?? 30;
|
|
382
|
+
const timebase = Math.round(frameRate);
|
|
383
|
+
const ntsc = options.ntsc ?? (Math.abs(frameRate - 29.97) < 0.05 || Math.abs(frameRate - 59.94) < 0.05);
|
|
384
|
+
const ntscStr = ntsc ? "TRUE" : "FALSE";
|
|
385
|
+
const width = scene.stage?.width ?? DEFAULT_STAGE.width;
|
|
386
|
+
const height = scene.stage?.height ?? DEFAULT_STAGE.height;
|
|
387
|
+
const sequenceName = options.sequenceName ?? scene.name ?? "Flair Sequence";
|
|
388
|
+
const timecodeStart = options.timecodeStart ?? "00:00:00:00";
|
|
389
|
+
const mediaPrefix = options.mediaPathPrefix ?? "file://localhost/media/";
|
|
390
|
+
const includeTextTrack = options.includeTextTrack ?? true;
|
|
391
|
+
const includeMarkers = options.includeMarkers ?? true;
|
|
392
|
+
const timings = calculateShotTimings(scene, frameRate);
|
|
393
|
+
const totalFrames = timings.length > 0 ? timings[timings.length - 1].endFrame : 0;
|
|
394
|
+
const startFrameOffset = timecodeToFrames(timecodeStart, frameRate);
|
|
395
|
+
const lines = [];
|
|
396
|
+
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
397
|
+
lines.push("<!DOCTYPE xmeml>");
|
|
398
|
+
lines.push('<xmeml version="4">');
|
|
399
|
+
lines.push(` <sequence id="sequence-1">`);
|
|
400
|
+
lines.push(` <name>${escapeXml2(sequenceName)}</name>`);
|
|
401
|
+
lines.push(` <duration>${totalFrames}</duration>`);
|
|
402
|
+
lines.push(" <rate>");
|
|
403
|
+
lines.push(` <timebase>${timebase}</timebase>`);
|
|
404
|
+
lines.push(` <ntsc>${ntscStr}</ntsc>`);
|
|
405
|
+
lines.push(" </rate>");
|
|
406
|
+
lines.push(" <timecode>");
|
|
407
|
+
lines.push(" <rate>");
|
|
408
|
+
lines.push(` <timebase>${timebase}</timebase>`);
|
|
409
|
+
lines.push(` <ntsc>${ntscStr}</ntsc>`);
|
|
410
|
+
lines.push(" </rate>");
|
|
411
|
+
lines.push(` <string>${timecodeStart}</string>`);
|
|
412
|
+
lines.push(` <frame>${startFrameOffset}</frame>`);
|
|
413
|
+
lines.push(" <displayformat>NDF</displayformat>");
|
|
414
|
+
lines.push(" </timecode>");
|
|
415
|
+
if (includeMarkers) {
|
|
416
|
+
timings.forEach(({ shot, index, startFrame, endFrame }) => {
|
|
417
|
+
const name = shot.title || `Shot ${index + 1}`;
|
|
418
|
+
const comment = shot.caption || "";
|
|
419
|
+
lines.push(" <marker>");
|
|
420
|
+
lines.push(` <name>${escapeXml2(name)}</name>`);
|
|
421
|
+
lines.push(` <comment>${escapeXml2(comment)}</comment>`);
|
|
422
|
+
lines.push(` <in>${startFrame}</in>`);
|
|
423
|
+
lines.push(` <out>${endFrame}</out>`);
|
|
424
|
+
lines.push(" </marker>");
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
lines.push(" <media>");
|
|
428
|
+
lines.push(" <video>");
|
|
429
|
+
lines.push(" <format>");
|
|
430
|
+
lines.push(" <samplecharacteristics>");
|
|
431
|
+
lines.push(` <width>${width}</width>`);
|
|
432
|
+
lines.push(` <height>${height}</height>`);
|
|
433
|
+
lines.push(" <pixelaspectratio>square</pixelaspectratio>");
|
|
434
|
+
lines.push(" <rate>");
|
|
435
|
+
lines.push(` <timebase>${timebase}</timebase>`);
|
|
436
|
+
lines.push(` <ntsc>${ntscStr}</ntsc>`);
|
|
437
|
+
lines.push(" </rate>");
|
|
438
|
+
lines.push(" </samplecharacteristics>");
|
|
439
|
+
lines.push(" </format>");
|
|
440
|
+
lines.push(" <track>");
|
|
441
|
+
lines.push(" <enabled>TRUE</enabled>");
|
|
442
|
+
lines.push(" <locked>FALSE</locked>");
|
|
443
|
+
timings.forEach(({ shot, index, startFrame, endFrame, durationFrames }) => {
|
|
444
|
+
const shotName = shot.title || `Shot_${index + 1}`;
|
|
445
|
+
const fileId = `file-${index + 1}`;
|
|
446
|
+
const clipId = `clipitem-${index + 1}`;
|
|
447
|
+
const fileName = `${shotName.replace(/[^a-zA-Z0-9_-]/g, "_")}.mov`;
|
|
448
|
+
const fileUrl = `${mediaPrefix.replace(/\/+$/, "")}/${fileName}`;
|
|
449
|
+
lines.push(` <clipitem id="${clipId}">`);
|
|
450
|
+
lines.push(` <name>${escapeXml2(shotName)}</name>`);
|
|
451
|
+
lines.push(` <duration>${durationFrames}</duration>`);
|
|
452
|
+
lines.push(" <rate>");
|
|
453
|
+
lines.push(` <timebase>${timebase}</timebase>`);
|
|
454
|
+
lines.push(` <ntsc>${ntscStr}</ntsc>`);
|
|
455
|
+
lines.push(" </rate>");
|
|
456
|
+
lines.push(` <start>${startFrame}</start>`);
|
|
457
|
+
lines.push(` <end>${endFrame}</end>`);
|
|
458
|
+
lines.push(" <in>0</in>");
|
|
459
|
+
lines.push(` <out>${durationFrames}</out>`);
|
|
460
|
+
lines.push(` <file id="${fileId}">`);
|
|
461
|
+
lines.push(` <name>${escapeXml2(fileName)}</name>`);
|
|
462
|
+
lines.push(` <pathurl>${escapeXml2(fileUrl)}</pathurl>`);
|
|
463
|
+
lines.push(" <rate>");
|
|
464
|
+
lines.push(` <timebase>${timebase}</timebase>`);
|
|
465
|
+
lines.push(` <ntsc>${ntscStr}</ntsc>`);
|
|
466
|
+
lines.push(" </rate>");
|
|
467
|
+
lines.push(` <duration>${durationFrames}</duration>`);
|
|
468
|
+
lines.push(" <timecode>");
|
|
469
|
+
lines.push(" <rate>");
|
|
470
|
+
lines.push(` <timebase>${timebase}</timebase>`);
|
|
471
|
+
lines.push(` <ntsc>${ntscStr}</ntsc>`);
|
|
472
|
+
lines.push(" </rate>");
|
|
473
|
+
lines.push(" <string>00:00:00:00</string>");
|
|
474
|
+
lines.push(" <frame>0</frame>");
|
|
475
|
+
lines.push(" <displayformat>NDF</displayformat>");
|
|
476
|
+
lines.push(" </timecode>");
|
|
477
|
+
lines.push(" <media>");
|
|
478
|
+
lines.push(" <video>");
|
|
479
|
+
lines.push(" <samplecharacteristics>");
|
|
480
|
+
lines.push(` <width>${width}</width>`);
|
|
481
|
+
lines.push(` <height>${height}</height>`);
|
|
482
|
+
lines.push(" </samplecharacteristics>");
|
|
483
|
+
lines.push(" </video>");
|
|
484
|
+
lines.push(" </media>");
|
|
485
|
+
lines.push(" </file>");
|
|
486
|
+
if (shot.caption) {
|
|
487
|
+
lines.push(" <marker>");
|
|
488
|
+
lines.push(` <name>${escapeXml2(shotName)} Narration</name>`);
|
|
489
|
+
lines.push(` <comment>${escapeXml2(shot.caption)}</comment>`);
|
|
490
|
+
lines.push(" <in>0</in>");
|
|
491
|
+
lines.push(` <out>${durationFrames}</out>`);
|
|
492
|
+
lines.push(" </marker>");
|
|
493
|
+
}
|
|
494
|
+
lines.push(" <filter>");
|
|
495
|
+
lines.push(" <effect>");
|
|
496
|
+
lines.push(" <name>Basic Motion</name>");
|
|
497
|
+
lines.push(" <effectid>basic</effectid>");
|
|
498
|
+
lines.push(" <effectcategory>motion</effectcategory>");
|
|
499
|
+
lines.push(" <effecttype>motion</effecttype>");
|
|
500
|
+
lines.push(" <mediatype>video</mediatype>");
|
|
501
|
+
lines.push(" <parameter>");
|
|
502
|
+
lines.push(" <parameterid>scale</parameterid>");
|
|
503
|
+
lines.push(" <name>Scale</name>");
|
|
504
|
+
lines.push(" <value>100</value>");
|
|
505
|
+
lines.push(" </parameter>");
|
|
506
|
+
lines.push(" </effect>");
|
|
507
|
+
lines.push(" </filter>");
|
|
508
|
+
lines.push(" </clipitem>");
|
|
509
|
+
});
|
|
510
|
+
lines.push(" </track>");
|
|
511
|
+
const hasTextOverlays = timings.some((t) => t.shot.title || t.shot.caption);
|
|
512
|
+
if (includeTextTrack && hasTextOverlays) {
|
|
513
|
+
lines.push(" <track>");
|
|
514
|
+
lines.push(" <enabled>TRUE</enabled>");
|
|
515
|
+
lines.push(" <locked>FALSE</locked>");
|
|
516
|
+
timings.forEach(({ shot, index, startFrame, endFrame, durationFrames }) => {
|
|
517
|
+
const text = shot.title ?? shot.caption;
|
|
518
|
+
if (!text) return;
|
|
519
|
+
const generatorId = `generator-title-${index + 1}`;
|
|
520
|
+
lines.push(` <clipitem id="${generatorId}">`);
|
|
521
|
+
lines.push(` <name>${escapeXml2(text)}</name>`);
|
|
522
|
+
lines.push(` <duration>${durationFrames}</duration>`);
|
|
523
|
+
lines.push(" <rate>");
|
|
524
|
+
lines.push(` <timebase>${timebase}</timebase>`);
|
|
525
|
+
lines.push(` <ntsc>${ntscStr}</ntsc>`);
|
|
526
|
+
lines.push(" </rate>");
|
|
527
|
+
lines.push(` <start>${startFrame}</start>`);
|
|
528
|
+
lines.push(` <end>${endFrame}</end>`);
|
|
529
|
+
lines.push(" <in>0</in>");
|
|
530
|
+
lines.push(` <out>${durationFrames}</out>`);
|
|
531
|
+
lines.push(" <effect>");
|
|
532
|
+
lines.push(" <name>Text</name>");
|
|
533
|
+
lines.push(" <effectid>Text</effectid>");
|
|
534
|
+
lines.push(" <effectcategory>Text</effectcategory>");
|
|
535
|
+
lines.push(" <effecttype>generator</effecttype>");
|
|
536
|
+
lines.push(" <mediatype>video</mediatype>");
|
|
537
|
+
lines.push(" <parameter>");
|
|
538
|
+
lines.push(" <parameterid>str</parameterid>");
|
|
539
|
+
lines.push(" <name>Text</name>");
|
|
540
|
+
lines.push(` <value>${escapeXml2(text)}</value>`);
|
|
541
|
+
lines.push(" </parameter>");
|
|
542
|
+
lines.push(" </effect>");
|
|
543
|
+
lines.push(" </clipitem>");
|
|
544
|
+
});
|
|
545
|
+
lines.push(" </track>");
|
|
546
|
+
}
|
|
547
|
+
lines.push(" </video>");
|
|
548
|
+
lines.push(" </media>");
|
|
549
|
+
lines.push(" </sequence>");
|
|
550
|
+
lines.push("</xmeml>");
|
|
551
|
+
return lines.join("\n");
|
|
552
|
+
}
|
|
553
|
+
function exportCmx3600Edl(scene, options = {}) {
|
|
554
|
+
const frameRate = options.frameRate ?? 30;
|
|
555
|
+
const timebase = Math.round(frameRate);
|
|
556
|
+
const dropFrame = options.dropFrame ?? false;
|
|
557
|
+
const reel = (options.reel ?? "AX").slice(0, 8).padEnd(8, " ");
|
|
558
|
+
const title = (scene.name ?? "UNTITLED").toUpperCase().replace(/[^A-Z0-9_ -]/g, "");
|
|
559
|
+
const startTc = options.timecodeStart ?? "01:00:00:00";
|
|
560
|
+
const timings = calculateShotTimings(scene, frameRate);
|
|
561
|
+
let recordFrame = timecodeToFrames(startTc, timebase, dropFrame);
|
|
562
|
+
const lines = [];
|
|
563
|
+
lines.push(`TITLE: ${title}`);
|
|
564
|
+
lines.push(dropFrame ? "FCM: DROP FRAME" : "FCM: NON-DROP FRAME");
|
|
565
|
+
lines.push("");
|
|
566
|
+
timings.forEach(({ shot, index, durationFrames }) => {
|
|
567
|
+
const eventNum = (index + 1).toString().padStart(3, "0");
|
|
568
|
+
const srcInTc = "00:00:00:00";
|
|
569
|
+
const srcOutTc = framesToTimecode(durationFrames, timebase, dropFrame);
|
|
570
|
+
const recInTc = framesToTimecode(recordFrame, timebase, dropFrame);
|
|
571
|
+
recordFrame += durationFrames;
|
|
572
|
+
const recOutTc = framesToTimecode(recordFrame, timebase, dropFrame);
|
|
573
|
+
lines.push(`${eventNum} ${reel} V C ${srcInTc} ${srcOutTc} ${recInTc} ${recOutTc}`);
|
|
574
|
+
const clipName = shot.title || `Shot ${index + 1}`;
|
|
575
|
+
lines.push(`* FROM CLIP NAME: ${clipName}`);
|
|
576
|
+
if (shot.caption) {
|
|
577
|
+
lines.push(`* COMMENT: ${shot.caption}`);
|
|
578
|
+
}
|
|
579
|
+
lines.push("");
|
|
580
|
+
});
|
|
581
|
+
return lines.join("\n");
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export {
|
|
585
|
+
exportMotn,
|
|
586
|
+
createLottieEase,
|
|
587
|
+
exportLottie,
|
|
588
|
+
calculateShotTimings,
|
|
589
|
+
framesToTimecode,
|
|
590
|
+
timecodeToFrames,
|
|
591
|
+
exportFcp7Xml,
|
|
592
|
+
exportCmx3600Edl
|
|
593
|
+
};
|
|
594
|
+
//# sourceMappingURL=chunk-UQQNZSYL.js.map
|