@bendyline/squisq 2.11.6 → 2.11.7
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/{chunk-WAGJK36Q.js → chunk-3YNFMFNK.js} +13 -1
- package/dist/{chunk-764WHWKM.js → chunk-EF353KGH.js} +1 -1
- package/dist/{chunk-J2IPSQKP.js → chunk-IYZTF4VO.js} +255 -43
- package/dist/{chunk-UUI35INI.js → chunk-SKKBRLLM.js} +2 -2
- package/dist/{chunk-GRGLZF4A.js → chunk-SOIV7KDO.js} +2 -2
- package/dist/{chunk-BUJ5RV2P.js → chunk-YGGYYBJS.js} +2 -1
- package/dist/doc/index.js +3 -3
- package/dist/index.js +6 -6
- package/dist/jsonForm/index.js +1 -1
- package/dist/narration/index.js +3 -3
- package/dist/schemas/index.js +1 -1
- package/dist/transform/index.d.ts +34 -0
- package/dist/transform/index.js +2 -2
- package/package.json +1 -1
|
@@ -10494,6 +10494,7 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
10494
10494
|
blocksBySegment.get(segmentIndex).push({ block, originalIndex: index });
|
|
10495
10495
|
});
|
|
10496
10496
|
const expandedBlocks = new Array(blocks.length);
|
|
10497
|
+
const splitParts = /* @__PURE__ */ new Map();
|
|
10497
10498
|
for (const [segmentIndex, segmentBlocks] of blocksBySegment) {
|
|
10498
10499
|
const audioSegment = audioSegments[segmentIndex];
|
|
10499
10500
|
if (!audioSegment) {
|
|
@@ -10655,6 +10656,7 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
10655
10656
|
const partDuration = block.duration / numParts;
|
|
10656
10657
|
if (partDuration >= MIN_TRANSITION_GAP) {
|
|
10657
10658
|
block.duration = partDuration;
|
|
10659
|
+
const parts = [];
|
|
10658
10660
|
for (let p = 1; p < numParts; p++) {
|
|
10659
10661
|
const splitBlock = {
|
|
10660
10662
|
id: `${block.id}-split-${p}`,
|
|
@@ -10668,13 +10670,23 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
10668
10670
|
transition: { type: "dissolve", duration: 1 },
|
|
10669
10671
|
template: block.template
|
|
10670
10672
|
};
|
|
10671
|
-
|
|
10673
|
+
parts.push(splitBlock);
|
|
10672
10674
|
}
|
|
10675
|
+
splitParts.set(block, parts);
|
|
10673
10676
|
}
|
|
10674
10677
|
}
|
|
10675
10678
|
}
|
|
10676
10679
|
}
|
|
10677
10680
|
}
|
|
10681
|
+
if (splitParts.size > 0) {
|
|
10682
|
+
const sequenced = [];
|
|
10683
|
+
for (const block of expandedBlocks) {
|
|
10684
|
+
sequenced.push(block);
|
|
10685
|
+
const parts = block ? splitParts.get(block) : void 0;
|
|
10686
|
+
if (parts) sequenced.push(...parts);
|
|
10687
|
+
}
|
|
10688
|
+
expandedBlocks.splice(0, expandedBlocks.length, ...sequenced);
|
|
10689
|
+
}
|
|
10678
10690
|
return mergeShortBlocks ? expandedBlocks.filter((block) => block && block.duration > 0) : expandedBlocks.filter(Boolean);
|
|
10679
10691
|
}
|
|
10680
10692
|
function getAvailableTemplates() {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-SVN7T6BK.js";
|
|
6
6
|
import {
|
|
7
7
|
markdownToDoc
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3YNFMFNK.js";
|
|
9
9
|
import {
|
|
10
10
|
cloneAndFreezeData,
|
|
11
11
|
isTemplateBlock
|
|
@@ -512,7 +512,7 @@ function buildBlockSequence(analyzed, selected, config, images, rng) {
|
|
|
512
512
|
elementsSinceImage++;
|
|
513
513
|
if (config.interleaveImages && imageIndex < images.length && elementsSinceImage >= imageInterval) {
|
|
514
514
|
const img = images[imageIndex];
|
|
515
|
-
const
|
|
515
|
+
const imageBlock2 = {
|
|
516
516
|
template: "imageWithCaption",
|
|
517
517
|
id: `transform-img-${blockIdCounter++}`,
|
|
518
518
|
duration: 4,
|
|
@@ -522,7 +522,7 @@ function buildBlockSequence(analyzed, selected, config, images, rng) {
|
|
|
522
522
|
ambientMotion: rng.pick(AMBIENT_MOTIONS) ?? "zoomIn",
|
|
523
523
|
sourceBlockId: ab.block.id
|
|
524
524
|
};
|
|
525
|
-
blocks.push(
|
|
525
|
+
blocks.push(imageBlock2);
|
|
526
526
|
imageIndex++;
|
|
527
527
|
insertedCount++;
|
|
528
528
|
elementsSinceImage = 0;
|
|
@@ -846,12 +846,37 @@ function firstTitle(doc) {
|
|
|
846
846
|
}
|
|
847
847
|
|
|
848
848
|
// src/transform/narratedSegment.ts
|
|
849
|
+
var DEFAULT_SLIDES_PER_MINUTE = 5;
|
|
850
|
+
var DEFAULT_MAX_SLIDE_DURATION = 15;
|
|
851
|
+
var DEFAULT_MIN_SLIDE_DURATION = 6;
|
|
852
|
+
var DEFAULT_MAX_VIDEOS = 2;
|
|
853
|
+
var AMBIENT_MOTIONS2 = [
|
|
854
|
+
"zoomIn",
|
|
855
|
+
"zoomOut",
|
|
856
|
+
"panLeft",
|
|
857
|
+
"panRight"
|
|
858
|
+
];
|
|
849
859
|
function transformNarratedSegment(input, options = {}) {
|
|
850
860
|
const seed = options.seed ?? hashString(`${input.articleId}-${input.segmentId}`);
|
|
851
861
|
const heading = input.title.replace(/[\r\n]+/g, " ").trim() || input.segmentId;
|
|
852
|
-
const
|
|
862
|
+
const pacing = {
|
|
863
|
+
slidesPerMinute: options.slidesPerMinute ?? DEFAULT_SLIDES_PER_MINUTE,
|
|
864
|
+
maxSlideDuration: options.maxSlideDuration ?? DEFAULT_MAX_SLIDE_DURATION,
|
|
865
|
+
minSlideDuration: options.minSlideDuration ?? DEFAULT_MIN_SLIDE_DURATION,
|
|
866
|
+
maxVideos: options.maxVideos ?? DEFAULT_MAX_VIDEOS
|
|
867
|
+
};
|
|
868
|
+
const images = uniqueImages(input.images ?? []);
|
|
869
|
+
const videos = input.videos ?? [];
|
|
870
|
+
const contentTarget = contentSlotCount(
|
|
871
|
+
input.duration,
|
|
872
|
+
pacing,
|
|
873
|
+
images.length + Math.min(videos.length, pacing.maxVideos),
|
|
874
|
+
!!input.includeTitleBlock
|
|
875
|
+
);
|
|
876
|
+
const markdown = chunkNarration(input.text, contentTarget).map((chunk) => `# ${heading}
|
|
853
877
|
|
|
854
|
-
${
|
|
878
|
+
${chunk}`).join("\n\n");
|
|
879
|
+
const source = markdownToDoc(parseMarkdown(markdown), {
|
|
855
880
|
articleId: `${input.articleId}:${input.segmentId}`,
|
|
856
881
|
defaultDuration: Math.max(3, input.duration)
|
|
857
882
|
});
|
|
@@ -876,23 +901,41 @@ ${input.text}`), {
|
|
|
876
901
|
themeId: options.themeId,
|
|
877
902
|
registry: options.registry,
|
|
878
903
|
overrides: {
|
|
904
|
+
// The style's transformRatio is a share of source blocks, and a
|
|
905
|
+
// narrated segment is one block — so it would always cap promotion at
|
|
906
|
+
// a single extraction. Size the cap by content slots instead; the
|
|
907
|
+
// slides-per-minute budget and blocksPerSection still apply.
|
|
908
|
+
transformRatio: (contentTarget + 0.5) / Math.max(1, source.blocks.length),
|
|
879
909
|
...options.minConfidence === void 0 ? {} : { minConfidence: options.minConfidence },
|
|
880
910
|
...options.slidesPerMinute === void 0 ? {} : { budget: { slidesPerMinute: options.slidesPerMinute } }
|
|
881
911
|
}
|
|
882
912
|
});
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
913
|
+
const title = input.includeTitleBlock ? {
|
|
914
|
+
template: "title",
|
|
915
|
+
id: `${input.segmentId}-title`,
|
|
916
|
+
duration: 4,
|
|
917
|
+
audioSegment: 0,
|
|
918
|
+
title: input.title
|
|
919
|
+
} : void 0;
|
|
920
|
+
const transformedBlocks = transformed.doc.blocks;
|
|
921
|
+
const sourceIds = new Set(source.blocks.map((block) => block.id));
|
|
922
|
+
const promoted = transformedBlocks.filter((block) => !sourceIds.has(block.id));
|
|
923
|
+
const kept = promoted.length > 0 ? promoted : transformedBlocks.slice(0, 1);
|
|
924
|
+
const content = kept.map((block) => ({
|
|
925
|
+
...block,
|
|
926
|
+
id: namespacedId(block.id, input.segmentId)
|
|
927
|
+
}));
|
|
928
|
+
const paced = paceSegment({
|
|
929
|
+
segmentId: input.segmentId,
|
|
930
|
+
duration: input.duration,
|
|
931
|
+
title,
|
|
932
|
+
content,
|
|
933
|
+
videos,
|
|
934
|
+
images,
|
|
935
|
+
pacing,
|
|
936
|
+
rng: new SeededRandom(seed ^ 1540483477)
|
|
937
|
+
});
|
|
938
|
+
const timedBlocks = allocateTiming(paced, input.duration).map((block) => ({
|
|
896
939
|
...block,
|
|
897
940
|
audioSegment: 0
|
|
898
941
|
}));
|
|
@@ -908,33 +951,202 @@ ${input.text}`), {
|
|
|
908
951
|
}
|
|
909
952
|
};
|
|
910
953
|
}
|
|
911
|
-
function
|
|
912
|
-
|
|
913
|
-
|
|
954
|
+
function paceTarget(duration, pacing) {
|
|
955
|
+
return Math.max(
|
|
956
|
+
1,
|
|
957
|
+
Math.round(duration / 60 * pacing.slidesPerMinute),
|
|
958
|
+
Math.ceil(duration / pacing.maxSlideDuration)
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
function contentSlotCount(duration, pacing, visualCount, hasTitle) {
|
|
962
|
+
const target = paceTarget(duration, pacing);
|
|
963
|
+
const share = visualCount >= 3 ? 0.2 : 0.4;
|
|
964
|
+
return Math.max(1, Math.round(target * share), target - (hasTitle ? 1 : 0) - visualCount);
|
|
965
|
+
}
|
|
966
|
+
function paceSegment(args) {
|
|
967
|
+
const { segmentId, duration, title, content, pacing, rng } = args;
|
|
968
|
+
const fixedCount = (title ? 1 : 0) + content.length;
|
|
969
|
+
const maxCount = Math.max(1, Math.floor(duration / pacing.minSlideDuration));
|
|
970
|
+
const target = paceTarget(duration, pacing);
|
|
971
|
+
const videoCount = args.videos.length === 0 ? 0 : Math.min(
|
|
972
|
+
args.videos.length,
|
|
973
|
+
pacing.maxVideos,
|
|
974
|
+
Math.max(1, Math.floor(target * 0.3)),
|
|
975
|
+
Math.max(0, maxCount - fixedCount)
|
|
976
|
+
);
|
|
977
|
+
const videos = pickVideos(args.videos, videoCount);
|
|
978
|
+
const pool = standaloneImagePool(args.images, content);
|
|
979
|
+
let count = Math.max(target, fixedCount + videos.length);
|
|
980
|
+
let imageCount = 0;
|
|
981
|
+
for (; ; ) {
|
|
982
|
+
imageCount = Math.min(pool.length, Math.max(0, count - fixedCount - videos.length));
|
|
983
|
+
const longest = longestFittedSlide(duration, count, title, content, videos, imageCount, pacing);
|
|
984
|
+
if (longest <= pacing.maxSlideDuration || imageCount >= pool.length || count >= maxCount) {
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
count++;
|
|
988
|
+
}
|
|
989
|
+
const slot = duration / Math.max(1, count);
|
|
990
|
+
const items = [];
|
|
991
|
+
if (title) {
|
|
992
|
+
items.push({
|
|
993
|
+
block: { ...title, duration: Math.min(slot, pacing.minSlideDuration) },
|
|
994
|
+
at: -1,
|
|
995
|
+
rank: 0
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
content.forEach((block, index) => {
|
|
999
|
+
const anchor = typeof block.sourceStartTime === "number" ? block.sourceStartTime : (index + 0.5) / content.length * duration;
|
|
1000
|
+
items.push({ block: { ...unanchored(block), duration: slot }, at: anchor, rank: 1 });
|
|
1001
|
+
});
|
|
914
1002
|
videos.forEach((video, index) => {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
videoSrc: video.src,
|
|
921
|
-
posterSrc: video.posterSrc,
|
|
922
|
-
videoAlt: video.alt ?? "",
|
|
923
|
-
clipStart: video.clipStart,
|
|
924
|
-
clipEnd: video.clipEnd,
|
|
925
|
-
sourceDuration: video.sourceDuration,
|
|
926
|
-
caption: video.alt,
|
|
927
|
-
captionPosition: "bottom",
|
|
928
|
-
videoCredit: video.credit,
|
|
929
|
-
videoLicense: video.license
|
|
930
|
-
};
|
|
931
|
-
const insertion = Math.min(
|
|
932
|
-
result.length,
|
|
933
|
-
Math.round((index + 1) * result.length / (videos.length + 1))
|
|
934
|
-
);
|
|
935
|
-
result.splice(insertion + index, 0, block);
|
|
1003
|
+
items.push({
|
|
1004
|
+
block: videoBlock(video, index, segmentId, videoNominal(video, slot, pacing)),
|
|
1005
|
+
at: videoPosition(index, videos.length) * duration,
|
|
1006
|
+
rank: 2
|
|
1007
|
+
});
|
|
936
1008
|
});
|
|
937
|
-
|
|
1009
|
+
for (let index = 0; index < imageCount; index++) {
|
|
1010
|
+
items.push({
|
|
1011
|
+
block: imageBlock(pool[index], index, segmentId, slot, rng),
|
|
1012
|
+
at: (index + 0.5) / imageCount * duration,
|
|
1013
|
+
rank: 3
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
return items.map((item, order) => ({ ...item, order })).sort((a, b) => a.at - b.at || a.rank - b.rank || a.order - b.order).map((item) => item.block);
|
|
1017
|
+
}
|
|
1018
|
+
function longestFittedSlide(duration, count, title, content, videos, imageCount, pacing) {
|
|
1019
|
+
const slot = duration / Math.max(1, count);
|
|
1020
|
+
const nominal = [
|
|
1021
|
+
...title ? [Math.min(slot, pacing.minSlideDuration)] : [],
|
|
1022
|
+
...content.map(() => slot),
|
|
1023
|
+
...videos.map((video) => videoNominal(video, slot, pacing)),
|
|
1024
|
+
...Array.from({ length: imageCount }, () => slot)
|
|
1025
|
+
];
|
|
1026
|
+
const total = nominal.reduce((sum, d) => sum + d, 0);
|
|
1027
|
+
return total > 0 ? Math.max(...nominal) * (duration / total) : duration;
|
|
1028
|
+
}
|
|
1029
|
+
function videoNominal(video, slot, pacing) {
|
|
1030
|
+
return Math.min(slot, Math.max(pacing.minSlideDuration, video.clipEnd - video.clipStart));
|
|
1031
|
+
}
|
|
1032
|
+
function videoPosition(index, count) {
|
|
1033
|
+
if (count === 1) return 0.4;
|
|
1034
|
+
if (count === 2) return index === 0 ? 0.3 : 0.7;
|
|
1035
|
+
return (index + 1) / (count + 1);
|
|
1036
|
+
}
|
|
1037
|
+
function pickVideos(videos, count) {
|
|
1038
|
+
const picked = [];
|
|
1039
|
+
const seenSources = /* @__PURE__ */ new Set();
|
|
1040
|
+
for (const video of videos) {
|
|
1041
|
+
if (picked.length >= count) break;
|
|
1042
|
+
if (seenSources.has(video.src)) continue;
|
|
1043
|
+
seenSources.add(video.src);
|
|
1044
|
+
picked.push(video);
|
|
1045
|
+
}
|
|
1046
|
+
for (const video of videos) {
|
|
1047
|
+
if (picked.length >= count) break;
|
|
1048
|
+
if (!picked.includes(video)) picked.push(video);
|
|
1049
|
+
}
|
|
1050
|
+
return picked;
|
|
1051
|
+
}
|
|
1052
|
+
function standaloneImagePool(images, content) {
|
|
1053
|
+
const shown = /* @__PURE__ */ new Set();
|
|
1054
|
+
const accents = /* @__PURE__ */ new Set();
|
|
1055
|
+
for (const block of content) {
|
|
1056
|
+
const media = block;
|
|
1057
|
+
if (block.template === "imageWithCaption" && media.imageSrc) shown.add(media.imageSrc);
|
|
1058
|
+
if (media.accentImage?.src) accents.add(media.accentImage.src);
|
|
1059
|
+
if (media.backgroundImage?.src) accents.add(media.backgroundImage.src);
|
|
1060
|
+
}
|
|
1061
|
+
const available = images.filter((image) => !shown.has(image.src));
|
|
1062
|
+
return [
|
|
1063
|
+
...available.filter((image) => !accents.has(image.src)),
|
|
1064
|
+
...available.filter((image) => accents.has(image.src))
|
|
1065
|
+
];
|
|
1066
|
+
}
|
|
1067
|
+
function chunkNarration(text, count) {
|
|
1068
|
+
if (count <= 1) return [text];
|
|
1069
|
+
const sentences = [];
|
|
1070
|
+
const boundary = /[.!?]["'’”)\]]*\s+/g;
|
|
1071
|
+
let start = 0;
|
|
1072
|
+
let match;
|
|
1073
|
+
while ((match = boundary.exec(text)) !== null) {
|
|
1074
|
+
const end = match.index + match[0].length;
|
|
1075
|
+
const sentence = text.slice(start, end).trim();
|
|
1076
|
+
if (sentence) sentences.push(sentence);
|
|
1077
|
+
start = end;
|
|
1078
|
+
}
|
|
1079
|
+
const tail = text.slice(start).trim();
|
|
1080
|
+
if (tail) sentences.push(tail);
|
|
1081
|
+
if (sentences.length < 2) return [text];
|
|
1082
|
+
const chunkCount = Math.min(count, sentences.length);
|
|
1083
|
+
const words = sentences.map((sentence) => sentence.split(/\s+/).length);
|
|
1084
|
+
const totalWords = words.reduce((sum, n) => sum + n, 0);
|
|
1085
|
+
const chunks = [];
|
|
1086
|
+
let current = [];
|
|
1087
|
+
let seenWords = 0;
|
|
1088
|
+
sentences.forEach((sentence, index) => {
|
|
1089
|
+
current.push(sentence);
|
|
1090
|
+
seenWords += words[index];
|
|
1091
|
+
const due = totalWords * (chunks.length + 1) / chunkCount;
|
|
1092
|
+
if (seenWords >= due && chunks.length < chunkCount - 1) {
|
|
1093
|
+
chunks.push(current);
|
|
1094
|
+
current = [];
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
if (current.length > 0) chunks.push(current);
|
|
1098
|
+
return chunks.map((chunk) => chunk.join(" "));
|
|
1099
|
+
}
|
|
1100
|
+
function uniqueImages(images) {
|
|
1101
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1102
|
+
return images.filter((image) => {
|
|
1103
|
+
if (!image.src || seen.has(image.src)) return false;
|
|
1104
|
+
seen.add(image.src);
|
|
1105
|
+
return true;
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
function namespacedId(id, segmentId) {
|
|
1109
|
+
return id.startsWith(`${segmentId}-`) ? id : `${segmentId}-${id}`;
|
|
1110
|
+
}
|
|
1111
|
+
function unanchored(block) {
|
|
1112
|
+
const { sourceStartTime: _start, sourceDuration: _duration, ...rest } = block;
|
|
1113
|
+
return rest;
|
|
1114
|
+
}
|
|
1115
|
+
function imageBlock(image, index, segmentId, duration, rng) {
|
|
1116
|
+
const caption = image.alt && !/\.(jpe?g|png|gif|svg|webp)/i.test(image.alt) ? image.alt : "";
|
|
1117
|
+
return {
|
|
1118
|
+
template: "imageWithCaption",
|
|
1119
|
+
id: `${segmentId}-image-${index}`,
|
|
1120
|
+
duration,
|
|
1121
|
+
audioSegment: 0,
|
|
1122
|
+
imageSrc: image.src,
|
|
1123
|
+
imageAlt: image.alt ?? "",
|
|
1124
|
+
caption,
|
|
1125
|
+
captionPosition: "bottom",
|
|
1126
|
+
ambientMotion: rng.pick(AMBIENT_MOTIONS2) ?? "zoomIn",
|
|
1127
|
+
imageCredit: image.credit,
|
|
1128
|
+
imageLicense: image.license
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
function videoBlock(video, index, segmentId, duration) {
|
|
1132
|
+
return {
|
|
1133
|
+
template: "videoWithCaption",
|
|
1134
|
+
id: `${segmentId}-video-${index}`,
|
|
1135
|
+
duration,
|
|
1136
|
+
audioSegment: 0,
|
|
1137
|
+
videoSrc: video.src,
|
|
1138
|
+
posterSrc: video.posterSrc,
|
|
1139
|
+
videoAlt: video.alt ?? "",
|
|
1140
|
+
clipStart: video.clipStart,
|
|
1141
|
+
clipEnd: video.clipEnd,
|
|
1142
|
+
// The clip's source file length (VideoWithCaptionInput's meaning), not a
|
|
1143
|
+
// narration timing hint.
|
|
1144
|
+
sourceDuration: video.sourceDuration,
|
|
1145
|
+
caption: video.alt,
|
|
1146
|
+
captionPosition: "bottom",
|
|
1147
|
+
videoCredit: video.credit,
|
|
1148
|
+
videoLicense: video.license
|
|
1149
|
+
};
|
|
938
1150
|
}
|
|
939
1151
|
|
|
940
1152
|
export {
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
buildNarrationScript,
|
|
3
3
|
expectedSyllablesAt,
|
|
4
4
|
wordPosAtExpectedSyllables
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-EF353KGH.js";
|
|
6
6
|
import {
|
|
7
7
|
flattenRenderableBlocks
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3YNFMFNK.js";
|
|
9
9
|
|
|
10
10
|
// src/narration/types.ts
|
|
11
11
|
var DEFAULT_FEATURE_CONFIG = Object.freeze({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildNarrationScript,
|
|
3
3
|
parseNarrationTimingJson
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EF353KGH.js";
|
|
5
5
|
import {
|
|
6
6
|
SHAPE_NAMES,
|
|
7
7
|
buildChartData,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
templateRegistry,
|
|
33
33
|
writeCustomTemplatesToFrontmatter,
|
|
34
34
|
writeCustomThemesToFrontmatter
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-3YNFMFNK.js";
|
|
36
36
|
import {
|
|
37
37
|
iconMarker
|
|
38
38
|
} from "./chunk-7ZAAICW4.js";
|
|
@@ -29,10 +29,11 @@ function getSegmentAtTime(audio, time) {
|
|
|
29
29
|
}
|
|
30
30
|
return audio.segments.length - 1;
|
|
31
31
|
}
|
|
32
|
+
var BLOCK_START_TOLERANCE = 1e-3;
|
|
32
33
|
function getBlockAtTime(blocks, time) {
|
|
33
34
|
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
34
35
|
const block = blocks[i];
|
|
35
|
-
if (time >= block.startTime && time < block.startTime + block.duration) {
|
|
36
|
+
if (time + BLOCK_START_TOLERANCE >= block.startTime && time < block.startTime + block.duration) {
|
|
36
37
|
return block;
|
|
37
38
|
}
|
|
38
39
|
}
|
package/dist/doc/index.js
CHANGED
|
@@ -70,8 +70,8 @@ import {
|
|
|
70
70
|
validateMarkdownDoc,
|
|
71
71
|
validateMarkdownSource,
|
|
72
72
|
writeDashboardLayoutsToFrontmatter
|
|
73
|
-
} from "../chunk-
|
|
74
|
-
import "../chunk-
|
|
73
|
+
} from "../chunk-SOIV7KDO.js";
|
|
74
|
+
import "../chunk-EF353KGH.js";
|
|
75
75
|
import {
|
|
76
76
|
ASCII_CHAR_H,
|
|
77
77
|
ASCII_CHAR_W,
|
|
@@ -193,7 +193,7 @@ import {
|
|
|
193
193
|
wrapWithPersistentLayers,
|
|
194
194
|
writeCustomTemplatesToFrontmatter,
|
|
195
195
|
writeCustomThemesToFrontmatter
|
|
196
|
-
} from "../chunk-
|
|
196
|
+
} from "../chunk-3YNFMFNK.js";
|
|
197
197
|
import {
|
|
198
198
|
PATH_SHAPE_KINDS,
|
|
199
199
|
anchorPoint,
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
recordSlideShown,
|
|
28
28
|
traceWordPosAt,
|
|
29
29
|
vadStep
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-SKKBRLLM.js";
|
|
31
31
|
import {
|
|
32
32
|
fenceRendererLangs
|
|
33
33
|
} from "./chunk-PGI7HSWE.js";
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
getTransformStyleSummaries,
|
|
66
66
|
resolveTransformStyle,
|
|
67
67
|
transformNarratedSegment
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-IYZTF4VO.js";
|
|
69
69
|
import {
|
|
70
70
|
extractContent,
|
|
71
71
|
mapElementToBlock,
|
|
@@ -151,7 +151,7 @@ import {
|
|
|
151
151
|
pipStyleVars,
|
|
152
152
|
serializeTheme,
|
|
153
153
|
validateDocSchema
|
|
154
|
-
} from "./chunk-
|
|
154
|
+
} from "./chunk-YGGYYBJS.js";
|
|
155
155
|
import {
|
|
156
156
|
calculateBearing,
|
|
157
157
|
decodeGeohash,
|
|
@@ -236,7 +236,7 @@ import {
|
|
|
236
236
|
validateMarkdownDoc,
|
|
237
237
|
validateMarkdownSource,
|
|
238
238
|
writeDashboardLayoutsToFrontmatter
|
|
239
|
-
} from "./chunk-
|
|
239
|
+
} from "./chunk-SOIV7KDO.js";
|
|
240
240
|
import {
|
|
241
241
|
buildNarrationScript,
|
|
242
242
|
buildNarrationTimingJson,
|
|
@@ -246,7 +246,7 @@ import {
|
|
|
246
246
|
wordIndexAtChar,
|
|
247
247
|
wordIndexAtTime,
|
|
248
248
|
wordPosAtExpectedSyllables
|
|
249
|
-
} from "./chunk-
|
|
249
|
+
} from "./chunk-EF353KGH.js";
|
|
250
250
|
import {
|
|
251
251
|
ASCII_CHAR_H,
|
|
252
252
|
ASCII_CHAR_W,
|
|
@@ -368,7 +368,7 @@ import {
|
|
|
368
368
|
wrapWithPersistentLayers,
|
|
369
369
|
writeCustomTemplatesToFrontmatter,
|
|
370
370
|
writeCustomThemesToFrontmatter
|
|
371
|
-
} from "./chunk-
|
|
371
|
+
} from "./chunk-3YNFMFNK.js";
|
|
372
372
|
import {
|
|
373
373
|
PATH_SHAPE_KINDS,
|
|
374
374
|
anchorPoint,
|
package/dist/jsonForm/index.js
CHANGED
package/dist/narration/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
recordSlideShown,
|
|
28
28
|
traceWordPosAt,
|
|
29
29
|
vadStep
|
|
30
|
-
} from "../chunk-
|
|
30
|
+
} from "../chunk-SKKBRLLM.js";
|
|
31
31
|
import {
|
|
32
32
|
buildNarrationScript,
|
|
33
33
|
buildNarrationTimingJson,
|
|
@@ -37,8 +37,8 @@ import {
|
|
|
37
37
|
wordIndexAtChar,
|
|
38
38
|
wordIndexAtTime,
|
|
39
39
|
wordPosAtExpectedSyllables
|
|
40
|
-
} from "../chunk-
|
|
41
|
-
import "../chunk-
|
|
40
|
+
} from "../chunk-EF353KGH.js";
|
|
41
|
+
import "../chunk-3YNFMFNK.js";
|
|
42
42
|
import "../chunk-PUS54YU6.js";
|
|
43
43
|
import "../chunk-C3NAY5YP.js";
|
|
44
44
|
import "../chunk-Y4D6TDZT.js";
|
package/dist/schemas/index.js
CHANGED
|
@@ -151,6 +151,26 @@ declare function applyTransform(doc: Doc, style: TransformStyleInput, options?:
|
|
|
151
151
|
* Converts a narrated text segment and its media into a timed Doc using the
|
|
152
152
|
* canonical squisq transform pipeline. This is the reusable bridge for hosts
|
|
153
153
|
* that generate one visual sequence per narration segment.
|
|
154
|
+
*
|
|
155
|
+
* Pacing: a narrated segment is a single source block, so the text transform
|
|
156
|
+
* alone promotes only one or two extractions — a 60s segment would become one
|
|
157
|
+
* 60s slide, which timed players (`expandDocBlocks`) then split into repeated
|
|
158
|
+
* copies of that same slide. `paceSegment()` restores a visual rhythm: it aims
|
|
159
|
+
* for `slidesPerMinute`, places up to `maxVideos` clips, and fills the rest
|
|
160
|
+
* with standalone image slides until no slide runs past `maxSlideDuration`
|
|
161
|
+
* (or the images run out). Image-poor segments get more text slides instead:
|
|
162
|
+
* their narration is split into sentence runs, one source block each, since
|
|
163
|
+
* the transform promotes at most one extraction per type from a block.
|
|
164
|
+
*
|
|
165
|
+
* The output timeline is sequential and unanchored: content blocks are ORDERED
|
|
166
|
+
* by where their extraction is spoken, but carry no `sourceStartTime`. When a
|
|
167
|
+
* segment mixes anchored and floating blocks, `expandDocBlocks()` hoists every
|
|
168
|
+
* anchored block ahead of the floating ones and stretches whatever precedes
|
|
169
|
+
* the first anchor (typically a host's section header), so a fully sequential
|
|
170
|
+
* segment is the only shape that survives re-timing by hosts and players.
|
|
171
|
+
*
|
|
172
|
+
* Block ids are prefixed with the segment id so segments composed into one
|
|
173
|
+
* Doc never collide (the transform itself numbers from `transform-0`).
|
|
154
174
|
*/
|
|
155
175
|
|
|
156
176
|
interface NarratedSegmentVideo {
|
|
@@ -179,7 +199,21 @@ interface NarratedSegmentTransformOptions extends Pick<TransformOptions, 'themeI
|
|
|
179
199
|
style?: TransformStyleInput;
|
|
180
200
|
seed?: number;
|
|
181
201
|
minConfidence?: number;
|
|
202
|
+
/**
|
|
203
|
+
* Visual pace target. Also caps the text transform's promotions when set.
|
|
204
|
+
* Pacing defaults to 5 when omitted.
|
|
205
|
+
*/
|
|
182
206
|
slidesPerMinute?: number;
|
|
207
|
+
/** Longest a slide should hold before another visual is added. Default 15s. */
|
|
208
|
+
maxSlideDuration?: number;
|
|
209
|
+
/**
|
|
210
|
+
* Shortest slide the pacer schedules; bounds the slide count so timed
|
|
211
|
+
* players don't merge slides back together (they fold anything under ~5s).
|
|
212
|
+
* Default 6s.
|
|
213
|
+
*/
|
|
214
|
+
minSlideDuration?: number;
|
|
215
|
+
/** Most video clips placed in one segment. Default 2. */
|
|
216
|
+
maxVideos?: number;
|
|
183
217
|
}
|
|
184
218
|
interface NarratedSegmentResult extends TransformResult {
|
|
185
219
|
seed: number;
|
package/dist/transform/index.js
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
getTransformStyleSummaries,
|
|
10
10
|
resolveTransformStyle,
|
|
11
11
|
transformNarratedSegment
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-IYZTF4VO.js";
|
|
13
13
|
import "../chunk-SVN7T6BK.js";
|
|
14
|
-
import "../chunk-
|
|
14
|
+
import "../chunk-3YNFMFNK.js";
|
|
15
15
|
import "../chunk-PUS54YU6.js";
|
|
16
16
|
import "../chunk-C3NAY5YP.js";
|
|
17
17
|
import "../chunk-Y4D6TDZT.js";
|