@lumy-pack/scene-sieve 0.0.8 → 0.0.9
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/README.md +93 -0
- package/dist/cli.mjs +411 -7
- package/dist/commands/Sieve.d.ts +2 -0
- package/dist/constants.d.ts +2 -0
- package/dist/core/extractor.d.ts +23 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/segmenter.d.ts +38 -0
- package/dist/core/workspace.d.ts +1 -0
- package/dist/index.cjs +382 -4
- package/dist/index.mjs +382 -4
- package/dist/pipeline-worker.mjs +382 -4
- package/dist/types/index.d.ts +22 -0
- package/dist/utils/concurrency.d.ts +5 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/core/orchestrator.ts
|
|
2
|
-
import { randomUUID } from "crypto";
|
|
2
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3
3
|
|
|
4
4
|
// src/utils/logger.ts
|
|
5
5
|
import pc from "picocolors";
|
|
@@ -64,6 +64,8 @@ var PIXELDIFF_GAUSSIAN_KERNEL = 3;
|
|
|
64
64
|
var PIXELDIFF_BINARY_THRESHOLD = 30;
|
|
65
65
|
var PIXELDIFF_CONTOUR_MIN_AREA = 100;
|
|
66
66
|
var PIXELDIFF_SAMPLE_SPACING = 8;
|
|
67
|
+
var DEFAULT_MAX_SEGMENT_DURATION = 300;
|
|
68
|
+
var DEFAULT_SEGMENT_CONCURRENCY = 2;
|
|
67
69
|
function getTempWorkspaceDir(sessionId) {
|
|
68
70
|
return join(TEMP_BASE_DIR, `${WORKSPACE_PREFIX}${sessionId}`);
|
|
69
71
|
}
|
|
@@ -656,6 +658,23 @@ async function buildFrameList(framesDir, duration) {
|
|
|
656
658
|
extractPath: join2(framesDir, file)
|
|
657
659
|
}));
|
|
658
660
|
}
|
|
661
|
+
async function extractFramesForRange(inputPath, outputDir, fps, scale, startTime, duration) {
|
|
662
|
+
const outputPattern = join2(outputDir, FRAME_FILENAME_PATTERN);
|
|
663
|
+
await execa(ffmpegPath, [
|
|
664
|
+
"-ss",
|
|
665
|
+
String(startTime),
|
|
666
|
+
"-i",
|
|
667
|
+
inputPath,
|
|
668
|
+
"-t",
|
|
669
|
+
String(duration),
|
|
670
|
+
"-vf",
|
|
671
|
+
`fps=${fps},scale=-1:${scale}`,
|
|
672
|
+
"-q:v",
|
|
673
|
+
"2",
|
|
674
|
+
outputPattern
|
|
675
|
+
]);
|
|
676
|
+
return buildFrameList(outputDir, duration);
|
|
677
|
+
}
|
|
659
678
|
|
|
660
679
|
// src/core/input-resolver.ts
|
|
661
680
|
import { join as join4 } from "path";
|
|
@@ -718,6 +737,15 @@ async function finalizeOutput(ctx, selectedFrames) {
|
|
|
718
737
|
await rename(stagingDir, outputPath);
|
|
719
738
|
return outputFiles;
|
|
720
739
|
}
|
|
740
|
+
async function createSegmentWorkspace(parentWorkspacePath, segmentIndex) {
|
|
741
|
+
const segmentPath = join3(
|
|
742
|
+
parentWorkspacePath,
|
|
743
|
+
"segments",
|
|
744
|
+
String(segmentIndex)
|
|
745
|
+
);
|
|
746
|
+
await ensureDir(join3(segmentPath, "frames"));
|
|
747
|
+
return segmentPath;
|
|
748
|
+
}
|
|
721
749
|
async function cleanupWorkspace(workspacePath) {
|
|
722
750
|
if (!workspacePath) return;
|
|
723
751
|
try {
|
|
@@ -778,7 +806,9 @@ function resolveOptions(options) {
|
|
|
778
806
|
quality: options.quality ?? DEFAULT_QUALITY,
|
|
779
807
|
iouThreshold: options.iouThreshold ?? IOU_THRESHOLD,
|
|
780
808
|
animationThreshold: options.animationThreshold ?? ANIMATION_FRAME_THRESHOLD,
|
|
781
|
-
debug: options.debug ?? false
|
|
809
|
+
debug: options.debug ?? false,
|
|
810
|
+
maxSegmentDuration: options.maxSegmentDuration ?? DEFAULT_MAX_SEGMENT_DURATION,
|
|
811
|
+
concurrency: options.concurrency ?? DEFAULT_SEGMENT_CONCURRENCY
|
|
782
812
|
};
|
|
783
813
|
}
|
|
784
814
|
async function resolveInput(options, workspacePath) {
|
|
@@ -1030,13 +1060,361 @@ function pruneByThresholdWithCap(graph, frames, threshold, maxCount) {
|
|
|
1030
1060
|
return pruneTo(syntheticEdges, survivingFrames, maxCount);
|
|
1031
1061
|
}
|
|
1032
1062
|
|
|
1063
|
+
// src/core/segmenter.ts
|
|
1064
|
+
import { randomUUID } from "crypto";
|
|
1065
|
+
import { join as join5 } from "path";
|
|
1066
|
+
|
|
1067
|
+
// src/utils/concurrency.ts
|
|
1068
|
+
function concurrencyLimit(limit) {
|
|
1069
|
+
limit = Math.max(1, limit);
|
|
1070
|
+
let active = 0;
|
|
1071
|
+
const queue = [];
|
|
1072
|
+
return async (fn) => {
|
|
1073
|
+
while (active >= limit) {
|
|
1074
|
+
await new Promise((resolve2) => queue.push(resolve2));
|
|
1075
|
+
}
|
|
1076
|
+
active++;
|
|
1077
|
+
try {
|
|
1078
|
+
return await fn();
|
|
1079
|
+
} finally {
|
|
1080
|
+
active--;
|
|
1081
|
+
queue.shift()?.();
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/core/segmenter.ts
|
|
1087
|
+
function shouldSegment(resolvedOptions, originalOptions) {
|
|
1088
|
+
if (resolvedOptions.mode === "frames") return false;
|
|
1089
|
+
if (originalOptions.mode === "file") {
|
|
1090
|
+
if (originalOptions.inputPath.toLowerCase().endsWith(".gif")) return false;
|
|
1091
|
+
}
|
|
1092
|
+
return true;
|
|
1093
|
+
}
|
|
1094
|
+
function computeSegmentPlan(totalDuration, maxSegmentDuration, maxFrames, fps) {
|
|
1095
|
+
const effectiveFps = Math.max(0.5, Math.min(fps, maxFrames / totalDuration));
|
|
1096
|
+
if (totalDuration <= maxSegmentDuration) {
|
|
1097
|
+
return [
|
|
1098
|
+
{
|
|
1099
|
+
index: 0,
|
|
1100
|
+
startTime: 0,
|
|
1101
|
+
endTime: totalDuration,
|
|
1102
|
+
duration: totalDuration,
|
|
1103
|
+
allocatedFrames: Math.min(
|
|
1104
|
+
Math.ceil(effectiveFps * totalDuration),
|
|
1105
|
+
maxFrames
|
|
1106
|
+
),
|
|
1107
|
+
effectiveFps,
|
|
1108
|
+
overlapBefore: 0,
|
|
1109
|
+
overlapAfter: 0,
|
|
1110
|
+
extractStartTime: 0,
|
|
1111
|
+
extractDuration: totalDuration
|
|
1112
|
+
}
|
|
1113
|
+
];
|
|
1114
|
+
}
|
|
1115
|
+
const segmentCount = Math.ceil(totalDuration / maxSegmentDuration);
|
|
1116
|
+
const overlapTime = 1 / effectiveFps;
|
|
1117
|
+
const segments = [];
|
|
1118
|
+
for (let i = 0; i < segmentCount; i++) {
|
|
1119
|
+
const startTime = i * maxSegmentDuration;
|
|
1120
|
+
const endTime = Math.min((i + 1) * maxSegmentDuration, totalDuration);
|
|
1121
|
+
const duration = endTime - startTime;
|
|
1122
|
+
const overlapBefore = i > 0 ? 1 : 0;
|
|
1123
|
+
const overlapAfter = i < segmentCount - 1 ? 1 : 0;
|
|
1124
|
+
const extractStartTime = Math.max(
|
|
1125
|
+
0,
|
|
1126
|
+
startTime - overlapBefore * overlapTime
|
|
1127
|
+
);
|
|
1128
|
+
const extractEndTime = Math.min(
|
|
1129
|
+
totalDuration,
|
|
1130
|
+
endTime + overlapAfter * overlapTime
|
|
1131
|
+
);
|
|
1132
|
+
const extractDuration = extractEndTime - extractStartTime;
|
|
1133
|
+
segments.push({
|
|
1134
|
+
index: i,
|
|
1135
|
+
startTime,
|
|
1136
|
+
endTime,
|
|
1137
|
+
duration,
|
|
1138
|
+
allocatedFrames: Math.ceil(effectiveFps * duration),
|
|
1139
|
+
effectiveFps,
|
|
1140
|
+
overlapBefore,
|
|
1141
|
+
overlapAfter,
|
|
1142
|
+
extractStartTime,
|
|
1143
|
+
extractDuration
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
const totalAllocated = segments.reduce(
|
|
1147
|
+
(sum, s) => sum + s.allocatedFrames,
|
|
1148
|
+
0
|
|
1149
|
+
);
|
|
1150
|
+
if (totalAllocated > maxFrames) {
|
|
1151
|
+
segments[segments.length - 1].allocatedFrames -= totalAllocated - maxFrames;
|
|
1152
|
+
}
|
|
1153
|
+
return segments;
|
|
1154
|
+
}
|
|
1155
|
+
function mergeSegmentFrames(segmentResults) {
|
|
1156
|
+
if (segmentResults.length === 0) {
|
|
1157
|
+
return { frames: [], edges: [], animations: [] };
|
|
1158
|
+
}
|
|
1159
|
+
const allFrames = [];
|
|
1160
|
+
for (const result of segmentResults) {
|
|
1161
|
+
for (const frame of result.frames) {
|
|
1162
|
+
allFrames.push({
|
|
1163
|
+
frame: {
|
|
1164
|
+
...frame,
|
|
1165
|
+
// Use extractStartTime for timestamp correction (Section 18 note 1)
|
|
1166
|
+
timestamp: frame.timestamp + result.segment.extractStartTime
|
|
1167
|
+
},
|
|
1168
|
+
segmentIndex: result.segment.index,
|
|
1169
|
+
localId: frame.id
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
allFrames.sort((a, b) => a.frame.timestamp - b.frame.timestamp);
|
|
1174
|
+
const effectiveFps = segmentResults[0].segment.effectiveFps;
|
|
1175
|
+
const dupThreshold = 1 / (effectiveFps * 2);
|
|
1176
|
+
const uniqueFrames = [];
|
|
1177
|
+
for (const entry of allFrames) {
|
|
1178
|
+
if (uniqueFrames.length > 0) {
|
|
1179
|
+
const last = uniqueFrames[uniqueFrames.length - 1];
|
|
1180
|
+
if (Math.abs(entry.frame.timestamp - last.frame.timestamp) < dupThreshold) {
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
uniqueFrames.push(entry);
|
|
1185
|
+
}
|
|
1186
|
+
const globalIdMap = /* @__PURE__ */ new Map();
|
|
1187
|
+
const frames = uniqueFrames.map((entry, globalId) => {
|
|
1188
|
+
globalIdMap.set(`${entry.segmentIndex}:${entry.localId}`, globalId);
|
|
1189
|
+
return {
|
|
1190
|
+
id: globalId,
|
|
1191
|
+
timestamp: entry.frame.timestamp,
|
|
1192
|
+
extractPath: entry.frame.extractPath
|
|
1193
|
+
};
|
|
1194
|
+
});
|
|
1195
|
+
const edges = [];
|
|
1196
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
1197
|
+
for (const result of segmentResults) {
|
|
1198
|
+
for (const edge of result.edges) {
|
|
1199
|
+
const newSourceId = globalIdMap.get(
|
|
1200
|
+
`${result.segment.index}:${edge.sourceId}`
|
|
1201
|
+
);
|
|
1202
|
+
const newTargetId = globalIdMap.get(
|
|
1203
|
+
`${result.segment.index}:${edge.targetId}`
|
|
1204
|
+
);
|
|
1205
|
+
if (newSourceId === void 0 || newTargetId === void 0) continue;
|
|
1206
|
+
const edgeKey = `${newSourceId}-${newTargetId}`;
|
|
1207
|
+
const existingIdx = edgeMap.get(edgeKey);
|
|
1208
|
+
if (existingIdx !== void 0) {
|
|
1209
|
+
if (edges[existingIdx].score < edge.score) {
|
|
1210
|
+
edges[existingIdx] = {
|
|
1211
|
+
sourceId: newSourceId,
|
|
1212
|
+
targetId: newTargetId,
|
|
1213
|
+
score: edge.score
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
} else {
|
|
1217
|
+
edgeMap.set(edgeKey, edges.length);
|
|
1218
|
+
edges.push({
|
|
1219
|
+
sourceId: newSourceId,
|
|
1220
|
+
targetId: newTargetId,
|
|
1221
|
+
score: edge.score
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const animations = [];
|
|
1227
|
+
for (const result of segmentResults) {
|
|
1228
|
+
for (const anim of result.animations) {
|
|
1229
|
+
const newStartId = globalIdMap.get(
|
|
1230
|
+
`${result.segment.index}:${anim.startFrameId}`
|
|
1231
|
+
);
|
|
1232
|
+
const newEndId = globalIdMap.get(
|
|
1233
|
+
`${result.segment.index}:${anim.endFrameId}`
|
|
1234
|
+
);
|
|
1235
|
+
if (newStartId === void 0 || newEndId === void 0) continue;
|
|
1236
|
+
animations.push({
|
|
1237
|
+
...anim,
|
|
1238
|
+
startFrameId: newStartId,
|
|
1239
|
+
endFrameId: newEndId
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return { frames, edges, animations };
|
|
1244
|
+
}
|
|
1245
|
+
function buildSegmentContext(segment, frames, segmentWorkspacePath, resolvedOptions, onProgress) {
|
|
1246
|
+
return {
|
|
1247
|
+
options: {
|
|
1248
|
+
...resolvedOptions,
|
|
1249
|
+
fps: segment.effectiveFps,
|
|
1250
|
+
maxFrames: segment.allocatedFrames
|
|
1251
|
+
},
|
|
1252
|
+
workspacePath: segmentWorkspacePath,
|
|
1253
|
+
frames,
|
|
1254
|
+
graph: [],
|
|
1255
|
+
status: "ANALYZING",
|
|
1256
|
+
emitProgress: onProgress
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
async function processSegment(inputPath, segment, workspacePath, resolvedOptions, onProgress) {
|
|
1260
|
+
const framesDir = join5(workspacePath, "frames");
|
|
1261
|
+
const frames = await extractFramesForRange(
|
|
1262
|
+
inputPath,
|
|
1263
|
+
framesDir,
|
|
1264
|
+
segment.effectiveFps,
|
|
1265
|
+
resolvedOptions.scale,
|
|
1266
|
+
segment.extractStartTime,
|
|
1267
|
+
segment.extractDuration
|
|
1268
|
+
);
|
|
1269
|
+
if (frames.length < 2) {
|
|
1270
|
+
return { segment, frames, edges: [], animations: [] };
|
|
1271
|
+
}
|
|
1272
|
+
const ctx = buildSegmentContext(
|
|
1273
|
+
segment,
|
|
1274
|
+
frames,
|
|
1275
|
+
workspacePath,
|
|
1276
|
+
resolvedOptions,
|
|
1277
|
+
onProgress
|
|
1278
|
+
);
|
|
1279
|
+
const { edges, animations } = await analyzeFrames(ctx);
|
|
1280
|
+
return { segment, frames, edges, animations };
|
|
1281
|
+
}
|
|
1282
|
+
async function runSegmentedPipeline(options, resolvedOptions) {
|
|
1283
|
+
const pipelineStart = Date.now();
|
|
1284
|
+
const sessionId = randomUUID();
|
|
1285
|
+
let mainWorkspace = "";
|
|
1286
|
+
try {
|
|
1287
|
+
mainWorkspace = await createWorkspace(sessionId);
|
|
1288
|
+
logger.debug(`Segmented pipeline: workspace at ${mainWorkspace}`);
|
|
1289
|
+
const { resolvedInputPath } = await resolveInput(options, mainWorkspace);
|
|
1290
|
+
const inputPath = resolvedInputPath ?? resolvedOptions.inputPath;
|
|
1291
|
+
if (!inputPath) {
|
|
1292
|
+
throw new Error("No input path available for segmented pipeline");
|
|
1293
|
+
}
|
|
1294
|
+
const metadata = await getVideoMetadata(inputPath);
|
|
1295
|
+
const totalDuration = parseFloat(metadata.format?.duration ?? "0");
|
|
1296
|
+
if (totalDuration <= 0) {
|
|
1297
|
+
throw new Error(`Invalid video duration: ${totalDuration}`);
|
|
1298
|
+
}
|
|
1299
|
+
logger.debug(`Video duration: ${totalDuration}s`);
|
|
1300
|
+
const segments = computeSegmentPlan(
|
|
1301
|
+
totalDuration,
|
|
1302
|
+
resolvedOptions.maxSegmentDuration,
|
|
1303
|
+
resolvedOptions.maxFrames,
|
|
1304
|
+
resolvedOptions.fps
|
|
1305
|
+
);
|
|
1306
|
+
logger.debug(`Segment plan: ${segments.length} segments`);
|
|
1307
|
+
const limit = concurrencyLimit(resolvedOptions.concurrency);
|
|
1308
|
+
const segmentProgresses = new Array(segments.length).fill(0);
|
|
1309
|
+
const weights = segments.map((s) => s.duration / totalDuration);
|
|
1310
|
+
const emitOverallProgress = (phase) => {
|
|
1311
|
+
if (!options.onProgress) return;
|
|
1312
|
+
const overall = weights.reduce(
|
|
1313
|
+
(sum, w, i) => sum + w * (segmentProgresses[i] ?? 0),
|
|
1314
|
+
0
|
|
1315
|
+
);
|
|
1316
|
+
options.onProgress(phase, Math.min(100, overall));
|
|
1317
|
+
};
|
|
1318
|
+
options.onProgress?.("EXTRACTING", 0);
|
|
1319
|
+
const results = await Promise.all(
|
|
1320
|
+
segments.map(
|
|
1321
|
+
(segment) => limit(async () => {
|
|
1322
|
+
const segWorkspace = await createSegmentWorkspace(
|
|
1323
|
+
mainWorkspace,
|
|
1324
|
+
segment.index
|
|
1325
|
+
);
|
|
1326
|
+
const result = await processSegment(
|
|
1327
|
+
inputPath,
|
|
1328
|
+
segment,
|
|
1329
|
+
segWorkspace,
|
|
1330
|
+
resolvedOptions,
|
|
1331
|
+
(percent) => {
|
|
1332
|
+
segmentProgresses[segment.index] = percent;
|
|
1333
|
+
emitOverallProgress("ANALYZING");
|
|
1334
|
+
}
|
|
1335
|
+
);
|
|
1336
|
+
return result;
|
|
1337
|
+
})
|
|
1338
|
+
)
|
|
1339
|
+
);
|
|
1340
|
+
options.onProgress?.("ANALYZING", 100);
|
|
1341
|
+
const { frames, edges, animations } = mergeSegmentFrames(results);
|
|
1342
|
+
logger.debug(
|
|
1343
|
+
`Merged: ${frames.length} frames, ${edges.length} edges, ${animations.length} animations`
|
|
1344
|
+
);
|
|
1345
|
+
options.onProgress?.("PRUNING", 0);
|
|
1346
|
+
const survivingIds = pruneByThresholdWithCap(
|
|
1347
|
+
edges,
|
|
1348
|
+
frames,
|
|
1349
|
+
resolvedOptions.threshold,
|
|
1350
|
+
resolvedOptions.count
|
|
1351
|
+
);
|
|
1352
|
+
const prunedFrames = frames.filter((f) => survivingIds.has(f.id));
|
|
1353
|
+
options.onProgress?.("PRUNING", 100);
|
|
1354
|
+
options.onProgress?.("FINALIZING", 0);
|
|
1355
|
+
const ctx = {
|
|
1356
|
+
options: resolvedOptions,
|
|
1357
|
+
workspacePath: mainWorkspace,
|
|
1358
|
+
frames,
|
|
1359
|
+
graph: edges,
|
|
1360
|
+
animations,
|
|
1361
|
+
status: "FINALIZING",
|
|
1362
|
+
emitProgress: (percent) => options.onProgress?.("FINALIZING", percent)
|
|
1363
|
+
};
|
|
1364
|
+
let outputFiles = [];
|
|
1365
|
+
let outputBuffers;
|
|
1366
|
+
if (resolvedOptions.mode === "buffer" || resolvedOptions.mode === "frames") {
|
|
1367
|
+
outputBuffers = await readFramesAsBuffers(
|
|
1368
|
+
prunedFrames,
|
|
1369
|
+
resolvedOptions.quality
|
|
1370
|
+
);
|
|
1371
|
+
} else {
|
|
1372
|
+
outputFiles = await finalizeOutput(ctx, prunedFrames);
|
|
1373
|
+
}
|
|
1374
|
+
options.onProgress?.("FINALIZING", 100);
|
|
1375
|
+
logger.success(
|
|
1376
|
+
`Segmented pipeline: ${prunedFrames.length} scenes from ${frames.length} frames (${segments.length} segments)`
|
|
1377
|
+
);
|
|
1378
|
+
return {
|
|
1379
|
+
success: true,
|
|
1380
|
+
originalFramesCount: frames.length,
|
|
1381
|
+
prunedFramesCount: prunedFrames.length,
|
|
1382
|
+
outputFiles,
|
|
1383
|
+
outputBuffers,
|
|
1384
|
+
animations,
|
|
1385
|
+
video: {
|
|
1386
|
+
originalDurationMs: totalDuration * 1e3,
|
|
1387
|
+
fps: resolvedOptions.fps,
|
|
1388
|
+
resolution: {
|
|
1389
|
+
width: resolvedOptions.scale,
|
|
1390
|
+
height: Math.round(resolvedOptions.scale * 9 / 16)
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
executionTimeMs: Date.now() - pipelineStart
|
|
1394
|
+
};
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1397
|
+
logger.error(`Segmented pipeline failed: ${err.message}`);
|
|
1398
|
+
throw err;
|
|
1399
|
+
} finally {
|
|
1400
|
+
if (!resolvedOptions.debug) {
|
|
1401
|
+
await cleanupWorkspace(mainWorkspace);
|
|
1402
|
+
} else {
|
|
1403
|
+
logger.debug(`Debug mode: workspace preserved at ${mainWorkspace}`);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1033
1408
|
// src/core/orchestrator.ts
|
|
1034
1409
|
async function runPipeline(options) {
|
|
1035
|
-
const startTime = Date.now();
|
|
1036
|
-
const sessionId = randomUUID();
|
|
1037
1410
|
const debug = options.debug ?? false;
|
|
1038
1411
|
if (debug) setDebugMode(true);
|
|
1039
1412
|
const resolvedOptions = resolveOptions(options);
|
|
1413
|
+
if (shouldSegment(resolvedOptions, options)) {
|
|
1414
|
+
return runSegmentedPipeline(options, resolvedOptions);
|
|
1415
|
+
}
|
|
1416
|
+
const startTime = Date.now();
|
|
1417
|
+
const sessionId = randomUUID2();
|
|
1040
1418
|
const ctx = {
|
|
1041
1419
|
options: resolvedOptions,
|
|
1042
1420
|
workspacePath: "",
|