@meframe/core 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/model/CompositionModel.d.ts +6 -0
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +27 -2
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/types.d.ts +8 -2
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts +1 -0
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +22 -1
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +8 -1
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
- package/dist/stages/compose/LayerRenderer.js +20 -9
- package/dist/stages/compose/LayerRenderer.js.map +1 -1
- package/dist/stages/compose/font-system/FontManager.d.ts.map +1 -1
- package/dist/stages/compose/font-system/FontManager.js +6 -2
- package/dist/stages/compose/font-system/FontManager.js.map +1 -1
- package/dist/stages/compose/font-system/types.d.ts +1 -1
- package/dist/stages/compose/font-system/types.d.ts.map +1 -1
- package/dist/stages/compose/instructions.d.ts +1 -1
- package/dist/stages/compose/instructions.d.ts.map +1 -1
- package/dist/stages/compose/text-layer-animation-time.d.ts +8 -0
- package/dist/stages/compose/text-layer-animation-time.d.ts.map +1 -0
- package/dist/stages/compose/text-layer-animation-time.js +22 -0
- package/dist/stages/compose/text-layer-animation-time.js.map +1 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.js +1 -21
- package/dist/stages/compose/text-renderers/basic-text-renderer.js.map +1 -1
- package/dist/stages/compose/text-renderers/caption-stagger-entrance-renderer.d.ts +9 -0
- package/dist/stages/compose/text-renderers/caption-stagger-entrance-renderer.d.ts.map +1 -0
- package/dist/stages/compose/text-renderers/caption-stagger-entrance-renderer.js +188 -0
- package/dist/stages/compose/text-renderers/caption-stagger-entrance-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.js +1 -1
- package/dist/stages/compose/text-utils/caption-layout.d.ts +4 -0
- package/dist/stages/compose/text-utils/caption-layout.d.ts.map +1 -1
- package/dist/stages/compose/text-utils/caption-layout.js +14 -1
- package/dist/stages/compose/text-utils/caption-layout.js.map +1 -1
- package/dist/stages/compose/types.d.ts +3 -1
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/workers/stages/export/{export.worker.Cw9iPvkh.js → export.worker.Dztm6GuN.js} +218 -28
- package/dist/workers/stages/export/export.worker.Dztm6GuN.js.map +1 -0
- package/dist/workers/worker-manifest.json +1 -1
- package/package.json +1 -1
- package/dist/workers/stages/export/export.worker.Cw9iPvkh.js.map +0 -1
|
@@ -508,6 +508,23 @@ class WorkerChannel {
|
|
|
508
508
|
this.port.onmessage = handler;
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
|
+
function attachmentEntranceRelativeTimeUs(clipRelativeTimeUs, activeRanges) {
|
|
512
|
+
const containing = activeRanges?.find(
|
|
513
|
+
(r) => clipRelativeTimeUs >= r.startUs && clipRelativeTimeUs < r.endUs
|
|
514
|
+
);
|
|
515
|
+
if (!containing) {
|
|
516
|
+
return void 0;
|
|
517
|
+
}
|
|
518
|
+
return Math.max(0, clipRelativeTimeUs - containing.startUs);
|
|
519
|
+
}
|
|
520
|
+
function textAnimationFrameForTextLayer(layer, fallbackFrame, fps) {
|
|
521
|
+
const et = layer.entranceRelativeTimeUs;
|
|
522
|
+
if (et != null && Number.isFinite(et)) {
|
|
523
|
+
const frameDurationUs = 1e6 / fps;
|
|
524
|
+
return Math.floor(et / frameDurationUs);
|
|
525
|
+
}
|
|
526
|
+
return fallbackFrame;
|
|
527
|
+
}
|
|
511
528
|
function measureTextWidth(ctx, text, fontSize, fontFamily, fontWeight = 400) {
|
|
512
529
|
ctx.save();
|
|
513
530
|
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
@@ -824,7 +841,7 @@ function needsSpaceBetweenWords(locale, text) {
|
|
|
824
841
|
}
|
|
825
842
|
return !["zh-CN", "ja-JP", "ko-KR"].includes(locale);
|
|
826
843
|
}
|
|
827
|
-
function calculateYPosition$
|
|
844
|
+
function calculateYPosition$4(canvasHeight, totalHeight, globalPosition) {
|
|
828
845
|
if (!globalPosition) {
|
|
829
846
|
return canvasHeight / 2 - totalHeight / 2;
|
|
830
847
|
}
|
|
@@ -876,7 +893,7 @@ function renderBasicText(ctx, layer, canvasWidth, canvasHeight, _relativeFrame)
|
|
|
876
893
|
ctx.lineJoin = "round";
|
|
877
894
|
ctx.lineCap = "round";
|
|
878
895
|
const totalHeight = lines.length * fontSize * lineHeight;
|
|
879
|
-
const startY = calculateYPosition$
|
|
896
|
+
const startY = calculateYPosition$4(canvasHeight, totalHeight, layer.fontConfig?.globalPosition);
|
|
880
897
|
for (let i = 0; i < lines.length; i++) {
|
|
881
898
|
const line = lines[i];
|
|
882
899
|
const y = startY + i * fontSize * lineHeight + fontSize / 2;
|
|
@@ -890,22 +907,184 @@ function renderBasicText(ctx, layer, canvasWidth, canvasHeight, _relativeFrame)
|
|
|
890
907
|
}
|
|
891
908
|
ctx.restore();
|
|
892
909
|
}
|
|
893
|
-
|
|
910
|
+
const DEFAULT_DURATION_MS = 800;
|
|
911
|
+
const DEFAULT_STAGGER_MS = 50;
|
|
912
|
+
const SLIDE_BASE_PX = 50;
|
|
913
|
+
const LETTER_SPREAD_BASE_PX = 20;
|
|
914
|
+
const BLUR_START_PX = 10;
|
|
915
|
+
const FONT_REF_PX = 40;
|
|
916
|
+
function easeOutExpo(t) {
|
|
917
|
+
if (t <= 0) return 0;
|
|
918
|
+
if (t >= 1) return 1;
|
|
919
|
+
return 1 - Math.pow(2, -10 * t);
|
|
920
|
+
}
|
|
921
|
+
function calculateYPosition$3(canvasHeight, totalHeight, globalPosition) {
|
|
922
|
+
if (!globalPosition) {
|
|
923
|
+
return canvasHeight / 2 - totalHeight / 2;
|
|
924
|
+
}
|
|
925
|
+
if (globalPosition.top) {
|
|
926
|
+
const topPercent = parseFloat(globalPosition.top) / 100;
|
|
927
|
+
return canvasHeight * topPercent;
|
|
928
|
+
}
|
|
929
|
+
if (globalPosition.bottom) {
|
|
930
|
+
const bottomPercent = parseFloat(globalPosition.bottom) / 100;
|
|
931
|
+
return canvasHeight * (1 - bottomPercent) - totalHeight;
|
|
932
|
+
}
|
|
933
|
+
if (globalPosition.justifyContent === "center" || globalPosition.alignItems === "center") {
|
|
934
|
+
return canvasHeight / 2 - totalHeight / 2;
|
|
935
|
+
}
|
|
936
|
+
return canvasHeight / 2 - totalHeight / 2;
|
|
937
|
+
}
|
|
938
|
+
function charProgress(relativeFrame, fps, charIndex, staggerMs, durationMs) {
|
|
939
|
+
const tMs = relativeFrame / fps * 1e3;
|
|
940
|
+
const startMs = charIndex * staggerMs;
|
|
941
|
+
if (tMs <= startMs) return 0;
|
|
942
|
+
const raw = (tMs - startMs) / durationMs;
|
|
943
|
+
return easeOutExpo(Math.min(1, raw));
|
|
944
|
+
}
|
|
945
|
+
function buildCharSlots(ctx, layer, canvasWidth, canvasHeight) {
|
|
894
946
|
const fontConfig = layer.fontConfig?.textStyle;
|
|
895
|
-
if (!fontConfig) return;
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
const
|
|
902
|
-
|
|
947
|
+
if (!fontConfig) return null;
|
|
948
|
+
const fontSize = fontConfig.fontSize;
|
|
949
|
+
const fontFamily = fontConfig.fontFamily;
|
|
950
|
+
const fontWeight = fontConfig.fontWeight;
|
|
951
|
+
const lineHeight = fontConfig.lineHeight || 1.2;
|
|
952
|
+
const maxWidth = canvasWidth * 0.64;
|
|
953
|
+
const text = getLetterCaseText(layer.text, layer.letterCase);
|
|
954
|
+
let lines;
|
|
955
|
+
if (layer.wordTimings && layer.wordTimings.length > 0) {
|
|
956
|
+
const needsSpace = needsSpaceBetweenWords(layer.localeCode || "en-US", text);
|
|
957
|
+
const words = text.split(needsSpace ? /\s+/ : "");
|
|
958
|
+
lines = formEvenLinesWithWords(
|
|
959
|
+
ctx,
|
|
960
|
+
words,
|
|
961
|
+
maxWidth,
|
|
962
|
+
fontSize,
|
|
963
|
+
needsSpace,
|
|
964
|
+
fontFamily,
|
|
965
|
+
fontWeight
|
|
966
|
+
);
|
|
967
|
+
} else {
|
|
968
|
+
lines = wrapText(ctx, text, maxWidth, fontSize, fontFamily, fontWeight);
|
|
969
|
+
}
|
|
970
|
+
const totalHeight = lines.length * fontSize * lineHeight;
|
|
971
|
+
const startY = calculateYPosition$3(canvasHeight, totalHeight, layer.fontConfig?.globalPosition);
|
|
972
|
+
ctx.save();
|
|
973
|
+
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
974
|
+
const slots = [];
|
|
975
|
+
let globalIndex = 0;
|
|
976
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
977
|
+
const line = lines[lineIndex];
|
|
978
|
+
const y = startY + lineIndex * fontSize * lineHeight + fontSize / 2;
|
|
979
|
+
const lineWidth = measureTextWidth(ctx, line, fontSize, fontFamily, fontWeight);
|
|
980
|
+
let cx = canvasWidth / 2 - lineWidth / 2;
|
|
981
|
+
for (const ch of Array.from(line)) {
|
|
982
|
+
const cw = measureTextWidth(ctx, ch, fontSize, fontFamily, fontWeight);
|
|
983
|
+
slots.push({
|
|
984
|
+
ch,
|
|
985
|
+
x: cx + cw / 2,
|
|
986
|
+
y,
|
|
987
|
+
globalIndex
|
|
988
|
+
});
|
|
989
|
+
globalIndex++;
|
|
990
|
+
cx += cw;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
ctx.restore();
|
|
994
|
+
return { slots, fontSize, lineHeight };
|
|
995
|
+
}
|
|
996
|
+
function renderCaptionStaggerEntrance(ctx, layer, canvasWidth, canvasHeight, relativeFrame, fps, preset) {
|
|
997
|
+
const built = buildCharSlots(ctx, layer, canvasWidth, canvasHeight);
|
|
998
|
+
if (!built) return;
|
|
999
|
+
const { slots, fontSize } = built;
|
|
1000
|
+
const fontConfig = layer.fontConfig.textStyle;
|
|
1001
|
+
const fontFamily = fontConfig.fontFamily;
|
|
1002
|
+
const fontWeight = fontConfig.fontWeight;
|
|
1003
|
+
const fill = fontConfig.fill;
|
|
1004
|
+
const stroke = fontConfig.stroke;
|
|
1005
|
+
const strokeWidth = fontConfig.strokeWidth || 0;
|
|
1006
|
+
const scalePx = fontSize / FONT_REF_PX;
|
|
1007
|
+
const staggerMs = DEFAULT_STAGGER_MS;
|
|
903
1008
|
ctx.save();
|
|
904
|
-
ctx.
|
|
905
|
-
ctx.
|
|
906
|
-
ctx.
|
|
907
|
-
ctx.
|
|
908
|
-
|
|
1009
|
+
ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
1010
|
+
ctx.textAlign = "center";
|
|
1011
|
+
ctx.textBaseline = "middle";
|
|
1012
|
+
ctx.lineJoin = "round";
|
|
1013
|
+
ctx.lineCap = "round";
|
|
1014
|
+
const tMsGlobal = relativeFrame / fps * 1e3;
|
|
1015
|
+
for (const slot of slots) {
|
|
1016
|
+
let p;
|
|
1017
|
+
if (preset === "typewriter") {
|
|
1018
|
+
const startMs = slot.globalIndex * staggerMs;
|
|
1019
|
+
p = tMsGlobal >= startMs ? 1 : 0;
|
|
1020
|
+
} else {
|
|
1021
|
+
p = charProgress(relativeFrame, fps, slot.globalIndex, staggerMs, DEFAULT_DURATION_MS);
|
|
1022
|
+
}
|
|
1023
|
+
if (p <= 0 && preset !== "blur") continue;
|
|
1024
|
+
const slidePx = SLIDE_BASE_PX * scalePx;
|
|
1025
|
+
const spreadPx = LETTER_SPREAD_BASE_PX * scalePx;
|
|
1026
|
+
ctx.save();
|
|
1027
|
+
let opacity = p;
|
|
1028
|
+
let tx = 0;
|
|
1029
|
+
let ty = 0;
|
|
1030
|
+
let rot = 0;
|
|
1031
|
+
let sc = 1;
|
|
1032
|
+
let sy = 1;
|
|
1033
|
+
let blurPx = 0;
|
|
1034
|
+
switch (preset) {
|
|
1035
|
+
case "fade":
|
|
1036
|
+
opacity = p;
|
|
1037
|
+
break;
|
|
1038
|
+
case "slideUp":
|
|
1039
|
+
opacity = p;
|
|
1040
|
+
ty = (1 - p) * slidePx;
|
|
1041
|
+
break;
|
|
1042
|
+
case "scale":
|
|
1043
|
+
opacity = p;
|
|
1044
|
+
sc = Math.max(0.04, p);
|
|
1045
|
+
break;
|
|
1046
|
+
case "rotateScale":
|
|
1047
|
+
opacity = p;
|
|
1048
|
+
rot = (1 - p) * 45 * Math.PI / 180;
|
|
1049
|
+
sc = 0.5 + p * 0.5;
|
|
1050
|
+
break;
|
|
1051
|
+
case "blur":
|
|
1052
|
+
opacity = p;
|
|
1053
|
+
blurPx = (1 - p) * BLUR_START_PX;
|
|
1054
|
+
break;
|
|
1055
|
+
case "flip3d":
|
|
1056
|
+
opacity = p;
|
|
1057
|
+
sy = Math.max(0.04, p);
|
|
1058
|
+
sc = 1;
|
|
1059
|
+
break;
|
|
1060
|
+
case "typewriter":
|
|
1061
|
+
opacity = p >= 1 ? 1 : p;
|
|
1062
|
+
break;
|
|
1063
|
+
case "letterSpread":
|
|
1064
|
+
opacity = p;
|
|
1065
|
+
tx = (1 - p) * spreadPx * slot.globalIndex;
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
ctx.globalAlpha = opacity;
|
|
1069
|
+
if (blurPx > 0.01) {
|
|
1070
|
+
ctx.filter = `blur(${blurPx}px)`;
|
|
1071
|
+
}
|
|
1072
|
+
ctx.translate(slot.x + tx, slot.y + ty);
|
|
1073
|
+
ctx.rotate(rot);
|
|
1074
|
+
if (preset === "flip3d") {
|
|
1075
|
+
ctx.scale(1, sy);
|
|
1076
|
+
} else {
|
|
1077
|
+
ctx.scale(sc, sc);
|
|
1078
|
+
}
|
|
1079
|
+
if (stroke && strokeWidth > 0) {
|
|
1080
|
+
ctx.strokeStyle = stroke;
|
|
1081
|
+
ctx.lineWidth = strokeWidth;
|
|
1082
|
+
ctx.strokeText(slot.ch, 0, 0);
|
|
1083
|
+
}
|
|
1084
|
+
ctx.fillStyle = fill;
|
|
1085
|
+
ctx.fillText(slot.ch, 0, 0);
|
|
1086
|
+
ctx.restore();
|
|
1087
|
+
}
|
|
909
1088
|
ctx.restore();
|
|
910
1089
|
}
|
|
911
1090
|
function usToFrame$2(us, fps) {
|
|
@@ -1541,6 +1720,7 @@ class LayerRenderer {
|
|
|
1541
1720
|
renderTextLayer(layer) {
|
|
1542
1721
|
const animationType = layer.animation?.type;
|
|
1543
1722
|
const hasWordTimings = layer.wordTimings && layer.wordTimings.length > 0;
|
|
1723
|
+
const animFrame = textAnimationFrameForTextLayer(layer, this.currentFrame, this.fps);
|
|
1544
1724
|
const needsWordTimings = ["wordByWord", "characterKTV", "wordByWordFancy"].includes(
|
|
1545
1725
|
animationType || ""
|
|
1546
1726
|
);
|
|
@@ -1550,24 +1730,32 @@ class LayerRenderer {
|
|
|
1550
1730
|
}
|
|
1551
1731
|
switch (animationType) {
|
|
1552
1732
|
case "wordByWord":
|
|
1553
|
-
renderWordByWord(this.ctx, layer, this.width, this.height,
|
|
1733
|
+
renderWordByWord(this.ctx, layer, this.width, this.height, animFrame, this.fps);
|
|
1554
1734
|
break;
|
|
1555
1735
|
case "characterKTV":
|
|
1556
|
-
renderCharacterKTV(this.ctx, layer, this.width, this.height,
|
|
1736
|
+
renderCharacterKTV(this.ctx, layer, this.width, this.height, animFrame, this.fps);
|
|
1557
1737
|
break;
|
|
1558
1738
|
case "wordByWordFancy":
|
|
1559
|
-
renderWordByWordFancy(
|
|
1739
|
+
renderWordByWordFancy(this.ctx, layer, this.width, this.height, animFrame, this.fps);
|
|
1740
|
+
break;
|
|
1741
|
+
case "fade":
|
|
1742
|
+
case "slideUp":
|
|
1743
|
+
case "scale":
|
|
1744
|
+
case "rotateScale":
|
|
1745
|
+
case "blur":
|
|
1746
|
+
case "flip3d":
|
|
1747
|
+
case "typewriter":
|
|
1748
|
+
case "letterSpread":
|
|
1749
|
+
renderCaptionStaggerEntrance(
|
|
1560
1750
|
this.ctx,
|
|
1561
1751
|
layer,
|
|
1562
1752
|
this.width,
|
|
1563
1753
|
this.height,
|
|
1564
|
-
|
|
1565
|
-
this.fps
|
|
1754
|
+
animFrame,
|
|
1755
|
+
this.fps,
|
|
1756
|
+
animationType
|
|
1566
1757
|
);
|
|
1567
1758
|
break;
|
|
1568
|
-
case "fade":
|
|
1569
|
-
renderTextWithEntrance(this.ctx, layer, this.width, this.height, this.currentFrame);
|
|
1570
|
-
break;
|
|
1571
1759
|
default:
|
|
1572
1760
|
renderBasicText(this.ctx, layer, this.width, this.height, this.currentFrame);
|
|
1573
1761
|
break;
|
|
@@ -2914,7 +3102,7 @@ function resolveActiveLayers(layers, timestamp) {
|
|
|
2914
3102
|
);
|
|
2915
3103
|
});
|
|
2916
3104
|
}
|
|
2917
|
-
function materializeLayer(layer, frame, imageMap, globalTimeUs) {
|
|
3105
|
+
function materializeLayer(layer, frame, imageMap, globalTimeUs, clipRelativeTimeUs) {
|
|
2918
3106
|
const baseLayer = {
|
|
2919
3107
|
id: layer.layerId,
|
|
2920
3108
|
type: layer.type,
|
|
@@ -2937,6 +3125,7 @@ function materializeLayer(layer, frame, imageMap, globalTimeUs) {
|
|
|
2937
3125
|
}
|
|
2938
3126
|
if (layer.type === "text") {
|
|
2939
3127
|
const payload = layer.payload;
|
|
3128
|
+
const entranceRelativeTimeUs = clipRelativeTimeUs != null ? attachmentEntranceRelativeTimeUs(clipRelativeTimeUs, layer.activeRanges) : void 0;
|
|
2940
3129
|
return {
|
|
2941
3130
|
...baseLayer,
|
|
2942
3131
|
type: "text",
|
|
@@ -2945,7 +3134,8 @@ function materializeLayer(layer, frame, imageMap, globalTimeUs) {
|
|
|
2945
3134
|
fontConfig: payload.fontConfig,
|
|
2946
3135
|
animation: payload.animation,
|
|
2947
3136
|
wordTimings: payload.wordTimings,
|
|
2948
|
-
letterCase: payload.letterCase
|
|
3137
|
+
letterCase: payload.letterCase,
|
|
3138
|
+
entranceRelativeTimeUs
|
|
2949
3139
|
};
|
|
2950
3140
|
}
|
|
2951
3141
|
if (layer.type === "image") {
|
|
@@ -3215,7 +3405,7 @@ class ExportWorker {
|
|
|
3215
3405
|
if (!activeLayers.length) return null;
|
|
3216
3406
|
const clipStartUs = instruction.baseConfig.timeline?.clipStartUs ?? 0;
|
|
3217
3407
|
const globalTimeUs = clipStartUs + clipRelativeTime;
|
|
3218
|
-
const layers = activeLayers.map((layer) => materializeLayer(layer, frame, this.imageMap, globalTimeUs)).filter((layer) => layer !== null);
|
|
3408
|
+
const layers = activeLayers.map((layer) => materializeLayer(layer, frame, this.imageMap, globalTimeUs, clipRelativeTime)).filter((layer) => layer !== null);
|
|
3219
3409
|
if (!layers.length) return null;
|
|
3220
3410
|
return {
|
|
3221
3411
|
timeUs: clipRelativeTime,
|
|
@@ -3253,4 +3443,4 @@ const export_worker = null;
|
|
|
3253
3443
|
export {
|
|
3254
3444
|
export_worker as default
|
|
3255
3445
|
};
|
|
3256
|
-
//# sourceMappingURL=export.worker.
|
|
3446
|
+
//# sourceMappingURL=export.worker.Dztm6GuN.js.map
|